Repository navigation
docs: stop advertising sk-1234 as the master key in shipped configs and examples - #42011
Conversation
…nd examples Shipped proxy configs now read general_settings.master_key from os.environ/LITELLM_MASTER_KEY, the .env examples ship a blank value with the openssl generate command above it, and READMEs, the missing env vars page and Admin UI code snippets show a generate command or the <your-master-key> placeholder instead of the literal sk-1234 The two CircleCI docker runs that mount proxy_server_config.yaml and oai_misc_config.yaml now pass LITELLM_MASTER_KEY so their runtime key is unchanged
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
bugbot run |
| # Development Configs | ||
| LITELLM_MASTER_KEY = "sk-1234" | ||
| # Generate one with: echo "LITELLM_MASTER_KEY=sk-$(openssl rand -hex 32)" | ||
| LITELLM_MASTER_KEY = "" |
There was a problem hiding this comment.
Empty master key locks out operators
Medium Severity
Copying the shipped .env examples sets LITELLM_MASTER_KEY to an empty string, which get_secret keeps as "" rather than None. Auth then requires a key, the empty master key never matches, and show_missing_vars_in_env only treats master_key is None as missing, so the setup page never appears.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit c8e0f2d. Configure here.
There was a problem hiding this comment.
An empty key fails closed at this tip, verified live with 401s. #42019 merges first and refuses to boot on it, printing the generate command
|
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
1 issue from previous review remains unresolved.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit c8e0f2d. Configure here.


TLDR
Problem this solves:
sk-1234as the master keyHow it solves it:
LITELLM_MASTER_KEY.envexamples ship a blank value plus anopensslgenerate command<your-master-key>This is the behaviour-neutral companion to the upcoming change that makes the proxy refuse to boot when the master key is unset, empty or
sk-1234. No auth code changes hereWhat changed, by area
Shipped proxy configs now set
master_key: os.environ/LITELLM_MASTER_KEYinstead ofmaster_key: sk-1234:proxy_server_config.yaml,litellm/proxy/proxy_config.yaml,dev_config.yaml,_new_secret_config.yaml,wildcard_config.yaml, and underlitellm/proxy/example_config_yaml/theadaptive_router_example,oai_misc_config,pass_through_config,reject_clientside_metadata_tags_configandtool_permission_examplefiles. Runnable files carry no literal key at all, because a placeholder literal would itself become a publicly known working key if copied uneditedCI: the two CircleCI
docker runsteps that mountproxy_server_config.yamlandoai_misc_config.yamlnow pass-e LITELLM_MASTER_KEY="sk-1234", so the key those jobs run with is unchanged and the tests that sendsk-1234as the bearer token keep passing. Thepass_through_config.yamljob already passed it. No other CI block is touched.env.exampleanddocker/.env.exampleshipLITELLM_MASTER_KEYblank with# Generate one with: echo "LITELLM_MASTER_KEY=sk-$(openssl rand -hex 32)"on the line aboveDocs:
README.md,CONTRIBUTING.md, the in-package READMEs (anthropic_interface, containers, bitbucket, gitlab, workflows), the curl comments in the generic guardrailexample_config.yaml, andscripts/adaptive_router_demo/README.mduse<your-master-key>in curl and code examples. Where an example boots a proxy it now exports a generated key first and reuses it in the later stepThe "missing env vars" HTML page rendered by
admin_ui_utils.pyshows the generate command and a blank value instead ofLITELLM_MASTER_KEY="sk-1234"Admin UI: the code snippets in the agent builder Connect tab, the prompt editor "Get code" dialog, the public model hub, the AI Hub table, the API reference page, the cost tracking "how it works" panel and the MCP semantic filter test fall back to
<your-master-key>. I checked the two fallbacks that looked like runtime keys (AgentBuilderView.tsxandPromptCodeSnippets.tsx): both only feed displayed snippets, neither is sent on a request, so no request guard was neededUser Flow
Before: an operator who copies a shipped config and exports their own strong master key still ends up with a proxy that only accepts the public
sk-1234export LITELLM_MASTER_KEY="sk-$(openssl rand -hex 32)"and start the proxy withlitellm/proxy/wildcard_config.yamlAuthorization: Bearer $LITELLM_MASTER_KEYand get HTTP 401token_not_found_in_dbAuthorization: Bearer sk-1234and get HTTP 200 with the key listAfter: the same steps give a proxy that accepts only the key the operator generated
export LITELLM_MASTER_KEY="sk-$(openssl rand -hex 32)"and start the proxy withlitellm/proxy/wildcard_config.yamlAuthorization: Bearer $LITELLM_MASTER_KEYand get HTTP 200 with the key listAuthorization: Bearer sk-1234and get HTTP 401token_not_found_in_dbRelevant issues
Companion to #42019, which makes the proxy refuse to start on an unset, empty, or
sk-1234master key. Merge #42019 first, so a shipped config started withoutLITELLM_MASTER_KEYrefuses to boot and never runs unauthenticatedAffected release
Linear ticket
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
uv run pytest tests/test_litellm/<your_test_file>.py -v. Leave the suites (make test-unit-*,make test-unit) to CI: it finishes in ~15 minutes where a laptop takes an hour or more@greptileaito re-request a review after pushing changes)Delays in PR merge?
If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).
Screenshots / Proof of Fix
Shared setup: each leg runs from its own worktree and
.venvat the named commit with the developer.envmoved out of the tree. The proxy bootslitellm/proxy/wildcard_config.yamlwith--num_workers 2on a random free port against a local Postgres database created for that leg (mat627_before,mat627_after), withexport LITELLM_MASTER_KEY="sk-$(openssl rand -hex 32)"in the same shell (redacted to its first 6 characters below). The Admin UI dev server (npm run devinui/litellm-dashboard) points at that proxy. Every curl was repeated so both workers answered (4 times before, 6 times after) with identical output each time, and one copy is shown. No LLM calls are involved, since this change only touches which master key the proxy accepts, so the unified endpoints are out of scopeBefore (b946d12)
Shipped config with a generated LITELLM_MASTER_KEY (proxy on 22261)
echo "${LITELLM_MASTER_KEY:0:6}..."curl -s -w '\nHTTP %{http_code}\n' http://localhost:22261/key/list -H "Authorization: Bearer $LITELLM_MASTER_KEY"curl -s -w '\nHTTP %{http_code}\n' http://localhost:22261/key/list -H 'Authorization: Bearer sk-1234'Missing env vars page and keyless boot (no LITELLM_MASTER_KEY, no DATABASE_URL, proxy on 41977)
curl -s http://localhost:41977/sso/key/generate | grep MASTER_KEYcurl -s -w '\nHTTP %{http_code}\n' http://localhost:41977/key/list -H 'Authorization: Bearer sk-1234'curl -s -w '\nHTTP %{http_code}\n' http://localhost:41977/v1/modelsAdmin UI API reference page (dev server on 28455 against the proxy on 22261)
adminwith the generated key. The page shows the 401 below instead of the API referenceadminwithsk-1234instead: redirected to/ui/?login=successapi_key="sk-1234"on lines 11 and 18 of the snippetAfter (c8e0f2d)
Shipped config with a generated LITELLM_MASTER_KEY (proxy on 50267)
echo "${LITELLM_MASTER_KEY:0:6}..."curl -s -w '\nHTTP %{http_code}\n' http://localhost:50267/key/list -H "Authorization: Bearer $LITELLM_MASTER_KEY"curl -s -w '\nHTTP %{http_code}\n' http://localhost:50267/key/list -H 'Authorization: Bearer sk-1234'Missing env vars page and keyless boot (no LITELLM_MASTER_KEY, no DATABASE_URL, proxy on 34628)
curl -s http://localhost:34628/sso/key/generate | grep MASTER_KEYcurl -s -w '\nHTTP %{http_code}\n' http://localhost:34628/key/list -H 'Authorization: Bearer sk-1234'curl -s -w '\nHTTP %{http_code}\n' http://localhost:34628/v1/models, with noAuthorizationheader at all. This is the keyless boot the Medium caveat below describes, and the reason feat(proxy)!: refuse to start with an unset, empty, or publicly known master key #42019 merges firstAdmin UI API reference page (dev server on 37102 against the proxy on 50267)
adminwith the generated key: redirected to/ui/?login=success, and the page titled "OpenAI Compatible Proxy: API Reference" loadsapi_key="<your-master-key>", # litellm proxy API Keyand line 18 readsapi_key="<your-master-key>",. Nosk-1234on either changed tabBlank key from the .env examples (
LITELLM_MASTER_KEY="", proxy on 41977, database connected)curl -s -w '\nHTTP %{http_code}\n' http://localhost:41977/v1/modelscurl -s -w '\nHTTP %{http_code}\n' http://localhost:41977/key/list -H 'Authorization: Bearer sk-1234'curl -s -w '\nHTTP %{http_code}\n' http://localhost:41977/key/list -H 'Authorization: Bearer 'curl -s -w '\nHTTP %{http_code}\n' -X POST http://localhost:41977/v2/login -H 'Content-Type: application/json' -d '{"username":"admin","password":""}'A blank key fails closed on every route tried, and #42019 refuses to boot on it
Merged tree (origin/main 75f4c11 with #42019 at fdd614d and this PR, proxy on 43841, database connected)
LITELLM_MASTER_KEYunset: the proxy refuses to start withUnsafeMasterKeyError: LiteLLM proxy refused to start: no master key is set, so every request would be accepted without authenticationLITELLM_MASTER_KEY="": refused withthe master key is emptyLITELLM_MASTER_KEY=sk-1234: refused withthe master key is a publicly known defaultGET /key/listwithBearer sk-1234gets the same 401token_not_found_in_dbas the After legObservations from the run, none caused by this PR:
/key/listwith 500 "Database not connected", not 401. Pre-existing, PR leaves it aloneapi_key="your_api_key". PR leaves it alonetokencookie from another proxy hides the login form. PR leaves it aloneType
📖 Documentation
🚄 Infrastructure
Caveats (if any)
Medium
LITELLM_MASTER_KEYunset now boots with no master keysk-1234; at this tip a keyless boot servesGET /v1/modelsto a request with noAuthorizationheader (After proof, keyless boot step 3)Low
<your-master-key>also shows on pages aimed at virtual key holdersdocker-compose.hardened.ymlmountsproxy_server_config.yamland gets its key from.envtests/,cookbook/, Python docstring curl examples,ci_cd/, and the two operator scripts that default a client key tosk-1234(qa_sticky_session.sh,scripts/health_check/run_parallel_health_checks.sh)litellm/proxy/_experimental/outstill carries the oldsk-1234snippet fallbacks until the nextbuild_release_ui.shrun picks up the source changes; rebuilding it here would add a generated bundle diff to a docs changeos.environ/PROXY_MASTER_KEYand generates a random key when none is given, so it needed no changeallow_requests_on_db_unavailabletolerates a connection outage) and the refusal text, not whether an unset, empty, orsk-1234key is refusedproxy_e2e_anthropic_messages_tests(job 2192664): the two bedrocktest_bedrock_invoke_messages_with_all_beta_headerscases fail withinvalid beta flag, fixed on main by 7966f50integration-cost(job 2192672): the fireworks fallback cache-read case expects a stale price, repinned on main by 02736e2integration-extensions(job 2192673): all four modules fail collection withNo module named 'mcp.server.fastmcp', which is red on main's own pipelines 89898, 89899, and 89901 since 2026-09-19 23:48Z and green on 89877Live PR risk
Breaking
None found. No auth code changes; the shipped configs resolve
master_keythrough the existingos.environ/reader, and the two CircleCI docker runs that mount the changed configs receiveLITELLM_MASTER_KEY="sk-1234", so the tests that sendsk-1234as the bearer token keep passingBackward incompatible
LITELLM_MASTER_KEYused to boot keyed withsk-1234and now boots with no master key. At this tip that keyless boot answersGET /v1/modelswith HTTP 200 and the model list to a request carrying noAuthorizationheader (After proof, keyless boot step 3). feat(proxy)!: refuse to start with an unset, empty, or publicly known master key #42019 closes it by refusing to boot on an unset, empty, orsk-1234key (merged tree proof above), which is why it merges first and this PR is not merged before itLITELLM_MASTER_KEY=""from the.envexamples fails closed at this tip (401 on every request tried, 500 on UI login) and feat(proxy)!: refuse to start with an unset, empty, or publicly known master key #42019 refuses to boot on itRegression risk
build_and_testande2e_openai_endpointsmount the changed configs and pass the key explicitly, so theirsk-1234bearer tests still hold;proxy_pass_through_endpoint_testsalready passed itAgentBuilderView.tsxandPromptCodeSnippets.tsxsend nothing on a request, so no request guard was neededadmin_ui_utils.pyrenders the generate comment and a blank value (After proof, keyless boot step 1)Dependency graph
litellm/proxy/wildcard_config.yaml: verified live on both legsproxy_server_config.yaml,oai_misc_config.yaml,pass_through_config.yaml: tested by the CircleCI jobsbuild_and_test,e2e_openai_endpoints,proxy_pass_through_endpoint_testslitellm/proxy/proxy_config.yaml,dev_config.yaml,_new_secret_config.yaml, and the otherexample_config_yamlfiles: untested, the same one-linemaster_keychange as the verified configadmin_ui_utils.pymissing env vars page: verified live.env.exampleanddocker/.env.exampleblank key: verified live at the tipREADME.md,CONTRIBUTING.md, the package READMEs, and the adaptive router demo README: docs only, nothing to drivesk-1234keys refuse to boot with theUnsafeMasterKeyErrorheadlines quoted above, a generated key boots and rejectssk-1234Not verified
docker-compose.yml,docker-compose.hardened.yml) with the blank.envexampleFinal Attestation
Note
Medium Risk
Changes default proxy bootstrap and operator docs around authentication secrets; until the companion boot guard lands, starting shipped configs without
LITELLM_MASTER_KEYmay leave auth misconfigured.Overview
Stops treating
sk-1234as the default LiteLLM proxy master key in anything operators are likely to copy. Shipped proxy YAML now setsgeneral_settings.master_keytoos.environ/LITELLM_MASTER_KEYinstead of a literalsk-1234, so a generated env key is what the proxy actually honors when users follow the repo configs..env.exampleanddocker/.env.exampleship a blankLITELLM_MASTER_KEYplus anopenssl rand -hex 32generate hint. README, CONTRIBUTING, package READMEs, and dashboard code snippets use<your-master-key>(or$LITELLM_MASTER_KEYin runnable examples). The missing env vars HTML fromadmin_ui_utilsmatches that pattern.CircleCI passes
LITELLM_MASTER_KEY="sk-1234"into the two Docker jobs that mount updated configs (build_and_test,e2e_openai_endpoints) so CI behavior stays the same. Unit tests lock in the admin UI and snippet placeholder behavior.No proxy auth logic changes here; this pairs with a follow-up that refuses boot on unset/empty/
sk-1234keys.Reviewed by Cursor Bugbot for commit c8e0f2d. Bugbot is set up for automated code reviews on this repo. Configure here.