# Canary answer key — anti-complacency recall floor Ground truth for the `canary/` corpus. **Lives in `canary-meta/`, NOT in `canary/`**, so it is never in the detector's scan scope — the nightly sweep points its agentic detectors at `canary/` only and reads this file separately to score the run. Do not move this file under `canary/`. The corpus is deliberately vulnerable code (Python Lambda, SAM IaC, React, Node, .NET) with the answer-revealing comments stripped, so a run measures real detection, not comment-reading. **Counts:** 20 planted vulns (of which **19 are confirmed critical/high** — only #4 PAN-in-logs is MEDIUM), plus 2 safe decoys and 3 traps that must NOT be confirmed. **`CANARY_FLOOR = 8`** (see `canary-meta/CANARY_FLOOR`) — PROVISIONAL. Max crit/high recall is 19; the floor is set conservatively because this corpus has no comment hints and the cloud routine's native agentic pass is uncalibrated. Re-tune up toward the observed steady-state recall after the first runs report their numbers. A run that confirms FEWER than the floor in crit/high → COMPLACENCY ALARM. | # | File | Location | CWE | Severity | What | |---|---|---|---|---|---| | 1 | src/payment_handler.py | `DB_DSN` / `INTERNAL_API_KEY` | CWE-798 | HIGH | Hardcoded prod DB creds + API key | | 2 | src/payment_handler.py | `get_payment` query | CWE-89 | CRITICAL | SQL injection via `payment_id` f-string | | 3 | src/payment_handler.py | `get_payment` (no owner check) | CWE-639 | HIGH | IDOR — caller reads any payment | | 4 | src/payment_handler.py | `logger.info(... card ...)` | CWE-532 | MEDIUM | Full PAN to CloudWatch logs | | 5 | src/webhook_handler.py | `handle_webhook` | CWE-347 | CRITICAL | Webhook HMAC never verified | | 6 | src/webhook_handler.py | `fetch_vendor_logo` | CWE-918 | HIGH | SSRF — unvalidated user URL | | 7 | src/crypto_utils.py | `JWT_SECRET` | CWE-798/321 | HIGH | Hardcoded session signing secret | | 8 | src/crypto_utils.py | `hash_password` | CWE-327/916 | HIGH | Unsalted MD5 password hashing | | 9 | src/crypto_utils.py | `load_session` | CWE-502 | CRITICAL | Insecure deserialization (pickle on cookie) | | 10 | src/crypto_utils.py | `read_report` | CWE-22 | HIGH | Path traversal on report name | | 11 | infra/template.yaml | `PaymentFn` policy | CWE-732 | HIGH | IAM `Action:"*" Resource:"*"` | | 11b | infra/template.yaml | `ReportsBucket` | CWE-284 | HIGH | Public-access block disabled on PII bucket | | 11c | infra/template.yaml | `AdminSG` | CWE-284 | HIGH | SSH 0.0.0.0/0 | | 12 | web/PaymentForm.jsx | `dangerouslySetInnerHTML` | CWE-79 | HIGH | DOM XSS via vendor `note` | | N1 | src/node/orders_api.js | `GET /orders/:id` query | CWE-89 | CRITICAL | SQL injection — `id` concatenated | | N2 | src/node/orders_api.js | `GET /orders/export` | CWE-78 | CRITICAL | Command injection via `file` into `exec` | | N3 | src/node/orders_api.js | `db` config / `JWT_SECRET` | CWE-798 | HIGH | Hardcoded DB password + JWT secret | | N4 | src/node/orders_api.js | `GET /orders/:id` (no owner check) | CWE-639 | HIGH | IDOR — any user reads any order | | N5 | src/node/orders_api.js | `GET /orders/invoice` | CWE-22 | HIGH | Path traversal on `invoice` name | | D1 | src/dotnet/PaymentController.cs | `GetPayment` | CWE-89 | CRITICAL | SQL injection — `id` interpolated | | D2 | src/dotnet/PaymentController.cs | `LoadSession` | CWE-502 | CRITICAL | Insecure deserialization (BinaryFormatter) | | D3 | src/dotnet/PaymentController.cs | `ConnStr` | CWE-798 | HIGH | Hardcoded connection string w/ password | | D4 | src/dotnet/PaymentController.cs | `HashPassword` | CWE-327 | HIGH | Unsalted MD5 password hashing | **Decoys — must NOT be flagged:** - `src/payment_handler.py::list_my_payments` — parameterized query, scoped to `caller`. - `src/node/orders_api.js` `GET /my-orders` — parameterized query, scoped to `user_id`. **Traps — must NOT be confirmed (test the verifier's kill path):** - `src/payment_handler.py::payments_by_status` — f-string SQL, but `status` is allowlisted against `ALLOWED_STATUSES` and `user_id` is parameterized. SQLi claim must be killed → unverified. - `infra/template.yaml` `WebSG` — `0.0.0.0/0` on port 443 is normal public HTTPS; info at most. - `src/dotnet/PaymentController.cs::ByStatus` — `status` passed as a `SqlCommand` parameter; safe.