{"id":"sec-dev-cors-allowlist-fix","audience":"developers","category":"security","track":"security","technique":"constraints","title":"Replace a wildcard CORS setting with an allowlist","free":false,"task":"Fix this CORS setup so only your live front ends can call the API with cookies. Use the origins from the deployment notes in a const ALLOWED_ORIGINS array passed straight to origin (no callback function), keep credentials on, and don't add origins that are gone. Give the fixed setup in exactly 1 code block of at most 10 lines, with under 30 words outside it.","materialLabel":"Server code and deployment notes (attached to your prompt automatically)","material":"server/app.js (Express)\n\nimport express from \"express\";\nimport cors from \"cors\";\n\nconst app = express();\n\napp.use(cors({\n  origin: \"*\",\n  credentials: true,\n  methods: [\"GET\", \"POST\", \"PUT\", \"DELETE\"],\n}));\n\napp.use(express.json());\napp.use(\"/api\", apiRouter);\n\nDeployment notes (Nov 2026):\n- Customer app: https://app.example.com\n- Admin console: https://admin.example.com\n- The old staging site https://staging.example.com was shut down in September. Its DNS name is now unclaimed.\n- Local development uses the Vite proxy, so localhost does not need CORS.\n- Mobile apps call the API directly and don't use CORS.\n\nBug report from QA: \"Login cookie isn't sent from app.example.com, the browser says wildcard origin can't be used with credentials.\"","rules":[{"type":"codeBlocks","count":1},{"type":"maxCodeLines","max":10},{"type":"maxProseWords","max":30}],"mistakes":[{"title":"Not giving the real origins or style","why":"The AI writes placeholder origins or a custom callback function your team has to review line by line.","fix":"Say: \"A const ALLOWED_ORIGINS array with the two origins from the notes, passed directly as origin.\"","checks":["r0"]},{"title":"Keeping dead origins","why":"The AI adds staging and localhost to be helpful, and an unclaimed staging domain is a takeover risk.","fix":"Add: \"Don't include staging or localhost, and no special case for missing Origin.\"","checks":["r1"]},{"title":"Turning off credentials","why":"The AI's quickest fix for the browser error is dropping credentials, which breaks login.","fix":"Say: \"Keep credentials: true and the same methods.\"","checks":["r2"]},{"title":"A CORS lecture","why":"The AI explains CORS for paragraphs around the fix.","fix":"Limit it: \"1 code block, under 20 words outside it.\"","checks":["codeBlocks","maxCodeLines","maxProseWords"]}],"promptHints":[{"label":"Uses the real origins","patterns":["app\\.example\\.com","admin\\.example\\.com","deployment notes","allowlist"]},{"label":"Drops dead origins","patterns":["staging","localhost","shut down"]},{"label":"Keeps credentials","patterns":["credentials"]},{"label":"Asks for one code block","patterns":["(one|1|single) code block","code block"]},{"label":"Limits the explanation","patterns":["\\d+\\s*words","short (note|explanation)","one (line|sentence)","no (explanation|prose)"]}],"hint":"The AI can't know which front ends are live. Give it the exact origins, say which ones are gone, and that login cookies must keep working.","rubricLabels":["A const ALLOWED_ORIGINS array holds exactly https://app.example.com and https://admin.example.com and is passed directly as origin (no callback function, no wildcard)","It does not include https://staging.example.com or any localhost origin, and adds no special case for requests without an Origin header","credentials: true is kept and the allowed methods are left as they were"]}