Vulnerability-source provider contract¶
A provider turns normalized SBOM components into VulnerabilityFinding values. A built-in renderer adapts them into atomic assertions before it writes a format. Neither stage needs the provider’s request or storage format.
The compatibility guarantee for this contract begins with Vexcalibur 1.0. Before 1.0, pin an exact release. The compatibility policy explains which extension changes can ship within 1.x.
This reference covers both first-party sources maintained with Vexcalibur and external sources owned by an embedding application. External source code stays in the embedding’s package and implements the same public protocol.
Protocol¶
A source implements vexcalibur.api.VulnerabilitySource:
from vexcalibur.api import (
ComponentIdentity,
VulnerabilityFinding,
)
class ExampleSource:
def findings_for_components(
self,
components: tuple[ComponentIdentity, ...],
) -> tuple[VulnerabilityFinding, ...]:
return ()
findings_for_components receives the complete normalized component tuple and
returns zero or more immutable findings.
See the tested custom generation example for a provider that returns no findings and prints a custom execution report.
Custom providers cannot assign their own report category. The caller must pass
a complete GenerationExecutionContext with
FindingSourceCategory.CUSTOM before it requests a report. Vexcalibur records
custom without exposing the provider name or endpoint.
Vexcalibur reserves local_file, public_osv, and custom_osv for exact
built-in source implementations. An injected OSV client is an extension and
therefore records custom, regardless of the endpoint it contacts.
Optional preflight protocol¶
A source that must validate policy before Vexcalibur loads remote inventory can
also implement GenerationSourcePreflight:
from dataclasses import dataclass
from vexcalibur.api import (
ComponentIdentity,
GenerationSourcePreflight,
VulnerabilityFinding,
VulnerabilitySourceInputError,
)
@dataclass(frozen=True)
class ExampleSource(GenerationSourcePreflight):
public_data_sharing_allowed: bool
def validate_before_inventory_load(self) -> None:
if not self.public_data_sharing_allowed:
raise VulnerabilitySourceInputError(
"public data sharing requires explicit consent"
)
def findings_for_components(
self,
components: tuple[ComponentIdentity, ...],
) -> tuple[VulnerabilityFinding, ...]:
return ()
vexcalibur.api.generate_vex_from_github_source_result owns remote-inventory
generation for the supported Python API and the CLI. It calls the hook once,
before GitHub authentication. See the Python API
reference for the complete ordered contract.
The built-in GitHub helpers use the same internal sequence. The hook must only inspect local configuration. It must not open a file, create a network client, or make a request.
Raise VulnerabilitySourceInputError when the source configuration is invalid
for remote-inventory generation. Vexcalibur reports that exception as an
SbomError and retains the original exception as its cause. Other exceptions
propagate unchanged. A source with no preflight work can omit the method.
LocalFindingsSource omits the hook. Its document can refer to component
references from the GitHub SBOM, so Vexcalibur reads and validates the file
after it loads that inventory. This path does not send the inventory to a
finding service.
Component identity¶
Field |
Type |
Meaning |
|---|---|---|
|
|
CycloneDX |
|
|
Component name |
|
|
Component version when supplied |
|
|
Parsed package URL |
|
|
CycloneDX component type; defaults to |
The effective component version comes from the PURL when the PURL is versioned. Otherwise it comes from version. When both fields supply a version, Vexcalibur compares the decoded PURL version with version and rejects the component unless they are equal. Percent encoding that decodes to the same version is not a conflict.
Vulnerability finding¶
Field |
Type |
Required or default |
Meaning |
|---|---|---|---|
|
|
Required |
Vulnerability identifier. |
|
|
Required |
Provider or assessment source name. |
|
|
Required |
Provider or advisory URL with no username or password. CSAF requires an ASCII RFC 3986 HTTP(S) URI. |
|
|
Required |
Reference copied from an input |
|
|
Required |
Canonical serialized package URL for that component. This is a string, unlike |
|
|
|
Source update time. |
|
|
|
VEX disposition for the component and vulnerability. |
|
|
|
Human-readable analysis basis. |
|
|
|
Remediation or mitigation guidance. OpenVEX, CSAF, and SPDX 3 require it for |
|
|
|
Deployment impact. OpenVEX, CSAF, and SPDX 3 require it for |
|
|
|
Confirmed fixed product version. OpenVEX, CSAF, and SPDX 3 require it for |
|
|
|
Machine-readable remediation kind. CSAF requires it for |
remediation_category accepts mitigation, no_fix_planned, none_available, vendor_fix, or workaround.
component_ref must equal a reference in the input component tuple. The built-in adapter rejects an unknown reference, a duplicate component reference, or a finding package URL that differs from its component.
Do not place credentials, signed-download secrets, or access tokens in source_url. The shared adapter and document boundary reject URL userinfo without copying it into an error. Query values are format-visible and are not a secret-storage mechanism.
Low-level result mappers must require explicit provenance. They must not label arbitrary compatible-format results as an official provider by default.
OpenVEX, CSAF, and SPDX 3 reject action_statement, impact_statement, and
fixed_version on states where they are not required. OpenVEX and SPDX 3
reject nonidentical assertions for one vulnerability and emitted product. CSAF
groups same-effective-status provenance and evidence, but rejects
contradictory effective statuses for that pair. CSAF requires
remediation_category on exploitable and rejects it on other states; SPDX 3
accepts it only on exploitable. CycloneDX ignores all four evidence fields.
The adapter retains remediation_category. CSAF serializes it as the category
of a product-scoped remediation. SPDX 3 records it in status notes, so it
participates in SPDX grouping and document identity. CycloneDX and OpenVEX do
not serialize it, so it does not change their grouping, content, or document
identity.
An OpenVEX, CSAF, or SPDX 3 product must have a version in its package URL or component version field. Those renderers reject an assertion that would identify every package version.
Errors¶
Raise VulnerabilitySourceInputError when the component inventory cannot form valid provider queries or matches. The shared generation path reports this as an SBOM input error.
Raise a VulnerabilitySourceError subclass for configuration, network, response, parsing, or local-file failures. Keep a narrower provider exception when callers need to distinguish the failure.
Expected CLI failures should not expose Python tracebacks.
Network boundary¶
A network source must make public data sharing explicit. At minimum, it should:
identify its public endpoint.
require consent before sending package URLs, versions, or an SBOM-derived inventory there.
accept a private endpoint when the upstream API can be mirrored.
document the data that leaves the runner.
attribute returned data to the effective provider rather than a compatible wire protocol.
disable or explicitly validate redirects so queries cannot cross the chosen trust boundary.
bound encoded and decoded response bytes, pagination, overall time, parsed records, and the provider-to-component expansion.
The OSV source implements this policy with --allow-public-osv and --osv-url.
A custom source owns its network policy whether it is passed to
generate_vex_from_source or generate_vex_from_github_source_result.
An offline source should not create a network client. It should define limits for local data and reject ambiguous component matches.
Implementation contract¶
First-party provider code belongs under src/vexcalibur/sources. An external
provider remains in the embedding’s package; it does not need to modify or
install modules into the vexcalibur namespace.
Validate configuration before I/O.
Map
ComponentIdentityvalues to provider queries or lookup keys.Validate each response or local document.
Return
VulnerabilityFindingvalues in stable order.Leave assertion adaptation, grouping, and serialization to the selected renderer.
Do not duplicate output-format rules in a provider. For built-in formats, the document adapter owns shared identity checks. A renderer owns grouping, required evidence, and format-specific loss.
Tests¶
Cover configuration, trust-boundary enforcement, parsing, invalid shapes, mapping, and CLI error reporting. A paginated network source also needs exact-limit and limit-plus-one body tests, compressed and chunked responses, error-body limits, repeated and oversized tokens, pagination floods, record deduplication, total deadlines, and expansion-limit tests.
When a provider implements GenerationSourcePreflight, add an ordering test
that makes the hook fail. Assert that Vexcalibur did not resolve GitHub
credentials, construct a GitHub client, or call the inventory loader. Add a
successful ordering test that proves the hook runs exactly once before those
operations.
First-party providers put these tests in the Vexcalibur suite. External providers run the equivalent contract and integration tests in their owning package.
Contributors changing a first-party provider should also run the repository gates in Reproduce important gates.
Run live tests only with data approved for the provider’s public endpoint.