Table of Contents generated with DocToc

Contributing

Thanks for helping improve this repository. It is a reusable framework for running the ASF security-disclosure process as a set of agent-driven skills. Adopting projects ship a per-project configuration layer (<project-config>/) in their own tracker repo and consume this framework as a submodule — see projects/_template/ for the scaffold an adopter copies and fills in.

Before sending a patch, please skim this file end-to-end: it lays out the layering the repository depends on, and a patch that ignores the layering is hard to land no matter how correct it is in isolation.

Project structure

The tree has four layers, each with a clearly-scoped job. The invariant is that a skill running against an adopting project should be able to resolve every piece of context it needs from some combination of the four — no hard-coded project assumptions anywhere.

  • Root docs carry the cross-cutting rules every contributor, agent, and reviewer is expected to have read. README.md is the canonical 16-step handling process, from report-arrival to CVE publication. AGENTS.md is the editorial contract: tone, brevity, confidentiality, linking conventions, the placeholder substitution rule (<PROJECT>, <tracker>, <upstream>), and the informational-only treatment of reporter-supplied CVSS scores. how-to-fix-a-security-issue.md and new-members-onboarding.md are human-facing guides that sit alongside those.
  • Skills live under .claude/skills/. Each is a SKILL.md that encodes one workflow — importing a new report, syncing a tracker against the world, allocating a CVE, drafting a fix PR, or deduplicating two trackers. Skills use the <PROJECT> / <tracker> / <upstream> placeholders everywhere and resolve them at runtime. They must not contain project-specific strings.
  • Config lives under config/ and wires the runtime together. config/active-project.md declares which subtree under projects/ is active (checked in); config/user.md carries per-user preferences (tool access, PMC status, local clone paths) and is gitignored. Two prek hooks keep user.md off the remote. See config/README.md for the full tutorial.
  • Projects live under projects/, one subtree per supported ASF project. The active subtree holds every project-specific fact the skills depend on — the security model, the scope labels, the milestone conventions, the release trains, the canned reporter replies, the title-normalisation rules. projects/_template/ is the bootstrap scaffold for adding a new project.
  • Tools live under tools/, one subtree per external system the skills talk to. Each subtree is project-agnostic; it documents the adapter surface (search queries, threading rules, API semantics, state machines) in terms of placeholders that the active project fills in. The vulnogram/generate-cve-json/ subtree is the only Python package — a uv-managed CLI that emits paste-ready CVE 5.x JSON from a tracker body.

Directory tree

.
├── README.md                      # Canonical 16-step handling process + conventions
├── AGENTS.md                      # Editorial rules: tone, brevity, confidentiality,
│                                  # placeholder substitution, reporter-CVSS policy
├── CONTRIBUTING.md                # This file
├── how-to-fix-a-security-issue.md # Human-facing fix guide
├── new-members-onboarding.md      # Human-facing onboarding guide
│
├── .claude/
│   └── skills/                    # Agent workflows (invoked via the Skill tool)
│       ├── import-security-issue/SKILL.md
│       ├── import-security-issue-from-pr/SKILL.md
│       ├── sync-security-issue/SKILL.md
│       ├── allocate-cve/SKILL.md
│       ├── fix-security-issue/SKILL.md
│       ├── deduplicate-security-issue/SKILL.md
│       └── invalidate-security-issue/SKILL.md
│
├── config/                        # Runtime configuration layer
│   ├── README.md                  # Configuration tutorial + placeholder rule
│   ├── active-project.md          # Declares active_project (checked in)
│   ├── user.md                    # Per-user — gitignored, auto-bootstrapped by prek
│   ├── user.md.template           # Bootstrap template with TODOs
│   └── user.md.example            # Filled-in example
│
├── projects/                      # Templates for adopting projects' configs
│   └── _template/                 # Scaffold for bootstrapping a new project's
│                                  # `<project-config>/` (the per-project layer
│                                  # an adopter ships in their tracker repo).
│                                  # Files: project.md (manifest), security-model.md,
│                                  # canned-responses.md, release-trains.md,
│                                  # milestones.md, scope-labels.md, naming-
│                                  # conventions.md, title-normalization.md,
│                                  # fix-workflow.md, README.md — all stubbed
│                                  # with TODO placeholders.
│
├── tools/                         # Project-agnostic adapters per external system
│   ├── gmail/
│   │   ├── tool.md                # Adapter overview
│   │   ├── operations.md          # MCP call signatures + no-update-no-delete rule
│   │   ├── threading.md           # threadId and subject-matched fallback
│   │   ├── search-queries.md      # Canonical reusable query templates
│   │   ├── ponymail-archive.md
│   │   └── asf-relay.md
│   ├── github/
│   │   ├── tool.md
│   │   ├── operations.md
│   │   ├── labels.md
│   │   ├── issue-template.md
│   │   └── project-board.md       # GraphQL introspection + column-move recipe
│   ├── vulnogram/
│   │   ├── tool.md
│   │   ├── record.md              # DRAFT / REVIEW / PUBLIC state machine
│   │   ├── allocation.md
│   │   └── generate-cve-json/     # Python package (uv-managed CLI)
│   │       ├── pyproject.toml
│   │       ├── src/generate_cve_json/
│   │       ├── tests/
│   │       ├── SKILL.md
│   │       └── README.md
│   └── cve-org/
│       └── tool.md                # MITRE CVE Services v2 publication check
│
├── .pre-commit-config.yaml        # prek hooks: doctoc, EOF, forbid/bootstrap
│                                  # user.md, ruff/mypy/pytest for generate-cve-json
└── .github/                       # CI: pre-commit.yml, zizmor.yml, ISSUE_TEMPLATE

Getting set up

You need three tools on your machine:

  • uv — the Python runner used for generate-cve-json. Install via curl -LsSf https://astral.sh/uv/install.sh | sh or your package manager.
  • prek — the pre-commit-compatible hook runner. Install via uv tool install prek or pipx install prek.
  • gh CLI — needed to drive tracker reads (and, later, writes) if you plan to run any of the skills end-to-end. brew install gh or platform equivalent.

First-time clone:

git clone git@github.com:<tracker>.git
cd <tracker-repo-name>
prek install                   # wire the hooks into .git/hooks
prek run --all-files           # runs every hook on every file; does a
                               # one-time bootstrap of config/user.md
                               # from the template

The bootstrap-user-config hook will create config/user.md on the first run. Open it, grep for TODO, and fill in the lines that apply to your setup. The file is gitignored; a second hook (forbid-user-config) refuses any commit that stages it, so you cannot accidentally publish your local configuration.

Read config/README.md for the end-to-end configuration tutorial, including the placeholder convention and how the skills consume both layers.

Making changes

Think about which layer the change belongs in before you start editing:

You want to change …Edit under …
A step of the disclosure process that applies to every projectREADME.md
An editorial / confidentiality / style ruleAGENTS.md
Anything project-specific (canned reply, milestone convention, scope label, release-train state)the adopter's own <project-config>/ (lives in their tracker repo, not here)
An adapter surface for an external system (a new Gmail search template, a new GraphQL recipe, a new gh invocation, a new CVE-tool endpoint)the matching tools/<system>/ subtree
A skill's workflow.claude/skills/<name>/SKILL.md
Bootstrap scaffolding for a new projectprojects/_template/

Rules of thumb for each layer:

  • Root docs and skills are project-agnostic. Never paste concrete names like <upstream> or <tracker> into them. Use the placeholders <PROJECT>, <tracker>, <upstream> in backticked labels. URL targets in markdown links can point at concrete paths so the links stay clickable during review — the placeholder lives in the visible label only. The convention is documented in AGENTS.md and enforced by reviewer taste.
  • Tool adapters are project-agnostic. If a recipe varies per project (different Gmail domains, different GitHub org, different board node IDs), the adapter declares variables and the active project's [project.md](/project.md) fills them.
  • An adopter's <project-config>/ carries concrete names freely — it exists for exactly that. The adopter's own per-project files can reference their <upstream> repo directly, paste concrete package versions, name release managers, etc. — none of that lives in this framework repo.
  • Skills never mutate state without user confirmation. If you add a new action, write the proposal/confirm/apply shape into the skill and the guardrails into AGENTS.md. See the existing skills for the pattern.

Running the dev loop

Every change should pass prek run --all-files locally before you open a PR — CI runs the same config. The hook set:

  • doctoc regenerates TOCs on every .md file (except skill SKILL.md files, which keep YAML frontmatter at the top);
  • end-of-file-fixer, trailing-whitespace, mixed-line-ending, check-merge-conflict, detect-private-key — standard hygiene;
  • forbid-user-config — refuses any commit that stages config/user.md;
  • bootstrap-user-config — creates config/user.md from the template on first run;
  • ruff check / ruff format --check / mypy / pytest against the tools/vulnogram/generate-cve-json/ Python package.

For the Python package directly:

cd tools/vulnogram/generate-cve-json
uv run pytest                  # unit tests
uv run ruff check              # lint
uv run ruff format             # auto-format (check-only in CI)
uv run mypy                    # type-check

The package is invoked by the sync-security-issue and allocate-cve skills via uv run --project tools/vulnogram/generate-cve-json generate-cve-json <N> --attach from the repo root — that is the canonical invocation any new behaviour has to stay compatible with.

Opening a pull request

  • Base branch: main. Do not open PRs against any other branch unless explicitly coordinated.
  • Scope: keep one concern per PR. A skill-behaviour change, a tool-adapter addition, and a doc update should land as separate PRs.
  • Commit message shape: imperative-present subject, ≤72 chars, plain prose body explaining why. Look at recent merged commits for the cadence.
  • PR description: one ## Summary section with 1–3 bullets of what changed and why, and one ## Test plan section listing how you verified the change.
  • CI: prek run --all-files must pass. zizmor (GitHub Actions linting) must pass. Both run automatically on every PR.
  • Reviews: at least one approval from a repo collaborator. Any change that edits AGENTS.md or the skill files should get an extra set of eyes because those ripple into every future sync.

Confidentiality

This repository is private — only security-team members can read its tracker contents. The repo‘s contents (issue bodies, comments, labels, rollup entries, severity assessments) must never appear on a public surface. The repo’s identifiers (URLs, #NNN) are public-safe and may be cross-referenced from public PRs, reporter emails, and advisory text — see the Confidentiality of the tracker repository section of AGENTS.md for the three-layer rule. Practical rules:

  • A <tracker> URL or #NNN reference may appear in a public PR description as a cross-reference identifier, so long as the surrounding text does not characterise the change as a security fix (no CVE-, no “vulnerability” / “security fix” / “advisory” phrasing) before the advisory ships.
  • Never paste tracker comment text, label transitions, or body excerpts into a public surface — those are contents, not identifiers.
  • Never put reporter-identifying information into a <upstream> PR.
  • When sharing a tracker URL with an external reporter, pair it with a one-line note that the link is an identifier-only reference (the page will 404 for them, and that is expected).
  • Reporter-supplied CVSS scores are informational only. The security team scores independently during CVE allocation. Full rationale in AGENTS.md.
  • config/user.md stays gitignored. If you need to share a snippet with someone, paste it in chat — do not commit it.

Anything you are unsure about, stop and ask on security@apache.org before pushing.

Authoritative references

When this file and a layer-specific doc disagree, the layer-specific doc wins. Re-read it first: