Skip to content

Coordinated disclosure: github-safe.js helper interpolates argv into a shell string passed to execSync (AS-S001) #3411

Open

Description

@xuange-hu

Hi ruflo/claude-flow maintainers —

We scanned the top 100 MCP-related npm packages by weekly downloads as part of a security study (full report + methodology). @claude-flow/mcp@3.0.0-alpha.9 (current npm latest) came up with one finding we would like to hand you before anything is published — public disclosure is held for 14 days from the date on this issue.

The shipped helper .claude/helpers/github-safe.js runs (lines 101 and 105):

execSync(`gh ${args.join(' ')}`, { stdio: 'inherit' });

args comes from the script’s own argv, so any caller — including a prompt-injected agent that composes this command line — gets its content executed through a shell. Wildcards, $(…), backticks, ; all expand. The gap is that a helper named github-safe is exactly what agents (and humans) will trust without reading; the name promises sanitization the implementation does not do.

Suggested fix (drop the shell entirely):

execFileSync('gh', args, { stdio: 'inherit' });

If the intent is an allowlist of gh subcommands, validate args[0] against it before exec — either way no shell string is ever constructed.

Reproduce locally (static only, nothing executes):

npm pack @claude-flow/mcp && tar -xzf claude-flow-mcp-*.tgz
npx @yoshine2007/agent-scan@latest .

We are fans of the project — this is a one-line fix that turns a trust-checking finding into nothing. Happy to re-run and confirm clean before you ship.

— xuange-hu, agent-scan

Activity

  1. ruvnet commented on Sep 24, 2026

    @ruvnet
    Owner

    Confirmed against the published @claude-flow/mcp@3.0.0-alpha.9 tarball without executing an exploit. Current main already contains the shell-free source fix from commit 0e1d26c11, but that repair has not reached the published artifact.

    Draft PR #3412 adds package-copy parity and behavioral regression coverage across all three shipped helpers. Local checks pass 15/15 cases and the packed alpha.10 candidate contains the hardened helper. No release or merge was performed.

    Because the affected package remains published, please coordinate the release and disclosure through a private security advisory; do not treat source-only repair as remediation.

  2. ruvnet commented on Sep 25, 2026

    @ruvnet
    Owner

    2026-09-25 delivery verification (public-safe): the npm latest, alpha, and v3alpha dist-tags still resolve @claude-flow/mcp to 3.0.0-alpha.9; 3.0.0-alpha.10 is not published. The current Ruflo release/tag contains newer source, but its release path does not publish the standalone MCP package, so the affected registry artifact remains the user-visible version. Draft PR #3412 remains cleanly mergeable and its harmless regression passes 18/18 plus packed-candidate argv/body/bounds cases, but it does not change the package version, lockfile, or publishing workflow. Closure therefore requires a private advisory, an authorized standalone MCP release, retrieval of the exact published tarball, replay of all helper/error/bounds regressions against that tarball, and verification that the intended dist-tags moved. No release or merge was performed by this review.

  3. ruvnet commented on Sep 29, 2026

    @ruvnet
    Owner

    Delivery verification update (2026-09-29)

    The source/package gap tracked here is now closed in current official registry artifacts:

    • @claude-flow/mcp@3.0.0-alpha.10 published 2026-09-27
    • @claude-flow/mcp@3.0.0 published 2026-09-28; current latest, v3alpha, and alpha tags resolve to 3.0.0
    • exact alpha.10 and stable tarballs both install/import cleanly
    • alpha.10 passed 3/3 bounded direct-argument regression cases against a no-effect fake gh
    • stable 3.0.0 does not publish the affected helper surface
    • clean-install npm audit reports zero findings for both

    Artifact digests:

    • alpha.10 SHA-256 cb17b2537a05e7ce26759fcbf622df2e064bf5ef7a67239501fe43b42a97731c
    • 3.0.0 SHA-256 0c44e4e99634cd6e73d3fa64fb9bdb6a5516f9c5db0d1ac9fb56090db179a56d

    This verifies package publication and bounded local behavior only. Adoption and production outcomes remain unverified. No production endpoint or credential was used.

  4. nicholas-ruest commented on Sep 30, 2026

    @nicholas-ruest
    Contributor

    Independent verification (2026-09-30), separate from the prior maintainer checks above:

    • @claude-flow/mcp@latest/alpha/v3alpha all resolve to 3.0.0 (confirmed via npm view @claude-flow/mcp dist-tags).
    • Packed and inspected @claude-flow/mcp@latest (3.0.0) directly: it does not ship .claude/helpers/github-safe.js or any execSync call — the affected helper surface is absent from this artifact.
    • Packed and inspected @claude-flow/mcp@3.0.0-alpha.10 directly: it does ship .claude/helpers/github-safe.js, and all three gh invocations use execFileSync('gh', argv, ...) — no shell-string execSync remains.
    • In the repo source, all three shipped copies of github-safe.js pass the regression smoke (scripts/smoke-github-safe-injection.mjs, 18/18 cases: backtick/$()/semicolon bodies, oversized-body rejection, argv whitespace preservation).

    This confirms the fix (PR #3412) and the publish are both live on the registry as of this comment. No further code or release action is needed on our end for this report. Thanks again for the responsible disclosure and the clean repro steps — appreciated.

  5. ruvnet commented on Oct 7, 2026

    @ruvnet
    Owner

    @/tmp/claude-0/-home-user-ruflo/99a198e7-2058-59ed-a922-018777f60b76/scratchpad/triage/i3411.md


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions