You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@magpie.apache.org by po...@apache.org on 2026/09/08 21:05:22 UTC

(magpie) branch main updated: Record three lessons from a CVE publication that stalled (#1175)

This is an automated email from the ASF dual-hosted git repository.

potiuk pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/magpie.git


The following commit(s) were added to refs/heads/main by this push:
     new e6553702 Record three lessons from a CVE publication that stalled (#1175)
e6553702 is described below

commit e65537026437792a29cc22562c22a152e0af4227
Author: Jarek Potiuk <ja...@potiuk.com>
AuthorDate: Tue Sep 8 23:05:16 2026 +0200

    Record three lessons from a CVE publication that stalled (#1175)
    
    All three come from one advisory going out while its CVE record sat
    unpublished, and from a fix that merged onto the wrong branch for the
    release it was milestoned to.
    
    1. Writing CNA_private.state = PUBLIC is not the same as submitting to
       CVE Services. record.md said PUBLIC meant "record pushed to cve.org";
       in practice the state flip landed and cveawg.mitre.org kept returning
       404. record-update writes the tool's own copy, and re-pushing does not
       retry the submission -- submitting is a separate UI action. The tell is
       an empty cveMetadata.datePublished next to state PUBLISHED: CVE
       Services stamps that field on acceptance, so an empty one means the
       record was never accepted. It is a symptom to act on, never a field to
       fill in by hand, and doing so only makes the local copy self-consistent
       while cve.org still 404s. Also note www.cve.org is client-rendered, so
       an empty page there proves nothing -- verify against the API.
    
    2. Check the backport at pr merged, not at fix released. A PR merged to
       the development branch does not reach a patch release without a
       backport, and Affected versions is a claim about which release contains
       the fix. Caught one where the merged PR had no backport label and the
       tracker was milestoned to a patch release cutting the next day; the
       advisory would have named a version that never contained the fix. Probe
       by content rather than ancestry, because a cherry-pick changes the SHA
       and `diverged` proves nothing. The two resolutions -- backport, or move
       the milestone and widen Affected versions -- are not equivalent, and
       the second changes a CVE-affecting field, so neither may be chosen
       silently.
    
    3. An unpublished GHSA is a reporter-facing gap. github-advisory.md
       already detects the drift and describes this exact case, but files it
       as informational. On a GHSA-sourced tracker the advisory is the surface
       the reporter watches -- not the mailing-list advisory and not the CVE
       record -- so a tracker can be closeable while, to the reporter, nothing
       has happened since triage. Check published_at rather than state alone,
       do not let the tracker close silently past it, and fold the publish
       request into the existing admin hand-off relay so the reporter is not
       told everything is public while reading an advisory marked triage.
    
    Documentation only.
---
 skills/security-issue-sync/github-advisory.md    | 18 ++++++++
 skills/security-issue-sync/signals-to-actions.md | 53 ++++++++++++++++++++++++
 tools/cve-tool-vulnogram/record.md               | 41 +++++++++++++++++-
 3 files changed, 111 insertions(+), 1 deletion(-)

diff --git a/skills/security-issue-sync/github-advisory.md b/skills/security-issue-sync/github-advisory.md
index bb1d2146..196ddc6f 100644
--- a/skills/security-issue-sync/github-advisory.md
+++ b/skills/security-issue-sync/github-advisory.md
@@ -71,6 +71,24 @@ record it. Surface drift in the Step 2 proposal:
   tracker `announced` + CVE published while the advisory is still
   `triage`). Informational at the collaborator tier (state changes are an
   admin hand-off).
+
+  **Check `published_at`, not just `state`.** An advisory can carry the
+  CVE id, the right severity and complete content while `published_at`
+  is still `null` — the mirror was filled in but never published.
+
+  **This is a reporter-facing gap, not bookkeeping.** On a GHSA-sourced
+  tracker the advisory is the surface the reporter watches; the project
+  mailing-list advisory and the CVE record are not. So a tracker can be
+  correct and closeable while, from the reporter's side, nothing has
+  visibly happened since triage. Two consequences:
+
+  - **Do not let the tracker close silently past it.** When the tracker
+    reaches its terminal step with the advisory still unpublished,
+    surface it — closing is fine, going quiet is not.
+  - **Fold the publish request into the existing admin hand-off relay**
+    rather than opening a new thread, and pair it with the reporter
+    reply so the reporter is not told *"it is all public now"* while
+    reading an advisory still marked `triage`.
 - **access drift** — security-team roster
   (`gh api repos/<tracker>/collaborators --jq '.[].login'`) members
   missing from the advisory's `collaborating_users`. Informational at the
diff --git a/skills/security-issue-sync/signals-to-actions.md b/skills/security-issue-sync/signals-to-actions.md
index 7930998f..1f47ae36 100644
--- a/skills/security-issue-sync/signals-to-actions.md
+++ b/skills/security-issue-sync/signals-to-actions.md
@@ -1226,3 +1226,56 @@ will change and *why*. Group them by category:
   corrections surface the prior `draftId` for manual discard
   rather than silently shadowing it — is documented in
   [`tools/gmail/operations.md`](../../tools/gmail/operations.md#hard-limitation--no-update-no-delete).
+
+- **Signal — a fix PR merged, but not onto the branch its milestone
+  ships from.** When proposing `pr created` → `pr merged` on a
+  tracker whose milestone is a **patch** release, check that the fix
+  is actually on that release's branch. A PR merged to the main
+  development branch does not reach a patch release unless it is
+  also backported, and the tracker's *Affected versions* upper bound
+  is a claim about which release contains the fix.
+
+  **Check it at `pr merged`, not at `fix released`.** By the time
+  the release is cut it is too late: the wave ships without the fix
+  and the advisory names a version that never contained it. This is
+  the same failure the *containment* half of the
+  [`fix released` gate](SKILL.md) exists to prevent, moved one step
+  earlier so it is still cheap to correct.
+
+  **How to check — content probe, not ancestry.** A cherry-pick
+  changes the SHA, so `compare/<sha>...<branch>` reporting
+  `diverged` is not evidence of absence. Pick a distinctive string
+  the fix introduces (a comment line, a new identifier) and probe
+  each branch:
+
+  ```bash
+  gh api "repos/<upstream>/contents/<path>?ref=<branch>" \
+    --jq '.content' | base64 -d | grep -q "<distinctive string>"
+  ```
+
+  Probe the development branch, the release branch the milestone
+  ships from, and the corresponding stable branch. Present the
+  result as a table — the asymmetry is the finding.
+
+  **Corroborating signal:** projects that automate backports use a
+  per-branch label (`backport-to-<branch>` or similar). Its absence
+  on a merged PR whose tracker is milestoned to that branch's
+  release is a strong hint, but the label is a *convention* and the
+  content probe is the *evidence* — do not conclude either way from
+  the label alone.
+
+  **Two ways to resolve, and they are not equivalent** — surface
+  both and let the operator choose, because they change what ships
+  in the published record:
+
+  1. **Backport** — apply the branch label, milestone the PR to the
+     patch release, and cherry-pick. The tracker's milestone and its
+     `< <patch>` upper bound both stay correct.
+  2. **Let it ride to the next feature release** — move the tracker
+     milestone, **and** widen *Affected versions* to that release.
+     Leaving the old bound in place is the actual danger: it tells
+     users to upgrade to a version that does not contain the fix.
+
+  Never pick silently. Option 2 changes a CVE-affecting body field,
+  so it belongs in the CVE-affecting bucket and needs explicit
+  confirmation.
diff --git a/tools/cve-tool-vulnogram/record.md b/tools/cve-tool-vulnogram/record.md
index 3f0e0b61..c21c7454 100644
--- a/tools/cve-tool-vulnogram/record.md
+++ b/tools/cve-tool-vulnogram/record.md
@@ -10,6 +10,7 @@
   - [Two record-write paths — API (default) and copy-paste (fallback)](#two-record-write-paths--api-default-and-copy-paste-fallback)
   - [`#source` paste flow](#source-paste-flow)
   - [State machine](#state-machine)
+    - [Writing `PUBLIC` is not the same as submitting to CVE Services](#writing-public-is-not-the-same-as-submitting-to-cve-services)
   - [Reviewer-comment signal](#reviewer-comment-signal)
   - [Record-generator round trip](#record-generator-round-trip)
   - [Release-manager checklist](#release-manager-checklist)
@@ -131,7 +132,7 @@ states the generic skills interact with:
 | `DRAFT` | Allocation (initial state post-allocation) | Record exists, ID is reserved, but content is still being filled in. Not visible on `cve.org`. |
 | `REVIEW` | **Sync, via the generator** (post-2026 convention; see below) | Ready for CNA review. ASF CNA reviewers may leave reviewer comments at this point (see *"Reviewer-comment signal"* below). Still not on `cve.org`. |
 | `READY` | Release manager once review feedback is addressed (or immediately after `REVIEW` if no feedback arrived) | Content is final and the record is staged for the advisory-send step. The advisory emails are dispatched from Vulnogram while in `READY`. Still not on `cve.org`. |
-| `PUBLIC` | **Sync, via `vulnogram-api-record-publish`**, when the advisory archive URL is captured on the tracker | Record pushed to `cve.org`. World-readable. The generic tracking-issue lifecycle terminates at the `close` action once this state has been reached. |
+| `PUBLIC` | **Sync, via `vulnogram-api-record-publish`**, when the advisory archive URL is captured on the tracker | Record is *cleared* for `cve.org`. **This state alone does not publish it** — submitting to CVE Services is a separate action, see [*Writing `PUBLIC` is not the same as submitting to CVE Services*](#writing-public-is-not-the-same-as-submitting-to-cve-services). The tracking-issue lifecycle terminates at `close` once `cveawg.mitre.org` actually resolves the record. |
 
 **`DRAFT` → `REVIEW` — sync-driven via the generator.** The
 `generate-cve-json` tool decides `REVIEW` versus `DRAFT` automatically
@@ -172,6 +173,44 @@ would use to decide it is safe to flip.
 Vulnogram documentation; the current action is literally labelled
 `PUBLIC` in the UI.
 
+### Writing `PUBLIC` is not the same as submitting to CVE Services
+
+The state flip and the upstream submission are **two different
+operations**, and the record can sit in `PUBLIC` while `cve.org`
+still knows nothing about it.
+
+`vulnogram-api-record-update` writes Vulnogram's own copy of the
+record. It does not submit to CVE Services, and **re-pushing the
+same record does not trigger a submission either** — a second
+`record-update` is a content write, not a retry of the publish.
+Submitting is a separate action in the Vulnogram UI.
+
+**The diagnostic: an empty `cveMetadata.datePublished`.** CVE
+Services stamps `datePublished` when it *accepts* a record. So:
+
+| `cveMetadata.state` | `datePublished` | Reading |
+|---|---|---|
+| `PUBLISHED` | set | Record really is live; verify with `cveawg.mitre.org` |
+| `PUBLISHED` | **empty** | Marked published locally, **never accepted upstream** — the submission has not completed |
+
+An empty `datePublished` is a *symptom to act on*, never a field
+to fill in by hand. Writing a timestamp into it makes the local
+record self-consistent and moves nothing: `cveawg.mitre.org` will
+keep returning 404 because the record was never submitted.
+
+**Verify publication against `cveawg.mitre.org`, never against the
+CVE tool's own copy.** The tool reports its local state; only the
+CVE Services API can say whether MITRE holds the record. Note that
+`www.cve.org/CVERecord?id=…` is a client-rendered app, so an
+"empty" page there proves nothing — use the API endpoint.
+
+**When `cveawg` still 404s and the state is `PUBLIC`:** re-run the
+submit action in the Vulnogram UI and read the error it returns.
+The OAuth API path does not surface submission errors. If the UI
+reports success and `cveawg` still 404s after propagation time,
+that is a CNA-tooling problem for the CNA administrators, not a
+project-side one.
+
 ## Reviewer-comment signal
 
 ASF CNA reviewers leave comments on `REVIEW`-state records. Those