fix: body dumps off by default (ACP_DUMP_BODY) + reuse log-mask in dumps (#276) - #278
Conversation
bili.log and the launcher tmp log (bili-proxy-${port}.log) received
plaintext secrets and non-public upstream endpoints:
- 'forward GET/POST -> <upstreamUrl>' logged the full upstream URL on
every request (always-on, not debug-gated)
- '-> upstream headers:' / '<- upstream response headers:' logged
authorization / x-api-key / cookie / set-cookie values verbatim
- formatUpstreamError embedded the raw upstream URL and the failing
host (ECONNREFUSED 192.168.x.x, ENOTFOUND relay.internal)
- mitm/CONNECT logs exposed tunneled upstream host:port
New src/log-mask.ts:
- credential headers (key|auth|token|cookie) -> '<masked N chars>'
- non-public API hosts -> '<private-host>' (port kept); well-known
public endpoints (openai, chatgpt, anthropic, deepseek, googleapis,
azure, mistral, groq, ...) stay verbatim for debuggability
- userinfo/query/hash stripped from logged URLs (key-leak vectors)
- error message/address fields scrubbed of non-public hostnames
debug: true in the launcher is intentionally kept (per #255 triage);
dump-file gating and GC are tracked in separate issues.
tunnelThrough logged err.message verbatim; OS/undici error text embeds
the endpoint ("connect ECONNREFUSED 10.0.0.5:8443", "getaddrinfo
ENOTFOUND relay.internal"), re-leaking the host the template just
masked. Add maskHostInText() to log-mask.ts and apply it to the two
tunnel error log lines; formatUpstreamError now reuses the same helper
(also fixes the bracketed/bare IPv6 mismatch in its scrub).
+2 tests: unit coverage of maskHostInText, e2e CONNECT tunnel failure
asserting no raw target host in any captured log line.
…mps (#276) - Decouple all plaintext body dumps (dumps/req-*.json, raw/*-REQ.txt, raw/*-RES.txt, raw/*-INCOMING.txt, req-*-REREQUEST.json) from --debug: they are now opt-in via ACP_DUMP_BODY=1, so `bili <client>` users (launcher hardcodes debug:true) no longer leak conversation bodies to disk by default. - Reuse maskHeadersForLog from src/log-mask.ts for dump header filtering: now covers cookie/set-cookie (the old /key|auth|token/i regex missed them) and non-public hosts. - Mask the upstream URL in raw/*-REQ.txt (maskUrlForLog) and the incoming path in raw/*-INCOMING.txt (maskUrlsInText) so non-public hosts don't land in dump files. - INCOMING dump now honors ACP_RAW_DUMP_DIR (was hardcoded to stateDir/raw). - Add tests/dump-mask.test.ts: dumps off by default under --debug; with ACP_DUMP_BODY=1 dumps carry no credentials and no non-public host; dumps work without --debug.
|
[bot] Reviewed
Change-by-change verification:
Stacking note (expected, no action needed now): the branch is based on One minor observation (non-blocking): 🤖 ework agent · vllm-qwen/qwen3.8-27b |
📦 Built Package ArtifactBranch: Option A — Install from npm PR tag (recommended)npm install -g billion-context@pr-278Each push to this PR publishes a new version under the Option B — Download artifact
tar xzf billion-context-pr278.tgz
npm install -g packageThis comment is automatically updated on each push. |
Part B of #255, split out of PR #274 (which covered logs only). Stacked on
2026-08-27_mask-sensitive-logs— depends onsrc/log-mask.ts, so rebase to master once #274 lands.Changes
1. Body dumps off by default — every plaintext body dump was gated on
--debug, and the launchers hardcodedebug: true(src/launcher.ts:1035), so everybili <client>user silently wrote full conversation bodies to disk. All body dumps are now opt-in viaACP_DUMP_BODY=1, decoupled from--debug:dumps/req-*.json(src/server.ts)raw/*-REQ.txt/raw/*-RES.txt(src/server.ts)raw/*-INCOMING.txt(src/server.ts)req-*-REREQUEST.json(src/loop/core.ts)--debug/ACP_DEBUG=1now only produces verbose (already-masked, per #255) logs — no body dumps. Per #255 floor 5 the launcher keepsdebug: truefor troubleshooting; the fix is that dumps no longer follow it.2. Dump header filtering reuses
maskHeadersForLog(src/log-mask.ts) — the old/key|auth|token/iregex missedcookie/set-cookie; the shared rule covers them plus non-public hosts.3. Non-public hosts masked in dumps —
upstreamUrlinraw/*-REQ.txtviamaskUrlForLog; the incoming path inraw/*-INCOMING.txtviamaskUrlsInText(keeps the path's debug value, masks the embedded host).4. INCOMING dump honors
ACP_RAW_DUMP_DIR(was hardcoded tostateDir()/raw).5. Tests —
tests/dump-mask.test.ts: dumps off by default under--debug; withACP_DUMP_BODY=1dumps carry no credentials (authorization / x-api-key / cookie) and no non-public host; dumps still work without--debug.Verification
npm run typecheck— cleannpm test— 671 pass (668 + 3 new)npm run build— successResponse-body SSE dump (
ACP_DUMP_SSE) is untouched — already opt-in.