Repository navigation
Conversation
|
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Dumbris
left a comment
There was a problem hiding this comment.
Verified: the new test fails on main (payload and signature of a JWT after =, : or a quote were logged in clear) and passes here; race tests and CI lint for internal/logs are clean. Also checked combined with #1761 and #1762, and cross-reviewed. Masking the header twice once high_entropy runs afterwards is harmless.
|
Merged. Thank you, @rudycelekli! A JWT logged after Contributions like this are what keep MCPProxy moving, and they matter a lot to the project. One small ask, only if you feel like it: a short public note about your change (a tweet, a LinkedIn post, a line in a community you are part of) helps more people discover MCPProxy. No pressure at all, and I would be glad to repost it. |
## Bug The log sanitizer's `jwt` regex accepts any non-empty third segment (`[A-Za-z0-9\-_]+`), but its mask function always takes the last four characters with `parts[2][len(parts[2])-4:]`. A JWT-shaped string with a 1–3 character signature panics with `slice bounds out of range` inside `SecretSanitizer.Write`, which zap does not recover. The process logging the line crashes. ## Trigger Any log message or field containing, for example: ``` eyJhbGciOiJub25lIn0.eyJzdWIiOiIxIn0.x ``` This can come from upstream tool output, an error body echoed into a log, or a test or unsigned token. The result is `panic: runtime error: slice bounds out of range [-3:]`. ## Fix When the signature is shorter than four characters, mask it fully (`<header>.***.***`) instead of slicing. This touches the same `jwt` block that #1760 moves. Whichever lands second needs a trivial rebase. ## Test `TestSanitizer_JWTShortSignatureDoesNotPanic` runs signatures of length 1, 2 and 3 through `sanitizeString`, recovers any panic, and checks that the payload is masked. ## Commands run - `CGO_ENABLED=0 go test -count=1 ./internal/logs`: ok - Without the fix, the new test fails on all three inputs with `panicked: runtime error: slice bounds out of range [-3:]`, `[-2:]` and `[-1:]`. - `gofmt -l` on the edited files: no output
## Summary
Fixes two secret leaks in `internal/logs/sanitizer.go` that were already
on `main`.
**1. Bearer suffix leak (pattern order).** The patterns run one after
another over the rewritten string, and every mask inserts `***`, which
no pattern's character class accepts. `github_token`, `openai_key` and
`anthropic_key` ran before `bearer_token`. So when a Bearer credential
started with a known key prefix, the key pattern masked it first, and
bearer then stopped at the inserted `***`:
```
before: Bearer sk-AbCdEf1234567890GhIjKlMnOpQrStUvWxYz0123456789.EXTRA_SECRET
-> Bearer sk-A***Ab***89.EXTRA_SECRET
after: -> Bearer sk-A***ET
```
The same thing could happen inside a JWT: a key-shaped run after a `-`
in a segment got masked before `jwt` ran, and the payload and signature
were logged. The fix runs `bearer_token`, then `jwt`, before the
key-specific patterns. A comment above the patterns now states the
ordering rule.
**2. Unsigned JWT (`alg: none`).**
`eyJhbGciOiJub25lIn0.eyJzdWIiOiIxIn0.` has an empty third segment, and
the trailing `\b` could never match after a `.`, so the payload was
logged in clear. The third segment is now `[A-Za-z0-9\-_]*` and the
trailing `\b` is gone. The output is `header.***.***`.
Existing mask formats are unchanged. That includes the tests from #1760,
#1761 and #1762. A credential after `Bearer` now gets the Bearer mask
instead of the key mask.
## Testing
- New `internal/logs/sanitizer_pattern_order_test.go`: Bearer +
sk-/sk-proj-/ghp_/sk-ant- suffix, a JWT with a key-shaped run inside it,
and an unsigned JWT in four contexts. All of these failed before the
fix.
- `go test -race ./internal/logs/` passes.
- golangci-lint v2 (`.github/.golangci.yml`) reports 0 issues, both bare
and with `--build-tags server`.
- Cross-review (opencode gpt-6.1-sol, high): CLEAN.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Bug
SecretSanitizerapplies its patterns in registration order, andhigh_entropyis registered beforejwt.high_entropymatches after=,:or a quote and stops at the first., so for a JWT in any of those contexts it masks only the header (eyJ***J9). Thejwtregex then no longer matches the rewritten string, so the payload and signature are written to the log in clear text. A standard header ({"alg":"HS256","typ":"JWT"}) is trivially guessable, so in practice the whole token is recoverable.Trigger
Any log line or field containing:
On main this is logged as:
token=<jwt>andid_token: <jwt>leak the same way. A bare JWT (got <jwt>) is masked correctly, becausehigh_entropyneeds the=/:/quote context.Fix
Register the
jwtpattern beforehigh_entropy. The JWT is then masked first (<header>.***.<last4>). Any remaining high-entropy header after a quote is still masked byhigh_entropy, as before. No pattern changes.Test
TestSanitizer_JWTInKeyValueAndQuotedContextschecks that neither the payload nor the signature survives in the JSON,token=,id_token:and bare forms.Commands run
CGO_ENABLED=0 go test -count=1 ./internal/logs: okgit stashofsanitizer.go), the new test fails on 3 of 4 inputs withJWT payload/signature leaked. The bare form passes in both cases.gofmt -lon the edited files: no output