From c13271a6312f568152c1ce5369a5ea94fad1a0dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Tue, 18 Aug 2026 15:29:40 +0000 Subject: [PATCH] runbook: record the hand-rolled-proxy bodyParser trap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- plugins/EXTRACTING-A-PLUGIN.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/plugins/EXTRACTING-A-PLUGIN.md b/plugins/EXTRACTING-A-PLUGIN.md index c92bf2a3..c64e3841 100644 --- a/plugins/EXTRACTING-A-PLUGIN.md +++ b/plugins/EXTRACTING-A-PLUGIN.md @@ -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.