Security review for AI-written Python · Challenge 04

Lesson 4: Review like an attacker

Checkpoint l0410 ptsgated by test suiteAny coding agent + an attack-replay harness

Green tests and a 9/9 replay are the floor. Replay the second skin, run a static scanner and note what it misses, attack one thing the suite never tried, and write down what is still unreviewed.

Doing this with your agent? One sentence starts the whole course:

Read https://flypython.com/skills/flypython/SKILL.md and start the FlyPython course `secure-ai-generated-code`.

Lesson 4: Review like an attacker

Objective

Both suites pass, both skins replay fully blocked, and you hold a written record of what was reviewed, what a scanner did and did not catch, and what nobody has reviewed yet.

Why this lesson exists

blocked=9/9 is easy to over-read. It means nine specific attacks are blocked. A real attacker tries a tenth. Security review is the habit of asking what the tests did not try — and writing the answer down instead of assuming it.

The lesson

Run the maintainer pair and both replays yourself:

python verify.py starter
python verify.py solution
python starter/attack_replay.py scenario/team-notes
python starter/attack_replay.py scenario/clinic-notes

The suite only replays team-notes. clinic-notes uses different payloads — x' OR 1=1 --, an injected owner instead of an injected query, scans/../../secrets.txt, an empty key. If your fixes are mechanisms, it prints blocked=8/8 without any change. If it does not, one of your fixes was a filter.

Then do the review the suite cannot do:

  1. Run a static scanner, then read what it missed. If you have Ruff (the repository’s development dependencies include it), run its flake8-bandit rules on your fix:

    ruff check --isolated --select S starter/notes_app.py
    

    Your fix should report nothing. For comparison, on the original starter Ruff 0.16.10 reports S608 (SQL built from strings), S324 (MD5, twice), and S301 (pickle.loads) — and nothing for the path join or the "dev-secret-change-me" fallback. If you committed a baseline, run the same command on it to see this yourself. A scanner is a fast first pass that catches known patterns; it is not a review.

  2. Attack one thing the suite never tried. Pick one: a search query with Unicode case (É vs é), an attachment name that is a directory, a session token with a valid signature over a JSON list, a password that is 10 000 characters long. Predict, run, and write down the result.

  3. Read the diff for what it removed. git diff the starter against your baseline. Confirm pickle and md5 are gone and that no except Exception swallows a SecurityError.

Exercise

Write the security evidence record for this change:

  • Verified: the commands you ran, both replays, the scanner output.
  • Not verified: at least — authentication and authorization as a whole, how the session token is transported (cookie flags, TLS), rate limiting on login, key storage and rotation, dependency vulnerabilities, and every attack not in attacks.json.
  • Known limits: anything you found in step 2 that the contract never promised, and the password-hash choice (PBKDF2 rather than Argon2id).

Checkpoint

Run python verify.py. This checkpoint’s code prints once the starter suite passes; the maintainer pair above must both be green for the gate to mean what it says. You pass the lesson when you can answer:

  1. Did clinic-notes pass without changes? If not, which fix was a filter?
  2. Which two flaw classes did the scanner miss on the original starter, and why would a pattern-based tool miss them?
  3. What did your extra attack do, and is it on your “not verified” list?

Expected evidence

Both green runs, both replay outputs, the scanner output, and your three-line evidence record.

Hints

Stuck? Open one at a time.

Hint 1 — What this checkpoint tests

That starter AND solution both pass — the maintainer pair verify.py starter / verify.py solution must both be green, and python verify.py prints this code once your starter suite passes.

Hint 2 — Scanners find patterns, not intent

On this starter, ruff's S rules flag the f-string SQL, MD5, and pickle.loads — and say nothing about the path join or the fallback secret. Treat a clean scan as one line of evidence.

Hint 3 — When you are done

You have a security evidence record — verified / not verified / known limits — that names what nobody reviewed.

Submit

Done? Record it.

Use the 8-character code printed by python verify.py. For a self-reported checkpoint, answer the lesson questions first, then run python verify.py --attest l04.

Youbrowser

Submit the claim code

Your agentauto-submit

Let the agent solve and submit

Hand it this checkpoint (copy button on hover) — it solves, verifies, and submits on its own:

Work on the FlyPython challenge "Security review for AI-written Python" (course id course-secure-code), checkpoint l04.
Machine-readable brief: https://flypython.com/api/challenges/secure-ai-generated-code
Open the course folder and read TASK.md first — it is the contract.
Rules: smallest change, no new dependencies, never edit tests/ or solution/.
Check starter/ with python verify.py; ask me the lesson questions before using --attest ID for any self-reported checkpoint.
Target: solve only checkpoint l04, and submit it as soon as it passes.

With a token from your agent page (env FLYPYTHON_TOKEN), it records the result directly:

curl -X POST https://flypython.com/api/claims \
  -H "Authorization: Bearer $FLYPYTHON_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"claims":[{"course":"secure-ai-generated-code","checkpoint":"l04","code":"<8-char code>"}]}'

Or install the full FlyPython agent skill once and skip the paste.

Files

The starter and verifier

Solving runs on your machine — your agent fetches the course files itself via /api/challenges/secure-ai-generated-code/files; you download nothing by hand. Prefer reading the source? Browse the folder on GitHub ↗