Skip to content

fix(logs): don't panic masking a JWT with a short signature - #1761

Merged
Dumbris merged 1 commit into
smart-mcp-proxy:mainfrom
rudycelekli:fix/log-sanitizer-jwt-short-sig-20261023-01
Oct 25, 2026
Merged

Dumbris merged 1 commit into
smart-mcp-proxy:mainfrom
rudycelekli:fix/log-sanitizer-jwt-short-sig-20261023-01

Conversation

@rudycelekli

Copy link
Copy Markdown
Contributor

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

@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@Dumbris
Dumbris force-pushed the fix/log-sanitizer-jwt-short-sig-20261023-01 branch from d6500d4 to 8bcff51 Compare October 25, 2026 06:45
@Dumbris

Dumbris commented Oct 25, 2026

Copy link
Copy Markdown
Member

Rebased onto main after #1760 moved the jwt block (as you predicted): the short-signature guard now sits in the moved block, nothing else changed. Verified the test fails on main and passes here, with race tests and CI lint clean. Will merge once CI is green.

@Dumbris Dumbris left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified: the new test panics on main (slice bounds out of range for a 1-3 char signature) and passes here; race tests and CI lint for internal/logs clean on top of #1760 and #1762. The one red job, Unit Tests (shuffle), fails in internal/server TestTPAFetchLifecycle (refresh-cycle count), unrelated to this change and green on main.

@Dumbris
Dumbris merged commit 0cfb7eb into smart-mcp-proxy:main Oct 25, 2026
45 of 46 checks passed
@Dumbris

Dumbris commented Oct 25, 2026

Copy link
Copy Markdown
Member

Merged. Thank you, @rudycelekli! A JWT-shaped string with a one-to-three character signature, anywhere in a log line (including upstream tool output), used to panic inside the log sanitizer and take the process down. It is now masked without slicing.

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.

Dumbris added a commit that referenced this pull request Oct 29, 2026
## 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>
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.

3 participants