# Off Guard: Breaking LiteLLM from authentication bypass to cloud compromise

LiteLLM's MCP auth handler accepts any garbage Bearer token, and custom code guardrails exec unsandboxed Python, yielding root RCE on default-key deployments.

- Payout: Real-world hack
- Severity: critical
- Program: LiteLLM
- Credited: Wiz Research
- Disclosed: 2026-09-09
- CVE: CVE-2026-59822, CVE-2026-59821
- Write-up quality: 8/10
- Original: https://www.wiz.io/blog/off-guard-breaking-litellm-from-authentication-bypass-to-cloud-compromise
- Library page: https://bugbountylibrary.com/r/blog%3Awiz-io-blog-off-guard-breaking-litellm-from-authentication-bypass-to-cloud-compromise
- Tags: domain:ai domain:cloud impact:internal-access impact:rce kind:incident platform:threat-intel sev:critical tech:aws technique:chained technique:source-review vuln:auth-bypass vuln:rce vuln:ssrf

**Target:** Open-source LLM gateway (MCP endpoint, guardrails, pass-through proxy)
**Root cause:** MCP auth handler catches 401/403 from key validation and returns an empty UserAPIKeyAuth instead of rejecting; guardrail registration endpoint exec()s admin-supplied Python without stripping __builtins__ or applying the forbidden-patterns check used by the test endpoint.
**Steps:**
1. Send `POST /mcp/` with `Authorization: Bearer a` and a JSON-RPC `initialize` body; server returns 200 with `mcp-session-id`, establishing an authenticated MCP session.
2. Use the MCP session to invoke connected tools (DB queries, GitHub, filesystem) with arbitrary arguments.
3. For RCE: `POST /guardrails` with `Authorization: Bearer <master_key>` and `custom_code` containing `import os` and `os.popen('id')`; code runs at registration time.
4. Trigger any chat completion so the guardrail's `apply_guardrail` returns `_cmd` as the block reason, leaking command output.
5. For cloud creds: `POST /config/pass_through_endpoint` with `target: http://169.254.169.254/latest/` and `include_subpath: true`, then `GET /meta/meta-data/iam/security-credentials/<role>` to retrieve IAM keys.
6. Defeat IMDSv2 by sending `x-pass-X-aws-ec2-metadata-token-ttl-seconds: 21600`, which is forwarded with the `x-pass-` prefix stripped.
**How found:** Used Claude Code to read LiteLLM source, enumerating features that take user input into execution contexts: guardrails, pass-through endpoints, model config, hooks, MCP. Spotted the MCP handler's 401/403 catch returning an empty auth object, and that the guardrail registration path lacked the sandbox applied by the test endpoint.
**Exploitation:** Attacker with a default master key (sk-1234) or on a no-auth instance registers a malicious custom_code guardrail, achieving root code execution in the container; alternatively configures a pass-through endpoint to the EC2 metadata service to steal IAM credentials, then pivots to Bedrock/Vertex and other cloud resources.
**Escalation:** Chains default/missing master key (9.6% of 3,074 scanned instances) with the guardrail RCE to make it effectively pre-auth; pass-through endpoint reaches IMDS to escalate from container to cloud IAM role.
**Insight:** When a feature has a 'test' path and a 'register/save' path, diff their security controls: here the test endpoint stripped __builtins__ and ran a forbidden-patterns check, while registration did neither. Also check whether auth fallback branches swallow 401/403 and return an empty-but-truthy auth object.
