Tenant isolation

  • All tenant data lives in a single Cloud SQL for PostgreSQL cluster with row-level security enforced on every tenant-scoped table. Postgres — not application code — refuses to return rows where tenant_id does not match the session’s claim.
  • The tenant session variable is set from the JWT at the start of every request via SET LOCAL draxis.tenant_id. There is no code path that issues SET row_security = off or switches to a superuser connection.
  • Cross-tenant rollups (org admins, platform admins) use widened RLS policies gated by role claims, audit-logged on every use.
  • Impersonation requires the platform or org admin role, is audit-logged with both identities, and is rendered as a persistent banner in the UI.
  • See Multi-tenancy for the data-layer model in detail.

Authentication

  • JWT access tokens, 15-minute expiry, signed with an RS256 key pair held in Secret Manager and loaded at cold start.
  • Refresh tokens, HTTP-only secure cookie, 30-day expiry, rotated on every refresh.
  • Dual-key verification window during JWT signing-key rotations so in-flight sessions survive a key change without user-visible re-login.
  • MFA (TOTP) with per-platform / per-organization / per-tenant / per-user enforcement; recovery codes generated at enrollment.
  • Rate limits: /api/auth/login is limited to 50 attempts per IP per 15 minutes. /api/auth/* globally is limited to 100 requests per IP per 15 minutes. Enforced at the application tier and backstopped by Cloud Armor (600 req/min/IP on /api/*).

Secrets & encryption

  • Secret Manager holds every runtime secret — Anthropic API key, JWT signing keys, column-encryption key, Langfuse keys. Secrets are fetched at cold start into a tmpfs file and injected as env vars. They never touch the image, the persistent filesystem, or a git-tracked file.
  • Secret versions are pinned by version number in production (never latest); rotation adds a new version and is rolled out via the normal deploy.
  • Vendor credentials (API keys, OAuth tokens) are stored in kri_source with application-level column encryption using the encryption-key secret. They are never written to logs.
  • Passwords are hashed with bcrypt (cost factor 12). Plain-text passwords never touch disk.
  • Encryption at rest: Cloud SQL data, GCS buckets, Artifact Registry, and persistent disks all use CMEK keys in Cloud KMS, automatically rotated every 90 days.
  • Encryption in transit: TLS 1.2+ everywhere external, enforced at the global HTTPS Load Balancer. Internal traffic between the app tier and Cloud SQL flows over a VPC connector on Google’s private network and uses Cloud SQL Auth Proxy with mTLS. Self-signed certs are never accepted in outbound integration fetches.

Network security

  • Cloud Armor sits in front of the Load Balancer with the OWASP Core Rule Set (SQLi, XSS, LFI, RFI, RCE), per-IP rate limits, adaptive protection, and a configurable geo allow / block list.
  • Compute has no public IP. All outbound egress routes through Cloud NAT; all inbound arrives via the Load Balancer only.
  • Administrative access uses Identity-Aware Proxy (IAP): no open SSH ports, tunneled SSH through IAP for approved engineers.
  • VPC flow logs are enabled (sampled) and shipped to the security project for retention.

Identity & IAM

  • Per-service-account least privilege: separate SAs for runtime, deploys, and backups. No human has direct project-level roles; access goes through Google Groups.
  • Workload Identity Federation for GitHub Actions — no long-lived service-account keys anywhere. The pool accepts only attestations from the Draxis repo, constrained by branch for production.
  • Org policies enforce no service-account-key creation, OS Login required, Shielded VM required, uniform bucket-level access, public-access prevention, and no VM external IPs.

Supply-chain security

  • Container images are pinned by digest, signed with cosign, and scanned by Artifact Analysis + Trivy on every push. Dependabot and npm audit --audit-level=high run in CI.
  • An SBOM is generated at build time (Syft) and attached to each image.
  • Binary Authorization evaluates cosign attestations at deploy time (warn-only initially; enforce after a clean steady-state window).

Audit trails

  • Every write to the platform schema emits an audit-log row: actor, action, target, a structured diff, and (if applicable) the impersonator identity.
  • Panel sessions keep a full transcript and synthesis-rationale audit (GET /api/panel-sessions/<id>/audit).
  • Integration runs persist started_at / finished_at / status / summary; the summary never contains secrets.
  • Cloud Audit Logs (Admin Activity always on, plus Data Access for Secret Manager, KMS, and Storage) are exported via an org-level log sink to a separate draxis-security GCP project on a write-once bucket with 400-day retention — tamper-evident even from the Draxis operators running the production account.
  • Alerts fire on: root-level IAM changes, service-account-key creation attempts, firewall-rule changes, and Secret Manager access from unexpected service accounts.

Backups & recovery

  • Cloud SQL automated backups + point-in-time recovery with transaction-log retention. RPO within minutes.
  • Restore procedure documented in a runbook that is exercised quarterly in staging. A failed drill blocks the next production deploy.
  • Tenant export (pg_dump-based, filtered by tenant_id) is available on request for portability or legal-hold purposes.

LLM observability

Every Anthropic call is traced through Langfuse Cloud (managed endpoint — no self-hosted trace store). Tenant-identifying fields are captured as Langfuse tags so a compromised trace cannot be replayed against another tenant. Raw prompt contents are retained per the tenant’s retention policy.

Responsible disclosure

Report vulnerabilities to security@draxis.ai. See the Trust Center for our full disclosure policy and SLAs.