From 2babbc00f14eb93306a651744fab3c7650de0c65 Mon Sep 17 00:00:00 2001 From: Vijit Singh Date: Mon, 13 Jul 2026 21:56:42 -0500 Subject: [PATCH] fix(dashboard): enriched read probe drops the masked-token bearer (regression from 1.5.1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1.5.1's #508 fix made load_worker_endpoints KEEP a descriptor whose token is the masked {"__secret__": true} sentinel (so the worker is editable). But the read-probe auth (_auth_header) then treated that dict as a bearer — `Bearer {'__secret__': True}` — so every enriched-feed probe to :8081 returned HTTP 401 and per-rig stats vanished. Use the per-worker token as a bearer only when it's a real string; the masked sentinel falls through to the fleet auth mode (e.g. name), which is what worked before #508. The host-side runner still resolves the real token for the control path. Adds a probe test for the sentinel-token case. Bumps VERSION 1.5.1 -> 1.5.2. Co-Authored-By: Claude Opus 4.8 --- CHANGELOG.md | 11 +++++++++++ VERSION | 2 +- .../mining_dashboard/client/xmrig_client.py | 6 +++++- build/dashboard/pyproject.toml | 2 +- build/dashboard/tests/client/test_xmrig_client.py | 14 ++++++++++++++ build/dashboard/uv.lock | 2 +- 6 files changed, 33 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 749ddc7e..e84aaf87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,17 @@ per the process in [`docs/releasing.md`](docs/releasing.md). ## [Unreleased] +## [1.5.2] - 2026-07-14 + +### Fixed + +- **Enriched per-rig stats work again when `dashboard.workers[]` is populated (regression from 1.5.1).** + 1.5.1's #508 fix kept a worker descriptor whose token is the masked `{"__secret__": true}` sentinel; + the read-probe auth then stringified that dict into the `Authorization: Bearer` header, so every + enriched-feed probe returned HTTP 401 and per-rig stats disappeared. The probe now uses a per-worker + token only when it's a real string, falling through to the fleet auth mode (e.g. `name`) for the + masked sentinel — the host-side runner still uses the real token for the control path. + ## [1.5.1] - 2026-07-14 ### Fixed diff --git a/VERSION b/VERSION index 26ca5946..4cda8f19 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.5.1 +1.5.2 diff --git a/build/dashboard/mining_dashboard/client/xmrig_client.py b/build/dashboard/mining_dashboard/client/xmrig_client.py index 93825a1e..2916554f 100644 --- a/build/dashboard/mining_dashboard/client/xmrig_client.py +++ b/build/dashboard/mining_dashboard/client/xmrig_client.py @@ -152,7 +152,11 @@ def _auth_header(self, name_token, override_token=""): A per-worker token (#172) implies token-auth for that worker only, whatever the fleet-wide mode says. """ - if override_token: + # Only a real STRING token overrides the fleet auth. The container reads the MASKED config + # (#440), where a per-worker token is the {"__secret__": true} sentinel — it means "a token + # exists but the container doesn't hold it", so fall through to the fleet auth mode (e.g. name) + # for the read probe. The host-side runner still uses the real token for control (#508/#440). + if isinstance(override_token, str) and override_token: return {"Authorization": f"Bearer {override_token}"} mode = XMRIG_API_AUTH if mode == "name": diff --git a/build/dashboard/pyproject.toml b/build/dashboard/pyproject.toml index 74a7aec2..1b454053 100644 --- a/build/dashboard/pyproject.toml +++ b/build/dashboard/pyproject.toml @@ -7,7 +7,7 @@ name = "mining-dashboard" # Keep in lockstep with the top-level VERSION file — the single source of truth for the stack version # (#44). A shell test (tests/stack/run.sh) fails if these drift; the dashboard *displays* the version # from VERSION (baked in as PITHEAD_VERSION, #58), so this is packaging metadata only. -version = "1.5.1" +version = "1.5.2" description = "Monitoring dashboard and XvB switching engine for Pithead" readme = "README.md" requires-python = ">=3.11" diff --git a/build/dashboard/tests/client/test_xmrig_client.py b/build/dashboard/tests/client/test_xmrig_client.py index 449a0b9f..319e9928 100644 --- a/build/dashboard/tests/client/test_xmrig_client.py +++ b/build/dashboard/tests/client/test_xmrig_client.py @@ -315,6 +315,20 @@ async def test_override_token_beats_fleet_name_auth(monkeypatch): assert session.calls[0][1]["Authorization"] == "Bearer per-rig-secret" +async def test_masked_sentinel_token_falls_through_to_fleet_name_auth(monkeypatch): + # The container reads the MASKED config (#440): a per-worker token is the {"__secret__": true} + # sentinel, which must NOT be sent as a bearer (stringifying the dict 401s). It means "a token + # exists but the container doesn't hold it", so the read probe falls through to the fleet auth + # mode; the host-side runner uses the real token for control (#508). + monkeypatch.setattr(xc, "XMRIG_API_AUTH", "name") + _with_overrides( + monkeypatch, [{"name": "rig1", "host": "10.0.0.1", "token": {"__secret__": True}}] + ) + session = FakeSession(response=FakeResponse(200, {"ok": True})) + await XMRigWorkerClient(session).get_stats("10.0.0.1", "rig1") + assert session.calls[0][1]["Authorization"] == "Bearer rig1" + + async def test_match_is_by_stratum_name_before_plus_suffix(monkeypatch): _with_overrides(monkeypatch, [{"name": "rig1", "port": 18088}]) session = FakeSession(response=FakeResponse(200, {"ok": True})) diff --git a/build/dashboard/uv.lock b/build/dashboard/uv.lock index be352f19..9d7e79c9 100644 --- a/build/dashboard/uv.lock +++ b/build/dashboard/uv.lock @@ -772,7 +772,7 @@ wheels = [ [[package]] name = "mining-dashboard" -version = "1.5.1" +version = "1.5.2" source = { editable = "." } dependencies = [ { name = "aiofiles" },