Security · Compliance · Trust center

Security woven into every record.

Tenant isolation enforced at the database, not the application. Row-level access decided per record. Every read and write logged. AI agents inherit the same scope as the human they act for.

Compliance.

Where we are. What’s in progress. What’s projected. Nothing here is aspirational.

  • SOC 2 Type IISecurity & availability audit
    In progress

    Projected Q4 2026

  • GDPREU data-subject rights
    Audited
  • ISO 27001Information-security management
    Planned

    Projected 2027

  • CCPACalifornia consumer rights
    Audited
  • HIPAAUS health-data handling
    Planned

    Projected 2027

In-progress and planned items are first-class. We don’t move them to “ready” until the auditor signs. Last reviewed Q1 2026.

Access decided per row. Not per app.

Every record carries a tenant, an owner, and a scope. One access function decides who can see what — and it runs whether the request comes from the UI, the API, or an AI agent’s tool call.

Five scopes evaluated per record.

  • 5 access scopes: `org` · `team` · `managed` · `assigned` · `own`
  • Scopes evaluated per record at query time
  • Same logic in UI / API / agent tool
  • Role-based permissions on top (e.g. `crm.contacts:read`)
  • Custom roles per tenant

Every read, write, and access decision. Logged.

The audit log captures every action — by humans, by AI agents, and by system jobs. Filter by user, record, time, or rule. Export to CSV in one click. Retained per your tenant’s policy (default 90 days, up to seven years).

Audit log with mixed event types.

  • Every CRUD action logged
  • Per-agent tool invocation logged separately (three-lens)
  • Access decisions logged with the rule that fired
  • Retention policy per tenant (default 90 days, configurable up to 7 years)
  • CSV export on demand

One invariant. Enforced everywhere.

Every database query from a user surface MUST pass through an access function. Our linter blocks any commit that bypasses it. The rule is the same for engineers, AI agents, and you.

Tenant isolation as an architecture invariant.

Most platforms enforce tenant scope in code paths. We enforce it as an architecture invariant. Every database query from a user-facing surface — every page render, every API call, every AI tool call — runs through an access function that is bound to your tenant. We can’t bypass it. Our linter blocks code that tries. Engineers can ship features; they can’t ship features that leak across tenants.

  • F900 invariant in `CLAUDE.md` and code review
  • `pnpm check:tenant-isolation` linter blocks unsafe calls
  • System-context calls (jobs, hooks) require explicit `// system-context: <reason>` comment
  • AI tools call `tenantWhere(ctx, ...)` before any DB query
  • Tested via dedicated isolation tests in CI

Your data. Your region.

Pick a region when you create the tenant. Data and backups stay there. Cross-region access logs every time we touch your data from outside.

Tenant-level region pin.

  • Regions: US (us-east-1) · EU (eu-west-1) · APAC (ap-southeast-1)
  • Tenant-level region pin
  • Backup co-located with region
  • Sub-processor list per region
  • Cross-region access logged

Retention. Breach response. Tenant erasure.

Three policies that run as code, not paragraphs in a contract.

  • Retention: `retentionPruneJob` removes records older than your tenant’s policy. Configurable per collection.
  • Breach notification: `notifyBreachJob` triggers on detected event; impacted tenants notified within SLA window. `DataBreachEvents` collection logs every incident.
  • Tenant erasure: `eraseTenantJob` purges all tenant data on confirmed request — including backups within retention window.
  • Access reviews: `accessReviewJob` produces quarterly review reports automatically.
  • Password expiry: configurable per tenant (`passwordExpiryJob`).

For engineers

What an engineer sees on day one.

Read the access function, the linter rule, and the audit schema before your team’s vendor review meeting.

tsRead-only
// Every user-context query passes both
const contacts = await payload.find({
  collection: 'crm-contacts',
  where: tenantWhere(ctx, additionalWhere),
  user: ctx.user,
  overrideAccess: false,
})

// System-context calls require justification
// system-context: scheduled retention prune
await payload.delete({
  collection,
  where,
  overrideAccess: true,
})

See compliance status, in full.

  • SOC 2 Type IIIn progress
    Audit firm
    Pending selection
    Next
    Q4 2026
  • GDPRReady
    Audit firm
    Next
    Ongoing
  • ISO 27001Planned
    Audit firm
    Pending selection
    Next
    2027
  • CCPAReady
    Audit firm
    Next
    Ongoing
  • HIPAAPlanned
    Audit firm
    Pending selection
    Next
    2027
  • Sub-processorsPublished
    Audit firm
    Next
    Reviewed Q1

Need a SOC 2 report or a DPA?

Security review packages on request. SOC 2 reports under NDA. Custom DPAs for enterprise.