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 path under each one names where it lives in the product repository, which is private; we share the threat model and the decision records with a customer on request. 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

    When paid plans open, card and UPI details are entered on Razorpay or Stripe pages, never on Grownz. Webhooks are signature-checked, timestamp-windowed and de-duplicated.

    apps/api/src/modules/billing

  10. 10

    It is written down

    12 architecture decision records, a STRIDE threat model, a data-classification register with retention, an 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

Every provider, named.

Everything the app and this website depend 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

    Hosts this website and runs its forms

  • Stripe

    Billing outside India, when paid plans open

  • Razorpay

    Billing in India, cards and UPI, when paid plans open

  • Resend

    Email: sign-in codes, invites, digests, this website’s forms

  • Inngest

    Scheduled jobs: FX refresh, reminders, digests

  • Sentry

    Error reporting, errors only, no session replay

  • Doppler

    Secrets management, never in git

  • Cloudflare

    Turnstile, the check that this website’s forms are sent by a person

  • Google

    Analytics on this website, only after you allow it

  • 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.

This site names the providers, not where each one hosts data. 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.

  • Live payments. Plus and Pro are built but not on sale; every household starts on Free, and the early-bird code gives Plus free for 12 months.

  • 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.

  • Native store apps. Grownz is an installable web app on any phone.

Report a security issue: bettroifze@gmail.com. Good-faith research is welcome. 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.

Security and trust | Grownz