You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
litellm-community: one unknown API key is retrying ~900 times/hour and getting 401 every time #21
A single key hash accounts for 1,815 failed auth attempts in 2 hours on litellm-community (about 15/min, sustained). Two more are doing the same at lower volume:
key hash (prefix)
attempts / 2h
684d383f6140
1,815
8a2a0db71c07
566
92fb6c51db26
90
Every one returns 401 Unable to find token in cache or LiteLLM_VerificationTokenTable.
Why it is worth a look
The top hash does not exist in LiteLLM_VerificationToken at all, not even soft-deleted. So it is not a churned member whose key was revoked: it is a key we never issued. Either a client configured with a typo'd or fabricated key, or someone probing.
Cost today is small but not zero: each attempt is a cache miss plus a DB lookup, and the failures dominate the proxy's ERROR log to the point that they buried the evidence while diagnosing the 2026-08-07 OOM incident (helmcode/nan-devops#114). The signal-to-noise cost is the real one.
The requester IP in the logs is always 10.244.8.25, the in-cluster tunnel pod, so the originating IP has to come from cf-connecting-ip / the client_ip request tag rather than from requester_ip.
Options
Identify the origin via Cloudflare (cf-connecting-ip on the 401s) and, if it is a real member with a broken config, tell them.
Rate-limit repeated auth failures per source IP at the edge. Note this is auth failure, so it happens before the usage hook and before any per-key limit; it would have to live in Traefik or Cloudflare.
Accept it, but lower the log level of the 401 path so it stops drowning the proxy's ERROR stream.
Still happening, and now attributable. Measured 2026-09-12.
Volume
115 responses of 401 against litellm-community in an 8-minute window
(2026-09-12 10:57:21Z → 11:05:27Z), from Traefik access logs. That is ~860/hour,
consistent with what the hook logs (~160 per 20 minutes across the 8 pods).
Origin — answerable without the Cloudflare dashboard
Traefik logs carry request_Cf-Connecting-Ip and request_User-Agent, so option 1 of this
issue needs no Cloudflare access:
Three sources carry 91% of it. The user agents are real SDKs, the paths are coherent, and
the rate is steady — this is automated clients retrying dead credentials, not a scanner
probing for valid keys. Two of the three are hosting providers (Oracle Cloud, OVH), so they
are agents running somewhere, not someone at a laptop.
The keys are not ours and never were
hashes observed in the 401s -> 0 rows in LiteLLM_VerificationToken, not even soft-deleted
key suffixes (sk-...2PYA, sk-...6jSA) -> 0 real keys end with them
92fb6c51db26 from the original table is still there a month later; the top offender
then (684d383f6140) is gone.
What I could NOT establish
Whether these are ex-members whose keys were hard-deleted (the 2026-08-20 mass delete
removed 113 keys, leaving no rows). Nothing in the DB links a fabricated hash back to a
person, and the key-alias join against members is not sound enough to answer it — I tried:
67 subscribed members appear to have no key when joined on lower(key_alias) = lower(discord_user), but 21 of the first 60 of those consumed 199,529 metered rows in the
last 30 days, so they do have working keys and the join simply misses aliases that differ
from the Discord handle. Recording that here so nobody re-derives the same wrong number: that join does not answer this question.
The log-noise argument is weaker than this issue claimed
Measured over 20 minutes across all 8 pods:
ERROR lines 2058 of which auth-related 353 (17%)
WARNING lines 843 of which our 401 line 161 (19%)
It is real but it does not dominate. The actual top ERROR source is unrelated: 794 lines
(39%) are LiteLLM Redis Caching: async set() - Got exception from REDIS, the same Valkey
timeout family as #51. If the goal is a readable ERROR stream, #51 buys more than this does.
(An earlier draft of this comment said auth was 0% of ERROR. That was a broken grep, not a
measurement.)
Recommendation
Option 2, bounded: rate-limit repeated auth failures per source IP at the edge. It is
auth failure, so it happens before the hook and before any per-key limit — it has to live in
Cloudflare or Traefik. Three IPs at ~860/h is well inside what a WAF rule handles, and it
needs no per-member diagnosis, which is the part that cannot be done from our side.
Option 1 is closed as far as it can go: we know the origins, and they map to nothing we
hold. Option 3 (lowering the log level) treats a symptom that is 17% of the problem.
Not doing either unilaterally — an edge rule is a production change on the request path for
every caller, and it should be a deliberate call, not a side effect of triage.
Closing: investigated, attributed, and not worth the change. Owner decision 2026-09-12.
The measurement above answers the question this issue asked, and the answer is that it does
not justify touching the edge:
volume
~860/h, against a proxy serving ~200 rps — 0.1% of traffic
cost per attempt
one cache miss plus one DB lookup
member impact
none — the keys never existed, so nobody legitimate is waiting behind them
log cost
17% of ERROR, not the dominant source this issue assumed
Acting would mean a rate-limit rule on the request path of every caller, to save 0.1% of
traffic and 17% of one log stream. That is the wrong trade.
What the investigation did settle, and is worth keeping:
The origins are identifiable from Traefik alone (Cf-Connecting-Ip, User-Agent) — no
Cloudflare dashboard needed. Three hosting-provider IPs carry 91%, with real SDK user
agents. Automated clients retrying dead credentials, not a scanner.
The lower(key_alias) = lower(discord_user) join does NOT answer "which members have no
key" — 21 of 60 apparent misses had consumed 199,529 metered rows in 30 days. Recorded so
nobody re-derives it.
Reopen if the volume changes shape — a jump in distinct source IPs would mean something
different from three stuck agents, and that is worth a WAF rule.
Activity
sre-helmcode commentedon Sep 12, 2026
Still happening, and now attributable. Measured 2026-09-12.
Volume
115 responses of 401 against
litellm-communityin an 8-minute window(2026-09-12 10:57:21Z → 11:05:27Z), from Traefik access logs. That is ~860/hour,
consistent with what the hook logs (~160 per 20 minutes across the 8 pods).
Origin — answerable without the Cloudflare dashboard
Traefik logs carry
request_Cf-Connecting-Ipandrequest_User-Agent, so option 1 of thisissue needs no Cloudflare access:
Three sources carry 91% of it. The user agents are real SDKs, the paths are coherent, and
the rate is steady — this is automated clients retrying dead credentials, not a scanner
probing for valid keys. Two of the three are hosting providers (Oracle Cloud, OVH), so they
are agents running somewhere, not someone at a laptop.
The keys are not ours and never were
92fb6c51db26from the original table is still there a month later; the top offenderthen (
684d383f6140) is gone.What I could NOT establish
Whether these are ex-members whose keys were hard-deleted (the 2026-08-20 mass delete
removed 113 keys, leaving no rows). Nothing in the DB links a fabricated hash back to a
person, and the key-alias join against
membersis not sound enough to answer it — I tried:67 subscribed members appear to have no key when joined on
lower(key_alias) = lower(discord_user), but 21 of the first 60 of those consumed 199,529 metered rows in thelast 30 days, so they do have working keys and the join simply misses aliases that differ
from the Discord handle. Recording that here so nobody re-derives the same wrong number:
that join does not answer this question.
The log-noise argument is weaker than this issue claimed
Measured over 20 minutes across all 8 pods:
It is real but it does not dominate. The actual top ERROR source is unrelated: 794 lines
(39%) are
LiteLLM Redis Caching: async set() - Got exception from REDIS, the same Valkeytimeout family as #51. If the goal is a readable ERROR stream, #51 buys more than this does.
(An earlier draft of this comment said auth was 0% of ERROR. That was a broken grep, not a
measurement.)
Recommendation
Option 2, bounded: rate-limit repeated auth failures per source IP at the edge. It is
auth failure, so it happens before the hook and before any per-key limit — it has to live in
Cloudflare or Traefik. Three IPs at ~860/h is well inside what a WAF rule handles, and it
needs no per-member diagnosis, which is the part that cannot be done from our side.
Option 1 is closed as far as it can go: we know the origins, and they map to nothing we
hold. Option 3 (lowering the log level) treats a symptom that is 17% of the problem.
Not doing either unilaterally — an edge rule is a production change on the request path for
every caller, and it should be a deliberate call, not a side effect of triage.
sre-helmcode commentedon Sep 12, 2026
Closing: investigated, attributed, and not worth the change. Owner decision 2026-09-12.
The measurement above answers the question this issue asked, and the answer is that it does
not justify touching the edge:
Acting would mean a rate-limit rule on the request path of every caller, to save 0.1% of
traffic and 17% of one log stream. That is the wrong trade.
What the investigation did settle, and is worth keeping:
Cf-Connecting-Ip,User-Agent) — noCloudflare dashboard needed. Three hosting-provider IPs carry 91%, with real SDK user
agents. Automated clients retrying dead credentials, not a scanner.
lower(key_alias) = lower(discord_user)join does NOT answer "which members have nokey" — 21 of 60 apparent misses had consumed 199,529 metered rows in 30 days. Recorded so
nobody re-derives it.
cost members: a stranded concurrency slot burns 1 of their 5 for up to 11 minutes.
Reopen if the volume changes shape — a jump in distinct source IPs would mean something
different from three stuck agents, and that is worth a WAF rule.