fix(mcp): redact Octokit error tool-results and widen GitHub token regex#238
Conversation
Five GitHub-touching MCP servers and the shared state-fetchers fetcher
returned raw Octokit error messages to the agent tool-result channel,
leaking the installation token echoed in the credential URL and Bearer
header (OWASP MCP01). Add config-free redactErrorMessage() and wire it
into all six catch sites; delete the divergent merge-readiness inline
regex.
Also widen the canonical ghp_/gho_/ghs_/ghr_ token regexes in sanitize.ts
from {36} to open-ended [A-Za-z0-9._-]{36,} so the new ~520-char
ghs_APPID_JWT installation token (GitHub rollout 2026-04-27) is scrubbed.
Closes #224
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015v2bspPF1ZTTG9ZZrbBgA1
|
Warning Review limit reached
More reviews will be available in 22 minutes and 42 seconds. Learn how PR review limits work. Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file). ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits. 🚦 How do rate limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (11)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
This PR addresses secret leakage in MCP tool-result outputs by ensuring Octokit error messages are redacted before being returned to the agent, and updates GitHub token redaction to cover the new long ghs_APPID_JWT installation-token format.
Changes:
- Introduces
redactErrorMessage(err)and wires it into 6 known leak catch-sites so tool results don’t echo credentialed URLs / tokens. - Widens
ghp_/gho_/ghs_/ghr_token regexes to match GitHub’s new variable-length installation token format. - Adds unit tests for the new redaction helper, token-regex coverage, and a wiring regression guard.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
src/utils/log-redaction.ts |
Adds redactErrorMessage for tool-result-safe error sanitization. |
src/utils/sanitize.ts |
Widens GitHub token patterns in both input-side and output-side secret redactors; documents ordering vs JWT. |
src/mcp/servers/comment.ts |
Routes caught error messages through redactErrorMessage. |
src/mcp/servers/inline-comment.ts |
Routes caught error messages through redactErrorMessage. |
src/mcp/servers/github-state.ts |
Routes fail() error messages through redactErrorMessage. |
src/mcp/servers/resolve-review-thread.ts |
Routes caught error messages through redactErrorMessage. |
src/mcp/servers/merge-readiness.ts |
Replaces divergent inline regex with redactErrorMessage. |
src/github/state-fetchers.ts |
Routes dispatch tool error messages through redactErrorMessage. |
test/utils/log-redaction.test.ts |
Adds unit tests for redactErrorMessage across token / header / URL / DB URL cases. |
test/mcp/error-redaction-wiring.test.ts |
Adds source-assertion wiring guard for all 6 catch sites. |
test/utils/sanitize.test.ts |
Adds a new-format stateless ghs_ token case to the canonical suite. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| export function redactErrorMessage(err: unknown): string { | ||
| const raw = err instanceof Error ? err.message : String(err); | ||
| return redactSecrets(redactCredentialUrls(raw)).body; | ||
| } |
| const repoRoot = join(import.meta.dir, "..", ".."); | ||
| const ASSIGN = "= redactErrorMessage("; | ||
|
|
| it("merge-readiness.ts uses redactErrorMessage and drops the inline regex", () => { | ||
| const src = readSource("src/mcp/servers/merge-readiness.ts"); | ||
| expect(src).toContain(ASSIGN); | ||
| expect(src).not.toContain("x-access-token:"); | ||
| }); |
Summary
Five GitHub-touching MCP servers (
comment,inline-comment,github-state,resolve-review-thread,merge-readiness) and the sharedstate-fetchers.tsfetcher returned raw Octokit error messages directly to the agent's tool-result channel. Those messages echo the full credential URL (https://x-access-token:<ghs_token>@api.github.com/...) andAuthorization: Bearer <token>headers with no redaction — OWASP MCP01 tool-result token leakage.merge-readinesshad a hand-rolled inline regex that had already drifted from the canonical redactor; the other four did nothing.A second, live gap was closed during review: the canonical
redactGitHubTokens/redactSecretsregexes insrc/utils/sanitize.tsonly matched the legacy 36-charghs_body. GitHub's 2026-04-24 changelog announces a new ~520-char statelessghs_APPID_JWTinstallation-token format, rolling out to all App installation tokens mid-May–late-June 2026, and explicitly namesghs_[A-Za-z0-9]{36}as a regex that will break. This app mints installation tokens itself, so its own live token was about to become unredactable. Theghp_/gho_/ghs_/ghr_patterns are widened to GitHub's recommended open-ended[A-Za-z0-9._-]{36,}shape so both the legacy and new-format tokens are scrubbed.Flow
flowchart TD OctErr["Octokit error<br/>echoes credential URL and Bearer header"]:::new --> Redact["redactErrorMessage<br/>redactCredentialUrls then redactSecrets<br/>byte-deletion only, no marker"]:::new Redact --> AgentResult["agent tool-result<br/>credential-free"]:::keep TokenRegex["GitHub token patterns widened<br/>open-ended body, 36 or more chars<br/>covers legacy and new stateless token"]:::new --> SanitizePipe["redactSecrets pipeline"]:::keep classDef new fill:#1a5276,color:#ffffff classDef keep fill:#ecf0f1,color:#2c3e50Before: Octokit error → raw string (credential URL / Bearer header) → agent tool-result.
redactGitHubTokensmatched only exactly-36-charghs_tokens; new ~520-char installation tokens passed through unredacted.Changes
src/utils/log-redaction.ts— new config-free helperredactErrorMessage(err): extracts the message string, runsredactCredentialUrlsthenredactSecrets, returns the cleaned body. Byte-deletion only, no[REDACTED_X]marker (CLAUDE.md security invariant 2 — markers leak probing signal on output paths).src/mcp/servers/{comment,inline-comment,github-state,resolve-review-thread,merge-readiness}.ts— everycatchnow routes throughredactErrorMessageinstead of rawerr.message.merge-readiness's divergent inline regex is deleted.src/github/state-fetchers.ts— sixth catch site (dispatchGithubStateTool) wired toredactErrorMessage. Stays config-free (log-redactionimports onlypino+sanitize).src/utils/sanitize.ts—ghp_/gho_/ghs_/ghr_token regexes widened from{36}to open-ended[A-Za-z0-9._-]{36,}(input-sideredactGitHubTokensand output-sideredactSecretsin parity) so the new ~520-charghs_APPID_JWTformat is scrubbed. Ordering invariant documented: the GitHub-token patterns must stay above the JWT pattern, since a stateless token embeds a JWT.test/utils/log-redaction.test.ts— unit tests forredactErrorMessage: bare token,Bearerheader, credential URL, the new ~520-char format, and benign passthrough (Error + non-Error).test/mcp/error-redaction-wiring.test.ts— source-assertion guard binding each of the 6 catch sites to= redactErrorMessage(, so the wiring cannot silently regress without a heavyweight MCP subprocess harness.test/utils/sanitize.test.ts— new-format (~520-char)ghs_case added to the canonical token-redaction suite.Related issues
Closes #224
Test plan
redactErrorMessageunit tests incl. the new ~520-charghs_APPID_JWTshape (bare / Bearer / credential URL)sanitizesuitetypecheck/lint/formatcleanbun testshows pre-existing DB-skip noise only; the isolated CI runner (scripts/test-isolated.sh) reports 0 genuine assertion failures and all target test files pass