Skip to content

filter semantics: Cardigann regexp filter mishandles non-participating groups #531

Description

@forkwright

Finding

The regexp filter (line 94) uses caps.get(1).or_else(|| caps.get(0)), which cannot distinguish 'no capture group' from 'group 1 exists but did not participate in this match' (e.g. (?:(\d+)|abc) on 'abc'), so it returns the whole match instead of the empty string upstream Cardigann/Go semantics produce — yielding a wrong extracted field.

Evidence

crates/zetesis/src/client/cardigann/filters.rs:94. Surfaced by the 2026-07-03 deep-audit workflow (adversarially verified + Opus-judged).

Why this matters

A capture group that exists in the pattern but doesn't participate in a given match returns the whole matched text instead of empty, silently corrupting extracted indexer fields — a wrong-data bug, not just a crash.

Desired correction

Track whether the pattern statically has a group 1 (e.g. Regex::captures_len() > 1) and, if so, return group 1's matched text or empty when it didn't participate, only falling back to group 0 when the pattern truly has no capture group.

Done when: the defect's failure mode no longer reproduces and a regression test covers it.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions