switch off Vaultwarden's routers, pending extraction into a plugin
Same treatment as the browser relay: mounts commented, code left on disk. Its
tables were already commented out of the schema earlier tonight, which is what
made this necessary — /api/vault was mounted against tables db:push no longer
creates, so a fresh core install shipped an endpoint that could only fail with a
Postgres "relation does not exist".
Vaultwarden is not one mount. Eight places had to go, and grepping for `vault`
found them only because several are not named after a router:
hono.ts /api/vault the authenticated reverse-proxy
/vaultwarden the unauthenticated one for the browser extension
VAULT_ONLY_PREFIXES loop /identity, /notifications, /icons, /events
the isBitwardenClient diverter an /api/* middleware that hands Bitwarden
clients to the vault router before anything else sees them
./api/vault/sidecar-server a SIDE-EFFECT import capturing the sidecar's port
UNPROTECTED_API_PREFIXES the '/vault' entry
server.tsx the 'vault' ws provider, its handler, and the notifications upgrade route
The side-effect import is the one worth naming: it registers a sidecar listener
and appears in no route table, so nothing about unmounting the routers would have
stopped it running.
No capability registry change, unlike browser and task-logs. Vaultwarden is
exempt from totality on both halves — EXEMPT_API_PREFIXES has '/vault'
("Bitwarden protocol clients authenticate to Vaultwarden, not to Officer") and
EXEMPT_WS_PROVIDERS has 'vault'. So nothing claims it and nothing breaks by
unmounting it. I said the opposite before checking; the check is what settled it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+12
-12
@@ -16,7 +16,7 @@ import { taskRunnerWebsocket } from './servers/api/tasks/task-executor';
|
|||||||
import { pipelineWebsocket } from './servers/api/tasks/pipeline-executor';
|
import { pipelineWebsocket } from './servers/api/tasks/pipeline-executor';
|
||||||
import { cliampWebsocket, cliampAudioWebsocket } from './servers/api/cliamp/relay';
|
import { cliampWebsocket, cliampAudioWebsocket } from './servers/api/cliamp/relay';
|
||||||
import { desktopWebsocket } from './servers/api/desktop/websocket';
|
import { desktopWebsocket } from './servers/api/desktop/websocket';
|
||||||
import { vaultWebsocket, upgradeVaultWs } from './servers/api/vault/websocket';
|
// import { vaultWebsocket, upgradeVaultWs } from './servers/api/vault/websocket';
|
||||||
import officerWeb from './apps/officer-web/index.gen.html';
|
import officerWeb from './apps/officer-web/index.gen.html';
|
||||||
// import { startBrowserRelay } from './servers/api/browser/relay'; // switched off — see below
|
// import { startBrowserRelay } from './servers/api/browser/relay'; // switched off — see below
|
||||||
import { registerSidecar, unregisterSidecar, handleSidecarMessage } from './servers/sidecar-registry';
|
import { registerSidecar, unregisterSidecar, handleSidecarMessage } from './servers/sidecar-registry';
|
||||||
@@ -44,7 +44,7 @@ type WSData = {
|
|||||||
| 'cliamp'
|
| 'cliamp'
|
||||||
| 'cliamp-audio'
|
| 'cliamp-audio'
|
||||||
| 'desktop'
|
| 'desktop'
|
||||||
| 'vault'
|
// | 'vault'
|
||||||
| 'sidecar';
|
| 'sidecar';
|
||||||
sessionId?: string;
|
sessionId?: string;
|
||||||
cwd?: string;
|
cwd?: string;
|
||||||
@@ -138,7 +138,7 @@ const handlers: Record<string, any> = {
|
|||||||
cliamp: cliampWebsocket,
|
cliamp: cliampWebsocket,
|
||||||
'cliamp-audio': cliampAudioWebsocket,
|
'cliamp-audio': cliampAudioWebsocket,
|
||||||
desktop: desktopWebsocket,
|
desktop: desktopWebsocket,
|
||||||
vault: vaultWebsocket,
|
// vault: vaultWebsocket,
|
||||||
sidecar: sidecarWebsocket,
|
sidecar: sidecarWebsocket,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -279,10 +279,10 @@ const server = serve({
|
|||||||
},
|
},
|
||||||
// Vaultwarden notifications hub: upgrade the WebSocket here (proxied to upstream by vaultWebsocket);
|
// Vaultwarden notifications hub: upgrade the WebSocket here (proxied to upstream by vaultWebsocket);
|
||||||
// everything else on this path (SignalR long-poll negotiate/poll) falls through to the HTTP proxy.
|
// everything else on this path (SignalR long-poll negotiate/poll) falls through to the HTTP proxy.
|
||||||
'/api/vault/notifications/*': (req, server) => {
|
// '/api/vault/notifications/*': (req, server) => {
|
||||||
if (req.headers.get('upgrade') === 'websocket') return upgradeVaultWs(req, server);
|
// if (req.headers.get('upgrade') === 'websocket') return upgradeVaultWs(req, server);
|
||||||
return honoServer.fetch(req, server);
|
// return honoServer.fetch(req, server);
|
||||||
},
|
// },
|
||||||
'/api/sidecar/register': (req: Request, server: any) => {
|
'/api/sidecar/register': (req: Request, server: any) => {
|
||||||
const ok = server.upgrade(req, {
|
const ok = server.upgrade(req, {
|
||||||
data: { provider: 'sidecar', userId: 0, email: '', username: '' },
|
data: { provider: 'sidecar', userId: 0, email: '', username: '' },
|
||||||
@@ -308,14 +308,14 @@ const server = serve({
|
|||||||
// entry for the same reason /dav does: only the paths listed here reach hono, and anything else
|
// entry for the same reason /dav does: only the paths listed here reach hono, and anything else
|
||||||
// falls through to the SPA — which answers 200 with the React shell, so a missing line here looks
|
// falls through to the SPA — which answers 200 with the React shell, so a missing line here looks
|
||||||
// like a working endpoint returning nonsense rather than a 404.
|
// like a working endpoint returning nonsense rather than a 404.
|
||||||
'/vaultwarden/*': honoServer.fetch,
|
// '/vaultwarden/*': honoServer.fetch,
|
||||||
// The same proxy at the root, so the extension needs only the bare Officer URL. These four prefixes
|
// The same proxy at the root, so the extension needs only the bare Officer URL. These four prefixes
|
||||||
// are Vaultwarden's alone — nothing in Officer answers on them — so routing them here costs nothing.
|
// are Vaultwarden's alone — nothing in Officer answers on them — so routing them here costs nothing.
|
||||||
// `/api/*` already reaches hono below, where a Bitwarden client header diverts it.
|
// `/api/*` already reaches hono below, where a Bitwarden client header diverts it.
|
||||||
'/identity/*': honoServer.fetch,
|
// '/identity/*': honoServer.fetch,
|
||||||
'/notifications/*': honoServer.fetch,
|
// '/notifications/*': honoServer.fetch,
|
||||||
'/icons/*': honoServer.fetch,
|
// '/icons/*': honoServer.fetch,
|
||||||
'/events/*': honoServer.fetch,
|
// '/events/*': honoServer.fetch,
|
||||||
'/': officerWeb,
|
'/': officerWeb,
|
||||||
'/*': officerWeb,
|
'/*': officerWeb,
|
||||||
'/api': honoServer.fetch,
|
'/api': honoServer.fetch,
|
||||||
|
|||||||
+12
-12
@@ -20,8 +20,8 @@ import { settingsRouter } from './api/settings/settings';
|
|||||||
import { dashboardsRouter } from './api/dashboards';
|
import { dashboardsRouter } from './api/dashboards';
|
||||||
import { router as fileBrowserRouter } from './api/file-browser/router';
|
import { router as fileBrowserRouter } from './api/file-browser/router';
|
||||||
import { musicRouter } from './api/music/router';
|
import { musicRouter } from './api/music/router';
|
||||||
import { vaultRouter } from './api/vault/router';
|
// import { vaultRouter } from './api/vault/router';
|
||||||
import { publicVaultRouter, VAULT_ONLY_PREFIXES, isBitwardenClient } from './api/vault/public-router';
|
// import { publicVaultRouter, VAULT_ONLY_PREFIXES, isBitwardenClient } from './api/vault/public-router';
|
||||||
import { agentHandoffRouter } from './api/agent-handoff/router';
|
import { agentHandoffRouter } from './api/agent-handoff/router';
|
||||||
import { slskdRouter } from './api/slskd/router';
|
import { slskdRouter } from './api/slskd/router';
|
||||||
import { headscaleRouter } from './api/headscale/router';
|
import { headscaleRouter } from './api/headscale/router';
|
||||||
@@ -44,7 +44,7 @@ import { systemMonitorRouter } from './api/system-monitor/system-monitor';
|
|||||||
import { activityRouter } from './api/activity/router';
|
import { activityRouter } from './api/activity/router';
|
||||||
// Vault still hand-rolls its port capture, so it keeps a side-effect import; every other HTTP sidecar
|
// Vault still hand-rolls its port capture, so it keeps a side-effect import; every other HTTP sidecar
|
||||||
// registers its listener when createSidecarProxy runs inside the router this file already imports.
|
// registers its listener when createSidecarProxy runs inside the router this file already imports.
|
||||||
import './api/vault/sidecar-server'; // side-effect: capture the officer-vault reverse-proxy port
|
// import './api/vault/sidecar-server'; // side-effect: capture the officer-vault reverse-proxy port
|
||||||
import { dockRouter } from './api/dock/dock';
|
import { dockRouter } from './api/dock/dock';
|
||||||
import { integrationsRouter, googleCallbackHandler } from './api/integrations/integrations';
|
import { integrationsRouter, googleCallbackHandler } from './api/integrations/integrations';
|
||||||
import { queueRouter } from './api/queue/queue';
|
import { queueRouter } from './api/queue/queue';
|
||||||
@@ -101,7 +101,7 @@ honoServer.route('/api/waitlist', waitlistRouter);
|
|||||||
// Vaultwarden reverse-proxy — mounted TOP-LEVEL (not under protectedRouter): the Bitwarden client
|
// Vaultwarden reverse-proxy — mounted TOP-LEVEL (not under protectedRouter): the Bitwarden client
|
||||||
// carries its own bearer token, not a platform session JWT, so userMiddleware would 401 it. The
|
// carries its own bearer token, not a platform session JWT, so userMiddleware would 401 it. The
|
||||||
// notifications WebSocket is upgraded at the serve level (server.tsx).
|
// notifications WebSocket is upgraded at the serve level (server.tsx).
|
||||||
honoServer.route('/api/vault', vaultRouter);
|
// honoServer.route('/api/vault', vaultRouter); // switched off 2026-08-13 — Vaultwarden is a plugin
|
||||||
|
|
||||||
// The same Vaultwarden, with NO Officer authentication, so the Bitwarden browser extension can point at
|
// The same Vaultwarden, with NO Officer authentication, so the Bitwarden browser extension can point at
|
||||||
// this host instead of at a second public hostname for Vaultwarden. Deliberately its own mount rather
|
// this host instead of at a second public hostname for Vaultwarden. Deliberately its own mount rather
|
||||||
@@ -109,7 +109,7 @@ honoServer.route('/api/vault', vaultRouter);
|
|||||||
// Authorization header for a server-held token, and blending the two would put an unauthenticated branch
|
// Authorization header for a server-held token, and blending the two would put an unauthenticated branch
|
||||||
// inside the authenticated path. Temporary — see public-router.ts for what replaces it and why leaving it
|
// inside the authenticated path. Temporary — see public-router.ts for what replaces it and why leaving it
|
||||||
// open is not a new exposure.
|
// open is not a new exposure.
|
||||||
honoServer.route('/vaultwarden', publicVaultRouter);
|
// honoServer.route('/vaultwarden', publicVaultRouter); // switched off with the above
|
||||||
|
|
||||||
// …and at the ROOT, so the extension can be pointed at the bare Officer URL with no path at all.
|
// …and at the ROOT, so the extension can be pointed at the bare Officer URL with no path at all.
|
||||||
//
|
//
|
||||||
@@ -117,12 +117,12 @@ honoServer.route('/vaultwarden', publicVaultRouter);
|
|||||||
// for a Bitwarden client. It is deliberately narrow: the four prefixes below belong to Vaultwarden and
|
// for a Bitwarden client. It is deliberately narrow: the four prefixes below belong to Vaultwarden and
|
||||||
// to nothing else here, and `/api/*` is diverted ONLY when the request carries a Bitwarden client
|
// to nothing else here, and `/api/*` is diverted ONLY when the request carries a Bitwarden client
|
||||||
// header. An ordinary Officer request never matches, so nothing that worked before changes.
|
// header. An ordinary Officer request never matches, so nothing that worked before changes.
|
||||||
for (const prefix of VAULT_ONLY_PREFIXES) honoServer.route(prefix, publicVaultRouter);
|
// for (const prefix of VAULT_ONLY_PREFIXES) honoServer.route(prefix, publicVaultRouter);
|
||||||
|
//
|
||||||
honoServer.use('/api/*', async (ctx, next) => {
|
// honoServer.use('/api/*', async (ctx, next) => {
|
||||||
if (!isBitwardenClient(ctx.req.raw.headers)) return next();
|
// if (!isBitwardenClient(ctx.req.raw.headers)) return next();
|
||||||
return publicVaultRouter.fetch(ctx.req.raw, ctx.env);
|
// return publicVaultRouter.fetch(ctx.req.raw, ctx.env);
|
||||||
});
|
// });
|
||||||
honoServer.get('/api/integrations/google/callback', googleCallbackHandler);
|
honoServer.get('/api/integrations/google/callback', googleCallbackHandler);
|
||||||
|
|
||||||
// Agent-to-agent handoff — mounted TOP-LEVEL for the same reason the vault is: the caller is a Claude
|
// Agent-to-agent handoff — mounted TOP-LEVEL for the same reason the vault is: the caller is a Claude
|
||||||
@@ -243,7 +243,7 @@ export const UNPROTECTED_API_PREFIXES: string[] = [
|
|||||||
'/auth',
|
'/auth',
|
||||||
'/landing-page-data',
|
'/landing-page-data',
|
||||||
'/waitlist',
|
'/waitlist',
|
||||||
'/vault',
|
// '/vault', // switched off with the Vaultwarden mounts — see above
|
||||||
'/sidecar',
|
'/sidecar',
|
||||||
'/agent-handoff',
|
'/agent-handoff',
|
||||||
];
|
];
|
||||||
|
|||||||
Reference in New Issue
Block a user