Triage Air-Gap Workflows

This guide explains how to triage vulnerability findings in fully air-gapped environments using Stella Ops. The workflow moves signed evidence bundles across the air gap, captures triage decisions offline, and generates replay tokens so every decision stays auditable. It is written for security reviewers and operators working on sealed networks.

Overview

Triage in an air-gapped estate follows a round trip: a connected machine exports a signed evidence bundle, the bundle is transferred across the air gap, a reviewer makes decisions offline, and the signed decisions are synced back. Three workflows are documented below — bundle-based triage, batch triage for high volume, and an evidence-first review mode — followed by configuration, bundle/decision formats, replay tokens, and troubleshooting.

Workflow 1: Offline Triage with Evidence Bundles

Step 1: Export Evidence Bundle (Connected Machine)

# Export triage bundle for specific findings
stellaops triage export \
  --scan-id scan-12345678 \
  --findings CVE-2024-1234,CVE-2024-5678 \
  --include-evidence \
  --include-graph \
  --output triage-bundle.stella.bundle.tgz

# Export entire scan for offline review
stellaops triage export \
  --scan-id scan-12345678 \
  --all-findings \
  --output full-triage-bundle.stella.bundle.tgz

Step 2: Bundle Contents

The .stella.bundle.tgz archive contains:

triage-bundle.stella.bundle.tgz/
├── manifest.json           # Signed bundle manifest
├── findings/
│   ├── index.json          # Finding list with IDs
│   ├── CVE-2024-1234.json  # Finding details
│   └── CVE-2024-5678.json
├── evidence/
│   ├── reachability/       # Reachability proofs
│   ├── callstack/          # Call stack snippets
│   ├── vex/                # VEX/CSAF statements
│   └── provenance/         # Provenance data
├── graph/
│   ├── nodes.ndjson        # Dependency graph nodes
│   └── edges.ndjson        # Graph edges
├── feeds/
│   └── snapshot.json       # Feed snapshot metadata
└── signature.dsse          # DSSE envelope

Step 3: Transfer to Air-Gapped Environment

Transfer using approved methods:

Step 4: Import and Verify

On the air-gapped machine:

# Verify bundle integrity
stellaops triage verify-bundle \
  --input triage-bundle.stella.bundle.tgz \
  --public-key /path/to/signing-key.pub

# Import for offline triage
stellaops triage import \
  --input triage-bundle.stella.bundle.tgz \
  --workspace /opt/stellaops/triage

Step 5: Perform Offline Triage

# List findings in bundle
stellaops triage list \
  --workspace /opt/stellaops/triage

# View finding with evidence
stellaops triage show CVE-2024-1234 \
  --workspace /opt/stellaops/triage \
  --show-evidence

# Make triage decision
stellaops triage decide CVE-2024-1234 \
  --workspace /opt/stellaops/triage \
  --status not_affected \
  --justification "Code path is unreachable due to config gating" \
  --reviewer "security-team"

Step 6: Export Decisions

# Export decisions for sync back
stellaops triage export-decisions \
  --workspace /opt/stellaops/triage \
  --output decisions-2025-01-15.json \
  --sign

Step 7: Sync Decisions (Connected Machine)

# Import and apply decisions
stellaops triage import-decisions \
  --input decisions-2025-01-15.json \
  --verify \
  --apply

Workflow 2: Batch Offline Triage

For high-volume environments.

Step 1: Export Batch Bundle

# Export all untriaged findings
stellaops triage export-batch \
  --query "status=untriaged AND priority>=0.7" \
  --limit 100 \
  --output batch-triage-2025-01-15.stella.bundle.tgz

Step 2: Offline Batch Processing

# Interactive batch triage
stellaops triage batch \
  --workspace /opt/stellaops/triage \
  --input batch-triage-2025-01-15.stella.bundle.tgz

# Keyboard shortcuts enabled:
# j/k - Next/Previous finding
# a   - Accept (affected)
# n   - Not affected
# w   - Will not fix
# f   - False positive
# u   - Undo last decision
# q   - Quit (saves progress)

Step 3: Export and Sync

# Export batch decisions
stellaops triage export-decisions \
  --workspace /opt/stellaops/triage \
  --format json \
  --sign \
  --output batch-decisions.json

Workflow 3: Evidence-First Offline Review

Step 1: Pre-compute Evidence

On connected machine:

# Generate evidence for all high-priority findings
stellaops evidence generate \
  --scan-id scan-12345678 \
  --priority-min 0.7 \
  --output-dir ./evidence-pack

# Include:
# - Reachability analysis
# - Call stack traces
# - VEX lookups
# - Dependency graph snippets

Step 2: Package with Findings

stellaops triage package \
  --scan-id scan-12345678 \
  --evidence-dir ./evidence-pack \
  --output evidence-triage.stella.bundle.tgz

Step 3: Offline Review with Evidence

# Evidence-first view
stellaops triage show CVE-2024-1234 \
  --workspace /opt/stellaops/triage \
  --evidence-first

# Output:
# ═══════════════════════════════════════════
# CVE-2024-1234 · lodash@4.17.20
# ═══════════════════════════════════════════
#
# EVIDENCE SUMMARY
# ────────────────
# Reachability: EXECUTED (tier 2/3)
#   └─ main.js:42 → utils.js:15 → lodash/merge
#
# Call Stack:
#   1. main.js:42    handleRequest()
#   2. utils.js:15   mergeConfig()
#   3. lodash:merge  <vulnerable>
#
# VEX Status: No statement found
# EPSS: 0.45 (Medium)
# KEV: No
#
# ─────────────────────────────────────────────
# Press [a]ffected, [n]ot affected, [s]kip...

Configuration

Environment Variables

VariableDescriptionDefault
STELLAOPS_OFFLINEEnable offline modefalse
STELLAOPS_TRIAGE_WORKSPACETriage workspace path~/.stellaops/triage
STELLAOPS_BUNDLE_VERIFYVerify bundle signaturestrue
STELLAOPS_DECISION_SIGNSign exported decisionstrue

Config File

# ~/.stellaops/triage.yaml
offline:
  enabled: true
  workspace: /opt/stellaops/triage
  bundle_verify: true

decisions:
  require_justification: true
  sign_exports: true
  
keyboard:
  enabled: true
  vim_mode: true

Bundle Format Specification

manifest.json

{
  "version": "1.0",
  "type": "triage-bundle",
  "created_at": "2025-01-15T10:00:00Z",
  "scan_id": "scan-12345678",
  "finding_count": 25,
  "feed_snapshot": "sha256:abc123...",
  "graph_revision": "sha256:def456...",
  "signatures": {
    "manifest": "sha256:ghi789...",
    "dsse_envelope": "signature.dsse"
  }
}

Decision Format

{
  "finding_id": "finding-12345678",
  "vuln_key": "CVE-2024-1234:pkg:npm/lodash@4.17.20",
  "status": "not_affected",
  "justification": "Code path gated by feature flag",
  "reviewer": "security-team",
  "decided_at": "2025-01-15T14:30:00Z",
  "replay_token": "rt_abc123...",
  "evidence_refs": [
    "evidence/reachability/CVE-2024-1234.json"
  ]
}

Replay Tokens

Each decision generates a replay token for audit trail:

# View replay token
stellaops triage show-token rt_abc123...

# Output:
# Replay Token: rt_abc123...
# ─────────────────────────────
# Finding: CVE-2024-1234
# Decision: not_affected
# Evidence Hash: sha256:xyz789...
# Feed Snapshot: sha256:abc123...
# Decided: 2025-01-15T14:30:00Z
# Reviewer: security-team

Verify Token

stellaops triage verify-token rt_abc123... \
  --public-key /path/to/key.pub

# ✓ Token signature valid
# ✓ Evidence hash matches
# ✓ Feed snapshot verified

Troubleshooting

Error: Bundle signature invalid

Error: Bundle signature verification failed

Solution: Ensure the correct public key is used:

stellaops triage verify-bundle \
  --input bundle.tgz \
  --public-key /path/to/correct-key.pub \
  --verbose

Error: Evidence not found

Error: Evidence for CVE-2024-1234 not included in bundle

Solution: Re-export with evidence:

stellaops triage export \
  --scan-id scan-12345678 \
  --findings CVE-2024-1234 \
  --include-evidence \
  --output bundle.tgz

Error: Decision sync conflict

Error: Finding CVE-2024-1234 has newer decision on server

Solution: Review and resolve:

stellaops triage import-decisions \
  --input decisions.json \
  --conflict-mode review

# Options: keep-local, keep-server, newest, review