Skip to content

feat(minimald): ship an AppArmor profile granting minimald userns - #740

Merged
norrietaylor merged 18 commits into
mainfrom
feat/apparmor-profile-minimald
Jul 16, 2026
Merged

feat(minimald): ship an AppArmor profile granting minimald userns#740
norrietaylor merged 18 commits into
mainfrom
feat/apparmor-profile-minimald

Conversation

@norrietaylor

@norrietaylor norrietaylor commented Jul 13, 2026

Copy link
Copy Markdown
Member

Closes #610 via option 1 — ship an AppArmor profile for minimald granting userns, — and plumb it into the curl … | sh installer so a stock Ubuntu 24.04+ user is told exactly what to run, at install time, without a checkout.

Why

Ubuntu 24.04+ defaults kernel.apparmor_restrict_unprivileged_userns=1, which bars unconfined programs from creating unprivileged user namespaces. Every minimald sandbox is one, so on a stock 24.04 host no session can start — the child dies writing /proc/self/uid_map with EPERM, surfacing only as an opaque DIAG in the daemon log.

The profile is the accommodation Ubuntu intends, and is the same shape the distro ships for rootlesskit, runc and podman: flags=(unconfined) — it confines nothing — existing only to give minimald a named label that can carry the userns permission. Unlike setting the sysctl to 0, it does not hand user namespaces back to every other program on the host.

What the user experiences during install

The profile is delivered and surfaced by the installer itself — no checkout required, and the installer never asks for sudo.

On any hostcurl … | sh downloads the binaries and, on Linux, three extra files (the profile, its tunable, and the loader) into ~/.local/share/minimal/apparmor/. The install finishes normally.

On a restricted host (stock Ubuntu 24.04+) — after placing the files, the installer reads kernel.apparmor_restrict_unprivileged_userns. If it is 1 — the one case where sessions would otherwise die at uid_map — it ends with a plain-language note, instead of leaving the user to discover the failure later at first attach:

note: this host restricts unprivileged user namespaces (Ubuntu 24.04+);
  minimald's session sandbox cannot start until you install its AppArmor
  profile — a one-time step that needs root:
      sudo bash ~/.local/share/minimal/apparmor/install-apparmor-profile.sh
  details: https://github.com/gominimal/minimal/blob/main/docs/reference/linux-host-setup.md

The install still exits 0; the user runs that one root command when they choose to. That command is the only place a password is asked for.

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features
    • Added AppArmor profile and tunables for minimald to support unprivileged user namespaces on Ubuntu 24.04+.
    • Added an AppArmor profile installer with install, uninstall, and profile validation.
    • Added a Linux startup preflight warning when host user-namespace support is blocked.
    • Daemon data directory now respects an absolute XDG_DATA_HOME.
  • Documentation
    • Updated Ubuntu 24.04+ host setup guidance with remediation steps, including --path usage and a sysctl workaround.
  • Bug Fixes
    • Improved cleanup/uninstall and corrected nested subpath installation handling.
  • Tests
    • Expanded CI and end-to-end tests to validate AppArmor components and parsing.

Ubuntu 24.04+ defaults kernel.apparmor_restrict_unprivileged_userns=1,
which bars unconfined programs from creating unprivileged user
namespaces. Every minimald session is one (hakoniwa forks and the child
writes /proc/self/uid_map), so on a stock 24.04 host no session can
start: the child dies with EPERM before it runs, surfacing only as an
opaque uid_map DIAG in the daemon log.

Ship the accommodation Ubuntu intends, the same shape the distro ships
for rootlesskit, runc and podman: an unconfined-mode profile whose sole
purpose is to give minimald a named label carrying the `userns`
permission. It confines nothing, and unlike setting the sysctl to 0 it
does not hand user namespaces back to every other program on the host.

minimald has no single install path (the installer puts it in
${MINIMAL_BIN:-~/.local/bin}, a package would use /usr/bin), and AppArmor
attaches profiles by executable path, so the attachment set lives in a
tunable; --path extends it for dev builds via tunables/minimald.d.

Verified on Ubuntu with the restriction on: without the profile an
interactive attach yields no sandbox shell; with it loaded the daemon
runs as `minimald (unconfined)` and the shell comes up in its own user
namespace. Unconfined processes remain unable to unshare.

Refs: #610
@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 48 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fd8e0779-3720-4028-8746-d1454e2dbb52

📥 Commits

Reviewing files that changed from the base of the PR and between 85c07a7 and 2eb3cea.

📒 Files selected for processing (5)
  • crates/paths/src/lib.rs
  • scripts/install.sh
  • scripts/install_test.sh
  • scripts/session-e2e.sh
  • scripts/stage-release.sh
📝 Walkthrough

Walkthrough

Adds an AppArmor profile and installer for minimald user namespaces, detects host restrictions, packages and tests the artifacts, documents Ubuntu 24.04+ setup, integrates uninstall handling, and extends CI validation.

Changes

AppArmor support

Layer / File(s) Summary
User-namespace restriction detection
Cargo.toml, crates/sandbox2/src/lib.rs, crates/minimald/src/main.rs
Detects Linux user-namespace restrictions and logs startup remediation guidance.
AppArmor profile and tunables
packaging/apparmor/*
Defines the unconfined minimald profile with userns permission and configurable executable paths.
Profile installation lifecycle
scripts/install-apparmor-profile.sh
Adds check, install, custom-path, replacement, status, and uninstall modes.
Installer distribution and lifecycle integration
scripts/stage-release.sh, scripts/install.sh
Stages AppArmor artifacts, supports nested destinations, warns about restrictions, and integrates optional profile removal.
Ubuntu host guidance and installer tests
README.md, docs/reference/linux-host-setup.md, scripts/install_test.sh
Documents the restriction and remediation options, and tests installation, advisories, platform filtering, and uninstall behavior.
CI profile validation and native lane updates
.github/workflows/ci-shell-installer.yml, .github/workflows/ci-linux-native.yml
Adds AppArmor change detection, ShellCheck, parser checks, success-gate integration, and removes unnecessary user-namespace setup.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Administrator
  participant Installer
  participant AppArmor
  participant minimald
  participant sandbox2
  Administrator->>Installer: Install bundled AppArmor profile
  Installer->>AppArmor: Copy tunables and profile
  Installer->>AppArmor: Load profile with apparmor_parser
  minimald->>sandbox2: Check user namespace restriction
  sandbox2->>AppArmor: Inspect host restriction and label
  AppArmor-->>sandbox2: Return restriction state
  sandbox2-->>minimald: Provide remediation result
Loading

Possibly related issues

  • #610 — Directly addresses Ubuntu 24.04+ AppArmor restrictions blocking minimald user namespaces.
  • #687 — Relates to the expanded shell-installer CI filtering and success-gate changes.

Possibly related PRs

  • gominimal/minimal#768 — Also changes scripts/install.sh behavior around stopping a running daemon during installation.

Suggested reviewers: twitchyliquid64

Poem

A bunny hops where profiles lie,
And lets user namespaces fly.
Parser checks guard every trail,
Ubuntu hosts now know the tale.
Install, detect, and hop away!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly states the main change: shipping an AppArmor profile for minimald user namespace access.
Linked Issues check ✅ Passed The PR ships the profile, adds installer support, and warns/docs the Ubuntu 24.04 restriction as requested in #610.
Out of Scope Changes check ✅ Passed The workflow, tests, docs, and runtime checks all support the AppArmor/userns accommodation and stay within scope.

Comment @coderabbitai help to get the list of available commands.

@norrietaylor
norrietaylor marked this pull request as draft July 14, 2026 00:30
norrietaylor and others added 2 commits July 13, 2026 17:31
shellcheck SC2155: `readonly VAR=$(cmd)` masks the command's exit
status, so a missing packaging/apparmor dir would go unnoticed instead
of failing the script.
@norrietaylor
norrietaylor marked this pull request as ready for review July 14, 2026 19:48
norrietaylor and others added 4 commits July 14, 2026 15:10
native-daemon-e2e lifted Ubuntu's unprivileged-userns restriction
(`sysctl ...=0`) on the theory that the session sandbox needs it. It does
not: session-e2e.sh drives activate/list/destroy/stop only and never
forks a session sandbox, so the job needs no user namespace — verified by
running the e2e green with the restriction left on. Remove the step and
leave a comment so it is not re-added; correct the job header, which
wrongly claimed the e2e execs inside the sandbox (it does not — that path
is proven in Rust, per session-e2e.sh).

Sandbox-forking coverage that genuinely needs userns stays put: the
workspace tests and the minimald-root-integration job (whose own sysctl
step is unaffected — its userns comes from the cargo test binary, not
minimald).

Refs: #610
The curl|sh installer had no path to the AppArmor profile that lets
minimald create the unprivileged user namespace every session sandbox
needs: the profile and its loader lived only in the source tree, so
`sudo scripts/install-apparmor-profile.sh` was a dead reference for
anyone who installed via curl|sh. On stock Ubuntu 24.04+ that left every
session dying at uid_map with an opaque EPERM and no in-product hint.

Plumb it in without ever elevating from the installer:

- stage-release.sh ships packaging/apparmor/{minimald,tunables/minimald}
  and install-apparmor-profile.sh as data-prefix components, installed to
  ~/.local/share/minimal/apparmor/.
- install.sh prints a gated, Linux-only advisory when it detects
  apparmor_restrict_unprivileged_userns=1, pointing at the shipped loader
  (sudo bash ...). Advice only; it never runs sudo.
- On --uninstall, offer to remove the system profile too: prompt on a
  terminal, advise the root command otherwise, run before the record walk
  deletes the loader. Never touches /etc/apparmor.d unattended.
- install-apparmor-profile.sh resolves its source dir from either the
  checkout layout or the shipped-flat layout.
- The install loop now creates each component's parent dir, so a
  component whose subpath nests directories installs correctly.

install_test.sh covers placement, the advisory on/off, darwin-skip, and
the uninstall advisory (no elevation, loader removed).

Refs: #610

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.github/workflows/ci-linux-native.yml (1)

113-135: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Require CODEOWNER approval for this workflow change. .github/workflows/ci-linux-native.yml is frozen and CODEOWNER-gated, so this userns setup removal should go through the workflow owners or be reverted.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci-linux-native.yml around lines 113 - 135, Restore the
user namespace setup in the native CI workflow, or obtain and preserve the
required CODEOWNER approval for its removal. Update the workflow section
associated with the native E2E job and remove the comment asserting that userns
setup is intentionally unnecessary unless the approved change is retained.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/reference/linux-host-setup.md`:
- Around line 52-54: Remove the `$` shell prompt from the command-only console
block in the Linux host setup documentation, leaving the command itself
unchanged and without adding output.

In `@scripts/install.sh`:
- Around line 805-816: Update the Linux user-namespace advisory block around
userns_sysctl and apparmor_loader to query the bundled AppArmor loader’s
check/status mode before printing the warning. Only emit the existing messages
when the profile is not already active, while preserving the current sysctl,
loader existence, and message conditions.

---

Outside diff comments:
In @.github/workflows/ci-linux-native.yml:
- Around line 113-135: Restore the user namespace setup in the native CI
workflow, or obtain and preserve the required CODEOWNER approval for its
removal. Update the workflow section associated with the native E2E job and
remove the comment asserting that userns setup is intentionally unnecessary
unless the approved change is retained.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a259da7c-9983-45ce-b1a1-18bf9c2c6b3c

📥 Commits

Reviewing files that changed from the base of the PR and between 5008f4c and 463e1ec.

📒 Files selected for processing (7)
  • .github/workflows/ci-linux-native.yml
  • README.md
  • docs/reference/linux-host-setup.md
  • scripts/install-apparmor-profile.sh
  • scripts/install.sh
  • scripts/install_test.sh
  • scripts/stage-release.sh
🚧 Files skipped from review as they are similar to previous changes (2)
  • README.md
  • scripts/install-apparmor-profile.sh

Comment thread docs/reference/linux-host-setup.md
Comment thread scripts/install.sh
norrietaylor and others added 11 commits July 15, 2026 11:19
The install-time advisory checked only the sysctl and the shipped
loader, so a reinstall on an already-remediated host still claimed the
sandbox "cannot start" — the profile is loaded and sessions work, yet
the sysctl stays 1. Gate the advisory on the absence of the installed
profile at /etc/apparmor.d/minimald (via the same MINIMAL_OVERRIDE_-
APPARMOR_DIR seam the uninstall path uses), so it fires only on a host
that is restricted and not yet remediated.

Also show the loader's output in the curl-installed docs example, so the
command block carries output like its sibling (satisfies markdownlint
MD014).

Both raised by CodeRabbit on #740.
…tartup

Every session sandbox starts by unsharing an unprivileged user
namespace, forked from the daemon with no exec in between. On a host
that refuses it (stock Ubuntu 24.04+ with an unconfined daemon, or user
namespaces disabled outright) the denial previously surfaced only when
the first attach died writing /proc/self/uid_map, with nothing useful
in the daemon log.

sandbox2 gains user_namespaces_restriction() — a read-only probe of the
userns quota, the apparmor_restrict_unprivileged_userns sysctl, euid,
and the process's own AppArmor label, mirroring the crate's existing
net-namespace probe — and minimald warns at startup naming the
restriction and the exact fix. Root (the in-guest microVM pid-1) and
confined daemons stay silent, so the common case logs nothing.

Refs: #610

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The reference docs ship to https://docs.minimal.dev/ with this release,
so the advisory's details link should point there rather than at a
GitHub blob URL. The build-specs link to minimal.ncl stays on GitHub:
it targets source, not a hosted doc page.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… running binary

The startup warning hardcoded ~/.local/share for the shipped loader, so
the advised command was wrong whenever XDG_DATA_HOME was set, and it
omitted --path, so a daemon outside the tunable's stock attachment set
(a custom MINIMAL_BIN, a dev build) was told to load a profile that
would never attach to it. paths gains minimal_data_dir(), resolving
exactly like the installer's data prefix, and the fix string now carries
--path <current_exe>.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The parse loop consumes $@ before the root check, so the hinted
`sudo $0 $*` always printed bare — a copy-pasted retry of
--uninstall would install, and one of --path would skip the attachment.
Capture the original argv up front and print that.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… only for bin/lib swaps

Two review findings against the advisory and the R5.5 stop:

- The stock tunable attaches only /usr/bin, /usr/local/bin, and
  ~/.local/bin, so for a custom MINIMAL_BIN the advised command now
  carries --path "$bindir/minimald", and a loaded profile counts as
  remediation only when the tunables actually name that binary —
  previously the advisory suppressed itself while sessions stayed
  broken.
- stop_running_daemon now fires only for bin/lib component swaps:
  replacing a data file (the shipped apparmor text) must not kill live
  sessions to swap a file the running daemon does not serve from.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nothing

stage_repo_file wrote the three apparmor files into ARTIFACTS_DIR before
the dry-run branch, mutating a directory the caller owns (and dying
outright when it is read-only). Stage into the ephemeral workdir and let
the manifest loop prefer staged files over same-named artifacts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ox proof

The native-e2e lane dropped its userns sysctl step on the rationale that
session-e2e.sh forks no sandbox — true when written, falsified by the
in-sandbox `min add` proof (#760) merged since: on a restricted runner
the proof's sandbox dies at uid_map and surfaces as the misleading
'jq was already present' error (the absent-marker never prints).

Load the shipped profile from the script itself, attached to the
minimald the run will spawn — dogfooding the documented remediation on
CI and on any restricted host running the e2e locally. Native Linux
only: VM lanes create their userns in-guest as root.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@norrietaylor
norrietaylor force-pushed the feat/apparmor-profile-minimald branch from 060875b to 2eb3cea Compare July 16, 2026 00:10
@norrietaylor
norrietaylor merged commit ca23aeb into main Jul 16, 2026
30 checks passed
@norrietaylor
norrietaylor deleted the feat/apparmor-profile-minimald branch July 16, 2026 00:43
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.

DM2: apparmor_restrict_unprivileged_userns=1 (Ubuntu 24.04 default) blocks rootless sandbox uid_map

2 participants