Skip to content

feat(#530): audit out-of-band config changes, group the trail by time#711

Merged
VijitSingh97 merged 5 commits into
develop-v1.11from
feat/530-audit-out-of-band
Jul 21, 2026
Merged

feat(#530): audit out-of-band config changes, group the trail by time#711
VijitSingh97 merged 5 commits into
develop-v1.11from
feat/530-audit-out-of-band

Conversation

@VijitSingh97

Copy link
Copy Markdown
Collaborator

Closes #530

Targets the v1.11 integration branch; merges to develop after v1.10 ships.

Summary

Extends the #349 Security panel's audit trail to catch changes it never saw, and adds time-based grouping so the trail scales past a flat tail.

Ask 1 — out-of-band detection:

Both reuse the existing audit_service._clean sanitizer (same whitelist-charset, length-capped treatment as every other audit field) before anything is persisted.

Ask 2 — time grouping + persistence decision:

Added an hour/day/month dropdown to the AuditCard (default "All", so existing behavior is unchanged until an operator opts in). Grouping needed more history than control.log's own trimmed tail (the writers already cap it) can offer, and the out-of-band detections above have no log to live in at all — they're generated in the dashboard container, which only holds write access to the control spool's requests/ leg (results/, audit/, masked/ are read-only mounts, see docker-compose.yml). So:

  • New audit_events SQLite table (same pattern as worker_config/payouts/blocks/Dashboard: Worker Inspect page — read/edit each miner's XMRig config over its API, with versioned config history + per-config hashrate stats #185): permanent, no pruning — audit events are human-paced admin actions, not a hot metrics series, matching the precedent those tables already set.
  • Each poll cycle opportunistically mirrors control.log's recent entries into this table (INSERT OR IGNORE on the log's own id, idempotent) alongside the two out-of-band kinds.
  • /api/audit now merges the live log tail (so a commit that just landed shows with zero lag, no regression) with the persisted table (so grouping can look back further, and out-of-band rows — which never touch the log — show up at all), deduplicated by id.

Deferred

  • A live rig producing a real rig-edit audit row end-to-end is not its own tier-4 matrix leg — the underlying mechanism (a direct rig-side edit becoming visible in the enriched feed / masked prefill) is already proven live by the existing #516 row in testing-strategy.md; this PR's detection logic on top of it is unit-tested (tier 1) with a real StateManager.
  • rig-edit rows can't name the changed keys (see limitation above) — would need a RigForge-side contract change to expose a per-key diff, out of scope here.

ponytail-review

Ran a pass for over-engineering before opening this PR. One real finding: .card-header-row's comment claimed it was "generic — reuse wherever a card needs one control next to its heading" while it has exactly one caller today. Fixed (dropped the forward-looking framing, kept the one-line class since flexbox-header-row is the natural factoring either way). Nothing else worth cutting — the remaining complexity (secret-sentinel-as-opaque-leaf handling, the whole-second/sub-second timestamp grace window, fail-open on a DB read hiccup, id-based idempotent mirroring) is load-bearing correctness/security logic, not speculative flexibility.

Test plan

  • make lint: green (ruff, biome, yamllint, markdownlint, docs-voice, shellcheck/shfmt, taplo). lint-proto could not run — Docker daemon isn't available in this environment (unrelated to this diff; no proto files touched).
  • make test: green — dashboard pytest (all tiers), frontend node --test (230/230), pithead shell suite (1541/1541), compose hardening invariants, integration harness self-test, fake-daemon contract test.
  • Dashboard coverage: 96.73% (gate: ≥80%).
  • Patch coverage (diff-cover vs origin/develop-v1.11, this branch's actual base): 100% (gate: ≥90%).

🤖 Generated with Claude Code

VijitSingh97 and others added 3 commits July 20, 2026 21:09
Detects two kinds of config drift the #33 control-channel audit never saw:
config.json changing without a matching dashboard commit (host-edit), and a
rig applying a control-API change the dashboard never sent (rig-edit). Both
append key-names-only rows to a new durable audit_events table, which also
mirrors control.log so the Security panel's new hour/day/month grouping can
drill deeper than the log's own trimmed tail.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… CSS class

ponytail-review pass: .card-header-row has exactly one caller today (the
audit trail's grouping select) — the comment claimed forward-looking
genericity it hasn't earned yet.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ation window

Review follow-up on PR #711:

- HIGH: rig-edit re-fired every ~30s poll into a permanent, never-pruned table
  — a rig keeps reporting its last terminal change_id, and the random row id
  defeated INSERT OR IGNORE. Fix: deterministic id `rig-edit-<worker>-<change_id>`
  (bounds the table to distinct real edits, survives restart) plus an in-memory
  (worker, change_id) guard that skips the redundant per-poll DB work. A rogue
  unauthenticated rig can no longer flood the trail with one bogus change_id.
  Tested across two consecutive polls and across a simulated restart.
- MEDIUM: _read_host_config now re-applies the SECRET_PATHS mask (mirroring
  control_service.read_config's defense-in-depth pass), so a host-side masking
  regression can't leave a raw secret value resident in _last_host_config. The
  mount is already the pre-masked copy (#440) — the raw config.json is never
  mounted — so this is belt-and-suspenders, not a live leak; docstring corrected
  to state that accurately.
- LOW: pinned the ≤1s "explained by a fresh commit" correlation window with a
  boundary test and named the ceiling in a ponytail comment.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
VijitSingh97 and others added 2 commits July 21, 2026 10:22
…ping select

Security review follow-up on PR #711:
- _record_audit_event only ran actor/keys through audit_service._clean, despite
  the docstring's claim every field gets the same whitelist treatment. action/
  status/source were code-controlled closed-enum values already, so this closes
  the gap defense-in-depth (no behavior change on any real input).
- The hour/day/month <select> in AuditCard had no accessible name (no aria-label,
  no associated <label for>) — a regression against this repo's own precedent
  (xvb-tier-select, chart-controls role=group aria-label, etc). Added
  aria-label="Group audit trail by", pinned with a test assertion.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The out-of-band host-config watcher (`_watch_host_config`) suppressed a
detected config.json diff whenever ANY control-channel commit/applied entry
landed in the correlation window, regardless of which keys that commit touched
(`explained = any(...)`). A legit dashboard commit of key A concurrent with a
host-side hand-edit of key B silently swallowed B — the exact out-of-band
change this feature exists to catch.

Now each fresh commit only explains the keys it actually changed: its audit
`keys` (env-var names) are bridged to config.json paths via the new
`control_service.env_key_config_paths` (reusing EDITABLE_ENV_KEY_PATHS, plus
the synthetic DASHBOARD_ENERGY -> `dashboard.energy` prefix the commit gate
folds in). Any changed key no recent commit covers is still recorded. The 1s
time window stays as an outer bound.

Tier-1 tests: a commit of key A + host-edit of key B records B (not A), and an
energy-subkey commit is explained by prefix. Both fail on the old time-only
logic.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@VijitSingh97
VijitSingh97 merged commit c5a3b1d into develop-v1.11 Jul 21, 2026
16 checks passed
VijitSingh97 added a commit that referenced this pull request Jul 21, 2026
…d branch — union CHANGELOG + dashboard.md

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@VijitSingh97
VijitSingh97 deleted the feat/530-audit-out-of-band branch July 21, 2026 23:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant