trailofbits / trailofbits/coop
Profile install scripts run before the guest user is created
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 243
- Forks
- 13
- Avg merge
- 1d 20h
- Merged PRs (30d)
- 30
Description
Summary
Profile pre_install / post_install scripts run before the guest user is created, so a profile that writes into the guest user's home directory (e.g. installing dotfiles) either fails or writes into the wrong place. The files land in the base CI rootfs's leftover /home/ubuntu, which is then orphaned when guest-config.sh replaces the uid-1000 user.
Root cause
compose_recipe in src/setup.rs assembles the chroot provisioning script in this order:
- preamble +
export GUEST_USER=... - base packages
- profile pre-install scripts (
src/setup.rs:664) - third-party repos + packages
- profile post-install scripts (
src/setup.rs:704) guest-config.sh— this is where the guest user is created (src/setup.rs:720, script atscripts/guest/guest-config.sh:61-95)- claude-code / codex
So at steps 3 and 5 the guest user and its /home/<user> do not exist yet. The comment at src/setup.rs:719 shows the placement was only chosen to be "before claude-code" — profile scripts that depend on the user were never accounted for.
Symptoms
Two users / home directories are visible in the guest:
<guest_user>//home/<guest_user>— the configured guest user, created byguest-config.shat step 6.ubuntu//home/ubuntu— ships with the base CI rootfs image (not created by coop at launch).
When a custom guest user is configured, guest-config.sh evicts the pre-existing uid-1000 user:
# scripts/guest/guest-config.sh:69-75
EXISTING=$(getent passwd 1000 | cut -d: -f1) || true
if [[ -n "$EXISTING" ]]; then
userdel "$EXISTING" # no -r → /home/ubuntu is left on disk
fi
useradd -m -s /bin/bash --uid 1000 -G sudo,docker "${GUEST_USER}"
Two leaks:
- Only the uid-1000 occupant is removed. If the base image's
ubuntuis not at uid 1000, it survives as a full account alongside<guest_user>. userdelis called without-r, so/home/ubuntuis left behind as an orphan even when the account is removed.
A profile installing dotfiles at step 5 finds no /home/<guest_user>. If it falls back to (or hardcodes) /home/ubuntu, the files land there — and are then stranded when step 6 deletes/replaces the uid-1000 user, leaving the real guest home empty.
Fix
Create the guest user before profile scripts run. guest-config.sh cannot simply be moved to the top, because it also symlinks the claude binary that is not installed until step 7. Split it instead:
- Extract the user + home creation block (
scripts/guest/guest-config.sh:61-95:useradd/usermod, sudoers,/home/<user>ownership,.localtree,.sshkeys) into its own script segment. - Run that segment in
compose_reciperight after base packages and beforepre_installs(src/setup.rs:663). - Leave the rest of
guest-config.sh(networking, docker daemon, claude symlink, services, sshd) where it is atsrc/setup.rs:720. - Add
-rto theuserdelso the orphan/home/ubuntudoes not linger. - Add a unit test asserting the
useraddline precedes profile pre/post-install in the composed recipe (guards against regressing the ordering).
Acceptance criteria
- A profile
post_install(orpre_install) script can write to/home/<guest_user>and have it persist. - No orphaned
/home/ubunturemains after provisioning with a custom guest user. - A test pins the ordering: guest-user creation before profile scripts.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in src/setup.rs at compose_recipe and inspect the guest-config.sh user-creation block at scripts/guest/guest-config.sh:61-95. Trace how the recipe places pre_install, post_install, and guest-config steps, then review the existing unit-test patterns before adding an ordering assertion. Done means profile scripts can persist files in /home/<guest_user>, no orphaned /home/ubuntu remains, and the ordering test passes.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust, shell
- Domain
- cli, infrastructure
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 72/100