P07 - Threat-model the path-handling boundary
Choose the commands you use
Operating system
CodeArbiter host
Know before you begin
P07 is a public guided and runnable Academy lesson in this preview. It is opt-in and read-only. Start in the Academy clone that completed P06. Switch to main, then confirm git status --short has no output before Prepare.
Keep a native terminal at the clone root and one CodeArbiter harness open at that same clone. Native terminal commands go directly into the terminal and never begin with !. Harness-shell commands, when a lesson uses them, begin with exactly one !. This lesson uses no harness-shell commands. CodeArbiter commands and agent messages are entered in the selected harness and never begin with !.
The bounded target is academy_engine/paths.py. It handles learner-controlled archive-member or overlay-destination input beneath the selected repository root. P07 checks containment or rejection before a destination write. It does not edit the target.
What you will prove
You will commit one learner-owned Academy report after Prepare at .codearbiter/reports/academy/P07-threat-model.md. The report summarizes the read-only advisory analysis in four threat-model sections followed by a separate Academy target identity binding. The target stays byte-identical from the prepared commit to the final commit.
The report can use either CLEAR TO IMPLEMENT or BLOCKED - resolve findings first. Neither clearance outcome authorizes a P07 code change. Both are advisory conclusions from the review.
Prepare safely
Read the security-review boundary
You · Active CodeArbiter harness · All operating systems
P07 is an opt-in, read-only threat model for archive-import containment in academy_engine/paths.py. It does not authorize a code change, even when Clearance says CLEAR TO IMPLEMENT.
Threat modeling identifies risks and controls before implementation. It is not an implementation lane.
You know the target, review boundary, and that clearance remains advisory.
If you need to change the target or implement a control, stop this lesson and use the appropriate governed work lane.
Check can validate the committed report and target identity. It cannot grant implementation authority.
Prepare a numbered threat-model attempt
At the clean Academy clone root, run Prepare in a native terminal. Native-terminal commands go directly into that terminal and never begin with !.
Prepare creates a numbered branch and freezes the target blob and SHA-256 before the report exists.
You · Native terminal · Windows
$academy = "$env:LOCALAPPDATA\ArbiterAcademy\preview-0.30\Scripts\arbiter-academy.exe"
& $academy --repository (Get-Location).Path prepare P07-threat-model
You · Native terminal · macOS
academy="${XDG_DATA_HOME:-$HOME/.local/share}/arbiter-academy/preview-0.30/bin/arbiter-academy"
"$academy" --repository "$PWD" prepare P07-threat-model
You · Native terminal · Linux
academy="${XDG_DATA_HOME:-$HOME/.local/share}/arbiter-academy/preview-0.30/bin/arbiter-academy"
"$academy" --repository "$PWD" prepare P07-threat-model
Academy switches this clone to academy/P07-threat-model/ATTEMPT_NUMBER and provides the frozen scenario.
If Prepare stops, preserve its message and resolve only the named clean-worktree, base-branch, or external-authority condition. Do not force-reset or delete another attempt.
The prepared commit is the target identity that Check compares with the final commit.
Inspect the path-handling target
Read the target and prepared scenario in a native terminal. These are direct terminal commands, never harness passthrough commands, so they never begin with !.
The model must describe the actual archive-import containment boundary, not a generic security checklist.
You · Native terminal · Windows
Get-Content academy_engine\paths.py
Get-Content training_scenarios\P07-threat-model.json
You · Native terminal · macOS
sed -n '1,260p' academy_engine/paths.py
cat training_scenarios/P07-threat-model.json
You · Native terminal · Linux
sed -n '1,260p' academy_engine/paths.py
cat training_scenarios/P07-threat-model.json
You can name the learner-controlled input, repository root, and containment-before-write boundary.
If the target or scenario is missing or locally changed, preserve the attempt and start a new one. Do not substitute another path or alter the target.
The target must remain byte-identical from Prepare through Check.
Preparation materializes the frozen scenario descriptor for this attempt. The installed verifier later recomputes the target identity from committed Git objects; it does not trust a copied value from the report.
ATTEMPT_NUMBER is the number Academy prints, such as 1; do not type it literally. Stay on the numbered P07 branch until Check or Reset tells you otherwise.
Practice
Have CodeArbiter draft the STRIDE analysis
In the selected harness, invoke the host-native threat-model command for the exact archive-import target. It is a CodeArbiter command, never a ! terminal passthrough command. The command is read-only: it writes no file and returns advisory STRIDE analysis for your review.
The opt-in threat-model lane supplies advisory analysis only; a later learner-owned Academy record may summarize it without claiming command output.
Your agent · Claude Code harness · All operating systems
/ca:threat-model "academy_engine/paths.py archive-import containment boundary"
Your agent · Codex harness · All operating systems
$ca-threat-model "academy_engine/paths.py archive-import containment boundary"
Your agent · Pi harness · All operating systems
/ca-threat-model "academy_engine/paths.py archive-import containment boundary"
Your agent · Pi harness · All operating systems
/skill:ca-threat-model "academy_engine/paths.py archive-import containment boundary"
The command writes no file and returns advisory STRIDE analysis without treating clearance as permission to edit code.
If the command edits the target, stages, commits, or makes an approval claim, stop. Preserve the attempt and use Reset rather than rewriting history.
Check does not prove that the agent drafted first or that this command was invoked.
Review the draft against the real boundary
You · Active CodeArbiter harness · All operating systems
Review the draft yourself. Confirm Scope names controlled archive input, the repository root, and containment or rejection before write. Confirm six distinct STRIDE threats appear in S, T, R, I, D, E order. Treat each control and Clearance as analysis, not authorization.
A STRIDE heading alone is not a threat model. Each row needs a concrete relationship, category-specific outcome, and control disposition.
You can identify a concrete revision or accept a complete, target-specific model.
If a row is generic, repeated, out of order, or a clearance overclaims authority, ask the agent to revise only the advisory analysis and review it again before the learner-owned report is drafted. Do not claim that the read-only command wrote a file.
Check can reject malformed report evidence. It does not prove that you reviewed the draft.
The learner-owned report must be strict UTF-8 with LF line endings and one final newline. Keep it at or below 12 KiB. Its sections are Scope, STRIDE findings, Recommended controls before implementation, and Clearance, in that order. Scope must name controlled input, repository-root boundary, and containment or rejection before write. STRIDE findings need six distinct, concrete threats in S, T, R, I, D, E order. Each row needs likelihood, impact, and a PRESENT:, PLANNED:, GAP:, or justified N/A: control.
Use concrete controls in the report: keep destination resolution under the selected repository root before creating or copying a file. Reject absolute, traversal, symlink, and Windows reparse-point ancestors in archive destinations. Fail closed on a different drive or an unrepresentable containment path before any write.
The next learner request creates the report from the advisory analysis and prepared scenario values, validates its binding, stages only that report, and stops for your review before the commit gate.
Draft, bind, and stage the learner-owned report
After reviewing the read-only advisory analysis, paste the copied text request into your selected harness. It is a learner request, not a CodeArbiter command or shell command. It drafts and validates learner-owned Academy evidence, stages only that report, then stops for your review before the commit gate.
The learner-owned Academy evidence explains the advisory analysis, while the staged one-path boundary makes the later commit reviewable without implying that ca-threat-model wrote the report.
You · Claude Code harness · All operating systems
Draft only .codearbiter/reports/academy/P07-threat-model.md from the reviewed advisory STRIDE analysis and exact prepared scenario values. Keep the four learner-authored native sections in order, then append the Academy Target-SHA256/identity binding with exact target path, prepared blob, head blob, and SHA-256 values. Validate strict UTF-8 with LF line endings and one final newline. Stage only that report, show the staged path list and diff, then stop for my review. Do not change academy_engine/paths.py, commit, or claim that $ca-threat-model wrote the report.
You · Codex harness · All operating systems
Draft only .codearbiter/reports/academy/P07-threat-model.md from the reviewed advisory STRIDE analysis and exact prepared scenario values. Keep the four learner-authored native sections in order, then append the Academy Target-SHA256/identity binding with exact target path, prepared blob, head blob, and SHA-256 values. Validate strict UTF-8 with LF line endings and one final newline. Stage only that report, show the staged path list and diff, then stop for my review. Do not change academy_engine/paths.py, commit, or claim that $ca-threat-model wrote the report.
You · Pi harness · All operating systems
Draft only .codearbiter/reports/academy/P07-threat-model.md from the reviewed advisory STRIDE analysis and exact prepared scenario values. Keep the four learner-authored native sections in order, then append the Academy Target-SHA256/identity binding with exact target path, prepared blob, head blob, and SHA-256 values. Validate strict UTF-8 with LF line endings and one final newline. Stage only that report, show the staged path list and diff, then stop for my review. Do not change academy_engine/paths.py, commit, or claim that $ca-threat-model wrote the report.
Only .codearbiter/reports/academy/P07-threat-model.md is staged with four native sections followed by the Academy binding section; academy_engine/paths.py is unchanged and the agent has stopped for your review.
If another path is staged, the report lacks its exact prepared binding, or the request changes the target, stop and Reset. Do not unstage around an unclear boundary, commit, or modify academy_engine/paths.py to fit a digest.
The staged diff makes the report-only commit boundary visible. Check validates the target binding and report bytes, but does not prove the host command ran or produced the learner-owned prose.
After Clearance, add ## Academy Target-SHA256/identity binding with the exact target path, prepared blob, head blob, and SHA-256 values from the prepared scenario. Do not mix Academy-Target- labels into native sections. Do not add a secret, a generic governance event, another changed path, or a claim that a host command was invoked.
Commit the reviewed report through CodeArbiter
Only after you accept the report boundary, invoke the host-native commit gate. It is a CodeArbiter harness command, never a ! terminal passthrough command. Commit only .codearbiter/reports/academy/P07-threat-model.md after Prepare.
One report-only commit keeps final evidence reconstructable and prevents the review becoming a hidden implementation change.
Your agent · Claude Code harness · All operating systems
/ca:commit
Your agent · Codex harness · All operating systems
$ca-commit
Your agent · Pi harness · All operating systems
/ca-commit
Your agent · Pi harness · All operating systems
/skill:ca-commit
One final commit after Prepare adds only the P07 report while academy_engine/paths.py remains unchanged.
If the commit gate blocks, preserve its finding and correct only the named report boundary in a new attempt. Do not amend, rebase, or add a generic event record.
The final commit can prove its paths and bytes. It cannot authenticate an approval or conversation.
Inspect the report-only commit
Use a native terminal to inspect the final commit and clean worktree. These are direct terminal commands and never begin with !.
A local inspection makes path scope and target preservation visible before external Check.
You · Native terminal · Windows
git show --stat --oneline HEAD
git diff HEAD^ HEAD -- academy_engine/paths.py
git status --short
You · Native terminal · macOS
git show --stat --oneline HEAD
git diff HEAD^ HEAD -- academy_engine/paths.py
git status --short
You · Native terminal · Linux
git show --stat --oneline HEAD
git diff HEAD^ HEAD -- academy_engine/paths.py
git status --short
The commit adds only the report, the target diff is empty, and git status --short prints nothing.
If another path changed or the target has a diff, stop and preserve the attempt. Use Reset for a clean retry instead of concealing the extra change.
This inspection helps you read the final boundary. It does not replace Check.
Recognize success
The final branch has one report-only commit after Prepare. The report has the four native sections in order, the separate Academy Target-SHA256/identity binding, and a permitted advisory Clearance. The final target object and raw SHA-256 match the prepared target. git status --short is empty.
Check
Run the external Academy Check
Run Check in a native terminal after the report-only commit and clean status. Native-terminal commands never begin with !.
Academy recomputes target identity, report grammar, Git order, and live worktree outside the learner flow.
You · Native terminal · Windows
$academy = "$env:LOCALAPPDATA\ArbiterAcademy\preview-0.30\Scripts\arbiter-academy.exe"
& $academy --repository (Get-Location).Path check P07-threat-model
You · Native terminal · macOS
academy="${XDG_DATA_HOME:-$HOME/.local/share}/arbiter-academy/preview-0.30/bin/arbiter-academy"
"$academy" --repository "$PWD" check P07-threat-model
You · Native terminal · Linux
academy="${XDG_DATA_HOME:-$HOME/.local/share}/arbiter-academy/preview-0.30/bin/arbiter-academy"
"$academy" --repository "$PWD" check P07-threat-model
Check prints checkpoint P07-threat-model: passed and records local progress.
If Check fails, preserve the committed attempt and read its failed predicate. Use Reset for a new numbered attempt instead of rewriting evidence.
Check proves report bytes, target identity, allowed history, and clean state. It does not prove that a host command was invoked or that the agent drafted first.
Check proves the committed report grammar and bytes, prepared and final target identity, allowed Git ordering, report-only path scope, and clean final worktree. It does not prove that a host command was invoked. It does not prove that the agent drafted first. It does not prove that you reviewed the draft. It does not prove the review happened in any particular order. Those are learner and team practices, not final-state facts that this verifier can authenticate.
Recover or continue
If Scope is generic, STRIDE rows are reordered, Academy labels mix into native fields, a target value is stale, the target changed, or Check names a failed predicate, preserve the attempt. Do not amend, rebase, force-reset, or alter academy_engine/paths.py to fit an old digest.
Hint 1. Trace one untrusted archive-member or overlay-destination value from normalization through resolved containment to the extraction root. State what the code must establish before it creates or copies a destination.
Hint 2. The Threat cells need real outcomes. A row that only says "Tampering" does not say what an attacker can change or which containment failure would permit it.
Hint 3. Academy target identity is verifier evidence, not native threat-model prose. Keep it after Clearance and copy the prepared values exactly. A passing Check does not turn this review into authorization to implement controls.
Preserve the failed attempt and retry
Use Reset only when P07 needs another attempt. Run it in a native terminal at the Academy clone root; it archives the earlier attempt before creating the next numbered retry branch. Native-terminal commands never begin with !.
A retry leaves rejected report evidence reachable instead of destroying it with a force reset or history rewrite.
You · Native terminal · Windows
$academy = "$env:LOCALAPPDATA\ArbiterAcademy\preview-0.30\Scripts\arbiter-academy.exe"
& $academy --repository (Get-Location).Path reset P07-threat-model
You · Native terminal · macOS
academy="${XDG_DATA_HOME:-$HOME/.local/share}/arbiter-academy/preview-0.30/bin/arbiter-academy"
"$academy" --repository "$PWD" reset P07-threat-model
You · Native terminal · Linux
academy="${XDG_DATA_HOME:-$HOME/.local/share}/arbiter-academy/preview-0.30/bin/arbiter-academy"
"$academy" --repository "$PWD" reset P07-threat-model
Academy archives the prior attempt and prints the next academy/P07-threat-model/ATTEMPT_NUMBER branch.
If Reset stops, preserve its message and current branch. Resolve only the named condition; never delete the earlier attempt or alter the frozen target.
The failed branch remains inspectable while the retry starts from the immutable lesson base.
After Check passes, leave the completed branch intact and continue to P08. Do not substitute unpublished source exercises for a released lesson.
Understand the mechanism
$ca-threat-model is read-only advisory analysis: it writes no report. Threat modeling connects an untrusted input to a concrete boundary, then the learner-owned Academy record preserves threats and controls that matter at that boundary. STRIDE supplies coverage categories, not a substitute for specific reasoning. The Academy binding freezes which source bytes were reviewed without pretending it proves the live conversation, command invocation, or implementation authority.