Skip to content

Block .git/modules/, warn on git-attribute/rebase paths, hint on submodule add#129

Merged
JAORMX merged 1 commit intomainfrom
sec/diff-security-patterns
Apr 17, 2026
Merged

Block .git/modules/, warn on git-attribute/rebase paths, hint on submodule add#129
JAORMX merged 1 commit intomainfrom
sec/diff-security-patterns

Conversation

@JAORMX
Copy link
Copy Markdown
Contributor

@JAORMX JAORMX commented Apr 17, 2026

Summary

Closes a MEDIUM security finding — a compromised agent could write attacker-controlled content to submodule configs / hooks / git attributes / rebase-todo state and have it flushed back, giving RCE on the host via filter drivers, submodule hooks, or rebase exec directives.

Design principle applied consistently: only hard-block paths whose RCE surface fires on routine git operations (hooks, config). Paths whose RCE requires an explicit user action (rebase --continue, git submodule update, git checkout with a pre-configured filter driver) stay flushable and are surfaced via stderr warnings — blocking them would break legitimate agent workflows (resuming rebases, adding submodules, editing .gitattributes).

Changes

DefaultDiffSecurityPatterns (hard-block, silent strip):

  • ADD .git/modules/ — submodule configs/hooks have the same RCE surface as top-level .git/config / .git/hooks/, and they auto-fire on routine ops.

DefaultSensitivePathRules — new Tier 2 entries (stderr WARNING, flush proceeds):

  • .gitattributes (any depth) — can reference filter drivers (filter=/diff=/merge=) that execute on git checkout, git archive, git diff. Filter definition must exist in the user's ~/.gitconfig for exploitation, so routine edits (text/eol/binary) still work.
  • .gitmodules (any depth) — submodule URLs fetched on git submodule update.
  • .git/info/attributes — same filter-driver surface as .gitattributes, local-to-worktree.
  • .git/rebase-merge/git-rebase-todoexec <cmd> directives run on git rebase --continue.

UX recovery hint: the one asymmetric side-effect — .gitmodules flushes but .git/modules/<name>/ is silently hard-stripped, leaving the host with a half-populated submodule after git submodule add — is mitigated by a stderr note after successful flush pointing the user at git submodule update --init to materialize the submodule gitdir.

Workflows verified (via UX review)

  • Agent adds/edits .gitattributes / .gitmodules / .github/workflows/ / Makefile / Jenkinsfile — works with warning
  • Agent resumes interactive rebase on host — works (rebase-merge state syncs; git-rebase-todo warned)
  • Agent runs git am / git gc / cherry-pick / edits .git/info/exclude — works silently
  • Agent adds submodule — .gitmodules flushes, user gets recovery hint
  • Agent edits .git/hooks/ / .husky/ — rejected (pre-existing policy, unchanged)

Test plan

  • task fmt && task lint && task test — all green
  • Targeted tests in pkg/domain/snapshot/{exclude,sensitive}_test.go and internal/infra/exclude/loader_test.go assert both the new matches AND the negative cases (paths that MUST continue to sync back)
  • Headless end-to-end: bbox claude-code --no-mcp --exec /bin/sh -- -c 'ls /workspace/.git/info/' confirms .git/info/exclude is visible in the snapshot (not stripped) and VM cycles cleanly

🤖 Generated with Claude Code

…odule add

DefaultDiffSecurityPatterns previously blocked only `.git/config` and
`.git/hooks/` from flushing back to the host. `.git/modules/<name>/`
carries the same RCE surface — `<name>/config` accepts exec-class
keys, `<name>/hooks/` auto-fires on routine git operations — so add
it to the hard-block list. Nothing else is added to the hard-block
list: the deliberate design choice is that paths whose RCE surface
only fires on explicit user action (`rebase --continue`, `git checkout`
with a pre-defined filter driver, `git submodule update`) stay
flushable and are surfaced via SensitivePathRules warnings instead.
Blocking them would break legitimate agent workflows (resuming
rebases, adding submodules, editing .gitattributes).

Add four Tier 2 entries to DefaultSensitivePathRules (warn on
stderr, still flush):

- .gitattributes (basename) — can reference filter drivers
  (filter=/diff=/merge=) that execute during `git checkout`,
  `git archive`, or `git diff`. The filter definition itself must
  exist in the user's ~/.gitconfig for exploitation, so warn + flush
  lets routine edits (text/eol/binary attrs) proceed.
- .gitmodules (basename) — submodule URLs are fetched on
  `git submodule update` (explicit user action).
- .git/info/attributes (exact path) — same filter-driver surface
  as .gitattributes but scoped to the local worktree.
- .git/rebase-merge/git-rebase-todo (exact path) — `exec <cmd>`
  directives run when the user runs `git rebase --continue`.

To close the `git submodule add` UX rough edge — `.gitmodules` flushes
but `.git/modules/<name>/` is silently hard-stripped, leaving the host
with a half-populated submodule — emit a stderr note after successful
flush pointing the user at `git submodule update --init` for recovery.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@JAORMX JAORMX merged commit 4e656a6 into main Apr 17, 2026
8 checks passed
@JAORMX JAORMX deleted the sec/diff-security-patterns branch April 17, 2026 07:51
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.

1 participant