Repository navigation
bug(core): fine-grained GitHub PATs (github_pat_) pass both shared redactors unmasked #6061
Description
What happened
Both shared secret redactors mask every classic GitHub token family but not fine-grained PATs (github_pat_...):
redactSecretsinpackages/core/src/redaction.ts(line 74:gh[pousr]_[0-9A-Za-z_]{20,}), used by the PTY screen collector, the diagnostic log, and MCP secret handling.- the display redactor in
packages/core/src/display-redaction.ts(line 102:gh[opusr]_[A-Za-z0-9]{30,}), the single source for the Desktop quiet panel and the TUI (packages/ui/src/redact.ts).
github_pat_... is the fine-grained PAT format GitHub has issued by default since October 2022, and it is the format Maka itself imports as a GitHub Copilot credential: isSupportedGitHubCopilotAccountToken (packages/runtime/src/subscription-credentials.ts:511) accepts exactly gho_, ghu_, and github_pat_ (classic ghp_ is refused with a dedicated error).
For a user who has a fine-grained PAT in the environment Maka reads (COPILOT_GITHUB_TOKEN, GH_TOKEN, GITHUB_TOKEN in apps/desktop/src/main/oauth/github-copilot-local-credential.ts), or who prints one inside a command, the token survives both redactors wherever it appears bare, with no sensitive key name in front of it:
- Session shells inherit the Host environment (
packages/runtime/src/shell-run-manager.ts:788), sogh auth token,echo $GITHUB_TOKEN, or a token embedded in prose puts the raw text into tool output. Keyed lines such asenv/printenvoutput (GITHUB_TOKEN=github_pat_...) were already masked by the core redactor's sensitive-key assignment rule; the display redactor has no such rule, so those lines still leaked there. - For a bare token,
packages/runtime/src/pty-screen-collector.ts:501usesredactSecrets(line) !== lineas its whole-line suppression test, so the line is neither redacted nor suppressed and reaches the model and the transcript. - The quiet-panel/TUI preview (
packages/core/src/tool-quiet-preview.ts) renders the value in plain text for both bare and keyed forms, including text users screenshot or paste into bug reports. - The diagnostic log (
packages/core/src/diagnostic-log.ts:58) stores the core-redacted text, so bare tokens land in exported diagnostics bundles; keyed forms were already masked there. - MCP secret scrubbing uses
isPatternSecret(packages/core/src/mcp-secrets.ts:93) as the only signal for free-form positions, so--custom=github_pat_...or a query value under a harmless key is not recognized as credential material.
Measured against the built @maka/core at fa26868b1d223acc833089e94b6834188926f49c (fake bare tokens, repeated characters):
family | core redactSecrets | display redactSecrets | pty line suppression
github_pat_ (fine-grained) | LEAKS UNCHANGED | LEAKS UNCHANGED | does NOT suppress
ghp_ (classic PAT) | [redacted] | <redacted> | suppresses
gho_ (OAuth) | [redacted] | <redacted> | suppresses
ghu_ (user-to-server) | [redacted] | <redacted> | suppresses
ghs_ (server-to-server) | [redacted] | <redacted> | suppresses
ghr_ (refresh) | [redacted] | <redacted> | suppresses
isPatternSecret(github_pat_): false
isPatternSecret(ghp_): true
Expected: the fine-grained family is masked on the same terms as the five classic families above.
How to reproduce
npm ci && npm run build- Save and run this script (
node repro.mjs); it imports the built redactors directly:
import { redactSecrets as coreRedactor } from './packages/core/dist/redaction.js';
import { redactSecrets as displayRedactor } from './packages/core/dist/display-redaction.js';
import { isPatternSecret } from './packages/core/dist/mcp-secrets.js';
const fineGrained = 'github_pat_11' + 'a'.repeat(80); // fake
console.log('core: ', coreRedactor(fineGrained) === fineGrained ? 'LEAKS' : 'masked');
console.log('display:', displayRedactor(fineGrained) === fineGrained ? 'LEAKS' : 'masked');
console.log('control:', coreRedactor('ghp_' + 'a'.repeat(36)) === 'ghp_' + 'a'.repeat(36) ? 'LEAKS' : 'masked');
console.log('mcp: ', isPatternSecret(fineGrained));Actual output: core: LEAKS, display: LEAKS, control: masked, mcp: false.
Environment
- Maka version or commit:
fa26868b1d223acc833089e94b6834188926f49c(main) - OS and version: Windows 11 24H2; the defect is pure string matching, so any OS reproduces it
- Surface:
@maka/coreshared redactors (Desktop, TUI, Runtime Host, diagnostics) - Node.js version, if running from source: 24.21.0
Logs, screenshots, or additional context
SECURITY.mdsection 2.4 listsredactSecrets()as a heuristic the project ships but does not treat as a boundary, and says reports that limit it "are welcome as ordinary issues, not security advisories". Filing accordingly.- Precedent for single-family redaction fixes: [codex] Redact tool telemetry args summaries #298, observation_id is not redacted, unlike every field beside it #2042, fix(core): always fully redact bare provider tokens #3688, fix(core): shared redactors leave URL userinfo credentials intact #4593 (fixed by fix(core): redact URL userinfo credentials in shared redactors #4597), bug(core): redactSecrets misses a sensitive assignment inside a harmless assignment's value #5433 (fixed by fix(core): redact a secret assignment nested in a harmless assignment's value #5434).
- Scope: one added alternative in each of the two pattern lists, plus tests; no new dependency and no behavioral change for any input the redactors already mask.
AI assistance disclosure: measurements and drafting were produced with AI (Codex) at my request and under my direction; I reviewed the reproduction and the cited code paths.
Activity
fix(core): mask fine-grained GitHub PATs in both shared redactors
fix(core): mask fine-grained GitHub PATs in both shared redactors