Skip to content

fix(security): Anthropic credentials are not fully scrubbed when a custom ANTHROPIC_BASE_URL is configured - #598

Open
failsafesecurity wants to merge 1 commit into
shareAI-lab:mainfrom
failsafesecurity:openclaw/fix-anthropic-credentials-are-not-fully-scru
Open

failsafesecurity wants to merge 1 commit into
shareAI-lab:mainfrom
failsafesecurity:openclaw/fix-anthropic-credentials-are-not-fully-scru

Conversation

@failsafesecurity

Copy link
Copy Markdown

Security fix: Anthropic credentials are not fully scrubbed when a custom ANTHROPIC_BASE_URL is configured

Severity: HIGH (CVSS 7.1)
CWE: CWE-522
File: s01_agent_loop/code.py

What this fixes

The defect is that Anthropic credentials are not fully scrubbed when a custom ANTHROPIC_BASE_URL is configured. The Anthropic SDK automatically reads both ANTHROPIC_API_KEY and ANTHROPIC_AUTH_TOKEN from the environment and attaches whichever is present to outbound requests, so removing only one credential — or removing none at all — leaves the other available to be sent to the configured base URL.

In the numbered chapters (s01_agent_loop/code.py:48-53), code that switches to a custom base URL pops only ANTHROPIC_AUTH_TOKEN and leaves ANTHROPIC_API_KEY intact, so the real Anthropic key still travels in the X-Api-Key header. The generated-agent templates in skills/agent-builder/scripts/init_agent.py (lines 34-39 and 94-99) and skills/agent-builder/references/minimal-agent.py:20 perform no credential scrub at all, so a scaffolded agent can send either the real Anthropic key or the Claude Code OAuth token to whatever base URL is set.

s01_agent_loop/code.py:48-53 (identical in s02-s15, s17 and agents/*) calls load_dotenv(override=True), then inside if os.getenv("ANTHROPIC_BASE_URL"): executes os.environ.pop("ANTHROPIC_AUTH_TOKEN", None) and constructs Anthropic(base_url=os.getenv("ANTHROPIC_BASE_URL")). .env.example:2-3 instructs users to set ANTHROPIC_API_KEY and documents switching providers via ANTHROPIC_BASE_URL, so a user following the documentation has a real key in the environment when they point the client at a third-party endpoint. The SDK resolves credentials from both ANTHROPIC_API_KEY (header X-Api-Key via _api_key_auth) and ANTHROPIC_AUTH_TOKEN (header Authorization: Bearer via _bearer_auth); auth_headers returns {**self._api_key_auth, **self._bearer_auth}, so both credentials are attached to requests to the configured base URL. In a capture against a server bound to that URL, the request to POST /v1/messages carried `x-api-key: [REDACTED] while the authorization header was absent, confirming the scrub removes only the OAuth token.

skills/agent-builder/scripts/init_agent.py:89-99 (level-0 template), :94-99 and :96-99 (level-1 template), and :34-39 (level-0 TEMPLATE) build the client after a bare load_dotenv() with client = Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY"), base_url=os.getenv("ANTHROPIC_BASE_URL")) and no conditional scrub. skills/agent-builder/references/minimal-agent.py:20 uses the same api_key=os.getenv("ANTHROPIC_API_KEY") pattern. Passing api_key= explicitly forces the SDK to attach X-Api-Key: [REDACTED] to every request regardless of base_url. When ANTHROPIC_API_KEYis unset,os.getenv returns None, the SDK's explicit-credential check (/app/.venv/lib/python3.12/site-packages/anthropic/_client.py:203-212) is False, and the SDK reads both ANTHROPIC_API_KEYandANTHROPIC_AUTH_TOKENfrom the environment;_bearer_auth(same file, lines 348-351) emitsAuthorization: Bearer on every request to the configured base URL. A capture onPOST /v1/messagesshowedAuthorization: Bearer [REDACTED] (also reproduced with dummy-oauth-token and sk-ant-oat01-REALTOKEN as dummy tokens). The chapters guard this explicitly (e.g. s01_agent_loop/code.py:50-51, and agents/s01_agent_loop.py:44-45), proving the authors treat the token as sensitive; the scaffold dropped that guard. Control: inserting the chapters' os.environ.pop("ANTHROPIC_AUTH_TOKEN", None) before client construction yields client.auth_token=None and no request. ANTHROPIC_BASE_URL is read from the environment or a project .env, so a cloned or shared project directory can control the endpoint that receives the credential.

Preconditions: Exploitation requires a real Anthropic credential (ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN) plus a non-default ANTHROPIC_BASE_URL pointing at a third-party endpoint, set in the environment or .env as the repository documentation instructs for alternative providers.

The change

Added os.environ.pop("ANTHROPIC_API_KEY", None) inside the existing if os.getenv("ANTHROPIC_BASE_URL"): block so that when a custom base URL is configured, both ANTHROPIC_AUTH_TOKEN and ANTHROPIC_API_KEY are scrubbed from the environment before the Anthropic client is constructed. This prevents the SDK from automatically attaching the real Anthropic API key to outbound requests sent to the custom base URL.

Impact if unpatched

A real Anthropic API key or Claude Code OAuth token is exfiltrated to whatever endpoint is configured via ANTHROPIC_BASE_URL, enabling credential theft, billing abuse, and unauthorized API use. Every agent created by the recommended scaffolder inherits the leak.


Prepared by OpenClaw BountyBot. Finding surfaced by Failsafe Nexus (Pandora) and the patch adversarially reviewed by GLM. Please review carefully — automated patches are a starting point, not a guarantee.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants