JWT auth validated Entra app tokens but never carried an agent identity
into the authenticated principal, so agent policies (trace id
requirement, per-agent MCP restrictions, agent spend attribution) only
applied to virtual keys bound to an agent. A new litellm_jwtauth field,
agent_id_jwt_field, names the claim (dot notation supported) that is
matched against a registered agent's id, then name; the canonical
agent_id flows through the standard and proxy-admin JWT paths, and a
configured claim naming no registered agent fails closed with 403
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
CredentialLiteLLMParams omitted tenant_id, client_id, client_secret,
azure_scope, azure_username and azure_password, so the strict dump used
by credential reuse and Azure client init dropped them and the reused
credential ended with no auth at all
Co-authored-by: yassin <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
The e2e harness exists to prove product features end to end against a live
proxy. The prior Hard Rule carved out an exception for "tests that cover the
harness itself" and pointed at coverage_registry/test_collector.py, which in
practice invited unit tests of harness helpers to be staged alongside e2e
work. That is the wrong tool: harness logic that is worth locking down does
not need a mock-driven unit test living under tests/e2e.
Drop the carve-out. The Hard Rule now reads that no unit tests of any kind
belong under tests/e2e, and the passing mention of unmarked harness coverage
in the transport section is removed so the doc no longer contradicts itself.
coverage_registry/test_collector.py still exists on disk and is left in place
for now; whether to relocate or remove it is a separate decision.
Keeps the base's rule that a non-admin id lookup matching no spend-log row answers 403, so the detail route never consults cold storage without an owner row
A guardrail that answers one rewritten text per message it saw no longer
matches the texts the Responses handler extracted once the request carries
instructions or tool items, so the rewrite was rejected with a 500. Spread
such an answer over the structured messages' text slots and write it back
through the structured path, have Prompt Security modify return
structured_messages directly, and give the chat completions pairing the same
named rejection instead of a silent misalignment when the counts differ.
The notice now asks the sweep's own duplicateTarget whether the title
match would close the issue, so a two-word title no longer gets a close
warning the sweep would refuse to act on. The ask no longer promises
that a reply removes the label, since nothing does that automatically
A verdict is now dropped when it names a pull request, the issue itself,
or a newer issue, and the label goes on before the comment so a failed
comment leaves no marker and the rerun finishes the job. The flag logic
lives in scripts/flag-duplicate-issue.ts next to the sweep it feeds,
sharing normalizeTitle and the marker format, with bun tests that run on
pull requests touching it
A run over MAX_BASE64_LENGTH_STDOUT_LOG now stays in the log line only when it is hex or decimal with at least two distinct characters. Collapsing only mixed-case runs let every constant-byte payload through: 0x00 encodes to AAAA, 0x01 to AQEB, 0x55 to VVVV, 0xAA to qqqq, so a zero-filled upload still paid the full secret regex.
The two traceback tests that raised a 100,000-character run of one letter now raise the same text the other length-cap tests use, since a single-letter run is exactly the shape the collapse treats as a constant-byte payload
Only mixed-case runs of the base64 alphabet collapse now, so a long hex digest,
numeric id, or padding run stays in the debug line. The truncation filter also
formats the traceback at every level and collapses base64 runs in it before the
secret regex sees it, instead of only capping its length at INFO and above
The old check_duplicate_issues.yml matched on title wording, so it missed the
same bug reported in different words. Over one full week of new issues (167,
5 to 12 Sep) it flagged 2, both wrong, while hand review found 11 real
duplicates that nothing caught.
The new workflow fetches the issue through the API into a file, runs
openai/codex-action with a fixed prompt and an output schema, and lets Codex
search the tracker with gh. At a 0.95 confidence gate it would have posted 12
comments that week, 9 naming a real duplicate. It reuses the same marker
comment and potential-duplicate label as before so auto-close-duplicates.yml
keeps working unchanged, and warns about the auto-close only when the titles
actually match.
Traffic goes through LiteLLM: the key is a virtual key and the endpoint is
the proxy's /v1/responses. Comments and labels stay off until the
DUPLICATE_CHECK_ENABLED repo variable is set.
Since #37391 every log record went through the secret-redaction regex twice, once in the
filter and again in the formatter, and the formatter pass ran on the whole formatted line. At
DEBUG level a multi-megabyte request body (a multi-page PDF upload to /v1/ocr) turned each of
those lines into ten seconds of synchronous regex work on the event loop, long enough for a
Kubernetes liveness probe to restart the pod mid-request.
The filter is now the complete scrubber (message, exception text, stack info, and extras) and
stamps the record, so the formatters skip records that are already clean. The stdout
truncation filter also collapses base64 runs longer than MAX_BASE64_LENGTH_STDOUT_LOG (4096
by default) at every level before the secret regex sees them, so a debug line carrying a
request body costs milliseconds instead of seconds.
* fix(proxy): bound tool and guardrail index create_many by the spend-log statement budgets
One flush drains up to MAX_LOGS_PER_INTERVAL source transactions or logs, but a
transaction fans out to one LiteLLM_SpendLogToolIndex row per tool and a log
to one LiteLLM_SpendLogGuardrailIndex row per guardrail, so the index
create_many payload was unbounded. Both index writes now go through
spend_log_write_batches(SPEND_LOG_WRITE_BATCH_MAX_BYTES, SPEND_LOG_WRITE_BATCH_MAX_ROWS).
The tool index write moves out of the rollup batch_() so the split reduces
the query-engine payload; replayed index rows are no-ops under
skip_duplicates, and the daily rollup upserts stay in one transaction
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* test(proxy): pin the row budget in the index fan-out tests
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
---------
Co-authored-by: yucheng <yucheng@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>