Skip to content

feat(security-issue-sync): five pre-push hygiene gates for CVE-record quality#372

Merged
potiuk merged 4 commits into
apache:mainfrom
potiuk:security-issue-sync-summary-version-gate
May 29, 2026
Merged

feat(security-issue-sync): five pre-push hygiene gates for CVE-record quality#372
potiuk merged 4 commits into
apache:mainfrom
potiuk:security-issue-sync-summary-version-gate

Conversation

@potiuk

@potiuk potiuk commented May 29, 2026

Copy link
Copy Markdown
Member

Summary

Five pre-push hygiene gates added to security-issue-sync. All five are validated on every sync pass (not gated to a single transition); each one is a Step 1d detector + Step 2b propose-rewrite that surfaces in the proposal and applies after user confirmation. A unified Step 5b pre-push hygiene-gate scan re-runs all five gates against the regenerated JSON immediately before any vulnogram-api-record-update push, refusing the push when any gate fails the regenerated content (catches the case where body fields drift between the Step 2b proposal cycle and the actual push — e.g. user applied only a subset of proposed updates).

The five gates

# Gate Detects Fix shape
1 Short public summary names an upgrade-target version Generic "upgrade to the version that contains the fix" — no <package> <X.Y.Z> pattern Name the upgrade target verbatim per scope
2 Short public summary states trigger conditions Bug mechanism described but missing two of three: WHO (attacker role / capability), WHEN (config / deployment shape), ACTION (step + surface) Add the missing element(s); reader can answer "does this affect us?" without opening the patch
3 Incomplete-fix-to-another-CVE cross-CVE clause Tracker is a follow-up to a prior PUBLISHED CVE but summary doesn't name the prior CVE / tell users who applied the prior fix to also apply this one Add explicit cross-CVE + cross-product clause; cross-product splits (provider + core) look like duplicates from the outside
4 Issue-title hygiene Title carries adopter-specific or internal noise — project-name prefix/suffix, [GHSA-...] / (ZDRES-...) / (HUNTR-...) / (GHSL-...) external IDs, (split from #NNN) internal markers, [Security Report] classifiers, version-noise suffixes Re-run the security-cve-allocate Step 2 title-strip cascade; preserve stripped context as audit trail in body / rollup
5 CWE field carries the long-form description Bare CWE-NNN token without : <Canonical Title> Expand to CWE-NNN: <Canonical Title>; prefer the project's advised CWEs list when declared

Step 5b pre-push hygiene-gate scan

A new sub-step in Step 5b (between the skip-condition gate and the session probe) re-runs all five gates on the JSON about to be pushed. When any gate fails the regenerated JSON, the recovery is to fix the underlying body field (or title), re-regen, then re-scan — not to push a degraded record. This protects against the "body proposal landed but the push went out on a stale or partial edit" failure mode.

Real-world examples (caught during ad-hoc sync, motivating these rules)

  • Gate 1 on airflow-s/airflow-s#259 (CVE-2026-40961) — generic "upgrade to the Airflow version that contains the fix" survived multiple sync passes.
  • Gate 4 on the same tracker — title was "Open Redirect Bypass Vulnerability in Apache Airflow", with in Apache Airflow trailing the title and shipping into the CVE record's title field.
  • Gate 3 on airflow-s/airflow-s#233 (CVE-2026-49298) — the airflow-core follow-up to CVE-2026-27173 (K8s provider, already PUBLIC); without the cross-CVE clause, K8s deployers who applied the provider fix would think they're done.
  • Gate 2 on airflow-s/airflow-s#244 (CVE-2026-33264) — original summary described the import_string() bug mechanism but didn't enumerate the attacker role / config that exposes deployments.

Changes

  • Step 1d — five new signal-table rows (one per gate)
  • Step 2b — three new paragraphs (Short public summary trigger conditions, Incomplete-fix-to-another-CVE) + extension of the existing summary-quality paragraph (upgrade-target requirement)
  • Step 5b — new sub-step 1b (pre-push hygiene-gate scan)
  • All examples use <project> / <package> / <sibling-package> / <current-package> placeholders per the framework's placeholder-convention rules; the check-placeholders pre-commit hook is green.

Test plan

  • skill-and-tool-validate pre-commit hook green
  • check-placeholders green
  • Skill markdown self-consistent — Step 2b paragraphs cross-reference matching Step 1d rows, Step 5b scan cross-references the Step 1d rows
  • Manual end-to-end: a sync pass against a tracker carrying generic summary / dirty title / un-marked incomplete-fix / bare CWE surfaces rewrites as proposed body-field updates; the Step 5b pre-push scan refuses the push if the user applies only a subset
Was generative AI tooling used to co-author this PR?
  • Yes — Claude Opus 4.7 (1M context)

potiuk and others added 2 commits May 29, 2026 12:19
…ort public summary on every push

The *"Short public summary for publish"* body field powers the
published CVE description that end users read in the advisory. A
summary that names the vulnerability accurately but lacks the
concrete upgrade-target version (e.g. *"upgrade to the Airflow
version that contains the fix"* without naming `3.3.0`) is a
defect — it forces the reader to open another tab to figure out
which version to pin, exactly the friction the advisory is
supposed to remove.

The skill already enforces this as a gate at the
`pr merged → fix released` transition. Widen the rule so it fires
on **every** sync pass that proposes a body-field update or a JSON
regen, not only at one boundary.

Two changes:

1. Step 2b paragraph: explicitly call out the "every sync pass"
   trigger plus the per-scope target shapes (`apache-airflow X.Y.Z
   or later`, `apache-airflow-providers-<name> X.Y.Z or later`,
   `apache-airflow-helm-chart X.Y.Z or later`).
2. Step 1d signal-table row: detector for "summary populated but
   does not name a concrete upgrade-target version". Surfaces the
   rewrite proposal on every qualifying pass so the tightening
   lands lock-step with the next JSON regen.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…her-CVE summary rules

Two complementary additions to security-issue-sync, both validated
on every sync pass (not gated to one transition):

1. Issue-title hygiene. The GitHub issue title is read verbatim
by generate-cve-json into containers.cna.title, so it ships to
cve.org and the published advisory. Re-use the same title-strip
cascade security-cve-allocate applies at allocation time —
project-name tokens, internal split-markers, report-form
classifiers, external-tracker IDs, version-noise suffixes.
Titles drift between allocation and the final regen, so the
cascade has to re-run on every sync. Preserve stripped context
as audit trail in body / rollup; never silently drop it.

2. Incomplete-fix-to-another-CVE summary expansion. When a
tracker is a follow-up to a previously-PUBLISHED CVE — typically
because the original root cause spanned multiple products
(provider + core, chart + core) and the team split per
product/package — the Short public summary for publish must
do more than describe this tracker's vulnerability in isolation.
It must (1) name the prior CVE explicitly, (2) state the prior
fix did not cover the current product, (3) tell users who
already applied the prior fix to also apply this one. Without
this cross-CVE + cross-product framing, downstream readers see
two CVEs about the same root-cause class and assume one fix
covered both — missing that two upgrades are needed.

Both rules are detector + propose-rewrite. The user confirms
each summary/title edit before regen + push to Vulnogram, same
as the existing summary-quality rule.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@potiuk potiuk changed the title feat(security-issue-sync): require named upgrade-target version in Short public summary on every push feat(security-issue-sync): three pre-push hygiene gates — summary upgrade-target, title cleanup, incomplete-fix cross-CVE May 29, 2026
potiuk and others added 2 commits May 29, 2026 16:17
…n) in Short public summary

Extend the Step 2b summary-quality rule and the matching Step 1d
detector with a fourth gate: the summary must state the triggering
conditions, not only the bug mechanism.

The reader scans the published advisory asking "does this affect
us?" — and the answer comes from trigger context, not from a
mechanical description of the bug. Concretely the summary must
make these three things unambiguous in one sentence each (in any
order):

  WHO    — attacker role / required capability (an authenticated
           UI user with Op permissions, a Dag author, a partner
           with write access to the bucket, a worker holding a
           valid Execution-API JWT, etc.).
  WHEN   — deployment shape / config / feature that has to be
           active (when [opensearch] host embeds credentials,
           when the Kubernetes executor is configured, affects
           deployments where the webserver is reachable by
           untrusted users, etc.).
  ACTION — the step the attacker takes against which surface
           (follows a crafted next= redirect URL, uploads an
           object containing .. path segments, PATCHes the
           deferred-state endpoint with crafted next_kwargs,
           etc.).

A summary without all three forces the reader to open the issue
PR / patch to figure out the trigger — exactly the work the
advisory should remove.

Step 2b paragraph: enumerate the three required elements with
worked example wordings (positive + negative).

Step 1d signal-table row: detector heuristic looks for phrases
that mark each of the three elements (the regex set is in the
row); fires when fewer than two of three are unambiguously
present.

Apply this gate in parallel with the upgrade-target rule (same
sync pass) so a rewrite covers both gates at once.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…hygiene-gate scan

Two more pre-push hygiene gates on top of the four existing ones
(upgrade-target version, trigger conditions, title cleanup,
incomplete-fix cross-CVE clause):

1. CWE field must carry the long-form `CWE-NNN: <Canonical
   Title>` shape, not just a bare `CWE-NNN` token. Examples:
   `CWE-22: Improper Limitation of a Pathname to a Restricted
   Directory ('Path Traversal')`, `CWE-502: Deserialization of
   Untrusted Data`, `CWE-601: URL Redirection to Untrusted Site
   ('Open Redirect')`. The value lands verbatim in the CVE
   record's `problemTypes[].descriptions[].description`; readers
   don't memorise MITRE numbering. Prefer a CWE from the
   project's *advised CWEs* list when one is declared.

2. Step 5b pre-push hygiene-gate scan. Before any
   `vulnogram-api-record-update` push, re-scan the JSON about to
   be pushed for all five pre-push gates: title strip cascade,
   upgrade-target version, trigger conditions, incomplete-fix
   cross-CVE clause, CWE long form. When any gate fails the
   regenerated JSON, the recovery is to fix the underlying body
   field (or title), re-regen, then re-scan — not to push a
   degraded record. The gates protect against the case where
   body fields drift between the Step 2b proposal cycle and the
   actual push (e.g. a user applied only a subset of the proposed
   updates).

The pre-push scan is the safety net that catches the "body
proposal landed but the push went out on a stale or partial
edit" failure mode; the individual Step 1d detector rows surface
the rewrite proposals on every sync pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@potiuk potiuk changed the title feat(security-issue-sync): three pre-push hygiene gates — summary upgrade-target, title cleanup, incomplete-fix cross-CVE feat(security-issue-sync): five pre-push hygiene gates for CVE-record quality May 29, 2026
@potiuk potiuk merged commit 1d921a0 into apache:main May 29, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant