Generate Cve Json logo

Generate Cve Json

Organization
apache
generate-cve-json

Generate a CVE 5.x JSON document from an <tracker> tracking issue, ready to paste into the Vulnogram `#source` tab of the ASF CVE tool at https://cveprocess.apache.org/cve5/<CVE-ID>#source. The conversion is deterministic: same issue in, same JSON bytes out. Handles multiple credits (one per line) and multiple references (URLs extracted from the issue's "Public advisory URL" and "PR with the fix" fields; the "Security mailing list thread" field is treated as internal-only and never exported).

Overview

Publisherapache
Repositorymagpie
Skill namegenerate-cve-json
Stars
132
Forks
120
Bundled files
17
LicenseApache-2.0
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • 17 bundled files

    Scripts, templates, and references the model can read while it works. Files are read-only and never executed.

  • Open source

    Published by apache on GitHub. Read the source before you install it.

Installation

Install the Generate Cve Json AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
    https://github.com/apache/magpie/tree/main/tools/cve-tool-vulnogram/generate-cve-json
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/apache/magpie.git /tmp/magpie
mkdir -p .claude/skills
cp -r /tmp/magpie/tools/cve-tool-vulnogram/generate-cve-json .claude/skills/generate-cve-json
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Generate Cve Json in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Generate Cve Json on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Generate Cve Json is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

generate-cve-json

This skill produces a CVE 5.x JSON document from a tracking issue in <tracker>, ready to paste into the Vulnogram "#source" tab of the ASF CVE tool. The goal is to eliminate the manual "copy each field from the issue into the right Vulnogram form input" step when you are preparing to publish an advisory.

Project-agnostic by design. All project-specific values (vendor, top-level product / package name, project display map, CNA org id, generator tag, …) are loaded from a TOML config the adopting project ships at <project-config>/tools/cve-tool-vulnogram/cve-json-config.toml. Concrete apache-foo-project-* strings appearing in this document are illustrative examples of how a project with a project-style package layout would configure things; replace them mentally with the adopter's own package taxonomy. The schema and lookup order are documented in the package README.

Golden rule: the script generates a proposal JSON document. It parses a handful of structured fields from the issue body, but it cannot read the security team member's mind. Always review the generated JSON before pasting, and always do the final review inside Vulnogram before moving the CVE from DRAFT → REVIEW → READY → PUBLIC.

Release-vote gating (opt-in, recommended for ASF projects). The emitted CNA_private.state follows a tri-state state machine:

  • DRAFT — the CNA is incomplete or the project has opted into release-vote gating and no vote is in progress yet.
  • REVIEW — the CNA is review-ready (CVE ID + title + description + affected versions + CWE + severity + ≥ 1 credit + ≥ 1 reference) and either the project hasn't opted into gating (legacy: ready ⇒ REVIEW) or an RC vote is in progress (signalled by the configured tracker label or a --review CLI flag).
  • PUBLIC — the CNA is review-ready and the public advisory has shipped (a vendor-advisory reference is present).

Projects opt into gating by setting [workflow].release_vote_gating = true in their cve-json-config.toml and choosing the label name via [workflow].rc_voting_label (default "rc voting"). The sync skill is responsible for detecting [VOTE] threads on the project's dev list (e.g. dev@<project>.apache.org) and proposing the label add/remove; the generator only reads the label on the tracker. Non- ASF adopters who publish advisories without a separate release-vote step typically leave gating off — the legacy "ready ⇒ REVIEW" behaviour is the right default for that workflow.

Projects whose own scanner reports through the security list (and so leaves no N/A — thread sentinel) list its name in [discovery].internal_markers in the same file, for example internal_markers = ["<scanner name in the thread field>", "<its public credit name>"]; the generator then records those finds as source.discovery: INTERNAL.

Determinism: the same input issue body produces exactly the same JSON bytes on every run. The script uses only the Python standard library, has no timestamps or machine-dependent values in its output, sorts JSON keys, and sorts references alphabetically. This lets you paste the result into Vulnogram, tweak fields in the tool, re-run the script later, and cleanly diff the two to see what the tool has added / what you changed by hand.


Inputs

  • Issue number (required) — e.g. 232.
  • Optional CLI overrides:
    • --cve-id CVE-YYYY-NNNN+ — override the CVE ID if the issue body's CVE tool link field has not yet been filled in, or retarget the JSON to a different CVE ID.

    • --title "<vendor>: <product>: …" — override the CVE title. Default is the GitHub issue title with the project's <vendor>: <product>: prefix (sourced from the TOML config) when it does not already start with that phrase.

    • --version-start X.Y.Z — override the start of the affected version range (the affected[].versions[].version field). Default is the lower bound parsed from the Affected versions field when it uses >= X, < Y syntax, otherwise "0".

    • --remediation-developer "Name" — append a type: "remediation developer" credit on top of whatever the body's Remediation developer field already lists (auto-populated by the security-issue-sync skill from the linked PR's author). Repeat the flag to add multiple developers; duplicates between the body field and CLI flags are dropped silently. The reporter credit(s) from the Reporter credited as field are always emitted with type: "finder".

    • --vendor / --product / --package-name / --collection-url — override the product identity fields. The defaults come from the project's TOML config (product.vendor, product.default_product, product.default_package_name, product.default_collection_url). They are used as the identity for Affected versions lines that don't start with a recognisable per-package directory name (see the multi-product note below). --collection-url reaches the record only through the purl it is used to derive (see product.purl_type below) — except for a product that opts out of purls, the one case a record still carries a collectionURL.

    • Helm charts — purl_type = "helm", and you must know where the chart is published. A chart is identified by its repository, not by its name: two projects may both ship a chart called superset. So the generator emits

      text
      pkg:helm/<chart>?repository_url=<base URL>

      taking the base URL from product.default_collection_url (or the package's collection_url override), and refuses to emit a purl at all without one rather than producing a chart identifier that matches somebody else's chart.

      If the base URL is not recorded anywhere, ask whoever publishes the chart. It is whatever a user would put in helm repo add — the https://… site serving index.yaml, or the oci://… registry path for a chart published as an OCI artifact. Either is accepted verbatim; a trailing slash is dropped so the same repository does not produce two identifiers. Do not infer it from the project's homepage: charts are routinely served from a different host than the project site.

      Note that helm is not one of the types the purl specification registers. It is used because the scanners that would match this advisory to a deployed chart emit it, and because the two spec-correct encodings — pkg:oci/ for an OCI registry and pkg:generic/ for a classic repository — would give the same chart two different identities depending on how it happened to be published. A strict validator may object to the type; being invisible to every scanner is the worse outcome.

    • product.purl_type (config only) — the Package URL type for the project's packages (pypi, npm, cargo, …). Every affected[] entry carries a packageURL (pkg:<type>/<packageName>). Per the CVE Record Format the purl never includes a version — the entry's versions[] carries the range.

      The purl is the only package identifier the record carries. The ASF CVE tool treats the Package URL as the recommended identifier and derives the legacy collectionURL / packageName pair from it when the record is serialised for publication, so the generator emits the purl alone. Writing the pair as well would hand the tool two identifiers that can disagree — which it reports rather than silently reconciles.

      A purl is required, not optional. A record needs one to be promoted, so the generator refuses to emit a record it cannot build a purl for rather than producing one the CNA will reject later. Leaving the key unset is fine when the type can be derived from product.default_collection_url — PyPI, npm, crates.io, RubyGems and NuGet hosts are recognised — and only hosts whose type is unambiguous are mapped, because a guessed type yields a valid-looking identifier pointing at the wrong ecosystem.

      Set purl_type = "none" to state deliberately that a product has no package host — a source-only release published to dist.apache.org and nowhere else, for instance. That is an explicit decision rather than an omission, which is the distinction the previous optional behaviour lost. Those entries — and only those — carry collectionURL / packageName instead, because there is no purl for the CVE tool to derive them from. Name normalisation follows the package-url spec per type, and is implemented for the types whose rules have been read from it: pypi (lowercased, _ becomes -) and npm (scope becomes the namespace, so @angular/animation renders as pkg:npm/%40angular/animation; names keep their case, since mixed-case npm packages were grandfathered in). Any other type has its name passed through unchanged, and a name needing namespace semantics is covered by product.purl_namespace below, and without one such a type is an error rather than a guessed purl.

    • product.purl_namespace (optional, config only) — the namespace for purl types that require one: a Maven groupId, a Composer vendor, a Go module prefix. Types the spec gives no namespace (pypi, cargo, gem, nuget) ignore it, and an npm scope carried in the package name itself wins over it. A type that requires a namespace and has none is an error naming this key — a guessed Maven groupId would point at another organisation's artifact, and silently omitting the purl produces an unpromotable record.

    • [packages.overrides."<packageName>"] (config only) — per-package distribution identity, for a project that ships to more than one ecosystem. Accepts product, collection_url and purl_type; anything omitted falls through to the product.* default, so a package that differs only in purl type need not restate its collection URL.

      The case this exists for: a project whose packages are on PyPI and which also ships, say, a Helm chart from its own site. product.* describes only the majority ecosystem, so without an override the odd-one-out inherits it and the record claims pkg:pypi/<chart> — a package that host does not carry. Since purls are what scanners match on, a wrong one is acted upon, unlike the wrong free-text product name it replaced.

      toml
      [packages.overrides."apache-example-helm-chart"]
      product = "Apache Example Helm Chart"
      collection_url = "https://example.apache.org/"
      purl_type = "none"

      The key is the resolved packageName, so the package must be one the configured package_pattern matches — extending that pattern is a prerequisite, not an extra. --product-for still wins over the product set here.

    • --product-for PACKAGE=PRODUCT — override the CVE product display name for a specific packageName. Repeat to override multiple packages. Useful when a package is not in the project's project_display_map config, or when an acronym needs different casing from the title-cased fallback. Example: --product-for apache-foo-project-baz='Apache Foo Project Baz'.

    • --org-id <uuid> — override the CNA assigner org id (defaults to the ASF org id).

    • --discovery <word> — override source.discovery (one of UNKNOWN, INTERNAL, EXTERNAL, USER; default derived as below).

    • --no-envelope — emit only the inner cna container instead of the full CVE 5.x record (envelope is the default).

    • --review / --draft (mutually exclusive) — force the emitted CNA_private.state to REVIEW or DRAFT regardless of the tracker's labels. Useful in two cases:

      • --review lets a release manager nudge a record forward by hand when the rc voting label is not yet set on the tracker.
      • --draft walks a record back when an RC vote was cancelled or failed and the label is still around. Both flags only matter when release-vote gating is enabled in the project's TOML config (see below); otherwise the state is derived from the CNA's readiness alone and these flags have no effect beyond what the legacy logic produces.
    • --attach — after generating the JSON, embed it at the end of the tracking issue's body (after the CVE tool link field), wrapped in a collapsible <details> block. The block is bracketed by HTML-comment markers (<!-- generate-cve-json: cve=CVE-YYYY-NNNN+ version=v1 --> … <!-- generate-cve-json:end cve=CVE-YYYY-NNNN+ version=v1 -->) that the script uses on later runs to find the existing block and replace it in place, so re-runs update the embedded attachment instead of duplicating it or breaking other body fields. The attachment lives in the body — not as a comment — so it stays above every status-change comment in the timeline (effectively "pinned" without needing any pin mechanism). Requires the positional issue argument; incompatible with --stdin.


Prerequisites on the tracking issue

For the generated JSON to be useful, the issue body should already be filled in through a prior security-issue-sync run. In particular:

  • Short public summary for publish — becomes the CVE description.

  • Affected versions — becomes the CVE affected[] list. The script understands the common version-expression shapes (< 3.2.2, >= 2.0.0, < 3.2.2, <= 3.2.1, a bare version like 3.1.5, and a bare lower bound like >= 2.0.0). Multi-product CVEs are supported — put one package per line, prefixing each with the package directory name as it appears in the adopter's repo, and the script emits one affected[] entry per line with the right product and package identity. Example (illustrative — using a hypothetical apache-foo project's sub-project layout):

    apache-foo-project-alpha <=6.5.0
    apache-foo-project-beta <=1.9.0

    Known package directory names are resolved to the vendor-preferred display casing via the project's packages.project_display_map config table; unknown packages fall back to title-cased dash-split and can be overridden with --product-for. A line without a package prefix (or a single-line field) falls back to the --product / --package-name defaults, which preserves the single-product behaviour.

    < NEXT VERSION placeholder — multi-package trackers don't know which package version will ship the fix until the wave's release manager picks it during a release cut. Until then, the Affected versions lines use the literal token NEXT VERSION as the upper bound, e.g.:

    apache-foo-project-alpha < NEXT VERSION
    apache-foo-project-beta < NEXT VERSION

    The generator strips < NEXT VERSION before parsing each line and emits a versions[] entry without lessThan (open-ended upper bound — "affected from <low> onwards, no fix released yet"). When the wave ships and the version is known, the security-issue-sync skill replaces each NEXT VERSION with the actual < X.Y.Z and the next regen produces a fully-bounded entry. Case-insensitive; combines with a lower bound (e.g. >= 2.0.0, < NEXT VERSION becomes {version: "2.0.0", status: "affected"}).

  • Security mailing list thread — internal navigation reference only; the script does not export URLs from this field into references[]. Keep whatever the reporter or triager put there.

  • Public advisory URL — each URL in this field is extracted and added to references[] with tags: ["vendor-advisory"]. Populated by the release manager (or the security-issue-sync skill) once the advisory is archived on <users-list>. The --advisory-url CLI flag still exists for ad-hoc overrides.

  • PR with the fix — each URL in this field becomes a reference URL. Multiple URLs are supported: paste them on separate lines, as a bullet list, or comma-separated — the script extracts every https?://… token it finds.

  • Reporter credited as — each line becomes one CVE credit entry with type: "finder". Multiple credits are supported: put each person on their own line. Full Name, Affiliation on a single line is treated as one credit, not two, so the common Jed Cunningham, Astronomer pattern works as expected. Bullets (- , * , 1. ) are stripped. Blank lines are ignored. If you need to credit many people::

    Jed Cunningham
    Saurabh Banawar
    selen (Huntr bounty 3e88d364-5047-4768-a52c-6568f21ef35b)
  • Remediation developer — each line becomes one CVE credit entry with type: "remediation developer". Same parsing rules as Reporter credited as (newline-separated, Full Name, Affiliation is one credit, bullets stripped). Auto-populated by the security-issue-sync skill from the linked PR's author the first time PR with the fix is set; manual edits survive subsequent syncs (the skill only proposes appending names that aren't already there). The --remediation-developer CLI flag adds further names on top of whatever the body already lists.

Bot / AI credit policy. This generator is intentionally neutral on credit content: whatever a tracker's Reporter credited as or Remediation developer field carries is what lands in credits[]. The filtering of obvious bot / AI accounts (e.g. dependabot[bot], *-scanner, automated-*) happens upstream in the skills at extraction time — see bot-credits-policy.md for the detection rule and the per-skill enforcement sites. Keeping the filter upstream means an intentional human override (typed directly into the field) survives every JSON regeneration without needing a special bypass flag here.

  • CWE — CWE-285: Improper Authorization style works; so does a bare CWE-285 or a plain sentence. The script extracts the CWE-\d+ token for the cweId field and uses the rest as the human-readable description.
  • Severity — None, Low, Medium, High, Critical (case-insensitive) are emitted as the text content of a metrics[].other block. Vulnogram lets you replace this with a CVSS vector in its form if you want a numeric score.
  • CVE tool link — the ASF CVE tool URL, e.g. https://cveprocess.apache.org/cve5/CVE-2026-40913. The script extracts the CVE-YYYY-NNNN+ token from this field. If the field is still _No response_, pass the CVE ID with --cve-id.

If one of these fields is missing, the JSON still generates, but the reviewer will need to fill the gap in Vulnogram. The skill surfaces any empty field in the proposal so nothing is silently skipped.


Prerequisites

  • gh CLI authenticated with collaborator access to <tracker> — the script reads the tracker via gh.
  • uv installed — the script is a small uv-managed Python project and is invoked as uv run --project tools/cve-tool-vulnogram/generate-cve-json generate-cve-json <N>.

See Prerequisites for running the agent skills in README.md.


Step 0 — Pre-flight check

Before reading the tracker:

  1. gh api repos/<tracker> --jq .name returns the adopter's tracker repo name (per <project-config>/project.md), and
  2. uv --version returns.

If either fails, stop and tell the user what to install or log in to.


Step 1 — Verify the issue has the required fields

Fetch the issue body and check every template field the script reads. If a field is missing or still _No response_, either run security-issue-sync first to fill it in, or override it on the command line.

bash
gh issue view <N> --repo <tracker> --json body --jq .body \
  | grep -E '^###|^_No response_'

Ask the user whether to proceed if any critical field is empty (description, affected versions, CVE tool link, credits). Do not silently generate a JSON with placeholder values.


Step 2 — Run the generator

Run the project's console script through uv run --project, which prepares the (cached) virtualenv on first use and reuses it on later runs:

bash
uv run --project <framework>/tools/cve-tool-vulnogram/generate-cve-json generate-cve-json <N> \
  --output /tmp/<CVE-ID>.json \
  --version-start <earliest-affected-version>

--version-start is the one flag the tracking issue body almost never contains and that Vulnogram expects filled in (the body field usually encodes only the upper bound). The remediation developer credit comes from the body's Remediation developer field, populated by the security-issue-sync skill from the linked PR's author — no CLI flag needed in the normal flow. For a fix that landed in 3.2.2 and was first introduced in 3.0.0, for example:

bash
uv run --project <framework>/tools/cve-tool-vulnogram/generate-cve-json generate-cve-json 232 \
  --output /tmp/CVE-2026-40913.json \
  --version-start 3.0.0

Pass --remediation-developer "Name" only when you need to add a developer credit on top of (or in place of) what the body already contains — for example a co-author who didn't end up as the PR's GitHub author.

Additional flags, all optional:

  • --cve-id CVE-YYYY-NNNN+ — override the CVE ID if the CVE tool link field is empty.
  • --title "<vendor>: <product>: …" — override the title.
  • --vendor / --product / --package-name / --collection-url — override product identity (defaults sourced from the project's TOML config under [product]).
  • --org-id <uuid> — override the CNA assigner org id (defaults to the ASF org id).
  • --discovery UNKNOWN|INTERNAL|EXTERNAL|USER — override source.discovery (for example INTERNAL for an issue a maintainer found and credited under a name that does not match their GitHub profile).
  • --no-envelope — emit only the cna container (no cveMetadata, no dataType/dataVersion wrapper). Use this if Vulnogram's #source tab is in "inner block only" mode.
  • --stdin — read the issue body from stdin instead of calling gh. Useful for offline iteration and for drafting by hand.
  • --report-json <path> — also write a machine-readable report (field names, line numbers and kinds; never body text): normalisation_changes (what the in-memory normalisation below rewrote), needs_decision (fields repeated with different values), and missing_required (empty required fields, each with agent_may_propose; false for Severity and CVE tool link). It also carries cna_private_state, the state the record was given: DRAFT while the summary, affected versions, CWE, severity or CVE link is empty (a title standing in for the summary included); an empty reporter credit or a non-template duplicate does not hold it back. It is written before the record is built, so it exists on exit 2 too (with cna_private_state: null). The calling skill acts on it per the CVE JSON fallback; the tool itself never calls a model.

Before reading any field the script normalises the body in memory with github-body-field's normalise: CRLF becomes LF, a heading written ### Name, ###<tab>Name, indented or with closing #s still names its template field (the name itself must match exactly, case included, as on main), a heading-shaped line inside a code fence never counts, and copies of a field that hold the same value collapse to one. A field repeated outside fences with different values is never guessed: the script exits 2 with an error naming the field and the heading line numbers, and writes no record.

The script is deterministic — re-running it with the same flags and the same tracking-issue body produces the same JSON bytes.

Output shape (in brief)

The generated record matches what Vulnogram exports after a save, minus editor cruft. Notable fields:

  • containers.cna.affected[] — vendor, product, packageURL (the version-less purl) and a versions[] entry with version, lessThan, status: "affected", versionType: "semver". An entry for a product configured with purl_type = "none" carries collectionURL / packageName in place of the purl.
  • containers.cna.descriptions[] — both a plain value and an HTML supportingMedia alternative (Vulnogram's WYSIWYG mode needs both).
  • containers.cna.problemTypes[].descriptions[] — cweId, human-readable description, type: "CWE".
  • containers.cna.metrics[].other — type: "Textual description of severity" and content.text = the severity word.
  • containers.cna.credits[] — one entry per Reporter credited as line (type "finder"), plus one entry per Remediation developer body line and per --remediation-developer CLI override (type "remediation developer"); duplicates between the body field and CLI flags are dropped silently.
  • containers.cna.references[] — URLs with auto-tagged tags:
    • GitHub pull/ or commit/ URLs → ["patch"];
    • lists.apache.org / security.apache.org → ["vendor-advisory"];
    • everything else → no tag.
  • containers.cna.source.discovery — "INTERNAL" when the project found the issue itself: Security mailing list thread starts with N/A — (no reporter thread: the import-from-pr, -md and -scan sentinels, or a maintainer's own N/A — internal finding while fixing …), an entry of the config's [discovery].internal_markers (the name of the project's own scanner, say) appears in that field or in Reporter credited as (case-insensitive substring), or every person in Reporter credited as is a collaborator of the tracker repo — the security team — matched by @login or GitHub profile name (one gh api graphql call, only when it can change the answer; if it fails, a warning and "EXTERNAL"); else "EXTERNAL" when Reporter credited as names a reporter, otherwise "UNKNOWN"; --discovery overrides it.
  • containers.cna.providerMetadata.orgId — ASF assigner org id.
  • cveMetadata — assignerOrgId, cveId, serial, state: "PUBLISHED".

Step 3 — Surface the output to the user

After the script finishes, print these three things in order:

  1. The output file path, with a one-line cat suggestion so the user can review the JSON in the terminal:

    ```
    Wrote /tmp/cve-CVE-2026-40913.json
    cat /tmp/cve-CVE-2026-40913.json
    ```
  2. A clipboard-copy command appropriate to the user's platform. On Linux with xclip installed:

    ```
    xclip -selection clipboard < /tmp/cve-CVE-2026-40913.json
    ```

    On Wayland: wl-copy < /tmp/cve-...json. On macOS: pbcopy < …. If xclip / wl-copy / pbcopy is not on PATH, skip the clipboard command and tell the user to copy manually.

  3. The Vulnogram #source paste URL, as a clickable link rendered per the "Linking CVEs" rule in AGENTS.md:

    ```
    Paste the JSON into the Vulnogram #source tab:
      [CVE-2026-40913](https://cveprocess.apache.org/cve5/CVE-2026-40913#source)
    ```

The #source tab on the ASF CVE tool is the direct "paste raw JSON" view of the Vulnogram form. The page loads the current record, you paste the script output over the top, click Save, and the form view reflects the new values.

Optional: --attach to embed (or refresh) the JSON in the issue body

If the user also wants the JSON attached to the tracking issue itself (so it is discoverable from the issue without needing the local file), add --attach to the invocation:

bash
uv run --project <framework>/tools/cve-tool-vulnogram/generate-cve-json generate-cve-json 232 \
  --output /tmp/CVE-2026-40913.json \
  --version-start 3.0.0 \
  --attach

What --attach does:

  • After generating the JSON (exactly the same bytes as without --attach), edits the tracking issue's body to embed the full JSON inside a four-backtick fenced code block, collapsed behind a <details> disclosure so long records don't bloat the issue view. The block is appended after the existing template fields, right after the CVE tool link field, so it lives at the end of the body.
  • Brackets the attachment with a pair of hidden HTML-comment markers (<!-- generate-cve-json: cve=CVE-YYYY-NNNN+ version=v1 --> … <!-- generate-cve-json:end cve=CVE-YYYY-NNNN+ version=v1 -->) so subsequent runs can find the existing embedded block and replace it in place, without spawning duplicates and without touching any other text in the body.
  • Re-running with --attach is safe and idempotent: same issue body → same JSON → the script patches the body, leaving you with one and only one embedded attachment per CVE id. If the current body already matches what the script would write, the PATCH is skipped entirely (no no-op timestamp on the issue).
  • The script prints Embedded CVE JSON in issue body on <tracker>#NNN on first run and Replaced CVE JSON in issue body on <tracker>#NNN on subsequent runs, followed by a URL that deep-links to the ## CVE JSON — paste-ready for … heading anchor inside the body.

Why embedded in the body and not as a comment? Two reasons:

  1. Natural "pinning" without an API for it. GitHub has no pin-comment API. A separate comment ends up buried below every status-change comment — so a newcomer looking at the issue sees a long comment timeline with no obvious way to find the CVE JSON. The issue body always renders above the entire comment timeline, so anything embedded in the body is effectively pinned.
  2. One place to read the tracker. The reporter-template fields, the CVE metadata, and the paste-ready JSON are all in one place — no hunting through the timeline to reconstruct the current state.

(GitHub also does not expose its user-attachments file-upload pipeline to the REST API — only the web UI drag-and-drop uses it — so a real file attachment isn't available to automation anyway. Embedding as body text is the closest automatable equivalent and is directly visible without a download round-trip.)

Confidentiality. The embedded block lives inside the private repo, so it inherits the repo-wide confidentiality rules. Linking CVE references inside the block follows the "Linking CVEs" rule in AGENTS.md: before publication the block links the ASF CVE tool; after publication, re-running the script includes a cve.org link as well.


Step 4 — Propose an issue comment recording the update

Per the "Keeping the reporter informed" rule in README.md, any status change on an issue must be recorded in an issue comment. Pasting a new version of the CVE record is a status change. Propose (and, on confirmation, post) a short comment like:

CVE entry regenerated from the tracking issue — generated paste-ready JSON for CVE-2026-40913 from the current body fields (description, affected versions < 3.2.2, CWE-285, Low severity, N credits, M references). Pasted into the Vulnogram #source tab; the record is now in sync with the tracking issue.

Include a count of credits and references so a later reviewer can sanity-check that nothing was dropped.


Step 5 — Never edit the JSON in place after pasting

Once the JSON has been pasted into Vulnogram and saved, do not edit the local JSON file to match tool-side changes. Re-run the script instead (it is deterministic — you will get a clean baseline), diff the new output against the current Vulnogram state, and paste the merged JSON back. This keeps the tracking issue as the single source of truth: if Vulnogram shows something different from the generated JSON, either the issue body is out of date and needs a security-issue-sync run, or the tool-side difference is intentional and the reviewer will keep it.


Guardrails

  • Confidentiality. The script deliberately drops any URL that points at cveprocess.apache.org or the project's <tracker> repo from the references list before serialising. Those URLs are private ASF-internal links and should not appear in a published CVE record. See the "Confidentiality of <tracker>" section of AGENTS.md.
  • CVE IDs are always linked per the "Linking CVEs" rule in AGENTS.md. When the skill mentions the CVE in proposals, recaps or comments on the <tracker> issue, it must render the ID as a markdown link — before publication to the ASF CVE tool, and additionally to cve.org after publication.
  • <tracker> references are always linked per the "Linking <tracker> issues and PRs" rule in AGENTS.md. When the skill mentions the tracking issue in its own comments, render it as a markdown link.
  • Deterministic output is a feature. Do not introduce timestamps, random UUIDs, ordering dependencies on dict iteration, or other sources of non-determinism into the script. If you need to add a new field, make sure the output still hashes the same across runs on the same input.
  • Multi-entry fields. Credits are split on newlines only to preserve the Full Name, Affiliation pattern. References are extracted from URL tokens in the field value. Do not reintroduce comma-splitting on credits.
  • No envelope means no metadata. --no-envelope drops the cveMetadata block which includes the CVE ID; the JSON is pure CNA content. Make sure the user knows they will have to set the CVE ID by hand in Vulnogram in that mode.

References

  • AGENTS.md — repo-wide conventions (confidentiality, Linking CVEs, Linking <tracker> issues and PRs, release-branch defaults).
  • README.md — handling process, in particular step 13 (fill in CVE tool fields and send advisory from the tool) and step 15 (paste the attached JSON into Vulnogram's #source tab, move the CVE to PUBLIC, close the issue).
  • security-issue-sync — the sibling skill that populates the tracking issue fields this skill consumes.
  • security-issue-fix — the other sibling skill that opens a public PR and updates the tracking issue with the fix URL.

Bundled files

The model reads these on demand while the skill is loaded. They are exposed as readable files and are never executed.

Frequently asked questions

What does the Generate Cve Json AI skill do?

Generate a CVE 5.x JSON document from an <tracker> tracking issue, ready to paste into the Vulnogram `#source` tab of the ASF CVE tool at https://cveprocess.apache.org/cve5/<CVE-ID>#source. The conversion is deterministic: same issue in, same JSON bytes out. Handles multiple credits (one per line) and multiple references (URLs extracted from the issue's "Public advisory URL" and "PR with the fix" fields; the "Security mailing list thread" field is treated as internal-only and never exported).

Why use Generate Cve Json on TypingMind?

Because you install it once and use it with any model. Generate Cve Json is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Generate Cve Json in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/apache/magpie/tree/main/tools/cve-tool-vulnogram/generate-cve-json. TypingMind reads its SKILL.md and bundles its files and installs it as a skill you can enable per chat.

Which AI models can use Generate Cve Json?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Generate Cve Json?

As many as you like. As long as a model supports skills, you can use Generate Cve Json with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Generate Cve Json AI skill free?

Yes. It is published on GitHub by apache under the Apache-2.0 license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇