Worked example
We ran this plan on ourselves first.
This is the modernization plan for our own monorepo — the same one we run with clients, with the same tools, measured on 27 September 2026 against a working tree rather than estimated. The numbers below are what the commands returned. You can check every one of them against your own images tonight.
01 · The baseline
Most of what a scanner reported could never be closed.
Two images, the same database version, different bases. The headline is 236 findings down to 8, and that is the least interesting part. The number that changes a budget conversation is 202 of 236 — the findings with no fix available upstream. Nobody was accepting that risk deliberately. They were accepting it by exhaustion, every week, in a report nobody could act on.
What the base image was costing
Vulnerability findings in the same database version on two different bases, split by whether a fix existed upstream at all. The dark segment is the part no amount of triage closes — it leaves when the packages do, not when the patches arrive.
| Measured | Stock community image | Hardened image |
|---|---|---|
| Image size | 1.01 GB | 0.64 GB |
| Packages | 340 | 296 |
| Critical | 9 | 0 |
| High | 72 | 0 |
| Medium | 79 | 7 |
| Low | 14 | 1 |
| Negligible | 62 | 0 |
| Runs as | root | non-root uid |
| Signed | no | yes, logged in a public transparency log |
Two of the remaining findings are in the database vendor’s own shipped jars. No base image can fix those; they close when that vendor ships a release. Saying so is the difference between a rebase and a claim of zero — and it is what sets up the question of who writes the exception record, and where it lives.
02 · The dependency line
Twenty-five dependencies were chosen. Seven hundred and eighty-four execute.
25
direct dependencies
What anyone actually reviewed
784
resolved at install
What actually runs on the machine
1
critical alert, direct
Found before any pipeline existed
That ratio is the entire argument for scanning the lockfile rather than the manifest. It is also why a vulnerability scanner is not sufficient on its own: a CVE scanner answers “does this contain a known-vulnerable version?” A behavior analyzer answers “did this package just start reading environment variables, adding an install script, or phoning home?” — at install time, before a CVE exists. The second question is the one that catches a fresh maintainer compromise, because by definition there is no CVE yet.
03 · The tools
Named, because a plan you cannot check is a brochure.
We resell none of these and hold no partner tier with any of them. They are here because they are what produced the numbers above — and because “a hardened base image” is unverifiable where a named registry, a named signature issuer and a public log index can be looked up by anyone who doubts us.
Chainguard
Hardened base imagesMinimal, daily-rebuilt images with no package manager and a non-root default. The source of the 236-to-8 delta above, and of the SBOM and signature that come attached to every pull.
chainguard.dev ↗Socket.dev
Dependency behaviorScores packages on what they do at install time — new install scripts, network calls, filesystem and environment access — rather than only on known CVEs. The control that catches a fresh maintainer compromise, where a CVE scanner by definition cannot.
socket.dev ↗Sigstore
Keyless signing and transparencyCosign signs with a short-lived certificate from Fulcio, issued against the CI identity token, and the signature is recorded in Rekor, a public append-only log. No key is ever stored, and any claim about what shipped carries a log index anyone can look up.
sigstore.dev ↗Syft and Grype
SBOM and vulnerability scanningSyft produces the SPDX inventory of what is actually in an image; Grype reports the known-vulnerable components in it. Together they turn “what did we ship” from a conversation into a query.
github.com/anchore/syft ↗
04 · The plan
The database goes first, because it is the hard case.
The instinct is to start with the website, because that is the thing with a URL. Start with the stateful service instead: it is the only thing actually deployed, its blast radius is one container and a reload, and a persistent volume with a non-root uid exercises every failure mode a stateless container will not. Solve it there and the rest are easy — and everything downstream inherits the registry, the signing identity and the verification policy it established.
Fourteen weeks, in order
Phases run sequentially because each one inherits the registry, the signing identity and the verification policy established by the one before it. Phases drawn in color are shown in full on this page; the rest are the execution detail an engagement works through.
Nothing is enforced until week nine. Every gate before that observes, comments or warns — because the gate that gets disabled at 2am is worse than the gate that was never installed, and it takes the credibility of the next one with it.
Phase 0
Prove the base image and the dependency line
Before any pipeline exists, establish what the current artifacts actually contain and whether their provenance can be verified at all.
- 1Pull both the stock and the hardened image and scan each with Grype.
- 2Generate an SPDX SBOM for the hardened image with Syft.
- 3Verify the image signature with Cosign against the issuer that actually signed it, and record the Rekor log index.
- 4Score the direct dependencies with Socket and read the dimensions apart.
ArtifactA measured baseline, a signature you have personally verified, and a first SBOM.
Phase 1
Version control and credential hygiene
Nothing downstream is safe until the work can be reverted and the credentials have not been published.
- 1Initialize the repository and push to the organization.
- 2Confirm .gitignore covers every environment file, with the examples re-included.
- 3Rotate any credential that has appeared in a transcript or a process listing.
- 4Branch protection on the default branch, with required status checks.
- 5Enable secret scanning and push protection.
- 6Add CODEOWNERS, and turn on dependency alerting while the policy gate is still months away.
ArtifactA repository that can be reverted, and a credential that has not been published.
05 · How it gets enforced
Nothing blocks until the team already knows what it would have blocked.
Progressive enforcement
Every gate ships in observe mode, moves to warn, then blocks. The ladder is published before the first scan runs, so a team always knows which rung it is standing on and what the next one costs.
- 1ObserveScan, record, never fail.Moves up when · Baseline established and the false-positive rate known.
- 2AdviseComment on the pull request.Moves up when · Teams are reading the comments.
- 3WarnNon-blocking failure, visible in the check.Moves up when · Warning volume trending down.
- 4Block criticalFail on critical findings only.Moves up when · No critical findings for two weeks.
- 5EnforceFail on any policy violation.Moves up when · Exception process in use and not abused.
- 6SustainPolicy as code, reviewed quarterly.Moves up when · —
The workflow, once
Signing uses the CI identity token, so there is no key to store anywhere. Three separate identities mean a compromised build job cannot push to production — a separation that costs nothing at this size and is nearly impossible to retrofit later.
Lint, typecheck, test
The cheap checks first
Dependency policy
Behavior at install time, not just known CVEs
Buildbuild identity
Multi-stage, non-root, minimal base
SBOM and scan
Generated per artifact, stored per release
Sign and attestrelease identity
Keyless, using the CI identity token
Push
To the private registry
Verify on admissiondeploy identity
Deployment refuses anything unsigned
The rest of the plan
Everything above is what we can show you. The rest is what we do with you.
The remaining 5 phases are execution detail, and they are specific to an estate: which registry, which identity, which gate order, which exceptions get an owner and an expiry. We work through them in the engagement, and you keep the runbook afterwards whether or not you keep us.
Phase 2 · weeks 2–4
The data tier hardened
Phase 3 · weeks 4–6
The dependency line
Phase 4 · weeks 6–9
Containerize the applications
Phase 5 · weeks 9–12
The pipeline, and progressive enforcement
Phase 6 · weeks 12–14
Orchestration, when it is earned
A plan built from plausible numbers is a brochure. This one was measured on 27 September 2026 against a real working tree, and the findings it names — including the critical alert on one of our own direct dependencies — were real findings in this repository, found before any pipeline existed to catch them.