Files
vault/manifest.ts
T
pastilhasandClaude Opus 5 224a525751 restore the Bitwarden door, as one declared unauthenticated path
A stock Bitwarden client speaks Vaultwarden's protocol and nothing else: it
gets its own bearer token from /identity/connect/token and has nowhere to put
a platform JWT, so the authenticated /api/vault router cannot serve it. This
was dropped during the extraction because a plugin's routes mount behind the
account gate.

It comes back through the platform's own mechanism rather than an exception:
`unauthenticatedPaths: ['/bw/*']` in the manifest, resolved against this
plugin's mount and re-checked for containment at boot.

ONE prefix, not the four root mounts the platform version needed. A Bitwarden
client takes `https://<officer>/api/vault/bw` as its server URL and derives
/api, /identity, /icons and /notifications beneath it, so the whole protocol
fits under a single declared path.

The router is mounted BEFORE this file's middleware, deliberately: Hono
collects handlers in registration order, so a route registered ahead of a
`.use()` answers without it. Verified rather than assumed, because it is an
auth boundary — /api/vault/bw/* reaches the passthrough with no session while
/api/vault/config still goes through userMiddleware and the owner gate.

Not new exposure: everything here was already reachable at the Vaultwarden URL
it replaces, guarded by the same master password and the same Vaultwarden auth.
Officer adds no security to a Bitwarden client because it has no credential to
check. What it buys is that Vaultwarden need not be exposed at all — on this
install it listens on 127.0.0.1 only, and Officer, already behind the tailnet
and already terminating TLS, becomes the single address any client needs. Net
reduction in surface.

Still temporary, and still deleted rather than adjusted once a first-party
extension authenticates to Officer against the /session/login broker.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 14:26:50 +00:00

53 lines
2.3 KiB
TypeScript

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: '1.0.0',
platform: '>=1.0.0',
label: 'Vault',
summary: 'Your Vaultwarden, behind an Officer session — passwords for the apps that speak Bitwarden',
// 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://<officer>/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/*'],
};