Scope: public website + seeded registry (current), with forward posture for Phases 3–6 (auth, uploads, certificates, payments). Method: assets → adversaries → attacks → mitigations. Revisit at every phase gate.
Assets worth protecting
- Registry integrity — statuses, claims, history (the product is trust)
- Precise geometry & identity documents — safety + data protection
- Issuer keys — Amoy now, mainnet someday; also Supabase service role
- Funds & payment data — Phase 6
- Reputation for honesty — simulated vs real labelling
Adversaries
Opportunistic scrapers/bots · land-dispute counterparties seeking data or manipulation · fraudulent registrants (fake/duplicate assets) · insider error (most likely!) · key thieves · demo-day failure modes (offline, expired tokens).
Attacks and mitigations
A. Public web (current build)
| Threat | Mitigation |
|---|---|
| Scraping precise farm locations | Only generalised geometry exists client-side (fixtures contain no precise data); Phase 3 keeps precise columns behind no-anon RLS |
| Content injection via search params | q/type/status validated against allowlists; React escapes output; no dangerouslySetInnerHTML |
| Fake-record confusion | Registry refs are unique + human-quotable; superseded/revoked records stay visible with reasons |
| Demo failure (no network/keys) | Fixture-first data layer; schematic SVG map; system fonts; zero runtime secrets |
B. Registration & verification (Phase 3 posture)
| Threat | Mitigation |
|---|---|
| Duplicate/overlapping claims | Geometry overlap + proximity screening before verifier review; disputes as first-class workflow |
| Fraudulent evidence | sha256 at upload; verifier review; field inspection tier for higher-value claims; revocation with public reasons |
| Privilege escalation | Roles in user_roles granted by admin only; RLS has_role() checks; audit log on grants |
| IDOR on evidence files | Private bucket; server-issued short-lived signed URLs after grant check; no direct storage paths in client |
| Mass-assignment / injection | Zod validation on every server action; parameterised queries via Supabase client |
C. Chain & keys (Phase 5 posture)
| Threat | Mitigation |
|---|---|
| Issuer key theft | Key separation: deployer ≠ issuer ≠ admin; keys in Vercel/GH encrypted secrets, never in repo; Pausable contract; revocation path |
| Wrong data pinned forever | Pre-pin review against DATA_CLASSIFICATION.md; metadata contains public-class fields only; versioned tokenURI |
| Token misinterpreted as title | Transfers disabled; contract emits registry ref; UI copy; docs |
| Testnet/mainnet confusion | Amoy-only check constraint; distinct explorer links; mainnet checklist gate |
D. Payments (Phase 6 posture)
Mock mode default; provider adapter isolates PCI surface to the provider's hosted flow; sponsorship requires recorded disclosure acceptance; refund path documented before live mode; no card data ever touches Corngate servers.
Key management (summary)
- Never in repo.
.envgit-ignored;.env.exampledocuments names only. - Supabase service-role key: server-only, never
NEXT_PUBLIC_*. - Amoy deployer/issuer keys: generated fresh for the pilot, funded minimally, stored in platform secret managers; rotation documented in DEPLOYMENT runbook (Phase 10).
- Mainnet keys: do not exist in v0.1 (see REGULATORY_BOUNDARIES §5).
Incident posture
Audit log append-only; single admin contact route (/contact → founder) for disputes/takedowns; incident notes belong in STATUS.md changelog until a formal register exists (Phase 9).
