@@ -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
966A security and hardening release closing a 35-finding adversarial audit (plus
0 commit comments