Repository navigation
fix(remote-worker): bake a system git identity into the sandbox image - #463
Conversation
…rossoctl#415) An agent's first `git commit` in a sandbox failed with exit 128 ("Please tell me who you are") until it configured an identity itself -- observed live on the VM demo (docs/demos/vm-multi-user-demo.md, fix list 5), on both P4 sessions and on the container tier. Each workaround cost a tool call and a model round, in front of an audience. Fix both leaf images the way the issue proposes: a system gitconfig (/etc/gitconfig, user.name "MOCA sandbox" / user.email sandbox@moca.invalid), which applies to ANY uid the images can run as -- including the arbitrary gid-0 uid under nonroot-v2 that rossoctl#372's writable HOME prepares but cannot pre-configure -- and which a user's or agent's own higher-precedence config still overrides. The P4 rootfs inherits it for free, being exported from this image by build-rootfs.sh. Guards, per the issue: - research_tooling_test.go: a static pin of the exact RUN instruction (and its position before the USER drop), plus a runtime check that extracts the printf's format string, installs it as a scratch environment's ONLY git config, and runs a real no-prior-config commit -- so a gitconfig the line still prints but git silently ignores fails CI, not the next demo. - build-snapshot.sh: check_git_identity runs a real empty commit (with user.useConfigOnly=true, so an unconfigured git fails instead of auto-guessing) in the booted guest of both VMM arms, before quiescing -- a rootfs built from an image that lost its gitconfig now fails the snapshot build, not the demo. build-snapshot.test.sh pins the gate's contract (existence, both arms, probe->check->quiesce order, useConfigOnly, real commit, rossoctl#415 pointer). Verified: go test ./... in remote-worker (including mutation tests on the new guards: commented-out RUN, RUN-after-USER, and a typo'd section header all fail), hadolint clean on both Dockerfiles, shellcheck -S warning clean, build-snapshot.test.sh and build-rootfs.test.sh PASS. Co-Authored-By: Claude Code <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
ingpaolodettori-dev
left a comment
There was a problem hiding this comment.
Summary
This bakes a system /etc/gitconfig (MOCA sandbox <sandbox@moca.invalid>) into both leaf images, so an agent's first git commit no longer exits 128. It adds two guards: a Go static pin plus a runtime check, and a snapshot-build gate that runs a real commit with useConfigOnly in the booted guest. I checked that guest_client passes the guest command's exit code through (os.Exit(e.ExitCode)), so the gate does fail closed. I found no blocking issues. Two suggestions: (1) the gate's /tmp/id-check repo gets captured into the memory snapshot and so ships in every restored sandbox, and (2) the Go runtime test should pin useConfigOnly and assert the resulting identity, so it can't pass on git's auto-detected identity.
Author: pdettori (MEMBER — maintainer)
Areas reviewed: Dockerfile, Shell, Go tests, Security
Agent/IDE config (.claude/.vscode): none
Commits: 1 commit, all signed-off: yes
CI status: passing (14/14, including hadolint, shellcheck, microvm-gates, k8s-kind-e2e)
| local uds="$1" | ||
| local out rc | ||
| out="$("$STAGE/guest_client" -uds "$uds" -port 1024 -timeout-s 30 \ | ||
| -command 'git init -q /tmp/id-check && cd /tmp/id-check && git -c user.useConfigOnly=true commit --allow-empty -m x 2>&1' \ |
There was a problem hiding this comment.
suggestion — The repo this check creates ends up in every restored sandbox. check_git_identity runs before quiesce_guest and the snapshot. The snapshot captures guest RAM (mem_backend / memfile), and that includes the /tmp tmpfs. So every VM restored from this snapshot boots with /tmp/id-check, which holds a MOCA sandbox commit. The comment at L841 ("the throwaway repo leaves no trace") is only true for a cold boot, not for a snapshot restore.
You could remove it inside the same guest command and keep the commit's exit code:
-command 'git init -q /tmp/id-check && cd /tmp/id-check && git -c user.useConfigOnly=true commit --allow-empty -m x 2>&1; rc=$?; cd / && rm -rf /tmp/id-check; exit $rc'It's also worth a build-snapshot.test.sh check that pins the rm -rf /tmp/id-check, and a fix to the L841 comment.
| local out rc | ||
| out="$("$STAGE/guest_client" -uds "$uds" -port 1024 -timeout-s 30 \ | ||
| -command 'git init -q /tmp/id-check && cd /tmp/id-check && git -c user.useConfigOnly=true commit --allow-empty -m x 2>&1' \ | ||
| 2>/dev/null)" && rc=0 || rc=$? |
There was a problem hiding this comment.
nit — 2>/dev/null throws away guest_client's own diagnostics (dial or CONNECT failures, guest error: …). A transport failure then gets reported as "the guest git has no usable identity", with an empty Guest output:. The guest command already merges its own stderr with 2>&1, so you could capture host-side stderr as well (2>&1) to make the error message accurate in both cases.
| } | ||
| for _, args := range [][]string{ | ||
| {"init", "-q"}, | ||
| {"commit", "--allow-empty", "-m", "#415"}, |
There was a problem hiding this comment.
suggestion — This runtime check can pass without testing anything, which is the same failure mode the shell gate avoids with user.useConfigOnly=true. When git has no user.name/user.email, it doesn't fail outright. It falls back to an identity built from the passwd entry and the hostname, and it only refuses when that email isn't fully qualified. On a host whose hostname resolves to an FQDN (many CI runners and dev machines), a broken baked gitconfig (typo'd [user] header, literal \t) still commits. The test then passes, so it doesn't catch the drift its doc comment says it catches. Separately, GIT_CONFIG_SYSTEM needs git ≥ 2.32. Older git ignores it and reads the host's /etc/gitconfig.
Two changes would close both gaps:
{"-c", "user.useConfigOnly=true", "commit", "--allow-empty", "-m", "#415"},and then assert the identity that was actually used:
out, err := run("log", "-1", "--format=%an <%ae>")
// want "MOCA sandbox <sandbox@moca.invalid>"(Today the static systemGitconfig pin catches these mutations, but only because it pins the exact bytes. The runtime test should hold up by itself.)
…eck, stderr capture, vacuous-pass guard Three review findings on the rossoctl#415 gate: - check_git_identity's throwaway repo was captured in the snapshot: the check runs before quiesce_guest, and the snapshot captures guest RAM, /tmp's tmpfs included, so /tmp/id-check shipped in every restored sandbox. The repo is now removed INSIDE the same guest command, with the commit's exit code carried across the cleanup (rc=$?; ...; exit $rc) so the gate still fails closed. build-snapshot.test.sh pins both the rm and the exit-code carry, and the comment claiming "leaves no trace" is replaced with the accurate snapshot-RAM reasoning. - guest_client's host-side stderr was discarded (2>/dev/null), so a transport failure (dial, CONNECT refused) was misreported as "the guest git has no usable identity" over an empty Guest output line. Now captured (2>&1) and the error message says either could have failed. Test pins the absence of 2>/dev/null in the gate body. - TestTheBakedGitIdentitySatisfiesACommit could pass on git's auto-guessed identity: without user.useConfigOnly, an unconfigured git falls back to passwd/hostname and only refuses when the guessed email is not an FQDN -- on many CI runners a broken baked gitconfig still committed. The commit now runs with user.useConfigOnly=true (same as the shell gate) AND asserts the commit's author IS the baked identity, so no other identity can satisfy it. Doc comment also notes GIT_CONFIG_SYSTEM needs git >= 2.32. Verified: full go test ./... in remote-worker, build-snapshot.test.sh, build-rootfs.test.sh, shellcheck -S warning. Mutation tests: broken gitconfig fails the Go test with and without useConfigOnly (the identity assertion holds on its own), and removing the guest-side cleanup fails both new shell checks. Co-Authored-By: Claude Code <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…tagged fallback, rossoctl#463 identity - MI1 §6.6/§12.2: the 403 single_tenant_deployment message no longer offers MOCA_TENANCY=multi as the fix. Between S2 and S5 that is the posture §6.6 calls not honest, and nothing refuses it at startup, so the message makes multi conditional on per-user sandbox owner binding (S5 on the container tier) and cites §6.6; the S2 test pins that. - Both runbooks: if v0.5.1 is not tagged yet, check out rossoctl#465's merge commit, which it marks. - vm-multi-user-demo.md: fix-list row 5 reads Fixed (rossoctl#463), and 4d's run record says the failed first commit predates rossoctl#463. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
#407) S2's pin makes MOCA_TENANCY=single serve only the first subject, and multi is only honest once S5 binds each container sandbox to one owner. Decide the order: the two-user demo runs at v0.5.1 (tagged on this change's merge commit, the last release before S2) until S5, then on main under multi. v0.5.0 predates #463's sandbox git identity, which the multi-user demo relies on. - MI1 §6.6 records the sequencing and that setup-vm.sh adds no refusal of its own (it cannot tell how many people will log in); §10.3 points deploy/vm at it. - MI1 §12.2 requires an S2 test pinning 403 single_tenant_deployment, so the runbooks can quote it. - Both VM runbooks and deploy/vm/README.md state the tag and the refusal code. Fixes #407 Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
Fixes #415.
Problem
An agent's first
git commitin a sandbox failed with exit 128 ("Please tell me who you are") until it configured an identity itself — observed live on the VM demo (docs/demos/vm-multi-user-demo.md, fix list 5), on both P4 sessions and on the container tier. Each workaround cost a tool call and a model round, in front of an audience.Fix
A system gitconfig (
/etc/gitconfig:user.name = MOCA sandbox,user.email = sandbox@moca.invalid) baked into both leaf images (remote-worker/Dockerfile,Dockerfile.runtime), exactly as the issue proposes:HOMEprepares but cannot pre-configure.build-rootfs.sh.Guards (per the issue)
research_tooling_test.go:RUN printfinstruction in both files, and its position before theUSER 1001drop;\\t) fails CI, not the next demo.build-snapshot.sh:check_git_identityruns a real empty commit (withuser.useConfigOnly=true, so an unconfigured git fails instead of auto-guessing an identity) in the booted guest of both VMM arms, afterprobe_capabilities, beforequiesce_guest. A rootfs built from an image that lost its gitconfig now fails the snapshot build at build time.build-snapshot.test.shpins the gate's contract (existence, both arms, probe→check→quiesce order,useConfigOnly, real commit).Verification
go test ./...in remote-worker passes; the new guards were mutation-tested (commented-out RUN, RUN-after-USER, typo'd section header each fail the respective test).shellcheck -S warningclean.build-snapshot.test.sh209 ok / 0 fail;build-rootfs.test.shPASS.🤖 Assisted-By: Claude Code