12 KiB
Risk Bundles (Airgap)
Risk bundles package vulnerability intelligence data for offline/air-gapped environments. They provide deterministic, signed archives containing provider datasets (CISA KEV, FIRST EPSS, OSV) that can be verified and imported without network connectivity.
Bundle Structure
A risk bundle is a gzip-compressed tar archive (risk-bundle.tar.gz) with the following structure:
risk-bundle.tar.gz
├── manifests/
│ └── provider-manifest.json # Bundle metadata and provider entries
├── providers/
│ ├── cisa-kev/
│ │ └── snapshot # CISA Known Exploited Vulnerabilities JSON
│ ├── first-epss/
│ │ └── snapshot # FIRST EPSS scores CSV/JSON
│ └── osv/ # (optional) OpenSSF OSV bulk JSON
│ └── snapshot
└── signatures/
└── provider-manifest.dsse # DSSE envelope for manifest
Provider Manifest
The provider-manifest.json contains bundle metadata and per-provider entries:
{
"version": "1.0.0",
"bundleId": "risk-bundle-20241211-120000",
"createdAt": "2024-12-11T12:00:00Z",
"inputsHash": "sha256:abc123...",
"providers": [
{
"providerId": "cisa-kev",
"digest": "sha256:def456...",
"snapshotDate": "2024-12-11T00:00:00Z",
"optional": false
},
{
"providerId": "first-epss",
"digest": "sha256:789abc...",
"snapshotDate": "2024-12-11T00:00:00Z",
"optional": true
}
]
}
| Field | Description |
|---|---|
version |
Manifest schema version (currently 1.0.0) |
bundleId |
Unique identifier for this bundle |
createdAt |
ISO-8601 UTC timestamp of bundle creation |
inputsHash |
SHA-256 hash of concatenated provider digests (deterministic ordering) |
providers[] |
Array of provider entries sorted by providerId |
Provider Entry Fields
| Field | Description |
|---|---|
providerId |
Provider identifier (cisa-kev, first-epss, osv) |
digest |
SHA-256 hash of snapshot file (sha256:<hex>) |
snapshotDate |
ISO-8601 timestamp of provider data snapshot |
optional |
Whether provider is required for bundle validity |
Provider Catalog
| Provider | Source | Coverage | Refresh | Required |
|---|---|---|---|---|
cisa-kev |
CISA Known Exploited Vulnerabilities | Exploited CVEs with KEV flag | Daily | Yes |
first-epss |
FIRST EPSS scores | Exploitation probability per CVE | Daily | No |
osv |
OpenSSF OSV | OSS advisories with affected ranges | Weekly | No (opt-in) |
Building Risk Bundles
Using the Export Worker
The ExportCenter worker can build risk bundles via the stella export risk-bundle job:
# Build bundle with default providers (CISA KEV + EPSS)
stella export risk-bundle --output /path/to/output
# Include OSV providers (larger bundle)
stella export risk-bundle --output /path/to/output --include-osv
# Build with specific bundle ID
stella export risk-bundle --output /path/to/output --bundle-id "custom-bundle-id"
Using the CI Build Script
For CI pipelines and deterministic testing, use the shell scripts:
# Build fixture bundle for CI testing (deterministic)
ops/devops/risk-bundle/build-bundle.sh --output /tmp/bundle --fixtures-only
# Build with OSV
ops/devops/risk-bundle/build-bundle.sh --output /tmp/bundle --fixtures-only --include-osv
# Build with custom bundle ID
ops/devops/risk-bundle/build-bundle.sh --output /tmp/bundle --fixtures-only --bundle-id "ci-test-bundle"
Build Script Options
| Option | Description |
|---|---|
--output <dir> |
Output directory for bundle artifacts (required) |
--fixtures-only |
Use fixture data instead of live provider downloads |
--include-osv |
Include OSV providers (increases bundle size) |
--bundle-id <id> |
Custom bundle ID (default: auto-generated with timestamp) |
Build Outputs
After building, the output directory contains:
output/
├── risk-bundle.tar.gz # The bundle archive
├── risk-bundle.tar.gz.sha256 # SHA-256 checksum
└── manifest.json # Copy of provider-manifest.json
Verifying Risk Bundles
Using the CLI
# Basic verification
stella risk bundle verify --bundle-path ./risk-bundle.tar.gz
# With detached signature
stella risk bundle verify --bundle-path ./risk-bundle.tar.gz --signature-path ./bundle.sig
# Check Sigstore Rekor transparency log
stella risk bundle verify --bundle-path ./risk-bundle.tar.gz --check-rekor
# JSON output for automation
stella risk bundle verify --bundle-path ./risk-bundle.tar.gz --json
# Verbose output with warnings
stella risk bundle verify --bundle-path ./risk-bundle.tar.gz --verbose
CLI Options
| Option | Description |
|---|---|
--bundle-path, -b |
Path to risk bundle file (required) |
--signature-path, -s |
Path to detached signature file |
--check-rekor |
Verify transparency log entry in Sigstore Rekor |
--json |
Output results as JSON |
--tenant |
Tenant context for verification |
--verbose |
Show detailed output including warnings |
Using the Verification Script
For offline/air-gap verification without the CLI:
# Basic verification
ops/devops/risk-bundle/verify-bundle.sh /path/to/risk-bundle.tar.gz
# With detached signature
ops/devops/risk-bundle/verify-bundle.sh /path/to/risk-bundle.tar.gz --signature /path/to/bundle.sig
# Strict mode (warnings are errors)
ops/devops/risk-bundle/verify-bundle.sh /path/to/risk-bundle.tar.gz --strict
# JSON output
ops/devops/risk-bundle/verify-bundle.sh /path/to/risk-bundle.tar.gz --json
Verification Steps
The verification process performs these checks:
- Archive integrity - Bundle is a valid tar.gz archive
- Structure validation - Required files present (
manifests/provider-manifest.json) - Manifest parsing - Valid JSON with required fields (
bundleId,version,providers) - Provider hash verification - Each provider snapshot matches its declared digest
- Mandatory provider check -
cisa-kevmust be present and valid - DSSE signature validation - Manifest signature verified (if present)
- Detached signature - Bundle archive signature verified (if provided)
Exit Codes
| Code | Meaning |
|---|---|
| 0 | Bundle is valid |
| 1 | Bundle is invalid or verification failed |
| 2 | Input error (missing file, bad arguments) |
JSON Output Format
{
"valid": true,
"bundleId": "risk-bundle-20241211-120000",
"version": "1.0.0",
"providerCount": 2,
"mandatoryProviderFound": true,
"errorCount": 0,
"warningCount": 1,
"errors": [],
"warnings": ["Optional provider not found: osv"]
}
Importing Risk Bundles
Prerequisites
- Verify the bundle before import (see above)
- Ensure the target system has sufficient storage
- Back up existing provider data if replacing
Import Steps
- Transfer the bundle to the air-gapped environment via approved media
- Verify the bundle using the CLI or verification script
- Extract to staging:
mkdir -p /staging/risk-bundle tar -xzf risk-bundle.tar.gz -C /staging/risk-bundle - Validate provider data:
# Verify individual provider hashes sha256sum /staging/risk-bundle/providers/cisa-kev/snapshot sha256sum /staging/risk-bundle/providers/first-epss/snapshot - Import into Concelier:
stella concelier import-risk-bundle --path /staging/risk-bundle
Error Handling
| Error | Cause | Resolution |
|---|---|---|
| "Bundle is not a valid tar.gz archive" | Corrupted download/transfer | Re-download and verify checksum |
| "Missing required file: manifests/provider-manifest.json" | Incomplete bundle | Rebuild bundle |
| "Missing mandatory provider: cisa-kev" | KEV snapshot missing | Rebuild with valid provider data |
| "Hash mismatch: cisa-kev" | Corrupted provider data | Re-download provider snapshot |
| "DSSE signature validation failed" | Tampered manifest | Investigate chain of custody |
CI/CD Integration
GitHub Actions / Gitea Workflow
The .gitea/workflows/risk-bundle-ci.yml workflow:
- Build job: Compiles RiskBundles library, runs tests, builds fixture bundle
- Offline kit job: Packages bundle for offline kit distribution
- Publish checksums job: Publishes checksums to artifact store (main branch only)
# Trigger manually or on push to relevant paths
on:
push:
paths:
- 'src/ExportCenter/StellaOps.ExportCenter.RiskBundles/**'
- 'ops/devops/risk-bundle/**'
workflow_dispatch:
inputs:
include_osv:
type: boolean
default: false
Offline Kit Integration
Risk bundles are included in the Offline Update Kit:
offline-kit/
└── risk-bundles/
├── risk-bundle.tar.gz
├── risk-bundle.tar.gz.sha256
├── manifest.json
├── checksums.txt
└── kit-manifest.json
The kit-manifest.json provides metadata for offline kit consumers:
{
"component": "risk-bundle",
"version": "20241211-120000",
"files": [
{"path": "risk-bundle.tar.gz", "checksum_file": "risk-bundle.tar.gz.sha256"},
{"path": "manifest.json", "checksum_file": "manifest.json.sha256"}
],
"verification": {
"checksums": "checksums.txt",
"signature": "risk-bundle.tar.gz.sig"
}
}
Signing and Trust
DSSE Manifest Signature
The signatures/provider-manifest.dsse file contains a Dead Simple Signing Envelope:
{
"payloadType": "application/vnd.stellaops.risk-bundle.manifest+json",
"payload": "<base64-encoded-manifest>",
"signatures": [
{
"keyid": "risk-bundle-signing-key",
"sig": "<signature>"
}
]
}
Offline Trust Roots
For air-gapped verification, include public keys in the bundle:
signatures/
├── provider-manifest.dsse
└── pubkeys/
└── <tenant>.pem
Sigstore/Rekor Integration
When --check-rekor is specified, verification queries the Sigstore Rekor transparency log to confirm the bundle was published to the public ledger.
Determinism Checklist
Risk bundles are designed for reproducible builds:
- Fixed timestamps for tar entries (
--mtime="@<epoch>") - Sorted file ordering (
--sort=name) - Numeric owner/group (
--owner=0 --group=0 --numeric-owner) - Deterministic gzip compression (
gzip -n) - Providers sorted by
providerIdin manifest - Files sorted lexicographically in bundle
- UTF-8 canonical paths
- ISO-8601 UTC timestamps
Troubleshooting
Common Issues
Q: Bundle verification fails with "jq not available"
A: The verification script uses jq for JSON parsing. Install it or use the CLI (stella risk bundle verify) which has built-in JSON support.
Q: Hash mismatch after transfer
A: Binary transfers can corrupt files. Use checksums:
# On source system
sha256sum risk-bundle.tar.gz > checksum.txt
# On target system
sha256sum -c checksum.txt
Q: "Optional provider not found" warning
A: This is informational. Optional providers (EPSS, OSV) enhance risk analysis but aren't required. Use --strict if you want to enforce their presence.
Q: DSSE signature validation fails in air-gap
A: Ensure the offline trust root is configured:
stella config set risk-bundle.trust-root /path/to/pubkey.pem
Related Documentation
- Offline Update Kit - Complete offline kit documentation
- Mirror Bundles - OCI artifact bundles for air-gap
- Provider Matrix - Detailed provider specifications
- ExportCenter Architecture - Export service design