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
Original file line number Diff line number Diff line change
Expand Up @@ -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 "<pmc>")
Expand All @@ -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:
Expand All @@ -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")


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down
12 changes: 9 additions & 3 deletions plugins/magpie-security/skills/cve-allocate/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
---

<!-- Placeholder convention (see AGENTS.md#placeholder-convention-used-in-skill-files):
Expand Down Expand Up @@ -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).
`<sm> 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.
Expand Down
4 changes: 4 additions & 0 deletions plugins/magpie-security/skills/cve-allocate/api-allocation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 "<pmc>")
Expand All @@ -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:
Expand All @@ -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")


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down
12 changes: 12 additions & 0 deletions tools/security-management/tests/test_cve_allocate.py
Original file line number Diff line number Diff line change
Expand Up @@ -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),
(
{
Expand All @@ -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):
Expand All @@ -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"),
[
Expand Down
Loading