Case Study: Zyght SaaS Breach — Tenant Isolation

In April 2026, Zyght — a Chilean HSE (Health, Safety, Environment) SaaS platform — was breached. 6.1TB of data from 90+ organizationswas stolen, including worker PII, medical records, incident reports, compliance audits, and operational security information. Many affected companies are classified as Critical Infrastructure Operators (Operadores de Importancia Vital) by Chile's national cybersecurity agency (ANCI).

The Root Problem

The breach was catastrophic not because of a sophisticated exploit, but because of shared-everything multi-tenancy. When the attacker gained access to one entry point, they reached data from all 90+ tenants through a single application and database layer.

typical shared-tenant architecture
Internet
  └─ Load Balancer
      └─ 1 Application (shared)
          └─ 1 Database (shared)
              ├─ Codelco data
              ├─ SQM data
              ├─ Falabella data
              ├─ Nestlé data
              ├─ Banco de Chile data
              └─ ... 85 more organizations

Compromise the app = access ALL tenant data
Total exfiltration: 6.1TB, 19 million files

Mentat: Sandbox-per-Tenant

In a Mentat deployment, each tenant runs in its own isolated sandbox with its own database connection. The kernel enforces the boundary — not application logic.

sandbox-per-tenant architecture
Internet
  └─ Caddy (ingress)
      ├─ Sandbox: zyght-codelco
      │   ├─ PID namespace (isolated)
      │   ├─ Mount namespace + pivot_root
      │   ├─ Own DB credentials (secrets isolated)
      │   ├─ cgroups v2 limits
      │   └─ DB: codelco_hse
      │
      ├─ Sandbox: zyght-sqm
      │   ├─ PID namespace (isolated)
      │   ├─ Mount namespace + pivot_root
      │   ├─ Own DB credentials
      │   ├─ cgroups v2 limits
      │   └─ DB: sqm_hse
      │
      └─ ... 88 more sandboxes

Compromise one sandbox = access ONE tenant
Blast radius: ~68GB instead of 6.1TB

Impact Comparison

MetricShared Tenancy (Zyght)Sandbox-per-Tenant (Mentat)
Data exposed6.1TB — all 90+ tenants~68GB — one tenant only
Files exposed19 million~210K (proportional)
Organizations affected90+1
Lateral movementTrivial — shared process spaceBlocked — PID + mount namespace isolation
Credential scopeMaster DB credentials serve all tenantsEach sandbox has only its own DB credentials
Exfiltration detection6.1TB over network — hard to miss but wasn't caughtcgroups v2 memory + CPU anomaly detection per sandbox
RecoveryRotate credentials for 90+ orgs, notify all, assume total compromiseRotate credentials for 1 org, contained incident

Defense Layers

Layer 1Sandbox per tenantEach organization runs in its own namespace. No shared process space
Layer 2Isolated credentialsEach sandbox receives only its own DB connection string via Mentat secrets. No master credentials
Layer 3pivot_rootAttacker inside sandbox cannot read host filesystem, other sandboxes, or Mentat state
Layer 4cgroups v2 monitoringAnomalous data transfer (6.1TB) triggers ScaleWatcher alerts. Per-sandbox memory and CPU tracked
Layer 5Network namespaceCLONE_NEWNET + veth restricts egress to only the tenant's DB endpoint. No arbitrary outbound connections
Layer 6Immutable baseApplication code in read-only overlayfs layer. Attacker cannot modify the app to persist a backdoor

Hull: Per-Tenant seccomp, Landlock, and Bridge Egress

Sandbox-per-tenant already limits the blast radius to one tenant. The Hull driver hardens each tenant further by adding policy that is trivial to configure once and applies to every new tenant automatically:

seccomp profile per tenantEach tenant runs under the Hull node profile (or beam / dotnet for non-Node stacks). An attacker who gets code exec via SQL injection cannot ptrace into sibling processes, cannot call bpf to read kernel memory, cannot keyctl to steal credentials from other sandboxes. Even <100 syscalls out of ~400 are allowed.
Landlock per tenantFilesystem allowlist locks the workload to its own tenant directory. Even if an attacker runs as uid 0 inside the sandbox, open()on another tenant’s files returns EACCES — the LSM enforces the path boundary below the VFS, below capabilities.
Bridge egress per tenantWith network: bridge, each tenant gets its own IP on hull0(10.88.0.X). An nftables rule can restrict egress so tenant N can only reach its own database endpoint — not other tenants’ DBs, not arbitrary C2 servers, not the Mentat control plane. The tenant exfiltration paths literally do not route.
Zero shared daemonHull has no dockerd, no containerd, no shim process tree. A CVE in container tooling cannot be used to jump between tenants through a shared root-owned daemon — there is no shared daemon. Each hull run exits as soon as it has forked the tenant workload.
--rootless NEWUSEREach tenant can run --rootless so even the in-container uid 0 is mapped to a distinct unprivileged host uid per tenant. Even if an attacker fully compromises one tenant sandbox, the hostidentity they land on cannot touch any other tenant’s data directory.

The sandbox driver reduces blast radius from 90 tenants to 1. Hull further reduces the depth of what that one compromised tenant can do — a SQL injection victim cannot trivially become a full container escape, and a full container escape cannot trivially become lateral movement.

Example Configuration

zyght-tenant.yaml
# Each tenant gets its own service definition
services:
  - name: zyght-codelco
    replicas: 1
    driver: sandbox
    config:
      base: node22
      rootfs: /opt/mentat/sandboxes/zyght-codelco/rootfs
      command: /usr/local/bin/node
      args: ["server.js"]
      env:
        - TENANT_ID=codelco
        # DB credentials injected via mt secret set
      memory_mb: 512
      cpu_percent: 25
      max_pids: 64
    port: 3001
    health_check:
      path: /health
      interval_secs: 10
    ingress:
      domain: codelco.zyght.com
      path: /
    scale:
      min: 1
      max: 3
      metric: cpu
      scale_up_at: 70
      scale_down_at: 20
      cooldown_secs: 30

# Secrets set separately (never in YAML)
# mt secret set ZYGHT_CODELCO_DB_URL "postgres://..."
# mt secret set ZYGHT_SQM_DB_URL "postgres://..."
# Each sandbox only receives its own secret

Honest Limitations

Mentat is an infrastructure orchestrator, not a WAF or application security tool. It provides the second line of defense:

  • First line(not Mentat's scope): input validation, authentication, authorization, encryption at rest, WAF rules. These prevent the initial compromise.
  • Second line (Mentat): tenant isolation, blast radius reduction, credential scoping, anomaly detection via cgroups. These limit damage when the first line fails.

If the application itself has a SQL injection vulnerability, Mentat cannot prevent the attacker from using the application's legitimate database connection. But the attacker only reaches one tenant's data instead of all 90.

The Lesson

The Zyght breach is not a story about a missing firewall or an unpatched CVE. It is a story about architectural trust boundaries. When 90 critical infrastructure operators share one application and one database, the blast radius of any single vulnerability is total. Sandbox-per-tenant isolation makes that architecture physically impossible — the kernel enforces what application logic failed to protect.