This starts chapter C2. Its course guide is C2 in the Function C course guide →
Not recorded yet. The lesson below is complete and runnable today — the video is added later without touching this page.
recordings/C2.0.mp4A security pipeline built as if it were exempt from the risks it exists to find.
Build the pipeline and the harness as one system, and hold both to the same evidence standard.
Run a real LLM loop against a CyberTravels finding, then add the verifier and watch the same loop refuse what it just accepted.
Half of what is sold as AI security tooling cannot run before a deploy, and half of it tells you nothing after one. Buy across that line without noticing it and you own two tools that agree with each other and a gap between them where the incidents happen.
cybertravels/appsec/, and it ships inside the tree it scans. That is the lesson's argument rather than a filing decision: a pipeline in its own repository drifts from the code it reviews, and one that exempts itself from its own stages is the expensive kind. The stage table names what each stage may claim — claim_for(10) says reachable, claim_for(12) says exploited, and a report that blurs them is one nobody can act on.The pipeline as one system, split by what each half can honestly assert: before a deploy there is source and no running thing, so every finding is a hypothesis; after it there is a disposable replica, so a finding can be demonstrated. Including where the pipeline lives, and why exempting it from its own stages is the expensive choice.
A security pipeline built as if it were exempt from the risks it exists to find is the one nobody audits.
Learn the line first — what can run before a deploy and what only works after one — then build the pipeline and its harness as one system.
Every stage after this reports its own number. This lesson's output is the order they run in, which is what makes those numbers comparable.
Measure this
| what to measure | denominator or target |
|---|---|
| nothing is computed here | the output is the order the stages run in, which is what makes their numbers comparable |
ONE LINE, AND EVERY TOOL SITS ON ONE SIDE OF IT
PRE-DEPLOYMENT | POST-DEPLOYMENT
artefacts sitting still | a system that is running
|
source, manifest, IaC plan, | real traffic, the identity the
tool schema, SBOM | workload actually got, what left
|
cheap . repeatable . CAN BLOCK | sees what really happened . CANNOT
THE MERGE | block the merge that caused it
|
SAST SCA/SBOM IaC policy | DAST egress enforcement drift
threat model tool-schema review | detection canaries attestation
|
blind to: the identity it gets | blind to: the commit, until after
|
C2.2 - C2.7 | C2.8 - C2.18, and Function E
the gap is not a tool you are missing. it is the line itself:
nothing on the left can see a runtime fact, and nothing on the
right can stop the change that created oneCyberTravels ships faster than Alex can read. The Coding Agent opens pull requests that touch a hundred files, and the review that used to be a careful hour is now a scroll. Function C is what he builds instead of scrolling.
It is one pipeline, built in order, and the first thing to get straight is where each piece of it can run at all — because half of what is sold as AI security tooling cannot run before a deploy, and half of it tells you nothing after one.
Pre-deployment works on artefacts sitting still. Source, a manifest, an IaC plan, a tool schema, an SBOM. Nothing is running, so the analysis is cheap, repeatable and — the property that matters most — it can block a merge. It is also structurally blind to every fact that does not exist until deployment: the identity the workload actually got, the route it can actually reach, the traffic it actually saw.
Post-deployment works on a system that is running. It sees what really happened, which is the only way to learn some things at all. And it cannot block the change that caused it. By the time it has an opinion, the merge is in.
That is the whole of the distinction, and it decides more than people expect. A risk covered only on the left is one you can prevent and cannot detect. A risk covered only on the right is one you can detect and cannot prevent. Both of them appear on a tooling inventory as the same word — covered — and only one of them is what the person reading the inventory believed they had bought.
Stages 1 to 10 are pre-deployment: ingest the estate, derive a threat model, audit the code, scan the supply chain, filter to what is reachable. They are cheap and they can gate. Stages 11 to 15 need something running: a replica to attack, an exploit to execute, a patch to prove, a report to sign. They are the expensive half, and they exist because the cheap half produces hypotheses rather than facts.
Nothing in this chapter fixes the middle. The gap between the two sides is not a tool you are missing — it is the line itself, and the honest move is to know which side each of your controls is on before somebody asks you whether a risk is covered.
| before the Coding Agent | after | |
|---|---|---|
| pull requests per week | 6 | 40 |
| files touched per PR | 3 | up to 120 |
| reviewers | 1 | 1 |
| technique | pre-deploy | post-deploy | what only the other side sees |
|---|---|---|---|
| SAST / code analysis | yes | no | whether the vulnerable path is reached with real traffic |
| Dependency and SBOM scanning | yes | partly | what actually loaded, versus what the manifest pinned |
| IaC and policy-as-code | yes | no | the identity and network the workload really got |
| Threat modelling | yes | re-run on drift | entry points added by a config change, not a commit |
| Tool-schema and MCP surface review | yes | yes | a description edited by a server after review |
| DAST / attack simulation | against a replica | yes | responses only a live system produces |
| Guardrails and egress enforcement | config only | yes | the request that was actually attempted |
| Runtime posture and drift | no | yes | a guardrail switched off after the demo — B3.9 |
| Detection and canaries | no | yes | somebody using a credential nothing legitimate touches |
| Attestation | signs the claim | re-checks it | whether the deployment still matches what was signed |
The table above is a reference. This is the same question asked of CyberTravels' own inventory, which is the version that produces an answer somebody has to do something about.
It counts three things separately: how many of the techniques can actually block a merge, which risks are covered only on the side that can prevent them, and which only on the side that can merely observe. All three read as “covered” on a tooling inventory.
Three numbers come out of that run and each one has a different owner.
Gate-capable. Four of ten. Everything else produces a ticket, and a ticket is a request rather than a control. If your security posture depends on tickets being worked, it depends on next quarter's headcount.
Post-only risks. These are the ones that get signed off in a review, because a control exists, it runs, and it reports. It reports afterwards. The sign-off is not wrong about the control; it is wrong about what the control does.
Uncovered. One, and it is the subject of C2.7: an artefact in the build with no manifest entry has no identifier, so the SCA pass on the left never had anything to look up and the report was clean about it. That is a gap nothing on either side is currently pointed at.
The skill — skills/appsec/sdlc-control-placement/SKILL.md
name: sdlc-control-placement
description: >-
Place each security technique on the pre-deployment / post-deployment line and
report which risks are covered only on the side that cannot act on them. Use
when reviewing AppSec tooling coverage, when choosing where a new control
belongs, or when a risk is described as covered and the incident happened
anyway.
allowed-tools: Read, Grep, GlobEvery security technique works on one of two things: artefacts sitting still — source, a manifest, an IaC plan, a tool schema — or a system that is running. That is not a taxonomy for its own sake. It decides two properties that nothing else changes:
A risk covered only on the left is a risk you can prevent and cannot detect. A risk covered only on the right is one you can detect and cannot prevent. Both appear as "covered" on a tooling inventory, and only one of them is what the person reading the inventory thought they were buying.
Before buying anything, when a tooling inventory is presented as coverage, and after any incident where the answer to "did we have a control for that" was yes. Also when placing a new control: the same technique run at build time and at runtime answers different questions, and choosing is a design decision rather than a scheduling one.
1 — List the techniques you actually run, not the ones on the architecture diagram. A tool that is deployed and muted is not a control.
2 — Place each one. Pre, post, or genuinely both. "Both" is rarer than it looks: a technique that runs at build time and again at runtime counts as both only if the runtime pass can see something the build pass could not.
3 — For each one, name what the other side sees that it cannot. This is the sentence that turns a list into an argument. If you cannot write it, the placement is wrong or the technique is not doing what you think.
4 — Map risks to sides, not to tools. For each risk class, which sides cover it. Then count the two failure shapes: preventable but invisible (pre only) and visible but unpreventable (post only).
5 — Report the uncovered and the one-sided separately. A risk with nothing on either side is a gap everybody understands. A risk covered only on the side that cannot act is the one that gets signed off, and it is the one this procedure exists to surface.
Input — the fixture committed at the top of scripts/sdlc_control_placement.py. Edit it and re-run: the buckets, counts and verdicts below are derived from it, not hard-coded.
Output — the opening lines of a real run:
techniques, placed
technique side gate blind to
SAST / code analysis pre yes whether the vulnerable path is reached with real traffic
SCA and SBOM scanning pre yes what actually loaded, versus what the manifest pinned
IaC and policy-as-code pre yes the identity and network the workload really got
threat modelling pre - entry points added by a config change, not a commit
tool-schema / MCP review both yes a description edited by the server after review
DAST / attack simulation post - the commit that introduced it
The run continues past this. test_skills.py executes the script on every build, with no model configured — so what CI proves is that it runs and refuses legibly, not that it prints these lines. The output above is a recorded run against a real model, and nothing re-diffs it: a model's answer is not reproducible, and this repository does not claim otherwise anywhere it can be checked.
{
"techniques": [{"name": "str", "side": "pre|post|both", "blind_to": "str"}],
"risks": [{"risk": "str", "sides": ["pre|post"], "shape": "covered|pre-only|post-only|uncovered"}],
"counts": {"pre_only": 0, "post_only": 0, "uncovered": 0},
"gate_capable": 0
}
gate_capable counts the techniques that can actually block a merge. It is usually much smaller than the tooling budget suggests, and it is the number that decides how much of your security posture is advisory.
Three ways to do this lesson. Pick one. They run the same procedure against the same code — what changes is who drives it, and how much you see.
The agent reads the lesson, explains the idea, fetches the code, runs the check and answers your questions as you go. This is the one to take if you are here to learn.
Install it, once. From the folder you cloned. --all does every agent it finds on your machine:
python3 scripts/install_skills.py --all --lessons C2.0Then restart your agent and open it in that folder. On Windows, if python3 says Python was not found, use python instead.
Then pick c2-0-the-ai-sdlc:
| agent | install just for this agent | start it |
|---|---|---|
| Claude Code | python3 scripts/install_skills.py --tool claude --lessons C2.0 | type /c2-0-the-ai-sdlc |
| GitHub Copilot (VS Code) | python3 scripts/install_skills.py --tool copilot --lessons C2.0 | type /c2-0-the-ai-sdlc in Chat |
| Cursor | python3 scripts/install_skills.py --tool cursor --lessons C2.0 | type / in Agent chat and search for it |
| Codex | python3 scripts/install_skills.py --tool agents --lessons C2.0 | type $c2-0-the-ai-sdlc, or run /skills |
No agent, no conversation. The same lesson executes and prints the same readback. Take this one if you have no assistant, if you are reviewing rather than learning, or if you want the result without the teaching around it.
python3 scripts/lesson.py C2.0Neither of the above, and no model needed. The first command writes CyberTravels exactly as it stood at the end of this lesson; the second prints the diff — the lines this lesson added or changed, and nothing from any later one.
python3 scripts/checkpoint.py --at C2.0 --out work/cybertravels
python3 scripts/checkpoint.py --at C2.0 --diffOn Windows, type python wherever these say python3 — and use \ instead of / in the paths. Everything else is identical. A0.0 step 2 explains why.
Ways 1 and 2 need a model; way 3 does not. A signed-in Claude Code CLI needs no API key, and any OpenAI-compatible endpoint works too. With neither, the audit exits 2 and says so rather than inventing an answer. A0.0 sets this up.
Ways 1 and 2 both end with the same readback: What I did, What changed, The number and Read this next. A step that was skipped or refused is reported as exactly that, never as a pass.
If you did the last lesson’s challenge, expect this to stop. Most challenges ask you to edit the code, and ways 1 and 2 will not overwrite your edits: you get stopped, not a failure, with two options — --out for a fresh folder that keeps your work, or --force to discard it and start this lesson clean.
Four of ten techniques can block a merge; the other six produce tickets. Two risks are covered only before the deploy — preventable, and invisible once shipped — and three only after it, where detection is the whole of the control and the merge that caused it already went through. One is covered by nothing on either side, and that one is the undeclared vendored binary C2.7 goes after.
Write your own inventory into the same two columns, then delete every row that is deployed and muted. What is left is your coverage. The row that will start an argument is the one somebody believes is preventative and is in the right-hand column.
Tools used
Skill source: skills/appsec/sdlc-control-placement