Security & compliance
Built security-first
Tenant isolation, encryption at rest, MFA, audit trails, and least-privileged roles from day one. For the full compliance posture, see the Trust Center.
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_iddoes 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 issuesSET row_security = offor 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/loginis 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
tmpfsfile 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_sourcewith application-level column encryption using theencryption-keysecret. 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=highrun 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-securityGCP 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 bytenant_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.