runbook: record the hand-rolled-proxy bodyParser trap

Cost an afternoon on the vault extraction and presented as far as possible from
its cause: every POST through /api/vault answered "sidecar unreachable" while
bodyless GETs worked, so it read as an intermittently dead sidecar rather than
a consumed request stream.

bodyParser runs across the protected router and reads JSON and form bodies,
disturbing the stream; a catch-all that then forwards ctx.req.raw.body forwards
something already consumed, Bun's fetch throws, and the catch reports the
sidecar as down.

createSidecarProxy registers its prefix itself, so core sidecars and any plugin
using it never meet this. Vault proxies by hand and the extraction dropped the
registration. Found by the mobile dev, who went looking on the server side
after the app kept failing where the browser extension did not — the split was
the clue, since the extension's routes are mounted above the protected router
and never see bodyParser at all.

Verified before recording: registerProxiedPrefix and isProxiedPath do what the
fix assumes, bodyParser consults them, create-proxy.ts:83 registers for core,
no vault handler reads ctx.get('body') (all three call ctx.req.json()), and the
registered prefix covers every path the mobile app uses while still not
matching /api/vaultwarden.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-18 15:29:40 +00:00
co-authored by Claude Opus 5
parent 8367fada13
commit c13271a631
+10
View File
@@ -159,6 +159,16 @@ A normal refresh is enough; the shell is `no-store`. When the log's last line ap
module cache holds it for the lifetime of the process — so editing a manifest while developing changes
nothing until `pm2 restart officer`. Costs ten minutes the first time, because the plugins page keeps
cheerfully showing the old values. `outdated` cannot notice a version bump without a restart either.
- **A router that proxies BY HAND must call `registerProxiedPrefix`.** `bodyParser` runs on the whole
protected router and reads JSON and form bodies, which DISTURBS the request stream — so a catch-all that
then forwards `ctx.req.raw.body` forwards an already-consumed stream, Bun's fetch throws, and the proxy
reports the sidecar as unreachable. Every bodyless GET keeps working, so it presents as "the sidecar is
down" for POSTs only, which is a long way from the cause. `createSidecarProxy` registers the prefix
itself, so core sidecars and any plugin using it never meet this; vault proxies by hand and its
extraction dropped the registration on 2026-08-18, costing an afternoon. If your plugin hand-rolls a
proxy: `registerProxiedPrefix(PREFIX)` beside the mount, and check no handler under it reads
`ctx.get('body')`.
- **A plugin importing platform code is fine (`@@/…`); the reverse is not.** If something in `src/` imports
from your feature and cannot move — a widget, a relay — that piece stays, and the boundary goes around
it. Find those before you plan the split; they decide it for you.