All notable changes to the Safebots Infrastructure project will be documented here.
The format is based on Keep a Changelog. This project will adhere to Semantic Versioning starting from the 1.0.0 release.
Pre-release: The 1.0.0 version is held until first public release. All entries below are development milestones in the working tree.
Scope: This changelog covers ONLY the Infrastructure repo (Linux / AMI / server-runtime level). The Safebox plugin has its own changelog in its own repo.
The privileged-operations interface for Safebox is now a small Node.js HTTP API listening on 127.0.0.1:7780. Safebox Node sends HMAC-signed JSON; the System component validates, dispatches to child_process.execFile, and returns the result.
This replaces both (a) the 1577-line Node daemon that existed at the start of this sprint AND (b) the ~1000-line shell-and-systemd component we briefly built as an alternative. The System component is smaller than either, and the shape matches what Safebox actually wants on the client side.
Component layout:
aws/scripts/components/system/
├── install-system.sh (~124 lines)
├── server.js (~239 lines, HTTP routing + HMAC verify + audit)
├── auth.js (~105 lines, HMAC sign/verify + nonce LRU)
├── secret.js (~115 lines, TPM-derived or random fallback)
├── config.js (~105 lines, managed-containers.json loader)
├── opsSystem.js (~309 lines, /system handler — execFile for 9 tools)
├── opsTest.js (~372 lines, /test handlers + keepalive + yields)
├── package.json (zero npm dependencies)
├── units/safebox-system.service
└── sudoers/safebox-system (the entire privileged surface — ~20 lines of rules)
~1100 lines of Node + ~50 lines of sudoers. The System component itself runs as unprivileged safebox-infra user. Privileged ops (dnf, zfs, docker) elevate via sudo against the tight allowlist in /etc/sudoers.d/safebox-system. That sudoers file is what an auditor reads to know exactly what the System component can do.
What changed for Safebox:
- The wire is HTTP on
127.0.0.1:7780with HMAC-SHA-256 over a canonical envelope. No more request-files-in-/run/safebox/jobs, no systemctl invocation, no polkit. - Two endpoint families:
POST /system(synchronous: blocks until npm/git/dnf/etc. finish) andPOST /test/*(async lifecycle with keepalive every 30s, ring-buffered yields, aggressive 60s teardown by default). managed-containers.json(already in the repo) is the authorization config. Each container declaresallowedActionsand animagePattern. The System component enforces both.- Field validation regexes are unchanged from the prior shell-component spec.
- See
aws/docs/SYSTEM-PROTOCOL.mdfor the full protocol Safebox programs against.
HMAC secret: stored at /etc/safebox/system.hmac (mode 0640, group safebox-infra-readers). The System component derives the secret from the TPM event log via HKDF-SHA-256 on first boot (bound to measured boot state), falling back to existing on-disk bytes or random 32 bytes if TPM isn't available. The fallback path logs WARN loudly so operators notice if attestation isn't binding the key.
Smoke tests passing:
- HMAC verify on every request (T1 valid, T2 invalid → 401)
- Routing (T4 unknown endpoint → 404)
- Container authorization (T5 unknown container, T6 action not in allowedActions → 403)
- Argument injection defense (T7
--registry=evil.compackage name rejected) - Workspace prefix enforcement (T9
/etcrejected) - Image pattern enforcement (T10 wrong-pattern image → IMAGE_NOT_ALLOWED)
- Test lifecycle (T11 unknown test → 404)
- Happy-path execFile reaches the underlying tool (T8 npm list)
Why this shape:
The earlier shell-and-systemd component was correct but ate too much auditor time: ~1000 lines of bash that someone has to read in detail. The Node System component is denser (~1100 lines of focused Node) but uses idioms most reviewers already know, and the privileged-surface check collapses to one sudoers file plus one Node file (opsSystem.js). The HMAC layer adds defense in depth against in-PHP application bugs that don't escalate to full apache RCE — a SQL injection in a Safebox PHP plugin can't forge a System component request because the HMAC key lives in Safebox Node's process memory, not in PHP's.
The trust boundary is explicit and documented in SYSTEM-PROTOCOL.md § "Out of scope for 1.0": if Safebox Node itself is compromised, the System component will execute what it's asked. M-of-N governance is inside Safebox and runs before the call. Per-request governance signatures (Ed25519) are the documented upgrade path for 1.1.
A shell-and-systemd system component was built and then replaced by the Node System component above. The shell version is preserved in git history (commits 3f67cc9, acc5ebe, 35e6f99) for anyone evaluating the alternatives.
Pre-release security pass on the base AMI installer identified ten issues. All fixed in the current install-base.sh.
-
Infrastructure Bug 1 — Missing directory creation. The script ran
cd /opt/safeboxandcat > /opt/safebox/manifests/base.jsonwithout ever creating/opt/safeboxor/opt/safebox/manifests/. On a clean install, the script failed partway through. Now creates/opt/safebox,/opt/safebox/manifests,/opt/safebox/lib, and/srv/safebox/runtimes/system/with explicitmkdir -pand proper permissions before anything tries to write into them. -
Infrastructure Bug 2 — ZFS pool assumed to exist. The script created datasets on
safebox-poolwithout verifying the pool itself was provisioned. Now fails fast with a clear error message and pool-creation instructions ifzpool list safebox-pooldoesn't find it. -
Infrastructure Bug 3 — Unpinned npm install. The previous version ran
npm install --production <packages>which pulls the latest version of each, making the build non-reproducible and exposing it to supply chain attacks (e.g. the TanStack / "Mini Shai-Hulud" campaign of May 2025). Now usesnpm ci --production --ignore-scriptsagainst a checked-inpackage-lock.jsonwith integrity hashes for every tarball.--ignore-scriptsblocks lifecycle scripts (the specific TanStack attack vector — postinstall hooks that modified.claude/settings.jsonand.vscode/tasks.json). -
Infrastructure Bug 4 — Unpinned dnf packages. The previous version ran
dnf install -y mariadb105-server php-fpm nginx docker-ce nodejs npm zfswithout version pins, so the build was non-reproducible. Now uses an explicitSYSTEM_PACKAGESarray with versioned package specs, plus a post-install verification loop that fails the build if any package's installed version doesn't match the pin. -
Infrastructure Bug 5 — Interactive shell access defeats the attestation model, but the prior installer permitted it. The docs claimed telnetd was removed via
finalize-ami3.sh, but no such script existed in the repo, and even the docs only addressed telnetd while leaving SSH and the AWS SSM agent in place. SSH and SSM Session Manager are both remote-shell mechanisms; once any human has a shell, they can read/run/safebox/zfs-keyfrom memory,kexeca new kernel, or modify a running binary — and TPM attestation says nothing about what processes do after boot. The installer now removes ALL interactive-shell paths in four tiers:- Tier 1 — Legacy daemons:
telnet,telnet-server(CVE-2026-32746),rsh,rsh-server,rlogin,vsftpd,proftpd,tftp,tftp-server,cockpit,cockpit-ws,cockpit-bridge,webmin - Tier 2 — SSH:
openssh-server,openssh-clients,openssh - Tier 3 — AWS SSM:
amazon-ssm-agent(the agent is a shell-spawning mechanism just like sshd; removing it does NOT remove IAM-role-based AWS API access, which goes through instance metadata, not the SSM agent) - Tier 4 — TTY/console getty:
getty@.service,serial-getty@ttyS0.service,debug-shell.servicemasked Plus socket-unit masking for tiers 1–3 to prevent reactivation by future package updates, plus a post-removal verification gate that checkscommand -v sshd,rpm -q amazon-ssm-agent, and that no process is listening on ports 22, 23, 513, 514, 5985, or 5986. If any verification fails, the AMI build aborts. Operationally this means diagnostics happen against an offline ZFS snapshot of a terminated instance, not on a live shell — seedocs/SECURITY-HARDENING.mdfor the full operational model.
- Tier 1 — Legacy daemons:
-
Infrastructure Bug 6 — Node.js version unpinned.
dnf install nodejscould install Node 18, 20, or 22 depending on mirror state at build time. Now pinned tonodejs-20.18.0as part of theSYSTEM_PACKAGESarray. Updates documented inline. -
Infrastructure Bug 7 — Docker daemon defaults. Containers run as host root by default without explicit user-namespace remapping. Now writes
/etc/docker/daemon.jsonwith:userns-remap=default(containers run as host UID 100000+ instead of UID 0)no-new-privileges=trueicc=false(containers can't talk to each other on the default bridge)storage-driver=zfswith explicit fsname- Log rotation caps (100 MB × 5 files)
- Creates the required
dockremapuser/group
-
Infrastructure Bug 8 — PHP-FPM defaults.
php-fpminstalled withexpose_php=On(PHP version leaked in HTTP headers) andallow_url_fopen=On(PHP couldfile_get_contents('http://attacker.com/...')directly, bypassing the Safebox Protocol.HTTP layer's SSRF protections). Also nodisable_functions. Now:expose_php=Offallow_url_fopen=Offallow_url_include=Off(defense in depth)disable_functions = exec, passthru, shell_exec, system, proc_open, popen, curl_multi_exec, parse_ini_file, show_source, dl, phpinfo
-
Infrastructure Bug 9 — ZFS encryption misconfiguration.
zfs create -o encryption=onwithoutkeyformatorkeylocationeither fails or falls back to interactive passphrase mode that breaks unattended builds. Now usesencryption=aes-256-gcm,keyformat=raw, andkeylocation=file:///run/safebox/zfs-key. The key file is generated byscripts/generate-attested-key.sh(sealed to TPM PCRs) BEFOREinstall-base.shruns. The installer fails fast if the key file is missing. -
Infrastructure Bug 10 — No auditd configuration. The base installer didn't enable kernel-level audit logging for security-sensitive events. Now writes
/etc/audit/rules.d/safebox.rulescovering:- ZFS key file access (
safebox_zfs_key) - Package manager invocations (
safebox_pkg) - Sensitive config file writes (
safebox_config) - setuid privilege escalation attempts (
safebox_priv_esc)
- ZFS key file access (
A new component exposing a localhost-only HTTP API at 127.0.0.1:7780 for the Safebox plugin. Implements package management, version control, database migrations, dnf, ZFS operations, and ZFS-based test environments. See the top-level "System component (May 20, 2026)" entry above for the full description.
Historical note: earlier iterations of this work were called "Component #20: system" and described an auth-token API, then a shell-and-systemd dispatcher. Both predate the current Node System component. The history is in git (commits
3f67cc9,acc5ebe,35e6f99) for anyone reviewing the design evolution.
All 15+ package managers (npm, yarn, pnpm, composer, pip, pipenv, poetry, cargo, gem, bundle, go, mvn, gradle, dnf, apt, apk) pinned to specific versions with SHA256 checksum verification before execution.
- Manifest at
/srv/safebox/runtimes/system/package-versions/ - Verification adds ~50ms per install but prevents trojan horse execution
- Mitigates supply chain attacks like the May 2025 TanStack/"Mini Shai-Hulud" campaign
- Pattern A — one operation per container (entrypoint IS the operation, container exits when done)
- Database state cloning (MySQL/Postgres data on ZFS datasets)
- Resource limits: 10 envs per user, 50 total, 10 GB per env (ZFS quota), 16 MB telemetry cap
- Manifest-driven telemetry collection — only files declared in
outputFilesare returned - Test containers run as
nobody(UID 5000+),network=none, no production secrets
/app/manifest.json inside each code-runner image declares envVars, outputFiles, exitCodes, resourceHints, and (planned post-1.0) cacheMounts. Manifest is signed alongside image hash.
Brain-aligned context scoring using fMRI-derived embeddings to predict which document chunks a human brain would actually retain. Reduces hallucinations by ~31% on retrieval-augmented tasks.
Pre-release audits resolved 22 additional infrastructure issues across:
- ZFS encryption config — per-dataset encryption enforced
- Path inconsistencies —
/srv/safeboxvs/opt/safeboxunified - User creation —
safeboxuser UID/GID pinned - Python server scope — request-scoped instances
- Memory exhaustion — vLLM tensor cache respects
--gpu-memory-utilization - Socket hangs — bounded retry with exponential backoff
- Score validation — embeddings reject NaN/Inf before storage
- Package verification — per-component SHA256 manifest at install
- recv/send handling — partial reads reassembled correctly
- Workflow error handling — failed steps propagate errors instead of silent success
- ZFS quota enforcement — applied at dataset create
- Whisper Turbo timeouts — raised from 30s to 120s for long audio
- TRIBE embedding cache — bounded LRU instead of unbounded growth
- MariaDB binlog — retention reduced from 30 days to 7
- Nginx/PHP-FPM workers — calculated from cgroup CPU quota, not host CPU count
- Docker overlay2 → zfs storage driver
- Log rotation —
/var/log/safebox/policy - Cascading manifest verification — race condition at component install
- TPM PCR validation — empty PCR values rejected
- Deterministic RNG seed handling — empty seed rejected explicitly
- Component dependency check — circular dependencies detected at build time
- Workflow ID validation — strict regex enforcement
- Initial 18-component composable architecture
- 70+ AI models across 5 LLM tiers (tiny to XL)
- Deterministic inference via LD_PRELOAD (AI-only RNG)
- ZFS + Docker + MariaDB storage architecture
- Cascading manifest system with auto-discovery
- Complete PDF and video ingestion pipelines
- GPL-free runtime guarantee
- Triple-layer encryption (Nitro + vTPM + ZFS)
- OpenAI Privacy Filter 1.5B (Apache 2.0) — PII redaction
- Qwen 3.6 27B (Apache 2.0) — Coding specialist, 77.2% SWE-bench
- Gemma 4 31B Dense (Apache 2.0) — Math/reasoning, 89.2% AIME 2026
- Gemma 4 26B MoE (Apache 2.0) — Efficient, 3.8B active
- GLM-5.1 744B MoE (MIT) — #1 SWE-bench Pro, 8-hour autonomous coding
- Qwen 3.6 27B: 77.2% SWE-bench, matches Claude 4.5 Opus
- Gemma 4 31B: 89.2% AIME 2026
- GLM-5.1: #1 SWE-bench Pro (58.4%)
- PDF ingestion: 5-10 pages/sec
- Video ingestion: 1-2 scenes/sec
- Audio transcription: 10x realtime
- ZFS clone: <100ms
- Package install with pinning: +50ms verification overhead
Phase A:
- Cache mounts for test environments (per-workspace, tenant-language-version)
- Network namespaces with M-of-N-signed registry
- Multi-arch images (ARM64)
- Operator-configurable per-tenant resource caps
- HMAC + timestamp for remote executor wire protocol (not just localhost)
- GCP and Azure parity with AWS
Phase B:
- Llama 4 Scout integration (10M context)
- Real-time streaming inference
- Multi-modal unified embeddings
- Cross-lingual video search
- Edge optimization (Q3_K_M quantization)
- Kubernetes deployment support
- Network mode
bridge(advanced)