Not recorded yet. The lesson below is complete and runnable today — the video is added later without touching this page.
recordings/E2.3.mp4Platform-layer compromise is invisible to workload-layer detection. The escape, the poisoned cache entry and the silently expired exemption all look like normal operation from inside.
Named escape primitives rather than anomaly scoring (C1.4), cache integrity diffing against a manifest (C5.4), upload scanning (C3.4), secret scanning wired to automated revocation (C4.1), and exemption-state reconciliation (C6.3).
Run four platform detectors over one day of events and see which of them a generic anomaly score would have missed.
The escape, the poisoned cache entry and the silently expired exemption all look like normal operation from inside the workload. These are detections whose subject is the platform running the agent, and not one of them reads the agent's own logs.
exemption_reconciliation(): B3.9's Exemption carries an expiry, nothing reconciles the register against the running configuration, so the expiry is a date that passes and the control stays off.Why a workload-layer detection cannot see a platform-layer compromise, and the named primitives that can — a spawn under a profile that forbids it, a cache entry that differs from its manifest, an exemption that expired while the control stayed off.
Platform-layer compromise is invisible to workload-layer detection — not detected late, not detected at all.
Write detections whose subject is the platform: escape primitives, poisoned cache entries, silently expired exemptions.
Platform events caught by named primitives versus by a generic anomaly score, which is the comparison that shows why they are separate rules.
Measure this
| what to measure | denominator or target |
|---|---|
| platform events caught by named primitives, against a generic anomaly score | two counts on the same events |
subject of the detection = the platform, not the workload
escape primitives ptrace non-child . LD_PRELOAD . /proc/self/mem
unshare/setns . non-harness write to the RPC path
a process that outlives its tool call
cache integrity repository vs manifest, hourly
modified | unexpected | MISSING
credential exposure repos, CI logs, image layers, public internet
-> automatic revocation, minutes not hours
exemption drift live control state vs the approved registerE2.2 wrote detections for what an agent does to your estate. These are detections for what happens to the platform running it — and they are different rules, on different data, with different owners.
Four of them come straight out of the incident register, and each catches something that looks like normal operation from inside the workload:
Escape detection (C1.4). Named primitives, not anomaly scoring: ptrace attach to a non-child, LD_PRELOAD on a spawned process, writes to /proc/self/mem, unexpected unshare/setns, a new listening socket, and — the one the incident's spoofing depended on — a background process that outlives the tool call that spawned it. Plus any process that is not the harness writing to the harness's RPC path.
Cache integrity diffing (C5.4). An hourly comparison of what is in the artifact repository against a manifest of what should be. Poisoned targets were staged in exactly this way, and a deletion is as much a signal as a modification.
Secret scanning wired to revocation (C4.1). Not only source: CI logs, image layers, wikis, ticket attachments, and external exposure monitoring — the incident's credentials were found on the public internet. Revocation has to be automatic, because the interval between discovery and redistribution was minutes.
Exemption-state reconciliation (C6.3). Live control state against the approved register, continuously. Any control disabled without a matching unexpired exemption is a P1, and expiry re-enables automatically with no grace period.
Anchor → E1.0. A different subject is a different clock. Workload-layer detection does not fire late on a platform compromise — it does not fire at all, which is why these are separate rules, on separate data, with separate owners.
| detection | owner | data source |
|---|---|---|
| escape detection (C1.4) | platform security | host sensor |
| cache integrity (C5.4) | build / release | artifact repository |
| secret scanning (C4.1) | appsec | repos, CI, the public internet |
| exemption drift (C6.3) | governance | control registry |
| upload scanning (C3.4) | the ingest team | upload path |
Here is the problem, as a number. A seven-event sandbox escape scores 0.07 against a generic volume anomaly rule. That is: effectively invisible.
The skill writes four rules whose subject is the platform itself rather than the traffic volume — orphaned processes, cache differences, escape signatures.
Then it runs both approaches over the same events, so you can see the two scores side by side rather than take the claim on trust.
The skill — skills/detection/agent-platform-detections/SKILL.md
name: agent-platform-detections
description: >-
Write detections whose subject is the agent platform itself — sandbox escape,
orphaned processes, artefact cache tampering, secret exposure — and compare
them against a generic volume anomaly on the same events. Use when the thing
being attacked is the harness rather than the application.
allowed-tools: Read, Grep, GlobAn escape from an agent sandbox is seven quiet events. It does not look like volume, it does not look like a spike, and a generic anomaly detector ranks it near zero. Detections whose subject is the platform are specific by necessity, and each of them names a mechanism rather than a statistic.
Any estate running agent workloads on shared infrastructure, and after any incident where "we would have seen it" was asserted without a named rule.
1 — Enumerate the platform's own failure modes. Escape from the sandbox, processes that outlive their tool call, artefact cache contents changing between runs, credentials appearing in output, tools invoked outside the manifest.
2 — Write one named rule per mechanism. Each with the specific condition it matches — a process tree deviating from the baseline, a PID alive after its call returned, a manifest hash mismatch. Named rules can be argued with; scores cannot.
3 — Run them against a real sequence. All the rules that should fire, should. Record which fired and on which event, because that mapping is what an analyst uses to reconstruct.
4 — Score the same sequence with a generic anomaly detector. Report both numbers side by side. The comparison is the argument for writing platform detections at all.
5 — Check each rule's data dependency. A cache-diff rule needs a manifest; an orphan rule needs process accounting. A rule whose input is not collected is a plan.
Input — the fixture committed at the top of scripts/agent_platform_detections.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:
t rule process
3 ptrace attach to a non-child solve.py
4 LD_PRELOAD on a spawned process solve.py
5 non-harness write to the RPC path x.so
5 process outside the baseline tree x.so
7 process outside the baseline tree sidecar
generic anomaly score for this run: 0.07 (seven syscalls is unremarkable)
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.
{
"mechanisms": ["str"],
"rules": [{"name": "str", "condition": "str", "requires": "str"}],
"sequence": [{"event": "str", "rules_fired": ["str"]}],
"generic_anomaly": {"score": 0.0},
"coverage": {"fired": 0, "expected": 0, "missing_inputs": ["str"]}
}
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 E2.3Then restart your agent and open it in that folder. On Windows, if python3 says Python was not found, use python instead.
Then pick e2-3-detections-whose-subject-is-the-agent:
| agent | install just for this agent | start it |
|---|---|---|
| Claude Code | python3 scripts/install_skills.py --tool claude --lessons E2.3 | type /e2-3-detections-whose-subject-is-the-agent |
| GitHub Copilot (VS Code) | python3 scripts/install_skills.py --tool copilot --lessons E2.3 | type /e2-3-detections-whose-subject-is-the-agent in Chat |
| Cursor | python3 scripts/install_skills.py --tool cursor --lessons E2.3 | type / in Agent chat and search for it |
| Codex | python3 scripts/install_skills.py --tool agents --lessons E2.3 | type $e2-3-detections-whose-subject-is-the-agent, 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 E2.3Neither 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 E2.3 --out work/cybertravels
python3 scripts/checkpoint.py --at E2.3 --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 named rules fire on a seven-event escape sequence that scores 0.07 on a generic volume anomaly. The orphaned-process rule isolates the one background process that outlived its tool call. The cache diff reports one modified, one unexpected and one missing artifact; automated revocation closes a credential in 2 minutes against 240 with a human in the loop; and exemption reconciliation raises a P1 for both an expired exemption and an unapproved one.
Pick the orphaned-process rule and write it for your own platform. It is one query, it has a low false-positive rate, and on most agent platforms nobody has ever run it.
Tools used
Skill source: skills/detection/agent-platform-detections