Preview 0.30

P07 - Threat-model the path-handling boundary

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.

Why

Threat modeling identifies risks and controls before implementation. It is not an implementation lane.

Expected result

You know the target, review boundary, and that clearance remains advisory.

If that does not happen

If you need to change the target or implement a control, stop this lesson and use the appropriate governed work lane.

Evidence

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 !.

Why

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

Expected result

Academy switches this clone to academy/P07-threat-model/ATTEMPT_NUMBER and provides the frozen scenario.

If that does not happen

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.

Evidence

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 !.

Why

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

Expected result

You can name the learner-controlled input, repository root, and containment-before-write boundary.

If that does not happen

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.

Evidence

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.

Why

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"

Expected result

The command writes no file and returns advisory STRIDE analysis without treating clearance as permission to edit code.

If that does not happen

If the command edits the target, stages, commits, or makes an approval claim, stop. Preserve the attempt and use Reset rather than rewriting history.

Evidence

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.

Why

A STRIDE heading alone is not a threat model. Each row needs a concrete relationship, category-specific outcome, and control disposition.

Expected result

You can identify a concrete revision or accept a complete, target-specific model.

If that does not happen

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.

Evidence

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.

Why

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.

Expected result

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 that does not happen

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.

Evidence

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.

Why

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

Expected result

One final commit after Prepare adds only the P07 report while academy_engine/paths.py remains unchanged.

If that does not happen

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.

Evidence

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 !.

Why

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

Expected result

The commit adds only the report, the target diff is empty, and git status --short prints nothing.

If that does not happen

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.

Evidence

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 !.

Why

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

Expected result

Check prints checkpoint P07-threat-model: passed and records local progress.

If that does not happen

If Check fails, preserve the committed attempt and read its failed predicate. Use Reset for a new numbered attempt instead of rewriting evidence.

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 !.

Why

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

Expected result

Academy archives the prior attempt and prints the next academy/P07-threat-model/ATTEMPT_NUMBER branch.

If that does not happen

If Reset stops, preserve its message and current branch. Resolve only the named condition; never delete the earlier attempt or alter the frozen target.

Evidence

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.

Next step

Continue with P08-repository-hygiene.

Recovery guidance