Repository navigation
Releases
hisn 0.4.3
0.4.3 adds no features. It locks in the 0.4.2 fixes with regression tests,
adds a release gate, fixes smaller audit findings, and restructures
session startup so the coming egress proxy, cgroup v2 limits and
multi-seat work can be added safely.
Host evidence (rubin): make release-gate all ok (fmt, clippy, make
check, make test), leak suite 333 passed, 0 failed, 9 skipped, 1 weak;
with HISN_TEST_ROOTFS and HISN_BIG_TESTS the leak suite reports 346
passed, 0 failed, 5 skipped, 1 weak; make leaktest on a terminal also
reports 333 passed, 0 failed, 9 skipped, 1 weak. The gate's
pseudo-terminal step was skipped because script is not installed; the
direct terminal run covers it. No unit-test count for rubin is recorded;
no sessions, leak or integration tests were run in the build sandbox.
Changed
- Host services run in their own process group. Each host service is
a process-group leader with stdin/dev/null(stdout and stderr too).
Stopping a session signals the whole group, then SIGKILLs it after the
leader is reaped, so a TERM-ignoring child does not survive. - Startup fails at once when a host service exits before readiness
completes, withhost_service <name>: exited with status <n> before readiness completed. - A service shows
startinginls --jsonuntil readiness passes,
thenready.exitstaysnullfor in-sandbox services. - SSH agent proxy timeouts. The host agent socket now has a 30 s
read timeout alongside the existing 30 s write timeout. - Release gate.
make release-gateruns fmt, clippy,make check,
make testand the leak suite (pipe, then pseudo-terminal when
scriptis present), stopping at the first failure.
Fixed
recv_with_fdtreats a truncated control message as an error.rootfs.rsdupfailures now return the OS error instead of wrapping -1.stdio_to_dev_nullcloses the opened fd only above fd 2 on error.- The SSH agent client counter is decremented on every exit path via a
guard inside the worker thread. sessionmeta::rewriteremoves a stale temp of the same name before
creating a new one.- Leak tests L77/L78 take the helper socket path from
argv[1]; the
release gate pipes the leak suite and reports a skipped pseudo-terminal
run honestly; dead-service detection, the cleanup guard's group kill,
and the ExecTable insert panic are fixed.
Hardening
run_sessionis split into named phases behind one cleanup guard; a new
src/services.rsholds the service code; host and in-sandbox stops
share onestop_and_reappath; one profile load; one exec table.- Leak cases L76–L81: exec cancel on early disconnect, hung and slow
readiness probes, load-time rejections, host-service group stop, and
readiness failing for an already-dead host service.
Leak suite: the portable subset runs in CI; host-only checks (user namespaces, rootfs, attach, leases) are verified on a real host before tagging — see the release notes for the host result. The aarch64 binary is built and started (--version) but has not been leak-tested. The glibc binaries (hisn-x86_64-linux-gnu, hisn-aarch64-linux-gnu) are dynamically linked and built natively on Ubuntu 22.04 (glibc 2.35 or newer required). The musl binary (hisn-x86_64-linux-musl) is statically linked. Verify with sha256sum -c SHA256SUMS. See README, "Verifying a build".
hisn 0.4.2
hisn v0.4.2
0.4.2 closes the findings of a code audit of 0.4.1. It adds no features. Profiles that used to panic or hang the supervisor at run time are now rejected when they load, readiness probes can no longer outlive their timeout, a cancelled exec no longer keeps running, and session.json updates are safe across processes.
Upgrade notes
- A profile that loaded under 0.4.1 can now be rejected. This affects
stop.graceor a readinesstimeoutabove 86400 s, NUL bytes in exec strings, and any use ofready.tcp. ready.tcpwas accepted before but never became ready, so a profile using it always timed out. It now fails at load withE_NOT_SUPPORTED.- A
ready.commandprobe that hangs is no longer retried. It is killed whenready.timeoutexpires and startup fails. - The control-socket protocol,
ls --jsonoutput, thesupervisor.pidformat and exit codes are unchanged.
Changed
- Profile validation.
stop.graceand readinesstimeoutare capped at 86400 s. NUL bytes inhost_services.exec,ready.commandand service argv are rejected.ready.tcpis rejected as not implemented. - Readiness probes are bounded by
ready.timeout. Aready.commandprobe gets the time remaining until the readiness deadline. At the deadline the probe and its process group are killed and startup fails. A slow probe that finishes within the timeout now succeeds. session.jsonis written under a lock. Every read-modify-write (set_state,record_member,touch_lease) takes an exclusiveflockonsession.json.lockin the session directory, in the supervisor and inhisn stop.
Fixed
- Cancelled exec kept running. If a client disconnected before hisn sent
started, the program was never signalled and the session could not idle out while it ran. The exec is now cancelled. - Supervisor panics from profile values. A very large
graceortimeoutoverflowed the deadline arithmetic, and a NUL byte in an exec string panicked at start. Both are now load-time errors. - Host-service stop race. SIGKILL is never sent to a pid that was already reaped, and an interrupted wait no longer cuts the stop grace short.
- Readiness reported too early. An interrupted wait on a
ready.commandprobe was treated as success. The wait result is now checked. - Lost
session.jsonupdates. The supervisor andhisn stopcould overwrite each other's changes.
Hardening
- Host-service and readiness-probe children do no allocation between
forkandexec. Readiness probes close inherited descriptors and die with the supervisor. - The SSH agent proxy computes key fingerprints with the
sha2crate. Its output is unchanged.
Internal
- Removed dead code:
session::mode_of,ServiceHandle.name,init::raw_fd, a stale#[allow(clippy::too_many_arguments)]and an unused parameter. - Leak test L33 passes
--no-tty, so the suite also passes when run directly on a terminal.
Verified
On the release host (Fedora 45, x86_64): unit tests 406 passed, 0 failed. Leak suite 307 passed, 0 failed, 9 skipped, 1 weak. The exec-cancel and readiness-timeout checks pass end to end.
The portable subset of the leak suite runs in CI. The host-only checks (user namespaces, rootfs, attach, leases) are covered by the host result above. The aarch64 binary is built and started (--version) but has not been leak-tested.
Binaries
The glibc binaries (hisn-x86_64-linux-gnu, hisn-aarch64-linux-gnu) are dynamically linked and built natively on Ubuntu 22.04, so they need glibc 2.35 or newer. The musl binary (hisn-x86_64-linux-musl) is statically linked. Verify with sha256sum -c SHA256SUMS. See README, "Verifying a build".
hisn 0.4.1
hisn v0.4.1
0.4.1 makes run --detach return, completes the ls --json client view, removes the fixed 100 ms waits on session start and stop, and hardens the 0.4.0 seat model.
Highlights
hisn run --detachforks the supervisor, waits for readiness, prints<id> <name|-> readyand exits 0.- A failed start exits 125, with or without
--detach. - Session readiness and
hisn stopare event-driven. No fixed 100 ms poll remains on either path. hisn ls --jsonnow reports live clients and execs (hisn-ls/v2, additive).hisn ls --prunegains--name-prefixand--dry-run.- Observer queries (
ls,inspect,sessioninfo) no longer count as clients, so they cannot keep an idle session alive.
Added
hisn run --detachreturns. The child callssetsidand writes stdio to<session-dir>/supervisor.log(mode 0600). The parent prints RISK lines before forking, because the child's stderr is not the caller's.hisn ls --prune --name-prefix <prefix>prunes only stopped or crashed sessions whose name starts with<prefix>. An unnamed session never matches. Plain--pruneis unchanged.hisn ls --prune --dry-runlists the sessions that would be pruned and removes nothing.hisn ls --jsonadditions (hisn-ls/v2, same schema name):clients: live control connections (uid, pid, label, label_source, connected_since).execs: a live attach appears withattach: true.services,rootfs,members,lease, anddirfields.- For a stopped or crashed session,
clientsis[]and CLIENTS is 0.
- A
sessioninfocontrol op. The supervisor uses it to supply the live client and exec view, whichsession.jsonon disk cannot. hisn exec --client LABEL, the same flag asattach.hisn run --env-file PATH. It readsKEY=VALUElines (blank lines and#comments ignored; validated like--env). Use it for secret-shaped values so they stay out of/proc/<pid>/cmdline.- Leak tests L73 (
ls --prune --name-prefix), L74 (ls --jsonclients) and L75 (run --detachreturns). - Tests
tests/latency.rs,tests/detach_pipes.rsandtests/observers.rs.
Changed
- Readiness polls the control socket with a nonblocking connect and exponential backoff, 1 ms up to 20 ms. A stalled listener cannot block the waiter.
hisn stopwaits on the supervisor's pidfd. Only the no-pidfd fallback polls, with bounded exponential backoff.- A stopped session keeps its name. The name is reusable once
hisn ls --pruneremoves the directory and repairs the index. - Name reservation checks the index, so concurrent
runs are race-free.releasedrops a name only if it still maps to the releasing session's id. hisn stopon a stopped session printsalready stopped. On a crashed one it printscrashed, no supervisor. Both exit 0.hisn inspectredacts[env.set]values whose names matchlooks_secret.touch_leasewritessession.jsononly when the session has a lease.
Fixed
- A failed
run --detachstart returned 1 instead of 125 (refused profile, missingrisk_ack,home.persistrefusal, service readiness timeout). It now exits with the named constantEXIT_START_FAILED(125) in every case. A running session's command status is unchanged. - A detached supervisor inherited the parent's stdout, stderr and ready-pipe write ends. A caller that held those pipes never saw EOF. Go's exec reported
WaitDelay expired before I/O completeand treated a live session as dead. The child now closes every inherited fd except the ready pipe. - RISK lines never reached the caller under
--detach. The parent now prints them before the fork. - Client records were keyed by file descriptor number. The kernel reuses a closed fd at once, so a later connection could replace a live record and a close could remove the wrong one.
clientsstayed at 1 after the connection closed and idle never fired. Connections now have a process-unique id, and the count and records live in one registry. - Observers and bare probes could register as clients and bump
last_activity. Registration now happens only after a request is admitted. A refused request never registers. A disconnect bumps activity only for a counted connection.
Hardening
redact_env_setredacts[env.set]values by parsing the policy TOML.- The host environment is read with
vars_os(). A non-UTF-8 variable no longer panics the supervisor. - Client connect and exec registration take
lease_lock, the same lock the idle check holds. An exec admitted during the idle check is not stopped under it. is_loopback_nsalso rejects0.0.0.0,::,::ffff:127.0.0.0/8and::ffff:0.0.0.0.build_resolv_confrefuses a[net] nameserversoverride that is empty or loopback-only. Before, onlyhisn profile validatedid.- Detached-child fd hygiene: all inherited fds of 3 and above are closed, and 0 to 2 are reopened from
/dev/nulluntil stdio moves tosupervisor.log. - RISK lines are printed before the fork under
--detach.
Compatibility
- The
ls --jsonschema name stayshisn-ls/v2. The changes are additive, and 0.4.0 fields are kept. hisn ls --prunewithout the new flags behaves as in 0.4.0.runwithout--detachis unchanged.- If the
sessioninforequest fails,lsshows emptyclients,execsandservicesfor that session. It does not fail. - 0.4.1-only:
run --detachreturning,--env-file,exec --client,--name-prefix,--dry-run, thesessioninfoop, and the liveclients,execs,servicesandrootfsdata inls --json. - Not tested in the tree: a 0.4.1 client against a running 0.4.0 supervisor.
Known issue carried forward: ttyname() inside an attach still fails. The fix needs a protocol change and a host to verify it. See CHANGELOG and DESIGN section 20.1.
Verified
- Unit tests and
tests/leak/test_l8.pywere run in the build sandbox, per the CHANGELOG. - The build sandbox blocks user namespaces. The leak suite and the
--detachand attach host steps could not run there. - Latency is asserted in
tests/latency.rs: a socket that appears after 8 ms must be observed ready in under 60 ms, an already-ready socket in under 30 ms, and a stop in under 80 ms. A fallback test asserts no fixed 100 ms sleep. The tree holds no trace output. The test prints the measured times, but no trace output is committed, so this note quotes no measured figure. The earlier CHANGELOG figures in microseconds were removed: they cannot describe the 8 ms case, which takes at least 8 ms by construction. - No leak suite result for 0.4.1 is committed. The last recorded one is 308 passed, 0 failed, 6 skipped, 1 weak, for 0.4.0. This note gives no 0.4.1 count. The same applies to two-host seat and audit archival runs: nothing in the tree records them for 0.4.1.
- Host steps to run on rubin:
cargo test --release,HISN_TEST_ROOTFS="$(hisn rootfs path wakil-dev)" make leaktest,hisn run --name t --detach,hisn ls --json,hisn ls --prune --name-prefix wakil-.
hisn 0.4.0
0.4.0 adds seats: a hisn session is a named place that several clients join,
locally or over SSH (ssh -t host hisn attach --session <name>), sharing one
sandbox, one pid namespace, one session env snapshot and one audit log, with
idle leases, an explicit hisn stop that keeps the session directory, and
archival of the audit on prune.
Host evidence: make leaktest on rubin: 308 passed, 0 failed, 6 skipped,
1 weak; remote attach verified on smaragd (Fedora 44, kernel 7.2.13).
Security: equal access per uid in 0.4.0; the client record, the single
authorize() seam and the per-client audit ship now, so roles are a policy
change later.
Added
- Named sessions:
hisn run --name <name>(unique per user);--session
accepts a name or id everywhere;hisn lsshows NAME, ID, STATE, CWD,
ROOTFS, NET, AGE, CLIENTS, LEASE. A per-user index in the/tmp/hisn-$UID
root is authoritative; a fallback scan repairs it (ADR 0014). - Attach:
hisn attach --session <name|id>— an interactive shell
($SHELL from the env snapshot, else /bin/sh) in the session cwd. Multiple
attaches are peers. The detach key Ctrl-] d closes the client; the shell
is killed on disconnect (no --detach in 0.4.0) (ADR 0015). - Client identity and the authorization seam: every control request
carries aClientRecord(uid, pid, label, req_id, seq, principal). One
authorize()function gates every op; in 0.4.0 it allows iff
uid == owner.uid. TheAuthorizedtoken cannot be forged outside the
authzmodule. Every audit line carriesclient,seq,authz.
Members are append-only insession.json(ADR 0016). - Session env snapshot:
--env K=Vat run declares a session env;
precedence: scrub < declared < computed; a request naming a declared key
getsE_ENV_LOCKED(ADR 0017). - Leases, stop, archival:
[session] idle_timeout(duration string,
default none).hisn stop --session Xsets state stopped and keeps the
dir.ls --prunearchivesgrants.jsonl+session.jsonto
$XDG_STATE_HOME/hisn/audit/<id>.jsonl(0600) before removing;--purge
skips archival (ADR 0019). Idle expiry runs the same stop function as
hisn stop(dir kept asstopped, auditlease/expired); a foreground
hisn runthen exits 143 withhisn: session <name|id> stopped: idle for <d> (idle_timeout <d>). A stop racing the session end no longer removes
the dir.hisn ls --jsongainsdir. - Lifecycle state:
session.jsoncarriesstate ∈ {creating, running, stopping, stopped, crashed}.
Known issues (0.4.1)
hisn run --detachstays in the foreground instead of returning once the
session is ready.- Inside an attach,
ttyname()fails (tty: no such device): the client's
pty slave is not visible in the session's private/dev/pts.isattyis
true (L20), so interactive programs work; programs that need the tty's
name do not. hisn ls --jsonlacks theclientsarray and does not list a live
attach underexecs.- CLIENTS is not zeroed on a stopped session.
- L56 and L58 remain host-only / documented checks (not automated in the
leak suite).
Changed
- Repository cleanup (no code change): process documents (plans, patches,
host-run logs, the verification record) are removed from the tree; their
decisions are indocs/adr/. The policy schema is regenerated from the
struct (addslandlock,shm,shm_size,tmp_size). Example profiles use
/tmp/hisn-outasgrantable_rw, so they validate on a fresh host. CI builds
the musl binary and validates the example profiles.
Leak suite: the portable subset runs in CI; host-only checks (user namespaces, rootfs, attach, leases) are verified on a real host before tagging — see the release notes for the host result. The aarch64 binary is built and started (--version) but has not been leak-tested. The glibc binaries (hisn-x86_64-linux-gnu, hisn-aarch64-linux-gnu) are dynamically linked and built natively on Ubuntu 22.04 (glibc 2.35 or newer required). The musl binary (hisn-x86_64-linux-musl) is statically linked. Verify with sha256sum -c SHA256SUMS. See README, "Verifying a build".
hisn 0.3.3
Fixed
- Session DNS on systemd-resolved hosts. The session's
/etc/resolv.conf
is now built from the host's non-loopback nameservers, else the
systemd-resolved upstream file/run/systemd/resolve/resolv.conf, else the
launch fails withno usable DNS resolver …. Loopback nameservers are never
passed in, and an empty resolv.conf is never written. Evidence reported from a
Fedora 45 host (hisn 0.3.2, wakil-dev rootfs): the session's resolv.conf was
empty,getent hosts example.comreturned rc=2, whilecurl -sI https://1.1.1.1
returned HTTP/2 301. The host resolv.conf was the systemd stub (127.0.0.53)
and the real upstreams were in/run/systemd/resolve/resolv.conf. The Fedora
host itself was not re-run for this release. On the build host (non-loopback
host resolv.conf) anetwork = "host"session showed the host nameserver
with loopback dropped, andgetent hosts example.comreturned rc=0. - Rootfs sessions on
network = "host"no longer fail withno usable DNS resolverwhen the image's ownetc/resolv.confis empty (the wakil-dev
image). The first version of this fix read only the image file for a rootfs
session, so such a launch failed closed while the host had working
resolvers. The precedence is now:[net] nameservers, then the host
/etc/resolv.conf, then/run/systemd/resolve/resolv.conf, and the image's
etc/resolv.confonly when neither host source has a usable nameserver. A
populated image never shadows a working host resolver. The image file is read
withopenat2(RESOLVE_BENEATH | RESOLVE_NO_SYMLINKS), so a symlink in any
component, includingetc, is refused, and the upstream file is read
from the real host, not from inside the image. Regression check: leak test
L50 (runs whenHISN_TEST_ROOTFSis set).
Added
[net] nameservers = ["ip", ...](profile): replaces the derived resolver
list. Empty, non-IP and loopback entries are rejected athisn profile validate.hisn exec --no-tty: non-interactive exec with no pty. Stdin is
/dev/null; stdout and stderr are client-owned pipes that the client copies to
its own stdout and stderr, so the sandbox never receives the caller's terminal.
--tty --no-ttyis a usage error. The refusal for a terminal without either
flag still applies and now names both options.mounts.roacceptssrc:dst(ADR 0012): the host pathsrcis bound
read-only (ro,nosuid,nodev) at the in-session pathdst. Same-path entries
are unchanged.dstunder/run/hisnor a runtime root,dstcontaining
.., anddstcolliding with another session mount are rejected at validate,
naming the path. A symlinkedsrcdirectory is resolved on the host.policy validateandprofile validatenow run the mount plan, so mount
conflicts are refused at validate, not only at session start.- Leak tests L47–L49 (
src:dstmount, validate refusals, symlinked source).
The leak suite (L1-L45) passed on Ubuntu 22.04 and 24.04 x86_64 CI runners for this exact commit before these binaries were built; the aarch64 binary is built and started (--version) but has not been leak-tested. The glibc binaries (hisn-x86_64-linux-gnu, hisn-aarch64-linux-gnu) are dynamically linked and built natively on Ubuntu 22.04 (glibc 2.35 or newer required). The musl binary (hisn-x86_64-linux-musl) is statically linked. Verify with sha256sum -c SHA256SUMS. See README, "Verifying a build".
hisn 0.3.2
Hardening before the tag (same version):
rootfs importmoved torootfs_import: streamingtarcrate extraction with
explicit hardening, unique staging, no-clobber / rename-aside--replace,
manifest.jsonwritten without following archive links, option-like
--from-imagevalues refused.[rootfs] namethat cannot be resolved is an error (it used to fall back to the
host toolchain); a rootfs overlapping a writable mount, or with a symlinked
/etc, is refused.[home] persistrefuses fixed host trees, shared dirs, hisn's own state dirs and
directories owned by someone else.- ssh proxy rejects malformed SIGN_REQUESTs locally, caps clients, times out idle
ones.
Added
- Persistent
$HOME([home] persist,--home-persist): a profile may
declare a persistent home directory that survives across sessions, bind-mounted
rw at the same in-sandbox path. Refuse list (real$HOME, ancestors of
$HOME,/, cwd, ancestors of cwd) enforced before any namespace exists. Risk
labelhome-persist(Data). ADR 0009. Leak test L42. - Read-only rootfs (
[rootfs] name|path,--rootfs-path): a profile may
declare a rootfs tree that replaces the host toolchain dirs (/usr,/bin,
/sbin,/lib,/lib32,/lib64,/opt,/nix,/etc) inside the sandbox.
All rootfs mounts arero, nosuid, nodev. Host/usr,/opt,/nixare not
visible when a rootfs is set.hisn rootfs import|list|path|rmsubcommand with
streaming extraction (via thetarcrate — handles ustar prefix, GNU
LongLink, and PAX extended headers) and hardening (strip setuid, reject
traversal, reject hard links outside, reject symlink-then-write, skip
devices, atomic staging).hisn checkreports the rootfs store. Risk label
rootfs(Trust). ADR 0010. Leak tests L43, L44, L46. - SSH agent T1 (
[ssh] agent = true,keys = [...]): a filtering proxy that
forwards onlyREQUEST_IDENTITIESandSIGN_REQUEST, rejects all mutations
(ADD,REMOVE,LOCK,UNLOCK,EXTENSION), filters by fingerprint
allowlist, and injectsSSH_AUTH_SOCKinto the scrubbed env. The host
SSH_AUTH_SOCKis never mounted. Every forwarded sign request is logged
(fingerprint, session id) — allowlist or not. The accept loop retries on
EMFILE/ENFILE/EAGAIN. Risk labelssh-agent(Signing). ADR 0011.
Leak test L45.
Changed
Cargo.tomlversion bumped to 0.3.2.- CI: musl target (
x86_64-unknown-linux-musl) added to the release workflow
alongside the glibc build, with a-muslsuffix in the asset name. Glibc
remains the default download.
Tests
- L42 (home-persist): persist dir created, cross-session persistence (file
written in session A exists in B), symlink escape blocked, pre-0.3.2 home
location fails, each refuse-list case (real$HOME, ancestor of$HOME,
/, cwd, ancestor of cwd) rejected before any namespace exists, HOME env
is the sandbox path, persist ≠ real$HOME. - L43 (rootfs, with
HISN_TEST_ROOTFS): host/optmarker not visible,
host binary not executable,/usrmount showsro,nosuid,nodevin
mountinfo, remount cannot drop ro, username matches,nosuidenforced. - L44 (rootfs import hardening, always runs): path traversal, absolute names,
hard link escape, setuid stripping on a synthetic tar. - L46 (rootfs symlink traps, with
HISN_TEST_ROOTFS): symlink traps in
/usr/local/binand absolute symlinks to host$HOMEdo not reach host
data. - L45 (ssh-agent): two ed25519 keys; with
keys=[fp1]:ssh-add -lshows
only key1, key2 filtered, ssh-add (add) and ssh-add -D (remove all) from
inside session fail, host agent still lists both keys, proxy socket absent
after teardown; withoutkeys=: both keys listed, add/remove still fail
(skip if ssh-agent or ssh-add not found).
The leak suite (L1-L45) passed on Ubuntu 22.04 and 24.04 x86_64 CI runners for this exact commit before these binaries were built; the aarch64 binary is built and started (--version) but has not been leak-tested. The glibc binaries (hisn-x86_64-linux-gnu, hisn-aarch64-linux-gnu) are dynamically linked and built natively on Ubuntu 22.04 (glibc 2.35 or newer required). The musl binary (hisn-x86_64-linux-musl) is statically linked. Verify with sha256sum -c SHA256SUMS. See README, "Verifying a build".
hisn 0.3.1
Fixed
- L17 signal race (7b162c7, 809da11): the control thread left
SIGCHLD,
SIGTERM,SIGINT,SIGHUPandSIGWINCHunblocked, so the kernel could
deliver them to it and the main thread'ssigsuspendwas never woken (a
detached supervisor hung after its init exited; seen on ubuntu-22.04 CI).
The control thread now blocks them. 7b162c7 also made the L29 test and
profiles/docker-socket.tomlusenetwork = "none"(host mode needs
Landlock ABI >= 6, absent on ubuntu-22.04). - Verification rows for CI and the host run: see
docs/VERIFICATION.txt.
The 0.3.0 tag points at afdccd0 and does not contain these fixes.
The leak suite (L1-L24) passed on Ubuntu 22.04 and 24.04 x86_64 CI runners for this exact commit before these binaries were built; the aarch64 binary is built and started () but has not been leak-tested. Binaries are dynamically linked against glibc and built natively on Ubuntu 22.04 (glibc 2.35 or newer required). Verify with sha256sum -c SHA256SUMS. See README, "Verifying a build".
hisn 0.2.2
Fixed
- User name inside the sandbox. 0.2.1 took the name for
/etc/passwdand
/etc/groupfrom$USER, which a scrubbed launch environment (Wakil,
env -i) does not have, soid -unandecho $USERshoweduser. The name
now comes fromgetpwuid_r(geteuid())(fallbackuseronly if the lookup
fails or the name is unusable;hisn run -vlogs why).
Changed
USERandLOGNAMEare set in the sandbox environment, computed from that
name, likeHOME: never copied from the host environment, andenv.pass,
env.setandexecrequests cannot override them (anexecrequest naming
them is refused as a computed name). POLICY-SPEC and DESIGN §8 updated.
A 0.2.1 policy yields the same mount plan. Differences: the two new
variables; the content of the generated/etc/passwd//etc/group(host
login name instead of the placeholderuserunder a scrubbed launch
environment); a policy that setUSER/LOGNAMEnow loses to the computed
value. Session start now queries the host user database once
(getpwuid_r), which can be slow with an unreachable network backend.
Tests
- Leak test L1c (
id -un,$USER,$LOGNAME,getent passwdshow the host
user, also for a session started underenv -i); unit tests for the lookup
and fallback, stub generation and the computed names.
The leak suite (L1-L24) passed on Ubuntu 22.04 and 24.04 x86_64 CI runners for this exact commit before these binaries were built; the aarch64 binary is built and started () but has not been leak-tested. Binaries are dynamically linked against glibc and built natively on Ubuntu 22.04 (glibc 2.35 or newer required). Verify with sha256sum -c SHA256SUMS. See README, "Verifying a build".
hisn 0.2.1
Added
/dev/shm(session.shm, defaultfalse): optional tmpfs at/dev/shm
with configurable size (session.shm_size, MiB, default 64). Default off =
no/dev/shm(0.2 behavior)./tmpsize (session.tmp_size, MiB, default 1024, range 16..=65536):
replaces the hardcoded 1 GiB constant with a policy key.--ready-fd Nonhisn run: after the session is live, writes"ready\n"
to fd N and closes it. For programmatic readiness (Wakil).- Opaque exec ids: every exec gets
exec_id = "e-<n>"(monotonic, never
reused).signalacceptsexec_id(preferred) orpid(deprecated, kept
for 0.2 clients); a recycledpidis rejected withE_NOT_FOUND. - Frozen
--jsonschemas:hisn ls --jsonandhisn inspect --jsonemit
{"schema":"hisn-ls/v1",…}/{"schema":"hisn-inspect/v1",…}.cwd,
hostname,networkare included inls --json. hisn ls --prune: remove stale session directories (no live
supervisor). Stale = control socket refuses AND supervisor pid is gone or
has a different starttime. Removal is fd-relative (openat2with
NO_SYMLINKS|NO_XDEV|BENEATH), so a planted symlink is unlinked and never
followed and a planted bind mount is never entered; it is owner-checked and
confined to the one session directory.- Stale session cleanup on
hisn run --session-id X: if the dir is
stale, remove it and proceed; if live,E_EXISTSstands. - Stale session cleanup on
hisn kill --session X: if stale, remove the
dir and exit 0 with"session X was stale; removed". supervisor.pidfile in the session dir: written at start with the
supervisor pid and starttime for stale-detection.- R5 ancestry retry (OPEN-POINTS P16):
is_descendant_ofretries the
whole/proc/<pid>/statuswalk onENOENT/ESRCH/EINTR(3 attempts,
50 ms apart). The retry targets the sandbox init (a specific pid), not
"any pid 1".
Changed
- passwd/group stub verified to use the host username (
HostFacts.username
from$USER);id -uninside the sandbox prints the host username. - init argv scrub: after START, init overwrites its argv memory in place
with"hisn-init"so the session id, policy path, and session dir do not
appear in/proc/1/cmdlineinside the sandbox. Best-effort. - musl ioctl casts (OPEN-POINTS P22): ioctl request constants cast with
as _sox86_64/aarch64-unknown-linux-muslcompile. The musl target
has not been leak-tested (stdlib not installed in the build sandbox).
Known issues
- Pending from the external §14 review (OPEN-POINTS): align DESIGN §4.5 with §7
on root-tmpfs renames; stop a continuous stream of spawns from starving a
hard revoke.
The leak suite (L1-L24) passed on Ubuntu 22.04 and 24.04 x86_64 CI runners for this exact commit before these binaries were built; the aarch64 binary is built and started () but has not been leak-tested. Binaries are dynamically linked against glibc and built natively on Ubuntu 22.04 (glibc 2.35 or newer required). Verify with sha256sum -c SHA256SUMS. See README, "Verifying a build".
hisn 0.2.0
First release.
Added
- Sandbox sessions (
hisn run): user, mount, pid, ipc, uts and net
namespaces; an empty tmpfs root entered withpivot_rootand the old root
detached; the working directory at the identical host path (rw), a read-only
toolchain, private tmpfs$HOME,/tmpand/run. Anything else does not
exist (ENOENT). - Confinement inside the sandbox: all capabilities dropped,
no_new_privs,
a seccomp denylist (mount,unshare,setns,open_tree,ptrace,bpf,
io_uring,clonewithCLONE_NEW*,clone3answered with ENOSYS, ...),
Landlock as a second write-restricting layer, a non-dumpable capless PID 1,
rlimits, and an environment rebuilt from scratch (env.passallowlist). - Grants (
hisn grant,hisn revoke,hisn ls,hisn inspect,
hisn kill): the host-side supervisor adds and removes bind mounts in a
running session through a control socket the sandbox cannot name or use.
Grants end with the session. Soft revoke guarantees only that the path no
longer resolves;revoke --hardkills every sandbox process. hisn exec(v0.2): run further commands in a running session, in the same
namespaces, with the same confinement and the same grants, optionally on a
pty (--tty, with resize).hisn run --detachstarts a session that is only
exec'd into. Confinement is re-applied on every exec (DESIGN §14).- Policy (
hisn-policy/v1TOML,hisn policy validate, JSON schema in
schemas/), including[landlock] scoped(below). hisn check: host capability report (kernel, user namespaces, Landlock,
seccomp, mount API); exit 2 when a required capability is missing.- Leak suite (
make leaktest, L1-L24): onePASS|FAIL|SKIP|WEAKline per
check and aRESULT:line; non-zero exit on any FAIL.make leaktest-log
writes a dated log with a header (commit, tree state, date, host, kernel,
hisn version).docs/VERIFICATION.txtrecords what was run, where. - Documentation: DESIGN, POLICY-SPEC, OPEN-POINTS, ROADMAP, WAKIL-INTEGRATION.
Changed
- Landlock
signalandabstract_unix_socketscoping is off by default
([landlock] scoped = []) so that one exec can signal processes started by an
earlier exec of the same session (OPEN-POINTS P17). The filesystem ruleset,
capabilities,no_new_privs, seccomp and environment scrubbing are
unchanged. Opt in withscoped = ["signal"]and/or
scoped = ["abstract_unix_socket"]. - With
network = "host"the abstract-unix-socket scope is always applied; only
--allow-host-abstract-socketsremoves it, and that is audited (found by the
external §14 review; already enforced, now covered by leak test L24).
Security
hisn execverifies fail-closed, before running anything, that the root it
entered is the sandbox root (DESIGN D25).
The leak suite (L1-L24) passed on Ubuntu 22.04 and 24.04 x86_64 CI runners for this exact commit before these binaries were built; the aarch64 binary is built and started () but has not been leak-tested. Binaries are dynamically linked against glibc and built natively on Ubuntu 22.04 (glibc 2.35 or newer required). Verify with sha256sum -c SHA256SUMS. See README, "Verifying a build".