This is an automated email from the ASF dual-hosted git repository. lukaszlenart pushed a commit to branch docs/triage-public-non-vulnerability-notice in repository https://gitbox.apache.org/repos/asf/struts.git
commit 97ca328828cff5ba5f4b29c71ee715756356e9e2 Author: Lukasz Lenart <[email protected]> AuthorDate: Thu Nov 5 15:39:16 2026 +0100 docs(skills): owe a [NOT-VULNERABILITY] user-list notice for public reports closed as non-vulnerabilities The triaging-security-reports skill ended every triage at the verdict, whatever channel the report arrived on. For a report that is already public (Full Disclosure, a blog, a scanner advisory) and triages as a non-vulnerability, that leaves users and scanner vendors with only the reporter's version of the claim. The assessment now states the disclosure status after the verdict and, for a public non-vulnerability, that a [NOT-VULNERABILITY] notice on [email protected] is owed, with its subject. The drafting gate is unchanged: the notice is drafted only when asked. When it is, the skill gives the notice's shape, modelled on the Logging PMC's posts on log4j-user@: tagged subject, header with disposition and TLP:CLEAR, the request-to-sink path, the ownership boundary with its THREAT_MODEL / SECURITY.md section, the hardening ticket in functional terms, what users should do, references, and a closing that routes new evidence to security@. Every sentence comes from the report or the assessment. Baseline, unedited skill: three assessment runs all closed with "a public statement is the PMC's call" and never named the user list; five notice runs all produced an untagged prose subject, all invented "several of you have asked", four of five stated bulletin or release outcomes, none carried a disposition or references. With the edit: four assessment runs produced the two lines and drafted nothing; five notice runs followed the shape on every point, with no audience claim, no name, no release or date. Co-Authored-By: Claude Mythos 5.1 <[email protected]> --- .claude/skills/triaging-security-reports/SKILL.md | 72 ++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/.claude/skills/triaging-security-reports/SKILL.md b/.claude/skills/triaging-security-reports/SKILL.md index cc5abe5ca..6ce2be63d 100644 --- a/.claude/skills/triaging-security-reports/SKILL.md +++ b/.claude/skills/triaging-security-reports/SKILL.md @@ -1,6 +1,6 @@ --- name: triaging-security-reports -description: Use when a vulnerability or security report arrives for triage, when assessing a CVE/RCE/OGNL/injection claim against the code, when deciding whether a finding is the framework's fault or the application's, or when drafting a reply to a security researcher — to research the claim from source without trusting the reporter and without fabricating your own facts. +description: Use when a vulnerability or security report arrives for triage, when assessing a CVE/RCE/OGNL/injection claim against the code, when deciding whether a finding is the framework's fault or the application's, when drafting a reply to a security researcher, or when a claim that is already public (a Full Disclosure post, a blog, a scanner advisory) needs the project's position stated for users and scanner vendors — to research the claim from source without trusting the reporter [...] --- # Triaging Security Reports @@ -101,6 +101,36 @@ differential is the ownership evidence. - **Does it require an operator to opt into an insecure configuration?** A documented, opt-in setting (e.g. `cookiesName=*`, `devMode=true`) that works as advertised is the operator's responsibility, provided the docs carry the warning. Say "X works as documented; the operator owns the security implications of enabling it" — not "not a vuln *in the default config*." - **Is the sink only reachable through application code?** Then the path table says so, and the ownership rule above applies — state that boundary explicitly. +## A public report closes in public + +Where the report was published decides what the project owes after the verdict, so the assessment +states it in one line, right after the verdict: + +``` +Disclosure: private (security@ only) | public — <where>, <date>, <link> +``` + +A report is public when its text is already readable by anyone: a Full Disclosure or oss-security +post, a blog, a public GitHub or Jira issue, a scanner or SCA advisory id, a social-media thread. +The verdict does not change with the channel. The follow-up does: + +- **Private, any verdict** — the reporter gets the reply; nothing is published before the PMC decides. +- **Public, vulnerability** — the bulletin is the public statement + ([`creating-security-bulletins`](../creating-security-bulletins/SKILL.md)). +- **Public, not a vulnerability** — any `OUT-OF-MODEL`, `BY-DESIGN`, `KNOWN-NON-FINDING` or + `DUPLICATE` disposition, or `VALID-HARDENING` alone — a `[NOT-VULNERABILITY]` notice on + `[email protected]` is owed, and the assessment says so in a second line: + + ``` + Outcome notice: owed — [NOT-VULNERABILITY] <subject> + ``` + + Until that notice exists, users and scanner vendors have only the reporter's version of the + claim. The notice is the one statement they can point to, and it is written once. + +Posting the notice is the PMC's decision and drafting it is a task the user starts — the gate +below applies to it exactly as to a reporter reply. The assessment's part is the two lines above. + ## Drafting the reply — only when you are asked **Triage ends with the assessment. Do not draft, create, or send a reply until the user asks you to.** @@ -134,6 +164,41 @@ Once you *have* been asked: decision comes later and report the question to the user; do not answer it. - Acknowledge anything the reporter got right (e.g. correct CVE-fix verification) — it builds the relationship and signals you actually read it. - Keep it private: no public issue, PR, Jira, or list thread before triage. Never open a PR that is itself the security fix (see [`CLAUDE.md`](../../../CLAUDE.md)). + After triage, a public report closed as a non-vulnerability gets its `[NOT-VULNERABILITY]` notice + (below); a private report gets its reply and nothing public. + +### The `[NOT-VULNERABILITY]` notice + +Asked for the user-list notice on a public report, write it as these parts, in this order. It is +the shape the Logging PMC posts on `log4j-user@` (search that list for `[NOT-VULNERABILITY]` for +live examples): + +1. **Subject** — `[NOT-VULNERABILITY] <public id, where one exists>: <what the report claims, as one + functional clause>`. Example: `[NOT-VULNERABILITY] sonatype-2026-006746: FilteredObjectInputStream + is no safer than ObjectInputStream on untrusted data`. +2. **Header block** — the report (link, publication date, publisher), the disposition from + [`THREAT_MODEL.md`](../../../THREAT_MODEL.md) §13, and `TLP:CLEAR`. +3. **In short** — the verdict in two sentences: not a vulnerability in Apache Struts, and the one reason. +4. **What the report describes** — the claim and the mechanism, in words. No payload, no PoC string. +5. **What the framework does with that input** — the request-to-sink path from your table, hop by + hop, each hop grounded in a file you read this session or a documentation page. This is the part + a scanner vendor will quote. +6. **Why that is not a vulnerability** — the ownership boundary, which hop the application or the + operator owns, with the `THREAT_MODEL.md` / [`SECURITY.md`](../../../SECURITY.md) section that says so. +7. **What the project is doing** — the hardening ticket, where one exists, in functional terms + (`WW-XXXX adds java.beans to the default excluded packages`). Nothing more: no fix version, no + date, no statement about bulletins or CVEs. +8. **What users should do** — usually nothing. The setting to keep on; what to check in their own code. +9. **References** — the report, the ticket, `THREAT_MODEL.md`, `SECURITY.md`, the security guide page. +10. **Closing** — new evidence goes to `[email protected]`; questions go to the list. Signed + "on behalf of the Apache Struts PMC" by the PMC member who posts it, from their `@apache.org` + address; the draft itself carries no name. + +Every sentence in the notice comes from the report or from your assessment. Its readers are +described only by what you can link: when a user-list thread asking about the report exists, cite +it; when none exists, the notice says nothing about who has asked. The reporter is referred to by +the report, not by name, and the notice carries no view of them, their motives, or any other +exchange with them. ## Red Flags — STOP @@ -148,6 +213,9 @@ Once you *have* been asked: - Writing "not a vulnerability in the default configuration" → reframe as vuln-or-not + operator responsibility. - About to create a reply draft that nobody asked for — the verdict is the deliverable, the draft is a separate task. - About to write a severity rating, a bulletin, a fix version, or a CVE (or its timing) into a reply as though it were decided — it isn't yours to decide. +- The report is public and your assessment ends with "a public statement is the PMC's call" — the `Disclosure:` and `Outcome notice:` lines are missing. +- Writing "several users have asked" into a notice with no list thread to link. +- A notice that names a release, a date, a bulletin decision, or the reporter. ## Common Mistakes @@ -165,3 +233,5 @@ Once you *have* been asked: | "Triage is done, so drafting the reply is the next step" | Triage ends at the assessment. Replying is a separate task the user starts. | | "A draft is harmless — it isn't sent" | The draft is the artifact. Creating it unasked decides for the user that the project is ready to answer. | | "The reporter asked about a CVE, so I should answer it" | Report the question to the user. CVE timing is a PMC choice (not a fixed rule), so answering it commits the project to a process decision that isn't yours. | +| "The verdict is the same whoever posted it, so where it was posted doesn't matter" | The verdict is the same. What the project owes afterwards is not: a public claim closed as a non-vulnerability stays "confirmed" in every scanner feed until the project's own notice exists. | +| "Posting publicly is a communications call, not triage" | Posting is the PMC's. Saying that a notice is owed, and what its subject is, is the assessment's. |
