diff --git a/organizations/ASF/organization.md b/organizations/ASF/organization.md index b6fc308ae..cebcde2cf 100644 --- a/organizations/ASF/organization.md +++ b/organizations/ASF/organization.md @@ -115,6 +115,7 @@ governance: security_inbox: kind: mailing-list foundation_security_address: security@apache.org # ASF security team forwards reports here + cve_tool_senders: [security@apache.org] # organization-wide CVE bookkeeping sender has_forwarder_relay: true list_filter_query: "list:" reporter_response_timeout_days: 14 # ASF policy: a silent reporter must not block the team diff --git a/plugins/magpie-security/skills/issue-import/candidate-listing.md b/plugins/magpie-security/skills/issue-import/candidate-listing.md index 41fccb41f..e2a053a21 100644 --- a/plugins/magpie-security/skills/issue-import/candidate-listing.md +++ b/plugins/magpie-security/skills/issue-import/candidate-listing.md @@ -115,7 +115,7 @@ This is a pre-flight no-op classifier in the sense of `security-issue-sync`'s [b Never read a body to decide. **Run it.** Write `/threads.json` with the Write tool — -`upstream`, `security_list`, `cve_tool_senders` (security-team members who post CVE-tool notifications, if any), `named_thread_id` under `import thread:`, +`upstream`, `security_list`, `cve_tool_senders` (resolved from `security_inbox.cve_tool_senders`; security-team members who post CVE-tool notifications, if any), `named_thread_id` under `import thread:`, and `threads[]`, each `{thread_id, messages: [{subject, from}]}` — then: ```bash diff --git a/plugins/magpie-setup/skills/setup/install.md b/plugins/magpie-setup/skills/setup/install.md index 85556e621..e58886d66 100644 --- a/plugins/magpie-setup/skills/setup/install.md +++ b/plugins/magpie-setup/skills/setup/install.md @@ -874,6 +874,7 @@ available). These pre-populate `/project.md` in Step 9: - `dev_list` is the target of the `dev` or `issues` or `pullrequests` notification containing `dev@`. If not found, default to `dev@.apache.org`. - `users_list` is the target of the `users` notification, or defaults to `users@.apache.org`. - `security_list` has no default: not every ASF project has a dedicated `security@.apache.org` list. Detect it and ask, as [`adopt.md` Step 4b](adopt.md#4b--scaffold-whatever-is-still-missing) does; when no dedicated list is found, recommend requesting one from ASF Infra and offer `private@.apache.org` only as a flagged fallback. + - `security_inbox.cve_tool_senders` inherits `security@apache.org` from the ASF organization profile. Ask which ASF CNA reviewers currently send CVE-tool notifications to the project. If the answer is non-empty, write one project-level list containing `security@apache.org` and the confirmed reviewer addresses because project values replace, rather than merge with, organization defaults. An empty answer leaves the key absent and keeps the inherited address; never guess reviewer addresses. - `private_list` defaults to `private@.apache.org`. - `announce_list` defaults to `announce@apache.org`. - `github.homepage` in `.asf.yaml` may refine `product_family_url` if the GitHub repo homepage was empty. @@ -1329,6 +1330,7 @@ to this repo. - Replace the placeholder `TODO` lines with the values auto-sourced in Step 4b: - Always (organization-agnostic): `upstream_repo`, `upstream_default_branch`, `product_family_url`, `labels`. - **Only when `organization: ASF`** (the ASF-specific block of Step 4b): `dev_list`, `commits_list`, `users_list`, `private_list`, `announce_list`. `security_list` is not auto-sourced: settle it with the detect-and-recommend rule in `adopt.md` Step 4b ("Ask where security reports go explicitly"). For a non-ASF `organization`, leave these as `TODO` — do not write `apache.org` addresses. + - For ASF adopters with confirmed reviewer addresses, write `security_inbox.cve_tool_senders` with both `security@apache.org` and those addresses. If none were confirmed, leave the key absent so the organization default remains effective. Do not write guessed personal addresses. - Prompt the user to confirm the auto-sourced values, and only ask them to input values for stable fields that were absent or couldn't be derived. - Stage the written files in `.apache-magpie-overrides/` to git (`git add`). diff --git a/plugins/magpie-setup/templates/project.md b/plugins/magpie-setup/templates/project.md index 0bdba0408..ee049e72d 100644 --- a/plugins/magpie-setup/templates/project.md +++ b/plugins/magpie-setup/templates/project.md @@ -384,6 +384,11 @@ security_inbox: # The concrete inbound address / channel ID / form URL for this project. # Consumed by: security-issue-import, security-issue-sync, canned-responses. address: + # Optional override: addresses that send CVE-tool bookkeeping mail. + # ASF projects inherit security@apache.org; if overriding this list, repeat + # that address and add the active CNA reviewers. + # Consumed by: security-issue-import's Step 1 pre-filter. + # cve_tool_senders: [security@apache.org, reviewer@apache.org] ``` **Optional override — `reporter_response_timeout_days`.** Days of diff --git a/tools/security-management/tests/test_issue_import.py b/tools/security-management/tests/test_issue_import.py index ffd59a378..a7d5f411c 100644 --- a/tools/security-management/tests/test_issue_import.py +++ b/tools/security-management/tests/test_issue_import.py @@ -239,6 +239,37 @@ def test_prefilter_rules(capsys, tmp_path) -> None: assert [t["thread_id"] for t in out["dropped"]] == ["t-bookkeeping"] +def test_prefilter_drops_asf_cna_bookkeeping_from_configured_senders(capsys, tmp_path) -> None: + threads = { + "upstream": "apache/foo", + "security_list": "security@foo.apache.org", + "cve_tool_senders": ["security@apache.org", "reviewer@apache.org"], + "threads": [ + { + "thread_id": "reservation", + "messages": [ + {"subject": "CVE-2026-12345 reserved for foo", "from": "security@apache.org"}, + ], + }, + { + "thread_id": "review-comment", + "messages": [ + { + "subject": "Comment added on CVE-2026-12345", + "from": "ASF Reviewer ", + }, + ], + }, + ], + } + + code, out = run(capsys, "prefilter", "--threads", write(tmp_path, "threads.json", threads)) + + assert code == 0 + assert out["keep"] == [] + assert [thread["thread_id"] for thread in out["dropped"]] == ["reservation", "review-comment"] + + def test_dedup_queries_batch_by_six(capsys, tmp_path) -> None: ids = [f"19d2f40286{i:06x}" for i in range(8)] code, out = run(