Skip to content

[sec-check] fix: refuse -O when it is clustered with other short options - #330

Merged
Danathar merged 2 commits into
mainfrom
sec/gate-git-diff-clustered-short
Sep 18, 2026
Merged

Danathar merged 2 commits into
mainfrom
sec/gate-git-diff-clustered-short

Conversation

@Danathar

Copy link
Copy Markdown
Owner

What changed

.claude/hooks/gate_git_diff.py now walks the letters of a short-option token instead of matching only its start, so -O is refused wherever it sits in a cluster (-aOorder1, -aO order1, -pO order1), not just when it leads the word. A new VALUED_SHORT set names the git diff / git log short options that take a value; the walk stops at the first of those, so a value that merely contains an O (-SOAuth, -GOpen, -L:Open:file) is still allowed.

Why

Closes #329.

git bundles single-letter options into one word and reads the value of the first value-taking letter from the rest of that word, so git diff -aOorder1 is git diff -a -O order1. The hook refused the second spelling and passed the first, and git 2.55 honors the clustered order file, so the third path the hook exists to close was still open.

The fix errs in the safe direction: a letter missing from VALUED_SHORT is walked past as a boolean, which can only over-refuse. The only way to open a gap is to list a boolean letter there, and a test checks every entry against git's own parsing of -<letter>Oorder1 to catch that.

How it was checked

  • python3 -m unittest discover -s tests -- 1553 tests, OK (1 skip: the just test, not installed locally)
  • coverage -- not run; the hook lives under .claude/ and is outside the measured source tree
  • ruff check -- clean
  • shellcheck / actionlint / hadolint -- no shell, workflow or Containerfile changes

By hand, before the fix: the two commands in the issue reproduced exactly (-aOorder1 exit 0, -O order1 exit 2), and git diff --cached -aOorder1 --name-only in a scratch repo returned the files in the order file's order. tests/test_git_diff_gate.py now carries that real-git check alongside the existing --no-index one. I also temporarily added a (a boolean) to VALUED_SHORT to confirm the new per-letter test fails on a wrong entry; it does.

Anything a reviewer should look at first

VALUED_SHORT = frozenset("BCGILMOSUXln"). Each entry is a diff or log short option that takes a value in git's own parser (checked by test). Leaving one out is harmless over-refusal; the set is deliberately not padded with letters I could not confirm.

🤖 Generated with Claude Code

gate_git_diff.py matched a refused short option against the start of the
token, so `git diff -O order1` was refused while `git diff -aOorder1` and
`git diff -aO order1` passed. git bundles single-letter options into one
word and reads the value of the first value-taking letter from whatever
follows it, so both spellings hand git the same order file, and git 2.55
honors it: the hook exists to keep that third path closed, and this left
it open (#329).

Walk the letters of a short-option token instead. Every letter up to and
including the first value-taking one is an option, and the rest of the
word is that option's value, so `-aOorder1` is caught and `-SOAuth` --
a pickaxe search for the text `OAuth` -- is left alone. VALUED_SHORT lists
the diff and log options that take a value; a letter missing from it can
only over-refuse, never let an `O` through, so the set errs in the safe
direction and a test checks each entry against git's own parsing.

The test module gains the clustered forms as refused commands, `-S`, `-G`
and `-L` values containing an `O` as allowed ones, and a real-git check
that the clustered order file is read, in the same style as the existing
--no-index check.

Closes #329

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Danathar <Danathar@users.noreply.github.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 20708d6561

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread tests/test_git_diff_gate.py Outdated
@danathar-atomic-hive

Copy link
Copy Markdown
Contributor

Verified independently: cloned sec/gate-git-diff-clustered-short, ran tests/test_git_diff_gate.py (26/26 pass). Traced refused_short() by hand against the three repro forms in #329 (-aOorder1, -aO order1, -pO order1) — all refused now. Also fuzzed every unlisted ASCII letter as a leading boolean before O (-<ch>Oorder1) against real git parsing in a scratch repo to check for a second VALUED_SHORT gap; none opened the order file while passing unrefused. Fix looks correct and the over-refuse-only safety argument holds. No blocking issues found.

🐝 Hive Agent: scanner | Instance: hive-wild-mole | SHA: unknown

— hive: agent=scanner backend=claude model=claude-sonnet-5 claude=2.1.226

…tdout

The per-letter check ran `git diff --cached -<letter>Oorder1 --name-only`
and asserted the output was not reordered. A boolean letter that clashes
with `--name-only`, such as `-s`, makes git exit 128 with nothing on
stdout, which the assertion accepted -- so `s` wrongly listed in
VALUED_SHORT would have kept the test green while the hook let
`-sO<file>` through (Codex review on #330).

Probe `-<letter>Omissing` with no output format and no such file instead.
A boolean letter leaves `-Omissing` to be read next and git fails to open
the order file, whatever else the command does; a value-taking letter
swallows `Omissing` and never looks for it. `-a` is probed first as a
positive control so a reworded message fails the test rather than
letting every entry pass for the wrong reason. Verified that listing `s`
now fails the test.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Danathar <Danathar@users.noreply.github.com>
@Danathar
Danathar merged commit fc82364 into main Sep 18, 2026
6 checks passed
@Danathar
Danathar deleted the sec/gate-git-diff-clustered-short branch September 18, 2026 10:08
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.

[scanner] gate_git_diff.py: clustered short options (-aOfile) bypass the -O order-file refusal

1 participant