Architecture & security

Trust, built into the schema. 

Stock audits move a lender's most sensitive data between four parties. So we didn't bolt security on, we started there: isolation enforced by the database, encryption per tenant, presence proven by GPS.

PER-TENANT TRUST BOUNDARYWeb portalField appScannerHardened edge01next.config.tsSession rotation02middleware.tsPer-IP throttle03rate-limit.tsKey-verified identity04jwt.tsZero-trust claims05permissions.tsSchema enforcement06@repo/validationRow-level isolation07db/rls.tsPostgresRLS on every rowAES-256-GCM at restPer-org DEKstored wrappedMaster keyenv · never in the DBOUTSIDE THE DATABASEwraps

The path of a single request. Every gate is a real control - the file it lives in is printed beneath it.

AES-0

GCM encryption at rest

0%

Requests scoped by RLS

0

Full Aadhaar numbers stored

0%

Scans carry GPS provenance

The six things that matter

Security you can point to

No hand-waving. Each pillar maps to a concrete mechanism in the platform.

Isolation

Row-level security, not app-level hope

Every request runs inside a transaction that sets request.jwt.claims and switches to the non-superuser authenticated role - so Postgres itself refuses to return another tenant's rows. It is the database enforcing isolation, not a WHERE clause an application bug could drop. An automated suite tries to breach it on every run.

Encryption

Envelope encryption, keys held apart

Each organisation gets a random 256-bit data key. That key is stored only in wrapped form, sealed by a master key that lives in the environment and never in the database. Sensitive fields - inventory item names, addresses, signatory details - are AES-256-GCM ciphertext at rest. A stolen dump decrypts to nothing.

Identity

Identity verified against published keys

Access tokens are checked locally against Supabase's JWKS public keys, cached hourly, with an authoritative remote check as fallback. Crucially we never trust the role or tenant claimed inside a token: both are re-read from our own users table on every request, and deleted or deactivated accounts are refused.

Access

Authorization checked on the server, every time

A role-to-permission matrix gates every API route through a single withAuth wrapper - permissions are never inferred from the client. Privileged actions are gated further: a tenant admin cannot assign or alter a super-admin, and cross-tenant writes are rejected even on the RLS-bypassing admin path.

Provenance

Geo-fenced, tamper-evident counting

Every scan carries the device's GPS. The server computes great-circle distance from the store and applies the client's fence: hard mode voids an off-site scan outright, soft mode records and flags it for review. Reports are stamped with a SHA-256 hash over canonicalised content, so any later edit is detectable.

Resilience

The count survives the device

Counting never waits for a network: scans are written to the device and sync themselves when signal returns. That local copy is also sealed - encrypted with a key the device holds only the public half of, so it can write the count and never read it back. A laptop lost in a godown leaks nothing, and a device that never reconnects can still be opened by an admin holding that audit's key. The copy destroys itself 24 hours after the audit closes.

Privacy

Minimal-footprint identity data

Aadhaar verification validates the Verhoeff checksum, then stores only the last four digits and a one-way salted reference - never the number itself - with the signatory's name encrypted at rest. New passwords are screened against a common-weak denylist, and PII is never written to logs in the clear.

Defense in depth

Eight controls, in the order a request meets them

Each is a specific mechanism in a specific file, not a policy statement. A request clears every layer before it reaches your data.

Hardened edgenext.config.ts

HSTS (1 year, subdomains), nosniff, SAMEORIGIN, strict-origin-when-cross-origin, and a Permissions-Policy granting only camera and geolocation - the two the scan station needs.

Session rotationmiddleware.ts

Edge middleware refreshes the Supabase session on navigation, so auth cookies rotate before a Server Component ever reads them.

Per-IP throttleserver/rate-limit.ts

Per-IP fixed-window rate limits on every auth route: 10 logins a minute, 5 registrations per 5 minutes, 5 join and bootstrap attempts per 10 minutes.

Key-verified identityserver/jwt.ts

Token signatures are verified against the project's published JWKS public keys - asymmetric, so a leaked server secret cannot mint a valid token. The cookie session is verified server-side. Neither path trusts client-supplied claims.

Zero-trust claimsserver/permissions.ts

A single withAuth wrapper discards whatever the token claims and re-reads the caller's role and tenant from the database, then checks the route's required permission against the role matrix before the handler runs.

Schema enforcement@repo/validation

Every request body is parsed by a Zod schema from the shared validation package - malformed or unexpected input never reaches a service.

Row-level isolationpackages/db/rls.ts

The service runs inside a transaction under the authenticated role with the caller's claims set, so Postgres RLS policies apply to every read and write.

Sealed on the devicelib/offline/vault.ts

Every scan is also written to an encrypted store in the browser, sealed with the public key of that one client, store and audit. The device can write it and never read it back, so a stolen laptop is worthless - and a count survives a device that never finds a network again.

Sealed at restserver/envelope.ts

Sensitive columns are sealed with the organisation's own data key before they are written. That key is itself stored wrapped, and the master key that unwraps it lives outside the database entirely.

Asserted is not the same as verified

The claims above are covered by automated tests that run on every change: a suite that attempts a cross-tenant read and asserts Postgres refuses it, a permission-matrix suite that asserts each role reaches only its own routes, a canonicalisation suite proving two logically identical reports hash identically, and a duplicate-scan suite for the counting pipeline. Isolation is a test that fails loudly, not a sentence on a marketing page.

Security your clients can audit

Bring your most sensitive audits onto a platform that treats isolation and provenance as non-negotiable.