Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 26 additions & 5 deletions projects/_template/title-normalization.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,35 @@ TODO: one rule per bullet, applied in order. Typical patterns:
often reveals a nested `Security Issue |` tag.
6. Trailing `in <Project Name>` — TODO
7. Trailing bare version parens — TODO
8. Trailing GHSA ID paren — `[ \t]*\(GHSA-[\w-]+\)\.?[ \t]*$`
9. Trailing known external-tracker IDs (square or round brackets) —
`[ \t]*(?:\[(?:ZDRES|HUNTR|GHSL)-[\w-]+\]|\((?:ZDRES|HUNTR|GHSL)-[\w-]+\))\.?[ \t]*$`
Strips trailing IDs from known external trackers — `(ZDRES-223)`,
8. GHSA ID paren, **anywhere in the title** —
`[ \t]*\(GHSA-[\w-]+\)\.?`
9. Known external-tracker IDs (square or round brackets),
**anywhere in the title** —
`[ \t]*(?:\[(?:ZDRES|HUNTR|GHSL)-[\w-]+\]|\((?:ZDRES|HUNTR|GHSL)-[\w-]+\))\.?`
Strips IDs from known external trackers — `(ZDRES-223)`,
`[HUNTR-456]`, `(GHSL-2024-001)` — in either bracket style. Extend
the alternation per project when a new reporter brand surfaces
(e.g. `SNYK-…`, `BDSA-…`, internal bug-bounty platforms).
10. Trailing *"split from #NNN"* paren — `[ \t]*\([^)]*split from #\d+[^)]*\)\.?[ \t]*$`
10. *"split from #NNN"* paren, **anywhere in the title** —
`[ \t]*\([^)]*split from #\d+[^)]*\)\.?`
10b. Prior-CVE parenthetical, **anywhere in the title** —
`[ \t]*\([^)]*\bCVE-\d{4}-\d{4,7}\b[^)]*\)\.?`
Catches `(CVE-…)`, `(possible CVE-… variant)`, `(incomplete fix
for CVE-…)`, `(fix-bypass of CVE-…)`. The cross-CVE relationship
belongs in the public summary's cross-CVE clause, never in the
title.

**Why items 8–10b are not end-anchored.** Each requires a
well-formed tracker ID inside the parentheses, so the
over-stripping risk is low, and the reason the parenthetical does
not belong in `containers.cna.title` has nothing to do with where
in the string it sits. An end-anchored cascade reports
*changed=no* on a title that still carries one mid-string — e.g.
`Path traversal (incomplete fix for CVE-2026-12345) in the
parser` — and that title then ships to the CVE record. Contrast
the reporter *follow-up* paren below, which stays end-anchored on
purpose: its `<name>` part is loose enough that stripping it
mid-title could eat a substantive clause.
11. Trailing trivia — strip trailing whitespace, trailing `.`,
collapse internal whitespace.
12. Capitalise — upper-case the first letter; leave the rest alone
Expand Down
47 changes: 31 additions & 16 deletions skills/security-cve-allocate/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -337,37 +337,52 @@ patterns_leading = [
patterns_trailing = [
r"[ \t]+in[ \t]+<upstream-name>[ \t]*\.?$",
r"[ \t]*\(<upstream-name>(?:[ \t]+v?\d+(?:\.\d+)*(?:\.x)?)?\)\.?[ \t]*$",
r"[ \t]*\(GHSA-[\w-]+\)\.?[ \t]*$",
# Trailing IDs from known external trackers, square or round
# brackets. Extend the alternation per project.
r"[ \t]*(?:\[(?:ZDRES|HUNTR|GHSL)-[\w-]+\]|\((?:ZDRES|HUNTR|GHSL)-[\w-]+\))\.?[ \t]*$",
r"[ \t]*\([^)]*split from #\d+[^)]*\)\.?[ \t]*$",
# Trailing parentheticals that mention a prior CVE ID. The cross-
# CVE relationship belongs in the public summary (Gate #3
# cross-CVE clause), never in the title — the title ships as
# `containers.cna.title` and prior-CVE references read as noise
# to downstream advisory consumers. Catches every shape observed
# in manual title cleanups: `(CVE-...)`,
# `(possible CVE-... variant)`, `(incomplete fix for CVE-...)`,
# `(fix-bypass of CVE-...)`, `(CVE-... <anything>)`, etc.
r"[ \t]*\([^)]*\bCVE-\d{4}-\d{4,7}\b[^)]*\)\.?[ \t]*$",
# Trailing `(<reporter name> follow-up)` parenthetical. Reporter
# attribution belongs in the credits field, never in the public
# title. The `<name>` part matches name-like tokens (word chars,
# dots, hyphens, single inline spaces) to avoid over-stripping
# substantive technical parentheticals that happen to contain
# the word `follow-up`.
# the word `follow-up`. Kept position-anchored deliberately: the
# pattern is loose enough that stripping it mid-title could eat a
# substantive clause.
r"[ \t]*\([\w.][\w. -]*[ \t]+follow-up\)\.?[ \t]*$",
]
# Identifier-bearing parentheticals are stripped **anywhere** in the
# title, not just at the end. Each carries an external or internal
# tracker ID whose place is the credits field, the public summary's
# cross-CVE clause, or the issue body — never
# `containers.cna.title`. That rationale does not depend on where in
# the string the parenthetical happens to sit, and real titles put
# them mid-string often enough that an end-anchored cascade reports
# `changed=no` on a title that still carries one (e.g.
# `Path traversal (incomplete fix for CVE-2026-12345) in the parser`).
# Over-stripping risk stays low because every pattern requires a
# well-formed ID inside the parentheses. The trailing `\s+` collapse
# at the end of the cascade closes the gap left behind mid-string.
patterns_anywhere = [
r"[ \t]*\(GHSA-[\w-]+\)\.?",
# IDs from known external trackers, square or round brackets.
# Extend the alternation per project.
r"[ \t]*(?:\[(?:ZDRES|HUNTR|GHSL)-[\w-]+\]|\((?:ZDRES|HUNTR|GHSL)-[\w-]+\))\.?",
r"[ \t]*\([^)]*split from #\d+[^)]*\)\.?",
# Parentheticals that mention a prior CVE ID. The cross-CVE
# relationship belongs in the public summary (Gate #3 cross-CVE
# clause), never in the title. Catches every shape observed in
# manual title cleanups: `(CVE-...)`, `(possible CVE-... variant)`,
# `(incomplete fix for CVE-...)`, `(fix-bypass of CVE-...)`.
r"[ \t]*\([^)]*\bCVE-\d{4}-\d{4,7}\b[^)]*\)\.?",
]

# Leading passes twice — strip order reveals nested tags.
for _ in range(2):
for p in patterns_leading:
t = re.sub(p, "", t, flags=re.IGNORECASE)
# Trailing passes until idempotent.
# Anywhere + trailing passes until idempotent.
prev = None
while prev != t:
prev = t
for p in patterns_anywhere:
t = re.sub(p, "", t, flags=re.IGNORECASE)
for p in patterns_trailing:
t = re.sub(p, "", t, flags=re.IGNORECASE)

Expand Down
Loading