Skip to content

fix: mask credentials and non-public API endpoints in proxy logs (#255) - #274

Merged
ranxianglei merged 2 commits into
masterfrom
2026-08-27_mask-sensitive-logs
Aug 27, 2026
Merged

fix: mask credentials and non-public API endpoints in proxy logs (#255)#274
ranxianglei merged 2 commits into
masterfrom
2026-08-27_mask-sensitive-logs

Conversation

@ranxianglei

Copy link
Copy Markdown
Owner

Part B of #255 (this round's scope per triage): guarantee no sensitive info in proxy logsbili.log and the launcher tmp log bili-proxy-${port}.log. debug: true in the launcher is intentionally kept; dump-file gating and GC are tracked in separate issues.

What was leaking (verified on v0.1.55)

Log line Leak
forward POST → <url> (always-on, not debug-gated) full upstream URL incl. non-public relay host
→ upstream headers: (debug) authorization: Bearer sk-..., x-api-key, cookie verbatim
← upstream response headers: (debug) set-cookie verbatim
formatUpstreamError raw upstream URL + failing host (ECONNREFUSED 192.168.x.x, ENOTFOUND relay.internal)
mitm / CONNECT logs tunneled upstream host:port

Changes

  • new src/log-mask.ts — single masking module:
    • credential headers (key|auth|token|cookie) → <masked N chars> (length hint kept for debuggability)
    • non-public API hosts → <private-host> (port kept — host is the secret, port isn't); well-known public endpoints (openai, chatgpt, anthropic, deepseek, googleapis, azure, mistral, groq, cohere, together, fireworks, x.ai, openrouter, huggingface, moonshot, zhipu, volcengine, aliyuncs, baidu, minimax) stay verbatim
    • userinfo / query / hash stripped from logged URLs (key-in-query leak vector)
    • error message / address fields scrubbed of non-public hostnames
  • src/server.ts — forward line, ws-reject line, unrecognized-path line (client req.url can embed an absolute upstream URL via /bili/), hdrLog, respLog all routed through the masker
  • src/upstream-proxy.tsformatUpstreamError: url= masked; error text/address scrubbed; proxy= field unchanged (existing behavior: host visible, creds redacted — it's a debug signal for which proxy was used, not the user's API endpoint)
  • src/mitm.ts — all 9 CONNECT/tunnel/mitm log lines masked
  • tests/log-mask.test.ts — 11 tests: unit coverage of every masker + 2 end-to-end tests that boot the proxy against a mock upstream and assert no credential, no non-public host in ANY captured log line (debug on, log on)

Intentionally NOT changed (scope)

  • debug: true in src/launcher.ts — kept per triage
  • dump files (dumps/, raw/) — content masking + gating → new issue
  • no GC for dumps/raw/tmp logs/bili.log.old → new issue
  • upstream error body snippets (loop/core.ts, server.ts) not URL-scanned — bodies aren't expected to carry endpoints; noted in issue
  • client-hdr prefix8/suffix4 display kept by design

Verification

typecheck clean · 668/668 tests pass · build OK (rebased onto master @ v0.1.57)

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.
@github-actions

github-actions Bot commented Aug 27, 2026

Copy link
Copy Markdown

📦 Built Package Artifact

Branch: 2026-08-27_mask-sensitive-logs (39be0fe)

Option A — Install from npm PR tag (recommended)

npm install -g billion-context@pr-274

Each push to this PR publishes a new version under the pr-274 npm tag.

Option B — Download artifact

  1. Download the artifact from the Actions run
  2. Extract the tarball and install:
tar xzf billion-context-pr274.tgz
npm install -g package

This comment is automatically updated on each push.

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.
@ranxianglei

Copy link
Copy Markdown
Owner Author

[bot] Reviewed PR on branch 2026-08-27_mask-sensitive-logs (commit 1e5a0f9).

Verification (as submitted): npm run typecheck clean · npm test 668/668 pass · npm run build OK. The masker design is sound — public-host suffix list with dot-boundary matching, userinfo/query/hash dropped from logged URLs, credential headers → length hint, and the two e2e tests asserting no credential / no non-public host in any captured log line are a good guarantee.

Found one residual leak — fixed and pushed as 39be0fe:

src/mitm.ts tunnelThrough logged err.message verbatim (the connect failed: line, and the ${where} closed: line). OS/undici error text embeds the endpoint — I verified empirically that a failed net.connect produces connect ECONNREFUSED 127.0.0.1:1 — so the template masked the host but the error message re-leaked it:

tunnel <private-host>:8443 connect failed: connect ECONNREFUSED [internal-ip].5:8443

Same leak class formatUpstreamError already handled, just missed in mitm. Fix: new maskHostInText(text, host) in src/log-mask.ts (scrubs only the tunnel-target host; other addresses in the text, e.g. the proxy's, stay visible — consistent with the proxy= design decision) applied to both tunnel error lines; formatUpstreamError now reuses the same helper, which also fixes a latent IPv6 mismatch there (URL.hostname is bracketed [::1], OS error text is bare ::1). +2 tests: unit coverage of maskHostInText (incl. IPv6 both forms, public-host passthrough, other-address preservation) and an e2e that boots the proxy with MITM on, drives a real CONNECT to a dead port, and asserts no raw target host in any captured log line.

Re-verified after fix: typecheck clean · 670/670 tests pass · build OK.

Scope notes (agree with the issue): dump files still write upstreamUrl verbatim and their maskHdr doesn't cover cookie — correctly deferred to the dump-masking issue. Upstream error body snippets not URL-scanned — fine, noted.

Everything else checked clean: no other log lines in server.ts/launcher.ts/client-config.ts/ca.ts carry URLs or header values unmasked; the ACP_DEBUG client-hdr prefix8/suffix4 display is the intentional exception.

Ready for human merge — I can't merge PRs per AGENTS.md.

🤖 ework agent · vllm-qwen/qwen3.8-27b

@ranxianglei
ranxianglei merged commit d3d2ddd into master Aug 27, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant