From c07164d5947f6f1a26f960c3926fcc711ef14f13 Mon Sep 17 00:00:00 2001 From: Harish Kesava Rao Date: Fri, 7 Aug 2026 07:50:08 +0530 Subject: [PATCH 1/2] docs(security): add reporter-unresponsiveness escalation per ASF policy to security-issue-sync --- docs/security/process.md | 18 +++++++++++++ docs/security/roles.md | 17 ++++++++++++ projects/_template/project.md | 12 +++++++++ skills/security-issue-sync/gather.md | 23 ++++++++++++++-- .../security-issue-sync/signals-to-actions.md | 27 +++++++++++++++++++ 5 files changed, 95 insertions(+), 2 deletions(-) diff --git a/docs/security/process.md b/docs/security/process.md index a0a7b176b..39d640c9a 100644 --- a/docs/security/process.md +++ b/docs/security/process.md @@ -219,6 +219,24 @@ rollup entries on the tracker (per [`tools/github/status-rollup.md`](../../tools/github/status-rollup.md)) with the action label `Sync (Step 4 escalation)`. +**This is separate from reporter unresponsiveness.** The escalation +above widens the audience when the *security team's own* discussion +has stalled; it says nothing about a reporter who has gone silent. +Per [ASF security policy](https://www.apache.org/security/committers.html), +reporter unresponsiveness must **not** block the project team from +moving to the next steps, particularly for a high-severity or +high-impact issue — the team agrees the fix, announcement, and +release schedule *with* the reporter when possible, but does not wait +indefinitely for that agreement. `security-issue-sync` operationalises +this: at every sync pass it checks the reporter thread for staleness +(the security team's last outbound message older than +`/project.md`'s `reporter_response_timeout_days`, +default 14, with no reply since) and, if stale, proposes proceeding +without further reporter sign-off as an explicit, user-confirmed +proposal item — see [`security-issue-sync` Step 1c/2b](../../skills/security-issue-sync/gather.md#1c-find-the-real-reporter-and-read-the-mailing-list-thread). +This never happens silently; the security team still confirms before +the process moves forward without the reporter. + ### Step 5 — Land the valid/invalid consensus If valid, apply exactly one scope label from diff --git a/docs/security/roles.md b/docs/security/roles.md index fc105410a..b3accb844 100644 --- a/docs/security/roles.md +++ b/docs/security/roles.md @@ -103,6 +103,23 @@ Reusable wording for the common cases lives in [`/canned-responses.md`](/canned-responses.md) — consult it before drafting a reply from scratch. +**A silent reporter does not block the process.** Keeping the reporter +informed is a commitment the team makes *to* the reporter — it is not +a requirement that the reporter respond before the team can move +forward. Per [ASF security policy](https://www.apache.org/security/committers.html), +if the reporter is unresponsive in a reasonable timeframe this should +not block the project team from moving to the next steps, particularly +if an issue is of high severity or impact. `security-issue-sync` tracks +this for you: when the security team's last outbound message on the +reporter thread is older than the project's configurable +`reporter_response_timeout_days` (declared in +`/project.md` → *Security inbox*, default 14 days) and +no reply has landed since, sync surfaces an explicit proposal item +asking whether to proceed with the fix and announcement without +further reporter sign-off. Confirming that proposal is a normal part +of the workflow, not an exception — you are not waiting on a reply +that never comes. + **When there's no direct reporter contact** (ASF-relay reports, read-only GHSA, anonymous tips), the team communicates with the *forwarder* instead — the security-team member or relay service diff --git a/projects/_template/project.md b/projects/_template/project.md index 53a424f49..8b2360d95 100644 --- a/projects/_template/project.md +++ b/projects/_template/project.md @@ -315,6 +315,18 @@ security_inbox: # The concrete inbound address / channel ID / form URL for this project. # Consumed by: security-issue-import, security-issue-sync, canned-responses. address: + + # Days of reporter silence, counted from the security team's most + # recent outbound message on the reporter thread, before + # security-issue-sync proposes proceeding with the fix and + # announcement without further reporter sign-off. Per ASF policy + # (https://www.apache.org/security/committers.html), an unresponsive + # reporter must not block the project team, particularly for a + # high-severity or high-impact issue. Tune to your own project's + # norms — a project handling especially sensitive reports may want a + # longer window; one under heavy report volume may want it shorter. + # Consumed by: security-issue-sync (Step 1c staleness check / Step 2b proposal). + reporter_response_timeout_days: 14 ``` ### Forwarders diff --git a/skills/security-issue-sync/gather.md b/skills/security-issue-sync/gather.md index 711df3eb4..7c0327ae8 100644 --- a/skills/security-issue-sync/gather.md +++ b/skills/security-issue-sync/gather.md @@ -208,7 +208,26 @@ Process for finding the real reporter and the original thread: - the latest message in the thread, *who* sent it, and whether the ball is in our court. -5. **Sync a reporter-confirmed credit line into the issue body** whenever +5. **Check for reporter staleness.** Using the message timestamps and + senders extracted in step 4, find the security team's **most recent + outbound message** to the reporter on this thread and the reporter's + **most recent reply**, if any. If the team's latest outbound message + is older than + [`/project.md`](../..//project.md#security-inbox)'s + `reporter_response_timeout_days` (default 14) **and** no reporter + reply has landed since that message, mark the thread **stale** and + surface it in Step 2b per the *Reporter unresponsiveness* row in + [`signals-to-actions.md`](signals-to-actions.md#step-2b--proposed-changes-signal-to-action-lookup-table). + Per [ASF security policy](https://www.apache.org/security/committers.html), + an unresponsive reporter must not block the project team from moving + to the next steps, particularly for a high-severity or high-impact + issue — but this is a **proposal, not an automatic action**; the + user still confirms before the team proceeds without the reporter. + Skip this check when the latest message on the thread is *from* the + reporter (the ball is already in the security team's court) — the + staleness clock only runs while we are the ones waiting on a reply. + +6. **Sync a reporter-confirmed credit line into the issue body** whenever the mail thread contains a clear credit confirmation from the reporter that has not yet been reflected in the tracker's *"Reporter credited as"* field. This is a dedicated check, not an afterthought — reporters @@ -264,7 +283,7 @@ Process for finding the real reporter and the original thread: world, hard to correct after publication, and directly undermines the trust the reporter extended to us. -5. **If you cannot find the original thread**, say so explicitly in the +7. **If you cannot find the original thread**, say so explicitly in the proposal and ask the user whether the GitHub issue author is also the reporter (which does happen for issues a security team member discovered themselves). Do not assume. diff --git a/skills/security-issue-sync/signals-to-actions.md b/skills/security-issue-sync/signals-to-actions.md index 4524155aa..5a5561e70 100644 --- a/skills/security-issue-sync/signals-to-actions.md +++ b/skills/security-issue-sync/signals-to-actions.md @@ -1149,6 +1149,33 @@ will change and *why*. Group them by category: this tracker's thread (check via the existing Gmail draft-scan the skill performs in Step 1e before proposing a new draft). +- **Reporter unresponsiveness (proceed-without-sign-off proposal)** — + when Step 1c's staleness check + ([`gather.md` 1c step 5](gather.md#1c-find-the-real-reporter-and-read-the-mailing-list-thread)) + marks the reporter thread **stale** (the security team's latest + outbound message is older than + [`/project.md`](../..//project.md#security-inbox)'s + `reporter_response_timeout_days`, default 14, with no reporter reply + since), propose a **numbered proposal item** — not a silent + status-comment entry — with this exact shape: + + > *N.* Reporter has not replied in **`` days** — propose + > proceeding with fix and announcement without further reporter + > sign-off, per [ASF security policy](https://www.apache.org/security/committers.html). + + Substitute the actual elapsed day count for ``. This item is + purely a proposal: it does not itself flip any label, close anything, + or send anything — it asks the user to confirm that the team should + keep moving through the remaining steps (discussion, fix, release, + advisory) treating the reporter's last-known position as final, + rather than waiting on a further reply. If the user confirms, continue + normally with whichever other Step 2b items this sync pass already + proposes; do **not** hold those items back pending reporter reply once + the user has confirmed proceeding without them. Re-surface this item + on every subsequent sync pass while the thread remains stale (the + reporter may reply at any time, at which point the check in + `gather.md` no longer fires and this item stops appearing). + - **Draft email to reporter (other reasons)** — whenever the ball is in our court on the email thread for any other reason (a question from the reporter, a follow-up needed for triage, communicating a negative From a5d646a801429874c3fccd98d70c8de483b64777 Mon Sep 17 00:00:00 2001 From: Harish Kesava Rao Date: Thu, 13 Aug 2026 19:27:07 +0530 Subject: [PATCH 2/2] test(security-issue-sync): add eval fixtures for reporter-unresponsiveness escalation Regression coverage for the Step 1c staleness check and its Step 2b proposal item: case-6 asserts the proposal fires when the reporter thread is stale, case-7 asserts it stays silent when the reporter has replied (the skip condition in gather.md). Extends output-spec.md's category enum with reporter_unresponsive to make the new item type gradable. Co-Authored-By: Claude Sonnet 5 --- .../expected.json | 16 ++++++++++++ .../case-6-reporter-unresponsive/report.md | 26 +++++++++++++++++++ .../expected.json | 8 ++++++ .../report.md | 26 +++++++++++++++++++ .../fixtures/output-spec.md | 4 ++- 5 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 tools/skill-evals/evals/security-issue-sync/step-2b-proposed-changes/fixtures/case-6-reporter-unresponsive/expected.json create mode 100644 tools/skill-evals/evals/security-issue-sync/step-2b-proposed-changes/fixtures/case-6-reporter-unresponsive/report.md create mode 100644 tools/skill-evals/evals/security-issue-sync/step-2b-proposed-changes/fixtures/case-7-reporter-recently-replied/expected.json create mode 100644 tools/skill-evals/evals/security-issue-sync/step-2b-proposed-changes/fixtures/case-7-reporter-recently-replied/report.md diff --git a/tools/skill-evals/evals/security-issue-sync/step-2b-proposed-changes/fixtures/case-6-reporter-unresponsive/expected.json b/tools/skill-evals/evals/security-issue-sync/step-2b-proposed-changes/fixtures/case-6-reporter-unresponsive/expected.json new file mode 100644 index 000000000..a60c2df6b --- /dev/null +++ b/tools/skill-evals/evals/security-issue-sync/step-2b-proposed-changes/fixtures/case-6-reporter-unresponsive/expected.json @@ -0,0 +1,16 @@ +{ + "items": [ + { + "number": 1, + "category": "reporter_unresponsive", + "action": "Reporter has not replied in 26 days — propose proceeding with fix and announcement without further reporter sign-off, per ASF security policy.", + "reason": "reporter_thread_stale is true — the security team's last outbound message (2026-05-20) is 26 days old, exceeding reporter_response_timeout_days (14), with no reporter reply since." + } + ], + "labels_to_add": [], + "labels_to_remove": [], + "milestone_create_needed": false, + "assignee_proposed": null, + "has_bare_issue_numbers": false, + "reporter_unresponsive_proposed": true +} diff --git a/tools/skill-evals/evals/security-issue-sync/step-2b-proposed-changes/fixtures/case-6-reporter-unresponsive/report.md b/tools/skill-evals/evals/security-issue-sync/step-2b-proposed-changes/fixtures/case-6-reporter-unresponsive/report.md new file mode 100644 index 000000000..1e3f040c8 --- /dev/null +++ b/tools/skill-evals/evals/security-issue-sync/step-2b-proposed-changes/fixtures/case-6-reporter-unresponsive/report.md @@ -0,0 +1,26 @@ + + +## Observed tracker state + +**Tracker:** airflow-s/airflow-s#341 +**Current labels:** security issue, providers, cve allocated +**Current milestone:** Providers 2026.6.1 (exists on tracker) +**Current assignee:** (none) + +## Gathered state from Step 1 + +**PR with the fix:** (none — no fix PR opened yet) +**Process step:** 7 (cve allocated, pending fix) + +**Reporter thread status (Step 1c):** +- `reporter_response_timeout_days: 14` (from `projects/airflow-s/project.md` → Security inbox; default) +- Security team's last outbound message: 2026-05-20 — "Thanks for the report, we've allocated + CVE-2026-31842 and are working on an allowlist-based fix now." +- Reporter's last reply: none since that message +- Today: 2026-06-15 +- `elapsed_days_since_last_outbound: 26` +- `reporter_thread_stale: true` (26 > 14, no reporter reply since; latest thread message is + from the security team, not the reporter) + +Produce the numbered proposal for this tracker. diff --git a/tools/skill-evals/evals/security-issue-sync/step-2b-proposed-changes/fixtures/case-7-reporter-recently-replied/expected.json b/tools/skill-evals/evals/security-issue-sync/step-2b-proposed-changes/fixtures/case-7-reporter-recently-replied/expected.json new file mode 100644 index 000000000..bd9244257 --- /dev/null +++ b/tools/skill-evals/evals/security-issue-sync/step-2b-proposed-changes/fixtures/case-7-reporter-recently-replied/expected.json @@ -0,0 +1,8 @@ +{ + "items": [], + "labels_to_add": [], + "labels_to_remove": [], + "milestone_create_needed": false, + "assignee_proposed": null, + "has_bare_issue_numbers": false +} diff --git a/tools/skill-evals/evals/security-issue-sync/step-2b-proposed-changes/fixtures/case-7-reporter-recently-replied/report.md b/tools/skill-evals/evals/security-issue-sync/step-2b-proposed-changes/fixtures/case-7-reporter-recently-replied/report.md new file mode 100644 index 000000000..fafac6c5c --- /dev/null +++ b/tools/skill-evals/evals/security-issue-sync/step-2b-proposed-changes/fixtures/case-7-reporter-recently-replied/report.md @@ -0,0 +1,26 @@ + + +## Observed tracker state + +**Tracker:** airflow-s/airflow-s#342 +**Current labels:** security issue, providers, cve allocated +**Current milestone:** Providers 2026.6.1 (exists on tracker) +**Current assignee:** (none) + +## Gathered state from Step 1 + +**PR with the fix:** (none — no fix PR opened yet) +**Process step:** 7 (cve allocated, pending fix) + +**Reporter thread status (Step 1c):** +- `reporter_response_timeout_days: 14` (from `projects/airflow-s/project.md` → Security inbox; default) +- Security team's last outbound message: 2026-06-01 — "We've allocated CVE-2026-31855 and are + scoping the fix now — will follow up with a timeline." +- Reporter's last reply: 2026-06-05 — "Thanks, that timeline works for us." +- Today: 2026-06-15 +- The latest message on the thread is **from the reporter** (2026-06-05), so the ball is in the + security team's court — the Step 1c staleness check does not run. +- `reporter_thread_stale: false` + +Produce the numbered proposal for this tracker. diff --git a/tools/skill-evals/evals/security-issue-sync/step-2b-proposed-changes/fixtures/output-spec.md b/tools/skill-evals/evals/security-issue-sync/step-2b-proposed-changes/fixtures/output-spec.md index 71505e3b8..e12b02d01 100644 --- a/tools/skill-evals/evals/security-issue-sync/step-2b-proposed-changes/fixtures/output-spec.md +++ b/tools/skill-evals/evals/security-issue-sync/step-2b-proposed-changes/fixtures/output-spec.md @@ -12,7 +12,7 @@ The observed state (current tracker) vs. gathered state (PR, milestone, assignee "items": [ { "number": 1, - "category": "labels" | "milestone" | "assignee" | "body_field" | "status_comment", + "category": "labels" | "milestone" | "assignee" | "body_field" | "status_comment" | "reporter_unresponsive", "action": "", "reason": "" } @@ -32,9 +32,11 @@ Field rules: - **Assignee:** propose an assignee only at the PR-merged hand-off step, and only when the tracker currently has no assignee. While the fix PR is still open, propose no assignee (`assignee_proposed` is `null`). - **`status_comment` items** fire only on specific triggers, never to narrate routine progress. At the `pr created` → `pr merged` transition (process step 11), propose a fill-fields status comment ONLY if one of the six mandatory body fields — CWE, Affected versions, Severity, Reporter credited as, Short public summary for publish, PR with the fix — is empty or `_No response_`. If all six are populated (or the gathered state does not flag any missing), propose no status comment for the transition. (Disclosure-overdue and distributor pre-announcement status comments keep their own separate triggers.) - **No speculative labels.** Add `cve allocated` only once a CVE has actually been allocated; do not add `pr created` / `pr merged` before the process reaches those transitions. At an assessed / pre-CVE tracker (process step ~6), the usual sole change is the milestone — propose no label add or remove. + - **`reporter_unresponsive` items** fire only when the gathered state's Step 1c reporter-thread check reports the thread stale (the security team's last outbound message is older than `reporter_response_timeout_days`, default 14, with no reporter reply since). Propose it as its own numbered item, never folded into a `status_comment` item. Propose no such item when the thread is not stale, or when the latest message on the thread is from the reporter (the staleness check does not run while the ball is in the reporter's court). - `labels_to_add` / `labels_to_remove`: flat lists of all label changes across all items. - `milestone_create_needed`: `true` when the required milestone does not yet exist on the tracker and a `gh api` create call is included in the proposal. - `assignee_proposed`: the GitHub handle proposed for assignee, or `null`. Only propose a security-team collaborator, never an external reporter. - `has_bare_issue_numbers`: `true` if any item contains a bare `#NNN` without a full URL — should be `false`. - `overdue_for_disclosure_proposed`: `true` when `items` contains an escalation item due to the CVD window expiring. Optional field — omit (or set `false`) when the tracker is not overdue. - `distributor_notify_proposed`: `true` when `items` contains a distributor pre-announcement draft proposal. Optional field — omit (or set `false`) when `pre_announce_distributors` is `false` or the fix is not yet in a pending release. +- `reporter_unresponsive_proposed`: `true` when `items` contains a `reporter_unresponsive` item. Optional field — omit (or set `false`) when the reporter thread is not stale.