Files
hq/02-DECISIONS/0052-a-filter-rule-names-its-source.md
T
jschoubben 4e80820e2f Design connectivity in full: overlay, resolution, exposure, filtering, certificates
Written as one document because the five are one design. They share inputs,
they must agree, and every one of them today is computed in a different place
by a different module from a different copy of the same facts.

The through-line is that none of the five can be answered by a machine alone,
so all five are decided centrally and delivered as `file` resources. That costs
no new host vocabulary and removes both remaining direct database connections
from nodes -- wireguard and traefik are the only two, and both are connectivity.

Three decisions fall out, all proposed:

0050 -- reachability is declared, not inferred from an address. The RFC1918
regex is wrong for carrier-grade NAT (100.64/10 tests as public, so an endpoint
is written to an address nothing can reach), wrong for IPv6, and wrong for a
routable address behind a closed firewall. The lab needing TEST-NET-3 to
satisfy the regex is the same bug from the other side. Also kills hub election
by address prefix, which fails silently and makes renumbering an outage.

0051 -- the enrolment token carries where the mesh is and how to recognise it.
Closes two circles with one mechanism: verifying the mesh needed the CA, and
obtaining the CA meant trusting whoever handed it over; and a node had to reach
the mesh before it could resolve any mesh name. An address plus a fingerprint,
carried out of band, resolves both -- and closes the CA question 0049 deferred.

0052 -- a filter rule names its source. `scope:` is declared in five manifests,
is part of no rule type, and is referenced by no code, so those manifests
appear to restrict ports and restrict nothing. Removed rather than implemented;
the general fix is refusing unknown keys, which the host already does and
manifests do not.

Also corrects two claims in 0049 asserting wireguard was already handled.
Research 006 says both modules still reach upward; neither is.
2026-08-27 00:36:49 +02:00

3.6 KiB

status, date, deciders, reconstructed, extends
status date deciders reconstructed extends
proposed 2026-08-27 jochen false 0043-a-declaration-is-an-ordered-list-of-owned-resources.md

52. A filter rule names its source, or it is not a rule

Context

Research 004 found this while looking for something else:

Several manifests declare scope: public on firewall rules — wireguard, traefik, gitea, mailu, qbittorrent. It is not part of the rule type and is referenced by no code in the firewall path. Real scoping is done with from:.

So five manifests appear to restrict a port and restrict nothing. Anyone reading them — including whoever wrote the next one by copying — sees an access control that does not exist.

Research 004 already names the shape: it is how-we-build's an unenforced rule is indistinguishable from a wrong one, and costs more, because people believe it. This is that rule, in the firewall, on the modules most worth restricting.

The mechanism that let it happen is worth more than the instance. scope: was accepted because unknown keys were ignored. Nothing rejected it, nothing warned, and it spread by copying for long enough to reach five manifests.

Decision

A rule names its source. from: is the only way to scope a rule, and a rule without one is open — which it must therefore say plainly rather than imply otherwise.

scope: is removed, not implemented. Giving it meaning would leave two ways to express one thing, and a manifest carrying both would need a precedence rule nobody would remember. The five manifests are corrected to from: where they meant to restrict something, and left open where they did not — and finding out which is which is part of the work, not a formality.

An unknown key is refused. This is the general fix and the reason to bother:

A manifest carrying a key the schema does not define is rejected, naming the key.

The host already works this way — its declaration parser sets DisallowUnknownFields and collects every problem into one refusal (ADR 0043). Manifests are the layer where that discipline is missing, and scope: is what missing looks like: not a wrong value, an invented one, silently accepted for months.

Consequences

  • This class of fiction stops at validation rather than at an audit. A misspelled form:, an invented scope:, a key from a different schema — each fails on the manifest that introduces it, once, instead of spreading.
  • Existing manifests will fail validation, and some of those failures will be keys somebody believed were doing something. That is the finding, not the cost — but it means the refusal cannot be switched on without reading every manifest first.
  • The firewall becomes reviewable. Today a rule's real effect is only visible by knowing which keys are fictional. Afterwards the manifest says what happens.
  • Five manifests need a decision each, and wireguard and traefik genuinely are open to the world — they must be, which the manifest should state rather than appear to deny.
  • It does not make the rules correct, only honest. A rule that says from: anywhere is legitimately open; this record ensures it says so.

References