Skip to content

Commit e646da7

Browse files
ZhiXiao-LinRoy Lin
andauthored
docs(readme): document new operator-facing env vars (decompression caps, seccomp root, snapshot/mirror) (#147)
* docs(readme): document the new operator-facing env vars The env-var reference was missing several operator-facing knobs added by the recent operability + security hardening: - A3S_BOX_MAX_LAYER_BYTES / A3S_BOX_MAX_BUILD_EXTRACT_BYTES — decompression-bomb caps on layer pull and build ADD/COPY auto-extract (defaults 16 GiB / 4 GiB). - A3S_BOX_SECCOMP_PROFILE_ROOT — root the CRI localhostProfile seccomp path is confined to (default /var/lib/kubelet/seccomp). - A3S_BOX_MAX_SNAPSHOTS / A3S_BOX_MAX_SNAPSHOT_BYTES — snapshot auto-prune caps (were documented in prose but absent from the reference table). - A3S_REGISTRY_MIRRORS — registry mirror map. Docs-only; no behavior change. * docs(changelog): record post-2.3.0 security/concurrency/operability hardening Fill the empty [Unreleased] section with the three post-2.3.0 audits merged to main (#131-#146): untrusted-input security (CRITICAL digest path-traversal, whiteout host-delete, decompression bombs, CRI seccomp confinement), daemonless lifecycle concurrency races, and 24 operability findings. Docs-only. --------- Co-authored-by: Roy Lin <roylin@a3s.box>
1 parent ad26f69 commit e646da7

2 files changed

Lines changed: 63 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,63 @@ All notable changes to A3S Box will be documented in this file.
44

55
## [Unreleased]
66

7+
Post-2.3.0 hardening: three adversarial audits — production-operability (24
8+
findings), untrusted-input security (4), and concurrency/atomicity (4) — all
9+
fixed and validated on real microVMs. No breaking API changes.
10+
11+
### Security
12+
13+
Image extraction runs **host-side before the microVM boots**, so a malicious
14+
image's reach here bypasses VM isolation:
15+
16+
- **Arbitrary host file write via registry digest path-traversal (CRITICAL).** The
17+
manifest digest (`Docker-Content-Digest`, returned verbatim by the registry)
18+
flowed into `Path::join` unvalidated, so `sha256:../../../../<path>` wrote the
19+
attacker-shaped manifest to an arbitrary host path on `pull` in the default
20+
config (signature policy is Skip by default; the box runtime often runs as
21+
root). Digests are now validated as canonical `sha256:<64-hex>` at the trust
22+
boundary before any path use.
23+
- **Arbitrary host file/dir deletion via whiteout symlink escape.** A layer
24+
whiteout whose parent was an absolute symlink (e.g. `esc -> /etc`) deleted host
25+
files/dirs through it. Whiteout parents are now confined within the extraction
26+
target.
27+
- **Host disk exhaustion via decompression bomb.** Layer pull and build
28+
`ADD`/`COPY` auto-extract streamed gzip/zstd/bzip2/xz with no decompressed-size
29+
cap. Bounded by `A3S_BOX_MAX_LAYER_BYTES` (16 GiB) and
30+
`A3S_BOX_MAX_BUILD_EXTRACT_BYTES` (4 GiB), env-overridable.
31+
- **CRI seccomp `localhostProfile` path confinement.** The attacker-set pod field
32+
was read off disk unconfined (an arbitrary host-file open oracle); it is now
33+
confined to `A3S_BOX_SECCOMP_PROFILE_ROOT` (default `/var/lib/kubelet/seccomp`),
34+
rejecting `..` and out-of-root paths.
35+
36+
### Fixed
37+
38+
- **Daemonless lifecycle concurrency races** (the `monitor` daemon, CLI
39+
processes, and CRI server coordinate via a per-write flock that does not span an
40+
`await`):
41+
- The monitor no longer resurrects a box the user `stop`ped during its
42+
up-to-10s health-restart window.
43+
- A user `restart` and the monitor's auto-restart can no longer both boot the
44+
same box (now serialized by a per-box boot lock); previously the second record
45+
write overwrote the first's PID, orphaning an untracked VM.
46+
- `kill`'s host-signal fallback re-checks PID start-time identity before
47+
signalling, so a reused PID is never signalled.
48+
- The warm pool no longer leaks a VM pushed into the idle set during shutdown
49+
drain.
50+
- **Operability (24 findings)** across crash-recovery, upgrade-compat,
51+
disk-pressure, concurrency, network-lifecycle, and config-validation — e.g.
52+
PID-reuse liveness via start-time identity, corrupt-store quarantine instead of
53+
a hard fail, durable (fsync'd) state writes, bounded snapshot / build-cache /
54+
CRI-log growth, atomic CRI network attach, stable bridge IPs across stop/start,
55+
and fail-closed `--cpus` / `--memory-swap` validation.
56+
57+
### Changed
58+
59+
- New operator-tunable caps (generous defaults, env-overridable), documented in
60+
the Environment variables table: `A3S_BOX_MAX_LAYER_BYTES`,
61+
`A3S_BOX_MAX_BUILD_EXTRACT_BYTES`, `A3S_BOX_SECCOMP_PROFILE_ROOT`,
62+
`A3S_BOX_MAX_SNAPSHOTS` / `A3S_BOX_MAX_SNAPSHOT_BYTES`.
63+
764
## [2.3.0] — 2026-06-16
865

966
A security and hardening release closing a 35-finding adversarial audit (plus

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,12 @@ matrix, and CRI smoke procedures.
437437
| `A3S_BOX_HOST_SMOKE_TIMEOUT_SECS` | Boot timeout override for ignored host smoke tests. |
438438
| `A3S_BOX_UNSAFE_HOST_RUN` | Opt into unsafe macOS host execution for Dockerfile `RUN` experiments. |
439439
| `A3S_BOX_BUILDCACHE_MAX_BYTES` | Cap on the total size of cached build layers at `~/.a3s/buildcache` (oldest evicted first). Default: 2 GiB. |
440+
| `A3S_BOX_MAX_LAYER_BYTES` | Cap on total decompressed bytes per OCI image layer during `pull` (decompression-bomb guard). Default: 16 GiB. |
441+
| `A3S_BOX_MAX_BUILD_EXTRACT_BYTES` | Cap on total decompressed bytes when a build `ADD`/`COPY` auto-extracts a local tar archive (decompression-bomb guard). Default: 4 GiB. |
442+
| `A3S_BOX_MAX_SNAPSHOTS` | Auto-prune on every `snapshot create` to keep at most N newest snapshots per box (unset = unbounded). |
443+
| `A3S_BOX_MAX_SNAPSHOT_BYTES` | Auto-prune on every `snapshot create` to keep snapshots under a total byte cap (unset = unbounded). |
444+
| `A3S_BOX_SECCOMP_PROFILE_ROOT` | Root directory a CRI `localhostProfile` seccomp path is confined to (paths outside it, or containing `..`, are rejected). Default: `/var/lib/kubelet/seccomp`. |
445+
| `A3S_REGISTRY_MIRRORS` | Registry mirror map (`host=mirror,host=mirror`); pulls fetch layers/manifests from the mirror while keeping the canonical image reference. |
440446
| `KRUN_SNAPSHOT_MEM_FILE` | Path the booted template writes its file-backed guest RAM to when capturing a snapshot-fork template. |
441447
| `KRUN_SNAPSHOT_SOCK` | Control socket the template listens on for the `snapshot <path>` command (Linux `/dev/kvm` only). |
442448
| `KRUN_RESTORE_FROM` | Path to a snapshot the microVM restores from as a Copy-on-Write fork instead of cold booting. |

0 commit comments

Comments
 (0)