mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-12 23:01:41 +00:00
ci: replace demo scripts with auths-dev/sign release workflow
This commit is contained in:
parent
8f236f285f
commit
7906ee1390
3 changed files with 59 additions and 195 deletions
59
.github/workflows/auths-sign-release.yml
vendored
Normal file
59
.github/workflows/auths-sign-release.yml
vendored
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
name: Auths Sign Release Artifacts
|
||||
|
||||
# Runs after the existing publish_to_pypi workflow builds artifacts.
|
||||
# Signs the sdist (.tar.gz) and wheel (.whl) before they are published,
|
||||
# creating .auths.json attestation files that consumers can verify.
|
||||
#
|
||||
# To activate:
|
||||
# 1. Store AUTHS_CI_TOKEN as a repository secret
|
||||
# (generate with: auths init --profile ci --github-action)
|
||||
# 2. Remove the 'if: false' guard below
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
sign:
|
||||
name: Sign release artifacts
|
||||
if: false # Remove this line to activate
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
environment: pypi-publish
|
||||
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Build package
|
||||
run: |
|
||||
python -m pip install --upgrade pip build==1.4.2
|
||||
cp model_prices_and_context_window.json litellm/model_prices_and_context_window_backup.json
|
||||
rm -rf build dist
|
||||
python -m build
|
||||
|
||||
- name: Sign artifacts with Auths
|
||||
uses: auths-dev/sign@v1
|
||||
with:
|
||||
token: ${{ secrets.AUTHS_CI_TOKEN }}
|
||||
files: |
|
||||
dist/*.tar.gz
|
||||
dist/*.whl
|
||||
verify: true
|
||||
note: 'PyPI release ${{ github.sha }}'
|
||||
|
||||
# After this step, each file in dist/ has a corresponding .auths.json
|
||||
# attestation file. These can be:
|
||||
# - Published alongside the package (e.g., as GitHub Release assets)
|
||||
# - Uploaded to the Auths registry (auths artifact publish)
|
||||
# - Verified by consumers: auths artifact verify dist/litellm-*.tar.gz
|
||||
|
||||
- name: Publish to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
# Security Cookbook: Auths Commit Verification
|
||||
|
||||
## Background
|
||||
|
||||
On March 24, 2026, LiteLLM was the target of a supply chain attack. The attacker
|
||||
compromised the Trivy GitHub Action, which exfiltrated the `PYPI_PUBLISH` token from
|
||||
LiteLLM's CI/CD pipeline. The stolen token was used to publish malicious versions
|
||||
(v1.82.7 and v1.82.8) directly to PyPI. The source code on GitHub was never modified.
|
||||
|
||||
The attack succeeded because **there was no cryptographic binding between the published
|
||||
package and a verified maintainer identity**.
|
||||
|
||||
## What is Auths?
|
||||
|
||||
[Auths](https://github.com/auths-dev/auths) provides Ed25519 signatures bound to
|
||||
KERI-based decentralized identifiers (DIDs). With Auths:
|
||||
|
||||
- Every commit and artifact carries a signature from the maintainer's cryptographic identity
|
||||
- The signature is bound to the maintainer's device keychain (not a registry account)
|
||||
- Stealing PyPI/npm credentials is insufficient without the signing key
|
||||
- Verification happens locally — no network calls to a central authority
|
||||
|
||||
## How Auths Addresses the Attack
|
||||
|
||||
The real attack bypassed Git entirely — the attacker published directly to PyPI with
|
||||
no corresponding commit. Commit-level signing alone would not have caught a
|
||||
registry-only publish. However, Auths establishes a verifiable chain: every legitimate
|
||||
release must trace back to a signed commit by an authorized maintainer. A package
|
||||
published without a matching signed commit has no valid attestation chain and would be
|
||||
flagged by consumers and CI pipelines that verify signatures.
|
||||
|
||||
This workflow adds the commit-signing layer via the
|
||||
[`auths-dev/verify`](https://github.com/auths-dev/verify) GitHub Action. A full
|
||||
deployment would also use `auths artifact sign` (via
|
||||
[`auths-dev/sign`](https://github.com/auths-dev/sign)) in the release workflow to
|
||||
bind published packages to signed commits.
|
||||
|
||||
## Running the Simulation
|
||||
|
||||
The simulation script uses the Auths Python SDK to demonstrate the core cryptographic
|
||||
primitive — it shows that only the holder of the maintainer's private key can produce
|
||||
a valid signature:
|
||||
|
||||
```bash
|
||||
pip install auths
|
||||
python auths_attack_simulation.py
|
||||
```
|
||||
|
||||
No CLI installation, git, or ssh-keygen needed — the script uses the SDK directly.
|
||||
|
||||
## Adding Auths to Your Workflow
|
||||
|
||||
See the GitHub Actions workflow at `.github/workflows/auths-verify-commits.yml`
|
||||
and the allowed signers configuration at `.auths/allowed_signers`.
|
||||
|
|
@ -1,141 +0,0 @@
|
|||
"""
|
||||
Auths Attack Simulation: LiteLLM March 24, 2026 Supply Chain Incident
|
||||
|
||||
Demonstrates how Auths cryptographic verification would have detected the
|
||||
unauthorized PyPI publish that compromised LiteLLM v1.82.7 and v1.82.8.
|
||||
|
||||
What happened:
|
||||
1. Attacker compromised the Trivy GitHub Action (March 19)
|
||||
2. LiteLLM's CI ran Trivy without version pinning
|
||||
3. Compromised Trivy exfiltrated the PYPI_PUBLISH token from GitHub Actions
|
||||
4. Attacker used the stolen token to publish malicious versions to PyPI
|
||||
5. The malicious packages contained a credential stealer in a .pth file
|
||||
6. Source code on GitHub was never modified — the attack existed only in PyPI
|
||||
|
||||
How Auths closes this gap:
|
||||
The real attack bypassed Git entirely — the attacker published directly to
|
||||
PyPI with no corresponding commit. Auths establishes a policy that every
|
||||
legitimate release must trace back to a signed action by an authorized
|
||||
maintainer. A package published without a valid signature from a known
|
||||
maintainer identity has no valid attestation and would be rejected.
|
||||
|
||||
This simulation uses the Auths Python SDK to demonstrate the core
|
||||
cryptographic primitive: sign an action with a maintainer's key, then
|
||||
show that verification succeeds for the legitimate release and fails
|
||||
for an unauthorized or tampered one.
|
||||
|
||||
Usage:
|
||||
pip install auths
|
||||
python auths_attack_simulation.py
|
||||
|
||||
Requires: auths (Python SDK)
|
||||
"""
|
||||
import json
|
||||
import sys
|
||||
|
||||
|
||||
def main() -> None:
|
||||
print("=" * 70)
|
||||
print("Auths Attack Simulation: LiteLLM Supply Chain Incident (March 24, 2026)")
|
||||
print("=" * 70)
|
||||
print()
|
||||
|
||||
try:
|
||||
from auths import generate_inmemory_keypair, sign_action, verify_action_envelope
|
||||
except ImportError:
|
||||
print("The 'auths' Python SDK is not installed.")
|
||||
print()
|
||||
print("Install it with:")
|
||||
print(" pip install auths")
|
||||
print()
|
||||
print("Or visit: https://github.com/auths-dev/auths")
|
||||
sys.exit(0)
|
||||
|
||||
# Generate ephemeral identities — no filesystem, no keychain needed
|
||||
maintainer_priv, maintainer_pub, maintainer_did = generate_inmemory_keypair()
|
||||
attacker_priv, _attacker_pub, attacker_did = generate_inmemory_keypair()
|
||||
|
||||
# ── Step 1: Legitimate maintainer signs a release ──────────────────
|
||||
print("[1] Legitimate maintainer signs release v1.82.6...")
|
||||
print()
|
||||
|
||||
release_payload = json.dumps({
|
||||
"package": "litellm",
|
||||
"version": "1.82.6",
|
||||
"digest": "sha256:abc123def456...",
|
||||
"registry": "pypi",
|
||||
})
|
||||
|
||||
legitimate_envelope = sign_action(
|
||||
maintainer_priv, "release", release_payload, maintainer_did,
|
||||
)
|
||||
|
||||
result = verify_action_envelope(legitimate_envelope, maintainer_pub)
|
||||
print(f" Signed by: {maintainer_did}")
|
||||
print(f" Verification: {'PASSED' if result.valid else 'FAILED'}")
|
||||
print()
|
||||
|
||||
# ── Step 2: Attacker publishes with stolen PyPI token ──────────────
|
||||
print("[2] Attacker publishes v1.82.7 using stolen PyPI token...")
|
||||
print(" (Attacker has registry credentials but NOT the maintainer's signing key)")
|
||||
print()
|
||||
|
||||
malicious_payload = json.dumps({
|
||||
"package": "litellm",
|
||||
"version": "1.82.7",
|
||||
"digest": "sha256:malicious_payload_hash...",
|
||||
"registry": "pypi",
|
||||
})
|
||||
|
||||
# Attacker signs with their own key — NOT the maintainer's
|
||||
attacker_envelope = sign_action(
|
||||
attacker_priv, "release", malicious_payload, attacker_did,
|
||||
)
|
||||
|
||||
# Verify against the MAINTAINER's public key (the only trusted key)
|
||||
result = verify_action_envelope(attacker_envelope, maintainer_pub)
|
||||
print(f" Signed by: {attacker_did}")
|
||||
print(f" Verification against maintainer key: {'PASSED' if result.valid else 'FAILED'}")
|
||||
if result.error:
|
||||
print(f" Reason: {result.error}")
|
||||
print()
|
||||
|
||||
# ── Step 3: Show tampered legitimate envelope also fails ───────────
|
||||
print("[3] Attacker tampers with a legitimately-signed envelope...")
|
||||
print()
|
||||
|
||||
envelope = json.loads(legitimate_envelope)
|
||||
envelope["payload"]["version"] = "1.82.7"
|
||||
envelope["payload"]["digest"] = "sha256:malicious_payload_hash..."
|
||||
tampered_json = json.dumps(envelope)
|
||||
|
||||
result = verify_action_envelope(tampered_json, maintainer_pub)
|
||||
print(f" Original signer: {maintainer_did}")
|
||||
print(f" Tampered payload version: 1.82.7")
|
||||
print(f" Verification: {'PASSED' if result.valid else 'FAILED'}")
|
||||
if result.error:
|
||||
print(f" Reason: {result.error}")
|
||||
print()
|
||||
|
||||
# ── Summary ────────────────────────────────────────────────────────
|
||||
print("-" * 70)
|
||||
print("SUMMARY")
|
||||
print()
|
||||
print(" v1.82.6 (legitimate, signed by maintainer): VERIFIED")
|
||||
print(" v1.82.7 (attacker's key, not trusted): REJECTED")
|
||||
print(" v1.82.7 (tampered legitimate envelope): REJECTED")
|
||||
print()
|
||||
print("NOTE: The real March 24 attack bypassed Git entirely — the attacker")
|
||||
print("published directly to PyPI with no commit at all. This simulation")
|
||||
print("demonstrates the cryptographic primitive that Auths provides: only")
|
||||
print("the holder of the maintainer's private key can produce a valid")
|
||||
print("signature. In a full deployment, the CI/CD pipeline would use")
|
||||
print("'auths artifact sign' to bind the published package to the")
|
||||
print("maintainer's identity, and consumers would verify before installing.")
|
||||
print()
|
||||
print("Learn more: https://github.com/auths-dev/auths")
|
||||
print("=" * 70)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Add table
Reference in a new issue