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
620 changes: 620 additions & 0 deletions .claude/skills/security-issue-import-via-forwarder/SKILL.md

Large diffs are not rendered by default.

114 changes: 54 additions & 60 deletions .claude/skills/security-issue-import/SKILL.md

Large diffs are not rendered by default.

93 changes: 58 additions & 35 deletions .claude/skills/security-issue-invalidate/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -545,23 +545,34 @@ named explicitly** in the Step 5e rollup terminal entry:
call above returns 403, or the operator is running from a
triager account that does not hold GHSA-write membership.
In that case the GHSA channel is **not** self-sufficient;
the closure must be relayed via ASF Security so Arnout
Engelen (or another `@apache.org` forwarder with the
required GHSA-write permissions) can post the closure
comment / state-change on our behalf. Draft an
ASF-relay-shape message per
[`tools/gmail/asf-relay.md`](../../../tools/gmail/asf-relay.md):
recipient is `engelen@apache.org` (or the named forwarder
who relayed the original GHSA report); body includes the
clickable GHSA URL on its own line + a paste-ready block
in the reporter's voice with the invalid-disposition
rationale + canonical CVE-ID (when `duplicate`) for the
forwarder to post on the GHSA. Record in the rollup
terminal entry: *"GHSA-relay-only reporter channel
(GHSA-XXXX-XXXX-XXXX); operator lacks GHSA-write access on
`<upstream>`. ASF-relay draft `<draftId>` queued to
`<forwarder@apache.org>` requesting they post the closure
comment on the GHSA on our behalf — awaiting user review."*
the closure must be relayed via a forwarder with the
required GHSA-write permissions so they can post the
closure comment / state-change on our behalf. If the
parent tracker was imported via a forwarder adapter (per
the optional
[`security-issue-import-via-forwarder`](../security-issue-import-via-forwarder/SKILL.md)
sub-skill — i.e. when `forwarders.enabled` is non-empty in
`<project-config>/project.md` and a registered adapter
applies), route the drafted message through that adapter's
`contact_handle` and use the adapter's
`reporter_addressing_block` convention. See
[`tools/forwarder-relay/README.md`](../../../tools/forwarder-relay/README.md)
for the contract. The drafted body includes the clickable
GHSA URL on its own line + a paste-ready block in the
reporter's voice with the invalid-disposition rationale +
canonical CVE-ID (when `duplicate`) for the forwarder to
post on the GHSA. Worked example: for an `airflow-s`
adopter with the `asf-security` forwarder enabled, the
adapter resolves the contact to `engelen@apache.org` (or
the named `@apache.org` forwarder who relayed the original
GHSA report) and the paste-ready block follows the
[`tools/gmail/asf-relay.md`](../../../tools/gmail/asf-relay.md)
shape. Record in the rollup terminal entry: *"GHSA-relay-only
reporter channel (GHSA-XXXX-XXXX-XXXX); operator lacks
GHSA-write access on `<upstream>`. Forwarder-relay draft
`<draftId>` queued to `<forwarder-contact>` requesting they
post the closure comment on the GHSA on our behalf —
awaiting user review."*

For every other `security@`-imported tracker, the invalidation
reply is one of the five
Expand All @@ -575,16 +586,26 @@ the **recipient** and the **body shape**.
`tracker.reporterEmail` (the `From:` of the inbound root
message). The reply lands on the inbound thread via thread
attachment.
- **Via-forwarder mode** (ASF-security relay or any other case
in the [policy's detection list](../../../docs/security/forwarder-routing-policy.md#when-does-via-forwarder-mode-apply)):
`toRecipients` is the **forwarder contact** (the
`@apache.org` forwarder address from the inbound `From:` for
ASF-relay, or the named contact from the explicit
no-direct-contact marker comment). The body follows the
- **Via-forwarder mode** (the parent tracker was imported via
a forwarder adapter — see the optional
[`security-issue-import-via-forwarder`](../security-issue-import-via-forwarder/SKILL.md)
sub-skill and the
[policy's detection list](../../../docs/security/forwarder-routing-policy.md#when-does-via-forwarder-mode-apply)):
`toRecipients` is the **forwarder contact** resolved via the
matching adapter's `contact_handle` per
[`tools/forwarder-relay/README.md`](../../../tools/forwarder-relay/README.md)
(or the named contact from an explicit no-direct-contact
marker comment on the tracker). The body follows the
adapter's `reporter_addressing_block` convention and the
*Report assessed as invalid* milestone-body shape in the
policy doc — short, references the external identifier (GHSA
ID, HackerOne URL) rather than restating the technical
detail.
policy doc — short, references the external identifier
(GHSA ID, HackerOne URL) rather than restating the
technical detail. Worked example: for an `airflow-s` adopter
with the `asf-security` forwarder enabled, the adapter
resolves the contact to the `@apache.org` forwarder address
from the inbound `From:` and the paste-ready reporter block
follows the [`tools/gmail/asf-relay.md`](../../../tools/gmail/asf-relay.md)
shape.
- `ccRecipients`: always includes `<security-list>`
(`<security-list>` for the adopting project) —
value comes from
Expand Down Expand Up @@ -612,13 +633,15 @@ the **recipient** and the **body shape**.
body MUST name the canonical `CVE-YYYY-NNNNN` ID
verbatim — e.g. *"This is the same root cause as
`CVE-2026-XXXXX` which we already track and ship the fix
for in `apache-airflow` X.Y.Z."* This lets the ASF
Security team's dedup workflow group the two threads
(per Arnout Engelen's 2026-05-29 ASF-Security ask in the
Kyuubi SSRF context). For via-forwarder mode this
additionally goes inside the *paste-ready block in the
reporter's voice* per the
[asf-relay.md shape](../../../tools/gmail/asf-relay.md).
for in `apache-airflow` X.Y.Z."* This lets a forwarder's
dedup workflow group the two threads (worked example: the
ASF Security team's dedup workflow groups by canonical
CVE-ID, per Arnout Engelen's 2026-05-29 ASF-Security ask
in the Kyuubi SSRF context). For via-forwarder mode this
additionally goes inside the adapter's paste-ready
reporter-voice block per the matching adapter's
`reporter_addressing_block` convention — see
[`tools/forwarder-relay/README.md`](../../../tools/forwarder-relay/README.md).
- **Polite-but-firm.** Per
[`AGENTS.md`](../../../AGENTS.md#tone-polite-but-firm--no-room-to-wiggle), state
the team's position once, clearly, with reasoning. Do not
Expand Down Expand Up @@ -661,11 +684,11 @@ upsert recipe). Shape:

**Reporter notification:** <one of — required line, never omit:>
- **`security@`-imported, direct-reporter mode:** Gmail draft `<draftId>` created on thread `<threadId>` anchored at message `<messageId>` — awaiting user review.
- **`security@`-imported, via-forwarder mode (ASF-relay):** ASF-relay draft `<draftId>` to `<forwarder@apache.org>` on thread `<threadId>` per [`asf-relay.md`](https://github.com/apache/airflow-steward/blob/main/tools/gmail/asf-relay.md) shape (clickable URL + paste-ready reporter-voice block) — awaiting user review.
- **`security@`-imported, via-forwarder mode:** Forwarder-relay draft `<draftId>` to `<forwarder-contact>` on thread `<threadId>` per the matching adapter's `reporter_addressing_block` convention (clickable URL + paste-ready reporter-voice block) — awaiting user review. For an `airflow-s` adopter with the `asf-security` forwarder enabled, the contact resolves to an `@apache.org` forwarder and the block follows the [`tools/gmail/asf-relay.md`](https://github.com/apache/airflow-steward/blob/main/tools/gmail/asf-relay.md) shape.
- **`security@`-imported, `duplicate` disposition:** *(same as direct or via-forwarder above; the draft body MUST name the canonical CVE-ID per Step 5d).*
- **No notification owed — internal audit finding:** Tracker imported from project-internal markdown audit (`<source-markdown>`), no inbound `security@` thread, no reporter to notify.
- **No Gmail draft owed — GHSA-relay-only, operator has GHSA-write access:** GHSA-relay-only reporter channel (`GHSA-XXXX-XXXX-XXXX`); closure communicated as GHSA comment `<URL>` / advisory state set to `<withdrawn|informational>`. No Gmail reply needed.
- **ASF-relay draft owed — GHSA-relay-only, operator lacks GHSA-write access:** GHSA-relay-only channel (`GHSA-XXXX-XXXX-XXXX`); operator's account does not have GHSA-write on `<upstream>`. ASF-relay draft `<draftId>` queued to `<forwarder@apache.org>` requesting they post the closure comment on the GHSA on our behalf — awaiting user review.
- **Forwarder-relay draft owed — GHSA-relay-only, operator lacks GHSA-write access:** GHSA-relay-only channel (`GHSA-XXXX-XXXX-XXXX`); operator's account does not have GHSA-write on `<upstream>`. Forwarder-relay draft `<draftId>` queued to `<forwarder-contact>` requesting they post the closure comment on the GHSA on our behalf — awaiting user review.
- **PR-imported:** none (no reporter; per [Reporter credit policy](https://github.com/<tracker>/blob/<tracker-default-branch>/.claude/skills/security-issue-import-from-pr/SKILL.md#reporter-credit-policy-for-public-pr-imports)).
- **Indeterminate import path:** none (flag from Step 2 surfaced; user explicitly chose silent close).

Expand Down
40 changes: 19 additions & 21 deletions .claude/skills/security-issue-sync/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -2335,27 +2335,25 @@ will change and *why*. Group them by category:
artifact link. See the "Brevity: emails state facts, not context"
section of [`AGENTS.md`](../../../AGENTS.md).

**Apply the [forwarder-routing policy](../../../docs/security/forwarder-routing-policy.md)
to decide whether to propose the draft at all.** Run the detection
rules in the policy doc to determine the tracker's routing mode:

* **Direct-reporter mode** — proceed as written above; the draft
targets the reporter on the inbound thread.
* **Via-forwarder mode + event is on the [milestone list](../../../docs/security/forwarder-routing-policy.md#milestones--do-relay)**
(report accepted as valid, CVE allocated, advisory sent,
invalidation, or a specific *"we need additional information"*
question) — propose the draft to the **forwarder contact**, not
the reporter, using the short milestone-body shape from the
policy doc. Reference the external identifier (GHSA ID,
HackerOne URL, internal ticket number) rather than repeating
the technical detail of the report.
* **Via-forwarder mode + event is NOT on the milestone list**
(regular workflow status, credit-form questions, reviewer-
comment relays) — **suppress the draft entirely**. Record in
the proposal recap *"skipped reporter draft: `<event>` not on
the via-forwarder milestone list"* so the user can see why
no message was proposed. The forwarder is not pinged with
low-signal updates.
**Route through the forwarder-relay adapter when one is registered.**
If the parent tracker carries a forwarder-adapter marker (set by
the optional
[`security-issue-import-via-forwarder`](../security-issue-import-via-forwarder/SKILL.md)
sub-skill when `forwarders.enabled` is non-empty in
[`<project-config>/project.md`](../../../<project-config>/project.md)
and the inbound message matched a registered adapter), route any
drafted reply through that adapter's `contact_handle` and use the
adapter's `reporter_addressing_block` convention. See
[`tools/forwarder-relay/README.md`](../../../tools/forwarder-relay/README.md)
for the contract — including the per-event do-relay / suppress
matrix the adapter applies to decide whether a draft should be
proposed at all (e.g. CVE-allocated and advisory-sent events
relay; routine credit-form questions and reviewer-comment relays
are suppressed). When no adapter is registered (the
`forwarders.enabled` list is empty, or the tracker has no
forwarder-adapter marker), proceed in direct-reporter mode as
written above — the draft targets the reporter on the inbound
thread.

**Never send.** Always create a draft. Prefer attaching it to the
inbound mail thread (the default `claude_ai_mcp` backend resolves
Expand Down
1 change: 1 addition & 0 deletions docs/labels-and-capabilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ Capabilities for every skill currently in
| `security-issue-import` | `capability:intake` |
| `security-issue-import-from-md` | `capability:intake` |
| `security-issue-import-from-pr` | `capability:intake` |
| `security-issue-import-via-forwarder` | `capability:intake` |
| `security-issue-sync` | `capability:intake` *(+ `capability:reconciliation` once [#337](https://github.com/apache/airflow-steward/issues/337) lands the ASF-dashboard step)* |
| `setup-shared-config-sync` | `capability:intake` + `capability:setup` *(reconciles user-scope config to a sync repo; the act is intake, the subject is setup)* |
| `security-cve-allocate` | `capability:resolve` |
Expand Down
24 changes: 24 additions & 0 deletions tools/forwarder-relay/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
- [tools/forwarder-relay/ — adapter contract](#toolsforwarder-relay--adapter-contract)
- [What "a relay message" means](#what-a-relay-message-means)
- [Today's adapters](#todays-adapters)
- [Sub-skill consumers](#sub-skill-consumers)
- [Interface](#interface)
- [`detect(message) -> adapter_name | null`](#detectmessage---adapter_name--null)
- [`extract_credit(body) -> {name, kind, raw_string} | null`](#extract_creditbody---name-kind-raw_string--null)
Expand Down Expand Up @@ -100,6 +101,29 @@ support, they implement an adapter directory under
below, and add `<name>` to the `forwarders.enabled` list in
their `<project-config>/project.md`.

The ASF-security adapter's `preamble_match` regex,
`credit_extraction_rule`, `contact_handle` (the `@raboof`
default, lifted into project.md
`forwarders.asf-security.contact_handle`), and
`reporter_addressing_block` convention all live in
[`tools/gmail/asf-relay.md`](../gmail/asf-relay.md). This is
the only forwarder adapter shipping today; the contract above
describes the interface for additional adapters.

### Sub-skill consumers

ASF adopters install the optional sub-skill
[`security-issue-import-via-forwarder`](../../.claude/skills/security-issue-import-via-forwarder/SKILL.md)
to enable forwarder-aware handling. The sub-skill consumes the
`forwarders.enabled` config knob from
[`<project-config>/project.md`](../../projects/_template/project.md)
and runs after the main classification cascade in
`security-issue-import`, `security-issue-invalidate`, and
`security-issue-sync`. Generic skill bodies no longer carry
the ASF-relay row inlined in their main classification tables
— they reference the sub-skill as the *"follow this if
forwarder mode is enabled"* extension instead.

## Interface

A forwarder-relay adapter exposes the following operations. Skills
Expand Down
19 changes: 19 additions & 0 deletions tools/mail-archive/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,25 @@ placeholder above is named, with a one-paragraph justification, so
that an adopter who needs that backend can author the adapter
without re-inventing the contract.

The PonyMail adapter's `search_thread_url` template,
`fetch_thread_by_url` recipes, `list_recent_threads` filter, and
`publication_signal_url` all live in
[`tools/ponymail/`](../ponymail/). This is the only mail-archive
adapter shipping today; the contract above describes the interface
for additional adapters (Hyperkitty / Discourse / Google Groups /
GitHub Discussions / none).

The skills that consume this contract today are:

- [`security-issue-import`](../../.claude/skills/security-issue-import/SKILL.md)
— PonyMail URL construction at receipt time (Step 5: per-month
search URL + per-thread permalink verification).
- [`security-issue-sync`](../../.claude/skills/security-issue-sync/SKILL.md)
— Step 1c / 1e / 1h / 2b — thread lookup and advisory-published
signal scan.
- [`security-issue-invalidate`](../../.claude/skills/security-issue-invalidate/SKILL.md)
— relay-thread search for the closing-reply step.

## Interface

Every adapter exposes the verbs below. Each verb declares:
Expand Down
Loading