From eebcbf0184e19e9e2c7c4d4aeb6d66af58743097 Mon Sep 17 00:00:00 2001 From: Anurag Singh Date: Sat, 14 Nov 2026 11:59:33 +0530 Subject: [PATCH 1/5] fix(security): treat a missing CVE-tool adapter as the recipe path cve-tool exits 20 (no cve_authority.tool declared), 21 (no adapter for the configured tool) and 22 (the adapter does not implement the operation). Step 0 item 5 fell through to "unexpected server-check exit N" for those or routed them through the allocate-stage fallback, while api-allocation.md documents them as the recipe path. Take that path with a reason naming the missing key, for both the server check and the allocate check. --- .../src/security_management/cve_allocate/gate.py | 11 +++++++++++ tools/security-management/tests/test_cve_allocate.py | 12 ++++++++++++ 2 files changed, 23 insertions(+) diff --git a/tools/security-management/src/security_management/cve_allocate/gate.py b/tools/security-management/src/security_management/cve_allocate/gate.py index cd940efca..5c8db1315 100644 --- a/tools/security-management/src/security_management/cve_allocate/gate.py +++ b/tools/security-management/src/security_management/cve_allocate/gate.py @@ -187,6 +187,13 @@ def _check(scope: str, data: Mapping[str, Any]) -> str: return cve_tool.command(data, "check", *flags) +_NO_TOOL = { + 20: "no `cve_authority.tool` declared anywhere; take the recipe path and say which key is missing", + 21: "the configured CVE tool has no adapter; take the recipe path", + 22: "the adapter does not implement the check operation; take the recipe path", +} + + def preflight(data: Mapping[str, Any]) -> dict[str, Any]: pmc = one_line(data.get("pmc")) setup = cve_tool.command(data, "setup", "--pmc", pmc or "") @@ -213,6 +220,8 @@ def out( ) if server == 3: return out("recipe", "network error on the server check; mention it") + if server in _NO_TOOL: + return out("recipe", _NO_TOOL[server]) if server != 0: return out("recipe", f"unexpected server-check exit {server}") if data.get("allocate_exit") is None: @@ -232,6 +241,8 @@ def out( return out(None, f"allocate token {state}: offer the setup, then re-run the check", offer_setup=True) if code == 4: return out("recipe", "unsupported: the server answers HTML") + if code in _NO_TOOL: + return out("recipe", _NO_TOOL[code]) return out("recipe", f"allocate check failed (exit {code}); mention the error") diff --git a/tools/security-management/tests/test_cve_allocate.py b/tools/security-management/tests/test_cve_allocate.py index b8c3cbad9..5d17df193 100644 --- a/tools/security-management/tests/test_cve_allocate.py +++ b/tools/security-management/tests/test_cve_allocate.py @@ -266,6 +266,9 @@ def test_the_title_needs_the_project_names(capsys, tmp_path): ({"governance_member": True}, None, False), ({"governance_member": True, "server_exit": 4}, "recipe", False), ({"governance_member": True, "server_exit": 3}, "recipe", False), + ({"governance_member": True, "server_exit": 20}, "recipe", False), + ({"governance_member": True, "server_exit": 21}, "recipe", False), + ({"governance_member": True, "server_exit": 22}, "recipe", False), ({"governance_member": True, "server_exit": 0}, None, False), ( { @@ -290,6 +293,9 @@ def test_the_title_needs_the_project_names(capsys, tmp_path): ({"governance_member": True, "server_exit": 0, "allocate_exit": 1}, None, True), ({"governance_member": True, "server_exit": 0, "allocate_exit": 2}, None, True), ({"governance_member": True, "server_exit": 0, "allocate_exit": 4}, "recipe", False), + ({"governance_member": True, "server_exit": 0, "allocate_exit": 20}, "recipe", False), + ({"governance_member": True, "server_exit": 0, "allocate_exit": 21}, "recipe", False), + ({"governance_member": True, "server_exit": 0, "allocate_exit": 22}, "recipe", False), ], ) def test_preflight_picks_the_step_3_path(capsys, tmp_path, data, path, setup): @@ -303,6 +309,12 @@ def test_preflight_picks_the_step_3_path(capsys, tmp_path, data, path, setup): ) +def test_preflight_exit_20_names_the_missing_key(capsys, tmp_path): + out = ok(capsys, tmp_path, "preflight", {"pmc": "airflow", "governance_member": True, "server_exit": 20}) + assert out["path"] == "recipe" + assert "cve_authority.tool" in out["reason"] + + @pytest.mark.parametrize( ("data", "next_step", "cve_id", "rerun"), [ From d7fb8146fac26c94923351b9d34ce514c7960a8b Mon Sep 17 00:00:00 2001 From: Anurag Singh Date: Sat, 14 Nov 2026 11:59:42 +0530 Subject: [PATCH 2/5] fix(security): keep the step 3 recipe wording adapter-neutral The allocation URL may deliver the CVE ID outside the Vulnogram form (a form result, or an ID by email from MITRE), so name those channels instead of presenting Vulnogram's Allocate button as the only outcome. --- .../src/security_management/cve_allocate/recipe.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/security-management/src/security_management/cve_allocate/recipe.py b/tools/security-management/src/security_management/cve_allocate/recipe.py index bb45de778..c7e63b56a 100644 --- a/tools/security-management/src/security_management/cve_allocate/recipe.py +++ b/tools/security-management/src/security_management/cve_allocate/recipe.py @@ -100,7 +100,8 @@ def render(data: Mapping[str, Any]) -> dict[str, Any]: "2. In the *Title* field, paste this:\n\n" f"{indented}\n\n" "3. Submit the allocation. The CVE tool returns a `CVE-YYYY-NNNNN` ID — for the Vulnogram " - "adapter, clicking *Allocate*.\n" + "adapter, clicking *Allocate*; for other adapters, wherever the tool delivers it " + "(a form result, an ID by email).\n" "4. Paste the allocated CVE ID back into this conversation — the skill will pick it up and " "update the tracker.\n" ) From 55da04c2bcb3b74be258e1247d4f7726452ed389 Mon Sep 17 00:00:00 2001 From: Anurag Singh Date: Sat, 14 Nov 2026 11:59:42 +0530 Subject: [PATCH 3/5] docs(cve-allocate): spell out the non-Vulnogram allocation path Step 0 item 5 never said what an adopter whose cve_authority.tool is not vulnogram does; it now states that the API pre-flight is skipped and Step 3 takes the recipe path, making the print-the-link-and-take-the-ID flow plus a plain CVE JSON 5 record the documented default for projects allocating through another CNA. api-allocation.md carries the same scope line. --- plugins/magpie-security/skills/cve-allocate/SKILL.md | 10 ++++++++-- .../skills/cve-allocate/api-allocation.md | 4 ++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/plugins/magpie-security/skills/cve-allocate/SKILL.md b/plugins/magpie-security/skills/cve-allocate/SKILL.md index 52319ccbf..5881bd32c 100644 --- a/plugins/magpie-security/skills/cve-allocate/SKILL.md +++ b/plugins/magpie-security/skills/cve-allocate/SKILL.md @@ -247,8 +247,14 @@ Before touching the tracker, verify: Plus confirm `~/.config/apache-magpie/` is writable (for the redactor's mapping file). -5. **API allocation pre-flight** (Vulnogram adapter, governance-authorised users only). - Check whether the CVE tool can allocate over its API and whether an allocate token is live, +5. **API allocation pre-flight** (governance-authorised users only, and only when the configured CVE tool can allocate over an API — + today that is the `vulnogram` adapter). + When `cve_authority.tool` is not `vulnogram`, or `cve-tool` cannot be probed — exit `20` no tool declared, + `21` the tool has no adapter, `22` the adapter does not implement the check (the planned adapters `mitre-form`, `cve-org-direct` and `ghsa`) — + **skip this item entirely and take the `recipe` path in Step 3**: print the allocation link and the stripped title and take the allocated ID by hand. + For a project allocating through another CNA (for example the GitHub CNA or MITRE directly), that print-the-link-and-take-the-ID flow + plus a plain CVE JSON 5 record *is* the default, and `api-allocation.md` does not apply to it. + When the tool is `vulnogram`, check whether it can allocate over its API and whether an allocate token is live, per [`api-allocation.md` § *Pre-flight*](api-allocation.md#pre-flight-step-0-item-5). ` preflight` (`{governance_member, pmc, server_exit, allocate_exit, allocate_stdout}`) turns the exits into the path, the next check to run, or the setup to offer. The outcome picks Step 3's path (`api` or `recipe`); it never blocks the skill, since the recipe always works. diff --git a/plugins/magpie-security/skills/cve-allocate/api-allocation.md b/plugins/magpie-security/skills/cve-allocate/api-allocation.md index d7cd79213..d4f1c9d51 100644 --- a/plugins/magpie-security/skills/cve-allocate/api-allocation.md +++ b/plugins/magpie-security/skills/cve-allocate/api-allocation.md @@ -3,6 +3,10 @@ # API allocation (Vulnogram adapter) +This page applies only when `cve_authority.tool` is `vulnogram`. +Any other tool, and the unset case, takes Step 3's `recipe` path instead: do not run the checks below. +The generated CVE record stays a plain CVE JSON 5 either way. + When the CVE tool can allocate over its API, Step 3 reserves the CVE ID itself instead of printing a form-fill recipe, so the governance-authorised user does not open the allocation form or paste the ID back. From c619b3e34b244746b1155f94d19ed460de510b10 Mon Sep 17 00:00:00 2001 From: Anurag Singh Date: Sat, 14 Nov 2026 12:11:07 +0530 Subject: [PATCH 4/5] docs(cve-allocate): re-stamp measured_tokens for the longer Step 0 item 5 --- plugins/magpie-security/skills/cve-allocate/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/magpie-security/skills/cve-allocate/SKILL.md b/plugins/magpie-security/skills/cve-allocate/SKILL.md index 5881bd32c..a3c9e1291 100644 --- a/plugins/magpie-security/skills/cve-allocate/SKILL.md +++ b/plugins/magpie-security/skills/cve-allocate/SKILL.md @@ -21,7 +21,7 @@ argument-hint: "[issue-number] [CVE-YYYY-NNNNN]" capability: capability:resolve surface_hash: sha256:c96f5623aa6c4212 license: Apache-2.0 -measured_tokens: 7047 +measured_tokens: 7250 ---