Files
hq/02-DECISIONS/0052-a-filter-rule-names-its-source.md
T
jschoubben ef5dd0751b Approve 0049-0053; drop a to-be item superseded by ADR 0044
The 'domain grouping' item cited ADR 0017 as live guidance. 0044 superseded
it -- there is no domain module to group into, so there is no domain list to
settle.
2026-08-27 01:00:29 +02:00

3.6 KiB

status, date, deciders, reconstructed, extends
status date deciders reconstructed extends
accepted 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