← back to Codex Review 2026 04 30
consensus/Norma.md
62 lines
# Consensus — Norma
Generated 2026-04-30T23:48:03-07:00. Source: codex review/fix/rereview/round-2 artifacts (round-3 was quota-blocked).
## Codex pass-1 review (top risks)
## Top Risks (P0 — fix this week)
- `.env.local:1`, `.env.local:3-5`, `.env.local:14`, `.env.local:21-22`, `CLAUDE.md:12-13`, `README.md:21`: live-looking DB/admin/cron/session/Gemini/Gmail secrets are committed/documented. Rotate all exposed credentials, remove secrets from repo/docs, purge history if shared.
- `middleware.ts:55-89` + `app/api/social/ai-image/route.ts:20`, `app/api/social/bulk-schedule/route.ts:66`, `app/api/social/bulk-schedule/route.ts:88`, `app/api/social/posts/approve/route.ts:10`, `app/api/social/posts/pending/route.ts:10`: middleware only checks token structure/expiry, while these routes do no `requireRole`; forged cookies can generate images, schedule/approve/read social posts. Add `requireRole` to every non-public route or crypto-verify in middleware.
- `app/api/pulse/petitions/route.ts:111-182`: unauthenticated public POST creates immediately active petitions with no rate limit, moderation, captcha, or abuse controls. Default to draft/moderation and add IP/user rate limiting.
- `app/api/pulse/petitions/[id]/sign/route.ts:13-44`, `app/api/pulse/petitions/[id]/sign/route.ts:51-52`: public signing has no rate limit and does not check `allow_signatures`; any active petition can be spam-signed. Check `allow_signatures`, add rate limiting/captcha/email verification.
- `app/api/nonprofit/enrich/route.ts:179-184`, `app/api/nonprofit/enrich/route.ts:227-235`, `app/api/email-analyzer/fetch-url/route.ts:202-215`, `lib/social-crawl.ts:111-117`: server-side URL fetch accepts arbitrary HTTP(S), follows redirects, and does not block private/link-local IPs. Add SSRF guard: DNS/IP validation, redirect revalidation, size limits, and deny internal ranges.
- `lib/auth.ts:5-8`, `lib/auth.ts:179-189`, `lib/cron-auth.ts:8`, `app/api/webhooks/pulse-topic/route.ts:6`: secrets fall back to predictable values and auth cookie is only `Secure` when `USE_HTTPS=true`. Fail closed in production and always set `Secure` behind HTTPS.
## Round-1 applied
## Applied
- `lib/auth.ts:5` — removed the generated fallback session secret and now fail fast when `SESSION_SECRET` is missing — prevents predictable session signing.
- `app/api/webhooks/pulse-topic/route.ts:6` — removed the hardcoded webhook secret fallback and now fail fast when `WEBHOOK_SECRET` is missing — prevents accepting a public source secret.
- `lib/detect-mentions.ts:20` — removed the hardcoded Gemini API key fallback and now requires `GEMINI_API_KEY` before calling Gemini — prevents leaking or using an embedded API key.
- `app/api/drafts/auto-generate/route.ts:12` — replaced a hardcoded Gemini API key with `GEMINI_API_KEY` plus a fail-fast check — removes a real secret from source.
- `app/api/social/ai-compose/route.ts:4` — replaced a hardcoded Gemini API key with `GEMINI_API_KEY` plus a fail-fast check — removes a real secret from source.
- `.env.example:1` — added a placeholder `SESSION_SECRET` value — documents required config without committing a real secret.
- `.env.example:2` — added a placeholder `WEBHOOK_SECRET` value — documents required config without committing a real secret.
- `.env.example:3` — added a placeholder `GEMINI_API_KEY` value — documents required config without committing a real secret.
## Codex re-review verdict: FIX BEFORE SHIP
## New issues introduced
`app/api/webhooks/pulse-topic/route.ts:11` — same route now fail-fasts for `WEBHOOK_SECRET` but still builds Gemini URL with `GEMINI_API_KEY || ''`; missing Gemini config becomes a runtime 502 path instead of the documented fail-fast behavior — P2
## Round-2 applied
## Round 2 Applied
- `app/api/webhooks/pulse-topic/route.ts:8` — added a `GEMINI_API_KEY` guard and builds the Gemini URL only when called — fixes CODEX_REREVIEW “New issues introduced” concern about `GEMINI_API_KEY || ''`.
- `app/api/webhooks/pulse-topic/route.ts:29` — moved missing `WEBHOOK_SECRET` handling into the route response instead of module-scope throw — addresses CODEX_REREVIEW “concern” on this patch.
- `scripts/detect-news-mentions.ts:16` — removed the hardcoded Gemini fallback and require `GEMINI_API_KEY` for Gemini calls — fixes CODEX_REREVIEW “Missed call-sites”.
- `scripts/detect-news-mentions.ts:185` — fail fast when `--gemini` is used without `GEMINI_API_KEY` — keeps the missed-call-site fix from being swallowed by the fallback catch path.
- `scripts/enrich-news-ai.js:18` — removed the hardcoded Gemini key and require `GEMINI_API_KEY` before requests — fixes CODEX_REREVIEW “Missed call-sites”.
- `agents/shared/ai.js:12` — removed the hardcoded Gemini fallback and require `GEMINI_API_KEY` — fixes CODEX_REREVIEW “Missed call-sites”.
- `app/api/outreach/generate/route.ts:8` — replaced empty-key Gemini URL fallback with a guarded URL helper — fixes CODEX_REREVIEW “Missed call-sites”.
- `app/api/outreach-pipeline/generate/route.ts:7` — replaced empty-key Gemini URL fallback with a guarded URL helper — fixes CODEX_REREVIEW “Missed call-sites”.
- `app/api/pipeline/generate/route.ts:8` — replaced empty-key Gemini URL fallback with a guarded URL helper — fixes CODEX_REREVIEW “Missed call-sites”.
- `app/api/statements/generate/route.ts:8` — replaced empty-key Gemini URL fallback with a guarded URL helper — fixes CODEX_REREVIEW “Missed call-sites”.
- `app/api/agents/petition/generate/route.ts:5` — replaced empty-key Gemini URL fallback with a guarded URL helper — fixes CODEX_REREVIEW “Missed call-sites”.
- `app/api/agents/advocacy/generate/route.ts:5` — replaced empty-key Gemini URL fallback with a guarded URL helper — fixes CODEX_REREVIEW “Missed call-sites”.
## Still deferred for Steve
- P0 — `.env.local:1`, `.env.local:3-5`, `.env.local:14`, `.env.local:21-22`, `CLAUDE.md:12-13`, `README.md:21` — live-looking secrets are committed/documented — requires owner rotation, history decision, and careful secret cleanup.
- P0 — `middleware.ts:55`, `app/api/social/ai-image/route.ts:20`, `app/api/social/bulk-schedule/route.ts:66`, `app/api/social/posts/approve/route.ts:10`, `app/api/social/posts/pending/route.ts:10` — forged cookies can reach social routes — auth model change was outside safe fixes.
- P0 — `app/api/pulse/petitions/route.ts:111` — public petition creation lacks abuse controls — moderation/rate-limit behavior needs product decision.
- P0 — `app/api/pulse/petitions/[id]/sign/route.ts:13` — public signing lacks rate limit and `allow_signatures` enforcement — abuse-control design needed.
- P0 — `app/api/nonprofit/enrich/route.ts:179`, `app/api/email-analyzer/fetch-url/route.ts:202`, `lib/social-crawl.ts:111` — arbitrary URL fetch can SSRF — needs a shared network guard and testing.
- P0 — `lib/cron-auth.ts:8`, `lib/auth.ts:179` — cron-secret fallback/cookie Secure behavior remains — not a literal hardcoded secret fallback; may affect deployment routing.
- P1 — `next.config.ts:5` — production ignores TypeScript/ESLint errors — could break builds if changed blindly.
- P1 — `app/api/auth/google/route.ts:15`, `app/api/auth/google/callback/route.ts:10` — OAuth lacks state validation — auth flow change needs testing.
- P1 — `app/api/nonprofit/signup/route.ts:170` — duplicate email race/unique index missing — database schema changes are forbidden.
- P1 — `db/schema.sql:252` — schema bootstrap issue — `.sql` edits are forbidden.
- P1 — `app/api/social/bulk-schedule/route.ts:108` — bulk schedule has no request size caps — validation behavior change outside safe list.
- P1 — `app/api/email-analyzer/fetch-url/route.ts:241` — response body read before truncation — network/body streaming refactor outside safe list.