Security
Security posture

OWASP MCP Top 10 coverage

Where MCP Hangar maps against the OWASP MCP Top 10 (2025) — what it enforces, which component does it, and the three categories that are out of scope by design.

Updated August 9, 2026

OWASP MCP Top 10 coverage

How MCP Hangar maps against the OWASP MCP Top 10 (2025), category by category, against the shipped release (2.5.0).

The page is deliberately unflattering in places. Hangar is a policy enforcement plane: it enforces deterministic policy on the MCP call path and produces an attributable audit trail. It does not read prompt or tool-argument semantics and will not pretend to — so three of the ten are out of scope by design and four are partial, each with the limit named rather than implied. Two rows carried “needs owner review” until the operator’s source settled them; what that produced is a narrower claim than either internal record made, and it is below the map.

The engineering record this page summarises is the docs’ own OWASP MCP Top 10 coverage page, maintained beside the code. Where the two disagree, the docs page wins and this one is the bug.

The map

#CategoryPostureWhat enforces itDocs
MCP01Token Mismanagement & Secret ExposurePartialFront-door auth: RFC 8707 audience binding, RFC 9728 protected-resource metadata, JWT/OIDC + JWKS, API keys stored only as SHA-256 hashes with optional expiry. Plus a named secret-pattern scan on tool arguments in the L7 policy languageAuthentication · Egress policy
MCP02Privilege Escalation via Scope CreepPartialTool-access authz (gate a) and the approval gate (gate f): per-tenant scoping, tool allow/deny lists, a human checkpoint on privileged callsEgress policy · Approval adapters
MCP03Tool PoisoningCoveredTool-schema digest-pin verify (gate c, opt-in) plus the tool-withdrawal check (gate b) — a schema that drifts after approval fails the call closed, or is recorded in advisory modeDigest pinning · ADR-004
MCP04Software Supply Chain & Dependency TamperingPartialHangar’s own artifacts are cosign-signed and SBOM’d, and chart digests are pinned. For MCP-server images the operator does check digest pinning at admission — but the policy defaults to warn, which admits the pod and records a warning. block is opt-in at operator startup, and a per-server annotation waives itADR-013 · docs coverage record
MCP05Command Injection & ExecutionOut of scope by design
MCP06Intent Flow SubversionOut of scope by design
MCP07Insufficient Authentication & AuthorizationCoveredThe auth middleware binds the authenticated caller and tenant before any gate runs; every governed route passes one authz chokepoint; identity is propagated end to end and the path fails closedAuthentication · Front door
MCP08Lack of Audit and TelemetryCoveredDomain events and OTel spans at every governed step; each allowed call carries caller identity into its audit record; SIEM export in CEF, LEEF 2.0, RFC 5424 syslog and JSON-linesCompliance · Observability
MCP09Shadow MCP ServersPartialRegistry of MCPServer resources, admission validation, per-server egress policy, and — in namespaces labelled mcp-hangar.io/enforce-egress=true — a webhook that rejects a pod claiming to be a provider for a server that is not registered. It keys on the pod’s own mcp-hangar.io/provider label, so it catches misconfiguration, not concealmentADR-013 · Egress policy
MCP10Context Injection & Over-SharingOut of scope by design

Gate letters (ai) are the ordered enforcement gates a call meets; see the request path.

Covered

MCP03 — Tool Poisoning. A poisoned tool changes its schema or description after it was approved — the “rug pull”. Hangar pins tool-schema digests per tenant and refuses calls whose digest no longer matches the pinned value; in advisory mode it records the drift instead. The same pin is re-verified before an async task’s result is handed back, so a rug-pull mid-flight fails closed too. This is the direct answer to MCP03.

One qualifier: pinning is opt-in. Where a pin exists, mismatch enforcement defaults to blocking — but nothing is pinned until someone pins it, so a default install has the gate wired and no pins to check against.

MCP07 — Insufficient Authentication & Authorization. This is the core of the product. Tokens are audience-bound (RFC 8707) so a token minted for one resource cannot be replayed against another; multi-issuer trust follows RFC 9728. JWT/OIDC with JWKS validation and per-tenant scoping decide who may call what, the approval gate adds a human decision point for high-risk calls, and caller identity is propagated end to end. Enforcement is route-driven and fails closed.

MCP08 — Lack of Audit and Telemetry. Every allowed tool call carries caller identity into its audit record, exported to SIEM in CEF, LEEF 2.0, RFC 5424 syslog and JSON-lines. OTLP is a separate trace and audit-span path, not a SIEM format. Prometheus metrics and Grafana dashboards cover the operational side.

Two qualifiers, because “covered” should survive an auditor. The event-sourced audit log only began actually persisting in core 2.4.0; on earlier releases the store was empty and nothing was backfilled, so an append-only-trail claim about a 2.3.x deployment is not supportable. And append-only is enforced at the application layer — pairing it with database-level immutability is the deployer’s job, not something Hangar can assert for you.

Partial

MCP01 — Token Mismanagement & Secret Exposure. Audience binding and protected-resource metadata address token reuse; API-key hygiene (raw keys are never persisted, only SHA-256 hashes, with optional expiry) reduces secret exposure. The L7 policy language can also refuse a call whose arguments match a named secret-pattern group — AWS keys, JWTs, PEM blocks, provider tokens.

Why this is Partial and not Covered, in two sentences. The pattern scan is a deterministic tripwire and the docs say so plainly: it is defeated by any encoding — base64, URL-encoding, or splitting a secret across arguments — so it catches accidents, not an adversary. And credential brokering for upstreams, so an MCP server never sees a raw upstream token at all, is not shipped; it is noted as future work in the MCPEgressPolicy epic.

MCP02 — Privilege Escalation via Scope Creep. Tool allow/deny lists and per-tenant scoping bound what a caller can reach, and the approval gate catches privileged calls. There is no automatic detection of gradual scope expansion — that would take behavioural baselining, which this product deliberately does not do. Enforcement here is deterministic: a policy either permits a call or it does not.

Where the two partial rows stop

MCP04 and MCP09 turned on the same question — whether the operator’s enforcement phases actually shipped — and two internal records answered it differently. ADR-013 called phases 1–3 shipped in operator v0.13.0; the docs coverage record still listed the same work as tracked in open issues.

The operator’s source settles it: the mechanisms shipped, and they are narrower than “an unregistered server gets no traffic”. Both records were partly right, and neither described what the code does.

MCP09 — the admission gate keys on self-declaration. A pod is rejected when it carries mcp-hangar.io/provider=<name> and no MCPServer of that name exists in its namespace. A pod without that label is explicitly admitted as “not an MCP-server pod”. So the gate catches a provider pointed at a server nobody registered — a misconfiguration — and not a workload that simply never announces itself, which is what “shadow” means. What constrains that one is the default-deny egress at L3/L4, not admission. The webhook is also scoped by namespace selector to mcp-hangar.io/enforce-egress=true, so it is off wherever that label is absent.

MCP04 — the image check defaults to advisory. Digest pinning is evaluated at admission, but the policy has three settings and ships as warn: an unpinned image is admitted with a warning recorded. block has to be turned on at operator startup, and an annotation can waive it per server. So “images are digest-pinned” is available, not the default.

Neither limit is a defect — an opt-in, fail-closed webhook that does not gate namespaces which never opted in is a deliberate blast-radius choice. They are recorded here because the distance between “we have a registry” and “an unregistered server gets no traffic” is exactly what OWASP’s MCP09 prevention control asks about, and a coverage page that blurred it would be worth less than one that says where the line is.

Out of scope by design

MCP05 (Command Injection & Execution), MCP06 (Intent Flow Subversion) and MCP10 (Context Injection & Over-Sharing) are prompt, intent and context semantic risks. Detecting them means interpreting the meaning of prompts, arguments and responses.

An enforcement plane enforces policy; it does not guess intent. Hangar decides who may call what and records what happened. Adding a probabilistic classifier would contradict that: it would turn a binary outcome into a score, and a score into a false-positive triage queue. There is no anomaly detection here and none is planned. These categories belong to a different layer — prompt-firewall and semantic-analysis tools — and Hangar does not claim them.

Two things do reduce blast radius without touching semantics, and they are worth naming precisely because they are not coverage: per-tenant scoping limits what a subverted call can reach, and the audit trail makes the attempt attributable afterwards. Neither detects the attack.

Version qualifiers

Coverage is a property of a release, not of a project. Where it matters:

  • The rows above are written against core 2.5.0.
  • The L7 MCPEgressPolicy engine ships in core 1.6.0, but end-to-end L7 egress needs the operator-side controller too: core 1.6.0+ and operator v0.14.0+. With only one half installed, a policy is written but not delivered — and the operator has to be run pointed at Hangar, or only the L3/L4 backstop applies and the L7 rules never reach the data plane.
  • Tool-schema digest pinning is opt-in. It is not on in a default install.
  • The approval gate is a real gate from core 2.1.0. On earlier builds the configuration key that was supposed to hold a tool for a human parsed as no policy at all, and those calls ran ungated.
  • The append-only audit trail is only actually persisted from core 2.4.0.

MIT-licensed and self-hosted throughout — there is no hosted tier, so nothing here depends on a service we run.