Topic: mcp server security controls

MCP server security controls — 15 controls mapped to OWASP, NIST, and SOC 2

A compliance-ready control framework for enterprise MCP server adoption: fifteen discrete technical and procedural controls, each mapped to OWASP API Security or LLM Apps Top 10, NIST SP 800-53, and SOC 2 Trust Service Criteria, with implementation guidance and a rating for what's automatically detectable versus what requires manual review.

TL;DR

Enterprise adoption of MCP servers requires a control framework — not just a "looks safe" eyeball review. The fifteen controls below cover the full threat surface of MCP servers: outbound request safety, credential isolation, command parameterization, prompt-injection resistance, least-privilege scope, dependency hygiene, disclosure process, CI gate enforcement, and re-audit cadence. Ten of the fifteen are automatically detectable via a scanner like SkillAudit; five require procedural controls that the scanner evidence supports. Each control is mapped to at least one recognized compliance framework so you can cite it in a SOC 2 audit or an internal security review.

Why a control framework, not just a best-practices list

A "best practices" list is good for authors. A control framework is what security engineers, compliance officers, and team leads need when they're accountable for the outcome. A control has three parts that a best-practice tip typically omits: a testable definition of what "implemented" means, a mapping to a recognized framework that justifies the control's existence to an auditor, and a stated detection method that produces auditable evidence. Without those three parts, a "use an allowlist for outbound requests" recommendation is advice. With them, it's Control MCP-05: URL Allowlist Enforcement — tested by static AST scan for fetch() call sites, mapped to OWASP API5 (Broken Function-Level Authorization), producing evidence in the form of a SkillAudit grade report for every server in the approved inventory.

The fifteen controls below were derived from SkillAudit's review of 101 of the most-installed MCP servers, the OWASP API Security 2023 and LLM Apps 2025 Top 10, NIST SP 800-53 Rev 5, and SOC 2 Trust Service Criteria. Each control addresses a finding class that appears in the corpus; none is hypothetical.

The control framework

Control ID Control name Frameworks Detection SkillAudit axis
MCP-01Outbound URL allowlistOWASP API5, NIST SC-7Automated (static)Security
MCP-02DNS rebinding protectionOWASP API5, NIST SC-7Automated (static)Security
MCP-03Credential isolation (no log echoing)OWASP API8, NIST IA-5, SOC 2 CC6Automated (static)Credentials
MCP-04Typed config objects (no raw env dump)OWASP API8, NIST IA-5Automated (static)Credentials
MCP-05Parameterized command executionOWASP API3, NIST SI-10Automated (static)Security
MCP-06Prompt injection resistanceOWASP LLM01, NIST SI-10Automated (LLM probe)Security
MCP-07Least-privilege scope declarationOWASP API1, NIST AC-6, SOC 2 CC6Automated (static)Permissions
MCP-08Scope-vs-handler drift detectionOWASP API1, NIST AC-6Automated (static)Permissions
MCP-09Dependency advisory monitoringOWASP API8, NIST SI-2, SOC 2 CC7Automated (SCA)Maintenance
MCP-10Dependency pinning and lock filesNIST SA-12, SOC 2 CC8Automated (static)Maintenance
MCP-11Active maintainer and commit recencyNIST SA-12, SOC 2 CC9Automated (metadata)Maintenance
MCP-12Vulnerability disclosure policy (SECURITY.md)NIST IR-6, SOC 2 CC7Automated (metadata)Documentation
MCP-13Pre-publish security audit (min Grade B)NIST SA-11, SOC 2 CC8Procedural (audit record)All axes
MCP-14CI/CD scan gate on every PRNIST SA-11, SOC 2 CC8Procedural (CI log)All axes
MCP-15Quarterly re-audit and grade regression alertNIST CA-7, SOC 2 CC7Procedural (audit history)All axes

Ten controls (MCP-01 through MCP-12) are automatically detectable by static analysis and metadata checks — meaning a scanner run produces the evidence record. Five controls (MCP-13 through MCP-15) are procedural — meaning the scanner audit report is the evidence record, but the control is implemented by the team's process of running the scanner, not by the server's code.

Control-by-control implementation guidance

MCP-01 — Outbound URL allowlist

What "implemented" means: Every fetch(), axios.get(), got(), and equivalent outbound HTTP call site uses a validated URL that has been checked against an explicit allowlist of permitted domains or prefixes before the request is made. The allowlist is a named constant or config value, not inline logic. Common failure pattern (corpus, 50% of SSRF-susceptible servers): a url or endpoint parameter taken directly from a tool argument is passed into a fetch call without validation. Verification: SkillAudit static pass flags every call site where a user-controlled variable reaches a fetch call without an intervening validation check; result is file-and-line cited in the audit report.

MCP-02 — DNS rebinding protection

What "implemented" means: The URL allowlist is enforced after DNS resolution, not before — i.e., the server validates that the resolved IP address is not in RFC 1918 private ranges (10.x.x.x, 172.16-31.x.x, 192.168.x.x), the 169.254.x.x IMDS range, or loopback (127.x.x.x / ::1) after each DNS lookup, not just at URL-parse time. Why post-resolution matters: an attacker who controls a DNS server can return a benign IP at parse time and a private-range IP at fetch time, bypassing a pre-resolution blocklist entirely. An allowlist that explicitly names permitted domains is not vulnerable to this if the server validates the resolved IP; a blocklist that blacklists private ranges at URL parse time is. Verification: static check for IP validation logic after DNS resolution calls.

MCP-03 — Credential isolation (no log echoing)

What "implemented" means: No log statement, error serializer, or debug output pathway writes any environment variable, API key, token, connection string, or bearer credential to stdout, stderr, or a log sink. Typed config objects strip secret fields before any logging. Common failure pattern (corpus, 38%): console.log(process.env) in a startup log, or an error serializer that includes the full config struct including connection strings. Verification: SkillAudit static pass flags process.env in log calls and config objects passed to serializers without redaction.

MCP-04 — Typed config objects (no raw env dump)

What "implemented" means: The server reads environment variables once at module init into a typed config object that exposes only the fields required by tool handlers. process.env.SECRET_KEY is consumed once; the config object exposes only config.apiKey with a get accessor that returns a masked representation for any logging purpose. Tool handler code never calls process.env directly. Why this matters: if a tool handler has a prompt-injection vulnerability that causes it to log its arguments, a handler that calls process.env.DATABASE_URL directly leaks that credential; a handler that reads from a typed config object with a non-loggable accessor does not.

MCP-05 — Parameterized command execution

What "implemented" means: Every exec(), spawn(), execSync(), spawnSync(), or subprocess.run() call passes user-controlled arguments as an argument array to the spawn call, not as interpolated strings to a shell. spawn('git', ['clone', userUrl]) is safe; exec(`git clone ${userUrl}`) is not — a user who controls userUrl can inject https://example.com; rm -rf /. Corpus prevalence: 43% of servers that execute shell commands had at least one unsafe interpolation path. Verification: static check for template literal or string concatenation inside exec/spawn call sites.

MCP-06 — Prompt injection resistance

What "implemented" means: When the server's tool responses include content fetched from a third-party service or user-supplied data, the agent processing those responses does not execute embedded adversarial instructions. This is partially a property of the server (how it structures tool output), partly a property of the surrounding agent's system prompt hardening. Why it requires active probing: static analysis cannot determine whether a given tool output structure is injection-susceptible without running a model against it. SkillAudit's 14-probe bank runs the server in a sandboxed environment and submits adversarial tool responses designed to trigger a range of injection patterns. Note on stability: this score can regress when the underlying model retrains, even without code changes — see MCP-15 re-audit control.

MCP-07 — Least-privilege scope declaration

What "implemented" means: The server's capability manifest (SKILL.md for Claude Skills, JSON-RPC tool definitions for MCP servers, OAuth scope list for OAuth-using servers) requests only the capabilities and permissions that the documented tool set requires. Common failure pattern (corpus, 25% of OAuth-using servers): a server with only read-only documented tools requests write or admin OAuth scopes. Verification: SkillAudit compares declared capabilities against the handler implementations detected in the static pass; a mismatch flags as scope-vs-handler drift.

MCP-08 — Scope-vs-handler drift detection

What "implemented" means: The declared capability manifest accurately reflects what the handlers actually do — if the manifest declares filesystem:read, the handlers must not call fs.writeFile. Drift in either direction is a control failure: over-declaring (claiming write when only read is needed) creates unnecessary exposure; under-declaring (claiming read but actually writing) means the operator cannot enforce the principal-of-least-privilege assumption. Implementation note: this control pairs with MCP-07 but is distinct — MCP-07 catches over-broad requests; MCP-08 catches mismatches between what's declared and what's implemented.

MCP-09 — Dependency advisory monitoring

What "implemented" means: The server's direct and transitive dependency tree is monitored against public advisory feeds (GitHub Advisory Database, OSV, NVD) and the operator receives notification within 48 hours of a new CVE affecting a dependency in the server's production dependency tree. Implementation options: Dependabot on the server's GitHub repo, npm audit / pip-audit in CI, OSV-Scanner as a scheduled job, or SkillAudit's dependency-advisory signal in the audit report. Evidence: a scan report with no open advisories above medium severity, or a documented and time-bounded exception for known advisories.

MCP-10 — Dependency pinning and lock files

What "implemented" means: The server's package manifest pins exact versions of direct dependencies (using package-lock.json, yarn.lock, poetry.lock, or pipfile.lock), and the lock file is committed to the repository. Pointing at a floating range (^1.2.0) or a GitHub branch without a commit SHA introduces non-reproducible builds where a dependency update can introduce a vulnerability without any change to the server's own code.

MCP-11 — Active maintainer and commit recency

What "implemented" means: The server has a named maintainer who has made at least one commit in the last 6 months, responds to issues (median issue response time under 30 days), and has not explicitly archived the repository. Why this matters for a control: an unmaintained server will not receive security patches for future vulnerabilities; approving it for production use without a maintenance exit plan creates a control gap that grows over time.

MCP-12 — Vulnerability disclosure policy (SECURITY.md)

What "implemented" means: The server's repository contains a SECURITY.md (or equivalent) that documents: a contact address for reporting vulnerabilities, a response time commitment (e.g., "we will acknowledge within 48 hours"), and a disclosure timeline (e.g., "we will publish an advisory within 90 days of a confirmed finding"). Why this is a control and not just a nice-to-have: without a disclosure policy, a researcher who finds a vulnerability in a server your team uses has no established channel — they may publish immediately, leaving you with no advance notice to patch or remove the server.

MCP-13 — Pre-publish security audit (min Grade B)

What "implemented" means: Every MCP server or Claude Skill published to a public directory or approved for team use has a SkillAudit report on record with a grade of B or above before publication or before the first team deployment. The report is linked from the server's README or from the team's internal MCP inventory. Evidence: the public audit URL (e.g., https://skillaudit.dev/audits/[server-name]) in the server's documentation or the team's approved-server list.

MCP-14 — CI/CD scan gate on every PR

What "implemented" means: A CI check runs SkillAudit (or equivalent) against the server's codebase on every pull request that modifies the server's code; a grade below the configured threshold fails the check and blocks the merge. The threshold is configured as a policy (e.g., "minimum Grade B on security and credentials axes; Grade C acceptable on compatibility axis with documented exception"). Implementation: SkillAudit's GitHub Action integration provides a skillaudit/scan-action that runs in CI and posts the grade as a check result.

MCP-15 — Quarterly re-audit and grade regression alert

What "implemented" means: Every server in the team's approved inventory is re-audited at least quarterly, and the operator has subscribed to grade-change notifications so that a regression from B to C or from A to B triggers an alert and review. Why prompt-injection makes this control non-optional: a server's prompt-injection susceptibility score can change without a code change when the underlying Claude model retrains — a server that was A on prompt-injection in Q1 may score B in Q2 on the same code, because the model's behavioral guardrails have changed. Quarterly re-audits catch this class of regression that no code-review process would detect.

Evidence map for SOC 2 Type II and ISO 27001

If you're writing a SOC 2 Type II or ISO 27001 control statement for MCP server security, the evidence you need for each control class is:

Where this page sits in the cluster

This page covers the control framework layer — discrete controls for teams that need formal documentation. Sibling pages cover adjacent questions:

How SkillAudit covers these controls

SkillAudit provides automated detection for ten of the fifteen controls in this framework. A full audit (10–90 seconds per server) produces a graded report with per-axis sub-scores, file-and-line citations for every finding, and remediation hints. The Team plan adds: a JSON policy export that maps minimum-grade thresholds to the controls above; a GitHub Action (skillaudit/scan-action) that implements MCP-14 as a CI gate; email alerts for grade regressions that implement MCP-15; and an audit history log for SOC 2 / ISO 27001 evidence. For the five procedural controls (MCP-13 through MCP-15), the audit report and audit history log are the evidence records that satisfy the control — the process of running the scanner is the control implementation.

Get early access

Related questions

Can I use this framework as-is for a SOC 2 audit, or does it need adaptation?

The framework above provides the control IDs, framework mappings, and evidence requirements that map to SOC 2 Trust Service Criteria. In practice, your auditor will ask you to translate these into your organization's control language and show evidence from your specific tool set. The SkillAudit audit report and history log are acceptable as evidence for the automated controls; the procedural controls need your internal process documentation (e.g., "our policy is: no MCP server reaches production without a SkillAudit report on record showing Grade B or above — see audit log export"). The framework gives you the structure; your internal documentation connects it to your specific environment.

Which controls are highest priority if we can only implement five first?

MCP-01 (URL allowlist), MCP-03 (credential isolation), MCP-05 (parameterized commands), MCP-07 (least-privilege scope), and MCP-13 (pre-publish audit) give you coverage of the four finding classes that produce the most severe confirmed-exploitable findings in the corpus — SSRF (50%), credential exposure (38%), command injection (43%), and scope drift (25%). A scan-before-install policy (MCP-13) ensures you catch all five of the automated controls simultaneously for every new server, making it the highest-leverage procedural control. Add MCP-15 (re-audit cadence) as a sixth if prompt-injection regression risk concerns you.

How does this control framework relate to the NIST AI RMF?

NIST AI RMF (AI Risk Management Framework) is a top-level governance structure for AI systems, not a technical control catalog. The controls in this framework are the technical implementation layer beneath NIST AI RMF's "Govern," "Map," "Measure," and "Manage" functions. MCP-13 through MCP-15 implement the "Measure" function (regular testing and re-evaluation); MCP-01 through MCP-12 implement the "Manage" function (applied controls that reduce identified risks); the inventory and evidence processes implement the "Map" and "Govern" functions. NIST AI RMF doesn't specify technical controls — this framework does.

What about MCP servers built internally — do these controls still apply?

Yes, and arguably with higher priority than for community servers. Internal MCP servers often have access to a broader credential footprint (internal APIs, databases, SSO tokens, CI/CD secrets) and don't benefit from the public scrutiny that catches obvious bugs in open-source packages. All fifteen controls apply to internal servers; MCP-12 (SECURITY.md) is adapted to require an internal contact rather than a public disclosure email; MCP-11 (active maintainer) becomes "does this server have a named owner on-call" rather than public commit recency.

Is Grade B the right minimum threshold, or should we require Grade A?

Grade B means no findings with confirmed-exploitable severity on the security and credential axes, with at most one medium-severity finding on a lower-weight axis. Grade A means no findings above low-severity on any axis. For most enterprise use cases, Grade B is the right threshold for general approval — it clears the confirmed-exploitable findings while allowing servers that have a minor documentation gap or a medium-severity best-practice deviation. Grade A is appropriate for servers with access to production credentials or broad filesystem access. The per-axis threshold approach (Block D/F on security/credentials regardless of overall grade; allow C with documented exception on maintenance/compatibility) is more nuanced than a single letter cutoff and is what SkillAudit's Team plan policy export supports.

Further reading