Last updated: 2026-06-19
- Security and privacy are release blockers.
- Validate all untrusted input at API, Worker, webhook, and form boundaries.
- Enforce authorization server-side.
- Never commit secrets, tokens, app passwords, private keys, production IDs, or real customer data.
- Do not expose internal errors to users.
- Report only verified security checks.
.env,.env*.local,.vercel, and*.pemare ignored.- Required runtime secrets/config currently identified:
GMAIL_USERGMAIL_APP_PASSWORDLISTINGS_API_URLwhere deployed listings API differs fromhttps://malickland.net/api/listings- Cloudflare Worker secret
API_TOKEN - Optional Cloudflare Worker secret
LEADS_WEBHOOK - Cloudflare account ID and KV namespace IDs in deployment config
- Google Apps Script Sheet and Drive folder IDs
.env.exampleexists with names/placeholders only; never replace placeholders with real values in committed files.
- Production dependency advisories were found on 2026-05-27 in
next@16.1.6, transitivepostcss, andnodemailer@8.0.3; a newnodemailer <=9.0.0advisory was found on 2026-06-19. Manifests now targetnext@^16.2.6,nodemailer@^9.0.1,@types/nodemailer@^8.0.1,eslint-config-next@^16.2.6, plus apostcss@^8.5.10override, and bothnpm audit --omit=dev --jsonand fullnpm audit --jsonreported 0 vulnerabilities on 2026-06-19. /api/contactnow validates JSON shape, text types, required fields, field lengths, email shape, trims/sanitizes input, checks Gmail configuration before sending, avoids logging submitted form data, enforces request rate limits before mail send, and has repeatable validation plus route-level abuse tests./api/contactproduction abuse protection depends onCONTACT_RATE_LIMIT_REDIS_REST_URLandCONTACT_RATE_LIMIT_REDIS_REST_TOKENfor durable cross-instance limits. Without those values, the route falls back to an in-memory limiter that is suitable only for local development or a single long-running Node process. Partial Redis configuration and Redis timeouts fail closed.CONTACT_RATE_LIMIT_TRUST_PROXY_HEADERS=trueshould only be set when the hosting/proxy layer strips or overwrites inbound IP headers.- Cloudflare Worker CORS is currently
*; this may be too permissive for write-adjacent APIs. - Worker listing HTML needs focused review for image URL and script-context injection safety.
- Listing manager stores the Worker API token in
localStorage; this must remain a trusted local/admin-only tool, not a public admin surface. - Apps Script deployment guidance uses "Anyone" access and needs compensating validation/authentication when exposed to the internet.
- Lead capture stores IP address in KV; retention, necessity, and privacy notice should be reviewed.
npm audit --omit=devnpm audit- Contact API validation and failure-path tests.
- Contact API production abuse protection, including Redis REST limiter configuration before production deployment.
- Worker payload size, field validation, CORS/origin, and XSS tests.
- Secret scan before commit/deploy.
- Verify deployment route ownership and avoid accidental public admin/tool exposure.
- Verify no real credentials appear in docs, screenshots, logs, or committed files.
Agents must stop and document blockers in WORK_LOG.md and TASKS.md when security implications are unclear, credentials are missing, production data could be affected, destructive changes are required, or infrastructure changes cannot be verified safely.
- Every validated contact submission may be persisted to Supabase table
contact_leads(PII: name, email, phone, message, attribution) whenLEAD_BACKUP_SUPABASE_URL+LEAD_BACKUP_SUPABASE_KEYare configured. Purpose: lead-safety gate ("no lead silently drops"). - The shipped key is the Supabase publishable/anon key (non-secret by design). RLS grants INSERT only; no select/update/delete policies exist, so that key cannot read or tamper with stored leads. Reading requires the Supabase dashboard/service role.
- Known bounded risk: anyone with the publishable key can insert rows directly (spam), but cannot read PII. Rows are size-capped via CHECK constraints. If table spam appears, rotate the publishable key or switch to a service-role-only insert path (documented in DECISIONS.md).
- Double-failure path (mail failed AND backup unavailable): the sanitized lead is written to server logs (Vercel) as last-resort recovery. This intentionally places PII in logs only when the alternative is silent loss; log access is limited to the Vercel team.
- Never commit any Supabase key; env names are documented in
.env.example.