{"id":"sec-dev-upload-handler-review","audience":"developers","category":"security","track":"security","technique":"grounding","title":"Review a file upload handler against the team checklist","free":false,"task":"Review this upload handler against the team's upload checklist and write one numbered line per checklist item, in checklist order, each starting 'Pass' or 'Fail' and naming the code line that shows it. Judge only what the code shows. Plain text, under 110 words.","materialLabel":"Upload handler and checklist (attached to your prompt automatically)","material":"Team upload checklist (security wiki):\nC1. Size limit enforced before the file is saved\nC2. File type checked from the file's content, not only the extension\nC3. Saved with a server-generated name, never the user's file name\nC4. Stored outside the public web folder\nC5. Only logged-in users can upload\n\nsrc/routes/avatar.js\n 1  import multer from \"multer\";\n 2  import { fileTypeFromBuffer } from \"file-type\";\n 3  const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: 2 * 1024 * 1024 } });\n 4\n 5  router.post(\"/avatar\", requireLogin, upload.single(\"avatar\"), async (req, res) => {\n 6    const type = await fileTypeFromBuffer(req.file.buffer);\n 7    if (!type || ![\"image/png\", \"image/jpeg\"].includes(type.mime)) {\n 8      return res.status(400).send(\"PNG or JPEG only\");\n 9    }\n10    const dest = path.join(\"public/uploads\", req.file.originalname);\n11    await fs.writeFile(dest, req.file.buffer);\n12    res.json({ url: \"/uploads/\" + req.file.originalname });\n13  });\n\nPR description: \"Ready to merge, I followed the checklist.\"","rules":[{"type":"numberedItems","count":5},{"type":"plainText"},{"type":"maxWords","max":110}],"mistakes":[{"title":"A general security review","why":"The AI lists every upload risk it knows instead of checking your 5 items.","fix":"Say: \"One line per checklist item, in order.\"","checks":["r2","numberedItems"]},{"title":"Fail-by-default","why":"The AI marks items as risky even where the code clearly handles them.","fix":"Add: \"Judge only what the code shows; Pass when it's handled.\"","checks":["r0"]},{"title":"Trusting the PR description","why":"The AI accepts 'I followed the checklist' and passes everything.","fix":"Ask: \"Name the code line that proves each result.\"","checks":["r1"]},{"title":"Trusting the AI to count words","why":"AI is bad at counting. Ask for \"under 110 words\" and it often lands well over.","fix":"Ask for less than the limit and cap each line: \"each line under 15 words, under 80 words total.\"","checks":["maxWords"]},{"title":"Letting markdown in","why":"AI adds bold and headings by default, and in the PR review they show up as stray symbols.","fix":"Add: \"Plain text only, no bold, no headings.\"","checks":["plainText"]}],"promptHints":[{"label":"Uses the checklist","patterns":["checklist","c1","each item"]},{"label":"Gives the labels","patterns":["pass","fail"]},{"label":"Asks for line evidence","patterns":["line (number)?","evidence","cite","which line"]},{"label":"Keeps it to the material","patterns":["only (use )?(the )?(facts|what|info|details)","don'?t (invent|make up|guess|add)","not in the","stick to","nothing (else|extra|outside)"]},{"label":"Asks for plain text","patterns":["plain","no bold","no markdown","no formatting","no headings"]}],"hint":"A checklist review is only useful if each answer points at proof. Ask for one Pass or Fail per item, in order, with the code line that shows it.","rubricLabels":["C1 (line 3), C2 (lines 6 to 7) and C5 (line 5, requireLogin) are marked Pass","C3 is marked Fail because line 10 uses req.file.originalname, and C4 is marked Fail because files go to public/uploads","Each line names the code line number that shows the result, and the lines are in checklist order C1 to C5"]}