Offline Kit Parity for Scanner Standards (SC10)
Status: Draft · Date: 2025-12-04 Scope: Define offline-kit contents, DSSE signing, and parity requirements for schemas, adapters, mappings, and fixtures to enable air-gapped operation.
Objectives
- Bundle all schema/adapter/fixture artifacts for offline use.
- Sign bundles with DSSE for integrity verification.
- Ensure offline kit matches online capabilities.
- Document verification procedures for air-gapped environments.
Bundle Structure
out/offline/scanner-standards-kit-v1/
├── manifest.json # Bundle manifest with all hashes
├── manifest.dsse # DSSE signature over manifest
├── schemas/
│ ├── cyclonedx-1.7.schema.json # CDX 1.7 JSON schema
│ ├── cyclonedx-1.6.schema.json # CDX 1.6 JSON schema
│ ├── spdx-3.0.1.schema.json # SPDX 3.0.1 JSON-LD schema
│ └── slsa-provenance-v1.schema.json
├── adapters/
│ ├── mapping-cvss4-to-cvss3.csv
│ ├── mapping-cdx17-to-cdx16.csv
│ ├── mapping-slsa12-to-slsa10.csv
│ └── hashes.txt
├── fixtures/
│ ├── cdx17-cbom/
│ │ ├── sample-cdx17-cbom.json
│ │ ├── sample-cdx16.json
│ │ ├── source-track.sample.json
│ │ └── hashes.txt
│ └── competitor-adapters/
│ ├── fixtures/
│ │ ├── normalized-syft.json
│ │ ├── normalized-trivy.json
│ │ └── normalized-clair.json
│ └── coverage.csv
├── tools/
│ ├── versions.json # Pinned tool versions
│ └── checksums.txt # Tool binary hashes
└── trust/
├── root-ca.pem # Trust root for signature verification
└── keyring.json # Signing key metadata
Manifest Format
{
"version": "1.0.0",
"created": "2025-12-04T00:00:00Z",
"creator": "stellaops-scanner",
"artifacts": [
{
"path": "schemas/cyclonedx-1.7.schema.json",
"type": "schema",
"format": "cyclonedx",
"version": "1.7",
"blake3": "a1b2c3d4...",
"sha256": "e5f6a7b8..."
},
{
"path": "adapters/mapping-cvss4-to-cvss3.csv",
"type": "adapter",
"source": "cvss4",
"target": "cvss3.1",
"blake3": "fa600b26...",
"sha256": "072b66be..."
},
{
"path": "fixtures/cdx17-cbom/sample-cdx17-cbom.json",
"type": "fixture",
"format": "cyclonedx",
"version": "1.7",
"blake3": "27c6de0c...",
"sha256": "22d8f6f8..."
}
],
"tools": {
"syft": {
"version": "1.0.0",
"blake3": "...",
"sha256": "..."
},
"trivy": {
"version": "0.50.0",
"blake3": "...",
"sha256": "..."
}
},
"manifestHash": {
"blake3": "...",
"sha256": "..."
}
}
DSSE Signing
Offline kit and signed-SBOM verification tests must use real local DSSE signatures. Envelope shape, JWS headers, placeholder certificates, Rekor UUIDs, and Fulcio issuer strings are not proof; Scanner records them as unavailable or invalid until the relevant local trust-root/Rekor verifier succeeds.
For local Rekor transparency, the offline kit must carry a Rekor receipt, a signed checkpoint, and a local Rekor public key. Scanner verifies that the receipt is bound to the DSSE envelope bytes, the checkpoint signature verifies against the local key, and the Merkle path recomputes the checkpoint root. It does not call online Rekor during offline-kit import.
Signature Format
{
"payloadType": "application/vnd.stellaops.scanner.manifest+json",
"payload": "<base64-encoded-manifest>",
"signatures": [
{
"keyid": "stellaops-scanner-release-2025",
"sig": "<base64-signature>"
}
]
}
Signing Process
#!/bin/bash
# scripts/scanner/sign-offline-kit.sh
MANIFEST="out/offline/scanner-standards-kit-v1/manifest.json"
DSSE="out/offline/scanner-standards-kit-v1/manifest.dsse"
KEY_ID="stellaops-scanner-release-2025"
# Compute manifest hash
MANIFEST_HASH=$(b3sum "${MANIFEST}" | cut -d' ' -f1)
# Sign with DSSE
stellaops-sign dsse \
--payload-type "application/vnd.stellaops.scanner.manifest+json" \
--payload "${MANIFEST}" \
--key-id "${KEY_ID}" \
--output "${DSSE}"
echo "Signed manifest: ${DSSE}"
echo "Manifest BLAKE3: ${MANIFEST_HASH}"
Verification
Offline Verification Steps
#!/bin/bash
# scripts/scanner/verify-offline-kit.sh
KIT_DIR="out/offline/scanner-standards-kit-v1"
# 1. Verify DSSE signature
stellaops-verify dsse \
--envelope "${KIT_DIR}/manifest.dsse" \
--trust-root "${KIT_DIR}/trust/root-ca.pem" \
--expected-payload-type "application/vnd.stellaops.scanner.manifest+json"
# 2. Extract manifest and verify artifacts
MANIFEST=$(stellaops-verify dsse --envelope "${KIT_DIR}/manifest.dsse" --extract-payload)
# 3. Verify each artifact hash
for artifact in $(echo "${MANIFEST}" | jq -r '.artifacts[] | @base64'); do
path=$(echo "${artifact}" | base64 -d | jq -r '.path')
expected_blake3=$(echo "${artifact}" | base64 -d | jq -r '.blake3')
actual_blake3=$(b3sum "${KIT_DIR}/${path}" | cut -d' ' -f1)
if [[ "${actual_blake3}" != "${expected_blake3}" ]]; then
echo "FAIL: ${path} hash mismatch"
exit 1
fi
echo "PASS: ${path}"
done
echo "All artifacts verified"
Programmatic Verification
// src/Scanner/StellaOps.Scanner.Offline/OfflineKitVerifier.cs
public class OfflineKitVerifier
{
public async Task<VerificationResult> VerifyAsync(
string kitPath,
ITrustRootProvider trustRoots)
{
var manifestPath = Path.Combine(kitPath, "manifest.json");
var dssePath = Path.Combine(kitPath, "manifest.dsse");
// Verify DSSE signature
var envelope = await DsseEnvelope.LoadAsync(dssePath);
var signatureValid = await _verifier.VerifyAsync(envelope, trustRoots);
if (!signatureValid)
return VerificationResult.SignatureInvalid;
// Parse manifest
var manifest = JsonSerializer.Deserialize<OfflineManifest>(
envelope.Payload);
// Verify each artifact
foreach (var artifact in manifest.Artifacts)
{
var artifactPath = Path.Combine(kitPath, artifact.Path);
var actualHash = await HashUtil.Blake3Async(artifactPath);
if (actualHash != artifact.Blake3)
return VerificationResult.ArtifactHashMismatch(artifact.Path);
}
return VerificationResult.Success(manifest);
}
}
Parity Requirements
Required Contents
| Category | Online | Offline Kit | Notes |
|---|---|---|---|
| CDX 1.7 Schema | CDN fetch | Bundled | Schema validation |
| CDX 1.6 Schema | CDN fetch | Bundled | Downgrade validation |
| CVSS v4→v3 Adapter | API lookup | Bundled CSV | Pure function |
| CDX 1.7→1.6 Adapter | API lookup | Bundled CSV | Pure function |
| SLSA 1.2→1.0 Adapter | API lookup | Bundled CSV | Pure function |
| Golden Fixtures | Test repo | Bundled | Determinism tests |
| Tool Binaries | Package registry | Bundled/Checksum | Syft, Trivy |
| Trust Roots | Online PKI | Bundled PEM | Signature verification |
Functional Parity
The offline kit must support:
| Operation | Online | Offline |
|---|---|---|
| Schema validation | Yes | Yes |
| SBOM serialization | Yes | Yes |
| Downgrade conversion | Yes | Yes |
| Hash verification | Yes | Yes |
| DSSE verification | Yes | Yes |
| Determinism testing | Yes | Yes |
| Rekor transparency | Yes | Yes* |
*Offline mode uses mirrored checkpoints instead of live Rekor
Bundle Generation
CI Workflow
# .gitea/workflows/offline-kit.yml
jobs:
build-offline-kit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Collect schemas
run: |
mkdir -p out/offline/scanner-standards-kit-v1/schemas
cp schemas/cyclonedx-*.json out/offline/scanner-standards-kit-v1/schemas/
cp schemas/spdx-*.json out/offline/scanner-standards-kit-v1/schemas/
- name: Collect adapters
run: |
mkdir -p out/offline/scanner-standards-kit-v1/adapters
cp docs/modules/scanner/fixtures/adapters/*.csv out/offline/scanner-standards-kit-v1/adapters/
cp docs/modules/scanner/fixtures/adapters/hashes.txt out/offline/scanner-standards-kit-v1/adapters/
- name: Collect fixtures
run: |
mkdir -p out/offline/scanner-standards-kit-v1/fixtures
cp -r docs/modules/scanner/fixtures/cdx17-cbom out/offline/scanner-standards-kit-v1/fixtures/
cp -r docs/modules/scanner/fixtures/competitor-adapters out/offline/scanner-standards-kit-v1/fixtures/
- name: Generate manifest
run: scripts/scanner/generate-manifest.sh
- name: Sign bundle
run: scripts/scanner/sign-offline-kit.sh
env:
SIGNING_KEY: ${{ secrets.SCANNER_SIGNING_KEY }}
- name: Verify bundle
run: scripts/scanner/verify-offline-kit.sh
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: scanner-standards-kit-v1
path: out/offline/scanner-standards-kit-v1/
Update Procedure
Offline Kit Refresh
When schemas/adapters change:
- Update source artifacts in repository
- Run hash verification CI
- Generate new manifest
- Sign with release key
- Publish new kit version
- Document changes in release notes
Version Compatibility
| Kit Version | Scanner Version | CDX Version | CVSS Support |
|---|---|---|---|
| v1.0.0 | 1.x | 1.6, 1.7 | v3.1, v4.0 |
| v1.1.0 | 1.x | 1.6, 1.7, 1.8* | v3.1, v4.0 |
*Future versions
Links
- Sprint:
docs/implplan/SPRINT_0186_0001_0001_record_deterministic_execution.md(SC10) - Roadmap:
docs/modules/scanner/design/standards-convergence-roadmap.md(SC1) - Governance:
docs/modules/scanner/design/schema-governance.md(SC9) - Offline Operation:
docs/OFFLINE_KIT.md
