From 7906ee1390b4b89dabf38bb81d7c34f7583efcbb Mon Sep 17 00:00:00 2001 From: bordumb Date: Sun, 5 Apr 2026 01:21:13 -0700 Subject: [PATCH] ci: replace demo scripts with auths-dev/sign release workflow --- .github/workflows/auths-sign-release.yml | 59 ++++++++ cookbook/security/README.md | 54 ------- cookbook/security/auths_attack_simulation.py | 141 ------------------- 3 files changed, 59 insertions(+), 195 deletions(-) create mode 100644 .github/workflows/auths-sign-release.yml delete mode 100644 cookbook/security/README.md delete mode 100644 cookbook/security/auths_attack_simulation.py diff --git a/.github/workflows/auths-sign-release.yml b/.github/workflows/auths-sign-release.yml new file mode 100644 index 00000000000..fb61653064a --- /dev/null +++ b/.github/workflows/auths-sign-release.yml @@ -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 diff --git a/cookbook/security/README.md b/cookbook/security/README.md deleted file mode 100644 index 61f4c1cec3a..00000000000 --- a/cookbook/security/README.md +++ /dev/null @@ -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`. diff --git a/cookbook/security/auths_attack_simulation.py b/cookbook/security/auths_attack_simulation.py deleted file mode 100644 index 766d0b506eb..00000000000 --- a/cookbook/security/auths_attack_simulation.py +++ /dev/null @@ -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()