# Operating runbook

The procedure for an operator who has to close this CVE on a host, including the decisions the
scripts do not make for you. It covers the Debian and Ubuntu package path; every other component
class — Node.js, Bun, Python, .NET, Envoy, Istio — is in the
[remediation playbooks](08-Remediation-Playbooks).

## 0. Pre-flight

| Check | Command | Pass condition |
|---|---|---|
| Host reachable, key-based access | `ssh -o BatchMode=yes <host> true` | exit 0 |
| OS supported | `ssh <host> 'grep PRETTY_NAME /etc/os-release'` | Debian 12, Ubuntu 24.04 or 26.04 |
| Package present | `ssh <host> 'dpkg -l libbrotli1'` | a package line, or `NOT_INSTALLED` is the expected answer |
| Disk room for the backup | `ssh <host> 'df -h /root /var'` | at least 50 MB free on both |
| Scripts delivered | `ssh <host> 'cd /root/cve-2026-111497 && bash -n *.sh'` | `syntax OK` |
| Change window agreed | — | the restart touches the web tier and the panel |

The last row is not bureaucracy: `remediate.sh` restarts nginx, the PHP-FPM pools and the hosting
panel. On a busy host that is a visible event.

## 1. Audit

```bash
ssh <host> 'bash /root/cve-2026-111497/detect.sh'; echo "exit=$?"
```

| Exit | Action |
|---:|---|
| 0 | stop: nothing to do, record `VERDICT: SAFE` |
| 3 | stop: `libbrotli1` absent; re-check if Brotli arrives with a future package |
| 10 | stop: read the message; the audit itself failed, the host state is unknown |
| 1 | continue to step 2 |

Read the four numbers before trusting the verdict: `vulnerable`, `backported`, the apt candidate and
the consumer summary. A `VULNERABLE` verdict with an empty consumer list means a vulnerable package
nobody currently maps — still worth fixing, but not an emergency.

## 2. Decide the remediation path

| Situation | Path |
|---|---|
| Candidate newer than installed, changelog names the CVE | `remediate.sh` |
| Candidate newer, changelog does **not** name the CVE | stop, escalate: a rebuild is not a fix |
| No candidate at all | manual: block `Content-Encoding: br` at the edge, or build brotli 1.2.1 from source |
| Runtime bundles its own Brotli (Node, Bun, .NET, Python wheel) | separate update — see [remediation playbooks](08-Remediation-Playbooks); the package update does nothing for these |
| Brotli compiled into a binary or an image | separate rebuild; `dpkg` cannot see it |

## 3. Remediate

```bash
ssh <host> 'bash /root/cve-2026-111497/remediate.sh'; echo "exit=$?"
```

Expected: exit 0 and a line `REMEDIATED: <old> -> <new>`. During the run the script prints the backup
path, the authenticity-gate result, the dry-run set and every unit it restarts. Those lines are the
audit trail; keep them in the change record.

Exit 10 means the run stopped before changing anything — except for the backup, which is harmless.
Read the message: a failed authenticity gate and a failed download are different problems.

## 4. Verify

```bash
ssh <host> 'bash /root/cve-2026-111497/verify.sh'; echo "exit=$?"
```

`VERIFY: PASS` requires all five checks: version changed, changelog names the CVE, no stale server
mapping, no failed units, functional round-trip. A `FAIL` is not "close enough": go back to the
stale-mapping scan and the unit list.

## 5. Post-checks the script cannot do for you

| Check | Why |
|---|---|
| Hosted sites answer with their pre-change status | a restart can leave a pool with a bad socket |
| Panel login works | the panel daemons were restarted through the panel's own tool |
| `systemctl --failed` re-read after five minutes | a unit can fail on a delayed timer |
| Application smoke test of an outbound HTTP call | the patched decode path is the one that matters |
| Cron and queue workers alive | a restarted worker may not come back on its own |

A one-shot example for a hosting box:

```bash
for d in $(ls /etc/nginx/fastpanel2-sites/*/*.conf 2>/dev/null | head -20); do
  host=$(basename "$d" .conf)
  printf '%s -> %s\n' "$host" "$(curl -s -k -o /dev/null -w '%{http_code}' --resolve "$host:443:127.0.0.1" "https://$host/")"
done
```

## 6. Verification checklist

A host is closed when every box has evidence next to it. The first five come from the scripts; the
rest are the operator's.

- [ ] `detect.sh` verdict and exit code recorded, before and after the change.
- [ ] `verify.sh` returned `VERIFY: PASS`, and the five checks it makes are visible in the output.
- [ ] No server process maps a pre-patch inode (the scan in the [inventory](07-Inventory) page).
- [ ] `systemctl --failed` is empty, re-read five minutes after the change.
- [ ] Decode round-trip succeeded, or its absence is explained.
- [ ] Hosted sites answer with their pre-change status.
- [ ] Deferred desktop and infrastructure consumers are named in the record with the sentence that
they reload the patched library on the next login or reboot.
- [ ] Backup directory exists and is non-empty; its path is in the record.
- [ ] Inventory rows carry the new version, the restart time and the verification result.
- [ ] Artifacts rebuilt independently of this host (images, standalone binaries) were rescanned.

## 7. Rollback

```bash
ls /root/cve-2026-111497-fix-*/deb/
dpkg -i /root/cve-2026-111497-fix-<timestamp>/deb/libbrotli1_*.deb
# restart the units listed by the remediator, then re-run verify.sh to record the state
```

Rollback restores a vulnerable library. Use it when the patched package breaks a dependency, record
the reason, and escalate: the host returns to the exposure it had before the change.

## 8. Closing the change

| Artifact | Content |
|---|---|
| `report.json` | machine-readable audit result, one per run |
| `state.before`, `state.after` | versions and inodes, the evidence trail |
| Backup directory | rollback source, retained until closure |
| Change record | the four commands, their exit codes, the units restarted, the deferred list |

The deferred list belongs in the record with the sentence "reloads the patched library at the next
login or reboot" — otherwise the next auditor reads the mapping and thinks the host is still
vulnerable.

## 9. Recurrence

| Interval | Action |
|---|---|
| after every distro security update touching Brotli | `detect.sh` on the affected class of hosts |
| when a runtime is upgraded | check the bundled decoder version, not only the package |
| monthly | re-read the advisory digest for new ecosystem entries |

Next: [Traps](11-Traps).
