Table of Contents generated with DocToc
- Security workflow — roles, conventions, and role guides
Security workflow — roles, conventions, and role guides
Three roles share the security-issue handling process —
issue triager (Steps 1–6), remediation developer (Steps 7–11),
and release manager (Steps 12–15). This document covers who
owns what, the shared conventions every role observes, and
the per-role workflow. The detailed step descriptions live
in process.md.
Who this guide is for
Three roles share the handling process. Any security-team member can take on any of them for a given issue, and in practice people rotate — but at any moment a given tracking issue has exactly one person who owns the next move.
Pick whichever applies to you now:
- I am new to the security team, or I mostly just want to comment on
issues. Read Shared conventions below. The
adopting project’s security-issues board — see
<project-config>/project.md → GitHub project board— is the main view. You do not need an agent for commenting. - I am a rotational triager — running
import new reportsandsync alla few times a week. Jump to For issue triagers — Steps 1–6. - I picked up a tracker and am about to open a fix PR. Jump to For remediation developers — Steps 7–11.
- I am the release manager for a cut containing a security fix. Jump to For release managers — Steps 12–15.
- I am looking up a specific step or label. Go straight to Process reference or Label lifecycle.
Shared conventions
These conventions bind every role. If you are unsure whether a rule applies to you, it does.
Keeping the reporter informed
The security team commits to keeping the original reporter informed about the state of their report at every status transition, on the original mail thread (not on the GitHub-notifications mirror thread). A short status update should be sent to the reporter whenever any of the following happens:
- the report has been acknowledged or assessed (valid / invalid);
- a CVE has been allocated;
- a fix PR has been opened;
- a fix PR has been merged;
- the issue has been scheduled for a specific release (milestone set);
- the release has shipped and the public advisory has been sent;
- the CVE record has been published on cve.org (completes the disclosure);
- any credits or fields visible in the eventual public advisory have changed.
Each status update should plainly state what has changed, link to the relevant artifact (PR URL, CVE ID, advisory link), and state what comes next. If the reporter has not yet replied with their preferred credit, ask the credit-preference question — but do not re-ask it if it has already been asked on the same thread and is still awaiting a reply. Pinging the reporter twice about the same open question is rude and gets us blocklisted; default to the reporter’s full name from the original email if they do not respond before publication.
Reusable wording for the common cases lives in
[<project-config>/canned-responses.md](
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
that delivered the report. In that via-forwarder mode, only
the five lifecycle milestones (report accepted as valid, report
invalidated, CVE allocated, advisory sent, additional information
requested) are relayed. Regular workflow status (label flips,
PR-opened, PR-merged) and credit-confirmation questions are
not sent to the forwarder — they would burn the forwarder’s
goodwill with low-signal updates. See
forwarder-routing-policy.md for
detection rules, the full milestone list, and the negative space.
Recording status transitions on the tracker
Every status transition must also be recorded as a comment on the GitHub
issue in <tracker>, not only sent by email. The two channels
serve different audiences: the email keeps the reporter informed; the issue
comment keeps the rest of the security team and the release manager informed
without forcing them to reconstruct the state from labels and timestamps. The
comment should briefly state what changed, link to the artifact (PR URL, CVE
ID, advisory link), and indicate whether the reporter has been notified.
Confidentiality
Confidentiality of the private tracker (<tracker> for the
adopting project) is both a lifecycle rule and a writing rule:
every transition you record on a tracker, every status comment, every
email draft has to respect it. The full rule set — forbidden surfaces,
allowed surfaces, scrubbing guidance, the exception buckets for private
security@ / private@ threads and in-repo gh issue comment calls —
lives in
AGENTS.md — Confidentiality of the tracker repository.
Read it before editing anything that might be seen outside the team.
The threat model enumerates the trust boundaries this rule defends and the adversaries each role should expect on those boundaries.
For issue triagers — Steps 1–6
You own the tracker from an inbound report on <security-list>
through to a CVE allocated, a scope label applied, and the issue ready for a
remediation developer to pick up. Step 6 (the CVE allocation itself) is
PMC-gated: only the adopting project’s PMC members can submit the
CVE-tool allocation form. If you are not on the PMC you relay a
pre-drafted request to a PMC
member — either way you are the one who lands the resulting CVE ID back into
the tracker.
Daily triage loop
A typical triage sweep runs three skills in order:
import new reports—security-issue-importscans<security-list>for threads not yet imported, classifies each candidate (real report vs. automated-scan / consolidated / media / spam), and proposes a tracker per valid report plus a receipt-of-confirmation Gmail draft. See Step 2.sync all—security-issue-syncreconciles every open tracker against its mail thread, the fix PR, the release train, and the users@ archive. Proposes label / milestone / assignee / body changes in one pass.allocate CVE for issue #N—security-cve-allocatewhen a report has been assessed as valid. See Step 6.
Nothing is applied without an explicit confirmation — each skill is a proposal engine, not an auto-pilot.
Assessing a report
For each needs triage tracker, drive the validity assessment in comments,
pulling at least one other security-team member into the discussion. Use the
canned-response templates from [<project-config>/canned-responses.md](
When the report is confirmed valid, apply exactly one scope label from
the project’s scope set (declared in
[<project-config>/scope-labels.md](security-issue-sync skill surfaces this as
a blocker. See
Step 5.
If discussion stalls for about 30 days, escalate to a broader audience per Step 4.
Allocating the CVE
Use security-cve-allocate. The skill asks up
front whether you are on the PMC; if not, it reshapes the recipe into an
@-mention relay message you forward to a PMC member on the tracker or on
the <security-list> thread. Once the allocated CVE-YYYY-NNNNN
is pasted back, the skill wires it into the tracker in one pass (the CVE
tool link body field, the cve allocated label, a status-change comment, a
refreshed CVE-JSON attachment) and hands off to security-issue-sync to
reconcile the rest of the tracker. See Step 6
for the full detail.
Tools you use most
security-issue-import— “import new reports” at the start of each triage sweep. The entry point into the process for<security-list>reports.security-issue-import-from-pr— “import a tracker from PR” when a security-relevant fix landed publicly without going through<security-list>and the team has agreed it warrants a CVE. Lands directly in theAssessedcolumn.security-issue-sync— “sync” or “sync all”. Surfaces stalled issues, missing fields, credit replies, and scope-split requirements in one combined proposal.security-cve-allocate— “allocate a CVE for” .generate-cve-json— to refresh the paste-ready JSON embedded in the issue body on demand.security-issue-deduplicate— when two trackers describe the same root-cause bug discovered independently.security-issue-invalidate— “close NN as invalid” once Step 5 lands a consensus-invalid decision. Applies theinvalidlabel, archives the project-board item, and (for<security-list>-imported trackers) drafts a reply to the reporter explaining the reasoning.
For remediation developers — Steps 7–11
You own the tracker from a CVE allocated to a merged public fix PR in
<upstream> (including the pr merged hand-off where the tracker sits
waiting for the release train to ship). The role name matches the
remediation developer credit you receive in the published CVE record (see
credits[] with type: "remediation developer" in the generated CVE JSON).
Picking up a tracker
Pick a tracker that has a scope label, cve allocated, and clear consensus
on the fix shape. Self-assign yourself on GitHub so the board reflects
ownership. See Step 7.
Attempting an automated fix
Before writing the fix by hand, consider letting the
security-issue-fix skill try
it first. Invoked as “try to fix issue #N” (or “draft a PR for #N”), the
skill:
- runs
security-issue-syncfirst to make sure the tracker’s state is current; - reads the full tracker discussion and the linked
security@mail thread and decides whether the issue is easily fixable — clear consensus on the fix shape, small scope, known location in<upstream>. If it is not, the skill stops and tells you what more the tracker needs before it is safe to attempt; - if it is, proposes an implementation plan (which file(s) to touch, what to change, what tests to add) and waits for your explicit confirmation before making any edits;
- writes the change in your local
<upstream>clone, runs the local static checks and tests, and iterates on failures; - opens the public PR from your fork via
gh pr create --webwith a scrubbed title and body — every public surface (commit message, branch name, PR title, PR body, newsfragment) is grep-checked forCVE-, the<tracker>repo slug,vulnerability, “security fix” and similar leakage before being written or pushed; - updates the
<tracker>tracking issue with the new PR link and applies thepr createdlabel, handing back off tosecurity-issue-sync.
The skill refuses to proceed in cases where a human decision still needs to happen: reports that are still being assessed, reports not yet classified as valid vulnerabilities, and changes that require the private-PR fallback in Step 9. If it refuses, fall back to the manual flow below.
Even when the skill succeeds end-to-end, you remain the PR’s author
and reviewer-facing contact on the public <upstream> PR. Stay
on the PR through review and merge.
Opening the public fix PR manually
If you are writing the fix by hand, write the code change in your local
<upstream> clone, run the local checks and tests, and open the PR
via gh pr create --web. The PR description must not reveal the CVE,
the security nature of the change, or link back to <tracker> —
see Step 8 and the
confidentiality rules in
AGENTS.md.
Request a backport-to-v3-2-test (or equivalent) label on the public PR
when the fix should ship on a patch train.
Private-PR fallback
In exceptional cases — highly critical fixes, or code that needs private
review — open the PR against the main branch of <tracker>
instead of <upstream>. CI does not run there, so run static checks and
tests manually before asking for review. Once approved, re-open the PR in
<upstream> by pushing the branch public. See
Step 9.
Handoff to the release manager
Once the <upstream> PR merges, security-issue-sync moves the tracker
from pr created to pr merged and sets the milestone of the release the
fix will ship in. The tracker then waits for the release train.
The pr merged → fix released hand-off is gated: every one of the six
mandatory CVE body fields must be populated (CWE, Affected versions,
Severity, Reporter credited as, Short public summary for publish,
PR with the fix) and the CVE record must have advanced to REVIEW
state in Vulnogram. If any field is still empty when the PR merges (Step 11)
or when the release ships (Step 12), sync posts a
Remediation-developer fill-fields comment on the tracker @-mentioning
you with the specific missing fields. The tracker stays assigned to you and
the RM hand-off is not posted until you fill them in. See
Step 11 and Step 12.
Tools you use most
security-issue-fix— “try to fix issue #N”. Proposes a plan, writes the code, runs local tests, and opens a--webPR with a scrubbed title/body. See Attempting an automated fix above for the full flow and the cases where the skill refuses to proceed.security-issue-sync— to keep the tracker’s labels, milestone, and assignee aligned with the PR state as it moves through review and merge.
For release managers — Steps 12–15
You own the tracker from the moment the fix actually ships (fix released)
to a closed tracking issue with a PUBLISHED CVE record. The hand-off from
the remediation developer is automatic: security-issue-sync detects the
milestone version on PyPI / the Helm registry, swaps pr merged →
fix released, and assigns the advisory-send to you.
Handoff from the remediation developer
Watch your fix released queue on the board. Until the pr merged →
fix released swap fires, the tracker is still the remediation developer’s
(Step 11 territory). Once it fires, it is yours. See
Step 12.
Sending the advisory
By the time the hand-off comment lands, every mandatory body field is
already populated (Step 12’s gate) and the CVE JSON has been pushed to
Vulnogram in REVIEW state. Your three actions are the numbered list in
the hand-off comment, all single clicks in Vulnogram — no shell
commands, no JSON paste:
- Address reviewer feedback (if any) and promote
REVIEW → READY. Open the record’s#sourcetab. If the CVE reviewer has posted comments, work through them on the same thread; when it is clear, change the State dropdown fromREVIEWtoREADYand save. Most CVEs go throughREVIEWwith no reviewer comments and the flip is immediate. - Preview and send. Open the
#emailtab — it renders the exact advisory email. Verify recipients (<users-list>and<announce-list>) and body, then click Send Email. - Stop. Sync drives the rest at the archive-URL trigger (Step 14).
Sync does the fix released → announced - emails sent flip at Step 14,
not here — you do not touch labels. Do not close the issue — sync
does that too, in the same Step 14 combined apply.
Capturing the public archive URL and closing out
This is a handoff the sync skill handles for you. Once the advisory has
been archived on the users@ list, the next security-issue-sync run
fires a single combined apply that:
- writes the URL into the Public advisory URL body field;
- extracts the short public summary from the archived advisory email and writes it back to the Short public summary for publish body field;
- flips labels
fix released → announced - emails sent + announced; - regenerates and re-pushes the CVE JSON;
- moves the Vulnogram record
READY → PUBLIC(the CNA-feed dispatch tocve.org); - moves the project board to the
Announcedcolumn; - closes the tracker;
- archives the tracker from the
Announcedcolumn; - if every milestone-sibling is also closed at that moment, closes the milestone too.
See Step 14 for the full sequence.
Publishing the CVE and closing the issue
Nothing to do. Step 14 above already moved the Vulnogram record to PUBLIC, closed the tracker, and archived it from the board. You receive a purely-informational wrap-up comment as a timeline marker that the lifecycle is complete. See Step 15.
A tracker that sits on announced - emails sent without announced for
more than a day or two is a signal that sync did not see the advisory
in the <users-list> archive yet — re-run sync or wait for the next
scheduled pass.
Post-release credit corrections
If credits need correction after announcement, respond to the announcement
emails with the missing credits, update the ASF CVE tool, and ask the ASF
security team to push the information to cve.org. See
Step 16.
Tools you use most
security-issue-sync— “sync CVE-YYYY-NNNN” to drill into one specific CVE before sending the advisory (confirms the hand-off comment was posted and reflects the current record state). Subsequent syncs by the security team drive the post-advisory close-out automatically when the archive URL appears on<users-list>.generate-cve-json— to regenerate the attachment on demand when a body field changes after the URL has been captured (rarely needed — sync regenerates and re-pushes on every relevant body change).