Skip to content

fix(runtime): bake safe.directory '*' into base image (#199, #197)#200

Merged
cbeaulieu-gt merged 1 commit intomainfrom
fix-199-runtime-safe-directory
May 5, 2026
Merged

fix(runtime): bake safe.directory '*' into base image (#199, #197)#200
cbeaulieu-gt merged 1 commit intomainfrom
fix-199-runtime-safe-directory

Conversation

@cbeaulieu-gt
Copy link
Copy Markdown
Member

@cbeaulieu-gt cbeaulieu-gt commented May 5, 2026

Summary

  • Inserts git config --system --add safe.directory '*' into runtime/base/Dockerfile immediately after the apt-get install block that installs git, before the GitHub CLI install block
  • Adds smoke-test assertion section (d.5) to runtime/scripts/smoke-test.sh that verifies the bake-in is present in every overlay image at STAGE 4
  • Documents the fix in CLAUDE.md under the CI Runtime section with a new Container git safety (post-runtime base: bake git config --system --add safe.directory '*' into Dockerfile #199) paragraph

Root cause

actions/checkout writes the workspace as the host runner UID. Inside the digest-pinned overlay containers the running UID is 1001 (runner), not the host UID — git's CVE-2022-24765 protection fires with fatal: detected dubious ownership in repository. This blocked claude-code-action@v1's internal git fetch / git hash-object setup steps in every container-pinned workflow, causing silent or noisy failures depending on the action's error handling.

Why --system and *

Why --system not --global: --global writes to $HOME/.gitconfig. The claude-code-action@v1 TypeScript entrypoint is invoked via bun, which resolves $HOME differently than a shell step — PR #198 commit 7fd62e3 confirmed empirically that --global config was invisible to the action's git calls even when it was visible to shell steps in the same job. --system writes to /etc/gitconfig, which is read by every process and every UID without any $HOME dependency.

Why * not a specific workspace path: The workspace path (/home/runner/work/...) is determined by the host runner at job time and injected via GITHUB_WORKSPACE. Hardcoding it in the image would break if the path changes (different consumer repo, future runner version). * is robust to any workspace path, to nested git operations, and to any future actions/checkout changes.

Security analysis

safe.directory '*' disables the CVE-2022-24765 ownership check globally inside the container. The CVE was designed to protect against an attacker placing a malicious .git/config in a directory that a privileged user cds into on a shared host. Inside an ephemeral GHA container:

  • The container is single-use and torn down after the job
  • The workspace is written by actions/checkout — a trusted action operating on the consumer's own repo
  • No untrusted user has write access to the container filesystem

The threat model the CVE defends against does not apply. This analysis matches PR #198's security assessment and the spec §7.3 amendment rationale referenced in the Dockerfile comment.

Smoke test coverage

New section (d.5) in runtime/scripts/smoke-test.sh runs:

git config --system --get-all safe.directory

inside the container as UID $SMOKE_UID (non-root) and asserts the output contains a line matching * exactly. A Dockerfile edit that accidentally removes or shadows /etc/gitconfig would fail this check at STAGE 4 before any overlay digest is promoted.

Test plan

  • STAGE 1 manifest validation passes (existing pull_request gate on runtime/**)
  • STAGE 2 base image rebuilds on merge to main
  • STAGE 3 overlay images rebuild on top of new base
  • STAGE 4 overlay smoke (incl. new d.5 safe.directory check) passes for all three overlays (review / fix / explain)
  • Follow-up commit/PR pins new overlay digests in .github/workflows/claude-*.yml (review/fix/explain — 5 workflow files affected)
  • Final dogfood verification on a fresh PR after digest pins land

Closes #199
Closes #197

🤖 Generated by Claude Code on behalf of @cbeaulieu-gt

actions/checkout writes the workspace as the host runner UID, but
the container running as a different UID hits git's CVE-2022-24765
protection: 'fatal: detected dubious ownership'. This blocks every
composite action that invokes git inside the digest-pinned overlay
containers — root cause of #197.

Add `git config --system --add safe.directory '*'` to the base
image's Dockerfile so every overlay inherits the exemption. STAGE 4
overlay smoke (smoke-test.sh d.5) asserts the bake-in is present.

`--system` (not `--global`) because /etc/gitconfig is read by every
UID without a $HOME dependency — empirically confirmed in PR #198
commit 7fd62e3 that --global was invisible to claude-code-action's
bun-invoked TS entrypoint. `*` (not specific workspace path) because
the exemption needs to be robust to nested git operations and any
future workspace path changes.

Security: safe.directory '*' disables the CVE-2022-24765 check
globally inside the container. The CVE protected against attackers
planting malicious .git/configs; inside ephemeral GHA containers
operating on workspaces we own, that threat does not apply.

The new digests will be pinned in .github/workflows/claude-*.yml as
a follow-up commit once STAGE 2/3 rebuild against this Dockerfile
change.

Closes #199.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 5, 2026

Claude Code is working…

I'll analyze this and get back to you.

View job run

@cbeaulieu-gt cbeaulieu-gt merged commit 3bb6a22 into main May 5, 2026
14 of 15 checks passed
@cbeaulieu-gt cbeaulieu-gt deleted the fix-199-runtime-safe-directory branch May 5, 2026 22:24
cbeaulieu-gt added a commit that referenced this pull request May 5, 2026
PR #200 (#199, #197) baked safe.directory '*' into the runtime base
image. The reusable workflows still referenced pre-#200 overlay
digests, leaving the dogfood broken. Bump all 7 occurrences to the
post-merge digests produced by runtime-build run 25405636887 against
commit 3bb6a22.

Per-overlay substitution counts (verified):
  review:  2 (claude-pr-review.yml, claude-tag-respond.yml)
  fix:     4 (claude-apply-fix.yml, claude-ci-failure.yml,
               claude-lint-failure.yml, claude-tag-respond.yml)
  explain: 1 (claude-tag-respond.yml)
  total:   7

STAGE 4-overlay smoke (incl. d.5 safe.directory check) passed for
all three overlays in run 25405636887, so these digests are
pre-validated.

Closes #201.

Co-authored-by: Claude Auto-Fix <claude-autofix@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant