Skip to content

halogen: the coordinator opens the unauthenticated :8731 inference port on its wifi uplink (flake check nas-topology is red) #460

Description

@mecattaf

What is wrong

modules/halogen.nix:516 opens the Halogen API port on the LAN interface for every host that sets services.halogen.enable, with no gate:

networking.firewall.interfaces.${cfg.lanInterface}.allowedTCPPorts = [ cfg.port ];

modules/strix.nix:68-72 declares Halogen on both twins and splits only autoStart:

enable       = true;
autoStart    = config.networking.hostName == "worker";
lanInterface = if config.networking.hostName == "coordinator" then "wlp192s0" else "enp191s0";

So the coordinator — which by design serves no model — still opens :8731 on wlp192s0, the Freebox wifi uplink.

The API has no authentication. modules/halogen.nix:46-48 states the safety argument for the door:

The API (OpenAI-compatible on :8731, /health for liveness) has NO authentication. It is published on the host network and admitted on the LAN interface only; every client on that segment is a pinned house device.

That argument holds for the worker's enp191s0 (wired into the BE550, a pinned-device segment). It does not transfer to the coordinator's wlp192s0, which is the wifi uplink, not the BE550 wired segment.

This is already asserted against, and the assert is failing

flake.nix:1695, in checks.nas-topology:

# The coordinator serves no model: no inference door on its LAN leg.
assert !(builtins.elem 8731 coordinator.networking.firewall.interfaces.wlp192s0.allowedTCPPorts);

nix flake check --offline --no-build fails on it today:

error: assertion '(! ((builtins).elem 8731 (coordinator).networking.firewall.interfaces.wlp192s0.allowedTCPPorts))' failed
       at .../flake.nix:1695:11

Reproduced on the committed tree at 8955c9e9 (not a working-tree artifact). Evaluated value:

$ nix eval --json .#nixosConfigurations.coordinator.config.networking.firewall.interfaces.wlp192s0.allowedTCPPorts
[80,443,8731]

checks.nas-topology is the first failing check in the run, so it also masks every check evaluated after it.

Live state on the coordinator

The door is real, not just declared:

$ sudo iptables -S | grep 8731
-A nixos-fw -i wlp192s0 -p tcp -m tcp --dport 8731 -j nixos-fw-accept

$ ss -lntp | grep 8731
(nothing listening)

$ systemctl is-active podman-halogen-flash.service
inactive

So today it is an open door to an empty room. The exposure is latent, not live: the moment an operator runs halogen-switch flash or halogen-switch qwen38-27b on the coordinator — the documented, intended way to use Halogen there — an unauthenticated OpenAI-compatible inference endpoint is reachable from the wifi segment for as long as the model is resident.

Where it came from

The 2026-09-16 change that declared Halogen on both twins (services.halogen.enable = true on the coordinator, with autoStart = false, so halogen-switch can start it on demand). The firewall line keys off cfg.enable, so "declare the server here" silently also meant "admit the LAN".

Nothing about the 2026-09-16 intent asked for the coordinator's door: modules/strix.nix:60-61 is explicit that the worker "is the fleet's utility endpoint (http://worker:8731)" and that on the coordinator "nothing starts there at boot".

Proposed fix

Split the door from the declaration. Add an explicit services.halogen.openLanPort, defaulting to autoStart, and gate line 516 on it:

  • worker (autoStart = true) keeps its door — no change, it is the fleet endpoint;
  • coordinator (autoStart = false) loses a door it was never meant to have;
  • an operator who genuinely wants to serve the fleet from the coordinator sets one option, in the open, instead of getting it as a side effect of enable.

Coordinator-local use via halogen-switch is unaffected: the container publishes on the host network and loopback is never filtered, so http://localhost:8731 keeps working with the LAN door shut.

The existing flake.nix:1695 assert is the regression test and needs no change.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions