Catalyst / admin/Synapse-Cortex 14.8 GB / 57.8 GB 40.0 GB free
Help Sign in

admin / Synapse-Cortex

public

Self Hosted ITSM Tool with RBAC/Tenanting and MFA

Code Issues Pull requests Pipelines Packages Security Insights Wiki Settings
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.