Anatomy of a Docker → CI/CD → AWS kill chain.
Walkthrough of one anonymized kill chain BleedWatch caught last quarter — the four stages, the surprise, and the product decision behind each detection layer.
Founder byline - 2026-05-04
The finding that started the rewrite
We caught it on a Tuesday afternoon. A scan job promoted a Docker layer secret to critical in a customer's tenant, and forty minutes later the same string showed up in a GitHub Actions audit row, and twelve minutes after that it correlated to a production AWS role with iam:* on three accounts.
I am redacting the customer. I am not redacting the shape of the chain, because the shape is the part you want to internalize.
This is what BleedWatch was built to surface. Not a list of secrets. A chain. The detection isn't the value — the correlation is. A leaked AKIA on Docker Hub is a low-value finding by itself. The same AKIA referenced under secrets.AWS_KEY in a deploy.yml that pushes to a role with s3:*, ec2:*, iam:* — that's a Proof of Threat. It's the only thing a CISO actually cares about, because it answers the question they all ask: "what's the blast radius if this gets popped tonight?"
Stage 1 — Layer 4 of a public image
The artifact was acme/api:prod-2026-05-01, public on Docker Hub. Layer 4 added ENV AWS_ACCESS_KEY_ID=AKIA...REDACTED. Layer 7 added ENV AWS_SECRET_ACCESS_KEY=.... Classic mistake, but not the interesting part — what matters is where it fell out of the engineer's mental model.
The team's policy explicitly forbade plaintext secrets in the repo. They had GitGuardian. They had git-secrets pre-commit hook. They had a .gitignore covering .env. They had a code review process. But the secret never lived in a tracked file. It lived in a Dockerfile that was committed and cleaned up two commits later, except the registry image kept layer 4 because Docker layers are immutable history.
This is the failure mode I see every week. The repo is clean. The artifact isn't. GitGuardian, Snyk, even GitHub native scanning — they read git. They don't pull tarballs. They don't expand layers. BleedWatch does, because the production artifact is the only thing an attacker actually has hands on.
Stage 2 — Workflow correlation
Our regex pipeline matched the AKIA, entropy scored 4.91, ONNX classifier promoted, semantic LLM pass confirmed it was a live-format token. That alone gets you a finding. What turns a finding into a chain is the next pass.
The scanner enumerated the customer's public repos, found .github/workflows/deploy-prod.yml, and saw the same AKIA prefix referenced under secrets.AWS_DEPLOY_KEY with a deploy job that did aws s3 cp and aws iam create-access-key. We don't have the secret value in the workflow — workflows reference secrets symbolically — but we have a strong signal that the same key is used in both surfaces. The 8-character prefix match plus shared deploy context promotes confidence.
I want to be honest about what this does and doesn't prove. It doesn't prove the workflow uses the leaked key. It proves the operating pattern is consistent with using it. That's enough to escalate. Real attackers will run the same correlation in their head.
Stage 3 — Cloud scope inference
The third pass looks at what the role can do. Not by probing. By reading what the workflow tells us — IAM action names, resource patterns, deployment targets. We saw iam:CreateAccessKey and iam:AttachUserPolicy. If those are real permissions, the deploy role can mint new credentials and escalate to anywhere in the account.
We do not test this. We never test this. Probing customer infrastructure with credentials we found public would be illegal in every jurisdiction we operate, and it would burn our entire business model. What we do is read what's declared — the workflow's intent — and assume the IAM matches. If we're wrong, the customer tells us. We tag the finding with inference: workflow-declared.
The result was a Proof of Threat card with severity critical and a blast-radius statement: "Layer 4 secret matches deploy role scope; if exploited, attacker can read S3, mint IAM access keys, and reach EC2 metadata on three production accounts."
Stage 4 — The runbook that landed in Slack
The customer's Slack got a single message with the title, the four-line evidence pack, the impact statement, and a remediation runbook. No 200-page PDF. No CVSS scoring theater. Five steps:
- Disable the exposed key in IAM.
- Rotate the deploy role and rebuild the image with multi-stage build (no ENV-injected secrets).
- Force-push a registry purge of the affected tag history.
- Add pre-publish layer scan to CI.
- Confirm no other public registry tag carries the same layer hash.
Their security engineer ran step 1 in eleven minutes. The whole chain — finding to remediation — was under an hour. That's the bar I'm pushing the platform toward: detection that lands as a next action, not as a row in a queue.
Tier mapping and why it's set this way
The base Docker layer detection ships in Community. The CI/CD correlation that takes a layer hit and links it to a workflow ships in Pulse. The IAM scope inference that turns a workflow link into a Proof of Threat ships in Shield. Active validation — actually confirming the key works against the customer's authorized scope — ships in Fortress through SaintScan, with the immutable audit trail required for that kind of authorized action.
The tier ladder isn't a packaging gimmick. Each tier maps to a distinct operating posture: discovery (Community), continuous monitoring (Pulse), correlated kill chains (Shield), authorized active validation (Fortress), managed program (Sentinel). If you don't need correlation, you don't need to pay for it. If you do, that's exactly where the value is.
What I'd build differently next time
Two things. First, the layer scan is still serial per-image — I want to fan it out by layer-hash so we don't re-scan the base ubuntu:22.04 for the 40th customer using it. That's a deduplication problem we'll solve in the next quarter.
Second, the IAM-scope inference is heuristic. I want it to be a graph. Workflow declares actions → actions map to a known IAM minimum policy → minimum policy maps to a blast-radius card. Roadmap item, not regret. The current version works. The next version will be better.
If you've shipped something like this and want to compare notes, [email protected] is open.