Commit Graph

2 Commits

Author SHA1 Message Date
Anso 6527bc971b feat(security): gate deploys on exploitation risk, not just severity (#1432)
Scan-policy deploy gates can now block on a known-exploited CVE (CISA KEV)
and on a fixable Critical/High finding, in addition to an optional severity
threshold. New policies default risk-first (KEV and fixable on, severity off);
existing policies keep their severity-only behavior. CVSS stays captured for
context but is never the sole basis for a block, and a finding whose
exploitability cannot be confirmed is treated as risky rather than safe
(incomplete scan detail fails closed on KEV/fixable inputs).

The decision logic is shared between the pre-deploy gate and the informational
post-scan banner via a pure helper, so the two never disagree. Block messages
and the block dialog now name the conditions an image matched. Backend and
frontend gates move together, the new inputs replicate across the fleet, and a
blocking policy with no active input is rejected on both sides.
2026-06-24 20:05:17 -04:00
Anso 27660f622b fix(fleet-sync): version the wire protocol and serialize per-node pushes (#967)
Hardens the scan-policy and CVE-suppression replication channel as the
foundation of a multi-PR fleet sync hardening track. No new endpoints,
no new tables, no schema changes; receivers still tolerate legacy
payloads (absent pushedAt and controlIdentity) for rollout safety.

Wire protocol:
- Sender stamps every push with a strictly-increasing pushedAt and a
  placeholder controlIdentity. Receiver rejects strictly-older pushedAt
  with 409 STALE_SYNC_PUSH so the next write retries.
- pushedAt comparison plus row replacement plus watermark write run in a
  single SQLite transaction; a partial-write window cannot leave the
  watermark behind the row state.

Concurrency and limits:
- Per-node mutex on the sender so concurrent control writes serialize
  per remote and never apply older state on top of newer.
- Sender-side row cap at MAX_SYNC_ROWS=5000 with a 6-hour throttled
  truncation alert so flapping configs cannot flood the operator.
- Route-level body limit raised to 5MB on POST /api/fleet/sync/:resource
  only; the global 100KB cap is unchanged. Oversize bodies return a
  structured 413 SYNC_PAYLOAD_TOO_LARGE.

Determinism and hygiene:
- getMatchingPolicy gains an id-ASC tiebreaker so two replicas resolve
  the same winner when policies tie on scope class.
- Inline comment documents why getMatchingPolicy filters node_id at SQL
  yet still relies on JS identity matching for replicated rows.
- Comment on the receive endpoint documents why no requirePaid is
  enforced (control's tier authorizes; replica trusts the bearer).
- STALE_SYNC_PUSH 409s no longer record a node failure; they are
  expected protocol outcomes, not health issues.

Public surface additions:
- DatabaseService.transaction(fn): generic SAVEPOINT-friendly wrapper.
- DatabaseService.getLocalScanPolicies / getLocalCveSuppressions: SQL
  filter on replicated_from_control = 0.
- StaleSyncPushError: typed sentinel the route translates to 409.
- fleetSyncConstants: shared MAX_SYNC_ROWS, body limit, state-key and
  error-code maps so the wire protocol has one source of truth.

Tests:
- 24 new vitest cases across fleet-sync-service, fleet-sync-routes, and
  database-matching-policy covering monotonic pushedAt, per-node
  serialization, row truncation and throttle, stale-push suppression,
  receiver back-compat, oversize-body 413, deterministic matching.
- Full backend suite: 1757 pass / 5 skipped.
2026-05-07 12:53:13 -04:00