Docs/Security & Compliance/Threat Model & Known Exposures

Threat Model & Known Exposures

Status: unverified (point-in-time)
Threat classes and mitigations below are grounded in the code and in internal security reviews (July–September 2026, not published). "Open" or "fixed" describes the code at the time of writing, not any particular deployment; confirm against your own running system before relying on any row. This page deliberately records classes and statuses, not exploit steps. Report new vulnerabilities as described in SECURITY.md.
Audience
Engineers and operators deploying or contributing to the platform
Related
Security Architecture, Secrets, Compliance Posture, Observability

1. Assets

2. Trust boundaries

flowchart TB
  subgraph Internet
    A1[Authenticated tenant user]
    A2[Anonymous: demo, share link, webhook sender, paired device]
    A3[Hostile MCP server or web page content]
  end
  subgraph Edge
    CF[Caddy / Ingress / optional CDN]
  end
  subgraph Runtime[Compose or Kubernetes]
    CORE[core: JWT + tenant context]
    KC[Keycloak]
    SBX[mcp-stdio-runner / tool-sandbox]
  end
  subgraph Data
    PG[(Postgres schema per tenant)]
    KV[(.env / Kubernetes Secret)]
  end
  A1 --> CF --> CORE
  A2 --> CF
  CORE --> PG
  KV -. env .-> CORE
  CORE --> SBX
  CORE -->|outbound fetch, MCP connect| A3
  A1 --> KC

Boundaries that matter most: internet to core (JWT, or a per-surface credential for anonymous paths); tenant to tenant (schema selection from a claim); core to sandbox (untrusted code); core to outbound network (SSRF); operator or automation tooling to the host or cluster.

3. Threat classes and mitigations in place

Threat classMitigation in code or configSource
Forged or foreign-client tokensSignature via JWKS; issuer set; azp allowlist. A live DAST confirmed alg:none, algorithm-confusion and bad-signature tokens rejected.SecurityConfig.java; internal DAST review
Cross-tenant accessSchema-per-tenant from validated claim; claim-less tokens 403; sanitized schema names; no caller-supplied tenant idsTenantContextFilter.java, SchemaNameSanitizer.java, internal SAST review
Privilege misuse within a tenantPERM_* matchers; denied attempts auditedSecurityConfig.java
Anonymous-surface abuseDemo tokens, per-IP limits and caps; share-link rate limits; signup limiter; device-claim limiter; Redis limiters with local fallbackSecurityConfig.java, LocalRateLimitFallback.java
Webhook spoofing and replayProvider signatures, constant-time compare, Slack replay window, billing dedupe ledgerWebhook controllers; V250__billing_webhook_event.sql
Untrusted code executionInterpreter allowlist plus inline-eval flag rejection; separate sandbox container with seccomp, cap-drop, no secretsMcpStdioCommandGuard.java; sandboxing design §8-§9
SSRF from tenant-supplied URLsDenylist, IPv6-mapped decode, trailing-dot normalization, resolve-time re-check (checkResolved); guards on demo/OpenAPI fetchersMcpRemoteUrlGuard.java
Prompt injection via tool outputGuardrail seam on every tool result; MCP output sanitizer strips invisible/bidi/tag characters. Described in source as partial, not a solution.GuardrailService.java
Credential theft at restAES-256-GCM, fail-fast key validation, dual-key rotationEncryptionService.java
Public metrics/actuator disclosureActuatorEdgeGuardFilter 404s forwarded requests to non-public actuator pathsActuatorEdgeGuardFilter.java
Operator or automation power-cycling a live deploymentProcedural rule (no host reset, stop or delete without an explicit human decision); give CI and automated agents a least-privilege identity without destructive permissionsDeployment Guide

4. Known exposures and open items

Status vocabulary: open = not shown fixed in the code; fixed in code = the code change is visible; deployment = depends on how you deploy, not on the code.

#ClassStatusRecorded in
1Public operational endpoints. Core 404s proxied requests to non-public actuator paths (ActuatorEdgeGuardFilter), so metrics stay private as long as core's port is only reached through a proxy. The Compose Caddyfile 404s the Keycloak admin console and master realm on auth.; with another edge (Helm Ingress, CDN) you must do the same. An edge rule for /actuator/* (exempting /actuator/health) is defence in depth.guard fixed in code; edge rules are deploymentObservability; deploy/caddy/Caddyfile; ActuatorEdgeGuardFilter.java
2Keycloak run mode. Compose runs start --import-realm by default; KEYCLOAK_START_COMMAND=start-dev exists for local use and must not be used on a server.deploymentdeploy/compose/docker-compose.yml
3Tenant-claim integrity. If the realm's unmanaged-attribute policy is ENABLED, a user can edit their own tenant_id. It must be ADMIN_EDIT; the realm reconcile (deploy/keycloak/reconcile-keycloak.sh, run by scripts/vegaduta up; the Helm reconcile Job) sets it, but a live realm must be checked.deployment; high priority to verifyKeycloak Provisioning
4Missing tenant context falls back to the shared public schema (app.tenancy.fail-on-missing-context defaults false; the code comment says it stays false until a soak proves the counter empty).open by design; flip pending a soak with an empty countercore/tenancy/.../TenantSchemaResolver.java
5Anonymous-by-prefix routes. Path prefixes such as /api/demo/** are open by construction; any new endpoint under them is unauthenticated. Two permitAll() matchers (/scim/v2/**, /api/tenant/comp-grant) rely on controller-level checks I did not trace.design risk; two items untracedSecurityConfig.java
6MCP remote connect: DNS rebinding and numeric-IP encodings were the top remaining remote-side risk after the first round of fixes; a resolve-time check is now present in the guard, its pinning completeness is not re-verified. Redirect re-guarding, pre-buffer result-size cap, static-tool sanitization, readiness-cache window, legacy env leak on SDK downgrade, and tool-name shadowing are follow-ups.partly fixed in code; remainder openMcpRemoteUrlGuard.java
7Untrusted-tenant STDIO MCP. Running published packages via allowed launchers is by design; the safe posture is trusted, staff-reviewed registration only. The isolation container exists and has been verified on a dev host; the feature ships flag-off.open posture constraintdocs/architecture/MCP-STDIO-SANDBOXING-DESIGN.md §8-§9; ADR-0009
8Code-intel execution host isolation. A data-governance review found directory-per-tenant separation but commands run as host processes without a container mode implemented.open as of that reviewservices/code-intel/
9Browser security headers on front-end origins. A live DAST once found none on the console origins (only the API had them); next.config now sets headers and the Compose Caddyfile adds HSTS, nosniff and a referrer policy.re-check your deploymentweb/next.config.*, web-admin/next.config.*
10Weak database password fallback. Config comments now say the fallback was removed for production paths.appears fixed in codeapplication.yml comments
11Secrets posture. Secrets sit in a plaintext deploy/compose/.env on Compose hosts; envelope/KMS encryption is not wired; no rotation schedule.openSecrets
12Encryption-key drift. A regenerated or mistyped APP_ENCRYPTION_KEY (new host, restore, second stack on the same database) makes stored credentials undecryptable.procedural risk (deployment)ADR-0010
13Security scanning. CodeQL runs on pull requests, pushes to main and weekly; static gates, gitleaks and dependency review run inside CI; OpenSSF Scorecard weekly.fixed in CI.github/workflows/ci.yml, codeql.yml, scorecard.yml
14Operator identity not separated from automation identity. Give CI and automated agents an identity without destructive permissions on your hosts or cluster.deployment—
15Edge configuration (HTTPS enforcement, minimum TLS, WAF and rate-limit rules, email DMARC policy) and plaintext service-to-service traffic inside the Compose networks or cluster must be considered per deployment.deploymentdeploy/caddy/Caddyfile, Helm Ingress values
16No dedicated incident-response plan, vendor review or DR drill ships with the project.openCompliance Posture §3
How to keep this page honest
When an item is closed, change its status and cite the commit, not the intent. When a new review lands, add it to the evidence list and its date to the row. Prefer editing a row over deleting it, so the history of what was once open stays readable.

Evidence & references