SBOM

CycloneDX Specification Deep Dive: Beyond the Basics

CycloneDX is more than a component list. This deep dive covers services, vulnerabilities, compositions, and the parts of the spec most teams overlook.

Michael
Security Standards Researcher
6 min read

Most CycloneDX users know it as a format for listing software components. That's accurate but incomplete. The specification includes capabilities that most teams never use -- service definitions, vulnerability tracking, composition completeness flags, and formulation records that describe how software was built.

Understanding these features matters because they determine how much value you extract from your SBOMs. A component list is useful. A fully-populated CycloneDX BOM is transformative.

CycloneDX Architecture

CycloneDX defines several BOM types:

  • Software Bill of Materials (SBOM) -- components in a software product
  • Software-as-a-Service BOM (SaaSBOM) -- services and APIs
  • Hardware Bill of Materials (HBOM) -- physical components
  • Operations BOM (OBOM) -- runtime configuration and environment
  • Vulnerability Exploitability eXchange (VEX) -- vulnerability status assertions
  • Manufacturing BOM (MBOM) -- build process documentation

All share the same schema. A single CycloneDX document can contain components, services, vulnerabilities, and formulation data simultaneously.

The Component Model

Components are the core of most SBOMs. CycloneDX's component model is richer than it first appears.

Component Types

{
  "type": "library",
  "name": "express",
  "version": "4.18.2",
  "purl": "pkg:npm/express@4.18.2",
  "scope": "required"
}

Supported types include application, framework, library, container, platform, device, firmware, file, operating-system, and data. Using the correct type improves downstream analysis -- a vulnerability in a framework has different implications than one in a file.

Scope

The scope field distinguishes between components needed at runtime (required), components only needed during development (optional), and components intentionally excluded (excluded). This is critical for accurate vulnerability assessment -- a dev-only dependency doesn't affect production.

Evidence

CycloneDX 1.5 introduced evidence fields that document how a component was identified:

{
  "type": "library",
  "name": "lodash",
  "version": "4.17.21",
  "evidence": {
    "identity": {
      "field": "purl",
      "confidence": 0.9,
      "methods": [
        {
          "technique": "manifest-analysis",
          "confidence": 0.95,
          "value": "package-lock.json"
        }
      ]
    },
    "occurrences": [
      {
        "location": "/app/node_modules/lodash/lodash.js",
        "line": 1,
        "offset": 0
      }
    ]
  }
}

Evidence tells consumers how confident they should be in the SBOM's accuracy. A component identified through binary analysis at 60% confidence deserves different treatment than one identified from a lockfile at 99% confidence.

External References

Components can include references to source repositories, issue trackers, build systems, documentation, and more:

{
  "externalReferences": [
    {
      "type": "vcs",
      "url": "https://github.com/expressjs/express"
    },
    {
      "type": "website",
      "url": "https://expressjs.com"
    },
    {
      "type": "issue-tracker",
      "url": "https://github.com/expressjs/express/issues"
    }
  ]
}

These references enable automated workflows: when a vulnerability is found, the issue tracker link lets you navigate directly to report or check for patches.

Services

CycloneDX isn't limited to libraries and packages. The services section describes external services your application depends on:

{
  "services": [
    {
      "bom-ref": "auth-service",
      "name": "Authentication Service",
      "provider": {
        "name": "Auth0",
        "url": ["https://auth0.com"]
      },
      "endpoints": [
        "https://myapp.auth0.com/oauth/token",
        "https://myapp.auth0.com/userinfo"
      ],
      "authenticated": true,
      "x-trust-boundary": true,
      "data": [
        {
          "flow": "bi-directional",
          "classification": "PII"
        }
      ]
    }
  ]
}

Service definitions capture trust boundaries and data flows. This is invaluable for:

  • Third-party risk assessment -- which external services does our application depend on?
  • Data flow analysis -- where does PII travel?
  • Incident scoping -- if Auth0 is compromised, which of our services are affected?

Vulnerabilities and VEX

CycloneDX can embed vulnerability information directly in the BOM:

{
  "vulnerabilities": [
    {
      "id": "CVE-2022-24999",
      "source": {
        "name": "NVD",
        "url": "https://nvd.nist.gov/"
      },
      "ratings": [
        {
          "source": { "name": "NVD" },
          "score": 7.5,
          "severity": "high",
          "method": "CVSSv31",
          "vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
        }
      ],
      "affects": [
        {
          "ref": "qs-component-ref",
          "versions": [
            {
              "version": "6.5.2",
              "status": "affected"
            }
          ]
        }
      ],
      "analysis": {
        "state": "not_affected",
        "justification": "code_not_reachable",
        "detail": "The vulnerable code path requires prototype pollution which is blocked by our input validation layer."
      }
    }
  ]
}

The analysis section is the VEX (Vulnerability Exploitability eXchange) portion. It lets you assert that a vulnerability doesn't affect your product even though the component is present. Valid states: exploitable, in_triage, not_affected, resolved, false_positive.

VEX prevents alert fatigue. Instead of 200 CVEs flagged against your SBOM, you document that 180 of them are not exploitable in your context, leaving 20 that actually need attention.

Compositions and Completeness

One of CycloneDX's most underused features is composition tracking:

{
  "compositions": [
    {
      "aggregate": "complete",
      "assemblies": ["myapp-ref"]
    },
    {
      "aggregate": "incomplete_first_party_only",
      "assemblies": ["vendor-sdk-ref"]
    }
  ]
}

Aggregate values include:

  • complete -- all components are known
  • incomplete -- some components are missing
  • incomplete_first_party_only -- only first-party components are listed
  • incomplete_third_party_only -- only third-party components are listed
  • unknown -- completeness is unknown
  • not_specified -- no assertion made

This is honest metadata. If your SBOM was generated from a lockfile and you know it misses native binary dependencies, say so. Consumers can then decide how much to trust the inventory.

Formulation

Added in CycloneDX 1.5, formulation describes how the software was built:

{
  "formulation": [
    {
      "bom-ref": "build-process",
      "components": [
        {
          "type": "application",
          "name": "gcc",
          "version": "12.2.0"
        }
      ],
      "workflows": [
        {
          "bom-ref": "ci-pipeline",
          "uid": "github-actions-build",
          "tasks": [
            {
              "bom-ref": "compile-step",
              "uid": "compile",
              "name": "Compile source code"
            }
          ]
        }
      ]
    }
  ]
}

Formulation creates a verifiable record of the build environment. Combined with signatures, this enables build provenance verification -- confirming that an artifact was built from specific source code using specific tools.

Dependency Graph

CycloneDX represents dependency relationships explicitly:

{
  "dependencies": [
    {
      "ref": "myapp",
      "dependsOn": ["express", "lodash", "pg"]
    },
    {
      "ref": "express",
      "dependsOn": ["body-parser", "cookie", "debug"]
    }
  ]
}

The dependency graph enables transitive vulnerability analysis. If debug has a CVE, you can trace the chain: myapp -> express -> debug. This helps prioritize remediation -- a transitive dependency five levels deep may be harder to update than a direct dependency.

Signing and Integrity

CycloneDX supports JSON Signature Format (JSF) for signing BOMs:

{
  "signature": {
    "algorithm": "ES256",
    "publicKey": {
      "kty": "EC",
      "crv": "P-256",
      "x": "...",
      "y": "..."
    },
    "value": "..."
  }
}

Signing proves the SBOM hasn't been tampered with since generation. This is important in supply chain scenarios where SBOMs are shared between organizations.

Versioning and Serialization

Each CycloneDX BOM should include:

{
  "bomFormat": "CycloneDX",
  "specVersion": "1.5",
  "serialNumber": "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79",
  "version": 1,
  "metadata": {
    "timestamp": "2022-05-12T10:30:00Z",
    "tools": [
      {
        "name": "syft",
        "version": "0.85.0"
      }
    ]
  }
}

The serialNumber uniquely identifies this BOM. The version field increments when the same BOM is updated (e.g., adding VEX data). The timestamp and tools metadata support audit and reproducibility.

How Safeguard.sh Helps

Safeguard supports the full CycloneDX specification, not just the component list. Upload SBOMs with services, compositions, and vulnerability analysis, and the platform uses all of it -- composition flags inform vulnerability assessment confidence, VEX data suppresses false positives, and dependency graphs enable transitive risk analysis. Safeguard validates incoming SBOMs against the CycloneDX schema, flags completeness gaps, and continuously enriches your data with live vulnerability intelligence.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.