Skip to content

bug(core): fine-grained GitHub PATs (github_pat_) pass both shared redactors unmasked #6061

Closed

Description

@JasonBuildAI

What happened

Both shared secret redactors mask every classic GitHub token family but not fine-grained PATs (github_pat_...):

  • redactSecrets in packages/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), so gh auth token, echo $GITHUB_TOKEN, or a token embedded in prose puts the raw text into tool output. Keyed lines such as env/printenv output (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:501 uses redactSecrets(line) !== line as 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

  1. npm ci && npm run build
  2. 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/core shared redactors (Desktop, TUI, Runtime Host, diagnostics)
  • Node.js version, if running from source: 24.21.0

Logs, screenshots, or additional context

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

  1. added 2 commits that reference this issue on Oct 11, 2026
    2d11d1b
    c895e0f
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions