Symptom
An operator rule that denies a hostname is silently overridden when an infrastructure auto-allow names the same hostname. Policy {"type":"hostname","value":"blob.core.windows.net","action":"deny"} on an Azure runner loses to autoAllowAzure's allow:
post-load auto-allow -> HasAllow=true HasDeny=false allowRule="blob.core.windows.net" denyRule=""
The CIDR path does not behave this way. ensureAllowed calls cidrDenyCoversLocked and skips loudly:
Skipping auto-allow: an explicit deny rule covers this network cidr=... autoAddedType=...
ensureHostnameAllowedLocked has no equivalent. It dedups only against an existing allow (trackedHostnames[hostname] == ActionAllow), so a denied name falls through, the allow is appended after the policy's deny, and matchHostnameRuleLocked hands the verdict to the last exact match — the auto-allow.
Not a regression
Pre-#119 the auto-allow pass ran after the config load and appended the same rule to the same slice; the replay layer reproduces that ordering exactly. Both orderings produce the identical verdict, pinned by TestAutoAllowReplay_HostnamePrecedenceUnchangedByOrdering (#120). This issue is the underlying asymmetry, not the reorder.
Scope of the override
Only an exact-name collision is questionable. A parent deny losing to a more specific auto-allow (policy denies core.windows.net, auto-allow names blob.core.windows.net) is the documented exact-beats-parent precedence and is arguably correct — the operator denied a domain, the daemon allowed one host under it that CI cannot run without. The exact-name case has no such defence: the operator named precisely the host the auto-allow names, and lost.
Proposal
Give the hostname path the CIDR path's veto: in ensureHostnameAllowedLocked, skip when the loaded ruleset already denies that exact name (and, for a port-limited auto-allow, when the deny covers those ports), logging the skip the way ensureAllowed does. trackedHostnames already carries the action, so the check is a branch on the lookup that is there.
Worth deciding at the same time:
- Whether a deny pattern that matches the name (
*.core.windows.net) should also veto. Patterns outrank parent rules but not exact matches today, so a pattern-vs-auto-allow collision has the same shape.
- What the run does afterwards. Denying
blob.core.windows.net on an Azure runner breaks Actions telemetry uploads; the operator asked for that, but the failure should be legible — the skip log line is what makes it so.
Out of scope
- The parent/pattern precedence rules themselves (
matchHostnameRuleLocked), which are load-bearing elsewhere.
- Auto-allow types the run cannot function without (loopback, the SaaS API hostname the policy fetch itself needs) — a veto there would let a policy lock the daemon out of its own control plane. If the veto lands, those may need to be exempt, which is its own decision.
Symptom
An operator rule that denies a hostname is silently overridden when an infrastructure auto-allow names the same hostname. Policy
{"type":"hostname","value":"blob.core.windows.net","action":"deny"}on an Azure runner loses toautoAllowAzure's allow:The CIDR path does not behave this way.
ensureAllowedcallscidrDenyCoversLockedand skips loudly:ensureHostnameAllowedLockedhas no equivalent. It dedups only against an existing allow (trackedHostnames[hostname] == ActionAllow), so a denied name falls through, the allow is appended after the policy's deny, andmatchHostnameRuleLockedhands the verdict to the last exact match — the auto-allow.Not a regression
Pre-#119 the auto-allow pass ran after the config load and appended the same rule to the same slice; the replay layer reproduces that ordering exactly. Both orderings produce the identical verdict, pinned by
TestAutoAllowReplay_HostnamePrecedenceUnchangedByOrdering(#120). This issue is the underlying asymmetry, not the reorder.Scope of the override
Only an exact-name collision is questionable. A parent deny losing to a more specific auto-allow (policy denies
core.windows.net, auto-allow namesblob.core.windows.net) is the documented exact-beats-parent precedence and is arguably correct — the operator denied a domain, the daemon allowed one host under it that CI cannot run without. The exact-name case has no such defence: the operator named precisely the host the auto-allow names, and lost.Proposal
Give the hostname path the CIDR path's veto: in
ensureHostnameAllowedLocked, skip when the loaded ruleset already denies that exact name (and, for a port-limited auto-allow, when the deny covers those ports), logging the skip the wayensureAlloweddoes.trackedHostnamesalready carries the action, so the check is a branch on the lookup that is there.Worth deciding at the same time:
*.core.windows.net) should also veto. Patterns outrank parent rules but not exact matches today, so a pattern-vs-auto-allow collision has the same shape.blob.core.windows.neton an Azure runner breaks Actions telemetry uploads; the operator asked for that, but the failure should be legible — the skip log line is what makes it so.Out of scope
matchHostnameRuleLocked), which are load-bearing elsewhere.