Homework

Homework — 403 vs 404, and the guard in SQL

Expand the app. Give your app the same owner surface, or work on SignFlow.

  1. Feel 403 vs 404. Temporarily change ownedDocument to fetch with an unscoped query and return 403 when doc.OwnerID != me. Log in as user B and request user A's id — you get 403, which confirms the id is real. Now revert to the owner-scoped query + 404. Write two sentences on what an attacker learns in each case.

  2. Delete the check, keep the guard. Comment out the if doc.Status != "draft" branch in DeleteDocument. Flip a document to sent and try to delete it — the UI will not show the button, so craft the POST by hand. Confirm DeleteDraftDocument reports zero rows deleted and the document survives. The business rule held with the handler check gone, because it lives in the WHERE.

  3. Download integrity. Download a document, run sha256sum on the file, and compare to the file_hash on the detail page — they match. Now edit the stored blob in uploads/ directly (append a byte) and download again: the bytes changed but file_hash did not. What would it take to detect that drift? (You build exactly that check in the signing phase.)

  4. A missing id. Request /documents/not-a-uuid and /documents/00000000-0000-0000-0000-000000000000. Both should be 404 — one because parseUUID rejects it before the DB, the other because the query returns no rows. Confirm neither reaches serverError (no 500 in the log).

Next phase, documents stop being private: you invite signers by email, and a document moves from draft to sent — the immutability rule you just built is what makes that transition safe.