Status: non-negotiable. Source of truth: this file. Owner: Mike Mazur (Mike Mazur).
This file consolidates the Red Team Standard from .agents/workflows/cell_commercialization.md Part 2 plus operational rules accumulated since. If a script, test, or benchmark fails because of any rule below — fix the script, never weaken the rule.
Cell exposes FFI calls from sandboxed guest Wasm code into the Rust host process. The guest is untrusted by definition (it's executing customer-supplied code). Every FFI is a potential escape hatch. The host runs on bare-metal Hetzner with full filesystem and network access — a successful escape is catastrophic.
We defend against:
- Sandbox escape via path traversal (e.g.,
../../../etc/passwd) - SSRF via outbound HTTP to cloud metadata or internal networks
- Denial of service via OOM panic or exhaustion of host resources
- Information disclosure via timing / side channels (out of scope for v1)
- Lateral movement between tenants in multi-tenant Hub deployments
We do NOT defend (yet) against:
- Side-channel timing attacks (Spectre-class) — wasmtime's standard defenses apply
- Adversarial Wasm bytecode itself — we trust wasmtime's verifier (industry-standard hardened)
- Physical access to the Hetzner host — that's an Ops concern, not a Cell concern
ANY path generated by host_fs_* calls MUST:
- Be resolved through
std::fs::canonicalize() - Verify
.starts_with(/sandbox_volumes/)(or the configured root) - Reject (return error code) on any failure
Implementation: cell/gateway/src/main.rs::host_fs_write and host_fs_read. The pattern looks like:
let root_volumes = resolve_synapse_root().join("sandbox_volumes");
let base_path = std::fs::canonicalize(&root_volumes).unwrap_or(root_volumes);
let target_path = base_path.join(path_str);
if let Ok(canon_target) = std::fs::canonicalize(&target_path) {
if !canon_target.starts_with(&base_path) {
return -1; // Block path traversal
}
}Test coverage: run_test.py Phase 2 tries ../../../../../../etc/passwd and expects rejection.
host_fetch MUST preemptively block outbound HTTP to:
| Range | What it is | Why blocked |
|---|---|---|
169.254.169.254 |
AWS / Hetzner cloud metadata service | Exposes IAM credentials, instance secrets |
127.0.0.0/8 |
Loopback | Reaches host services not meant for guests |
10.0.0.0/8 |
RFC1918 private networks | Lateral movement to internal infrastructure |
172.16.0.0/12 |
RFC1918 private | Same as above (NEW — was missing in v1) |
192.168.0.0/16 |
RFC1918 private | Same as above (NEW — was missing in v1) |
localhost |
Loopback alias | Same as 127.x |
*.local |
mDNS / Bonjour | Local network discovery |
Current implementation in main.rs::host_fetch:
if url.contains("169.254.169.254") || url.contains("127.0.")
|| url.contains("localhost") || url.contains("10.") {
return -1;
}Known gap: the current substring check is too permissive (catches 10.something.com legit domains) and too narrow (misses 172.16.x.x and 192.168.x.x). Pending: replace string contains with proper IP parse + CIDR check. Tracked as Horizon 1 hardening.
Test coverage: run_test.py Phase 3 attempts SSRF to 169.254.169.254 and expects rejection.
All FFIs accepting max_len parameters MUST enforce a hard ceiling. Currently 10 MB across host_fetch, host_fs_read, host_fs_write:
let max_len = std::cmp::min(max_len, 10_000_000); // 10MB Host Memory ProtectionWasm linear memory itself is bounded by wasmtime's per-instance configuration (default 4 GB for 32-bit Wasm, configurable per cell). We rely on wasmtime's bound enforcement for guest memory.
Per-IP rate limiter is implemented in main.rs::GatewayState::rate_limits. Default: 20 requests / second / IP. Configurable per deployment via env var SYNAPSE_RATE_LIMIT_RPS. Above the limit, the gateway returns 429 Too Many Requests.
Hub tier: rate limits differ per license tier. Pending implementation alongside the Ed25519 license validator (Plan Part 10.3, Horizon 2).
Pending implementation. Pro-tier FFIs (load_weights, host_mcp_call, GPU operations) MUST refuse to load if no valid Ed25519 license certificate is present in /etc/synapse/license.json or $SYNAPSE_LICENSE. Free tier (EdgeCell) gets only the safe-subset Wasm sandbox without these FFIs.
See plan Part 10.3 sub-layers A–D for the full architecture: Authority key, certificate format, gateway verification, receipt binding.
If run_test.py fails because of any rule above, fix the test or the calling code — never delete the security guard. Specifically:
- ❌ Don't comment out the canonicalize check to make a path test pass
- ❌ Don't widen the SSRF allowlist for a "convenience" feature
- ❌ Don't raise
max_lenceilings without a documented threat model exception in this file - ✅ Do refactor the test to use a sandbox-safe path
- ✅ Do propose new
host_*FFIs with explicit security review
Adding a new host_* FFI requires:
- A proposal in a
cell/journals/JC-NNN_<topic>.mdentry covering:- What guest code can do with the FFI
- What host resources it touches
- What the sandbox escape attack vectors are
- How those attacks are mitigated
- Code review by a separate session / agent (or Mike, for substantial new surfaces)
- Test coverage added to
run_test.pyfor at least one attack vector - This file (
SECURITY.md) updated with the new FFI and its threat model
Every Cell execution emits a SHA-256 receipt. The hash MUST include (in the canonical order):
SHA-256(execution_id|code_hash|result_hash|template|timestamp)
Future fields such as compiler fingerprint, license identity, or verifier status must extend the receipt under an explicitly versioned receipt schema. Any change to the hash composition is a breaking change and requires a major version bump plus auditor-facing release notes.
Cell MUST run with no outbound network access required. License verification is local (Ed25519 signature against an embedded public key). Telemetry is opt-in and defaults to OFF in air-gap mode. The Synapse Hub trust registry can be served from a local mirror for air-gapped customers.
grep -rE "sk_live_[a-zA-Z0-9]{8,}|e2b_[a-f0-9]{40}|password\s*=\s*\"" MUST return zero results in committed code. Test credentials live in .env.example and are sourced from environment variables. GitHub secret-scanning + push protection enforces this on every push.
Every release of Cell must pass:
python3 run_test.py— Military Audit: latency + path traversal + SSRFcd cell/sdk && python3 tests/stress_test.py --quick— concurrent cells, throughput, persistencecd cell/sdk && python3 tests/red_team.py— broader red-team coveragepython3 sdk/tests/benchmark_e2b.py— E2B parity benchmark
A failure in any of these blocks the release. The cell:ship-release skill (when it exists) checks all four.
If a sandbox escape, SSRF, or OOM is discovered in production:
- Take the affected node out of rotation immediately (DNS failover or systemd disable)
- Snapshot the host state (tarball of
/var/log/, sandbox volumes, gateway logs) - File a JC-NNN entry under SEVERITY: SECURITY with timeline, root cause, blast radius
- Fix at the root — the canonicalize / SSRF / cap rule that should have prevented it
- Add a test that reproduces the attack to
run_test.pyso it can never regress - Notify affected enterprise customers within 48 hours (Hub tier SLA commitment)
- Disclose publicly within 14 days (CVE if applicable) — see Manifesto Rule 4 below
Aligned with the Sovereign Compute Manifesto: we are accountable to the commons. Security vulnerabilities found by external researchers must be:
- Acknowledged within 48 hours of report (security@synapserun.dev — to be set up)
- Patched within 14 days for high-severity, 30 days for medium
- Disclosed publicly with credit to the researcher (and a bounty if material)
- Backported to all supported Cell versions (currently: latest only)
We do NOT issue silent patches. We do NOT NDA security researchers. We do NOT delay disclosure for marketing reasons.
- Threat model details: this file
- Code that enforces the rules:
cell/gateway/src/main.rs(search forhost_fetch,host_fs_read,host_fs_write) - Test coverage:
run_test.py,sdk/tests/red_team.py,sdk/tests/stress_test.py - License system:
cell/docs/2026-04-15-cell-commercial-setup-plan.mdPart 10.3 - Original framing:
.agents/workflows/cell_commercialization.mdPart 2