Files
hq/04-ISSUES/003-firewall-scope-is-read-by-no-code/00-report.md
T
jschoubben 9eb5576682 04-ISSUES/003 — fixed: the firewall scope is enforced now
The manifest refuses unknown keys (DisallowUnknownFields), 'from' is the field
that scopes a port and it is rendered to nftables (AsNftables), and the firewall
module applies the rule set. The chain from a declared scope to a dropped packet
is closed. Amended-design: ADR 0050.

Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
2026-09-04 20:56:44 +02:00

3.0 KiB

status, opened, located-in, fixed-by, amended-design
status opened located-in fixed-by amended-design
fixed 2026-08-22
mesh-control/internal/catalogue
mesh-catalog/modules/firewall
the manifest refuses unknown keys, `from` is the field that scopes a port and it is rendered to nftables, and the firewall module applies it 0050-a-machine-firewall-is-the-sum-of-what-it-listens-on.md

003 — A firewall rule's scope: is read by no code

Symptom

Five module manifests declare a scope: key on firewall rules. The key is not part of the firewall rule type and nothing reads it. Real scoping is expressed by a different field.

A manifest can therefore appear to restrict a port and restrict nothing.

Why this matters

This is the failure mode how-we-build.md names directly: an unenforced rule is indistinguishable from a wrong one, and costs more, because people believe it. Here it is worse than unenforced — the declaration reads as a restriction, so a reviewer checking whether a port is scoped will find that it is, and be wrong.

It also says something about the manifest as a whole: an unknown key is accepted silently. Any misspelled or invented key behaves this way, and this one was found by reading rather than by any check.

Evidence

  • Five manifests carry the key. Zero code paths consume it.
  • Recorded as an observation on 2026-08-22.

Resolution

Both open questions are answered, and the chain from a declared scope to a packet actually dropped is closed — recorded as ADR 0050.

  • Unknown keys are refused, not accepted. ParseManifest decodes with DisallowUnknownFields, so a scope: key the firewall type does not have is now rejected at the manifest — the general fix, of which this was one instance. A key that reads as a restriction can no longer be one nothing enforces.
  • The field that scopes a port is from, and it is read. A listens entry names its source — mesh, anywhere or machine — and the control plane renders the union of every module's listens into a node's whole nftables rule set (AsNftables), default-drop with an accept scoped to exactly the source each port named. The five scope: declarations were the wrong spelling of that intent; from is the right one, and it is enforced.
  • A module applies it. The rendered rule set is written to the node (the filtering resource), and the firewall module (mesh-catalog) loads it — the last link, without which the rules were computed and never dropped a packet.

Original open questions

  • Should the manifest reject unknown keys outright? — Yes; it does now (DisallowUnknownFields).
  • Were the five declarations intended to restrict something that is currently open? — They meant to scope a port and used a key nothing read; expressed through from, that intent is now enforced. Any manifest still carrying scope: is refused at parse, so it is found rather than believed.