# Feed Snapshot Freeze/Staleness Thresholds (SP6) Status: Draft · Date: 2025-12-04 Scope: Codify feed snapshot governance including freshness budgets, staleness thresholds, freeze procedures, and validation checks. ## Objectives - Define maximum age for vulnerability feed snapshots. - Establish freeze/thaw procedures for reproducible scans. - Set staleness detection and alerting thresholds. - Enable deterministic feed state for replay scenarios. ## Freshness Budgets ### Feed Types | Feed Type | Source | Max Age | Staleness Threshold | Critical Threshold | |-----------|--------|---------|---------------------|-------------------| | NVD | NIST NVD | 24 hours | 48 hours | 7 days | | OSV | OSV.dev | 12 hours | 24 hours | 3 days | | GHSA | GitHub | 12 hours | 24 hours | 3 days | | Alpine | Alpine SecDB | 24 hours | 48 hours | 7 days | | Debian | Debian Security Tracker | 24 hours | 48 hours | 7 days | | RHEL | Red Hat OVAL | 24 hours | 72 hours | 7 days | | Ubuntu | Ubuntu CVE Tracker | 24 hours | 48 hours | 7 days | ### Definitions - **Max Age**: Maximum time since last successful sync before warnings - **Staleness Threshold**: Time after which scans emit staleness warnings - **Critical Threshold**: Time after which scans are blocked without override ## Snapshot States ``` ┌─────────────┐ │ SYNCING │ ──────────────────────────────────┐ └─────────────┘ │ │ │ ▼ ▼ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ FRESH │ ──► │ STALE │ ──► │ CRITICAL │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ ▼ ▼ ▼ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ FROZEN │ │ FROZEN │ │ FROZEN │ │ (fresh) │ │ (stale) │ │ (critical) │ └─────────────┘ └─────────────┘ └─────────────┘ ``` ### State Transitions | Current State | Trigger | New State | Action | |---------------|---------|-----------|--------| | SYNCING | Sync complete | FRESH | Record snapshot hash | | FRESH | Max age exceeded | STALE | Emit warning | | STALE | Staleness threshold exceeded | CRITICAL | Block scans | | CRITICAL | Manual override | CRITICAL (override) | Log override | | Any | Freeze command | FROZEN | Lock snapshot | | FROZEN | Thaw command | Previous state | Unlock snapshot | ## Snapshot Schema ```json { "id": "nvd-2025-12-04T00-00-00Z", "feed": "nvd", "source": "https://nvd.nist.gov/feeds/json/cve/1.1/nvdcve-1.1-modified.json.gz", "syncedAt": "2025-12-04T00:00:00Z", "hash": { "blake3": "...", "sha256": "..." }, "state": "fresh", "advisoryCount": 250000, "lastModified": "2025-12-03T23:45:00Z", "frozen": false, "frozenAt": null, "frozenBy": null, "thresholds": { "maxAge": "PT24H", "staleness": "PT48H", "critical": "P7D" } } ``` ## Freeze Procedures ### Manual Freeze ```bash # Freeze current snapshot for reproducible scans stellaops feed freeze --feed nvd --reason "Audit baseline 2025-Q4" # Freeze with specific snapshot stellaops feed freeze --feed nvd --snapshot-id nvd-2025-12-04T00-00-00Z # List frozen snapshots stellaops feed list --frozen ``` ### Automatic Freeze (Replay Mode) When recording a scan for replay: 1. Scanner captures current snapshot IDs for all feeds 2. Snapshot hashes recorded in replay manifest 3. Replay execution uses frozen snapshots from manifest ```json { "replay": { "feeds": { "nvd": { "snapshotId": "nvd-2025-12-04T00-00-00Z", "hash": "b3:...", "state": "frozen" }, "osv": { "snapshotId": "osv-2025-12-04T00-00-00Z", "hash": "b3:...", "state": "frozen" } } } } ``` ### Thaw Procedures ```bash # Thaw a frozen snapshot (resume normal sync) stellaops feed thaw --feed nvd --snapshot-id nvd-2025-12-04T00-00-00Z # Force thaw all frozen snapshots stellaops feed thaw --all --force ``` ## Validation Checks ### Pre-Scan Validation ```python # scripts/scanner/validate_feeds.py def validate_feed_freshness(feed_states: dict) -> ValidationResult: errors = [] warnings = [] for feed, state in feed_states.items(): age = datetime.utcnow() - state['syncedAt'] thresholds = state['thresholds'] if age > parse_duration(thresholds['critical']): if not state.get('override'): errors.append(f"{feed}: Critical staleness ({age})") elif age > parse_duration(thresholds['staleness']): warnings.append(f"{feed}: Stale ({age})") elif age > parse_duration(thresholds['maxAge']): warnings.append(f"{feed}: Approaching staleness ({age})") return ValidationResult( valid=len(errors) == 0, errors=errors, warnings=warnings ) ``` ### Scan Output Metadata ```json { "scan": { "id": "scan-12345", "feedState": { "nvd": { "snapshotId": "nvd-2025-12-04T00-00-00Z", "state": "fresh", "age": "PT12H", "hash": "b3:..." }, "osv": { "snapshotId": "osv-2025-12-04T00-00-00Z", "state": "stale", "age": "PT36H", "hash": "b3:...", "warning": "Feed approaching staleness threshold" } } } } ``` ## API Endpoints ### Feed Status ```http GET /api/v1/feeds/status ``` Response: ```json { "feeds": { "nvd": { "state": "fresh", "snapshotId": "nvd-2025-12-04T00-00-00Z", "syncedAt": "2025-12-04T00:00:00Z", "age": "PT12H", "frozen": false, "thresholds": { "maxAge": "PT24H", "staleness": "PT48H", "critical": "P7D" } } } } ``` ### Freeze Feed ```http POST /api/v1/feeds/{feed}/freeze Content-Type: application/json { "snapshotId": "nvd-2025-12-04T00-00-00Z", "reason": "Audit baseline" } ``` ### Override Staleness ```http POST /api/v1/feeds/{feed}/override Content-Type: application/json { "snapshotId": "nvd-2025-12-04T00-00-00Z", "reason": "Emergency scan required", "approvedBy": "security-admin@example.com", "expiresAt": "2025-12-05T00:00:00Z" } ``` ## Alerting ### Alert Conditions | Condition | Severity | Action | |-----------|----------|--------| | Feed sync failed | Warning | Retry with backoff | | Feed approaching staleness | Info | Log, notify ops | | Feed stale | Warning | Notify ops, scan warnings | | Feed critical | Critical | Block scans, page on-call | | Feed frozen > 30 days | Warning | Review freeze necessity | ### Alert Payload ```json { "alert": { "type": "feed_staleness", "feed": "nvd", "severity": "warning", "snapshotId": "nvd-2025-12-04T00-00-00Z", "age": "PT50H", "threshold": "PT48H", "message": "NVD feed exceeds staleness threshold (50h > 48h)", "timestamp": "2025-12-06T02:00:00Z" } } ``` ## Determinism Integration ### Replay Requirements For deterministic replay: 1. All feeds MUST be frozen at recorded state 2. Snapshot hashes MUST match manifest 3. No network fetch during replay 4. Staleness validation skipped (uses recorded state) ### Validation Script ```bash #!/bin/bash # scripts/scanner/verify_feed_hashes.sh MANIFEST="replay-manifest.json" for feed in $(jq -r '.replay.feeds | keys[]' "${MANIFEST}"); do expected_hash=$(jq -r ".replay.feeds.${feed}.hash" "${MANIFEST}") snapshot_id=$(jq -r ".replay.feeds.${feed}.snapshotId" "${MANIFEST}") actual_hash=$(stellaops feed hash --snapshot-id "${snapshot_id}") if [[ "${actual_hash}" != "${expected_hash}" ]]; then echo "FAIL: ${feed} snapshot hash mismatch" echo " expected: ${expected_hash}" echo " actual: ${actual_hash}" exit 1 fi echo "PASS: ${feed} snapshot ${snapshot_id}" done ``` ## Links - Sprint: `docs/implplan/SPRINT_0186_0001_0001_record_deterministic_execution.md` (SP6) - Spine Versioning: `docs/modules/policy/contracts/spine-versioning-plan.md` (SP1) - Replay: `docs/replay/DETERMINISTIC_REPLAY.md`