vex-publish
Generate OpenVEX / CycloneDX VEX attestations from `.vulnetix/memory.yaml` triage decisions, optionally sign with cosign, optionally upload to Vulnetix and post to a GitHub PR.
Arguments: [--format openvex|cyclonedx] [--upload]
Grants: Bash(vulnetix:*) Read Grep Glob Write Bash(cosign:*) Bash(gh:*)
Vulnetix VEX Publication Skill
Use when
- Triage cycle is complete and you need to publish VEX statements for supply-chain consumers.
- A customer requested OpenVEX attestations for a specific delivery.
- Attaching VEX to a CycloneDX SBOM in a compliance bundle.
- Posting VEX status as a PR comment so reviewers see the security disposition.
- Auditing the decision history — VEX is the durable record of what was decided when.
Don’t use for
- Making the triage decisions — use
vulnetix_vuln(MCP) orvulnetix vdb vuln <id>,vulnetix_exploits(MCP),fixfirst; this skill publishes existing decisions. - Generating the SBOM itself — use
sbom-generate.
Conventions
Follows skills/_lib/contract.md. In short: use the vulnetix_* MCP tools when the agent has them and the CLI otherwise — both shape their own output, so there is no jq step any more. Independent calls go out as concurrent Bash tool calls in one message. One trailing suggestion, not a playbook. See the contract for surface selection, output style and memory writes.
Turns the decisions captured in .vulnetix/memory.yaml into a signed/uploadable VEX document.
Step 1: Load memory
Read .vulnetix/memory.yaml. Collect every entry with a non-default decision.choice (i.e. anything that isn’t investigating).
Step 2: Map decisions → VEX status
Mapping (Vulnetix CLI uses the same):
not-affected/risk-avoided→not_affectedfix-applied→fixedrisk-accepted/deferred/mitigated→affected(with mitigations)investigating→under_investigation
Step 3: Generate VEX
vulnetix triage --provider vulnetix --vex-format ${FORMAT:-openvex} -o json > .vulnetix/vex/${TIMESTAMP}.vex.json
Default format openvex. If user passes --format cyclonedx, the CLI emits CycloneDX VEX.
Step 4: Upload (conditional)
If user passed --upload:
vulnetix upload --file .vulnetix/vex/${TIMESTAMP}.vex.json
Otherwise, skip and report the local file path.
Step 5: GitHub PR comment (conditional)
If binaries.gh: true and we’re inside a PR (env GITHUB_REF or gh pr view --json number):
gh pr comment "$PR_NUMBER" --body-file .vulnetix/vex/${TIMESTAMP}.vex.summary.md
Where the summary file is a short Markdown rendering of the VEX (auto-generated from the JSON).
Step 6: Render report
VEX statements: N
- not_affected: N
- fixed: N
- affected (with mitigations): N
- under_investigation: N
File: .vulnetix/vex/<timestamp>.vex.json
Uploaded: <yes|no>
PR comment: <yes|no>
Edge cases & gotchas
- VEX status maps from
decision.choicevia a closed dict —not-affected/risk-avoided→not_affected,fix-applied→fixed,risk-accepted/deferred/mitigated→affected (with mitigation),investigating→under_investigation. Custom decision strings break the mapping. --format openvex(default) and--format cyclonedxproduce different schemas; consumers downstream typically prefer one or the other.--uploadsends to the Vulnetix triage endpoint — requires authenticated CLI; community-tier may rate-limit.- PR comment posting needs
binaries.gh: trueAND the current directory inside a PR-context git repo (CI envs typically have GITHUB_REF set). - Signing with cosign uses keyless OIDC by default; the user must have a valid OIDC identity (CI runners typically do, dev laptops may not).
- Memory schema requires
decision.choicepopulated for every entry to be published — entries withstatus: under_investigationare filtered out unless--include-investigatingis passed.