security/netbird: fix seven status page display defects - #5663
Open
BxnnyG wants to merge 8 commits into
Open
Conversation
The "ICE candidate endpoints (Local/Remote)" line was built from iceCandidateType, the same field already printed on the line above it, so the endpoint addresses were never shown. The daemon reports them in a separate field (iceCandidateEndpoint, with local/remote sub-fields). The endpoints are what distinguishes a working direct connection from a relayed one, so this was the least useful place for a duplicate. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
getElapsedTime() rejected any date in month 0 to catch the daemon's zero time (0001-01-01T00:00:00Z). January is month 0, so for one month a year "Last connection update" and "Last WireGuard handshake" read "-" for every peer, on every status page. Compare against the epoch instead: the zero time is negative, and the missing-value fallback new Date(... || 0) is exactly 0, so both stay "unknown" while real timestamps survive. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The connection status and peer detail blocks were assembled into an HTML string and passed to .html(). The content includes peer FQDNs, relay URIs and error strings that originate from the management server, so a peer name containing markup ends up parsed in the browser of an authenticated administrator. Build the table as elements and set the payload with .text() instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The interface type was read from status.kernelInterface, which the daemon does not emit. The field is called usesKernelInterface, so the expression always fell through to the netbirdIp branch and every installation was reported as "Userspace" regardless of the interface actually in use. Verified against a status --json capture from a running installation: with usesKernelInterface set to true the page still printed "Userspace". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Peers that have never been contacted are reported by the daemon with empty strings for the ICE candidates and the relay address, and with a latency of 0. The page printed those verbatim, so an idle peer showed "ICE candidate (Local/Remote): /", an empty relay address and, worst of all, "Latency: 0.00 ms" - a peer with no connection appeared to have the best round trip time in the list. Treat an empty string as unknown like null, and only format a latency that was actually measured. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
With the daemon stopped, netbird status --json writes gRPC dial errors to stderr and no JSON at all, so the API controller falls back to an empty result. The page rendered that empty result as a full status table with "undefined" in the daemon version, CLI version, FQDN and forwarding rules rows, and "Disconnected" everywhere else - the one explanation the administrator needed was the only thing missing. An empty result now produces a single line naming the likely cause. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
/api/core/firmware/info returns every known package, remote ones included, and distinguishes them with an "installed" flag. The version table filtered on the name alone, so it listed os-netbird-devel next to os-netbird on a system where only the latter is installed - two plugin versions that cannot even coexist, since each declares the other as a conflict. Filter on the flag, and build the table as elements while here, for the same reason the two blocks above it were changed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
BxnnyG
force-pushed
the
netbird/pr-status-display-fixes
branch
from
August 23, 2026 17:51
dfefdcb to
ccc29aa
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Important notices
If AI was used, please disclose:
Co-Authored-Bytrailers.I did not open an issue first and should have — I am happy to do that now, or to split anything here that is too large to review as a single change. Apologies for submitting without this template filled in; that was my mistake, not a deliberate omission.
The status page renders
netbird status --jsoninto two text blocks, with apackage version table below them. Reading that code against real daemon output
from a running 0.74.4 installation turned up seven places where the page states
something that is not true. All are small and independent, so each is its own
commit.
1. ICE candidate endpoints showed the candidate types. Both the "ICE candidate"
and the "ICE candidate endpoints" line were built from
iceCandidateType; thedaemon reports the endpoints separately in
iceCandidateEndpoint. The endpointaddresses are what you look at to tell a direct connection from a relayed one, so
this removed the page's only useful P2P debugging output.
2. Every timestamp read
-throughout January.getElapsedTime()rejected anydate with
getMonth() === 0to catch the daemon's zero time (0001-01-01), butJanuary is month 0. Comparing against the epoch catches the zero time (negative)
and the
|| 0fallback (exactly 0) without discarding a real month.3. The interface type was always reported as "Userspace". The expression read
status.kernelInterface; the daemon emitsusesKernelInterface, so it alwaysfell through to the
netbirdIpbranch. Confirmed by feeding a capture withusesKernelInterface: truethrough the renderer - it still printed "Userspace".4. Idle peers were shown with a latency of 0.00 ms. For peers it has not
dialled, the daemon reports
latency: 0and empty strings for the ICE candidatesand the relay address.
getOrDefaultused??, which does not catch an emptystring. With lazy connections enabled most peers are idle, so the list showed
several peers with
ICE candidate (Local/Remote): /, an empty relay address, andthe best round trip time in the table.
5. A stopped daemon rendered as a status table full of
undefined. With theservice stopped the CLI writes gRPC dial errors and no JSON, so the API controller
returns an empty result.
getPeerConnectionStatusonly guarded against a falsyvalue and an empty array is truthy, so the page showed
Daemon version: undefined,CLI version: undefined,FQDN: undefinedandForwarding rules: undefinedinstead of saying that the daemon is not running.
6. Peer names and daemon error strings were injected as markup. The assembled
text went through
.html(), and it contains peer FQDNs, relay URIs and errormessages originating from the management server. The table is now built as
elements with the payload set through
.text().7. The version table listed packages that are not installed.
/api/core/firmware/inforeturns every known package, remote ones included, anddistinguishes them with an
installedflag. The table filtered on the name only,so it showed
os-netbird-develnext toos-netbirdon a system wherepkg infolists only the latter - two plugin versions that cannot coexist at all, since each
declares the other as a conflict.
Tested on OPNsense with netbird 0.74.4 (userspace bind, lazy connections enabled,
four peers): status page with the tunnel up, after
netbird down, and withservice netbird stop. The renderers were additionally exercised against recordednetbird status --jsonoutput for each of those states. The version table changewas checked against
pkg info -x netbirdon the same installation, which listsnetbirdandos-netbirdonly.