We state the split plainly. The architecture Actionary runs on today satisfies the SOC 2 Trust Service Criteria that matter for a customer-facing platform. The Type II attestation — the auditor sitting with the evidence for twelve months — is on the compliance roadmap. Everything below distinguishes the two.

A SOC 2 auditor reads controls. Every control on this page maps to a row in a table, a query against the audit log, a policy enforced by Postgres, or a specific enforcement point in the codebase. Nothing here is aspirational.

The five load-bearing claims

Application code never touches secrets. Credentials resolve through cloud-SDK default credential chains, environment configuration, or IAM roles attached to compute. The application source has zero credential strings. A single platform master key is the root secret; every subsystem key — MFA, SSO, integration tokens, LLM provider keys, MCP OAuth, JWT signing — is HKDF-derived at boot.

Per-tenant isolation at every layer. A tenant id sits on every multi-tenant row, every object-storage path, every search index, every audit-log entry. Postgres row-level security enforces the boundary at the database — a query without the request-scoped tenant setting returns zero rows. See tenant isolation for the RLS + purpose-scoped runtime-role model.

Audit-everything, mask-nothing. Every administrative action and every entity mutation writes an audit-log row with actor, before, after, and timestamp. Errors surface with their root cause; no silent catch-all pattern exists in any router.

Fail-loudly capture as a compliance control. Every uncaught server-side exception lands in a forensic table with a full traceback; every browser-side failure lands in a matching client-side forensic table. Both surfaces triage from the platform-admin console, and every triage transition writes an audit-log row.

Server-side sessions carry the Common Criteria. Revocation is one database update. Idle clocks tick per session kind (staff 30 min, tenant 4 h). Staff identity sits physically separate from tenant identity — operator identity never carries through a tenant click. This maps directly onto CC6.1, CC6.2, CC6.3, and CC6.7.

Public-surface SOC 2 mapping

Twenty-three surfaces a security reviewer probes, mapped to the criterion each satisfies.

# Surface Mechanism Criterion
1 Tenant browser session __Host-prefixed cookie, opaque 32-byte id, HttpOnly, SameSite=Lax CC6.1
2 Staff console session __Host-prefixed cookie on a separate origin CC6.1
3 Support-access grant One-shot ticket, bounded by explicit expiry, single-tenant CC6.2
4 CSRF defence Session-bound token, in-memory store, constant-time compare CC6.1
5 MFA (TOTP) TOTP secret plus recovery codes, encrypted with a derived subsystem key CC6.1
6 Passkeys (WebAuthn) Public key plus strictly-increasing sign count, self-only management CC6.1
7 Step-up on destructive actions Fresh-factor within a 5-minute window, policy toggle audit-gated CC6.1
8 Enterprise SSO OIDC PKCE + SAML 2.0, signature verified, JIT provisioning CC6.2
9 Password storage OWASP-recommended scrypt tier, constant-time verify CC6.1, CC7.1
10 Session revocation Single database update on the sessions table CC6.3
11 Session-event audit An event row on every create / rotate / revoke / idle-expire CC6.7
12 Tenant isolation (DB) Postgres RLS keyed on a per-connection tenant setting, purpose-scoped role model CC6.1, CC6.6
13 Tenant isolation (HTTP) Multi-layer resolver, cross-tenant probes return a generic not-found CC6.6
14 Repository-layer tenant filter Every read scoped by tenant, every write stamps the tenant from context CC6.6
15 Object-storage keys Namespace prefix by tenant id — namespaces do not overlap CC6.6
16 Encryption at rest Server-side bucket encryption, plus at-column encryption on MFA / SSO / OAuth / LLM-key values CC6.7
17 Encryption in transit TLS everywhere at the reverse proxy, HSTS with a two-year max-age and includeSubDomains CC6.7
18 Audit log One canonical audit-log table covering record CRUD, admin actions, workflow definition changes, and MCP token events CC7.2, CC7.3
19 Server error capture Forensic-row table populated by a global exception handler, no router silent-catch CC7.2
20 Frontend error capture Client-side forensic-row table populated from the standard error events and the SPA root error boundary CC7.2
21 Token logging hygiene Bearer credentials never appear in logs; fingerprints (short prefix of a hash) only CC6.1
22 Operator runtime probes Live probes on /platform/operations, banner on degraded / down CC7.1, CC7.2
23 Rate limiting Redis-backed limiter on every state-changing endpoint CC6.6, CC7.2

The audit summary

The controls a Type II auditor reads as positive evidence, condensed.

  1. Tenant isolation at every layer of the stack, enforced by Postgres RLS at the database and by repository discipline in the code.
  2. RBAC gated at the request-dispatch layer with per-permission rows in the role-permissions table.
  3. Row-level permissions block on every list response; merge-time contract check on every pull request.
  4. Encryption at rest with subsystem keys derived from one master via HKDF-SHA256.
  5. Encryption in transit via TLS + HSTS on every origin.
  6. Audit-log capture of entity mutations with before / after payloads.
  7. Session-event audit lineage on every create / rotate / revoke / idle-expire.
  8. Fail-loudly error capture on both tiers, triaged with per-transition audit rows.
  9. Passkey enrolment with monotonic sign-count replay defence.
  10. Staff / tenant identity split — two tables, two origins, two cookies.
  11. Support-access grants as the only staff-to-tenant path, per-grant audit.
  12. Step-up MFA on destructive actions with policy-toggle audit chain.
  13. Password storage at the OWASP-recommended scrypt tier, standard-library-native.
  14. Rate limiting on every sensitive endpoint via a Redis-backed limiter.
  15. Cross-tenant probes return a generic not-found — no existence enumeration.
  16. Secrets outside the DB they protect: platform master key in environment, IAM roles for object storage.
  17. Continuous point-in-time recovery on managed Postgres, five-minute granularity, seven-day retention.
  18. Object storage versioning enabled for per-object recovery.
  19. Operator runtime probes on /platform/operations with degraded-state banner.
  20. OpenTelemetry pipeline across every service with W3C trace context.

What we deliberately ruled out

Storing storage credentials in the application database. Widens the secret surface to backups, replicas, SQL injection, and any operator with read access. Credentials live outside the DB they protect.

A superuser boolean on any user record. SOC 2 CC6.1 invariant. The staff-platform session kind is the only platform-admin signal. Bearer JWTs are stripped of any staff-platform session claim at the request boundary so a captured external token cannot claim staff elevation. See design principles.

Silent exception catches in router code. The “catch-Exception, return generic error” pattern is structurally absent across every router file. Exceptions bubble unconditionally to the global handler.

Third-party log shippers, ingestion delays, sampling. Every error lives in the same Postgres a compliance reviewer can query. No vendor login. No ingestion lag.

The honest gaps

Formal Type II attestation. The 12-month evidence window is the remaining engagement — the automation vendors that drive continuous evidence collection sit against controls that already run.

Partitioned audit-log storage. The audit table is append-only at the row level. The partition + cold-storage lifecycle triggers on volume; the partitioning helper is ready.

Persistent OpenTelemetry sink. The trace shape is captured across every hop. The persistent trace backend is the wiring item.

Automated GDPR Article 17 deletion. Automated at tenant scope — deleting a tenant moves it to a thirty-day trash, and a scheduled worker then removes every row it owns without further instruction, leaving an append-only record that the window was honoured. Record-scoped erasure inside a live tenant is manual against a documented workflow, and crypto-shredding for backup data is the target shape.

Cross-region object-storage replication and customer-managed encryption keys. Provisioned in-region with server-side bucket encryption and all four Block Public Access settings on. The enterprise-tier upgrade path is scoped.

For CISOs evaluating risk against a control matrix, the security substrate reads the way the CISO brief frames it: the controls are the code, and the code is the evidence.

The takeaway

SOC 2 is the auditor’s confidence that controls exist, run, and leave evidence. Actionary’s controls live as code in the platform. The security posture and tenant isolation pages read the same way an auditor’s fieldwork report does, because the substrate is the same substrate. The attestation follows the evidence. The evidence is already accumulating.