diff --git a/.claude/skills/security-issue-fix/SKILL.md b/.claude/skills/security-issue-fix/SKILL.md index c2e1b255..510d4aa6 100644 --- a/.claude/skills/security-issue-fix/SKILL.md +++ b/.claude/skills/security-issue-fix/SKILL.md @@ -807,7 +807,7 @@ If the query returns nothing, **propose creating the milestone**: ```bash # Write tool: file_path: /tmp/ms-title.txt, content: -# Write tool: file_path: /tmp/ms-desc.txt, content: Airflow release tracking. +# Write tool: file_path: /tmp/ms-desc.txt, content: release tracking. gh api repos//milestones \ -F title=@/tmp/ms-title.txt \ -f state=open \ diff --git a/.claude/skills/security-issue-import-via-forwarder/SKILL.md b/.claude/skills/security-issue-import-via-forwarder/SKILL.md index 5cf9378b..fa233b24 100644 --- a/.claude/skills/security-issue-import-via-forwarder/SKILL.md +++ b/.claude/skills/security-issue-import-via-forwarder/SKILL.md @@ -393,12 +393,12 @@ the skill produces: 1. **`to_recipients`** — the matched adapter's `contact_handle`, read from the adopter's [`/project.md → forwarders..contact_handle`](../../..//project.md#forwarders). - For the ASF-default adapter this is the security-team liaison - (currently `@raboof`, with a rota fallback when configured); - for huntr.com it would be huntr's program contact; for - HackerOne it would be the assigned triager. The adapter MAY - return a list of fallbacks — pick the first available one and - surface the chosen handle in the recap. + For the ASF-default `asf-security` adapter this is the + configured security-team liaison handle (with a rota fallback + when configured); for huntr.com it would be huntr's program + contact; for HackerOne it would be the assigned triager. The + adapter MAY return a list of fallbacks — pick the first + available one and surface the chosen handle in the recap. 2. **`addressing_block`** — the paste-ready block rendered by the adapter's `reporter_addressing_block()` per @@ -406,9 +406,10 @@ the skill produces: Parameters passed in: - `forwarder_first_name` — derived from the adapter's - `contact_handle` (the first-name part — for `@raboof`, - *"Arnout"*). When the handle is a list, use the first - available contact's first name. + `contact_handle` (the first-name part — e.g. for a handle + like `@some-liaison`, the first name *"Some"* derived from + the GitHub profile). When the handle is a list, use the + first available contact's first name. - `reporter_first_name` — the first-name part of the credit extracted at Step 2. Empty when Step 2 returned `null`; the adapter's wrapper falls back to a generic salutation diff --git a/.claude/skills/security-issue-import/SKILL.md b/.claude/skills/security-issue-import/SKILL.md index 6db8c2c5..8efb7784 100644 --- a/.claude/skills/security-issue-import/SKILL.md +++ b/.claude/skills/security-issue-import/SKILL.md @@ -552,8 +552,9 @@ user as a candidate in Step 5. Record a one-line entry in the recap's `dropped` section so the user knows the filter fired: > Dropped `19d2f402867e957e` *(already answered on-thread -> 2026-03-28 by @potiuk with the DoS-by-authenticated-users -> canned response; reporter silent since)*. +> 2026-03-28 by `` with the +> DoS-by-authenticated-users canned response; reporter silent +> since)*. **When to stay cautious.** If the team reply does not match a canned-response shape cleanly — e.g. the team member wrote a @@ -1059,7 +1060,7 @@ if not, fall through to the table below. | **Automated scanner dump**: SAST/DAST tool output, CodeQL/Dependabot alert paste, a string of "issues" with no human PoC | Body is machine-generated, contains multiple unrelated findings, no explanation of Security Model violation | Surface as a candidate with class `automated-scanner` and **do not** propose auto-import. In Step 5 the skill proposes a Gmail draft from the *"Automated scanning results"* canned response in [`canned-responses.md`](../../..//canned-responses.md) instead. | | **Consolidated multi-issue report**: one email bundles ≥3 unrelated vulnerabilities | The root message has headings like *"Issue 1"*, *"Issue 2"*, each of which would be its own tracker | Surface class `consolidated-multi-issue`; do not auto-import. Propose the "Sending multiple issues in consolidated report" canned reply. | | **Media / research-disclosure request**: reporter wants to publish a blog or talk about a finding we already know about | Body asks about disclosure timing, mentions a talk / blog / CVE on another vendor | Surface class `media-request`; do not auto-import. Propose the "When someone submits a media report" canned reply. | -| **Obvious spam / scam / phishing / crypto-scheme** | Cryptocurrency addresses, "bug bounty program" framing on a project that does not have one, no actual Airflow-specific content | Surface class `spam`; propose no action (user deletes in Gmail). | +| **Obvious spam / scam / phishing / crypto-scheme** | Cryptocurrency addresses, "bug bounty program" framing on a project that does not have one, no actual ``-specific content | Surface class `spam`; propose no action (user deletes in Gmail). | | **Follow-up on existing thread that Step 2 missed** | Root message mentions a CVE already allocated, or the body is *"re: "* but with a new threadId because the reporter replied from a different address | Surface class `cross-thread-followup`; do not auto-import. Propose a comment on the existing tracker instead. | | **Already fixed by a public PR** | Step 2c surfaced a STRONG match: a public PR in `` (open or merged, **not** filed in response to this report) already appears to fix the reported behaviour. The reporter sent `` independently. | Surface class `fix-already-public`; **do not** create a tracker. Propose a thank-without-credit Gmail draft per the [no-credit-when-fix-is-already-public policy](../security-issue-import-from-pr/SKILL.md#reporter-credit-policy-for-public-pr-imports): thank the reporter, point at the PR, ask them to verify the PR fixes their report, and ask them to come back if it does not. Reply shape is in Step 5; the draft is sent in Step 7 only if the user confirms. **If the reporter later replies saying the PR does not fix their report**, that reply will re-surface in the next skill run (a new thread message will be detected); at that point classify as `Report` and import for proper triage. | @@ -1122,7 +1123,7 @@ here. |---|---| | **The issue description** | The root email body, **verbatim** (preserve paragraphs, PoC code blocks, and any quoted sections). The body is private — the triager will copy it into a public CVE description only after Step 13. | | **Short public summary for publish** | Leave `_No response_`. Filled by the release manager at Step 13 in sanitised form. | -| **Affected versions** | Extract `Airflow ` / `>= X, < Y` / ` ` / `>= X, < Y` / `` for the airflow-s adopter). If the reporter gave only a single version they tested on (e.g. `3.1.5`), record that verbatim; the triager can widen the range later. Leave `_No response_` if no version is mentioned. | | **Security mailing list thread** | **Keep the private thread handle, and — if possible — also link the PonyMail archive entry.** The full URL-construction recipe (search URL template, month-token format, user-pastes-back flow, Gmail-threadId fallback) lives in [`tools/gmail/ponymail-archive.md`](../../../tools/gmail/ponymail-archive.md#use-case--security-issue-import); the adopting project's private-search URL template is declared in [`/project.md`](../../..//project.md#gmail-and-ponymail). Propose the constructed search URL to the user at Step 5, wait for them to paste back the resolved `lists.apache.org/thread/?` URL, and record both the PonyMail URL and the Gmail `threadId` in this field. The URL is **internal-only** — the `generate-cve-json` script will not export it to `references[]` — see the "CVE references must never point at non-public mailing-list threads" section of [`AGENTS.md`](../../../AGENTS.md). | | **Public advisory URL** | `_No response_`. Populated at Step 14 by `security-issue-sync` once the advisory is archived. | | **Reporter credited as** | The reporter's full display name from the email `From:` header (e.g. `Alice Example` from `"Alice Example" `). This is a **placeholder** — in direct-reporter mode, the receipt-of-confirmation reply in Step 7 asks the reporter to confirm their preferred credit form. **Apply the [bot/AI credit policy](../../../tools/cve-tool-vulnogram/bot-credits-policy.md) before populating** — if the `From:`-header name or address matches the bot detection rule (`*[bot]` suffix, known-bot list, `*-bot`/`*-ai`/`*-agent`/`*-gpt` suffix patterns, `noreply`/`no-reply`/`donotreply` / `security-alerts@` / `notifications@` service sender), **include** the detected name in the field (the CVE JSON generator emits it with `type: "tool"` per the policy's finder-side rule) and surface *"credited as tool: `` (matches bot policy — ``)"* in Step 5's proposal. Service-sender addresses (noreply / relays) are still suppressed from the field — they are routing artefacts, not identities; extract the real reporter from the email body instead. **In direct-reporter mode**, also fold the policy's *clarification-reply* into the Step 7 receipt-of-confirmation draft, asking whether a human behind the bot/AI handle should be **additionally** credited as finder (the tool credit stands either way). **In via-forwarder mode** (when the optional [`security-issue-import-via-forwarder`](../security-issue-import-via-forwarder/SKILL.md) sub-skill pre-classified the candidate via a registered forwarder adapter — for the ASF adopter this is the `asf-security` adapter — and the other cases enumerated in [`docs/security/forwarder-routing-policy.md`](../../../docs/security/forwarder-routing-policy.md#when-does-via-forwarder-mode-apply)), the **standalone** bot-credit clarification draft is suppressed — it is a credit-acceptance confirmation message, which the forwarder cannot meaningfully answer. The credit *question* itself is **not** suppressed: it folds as a single best-effort *"if a human was behind the tool, please pass back their preferred attribution"* line into the Step 7 receipt-of-confirmation draft instead, per the [question-vs-confirmation distinction](../../../docs/security/forwarder-routing-policy.md#negative-space--do-not-relay) in the forwarder-routing policy. The same bot-detection rule applies to the forwarder adapter's `extract_credit()` output (the detection runs on the relayed credit string, not on the forwarder's sender address); see [`tools/forwarder-relay/README.md`](../../../tools/forwarder-relay/README.md) for the adapter contract. The user can override per the policy doc. | diff --git a/.claude/skills/security-issue-invalidate/SKILL.md b/.claude/skills/security-issue-invalidate/SKILL.md index 078ea43a..904bda91 100644 --- a/.claude/skills/security-issue-invalidate/SKILL.md +++ b/.claude/skills/security-issue-invalidate/SKILL.md @@ -949,7 +949,7 @@ Tracker `#244` (*DAG author RCE on webserver via unrestricted import_string() in BaseSerialization.deserialize()*), import path: `security@`-imported. Step 3 mines five comments arguing the dag author is already trusted (with quotes from -@potiuk and @ephraimbuddy). Canned: *When someone claims Dag +two security-team members). Canned: *When someone claims Dag author-provided "user input" is dangerous*. Email draft created on thread `` with the canned spine + augmentation quoting the team's specific reasoning. Tracker closed as diff --git a/docs/security/forwarder-routing-policy.md b/docs/security/forwarder-routing-policy.md index 3a5003e0..55c69f09 100644 --- a/docs/security/forwarder-routing-policy.md +++ b/docs/security/forwarder-routing-policy.md @@ -22,9 +22,13 @@ reporter** — there is no individual reporter email, GitHub Private Reporting access is read-only, or the report arrived through a forwarding service — the skills route reporter-facing communication through whoever delivered the report to us instead (the *forwarder* -— typically the Apache Security team relaying via -`security@apache.org`, or an internal security-team member who -opened the tracker on someone else's behalf). +— typically a relay broker addressable at +`forwarders..contact_handle` per +[`/project.md`](../../projects/_template/project.md), +or an internal security-team member who opened the tracker on +someone else's behalf). The ASF-Airflow default has the ASF Security +team relaying via `` (the +`foundation_security_address` shared inbox). In that **"via-forwarder" mode**, only **important milestones** are relayed. Regular workflow chatter and credit-confirmation @@ -39,18 +43,21 @@ via-forwarder mode applies and *what* gets relayed. The mode applies to a tracker when **any** of the following is true: -1. **ASF-security relay.** The inbound report came from - `security@apache.org` with the ASF forwarding preamble; the - original reporter is not addressable directly on the relayed - thread. The personal `@apache.org` address of the forwarding - security-team member is the **forwarder contact**. (See - [`tools/gmail/asf-relay.md`](../../tools/gmail/asf-relay.md) for - the detection mechanics.) +1. **Forwarder-adapter relay.** The inbound report came from one + of the adapters enabled in `forwarders.enabled` with that + adapter's preamble; the original reporter is not addressable + directly on the relayed thread. The relay broker's personal + address (or, where the adapter declares it, the adapter's + `contact_handle`) is the **forwarder contact**. The ASF-Airflow + default is the `asf-security` adapter at + [`tools/forwarder-relay/README.md`](../../tools/forwarder-relay/README.md), + with the per-message detection mechanics described in + [`tools/gmail/asf-relay.md`](../../tools/gmail/asf-relay.md). 2. **GitHub Private Reporting we cannot reply on.** A GHSA-style private report we have read access to but can't post comments on as a security team. Whoever made us aware of the GHSA - (typically the same Apache Security team member, or an internal - escalation thread) is the forwarder. + (typically the same relay broker, or an internal escalation + thread) is the forwarder. 3. **`security-issue-import-from-md`-imported tracker.** The tracker came from a markdown file (AI scan / third-party scan output) with no inbound reporter at all. There is no reporter to @@ -93,8 +100,10 @@ on their behalf) would actually want to hear about: | **Additional information requested** | Any skill that needs a specific clarification from the reporter (re-reproduction steps, attack-vector clarification, affected-version range) | *"We need additional information to assess the report you forwarded: ``. If you can relay this to the original reporter and pass back a reply, that would help us land a decision."* | The drafts go to the **forwarder contact**, not to the relay list -address — same rule as the existing ASF-relay flow in -[`tools/gmail/asf-relay.md`](../../tools/gmail/asf-relay.md). The +address — same rule as the forwarder-relay adapter contract in +[`tools/forwarder-relay/README.md`](../../tools/forwarder-relay/README.md) +(ASF-Airflow default: the `asf-security` adapter detailed in +[`tools/gmail/asf-relay.md`](../../tools/gmail/asf-relay.md)). The body is short, references the external identifier (GHSA ID, HackerOne URL, internal ticket number) when one exists, and never re-states the technical detail of the report. @@ -109,16 +118,18 @@ suppression. * **CVE allocated** ([`security-cve-allocate`](../../.claude/skills/security-cve-allocate/SKILL.md) - Step 4 #5). Vulnogram typically emits its own allocation - notification when the CVE record is created, and even when - it doesn't, the team owes the reporter (or their forwarder) - a single short notification at this point regardless of - routing mode. The notification lands on whatever thread the - tracker's *Security mailing list thread* field resolves to; - in via-forwarder mode that is the relay thread, so the same - draft reaches the forwarder without any policy-specific - re-routing. The credit-preference question is still - suppressed in via-forwarder mode (per the + Step 4 #5). The `` adapter typically emits its own + allocation notification when the CVE record is created + (`cve_authority.emits_allocation_email: true` for the + ASF-Airflow Vulnogram default), and even when it doesn't, the + team owes the reporter (or their forwarder) a single short + notification at this point regardless of routing mode. The + notification lands on whatever thread the tracker's *Security + mailing list thread* field resolves to; in via-forwarder mode + that is the relay thread, so the same draft reaches the + forwarder without any policy-specific re-routing. The + credit-preference question is still suppressed in via-forwarder + mode (per the [Negative space](#negative-space--do-not-relay) section below) but the rest of the CVE-allocated notification still fires. @@ -200,19 +211,33 @@ applies one of three behaviours: The detection runs once per skill invocation; subsequent dispatch through the skill is consistent for that run. +The detection itself is the responsibility of the optional sub-skill +[`security-issue-import-via-forwarder`](../../.claude/skills/security-issue-import-via-forwarder/SKILL.md), +which dispatches through whichever adapter from `forwarders.enabled` +matches the inbound message (per +[`tools/forwarder-relay/README.md`](../../tools/forwarder-relay/README.md)). +Skills load the sub-skill only when `forwarders.enabled` is +non-empty in `/project.md`; when the list is empty, +via-forwarder mode falls back to the marker-comment escape hatch and +the `security-issue-import-from-md` import case. + ## Worked examples -**ASF-relayed GHSA report, advisory sent.** A report arrives via -`security@apache.org` carrying a GHSA reference; the import skill -classifies it as `ASF-security relay`, drafts the Step 7 receipt to -the forwarder (Arnout / the Apache Security team member who -relayed it). Weeks later the fix ships and the advisory is -archived on the users-list; `security-issue-sync` Step 14 -captures the URL and proposes an *Advisory sent* milestone draft -to the same forwarder contact: *"The advisory for -`CVE-2026-12345` has been sent and is archived publicly at -``. This completes the lifecycle for the report you -forwarded; thank you for the relay."* No technical detail is +**Forwarder-relayed GHSA report, advisory sent.** A report arrives +via the configured forwarder adapter (ASF-Airflow default: +`asf-security` carrying the relay preamble) with a GHSA reference; +the import skill classifies it as a forwarder-relay match, drafts +the Step 7 receipt to the forwarder contact (the relay broker's +personal address — for ASF-Airflow, the forwarding ASF Security +team member, e.g. `@raboof` per +`forwarders.asf-security.contact_handle`). Weeks later the fix +ships and the advisory is archived on the project's public users +list (per `archive_system.advisory_publication_signal_url`); +`security-issue-sync` Step 14 captures the URL and proposes an +*Advisory sent* milestone draft to the same forwarder contact: +*"The advisory for `` has been sent and is archived +publicly at ``. This completes the lifecycle for the report +you forwarded; thank you for the relay."* No technical detail is restated. (The intermediate *CVE allocated* notification landed on the same thread per the [Events handled outside this policy](#events-handled-outside-this-policy) @@ -227,11 +252,13 @@ clarification draft is **suppressed**; the credit field stays at line *"if the original reporter has a preferred credit form, please pass it back"* is folded in. -**Internal-escalation tracker.** An ASF PMC member forwards a +**Internal-escalation tracker.** A `governance.cve_allocation_gate`- +authorised member (ASF-Airflow default: a PMC member) forwards a private internal report to the security team verbally; the security team opens the tracker by hand and writes the `` marker -comment naming the PMC member as the forwarder contact. From that -point on, every sync skill that would draft to a reporter routes -to the named PMC member instead, and milestone-only suppression -applies as if the tracker had come in via ASF-relay. +comment naming the governance-authorised member as the forwarder +contact. From that point on, every sync skill that would draft to +a reporter routes to the named contact instead, and milestone-only +suppression applies as if the tracker had come in via a +forwarder-adapter relay. diff --git a/docs/security/how-to-fix-a-security-issue.md b/docs/security/how-to-fix-a-security-issue.md index 6d2e6114..e9f3b123 100644 --- a/docs/security/how-to-fix-a-security-issue.md +++ b/docs/security/how-to-fix-a-security-issue.md @@ -27,9 +27,13 @@ page is the two-minute summary. The adopting project's community monitors the project's `` (declared in `/project.md → Mailing lists`) for inbound - reports. Reports from elsewhere (GHSA, HackerOne, the ASF - `security@apache.org` relay) are forwarded onto that list so the - security team has a single inbox. + reports. Reports from elsewhere (GHSA, HackerOne, a foundation- + wide security relay declared in + `security_inbox.foundation_security_address`, or any other + adapter declared in `forwarders.enabled`) are forwarded onto + that list so the security team has a single inbox. (For the + airflow-s adopter, the foundation-wide relay is + `security@apache.org`.) 2. **Triage.** A rotating triager imports new reports into the private @@ -63,17 +67,22 @@ page is the two-minute summary. [`/canned-responses.md`](/canned-responses.md)). The draft is never sent — the triager reviews in Gmail before sending. The skill hard-stops if a CVE has already been - allocated (a Vulnogram REJECT is required first) or if the + allocated (a REJECT in the project's CVE tool — the adapter + named in `cve_authority.tool` — is required first) or if the advisory has shipped (closing as invalid then is a public retraction that needs explicit team escalation). 3. **CVE allocation.** - A PMC member of the adopting project allocates a CVE through the - project's CVE tool (declared in - [`/project.md → CVE tooling`](/project.md#cve-tooling)). - The allocation is PMC-gated; non-PMC triagers use the + A governance-authorised member of the adopting project (per + `governance.cve_allocation_gate` in + `/project.md`) allocates a CVE through the + project's CVE tool (the adapter named in `cve_authority.tool`, + with the allocation URL in `cve_authority.allocate_url`). + Triagers who do not satisfy the gate use the [`security-cve-allocate`](../../.claude/skills/security-cve-allocate/SKILL.md) skill to - produce a relay message for a PMC member to click through. + produce a relay message for a gate-passing member to click + through. (For the airflow-s adopter, the gate is PMC membership + and the CVE tool is Vulnogram.) 4. **Remediation.** A security-team member writes the fix in the public `` @@ -87,8 +96,11 @@ page is the two-minute summary. 5. **Release + advisory.** The release manager for the cut that carries the fix sends the public advisory to the project's users + announce lists, captures - the archive URL, and moves the CVE record to `PUBLIC` in the CVE - tool. + the archive URL (the page declared in + `archive_system.advisory_publication_signal_url`), and promotes + the CVE record from `publish-ready` to `public` in the project's + CVE tool (the adapter named in `cve_authority.tool`; the + generic state sequence is declared in `cve_authority.states`). 6. **Continuous improvement.** The security team encourages responsible vulnerability disclosure diff --git a/docs/security/new-members-onboarding.md b/docs/security/new-members-onboarding.md index 7ff875e1..154121d6 100644 --- a/docs/security/new-members-onboarding.md +++ b/docs/security/new-members-onboarding.md @@ -31,13 +31,17 @@ into its section. # How the team is composed -The security team is a group of people — mostly PMC members and -committers of the adopting project, but we also have security -researchers and people who are not yet committers but aspire to be, -and who are already active and known in the community. We also have -members of the security teams of stakeholders who deal with the -project's security outside of the community project itself — for -example, when they provide the project as a service. +The security team is a group of people — mostly members of the +project's governance body and committers of the adopting project +(for the ASF/Airflow named example: PMC members and committers of +`apache/airflow`), but we also have security researchers and people +who are not yet committers but aspire to be, and who are already +active and known in the community. The exact governance body the +team draws from is declared in +`/project.md → governance.cve_allocation_gate`. We +also have members of the security teams of stakeholders who deal +with the project's security outside of the community project itself +— for example, when they provide the project as a service. The team works on a voluntary basis. We understand that people have other commitments and lives, and we do not expect them to be available @@ -49,7 +53,9 @@ Being a member of the security team is not a permanent assignment; we rotate the team periodically (so far we have only rotated members once, after about 8 months, but we expect shorter rotation periods in the future). We are also open to new members joining the team at any -time — especially when PMC members wish to join. +time — especially when people who satisfy the project's +`governance.cve_allocation_gate` wish to join (for the ASF/Airflow +named example: PMC members of `apache/airflow`). We will likely re-evaluate the team composition and process in a few months, taking into account the involvement of people and their @@ -121,8 +127,11 @@ week. A good starting routine: the agent skills. Copy `.apache-steward-overrides/user.md` (scaffolded automatically when the project adopts steward) and fill in your GitHub handle, email, - PMC status, and (for remediation-developer work) the path to your - local `` clone. You can skip this step on day one; + governance-gate status (whatever + `/project.md → governance.cve_allocation_gate` + declares — for the ASF/Airflow named example: PMC membership), + and (for remediation-developer work) the path to your local + `` clone. You can skip this step on day one; skills fall back to runtime prompts when `.apache-steward-overrides/user.md` is missing. @@ -150,8 +159,10 @@ perspective: - **Release manager** is usually inherited rather than volunteered for: when you cut a release that contains a security fix, `security-issue-sync` hands those trackers to you with - `fix released` and you own them through the advisory + Vulnogram - steps. + `fix released` and you own them through the advisory + `` + steps (for the ASF/Airflow named example: the Vulnogram instance at + `cveprocess.apache.org`, declared in + `/project.md → cve_authority.record_url_template`). You can volunteer to provide a fix for a specific issue even before formally taking on the remediation-developer role — just comment on @@ -229,7 +240,9 @@ shape the team are small enough to read in one sitting: - [`README.md`](../../README.md) — the end-to-end handling process. - [`/canned-responses.md`](/canned-responses.md) — reply templates. - [`AGENTS.md`](../../AGENTS.md) — agent-facing conventions and confidentiality rules. -- `.apache-steward-overrides/user.md` — per-user configuration (PMC status, +- `.apache-steward-overrides/user.md` — per-user configuration + (governance-gate status per + `/project.md → governance.cve_allocation_gate`, local clone paths, optional tool backends) scaffolded during adoption. - [`/`](/) — project-specific content (roster, release trains, security model, scope labels, milestones, diff --git a/docs/security/threat-model.md b/docs/security/threat-model.md index cb3ebd14..9e3077da 100644 --- a/docs/security/threat-model.md +++ b/docs/security/threat-model.md @@ -13,7 +13,7 @@ - [B2 — Skill and private tracker](#b2--skill-and-private-tracker) - [B3 — Private tracker and public upstream](#b3--private-tracker-and-public-upstream) - [B4 — Pre-disclosure and post-disclosure](#b4--pre-disclosure-and-post-disclosure) - - [B5 — Agent host and ASF infrastructure](#b5--agent-host-and-asf-infrastructure) + - [B5 — Agent host and external infrastructure](#b5--agent-host-and-external-infrastructure) - [Adversaries](#adversaries) - [P1 — Malicious reporter](#p1--malicious-reporter) - [P2 — Hostile public contributor](#p2--hostile-public-contributor) @@ -47,13 +47,15 @@ ## Purpose Apache Steward automates the [16-step security-issue -lifecycle](process.md) on behalf of an ASF project's security team. +lifecycle](process.md) on behalf of a project's security team. Every skill that ships in the framework either reads from, writes -to, or moves data across a trust boundary that the foundation -treats as release-blocking — the private security tracker, the -embargoed pre-disclosure window, the upstream public repository, -the CVE Numbering Authority, and the credentials that authorise -each of those moves. +to, or moves data across a trust boundary the project treats as +release-blocking — the private security tracker, the embargoed +pre-disclosure window, the upstream public repository, the CVE +Numbering Authority configured under `cve_authority.tool`, and +the credentials that authorise each of those moves. (Named +example: for `airflow-s/airflow-s` the CNA tool is the ASF-hosted +Vulnogram at `cveprocess.apache.org`.) This document is the authoritative threat model for that automation. It enumerates the trust boundaries, the adversaries that may attack @@ -68,8 +70,10 @@ The intended readers are: Triage or Drafting against their tracker; - contributors proposing a new skill in the security family or a change that crosses one of the trust boundaries below; -- ASF Security and the project PMC during a pre-release security - review. +- the governance body identified by `governance.cve_allocation_gate` + and any foundation-level security review (named example: ASF + Security and the Airflow PMC for `airflow-s/airflow-s`) during + a pre-release security review. ## Scope @@ -81,8 +85,10 @@ In scope for this document: - the agent host's sandbox configuration in [`.claude/settings.json`](../../.claude/settings.json) and the [secure-agent-internals guide](../setup/secure-agent-internals.md); -- the credential surfaces a skill may touch — `gh` tokens, Vulnogram - OAuth tokens, Gmail OAuth tokens for the security mailing list, +- the credential surfaces a skill may touch — `gh` tokens, CNA-tool + OAuth tokens for the authority configured at `cve_authority.tool` + (named example: Vulnogram OAuth on `airflow-s`), mail-backend OAuth + tokens for the `` mail provider (`mail_provider.primary`), and any per-adopter scoped tokens declared in [`projects/_template/`](../../projects/_template/); - the data flows across the five trust boundaries enumerated in @@ -95,7 +101,8 @@ own threat model when they are introduced: - Auto-merge auto-merge — not implemented in v1; see [`docs/modes.md`](../modes.md). When proposed, Auto-merge requires - its own threat model entry and a separate ASF Security review. + its own threat model entry and a separate foundation-level security + review (named example: ASF Security review for `airflow-s`). - generic Drafting beyond `security-issue-fix` — proposed but not shipped. Each new Drafting skill ships with its own STRIDE row in the [STRIDE matrix](#stride-matrix-per-skill-family). @@ -108,9 +115,11 @@ own threat model when they are introduced: - adversaries with physical access to the maintainer's workstation — outside the agent's authority; covered by the maintainer's host hygiene, not by the framework. -- denial of service against ASF infrastructure (`lists.apache.org`, - `cveprocess.apache.org`, GitHub) — the framework can amplify but - not originate; rate-limit posture is delegated to those services. +- denial of service against the configured `` host, the + `archive_system.*` archive, and GitHub (named example: for + `airflow-s` these are `cveprocess.apache.org`, `lists.apache.org`, + and `github.com`) — the framework can amplify but not originate; + rate-limit posture is delegated to those services. ## Assumptions @@ -139,21 +148,26 @@ and triggers a re-audit. `denyRead`.** The default sandbox blocks the agent from reading that path. An adopter who relaxes that block (for example by adding it to `allowRead`) accepts the resulting threat surface. -5. **The CVE Numbering Authority API and the ASF mailing-list +5. **The CVE Numbering Authority API and the public mailing-list archives are authentic and uncompromised.** The framework treats - responses from `cveawg.mitre.org`, `cveprocess.apache.org`, and - `lists.apache.org` as authoritative for the data they return. + responses from `cveawg.mitre.org`, the `` host + (`cve_authority.allocate_url` / `cve_authority.record_url_template`), + and the `archive_system.*` archive as authoritative for the data + they return. (Named example: for `airflow-s` these resolve to + `cveprocess.apache.org` and `lists.apache.org`.) ## Definitions -- **Tracker** — the private security issue tracker for a project. - In `apache/airflow-security` this is a private GitHub repository; - the framework is tracker-agnostic but ships GitHub-issue support. -- **Upstream** — the public source repository where the fix PR is - opened (for the pilot adopter, `apache/airflow`). +- **Tracker** — the private security issue tracker (``) + for a project. The framework is tracker-agnostic but ships + GitHub-issue support; named example: `airflow-s/airflow-s` is a + private GitHub repository. +- **Upstream** — the public source repository (``) where + the fix PR is opened (named example: `apache/airflow` for the + pilot adopter). - **Embargo window** — the period between a report arriving on - `security@` and the public advisory being published. During this - window the existence and detail of the issue are confidential. + `` and the public advisory being published. During + this window the existence and detail of the issue are confidential. - **Triage / Mentoring / Drafting / Auto-merge** — see [`docs/modes.md`](../modes.md). Triage is read-only triage; Mentoring is mentoring; Drafting is agent-authored PRs gated on human review; Auto-merge is auto-merge (not shipped). @@ -192,7 +206,7 @@ and triggers a re-audit. │ Skill core │ └───────┬───────┘ │ - ── B5: agent host ↔ ASF infra ── + ── B5: agent host ↔ external infra ── │ ┌────────────┴────────────┐ │ Egress allowlist │ @@ -206,16 +220,17 @@ Any byte the agent reads that originated outside the framework is untrusted. The agent treats five untrusted-ingress sources as attacker-controlled by default: -- `security@.apache.org` mail bodies, including reporter- - supplied attachments and HTML-formatted multipart sections; +- `` mail bodies, including reporter-supplied + attachments and HTML-formatted multipart sections; - private tracker issue bodies and comments — confidential but not trusted, since a reporter or co-maintainer may have authored them; - public PR descriptions, commit messages, and review comments - pulled from upstream; + pulled from ``; - markdown report files passed to `security-issue-import-from-md`; - the contents of any URL the agent fetches inside the network - allowlist (a `lists.apache.org` archive page, a public commit on - `github.com`). + allowlist (an `archive_system.*` archive page, a public commit on + the `` / `` host — named example for `airflow-s`: + a `lists.apache.org` archive page or a commit on `github.com`). The threat at this boundary is content-as-instruction: a reporter who embeds prompt-injection text aimed at getting the agent to @@ -259,14 +274,18 @@ tracker. The threat is premature disclosure — a skill that adds the CVE ID to a public PR title before the advisory is out, or that posts a credit note on the PR before Step 16 runs. -### B5 — Agent host and ASF infrastructure +### B5 — Agent host and external infrastructure -Egress from the agent host to ASF and CVE infrastructure. Constrained -by `sandbox.network.allowedDomains`. The threat is two-way: an +Egress from the agent host to the configured external services — +the `` host, the `archive_system.*` archive, the +`` / `` host. Constrained by +`sandbox.network.allowedDomains`. The threat is two-way: an exfiltration attempt by a compromised dependency (which the -allowlist limits to ASF/CVE/GitHub destinations only — still bad, +allowlist limits to the configured destinations only — still bad, but bounded), and an inbound malicious response from one of those -destinations (a tampered archive page). +destinations (a tampered archive page). (Named example for +`airflow-s`: the allowlist covers `cveprocess.apache.org`, +`lists.apache.org`, and `github.com`.) ## Adversaries @@ -277,9 +296,9 @@ are tagged with the persona ID that motivates them. ### P1 — Malicious reporter -Submits a crafted message to `security@.apache.org` whose -real purpose is not to report a vulnerability but to manipulate the -agent that triages the report. +Submits a crafted message to `` whose real purpose +is not to report a vulnerability but to manipulate the agent that +triages the report. - **Capabilities** — can author arbitrary mail body, headers, and attachments; cannot read the tracker; cannot see the agent's @@ -323,18 +342,21 @@ compromise. ### P4 — Network-layer adversary -An attacker between the agent host and `lists.apache.org`, -`cveprocess.apache.org`, `cveawg.mitre.org`, or `api.github.com`. +An attacker between the agent host and the allowlisted destinations: +the `archive_system.*` archive, the `` host, the MITRE +CVE API at `cveawg.mitre.org`, or the `` / `` +platform API. (Named example for `airflow-s`: `lists.apache.org`, +`cveprocess.apache.org`, `cveawg.mitre.org`, `api.github.com`.) - **Capabilities** — TLS interception (assumed unsuccessful - against publicly-pinned ASF/MITRE/GitHub endpoints), DNS - tampering (assumed unsuccessful against system resolvers), or - outright connection blocking. + against publicly-pinned endpoints), DNS tampering (assumed + unsuccessful against system resolvers), or outright connection + blocking. - **Goal** — feed the agent a tampered archive page or a tampered CVE record so the agent acts on bad data. - **Surface** — any skill that fetches a URL inside the allowlist; most acute for `security-issue-sync` (which pulls archive pages) - and `security-cve-allocate` (which posts to CVE infra). + and `security-cve-allocate` (which posts to the CVE authority). ### P5 — Negligent insider @@ -366,8 +388,8 @@ interested in it, and the boundary that protects it. | CVE ID before advisory | Embargoed | P2 | B4 | | Credentials in `~/.config/apache-steward/` | Secret | P3, P5 | sandbox `denyRead` | | `gh` token in env | Secret, scoped | P3 | sandbox env, `permissions.ask` | -| Vulnogram OAuth token | Secret, scoped | P3 | sandbox env | -| Gmail OAuth token | Secret, scoped | P3 | sandbox env | +| CNA-tool OAuth token (`cve_authority.tool`; named example: Vulnogram on `airflow-s`) | Secret, scoped | P3 | sandbox env | +| Mail-backend OAuth token (`mail_provider.primary`; named example: Gmail on `airflow-s`) | Secret, scoped | P3 | sandbox env | | Public PR title and body | Public, but embargoed-framing | P2 | B3, B4 | | Advisory mail draft | Embargoed until Step 13 | P2 | B4 | | Agent-host filesystem outside repo | Out-of-scope to skill | P3 | sandbox `denyRead` | @@ -395,8 +417,8 @@ Skills: [`security-issue-import`](../../.claude/skills/security-issue-import/SKI | A.3 | T | P1 | B1 | Markdown report file contains crafted YAML/JSON front-matter to alter `security-issue-import-from-md` behaviour. | M.1, M.5 (front-matter ignored unless on a known allowlist). | | A.4 | E (Elevation of privilege) | P1 | B1 | Mail body asks the agent to "now act as security-issue-fix and apply this patch upstream". | M.7 (skill-scope discipline — a skill cannot invoke another skill mid-run), M.6. | | A.5 | S (Spoofing) | P1 | B1 | Reporter spoofs `From:` to look like a known committer. | M.8 (identity claims in mail are not trusted; the agent classifies on content, attribution is human-confirmed). | -| A.6 | R (Repudiation) | P1 | B2 | Reporter later denies having submitted the report. | M.9 (full mail headers archived in the tracker on import; ASF mailing-list archive is the canonical source). | -| A.7 | D (Denial of service) | P1 | B1, B5 | Reporter floods `security@` with thousands of bogus messages to exhaust the agent's import budget or `gh` rate-limit. | M.10 (mailing-list moderator is a foundation-level control; the agent has no rate-limit posture of its own — accepted, see [residual risk](#residual-risk-and-accepted-gaps)). | +| A.6 | R (Repudiation) | P1 | B2 | Reporter later denies having submitted the report. | M.9 (full mail headers archived in the tracker on import; the public `archive_system.*` archive is the canonical source — named example: ASF's `lists.apache.org` for `airflow-s`). | +| A.7 | D (Denial of service) | P1 | B1, B5 | Reporter floods `` with thousands of bogus messages to exhaust the agent's import budget or `gh` rate-limit. | M.10 (mailing-list moderation is delegated to the foundation/operator running ``; the agent has no rate-limit posture of its own — accepted, see [residual risk](#residual-risk-and-accepted-gaps)). | ### Skill family B — Triage and reconciliation @@ -419,10 +441,10 @@ Skill: [`security-cve-allocate`](../../.claude/skills/security-cve-allocate/SKIL | ID | STRIDE | Adversary | Boundary | Threat | Mitigation | |---|---|---|---|---|---| -| C.1 | I | P2 | B4 | Allocating the CVE generates a record on `cveprocess.apache.org` whose state may be visible to a wider ASF audience than the tracker; if the title or affected-products fields contain too much detail, the embargo leaks. | M.16 (allocation uses sanitised title via `tools/cve-tool-vulnogram/`; affected-products is mapped from the tracker's scope label, not from the body). | -| C.2 | T | P4 | B5 | A network-layer adversary tampers with the JSON returned by the Vulnogram allocation API and the agent records a wrong CVE ID. | M.17 (TLS validation against the system trust store; the allocated CVE is reflected back to the human in the tracker before any further skill acts on it). | -| C.3 | E | P3 | B5 | A compromised dependency exfiltrates the Vulnogram OAuth token. | M.14, M.15, M.18 (token is short-lived and scoped to allocation; rotation cadence is per-adopter). | -| C.4 | R | P5 | B2 | An insider's CVE allocation is later disputed (was it for tracker X or Y?). | M.9, M.19 (the allocation skill writes a tracker comment containing the Vulnogram URL and the JSON it submitted, before publish — auditable). | +| C.1 | I | P2 | B4 | Allocating the CVE generates a record on the `` host (`cve_authority.allocate_url`) whose state may be visible to a wider audience than the tracker — typically the governance body identified by `governance.cve_allocation_gate`; if the title or affected-products fields contain too much detail, the embargo leaks. (Named example: for `airflow-s`, this is the ASF-wide Vulnogram allocator visible to PMC members.) | M.16 (allocation uses sanitised title via the configured `` adapter; affected-products is mapped from `scope_detection.labels`, not from the body). | +| C.2 | T | P4 | B5 | A network-layer adversary tampers with the JSON returned by the `` allocation API and the agent records a wrong CVE ID. | M.17 (TLS validation against the system trust store; the allocated CVE is reflected back to the human in the tracker before any further skill acts on it). | +| C.3 | E | P3 | B5 | A compromised dependency exfiltrates the `` OAuth token (named example: Vulnogram OAuth on `airflow-s`). | M.14, M.15, M.18 (token is short-lived and scoped to allocation; rotation cadence is per-adopter). | +| C.4 | R | P5 | B2 | An insider's CVE allocation is later disputed (was it for tracker X or Y?). | M.9, M.19 (the allocation skill writes a tracker comment containing the `` record URL (`cve_authority.record_url_template`) and the JSON it submitted, before publish — auditable). | ### Skill family D — Public remediation @@ -446,8 +468,8 @@ must respect when assisting closure. | ID | STRIDE | Adversary | Boundary | Threat | Mitigation | |---|---|---|---|---|---| -| E.1 | I | P2 | B4 | Premature publication of the CVE record on `cve.org` before `lists.apache.org` carries the advisory. | M.26 (Step 14 gate — the public advisory URL must be present in the tracker before the agent will draft the CVE-record submission). | -| E.2 | T | P4 | B5 | The CVE record submitted to `cveawg.mitre.org` is tampered in transit, or the published `cve.org` record drifts from what was submitted. | M.17 (TLS validation against system trust store); M.27 (release-manager walks `DRAFT` → `REVIEW` → `READY` → `PUBLIC` in Vulnogram and is the human readback gate at each transition; the agent's post-close `cve.org` publication-check sweep flags drift after `PUBLIC`). | +| E.1 | I | P2 | B4 | Premature publication of the CVE record on `cve.org` before the public `archive_system.*` archive carries the advisory. | M.26 (Step 14 gate — the public advisory URL must be present in the tracker before the agent will draft the CVE-record submission). | +| E.2 | T | P4 | B5 | The CVE record submitted to `cveawg.mitre.org` is tampered in transit, or the published `cve.org` record drifts from what was submitted. | M.17 (TLS validation against system trust store); M.27 (the release manager walks the `cve_authority.states` sequence `allocated` → `review-ready` → `publish-ready` → `public` in the `` and is the human readback gate at each transition; the agent's post-close `cve.org` publication-check sweep flags drift after `public`. Named example for `airflow-s`: Vulnogram's `DRAFT` → `REVIEW` → `READY` → `PUBLIC`). | | E.3 | I | P5 | B3 | Step 16 credit corrections (a reporter requesting a different attribution) are applied by editing a closed tracker and inadvertently re-open the issue in a way that leaks. | M.28 (credit corrections are appended as a new comment, never as a body edit; the closed-state label is preserved). | ## Cross-skill threats @@ -526,16 +548,16 @@ describes it. | M.7 | Skill-scope discipline by authoring convention — each `SKILL.md` declares its own scope and does not chain into other skills mid-run. **Not** runtime-enforced; the discipline is a function of how the skills are written and reviewed. The residual gap (an injection that successfully prompts the agent to behave as a different skill) is captured in [residual risk #9](#residual-risk-and-accepted-gaps). | Per-skill [`SKILL.md`](../../.claude/skills/) authoring; not a runtime control. | | M.8 | Identity claims in inbound mail are not trusted; mail headers are recorded but not used for authorisation. | Skill family A behaviour. | | M.9 | Every agent-driven state transition is recorded as a tracker comment attributable to the agent's bot identity. | Skill behaviour; the bot identity is configured per adopter in `projects//project.md`. | -| M.10 | Mailing-list moderation rate-limit is a foundation-level control, not a framework control. | ASF infrastructure. | +| M.10 | Mailing-list moderation rate-limit is delegated to the operator running ``, not a framework control. (Named example for `airflow-s`: ASF mailing-list infrastructure.) | External infrastructure. | | M.11 | Label transitions in `security-issue-sync` are computed from observed external state (PR merge, release tag), not from tracker comment content. | [`security-issue-sync/SKILL.md`](../../.claude/skills/security-issue-sync/SKILL.md). | | M.12 | Public PR ↔ tracker cross-reference is one-way until Step 14. Tracker → PR link is added at PR-open time; PR → tracker link is added only after the public advisory URL is captured. | [`process.md` Steps 10 and 14](process.md). | | M.13 | Public PRs reference CVE IDs, never tracker IDs. | [`security-issue-fix/SKILL.md`](../../.claude/skills/security-issue-fix/SKILL.md) and [`security-issue-deduplicate/SKILL.md`](../../.claude/skills/security-issue-deduplicate/SKILL.md). | | M.14 | Network egress allowlist enforced by the runtime. | [`.claude/settings.json` `sandbox.network.allowedDomains`](../../.claude/settings.json). | | M.15 | Per-skill credential scope budget. The `gh` token granted to the agent is scoped to the minimum repos required by the skill family. | Per-adopter token configuration; documented in [`docs/setup/secure-agent-internals.md`](../setup/secure-agent-internals.md). | -| M.16 | CVE allocation uses a sanitised title produced by [`tools/cve-tool-vulnogram/`](../../tools/cve-tool-vulnogram/) title-normalisation. | [`projects/_template/title-normalization.md`](../../projects/_template/title-normalization.md). | +| M.16 | CVE allocation uses a sanitised title produced by the configured `` adapter's title-normalisation (named example: [`tools/cve-tool-vulnogram/`](../../tools/cve-tool-vulnogram/) for `airflow-s`). | [`projects/_template/title-normalization.md`](../../projects/_template/title-normalization.md). | | M.17 | TLS validation against the system trust store on every egress. | Default `requests`/`httpx` behaviour; pinning is *not* used — the assumption is that the system trust store is trustworthy. | -| M.18 | Token-scope and rotation cadence for Vulnogram, Gmail, and `gh` are an adopter-policy responsibility. The framework's [adopter scaffold](../../projects/_template/) does **not** ship a token-rotation template in v1; cadence is left to each adopter's security-team practice. See [residual risk #11](#residual-risk-and-accepted-gaps). | Adopter policy; no framework scaffold in v1. | -| M.19 | The CVE allocation skill writes the Vulnogram URL and the submitted JSON to a tracker comment before publish — auditable trail. | [`security-cve-allocate/SKILL.md`](../../.claude/skills/security-cve-allocate/SKILL.md). | +| M.18 | Token-scope and rotation cadence for the `` OAuth token (`cve_authority.tool`), the `mail_provider.primary` OAuth token, and `gh` are an adopter-policy responsibility. The framework's [adopter scaffold](../../projects/_template/) does **not** ship a token-rotation template in v1; cadence is left to each adopter's security-team practice. (Named example for `airflow-s`: Vulnogram, Gmail, and `gh`.) See [residual risk #11](#residual-risk-and-accepted-gaps). | Adopter policy; no framework scaffold in v1. | +| M.19 | The CVE allocation skill writes the `` record URL (`cve_authority.record_url_template`) and the submitted JSON to a tracker comment before publish — auditable trail. (Named example for `airflow-s`: the Vulnogram URL.) | [`security-cve-allocate/SKILL.md`](../../.claude/skills/security-cve-allocate/SKILL.md). | | M.20 | `security-issue-fix` scrubs embargo-framing terms from PR title and body until Step 14. | [`security-issue-fix/SKILL.md`](../../.claude/skills/security-issue-fix/SKILL.md). | | M.21 | Embargo window is minimised by promptly merging and releasing once the fix is reviewed; the diff itself is accepted as a controlled disclosure. | [`process.md` Steps 11 and 12](process.md). | | M.22 | Commit signing is expected on the fix branch by adopter policy; the human reviewer verifies the signed commit chain matches the agent's authored set. | Maintainer / adopter process; **not framework-enforceable** — see [residual risk #10](#residual-risk-and-accepted-gaps). | @@ -543,7 +565,7 @@ describes it. | M.24 | The agent's `git add` is path-scoped to the patched files. | [`security-issue-fix/SKILL.md`](../../.claude/skills/security-issue-fix/SKILL.md). | | M.25 | Agent authorship is recorded via a `Generated-by:` commit trailer in the public commit (per [`AGENTS.md` Commit and PR conventions](../../AGENTS.md#commit-and-pr-conventions) and [`security-issue-fix/SKILL.md`](../../.claude/skills/security-issue-fix/SKILL.md)). `Co-Authored-By:` is **forbidden** for agents per the same section — agents are assistants, not authors. The trailer is part of the public commit metadata and survives merge. | [`AGENTS.md`](../../AGENTS.md#commit-and-pr-conventions); [`security-issue-fix/SKILL.md`](../../.claude/skills/security-issue-fix/SKILL.md). | | M.26 | The agent will not draft the CVE-record submission until the public advisory URL is present in the tracker. | [`security-issue-sync/SKILL.md`](../../.claude/skills/security-issue-sync/SKILL.md). | -| M.27 | The CVE record is submitted to Vulnogram by the release manager, who walks it through `DRAFT` → `REVIEW` → `READY` → `PUBLIC`; only `PUBLIC` pushes to `cve.org`. The release manager (a human) is the readback gate at every transition. The agent runs a separate post-close `cve.org` publication-check sweep on closed-and-`announced` trackers within the last 90 days and surfaces any mismatch (record missing, state regressed, content tampered) for human review. | [`tools/cve-tool-vulnogram/record.md`](../../tools/cve-tool-vulnogram/record.md); [`security-issue-sync/SKILL.md`](../../.claude/skills/security-issue-sync/SKILL.md) (`sync closed announced` mode). | +| M.27 | The CVE record is submitted to the configured `` by the release manager, who walks it through the generic `cve_authority.states` sequence (`allocated` → `review-ready` → `publish-ready` → `public`); only `public` pushes to `cve.org`. The release manager (a human) is the readback gate at every transition. The agent runs a separate post-close `cve.org` publication-check sweep on closed-and-`announced` trackers within the last 90 days and surfaces any mismatch (record missing, state regressed, content tampered) for human review. (Named example for `airflow-s`: Vulnogram's `DRAFT` → `REVIEW` → `READY` → `PUBLIC`.) | [`tools/cve-tool-vulnogram/record.md`](../../tools/cve-tool-vulnogram/record.md); [`security-issue-sync/SKILL.md`](../../.claude/skills/security-issue-sync/SKILL.md) (`sync closed announced` mode). | | M.28 | Step-16 credit corrections are appended as new tracker comments; they never edit the closed tracker body. | [`process.md` Step 16](process.md). | | M.29 | CI lints `.claude/settings.json` on every PR that touches it, comparing against the shipped baseline. | **Planned, not yet shipped** — see [residual risk #4](#residual-risk-and-accepted-gaps). | @@ -567,9 +589,11 @@ the trigger that would force a re-evaluation. 3. **Patch-as-disclosure (D.2) is intrinsic, not a control failure.** The mitigation is operational (minimise the embargo-to-release window), not architectural. **Trigger for re-eval:** any - foundation-level decision to support a private-PR workflow that - delays public commit until advisory time. v1 explicitly chose the - public-PR path; see [`process.md` Step 8 vs Step 9](process.md). + policy decision (by the project or its parent governance body + identified via `governance.cve_allocation_gate`) to support a + private-PR workflow that delays public commit until advisory + time. v1 explicitly chose the public-PR path; see + [`process.md` Step 8 vs Step 9](process.md). 4. **Local sandbox override (X3) is unavoidable.** A maintainer editing `.claude/settings.json` locally cannot be prevented. The CI lint (M.29) catches changes shipped via PR but not local @@ -593,10 +617,13 @@ the trigger that would force a re-evaluation. allowlisted destinations. 8. **Attribution drift (D.6).** Agent authorship is recorded via a `Generated-by:` commit trailer per [`AGENTS.md`](../../AGENTS.md#commit-and-pr-conventions); - `Co-Authored-By:` for agents is forbidden. A future ASF policy - change on agent-authoring conventions would force a revision of - M.25 and the trailer wording. **Trigger for re-eval:** ASF Legal - or PMC guidance on agent-authoring attribution. + `Co-Authored-By:` for agents is forbidden. A future policy + change by the governance body (`governance.cve_allocation_gate`) + or the foundation hosting the project on agent-authoring + conventions would force a revision of M.25 and the trailer + wording. **Trigger for re-eval:** foundation-level legal or PMC + guidance on agent-authoring attribution (named example for + `airflow-s`: ASF Legal or the Airflow PMC). 9. **Skill-scope discipline (M.7) is convention, not enforcement.** No runtime mechanism prevents a skill's prompt from chaining into the behaviour of another skill mid-run; the discipline is a @@ -611,17 +638,19 @@ the trigger that would force a re-evaluation. D.3 entirely. The mitigation depends on adopter policy plus the human reviewer's verification of the signed commit chain. **Trigger for re-eval:** a framework-level mechanism to attest - to the signing posture of an agent run, or an ASF foundation- - level mandate. + to the signing posture of an agent run, or a foundation-level + mandate from the project's parent body (named example for + `airflow-s`: an ASF-wide mandate). 11. **Token-rotation cadence is undocumented in the adopter scaffold (M.18).** The v1 [`projects/_template/`](../../projects/_template/) - ships no template that prescribes rotation cadence for - Vulnogram, Gmail, or `gh` tokens. Adopters are expected to - operate a per-team rotation practice; the framework cannot - detect or enforce it. **Trigger for re-eval:** drafting a - `tokens.md` template under `projects/_template/`, or any - incident report involving stale-token misuse on an adopter - deployment. + ships no template that prescribes rotation cadence for the + `` OAuth, the `mail_provider.primary` OAuth, or `gh` + tokens. (Named example for `airflow-s`: Vulnogram, Gmail, and + `gh`.) Adopters are expected to operate a per-team rotation + practice; the framework cannot detect or enforce it. + **Trigger for re-eval:** drafting a `tokens.md` template under + `projects/_template/`, or any incident report involving + stale-token misuse on an adopter deployment. ## Re-audit cadence and ownership @@ -651,7 +680,8 @@ below are the framework's commitment. Ownership is the framework's security-skill-family maintainers (see the `CODEOWNERS` for `docs/security/` and `.claude/skills/security-*/`). -ASF Security review is required on the pre-release audit. +A foundation-level security review is required on the pre-release +audit (named example for `airflow-s`: ASF Security review). ## Change log