Skip to content

[Bug]: CI/CD supply chain hardening — pin GitHub Actions to commit SHAs, version-lock tools #2982

@la14-1

Description

@la14-1

Context

The LiteLLM supply chain attack showed how unpinned CI/CD dependencies can cascade into full credential theft and malicious package publishing. An attacker compromised Trivy (a scanning tool), used that foothold to steal PyPI publishing credentials from LiteLLM's CI, and published backdoored versions for ~3 hours (~3.4M downloads/day).

Spawn is not vulnerable to the exact same attack (we don't publish to npm/PyPI — CLI releases use gh release upload), but we share several of the same root-cause patterns.

Findings

HIGH — GitHub Actions pinned by tag, not commit SHA

All 11 workflow files reference actions by major version tag (@v4, @v2, @v7, etc.). A compromised action maintainer (or stolen credentials) can force-push malicious code to an existing tag.

Affected actions across all workflows:

  • actions/checkout@v4
  • actions/github-script@v7
  • oven-sh/setup-bun@v2
  • docker/login-action@v3
  • docker/build-push-action@v6
  • hashicorp/setup-packer@main ← worst case: branch ref, not even a tag

Fix: Pin every action to its full commit SHA with a version comment:

- uses: actions/checkout@e2f20c01b594fdf7527dbad7fecee28055a9b47a  # v4.0.0

HIGH — hashicorp/setup-packer@main + version: latest

packer-snapshots.yml uses a branch ref (@main) for the action AND version: latest for Packer itself — double-unpinned. Packer runs with DO_API_TOKEN in the same job, so a compromised action could exfiltrate cloud credentials.

MEDIUM — Unpinned tool installs

Tool Workflow Issue
shellcheck lint.yml apt-get install -y shellcheck — no version pin
bun agent-tarballs.yml bun-version: latest

MEDIUM — Agent curl \| bash installs without integrity checks

packer/agents.json installs claude, opencode, hermes via curl | bash with no checksum verification. The domain allowlist and command blocklist are good mitigations, but don't prevent a compromised upstream install script.

GOOD — Already solid

  • Secret isolation: Publishing/cloud credentials properly scoped to their workflow steps
  • No npm/PyPI tokens in CI — releases use gh release upload
  • Lockfile committed: bun.lock pins all dependency versions exactly
  • No lifecycle scripts: No postinstall/prepack hooks
  • Domain allowlist + command blocklist on agent tarball builds

Recommended mitigations (priority order)

  1. Pin all GitHub Actions to commit SHAs — highest ROI, prevents the Refactor spawn scripts with shared library and OAuth fallback #1 LiteLLM-style vector
  2. Pin hashicorp/setup-packer to a commit SHA + specific Packer version — this one has cloud creds in scope
  3. Pin shellcheck and bun to specific versions in CI
  4. Add SHA256 checksums for agent curl | bash installs where upstream provides them
  5. Enable Dependabot for GitHub Actions version updates (it can auto-PR SHA bumps)
  6. Consider adding permissions: blocks to every workflow (currently some inherit default write-all)

Filed from Slack by SPA

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingsafe-to-workSecurity triage: safe for automated processing

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions