Skip to content

fix: rootless containerd UID-remap fixes for /tmp/.cache and /home/leerie#85

Open
up_the_irons wants to merge 2 commits into
enricai:mainfrom
arpnetworks:fix-rootless-home-leerie-ownership
Open

fix: rootless containerd UID-remap fixes for /tmp/.cache and /home/leerie#85
up_the_irons wants to merge 2 commits into
enricai:mainfrom
arpnetworks:fix-rootless-home-leerie-ownership

Conversation

@up_the_irons

@up_the_irons up_the_irons commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Summary

Two fixes for the same root cause, bundled together since the second
directly builds on the first's code and docs:

1. /tmp/.cache world-writable (cherry-picked from arpnetworks/leerie, not yet upstream)

  • Under rootless containerd, container-entry.sh's privilege drop (unshare --user --map-user=$(id -u leerie)) remaps only outer UID 0 to inner leerie. /tmp/.cache was chowned to leerie's own non-zero UID, which isn't covered by that remap — broke corepack and tree-sitter-language-pack's download cache with EACCES.
  • Fix: make /tmp/.cache world-writable with the sticky bit (mirroring /tmp's own drwxrwxrwt), at both the Dockerfile and container-entry.sh's rootful-path runtime safety net.

2. /home/leerie/.local, .cache, .gnupg ownership (new)

  • Same bug class, hit directly this time: pip install --user failing with EACCES: /home/leerie/.local/lib.
  • Unlike /tmp/.cache (an arbitrary-tool XDG_CACHE_HOME), these are a fixed, known set of pre-created dirs, so the fix is more surgical: leave them root-owned at build time (outer UID 0 is the one value the remap does translate correctly — the same mechanism that already makes bind-mounted host dirs writable with no chown), and chown them to leerie at runtime in container-entry.sh's existing rootful-only guard instead.
  • Also adds /home/leerie/.local/bin to the image's PATH, so pip install --user-installed console scripts (e.g. pre-commit) have a working fallback when a git hook's embedded interpreter path doesn't resolve to a Python that has the package installed.

Test plan

  • tests/test_tmp_cache_writable.py and tests/test_home_leerie_ownership.py — source-coupling tests pinning both Dockerfile and container-entry.sh sides.
  • pytest tests/ passes.
  • Verified live against a real rootless containerd run: .leerie-setup.sh doing pip install --break-system-packages --user pre-commit failed with EACCES on the pre-fix image and succeeded after rebuilding with this fix.
  • docs/IMPLEMENTATION.md and CLAUDE.md updated for both fixes.

🤖 Generated with Claude Code

up_the_irons and others added 2 commits July 19, 2026 04:28
Under rootless containerd, container-entry.sh's privilege drop
(`unshare --user --map-user=$(id -u leerie)`) remaps only outer UID 0 to
inner leerie. A directory explicitly chowned to leerie's own non-zero UID
— as /tmp/.cache was, for mise's build-time cache — is not covered by that
remap and appears owned by nobody to the privilege-dropped process:
traversable via mode-755 "other" bits, but not writable. This silently
broke any tool that tries to create its own subdir under XDG_CACHE_HOME
(observed live for tree-sitter-language-pack's download-cache lock,
following the corepack incident that motivated COREPACK_HOME's own
dedicated cache dir).

Chasing each offending tool down individually doesn't scale, so this
makes /tmp/.cache itself world-writable with the sticky bit — the same
posture /tmp itself already has — at both the Dockerfile (build time,
the layer rootless relies on exclusively) and container-entry.sh (a
runtime safety net for the rootful/Fly path). Verified live: the exact
`unshare --user --map-user=...` mechanism reproduces the reported
`mkdir: Permission denied` against the pre-fix image and succeeds against
the fixed one, including the real tree_sitter_language_pack probe.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ootless

Same bug class as the /tmp/.cache fix (c7b11fb), hit directly this time:
pip install --user failing with EACCES: /home/leerie/.local/lib. Under
rootless containerd, container-entry.sh's privilege drop
(`unshare --user --map-user=$(id -u leerie)`) remaps only outer UID 0 to
inner leerie. The Dockerfile chowned /home/leerie, .local, .cache, and
.gnupg to leerie's own non-zero UID at build time — not covered by that
remap, so they appeared owned by nobody to the privilege-dropped process:
traversable, not writable.

Unlike /tmp/.cache (an arbitrary-tool XDG_CACHE_HOME, fixed by making it
world-writable), these are a fixed, known set of pre-created dirs, so the
fix is more surgical: leave them root-owned at build time — outer UID 0 is
the one value the remap does translate correctly, the same mechanism that
already makes bind-mounted host dirs writable with no chown at all — and
chown them to leerie at runtime in container-entry.sh's existing rootful-
only guard instead, since the rootful `runuser -u leerie` drop is a real
UID switch with no remap to rely on.

Also adds /home/leerie/.local/bin to the image's PATH. Console scripts
installed there via `pip install --user` (e.g. pre-commit) otherwise have
no fallback path when a git hook's embedded interpreter path doesn't
resolve to a Python that has the package installed (e.g. a host-side
commit against a hook generated inside the container, or vice versa).

tests/test_home_leerie_ownership.py pins both sides source-coupled
(mirroring test_tmp_cache_writable.py), including a guard that the
/tmp/.cache fix is unaffected. Docs updated: IMPLEMENTATION.md's
container-entry.sh row and the two Selenium-cache mentions that
previously described the old chown-at-build-time behavior.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@up_the_irons
up_the_irons force-pushed the fix-rootless-home-leerie-ownership branch from 26f0e86 to 1e80898 Compare July 19, 2026 11:29
@up_the_irons up_the_irons changed the title fix: don't chown /home/leerie subdirs to leerie's literal UID under rootless fix: rootless containerd UID-remap fixes for /tmp/.cache and /home/leerie Jul 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant