Files
webmail/app/api/system/update-status/route.ts
T
Linus Rath 05fe7f8780 fix: stop handing instance details to visitors who are not signed in
Several endpoints answered anyone with what fingerprints a deployment:
`/api/health?detailed=true` (versions, memory), the update status
(exact version and the text of a pending security advisory, even with
`loginShowVersion` off), `/api/plugins` (installed plugins and the hosts
they may reach), the admin policy (push relays, sidebar app URLs,
plugin lists) and `/api/config` (every domain each server serves).

Visitors without a session now get:

- the plain liveness answer from `/api/health`;
- the update status only where the login page shows the version, and
  never the advisory text;
- a 401 from `/api/plugins`, which the client only syncs after signing
  in;
- the policy without plugin lists, push relays and sidebar apps, marked
  `X-Bulwark-Policy-Scope: public`; the client fetches the full policy
  once it signs in;
- server domain lists only when servers are picked by domain.

A mailbox session, a remembered session cookie or the admin session
counts as signed in. Deployments without a session secret cannot tell
sessions apart and keep the old answers.
2026-09-26 17:03:24 +02:00

46 lines
1.8 KiB
TypeScript

import { NextRequest, NextResponse } from 'next/server';
import { checkOnce, freshStatus, loadState } from '@/lib/version-check';
import { configManager } from '@/lib/admin/config-manager';
import { canSeeInstanceDetails } from '@/lib/auth/instance-details';
// Public endpoint that returns the latest cached update status. Fed by the
// background scheduler started in instrumentation.node.ts; in production we
// never trigger a fresh upstream fetch from this route so an unauthenticated
// client can't use it to amplify traffic to the version server.
//
// In development we force a fresh fetch on every hit so changes to the
// version server's overrides take effect on the next page reload instead of
// requiring a dev-server restart. The 5s upstream timeout in fetchStatus
// caps the worst-case latency added to a dev reload.
export async function GET(request: NextRequest) {
if (process.env.NODE_ENV === 'development') {
await checkOnce({ reason: 'dev-reload' });
}
const state = await loadState();
// freshStatus drops a status persisted by an earlier build - after an
// upgrade it would still advertise the version we just upgraded from (#913).
let status = freshStatus(state);
// Visitors who are not signed in see the version only where the login
// page shows it (loginShowVersion), and never the advisory text: a
// pending security update tells an attacker which hole is still open.
if (status && !(await canSeeInstanceDetails(request))) {
await configManager.ensureLoaded();
status = configManager.get<boolean>('loginShowVersion', true) ? { ...status, advisory: null } : null;
}
return NextResponse.json(
{
status,
lastCheckedAt: state.lastCheckedAt,
lastSuccessAt: state.lastSuccessAt,
},
{
headers: {
'Cache-Control': 'no-store',
},
},
);
}