Skip to content

feat(proxy)!: default audit logs on for enterprise licenses - #37518

Merged
yucheng-berri merged 3 commits into
litellm_internal_stagingfrom
litellm_default_audit_logs_enterprise
Aug 20, 2026
Merged

feat(proxy)!: default audit logs on for enterprise licenses#37518
yucheng-berri merged 3 commits into
litellm_internal_stagingfrom
litellm_default_audit_logs_enterprise

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

TLDR

Problem this solves:

  • Enterprise admins record nothing until they add a setting
  • Audit logging is already premium-only, so off-by-default is friction
  • LITELLM_STORE_AUDIT_LOGS was ignored by the key hooks

How it solves it:

  • store_audit_logs is now tri-state, unset means auto
  • Unset plus a premium license resolves to enabled
  • One resolver replaces ~15 scattered gate checks
  • Explicit store_audit_logs: false stays a hard opt-out

User Flow

Before: an admin running an enterprise license creates a virtual key, then finds the audit trail empty because a setting they never knew about was missing

  1. They start the proxy with their enterprise license and no store_audit_logs in the config
  2. They send POST http://localhost:4000/key/generate with the master key and a key_alias, and get back 200 with a new sk- key
  3. They send GET http://localhost:4000/audit to review who created that key, and get {"audit_logs": [], "total": 0}
  4. Nothing they did through the admin API is recoverable for a compliance review until they find store_audit_logs: true, add it, and restart

After: the same admin gets the audit trail without configuring anything, and can still turn it off

  1. They start the proxy with their enterprise license and no store_audit_logs in the config
  2. They send the same POST http://localhost:4000/key/generate and get back 200 with a new sk- key
  3. They send GET http://localhost:4000/audit and see an entry with "action": "created", the key id, and the admin who created it, with the key value itself masked
  4. If they do not want it, they set store_audit_logs: false under litellm_settings and restart, and keys created after that are recorded nowhere

Admins without an enterprise license see no change, since audit entries were already refused for them and unset still means off.

Relevant issues

Linear ticket

Resolves LIT-5847

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have added meaningful tests
  • My PR passes all CI/CD checks (e.g., lint, format, unit tests)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have received a Greptile Confidence Score of at least 4/5 before requesting a maintainer review (Greptile reviews automatically once the PR is opened; only comment @greptileai to re-request a review after pushing changes)

Delays in PR merge?

If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).

Screenshots / Proof of Fix

Shared setup: one proxy on localhost:4000 against postgres, started the same way for every case with a real enterprise license in LITELLM_LICENSE, using litellm/proxy/dev_config.yaml, which carries no store_audit_logs and no audit_log_callbacks. The opt-out case uses a copy of that config with litellm_settings.store_audit_logs: false added. API responses below are trimmed to the fields that matter, and the UI screenshots come from a separate run with the audit table truncated first, where the Updated / Users row is written by the Admin UI login itself, not by the key creations

LITELLM_LICENSE="$LITELLM_LICENSE" uv run --no-sync litellm --config <config> --detailed_debug --port 4000

The two totals in the After section count rows left in the same postgres by earlier runs, so the signal is which object_id values are present, not the count itself

Before (a1afc2f)

Default, no store_audit_logs

  1. Create a key on the enterprise license
$ curl -s -X POST http://localhost:4000/key/generate -H "Authorization: Bearer sk-1234" -H "Content-Type: application/json" -d '{"key_alias": "audit-default-demo-before"}'
{"key_alias": "audit-default-demo-before", ..., "token_id": "eec838aa078bca75...", "created_by": "default_user_id"}
  1. Read the audit trail, nothing was recorded
$ curl -s "http://localhost:4000/audit?page=1&page_size=5" -H "Authorization: Bearer sk-1234"
{"audit_logs": [], "total": 0, "page": 1, "page_size": 5, "total_pages": 0}

Explicit store_audit_logs false

  1. Not run at the merge base, where off was already the only behavior

After (25de920)

Default, no store_audit_logs

  1. Create a key with the same call and the same config
$ curl -s -X POST http://localhost:4000/key/generate -H "Authorization: Bearer sk-1234" -H "Content-Type: application/json" -d '{"key_alias": "audit-default-demo-after-default-25de920930"}'
{"key_alias": "audit-default-demo-after-default-25de920930", ..., "token_id": "b7be25eba8f3b299...", "created_by": "default_user_id"}
  1. The creation is now recorded, attributed to the admin, pointing at the new key, with the key value masked
$ curl -s "http://localhost:4000/audit?page=1&page_size=5" -H "Authorization: Bearer sk-1234"
{"audit_logs": [{"changed_by": "default_user_id", "changed_by_api_key": "litellm_proxy_master_key",
  "action": "created", "table_name": "LiteLLM_VerificationToken",
  "object_id": "b7be25eba8f3b29914227ce3d7511200744546c9afef3fc26ddcdb37cb39cf5",
  "before_value": null,
  "updated_values": {"key": "sk-…*****S1lg", "key_alias": "audi****0930", "created_by": "default_user_id", ...}}, ...],
 "total": 2, "page": 1, "page_size": 5, "total_pages": 1}
  1. Creating a key named ui-audit-default-on in the Admin UI puts a Created / Keys row on http://localhost:4000/ui/logs/audit

Audit Logs tab showing the Created Keys row

  1. Its detail drawer shows the acting admin and the after-values, with key and key_name masked

Audit log drawer with masked key values

  1. Scrolling the same drawer shows key_alias masked too

Audit log drawer scrolled to key_alias

Explicit store_audit_logs false

  1. Restart with store_audit_logs: false and create another key on the same enterprise license
$ curl -s -X POST http://localhost:4000/key/generate -H "Authorization: Bearer sk-1234" -H "Content-Type: application/json" -d '{"key_alias": "audit-default-demo-after-opt-out-25de920930"}'
{"key_alias": "audit-default-demo-after-opt-out-25de920930", ..., "created_by": "default_user_id"}
  1. The trail is unchanged, still the two rows from the default runs and nothing for the opt-out key
$ curl -s "http://localhost:4000/audit?page=1&page_size=5" -H "Authorization: Bearer sk-1234"
{"audit_logs": [{"object_id": "b7be25eba8f3b29914227ce3d7511200744546c9afef3fc26ddcdb37cb39cf5", "action": "created", ...},
                {"object_id": "05ad32b5d72f55f1ea58ca6f4186b12fa52e0524c3b0b210a9edf82c090e965a", "action": "created", ...}],
 "total": 2, "page": 1, "page_size": 5, "total_pages": 1}
  1. Same in the UI: creating ui-audit-optout-should-not-log adds no row

Audit Logs tab unchanged after the opt-out key

No license

  1. Running with the license unset and the default config, creating ui-audit-no-license adds no row either

Audit Logs tab unchanged without a license

Type

🆕 New Feature

Caveats (if any)

  • Nothing on the LLM request path writes audit entries, so inference latency is untouched. Default-on adds one JSON serialization plus one insert per admin mutation, and most call sites already dispatch that write in the background, so the worst case is high-rate programmatic key provisioning
  • LiteLLM_AuditLog has no index beyond its primary key and no retention job, and GET /audit sorts by updated_at with a full count, so turning this on by default makes that growth common. Tracked in LIT-5848 rather than here
  • Several team and key-rotation tests mock Prisma rows that cannot serialize into an audit payload, so they now opt out of audit logging explicitly instead of relying on the old off-by-default
  • MCP management endpoints keep their existing empty audit stubs, this PR does not fill them in

Final Attestation

  • The tests check the right things, including the edge cases, and regressions in the respective real-world customer use-cases are not possible after this PR

Link to Devin session: https://app.devin.ai/sessions/e80aa2b7e845458f8a7a5c09bf013abe
Requested by: @yucheng-berri

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@yucheng-berri yucheng-berri self-assigned this Aug 19, 2026
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@greptile-apps

greptile-apps Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR makes audit logging tri-state and enables it by default for premium deployments while preserving explicit configuration and environment overrides.

  • Centralizes audit enablement in is_audit_logging_enabled.
  • Applies the resolver across key, user, team, and configuration mutation paths.
  • Adds precedence and premium-default tests while isolating unrelated mocked tests from audit side effects.

Confidence Score: 5/5

The PR appears safe to merge.

The previously reported plaintext-key concern was withdrawn correctly because audit models mask key fields before both database and callback sinks, and no blocking failure remains.

Important Files Changed

Filename Overview
litellm/proxy/management_helpers/audit_logs.py Adds the centralized config/environment/license resolver and applies it before audit persistence and callback dispatch.
litellm/proxy/hooks/key_management_event_hooks.py Routes key lifecycle auditing through the resolver; generated key fields are masked before reaching audit sinks.
litellm/proxy/proxy_server.py Uses resolved audit enablement when reporting callback initialization without changing callback registration.
tests/test_litellm/proxy/management_helpers/test_audit_log_callbacks.py Covers resolver precedence, premium defaults, explicit opt-out, and non-premium persistence behavior.

Reviews (3): Last reviewed commit: "test(proxy): disable audit logging for k..." | Re-trigger Greptile

Comment thread litellm/proxy/hooks/key_management_event_hooks.py
@codecov

codecov Bot commented Aug 19, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.89189% with 3 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
litellm/proxy/hooks/key_management_event_hooks.py 75.00% 1 Missing ⚠️
...y/management_endpoints/mcp_management_endpoints.py 66.66% 1 Missing ⚠️
litellm/proxy/proxy_server.py 0.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

yucheng-berri and others added 2 commits August 19, 2026 21:43
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@yucheng-berri

Copy link
Copy Markdown
Contributor

@greptileai review latest head

@yucheng-berri

Copy link
Copy Markdown
Contributor

bugbot run

@codspeed-hq

codspeed-hq Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing litellm_default_audit_logs_enterprise (25de920) with litellm_internal_staging (4d100bd)1

Open in CodSpeed

Footnotes

  1. No successful run was found on litellm_internal_staging (b8d5139) during the generation of this report, so 4d100bd was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 25de920. Configure here.

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

@greptileai the plaintext-key concern was withdrawn in thread, please re-review so the summary score reflects the current head

@mateo-berri mateo-berri left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Thanks!

@mateo-berri mateo-berri changed the title feat(proxy): default audit logs on for enterprise licenses feat(proxy)!x: default audit logs on for enterprise licenses Aug 20, 2026
@mateo-berri mateo-berri changed the title feat(proxy)!x: default audit logs on for enterprise licenses feat(proxy)!: default audit logs on for enterprise licenses Aug 20, 2026
@yucheng-berri
yucheng-berri merged commit 3a04860 into litellm_internal_staging Aug 20, 2026
76 of 77 checks passed
@yucheng-berri
yucheng-berri deleted the litellm_default_audit_logs_enterprise branch August 20, 2026 01:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants