← back to Codex Review 2026 04 30

stayclaim/review.md

36 lines

## Snapshot
- Address-first property-history / archive app with owner claim and sponsored placement remnants.
- Stack: TypeScript, Next.js 14, React 18, Tailwind, Node, PostgreSQL; PostGIS claimed in README but not observed in schema.
- How it's run: `npm run dev/start` on `0.0.0.0:9821`; launchd/pm2 notes for jobs; prod process manager unclear.
- Status: live — prod env/scripts present, recent project docs.

## Top Risks (P0 — fix this week)
- `.env.local:4`: real Google Maps browser key is in the workspace; if unrestricted, cost/abuse risk. Rotate it, remove `.env.local`, enforce HTTP referrer restrictions and quotas.
- `scripts/filmla-cpra-followup.sh:15-16`: hardcoded Basic Auth credential to a Gmail HTTP agent over plain HTTP tailnet URL; repo leak grants email search/send. Rotate credential; load from Keychain/env; require TLS or strict Tailscale ACL.
- `src/app/admin/layout.tsx:45-46`, `src/app/admin/data/page.tsx:167-192`: admin is explicitly unauthenticated and exposes claim/sponsored emails. Put real server-side auth in front of all `/admin/*`, plus nginx deny until ready.
- `src/app/dashboard/page.tsx:11-14`, `src/app/dashboard/page.tsx:21`, `src/app/dashboard/page.tsx:59-63`: `?email=` acts as authentication; anyone who knows an email can view owner listings/pending claims. Replace with magic-link/session auth before exposing.
- `src/app/search/page.tsx:128-130`, `src/app/api/search/route.ts:107-109`: OCR snippets are rendered with `dangerouslySetInnerHTML`; OCR/source text can become stored XSS. Escape source text and render `<mark>` tokens safely.
- `package.json:17`: `next@14.2.15` is below the patched `14.2.25` for CVE-2025-29927 middleware auth bypass; especially dangerous if `/admin` auth is added in middleware only. Upgrade Next or block `x-middleware-subrequest` at the proxy. 

## Notable Issues (P1 — fix this month)
- `src/app/claim/[slug]/page.tsx:34-60`: claim form has no action/client submit handler/API route; user submissions go nowhere. Wire it to a POST route or remove the CTA.
- `src/app/search/page.tsx:44-48`: server-side fetch trusts the incoming `Host` header to build an absolute URL; Host spoofing can trigger SSRF-ish outbound fetches and attacker-controlled results. Call shared search code directly or allowlist hosts.
- `src/app/api/search/route.ts:47-158`: no max query length; one huge query fans out across seven DB searches including OCR. Add Zod validation and hard caps before querying.
- `scripts/extract-news-addresses.ts:212-242`: deletes previous mentions, inserts, then marks extracted without a transaction/advisory lock; crash or concurrent run can leave partial data. Wrap per-issue work in a transaction and lock issue id.
- `src/app/admin/data/page.tsx:217-223`: DB errors in filter dropdowns are swallowed as empty options, hiding broken admin data. Log/report the error visibly.
- `package.json:12`: `ingest:airbnb` points to missing `scripts/ingest-airbnb.ts`; documented command fails. Remove or restore script.

## Polish (P2 — when convenient)
- `README.md:56` says dev port `9820`; `package.json:6-8` uses `9821`.
- `.env.production.example:9` contains a concrete prod-ish `DATABASE_URL`; make it placeholder-only.
- `scripts/pd-film-images-refresh.sh:16-17` hardcodes root SSH target/path; move host/path to env for easier rotation.

## Strengths
- Public write routes use Zod and parameterized SQL (`src/app/api/submit/route.ts:5-35`, `src/app/api/promote/route.ts:27-64`).
- Privacy gate exists at DB level for living-person associations (`db/schema.sql:124-143`), not only UI policy.
- Data ingestion scripts are mostly idempotent with `ON CONFLICT` patterns.

## Open questions for the owner
- Is `/admin` reachable from the public internet today?
- Is `.env.local` tracked, backed up, or synced anywhere outside this machine?
- Should claim flow remain in scope, or is this now a read-only archive?