Write and use a local findings file¶
The quickstart used a ready-made findings file. In this tutorial, we’ll write one finding. We’ll use it to generate VEX and confirm that our analysis reached the output.
Set up the project¶
You need:
Git.
Python 3.10 or newer.
uv sync --frozenprovisions the interpreter pinned in.python-version(currently 3.14) and downloads it if your system lacks it.uv.A POSIX-style shell.
Clone the source and enter its root:
git clone https://github.com/vexcalibur-dev/vexcalibur.git
cd vexcalibur
The checkout’s .tool-versions records the tested uv version. Activate that
version with your version manager before you continue.
Install the locked dependencies:
uv sync --frozen
Installing the dependencies may contact your configured package index. The later generation step uses only the local SBOM and findings file.
We’ll reuse tests/fixtures/sbom/cyclonedx-json-simple.json. Its Django component has the reference component:django.
Describe the finding¶
Create /tmp/vexcalibur-findings.json:
cat >/tmp/vexcalibur-findings.json <<'JSON'
{
"findings": [
{
"id": "CVE-2026-0001",
"component_ref": "component:django",
"source_name": "Internal Review",
"source_url": "https://security.example.test/reviews/CVE-2026-0001",
"modified": "2026-07-01T12:00:00Z",
"analysis_state": "not_affected",
"analysis_detail": "The application does not enable the affected feature."
}
]
}
JSON
The component reference connects the finding to the SBOM. The state and detail record the result of our exploitability review.
Generate VEX¶
Run the generator without a network source:
uv run --frozen vexcalibur generate \
tests/fixtures/sbom/cyclonedx-json-simple.json \
--offline \
--findings-file /tmp/vexcalibur-findings.json \
--timestamp 2026-07-01T12:00:00Z \
--output /tmp/vexcalibur-local-vex.json
The command should exit without output and create /tmp/vexcalibur-local-vex.json.
Check the analysis¶
Read the generated vulnerability entry:
uv run --frozen python - <<'PY'
import json
from pathlib import Path
vex = json.loads(Path("/tmp/vexcalibur-local-vex.json").read_text())
finding = vex["vulnerabilities"][0]
assert finding["id"] == "CVE-2026-0001"
assert finding["analysis"]["state"] == "not_affected"
assert finding["analysis"]["detail"] == (
"The application does not enable the affected feature."
)
assert finding["affects"][0]["ref"] == "component:django"
print("preserved local exploitability analysis")
PY
You should see preserved local exploitability analysis.
Vexcalibur validated the local JSON. It matched component:django. It copied the review state into CycloneDX VEX. It did not create an OSV client.
Use the local findings reference for every field, default, size limit, and matching rule.