Skip to content

Content-mode false negative: name-status vs unified-diff path-key desync drops special-char paths #3

Description

@lyubomir-bozhinov

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions