fix(security): honor api_keys auth in monolithic gemini_web2api.py#23
Merged
Sophomoresty merged 1 commit intoJun 2, 2026
Conversation
The README documents that setting api_keys in config.json gates /v1/*
behind Bearer / x-api-key auth. The modular package
(gemini_web2api/server.py) implements this via _authorized(). The
monolithic script the README's Quick Start runs
(`python gemini_web2api.py`) does not — DEFAULT_CONFIG omits api_keys,
do_GET / do_POST never check it, so /v1/chat/completions and
/v1/responses accept any request regardless of config.
Combined with the default host of 0.0.0.0, anyone reachable on the
operator's network can use the operator's Google account (and configured
cookie) to drive Gemini, with the activity attributed to the operator's
IP and Google account.
Fix:
- Add api_keys: [] to DEFAULT_CONFIG (defaults to no-auth, matches docs).
- Add _authorized() to GeminiHandler — same logic as the modular package.
- Gate /v1/* in do_GET and do_POST behind _authorized(), returning 401
with {"error": {"message": "invalid api key"}} on rejection.
Behavior matches the modular package and the README's documented contract:
- api_keys empty → /v1/* open (unchanged for anonymous users).
- api_keys non-empty → /v1/* require Bearer or x-api-key.
- / and /v1beta/* not gated (Google-native path parity with modular).
Detected by Aeon + Semgrep + manual review.
Severity: high (silent auth-bypass against documented behavior).
CWE-287 (Improper Authentication), CWE-1188 (Insecure Default Initialization).
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.
Summary
The README's documented contract says: configure
api_keysinconfig.jsonand/v1/*requiresAuthorization: Bearer <key>orx-api-key: <key>. The modular package (gemini_web2api/server.py) implements that. The monolithic script the README's Quick Start invokes (python gemini_web2api.py) does not —DEFAULT_CONFIGomitsapi_keys, anddo_GET/do_POSTnever check it. Any request to/v1/chat/completions,/v1/responses, or/v1/modelsis served regardless of what's configured.Impact
api_keys: ["sk-secret"]inconfig.json, and believes their proxy is gated. It is not.curl http://host:8081/v1/chat/completions -d '...'with noAuthorizationheader is served.hostis0.0.0.0, so the bound socket is reachable from any device on the same LAN (or wider, if a router or container exposes the port).gemini-3.1-prorouting, every request is signed with that cookie and SAPISIDHASH. Whoever hits the socket consumes the operator's Google account: prompts land in the operator's Gemini activity history, rate-limit / abuse signals attribute back to the operator's IP and Google account, and a Pro subscription cookie hands free Pro access to anyone within reach.Location
gemini_web2api.py— entrypoint shown in the README's Quick Start:DEFAULT_CONFIG(lines 48–61 in HEAD): noapi_keyskey.GeminiHandler.do_GET(line 437) anddo_POST(line 457): no auth check.The README's Configuration section claims the opposite:
That sentence is true for the modular package (
gemini_web2api/server.py's_authorized()) but not for the monolithic script.Fix
Bring the monolithic script to parity with the modular package — same
_authorized()shape, same gate location, same documented contract:"api_keys": []toDEFAULT_CONFIGso the config knob is present and serializable._authorized()toGeminiHandler— empty list → allow (anonymous mode, matches docs); non-empty list → requireAuthorization: Bearer <key>orx-api-key: <key>._authorized()at the top ofdo_GET/do_POSTfor/v1/*paths, returning401 {"error": {"message": "invalid api key"}}on rejection.No behavior change for users running anonymous (
api_keys: []). The change is invisible to them. Users who setapi_keysnow actually get the gating the README promises./and/v1beta/*remain ungated — same parity choice the modular package makes (the/v1betapath is for the Google-native protocol used bygemini-cliand similar tools, which speakGEMINI_API_KEY=none).Out of scope for this PR:
host: 0.0.0.0. That choice is its own discussion (PR feat: multi-account load balancing, mandatory Bearer auth, Linux/systemd deployment guide #22 proposes flipping it to127.0.0.1as part of a larger feature change). Even with127.0.0.1, an unauthenticated/v1/*is still wrong on a multi-user host or shared dev container, so this fix stands on its own.Verification
No test suite ships with the repo. A targeted harness (
verify-gemini-auth.cjs) exercises the patch by instantiatingGeminiHandleragainst mockrfile/wfile/ headers and dispatchingdo_GET/do_POSTdirectly:api_keys: []→GET /v1/modelsapi_keys: ["sk-1"]→ no headerapi_keys: ["sk-1"]→Authorization: Bearer sk-badapi_keys: ["sk-1"]→Authorization: Bearer sk-1api_keys: ["sk-1"]→x-api-key: sk-1api_keys: ["sk-1"]→POST /v1/chat/completionsno keyapi_keys: ["sk-1"]→GET /(not under/v1/)api_keys: ["sk-1"]→GET /v1beta/models(Google native)DEFAULT_CONFIG['api_keys'][]python3 -c 'import ast; ast.parse(open("gemini_web2api.py").read())'exits clean.Detected by
Aeon + Semgrep + manual review (threat-model-claims axis — the README's documented
api_keyscontract didn't hold at the script the Quick Start points at).Filed by Aeon.