Software supply chain security for small teams
Nearly every small product team reads its own code carefully and its dependencies not at all. Yet the code the team writes is maybe ten percent of what ships: the rest arrived through npm, PyPI, Maven, Docker Hub, GitHub Actions, and a dozen vendor SDKs. Your code is fine. Your dependencies are somebody else's Monday. The industry's worst incidents of the last several years — event-stream, colors/faker, xz, the npm "protestware" waves — were all supply-chain events: the application was innocent, and it went down anyway.
The standard enterprise answer (SBOM tooling, binary transparency, sigstore ecosystems) is real but heavy. The small-team answer is a half-day of setup plus habits that fit in one checklist. This pairs with the API key rotation checklist (the CI and platform secrets that make a dependency compromise worse) and the server hardening checklist (the blast radius on the box itself).
1. Count what you actually ship
You cannot defend a supply chain you have not enumerated. One afternoon, once:
- Generate the bill of materials the boring way:
npm ls --all,pip freeze, or your lockfile diffed against an empty directory install. The number is usually 5–20x what the team guesses. Write the number down — it is the denominator for every later decision. - List the non-package dependencies too: container base images, GitHub Actions (each
uses:line is code you execute on every push with your secrets attached), Terraform modules, vendored binaries in the repo, and the SDKs your payment and email vendors ship. - Mark the four that would end the company if compromised: the framework, the auth library, the deployment path, and the base image. These four get the strictest rules below; everything else gets the standard ones. That is the whole prioritization — the rest is decoration.
- Record where each language's lockfiles live and who regenerates them. The lockfile is load-bearing infrastructure, not a build artifact (see step 3).
2. Shrink the chain — the highest-yield hour in this checklist
The cheapest security control in software is subtraction. Every dependency you remove is a dependency that can never be compromised:
- Halve the count, once. Run depcheck or equivalent, then delete the dead weight: the left-pad-style micro-packages (write the function), the two date libraries where one survives, the "utility belt" packages where you use three functions, the transitive duplicates. Most teams cut 20–40% in one sitting and it feels like cleaning a garage.
- Adopt the one-question rule for new dependencies: "what does this do that I cannot write this afternoon?" A JSON parser, yes. A left-pad, no. A package with three contributors and a 40-line README gets the same answer as one you write yourself — because that is what you are doing.
- Prefer batteries, not aggregators. The dependency that pulls in 40 others to use one function is a supply-chain exposure multiplier. Check the dependency tree before adopting, not the stars.
- Retire on the same schedule you rotate keys. The quarterly pass from the rotation checklist gets one extra column: dependencies with no commits, no releases, or a dead repo get replaced this quarter, not eventually.
3. Lockfiles, pinning, and the update treadmill
- Commit the lockfile, always, for applications. An app without a lockfile builds differently on every machine, and "different" sometimes means "the compromised version." Lockfiles are the diff between your supply chain and the world's.
- Pin your direct dependencies (and CI actions) at known versions. Floating
^/~ranges andaction@mainmean an upstream compromise deploys into your build automatically, with zero action on your side. Pin, then update deliberately. - Updates on a schedule, not a popup. A monthly update PR (dependabot grouped, or hand-rolled) keeps the treadmill short so the diffs are reviewable. The failure mode to avoid is the year of drift followed by the terrifying 400-line upgrade.
- Read the changelog for the four crown-jewel dependencies; skim the rest. You are looking for behavior changes, new telemetry, and new required permissions — not line-by-line review.
4. Install-then-audit: the habit that replaces the scanner you can't afford
Commercial SCA tools are great; the free floor is lower than teams think:
npm audit/pip-audit/cargo auditin CI, blocking on the known-exploitable level. Tune the noise: fail on high-and-critical-with-a-fix, warn on the rest, review the warning list monthly. An audit that fails on everything is an audit everyone disables.- Audit at install time, not just deploy time. The habit is: new dependency → run the audit → check the package's age, contributors, and download curve (a package that spiked from 200 to 200,000 weekly downloads last month deserves a squint) → then commit.
- Watch for typosquats when typing names by hand.
reqeusts,crossenv, and friends exist because copy-paste is a skill. Install from the docs link, not from memory. - Export the dependency list into the evidence folder the same way the retention policy exports logs — when the next ecosystem-wide CVE lands, "here is our exact exposure, here is what we run" is a ten-minute answer instead of a two-day archaeology dig.
5. CI and Actions: the machine that holds your secrets
Every GitHub Action you run executes third-party code with whatever the workflow can reach. Treat the workflow file as production config:
- Pin actions to full-length commit SHAs, not tags.
actions/checkout@mainis a standing invitation; a tag can be moved, a SHA cannot. Thestep-security/harden-runneraction (free) egress-watches the job so an exfiltration attempt shows up as a red line instead of a quiet success. - Scope the CI secrets per repo and per environment. The deploy key for production lives in the deploy workflow only — not in every workflow that ever touched the repo, which is the default drift. The rotation checklist's rule applies: one secret per consumer.
- Restrict
GITHUB_TOKENpermissions to the minimum in the workflow file (contents: read is enough for most CI). A compromised action with a read-only token is an incident report; with write access to the repo, it is a domain-hijack-grade takeover of your build. - Require review for workflow-file changes on the main branch. Branch protection that reviews application code but waves through
.github/workflowschanges is a lock on the front door and a latch on the side. - Fork-PRs get no secrets, period. That is GitHub's default; verify nobody "fixed" it.
6. Build and deploy path integrity
- Build in CI from a clean checkout, never from a developer laptop. The laptop has three years of global packages and one node_modules nobody has audited since 2023. The clean-room build is what makes the lockfile meaningful.
- Pin the base image by digest, rebuild on a schedule.
node:20-slim@sha256:...plus a monthly rebuild means the base image you deploy is the base image you vetted — and that the monthly rebuild pulls the patched base within days of the vendor's fix. - Tag artifacts with the commit that built them so the response question "which builds contain the bad version?" is a grep, not a mystery.
- The deployment credential gets the shortest leash in the company — scoped to the deploy target, rotated on the secrets schedule, and revoked the same day a rotation happens.
7. The response plan: when the next big one lands
There will be a next xz, a next event-stream. The teams that come out fine are the ones with the hour already planned:
- Step 1 (minute 0–10): determine exposure. Grep the lockfiles for the package and version range; check the four crown jewels first. No exposure → document, watch the advisory, done. This step is why the dependency inventory exists.
- Step 2 (minute 10–30): contain. Freeze deploys from untrusted sources, revoke and rotate the CI/deploy secrets that the compromised path could reach (the rotation sequence from the key checklist: create → deploy → verify → revoke), and check egress logs for the IoCs the ecosystem publishes.
- Step 3 (minute 30–60): patch and rebuild from the fixed version, rebuild images from the new base, redeploy, and record the whole thing in the incident timeline — because a supply-chain response that isn't written down is a story, not evidence.
- Step 4: feed the customer questionnaire. "A critical dependency CVE shipped last quarter; here is our exposure assessment, our rotation log, and our rebuild record" is the answer that turns the worst week of the year into a deal-accelerating artifact.
8. The mistakes small teams make anyway
- "We're small, nobody targets us." Supply-chain attacks do not target you; they target the ecosystem you installed from, at scale, in bulk. xz did not check the size of your company first.
- Auditing without shrinking. Running
npm auditmonthly on a 1,400-dependency tree is bailing a boat. Cut the boat in half first, then bail. - Trusting a package because it is popular. Popularity is a lagging indicator — event-stream was popular too. The reviewable signals are recency of maintenance, contributor count, dependency count, and what it does that you cannot.
- Third-party code in the workflow, first-party trust in the token. The
GITHUB_TOKENis the keys to the repo; any step that runs unvetted code should hold nothing. - "We'll inventory dependencies when we have time." The inventory takes an afternoon and turns every future incident from an unknown into a query. It is the difference between the postmortem saying "we were exposed" and "we were not."
9. Close the loop: the quarterly supply-chain pass
- The pass, one hour: regenerate the dependency count and compare to last quarter (target: flat or down), review the four crown jewels' changelogs, rotate what the key checklist queues, re-pin anything that drifted, export the fresh inventory to the evidence folder.
- Keep the boundary honest: this checklist covers code you import and the machines that build it. The keys it uses are the secrets checklist, the runtime surface is the server hardening, and the monitoring that catches the weird egress is the monitoring checklist — one afternoon, done in order, is a materially harder company.
- Remember the economics: a half-day of setup and an hour a quarter buys you out of the incident class that ends companies without a single line of your own code being at fault. There is no cheaper insurance in engineering.