chore(deps): address Copilot review round 12 on #2243

Build the open-GHSA set from the raw alert feed, not from the grouped
alerts. groupAlerts deliberately drops an alert with no
first_patched_version, so an advisory that stays OPEN but loses its
patched version vanished from both the keys and that set — and
reconciliation would have called it "fixed or dismissed".

Same wrong direction as the superseded case fixed last round, reached a
different way: a still-open advisory must never stand itself down.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013H6Cew3KB3jVmQ5x4Dq1sq
Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
cliffhall 2026-09-04 08:22:18 -04:00 • committed by GitHub
commit bc47fcbcf0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 54 additions and 3 deletions

View file

@ -1019,7 +1019,8 @@ export function main(
const securityPrsOff = checkSecurityPrsStillDisabled(repo, spawn);
const groups = groupAlerts(openAlerts(repo, spawn));
const alerts = openAlerts(repo, spawn);
const groups = groupAlerts(alerts);
// ⚠️ Loaded BEFORE the zero-group early return, and reconciled after the loop.
// `openAlerts` asks for `state=open`, so an alert that is FIXED or DISMISSED
@ -1036,8 +1037,23 @@ export function main(
const boardProblems = [];
/** Grouping keys this run actually saw in the open feed. */
const seenKeys = new Set();
/** Every GHSA still open, in ANY group — the check a vanished key needs. */
const openGhsas = new Set(groups.flatMap((g) => g.ghsas));
/**
* Every GHSA still open, taken from the RAW feed rather than from `groups`.
*
* ⚠️ `groupAlerts` deliberately drops an alert with no `first_patched_version`
* — there is nothing to bump to, so nothing to file. Building this set from
* the groups would inherit that filter, so an advisory that stays open but
* LOSES its patched version would vanish from both the keys and this set, and
* reconciliation would call it "fixed or dismissed" (Copilot). Same wrong
* direction as the superseded case, reached a different way: what a still-open
* advisory must never do is stand itself down.
*/
const openGhsas = new Set(
alerts
.filter((a) => a.state === "open")
.map((a) => a.security_advisory?.ghsa_id)
.filter(Boolean),
);
for (const rawGroup of groups) {
seenKeys.add(rawGroup.key);

View file

@ -1415,6 +1415,41 @@ test("main omits the security-PR claim when the token could not read it", () =>
);
});
test("main will not stand down an open alert that lost its patched version", () => {
// groupAlerts drops an alert with no first_patched_version, so the bump's key
// disappears — but the advisory is still OPEN. Reading the open-GHSA set from
// the groups instead of the raw feed would report it as fixed or dismissed.
const [filed] = groupAlerts([alert({ ghsa: "GHSA-a" })]);
const unpatched = alert({ ghsa: "GHSA-a" });
unpatched.security_vulnerability.first_patched_version = null;
const spawn = fakeSpawn({
alertPages: [[unpatched]],
issues: [
{
number: 41,
title: buildIssueTitle(filed),
body: buildIssueBody(filed, asInstalled()),
},
],
});
inTempRepo({ "package-lock.json": lockWith("fast-uri", "3.1.5") }, () =>
withoutProjectToken(() =>
captureLog(() => main("o/r", spawn, "2026-09-04")),
),
);
const edit = ghCall(spawn, "edit");
assert.ok(edit, "the issue is still reconciled");
const body = edit.args[edit.args.indexOf("--body") + 1];
assert.doesNotMatch(
body,
/fixed or dismissed/,
"a still-open advisory must never stand itself down",
);
assert.match(body, /superseded/);
assert.match(body, /`GHSA-a` is still open/);
});
test("main reads every page of open dependabot issues", () => {
// The second page holds the matching marker. Truncating the lookup would
// file a duplicate issue rather than recognising this one.