up
This commit is contained in:
338
docs/airgap/advisory-implementation-roadmap.md
Normal file
338
docs/airgap/advisory-implementation-roadmap.md
Normal file
@@ -0,0 +1,338 @@
|
||||
# Offline and Air-Gap Advisory Implementation Roadmap
|
||||
|
||||
**Source Advisory:** 14-Dec-2025 - Offline and Air-Gap Technical Reference
|
||||
**Document Version:** 1.0
|
||||
**Last Updated:** 2025-12-14
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
This document outlines the implementation roadmap for gaps identified between the 14-Dec-2025 Offline and Air-Gap Technical Reference advisory and the current StellaOps codebase. The implementation is organized into 5 sprints addressing security-critical, high-priority, and enhancement-level improvements.
|
||||
|
||||
---
|
||||
|
||||
## Implementation Overview
|
||||
|
||||
### Sprint Summary
|
||||
|
||||
| Sprint | Topic | Priority | Gaps | Effort | Dependencies |
|
||||
|--------|-------|----------|------|--------|--------------|
|
||||
| [0338](../implplan/SPRINT_0338_0001_0001_airgap_importer_core.md) | AirGap Importer Core | P0 | G6, G7 | Medium | None |
|
||||
| [0339](../implplan/SPRINT_0339_0001_0001_cli_offline_commands.md) | CLI Offline Commands | P1 | G4 | Medium | 0338 |
|
||||
| [0340](../implplan/SPRINT_0340_0001_0001_scanner_offline_config.md) | Scanner Offline Config | P2 | G5 | Medium | 0338 |
|
||||
| [0341](../implplan/SPRINT_0341_0001_0001_observability_audit.md) | Observability & Audit | P1-P2 | G11-G14 | Medium | 0338 |
|
||||
| [0342](../implplan/SPRINT_0342_0001_0001_evidence_reconciliation.md) | Evidence Reconciliation | P3 | G10 | High | 0338, 0340 |
|
||||
|
||||
### Dependency Graph
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ Sprint 0338: AirGap Importer Core (P0) │
|
||||
│ - Monotonicity enforcement (G6) │
|
||||
│ - Quarantine handling (G7) │
|
||||
│ │
|
||||
└──────────────────┬──────────────────────────┘
|
||||
│
|
||||
┌─────────────────────┼─────────────────────┐
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌────────────────┐ ┌────────────────┐ ┌────────────────┐
|
||||
│ Sprint 0339 │ │ Sprint 0340 │ │ Sprint 0341 │
|
||||
│ CLI Commands │ │ Scanner Config │ │ Observability │
|
||||
│ (P1) │ │ (P2) │ │ (P1-P2) │
|
||||
│ - G4 │ │ - G5 │ │ - G11-G14 │
|
||||
└────────────────┘ └───────┬────────┘ └────────────────┘
|
||||
│
|
||||
▼
|
||||
┌────────────────┐
|
||||
│ Sprint 0342 │
|
||||
│ Evidence Recon │
|
||||
│ (P3) │
|
||||
│ - G10 │
|
||||
└────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Gap-to-Sprint Mapping
|
||||
|
||||
### P0 - Critical (Must Implement First)
|
||||
|
||||
| Gap ID | Description | Sprint | Rationale |
|
||||
|--------|-------------|--------|-----------|
|
||||
| **G6** | Monotonicity enforcement | 0338 | Rollback prevention is security-critical; prevents replay attacks |
|
||||
| **G7** | Quarantine directory handling | 0338 | Essential for forensic analysis of failed imports |
|
||||
|
||||
### P1 - High Priority
|
||||
|
||||
| Gap ID | Description | Sprint | Rationale |
|
||||
|--------|-------------|--------|-----------|
|
||||
| **G4** | CLI `offline` command group | 0339 | Primary operator interface; competitive parity |
|
||||
| **G11** | Prometheus metrics | 0341 | Operational visibility in air-gap environments |
|
||||
| **G13** | Error reason codes | 0341 | Automation and troubleshooting |
|
||||
|
||||
### P2 - Important
|
||||
|
||||
| Gap ID | Description | Sprint | Rationale |
|
||||
|--------|-------------|--------|-----------|
|
||||
| **G5** | Scanner offline config surface | 0340 | Enterprise trust anchor management |
|
||||
| **G12** | Structured logging fields | 0341 | Log aggregation and correlation |
|
||||
| **G14** | Audit schema enhancement | 0341 | Compliance and chain-of-custody |
|
||||
|
||||
### P3 - Lower Priority
|
||||
|
||||
| Gap ID | Description | Sprint | Rationale |
|
||||
|--------|-------------|--------|-----------|
|
||||
| **G10** | Evidence reconciliation algorithm | 0342 | Complex but valuable; VEX-first decisioning |
|
||||
|
||||
### Deferred (Not Implementing)
|
||||
|
||||
| Gap ID | Description | Rationale |
|
||||
|--------|-------------|-----------|
|
||||
| **G9** | YAML verification policy schema | Over-engineering; existing JSON/code config sufficient |
|
||||
|
||||
---
|
||||
|
||||
## Technical Architecture
|
||||
|
||||
### New Components
|
||||
|
||||
```
|
||||
src/AirGap/
|
||||
├── StellaOps.AirGap.Importer/
|
||||
│ ├── Versioning/
|
||||
│ │ ├── BundleVersion.cs # Sprint 0338
|
||||
│ │ ├── IVersionMonotonicityChecker.cs # Sprint 0338
|
||||
│ │ └── IBundleVersionStore.cs # Sprint 0338
|
||||
│ ├── Quarantine/
|
||||
│ │ ├── IQuarantineService.cs # Sprint 0338
|
||||
│ │ ├── FileSystemQuarantineService.cs # Sprint 0338
|
||||
│ │ └── QuarantineOptions.cs # Sprint 0338
|
||||
│ ├── Telemetry/
|
||||
│ │ ├── OfflineKitMetrics.cs # Sprint 0341
|
||||
│ │ └── OfflineKitLogFields.cs # Sprint 0341
|
||||
│ ├── Audit/
|
||||
│ │ └── OfflineKitAuditEmitter.cs # Sprint 0341
|
||||
│ ├── Reconciliation/
|
||||
│ │ ├── ArtifactIndex.cs # Sprint 0342
|
||||
│ │ ├── EvidenceCollector.cs # Sprint 0342
|
||||
│ │ ├── DocumentNormalizer.cs # Sprint 0342
|
||||
│ │ ├── PrecedenceLattice.cs # Sprint 0342
|
||||
│ │ └── EvidenceGraphEmitter.cs # Sprint 0342
|
||||
│ └── OfflineKitReasonCodes.cs # Sprint 0341
|
||||
|
||||
src/Scanner/
|
||||
├── __Libraries/StellaOps.Scanner.Core/
|
||||
│ ├── Configuration/
|
||||
│ │ ├── OfflineKitOptions.cs # Sprint 0340
|
||||
│ │ ├── TrustAnchorConfig.cs # Sprint 0340
|
||||
│ │ └── OfflineKitOptionsValidator.cs # Sprint 0340
|
||||
│ └── TrustAnchors/
|
||||
│ ├── PurlPatternMatcher.cs # Sprint 0340
|
||||
│ ├── ITrustAnchorRegistry.cs # Sprint 0340
|
||||
│ └── TrustAnchorRegistry.cs # Sprint 0340
|
||||
|
||||
src/Cli/
|
||||
├── StellaOps.Cli/
|
||||
│ └── Commands/
|
||||
│ ├── Offline/
|
||||
│ │ ├── OfflineCommandGroup.cs # Sprint 0339
|
||||
│ │ ├── OfflineImportHandler.cs # Sprint 0339
|
||||
│ │ ├── OfflineStatusHandler.cs # Sprint 0339
|
||||
│ │ └── OfflineExitCodes.cs # Sprint 0339
|
||||
│ └── Verify/
|
||||
│ └── VerifyOfflineHandler.cs # Sprint 0339
|
||||
|
||||
src/Authority/
|
||||
├── __Libraries/StellaOps.Authority.Storage.Postgres/
|
||||
│ └── Migrations/
|
||||
│ └── 003_offline_kit_audit.sql # Sprint 0341
|
||||
```
|
||||
|
||||
### Database Changes
|
||||
|
||||
| Table | Schema | Sprint | Purpose |
|
||||
|-------|--------|--------|---------|
|
||||
| `airgap.bundle_versions` | New | 0338 | Track active bundle versions per tenant/type |
|
||||
| `airgap.bundle_version_history` | New | 0338 | Version history for audit trail |
|
||||
| `authority.offline_kit_audit` | New | 0341 | Enhanced audit with Rekor/DSSE fields |
|
||||
|
||||
### Configuration Changes
|
||||
|
||||
| Section | Sprint | Fields |
|
||||
|---------|--------|--------|
|
||||
| `AirGap:Quarantine` | 0338 | `QuarantineRoot`, `RetentionPeriod`, `MaxQuarantineSizeBytes` |
|
||||
| `Scanner:OfflineKit` | 0340 | `RequireDsse`, `RekorOfflineMode`, `TrustAnchors[]` |
|
||||
|
||||
### CLI Commands
|
||||
|
||||
| Command | Sprint | Description |
|
||||
|---------|--------|-------------|
|
||||
| `stellaops offline import` | 0339 | Import offline kit with verification |
|
||||
| `stellaops offline status` | 0339 | Display current kit status |
|
||||
| `stellaops verify offline` | 0339 | Offline evidence verification |
|
||||
|
||||
### Metrics
|
||||
|
||||
| Metric | Type | Sprint | Labels |
|
||||
|--------|------|--------|--------|
|
||||
| `offlinekit_import_total` | Counter | 0341 | `status`, `tenant_id` |
|
||||
| `offlinekit_attestation_verify_latency_seconds` | Histogram | 0341 | `attestation_type`, `success` |
|
||||
| `attestor_rekor_success_total` | Counter | 0341 | `mode` |
|
||||
| `attestor_rekor_retry_total` | Counter | 0341 | `reason` |
|
||||
| `rekor_inclusion_latency` | Histogram | 0341 | `success` |
|
||||
|
||||
---
|
||||
|
||||
## Implementation Sequence
|
||||
|
||||
### Phase 1: Foundation (Sprint 0338)
|
||||
**Duration:** 1 sprint
|
||||
**Focus:** Security-critical infrastructure
|
||||
|
||||
1. Implement `BundleVersion` model with semver parsing
|
||||
2. Create `IVersionMonotonicityChecker` and Postgres store
|
||||
3. Integrate monotonicity check into `ImportValidator`
|
||||
4. Implement `--force-activate` with audit trail
|
||||
5. Create `IQuarantineService` and file-system implementation
|
||||
6. Integrate quarantine into all import failure paths
|
||||
7. Write comprehensive tests
|
||||
|
||||
**Exit Criteria:**
|
||||
- [ ] Rollback attacks are prevented
|
||||
- [ ] Failed bundles are preserved for investigation
|
||||
- [ ] Force activation requires justification
|
||||
|
||||
### Phase 2: Operator Experience (Sprints 0339, 0341)
|
||||
**Duration:** 1-2 sprints (can parallelize)
|
||||
**Focus:** CLI and observability
|
||||
|
||||
**Sprint 0339 (CLI):**
|
||||
1. Create `offline` command group
|
||||
2. Implement `offline import` with all flags
|
||||
3. Implement `offline status` with output formats
|
||||
4. Implement `verify offline` with policy loading
|
||||
5. Add exit code standardization
|
||||
6. Write CLI integration tests
|
||||
|
||||
**Sprint 0341 (Observability):**
|
||||
1. Add Prometheus metrics infrastructure
|
||||
2. Implement offline kit metrics
|
||||
3. Standardize structured logging fields
|
||||
4. Complete error reason codes
|
||||
5. Create audit schema migration
|
||||
6. Implement audit repository and emitter
|
||||
7. Create Grafana dashboard
|
||||
|
||||
**Exit Criteria:**
|
||||
- [ ] Operators can import/verify kits via CLI
|
||||
- [ ] Metrics are visible in Prometheus/Grafana
|
||||
- [ ] All operations are auditable
|
||||
|
||||
### Phase 3: Configuration (Sprint 0340)
|
||||
**Duration:** 1 sprint
|
||||
**Focus:** Trust anchor management
|
||||
|
||||
1. Create `OfflineKitOptions` configuration class
|
||||
2. Implement PURL pattern matcher
|
||||
3. Create `TrustAnchorRegistry` with precedence resolution
|
||||
4. Add options validation
|
||||
5. Integrate trust anchors with DSSE verification
|
||||
6. Update Helm chart values
|
||||
7. Write configuration tests
|
||||
|
||||
**Exit Criteria:**
|
||||
- [ ] Trust anchors configurable per ecosystem
|
||||
- [ ] DSSE verification uses configured anchors
|
||||
- [ ] Invalid configuration fails startup
|
||||
|
||||
### Phase 4: Advanced Features (Sprint 0342)
|
||||
**Duration:** 1-2 sprints
|
||||
**Focus:** Evidence reconciliation
|
||||
|
||||
1. Design artifact indexing
|
||||
2. Implement evidence collection
|
||||
3. Create document normalization
|
||||
4. Implement VEX precedence lattice
|
||||
5. Create evidence graph emitter
|
||||
6. Integrate with CLI `verify offline`
|
||||
7. Write golden-file determinism tests
|
||||
|
||||
**Exit Criteria:**
|
||||
- [ ] Evidence reconciliation is deterministic
|
||||
- [ ] VEX conflicts resolved by precedence
|
||||
- [ ] Graph output is signed and verifiable
|
||||
|
||||
---
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Unit Tests
|
||||
- All new classes have corresponding test classes
|
||||
- Mock dependencies for isolation
|
||||
- Property-based tests for lattice operations
|
||||
|
||||
### Integration Tests
|
||||
- Testcontainers for PostgreSQL
|
||||
- Full import → verification → audit flow
|
||||
- CLI command execution tests
|
||||
|
||||
### Determinism Tests
|
||||
- Golden-file tests for evidence reconciliation
|
||||
- Cross-platform validation (Windows, Linux, macOS)
|
||||
- Reproducibility across runs
|
||||
|
||||
### Security Tests
|
||||
- Monotonicity bypass attempts
|
||||
- Signature verification edge cases
|
||||
- Trust anchor configuration validation
|
||||
|
||||
---
|
||||
|
||||
## Documentation Updates
|
||||
|
||||
| Document | Sprint | Updates |
|
||||
|----------|--------|---------|
|
||||
| `docs/airgap/importer-scaffold.md` | 0338 | Add monotonicity, quarantine sections |
|
||||
| `docs/airgap/runbooks/quarantine-investigation.md` | 0338 | New runbook |
|
||||
| `docs/modules/cli/commands/offline.md` | 0339 | New command reference |
|
||||
| `docs/modules/cli/guides/airgap.md` | 0339 | Update with CLI examples |
|
||||
| `docs/modules/scanner/configuration.md` | 0340 | Add offline kit config section |
|
||||
| `docs/airgap/observability.md` | 0341 | Metrics and logging reference |
|
||||
| `docs/airgap/evidence-reconciliation.md` | 0342 | Algorithm documentation |
|
||||
|
||||
---
|
||||
|
||||
## Risk Register
|
||||
|
||||
| Risk | Impact | Mitigation |
|
||||
|------|--------|------------|
|
||||
| Monotonicity breaks existing workflows | High | Provide `--force-activate` escape hatch |
|
||||
| Quarantine disk exhaustion | Medium | Implement quota and TTL cleanup |
|
||||
| Trust anchor config complexity | Medium | Provide sensible defaults, validate at startup |
|
||||
| Evidence reconciliation performance | Medium | Streaming processing, caching |
|
||||
| Cross-platform determinism failures | High | CI matrix, golden-file tests |
|
||||
|
||||
---
|
||||
|
||||
## Success Metrics
|
||||
|
||||
| Metric | Target | Sprint |
|
||||
|--------|--------|--------|
|
||||
| Rollback attack prevention | 100% | 0338 |
|
||||
| Failed bundle quarantine rate | 100% | 0338 |
|
||||
| CLI command adoption | 50% operators | 0339 |
|
||||
| Metric collection uptime | 99.9% | 0341 |
|
||||
| Audit completeness | 100% events | 0341 |
|
||||
| Reconciliation determinism | 100% | 0342 |
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- [14-Dec-2025 Offline and Air-Gap Technical Reference](../product-advisories/14-Dec-2025%20-%20Offline%20and%20Air-Gap%20Technical%20Reference.md)
|
||||
- [Air-Gap Mode Playbook](./airgap-mode.md)
|
||||
- [Offline Kit Documentation](../24_OFFLINE_KIT.md)
|
||||
- [Importer Scaffold](./importer-scaffold.md)
|
||||
518
docs/airgap/offline-parity-verification.md
Normal file
518
docs/airgap/offline-parity-verification.md
Normal file
@@ -0,0 +1,518 @@
|
||||
# Offline Parity Verification
|
||||
|
||||
**Last Updated:** 2025-12-14
|
||||
**Next Review:** 2026-03-14
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
This document defines the methodology for verifying that StellaOps scanner produces **identical results** in offline/air-gapped environments compared to connected deployments. Parity verification ensures that security decisions made in disconnected environments are equivalent to those made with full network access.
|
||||
|
||||
---
|
||||
|
||||
## 1. PARITY VERIFICATION OBJECTIVES
|
||||
|
||||
### 1.1 Core Guarantees
|
||||
|
||||
| Guarantee | Description | Target |
|
||||
|-----------|-------------|--------|
|
||||
| **Bitwise Fidelity** | Scan outputs are byte-identical offline vs online | 100% |
|
||||
| **Semantic Fidelity** | Same vulnerabilities, severities, and verdicts | 100% |
|
||||
| **Temporal Parity** | Same results given identical feed snapshots | 100% |
|
||||
| **Policy Parity** | Same pass/fail decisions with identical policies | 100% |
|
||||
|
||||
### 1.2 What Parity Does NOT Cover
|
||||
|
||||
- **Feed freshness**: Offline feeds may be hours/days behind live feeds (by design)
|
||||
- **Network-only enrichment**: EPSS lookups, live KEV checks (graceful degradation applies)
|
||||
- **Transparency log submission**: Rekor entries created only when connected
|
||||
|
||||
---
|
||||
|
||||
## 2. TEST METHODOLOGY
|
||||
|
||||
### 2.1 Environment Configuration
|
||||
|
||||
#### Connected Environment
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
mode: connected
|
||||
network: enabled
|
||||
feeds:
|
||||
sources: [osv, ghsa, nvd]
|
||||
refresh: live
|
||||
rekor: enabled
|
||||
epss: enabled
|
||||
timestamp_source: ntp
|
||||
```
|
||||
|
||||
#### Offline Environment
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
mode: offline
|
||||
network: disabled
|
||||
feeds:
|
||||
sources: [local-bundle]
|
||||
refresh: none
|
||||
rekor: offline-snapshot
|
||||
epss: bundled-cache
|
||||
timestamp_source: frozen
|
||||
timestamp_value: "2025-12-14T00:00:00Z"
|
||||
```
|
||||
|
||||
### 2.2 Test Procedure
|
||||
|
||||
```
|
||||
PARITY VERIFICATION PROCEDURE v1.0
|
||||
══════════════════════════════════
|
||||
|
||||
PHASE 1: BUNDLE CAPTURE (Connected Environment)
|
||||
─────────────────────────────────────────────────
|
||||
1. Capture current feed state:
|
||||
- Record feed version/digest
|
||||
- Snapshot EPSS scores (top 1000 CVEs)
|
||||
- Record KEV list state
|
||||
|
||||
2. Run connected scan:
|
||||
stellaops scan --image <test-image> \
|
||||
--format json \
|
||||
--output connected-scan.json \
|
||||
--receipt connected-receipt.json
|
||||
|
||||
3. Export offline bundle:
|
||||
stellaops offline bundle export \
|
||||
--feeds-snapshot \
|
||||
--epss-cache \
|
||||
--output parity-bundle-$(date +%Y%m%d).tar.zst
|
||||
|
||||
PHASE 2: OFFLINE SCAN (Air-Gapped Environment)
|
||||
───────────────────────────────────────────────
|
||||
1. Import bundle:
|
||||
stellaops offline bundle import parity-bundle-*.tar.zst
|
||||
|
||||
2. Freeze clock to bundle timestamp:
|
||||
export STELLAOPS_DETERMINISM_TIMESTAMP="2025-12-14T00:00:00Z"
|
||||
|
||||
3. Run offline scan:
|
||||
stellaops scan --image <test-image> \
|
||||
--format json \
|
||||
--output offline-scan.json \
|
||||
--receipt offline-receipt.json \
|
||||
--offline-mode
|
||||
|
||||
PHASE 3: PARITY COMPARISON
|
||||
──────────────────────────
|
||||
1. Compare findings digests:
|
||||
diff <(jq -S '.findings | sort_by(.id)' connected-scan.json) \
|
||||
<(jq -S '.findings | sort_by(.id)' offline-scan.json)
|
||||
|
||||
2. Compare policy decisions:
|
||||
diff <(jq -S '.policyDecision' connected-scan.json) \
|
||||
<(jq -S '.policyDecision' offline-scan.json)
|
||||
|
||||
3. Compare receipt input hashes:
|
||||
jq '.inputHash' connected-receipt.json
|
||||
jq '.inputHash' offline-receipt.json
|
||||
# MUST be identical if same bundle used
|
||||
|
||||
PHASE 4: RECORD RESULTS
|
||||
───────────────────────
|
||||
1. Generate parity report:
|
||||
stellaops parity report \
|
||||
--connected connected-scan.json \
|
||||
--offline offline-scan.json \
|
||||
--output parity-report-$(date +%Y%m%d).json
|
||||
```
|
||||
|
||||
### 2.3 Test Image Matrix
|
||||
|
||||
Run parity tests against this representative image set:
|
||||
|
||||
| Image | Category | Expected Vulns | Notes |
|
||||
|-------|----------|----------------|-------|
|
||||
| `alpine:3.19` | Minimal | ~5 | Fast baseline |
|
||||
| `debian:12-slim` | Standard | ~40 | OS package focus |
|
||||
| `node:20-alpine` | Application | ~100 | npm + OS packages |
|
||||
| `python:3.12` | Application | ~150 | pip + OS packages |
|
||||
| `dotnet/aspnet:8.0` | Application | ~75 | NuGet + OS packages |
|
||||
| `postgres:16-alpine` | Database | ~70 | Database + OS |
|
||||
|
||||
---
|
||||
|
||||
## 3. COMPARISON CRITERIA
|
||||
|
||||
### 3.1 Bitwise Comparison
|
||||
|
||||
Compare canonical JSON outputs after normalization:
|
||||
|
||||
```bash
|
||||
# Canonical comparison script
|
||||
canonical_compare() {
|
||||
local connected="$1"
|
||||
local offline="$2"
|
||||
|
||||
# Normalize both outputs
|
||||
jq -S . "$connected" > /tmp/connected-canonical.json
|
||||
jq -S . "$offline" > /tmp/offline-canonical.json
|
||||
|
||||
# Compute hashes
|
||||
CONNECTED_HASH=$(sha256sum /tmp/connected-canonical.json | cut -d' ' -f1)
|
||||
OFFLINE_HASH=$(sha256sum /tmp/offline-canonical.json | cut -d' ' -f1)
|
||||
|
||||
if [[ "$CONNECTED_HASH" == "$OFFLINE_HASH" ]]; then
|
||||
echo "PASS: Bitwise identical"
|
||||
return 0
|
||||
else
|
||||
echo "FAIL: Hash mismatch"
|
||||
echo " Connected: $CONNECTED_HASH"
|
||||
echo " Offline: $OFFLINE_HASH"
|
||||
diff --color /tmp/connected-canonical.json /tmp/offline-canonical.json
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 Semantic Comparison
|
||||
|
||||
When bitwise comparison fails, perform semantic comparison:
|
||||
|
||||
| Field | Comparison Rule | Allowed Variance |
|
||||
|-------|-----------------|------------------|
|
||||
| `findings[].id` | Exact match | None |
|
||||
| `findings[].severity` | Exact match | None |
|
||||
| `findings[].cvss.score` | Exact match | None |
|
||||
| `findings[].cvss.vector` | Exact match | None |
|
||||
| `findings[].affected` | Exact match | None |
|
||||
| `findings[].reachability` | Exact match | None |
|
||||
| `sbom.components[].purl` | Exact match | None |
|
||||
| `sbom.components[].version` | Exact match | None |
|
||||
| `metadata.timestamp` | Ignored | Expected to differ |
|
||||
| `metadata.scanId` | Ignored | Expected to differ |
|
||||
| `metadata.environment` | Ignored | Expected to differ |
|
||||
|
||||
### 3.3 Fields Excluded from Comparison
|
||||
|
||||
These fields are expected to differ and are excluded from parity checks:
|
||||
|
||||
```json
|
||||
{
|
||||
"excludedFields": [
|
||||
"$.metadata.scanId",
|
||||
"$.metadata.timestamp",
|
||||
"$.metadata.hostname",
|
||||
"$.metadata.environment.network",
|
||||
"$.attestations[*].rekorEntry",
|
||||
"$.metadata.epssEnrichedAt"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 3.4 Graceful Degradation Fields
|
||||
|
||||
Fields that may be absent in offline mode (acceptable):
|
||||
|
||||
| Field | Online | Offline | Parity Rule |
|
||||
|-------|--------|---------|-------------|
|
||||
| `epssScore` | Present | May be stale/absent | Check if bundled |
|
||||
| `kevStatus` | Live | Bundled snapshot | Compare against bundle date |
|
||||
| `rekorEntry` | Present | Absent | Exclude from comparison |
|
||||
| `fulcioChain` | Present | Absent | Exclude from comparison |
|
||||
|
||||
---
|
||||
|
||||
## 4. AUTOMATED PARITY CI
|
||||
|
||||
### 4.1 CI Workflow
|
||||
|
||||
```yaml
|
||||
# .gitea/workflows/offline-parity.yml
|
||||
name: Offline Parity Verification
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 3 * * 1' # Weekly Monday 3am
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
parity-test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: '10.0.x'
|
||||
|
||||
- name: Set determinism environment
|
||||
run: |
|
||||
echo "TZ=UTC" >> $GITHUB_ENV
|
||||
echo "LC_ALL=C" >> $GITHUB_ENV
|
||||
echo "STELLAOPS_DETERMINISM_SEED=42" >> $GITHUB_ENV
|
||||
|
||||
- name: Capture connected baseline
|
||||
run: scripts/parity/capture-connected.sh
|
||||
|
||||
- name: Export offline bundle
|
||||
run: scripts/parity/export-bundle.sh
|
||||
|
||||
- name: Run offline scan (sandboxed)
|
||||
run: |
|
||||
docker run --network none \
|
||||
-v $(pwd)/bundle:/bundle:ro \
|
||||
-v $(pwd)/results:/results \
|
||||
stellaops/scanner:latest \
|
||||
scan --offline-mode --bundle /bundle
|
||||
|
||||
- name: Compare parity
|
||||
run: scripts/parity/compare-parity.sh
|
||||
|
||||
- name: Upload parity report
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: parity-report
|
||||
path: results/parity-report-*.json
|
||||
```
|
||||
|
||||
### 4.2 Parity Test Script
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# scripts/parity/compare-parity.sh
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
CONNECTED_DIR="results/connected"
|
||||
OFFLINE_DIR="results/offline"
|
||||
REPORT_FILE="results/parity-report-$(date +%Y%m%d).json"
|
||||
|
||||
declare -a IMAGES=(
|
||||
"alpine:3.19"
|
||||
"debian:12-slim"
|
||||
"node:20-alpine"
|
||||
"python:3.12"
|
||||
"mcr.microsoft.com/dotnet/aspnet:8.0"
|
||||
"postgres:16-alpine"
|
||||
)
|
||||
|
||||
TOTAL=0
|
||||
PASSED=0
|
||||
FAILED=0
|
||||
RESULTS=()
|
||||
|
||||
for image in "${IMAGES[@]}"; do
|
||||
TOTAL=$((TOTAL + 1))
|
||||
image_hash=$(echo "$image" | sha256sum | cut -c1-12)
|
||||
|
||||
connected_file="${CONNECTED_DIR}/${image_hash}-scan.json"
|
||||
offline_file="${OFFLINE_DIR}/${image_hash}-scan.json"
|
||||
|
||||
# Compare findings
|
||||
connected_findings=$(jq -S '.findings | sort_by(.id) | map(del(.metadata.timestamp))' "$connected_file")
|
||||
offline_findings=$(jq -S '.findings | sort_by(.id) | map(del(.metadata.timestamp))' "$offline_file")
|
||||
|
||||
connected_hash=$(echo "$connected_findings" | sha256sum | cut -d' ' -f1)
|
||||
offline_hash=$(echo "$offline_findings" | sha256sum | cut -d' ' -f1)
|
||||
|
||||
if [[ "$connected_hash" == "$offline_hash" ]]; then
|
||||
PASSED=$((PASSED + 1))
|
||||
status="PASS"
|
||||
else
|
||||
FAILED=$((FAILED + 1))
|
||||
status="FAIL"
|
||||
fi
|
||||
|
||||
RESULTS+=("{\"image\":\"$image\",\"status\":\"$status\",\"connectedHash\":\"$connected_hash\",\"offlineHash\":\"$offline_hash\"}")
|
||||
done
|
||||
|
||||
# Generate report
|
||||
cat > "$REPORT_FILE" <<EOF
|
||||
{
|
||||
"reportDate": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
|
||||
"bundleVersion": "$(cat bundle/version.txt)",
|
||||
"summary": {
|
||||
"total": $TOTAL,
|
||||
"passed": $PASSED,
|
||||
"failed": $FAILED,
|
||||
"parityRate": $(echo "scale=4; $PASSED / $TOTAL" | bc)
|
||||
},
|
||||
"results": [$(IFS=,; echo "${RESULTS[*]}")]
|
||||
}
|
||||
EOF
|
||||
|
||||
echo "Parity Report: $PASSED/$TOTAL passed ($(echo "scale=2; $PASSED * 100 / $TOTAL" | bc)%)"
|
||||
|
||||
if [[ $FAILED -gt 0 ]]; then
|
||||
echo "PARITY VERIFICATION FAILED"
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. PARITY RESULTS
|
||||
|
||||
### 5.1 Latest Verification Results
|
||||
|
||||
| Date | Bundle Version | Images Tested | Parity Rate | Notes |
|
||||
|------|---------------|---------------|-------------|-------|
|
||||
| 2025-12-14 | 2025.12.0 | 6 | 100% | Baseline established |
|
||||
| — | — | — | — | — |
|
||||
|
||||
### 5.2 Historical Parity Tracking
|
||||
|
||||
```sql
|
||||
-- Query for parity trend analysis
|
||||
SELECT
|
||||
date_trunc('week', report_date) AS week,
|
||||
AVG(parity_rate) AS avg_parity,
|
||||
MIN(parity_rate) AS min_parity,
|
||||
COUNT(*) AS test_runs
|
||||
FROM parity_reports
|
||||
WHERE report_date >= NOW() - INTERVAL '90 days'
|
||||
GROUP BY 1
|
||||
ORDER BY 1 DESC;
|
||||
```
|
||||
|
||||
### 5.3 Parity Database Schema
|
||||
|
||||
```sql
|
||||
CREATE TABLE scanner.parity_reports (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
report_date TIMESTAMPTZ NOT NULL,
|
||||
bundle_version TEXT NOT NULL,
|
||||
bundle_digest TEXT NOT NULL,
|
||||
total_images INT NOT NULL,
|
||||
passed_images INT NOT NULL,
|
||||
failed_images INT NOT NULL,
|
||||
parity_rate NUMERIC(5,4) NOT NULL,
|
||||
results JSONB NOT NULL,
|
||||
ci_run_id TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_parity_reports_date ON scanner.parity_reports(report_date DESC);
|
||||
CREATE INDEX idx_parity_reports_bundle ON scanner.parity_reports(bundle_version);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. KNOWN LIMITATIONS
|
||||
|
||||
### 6.1 Acceptable Differences
|
||||
|
||||
| Scenario | Expected Behavior | Parity Impact |
|
||||
|----------|-------------------|---------------|
|
||||
| **EPSS scores** | Use bundled cache (may be stale) | None if cache bundled |
|
||||
| **KEV status** | Use bundled snapshot | None if snapshot bundled |
|
||||
| **Rekor entries** | Not created offline | Excluded from comparison |
|
||||
| **Timestamp fields** | Differ by design | Excluded from comparison |
|
||||
| **Network-only advisories** | Not available offline | Feed drift (documented) |
|
||||
|
||||
### 6.2 Known Edge Cases
|
||||
|
||||
1. **Race conditions during bundle capture**: If feeds update during bundle export, connected scan may include newer data than bundle. Mitigation: Capture bundle first, then run connected scan.
|
||||
|
||||
2. **Clock drift**: Offline environments with drifted clocks may compute different freshness scores. Mitigation: Always use frozen timestamps from bundle.
|
||||
|
||||
3. **Locale differences**: String sorting may differ across locales. Mitigation: Force `LC_ALL=C` in both environments.
|
||||
|
||||
4. **Floating point rounding**: CVSS v4 MacroVector interpolation may have micro-differences. Mitigation: Use integer basis points throughout.
|
||||
|
||||
### 6.3 Out of Scope
|
||||
|
||||
The following are intentionally NOT covered by parity verification:
|
||||
|
||||
- Real-time threat intelligence (requires network)
|
||||
- Live vulnerability disclosure (requires network)
|
||||
- Transparency log inclusion proofs (requires Rekor)
|
||||
- OIDC/Fulcio certificate chains (requires network)
|
||||
|
||||
---
|
||||
|
||||
## 7. TROUBLESHOOTING
|
||||
|
||||
### 7.1 Common Parity Failures
|
||||
|
||||
| Symptom | Likely Cause | Resolution |
|
||||
|---------|--------------|------------|
|
||||
| Different vulnerability counts | Feed version mismatch | Verify bundle digest matches |
|
||||
| Different CVSS scores | CVSS v4 calculation issue | Check MacroVector lookup parity |
|
||||
| Different severity labels | Threshold configuration | Compare policy bundles |
|
||||
| Missing EPSS data | EPSS cache not bundled | Re-export with `--epss-cache` |
|
||||
| Different component counts | SBOM generation variance | Check analyzer versions |
|
||||
|
||||
### 7.2 Debug Commands
|
||||
|
||||
```bash
|
||||
# Compare feed versions
|
||||
stellaops feeds version --connected
|
||||
stellaops feeds version --offline --bundle ./bundle
|
||||
|
||||
# Compare policy digests
|
||||
stellaops policy digest --connected
|
||||
stellaops policy digest --offline --bundle ./bundle
|
||||
|
||||
# Detailed diff of findings
|
||||
stellaops parity diff \
|
||||
--connected connected-scan.json \
|
||||
--offline offline-scan.json \
|
||||
--verbose
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. METRICS AND MONITORING
|
||||
|
||||
### 8.1 Prometheus Metrics
|
||||
|
||||
```
|
||||
# Parity verification metrics
|
||||
parity_test_total{status="pass|fail"}
|
||||
parity_test_duration_seconds (histogram)
|
||||
parity_bundle_age_seconds (gauge)
|
||||
parity_findings_diff_count (gauge)
|
||||
```
|
||||
|
||||
### 8.2 Alerting Rules
|
||||
|
||||
```yaml
|
||||
groups:
|
||||
- name: offline-parity
|
||||
rules:
|
||||
- alert: ParityTestFailed
|
||||
expr: parity_test_total{status="fail"} > 0
|
||||
for: 0m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "Offline parity test failed"
|
||||
|
||||
- alert: ParityRateDegraded
|
||||
expr: |
|
||||
(sum(parity_test_total{status="pass"}) /
|
||||
sum(parity_test_total)) < 0.95
|
||||
for: 1h
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Parity rate below 95%"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. REFERENCES
|
||||
|
||||
- [Offline Update Kit (OUK)](../24_OFFLINE_KIT.md)
|
||||
- [Offline and Air-Gap Technical Reference](../product-advisories/14-Dec-2025%20-%20Offline%20and%20Air-Gap%20Technical%20Reference.md)
|
||||
- [Determinism and Reproducibility Technical Reference](../product-advisories/14-Dec-2025%20-%20Determinism%20and%20Reproducibility%20Technical%20Reference.md)
|
||||
- [Determinism CI Harness](../modules/scanner/design/determinism-ci-harness.md)
|
||||
- [Performance Baselines](../benchmarks/performance-baselines.md)
|
||||
|
||||
---
|
||||
|
||||
**Document Version**: 1.0
|
||||
**Target Platform**: .NET 10, PostgreSQL >=16
|
||||
Reference in New Issue
Block a user