SafetyCard Request a review

LiteLLM

Open-source AI gateway (proxy server) and Python SDK that unifies 100+ LLM providers behind an OpenAI-compatible API, with virtual keys, spend tracking, guardrails, an MCP gateway and A2A agent routing.

github.com/BerriAI/litellm · 2026-10-04 · a2bf67a

Defense-in-depth score

4.5 / 10

LiteLLM's gateway puts strong authentication and per-key authorization in front of every model and MCP tool call: it refuses to boot without a real master key, MCP servers are deny-by-default per key, and model-written code only runs in a remote sandbox. The dominant risk is what happens once tools run server-side: the README's lead example sets require_approval to never, after which the gateway executes model-chosen MCP calls with shared upstream credentials, for up to five rounds on streaming, with no taint tracking and no default spend cap. Config-named plugins and callbacks load in-process with no integrity check, and the shipped docker-compose defaults are not locked down.

Safest setup

LiteLLM checks every model and MCP tool call against the calling key, keeps MCP servers off for a key until you grant them, and hands tool calls back to your application unless a request asks it to run them. Credentials behind the gateway are shared, spend has no ceiling by default and the shipped docker-compose defaults are not locked down, so the safest setup starts from your own deployment settings, narrows each identity and turns on the limits LiteLLM already includes.

SetupHowWhat it gives you
Scoped upstream credentialsGive each provider key and MCP server credential only the access its tools need, read-only where possible, with a spending limit set at the providerThe upstream services themselves refuse anything outside that grant, whatever a caller or the model asks for C1
Per-user MCP credentialsSet auth_type to oauth2_token_exchange on each MCP server that supports itEach tool call acts with the requesting user's own grants instead of one shared credential C1
A model list per keySet models on every virtual key to the models its caller needsEach key reaches only the models you chose, not every model the gateway serves C1
Approval in your appKeep require_approval unset on MCP tool references in requestsThe gateway returns each tool call to your application, so it runs only after your app or a person approves it C2
Read-only tool listsSet allowed_tools on each MCP server to the tools the task needs, read tools where the task allowsKeys granted a server see and call only the tools you listed C3
Pinned tool catalogsSet pinned_tools on each MCP server to the tool definitions you reviewedThe model sees the definitions you approved, unpinned tools stay blocked, and changes upstream raise an alert C7
Reviewed plugin modulesName only callback, guardrail and router-plugin modules you have read, pinned to a version and shipped inside your own imageOnly code you reviewed runs inside the process that holds the gateway's keys C7
Spend and rate ceilingsSet max_budget, rpm_limit and tpm_limit on every virtual keyEach key stops at a spend ceiling and a request and token rate you chose C10

Approving with confidence

  • Read each tool call your application receives and approve calls one at a time.
  • Reserve require_approval: never for servers whose allowed tools only read; on streaming Responses the gateway then runs up to five rounds of model-chosen calls on its own.

Handling data

  • Spend logs record each tool call's arguments with the key, user and team; store them like the data those tools reach.
  • Set LITELLM_SALT_KEY so stored credentials are encrypted under a key separate from the master key.
  • Leave LITELLM_DISABLE_REDACT_SECRETS unset, and set turn_off_message_logging when third-party logging callbacks are on.

Key gaps (1)

  1. Config-named callback, guardrail and router-plugin modules (including ones fetched from S3/GCS) are executed in the gateway process with no integrity check, giving a malicious plugin every provider key, the master/salt key and the database. C7 · Third-party extensions

Criteria

C1 Identity & least privilege

0.53 / 1.00

The gateway refuses to start without a strong master key, and every request is then authorised in code against the calling virtual key, team, user and organisation before the gateway attaches its own credentials. MCP servers are deny-by-default per key (only servers granted to the key or team, or marked allow_all_keys, are reachable), and per-key tool permissions are re-checked on every tool call. The credentials behind that check are static and shared: the operator's provider API keys and each MCP server's single configured credential serve every caller, and a key with no model list can call every model. Per-user OAuth, token exchange and passthrough modes exist for MCP but are opt-in.

C2 Approval gates

0.33 / 1.00

When a caller attaches gateway MCP tools to /chat/completions or /responses, LiteLLM executes the model's tool calls itself only if every MCP tool reference in the request sets require_approval to "never"; otherwise the exact tool calls are handed back to the calling application unexecuted. That check is deterministic and the model cannot set it, but it is a blanket per-request switch held by the caller, there is no human approval step or risk tiering in the gateway, and the README's lead MCP example turns auto-execution on against a GitHub MCP server. Once enabled, upstream tool actions are as irreversible as the upstream makes them.

C3 Tool & action scoping

0.53 / 1.00

Tool scoping is by name: each MCP server can carry an allowed or disallowed tool list, an optional allowed-parameter-name list per tool, and pinned tool definitions, and per-key and per-team tool permissions narrow further. All of these checks run in one pre-call function that every gateway tool call goes through. Argument values are not validated against bounds or allowlists, and by default every tool an upstream server lists is exposed to any key that has the server. User-supplied URLs fetched by the gateway (images, files) go through an SSRF guard that rechecks redirects.

C4 Code-execution isolation

Strong 0.70 / 1.00

In the default gateway nothing executes model-written code: Jinja chat templates render in Jinja's immutable sandbox, operator-written custom-code guardrails run under RestrictedPython, and stdio MCP servers are disabled unless an environment flag is set. The one model-code path, the opt-in code-interpreter interception callback, always runs code in a remote sandbox service (E2B or OpenSandbox) and raises an error rather than falling back to local execution. Those sandboxes get internet access by default, and stdio MCP servers, when enabled, run as host subprocesses (root in the shipped image) with a scrubbed environment.

C5 Untrusted input blast radius

0.28 / 1.00

MCP tool results and upstream tool descriptions enter the model's context like any other message, with no provenance tagging or taint tracking by default. When auto-execution is on, the streaming Responses path lets the model choose further tool calls after reading tool output, for up to five rounds, so injected tool output can drive actions with the shared MCP credentials. By default the gateway returns tool calls to the calling application instead of running them. An opt-in tool_policy guardrail blocks tools marked trusted-input once untrusted tool output is in a chat conversation, and many third-party injection-detection guardrails can be enabled.

C6 Memory, context & configuration integrity

0.50 / 1.00

The model has no memory tool and the gateway auto-loads no workspace instruction files; configuration comes from the operator's config file or database. The /v1/memory store is written and read by applications and is filtered to the caller's user or team in the query, and it is never injected into prompts automatically. Responses sessions can be rebuilt from stored spend logs by previous_response_id (prompt content is only stored when store_prompts_in_spend_logs is enabled); session replay is not equally scoped. Cached or replayed context is not validated or provenance-tagged.

C7 Third-party extensions

0.23 / 1.00

Third-party code reaches the gateway three ways: Python callbacks, guardrails and router plugins named in the config (including modules downloaded from S3 or GCS) are imported and executed in-process with no hash or signature check; stdio MCP servers run operator-chosen commands as subprocesses when explicitly enabled; and remote MCP servers are connected over HTTP. Only proxy admins can add servers (user submissions need admin approval and cannot be stdio), and stdio subprocesses get a minimal environment. Pinning an MCP server's tool catalog, which serves the pinned definitions and alerts on drift, is available but off by default.

C8 Secrets & sensitive-data protection

0.38 / 1.00

Credentials stored in the database are encrypted with a key derived from LITELLM_SALT_KEY (falling back to the master key), virtual keys are stored hashed, log records pass through a credential-redaction filter by default, prompts are not written to spend logs unless enabled, and there is no vendor telemetry. Secrets are not placed in model context, but nothing scans tool results or model-bound messages for secrets. The shipped docker-compose file's defaults are not locked down, and the redaction filter can be turned off with an environment variable.

C9 Audit & traceability

0.50 / 1.00

Every gateway MCP tool call is logged through the same logging object as model calls, and the spend-log row carries the tool name, arguments, server, key, user, team and trace id, stored in Postgres outside anything the model can touch. Denied calls are logged as failures. There is no approval record because the gateway has no approval step, and change audit logs for keys, teams and MCP servers are written only for enterprise (premium) deployments. Logging is best-effort: setup failures are swallowed at debug level and spend logs are batched asynchronously.

C10 Limits & kill switch

0.50 / 1.00

Server-side tool execution is bounded: one round on the non-streaming paths and at most five rounds on streaming Responses, MCP calls time out after 60 seconds, and requests after 6000 seconds. Key, team and user budgets, RPM/TPM limits and a per-session iteration limiter are enforced in code but are all unset by default, so out of the box there is no spend ceiling. Blocking a key stops new requests but does not cancel calls already in flight.