Get started
Security, for whoever actually asks

Zero trust between tenants. Enforced by Postgres, not a promise.

This page names the actual mechanism, not a badge. If you're the one who has to sign off on this before your company gives FORA any data, this is written for you.

Stack

What FORA actually runs on.

React (Vite) on the frontend. Every read and write goes through a Vercel serverless function in api/, never straight from the browser to the database. Supabase-hosted Postgres for storage, with its own SOC 2 Type 2 report. No ORM abstracting away what a query actually touches.

Access control

How a request actually gets authorized, layer by layer.

Multi-tenant isolation here is not a Postgres policy doing the heavy lifting. It's application code, with Postgres itself as a deny-by-default backstop underneath it. Both matter, and they're not the same thing.

HMAC-signed sessions

Every request must carry a session token signed server-side with HMAC-SHA256. The signing secret lives only in the server environment, so a token can't be forged from the browser or replayed after tampering with its payload.

Tenant scoping lives in the handler

The signed session carries the caller's company id. Every query in every serverless function scopes explicitly to it. This, not a database policy, is where one company's data actually gets kept out of another's.

RLS: deny by default, zero policies

Row Level Security is enabled on every table with no policies defined. A request using the public/anon key is refused unconditionally by Postgres itself, before any application code runs at all — a backstop under the real boundary above, not a substitute for it.

The one bypass key never reaches a browser

The service-role credential that can bypass RLS exists only inside the Vercel serverless environment. It is never in a client bundle, never in a repo, never shipped anywhere a browser can read it.

Signed, time-limited file URLs

Documents and photos sit in private storage buckets. Every link the app hands out is signed and expires; there is no permanent public URL for a private object, and code that would build one is checked for on a recurring schedule.

Card data never touches our servers

Checkout runs entirely on Stripe's hosted page. FORA never receives, sees or stores a card number, and an incoming Stripe webhook is refused unless its signature verifies against our signing secret.

Verification, not a one-time claim

An automated audit suite re-runs every three days.

A security page written once at launch and never revisited is a claim, not a control. This one gets re-verified against the live system on a fixed schedule, across six areas:

CheckWhat it verifies
Storage exposureEvery storage bucket except the one deliberately public asset bucket is confirmed private, tested live against unauthenticated requests, not just read from config.
RLS coverageRow Level Security is confirmed enabled with zero policies on every table in the schema, including any added since the last pass.
Secret hygieneTracked files are scanned for hardcoded credentials, and env-file coverage in .gitignore is confirmed.
Signed-URL disciplineCode paths that build a storage URL are checked for one that constructs an unsigned public URL for a private object — a specific bug class, checked because it has recurred before.
External surfaceVercel preview-deployment access control and Stripe webhook signature verification, since this exposure lives in connected services, not in this repository's source.
Adversarial reviewA broader pass across authentication, authorization, injection, cross-tenant data access, file-upload handling, rate limiting and dependency vulnerabilities, not limited to one checklist.

Findings that only ever tighten access and are trivially reversible get fixed on the spot. Anything that's a real access-control judgment call, touches production availability, or means a credential needs rotating gets reported for a human decision, not silently patched.

Compliance

The paperwork behind the paperwork.

Full detail on what's collected, why, and who it's shared with lives in the actual documents. This is the short version.

SOC 2 Type 2 infrastructure

The Postgres provider FORA runs on is independently audited to SOC 2 Type 2. That's a third party checking the checker.

Data residency: Canada

Your records and uploaded files live in a database hosted in Canada, not wherever a default region happened to land.

Alberta PIPA & PIPEDA

Personal information is handled under Alberta's Personal Information Protection Act and the federal PIPEDA, not whichever rule happens to be convenient.

Read the actual terms

No summary replaces the real documents. Read the Privacy Policy and Terms of Use in full.

Your data stays yours

Export any time. Nothing is sold, and nothing is used to train a model outside your own account's Brain.

Straight about the rest

Hosting and AI processing run through the specific providers named in our Privacy Policy, not left vague.

Ask the person who built it, not a support form.

If you've got a specific question about where your data lives or how it's protected, you'll get a straight answer by email, not a canned one from a security questionnaire template.