Skip to content

feat: add lake-check-sandbox to make the sandbox startable - #191

Open
kim-em wants to merge 1 commit into
feat/lake-checkfrom
feat/lake-check-sandbox
Open

kim-em wants to merge 1 commit into
feat/lake-checkfrom
feat/lake-check-sandbox

Conversation

@kim-em

@kim-em kim-em commented Sep 23, 2026

Copy link
Copy Markdown
Collaborator

This PR adds a lake-check-sandbox input choosing how to make lake check's bubblewrap sandbox able to start on a runner that will not otherwise permit it. It stacks on #190 — feat: add lake-check input for sandboxed independent kernel checks, and should be reviewed after it.

GitHub-hosted runners restrict the unprivileged user namespaces bubblewrap needs, so lake-check fails there until something grants them. Granting them means changing the runner, which lean-action will not do uninvited, so the input takes "none" (the default, change nothing), "apparmor", "sysctl" or "setuid". Whichever is chosen is applied only when the sandbox has been probed and found not to work, so a runner that already permits user namespaces is left untouched.

"apparmor" is the narrowest and the documented choice. Ubuntu's restriction denies unprivileged user namespaces to programs whose AppArmor profile does not grant userns, so a profile covering bubblewrap leaves the restriction in force for every other program, where "sysctl" relaxes it runner-wide and "setuid" leaves a setuid binary behind. Measured on ubuntu-latest: all three make the sandbox work, and under "apparmor" and "setuid" the sysctl stays at 1.

Because these operations are privileged, they act only on a root-owned distribution bubblewrap at /usr/bin/bwrap or /bin/bwrap, never on a symlink, and never on an executable named by COMPARATOR_BWRAP. Without those guards, PATH or that variable would decide which file gets made setuid root or gains an AppArmor policy. Refusing a non-root owner also matters in its own right: chmod u+s on a file owned by the job user grants that user's privileges rather than root's, so it would leave the sandbox broken while appearing to have worked.

None of these changes is undone afterwards. The warnings and the documentation say so, because on a persistent self-hosted runner they outlive the job, and such runners are better configured once by their administrator with this left at "none".

The sandbox probe now exercises the namespace and mount operations Lake itself uses rather than a bare bind, since a runner can permit --ro-bind and still refuse the sandbox Lake actually builds.

A functional test runs paranoid on ubuntu-latest under "apparmor", so the option the documentation recommends is the one CI exercises.

🤖 Prepared with Claude+Codex

GitHub-hosted runners restrict the unprivileged user namespaces bubblewrap
needs, so `lake-check` cannot run there until something grants them. Making
that change means changing the runner, so `lean-action` does not do it
uninvited: `lake-check-sandbox` takes "none" (the default), "apparmor",
"sysctl" or "setuid", and the chosen setup is applied only when the sandbox is
probed and found not to work.

"apparmor" is the narrowest and the documented choice. Ubuntu's restriction
denies unprivileged user namespaces to programs whose AppArmor profile does not
grant `userns`, so a profile covering bubblewrap leaves the restriction in
force for everything else, where "sysctl" relaxes it for every program on the
runner and "setuid" leaves a setuid binary behind.

The privileged operations act only on a root-owned distribution bubblewrap at
/usr/bin/bwrap or /bin/bwrap, never on a symlink and never on one named by
COMPARATOR_BWRAP: otherwise PATH or that variable would decide which file gets
made setuid root or gains a policy. `chmod u+s` on a file owned by the job user
grants that user's privileges rather than root's, so refusing a non-root owner
also avoids a change that would look like it had worked.

None of these are restored afterwards, which the warnings and the documentation
say plainly, because on a persistent self-hosted runner they outlive the job.

The probe now exercises the namespace and mount operations Lake itself uses
rather than a bare bind, since a runner can permit `--ro-bind` and still refuse
the sandbox Lake actually builds.
@austinletson
austinletson added this pull request to stack #200 October 12, 2026 17:35
@austinletson

Copy link
Copy Markdown
Collaborator

I'd propose leaving the sandbox setup to users rather than doing it in the action. As far as I know, it's unusual for an action to relax a runner's security restrictions, and doing so here means lean-action takes on ~90 lines of privileged code that has to keep up with runner images, when one line in the user's workflow does the same thing transparently. Since lake-check is opt-in (it's not in the workflow lake new generates), I think it's reasonable to ask users to add the snippet from #190's README, as long as the README makes this very clear.

What do you think?

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.

2 participants