Topic: mcp server security github
MCP server security on GitHub — Action and PR gate
If you maintain a Model Context Protocol server on GitHub, or your team allows MCP servers into a fleet's agent stack, the right place to enforce security is the pull request — not the install. Here's the GitHub Action, the workflow YAML, and what it catches that traditional code scanning doesn't.
TL;DR
SkillAudit ships a GitHub Action (skillaudit/skillaudit-gate@v1, available on the Pro and Team plans) that runs the same six-axis scan you'd run from the website, posts the report card as a check on the PR, and fails the workflow if the grade falls below a configurable minimum. For author repos: catch SSRF, prompt-injection, and credential-echo before they merge. For consumer repos: fail PRs that add a new MCP server below a B grade. It composes with GitHub's native code scanning rather than competing with it — code scanning handles the conventional SAST surface; SkillAudit handles the MCP-specific surface. The 101-server corpus shows why the gap matters: 50% SSRF, 38% credential findings, 19% A-grade.
Why a PR-time gate, not an install-time check
An install-time scan is the right defense if you're a solo developer evaluating one server before claude plugin install. It's the wrong defense once a team is involved, for two reasons:
- Install-time is too late. By the time a server is in someone's
.mcp.jsonor~/.claude/plugins/, the credential-stealer or SSRF is already configured. The PR that added it is the natural choke point — and it's the one place a reviewer is paid to look. - Install-time is too distributed. Solo devs across a team will install whatever they need, on a schedule no central process can govern. The PR that adds an MCP server to the team's shared agent config (or to a CI agent's workflow) is centralized; that's where policy enforcement scales.
The same logic that put dependency review and code scanning into PR checks puts MCP-server review there too. An install-time scan catches the case where someone adds foo-mcp to their personal config; a PR gate catches the case where they add it to the team's. Both layers are useful; the PR gate is the higher-leverage one if you're building team policy.
The Action — what it does
- Detects MCP additions. Diffs the PR for new entries in common MCP config locations (
.mcp.json,mcp.config.json,~/.claude/plugins.json, custom paths via input). For author repos, uses the repo itself. - Resolves each addition to a canonical source. A GitHub URL, npm package name, or local path. The Action follows package-redirect chains the same way the website scanner does.
- Runs the six-axis scan. Same engine as the public scanner, same v0.3 methodology, same calibration set. The Action runs the static layer locally and the LLM-assist layer via the SkillAudit API (a Pro-tier API key is required for the LLM-assist axis).
- Posts the result as a check. The check status is "passing" if the resulting grade meets the minimum, "failing" otherwise. The check page links to the full report card at
/audits/<owner>-<repo>/. - Fails the workflow if any addition is below the minimum. Configurable per-repo via the
min-gradeinput. Default isB. Below the min = workflow fails = PR can't merge if branch protection is enabled.
Workflow YAML
Drop this into .github/workflows/skillaudit.yml in the repo where you want the gate enforced:
name: SkillAudit gate
on:
pull_request:
paths:
- '.mcp.json'
- 'mcp.config.json'
- 'src/**/*.ts'
- 'src/**/*.py'
jobs:
audit:
runs-on: ubuntu-latest
permissions:
pull-requests: write
contents: read
checks: write
steps:
- uses: actions/checkout@v4
- name: Run SkillAudit gate
uses: skillaudit/skillaudit-gate@v1
with:
# Minimum grade to allow. A | B | C | D | F.
# Below this = workflow fails.
min-grade: B
# Where to look for MCP server additions.
# Default: .mcp.json, mcp.config.json
mcp-config-paths: |
.mcp.json
mcp.config.json
# API key for the LLM-assist axis. Free tier runs static-only.
api-key: ${{ secrets.SKILLAUDIT_API_KEY }}
# Optional: scan this repo as itself (use for author repos).
scan-self: false
The action posts a check on the PR. The check page shows the grade per added server, the per-axis breakdown, and a deep link to the public report card. If the grade meets the minimum, the workflow passes; otherwise it fails and the merge is blocked (assuming branch protection is on).
What's caught at PR-time, and how it composes with code scanning
GitHub-native code scanning (CodeQL, third-party SARIF uploads from Semgrep, Snyk Code, etc.) is the right tool for conventional SAST findings — XSS, SQLi, OWASP web patterns, dependency CVEs through SCA. None of that goes away. SkillAudit's gate adds an orthogonal axis: does this MCP server's tool-handler code expose a class of bug that conventional SAST doesn't model.
Concretely, here's the split for a representative MCP-adding PR:
- Code scanning catches: a CVE in
axiosthe new server pulls in; an XSS pattern if the server renders HTML; a hard-coded secret string in the repo (gitleaks-style scan). - SkillAudit gate catches: the SSRF in
server.tool('fetch_url', ({url}) => fetch(url))that the new server registers; the credential echo in the tool's debug log; the prompt-injection vector that's reachable when the tool's output is consumed by a downstream model; the maintenance signal (last commit, archive flag) on the upstream repo; the over-broad OAuth scope. - Both: a string-interpolated
execSyncin the tool body. Code scanning would flag the dangerous sink; SkillAudit additionally pairs it with the registered-tool context that makes it reachable from LLM input.
The two layers compose. A typical Pro-plan team enables both: code scanning for the framework and the dependency tree, SkillAudit for the MCP surface. The GitHub code scanning alternative page covers the comparison in side-by-side detail.
Configuring the gate for different repo shapes
| Repo shape | Recommended min-grade | Recommended scan-self | Why |
|---|---|---|---|
| Author repo (you ship one MCP server) | A | true | The marketplace listing review uses the same axes; aim for a green badge before publishing. |
| Team agent config (you consume MCP servers) | B | false | B is install-with-confidence; C is install-with-caution. Don't auto-merge a C without a human read. |
| Internal-only MCP server (private repo) | B | true | Private MCP code is still tool-handler code. Same threat surface, same axes apply. |
| OSS MCP framework (FastMCP, mcp-use, etc.) | A | true | Framework code is run by every downstream user; the ceiling has to be higher. |
| Experimental fork during a security audit | F (i.e. don't gate) | true | You want the report on every PR but you're consciously below grade while you remediate. Set min-grade to F to keep the report posting without failing the build. |
Author flow: getting your repo's grade up before listing
If you're publishing a Claude skill or MCP server to a public marketplace and want the badge before submission, the gate becomes a self-improvement loop. Set scan-self: true, min-grade: A, push a branch, and read the failing check's report. Most A-grade fixes are mechanical; the patterns are documented in Anatomy of an A-grade MCP server:
- Switch
fetch(url)tofetch(url)behind an allowlist. - Remove the
console.log({env: process.env})from the debug branch. - Drop the unused
repoOAuth scope;public_repois enough for what your tools do. - Add a runnable example to the README that calls the tool with a real argument.
- Tag a release; the maintenance axis weights last-commit recency.
Each fix re-runs the scan in the next PR. The grade rebuilds. Authors in our corpus moved from F to A in roughly five PRs of mechanical work; the median was three.
Related questions
Does the Action work on private repos?
Yes — the Pro plan ($19/mo) includes private-repo scanning via the Action. The Team plan ($99/mo for 10 seats) adds SSO, policy export, and an audit log of gate decisions across the org's repos.
What if I don't have an API key — can I run static-only?
Yes. Without an API key the LLM-assist axis is skipped; the static layer runs locally on the GitHub-Action runner. Grade is reported with a "static-only" footnote so reviewers know the LLM-probe axis was not evaluated. Free for any public repo.
Does the Action upload SARIF for native code scanning integration?
Yes — set upload-sarif: true in the inputs. SkillAudit findings appear in the GitHub Security tab alongside CodeQL, Semgrep, and any other SARIF uploaders you have wired up. Useful when your security team already lives in that view.
How fast is the Action?
Static-only: under 30 seconds for a typical MCP server. Static + LLM-assist: 60-90 seconds for one new server, scaling with tool count. Cached if the upstream repo's HEAD hasn't moved since the last scan.
Can I run it on a schedule, not just on PRs?
Yes — the Action can be triggered from a schedule: workflow to re-scan all currently-installed MCP servers nightly. Useful for catching upstream regressions: a server that was an A last week may have shipped a credential leak in a patch release.
Further reading
- MCP server security scanner — what to look for in one — the threat-model deep dive.
- How to run an MCP server security scan — the buyer-side workflow without CI.
- The minimum-grade install gate policy — what min-grade to set, and why.
- GitHub code scanning vs SkillAudit — the orthogonality argument in side-by-side detail.
- Anatomy of an A-grade MCP server — the five patterns you'll need to reach if you set
min-grade: A. - Engine v0.3 methodology — the static + LLM-assist layers and their calibration.