STP
SBOM Observer/

Multi-Repository Scenarios

Managing SBOMs across distributed codebases and microservice architectures

Modern software increasingly consists of multiple repositories rather than monolithic codebases. Microservice architectures split functionality across dozens of services, each with own repository, build pipeline, and release cadence. Frontend, backend, mobile apps, infrastructure-as-code—all separate repositories contributing to unified product experience. This distributed architecture creates SBOM challenges: How to represent product composed of many components? How to aggregate SBOMs from multiple sources? How to maintain consistency across repositories?

Multi-repository SBOM management requires coordination mechanisms beyond single-repository practices. Individual service SBOMs are necessary but insufficient—consumers need product-level view showing complete system composition. Organizations must balance granularity (service-level detail) with comprehensibility (product-level aggregation) while avoiding duplication and maintaining consistency as services evolve independently.

Multi-Repository Patterns

Pattern 1: Independent Service SBOMs

Each repository generates and publishes independent SBOM describing single service.

Structure:

product-ecosystem/
├── api-service/ (repo 1)
│   └── generates api-service-sbom.json
├── web-frontend/ (repo 2)
│   └── generates web-frontend-sbom.json
├── mobile-app/ (repo 3)
│   └── generates mobile-app-sbom.json
└── background-workers/ (repo 4)
    └── generates workers-sbom.json

Advantages:

  • Simple—each team owns their SBOM
  • Natural boundaries matching organizational structure
  • Independent release cadences supported
  • Clear ownership and accountability

Challenges:

  • Consumers must track multiple SBOMs per product
  • Duplicate dependencies across services not visible
  • No unified product view
  • Difficult to answer "does Product X use Component Y?"

When appropriate: Early SBOM adoption, simple microservice architectures, technically sophisticated consumers comfortable managing multiple SBOMs.

Pattern 2: Aggregated Product SBOM

Central process aggregates service SBOMs into unified product-level SBOM.

Structure:

product-aggregator/ (orchestration repo)
├── collects api-service-sbom.json
├── collects web-frontend-sbom.json
├── collects mobile-app-sbom.json
├── collects workers-sbom.json
└── generates product-complete-sbom.json (aggregated)

Aggregation process:

def aggregate_sboms(service_sboms):
    """Combine multiple service SBOMs into product SBOM"""

    product_sbom = {
        'bomFormat': 'CycloneDX',
        'specVersion': '1.6',
        'serialNumber': f'urn:uuid:{uuid.uuid4()}',
        'version': 1,
        'metadata': {
            'component': {
                'name': 'complete-product',
                'version': PRODUCT_VERSION,
                'type': 'application',
                'description': 'Aggregated SBOM for complete product'
            }
        },
        'components': [],
        'dependencies': []
    }

    # Deduplicate components across services
    seen_components = {}

    for service_sbom in service_sboms:
        service_name = service_sbom['metadata']['component']['name']

        # Add service as component
        product_sbom['components'].append({
            'name': service_name,
            'version': service_sbom['metadata']['component']['version'],
            'type': 'application',
            'description': f'Microservice component',
            'bom-ref': f'service-{service_name}'
        })

        # Add service dependencies
        for component in service_sbom.get('components', []):
            purl = component.get('purl')

            if purl and purl in seen_components:
                # Duplicate—mark which services use it
                seen_components[purl]['used_by'].append(service_name)
            else:
                # New component
                component['used_by'] = [service_name]
                seen_components[purl] = component
                product_sbom['components'].append(component)

    return product_sbom

Advantages:

  • Single SBOM for consumers to manage
  • Deduplication reveals shared dependencies
  • Product-level vulnerability assessment
  • Clear product boundaries

Challenges:

  • Requires aggregation infrastructure and coordination
  • Version synchronization complexity
  • Aggregation pipeline becomes single point of failure
  • Update latency between service changes and product SBOM

When appropriate: Mature SBOM programs, consumer preference for unified view, complex multi-service products, compliance requirements for product-level documentation.

Pattern 3: Hierarchical SBOM with References

Root product SBOM references service SBOMs rather than duplicating content.

Structure:

{
  "bomFormat": "CycloneDX",
  "specVersion": "1.6",
  "metadata": {
    "component": {
      "name": "complete-product",
      "version": "2.5.0",
      "type": "application"
    }
  },
  "components": [
    {
      "name": "api-service",
      "version": "1.3.2",
      "type": "application",
      "bom-ref": "api-service-1.3.2",
      "externalReferences": [
        {
          "type": "bom",
          "url": "https://sbom.example.com/api-service/v1.3.2/sbom.json",
          "comment": "Detailed SBOM for this service"
        }
      ]
    },
    {
      "name": "web-frontend",
      "version": "2.1.0",
      "type": "application",
      "bom-ref": "web-frontend-2.1.0",
      "externalReferences": [
        {
          "type": "bom",
          "url": "https://sbom.example.com/web-frontend/v2.1.0/sbom.json",
          "comment": "Detailed SBOM for this service"
        }
      ]
    }
  ]
}

Advantages:

  • Lightweight root SBOM
  • No duplication—service SBOMs remain authoritative
  • Consumers can traverse references for depth they need
  • Clear service boundaries preserved

Challenges:

  • Consumers must follow references for complete view
  • Requires consumers support SBOM reference resolution
  • Network dependencies for complete analysis
  • Reference link maintenance as services move

When appropriate: Large-scale microservice architectures, sophisticated consumers with SBOM tooling, need for both high-level and detailed views.

Coordination Challenges

Version Synchronization

Product version 2.5.0 composed of api-service 1.3.2, web-frontend 2.1.0, mobile-app 3.0.1, workers 1.2.7. How to maintain consistent version mapping as services release independently?

Solution: Version manifest

{
  "product": "complete-product",
  "version": "2.5.0",
  "release_date": "2024-01-15",
  "services": [
    {"name": "api-service", "version": "1.3.2", "sbom_url": "..."},
    {"name": "web-frontend", "version": "2.1.0", "sbom_url": "..."},
    {"name": "mobile-app", "version": "3.0.1", "sbom_url": "..."},
    {"name": "workers", "version": "1.2.7", "sbom_url": "..."}
  ]
}

Centralized version manifest tracks which service versions comprise each product release. Product SBOM aggregation references manifest to collect correct service SBOM versions.

Release Cadence Mismatch

API service releases weekly. Mobile app releases monthly. Frontend releases bi-weekly. Product SBOM aggregation must handle asynchronous updates.

Strategies:

Snapshot approach: Product releases happen on schedule (monthly). Aggregation captures current service versions at release time. Product SBOM reflects snapshot of services as-deployed.

Continuous approach: Product SBOM regenerates whenever any service updates. Always reflects latest service compositions. Consumers receive frequent updates.

Hybrid approach: Major product releases trigger new aggregated SBOM. Minor service updates trigger incremental SBOM updates marked with version increments (product 2.5.0 → 2.5.1 when single service patches).

Build Pipeline Coordination

Each service has own CI/CD pipeline. Aggregation requires coordinating across multiple pipelines.

Centralized collection:

# Product aggregation pipeline (runs on schedule or trigger)
name: Aggregate Product SBOM

on:
  schedule:
    - cron: '0 2 * * *'  # Daily at 2 AM
  workflow_dispatch:  # Manual trigger

jobs:
  aggregate:
    runs-on: ubuntu-latest
    steps:
      - name: Fetch service SBOMs
        run: |
          # Download from SBOM repository
          curl https://sbom.example.com/api-service/latest/sbom.json -o api.json
          curl https://sbom.example.com/web-frontend/latest/sbom.json -o web.json
          curl https://sbom.example.com/mobile-app/latest/sbom.json -o mobile.json
          curl https://sbom.example.com/workers/latest/sbom.json -o workers.json

      - name: Aggregate SBOMs
        run: python aggregate-sboms.py api.json web.json mobile.json workers.json

      - name: Validate aggregated SBOM
        run: cyclonedx-cli validate --input-file product-sbom.json

      - name: Publish product SBOM
        run: |
          curl -X POST https://sbom.example.com/api/upload \
            -F "product=complete-product" \
            -F "sbom=@product-sbom.json"

Event-driven collection:

Service pipelines trigger aggregation pipeline on SBOM publication:

# Service pipeline (api-service)
- name: Publish SBOM
  run: upload-sbom.sh sbom.json

- name: Trigger product aggregation
  run: |
    curl -X POST https://api.github.com/repos/org/product-aggregator/dispatches \
      -H "Authorization: token €{{ secrets.DISPATCH_TOKEN }}" \
      -d '{"event_type": "service-sbom-updated", "client_payload": {"service": "api-service"}}'

Deduplication and Consolidation

Multiple services often use same dependencies. Proper deduplication prevents redundant component entries while preserving usage information.

Deduplication Strategy

def deduplicate_components(aggregated_components):
    """Consolidate duplicate components while tracking service usage"""

    unique_components = {}

    for component in aggregated_components:
        # Use PURL as unique identifier
        purl = component.get('purl')
        if not purl:
            # No PURL—cannot deduplicate reliably, keep as-is
            unique_components[component['name']] = component
            continue

        if purl in unique_components:
            # Duplicate found—merge usage information
            existing = unique_components[purl]

            # Combine used_by lists
            existing.setdefault('used_by', []).extend(component.get('used_by', []))
            existing['used_by'] = list(set(existing['used_by']))  # Deduplicate

            # Version conflict check
            if existing['version'] != component['version']:
                # Same component, different versions across services
                existing.setdefault('version_conflicts', []).append({
                    'version': component['version'],
                    'services': component.get('used_by', [])
                })
        else:
            # New unique component
            unique_components[purl] = component

    return list(unique_components.values())

Version Conflict Handling

Diamond dependencies at product level: Service A uses Library 1.0, Service B uses Library 2.0. How to represent in product SBOM?

Option 1: Document both versions

{
  "components": [
    {
      "name": "conflicting-lib",
      "version": "1.0.0",
      "purl": "pkg:npm/conflicting-lib@1.0.0",
      "properties": [
        {"name": "cdx:used-by-services", "value": "api-service"}
      ]
    },
    {
      "name": "conflicting-lib",
      "version": "2.0.0",
      "purl": "pkg:npm/conflicting-lib@2.0.0",
      "properties": [
        {"name": "cdx:used-by-services", "value": "web-frontend"}
      ]
    }
  ]
}

Both versions listed separately, annotated with which services use each.

Option 2: Document conflict explicitly

{
  "components": [
    {
      "name": "conflicting-lib",
      "version": "multiple",
      "properties": [
        {
          "name": "cdx:version-conflict",
          "value": "Service api-service uses 1.0.0, web-frontend uses 2.0.0"
        }
      ]
    }
  ]
}

Single entry documenting version discrepancy.

Recommendation: Option 1 for accuracy. Vulnerability scanning requires knowing both versions might be deployed.

Shared Component Libraries

Organizations often maintain internal shared libraries used across services. Managing SBOMs for shared components requires coordination.

Internal Component Registry

Publish internal component SBOMs to registry:

internal-component-registry/
├── auth-library/
│   ├── v2.1.0/sbom.json
│   └── v2.1.1/sbom.json
├── logging-framework/
│   ├── v1.3.0/sbom.json
│   └── v1.3.1/sbom.json
└── data-validation/
    └── v3.0.0/sbom.json

Service SBOMs reference internal components. Product aggregation includes internal component SBOMs transitively.

Transitive Internal Dependencies

Service depends on internal auth-library, which depends on internal logging-framework. Product SBOM must capture full internal dependency chain.

Solution: Treat internal components like external dependencies. Auth-library SBOM documents logging-framework dependency. Aggregation follows references recursively.

Update Propagation

When service SBOM updates, how quickly does product SBOM reflect changes?

Real-Time Propagation

Service SBOM publication triggers immediate product SBOM regeneration.

Advantages: Product SBOM always current within minutes

Challenges: High update frequency, potential instability from frequent regeneration

Suitable for: Critical systems requiring real-time transparency, mature automation infrastructure

Scheduled Propagation

Product SBOM regenerates on schedule (daily, weekly) regardless of service changes.

Advantages: Predictable update cadence, reduced infrastructure load

Challenges: Product SBOM may lag service changes by hours/days

Suitable for: Most organizations, stable release cadences, balanced freshness vs. operational overhead

Event-Driven Batching

Service updates queue aggregation requests. Aggregation runs when threshold met (5 services changed) or timeout reached (24 hours since last aggregation).

Advantages: Balances freshness with efficiency, reduces unnecessary aggregations

Challenges: More complex logic, potential confusion about update timing

Suitable for: Large-scale microservice architectures with frequent service updates

Governance and Ownership

Multi-repository SBOM requires clear ownership model.

Service-level ownership: Each service team owns their service SBOM. Responsible for quality, accuracy, updates.

Product-level ownership: Product management or architecture team owns product SBOM aggregation. Ensures consistency, coordinates across services, manages product-level distribution.

Platform team support: Platform/DevOps team provides SBOM infrastructure—repositories, aggregation tooling, CI/CD integration, monitoring.

Clear escalation: Service SBOM problems escalate to service team. Aggregation problems escalate to product owners. Infrastructure problems escalate to platform team.

Tools and Automation

SBOM aggregation tools:

  • Custom scripts (Python, Go, Bash)
  • CycloneDX merge utilities
  • Commercial SBOM management platforms with aggregation features
  • OSS Review Toolkit for complex scenarios

Repository infrastructure:

  • Dependency-Track (multi-project support)
  • Artifact registries (Artifactory, Nexus) with SBOM storage
  • Custom SBOM repositories with API

CI/CD integration:

  • GitHub Actions workflows for aggregation
  • GitLab CI multi-project pipelines
  • Jenkins orchestration jobs
  • Tekton pipelines for Kubernetes environments

Next Steps

On this page