Skip to content

Security and trust

Built for a sceptical reader.

Grownz is multi-tenant by household and the database, not the application, enforces who sees what. Everything below points at the file where it lives.

Ten controls

What protects your household, and where it is written.

These ten controls describe the Grownz product, the app you sign in to. The paths are where each one lives in its repository. This marketing website is a separate, mostly static site: it sends its own security headers, keeps no accounts and stores no financial data.

  1. 1

    One household cannot read another

    Every financial table is keyed by household. Postgres row-level security runs on a restricted database role that cannot bypass it, and cross-household pgTAP tests run in CI.

    infra/db/migrations/0001_roles_rls.sql, infra/db/tests/rls.sql

  2. 2

    The audit log only appends

    Update, delete and truncate are revoked on the audit log at the database, for every role.

    infra/db/migrations/0001_roles_rls.sql

  3. 3

    No token ever reaches browser JavaScript

    Web sessions live in an httpOnly cookie. The browser talks to a server proxy, and the proxy talks to the API. Cross-site writes are stopped by an origin check.

    ADR-0007, apps/web/app/api/proxy

  4. 4

    A strict Content Security Policy with nonces

    Every response carries a per-request nonce. Scripts run only when they carry it. Frames are refused, forms post only to Grownz.

    apps/web/middleware.ts

  5. 5

    Your Claude key is sealed

    The key you paste is encrypted with a server secret before it is stored, never logged, and only its last 4 characters are shown to you. Claude receives derived numbers, never your notes or documents.

    apps/api/src/modules/assistant, threat model: prompt injection

  6. 6

    Writes are idempotent and events are reliable

    Every mutating request carries an idempotency key, so a retry never duplicates. Domain events go through a transactional outbox.

    ADR-0009, ADR-0010

  7. 7

    Rate limits and honest errors

    Per-address and per-user rate limits sit in front of every API route. Errors follow RFC 7807, so a client always knows what went wrong.

    apps/api/src/app.ts, apps/api/src/lib/problem.ts

  8. 8

    Passwordless sign-in with brute-force limits

    A 6-digit code, 10-minute expiry, per-email and per-address limits, and an identical response for known and unknown emails.

    supabase/config.toml, threat model

  9. 9

    No card data touches Grownz

    Checkout, portal and cancellation happen on Razorpay or Stripe pages. Webhooks are signature-checked, timestamp-windowed and de-duplicated.

    apps/api/src/modules/billing

  10. 10

    It is written down

    11 architecture decision records, a STRIDE threat model, a data-classification register with retention, a published OpenAPI specification, CodeQL and Renovate in CI.

    docs/adr, docs/threat-model.md, docs/data-classification.md, apps/api/openapi.json

Data classification

Four levels, each with a handling rule.

From the data-classification register in the repository. Every new column with personal data is added to it in the same change.

LevelWhatHandling
RestrictedIdentity documents, tokens, sign-in codesEncrypted at the application layer, access audited, never in logs or analytics
ConfidentialFinancial detail and personal profileRow-level security, encrypted at rest, redacted in logs, aggregated only in analytics
InternalPreferences, flags, operational dataStandard controls
PublicMarketing content, Learn articlesNone needed

Who runs what

Ten providers, named.

Everything Grownz depends on to run, and why. The legal hub lists them as sub-processors.

  • Fly.io

    Runs the API and the web app

  • Supabase

    Postgres, authentication and file storage

  • Vercel

    Edge routing for the app domain

  • Stripe

    Billing outside India

  • Razorpay

    Billing in India, cards and UPI

  • Resend

    Transactional email: invites and digests

  • Inngest

    Scheduled jobs: FX refresh, reminders, digests

  • Sentry

    Error reporting, errors only, no session replay

  • Doppler

    Secrets management, never in git

  • Anthropic

    In the product, only when you supply your own Claude key. On this website, the assistant may send your question (never your data) to Anthropic on our key when that upgrade is switched on.

Hosting regions are not stated here until the production deployment is final. Sub-processor list.

Not built yet

What we do not have, in plain words.

A trust page that only lists strengths is marketing. These are the gaps today, and each is on the roadmap.

  • Self-serve data export. Request it by email and it is handled manually.

  • Self-serve account deletion. Same route, handled manually, with the retention periods in the data-classification register.

  • Malware scanning and metadata stripping on uploads. Uploads are signed, size-limited and private, but not yet scanned.

  • A public bug bounty. Report issues to the security address and expect a reply.

Report a security issue: [PLACEHOLDER: security contact address]. The help centre states the same limits under Limits and roadmap.

Your household, your rows.
Start with an email.

No password to leak. No card to store. No token in the browser.