{"slug":"security-review-pack","name":"Security Review Pack","kind":"knowledge","version":1,"format_version":1,"checksum":"b9be1983d9bd2857280239da58feee5efa01f51730b97335a7a4164fc2ced7d0","bundle":{"edges":[{"label":"hardcoded secrets are insecure code","edge_type":"related","sort_order":0,"is_directed":false,"source_slug":"secure-coding","target_slug":"secrets-handling"},{"label":"your deps' bugs are your bugs","edge_type":"related","sort_order":1,"is_directed":false,"source_slug":"dependency-hygiene","target_slug":"secure-coding"},{"label":"CI and lockfiles both leak","edge_type":"related","sort_order":2,"is_directed":false,"source_slug":"secrets-handling","target_slug":"dependency-hygiene"}],"pages":[{"body":"# The security review checklist\n\nThe pass a reviewer makes on every change that touches an input, an output, or a permission. Any unchecked box is a finding, not a suggestion.\n\n## Inputs\n- Every external input is validated at the boundary — type, length, range, allowed values — before it touches a query, a template, or a file path.\n- Identifiers from the client (ids, slugs) are authorized, not just parsed: \"can THIS caller act on THIS record\", on every route, not just the UI path.\n- File uploads: extension allow-list, size cap, content type verified server-side, stored outside the web root under a generated name.\n\n## Outputs\n- All rendering escapes by default; raw-HTML sinks require sanitization at the point of OUTPUT, not the point of storage.\n- Error responses say what failed, never how: no stack traces, SQL, hostnames, or dependency versions to the client.\n\n## Defaults\n- Deny by default: a route with no explicit permission check is a bug even when it \"only\" reads.\n- New endpoints get a rate limit before launch, not after the incident.\n","icon":"🛡️","title":"The security review checklist","category_slugs":["secure-coding"]},{"body":"# Injection defenses by surface\n\nOne rule per surface. There are no clever exceptions.\n\n- **SQL** — parameterized queries only. String-built SQL is a finding even when every current input is \"trusted\"; inputs change, the query stays.\n- **Shell** — no string-concatenated commands. Use argument arrays, and prefer a library call over a subprocess at all.\n- **HTML** — escape on output; sanitize (allow-list, not deny-list) anywhere users can author rich content.\n- **Paths** — resolve the final path and verify it is inside the intended directory; reject `..` before it reaches the filesystem, not after.\n- **Templates / prompts** — user content is DATA. It never becomes template code or system instructions, no matter how it's quoted.\n\nWhen a reviewer can't tell whether a value is attacker-controlled, treat it as if it is — provenance is the attacker's favorite ambiguity.\n","icon":"💉","title":"Injection defenses by surface","category_slugs":["secure-coding"]},{"body":"# Handling secrets end to end\n\n## Where secrets live\n- The environment or a secret store — never code, config files, test fixtures, or \"temporary\" commits (git history is immortal).\n- One secret, one purpose: a token shared across services can't be rotated without an outage, so it never gets rotated.\n\n## Where secrets must never appear\n- Logs — redact before shipping anywhere central; a debug line with a bearer token is a breach with a timestamp.\n- URLs — query strings end up in access logs, proxies, and browser history.\n- Error messages and crash reports.\n\n## Comparing and rotating\n- Compare tokens with a constant-time comparison (`hmac.compare_digest`), never `==`.\n- Rotation is a drill, not a document: if rotating your main API key takes more than an hour, that IS the finding.\n\n## When one leaks\nRotate FIRST, investigate second. Assume the old value is public forever, audit what it could reach, then close the path it leaked through.\n","icon":"🗝️","title":"Handling secrets end to end","category_slugs":["secrets-handling"]},{"body":"# Dependency hygiene that actually happens\n\nA dependency policy fails when it demands more diligence than a team has. These rules are the minimum that still works.\n\n## Adopting\n- Before adding a dependency, answer three questions in the PR: is it maintained (commits in the last year), what does it pull in transitively, and could we write the 20 lines ourselves?\n- Default to the standard library and the platform before a package.\n\n## Pinning\n- Lockfiles are committed, always — builds must be reproducible.\n- Direct dependencies get explicit version ranges; CI fails on a lockfile that drifts from the manifest.\n\n## Updating\n- A standing cadence beats heroics: minor/patch updates on a weekly schedule, security criticals same-day.\n- An audit gate in CI blocks known-vulnerable versions from merging — a gate is only useful if it can actually fail the build.\n\n## Removing\n- Unused dependencies are attack surface with zero benefit. Removing one is always an acceptable PR.\n","icon":"📦","title":"Dependency hygiene that actually happens","category_slugs":["dependency-hygiene"]}],"format":"bloom.knowledge_bundle","version":1,"bindings":[],"categories":[{"icon":"🛡️","name":"Secure Coding","slug":"secure-coding","color":"#f87171","sort_order":0,"description":"The checks a reviewer makes on every change that touches an input, an output, or a permission.","parent_slug":null,"default_mode":"enforce"},{"icon":"🗝️","name":"Secrets Handling","slug":"secrets-handling","color":"#fbbf24","sort_order":1,"description":"Where secrets live, where they must never appear, and the rotate-first leak drill.","parent_slug":null,"default_mode":"enforce"},{"icon":"📦","name":"Dependency Hygiene","slug":"dependency-hygiene","color":"#60a5fa","sort_order":2,"description":"Adopting, pinning, updating, and removing dependencies — the minimum policy that still works.","parent_slug":null,"default_mode":"follow"}],"exported_at":"2026-07-17T00:00:00Z","source_organization_id":null}}