Skip to main content
BlogSecurity

LiteLLM CVE-2026-42208: SQL Injection Fix and Gateway Hardening

How one unauthenticated Authorization header reached the LiteLLM database, what the advisory, Sysdig and CISA say, and the patch, detection and hardening steps to run now.

Matthews Wong

Software Engineer

October 202611 min read
LiteLLM CVE-2026-42208: SQL Injection Fix and Gateway Hardening

Key Takeaway

CVE-2026-42208 is a critical SQL injection in the LiteLLM proxy: an unauthenticated Authorization header reached a database query in versions 1.81.16 up to, but excluding, 1.83.7. Upgrade to 1.83.7 or later, treat any internet-reachable vulnerable instance as exposed, rotate virtual keys, the master key and provider credentials, and keep the gateway off the public internet.

The first question this advisory forces is which of your servers run a LiteLLM proxy, because an LLM gateway is the one service that keeps every upstream API key in a single database. The advisory describes exactly the worst case for that design: no login, no special route, one header on an ordinary chat completions request.

This post is a defender's read of CVE-2026-42208. It rests on the GitHub advisory, the CIRCL record that mirrors NVD and CISA data, Sysdig's honeypot write-up, a Cloud Security Alliance research note and LiteLLM's own production docs. It contains no exploit payloads, only what you need to patch, detect and shrink the blast radius. Anything I could not read in a source is left out.

What the advisory actually says

LiteLLM's proxy checks the API key on every request. In the affected versions, a database query used during that check put the caller-supplied key value into the query text instead of passing it as a separate parameter. An unauthenticated attacker could send a crafted Authorization header to any LLM route, the advisory names POST /chat/completions, and reach that query through the error-handling path. The result is read access to the proxy database and possibly write access, which the advisory says can lead to unauthorized access to the proxy and the credentials it manages.

The table collects the facts I could confirm directly. One source disagrees on the upper bound of the range, which I come back to in the trade-offs section.

FactWhat the source saysSource
Affected versionslitellm on PyPI, 1.81.16 or later and below 1.83.7GitHub advisory GHSA-r75f-5x8p-qvmc
Fixed version1.83.7 (tag v1.83.7-stable); the caller-supplied value is now always passed as a separate parameterGitHub advisory
SeverityCVSS 4.0 score 9.3 and CVSS 3.1 score 9.8, both Critical, network attack, no privileges, no user interactionCIRCL record of the CVE
Weakness classCWE-89, SQL injectionGitHub advisory
Exploitation statusAdded to the CISA KEV catalog on 2026-05-08 with a due date of 2026-05-11; CISA's SSVC entry records exploitation as active and technical impact as totalCIRCL record, CISA KEV
CreditTencent YunDing Security LabGitHub advisory

Why one header reached the database

The bug class is old and boring, which is the point. A lookup that should have been parameterised was built by pasting text into SQL, and the text came from the one thing an unauthenticated caller fully controls, the key they present. Authentication code runs before any authorisation, so a flaw there is reachable by everyone. That is why the CVSS vector says no privileges and no user interaction.

# Generic illustration of the bug class. NOT LiteLLM's source: the
# advisory does not publish it.

# Wrong: the caller's string becomes part of the SQL text, so whatever
# the caller sends is parsed as SQL.
query = f"SELECT * FROM tokens WHERE token = '{presented_key}'"
row = await conn.fetchrow(query)

# Right: the SQL text is fixed, the value travels separately and can
# never be parsed as SQL. This is what the advisory says 1.83.7 now does.
row = await conn.fetchrow(
    "SELECT * FROM tokens WHERE token = $1",
    presented_key,
)

The advisory does not publish the vulnerable source, so the snippet below is a generic illustration of the pattern and its fix, not LiteLLM's code. What the advisory does say is that the query is reached through the error-handling path, and that setting disable_error_logs to true under general_settings removes the route unauthenticated input takes to it.

The 1.83.7 release notes list one change that matches the description, a pull request titled use parameterized query for combined_view token lookup. The notes do not tie it to the CVE, so treat the match as likely rather than confirmed. The same release lists other security items, among them blocking arbitrary command execution through the MCP stdio transport and input validation on management endpoints, so upgrading buys more than this one fix.

What attackers went after, and the blast radius

Sysdig ran a honeypot and recorded the first injection attempt on 2026-04-26 at 04:24 UTC, 36 hours and 7 minutes after the advisory was indexed in the GitHub Advisory Database. The maintainers had published their own advisory on 2026-04-20, so measured from that moment the gap was about 127 hours. Both numbers are correct; they just start the clock at different events. Sysdig saw 17 attempts against three tables, then a second source address replaying the same payload set 21 minutes after the first activity.

TableWhat it holdsWhat an attacker gains
LiteLLM_VerificationTokenVirtual API keys, including the master keyImpersonation of any proxy user, and admin access with the master key
litellm_credentialsStored upstream provider credentials; Sysdig lists OpenAI, Anthropic and BedrockCalls straight to providers, bypassing the proxy's spend limits, filters and audit logging
litellm_configProxy environment variables, which Sysdig says typically include the PostgreSQL connection string, master key, callback webhook URLs and cache backendsA second foothold: the database itself, the webhooks and the cache

Sysdig is careful about what it did not see. There was no authenticated follow-on activity, no key minting and no reuse of extracted credentials, so it describes the activity as attempted extraction, not confirmed compromise. That is a statement about one honeypot. It says nothing about your instance, and the Cloud Security Alliance note makes the practical point that data reads may not leave application-layer log entries at all.

The same note states that for organisations holding organisation-level or account-root provider credentials in the proxy, the blast radius is comparable to a full cloud account compromise. I would not put it more strongly than that, but I would not put it more weakly either: the gateway is a secrets store with an HTTP front door.

Patch, workaround or fence: what each one does

Only the upgrade fixes the flaw. The other options reduce exposure while you schedule it, and the CSA note is explicit that the config workaround reduces risk without fixing the injection. The table lines them up by what they actually stop.

OptionStops the injection?CostSource
Upgrade to 1.83.7 or laterYes, the value is passed as a separate parameterA deploy, plus reading the release notes for the other changesGitHub advisory, release notes
disable_error_logs: true under general_settingsNo. It removes the path unauthenticated input uses to reach the queryLLM exceptions stop being stored in the databaseGitHub advisory, CSA note
Reverse proxy that rejects odd Authorization valuesNot the flaw, but it blocks the known request shape before it reaches the appA filter you must keep correct; an allowlist can reject legitimate keysSysdig mitigations
Network fence: VPN, API gateway or internal-only listenerNo, but unauthenticated strangers can no longer reach the routeClients outside the fence need a path inSysdig, CSA note

The config workaround is less of a sacrifice than it sounds. LiteLLM's own production page already recommends disable_error_logs: True, so that exceptions do not bloat the spend logs during a provider outage, and says to send them to your logging stack instead. If you were following that page, you had the workaround on before the CVE existed.

# Read the version the RUNNING process reports, not the one in requirements.txt.
pip show litellm | grep -i '^Version'
docker ps --format '{{.Names}}  {{.Image}}' | grep -i litellm
docker exec <container> python -c "import importlib.metadata as m; print(m.version('litellm'))"

# Upgrade; the advisory's patched range is 1.83.7 and above.
pip install --upgrade "litellm>=1.83.7"

# Bridge only, if you cannot deploy today (config.yaml):
# general_settings:
#   disable_error_logs: true

A patched version number is not proof you are patched. Read the version the running container reports, not the one in your requirements file, and confirm every replica restarted. A single forgotten worker on the old image keeps the door open.

Detection you can add to nginx today

Sysdig's detection guidance is to watch POST requests to /chat/completions whose Bearer token starts with a quote-terminated prefix, flag Authorization values containing SQL keywords or table names, and note that the user agent Python/3.12 aiohttp/3.9.1 appeared on every injection request. It also lists two source addresses, 65.111.27.132 and 65.111.25.67, both in AS200373. Treat those as indicators from one campaign, not a complete list.

Do not log the Authorization header to hunt for this. That writes every valid key into a log file. The config below flags only a boolean, using an allowlist for what a Bearer key may contain. Real keys are plain tokens, so anything with a quote, parenthesis or space is wrong by construction. This is my own design, built from Sysdig's mitigation of blocking quotes and parentheses. Test it against your real clients first.

# /etc/nginx/conf.d/llm-gateway.conf
# An allowlist for what a Bearer key may contain. Anything else, such as a
# quote, a parenthesis or a space inside the token, is flagged.
# Tune the character class to the key formats your own clients send.
map $http_authorization $auth_malformed {
    default                            1;
    ""                                 0;  # no header: the app rejects it
    "~^Bearer [A-Za-z0-9._~+/=-]+$"    0;
}

# Log the verdict, never the header: logging Authorization would write
# every valid key to disk.
log_format llm_sec '$remote_addr [$time_local] "$request" $status '
                   'auth_malformed=$auth_malformed ua="$http_user_agent"';

server {
    listen 443 ssl;
    server_name llm.example.internal;

    location / {
        access_log /var/log/nginx/llm-gateway.log llm_sec;
        if ($auth_malformed) { return 400; }   # refuse before the proxy sees it
        proxy_pass http://127.0.0.1:4000;      # your LiteLLM upstream
    }
}

Then search the history you already have. The retroactive check is cheap, and Sysdig calls a single matching request before patching a high-confidence indicator.

# Indicators Sysdig reported for one campaign. zgrep reads plain and .gz logs.
zgrep -hE '65\.111\.(27\.132|25\.67)' /var/log/nginx/access.log*
zgrep -h 'Python/3.12 aiohttp/3.9.1' /var/log/nginx/access.log* | grep 'POST /.*chat/completions'

# Your new guard: every refused request since deployment.
grep 'auth_malformed=1' /var/log/nginx/llm-gateway.log | tail -50

Hardening the proxy after the patch

Patching removes this bug, not the design problem. The CSA note advises threat-modelling the proxy tier like an identity provider or a secrets manager, in an isolated environment with its own secret store. The config below collects settings that appear in LiteLLM's production and config references. I verified each key against those pages, and the comments say why.

# config.yaml: keys taken from LiteLLM's production and config references.
general_settings:
  master_key: os.environ/LITELLM_MASTER_KEY   # never a literal in the file
  disable_error_logs: true                     # keeps exceptions out of the DB
  trusted_proxy_ranges: ["10.0.0.0/8"]         # [] if clients connect directly
  database_connection_pool_limit: 10
  # allowed_ips: ["203.0.113.10"]              # Enterprise licence only

litellm_settings:
  request_timeout: 600                         # default is 6000 seconds
  set_verbose: false
  json_logs: true

# Environment, set outside the file:
#   LITELLM_MODE=PRODUCTION        do not auto-load credentials from a local .env
#   LITELLM_LOG=ERROR
#   LITELLM_SALT_KEY=...           never change it after adding models, or
#                                  stored API keys become unreadable
#   DATABASE_URL=postgresql://user:pass@host:5432/litellm?sslmode=verify-full&sslrootcert=/certs/global-bundle.pem

Two more points belong outside the YAML. First, scope the provider keys the proxy stores: project-level keys with minimum permissions and spend limits, and for Bedrock an IAM role scoped to specific model ARNs, as the CSA note recommends. Second, allowed_ips exists but the docs mark it as an Enterprise feature, and they do not say how it reads client addresses behind a load balancer. I would rely on a network fence you control instead of assuming it behaves.

Where this goes wrong: the trade-offs and the named failure

The failure that made this CVE expensive is architectural, not the missing parameter. A gateway stood up to simplify key management became the one place where a single unauthenticated bug yields every provider credential, and it often sits on a public address because developers outside the VPN need it. Sysdig's recommendation to inventory AI gateway deployments, including ones stood up outside formal security review, is aimed at exactly this.

Clean logs do not mean clean. Data reads through SQL injection may leave no application-layer entry, so for an internet-reachable instance that ran an affected version the CSA note says to treat it as potentially compromised and rotate everything. Patching alone does not invalidate a key that has already been copied.

There are smaller traps. The workaround hides error visibility in the database, so plan where exceptions go instead. The version bound also differs between sources: the advisory, the CVE record and the CSA note all say below 1.83.7 is affected, while an AhnLab advisory writes 1.83.7 and below. I followed the advisory, but the safe move is to run 1.83.7 or newer and read the release notes for what is current. And the exploitation clock runs fast: CISA gave federal agencies three days from the KEV listing, 2026-05-08 to 2026-05-11, to apply mitigations.

What to do on Monday

Work in this order. The first two steps take minutes and tell you whether the rest is urgent.

  1. Inventory every LiteLLM proxy you run or that a team stood up without telling you, and record the version each one actually reports.
  2. Upgrade anything in the affected range to 1.83.7 or later, and restart every replica. If you cannot, set disable_error_logs to true and put the proxy behind a fence today.
  3. If an affected instance was internet-reachable, rotate in this order: the master key, virtual keys, then every stored provider credential. Revoke at the provider, not just in the LiteLLM UI.
  4. Add the nginx guard, then search historic logs for the two campaign addresses and the user agent Sysdig reported.
  5. Check provider billing and usage for traffic you cannot explain, especially from outside your proxy.

Last, shrink the next blast radius: scoped provider keys, spend caps at the provider, and a listener that is not on the public internet. The patch buys time; the architecture decides what the next bug costs.

The rule I take from CVE-2026-42208: an LLM gateway is a credentials vault that happens to proxy requests, so patch it like one, fence it like one, and assume that anything it stored while exposed has been read. Upgrade to 1.83.7 or later, rotate on suspicion, and keep the workaround as a bridge rather than a destination.

FAQ

Frequently Asked Questions

Short answers to what readers ask most about this topic.

Which LiteLLM versions are affected by CVE-2026-42208?

The GitHub advisory lists litellm 1.81.16 or later and below 1.83.7 as affected, and 1.83.7 as the patched version. One third-party advisory writes the bound as 1.83.7 and below, so the safe move is to run 1.83.7 or newer and read the release notes.

Do I need to be logged in to exploit the LiteLLM SQL injection?

No. The advisory says an unauthenticated attacker could send a crafted Authorization header to an LLM API route such as POST /chat/completions. The query is reached through the error-handling path, which is why the CVSS vector lists no privileges and no user interaction.

Does disable_error_logs fix CVE-2026-42208?

No, it is a workaround. Setting disable_error_logs to true under general_settings removes the path unauthenticated input uses to reach the vulnerable query, but the Cloud Security Alliance note says it reduces risk without fixing the injection. Upgrade to 1.83.7 or later as soon as you can.

What should I rotate if my LiteLLM proxy was exposed?

Rotate the master key, every virtual key and every stored upstream provider credential, and revoke them at the provider as well as in LiteLLM. Sysdig saw attackers target the token, credentials and config tables, and data reads may leave no application log entry. Also review provider billing for unexplained usage.

How can I detect attempts against my LiteLLM gateway?

Flag POST requests to chat completions whose Authorization header contains quotes, parentheses or SQL keywords, and search historic logs for the addresses and user agent Sysdig reported. Log a pass or fail flag rather than the header itself, so valid keys never reach your log files.