After install, a new Krill server appeared in the app and accepted API calls before the user had set a cluster PIN. Even with the wrong PIN-derived bearer token, POST requests returned 200. Beacons were also broadcasting immediately on install.
Three bugs compounded:
Open-access auth mode (Plugins.kt): when pin_derived_key does not exist yet, configureAuthentication() returned UserIdPrincipal("krill-open") for any bearer token — a deliberate “open until postinst sets one” fallback that was architecturally wrong. The postinst starts the server (systemctl restart krill.service) before the install script calls krill-reset-pin, creating a window where the server was fully open.
Beacons sent without PIN (ServerBeaconSender.kt): sendSignal() sent a beacon with an empty clusterToken when no PIN was configured, advertising the server to the LAN immediately on startup.
Incoming beacons accepted without PIN (ServerBeaconWireHandler.kt): the handler accepted beacons with an empty token when this server also had no PIN — two unpinned servers would connect to each other. The PIN check was also positioned after ServerIdentity.getSelfWithInfo(), making it impossible to unit-test without environment deps.
Also: /krill-token returned an empty string when no PIN was set, which the WASM client used as its auth token.
Plugins.kt: removed the open-access fallback; null (401) is returned until a PIN is configured.ServerBeaconSender.kt: added if (!pinProvider.isConfigured()) return at the top of sendSignal().ServerBeaconWireHandler.kt: moved the PIN guard to the very first statement in handleIncomingWire(), before any identity lookup, and simplified to “reject all when unconfigured.”Routes.kt: /krill-token now returns HTTP 503 instead of an empty string when no PIN is configured.pinProvider.isConfigured(). Add this check as a seam so it is testable without touching the filesystem.