{"schema_version":"1","site":"https://decosa.ai","id":"vex-triage","num":"63","name":"Signed VEX triage","tool_name":"Draft VEX for scanner findings","short":"VEX triage","blurb":"For product security teams and software vendors. Give it a Grype or Trivy report for a container image, its SBOM, and an evidence bundle from a small collector that runs next to the image. For each CVE, code checks the package, the version against the fix and the advisory ranges, the distro changelog, what actually loads the library, the settings the advisory names, and the platform. An open model then picks a VEX status with quoted evidence. Not affected needs positive evidence; everything else stays affected, fixed or under investigation. You get signed OpenVEX and CycloneDX VEX drafts and a signed evidence record for a security engineer to sign off.","status":"live","labels":{"industry":["software","compliance-trust"],"job":["review","attest"],"input":["files"],"deploy":["hosted","selfhost"],"status":"live","output":["record","data"],"data":["confidential"],"hardware":"gpu-96","licence":"permissive"},"industries":["software","compliance-trust"],"runs_in":["hosted","selfhost"],"part_of":[],"built_from":["typed-judgment","signed-record"],"models":"Qwen3.8-27B","where":"Hosted or self-host (air-gapped with a local advisory store)","hardware":"1x RTX PRO 6000 (96 GB) for Qwen3.8-27B; the collector, the checks, rules mode and the signing run on CPU","final_artifact":"Draft VEX statements with evidence, signed OpenVEX and CycloneDX VEX documents (DSSE), a Markdown report and a signed evidence record.","self_host_first":false,"verification":{"receipt_coverage":"full","summary":"Receipt per model call; every quote found in the advisory or check it cites; not_affected only on a strong code check; DSSE-signed VEX documents; signed hash-chained evidence record","manual_qa":{"hosted":{"date":"2026-09-26","result":"pass","p50_ms":14264,"p95_ms":null,"runs":null,"receipts_per_run":12,"cost_per_run_usd":0.014069},"selfhost":{"date":"2026-09-26","result":"pass","method":"fresh clone of decosa-api into a clean directory on our server, api image built from docker/api/Dockerfile, compose api service with a named data volume, direct route, local signing, DECOSA_VEX_OFFLINE=1; torn down after","notes":"The assembly prompt's smoke test passed against the already-running local Qwen3.8-27B vLLM (network_mode host instead of the compose llm service): libwebp CVE-2023-4863 not_affected (not in execute path), CVE-2022-0778 affected, CVE-2009-4487 under investigation, all receipts attested, envelope and record verified, 2.0 s; the rehearsal bundle passed 10/10. The collector's --image path (crane) found that absolute symlinks were dropped when unpacking; fixed in b5a465e and re-checked (5,370 files). Model-server startup was not re-run."},"known_limits":["Hosted verification ran on the pre-release server (decosa-api the pre-release branch on our server, gateway route). The production API gets this vertical when the branch merges.","Measured against one vendor's VEX (Canonical) on two Ubuntu releases, with constructed probe findings; not against Red Hat or Debian data or a security engineer's review.","Recall of not_affected is about one in three: distro VEX often rests on maintainer judgment this tool does not make.","Language packages (PyPI, npm, Go) use the same OSV range check but were not measured against labels; there is no language-level reachability.","NVD allows 5 requests per 30 s without a key, so the hosted service fetches few new NVD records per run; findings then rest on OSV and the scanner's own data."],"nightly_covers":null},"nightly":"https://api.decosa.ai/verify/status"},"eval_summary":{"metrics":[{"name":"not_affected precision against Canonical's VEX","value":"154 / 159","unit":null,"n":159,"split":"test","note":"held-out ubuntu:jammy-20220421, run once with the code frozen"},{"name":"False not_affected on vendor-affected findings","value":"5 / 378","unit":null,"n":378,"split":"test","note":"all five supporting checks hold on the image (other platform, or a program from another package)"},{"name":"not_affected recall","value":"154 / 474","unit":null,"n":474,"split":"test","note":"Canonical's not_affected statements for packages in the image, added as probes"},{"name":"under_investigation rate","value":"106 / 852","unit":null,"n":852,"split":"test","note":"98 of the 106 are findings Canonical calls not_affected"},{"name":"Justification equal to Canonical's","value":"151 / 154","unit":null,"n":154,"split":"test","note":"when both say not_affected"},{"name":"Supporting checks that hold when re-done with other tools","value":"159 / 161","unit":null,"n":161,"split":"test","note":"readelf, find, grep, file, packaging.version; 0 fail, 2 could not be parsed"},{"name":"Rules only (no model): not_affected precision","value":"159 / 164","unit":null,"n":164,"split":"test","note":"same checks, no model call"},{"name":"not_affected precision, dev set","value":"19 / 24","unit":null,"n":24,"split":"dev","note":"ubuntu:focal-20210416, where prompts and code were tuned"}],"dataset":"Grype 0.119 findings on ubuntu:focal-20210416 (dev, 458 findings) and ubuntu:jammy-20220421 (test, 912), plus Canonical's not_affected statements for packages in each image as probe findings; labels from Canonical's OpenVEX feed, 26 Sep 2026.","held_out":true,"caveats":["One vendor's labels (Canonical) on two releases; Canonical writes about source packages and this tool about the image, which explains all five risky errors on the test set.","Probe findings are constructed, so precision depends on the mix; read the per-class counts.","The model adds little to the decisions: rules only reach the same precision. The building agent read the statements; no independent security engineer.","Distro packages only; language packages were not measured."],"date":"2026-09-26","doc_url":"https://decosa.ai/metrics/evals/vex-triage"},"stack":{"summary":"For product security teams and software vendors. Give it a scanner report for a container image, its SBOM and an evidence bundle from a small collector that runs next to the image. For each finding, code checks the package, the version against the fix and the OSV and NVD ranges, the distro changelog, what actually loads the library, the settings and programs the advisory names, and the platform; an open model writes a VEX status with quoted evidence, and code gates it. Not affected needs a strong check; everything else stays affected, fixed or under investigation. Drafts for a security engineer to sign off.","tagline":"A Grype or Trivy report in; a VEX status per CVE with the evidence from the image, signed OpenVEX and CycloneDX VEX out.","deployment":"hosted-or-self-host","regulatory_note":"Checked 26 Sep 2026. Statuses and justifications follow CISA's Minimum Requirements for Vulnerability Exploitability eXchange (VEX) (21 Apr 2023, https://www.cisa.gov/resources-tools/resources/minimum-requirements-vulnerability-exploitability-exchange-vex) and OpenVEX v0.2.0 (https://github.com/openvex/spec); CycloneDX 1.6 VEX uses its own justification vocabulary (mapping in GET /vex/info). EU Cyber Resilience Act, Regulation (EU) 2024/2847: vulnerability and incident reporting (Art. 14) applies from 11 Sep 2026 and the main obligations, including documenting components with a software bill of materials (Annex I Part II), from 11 Dec 2027 (Art. 71(2)); we could not re-read the Official Journal text on 26 Sep 2026, so treat these dates as from our 26 Sep incident-pack check, not independently verified here. VEX is a way to state exploitability; this tool drafts statements and never says an image is secure, and it makes no CRA or other compliance determination. Model licence: Apache-2.0 (Qwen3.8-27B). Advisory sources: OSV.dev (per-record licence; GitHub advisories CC BY 4.0) and NVD (US government work).","components":[{"id":"llm","role":"One typed judgment per finding the rules do not settle: a VEX status with quoted evidence from the advisory lines and the checks, and the impact statement. Code gates every answer.","name":"Qwen3.8-27B (NVIDIA NVFP4)","hf_repo":"nvidia/Qwen3.8-27B-NVFP4","license":"Apache-2.0","params":"27.8B","quant":"NVFP4 (MLP) + FP8 (attention/GDN), FP8 KV cache, MTP speculative decoding k=3","vram_gb":57,"memory_gb_estimate":null,"engine":"vLLM 0.29.0","receipt_coverage":"strong","in_hosted_demo":true,"tiers":["standard"],"alternative_to":null},{"id":"checks","role":"The collector (on your machine) and the checks in decosa-api: package in the SBOM, version against the fix and the OSV/NVD ranges, the Debian changelog, ELF loader chains, the advisory's programs and settings, the platform; the evidence gates; OpenVEX, CycloneDX VEX and DSSE signing.","name":"decosa VEX checks and collector (code, no model)","hf_repo":null,"license":"AGPL-3.0-or-later","params":null,"quant":null,"vram_gb":0,"memory_gb_estimate":null,"engine":"Python 3.11+ on CPU","receipt_coverage":"none","in_hosted_demo":true,"tiers":["lite","standard"],"alternative_to":null}],"tiers":[{"id":"lite","label":"Lite · rules only, no GPU","summary":"The same checks and gates with no model call (mode \"rules\"): the same statuses on the held-out set, but no written impact statement beyond the check's own text, and nothing routed to under investigation.","components":["checks"],"hardware":"Any CPU","quality_evidence":[{"metric":"not_affected precision against Canonical's VEX, held-out jammy set","value":"159/164","source":"decosa-api docs/evals/vex-triage.md, 2026-09-26 (rules-only baseline on the same run)"},{"metric":"false not_affected on vendor-affected findings, held-out","value":"5/378","source":"decosa-api docs/evals/vex-triage.md, 2026-09-26"},{"metric":"not_affected recall, held-out","value":"159/474","source":"decosa-api docs/evals/vex-triage.md, 2026-09-26"}],"latency_note":"measured: the checks for hundreds of findings take seconds on CPU","in_hosted_demo":false,"receipt_coverage":"none","receipt_note":"No model call, so no receipts; the VEX documents and the record are signed by the server's key.","hosting":null},{"id":"standard","label":"Standard · the hosted demo, one 96 GB card","summary":"The checks plus one Qwen3.8-27B judgment per finding the rules do not settle: a written impact statement with quotes, and unclear cases sent to a person. Every model call receipted.","components":["llm","checks"],"hardware":"1x RTX PRO 6000 Blackwell 96 GB","quality_evidence":[{"metric":"not_affected precision against Canonical's VEX, held-out jammy set (912 findings, run once)","value":"154/159","source":"decosa-api docs/evals/vex-triage.md, measured on our server 2026-09-26, gateway route; code frozen on the focal dev set"},{"metric":"false not_affected on vendor-affected findings (the risky error), held-out","value":"5/378; all five checks hold on the image (other platform, or a program from another package) but Canonical scores the source package","source":"decosa-api docs/evals/vex-triage.md, 2026-09-26"},{"metric":"not_affected recall / under_investigation rate, held-out","value":"154/474 / 106/852","source":"decosa-api docs/evals/vex-triage.md, 2026-09-26"},{"metric":"supporting checks re-done with readelf, find and grep","value":"159/161 hold, 0 fail (2 could not be parsed)","source":"decosa-api docs/evals/vex-triage.md, scripts/vex_evidence_audit.py, 2026-09-26"},{"metric":"labels from Red Hat, Debian or a security engineer","value":"not measured yet","source":null}],"latency_note":"measured on our server under a shared gateway: seconds for the nginx sample; many minutes for hundreds of findings with calls in parallel","in_hosted_demo":true,"receipt_coverage":"strong","receipt_note":"Hosted: gateway-signed receipt per model call. Self-hosted: attested by the box's key.","hosting":null}],"alternates":[],"services":[{"name":"decosa-api","port":8445,"image":"${DECOSA_REGISTRY}/decosa-api:0.1.0","purpose":"The checks, the gates, the advisory store, DSSE signing and the HTTP API (/vex/*). No GPU. Binds 127.0.0.1 by default."},{"name":"decosa-llm","port":8000,"image":"${DECOSA_REGISTRY}/decosa-llm:0.1.0","purpose":"vLLM OpenAI endpoint for Qwen3.8-27B. Internal to the compose network; not needed in rules mode."},{"name":"collector (CLI, on your machine)","port":null,"image":null,"purpose":"python -m decosa_api.verticals.vex.collect: unpacks the image, searches it, writes the evidence bundle. The image never leaves the machine."}],"tools":[{"name":"Grype and Syft (Anchore)","url":"https://github.com/anchore/grype","license":"Apache-2.0","purpose":"The scanner report and SBOM the demo samples use; Grype reads the signed OpenVEX back with --vex (tested)."},{"name":"Trivy (Aqua Security)","url":"https://github.com/aquasecurity/trivy","license":"Apache-2.0","purpose":"Its JSON report is accepted as input; its --vex option was not tested here."},{"name":"crane (go-containerregistry)","url":"https://github.com/google/go-containerregistry","license":"Apache-2.0","purpose":"The collector unpacks images with crane export (or docker export)."},{"name":"OSV.dev API","url":"https://osv.dev","license":"per record (CVE/NVD public; GitHub advisories CC BY 4.0)","purpose":"Advisory text and ecosystem version ranges, fetched by id and kept with a hash."},{"name":"NVD CVE API 2.0","url":"https://nvd.nist.gov/developers/vulnerabilities","license":"US government work (public domain)","purpose":"Descriptions and CPE version ranges, fetched by CVE id; 5 requests per 30 s without a key."},{"name":"NVIDIA Vulnerability Analysis blueprint","url":"https://github.com/NVIDIA-AI-Blueprints/vulnerability-analysis","license":"Apache-2.0","purpose":"The version-range, package and Debian changelog backport checks follow its pre-triage ideas (rewritten, not copied)."},{"name":"decosa typed-judgment (vertical 24)","url":"https://decosa.ai/apps/typed-judgment","license":"AGPL-3.0-or-later (decosa-api)","purpose":"The answer format and the stated-confidence mapping, refit on the dev set."},{"name":"decosa record (vertical 07) and POST /record/verify","url":"https://decosa.ai/apps/record","license":"AGPL-3.0-or-later (decosa-api)","purpose":"The hash-chained, Ed25519-signed evidence record anyone can re-check."}],"hardware":[{"tier":"1x RTX PRO 6000 Blackwell 96 GB","fits":true,"notes":"Measured: the hosted Qwen3.8-27B runs on one of these cards on our server."},{"tier":"1x L40S / RTX 6000 Ada 48 GB","fits":null,"notes":"Not measured. FP8 Qwen3.8-27B with a shorter context."},{"tier":"CPU only","fits":true,"notes":"Rules mode (no model), the collector, signing and verification need no GPU; measured on the held-out set with the same precision as the model."}],"latency":[{"lane":"nginx sample, 12 findings, busy shared gateway","typical_ms":14264,"source":"measured on our server 2026-09-26, pre-release server, gateway route (12 model calls)"},{"lane":"912 findings, 8 calls in parallel","typical_ms":1372000,"source":"decosa-api docs/evals/vex-triage.md, held-out jammy run, 2026-09-26"},{"lane":"collector on nginx:1.20.0 (550 findings, 5,370 files)","typical_ms":1900,"source":"measured on our server 2026-09-26, rootfs already unpacked"}],"benchmark":{"title":"Does it only say not affected when it can show why?","intro":"Grype findings on two public Ubuntu images, scored against Canonical's own OpenVEX for the same CVE and package. Canonical's not_affected statements for packages in the image were added as probes, shaped like an upstream-matching scanner's findings. Everything was tuned on ubuntu:focal-20210416; ubuntu:jammy-20220421 was run once with the code frozen.","rows":[{"label":"not_affected that Canonical agrees with","value":"154 of 159","detail":"held-out jammy set; dev 19 of 24"},{"label":"Vendor-affected findings wrongly marked not_affected","value":"5 of 378","detail":"all five checks hold on the image: 32-bit, PowerPC or Windows only, or a program from another package"},{"label":"Canonical's not_affected found","value":"154 of 474","detail":"the rest rest on a maintainer's judgment this tool never makes"},{"label":"nginx:1.20.0 findings closed with strong evidence","value":"168 of 550","detail":"rules mode; mostly libraries only unloaded modules pull in; no vendor labels for this image"}],"points":[{"heading":"Where it disagrees","text":"Its not_affected answers are about the image, and a distro's VEX is about the source package: an AES-OCB bug that only affects 32-bit x86 is not in the execute path of an amd64 image, but Canonical still ships the fix and calls the package affected. All five disagreements on the held-out set are of that kind."},{"heading":"What the model adds","text":"Little to the decisions: the rules alone reach the same precision (159 of 164). The model writes the impact statement with quotes and sends unclear cases to under investigation; the gates turned 101 of its unsupported not_affected proposals into under investigation."}],"source":"decosa-api docs/evals/vex-triage.md, 2026-09-26"},"notes":[]},"buyer_facts":[{"label":"What it gives you","value":"A VEX status per finding with its evidence (the checks and the advisory lines it rests on), signed OpenVEX and CycloneDX VEX drafts in DSSE envelopes, a Markdown report and a signed evidence record listing every advisory's hash."},{"label":"What it does not do","value":"It does not scan (it triages a Grype or Trivy report), trace calls inside programs, see configs mounted at run time or dlopen by path, or decide attacker control or mitigations. It never says an image is secure; a security engineer signs off."},{"label":"Data retention","value":"Nothing kept but advisory text: reports, SBOMs and evidence bundles live in memory for the request; logs carry counts only. The image never reaches the API: the collector runs on your machine."},{"label":"What leaves the box","value":"Hosted: the scanner report, SBOM and evidence bundle (search hits, loader chains, config excerpts with secret-looking lines redacted) go to decosa-api, and the prompts to Qwen3.8-27B through our gateway, whose receipts hold hashes. Both routes: vulnerability ids to OSV.dev and NVD, which you can switch off."},{"label":"Round trip","value":"Tested: grype --vex with the signed OpenVEX from the nginx demo hides exactly the four findings it proves not affected and keeps the other 546."},{"label":"Typical run cost","value":"One model call per finding the rules do not settle: about a cent for the nginx sample at the gateway list price. Rules mode costs no model tokens."},{"label":"Findings per run","value":"25 by default, 60 hosted (known-exploited first, then severity and EPSS); self-host raises it with DECOSA_VEX_TRIAGE_MAX."}],"data_handling":{"page":"/data#vex-triage","self_host":{"level":"confidential","leaves":"identifiers","summary":"Runs on your machine; by default only short identifiers or a digest go to the public services listed in external_calls."},"hosted":{"level":"operator-processed","demo_only":false,"summary":"TLS to Decosa's server, then decrypted and processed by Decosa's API server, with the open models run by NEAR AI through OpenRouter, with Reka AI as the only fallback under Decosa's account.","gpus":"operator-contracted","third_parties":[],"retention":"Nothing kept but advisory text: reports, SBOMs and evidence bundles live in memory for the request; logs carry counts only. The image never reaches the API: the collector runs on your machine.","used_for_training":false,"encrypted_while_processed":false},"sealed_tier":{"applies":false,"note":"The sealed tier (raw chat only, never use-case pipelines) is paused at launch (/docs/sealed-tier)."},"external_calls":[{"to":"OSV.dev and NVD (NIST)","route":"both","sends":"identifiers","what":"Vulnerability ids (CVE, GHSA) to fetch advisory text, from the API and from the collector; never the report, the SBOM, the evidence or the image.","default":"on","off":"Set DECOSA_VEX_OFFLINE=1 and run the collector with --offline; load advisories with the import command or send them in the request. DECOSA_VEX_NVD=0 turns NVD alone off."}]},"console":{"href":"/tools/developer/vex-triage","input":"vex","lanes":[{"id":"checks","title":"Checks in code","kind":"list"},{"id":"statements","title":"VEX statements","kind":"list"},{"id":"documents","title":"Signed VEX","kind":"json"},{"id":"record","title":"Evidence record","kind":"json"}],"samples":[{"n":1,"id":"nginx-1.20.0","title":"Nginx 1.20.0","deep_link":"/tools/developer/vex-triage?sample=1&autorun=0"},{"n":2,"id":"nginx-1.20.0-image-filter","title":"Nginx 1.20.0 image filter","deep_link":"/tools/developer/vex-triage?sample=2&autorun=0"},{"n":3,"id":"python-3.10.0-slim","title":"Python 3.10.0 slim","deep_link":"/tools/developer/vex-triage?sample=3&autorun=0"}],"deep_link_params":{"sample":"1-based index into samples, or a sample id","autorun":"1 = start the run once the sample is loaded; 0 (default) = only preselect","reduce-motion":"1 = turn off animations"}},"api":{"base":"https://api.decosa.ai","contract":"/api/contract.json","contract_markdown":"/api/contract.md","reference":"/docs/api","keys":"/account/keys"},"prompts":{"hosted":"/prompts/vex-triage-hosted.md","selfhost":"/prompts/vex-triage-selfhost.md","assemble":"/prompts/vex-triage-assemble.md","mac":null},"rehearsal":{"bundle":"/samples/vex-triage.zip","bundle_url":"https://decosa.ai/samples/vex-triage.zip","folder":"/samples/vex-triage/","expected":"/samples/vex-triage/expected.json","files":["/samples/vex-triage/expected.json","/samples/vex-triage/inputs/evidence.json","/samples/vex-triage/inputs/grype.json"],"bytes":66935,"checks":["libwebp's known-exploited CVE-2023-4863 is not in the execute path (only an unloaded module loads it)","and its status is not_affected","OpenSSL's CVE-2022-0778 stays affected for both packages","CVE-2009-4487 is never not_affected on an exact-version NVD entry","the OpenVEX document names the author","every statement is marked pending review","the signed OpenVEX verifies","a changed OpenVEX payload does not","the evidence record verifies","every model call has a signed receipt"],"licence":"Public image (Docker Official Image nginx:1.20.0). Scanner report: Grype 0.119 (Apache-2.0); evidence bundle from decosa-api's collector (Apache-2.0). Advisory text comes from the API's own store (OSV.dev and NVD, public). Part of decosa-api, AGPL-3.0-or-later.","about":"The official nginx:1.20.0 image (May 2021) scanned with Grype, and the collector's evidence bundle for three of its findings. libwebp's CVE-2023-4863 is known exploited, but only the image filter module loads libwebp and the shipped nginx.conf does not load it: it must come back not_affected with vulnerable_code_not_in_execute_path. OpenSSL's CVE-2022-0778 is in a library nginx itself links: affected. CVE-2009-4487, where NVD lists a single exact nginx version, must never be not_affected on that weak evidence. The signed OpenVEX must verify, and fail once a status is changed; the evidence record must verify.","run":{"containers":"docker compose exec api python scripts/rehearse.py vex-triage","checkout":"python scripts/rehearse.py vex-triage --bundle vex-triage.zip --base-url http://127.0.0.1:8445","mac":".venv/bin/python scripts/rehearse.py vex-triage"},"guidance":"Set up with a coding agent (we recommend Claude Code with Claude Opus 5.5; any capable coding agent works) on mock data only, run the rehearsal until every check passes, then run your own data locally yourself. Never give the agent real data during setup."},"hardware_fit":{"check":"/self-host/hardware?use=vex-triage","data":"/api/hardware.json","tiers":[{"id":"lite","gpu_gb":0,"basis":null,"unknown":[]},{"id":"standard","gpu_gb":57.6,"basis":"stack","unknown":[]}],"mac":null},"links":{"page":"/tools/developer/vex-triage","json":"/use-cases/vex-triage.json","metrics":"/metrics/vex-triage","console":"/tools/developer/vex-triage","console_sample":"/tools/developer/vex-triage?sample=1&autorun=0","stack":"/tools/developer/vex-triage#stack","try_live":"/tools/developer/vex-triage","watch":"/tools/developer/vex-triage","build":"/tools/developer/vex-triage#build","self_host":"/tools/developer/vex-triage#self-host","prompts":{"hosted":"/prompts/vex-triage-hosted.md","selfhost":"/prompts/vex-triage-selfhost.md","assemble":"/prompts/vex-triage-assemble.md","mac":null}}}