v0.19.0
[0.19.0] — 2026-08-03
Releases 0.15.0 through 0.18.0 were never tagged or published. They landed
onmainas steps of one piece of work and are kept below as the record of what
changed when. This is the version that ships them, so upgrading from 0.14.0
means taking all of it — read those sections too, they describe behaviour in
this release.0.18.0 in particular shipped a fallback that let the jail run on kernels older
than 5.12. It is removed here, and because 0.18.0 was never published, no
released version ever offered it.
Changed — the rootless jail requires Linux 5.12 (ISOPOD_JAIL=1 only)
Breaking for jail users on older kernels; nothing else changes. The jail is
opt-in, so isopod itself gains no kernel floor — an unjailed run is unaffected,
and a host below 5.12 keeps everything except the optional second isolation layer.
mount_setattr(2) is the only mechanism that makes a bind read-only including
every mount beneath it and actually holds: the kernel walks the tree in one call,
so nothing can appear between reading a mount table and acting on it; it adds
MOUNT_ATTR_RDONLY and clears nothing, so it cannot trip the rule that a
nosuid/nodev/noexec bit locked into a user namespace may not be cleared; and
it reaches a mount that another mount is stacked over, which nothing driven by
path can do.
0.18.0 shipped a hand-rolled walk over /proc/self/mountinfo for older kernels.
It is removed. It failed all three ways above, in three successive rounds, each
one found only by the live suite on a hosted runner and never on a developer's
machine — because whether it fails at all is a property of the host's mount
table, not of the code. It dropped the locked flags and earned EPERM; it then
read flags off the wrong mountinfo line where two mounts share a mount point
(/proc/sys/fs/binfmt_misc is a systemd autofs with the real filesystem mounted
over it) and earned the same EPERM again; and it could never have reached a
shadowed mount at all, since a remount by path only ever finds the topmost.
A second implementation of a security boundary, on a path that only runs where
nobody tests, accumulates defects at full rate and reveals them at the rate the
untested hosts are exercised. It is worth less than the hosts it buys. Dogfood
finding #53.
On an older kernel the jail now refuses to start, naming the requirement, this
host's kernel release, and the fact that dropping ISOPOD_JAIL=1 starts an
unjailed VM.
Changed — the jail's tests follow the code they protected
The mountinfo parser and its unit tests are gone with the fallback they served.
What remains is tested where the guarantee now lives: the live probe's assertion
that a submount of a read-only bind is read-only, and the unsupported-kernel
path — both the message and, separately, the decision to use it. That second test
exists because a mutation proved the first was not enough: the message was
covered, the call site was not, so deleting the call left every test passing.