You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The web dashboard authenticates with one shared bearer token and has no notion of who is looking at it. That is fine for a single operator on a laptop and increasingly wrong for the way it is actually being used — a network-exposed dashboard several people open, with write paths behind it.
What exists today
DarlingWebHostService.DecideWebAuth is a pure function over three facts, and the matrix is pinned in DarlingWebAuthTests:
Network mode — a caller presents ?token= (constant-time compare), which is exchanged for an HMAC-signed HttpOnly SameSite=Strict session cookie and 302-redirected to strip the token from the URL. Out-of-CIDR is 403 even with a valid cookie and token. No credential gets a minimal inline login form.
Always on: a Host-header allowlist (anti-DNS-rebind) and SameSite=Strict.
That is a well-built gate, and none of it is what this issue proposes to remove. The problem is what the credential is.
Three concrete consequences
1. There is no identity, and the code says so.DarlingWebEndpoints.cs:634:
The updated_by stamp for a web edit. The web surface has no per-user identity, so a constant is honest — it marks the row as web-authored.
Every custom view anyone creates or edits is stamped web. The provenance column distinguishes web from MCP, which is real information, but it cannot answer "who changed this dashboard" because nothing in the request carries that.
2. The shared token gates writes, not just reads. Any authenticated seat can create, update and delete custom views, and the alert-tuning surface changes configuration the service delivers on. The comment at DarlingWebEndpoints.cs:156 is explicit that editing is deliberately available to any authenticated seat, on the reasoning that a network caller has already passed the token→cookie + CIDR gate. That reasoning is sound given one operator. With one shared secret and several people, it means no way to grant read-only access to anyone.
3. The token is, by design, readable by anyone who can log on to the host. From DarlingPrintEndpointTokenTests, in the repo's own words: it is a DPAPI LocalMachine-scope blob with an entropy constant compiled into an open-source binary, and darling.json deliberately grants INTERACTIVE read — so "an ordinary logged-on NON-administrator can already read and decrypt the blob in four lines of PowerShell." That is an accepted and correctly-documented property of the endpoint tokens. It is a much less comfortable property for the only credential standing between a LAN client and the dashboard's write paths.
And revocation is all-or-nothing: taking access away from one person means rotating the token and re-onboarding everyone, which is exactly the failure #2479 added --print-web-token to stop people hitting by accident.
What OIDC buys
Identity — updated_by becomes a real subject instead of the constant web, with no schema change (the column already exists and already carries a provenance string).
Per-user revocation — deprovision in the IdP, done. No shared-secret rotation, no re-onboarding.
Read-only seats — map IdP groups to a role, and gate the custom-view CRUD and alert-tuning writes on it. This is the first time the product could offer "let the team look, let two people change things."
MFA and conditional access for free, from whatever the org already runs.
The prerequisite that probably splits this in two
The web listener is plain HTTP.grep -c UseHttps across Darling/ returns 0; DarlingWebHostService calls options.Listen(...) with no TLS configuration at all.
That matters because most IdPs refuse a non-HTTPS redirect_uri — with the near-universal exception of http://localhost, which is exactly the loopback case and therefore the only place OIDC would work as things stand. Network mode, which is the mode that actually needs OIDC, is the mode that cannot have it yet.
So this likely wants to be two issues: an HTTPS listener (cert source, config shape, what happens on renewal, whether it is opt-in), and then OIDC on top. Worth deciding before any of it is designed, because if TLS is not on the table then this issue is loopback-only and largely pointless.
Open questions, none of which should be answered by me alone
Does the shared token stay as a fallback for headless/scripted callers and the loopback case, with OIDC as an opt-in for network mode? Leaning yes — dropping it would break anyone scripting against the web endpoints, and the tokenless-loopback path is a deliberate ergonomic choice. But two credential paths into one surface is exactly where auth bugs live, so the matrix in DarlingWebAuthTests would need to grow to cover it rather than being bolted around.
Which flow — authorization code + PKCE is the obvious answer for a browser app.
Where does client config live — darling.json's web.network block alongside the token, or the store's config_service so it is editable without touching the box?
Does the CIDR gate stay in front of OIDC? I would keep it. Defence in depth, and it is the thing that currently makes an out-of-CIDR request 403 even holding a valid credential.
MCP is unaffected — it has its own separate network block and token, and this issue does not propose changing it.
Related: #2479 (endpoint token reprint, the same shared-secret ergonomics), #1649 (loopback still requires a credential), #1562 (the current browser auth model).
The web dashboard authenticates with one shared bearer token and has no notion of who is looking at it. That is fine for a single operator on a laptop and increasingly wrong for the way it is actually being used — a network-exposed dashboard several people open, with write paths behind it.
What exists today
DarlingWebHostService.DecideWebAuthis a pure function over three facts, and the matrix is pinned inDarlingWebAuthTests:?token=(constant-time compare), which is exchanged for an HMAC-signed HttpOnlySameSite=Strictsession cookie and 302-redirected to strip the token from the URL. Out-of-CIDR is 403 even with a valid cookie and token. No credential gets a minimal inline login form.SameSite=Strict.That is a well-built gate, and none of it is what this issue proposes to remove. The problem is what the credential is.
Three concrete consequences
1. There is no identity, and the code says so.
DarlingWebEndpoints.cs:634:Every custom view anyone creates or edits is stamped
web. The provenance column distinguishes web from MCP, which is real information, but it cannot answer "who changed this dashboard" because nothing in the request carries that.2. The shared token gates writes, not just reads. Any authenticated seat can create, update and delete custom views, and the alert-tuning surface changes configuration the service delivers on. The comment at
DarlingWebEndpoints.cs:156is explicit that editing is deliberately available to any authenticated seat, on the reasoning that a network caller has already passed the token→cookie + CIDR gate. That reasoning is sound given one operator. With one shared secret and several people, it means no way to grant read-only access to anyone.3. The token is, by design, readable by anyone who can log on to the host. From
DarlingPrintEndpointTokenTests, in the repo's own words: it is a DPAPILocalMachine-scope blob with an entropy constant compiled into an open-source binary, anddarling.jsondeliberately grants INTERACTIVE read — so "an ordinary logged-on NON-administrator can already read and decrypt the blob in four lines of PowerShell." That is an accepted and correctly-documented property of the endpoint tokens. It is a much less comfortable property for the only credential standing between a LAN client and the dashboard's write paths.And revocation is all-or-nothing: taking access away from one person means rotating the token and re-onboarding everyone, which is exactly the failure #2479 added
--print-web-tokento stop people hitting by accident.What OIDC buys
updated_bybecomes a real subject instead of the constantweb, with no schema change (the column already exists and already carries a provenance string).The prerequisite that probably splits this in two
The web listener is plain HTTP.
grep -c UseHttpsacrossDarling/returns 0;DarlingWebHostServicecallsoptions.Listen(...)with no TLS configuration at all.That matters because most IdPs refuse a non-HTTPS
redirect_uri— with the near-universal exception ofhttp://localhost, which is exactly the loopback case and therefore the only place OIDC would work as things stand. Network mode, which is the mode that actually needs OIDC, is the mode that cannot have it yet.So this likely wants to be two issues: an HTTPS listener (cert source, config shape, what happens on renewal, whether it is opt-in), and then OIDC on top. Worth deciding before any of it is designed, because if TLS is not on the table then this issue is loopback-only and largely pointless.
Open questions, none of which should be answered by me alone
DarlingWebAuthTestswould need to grow to cover it rather than being bolted around.darling.json'sweb.networkblock alongside the token, or the store'sconfig_serviceso it is editable without touching the box?Related: #2479 (endpoint token reprint, the same shared-secret ergonomics), #1649 (loopback still requires a credential), #1562 (the current browser auth model).