← back to Codex Review 2026 04 30
trademarks-copyright/review.md
37 lines
## Snapshot
- Next.js app for finding trademark/copyright/domain opportunities and sending paid “Drops” newsletters.
- Stack: TypeScript, Next.js App Router, React 19, Tailwind, Postgres, local Ollama/Qwen, Stripe REST, Resend/SMTP/file email.
- How it's run: manual `npm run dev/start`; jobs via launchd/cron-style scripts; no pm2/systemd observed.
- Status: live/local-prod: recent logs and `.env.local` prod-ish config.
## Top Risks (P0 — fix this week)
- `package.json:18`, `src/middleware.ts:14`: Next `15.0.3` is vulnerable to middleware auth bypass; admin auth relies on middleware. Upgrade to `15.2.3+` or block `x-middleware-subrequest` at the edge. citeturn0search4
- `src/app/api/drops/send/route.ts:15`, `src/app/api/drops/compose/route.ts:8`, `src/app/api/score/route.ts:9`, `src/app/api/brands/hunt/route.ts:12`: public mutation routes can send newsletters, compose drops, rescore DB, and run crawler/LLM jobs. Put all operational routes behind server-side auth, not only middleware path matching.
- `src/app/api/drops/drop-items/[id]/route.ts:8`: public PATCH edits newsletter content/CTA URLs. Move under `/api/drops/admin/*` or enforce auth inside the handler.
- `src/app/api/drops/checkout/route.ts:23`: if Stripe is unset, any caller can set any subscriber email to paid tier at lines `24-30`. Remove dev fallback outside `NODE_ENV !== "production"` and require subscriber token/session.
- `.env.local:30`, `.env.local:47-48`: plaintext admin token and Basic auth credential, including raw password comment, sit in project dir. Rotate both; store outside repo/workspace backups.
- `src/app/api/drops/cron/route.ts:15`: cron route is open if `CRON_TOKEN` is unset; `.env.local:25` uses `change-me-before-public`. Fail closed when missing and rotate token.
## Notable Issues (P1 — fix this month)
- `src/app/api/drops/send/route.ts:43-45`, `src/app/api/drops/send/route.ts:85-90`, `src/lib/drops.ts:381-386`: transient email failure creates a delivery row and marks bounced, so future sends skip that subscriber forever for that drop. Track retryable failure separately or include failed deliveries in retry selection.
- `src/lib/drops.ts:197-210`, `src/lib/drops.ts:233-244`: compose checks/deletes/inserts without transaction/advisory lock; concurrent cron/admin calls can race and fail or waste LLM work. Wrap by `drop_date` in a transaction with lock/upsert.
- `src/app/api/drops/admin/subscribers/[id]/route.ts:18-21`: dynamic SQL identifiers are from an allowlist, good, but values are unvalidated; invalid tier/status can poison subscriber state. Validate enum/ranges before update.
- `src/lib/brandHunter.ts:283`, `src/app/api/brands/hunt/route.ts:18`: caller controls seed list and cap without auth or tight cap; can burn network/LLM resources. Auth it and clamp seeds/cap.
- `src/lib/email.ts:96-105`: file email fallback writes newsletter HTML with recipient metadata to `/tmp/drops`. In production, fail closed if no real email backend is configured.
## Polish (P2 — when convenient)
- `logs/*.log`: runtime logs are in the project tree despite `.gitignore:5`; keep logs outside repo directory.
- `README.md:22`, `PLAN.md:42`: docs mention Claude/Anthropic, but code uses local Qwen/Ollama. Update docs to prevent bad setup.
- Tests cover scoring/lifecycle only; no route/auth tests for the newsletter and admin surfaces.
## Strengths
- SQL query parameters are used consistently for user input; no obvious SQL injection observed.
- Admin middleware fails closed when `ADMIN_TOKEN` is missing in production.
- Email HTML escapes DB/LLM content before rendering.
## Open questions for the owner
- Is this exposed publicly, or only reachable over a private tunnel/VPN?
- Is Stripe intentionally disabled in production right now?
- Are `.env.local` and `logs/` included in any backup/sync/shared folder?
Verification: `npm test` passes, 23/23.