## Summary

This variant stage tested whether the LiteLLM SQL-injection fix for the `combined_view` virtual-key lookup could be bypassed through alternate authentication header sources instead of the parent `Authorization: Bearer` source. Two materially distinct header entry paths, `API-Key` and `x-litellm-api-key`, both reached the same vulnerable sink in `litellm==1.83.6` and produced remote SQL execution/authentication bypass on `GET /model/info`. The same payloads were rejected quickly by `litellm==1.83.7`, where the sink is parameterized, so this is an alternate trigger on vulnerable versions but **not** a confirmed fixed-version bypass.

## Fix Coverage / Assumptions

The original fix relies on the invariant that all normal LiteLLM API-key authentication paths eventually call the same database sink: `PrismaClient.get_data(table_name="combined_view", query_type="find_unique")` in `litellm/proxy/utils.py`. Upstream commit `4dc416ee749122ca91e3bca095217478663419e7` changes that sink from raw interpolation:

```python
WHERE v.token = '{token}'
```

to parameter binding:

```python
WHERE v.token = $1
response = await self._query_first_with_cached_plan_fallback(sql_query, hashed_token)
```

It also changes `_query_first_with_cached_plan_fallback()` to accept `*args` and pass them through to `self.db.query_first(...)`, including after cached-plan reconnect/retry.

The fix explicitly covers the combined-view token lookup, not just the `Authorization` header. It therefore covers the parent endpoint and alternate API-key headers that resolve to the same `hashed_token` lookup. It does not by itself parameterize unrelated raw SQL call sites elsewhere in the proxy. Source inspection found other raw SQL builders, but tested package `litellm==1.83.7` includes additional allow-list validation for the most relevant user-facing raw `ORDER BY` candidate in `/spend/logs/v2`, and the other noted raw SQL paths use constants, environment/configuration values, or positional parameters rather than unauthenticated attacker input.

## Variant / Alternate Trigger

Tested alternate triggers:

1. **Candidate A: `API-Key` header**
   - Entry point: `GET /model/info`
   - Input: `API-Key: ' OR EXISTS(SELECT 1 FROM pg_sleep(2)) -- api-key`
   - Code path: `litellm/proxy/auth/user_api_key_auth.py::get_api_key()` reads `SpecialHeaders.azure_authorization` (`API-Key`) and returns it as the submitted key; `_fetch_key_object_from_db_with_reconnect()` in `litellm/proxy/auth/auth_checks.py` calls `prisma_client.get_data(..., table_name="combined_view")`; `litellm/proxy/utils.py::PrismaClient.get_data()` performs the combined verification-token SQL lookup.

2. **Candidate B: `x-litellm-api-key` header**
   - Entry point: `GET /model/info`
   - Input: `x-litellm-api-key: ' OR EXISTS(SELECT 1 FROM pg_sleep(2)) -- x-litellm`
   - Code path: `get_api_key()` reads `SpecialHeaders.custom_litellm_api_key` and strips/uses the received value; the same auth DB lookup then reaches the combined-view SQL sink.

Both candidates are materially different header entry paths from the parent `Authorization: Bearer` path. Both are protected network API inputs and cross the same LiteLLM proxy trust boundary.

Additional bounded inspection:

- `security.md` was read. LiteLLM treats unauthenticated protected proxy access as P1 and authenticated malicious actions as P2; this analysis does not rely on the excluded non-`master_key` misconfiguration.
- A raw `/spend/logs/v2` ordering candidate was inspected and briefly probed. Current tested `litellm==1.83.7` rejects untrusted `sort_by` values with `Invalid sort_by ... Must be one of: endTime, request_duration_ms, spend, startTime, total_tokens`, so it is not a bypass of the auth-token fix.

## Impact

- **Package/component affected:** BerriAI LiteLLM proxy authentication, specifically API-key extraction in `litellm/proxy/auth/user_api_key_auth.py` and the shared combined verification-token lookup in `litellm/proxy/utils.py`.
- **Affected version tested:** `litellm==1.83.6`.
- **Fixed version tested:** `litellm==1.83.7`; repository release tag inspected: `v1.83.7-stable` (`e0d5c28db02b3219dbd944666a55f49732197922`); security fix commit: `4dc416ee749122ca91e3bca095217478663419e7`.
- **Risk level and consequences:** On vulnerable versions, alternate header sources can produce the same unauthenticated SQL injection/authentication bypass as the parent report. On the fixed version tested, the same alternate headers do not bypass the fix.

## Impact Parity

- **Disclosed/claimed maximum impact:** Unauthenticated SQL injection through proxy/admin API requests, potentially enabling extraction or modification of LiteLLM database contents and compromise of gateway configuration, keys, and logs.
- **Reproduced impact from this variant run:** On `litellm==1.83.6`, two alternate headers reached the vulnerable sink and caused PostgreSQL to execute `pg_sleep(2)`, with `GET /model/info` returning HTTP 200 protected model information. On `litellm==1.83.7`, identical requests returned HTTP 401 quickly.
- **Parity:** `partial` for vulnerable-version alternate trigger coverage; `none` as a fixed-version bypass because the patched version blocked both tested alternate header paths.
- **Not demonstrated:** No destructive database reads/writes, secret dumping, or fixed-version compromise was performed.

## Root Cause

The same underlying bug can be reached from multiple API-key header sources because LiteLLM accepts OpenAI/Azure/provider-compatible authentication headers and normalizes them into a key value. In vulnerable `litellm==1.83.6`, that value reaches `PrismaClient.get_data(table_name="combined_view")`, where the SQL predicate interpolates the token directly. A payload such as:

```text
' OR EXISTS(SELECT 1 FROM pg_sleep(2)) -- api-key
```

is inserted into the SQL `WHERE v.token = '<token>'` clause, making PostgreSQL evaluate the injected expression. The fixed commit `4dc416ee749122ca91e3bca095217478663419e7` moves the same value into a bound `$1` parameter, so alternate header sources no longer change SQL syntax.

## Reproduction Steps

1. Run `bundle/vuln_variant/reproduction_steps.sh`.
2. The script:
   - Starts a local PostgreSQL instance.
   - Uses/reuses `litellm==1.83.6` and `litellm==1.83.7` virtual environments.
   - Starts real LiteLLM proxy processes with a configured `LITELLM_MASTER_KEY`.
   - Seeds a valid virtual key row via `/key/generate` so the combined-view lookup has a row to match after injection.
   - Sends candidate payloads to `GET /model/info` using `API-Key` and `x-litellm-api-key` headers against both versions.
   - Writes `bundle/vuln_variant/result.json` and `bundle/vuln_variant/runtime_manifest.json`.
3. Expected evidence:
   - Vulnerable API-Key request: approximately two seconds, HTTP 200.
   - Vulnerable x-litellm-api-key request: approximately two seconds, HTTP 200.
   - Fixed API-Key request: quick HTTP 401.
   - Fixed x-litellm-api-key request: quick HTTP 401.
   - PostgreSQL log contains vulnerable raw `WHERE v.token = '' OR EXISTS(...)` and fixed `WHERE v.token = $1` parameterized queries.

The script exits `1` by design because no variant reproduced on the fixed version.

## Evidence

Primary artifacts:

- `bundle/vuln_variant/reproduction_steps.sh` — stage-specific reproducer, executed twice successfully.
- `bundle/vuln_variant/result.json` — structured timing/status checks.
- `bundle/vuln_variant/runtime_manifest.json` — runtime environment and proof artifact list.
- `bundle/logs/vuln_variant/reproduction_steps.log` — full script output from the idempotency run.
- `bundle/logs/vuln_variant/postgres_variant.log` — PostgreSQL statement evidence.
- `bundle/logs/vuln_variant/vulnerable_api_key_header.txt` and `bundle/logs/vuln_variant/vulnerable_x_litellm_header.txt` — vulnerable protected endpoint responses.
- `bundle/logs/vuln_variant/fixed_api_key_header.txt` and `bundle/logs/vuln_variant/fixed_x_litellm_header.txt` — fixed rejection responses.
- `bundle/logs/vuln_variant/source_delta_variant.txt` and `bundle/logs/vuln_variant/fixed_version.txt` — source/patch identity.

Key current-run `result.json` values:

```json
{
  "result": "alternate_trigger_blocked_by_fix",
  "vulnerable_api_key_header": {"time_seconds": 2.014991, "http_code": "200"},
  "vulnerable_x_litellm_header": {"time_seconds": 2.010438, "http_code": "200"},
  "fixed_api_key_header": {"time_seconds": 0.019809, "http_code": "401"},
  "fixed_x_litellm_header": {"time_seconds": 0.010958, "http_code": "401"}
}
```

The validation checks in `result.json` all evaluate true, including vulnerable delay/auth bypass, fixed rejection/no-delay, and PostgreSQL evidence of both vulnerable raw interpolation and fixed parameterization.

## Recommendations / Next Steps

- Treat the patch as correctly addressing the shared combined-view token lookup sink, but add explicit regression tests for all accepted API-key header sources: `Authorization`, `API-Key`, `x-litellm-api-key`, `x-api-key`, Google API key query/header compatibility, and Azure APIM subscription-key header where applicable.
- Add a static or unit test guard that forbids direct interpolation of attacker-controlled strings into `query_raw`, `query_first`, or `execute_raw` SQL text.
- Keep the `/spend/logs/v2` `sort_by` and `sort_order` allow-list tests because that path constructs a raw `ORDER BY` clause even though it is blocked in the tested fixed package.
- Rotate virtual keys and inspect logs for SQL-looking values in any supported API-key header on deployments that ran vulnerable versions.

## Additional Notes

- The stage script was executed twice after fixes and completed deterministically with exit code `1`, as expected for a negative fixed-version bypass result.
- One earlier probe observed that a repeated identical payload through two alternate headers could hit cache and suppress the second timing delay. The final script uses unique SQL comment suffixes for the two header candidates so each runtime check independently reaches PostgreSQL.
- The repository checkout state was not changed; source identity was gathered using read-only `git rev-list`, `git show`, and `git diff` commands.
