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
58 lines
3.0 KiB
Markdown
58 lines
3.0 KiB
Markdown
---
|
|
status: fixed
|
|
opened: 2026-08-22
|
|
located-in: [mesh-control/internal/catalogue, mesh-catalog/modules/firewall]
|
|
fixed-by: 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
|
|
amended-design: 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`](../../00-META/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](../../02-DECISIONS/0050-a-machine-firewall-is-the-sum-of-what-it-listens-on.md).
|
|
|
|
- **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.
|