# ADR-0011: Phase 7e.2 — Firewall audit (iptables / nftables / ufw)

**Status:** Accepted
**Date:** 2026-05-09
**Related:** ADR-0008 (Phase 7e), ADR-0009 (Phase 7e.1), ADR-0010 (Phase 7f)

## Context

Phase 7e.1 deferred firewall coverage to a dedicated phase because three
backends multiply the implementation surface (~equal to 7e itself).
This ADR records the design decisions for Phase 7e.2 — the spec lives
at `docs/superpowers/specs/2026-05-09-phase-7e2-firewall-design.md`.

## Decisions

### D1. Backend coverage = iptables + nftables + ufw

Cover the three backends that account for the vast majority of Linux
server deployments (Ubuntu/Debian/RHEL/Alpine). Defer firewalld
(CentOS/RHEL D-Bus + zone model) to Phase 7e.3 and macOS pf to 7e.4.

### D2. Per-backend Check granularity (3 Checks)

`iptables_audit` / `nftables_audit` / `ufw_audit`, not a single
`firewall_audit` with internal dispatch. Mirrors the Phase 7f Docker
three-Check pattern (`docker_daemon_audit` / `docker_runtime_audit` /
`docker_image_cve`). Trade-off vs. one-Check: more files, but each
Check stays small and parser code is isolated.

### D3. Cross-talk avoidance — Approach A (independent Checks + ufw-chain helper)

On Ubuntu with ufw enabled, `iptables -S` shows ufw-injected `ufw-*`
chains and the INPUT default policy commonly stays ACCEPT (real DROP
happens at end of the ufw-* chain). Naïve independent evaluation would
double-report.

Chosen approach: each backend Check probes for ufw-injected chains in
its own output via the shared `output_managed_by_ufw(stdout, backend=)`
helper; on detection it reports `not_applicable("ufw 已接管…")` and
does not emit a default-policy finding. `ufw_audit` runs independently.

Rejected alternatives:

- **Backend resolver** (probe once at orchestrator startup, mark a
  primary backend on `CheckContext`): introduces shared state across
  Checks, breaks the per-Check autonomy invariant established in 7e/7f.
- **Pure independent (no cross-talk awareness)**: noisy double-reports
  on every Ubuntu+ufw host.

### D4. Best-effort permission model — no sudo policy

iptables / nft / ufw all require root on most distros. Rather than
extending `ssh_policy.yaml` to support `sudo -n <cmd>` (a cross-cutting
discussion that should not be bundled with this phase), each Check
detects EPERM via stderr substring and yields `skipped` with a clear
remediation message ("配置 NOPASSWD sudoers 或以 root 身份运行").
Operators who want active coverage add a NOPASSWD sudoers entry for
the specific allowed_argv tuples and re-run.

### D5. Audit point coverage — A + B + C only

This phase covers default policy (A), backend activation (B), and IPv6
parallel checks (C). Deferred to 7e.5: log rules (D), exposed-ACCEPT
to 0.0.0.0/0 (E), state tracking (F), ufw+raw cross-backend conflict
(G). Rationale: A+B+C catch the highest-density misconfigurations; D–G
either overlap with `exposure_scan` (E) or have higher false-positive
risk (F has many legitimate minimalist setups without it).

### D6. ufw IPv6 detection deferred to 7e.5

`ufw status verbose` does not surface IPv6 status — that lives in
`/etc/default/ufw` (`IPV6=yes/no`). Reading that file would require a
new `cat /etc/default/ufw` command-policy entry and changes the
"firewall checks read tool stdout, not config files" invariant. iptables
/ nftables Checks already provide IPv6 parallel coverage at the
netfilter layer; for ufw specifically, IPv6 parity is implicit unless
the operator manually disabled it.

## Consequences

- 3 new Checks + 1 helper + 1 baseline section + 1 TI entry; Server-side
  audit Check count rises from 21 (v0.10.0) to 24 (v0.11.0).
- Default Lightsail run will emit `skipped` for all 3 firewall Checks
  (ubuntu user has no NOPASSWD sudo). This is an intentional UX choice;
  remediation field directs operators to the fix.
- Future 7e.3 (firewalld) and 7e.4 (pf) can adopt the same per-backend
  + bin-detect + best-effort-permission template wholesale.
