Skip to content

Latest commit

 

History

History
104 lines (83 loc) · 4.26 KB

L03.md

File metadata and controls

104 lines (83 loc) · 4.26 KB
id course-secure-code-l03
type course
title Lesson 3: Close one flaw class per turn
summary Five bounded turns — search, attachments, secret key, passwords, sessions — each closing one weakness class with a mechanism, never a filter, and each proven by its tests and the replay.
lang en-US
content_version 1
status reviewed
reviewed_on 2026-10-11
hints
title body
What this checkpoint tests
That the starter passes all sixteen tests through changes you supervised — `python verify.py` shows this code only once the starter suite is green.
title body
Watch for the filter
If a diff adds `replace("'", "")`, checks `'..' in name`, or compares against the attack strings in `attacks.json`, reject it. The contract asks for a mechanism.
title body
The replay goes green last
`test_replay_blocks_every_team_notes_attack` needs all five classes closed. It stays red until the final turn.

Lesson 3: Close one flaw class per turn

Objective

The starter passes all sixteen tests through five bounded turns, each closing one weakness class, and you can explain why each fix holds for inputs the tests never try.

Why this lesson exists

Ask an agent to "fix the security issues" and you get one large diff that mixes real fixes with filters, renames, and new helpers. You cannot tell which line stops which attack. One class per turn keeps each fix small enough to check against its mechanism from Lesson 2.

The lesson

Start a fresh agent session and say:

"Work through TASK.md against starter/notes_app.py, one weakness class per turn: find_notes, then read_attachment, then load_secret_key, then hash_password and verify_password together, then dump_session and load_session together. After each turn run python verify.py starter and python starter/attack_replay.py scenario/team-notes, show me the diff, and wait for me. Do not edit tests/, solution/, scenario/, or attack_replay.py."

Expect the failing count and the replay to move like this:

After turn Failing tests Replay (team-notes)
start 11 0/9
find_notes 9 2/9
read_attachment 6 4/9
load_secret_key 5 6/9
passwords 3 7/9
sessions 0 9/9

(Where the platform cannot create symlinks, the symlink test is skipped and the first two rows read 10 and 8.)

Hold each turn to its mechanism:

  • Search: the SQL string is a constant; values go in the parameter tuple. Reject any diff that escapes quotes or strips characters. LIKE with escaped wildcards is acceptable only if the escaping is complete; instr avoids the question.
  • Attachments: resolve first, then check containment with is_relative_to. Reject if ".." in name — it misses symlinks and absolute paths, and it refuses legitimate names like notes..txt.
  • Secret key: no fallback, ever. If the agent adds a "development default", it has reintroduced the flaw.
  • Passwords: secrets.token_bytes(16) for the salt, hashlib.pbkdf2_hmac for the digest, hmac.compare_digest for the check. verify_password returns False for any format it did not write.
  • Sessions: remove pickle from the file entirely. Verify the signature before decoding; catch only the decoding errors you expect and turn them into SecurityError.

After the sessions turn, run grep -n pickle starter/notes_app.py. It should print nothing.

Exercise

For the attachments fix, try two names the tests do not use, against scenario/team-notes/attachments: ./agenda.txt (legitimate — it should read) and agenda.txt/../../secrets.txt (it should be refused). Predict each result before you run it, and write down whether the mechanism handled both without a new line of code.

Checkpoint

Run python verify.py. This checkpoint's code appears only when the starter suite is green. You pass the lesson when you can answer:

  1. Which diff did you reject or narrow because it was a filter instead of a mechanism?
  2. Why does test_replay_blocks_every_team_notes_attack only pass on the last turn?
  3. What would happen if load_session decoded the payload first and checked the signature second?

Expected evidence

A green python verify.py starter, the replay printing blocked=9/9, the five diffs, and your exercise result.