ci: add Auths cryptographic commit verification (warn-only)

This commit is contained in:
bordumb 2026-04-03 19:03:30 -07:00
parent 127149c263
commit 73f6b82f58
No known key found for this signature in database
4 changed files with 269 additions and 0 deletions

23
.auths/allowed_signers Normal file
View file

@ -0,0 +1,23 @@
# Auths Allowed Signers
#
# This file lists the Ed25519 public keys of maintainers authorized to sign
# commits for this repository using Auths (https://github.com/auths-dev/auths).
#
# With Auths, every commit carries an Ed25519 signature bound to the
# maintainer's KERI-based decentralized identifier (DID). This creates a
# cryptographic chain of custody that cannot be forged with stolen registry
# credentials alone.
#
# Format:
# <DID>@auths.local namespaces="git" ssh-ed25519 <base64-public-key>
#
# To add your key:
# 1. Install Auths: pip install auths
# 2. Create identity: auths init
# 3. Configure Git: auths git setup
# 4. Export signers: auths git allowed-signers --output .auths/allowed_signers
# 5. Commit and push this file
#
# Documentation: https://github.com/auths-dev/auths/blob/main/docs/guides/platforms/ci-cd.md
#
# Maintainers — add your keys below this line:

View file

@ -0,0 +1,37 @@
name: Auths Commit Verification
on:
push:
branches: [main]
pull_request:
branches: [main]
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
permissions: {}
jobs:
verify:
name: Verify commit signatures
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
with:
fetch-depth: 0
persist-credentials: false
- name: Verify commits with Auths
uses: auths-dev/auths-verify-github-action@v1 # TODO: pin to SHA once stable
with:
allowed-signers: .auths/allowed_signers
fail-on-unsigned: 'false'
skip-merge-commits: 'true'
post-pr-comment: 'true'
github-token: ${{ secrets.GITHUB_TOKEN }}

View file

@ -0,0 +1,36 @@
# 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 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
## Running the Simulation
The simulation script recreates the attack scenario and demonstrates how Auths
verification catches the unauthorized commit:
```bash
pip install auths
python auths_attack_simulation.py
```
## 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`.

View file

@ -0,0 +1,173 @@
"""
Auths Attack Simulation: LiteLLM March 24, 2026 Supply Chain Incident
Demonstrates how Auths cryptographic commit 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
Why Auths prevents this:
With Auths, every release artifact carries an Ed25519 signature from the
maintainer's cryptographic identity (KERI-based DID). Stealing the PyPI
token is insufficient the attacker cannot produce a valid signature
without the maintainer's private key stored in their device keychain.
Usage:
pip install auths
python auths_attack_simulation.py
"""
import os
import shutil
import subprocess
import sys
import tempfile
def check_auths_cli() -> bool:
"""Check if the auths CLI is available."""
return shutil.which("auths") is not None
def run(cmd: list[str], cwd: str, check: bool = True) -> subprocess.CompletedProcess:
"""Run a command and return the result."""
return subprocess.run(
cmd,
cwd=cwd,
capture_output=True,
text=True,
check=check,
)
def setup_test_repo(tmpdir: str) -> str:
"""Create a temporary git repo with signed and unsigned commits."""
repo = os.path.join(tmpdir, "litellm-simulation")
os.makedirs(repo)
# Initialize repo
run(["git", "init"], cwd=repo)
run(["git", "config", "user.email", "maintainer@example.com"], cwd=repo)
run(["git", "config", "user.name", "LiteLLM Maintainer"], cwd=repo)
# Generate a test Ed25519 keypair for the "legitimate maintainer"
key_path = os.path.join(tmpdir, "test_key")
run(
["ssh-keygen", "-t", "ed25519", "-f", key_path, "-N", "", "-q"],
cwd=tmpdir,
)
# Configure git to sign with this key
run(["git", "config", "gpg.format", "ssh"], cwd=repo)
run(["git", "config", "user.signingkey", key_path], cwd=repo)
# Create allowed_signers file
pub_key_content = open(f"{key_path}.pub").read().strip()
signers_path = os.path.join(repo, ".auths")
os.makedirs(signers_path)
with open(os.path.join(signers_path, "allowed_signers"), "w") as f:
f.write(f"maintainer@example.com {pub_key_content}\n")
run(["git", "config", "gpg.ssh.allowedSignersFile", os.path.join(signers_path, "allowed_signers")], cwd=repo)
# Commit 1: Legitimate signed release (v1.82.6)
with open(os.path.join(repo, "litellm_version.py"), "w") as f:
f.write('version = "1.82.6"\n')
run(["git", "add", "."], cwd=repo)
run(["git", "commit", "-S", "-m", "release: v1.82.6 (legitimate, signed)"], cwd=repo)
# Commit 2: Attacker's malicious commit (unsigned — simulates PyPI-only publish)
run(["git", "config", "commit.gpgSign", "false"], cwd=repo)
with open(os.path.join(repo, "litellm_version.py"), "w") as f:
f.write('version = "1.82.7"\n')
with open(os.path.join(repo, "litellm_init.pth"), "w") as f:
f.write("# Simulated malicious payload — credential stealer\n")
f.write("import os; os.environ.get('AWS_SECRET_ACCESS_KEY') # exfiltrate\n")
run(["git", "add", "."], cwd=repo)
run(["git", "commit", "-m", "release: v1.82.7 (MALICIOUS — unsigned)"], cwd=repo)
return repo
def run_verification(repo: str) -> None:
"""Run auths verification and display results."""
result = run(
["auths", "verify", "HEAD~1..HEAD", "--allowed-signers", ".auths/allowed_signers"],
cwd=repo,
check=False,
)
if result.returncode != 0:
print(" BLOCKED: Unsigned commit detected")
if result.stdout:
print(f" Output: {result.stdout.strip()}")
if result.stderr:
print(f" Detail: {result.stderr.strip()}")
else:
print(" PASSED: All commits verified")
if result.stdout:
print(f" Output: {result.stdout.strip()}")
def main() -> None:
print("=" * 70)
print("Auths Attack Simulation: LiteLLM Supply Chain Incident (March 24, 2026)")
print("=" * 70)
print()
if not check_auths_cli():
print("The 'auths' CLI 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)
with tempfile.TemporaryDirectory() as tmpdir:
print("[1] Setting up simulation repository...")
repo = setup_test_repo(tmpdir)
print(" Created repo with 2 commits:")
print(" - v1.82.6: Legitimate release, signed by maintainer")
print(" - v1.82.7: Attacker's malicious version, unsigned")
print()
# Verify the legitimate commit
print("[2] Verifying legitimate commit (v1.82.6)...")
result = run(
["auths", "verify", "HEAD~2..HEAD~1", "--allowed-signers", ".auths/allowed_signers"],
cwd=repo,
check=False,
)
if result.returncode == 0:
print(" PASSED: Commit is signed by an authorized maintainer")
else:
print(" Result: ", result.stdout.strip() if result.stdout else result.stderr.strip())
print()
# Verify the malicious commit
print("[3] Verifying attacker's commit (v1.82.7)...")
run_verification(repo)
print()
# Summary
print("-" * 70)
print("RESULT: The attacker's unsigned commit would have been flagged.")
print()
print("In the real attack, the attacker used a stolen PyPI token to publish")
print("malicious packages directly to the registry. The source code on GitHub")
print("was never modified. With Auths, even if registry credentials are stolen,")
print("the attacker cannot produce a valid Ed25519 signature — the private key")
print("is bound to the maintainer's device keychain and never leaves it.")
print()
print("Learn more: https://github.com/auths-dev/auths")
print("=" * 70)
if __name__ == "__main__":
main()