Skip to content

feat(cloudflare-warp): declarative Zero Trust enrollment with per-host service modes - #448

Draft
Bad3r wants to merge 60 commits into
mainfrom
feat/cloudflare-warp-managed-enrollment
Draft

feat(cloudflare-warp): declarative Zero Trust enrollment with per-host service modes#448
Bad3r wants to merge 60 commits into
mainfrom
feat/cloudflare-warp-managed-enrollment

Conversation

@Bad3r

@Bad3r Bad3r commented Aug 8, 2026

Copy link
Copy Markdown
Owner

Summary

Turns cloudflare-warp from a package-on-PATH into a declarative Cloudflare Zero Trust
enrollment, and moves enablement from the shared baseline to per-host modules.

Before this change the app module only put warp-cli on PATH, so enrolling a device meant
running warp-cli register by hand and the Zero Trust team name landed in shell history.
The shared baseline also switched every shareCommon host to warp-svc at
mkOverride 1100, even though service mode is a per-host decision.

Managed enrollment (modules/apps/cloudflare-warp.nix)

  • Drives upstream services.cloudflare-warp and renders /var/lib/cloudflare-warp/mdm.xml
    from a sops template, so organization, auth_client_id, and auth_client_secret reach
    warp-svc without entering the Nix store.
  • mdm.xml is authoritative for service_mode, so serviceMode, autoConnect, and
    switchLocked are exposed as options and warp-cli mode is never called.
  • Branch selection is builtins.pathExists (secretsRoot + "/cloudflare-warp.yaml"). Without
    the secret the host installs warp-cli alone and declares neither warp-svc nor the
    connect oneshot: an unmanaged daemon holds CAP_NET_ADMIN and an open UDP port while
    serving only consumer WARP. A tmpfiles rule clears any stale mdm.xml, which would
    otherwise pin the old service mode and cached service token.
  • templates."cloudflare-warp-mdm".restartUnits is the sole restart owner for
    cloudflare-warp.service. sops compares the rendered template between generations, so a
    rotated credential and a changed serviceMode/autoConnect/switchLocked both restart
    warp-svc. A second restartTriggers hash on the unit would restart it twice per
    activation on hosts running sops.useSystemdActivation.
  • ExecStartPre parses the rendered fragment with xmllint --noout before installing it.
    Credentials are substituted after evaluation, so no Nix-side quoting can escape them; an
    XML metacharacter in one fails the unit instead of degrading warp-svc to unmanaged mode.
    xmllint's stderr is discarded and replaced with a fixed message, because it reports a parse
    error by echoing the offending source line, which is the credential, and neither this module
    nor upstream sets StandardError.
  • after/requires use flake.lib.security.sopsInstallSecretsDeps, which resolves to []
    on activation-script hosts because sops-install-secrets.service only exists under
    sops.useSystemdActivation (issue bug: Services fail with 'Unit sops-install-secrets.service not found' #37).
  • Warnings cover three configuration interactions that otherwise become silent: a local resolver
    on 127.0.0.1:53 fighting Gateway DNS, a missing secrets/cloudflare-warp.yaml leaving the
    host with the CLI and no daemon, and strict checkReversePath dropping asymmetric return
    traffic on the CloudflareWARP interface. services.dnscrypt-proxy.enable triggers the resolver
    warning independently. Its NetworkManager dnsmasq branch requires
    networking.networkmanager.enable alongside dns = "dnsmasq", since that option is declared
    unconditionally but only reaches NetworkManager.conf under mkIf enable. switchLocked
    deliberately remains documentation-only: it is a Cloudflare policy choice that legitimately
    combines with connectOnBoot, but it makes mismatch teardown report-only. The module summary,
    option description, reference row, and cheatsheet record that tradeoff.

Connect-on-boot guard

warp-svc starts idle (auto_connect only governs reconnect after a manual disconnect), so
an enrolled device stays outside the tunnel until someone connects. A best-effort oneshot
performs that connect, gated so it can never select consumer WARP: warp-cli connect on an
un-enrolled daemon registers against Cloudflare's consumer service, a different security
posture than Zero Trust. The first warp-cli connect requires warp-cli registration organization to match the
sops-provisioned organization. A later unanswered response may reuse that same-run confirmation only while no successful empty response is
held and no later successful response has recorded a mismatch. A successful empty response opens a bounded readiness
hold, so it cannot verify the tunnel or authorize another connect request. The script
disconnects a tunnel that reached Connected without a confirmed managed registration.

registration_state is tri-state (confirmed / mismatch / unknown) because
warp-cli 2026.3.846.0 answers registration organization with exit 0 and an empty string when
the device carries no Teams registration. Collapsing the two would let one timeout 5s firing
on a busy warp-svc disconnect a healthy managed tunnel, so only mismatch disconnects while
unknown leaves the tunnel up. A successful empty answer opens a bounded readiness hold only while no conclusive mismatch is retained. A fresh managed confirmation resets its three-answer window.
A later failed query cannot cancel that hold, because it has no organization result to resolve the
state. This keeps an interleaved timeout from consuming the unverified budget before a fourth empty
answer reaches mismatch and tears down consumer WARP. A retained mismatch instead keeps a later empty answer on the existing mismatch path, so it cannot
suppress enforcement of a foreign tunnel. After a failed cleanup, it also preserves the remaining
attempt and deadline budget for a fresh successful response to retry cleanup, without treating a
failed registration query itself as disconnect evidence. Command substitution captures stdout alone and
the check strips
whitespace from both sides, so a banner on stderr cannot turn a correctly enrolled device into a
mismatch without a redirect suppressing it; stderr is therefore left to reach the journal as
warp-cli's own reason for a failed check. An
empty managed organization, read once at startup, exits after the first status report instead
of spinning until the deadline. The confirmation branch also requires that configured organization
to be nonempty, so a future control-flow refactor cannot classify two empty strings as confirmed
and authorize consumer WARP.

The terminal log line normally names the state the run ended on. A conclusive mismatch remains
the exception: a later unanswered registration check cannot prove it resolved, so the terminal
diagnostic retains only its empty-or-foreign classification until a successful managed confirmation
clears it. This retained classification cannot turn a failed check into disconnect evidence, but it prevents an
earlier confirmed_once result from accepting a later Connected tunnel or authorizing a later
connect retry, and prevents a later successful empty answer from reopening the readiness hold until
a successful managed confirmation clears the mismatch. With neither a pending empty-answer hold nor a retained mismatch, unverified counts
unanswered checks against a live tunnel and is never reset. A hold persists until a successful registration response resolves
it, so intermittent failures cannot pre-empt the fourth empty answer reaching mismatch.
unverified also cannot gate that report: one early unanswered check on a leftover tunnel would
otherwise outrank a mismatch confirmed later, logging tunnel is up ... left it connected at <4>
for a tunnel the run had just torn down, with nothing at <3> to send the operator to the
dashboard. The report branches on the live registration_state or that retained mismatch
classification, then on the last warp-cli status, so the unverified line only prints while the
tunnel really is up. A mismatch is split by its retained empty-or-foreign classification, because
a device whose enrollment never completed reports no registration rather than a foreign tenant,
and the two send an operator to different places. The empty-organization exit runs a status query
before it leaves, so that outcome is not the one path with no record of the tunnel in the journal.

The unit is BindsTo= and Upholds= cloudflare-warp.service, which cover different triggers.
An explicit restart, which is what sops issues for restartUnits (try-restart), reaches the
oneshot through BindsTo= alone. An unexpected exit does not, because BindsTo= stops the
oneshot ahead of the restart-dependency propagation for that same event, so Upholds= is what
starts it again. PartOf= adds nothing either way and stays unset, since BindsTo= wins that
race whether or not PartOf= is also present. Verified against systemd 261.1 from this flake's
pin, with try-restart (the verb sops-install-secrets issues) as well as restart.

The oneshot also waits on network-online.target. Upstream orders warp-svc on network.target,
which says nothing about an associated link, and 25.05 decoupled multi-user.target from
network-online.target, so the retry budget could otherwise burn before Wi-Fi associates and
auto_connect = 0 would leave nothing to reconnect. Per-attempt states log at <4> and only
unrecoverable or enforcement states at <3>, so journalctl -p err stays quiet on a healthy
boot rather than reporting every warm-up as broken. Every connect-script warp-cli call passes
--accept-tos, which is a global option on 2026.3.846.0, so the fail-closed disconnect cannot
be the one call that gets refused.

Bounds: timeout -k 1s 5s per IPC call, since a bare timeout sends only SIGTERM and waits, 30 attempts or a 120s deadline, TimeoutStartSec=180, and
exit 0 after logging so a user who deliberately keeps WARP off does not boot into a failed
unit. With neither a pending empty-answer hold nor a retained mismatch, a live tunnel whose
registration goes unanswered three times is terminal, since nothing is left to request. A pending
hold remains until a successful response resolves it, and a retained mismatch keeps the existing
attempt/deadline budget open until a fresh successful response resolves or retries cleanup. The final log
line normally names the state that ended the run. A conclusive mismatch remains its terminal
diagnostic across later unanswered checks until a successful managed confirmation clears it
(unverified registration, no registration at all, wrong organization, refused connect, or
unreachable daemon).
enableStrictShellChecks = true routes the script through writeShellApplication, so
ShellCheck and errexit/nounset/pipefail cover this fail-closed guard.

Per-host enablement

Host Mode Reason
system76 warp (Full / Gateway with WARP) No local resolver bound to 127.0.0.1:53, so Gateway DNS does not collide. The raw services.cloudflare-warp block in services.nix is removed because a second definition of package would conflict.
tpnix tunnelonly NetworkManager runs dns = "dnsmasq" for private-host mappings, which warp would break. WARP still supplies the tunnel, HTTP filtering, network policies, and posture checks.

tpnix gates enable on flake.lib.nixos.hosts.tpnix.sopsRuntimeReady. The flag is true since
PR #305, so the gate is a kill switch rather than what keeps the host un-enrolled: losing the
runtime decryption key drops the WARP stack with it and keeps the
sops.secrets."cloudflare-warp/*" declarations from failing activation on an un-decryptable
payload.

Coverage

CI has no secrets submodule, so host evaluation only ever takes the un-enrolled path.
checks."apps/cloudflare-warp-module-eval" evaluates the module against an in-repo
non-secret fixture to force the managed branch and against a missing path for the un-enrolled
branch, deep-forcing the sops template content, the install ExecStartPre, and the connect
script text so a regression in the registration guard cannot reach main unnoticed. An exact
source-derived assertion requires a nonempty configured organization before confirmation assigns
confirmed_once and resets the empty-response readiness window. It also asserts that package/enable/udpPort/openFirewall reach
services.cloudflare-warp (headless selection has no other test now that services.nix no longer
sets it). The enrolled fixture covers non-default UDP 24080 with openFirewall = false;
mdmVariant covers default UDP 2408 with openFirewall = true and requires the port in
allowedUDPPorts, so a hardcoded false cannot leave managed hosts closed. The check also asserts
that the template is the only restart owner, the BindsTo/Upholds wiring, the shellcheck gate,
and that the un-enrolled branch declares no daemon and no connect unit. The fixture holds
placeholder values only and is not sops-encrypted: the check needs the path to exist and the
three keys to resolve, not real credentials.

Both managed-branch warnings are conditional, so forcing only the template and script text left
their predicates unreachable by any evaluation in this repo: a rename of
services.dnscrypt-proxy.enable, networking.networkmanager.dns, or
networking.firewall.checkReversePath would have surfaced first on an operator's rebuild after
the sops payload landed. The check now forces the managed warnings list with independent
fixtures: dnscrypt-proxy and NetworkManager dnsmasq under DNS-owning serviceMode "warp" must
warn; dnscrypt-proxy under tunnelonly must stay silent; and dnscrypt-proxy under 1dot1 must
warn. The independent checkReversePath = "strict" fixture also requires its warning. A baseline
host stays silent, and a staleDns fixture holds the NetworkManager negative case: dns = "dnsmasq" with NetworkManager off must not warn.

The rendered mdm.xml is asserted against a mdmVariant fixture that diverges on serviceMode,
autoConnect, and switchLocked, since the enrolled fixture holds all three at their option
defaults and would pass against hardcoded output; the enrolled fixture pins the other arm of
switchLocked's Nix if. warp-cli on PATH is asserted for the managed branch too, the one
branch where the module does not place the package itself.

Three further assertions pin properties that the previous substring matches let through. The
registration capture is matched whole, so neither 2>&1 (which would compare an enrolled
device as unmanaged) nor 2>/dev/null (which would drop warp-cli's reason for a failed check
from the journal) can return. The xmllint guard must carry both the stderr redirect and
exit 1, since suppressing the leak without the exit would install a malformed mdm.xml
silently. The terminal report must preserve a conclusive mismatch across later failed checks
with an empty-or-foreign classification, and it must not reference unverified. The scoped assertions prove that a retained mismatch blocks stale confirmed_once acceptance and
reopening the readiness hold, preserves the attempt and deadline budget for fresh cleanup, but
cannot make a failed check enter the live mismatch) disconnect enforcement. Readiness assertions pin the bounded empty-answer producer, reset it on a fresh confirmation,
and preserve it across failed checks. They require held_empty to block stale confirmed_once
acceptance and connection retries, retain mismatch gates, and prohibit connected=1 in the held
or ordinary unverified paths. Thus an interleaved timeout cannot pre-empt the fourth empty
response reaching mismatch or accept consumer WARP. Three more
cover the round after that: the
status query on the empty-organization exit, the split mismatch report, and a connectOff fixture
for connectOnBoot = false, the one managed sub-branch no fixture reached. Each was verified by
injecting the regression it targets and confirming it is the assertion that fails.

Docs

New pages under docs/cloudflare/warp/: deployment (dashboard prerequisites, the
secrets/cloudflare-warp.yaml payload, and pushing the submodule before bumping its pointer),
modes (which mode each host runs and which collide with a local resolver), operations
(verifying registration, reading cloudflare-warp-connect logs, recovering a stale
mdm.xml), reference (option to mdm.xml key mapping), and cheatsheet. Linked from
docs/cloudflare/README.md and docs/index.md.

Test plan

  • nix flake check path:. --accept-flake-config --no-build --offline (exit 0)
  • nix build 'path:.#checks.x86_64-linux."apps/cloudflare-warp-module-eval"' (exit 0, both
    sops branches forced)
  • Built unit-script-cloudflare-warp-connect-start; ShellCheck 0.11.0 passes on the connect
    script
  • Ran that built script against a stub warp-cli across eight states: verified tunnel,
    connected but unverifiable, connected to the wrong organization, banner on stderr,
    whitespace in the organization value, connect requested and refused, unreachable daemon,
    and empty organization secret
  • Modeled a foreign registration mismatch followed by a successful empty response: the retained
    mismatch prevents a readiness hold and routes a Connected tunnel through existing mismatch
    cleanup; the first three initial and post-confirmation empty responses remain held, then a
    fourth successful empty response routes to mismatch cleanup
  • Modeled a failed cleanup followed by three failed registration probes: retained mismatch leaves
    unverified unchanged and keeps the existing attempt/deadline budget open for a fresh successful
    response to retry cleanup without using stale state as disconnect evidence
  • Modeled an unanswered probe after same-run confirmation: it permits status acceptance or a
    connection retry only while neither a successful empty-response hold nor a mismatch is retained
  • Modeled a successful empty response after same-run confirmation: it stays held without status
    acceptance or a connect retry, and the fourth success reaches mismatch cleanup
  • Temporarily removed the retained-mismatch clause from the same-run connect gate and the nonempty
    managed-organization clause from confirmation. Each mutation failed
    apps/cloudflare-warp-module-eval on its focused assertion; the exact guards were restored
  • Temporarily reintroduced the post-confirmation empty-response bypass, removed each held-empty
    guard, and removed the confirmation reset. Each mutation failed
    apps/cloudflare-warp-module-eval on its focused assertion; the exact state guards were restored
  • Ran a strict shell state model for confirmed-to-empty settling, fourth-empty mismatch, timeout
    retry, held-empty plus timeout, retained mismatch, and readiness reset.
  • Replaced the production inherit (cfg) package udpPort openFirewall; temporarily with
    openFirewall = false;: apps/cloudflare-warp-module-eval failed with
    openFirewall = true must open udpPort
  • Temporarily removed the DNS mode gate and the "1dot1" allow-list member; the target
    evaluation failed on the tunnelonly and 1dot1 assertions, respectively
  • Scanned executable WARP documentation commands: no warp-cli command omits --accept-tos
  • Rendered both units to confirm BindsTo=/Upholds= and the absence of
    X-Restart-Triggers=
  • nix eval path:.#nixosConfigurations.system76.config.programs.cloudflare-warp.extended.serviceMode
    -> warp
  • nix eval path:.#nixosConfigurations.tpnix.config.programs.cloudflare-warp.extended.serviceMode
    -> tunnelonly
  • nix eval path:.#nixosConfigurations.tpnix.config.programs.cloudflare-warp.extended.enable
    -> true
  • treefmt --fail-on-change --no-cache (0 changed)
  • Pre-push hooks: apps-catalog-sync, flake-checker, gitleaks, managed-files-drift all passed

Note: secrets/cloudflare-warp.yaml is not committed, so both hosts currently build with
warp-cli installed, no daemon, and the build warning by design. Committing the sops payload
described in docs/cloudflare/warp/deployment.md switches them to managed enrollment with no
further module change.

Bad3r added 8 commits August 8, 2026 17:05
The shared baseline switched every shareCommon host to warp-svc at mkOverride 1100. Zero Trust enrollment is a
per-host decision: system76 runs Full "Gateway with WARP" while tpnix runs tunnel-only to keep its NetworkManager
dnsmasq private-host mappings, and a host without secrets/cloudflare-warp.yaml only reaches a degraded un-enrolled
daemon. Default the app off so modules/system76/cloudflare-warp.nix and modules/tpnix/cloudflare-warp.nix own
enablement explicitly.
…tials

The module only placed warp-cli on PATH, so enrolling a device meant running `warp-cli register` by hand and the
Zero Trust team name landed in shell history. Drive upstream services.cloudflare-warp instead and render
/var/lib/cloudflare-warp/mdm.xml from a sops template, so organization, auth_client_id, and auth_client_secret reach
the daemon without entering the Nix store. mdm.xml is authoritative for service_mode, so serviceMode/autoConnect/
switchLocked are exposed as options and `warp-cli mode` is never called.

Lifecycle details that are not obvious from the diff:

- templates."cloudflare-warp-mdm".restartUnits restarts cloudflare-warp.service on credential rotation, because
  restartTriggers hashes only the non-secret fields and would otherwise leave warp-svc on the stale service token
  until reboot (same pattern as usbguard.nix and duplicati-r2.nix).
- The un-enrolled branch adds an ExecStartPre `rm -f` for mdm.xml: the file caches the service token and pins
  service_mode, so a leftover copy would keep the daemon in managed mode instead of degrading.
- after/requires use flake.lib.security.sopsInstallSecretsDeps, which resolves to [] on activation-script hosts
  because sops-install-secrets.service only exists under sops.useSystemdActivation (issue #37).

Warnings cover the three ways the setup silently misbehaves: a local resolver on 127.0.0.1:53 fighting Gateway DNS,
a missing secrets/cloudflare-warp.yaml running warp-svc un-enrolled, and strict checkReversePath dropping the
asymmetric return traffic on the CloudflareWARP interface.

Validation: nix-instantiate --parse; nix fmt; nix eval path:.#nixosConfigurations.system76.config.programs.cloudflare-warp.extended.serviceMode
…guard

warp-svc starts idle: mdm.xml auto_connect only governs reconnect after a manual disconnect, so a freshly booted
enrolled device stays outside the tunnel until someone runs `warp-cli connect`. Add a best-effort oneshot that
performs that connect, gated so it can never select consumer WARP.

The guard is the point of the unit, not the connect. `warp-cli connect` on an un-enrolled daemon registers the
device against Cloudflare's consumer service, which is a different security posture than Zero Trust. So the script:

- exits early when no managed mdm.xml is possible (secrets/cloudflare-warp.yaml absent);
- compares `warp-cli registration organization` against the sops-provisioned organization before every connect
  attempt, and refuses to connect when they do not match;
- disconnects a tunnel that reached Connected without a confirmed managed registration.

Bounds: each IPC call gets `timeout 5s`, the retry loop stops at 30 attempts or a 120s deadline, and
TimeoutStartSec=180 covers the polling plus shell overhead, because warp-svc's IPC socket and mdm.xml registration
settle at different times. The unit exits 0 after logging the outcome so a user who deliberately keeps WARP
disconnected does not boot into a failed unit.

Validation: nix-instantiate --parse; nix fmt (0 changed)
services.system76 set services.cloudflare-warp directly, which runs warp-svc but never writes mdm.xml, so the host
sat un-enrolled and any Zero Trust posture had to be established by hand. Move enablement to
programs.cloudflare-warp.extended so the host picks up managed enrollment, the connect-on-boot guard, and the
mdm.xml lifecycle.

serviceMode "warp" is Full / Gateway with WARP: system76 has no local resolver bound to 127.0.0.1:53, so Gateway
DNS filtering does not collide the way it would on tpnix. autoConnect stays 0 so a manual disconnect sticks, and
switchLocked stays false so the tunnel remains user-controllable.

The raw services.cloudflare-warp block is removed rather than left alongside: the wrapper sets the same options
plus package/udpPort/openFirewall, and a second definition of services.cloudflare-warp.package would conflict.

Validation: nix eval path:.#nixosConfigurations.system76.config.programs.cloudflare-warp.extended.serviceMode
tpnix runs NetworkManager with dns = "dnsmasq" for its private-host mappings, so serviceMode "warp" would hand DNS
to Gateway and break those mappings (the module warns about exactly this collision). Use "tunnelonly" instead:
WARP still provides the tunnel, HTTP filtering, network policies, and posture checks, and Gateway DNS is left off
deliberately.

enable is gated on flake.lib.nixos.hosts.tpnix.sopsRuntimeReady, matching the other tpnix sops consumers
(duplicati.nix, printing.nix, fonts.nix). The flag is true since PR #305, so the gate is not what keeps the host
un-enrolled; it is a kill switch. If tpnix loses its runtime decryption key, flipping the flag drops the WARP stack
with it, removes the rendered mdm.xml on the next activation, and keeps the sops.secrets."cloudflare-warp/*"
declarations and the cloudflare-warp-mdm template from failing activation on an un-decryptable payload.

Validation: nix eval path:.#nixosConfigurations.tpnix.config.programs.cloudflare-warp.extended.serviceMode
The module picks its branch on `builtins.pathExists (secretsRoot + "/cloudflare-warp.yaml")`. CI has no secrets
submodule, so host evaluation only ever exercises the un-enrolled path, and a regression in the mdm template, the
secret-backed ExecStartPre, or the connect script's registration guard would reach main unnoticed.

Add checks."apps/cloudflare-warp-module-eval", which evaluates the module twice against an in-repo non-secret
fixture (cloudflare-warp-check-fixtures/cloudflare-warp.yaml) to force the managed branch, and against a missing
path to keep the un-enrolled assertions in the same check. The managed side is deep-forced so the sops template
content, the install ExecStartPre, and the connect script text are all evaluated rather than left as thunks.

The fixture holds placeholder values only and is not sops-encrypted: the check needs the path to exist and the
three keys to resolve, not real credentials.

Validation: nix-instantiate --parse
Managed enrollment has prerequisites that live outside this repository (a Zero Trust team name, a service token
with the right scope, and a device-enrollment policy that accepts it), and getting them wrong fails at activation
or leaves the device silently un-enrolled. Document the dashboard steps, the sops payload shape, and the failure
modes.

New pages under docs/cloudflare/warp/: deployment (dashboard prerequisites and the secrets/cloudflare-warp.yaml
payload), modes (why system76 runs "warp" and tpnix runs "tunnelonly", and which modes collide with a local
resolver), operations (verifying registration, reading cloudflare-warp-connect logs, recovering a stale mdm.xml),
reference (option-to-mdm.xml key mapping), and cheatsheet (warp-cli commands). Linked from docs/cloudflare/README.md
and docs/index.md so they are reachable from the existing docs tree rather than a new surface.

Validation: nix develop -c pre-commit run --all-files --hook-stage manual (markdown hooks)
…match

warp-cli 2026.3.846.0 answers `registration organization` with exit 0 and an empty string when the device
carries no Teams registration, so a real mismatch is distinguishable from a check that never answered. The
connect oneshot collapsed both into an empty managed_registration, so one `timeout 5s` firing on a busy
warp-svc made refresh_status run `warp-cli disconnect` against a healthy managed tunnel, and the connect gate
then refused to bring it back inside the same run. registration_state now carries confirmed/mismatch/unknown
and only mismatch disconnects; unknown leaves the tunnel up and retries.

Two cost fixes in the same loop: the second per-attempt refresh_registration is gone because `warp-cli
connect` does not change the registration organization, and an empty managed_org, read once at startup, now
exits after the first status report instead of spinning ~20 no-op attempts until the 120-second deadline.

Validation: nix fmt; nix build path:.#checks.x86_64-linux."apps/cloudflare-warp-module-eval";
nix flake check path:. --accept-flake-config --no-build --offline; bash -n and shellcheck on the rendered
enrolled and un-enrolled connect scripts.
@Bad3r Bad3r added type(enhancement) Net-new capability or intentional improvement. focus(security) Concrete security risk, secret exposure, auth boundary, or vulnerability. area(sops) sops-nix secrets, encrypted files, key material, or secret rendering. host(system76) area(networking) Networking services, DNS, VPN, SSH transport, or connectivity. area(cloudflare) Cloudflare services, APIs, deployments, or R2/Workers integration. area(docs) Repository documentation is a significant affected surface. host(tpnix) Specific to the tpnix host or its runtime contract. labels Aug 8, 2026
@github-project-automation github-project-automation Bot moved this to Backlog in ❄️ NixOS Aug 8, 2026
@claude

claude Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Claude finished @Bad3r's task in 5m 7s —— View job


Review

  • Read the diff
  • Trace the cloudflare-warp-connect registration state machine
  • Cross-check module-check.nix anchors
  • Cross-check operations.md / reference.md against the code
  • Post findings

Two findings, both in the connect script's registration state machine. Posted inline.

  1. [ISSUE] modules/apps/cloudflare-warp.nix:149 — the empty-answer readiness window closes into mismatch even after this run confirmed the managed registration, so four post-confirmation empty answers route a Connected managed tunnel into warp-cli disconnect. With auto_connect = 0 nothing reconnects, and the terminal line reports daemon reports no Zero Trust registration a few lines below managed Zero Trust registration confirmed. The bound is what tears down consumer WARP in the un-confirmed case and should stay there only. Fix this →

  2. [ENHANCEMENT] modules/apps/cloudflare-warp.nix:248[ -z "$held_empty" ] in the connect gate blocks connect retries, so a single refused first connect followed by empty answers leaves an enrolled host untunneled for the entire boot (no retry timer by design). The held-empty guard is needed on the connected=1 acceptance, not on issuing connect against a daemon whose managed organization this run already read. Fix this →

Checked and not reported: the <3>/<4> priority contract (all 15 prefixed lines are anchored, no shared prefixes remain), *Connected* glob safety in both case blocks (Disconnected cannot match), nounset coverage of every variable the terminal report reads, the mismatch_kind retention paths, sops placeholder rendering and ExecStartPre ordering, the disabled/disabledNoSecret/connectOff fixture wiring, and the two warning predicates against their positive and negative fixtures.
· branch feat/cloudflare-warp-managed-enrollment

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR introduces evaluation errors due to non-existent options and contains a logic bug in the connection script that causes unintended disconnections.

  • File: modules/apps/cloudflare-warp.nix
    services.cloudflare-warp.rootDir and services.cloudflare-warp.openFirewall are accessed/set in the config block but are not standard options in the upstream NixOS cloudflare-warp module. This will cause evaluation to fail when the module is enabled.
    Fix: Hardcode rootDir to /var/lib/cloudflare-warp (the standard StateDirectory for this service) and remove openFirewall from the services.cloudflare-warp attribute set, instead handling the firewall rule via networking.firewall.allowedUDPPorts = lib.mkIf cfg.openFirewall [ cfg.udpPort ];.

  • File: modules/apps/cloudflare-warp.nix
    In the cloudflare-warp-connect script (Patch 3), if the warp-cli registration organization command fails or times out, the managed_registration variable remains empty. This causes refresh_status to evaluate the device as having a "mismatched" registration and execute warp-cli disconnect, potentially killing a healthy, correctly enrolled tunnel.
    Fix: Implement the "unknown" state logic alluded to in Patch 8 to skip disconnection unless a mismatch is explicitly confirmed.

  • Truncated Diff: Patch 8
    The implementation for Patch 8 ("disconnect only on a confirmed registration mismatch") is missing from the provided unified diff, despite being listed in the subject lines.
    Fix: Provide the complete diff including the implementation for Patch 8.

Comment thread modules/apps/cloudflare-warp.nix Outdated
Comment thread modules/apps/cloudflare-warp.nix
Comment thread modules/apps/cloudflare-warp/module-check.nix Outdated
Comment thread docs/cloudflare/warp/deployment.md
Comment thread modules/apps/cloudflare-warp.nix Outdated
Comment thread modules/apps/cloudflare-warp.nix
Comment thread docs/cloudflare/warp/deployment.md
Comment thread modules/apps/cloudflare-warp/module-check.nix Outdated
The connect oneshot could not recover from a warp-svc crash: PartOf= propagates only explicit restart jobs, so
upstream's Restart=always respawn left the Type=oneshot RemainAfterExit=true unit active and the host untunneled until
the next rebuild. BindsTo= plus Upholds= on cloudflare-warp.service now stop and re-run it.

Other defects in the same unit:

* refresh_status left `connected` empty when the tunnel was up but `registration organization` never answered, so the
  loop spent all 30 attempts and the epilogue logged "connect never succeeded (daemon unreachable ...)" for a live
  tunnel. That state is terminal after three observations, and the epilogue now names the state that ended the run
  (unverified registration, wrong organization, refused connect, unreachable daemon).
* refresh_registration captured the check with 2>&1 and compared it to the secret with string equality, so a banner on
  stderr would classify a correctly enrolled device as "mismatch" and disconnect it. It reads stdout alone, strips
  whitespace from both sides, and reports the exit status on failure.
* refresh_registration ran twice per boot with only read-only calls in between.
* enableStrictShellChecks = true routes the script through writeShellApplication, so ShellCheck 0.11.0 and
  errexit/nounset/pipefail now cover a fail-closed security guard that nixpkgs otherwise builds unchecked.

Without secrets/cloudflare-warp.yaml the wrapper still started warp-svc: root, CAP_NET_ADMIN, an open UDP port, and no
mdm.xml, which serves only consumer WARP. enable now installs warp-cli alone in that state and declares neither the
daemon nor the connect oneshot.

restartTriggers on cloudflare-warp.service duplicated the mdm template's restartUnits: sops compares the rendered
template between generations, so both already fired on a serviceMode change, and under sops.useSystemdActivation the
two mechanisms restart warp-svc twice per activation. The template is the only restart owner.

An xmllint --noout ExecStartPre parses the rendered fragment before installing it, so a credential carrying an XML
metacharacter fails the unit instead of silently degrading warp-svc to unmanaged mode.

The flake check asserts package/enable/udpPort/openFirewall forwarding (headless selection lost its only test when
modules/system76/services.nix stopped setting it directly), the restart owner, the unit wiring, the shellcheck gate,
and the un-enrolled shape. Two splitString ordering assertions lacked the `lib.length parts > 1` guard and degraded to
always-passing whole-script searches when a marker moved. The lookup uses nixosAppHelpers.getApp instead of a
hand-rolled tree walker; config.flake.lib.nixos.getApp is unusable here because the same helper read back through the
flake.lib option merge returns a wrapper whose functionArgs are empty, so the module system cannot inject pkgs.

Validation: nix fmt; nix flake check path:. --accept-flake-config --no-build --offline;
built unit-script-cloudflare-warp-connect-start (ShellCheck 0.11.0 passes); ran that script against a stub warp-cli
across eight states (verified, unverifiable, wrong org, noisy stderr, whitespace org, refused connect, dead daemon,
empty secret); rendered both units to confirm BindsTo/Upholds and the absence of X-Restart-Triggers;
nix develop path:. -c pre-commit run --files <changed>.
The enrolled fixture only exercised openFirewall = false, so a hardcoded false in the production forwarding could pass while managed hosts use the default UDP 2408 rule.

Assert the true default of mdmVariant and its allowedUDPPorts entry. Validation: nix eval path:.#checks.x86_64-linux."apps/cloudflare-warp-module-eval".name --raw; temporary production hardcode rejected with openFirewall = true must open udpPort; nix build path:.#checks.x86_64-linux."apps/cloudflare-warp-module-eval"; nix develop path:. -c pre-commit run --files modules/apps/cloudflare-warp/module-check.nix.
@Bad3r

Bad3r commented Aug 10, 2026

Copy link
Copy Markdown
Owner Author

Review resolution

Implemented:

  • 5cac1ca0 makes the empty-registration readiness hold conditional on an absent retained mismatch. A foreign team followed by an empty response stays on mismatch cleanup, while initial and post-confirmation empty responses remain held.
  • c53e57da adds the default openFirewall = true coverage arm. The existing fixture still proves the non-default false arm, and mdmVariant now requires UDP 2408 in allowedUDPPorts.
  • The operations guide and PR body now describe the retained-mismatch boundary, both firewall arms, and the source-level regression proofs.

Rejected with technical justification:

  • The earlier proposal to change switchLocked plus connectOnBoot behavior remains rejected. Cloudflare permits that policy combination, and the PR author selected documentation-only handling: a mismatch is report-only when the user has deliberately locked the switch. Changing it would override an intentional policy choice.

Decisions:

  • No new user input was required for these two findings.
  • The previously asked switchLocked policy question was answered by the PR author in the resolved thread: retain the valid policy combination and document its report-only mismatch behavior.

Consequential coverage and validation:

  • All three exact source-derived anchors now require the readiness condition, so a partial test update cannot mask a production change.
  • Replacing the readiness gate or production firewall forwarding with the reported regressions makes apps/cloudflare-warp-module-eval fail. The restored target evaluates and builds successfully.
  • nix flake check path:. --accept-flake-config --no-build --offline passed. Scoped pre-commit checks passed for both commits.

These changes keep lifecycle enforcement fail-closed on conclusive foreign enrollment while preserving the bounded readiness path for non-conclusive results. The paired firewall checks make both production forwarding arms observable before a host rebuild.

Comment thread modules/apps/cloudflare-warp.nix
Comment thread modules/apps/cloudflare-warp/module-check.nix
Comment thread docs/cloudflare/warp/operations.md Outdated
Bad3r added 2 commits August 10, 2026 21:56
Both DNS warning positives used serviceMode = "warp", so removing the mode predicate left tpnix tunnelonly behavior without a test and could emit a self-contradictory warning.

Force a local resolver under tunnelonly and 1dot1. Validation: nix eval and nix build path:.#checks.x86_64-linux."apps/cloudflare-warp-module-eval"; temporary removal of the mode gate and of the 1dot1 member each failed the target evaluation; nix flake check path:. --accept-flake-config --no-build --offline; scoped pre-commit hooks.
The operations prose still gave bare registration and status commands, although cloudflare-warp 2026.3.846.0 refuses a bare call at the ToS prompt.

Align the operator instruction with the source and verification block. Validation: executable WARP command scan found no bare command; nix develop path:. -c lychee --no-progress --include-fragments docs/cloudflare/warp/operations.md; scoped pre-commit hooks; git diff --check.
@Bad3r

Bad3r commented Aug 10, 2026

Copy link
Copy Markdown
Owner Author

Automated review round resolved

Implemented:

  • a0c02e42 adds forced resolver-warning coverage for both remaining service-mode directions: a dnscrypt resolver remains silent under tunnelonly, and warns under 1dot1.
  • 50094198 adds --accept-tos to the two missed imperative operations commands, matching the source, verification block, and cheatsheet.
  • The PR body now names the DNS mode matrix, scopes the ToS claim to connect-script calls, and records the exact negative regression checks.

Rejected with technical justification:

  • Retrying disconnect solely from retained mismatch_kind after a failed registration query. The current, previously reviewed state contract keeps a failed query as unknown: retained state can diagnose, block stale success, and block a readiness hold, but cannot drive destructive teardown. A new managed registration can exist before the next successful probe, so historical mismatch state is not sufficient teardown evidence. Fresh successful foreign or empty results still retry cleanup.

Decisions:

  • No new user decision was needed. The rejection follows the existing documented policy in 622a7db7 and 27d76524, the source comments, the module assertion, operations guide, and PR body.

Consequential reliability work:

  • Both explicit DNS-owning modes are now observable in the evaluator, while tpnix's deliberate tunnelonly resolver design is protected against a false warning.
  • All executable WARP documentation commands now use the same noninteractive ToS contract as the connect script.
  • The mismatch invariant remains source-derived: a failed probe cannot be converted into a stale disconnect by accidentally reading the retained marker in the live mismatch arm.

Validation:

  • The two deliberate DNS predicate regressions each failed apps/cloudflare-warp-module-eval; the restored target evaluates and builds.
  • nix flake check path:. --accept-flake-config --no-build --offline passed from the clean linked worktree.
  • Scoped pre-commit hooks and git diff --check passed. The operations page link scan passed, and the WARP docs scan found no executable bare warp-cli command.

Comment thread modules/apps/cloudflare-warp.nix Outdated
managed_org is read and stripped before the startup gate. That gate reports status and exits on an empty value before the retry loop reaches its only refresh_registration call, so the inner branch and registration_state writer cannot execute.

Keep the causal cannot verify registration diagnostic: it names an unreadable or whitespace-only secret while the later line names the terminal no-connect action. Validation: nix-instantiate --parse modules/apps/cloudflare-warp.nix; targeted formatter; nix eval and nix build path:.#checks.x86_64-linux."apps/cloudflare-warp-module-eval"; scoped pre-commit hooks; git diff --check.
@Bad3r

Bad3r commented Aug 10, 2026

Copy link
Copy Markdown
Owner Author

Automated review round resolved

Implemented:

  • eadb83d5 removes the unreachable empty-organization branch from refresh_registration.
  • The only call is inside the retry loop, after the immutable startup gate reads the sops value, records status, and exits for an empty value. The dead fourth writer of registration_state is gone without changing a reachable state transition.

Rejected with technical justification:

  • Removing or folding away managed organization secret unavailable; cannot verify registration. That line names the root cause, including an unreadable or whitespace-only sops value. The later not connecting line names the terminal action after the required status capture. Commit 3d0a6634 added the causal diagnostic after the journal had named only the gate; removing it would regress the operations contract, source comment, and module assertions.

Decisions:

  • No user decision was needed. The current control flow makes the inner branch unreachable, while repository history and the existing docs establish the two diagnostics as distinct operator signals.

Consequential consistency work:

  • Re-scanned the full PR body and touched documentation. The body continues to state that an empty organization exits after one status report, and the operations guide continues to describe both preserved diagnostics accurately.
  • Re-scanned the evaluator anchors. They still require the empty-secret exit before the loop, the status record before exit, whitespace stripping before the causal diagnostic, and both <3> messages.

Reliability:

  • The registration state machine now contains only reachable writers. The fail-closed startup gate still prevents an empty secret from reaching a connect request, while the retained causal diagnostic keeps secret-format failures distinguishable from a generic no-connect action.

Validation:

  • nix-instantiate --parse modules/apps/cloudflare-warp.nix
  • Targeted formatter and git diff --check
  • Evaluated and built path:.#checks.x86_64-linux."apps/cloudflare-warp-module-eval"
  • Scoped pre-commit hooks
  • Clean-worktree nix flake check path:. --accept-flake-config --no-build --offline

All open inline threads are resolved.

Comment thread modules/apps/cloudflare-warp.nix
Comment thread modules/apps/cloudflare-warp.nix Outdated
Comment thread modules/apps/cloudflare-warp.nix Outdated
Bad3r added 4 commits August 10, 2026 22:42
A retained mismatch becomes unknown after a failed registration probe, but the ordinary unverified counter ended the run after three connected observations even after cleanup failed. A fresh successful registration result was then never reached to retry the existing mismatch cleanup.

Keep the live state unknown and preserve the retained classification only as a bounded retry reason. It neither disconnects nor accepts the tunnel, while the existing 30-attempt and 120-second limits still end the run. Validation: parsed source and module check; targeted formatter; evaluated and built path:.#checks.x86_64-linux."apps/cloudflare-warp-module-eval"; negative removal of the retained branch failed the evaluator; scoped pre-commit hooks; operations link check; git diff --check.
A failed registration query after a managed confirmation could leave a refused connect without another retry. With autoConnect = 0, the daemon then remained disconnected until a manual restart despite enrollment having been confirmed in the same run.

Reuse confirmed_once only while mismatch_kind is clear. A later successful mismatch still blocks a retry, preserving the fail-closed consumer-WARP guard.

Validation: nix-instantiate --parse modules/apps/cloudflare-warp.nix; nix-instantiate --parse modules/apps/cloudflare-warp/module-check.nix; targeted nix formatter; nix eval and nix build of apps/cloudflare-warp-module-eval; negative evaluator mutation removing mismatch_kind guard; scoped pre-commit hooks.
Cloudflare moved its service-token documentation from the service-auth path to service-credentials. The former reference returned HTTP 404, leaving the managed-enrollment README without a working credential reference.

Validation: targeted nix formatter; lychee --no-progress --include-fragments docs/cloudflare/warp/README.md docs/cloudflare/warp/operations.md docs/cloudflare/warp/reference.md; curl verification of the replacement URL returned HTTP 200.
managed_org is rejected before the retry loop, but equality in refresh_registration could still classify two empty values as confirmed if a later control-flow change bypassed that exit. confirmed_once would then authorize consumer WARP.

Require a nonempty managed organization at the confirmation producer. The existing startup diagnostic and exit remain in place; this only closes the future fail-open edge.

Validation: nix-instantiate --parse modules/apps/cloudflare-warp.nix; nix-instantiate --parse modules/apps/cloudflare-warp/module-check.nix; targeted nix formatter; nix eval and nix build of apps/cloudflare-warp-module-eval; negative evaluator mutation removing the nonempty guard; scoped pre-commit hooks.
@Bad3r

Bad3r commented Aug 10, 2026

Copy link
Copy Markdown
Owner Author

Automated review round resolved

Implemented

  • 3adfc5bf adds a retained-mismatch branch after the empty-readiness hold and before ordinary unverified accounting. A failed cleanup followed by unanswered registration probes keeps the existing bounded retry window available for a fresh successful result to retry cleanup. Retained state remains diagnostic and retry control only, never stale disconnect evidence.
  • c0a800b0 lets a same-run managed confirmation retry a refused connection after an unanswered or empty probe, but only while no successful mismatch has been retained. A current confirmation remains required for the first request.
  • 89a74285 requires a nonempty configured organization in the confirmation producer itself. The existing startup status-report and exit gate remain intact; the previously removed unreachable function-local early return was not restored.

Rejected with technical justification

  • None in this review round.

Decisions

  • No user decision was needed. Each change follows the existing managed-registration policy: only a fresh successful mismatch is teardown evidence, and a confirmed managed registration can be reused only within the same run until that evidence appears.

Consequential consistency work

  • Updated the module summary and option description, README, reference, operations guide, and PR body to distinguish initial confirmation, same-run retry authorization, retained mismatch cleanup, and the nonempty confirmation invariant.
  • Repaired the README's Cloudflare service-token reference in 4e56a740: Cloudflare moved the previous service-auth URL, which returned HTTP 404, to the current service-credentials path.
  • Re-scanned the PR body, changed documentation, and source-derived assertions for stale wording after the state-transition changes.

Reliability

The state machine now preserves bounded recovery after a failed cleanup without allowing historical mismatch state to disconnect a potentially re-enrolled device. It also avoids abandoning a valid same-run retry after transient daemon IPC failure, while a successful mismatch still blocks stale acceptance and connection attempts. Local nonempty confirmation makes consumer-WARP authorization fail closed even if future control flow changes the startup gate.

Validation

  • Parsed the changed Nix sources and ran the targeted formatter.
  • Evaluated and built path:.#checks.x86_64-linux."apps/cloudflare-warp-module-eval".
  • Injected each regression in turn: disabled retained-mismatch retry handling, removed the mismatch guard from the same-run connect gate, and removed the nonempty confirmation guard. Each failed its focused evaluator assertion before exact restoration.
  • Ran the strict-shell state model for retained cleanup, same-run retry, retained mismatch blocking, and empty-organization equality.
  • Ran scoped pre-commit hooks, git diff --check, and online Lychee checks for all changed WARP Markdown pages.
  • Ran nix flake check path:. --accept-flake-config --no-build --offline successfully from the clean linked worktree.

All three newly addressed inline threads were replied to and resolved before push.

Cloudflare retired the WARP diagnostic-log page. Its previous URL redirects to a 404, so the cheatsheet now links to the verified WARP client documentation root instead of promising a removed page.

Validation: targeted nix formatter; lychee --no-progress --include-fragments across every WARP documentation page; git diff --check.
@Bad3r

Bad3r commented Aug 10, 2026

Copy link
Copy Markdown
Owner Author

Documentation completeness fix

Implemented

  • 60e316dd replaces the removed Cloudflare WARP diagnostic-log URL in docs/cloudflare/warp/cheatsheet.md with the verified WARP client documentation root. The previous URL followed a redirect to HTTP 404.

Rejected with technical justification

  • None.

Decisions

  • None. The old destination no longer exists, and the verified WARP client documentation is the accurate non-broken replacement for a broad diagnostics reference.

Consequential consistency work

  • Re-ran the online link scan across every WARP documentation page after the first scan found the stale cheatsheet link. All 18 checks now pass.
  • Re-fetched the completed automated review results before this push. It produced no new unresolved thread or review finding.

Reliability

Operator diagnostics instructions now point to a live vendor reference instead of a dead page, so the recovery path remains usable when warp-diag is needed.

Validation

  • Targeted Nix formatter and scoped pre-commit hooks passed.
  • Online Lychee scan with fragments across README, cheatsheet, deployment, modes, operations, and reference passed with zero errors.
  • git diff --check passed.

Comment thread modules/apps/cloudflare-warp.nix Outdated
Comment thread modules/apps/cloudflare-warp.nix Outdated
A successful empty registration response after a prior confirmation was treated like an unanswered query. The confirmed_once shortcut then left held_empty open past its three-answer window and could accept or retry an unverified tunnel.

Count every successful empty response against the readiness window and reset it only on a fresh managed confirmation. Reuse confirmed_once after an unanswered query only when held_empty and mismatch_kind are clear.

Validation: nix-instantiate --parse on the module and evaluator; targeted formatter; apps/cloudflare-warp-module-eval evaluation and build; focused negative mutations; strict shell state model; scoped pre-commit; Lychee; nix flake check path:. --accept-flake-config --no-build --offline.
@Bad3r

Bad3r commented Aug 10, 2026

Copy link
Copy Markdown
Owner Author

Review resolution for #448

Implemented in b3cca565:

  • Restored the three-successful-empty-response readiness bound after a same-run managed confirmation.
  • Reset that readiness counter on a fresh managed confirmation, so a resolved earlier hold does not shorten a later settling window.
  • Required held_empty to be clear before stale confirmed_once can accept Connected or authorize another warp-cli connect.
  • Kept same-run reuse for unanswered registration checks only, with neither a pending successful-empty hold nor a retained mismatch.
  • Updated the module evaluator, WARP README, operations guide, reference table, source option documentation, and PR body.

Rejected with technical justification: none.

Decisions requested: none. The distinction between an unanswered probe and a successful empty response follows the existing bounded readiness-window and fail-closed mismatch contract.

Consequential fixes: added a confirmation-time counter reset and exact negative evaluator coverage for the empty producer, both held-empty guards, and the reset. A strict shell state model covers settling, fourth-empty mismatch, timeout-only reuse, held-empty timeout preservation, retained mismatch, and reset behavior.

Reliability: successful empty responses can no longer be reported as verified or trigger reconnects from stale confirmation. Failed probes remain non-destructive evidence, retained mismatches remain fail-closed, and fresh confirmation alone resolves the readiness hold. Validation passed for Nix parse, targeted evaluator and build, formatter, focused mutations, scoped hooks, WARP documentation links, and the offline flake check.

Comment thread modules/apps/cloudflare-warp.nix
Comment thread modules/apps/cloudflare-warp.nix
@Bad3r

Bad3r commented Aug 10, 2026

Copy link
Copy Markdown
Owner Author

Review resolution

Implemented

  • b3cca5655e1820b35a41db07ab3a0172a8e0b4cb bounds successful empty registration answers after a same-run confirmation, resets that window only after a fresh confirmation, and prevents held-empty state from authorizing stale status acceptance or a reconnect.
  • No additional code change was appropriate for this automated-review pass.

Rejected with technical justification

  • Restoring the confirmed_once branch for successful empty answers would make a current nonconfirmation unbounded and recreate the stale acceptance path. The fourth successful empty answer is intentionally conclusive after three bounded settling answers.
  • Removing held_empty only from the active connect gate would invoke warp-cli connect during the first three successful empty answers. mismatch_kind is not yet set in that window, so the suggested retained-mismatch condition would not prevent stale authorization.

Decisions

  • No user decision was required. The module option text, evaluator, WARP documentation, and PR body already define the relevant fail-closed policy: unanswered queries may reuse same-run confirmation only while no successful empty answer is held and no mismatch is retained.

Consequential consistency checks

  • Re-read both full thread contexts, verified the counter behavior with the source-derived state model, and checked that the rejection rationale matches the module, exact evaluator, documentation, and PR body.
  • Both addressed inline threads were replied to and resolved. No documentation or PR-body change is needed because they already describe the retained behavior.

Reliability result

The bounded readiness window tolerates daemon settling without treating a successful empty registration result as permission to select consumer WARP. A fresh managed confirmation is the only way to reopen active connection authorization after that state, while a persistent empty result follows the existing mismatch cleanup path.

@Bad3r
Bad3r marked this pull request as draft September 8, 2026 14:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area(cloudflare) Cloudflare services, APIs, deployments, or R2/Workers integration. area(docs) Repository documentation is a significant affected surface. area(networking) Networking services, DNS, VPN, SSH transport, or connectivity. area(sops) sops-nix secrets, encrypted files, key material, or secret rendering. focus(security) Concrete security risk, secret exposure, auth boundary, or vulnerability. host(tpnix) Specific to the tpnix host or its runtime contract. type(enhancement) Net-new capability or intentional improvement.

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

1 participant