Secrets and the software supply chain
Most of the code you ship was written by someone else: frameworks, libraries, base images, build tools and CI actions. Attackers know this. Compromising one popular package or a leaked deploy token can reach thousands of companies at once.
Secrets
A secret is anything that grants access: API keys, database passwords, signing keys, cloud credentials, OAuth client secrets.
- Never commit secrets, not even to a private repository. Git history keeps them forever, and repositories get cloned, forked and leaked. Enable secret scanning and push protection on your code host.
- Store secrets in a secret manager or your platform’s encrypted secrets, and inject them as environment variables at runtime.
- Give each environment its own secrets, so a leaked staging key can’t touch production.
- Rotate secrets on a schedule and immediately when someone leaves or a leak is suspected. If rotating a secret is scary, practise it until it’s routine.
If a secret was pushed, assume it’s compromised: revoke and rotate it first, then clean up history.
Dependencies
Every dependency is code you run with your own privileges. Before adding one, ask:
- Is it maintained, and by whom? How many people use it?
- Could a few lines of your own code do the job instead?
- What does it depend on? A small package can pull in a large tree.
Attacks to know about:
- Typosquatting: a malicious package named like a popular one (
reqeustsinstead ofrequests). - Account takeover: a maintainer’s account is compromised and a malicious version is published.
- Dependency confusion: a public package with the same name as your internal one gets installed instead.
- Install scripts: code that runs automatically when a package is installed.
Lockfiles and reproducible builds
A lockfile (package-lock.json, pnpm-lock.yaml, poetry.lock, Cargo.lock) records the exact version and checksum of every dependency, including transitive ones. Commit it, and install from it in CI (npm ci, pnpm install --frozen-lockfile) so builds can’t silently pick up a new, possibly malicious version.
Update dependencies deliberately: automated pull requests (Dependabot, Renovate) with tests, reviewed like any other change. Some teams also delay adopting brand-new releases for a few days, because malicious versions are often detected and removed quickly.
Know what you ship
A software bill of materials (SBOM) lists every component and version in a build. When the next widespread vulnerability is announced, an SBOM tells you in minutes whether you’re affected. Vulnerability scanners compare your dependencies against known advisories; run them in CI and fix critical findings in reachable code first.
CI/CD is part of the attack surface
The pipeline holds your most powerful credentials: it can deploy to production.
- Grant the CI token the minimum permissions it needs, and scope cloud credentials to one project and environment.
- Prefer short-lived credentials obtained through OpenID Connect over long-lived keys stored as secrets.
- Pin third-party CI actions to a full commit SHA, not a movable tag.
- Don’t run untrusted pull-request code in jobs that have access to secrets.
- Require review before changes to the pipeline itself are merged.
Least privilege everywhere
Every person, service and token should have only the access its job requires, for only as long as needed. When something is compromised, least privilege is what limits the damage.