Trust
Security, privacy and compliance
Every control below names a file in the product source and a piece of text that must appear in it. The build checks four things about that citation, and this is the complete list: the file exists; it still contains the cited text; the cited text appears in no more than 5% of the repository’s files, so that it names one implementation rather than a word that occurs everywhere; and any number of two or more digits that the control states appears somewhere in a file the control cites.
That last check is weaker than it sounds. It matches a number as plain text anywhere in the cited file, so a match can be a coincidence: a “24” in a control can be satisfied by an unrelated “24” in the code. It does not see a quantity written as a single digit or spelled out as a word, so “rotated every ninety days” is not checked at all. We tested this by writing fabricated controls designed to slip past it, and they did.
What the build cannot check at all is whether the sentence is a fair description of the code it points at. A control worded loosely, or worded tightly around numbers the check cannot see, would pass all four checks. That fit is a human judgement, made by whoever writes the entry and whoever reviews the change, and it is the part you are trusting us on rather than checking. The four checks are evidence that the control is written down, not evidence that anyone outside this company has examined it.
Access control and tenant isolation
| Control | Where to check it |
|---|---|
| Every state-changing API operation names both the permission it needs and the account it acts inside. The scope argument has no undefined member, so a route that forgets to say which account it is acting in does not compile. | packages/api/src/auth/authorize.ts |
| Named money and policy actions (voiding an invoice, approving a pricing exception, activating a price book, settling or clawing back a commission, and others of the same class) also require a fresh re-authentication at the moment they are attempted. | packages/api/src/routes/core/index.ts |
| Tenant isolation is enforced by PostgreSQL row-level security on the tables themselves, not only by application code. Security is forced rather than merely enabled, and the roles the application connects as cannot bypass it. | supabase/tests/020_rls.test.sql |
| A production deployment with no configured identity provider refuses every request with a 503 rather than serving an unauthenticated page. | apps/web/proxy.ts |
Data protection and retention
| Control | Where to check it |
|---|---|
| Executed agreements, signed documents and other evidence objects are written to object storage under a COMPLIANCE-mode Object Lock with a retain-until date and a legal-hold flag, so neither the application nor an operator can delete or overwrite them inside the retention window. | packages/integrations/src/evidence-storage/index.ts |
| Every evidence object carries a SHA-256 content hash, a storage version, a retention date, a legal-hold flag and a malware-scan status, and metadata that does not satisfy those requirements is rejected rather than stored. | packages/domain/src/compliance/index.ts |
| Counterparties are screened at registration, before signature and at partner activation, and re-screened on expiry. Four embargoed jurisdictions are refused regardless of what the screening provider answers, and a non-clear decision cannot be recorded without a stored match-evidence document. | packages/domain/src/compliance/index.ts |
| Reading a stored document requires either ownership of the account it belongs to or a named internal role. The purpose of the access is part of the decision, and an upload for a purpose that does not permit uploads is refused. | packages/domain/src/compliance/index.ts |
Auditability
| Control | Where to check it |
|---|---|
| Every core write returns the identifiers of an audit event and an outbox message written by the same transaction that wrote the row, so there is no state change without a corresponding record of who made it and what it replaced. | packages/api/src/routes/core/service.ts |
| The integrity of the audit chain is asserted by tests that run against a real PostgreSQL instance rather than a mock. | supabase/tests/903_secure_chain_validation.test.sql |
Application and transport security
| Control | Where to check it |
|---|---|
| Every document is served with a per-request nonce Content-Security-Policy. Framing, plugin objects and base-URI rewriting are all set to none, and the application writes no inline script of its own. | apps/web/proxy.ts |
| Every response carries headers for transport security, MIME-sniffing refusal, framing refusal and referrer policy, and a permissions policy that denies camera, microphone and geolocation. | apps/web/next.config.ts |
| Every state-changing API request must present a matching double-submit CSRF token and an origin the deployment allows before it reaches a handler, with one deliberate exception. Requests to the inbound provider webhook routes under /v1/webhooks/ are exempt from that check, and from the idempotency-key requirement, by path prefix. A provider posting a webhook is a server rather than a browser: it holds none of our cookies, so a CSRF token would only be a value we had handed it, and the check would prove nothing about who sent the request. Those routes are authenticated instead by verifying the provider’s signature over the raw request body, which is the control that actually establishes the sender. Safe methods (GET, HEAD and OPTIONS) are exempt as well, because they change nothing. | packages/api/src/middleware/security.tspackages/api/src/middleware/idempotency.tspackages/api/src/webhooks.ts |
| An inbound provider webhook is refused unless its signature verifies against the raw request body, and a redelivered event is claimed and deduplicated rather than applied a second time. | packages/api/src/webhooks.ts |
| State-changing requests require an idempotency key, and a repeated key replays the stored first response instead of performing the operation twice. The inbound provider webhook routes are exempt from this requirement as well, because a provider chooses its own retry identifiers; they are deduplicated instead by claiming the provider’s event ID. A response with status 500 or above is never stored for replay, so a dependency failure does not become a cached outage. | packages/api/src/middleware/idempotency.tspackages/api/src/webhooks.ts |
Secure development
| Control | Where to check it |
|---|---|
| Secret scanning runs across the whole working tree as part of the standard verification suite. | package.json |
| Dependency advisories fail the suite at high severity and above. | package.json |
| The published API contract is generated from the running application, and the suite fails if the committed contract has drifted from what the application actually serves. | package.json |
| The database schema is checked against its migrations for drift on every verification run. | package.json |
Third-party services this software is wired to
| Service | What it does | Where to check it |
|---|---|---|
| WorkOS | Browser identity, session issuance and step-up authentication. | apps/web/package.json |
| Stripe | Payment collection. Payment status is derived from verified webhooks, never entered by hand. | packages/integrations/package.json |
| Amazon S3 | Immutable evidence and document storage under Object Lock. | packages/integrations/package.json |
| Supabase (PostgreSQL) | Primary datastore, migrations and row-level security. | package.json |
| Netlify | Application hosting and build. | netlify.toml |
| Trigger.dev | Background task execution, one of two runtimes selected by CLOCKWORK_TASK_RUNTIME; the other is SQS in the deployment’s own account. | trigger.config.ts |
Capabilities with no vendor selected
| Capability | Selected under | Where to check it |
|---|---|---|
| Electronic signature | EXT-PROVIDER-01 | packages/integrations/src/esign/http-signing-client.ts |
| Tax determination | EXT-TAX-01 | packages/integrations/src/core/tax/http-tax-adapter.ts |
| Denied-party screening | EXT-PROVIDER-01 | packages/integrations/src/runtime/lifecycle-http-providers.ts |
| Accounting export, notification delivery and usage ingest | EXT-PROVIDER-01 | packages/integrations/src/production-adapters.ts |
What this page does not claim
- Requires EXT-LEGAL-01
docs/sprint-checklist.mdNo SOC 2 report, ISO 27001 certificate, PCI DSS attestation, HIPAA assurance or FedRAMP authorization is held, and none is claimed. The firm, scope and observation period for a SOC 2 Type II are not yet confirmed, the ISO 27001 body and scope are not yet chosen, and no independent penetration test has been started.
- Requires EXT-LEGAL-01
packages/domain/src/agreements/index.tsThe Data Processing Addendum, security addendum, acceptable use policy, privacy policy, service-level agreement and support policy are counsel deliverables. The software models each of these document types and can execute and store them; the approved text is not in this repository, so this page publishes none of it.
- Requires EXT-LEGAL-01
docs/external-gates.mdThe integrations listed above are a source-tree fact, not a subprocessor schedule. A subprocessor schedule names the entities that process personal data on a customer’s behalf under the Data Processing Addendum, states what each processes and where, and is produced with counsel. Do not treat the list above as one.
- Requires EXT-LEGAL-01
docs/external-gates.mdNothing on this page has been reviewed or approved by counsel, and nothing on it has been verified by an external auditor. Every statement above is derived from this repository’s source at build time and is only as good as that source.
This page is rendered from the source of the deployment you are reading it on. There is no separate publication step and no copy kept anywhere else, so it cannot be out of date relative to that deployment. It can still be behind changes we have merged since.