import type { PluginManifest } from '@@/plugins/manifest'; /** * Vault — a reverse-proxy to a self-hosted Vaultwarden, with Officer holding the credential. * * ── What this plugin is, and what it deliberately is not ── * * It is NOT a password manager. It stores no vault items, renders no cipher list, and cannot decrypt * anything: the vault is end-to-end encrypted and every route here is a dumb pass-through. What it owns is * the CREDENTIAL problem — Officer brokers the Vaultwarden token, keeps it server-side, and injects it on * the way through, so a device that has an Officer session never holds a vault token at all. * * It also does not install Vaultwarden. You point it at one you already run. That is the whole * configuration surface, and it lives in the database rather than in `.env` — see `db/schema.ts`. * * ── No `osDependencies` ── * * Nothing here shells out. The sidecar is a Bun HTTP/WS proxy and the upstream is reached with `fetch`. */ export const manifest: PluginManifest = { publisher: 'officerdev', version: '2026.08.27.01', platform: '>=1.0.0', label: 'Vault', summary: 'Your Vaultwarden, behind an Officer session — passwords for the apps that speak Bitwarden', /** passwords, off a cloud manager. */ tags: ['Security', 'Personal', 'Degoogling'], // A lucide glyph rather than artwork: this ships no assets/icon.png, and `KeyRound` is what the app // store catalogue already used for it. icon: 'KeyRound', color: '#175ddc', permissions: [ { key: 'vault', label: 'Vault', description: 'The password vault, and the Officer-held session that opens it', }, ], /* The Bitwarden door: served without an Officer session, because a stock Bitwarden client has nowhere to put one. It carries its own Vaultwarden bearer token, which Vaultwarden itself judges — see `api/public-router.ts` for why this is the same exposure through one door rather than a new one, and why it is temporary. ONE prefix, not four. A Bitwarden client is given `https:///api/vault/bw` as its server URL and derives `/api`, `/identity`, `/icons` and `/notifications` beneath it, so everything the protocol needs sits under a single declared path. The platform resolves this against the plugin's own mount and re-checks containment at boot, so it can only ever mean `/api/vault/bw/*`. */ unauthenticatedPaths: ['/bw/*'], /* The four paths a Bitwarden client derives from a bare server URL, claimed at the ROOT so that URL can be the bare Officer one. This is what makes the setup "paste https://officer.example.com and log in" rather than five fields copied into an advanced pane — which is the difference between a feature and a thing nobody uses. None of them is a name Officer serves: they belong to Vaultwarden's protocol and to nothing else here, so the path alone decides and no sniffing is involved. */ unauthenticatedRootPaths: ['/identity', '/notifications', '/icons', '/events'], /* `/api/*` is the one namespace BOTH servers claim — Vaultwarden has /api/sync, /api/ciphers; Officer has /api/chat, /api/settings. The path cannot separate them, so the client says who it is: every Bitwarden client stamps one of these, and older ones `device-type`. Routing, not authentication. A forged header reaches Vaultwarden, which demands its own credential exactly as it would have; nothing of Officer's is authorised by it. */ protocolHeaders: ['bitwarden-client-name', 'bitwarden-client-version', 'device-type'], };