Security Overview
Simcha DB Studio executes SQL and DDL against production databases and custodies live credentials for every managed connection. The threat model is accordingly a privileged one: the application is both an authenticated multi-user surface and a secret store. Controls are layered so that a failure in any single control does not collapse the whole posture.
Control Layers
| Layer | Mechanism | Property enforced |
|---|---|---|
| Authentication | Signed JWT (HS256) checked against the account on every request, bcrypt(12) credential hashing, optional TOTP MFA | Identity assurance; no server-side session store to compromise or exhaust |
| Authorization | RBAC middleware (extractUser / requireAuth / requireRole) over 4 roles plus custom roles, whose sections decide where a person can make changes; route gating by what the license includes; optional ban on self-approval | Least privilege; separation of duties across the change-management workflow |
| Secret confidentiality | AES-256-GCM at rest with per-record PBKDF2-SHA512 key derivation | Connection passwords, repository-source and identity-provider secrets, MFA secrets, and AI provider keys and extra header values unreadable at rest; AI keys are never returned by the settings API |
| Transport / header hardening | CSP, frame denial, nosniff, referrer and permissions policies on every page and API response, plus HSTS and COOP over HTTPS; strict CORS allowlist | Downgrade, framing, MIME-sniff, and cross-origin abuse mitigated |
| Abuse control | Tiered rate limiting (general vs. DB DevOps bulk windows) | Credential stuffing, brute force, and API flooding throttled |
| Accountability | Global audit middleware writing a DB-sealed, append-only hash chain | Non-repudiable, tamper-evident record of every state change |
Trust Boundaries
The browser never speaks to Express directly. Requests traverse Next.js API routes (pages/api/*) which forward to the Express backend, propagating Authorization, X-Request-Id, and the non-prod X-QA-Bypass-Key header. Auth, RBAC, license gating, encryption, and audit all execute server-side; the frontend holds a bearer token and nothing else of consequence. X-QA-Bypass-Key is honored only when QA_BYPASS_KEY is set, signs the request in as a real admin account (qa@internal), and the server refuses to start when QA_BYPASS_KEY is set together with NODE_ENV=production. X-Agent-Key (matching AGENT_BYPASS_KEY) only exempts internal automation from rate limits and grants no identity or permissions; leave AGENT_BYPASS_KEY unset unless automation needs it.
HTTP Header Posture
The web tier sends these headers on every page and API response the browser receives (the Express API behind it sets its own Helmet headers as well). The last two are added only when the request reached the app over HTTPS, as reported by your TLS proxy's X-Forwarded-Proto: https; browsers ignore them on plain HTTP.
| Header | Value | Rationale |
|---|---|---|
Content-Security-Policy | default-src 'self'; scripts and styles from the app only ('unsafe-eval' for the Monaco editor); images and workers also from data:/blob:; connections to the app and its WebSocket; no frames, plugins or foreign form targets; frame-ancestors 'none' | Constrains every resource to the app's own origin; unsafe-eval is a scoped exception for Monaco |
X-Frame-Options | DENY | Clickjacking / UI-redress defense for browsers that predate frame-ancestors |
X-Content-Type-Options | nosniff | Blocks MIME confusion on responses |
Referrer-Policy | strict-origin-when-cross-origin | No paths or query strings leak to other sites |
Permissions-Policy | camera, microphone, geolocation, payment and USB disabled | The app never needs these browser capabilities |
Cross-Origin-Opener-Policy | same-origin | Isolates the app's window from pages it opens (HTTPS only) |
Strict-Transport-Security | max-age=31536000 | Pins HTTPS for the app's host (HTTPS only) |
If NEXT_PUBLIC_API_URL points the browser at a separate API origin, that origin is added to the allowed connections. Treat any further CSP relaxation as a reviewable change.
Secrets Handled by the Application
- User credentials, bcrypt(12), unique per-hash salt, timing-safe verification. Never logged or echoed after the auth request.
- Managed connection passwords, AES-256-GCM at rest (
enc:wire format below); decrypted only at pool-open time viaExecutionRepository/ConnectionRepository.findByIdRaw(); masked in ordinary reads. - Repository / cloud tokens, GitHub, GitLab, Bitbucket, S3, Azure Blob, Dropbox credentials encrypted at rest in
repo_connections; decrypted only for the outbound API call. - MFA secrets and backup codes, TOTP seed encrypted at rest; backup codes stored as SHA-256 hashes, never plaintext.
- License signing key, server-side only; never serialized into API responses or the client bundle.
Deployment Prerequisites (fail-loud, not fail-open)
| Control | Severity | Requirement |
|---|---|---|
JWT_SECRET | Critical | ≥64 chars of CSPRNG output, from a secrets manager. Compromise = universal token forgery across all roles. |
ENCRYPTION_KEY | Critical | High-entropy, at least 32 characters, and required: the server refuses to start without it. Back it up with your other secrets; losing it makes stored connection passwords and MFA seeds unreadable. |
| TLS termination | Critical | TLS 1.2+ at the edge; HTTP→HTTPS redirect. The proxy must send X-Forwarded-Proto: https so the app adds HSTS. |
| Datastore isolation | Critical | The changeops/DevOps Postgres in a private subnet; ingress only from the app. |
| Non-prod bypass keys unset | Critical | Leave QA_BYPASS_KEY unset in production (the server refuses to start with it under NODE_ENV=production) and set AGENT_BYPASS_KEY only if internal automation needs its rate-limit exemption. |
| Managed-DB TLS | High | sslmode=require or verify-full to target databases. |
| Secret rotation | High | JWT_SECRET annually / on suspicion; DB passwords quarterly; repo tokens per provider policy. |
| Image scanning | High | Block critical/high CVEs pre-deploy; pin immutable tags. |