This project is a small shop API, already written, with four holes from this section. Your job is an audit: for each hole, see what the attacker gets, then close it with one check while honest visitors keep working.
The four attacks: read_token never checks the signature, so a visitor who writes their own token with {"customer_id": 2} reads another customer's orders. Anyone who reads the log sees Ana's password. GET /help/../secrets.txt hands out the token secret and the supplier's API key. And /continue?next=https://evil.example/login turns a link on the shop's own address into a trip to the attacker's page.
How to spot them: every value that comes from the request, a token, a JSON field, a path or a query parameter, is the visitor's choice. In this route from another shop, file=../app.py would read the shop's own code:
@app.get("/download")
def download():
file = request.args.get("file") # the visitor picks this
return send_file(os.path.join("files", file))
The requests and answers your code must give:
POST /login {"email": "ana@example.com", "password": "ana-pass-123"} → 200 {"token": "<token>"}
GET /_test/log → 200 ["POST /login {'email': 'ana@example.com', 'password': '***'}"]
GET /orders with Ana's token → 200 [{"id": 1, "ordered": "2026-03-02", "status": "paid"}, {"id": 3, "ordered": "2026-04-05", "status": "paid"}]
GET /orders with a forged token for customer 2 → 401 {"error": "Login required"}
GET /help/shipping.txt → 200 We ship every order within 2 working days.
GET /help/../secrets.txt → 404 {"error": "Page not found"}
GET /continue?next=/orders → 302 Location: /orders
GET /continue?next=https://evil.example/login → 302 Location: /
GET /continue?next=//evil.example/login → 302 Location: /
Your task: close the four holes: refuse a token whose signature doesn't match, hide the password in the log, answer 404 for a help path that leaves the help folder, and send any next that isn't a page of the shop to /.
Assignment
Follow the Coding Tutorial to add one check to each of read_token, log_request, help_page and continue_to.
Hint
Each fix is an if with one line inside. Compare signatures with hmac.compare_digest, compare the real path with HELP + os.sep so a folder like help-old doesn't slip through, and remember that a browser reads //evil.example as another website.
Free plan: 30 tutor messages. PRO is unlimited.
