A modern application is mostly other people's code: hundreds to thousands of transitive dependencies, each a party you implicitly trust with your build machine and production environment. Attackers noticed — typosquatted packages, hijacked maintainer accounts, compromised build pipelines (SolarWinds, xz-utils, event-stream, tj-actions) moved supply-chain attack from theory to routine. The defensive toolkit is now mature, open-source, and mostly cheap to adopt. This page walks it in deployment order.
Everything downstream depends on the install being determined: a lockfile (package-lock.json, uv.lock/poetry.lock, Cargo.lock, Gradle lockfiles / Maven's fixed-version convention) pinning every transitive dependency with content hashes, committed to the repo, and installed with the strict mode that refuses drift (npm ci, uv sync, pip install --require-hashes). An unlocked build means yesterday's audit describes a different application than today's deploy. Hash verification also converts a registry-side package swap from silent compromise into a build failure.
Stale dependencies are the most common vulnerability source — not exotic attacks, but known-CVE versions nobody bumped. Renovate (open-source, self-hostable) and GitHub's Dependabot automate the bumping; Renovate's grouping, scheduling, and per-package rules make it the usual choice at scale. The failure mode is social, not technical: fifty open bot PRs nobody reviews. Working configuration:
minimumReleaseAge of 3–7 days) on non-security updates — most hijacked-package incidents are detected within days of the malicious publish, and a short quarantine lets the ecosystem be your canary.The OSV database (osv.dev) aggregates advisories across ecosystems in a machine-readable format keyed by package version — the open infrastructure most scanners now sit on. Practical scanners: osv-scanner (Google's CLI, reads lockfiles directly, multi-ecosystem), pip-audit (Python), npm audit (built-in but noisy — pair with better tooling), Trivy/Grype (containers + languages in one pass), OWASP Dependency-Check (JVM-centric). Wire one into CI on every PR plus a scheduled daily run — the schedule matters because vulnerabilities are disclosed against unchanged code; a repo that only scans on push never learns its shipped version went bad. Gate on severity with an allowlist file for accepted-risk findings (with expiry dates), or triage rots into ignore-all.
One honest caveat: most scanners match versions, not reachability — a flagged CVE in a code path you never call is still a finding you must triage. Tools with call-graph reachability analysis reduce that noise but don't eliminate the triage duty.
A Software Bill of Materials — CycloneDX or SPDX format — is the machine-readable list of everything in an artifact. Two forces make it worth generating now: regulation (US federal procurement, EU Cyber Resilience Act) increasingly requires it, and incident response is transformed by it — when the next log4shell lands, grep over stored SBOMs answers "are we exposed, where" in minutes instead of a week of archaeology. Generation is a solved problem (Syft, CycloneDX plugins for Maven/Gradle/npm; Trivy emits both formats); the discipline is storing an SBOM per released artifact version, alongside the artifact.
Scanning asks "is this version known-bad?"; provenance asks "is this artifact what the source says it is?" Sigstore provides keyless signing (cosign) backed by OIDC identity and a transparency log — CI signs images/artifacts with its workload identity, and deploy-time policy verifies the signature and provenance before running anything. SLSA grades the build-integrity posture (source-tracked → hosted build → hardened, non-falsifiable provenance); GitHub's artifact attestations make SLSA-provenance generation a few workflow lines. npm provenance and PyPI Trusted Publishers apply the same OIDC pattern to package publishing — adopt them for anything you publish; they eliminate the stolen-long-lived-token attack that has claimed multiple high-profile packages.
Two attacks target the resolver rather than any package: typosquats (requets, lodahs) catch fat-fingered installs, and dependency confusion exploits hybrid public/private registries — publishing a public package named like your internal one at a higher version, which naive resolver configs prefer. Defenses: scoped/namespaced internal packages (@yourorg/...) with the namespace registered publicly, registry configuration that never falls through from private to public for internal names, and lockfile-enforced installs so a confusion attack requires a reviewed diff, not a transparent re-resolve.
Week one: lockfiles enforced + osv-scanner in CI. Month one: Renovate with grouping and cooldown, minimal-permission CI tokens, SHA-pinned actions. Quarter one: SBOM per release, cosign signing and verification on deploy, publisher provenance for anything you ship publicly. Each step is independently valuable; none requires a platform purchase.