Skip to main content
Stackra is a framework: your deployment runs its packages. This page says what the packages do for you, and for each control whether it is built (the code ships in the packages), designed (decided in an ADR, not complete in code) or planned. The rules contributors follow are in Security and Tenancy.

Summary

Tenant isolation

Every tenant-owned table carries tenant_id with ENABLE and FORCE ROW LEVEL SECURITY and a policy on the request’s tenant, so the table owner cannot bypass it and a query that forgets its filter returns zero rows, never another tenant’s data. Tables declare this through the scoped-table pattern (belongsToTenant()); hand-written RLS is not allowed. A cross-tenant miss answers “not found”, never “permission denied”, so a caller learns nothing about other tenants. The check that every tenant table in a database has both flags is planned.

Authorization

  • One authenticated principal per request, resolved once; handlers never read the caller from the body or client headers.
  • @stackra/iam answers deny when nothing matches, and an explicit deny beats every grant. Every action has a permission (code generation refuses one without), and every link of an actor chain (a service and the user it acts for) must hold it (0462).
  • The action pipeline order is fixed (tenancy, permission, precondition, entitlement, flag, idempotency); an app configures the steps but cannot reorder or skip them.
  • Level L3 and L4 actions need a step-up of authentication.

Identity

ZITADEL is the identity provider; @stackra/identity is a provider-neutral OIDC client (0206). Web sessions use a BFF with an HttpOnly cookie, code flow and PKCE; mobile uses PKCE with a rotated refresh token in secure storage; access tokens are short-lived JWTs.

Verification at the edge

Designed, not built. The pieces below are being built for 0.1.0 under STA-1566. Until then, the app’s EdgeHeaderGuard (@stackra/app) reads edge headers that nothing signs yet: do not expose an origin directly.
The design (0437, proposed):
  • Access tokens are verified against the identity provider’s JWKS on every request.
  • The edge Worker forwards tenant and product headers with a timestamp, a key id and an HMAC over the headers, method, path, body hash and request id. The API trusts them only when the signature is valid, younger than the validity window (60 seconds by default) and its request id unseen; otherwise only public endpoints answer. Two keys are live at once, so the secret rotates without a cut-over.
  • The origin accepts traffic only from the edge (Cloudflare Tunnel or authenticated origin pulls); the signature sits on top of that lock, never in place of it.

Audit evidence

  • Every action and data change is audited, and the evidence enters the outbox in the same transaction as the change.
  • @stackra/access-log keeps each tenant’s records in a SHA-256 hash chain: a changed, removed or reordered record breaks verification. stackra audit:verify checks an exported proof bundle offline (the chain and its checkpoints).
  • Built (STA-1579): ExportAuditAction streams a tenant’s write audit as NDJSON (chain fields included, so the receiver can verify it) to a SIEM’s HTTPS collector, under the L2 permission access_log.audit_log_entry.export. Sign-in, failed sign-in, second-factor, lockout and sign-out events from ZITADEL enter the same per-tenant chain through a signed webhook (ZitadelAuthEventsService).
  • Audit retention: access records are kept 400 days by default (AccessLogConfig.retentionDays, at least 90). The write audit’s retention is the setting AccessLogConfig.auditRetentionDays; until the operator states it, the write audit is kept without limit, and no job deletes from it yet.
  • Designed (0212): a KMS-signed checkpoint daily, append-only storage under S3 Object Lock, 13 months hot, then cold storage; money events kept seven years. A tenant can view and export its own audit log.

Encryption and keys

  • In transit everywhere; at rest through the cloud provider.
  • Tenant data keys are generated by AWS KMS and stored only wrapped, bound to the tenant by the encryption context, so a wrapped key copied to another tenant does not unwrap. A tenant can bring its own KMS key (0415).
  • A per-subject key is derived from the tenant key and a per-subject salt: destroying the salt makes that one person’s sealed fields unreadable. Deleting a tenant’s wrapped keys crypto-shreds the tenant (0188).
  • Operator access to production is break-glass only: MFA, one hour, alerted and audited (designed, 0188).

Secrets

Code names a secret; it never holds a value. Production values live in Infisical and reach runtimes by sync on deploy; there are no .env files, and CI reads secrets through short-lived OIDC logins (0173). Every commit is scanned for secrets.

Supply chain

What every package version has, from the release workflow and the registry (0453, 0174): Verify the SBOM signature of a release:

Data protection

Personal data is classified per field and redacted from logs, events and errors; telemetry carries none. Exports, erasure and retention defaults follow 0189. Where data lives is Data residency.
Owner to supply (STA-1576): the data processing agreement, the sub-processor list and the record of processing that 0189 decision 5 names. They are not published yet.

Reporting a vulnerability

Do not open a public issue. Use “Report a vulnerability” on the repository’s Security tab, or email security@figentra.com, with the affected package and version, steps to reproduce and the impact. We acknowledge within three business days and credit you in the advisory if you want (SECURITY.md). Never put a secret value in a report.