# Supply Chain Security

> Secure an open-source project against supply-chain attacks and handle vulnerability reports. Use when writing a SECURITY.md, setting up private vulnerability reporting, responding to a reported CVE, hardening GitHub Actions permissions, pinning dependencies, adding SBOM or build provenance, signing releases, or improving an OpenSSF Scorecard. Also use when evaluating whether a dependency or a maintainer handoff is trustworthy, and when reviewing a PR that touches CI, build scripts, or install hooks.

- Skill: `the-open-agent/supply-chain-security` (Agent Skill)
- Install (CLI): `npx skillmds@latest add the-open-agent/supply-chain-security`
- Raw SKILL.md: https://api.skillmd.com/api/skills/the-open-agent/supply-chain-security/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Security
- Author: the-open-agent (https://skillmd.com/u/the-open-agent)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/the-open-agent/supply-chain-security

---


# Supply Chain Security

Your project's security posture affects everyone downstream. A popular package is a
high-value target precisely because compromising it is cheaper than compromising its
users individually.

## The realistic threat model

Ranked by observed frequency in actual incidents, not by drama:

1. **Compromised maintainer account** — phishing, credential reuse, no 2FA. The
   single most common root cause of package compromise.
2. **Malicious dependency** — typosquat, or a legitimate package taken over via a
   maintainer handoff to a stranger who asked nicely.
3. **Compromised CI** — a workflow with excessive permissions, an unpinned third-party
   action, `pull_request_target` executing fork code.
4. **Malicious contribution** — a subtle backdoor in a PR from a patient contributor
   who spent months building trust.
5. **Build-time injection** — `postinstall` scripts, `setup.py` executing at install,
   compromised build servers.

Note what is *not* at the top: vulnerabilities in your own code. Those matter, but the
supply chain is where the leverage is.

## Baseline hardening

Do all of these. Together they take an afternoon and remove most of the realistic risk.

**Accounts**
- Hardware 2FA (WebAuthn) on GitHub and every package registry, for every maintainer.
  SMS 2FA is not sufficient against the phishing that actually happens.
- Registry publishing via **OIDC/trusted publishing** instead of long-lived tokens.
- Audit org membership and repo access quarterly. Remove inactive maintainers'
  *publish* rights, kindly and explicitly — this is not a demotion, it is hygiene.

**Repository**
- Branch protection on `main`, including for admins.
- Signed commits/tags for releases (`git config commit.gpgsign true`; Sigstore
  `gitsign` avoids GPG key management entirely).
- Enable **private vulnerability reporting**: Settings → Security → "Private
  vulnerability reporting". This gives researchers a channel that is not a public issue.
- Enable Dependabot alerts, secret scanning, and push protection.

**CI** (see also `ci-pipelines`)
```yaml
permissions:
  contents: read           # default-deny at the top of every workflow
```
- Pin third-party actions to a full commit SHA:
  ```yaml
  - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11  # v4.1.1
  ```
  Tags are mutable. A compromised action with `@v3` reaches every repo instantly —
  this has happened, more than once, at scale. Use Dependabot to keep pinned SHAs
  fresh so pinning does not mean staleness.
- Never `pull_request_target` + checkout of PR head.
- No self-hosted runners on public repos unless ephemeral and isolated.
- Restrict `GITHUB_TOKEN` to read-only by default in org settings.

**Dependencies** (see `dependency-hygiene`)
- Commit the lockfile. Install with `npm ci` / `pip install -r requirements.txt --require-hashes` / `cargo --locked`.
- Disable install scripts where feasible (`npm ci --ignore-scripts`).
- Review any new dependency's install hooks before adding it.

## SECURITY.md

```markdown
# Security Policy

## Supported Versions
| Version | Supported |
|---------|-----------|
| 4.x     | Yes       |
| 3.x     | Security fixes until 2026-12-31 |
| < 3.0   | No        |

## Reporting a Vulnerability
Report privately via [GitHub Security Advisories](../../security/advisories/new)
or email security@example.org.

Please do not open a public issue for security problems.

- Acknowledgement within **48 hours**
- Assessment within **7 days**
- Fix or mitigation plan within **30 days** for high severity
- We credit reporters in the advisory unless you prefer otherwise

## Scope
In scope: the published package, the CLI, the default configuration.
Out of scope: issues requiring an already-compromised host, denial of service
via deliberately malformed input to a debug-only tool, dependencies (report upstream).
```

Only promise timelines you will meet. A missed 48-hour promise is what turns a
cooperative researcher into a public disclosure.

## Handling a report

1. **Acknowledge fast**, even with no assessment yet.
2. **Reproduce**, and determine severity honestly. CVSS is a rough tool; the questions
   that matter are: what can an attacker do, what access do they need, and how many
   users are affected in a default configuration?
3. **Open a private GitHub Security Advisory.** It gives you a private fork to develop
   the fix, and a CVE request button.
4. **Fix in the private fork.** Do not push the fix to a public branch before the
   advisory — the commit *is* the disclosure, and exploit development is faster than
   user upgrades.
5. **Coordinate disclosure.** Standard is 90 days, or on fix release, whichever is
   first. Negotiate with the reporter; most are reasonable when you communicate.
6. **Release the fix** to every supported branch, patch releases only, no other changes
   in them.
7. **Publish the advisory** with affected versions, patched versions, workaround for
   users who cannot upgrade, and reporter credit.
8. **Notify downstream** — GitHub advisories propagate to Dependabot automatically.
   Also post where your users are.

If a reporter is hostile or demands a bounty you have not offered, stay factual and
keep to the process. If they publish early, do not escalate publicly; ship the fix.

## Provenance and signing

For anything widely installed, make the build verifiable:

```bash
npm publish --provenance                 # links artifact → repo → workflow run
# PyPI: attestations via trusted publishing (pypa/gh-action-pypi-publish)
cosign sign-blob --yes dist/tool.tar.gz  # Sigstore, keyless via OIDC
gh attestation verify dist/tool.tar.gz --repo owner/repo
```

Publish alongside a release: `SHA256SUMS`, signatures, and an SBOM
(`syft . -o spdx-json`, or `cargo sbom`). Also document the verification command in
your install docs — an unverifiable signature nobody knows how to check provides no
security, only the appearance of it.

## OpenSSF Scorecard

```yaml
# .github/workflows/scorecard.yml — weekly, publishes results to the Security tab
- uses: ossf/scorecard-action@v2
  with: { results_file: results.sarif, results_format: sarif, publish_results: true }
```

Treat the score as a checklist, not a target. The checks that actually reduce risk:
`Branch-Protection`, `Token-Permissions`, `Pinned-Dependencies`, `Dangerous-Workflow`,
`Signed-Releases`, `Code-Review`. The ones that measure process theater more than
safety: `CII-Best-Practices`, `Contributors`, `Packaging`. Do not contort the project
to raise a number.

## Reviewing PRs for supply-chain risk

Escalate scrutiny sharply when a PR touches:

- `.github/workflows/**` — especially `permissions`, new actions, or new triggers
- `package.json` scripts, `setup.py`, `build.rs`, `Makefile` install targets
- Any new dependency, especially one that is new, low-download, or recently transferred
- Base64 blobs, minified vendored code, unexplained binaries, or generated files
  committed without their generator
- Network calls in build or test code
- `.npmrc`, `.pypirc`, credentials handling, anything reading environment variables

Specific tells worth knowing: obfuscated payloads assembled from string fragments,
code that behaves differently under CI environment variables, a dependency version
bump paired with an unrelated build script change, and a first-time contributor whose
PR touches only CI configuration.

Ask about anything you do not understand. "I don't follow what this line does — can
you explain?" is a complete and sufficient review comment, and a legitimate
contributor will answer it happily.

## Maintainer handoff

The scenario behind several major incidents: a burned-out maintainer hands publishing
rights to a helpful stranger.

If you are handing off: verify identity beyond a GitHub account, transfer gradually
(commit rights → review rights → publish rights), keep 2FA requirements in place,
and announce the change publicly. If you are stepping away entirely and nobody has
earned that trust, **archive the project** and say so in the README. An archived
project is safe; an abandoned one with live publishing rights is a liability.

If you are asked to take over a project: expect and welcome scrutiny.

## Anti-patterns

- **No 2FA on the publishing account.** Root cause of the majority of package
  compromises.
- **Unpinned third-party actions.**
- **`pull_request_target` with fork checkout.**
- **Long-lived registry tokens** when OIDC is available.
- **Fixing a vulnerability in a public commit** before the advisory.
- **No SECURITY.md**, forcing researchers to open a public issue.
- **Promising a 24-hour response** you cannot honor.
- **Treating "it's just a dev dependency" as safe.** Dev dependencies run on developer
  machines with SSH keys and cloud credentials. That is the target.
- **Ignoring a report because the reporter was rude.**
- **Accepting a maintainer offer from a stranger** because you are tired.

