SBOM Schema Validation

Audience: scanner and fixture maintainers who add, update, or debug SBOM test fixtures in Stella Ops.

This document describes how Stella Ops validates SBOM (Software Bill of Materials) fixtures against official JSON schemas, how to run that validation locally, and how to troubleshoot the common failures.

Status (reconciled against source): The standalone schema-validation CI flow described below is currently archived — the workflows live under .gitea/workflows-archived/ (schema-validation.yml, determinism-gate.yml), not under .gitea/workflows/, so they do not run on the active PR/push gate today. The validation scripts (.gitea/scripts/validate/validate-*.sh) and the JSON schemas (docs/schemas/) still exist and can be run locally. CycloneDX schema validation for downstream/consumer pipelines is exposed as the reusable workflow template .gitea/workflow-templates/sbom-canonicalization-check.yml. Treat the per-format CI jobs in this document as a reference and roadmap until the workflows are restored.

Overview

Stella Ops validates SBOM fixtures against official JSON schemas to catch schema drift before runtime. This ensures:

Supported Formats

The canonical schema directory is docs/schemas/ (copies of the CycloneDX and SPDX schemas are also mirrored under docs/modules/sbom-service/schemas/).

FormatVersionSchema LocationValidator
CycloneDX1.7docs/schemas/cyclonedx-bom-1.7.schema.jsonsbom-utility
SPDX3.0.1docs/schemas/spdx-jsonld-3.0.1.schema.jsonpyspdxtools / check-jsonschema
OpenVEX0.2.0docs/schemas/openvex-0.2.0.schema.json (MISSING — not present in repo; the validate-vex.sh script and archived workflow reference it but the file is absent)ajv-cli

Note (verified against source): The archived workflows and the validate-sbom.sh script still hard-code docs/schemas/cyclonedx-bom-1.6.schema.json as the schema path, but no cyclonedx-bom-1.6.schema.json exists — only cyclonedx-bom-1.7.schema.json ships. The CycloneDX runtime validator (CycloneDxValidator.cs) does not pass an explicit --schema; it invokes sbom-utility validate --input-file <file> --format json and relies on sbom-utility’s built-in schema selection. The sbom-utility version pinned by the scripts and archived workflows is 0.16.0.

CI Workflows

Archived. Both workflows below currently reside under .gitea/workflows-archived/ and are not wired into the active CI gate. Their trigger paths and schema references (below) are reproduced as-authored; note the stale cyclonedx-bom-1.6.schema.json path inside them (see the Supported Formats note above).

Schema Validation Workflow

File: .gitea/workflows-archived/schema-validation.yml (archived; formerly .gitea/workflows/schema-validation.yml)

Runs on:

Jobs:

  1. validate-cyclonedx - Validates all CycloneDX fixtures
  2. validate-spdx - Validates all SPDX 3.0.1 fixtures
  3. validate-vex - Validates all OpenVEX 0.2.0 fixtures
  4. validate-negative - Verifies invalid fixtures are correctly rejected
  5. summary - Aggregates results

Determinism Gate Integration

File: .gitea/workflows-archived/determinism-gate.yml (archived; formerly .gitea/workflows/determinism-gate.yml)

The determinism gate runs a schema-validation job before the determinism-gate job; the drift-check job fails the workflow if schema validation fails, blocking the determinism check.

To skip schema validation (e.g., during debugging) the workflow exposes a workflow_dispatch input:

# Via workflow_dispatch input
skip_schema_validation: true

Fixture Directories

The archived schema-validation workflow scans these directories for SBOM fixtures:

DirectoryPurpose
src/__Tests/__Benchmarks/golden-corpus/Golden reference fixtures for reproducibility testing
src/__Tests/fixtures/Test fixtures for unit and integration tests
src/__Tests/__Datasets/seed-data/Initial seed data for development environments
src/__Tests/__Benchmarks/vex-lattice/OpenVEX fixtures (scanned by the validate-vex job only)
src/__Tests/fixtures/invalid/Excluded from positive validation - Contains intentionally invalid fixtures used by the validate-negative job for negative testing

Note: the local validate-sbom.sh --all helper scans only src/__Tests/__Benchmarks/golden-corpus/, and validate-vex.sh --all scans bench/golden-corpus, bench/vex-lattice, and datasets/ — these script defaults differ from the workflow’s fixture-directory list above.

Local Validation

Using the Validation Scripts

The scripts live under .gitea/scripts/validate/ (run them from there or by absolute path):

# Validate a single CycloneDX file
./.gitea/scripts/validate/validate-sbom.sh path/to/sbom.json

# Validate all CycloneDX fixtures in src/__Tests/__Benchmarks/golden-corpus/
# (--all takes no directory argument; the search root is fixed in the script)
./.gitea/scripts/validate/validate-sbom.sh --all

# Validate SPDX file
./.gitea/scripts/validate/validate-spdx.sh path/to/sbom.spdx.json

# Validate OpenVEX file
./.gitea/scripts/validate/validate-vex.sh path/to/vex.openvex.json

Each script auto-installs its underlying tool if missing (validate-sbom.shsbom-utility; validate-vex.shajv-cli + ajv-formats; validate-spdx.shspdx-tools / check-jsonschema). Both validate-sbom.sh and validate-vex.sh accept an optional --schema <path> override.

Using sbom-utility Directly

# Install sbom-utility (version pinned by the Stella Ops scripts/workflows)
curl -sSfL "https://github.com/CycloneDX/sbom-utility/releases/download/v0.16.0/sbom-utility-v0.16.0-linux-amd64.tar.gz" | tar xz
sudo mv sbom-utility /usr/local/bin/

# Validate against the bundled CycloneDX schema
sbom-utility validate --input-file sbom.json --schema docs/schemas/cyclonedx-bom-1.7.schema.json

# Or let sbom-utility pick the schema from the document's specVersion
# (this is what the runtime CycloneDxValidator does)
sbom-utility validate --input-file sbom.json --format json

Troubleshooting

Common Validation Errors

1. Invalid specVersion

Error: enum: must be equal to one of the allowed values

Cause: The specVersion field contains an invalid or unsupported version.

Solution:

// Invalid
"specVersion": "2.0"

// Valid
"specVersion": "1.6"

2. Missing Required Fields

Error: required: must have required property 'name'

Cause: A component is missing required fields.

Solution: Ensure all components have required fields:

{
  "type": "library",
  "name": "example-package",
  "version": "1.0.0"
}

3. Invalid Component Type

Error: enum: type must be equal to one of the allowed values

Cause: The component type is not a valid CycloneDX type.

Solution: Use valid types: application, framework, library, container, operating-system, device, firmware, file, data

4. Invalid PURL Format

Error: format: must match format "purl"

Cause: The package URL (purl) is malformed.

Solution: Use correct purl format:

// Invalid
"purl": "npm:example@1.0.0"

// Valid
"purl": "pkg:npm/example@1.0.0"

CI Failure Recovery

  1. Identify the failing fixture: Check CI logs for the specific file
  2. Inspect the fixture: open path/to/failing-fixture.json
  3. Run local validation: ./.gitea/scripts/validate/validate-sbom.sh path/to/failing-fixture.json
  4. Fix the schema issues: Use the error messages to guide corrections
  5. Verify the fix: Re-run local validation
  6. Push and verify CI passes

Negative Test Failures

If negative tests fail with “UNEXPECTED PASS”:

  1. The invalid fixture in src/__Tests/fixtures/invalid/ somehow passed validation
  2. Review the fixture to ensure it contains actual schema violations
  3. Update the fixture to include more obvious violations
  4. Document the expected error in src/__Tests/fixtures/invalid/README.md

The existing negative fixtures are cyclonedx-wrong-version.json, cyclonedx-missing-required.json, and cyclonedx-invalid-component.json; their expected errors are tabulated in src/__Tests/fixtures/invalid/README.md.

Adding New Fixtures

Valid Fixtures

  1. Create fixture in appropriate directory (src/__Tests/__Datasets/golden-corpus/, src/__Tests/fixtures/)
  2. Ensure it contains the format marker:
    • CycloneDX: "bomFormat": "CycloneDX"
    • SPDX: "spdxVersion" or "@context" with SPDX
    • OpenVEX: "@context" with https://openvex.dev/ns (or an "@type" of https://openvex...)
  3. Run local validation before committing
  4. CI will automatically validate on PR once the schema-validation workflow is restored from .gitea/workflows-archived/

Invalid Fixtures (Negative Testing)

  1. Create fixture in src/__Tests/fixtures/invalid/
  2. Add $comment field explaining the defect
  3. Update src/__Tests/fixtures/invalid/README.md with expected error
  4. Ensure the fixture has the correct format marker
  5. The validate-negative job verifies it fails validation

Schema Updates

When updating schema versions:

  1. Place the new schema in the canonical docs/schemas/ directory (keep the docs/modules/sbom-service/schemas/ mirror in sync). Also update the SCHEMA/DEFAULT_SCHEMA path references inside .gitea/scripts/validate/validate-*.sh and the archived workflows — they currently still point at the obsolete cyclonedx-bom-1.6.schema.json.
  2. Update SBOM_UTILITY_VERSION in the scripts/workflows if needed (currently 0.16.0)
  3. Run full validation to check for new violations
  4. Update documentation with the new version
  5. Update reproducibility.md with schema version changes (its Schema Versions table currently lists CycloneDX 1.6 and likewise needs reconciling to 1.7)

References

External specifications