admin / Synapse-Cortex
publicSelf Hosted ITSM Tool with RBAC/Tenanting and MFA
Synapse-Cortex / Synapse-Cortexv2 / docs / vuln-remediation-hitl / blueprint.md
9098 B · main
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 | # Human-in-the-Loop Vulnerability Remediation Playbook — Technical Blueprint Grounded in Synapse-Cortex v2's real primitives: `Playbook.graph_json` (node/edge graph) · `RemediationRun` status machine · the `{{command}}` token · `RemediationApproveRequest{command?}` · `guardrails.py`. ## Architectural constraint (non-negotiable) The discovery command is **stored in the playbook**, not chosen by the AI. It lives in `graph_json.nodes[].data.command` on the `phase1_discover` node (and, byte-identical, on `phase5_verify`). The assistant executes that literal string verbatim. The **only** AI-authored value in the whole graph is the `{{command}}` token on the `phase4_remediate` node — and even that is (a) re-run through the full guardrail engine and (b) gated on a human **Approve** click before it can run. Guardrails (`allowed_target_os`, `forbidden_commands`, and the non-overridable `BASELINE_FORBIDDEN_PATTERNS`) are enforced server-side and cannot be bypassed by graph content. ## Lifecycle ↔ `RemediationRunStatus` state machine | Phase | Action | `RemediationRun.status` | Where it runs | |------|--------|--------------------------|---------------| | 1 Discovery | Run predefined scan, capture raw stdout | `investigating` | pre-approval | | 2 Triage & Ticket | Parse output, rank High/Critical, emit ticket payload + `suggested_command` | `investigating` | assistant-side | | 3 Approval Gate | Post proposal, **suspend** | `pending_approval` (wait state) | — | | 4 Remediation | On Approve event, run approved command only | `executing` | post-approval | | 5 Verify & Close | Re-run Phase 1 command, diff, log, close | `succeeded` / `failed` | post-approval | `pending_approval` **is** the wait state: the run row persists, the assistant returns, and nothing else executes until the approve/reject endpoint fires. No polling loop or in-memory suspension — the DB row is the checkpoint. ## Data models ### 2a. Ticket payload (Phase 2 → Cortex) Extends the existing NetscanXi→Cortex ingest ticket (`POST /api/ingest/tickets`) with a `remediation` block. One ticket per asset; `external_ref` dedupes. ```json { "external_ref": "netscanxi:acme:vulns:RTZ9PSK8", "asset_id": "RTZ9PSK8", "title": "Vulnerabilities: db-01 — 3 High/Critical package(s)", "priority": "critical", "status": "new", "source": "NETSCAN_XI", "remediation": { "playbook": "vuln-remediation-hitl-linux", "target_os": "linux", "baseline_scan_ref": "phase1_discover", "findings": [ { "cve": "CVE-2020-1472", "package": "samba", "installed_version": "2:4.13.17~dfsg-0ubuntu1", "fixed_version": "2:4.15.13+dfsg-0ubuntu1", "cvss": 10.0, "severity": "CRITICAL", "kev": true, "business_impact": "Domain-controller takeover (Zerologon); full AD compromise. DC is business-critical.", "remediation_command": "apt-get install --only-upgrade -y samba" } ], "suggested_command": "apt-get install --only-upgrade -y samba libsmbclient samba-common" } } ``` **Field contract for `remediation.findings[]`** (the required fields the prompt must always produce): `cve`, `package`, `severity` ∈ {CRITICAL,HIGH,MEDIUM,LOW}, `business_impact` (free text), `remediation_command` (must match the approved apt idiom, see prompt). `suggested_command` is the single concatenated command that fills the graph's `{{command}}` token and becomes `RemediationRun.suggested_command`. ### 2b. `RemediationRun` fields used here (existing model) `status`, `ai_summary`, `proposed_plan` (JSON: the resolved step list), `execution_log` (JSON: per-command stdout/stderr/rc), `outcome_summary`, `suggested_command` (the editable default on the approval card), `approved_by_id` / `approved_at`, and the immutable `graph_snapshot` / `guardrails_snapshot` taken at proposal time. ## 3. The native approval gate (wait state + resume event) **Wait state:** `RemediationRun.status = "pending_approval"`. The playbook's `required_approval_level = "human_in_the_loop"` is what forces this stop. **Resume event — what the Cortex UI POSTs on "Approve":** ``` POST /api/tickets/{ticket_id}/remediation/runs/{run_id}/approve Cookie: <authenticated session> # the approver's identity = audit trail Content-Type: application/json { "command": "apt-get install --only-upgrade -y samba" } ``` - `command` is **optional**. Omitted → the stored `suggested_command` is used unchanged. Present → the human's edited command replaces it, and is re-checked by `run_guardrails(...)` before anything runs. - The server sets `approved_by_id`, `approved_at`, transitions `pending_approval → executing`, and only then dispatches. A guardrail failure on the (possibly edited) command transitions to `blocked`, never `executing`. - **Reject:** `POST …/runs/{run_id}/reject` → status `rejected`, nothing runs. ## 5. Verification & closure Phase 5 re-runs the **exact** Phase 1 string (guaranteed identical — same literal on both nodes, and the assistant is told to source it from the `phase1_discover` node, never to retype it). `verify_diff.verify_remediation(baseline, verify, target_packages)` decides success = every targeted package left the upgradable set with no regression. On success: append `outcome_summary` + the diff to the ticket's Actions Taken, set run `succeeded`, and move the ticket to `resolved`. On failure: run `failed`, ticket stays open with the still-pending list. --- ## Agent system prompt > You are the Synapse-Cortex remediation assistant executing the > **Human-in-the-Loop Vulnerability Remediation** playbook against a single > Linux (apt/dpkg) host over an authenticated SSH session. You operate strictly > inside the five phases below and never act outside them. > > **Absolute rules** > 1. The discovery/scan command is **predefined** in the playbook graph > (`phase1_discover.data.command`). Execute it **exactly as given, byte for > byte**. You must never invent, edit, wrap, augment, or substitute a scan > command. If the command is missing or empty, stop and report — do not > improvise one. > 2. The **only** command you may author is the targeted remediation command > that fills the `{{command}}` token on `phase4_remediate`. It must be a > package-upgrade command of the form > `apt-get install --only-upgrade -y <package> [<package> ...]` using only the > exact package names observed in the discovery output. Never `remove`, > `purge`, `dist-upgrade`, `do-release-upgrade`, add repositories, pipe to a > shell, or touch anything unrelated to the approved packages. > 3. You never execute Phase 4 or Phase 5 until you receive the native > **approve** event. When you propose, you stop and wait. > > **Phase 1 — Discovery.** Run `phase1_discover.data.command` verbatim over SSH. > Capture raw stdout unmodified as `baseline_scan`. Do not interpret exit codes > as failure unless the command could not run at all. > > **Phase 2 — Triage & ticket.** Parse `baseline_scan` (one upgradable package > name per line). Correlate each package with the CVEs on the ticket/asset. > Prioritise **CRITICAL and HIGH** (CVSS ≥ 7.0, or any KEV) — ignore Medium/Low > for remediation. Produce a ticket payload matching the documented schema: for > every High/Critical finding emit `cve`, `package`, `installed_version`, > `fixed_version`, `cvss`, `severity`, `kev`, a one-sentence concrete > `business_impact`, and a `remediation_command` > (`apt-get install --only-upgrade -y <package>`). Set `suggested_command` to the > single combined upgrade command covering all approved-for-proposal packages. > Write a two-to-three sentence `ai_summary`. Do not fabricate versions or CVEs — > if a value is unknown, use `null`. > > **Phase 3 — Approval gate.** Set the run to `pending_approval`, post the > proposal (summary + `suggested_command`) to the ticket, and **suspend**. Take > no host action. Wait for the approve event. > > **Phase 4 — Remediation.** On the approve event, use the approved command > exactly as returned (the human may have edited it; the server has already > re-checked it against guardrails). Run only that command over SSH. Record > stdout, stderr, and exit code to `execution_log`. Run nothing else. > > **Phase 5 — Verification & closure.** Re-run the **identical** Phase 1 command > — read it from `phase1_discover.data.command`; do not retype or modify it. > Capture `verify_scan`. Diff `baseline_scan` vs `verify_scan` for the packages > you remediated (see the verification module). If every targeted package left > the upgradable set, write the success outcome, log the before/after diff to the > ticket, and close it (`resolved`). If any remain, mark the run `failed`, keep > the ticket open, and report exactly which packages are still pending. Never > re-attempt automatically — that requires a fresh approved run. > > Report every phase transition and the raw evidence to the ticket's Actions > Taken log. When uncertain, stop and ask a human rather than broadening scope. |