fix(claude): preserve source file mode in marketplace tar#363
Merged
Conversation
abezzub-dr
added a commit
to abezzub-dr/moat
that referenced
this pull request
May 28, 2026
abezzub-dr
added a commit
to abezzub-dr/moat
that referenced
this pull request
May 28, 2026
Two follow-ups from review of majorcontext#363: Skip non-regular files (symlinks, devices, sockets) during the walk. Previously, os.ReadFile followed committed symlinks and copied the target's content into the tar under the symlink's name with the symlink's 0777 mode bits — a malicious marketplace could ship "bin/foo -> /etc/passwd" and end up with a world-writable, executable copy of the target inside the container. Skip the "." root entry. The destination directory is created by "mkdir -p" in the Dockerfile before "tar xf", so emitting a "./" header only chmod'd the destination to the host temp-clone-dir mode (0700 from os.MkdirTemp) on extraction.
The host-side marketplace tar in CollectMarketplaceTar wrote every file header with a hardcoded Mode: 0644, so executable hook scripts (e.g. bin/aw-hook, scripts/on-prompt-submit.sh) extracted at 0644 in the container and failed with "Permission denied" on UserPromptSubmit hooks. The fix derives Mode from info.Mode().Perm(), which is already in scope. Directory entries follow the same pattern for consistency. Fixes majorcontext#350.
Preserving info.Mode().Perm() literally narrows the safety margin against restrictive host umasks: a host with umask 0077 lays an upstream 100755 script down as 0700, the tar carries 0700, and BuildKit extracts it as root inside the build — leaving moatuser unable to read or execute the file. Previously the hardcoded 0644/0755 protected against this by accident. Mirroring owner bits to group and other preserves the executable preservation intent while keeping files usable for the non-root container user. Also asserts the bin/ directory is traversable in the existing mode-preservation test.
The Dockerfile sets USER ${containerUser} before COPY + tar xf
(dockerfile.go:187, 207-208), so the tar is extracted by the agent
user — not by root as the previous comment claimed. Owner bits alone
are sufficient; mirroring them into group and other only widened the
mode (0o755 → 0o777, 0o644 → 0o666) without unlocking any real access.
Reverts to plain int64(info.Mode().Perm()) and notes that .Perm()
intentionally drops setuid/setgid/sticky so marketplaces cannot smuggle
those bits into the image. Removes the umask test that asserted a
problem that does not exist in this code path.
Two follow-ups from review of majorcontext#363: Skip non-regular files (symlinks, devices, sockets) during the walk. Previously, os.ReadFile followed committed symlinks and copied the target's content into the tar under the symlink's name with the symlink's 0777 mode bits — a malicious marketplace could ship "bin/foo -> /etc/passwd" and end up with a world-writable, executable copy of the target inside the container. Skip the "." root entry. The destination directory is created by "mkdir -p" in the Dockerfile before "tar xf", so emitting a "./" header only chmod'd the destination to the host temp-clone-dir mode (0700 from os.MkdirTemp) on extraction.
17b9074 to
ef177d3
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
CollectMarketplaceTarwas writing every file's tar header with a hardcodedMode: 0644, so files extracted in the container always landed as-rw-r--r--regardless of the upstream git mode. Executable hook scripts (e.g.bin/aw-hook,scripts/on-prompt-submit.sh) then failed at runtime withPermission denied.Modefrominfo.Mode().Perm()(theinfovalue was already in scope fromd.Info()).TestCollectMarketplaceTarPreservesFileModecovering an executable and a non-executable file through the tar round-trip.Fixes #350.
Test plan
go test ./internal/providers/claude/— all marketplace tests pass, including the new mode-preservation test.make lint— clean.warpdotdev/claude-code-warp) and confirmls -lashows+xandUserPromptSubmitno longer errors withPermission denied.