Skip to content

API key leaked in cleartext error output when header value is invalid (e.g. CRLF .env) #91

Description

@sgriffiths

Summary

When the OpenAI/Anthropic providers fail to build the HTTP request, the resulting ProviderError interpolates the underlying exception, whose message contains the full Authorization / x-api-key header value. The user's API key is printed in cleartext to the terminal (and therefore to CI logs, screen recordings, and pasted bug reports).

Reproduced on the Python runtime, v1.6.0.

Reproduction

Any API key carrying a character that http.client rejects in a header value will trigger it. The realistic path is a .env file with CRLF line endings (the default when the file is created or edited on Windows/WSL), which leaves a trailing \r on the value:

printf 'OPENAI_API_KEY=sk-proj-REDACTED\r\n' > .env
set -a; source .env; set +a
oa run --spec examples/skill-wrapper/spec.yaml --task summarise \
  --input '{"text":"hello"}'

Output:

╭─ ✗ Run error ────────────────────────────────────────────────────────────────╮
│  OpenAI request failed: Invalid header value b'Bearer                        │
│  sk-proj-<THE USER'S ENTIRE REAL API KEY IS PRINTED HERE>\r'                 │
╰──────────────────────────────────────────────────────────────────────────────╯

Note the trailing \r — that is the whole trigger. The key itself is valid.

Root cause

Two separate defects compound:

1. The key is never sanitised on read.

oas_cli/providers/openai_http.py:54

api_key: str | None = os.environ.get(api_key_env) if api_key_env else None

No .strip(), so a CRLF .env yields a key ending in \r, which is an illegal header value. Same at oas_cli/providers/anthropic_http.py:87.

2. The error path leaks the credential.

oas_cli/providers/openai_http.py:259-260

except Exception as exc:
    raise ProviderError(f"OpenAI request failed: {exc}") from exc

The broad except Exception catches http.client's ValueError: Invalid header value b'Bearer sk-...\r' — an exception whose message embeds the header value — and interpolates it straight into a user-facing error.

Same shape in oas_cli/providers/anthropic_http.py:159-160 and :239. Anthropic sends the key via x-api-key, so it leaks identically.

Impact

  • The API key is written to stdout in a bright, copy-pasteable error panel. The failure mode ("my key doesn't work") is exactly the one that prompts users to paste their terminal output into an issue or a chat — publishing a live credential.
  • CRLF .env files are common on Windows/WSL, so this is reachable by accident, not just by malformed input.
  • The key is valid at the point it leaks; only its framing is wrong.

Suggested fix

Defence in depth — both halves are worth doing:

  1. Sanitise on read (fixes the trigger): .strip() the key when pulled from the environment, in both providers. One line each, and it makes the CRLF .env case just work.
  2. Redact on error (fixes the leak class): never interpolate a raw exception into a provider error without scrubbing the credential — e.g. replace occurrences of the key with ***, or catch the header-construction failure specifically and raise a message that names the problem (API key contains an invalid character — check for trailing whitespace or CRLF line endings in your .env) without echoing the value.

(2) matters independently of (1): any future exception whose message happens to carry the header would leak the same way.

Scope

  • Python runtime: affected — both openai_http.py and anthropic_http.py.
  • npm runtime: not affected by this path. npm/src/providers/openai.ts and anthropic.ts use fetch and only interpolate res.status plus the response body into OAError — the header value never reaches the message. Worth adding the same key-sanitising .trim() there for parity, but there is no leak to fix.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions