Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Architecture Decision Records

The consequential decisions behind protector — the why behind the code, not just the what. New decisions get a new numbered file; superseded ones stay (marked Superseded by NNNN).

Format: Michael Nygard's ADR style. Copy 0000-template.md to start one.

# Decision Status
0001 Async mitigation engine: propose / prove / respond, local-first Accepted
0002 Change-driven incident loop: diff the cluster, prove the delta, manage the debt Accepted
0003 Capability ports: depend on what a tool answers, not which tool it is Accepted
0004 Graph representation: in-memory petgraph, rebuilt from observed state Accepted
0005 Objectives are ATT&CK outcomes, not just secrets Accepted
0006 Build the substrate; treat KubeHound/IceKube as catalogue and optional provider Accepted
0007 Live network cuts are additive AdminNetworkPolicy Deny rules Accepted
0009 Asymmetric action bar: live evidence acts, latent exposure proposes Accepted (amended by 0011, 0013, 0016, 0017, 0022; corroboration made tool-agnostic + per-objective by 0014)
0010 Flannel actuator: quarantine the source with a default-deny NetworkPolicy Accepted (amended by 0022)
0011 The model corroborates positively; operator access is out of scope, defended in depth Superseded in part by 0013
0012 Exposure is observed where possible, declared (annotation) where it can't be — tunnels Accepted
0013 Proof winnows the search space; the model makes the exploitability call (positive gate + breach-relevance) Accepted (amended by 0016)
0014 First-party behavioral telemetry via eBPF, behind a tool-agnostic port (potential vs actual) Accepted (amended: per-objective corroboration landed; the Retire-Falco parity bar = measured decision-path coverage, retire the adapter not the port)
0015 Advisory evidence is mounted-snapshot-only (zero egress); structurally extracted + capped for injection safety Accepted (advisory feed retired; Rekor egress carve-out amended by 0020)
0016 The breach model: prove chains, enrich them, the model decides and isolates until clear Accepted (amended by 0017)
0017 Isolation persists on the breach condition: chain ∧ enrichment fingerprint (revert keys on entry_fingerprint) Accepted
0018 The breach notifier is the one sanctioned outbound path: operator-configured, off by default, redacted by default Accepted
0019 Dashboard v3: server-rendered (maud), zero-egress, light-theme presentation — the view_model/component/page split + the honesty invariants Accepted (amended: finding detail shows all proven paths; presentation mechanism superseded in part by 0025 — IA + honesty axes survive)
0020 Supply-chain trust is signature continuity: observe every image, learn a per-repo TOFU baseline, treat the signed→unsigned / identity-change regression as the signal — not prefix-gated single-identity (amended: baseline-relative downgrade; build-provenance as a second continuity axis) Accepted
0021 Two-setting operating posture: mode (audit default / enforce) + one enforceScope arms all three enforcement surfaces (signature + mesh webhooks + engine live cut), fail-closed webhook selector and actuation RBAC derived from it — no per-surface toggle, no wildcard Accepted
0022 Quarantine the internet-facing entry is the default containment (entry-only, additive/reversible default-deny); the surgical edge-cut is the refinement used only when it suffices Accepted
0023 Delta-aware adjudication: the full cluster state is the context, the change is the question Accepted
0024 Corroboration shapes must be load-bearing when merged, not deferred-dead — a predicate whose result is already fixed by an existing arm lands when it bites, not ahead of it Accepted
0025 Dashboard v4: a bundled Preact client reconciling from same-origin read-only JSON — supersedes 0019's maud server-render mechanism (its IA + honesty axes survive); view_model/props retained as the serde JSON contract, bundle built-from-source + gitignored, honesty stays server-derived Accepted (its server-rendered strip/nav superseded in part by 0027 — the body is now root-only)
0026 Promote qwen3:1.7b as the adjudication judge (bakeoff: 12/12, the only clean sweep of all three evidence types + every refute; deployed qwen2.5:3b-instruct is 11/12, misses exposed-secret-in-field) — pending Pi latency/RAM validation, strict-JSON on-Pi, the delta-aware prompt path, and in-cluster zero-egress availability Proposed
0027 Dashboard: the server emits a ROOT-ONLY shell (<head> + #dash-root); the status strip + tab nav move to the Preact client — supersedes 0025's server-rendered strip/nav. Honesty preserved (blank ≠ green; the all-clear/watching/judging-state tokens stay server-derived). Also fixes the reversed-args setInterval bug (dead poll + blank tab-swaps + CSP eval violation) with the CSP kept strict; SSR/hydration deferred Accepted
0028 Dashboard client: local state by default — App holds the 5 shared fields (+ the callback-decoupled poll) as plain useState, the hand-rolled store + reconcile tombstone are deleted, expansion/disclosure is local & ephemeral (native <details>; sessionStorage persistence dropped), keyed removal replaces the tombstone (a future cleared-cue is server-shipped), and the npm deps prune to build+test only (zero runtime). Extends 0025/0027 (both stand); the poll/CSP fix + strip persistence + server-derived honesty are retained Accepted
0029 The adjudicating model's verdict is authoritative: NO deterministic guards that override/second-guess its breach judgement, and NO capping/summarizing the evidence to steer it — the full enriched chain goes to the model and its call stands. Rare false-exploitable flips on huge borderline entries are a temp-0 tail event (diagnosed: not a config/model/prompt bug), accepted as a known cost under shadow-first; addressed only at the model layer (bakeoff-evaluated), never a verdict gate. Reaffirms 0013 (model decides) + 0016 (shadow-first). Does not remove the anti-fabrication guard_fabricated_cve (output grounding, not a judgement override) Accepted
0030 App-level, provider-agnostic OIDC verification supersedes edge-only trust: the dashboard + /api/*.json today carry only CSP (auth trusted entirely at the Cloudflare Access edge), so one kubectl port-forward reads the whole graph. Protector becomes an OAuth RESOURCE SERVER (verifies sig/iss/aud/exp/nbf, alg pinned to the issuer's asymmetric family — never from the token's own alg; extracts subject + a configurable tier) against a CONFIGURABLE issuer; it ships NO IdP (Dex/Keycloak out of scope; supersedes the in-cluster-Dex idea) and cannot actuate. ID-JAG is just a JWT the same verifier accepts. The JWKS/discovery GET is the ADR-0015 same-request-for-everyone lane (public keys in, no cluster datum out), not an egress breach. FAIL-CLOSED when configured (every error → 401/403/503, JWKS-unreachable is 503 not bypass); UNCONFIGURED behaves as today but logs loudly — the only bypass, and it announces itself. CF Access already issues a verifiable JWT, so pointing the verifier at the CF issuer closes the hole with no new interactive flow. Reaffirms 0016 (auth gates WHO MAY VIEW, not the verdict path) Proposed
0031 A read-only, tiered-redaction MCP server — the second sanctioned egress carve-out (pull-side sibling of 0018's push notifier). Four read-only tools (list_findings / explain_verdict / get_coverage / signing_inventory); NO actuation tool exists by construction (view-never-a-gate / shadow-first, 0016). Three tiers redacted/forensic/raw with the tier a server-enforced CEILING from a verified ADR-0030 claim (arg may only narrow) — redacted safe-by-construction, forensic/raw off-by-default, opt-in, journaled operator-owned egress; secret VALUES have no unlock tier. Redaction is server-side/in-cluster BEFORE egress, so protector IS the remote HTTP MCP server (reusing 0018's lifted scrubbers). Transport: RMCP behind our OIDC verifier (single-pathed, in-tree trust decisions; hand-rolled JSON-RPC fallback if it won't compose), ID-JAG protected-resource discovery for zero-touch enterprise auth. Depends on 0030 Proposed
0032 The model is the incident responder: over the whole internet-facing path it decides what is an attack and which cut to apply — choosing from a menu that determinism ENUMERATES — at minimum scope. Determinism proves/enriches/feeds/bounds; it does NOT decide the cut. The model now SEES per-node downstream evidence (was entry-only); the is_live_corroborated unconditional QuarantineWorkload auto-fire is deleted; internal-only actively-exploited pods become propose-only. Supersedes 0022's decision procedure (its vocabulary/shapes/ladder survive as the menu + proposal fallback); evolves 0009 (adjudicator veto → cut selector); realizes the north-star model-decides-the-cut shift. Judge tier deferred to 0033 (do NOT assume qwen3:1.7b; expected qwen3:4b). Realizes the VISION north star Proposed
0034 The cut-choice contract: refines 0032 §3 from a mechanism-menu to target-choice — the model outputs {assessment: attack/no_attack/uncertain, reason, contain:[node-key…]}, naming the compromised on-path nodes; determinism resolves each to its narrowest legal cut (the containment_for ladder / QuarantineWorkload), because per-target minimality is monotone/deterministic (no judgment to delegate) and a node-key is what a 1.7B can reliably copy from attended evidence. Collapses the 4-value verdict → 3-value assessment. Menu rendered as advisory input (mechanism + blast-radius); guards (menu-membership + per-node containment-grounding) downgrade to Uncertain, never Refuted; skeptic default is inert both ways (a model outage neither cuts nor lifts a standing cut); journal v2 stores the resolved cut_signature+fingerprint so replay can't repoint. Chosen for 1.7B-viability (re-scope: 1.7b-first); residual risk is grounded over-cut, measured by the T2b temp-0.8 bench. Supersedes 0032 §3 Proposed
0036 Disarm is a real, fast kill switch: the self-revert loop now reverts a standing cut when its OWN action class is no longer armed (not just on health/chain retirement, closing the enforce→audit orphaned-cut gap); a break-glass flag file (fixed mount path, presence-only, no content parsed) clamps actuation to dry-run and drives every standing cut to revert within one pass, with no image rebuild and no GitOps sync — narrows only, never arms wider than mode/enforceScope already permit. Chosen over a local admin endpoint: no new listener/auth surface, keeps working even if the dashboard/mesh/OIDC path is itself down Accepted

| 0037 | Shadow-bake arm-readiness: the human-read exit criterion (bake duration, zero unexplained model-over-cut on a clean workload, 0033's bench threshold, a model-under-cut spot-check, continuous coverage) an operator reads from the model-vs-deterministic cut-divergence comparator before the single ADR-0021 enforce flip, narrowest ADR-0035 rung first — the comparator only informs; nothing here or in code auto-arms | Proposed |

| 0040 | Node-scoped containment is a deterministic mechanism escalation of a model-decided target: when typed evidence proves the adversary broke the pod boundary (host-cred read; root + escape edge; ptrace/module-load; or ≥2 model-confirmed actively-exploited pods co-resident), the resolver escalates the model-named workload's cut from a pod NetworkPolicy to ContainNode (cordon + co-resident default-deny) — the model keeps what, determinism owns how (rejects a model-selectable node menu line: highest blast on the weakest measured axis). Own node action class at ladder rung 3 (above quarantine), propose-first by construction (a node always has alive collateral), reversible + ownership-marked + one-node-cap + control-plane-excluded + worker-floor; posture-derived nodes RBAC; ships shadow-first (trigger/proposal, then actuator). Also records: adversary-reach is a presentation annotation, never judge context (context-not-evidence enforced by absence from the prompt), and off-path auto-containment is a won't-build (reaffirms 0032 §6, strengthened by 0038). Refines 0032/0034; reuses 0010/0017/0021/0035/0036 rails | Proposed | | 0041 | Narrow the blanket notable-exec corroboration arm to shapes: a bare interactive-shell/pkg-mgr ProcessExec stops blanket-corroborating every objective (Behavior::ProcessExec → false in the flat arm — symmetric with PrivilegeChange/PtraceAttach/ModuleLoad, restoring the 0011 on-call-engineer FP guard), and exec-based corroboration re-homes in an entry-scoped reverse_shell_on_foothold shape (interactive-shell exec + internet NetworkConnection within a symmetric 60s window, foothold-gated) — unmasking the deferred reverse-shell shape per 0024. Corrects the framing: the model never sees corroborated (removed from the prompt by 0034); narrowing buys deterministic auto-apply-gate precision + honest latent/live presentation + 0024 unmasking, NOT model-input precision. Safe now because Falco's Alert arm still backstops (falco.enabled: true), pre-arming in audit. One real behavior change (inert under audit): a shell-only + decisive-attack incident becomes a proposal not an auto-cut in enforce (0011-correct). Package managers excluded (always egress); is_alarming_now/menu seeding untouched; a narrowing-delta counter feeds the retire-Falco parity bake (0037). Amends 0009's action-bar input | Proposed |

See also ../VISION.md for the longer-form narrative this ADR realizes.