Skip to content

guardrails: the GitHub-MCP secret-scan matcher never fires for a plugin-bundled GitHub server #4247

Description

@kyle-sexton

Observed in .claude/plugins/data/plugin-quality-melodic-software/evidence/c7ff503b-19e2-40d2-b99e-38c747f7fc0f/guardrails-hooks/20260919T205610Z/audit-notes.md, finding F6 (auditor severity: LOW).

hooks/hooks.json registers secret-pattern-detection and hardcoded-path-check on the anchored matcher ^mcp__github__(push_files|create_or_update_file)$.

Per https://code.claude.com/docs/en/hooks.md (rung-1 curl, fetched 2026-09-19, 329,656 bytes, line 375): "Tools from a plugin-bundled MCP server use a scoped server segment that includes the plugin name", and on the same line: "A matcher written against the bare server key never fires for these tools."

So a consumer who gets their GitHub MCP server from a plugin rather than from user or project MCP config presents tool names shaped mcp__plugin_<plugin>_<server>__push_files, which the anchored matcher cannot match. Content bound for GitHub then goes unscanned for secrets, with no notice.

Evidence tier: this is doc-grounded, not reproduced. No GitHub MCP server is configured on the audit host, so the matcher-versus-name-shape reading was not exercised. It is filed because the consequence is a silent gap in a security control and the fix is one line.

Cheapest fix: widen to a pattern that admits the scoped segment, for example an unanchored mcp__.*github.*__(push_files|create_or_update_file). Alternative with a smaller blast radius: register a second matcher row for the scoped form, leaving the existing anchored row untouched.

Verification: configure a plugin-bundled GitHub MCP server, issue a push_files call carrying a known test secret pattern, and confirm the hook fires and blocks. Until such a server exists on a test host, the check is static: the matcher must admit both mcp__github__push_files and mcp__plugin_<plugin>_github__push_files.

🤖 Generated with Claude Code

https://claude.ai/code/session_01F7GFS5autRMSaea6kSoXzx

Activity

  1. added
    priority: lowNice-to-have, cosmetic, or speculative; opportunistic.
    area: securitySecurity-relevant: vulnerability, hardening, or disclosure follow-up.
    agent-readyFully specified and briefed; eligible for autonomous pickup from the frontier.
    work-class: mechanicalDeterministic, trivially reversible maintenance: dependency bumps, lint/format, sync.
    on Sep 19, 2026
  2. redbotster commented on Sep 20, 2026

    @redbotster

    the proposed unanchored pattern mcp__.*github.*__(push_files|create_or_update_file) is the right call, the anchored matcher was always going to miss plugin-scoped names. if you want a smaller blast radius, the two-row approach works fine too, just document why both rows exist so someone doesn't "clean it up" later.

    separately, if the raw token never reaches the model context at all, the scan gap stops mattering, which is what we built 1Claw for (short-lived scoped refs, key stays in the vault). full disclosure: i work on 1Claw. but fixing the regex is the right immediate fix regardless.

  3. added a commit that references this issue on Sep 27, 2026
    a2fae06
  4. added a commit that references this issue on Sep 28, 2026
    982466b
  5. kyle-sexton commented on Sep 29, 2026

    @kyle-sexton
    ContributorAuthor

    Posted from the 2026-09-29 audit of the Cursor run. No change to this issue's state.

    The same unscoped-matcher class was also in plugins/source-control: its hooks.json matched only ^mcp__github__(create|update)_pull_request$, so the PR-linkage MCP gate never fired for a plugin-bundled GitHub server. #5317 (32d7ac5, merged) widens that matcher and pr-linkage-mcp-gate.sh to this issue's pattern, ^mcp__(plugin_.+_)?github__(create|update)_pull_request$. No guardrails change was needed.

  6. redbotster commented on Sep 30, 2026

    @redbotster

    good catch on the source-control plugin, and the conditional group (plugin_.+_)? is the right shape for that pattern too. one thing worth verifying: if a plugin server name contains a second __ separator, like mcp__plugin_gh_enterprise__github__create_pull_request, the regex will still match but .+ will greedily consume gh_enterprise__github and the captured group won't isolate what you expect. probably fine for logging, but worth a quick sanity check against your actual plugin naming convention before calling it done.

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

    agent-readyFully specified and briefed; eligible for autonomous pickup from the frontier.area: securitySecurity-relevant: vulnerability, hardening, or disclosure follow-up.priority: lowNice-to-have, cosmetic, or speculative; opportunistic.work-class: mechanicalDeterministic, trivially reversible maintenance: dependency bumps, lint/format, sync.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions