Repository navigation
fix(auth): treat a blank api_key as unset so the OPENROUTER_API_KEY fallback applies - #1117
Open
LukasParke wants to merge 4 commits into
Open
LukasParke wants to merge 4 commits into
LukasParke wants to merge 4 commits into
Conversation
…ack applies
The documented `api_key=os.getenv("OPENROUTER_API_KEY", "")` pattern yields
Security(api_key="") when the env var is unset, which short-circuits
get_security_from_env and produces an empty `Bearer ` header. Add an
sdk_init hook (user-owned, survives regeneration) that normalizes a blank
api_key to None, and fix the hand-maintained docs/overview.mdx snippet.
Fixes #591
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Automations to automatically generate PRs for you. |
|
Preview deployment for your docs. Learn more about Mintlify Previews.
|
The sdk_init hook clears a blank api_key so the OPENROUTER_API_KEY fallback applies, but the generated env fallback reads the variable per request without stripping it, so a whitespace-only env value (or a callable api_key returning blank) still produced a "Bearer " header. Register the same hook as a BeforeRequest hook that removes an Authorization header whose bearer token is blank, keeping "blank credential = absent" end to end without touching generated code. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…blank A callable api_key is resolved per request, after get_security_from_env has already skipped the environment, so a blank result left the request unauthenticated even when OPENROUTER_API_KEY was set. Replace a blank bearer token with the environment key when one is available, and drop the header only when it is not. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…lback The generated security code leaves an already-prefixed bearer token as is; match that when substituting the environment key so a value of "Bearer <token>" is not sent as "Bearer Bearer <token>". Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #591
Problem
OpenRouter(api_key=...)wraps any non-Nonevalue inSecurity(api_key=...)(sdk.py), andget_security_from_env(utils/security.py) returns early whenever security is set. So the documented patternapi_key=os.getenv("OPENROUTER_API_KEY", "")passes""when the variable is unset, the env fallback is skipped, and the client builds an emptyBearerheader (httpx rejects it withLocalProtocolError: Illegal header value b'Bearer ').Change
src/openrouter/_hooks/registration.py: register ansdk_inithook that setsconfig.security = Nonewhen a non-callableapi_keyis empty or whitespace. Env fallback then works as it does forOpenRouter(). If no key is available at all, noAuthorizationheader is sent. Callableapi_keys are resolved per request and are left alone. This file is user-owned: it is generated once, isn't tracked ingen.lock, and has noDO NOT EDITheader. A regen won't overwrite the fix, and the generatedsdk.py/security.pystay untouched.docs/overview.mdx(in.genignore, hand-maintained):os.getenv("OPENROUTER_API_KEY", "")->os.getenv("OPENROUTER_API_KEY").tests/test_api_key_resolution.py: 4 small tests usinghttpx.MockTransportthat record theAuthorizationheader. The 3 blank-key tests fail without the hook and pass with it.This uses the same approach as the closed #595, with a smaller hook and a simpler test (no thread/HTTP server).
Not included: generated snippets
The other ~225 occurrences in
README.md,README-PYPI.md,USAGE.mdanddocs/sdks/**come from Speakeasy. I didn't edit them by hand because the next regen would overwrite those edits.gen.yaml'susageSnippetsonly exposesoptionalPropertyRenderingandsdkInitStyle, and neither changes how security env vars render. Fixing the snippets (e.g. emittingos.getenv("OPENROUTER_API_KEY")or omittingapi_key) needs an upstream Speakeasy template change. Until then, this hook means the generated pattern works correctly.Verification
uv run --group dev pytest(9 passed),mypy src,pyright src(0 errors), andpylint src --rcfile pylintrc(10.00/10) all pass.🤖 Generated with Claude Code