SBOM generation tooling is mature. You can produce CycloneDX or SPDX from almost any codebase in minutes. The unsolved problem is what happens after generation: where do you store these SBOMs, how do you version them, who can access them, and how do consumers retrieve them?
Most organizations start with "upload to S3" and quickly discover that doesn't scale. Finding the right SBOM for the right product version across hundreds of projects, when a CVE drops and the clock is ticking, requires actual infrastructure.
Storage Patterns
Pattern 1: OCI Registry Attachment
OCI (Open Container Initiative) registries support attaching arbitrary artifacts to container images. Since most modern software ships as containers, attaching SBOMs to the image in the registry is the most natural distribution mechanism.
# Attach SBOM to an OCI image using ORAS
oras attach myregistry.io/myapp:v1.2.3 \
--artifact-type application/vnd.cyclonedx+json \
sbom.json
# Retrieve the SBOM
oras discover myregistry.io/myapp:v1.2.3 \
--artifact-type application/vnd.cyclonedx+json
oras pull myregistry.io/myapp:v1.2.3 \
--artifact-type application/vnd.cyclonedx+json
Benefits:
- SBOMs travel with the image -- no separate distribution channel
- Registry access controls apply to SBOMs automatically
- Consumers can pull the SBOM using the same image reference they use to deploy
- Supports signatures (Cosign) for SBOM integrity verification
Limitations:
- Only works for container-based software
- Registry must support OCI artifacts (most modern registries do)
- Non-container SBOMs (firmware, mobile apps) need a different mechanism
Pattern 2: Dedicated SBOM Store (S3/GCS/Azure Blob)
Object storage with a consistent naming convention is the simplest general-purpose approach:
s3://sbom-store/
├── projects/
│ ├── myapp/
│ │ ├── v1.2.3/
│ │ │ ├── sbom.cdx.json
│ │ │ ├── sbom.spdx.json
│ │ │ └── metadata.json
│ │ ├── v1.2.2/
│ │ │ └── ...
│ │ └── latest -> v1.2.3/
│ └── mylib/
│ └── ...
└── vendors/
├── acme-corp/
│ └── widget-sdk/
│ └── v3.1.0/
│ └── sbom.spdx.json
└── ...
Add a metadata file per version:
{
"project": "myapp",
"version": "v1.2.3",
"format": "cyclonedx-1.5",
"generated_at": "2023-09-25T10:00:00Z",
"generated_by": "syft-0.90.0",
"git_sha": "abc123def456",
"build_id": "ci-build-4567",
"environment": "production"
}
Benefits:
- Works for any software type
- Cheap and simple
- Easy to version and audit
Limitations:
- No built-in query capability (finding "all projects using lodash@4.17.19" requires scanning every SBOM)
- Access control is coarse-grained (bucket/prefix level)
- No built-in integrity verification
Pattern 3: Database-Backed SBOM Platform
For queryable SBOM storage, ingest SBOMs into a database:
-- Simplified schema
CREATE TABLE sboms (
id UUID PRIMARY KEY,
project VARCHAR NOT NULL,
version VARCHAR NOT NULL,
format VARCHAR NOT NULL,
created_at TIMESTAMP NOT NULL,
raw_document JSONB NOT NULL
);
CREATE TABLE components (
id UUID PRIMARY KEY,
sbom_id UUID REFERENCES sboms(id),
name VARCHAR NOT NULL,
version VARCHAR,
purl VARCHAR,
cpe VARCHAR,
supplier VARCHAR,
license VARCHAR
);
CREATE INDEX idx_components_purl ON components(purl);
CREATE INDEX idx_components_name_version ON components(name, version);
Now you can query across all SBOMs:
-- Find all projects using a specific vulnerable component
SELECT DISTINCT s.project, s.version
FROM components c
JOIN sboms s ON c.sbom_id = s.id
WHERE c.name = 'log4j-core'
AND c.version < '2.17.1';
This is what dedicated SBOM management platforms provide out of the box.
Versioning Strategy
SBOMs need a clear versioning model:
One SBOM per release. Every software release (git tag, version bump, deployment) generates a new SBOM. Previous SBOMs are retained for audit.
Immutable SBOMs. Once generated, an SBOM should not be modified. If the SBOM needs correction, generate a new version with a new timestamp.
Retention policy. Keep SBOMs for at least as long as the software version they describe is in use. For regulated industries, retention requirements may be years or decades.
# CI/CD: generate and store with version tag
VERSION=$(git describe --tags --always)
syft dir:. -o cyclonedx-json > sbom-${VERSION}.json
aws s3 cp sbom-${VERSION}.json s3://sbom-store/myapp/${VERSION}/sbom.cdx.json
Access Control
Not all SBOMs should be public. Your SBOM reveals your dependency tree, which reveals:
- Your technology stack
- Potentially vulnerable components (before you patch them)
- Third-party vendors you use
- Development tools and practices
Access control tiers:
- Internal only -- SBOMs available to your development and security teams
- Customer-accessible -- SBOMs shared with customers under NDA or contract
- Public -- SBOMs published openly (required by some regulations)
Implement access control at the storage layer:
# S3 bucket policy for tiered access
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::123456789:role/security-team"},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::sbom-store/*"
},
{
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::123456789:role/customer-portal"},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::sbom-store/public/*"
}
]
}
Distribution APIs
For programmatic access, wrap your SBOM store in an API:
GET /api/v1/sboms?project=myapp&version=latest
GET /api/v1/sboms/{id}/document
GET /api/v1/sboms/search?component=log4j-core&version_lt=2.17.1
POST /api/v1/sboms/upload
Standard API responses:
{
"id": "uuid-here",
"project": "myapp",
"version": "v1.2.3",
"format": "cyclonedx-1.5",
"component_count": 342,
"created_at": "2023-09-25T10:00:00Z",
"download_url": "/api/v1/sboms/uuid-here/document"
}
Integrity and Signing
SBOMs should be signed to prevent tampering during distribution:
# Sign with Cosign
cosign sign-blob --key cosign.key sbom.json > sbom.json.sig
# Verify
cosign verify-blob --key cosign.pub --signature sbom.json.sig sbom.json
For OCI-attached SBOMs, Cosign signs both the image and the SBOM in a single workflow:
# Sign the image
cosign sign myregistry.io/myapp:v1.2.3
# The attached SBOM inherits the image's signature context
Scaling Considerations
At enterprise scale (thousands of projects, daily builds), SBOM storage grows fast. A single SBOM can be 1-10 MB. With daily builds across hundreds of projects, you're generating terabytes of SBOM data annually.
Strategies:
- Compression -- SBOMs compress well (JSON is repetitive). Gzip typically achieves 80-90% reduction.
- Deduplication -- Many SBOMs share identical components. Store component data once and reference it.
- Lifecycle management -- Archive old SBOMs to cold storage after a retention period.
- Incremental storage -- Store only the diff from the previous SBOM version (complex but space-efficient).
How Safeguard.sh Helps
Safeguard provides the SBOM storage, distribution, and query infrastructure so you don't build it yourself. Upload SBOMs through the API or CI/CD integration, and the platform handles versioning, access control, and searchable indexing. Query any component across all your SBOMs instantly. Share SBOMs with customers through controlled access. The platform retains full SBOM history with immutable versioning, signed integrity verification, and the cross-project search capability that turns a pile of JSON files into actionable intelligence.