Files
git.stella-ops.org/docs/airgap/offline-bundle-format.md
master 491e883653 Add tests for SBOM generation determinism across multiple formats
- Created `StellaOps.TestKit.Tests` project for unit tests related to determinism.
- Implemented `DeterminismManifestTests` to validate deterministic output for canonical bytes and strings, file read/write operations, and error handling for invalid schema versions.
- Added `SbomDeterminismTests` to ensure identical inputs produce consistent SBOMs across SPDX 3.0.1 and CycloneDX 1.6/1.7 formats, including parallel execution tests.
- Updated project references in `StellaOps.Integration.Determinism` to include the new determinism testing library.
2025-12-24 00:36:14 +02:00

5.8 KiB

Offline Bundle Format (.stella.bundle.tgz)

Sprint: SPRINT_3603_0001_0001
Module: ExportCenter

This document describes the .stella.bundle.tgz format for portable, signed, verifiable evidence packages.

Overview

The offline bundle is a self-contained archive containing all evidence and artifacts needed for offline triage of security findings. Bundles are:

  • Portable: Single file that can be transferred to air-gapped environments
  • Signed: DSSE-signed manifest for authenticity verification
  • Verifiable: Content-addressable with SHA-256 hashes for integrity
  • Complete: Contains all data needed for offline decision-making

File Format

{alert-id}.stella.bundle.tgz
├── manifest.json                    # Bundle manifest (DSSE-signed)
├── metadata/
│   ├── alert.json                   # Alert metadata snapshot
│   └── generation-info.json         # Bundle generation metadata
├── evidence/
│   ├── reachability-proof.json      # Call-graph reachability evidence
│   ├── callstack.json               # Exploitability call stacks
│   └── provenance.json              # Build provenance attestations
├── vex/
│   ├── decisions.ndjson             # VEX decision history (NDJSON)
│   └── current-status.json          # Current VEX status
├── sbom/
│   ├── current.cdx.json             # Current SBOM slice (CycloneDX)
│   └── baseline.cdx.json            # Baseline SBOM for diff
├── diff/
│   └── sbom-delta.json              # SBOM delta changes
└── attestations/
    ├── bundle.dsse.json             # DSSE envelope for bundle
    └── evidence.dsse.json           # Evidence attestation chain

Manifest Schema

The manifest.json file follows this schema:

{
  "bundle_format_version": "1.0.0",
  "bundle_id": "abc123def456...",
  "alert_id": "alert-789",
  "created_at": "2024-12-15T10:00:00Z",
  "created_by": "user@example.com",
  "stellaops_version": "1.5.0",
  "entries": [
    {
      "path": "metadata/alert.json",
      "hash": "sha256:...",
      "size": 1234,
      "content_type": "application/json"
    }
  ],
  "root_hash": "sha256:...",
  "signature": {
    "algorithm": "ES256",
    "key_id": "signing-key-001",
    "value": "..."
  }
}

Manifest Fields

Field Type Required Description
bundle_format_version string Yes Format version (semver)
bundle_id string Yes Unique bundle identifier
alert_id string Yes Source alert identifier
created_at ISO 8601 Yes Bundle creation timestamp (UTC)
created_by string Yes Actor who created the bundle
stellaops_version string Yes StellaOps version that created bundle
entries array Yes List of content entries with hashes
root_hash string Yes Merkle root of all entry hashes
signature object No DSSE signature (if signed)

Entry Schema

Each entry in the manifest:

{
  "path": "evidence/reachability-proof.json",
  "hash": "sha256:abc123...",
  "size": 2048,
  "content_type": "application/json",
  "compression": null
}

DSSE Signing

Bundles support DSSE (Dead Simple Signing Envelope) signing:

{
  "payloadType": "application/vnd.stellaops.bundle.manifest+json",
  "payload": "<base64-encoded manifest>",
  "signatures": [
    {
      "keyid": "signing-key-001",
      "sig": "<base64-encoded signature>"
    }
  ]
}

Creation

API Endpoint

GET /v1/alerts/{alertId}/bundle
Authorization: Bearer <token>

Response: application/gzip
Content-Disposition: attachment; filename="alert-123.stella.bundle.tgz"

Programmatic

var packager = services.GetRequiredService<IOfflineBundlePackager>();

var result = await packager.CreateBundleAsync(new BundleRequest
{
    AlertId = "alert-123",
    ActorId = "user@example.com",
    IncludeVexHistory = true,
    IncludeSbomSlice = true
});

// result.Content contains the tarball stream
// result.ManifestHash contains the verification hash

Verification

API Endpoint

POST /v1/alerts/{alertId}/bundle/verify
Content-Type: application/json

{
  "bundle_hash": "sha256:abc123...",
  "signature": "<optional DSSE signature>"
}

Response:
{
  "is_valid": true,
  "hash_valid": true,
  "chain_valid": true,
  "signature_valid": true,
  "verified_at": "2024-12-15T10:00:00Z"
}

Programmatic

var verification = await packager.VerifyBundleAsync(
    bundlePath: "/path/to/bundle.stella.bundle.tgz",
    expectedHash: "sha256:abc123...");

if (!verification.IsValid)
{
    Console.WriteLine($"Verification failed: {string.Join(", ", verification.Errors)}");
}

CLI Usage

# Export bundle
stellaops alert bundle export --alert-id alert-123 --output ./bundles/

# Verify bundle
stellaops alert bundle verify --file ./bundles/alert-123.stella.bundle.tgz

# Import bundle (air-gapped instance)
stellaops alert bundle import --file ./bundles/alert-123.stella.bundle.tgz

Security Considerations

  1. Hash Verification: Always verify bundle hash before processing
  2. Signature Validation: Verify DSSE signature if present
  3. Content Validation: Validate JSON schemas after extraction
  4. Size Limits: Enforce maximum bundle size limits (default: 100MB)
  5. Path Traversal: Tarball extraction must prevent path traversal attacks

Versioning

Format Version Changes Min StellaOps Version
1.0.0 Initial format 1.0.0