The PR-verdict gate (.github/workflows/pr-verdict.yml) enforces its rules with literal grep matches against fixed tokens (INFERRED, quote marks, verdict phrases). None of these matches normalize the input first, so a token can be present to a human reader and absent to the regex.
Reproduction
Insert a zero-width space (U+200B) inside the token:
## 🟢 SAFE TO MERGE — INFERRED decision
Renders identically to INFERRED to a human. The gate's INFERRED scan does not match it — confirmed by running the check's grep -qE '(^|[^[:alnum:]])INFERRED([^[:alnum:]]|$)' against this string directly.
Same result substituting a homoglyph (Cyrillic І, U+0406, for Latin I) or full-width Unicode letters (INFERRED) — both render as INFERRED but don't match the ASCII-literal pattern.
Affected checks
All three places in the workflow that scan for a literal token are equally exposed:
Suggested fix
Patching each check individually would leave the same class of bug in whichever one is patched last. The fix belongs at a normalization layer — strip zero-width characters and normalize Unicode confusables on $BODY once, before any of the checks run — rather than three separate token-matching patches.
Threat model note
This requires an author deliberately crafting invisible/lookalike characters to fool both the gate and a human reader simultaneously. Worth weighing against the gate's actual purpose (catching an honest author's disclosure lapse) when prioritizing.
The PR-verdict gate (
.github/workflows/pr-verdict.yml) enforces its rules with literalgrepmatches against fixed tokens (INFERRED, quote marks, verdict phrases). None of these matches normalize the input first, so a token can be present to a human reader and absent to the regex.Reproduction
Insert a zero-width space (U+200B) inside the token:
Renders identically to
INFERREDto a human. The gate'sINFERREDscan does not match it — confirmed by running the check'sgrep -qE '(^|[^[:alnum:]])INFERRED([^[:alnum:]]|$)'against this string directly.Same result substituting a homoglyph (Cyrillic
І, U+0406, for LatinI) or full-width Unicode letters (INFERRED) — both render asINFERREDbut don't match the ASCII-literal pattern.Affected checks
All three places in the workflow that scan for a literal token are equally exposed:
INFERREDscan)INFERREDguard added in fix(ci): let the verdict line carry a trailing clause (HT-100) #202Suggested fix
Patching each check individually would leave the same class of bug in whichever one is patched last. The fix belongs at a normalization layer — strip zero-width characters and normalize Unicode confusables on
$BODYonce, before any of the checks run — rather than three separate token-matching patches.Threat model note
This requires an author deliberately crafting invisible/lookalike characters to fool both the gate and a human reader simultaneously. Worth weighing against the gate's actual purpose (catching an honest author's disclosure lapse) when prioritizing.