Severity: Medium (content-mode false negative via path-key desync)
Problem
Content-mode detection joins two independently-parsed views of the diff on the file path:
Mode::Content { patterns, exempt } => files.iter()
.filter(|f| !exempt.iter().any(|re| re.is_match(&f.path)))
.filter_map(|f| { let lines = added_lines.get(&f.path)?; ... })
files comes from git diff --name-status (parse_name_status: path = last tab field, untrimmed). added_lines is keyed by the unified-diff +++ b/<path> header (parse_added_lines: strip_prefix("+++ b/") then .trim_end()). The two path strings must be byte-identical for added_lines.get(&f.path) to hit.
They diverge for:
- git path-quoting: with default
core.quotePath, non-ASCII/special-char paths are emitted quoted with octal escapes; the two code paths can present different forms.
- trailing whitespace:
parse_added_lines trims the diff path, parse_name_status does not.
On divergence, added_lines.get(&f.path) returns None, the ? short-circuits, and the file is silently skipped — its added lines are never scanned against the content denylist.
Why it matters
This is a correctness false-negative distinct from the documented "denylists are bypassable" caveat: even for an honest operator, dangerous added content in a file with a non-ASCII or special-character path escapes the content gate with no signal.
Proposed fix
Canonicalize paths identically in both parsers (unquote, no trimming asymmetry), or drive content scanning from the name-status file list and reconcile the diff hunks to those exact keys. Add a fixture with a non-ASCII path carrying denylisted content.
Acceptance criteria
- A denylisted line added to a file with a non-ASCII/special-char path fires the content checkpoint.
- Both parsers agree on the path key for such files.
Severity: Medium (content-mode false negative via path-key desync)
Problem
Content-mode detection joins two independently-parsed views of the diff on the file path:
filescomes fromgit diff --name-status(parse_name_status: path = last tab field, untrimmed).added_linesis keyed by the unified-diff+++ b/<path>header (parse_added_lines:strip_prefix("+++ b/")then.trim_end()). The two path strings must be byte-identical foradded_lines.get(&f.path)to hit.They diverge for:
core.quotePath, non-ASCII/special-char paths are emitted quoted with octal escapes; the two code paths can present different forms.parse_added_linestrims the diff path,parse_name_statusdoes not.On divergence,
added_lines.get(&f.path)returnsNone, the?short-circuits, and the file is silently skipped — its added lines are never scanned against the content denylist.Why it matters
This is a correctness false-negative distinct from the documented "denylists are bypassable" caveat: even for an honest operator, dangerous added content in a file with a non-ASCII or special-character path escapes the content gate with no signal.
Proposed fix
Canonicalize paths identically in both parsers (unquote, no trimming asymmetry), or drive content scanning from the name-status file list and reconcile the diff hunks to those exact keys. Add a fixture with a non-ASCII path carrying denylisted content.
Acceptance criteria