Commit Graph
2 Commits
Author SHA1 Message Date
jschoubben aca9eb37ff An owner may be a number the machine has never heard of
A directory a module mounts into its container belongs to whoever runs
inside — grafana's 472, redis's 999, www-data's 33 — and none of those
has a row in the machine's passwd. Owner-by-name refused them all,
which looked principled and meant every module whose container drops
privileges could not own its own data.

The lab showed both coats of it in one run: the store's config file was
unreadable to the store, restarting forever on permission denied, and
the forge could not traverse into the 0700 root-owned directory that
held its files — a directory that had only become root-owned when
declaring it fixed 04-ISSUES/026, because Docker used to create it
0755. A fix that tightens ownership without a way to say whose it
should be moves the fault, not removes it.

"uid:gid" and bare "uid" are numeric and chowned as given; a name still
resolves as before, and a name with a colon is refused rather than
half-read.
2026-09-01 23:45:02 +02:00
jschoubben c57087d75d A user, bytes, and an archive — because most of what people install is
not a service

A shell, a terminal, a chat client, a desktop are a package plus
configuration in somebody's home. A mesh with no notion of a user can own
/etc and nothing anybody looks at, which is most of the reason to manage
a machine at all.

Three shapes, and the vocabulary test asserts the count precisely because
widening it widens what a compromised control plane can express:

  user     a login, its shell and its groups
  archive  a set of files, fetched by digest and unpacked
  (file)   gains `bytes` for what is not text, and `owner`

`user` also makes "zsh is my login shell" declared state. chsh is a
command, the link may not carry one, and a shell settable only by hand is
a shell the mesh cannot manage.

Groups are additive and never pruned — usermod without --append REPLACES
them, which would silently remove every group that makes a login able to
use the machine. A machine's own groups are not the mesh's to know about.

The archive is the one place this host reaches out on its own; everywhere
else it holds one outbound connection and fetches nothing. So it carries
the discipline the bootstrap already uses for images: pinned by digest,
and the digest checked before a single file is written.

Two decisions in the unpacker worth naming:

- an entry naming a path outside the archive is REFUSED, not sanitised.
  Rewriting it to land inside would put a file somewhere nobody asked for
  and report success. Found by the test: the first version quietly
  relocated it.
- symlinks and device nodes are refused rather than skipped, or an
  archive that needed one arrives silently incomplete.

A partial host does archives and refuses users: an archive needs a
filesystem and a way to fetch; a user needs a user database it is allowed
to write.
2026-08-30 03:22:38 +02:00