|
| 1 | +# Security Policy |
| 2 | + |
| 3 | +`mcp-auth-proxy` sits on the authorization path between MCP clients |
| 4 | +and a private MCP server. Vulnerabilities here can let an attacker |
| 5 | +mint tokens, impersonate users, exfiltrate identity material, or |
| 6 | +deny service. We take reports seriously and want to make it as easy |
| 7 | +as possible to disclose responsibly. |
| 8 | + |
| 9 | +## Reporting a vulnerability |
| 10 | + |
| 11 | +**Please do not open public GitHub issues for security problems.** |
| 12 | + |
| 13 | +Report privately via **GitHub Security Advisory**: |
| 14 | +<https://github.com/babs/mcp-auth-proxy/security/advisories/new> |
| 15 | + |
| 16 | +This opens a confidential thread with the maintainers and gives us |
| 17 | +a place to coordinate the fix, CVE assignment, and the eventual |
| 18 | +public advisory. |
| 19 | + |
| 20 | +We acknowledge new reports within **3 business days**. If you do not |
| 21 | +hear back within that window, please post a brief, *non-sensitive* |
| 22 | +nudge on the advisory thread — GitHub notification settings |
| 23 | +occasionally lose the initial ping. |
| 24 | + |
| 25 | +### What to include |
| 26 | + |
| 27 | +A useful report typically contains: |
| 28 | + |
| 29 | +- Affected version or commit hash (`git rev-parse HEAD` from your |
| 30 | + local checkout, or the image digest if you deployed a release). |
| 31 | +- Reproduction steps or a minimal proof-of-concept. Curl commands |
| 32 | + are ideal; screenshots are acceptable. |
| 33 | +- The configuration that triggers the issue — env vars, IdP type, |
| 34 | + whether Redis is enabled, `PROD_MODE`, `RENDER_CONSENT_PAGE`, |
| 35 | + `PKCE_REQUIRED`, `TRUSTED_PROXY_CIDRS`, etc. |
| 36 | +- Expected vs. observed behavior, and the security impact you |
| 37 | + believe this enables (e.g. token theft, account takeover, SSRF, |
| 38 | + bypass of `ALLOWED_GROUPS`). |
| 39 | +- Any relevant log snippets (redact bearer tokens, authorization |
| 40 | + codes, refresh tokens, and `id_token` values before sending). |
| 41 | + |
| 42 | +If you are not sure whether something is a vulnerability, send the |
| 43 | +report anyway — we would rather triage a non-issue than miss a real |
| 44 | +one. |
| 45 | + |
| 46 | +## Supported versions |
| 47 | + |
| 48 | +Security fixes are issued against the latest released minor version |
| 49 | +on `master`. Older versions do not receive backports. |
| 50 | + |
| 51 | +| Version | Supported | |
| 52 | +|---------|-----------| |
| 53 | +| `v1.x` (latest minor) | ✅ | |
| 54 | +| `< v1.0` (pre-release) | ❌ | |
| 55 | + |
| 56 | +The container images at |
| 57 | +`ghcr.io/babs/mcp-auth-proxy` follow the same support window. When a |
| 58 | +security fix lands, we publish a new patch tag (`v1.x.y`) and update |
| 59 | +the `:v1` floating tag. |
| 60 | + |
| 61 | +## Coordinated disclosure timeline |
| 62 | + |
| 63 | +Our default disclosure model is a **90-day** window from the date we |
| 64 | +acknowledge the report: |
| 65 | + |
| 66 | +- **Day 0** — report received, acknowledgement sent. |
| 67 | +- **Day 0–7** — triage, severity scoring (CVSS v3.1), reproduction. |
| 68 | +- **Day 7–60** — fix developed in a private branch or security |
| 69 | + advisory fork; reviewed; tests added under `handlers/*_test.go`, |
| 70 | + `replay/*_test.go`, or a new e2e case. |
| 71 | +- **Day 60–90** — coordinated release: patch tag, image rebuild, |
| 72 | + public advisory, credit, optional CVE. |
| 73 | + |
| 74 | +We are willing to extend the window if a fix requires upstream |
| 75 | +changes (Go stdlib, IdP, Redis client) or if you ask us to hold for |
| 76 | +your own coordinated rollout. |
| 77 | + |
| 78 | +If the issue is already being actively exploited in the wild, we |
| 79 | +will accelerate the timeline and may publish the fix and advisory |
| 80 | +together. |
| 81 | + |
| 82 | +## Scope |
| 83 | + |
| 84 | +In scope — anything that can be reached by traffic going through the |
| 85 | +proxy: |
| 86 | + |
| 87 | +- OAuth 2.1 endpoints: `/authorize`, `/token`, `/register`, |
| 88 | + `/consent`, `/callback`. |
| 89 | +- Discovery: `/.well-known/oauth-authorization-server`, |
| 90 | + `/.well-known/oauth-protected-resource[/<mount>]`. |
| 91 | +- The MCP pass-through path: `/mcp` (and any configured mount). |
| 92 | +- Token sealing / verification in the `token/` package. |
| 93 | +- Replay / single-use enforcement in the `replay/` package. |
| 94 | +- Authorization middleware in `middleware/auth.go`. |
| 95 | +- Configuration validation in `config/config.go`, especially flags |
| 96 | + that can weaken a control when set (`PROD_MODE`, |
| 97 | + `TRUSTED_PROXY_CIDRS`, `PKCE_REQUIRED`, `RENDER_CONSENT_PAGE`, |
| 98 | + `REDIS_REQUIRED`). |
| 99 | +- Container image hygiene (`Dockerfile`), supply chain (`go.sum`). |
| 100 | +- Metrics endpoint information disclosure (`/metrics`). |
| 101 | + |
| 102 | +The [threat model](docs/threat-model.md) lists the specific threats |
| 103 | +we already mitigate and which code, tests, and runbooks back them. |
| 104 | + |
| 105 | +Out of scope — documented, accepted residual risk (see the |
| 106 | +"Out of scope" section in `docs/threat-model.md` for the full |
| 107 | +discussion): |
| 108 | + |
| 109 | +- **IdP compromise.** If the configured OIDC IdP is compromised, the |
| 110 | + proxy trusts what the IdP says. Bug reports about Keycloak, Entra, |
| 111 | + Auth0, Okta, etc. belong with those vendors. |
| 112 | +- **Backend MCP server vulnerabilities.** The proxy forwards |
| 113 | + authenticated traffic; it does not sanitize JSON-RPC payloads to |
| 114 | + the upstream MCP server beyond what is required for transport. |
| 115 | +- **Browser-engine bugs.** The consent page is JS-free and |
| 116 | + CSP-locked (`default-src 'none'`); we do not separately defend |
| 117 | + against escapes of the browser's contextual HTML escaping. |
| 118 | +- **Denial of service via raw network capacity.** Per-IP rate limits |
| 119 | + protect specific endpoints; absorbing volumetric L3/L4 floods is |
| 120 | + the deployment operator's responsibility (CDN, WAF, k8s ingress). |
| 121 | +- **Misconfiguration where the operator explicitly opted out** of a |
| 122 | + control (e.g. `RENDER_CONSENT_PAGE=false`, `PKCE_REQUIRED=false`, |
| 123 | + `REDIS_REQUIRED=false`). These flags exist for compatibility; the |
| 124 | + associated weakening is intentional. We will still accept reports |
| 125 | + about additional weaknesses they expose beyond the documented |
| 126 | + trade-off. |
| 127 | +- **Social engineering and physical attacks** against operators, |
| 128 | + maintainers, or end users. |
| 129 | + |
| 130 | +If you are unsure whether a finding is in scope, send it anyway — we |
| 131 | +will tell you, and we will treat the conversation as confidential. |
| 132 | + |
| 133 | +## Safe harbor |
| 134 | + |
| 135 | +We will not pursue or support legal action against researchers who: |
| 136 | + |
| 137 | +- Make a good-faith effort to comply with this policy. |
| 138 | +- Avoid privacy violations, destruction of data, or interruption of |
| 139 | + service for users other than themselves. |
| 140 | +- Do not exploit a discovered vulnerability beyond the minimum |
| 141 | + necessary to demonstrate impact. |
| 142 | +- Give us reasonable time to fix the issue before public disclosure. |
| 143 | +- Do not extort, threaten, or attempt to monetize the finding |
| 144 | + outside of the recognition described below. |
| 145 | + |
| 146 | +Testing against your own deployment of the proxy is always allowed. |
| 147 | +Testing against deployments you do not control requires the |
| 148 | +operator's permission — the demo stack at `docker-compose.yml` and |
| 149 | +the Keycloak fixtures are explicitly available for this purpose. |
| 150 | + |
| 151 | +## Recognition |
| 152 | + |
| 153 | +We are happy to credit reporters in: |
| 154 | + |
| 155 | +- The public GitHub Security Advisory. |
| 156 | +- The CHANGELOG entry for the fix release. |
| 157 | +- An optional "Security acknowledgements" section in the README, |
| 158 | + added when the first credited report ships. |
| 159 | + |
| 160 | +If you prefer to stay anonymous, we will honor that. |
| 161 | + |
| 162 | +There is no monetary bounty program at this time. |
| 163 | + |
| 164 | +## Cryptographic verification (optional) |
| 165 | + |
| 166 | +The container images at `ghcr.io/babs/mcp-auth-proxy` are built by |
| 167 | +GitHub Actions; the build is reproducible from the tagged commit |
| 168 | +(`docs/release-checklist.md` documents the procedure). If you need |
| 169 | +to verify a binary against the source, build from the same tag with |
| 170 | +the same Go toolchain and compare hashes. |
| 171 | + |
| 172 | +We do not currently sign releases with a long-lived PGP key. The |
| 173 | +GitHub Security Advisory channel is already an encrypted, |
| 174 | +authenticated transport — please use it for sensitive material |
| 175 | +rather than email. |
| 176 | + |
| 177 | +## Companion documents |
| 178 | + |
| 179 | +- [`docs/threat-model.md`](docs/threat-model.md) — STRIDE coverage |
| 180 | + matrix mapping threats to code, tests, and runbooks. The |
| 181 | + authoritative list of what we already defend against. |
| 182 | +- [`docs/conformance.md`](docs/conformance.md) — RFCs the proxy |
| 183 | + claims to implement, with code and test evidence. Useful when |
| 184 | + scoping whether a report is a spec deviation or a vulnerability. |
| 185 | +- [`docs/configuration.md`](docs/configuration.md) — every |
| 186 | + configuration flag, its default, and the security trade-off if |
| 187 | + changed. |
| 188 | +- [`docs/runbooks/`](docs/runbooks/) — operational responses to |
| 189 | + security-relevant incidents (Redis outage, IdP outage, key |
| 190 | + rotation, consent-denial spikes, bulk revocation). |
0 commit comments