Skip to content

feat(#719): confirm-gate operationally-disruptive config fields (keep the perimeter host-only)#727

Merged
VijitSingh97 merged 4 commits into
develop-v1.11from
feat/719-config-editability
Jul 21, 2026
Merged

feat(#719): confirm-gate operationally-disruptive config fields (keep the perimeter host-only)#727
VijitSingh97 merged 4 commits into
develop-v1.11from
feat/719-config-editability

Conversation

@VijitSingh97

Copy link
Copy Markdown
Collaborator

Closes #719.

What this does

Lets operators edit a small set of operationally-disruptive config from the dashboard behind a type-to-confirm, instead of the current hard-refuse. The security perimeter stays host-only. Ships as v1.11 (feature + control-channel security-model change).

In scope — now confirm-gated (previously host-only DEST)

These render editable-with-confirm and commit only behind a typed APPLY:

  • Data-dir movesMONERO_DATA_DIR, TARI_DATA_DIR, P2POOL_DATA_DIR, DASHBOARD_DATA_DIR (re-sync)
  • Stratum portSTRATUM_PORT (p2pool.stratum_port; rigs must repoint)
  • Clearnet initial-sync ENABLEMONERO_CLEARNET_SYNC, TARI_CLEARNET_SYNC (host IP exposed during IBD, auto-reverts to Tor)
  • Monero prune ENABLEMONERO_PRUNE off→on only

describe_change is the source of truth: it emits a new CONFIRM flag per in-scope direction. The heavier direction still emits DEST and stays refused (prune DISABLE → full re-sync; TOR_DATA_DIR move).

Perimeter — MUST stay refused (guards I did NOT change)

The typed confirmation is UX friction, not a security control (a compromised dashboard can fill the box), so the boundary is unchanged. These stay host-only, verified by tests:

The gate still DEST-refuses every perimeter row, and the default-deny closed allowlist is only widened by a second allowlist (CONTROL_DASHBOARD_CONFIRM_KEYS) that carries exactly the in-scope keys — which still must clear the perimeter and the typed-confirmation check.

How the confirmation flows

  • configview.mjs commit → POST /api/control/commit now carries confirm (the typed word) for disruptive changes → server.pycontrol_service.submit(confirm=…) → request spool.
  • pithead control_process_request allows the confirm key and passes it to control_commitcontrol_approval_gate "$staged" "$confirm". A CONFIRM row proceeds only if confirm == "APPLY".
  • Every dashboard-confirmed destructive change is audited as a distinct commit-confirmed action (vs an ordinary commit).

Tests

Stack gate (tests/stack/run.sh):

  • confirm-gated candidate previews destructive:true + carries a CONFIRM row
  • confirm-gated commit without a token is refused / refusal asks for the typed APPLY
  • confirm-gated commit with the wrong token is refused
  • confirm-gated commit with APPLY applies + confirmed commit audits as commit-confirmed with the key name
  • Perimeter-still-refused (the teeth): perimeter RPC-LAN change is refused despite the APPLY token, prune DISABLE is refused despite the APPLY token, plus tor-egress-firewall disable, monero view-key set, wallet/auth/onion/xvb/healthchecks/workers all refused
  • describe_change unit: prune enable=CONFIRM / disable=DEST; stratum port=CONFIRM; monero/dashboard data_dir=CONFIRM, TOR_DATA_DIR=DEST; clearnet enable=CONFIRM / disable=INFO

Python (test_control_service.py): _confirm_keys served + perimeter excluded + disjoint from editable; test_confirm_keys_have_no_intra_repo_drift; commit carries the typed confirmation.

Frontend: markEditable confirm flag (+ editable-wins); PreviewModal warns and gates Confirm behind the typed APPLY; form field renders confirm-gated tooltip.

Docs + CHANGELOG ([Unreleased], Added/Changed) updated in house voice.

Local: make test-stack (1560 pass), make test-dashboard (1467 pass, control_service 100%), make test-frontend (227 pass), make lint-sh/py/js/md/docs-voice clean, patch coverage 100%. (lint-proto needs docker; no proto touched.)

⚠️ MANDATORY security-reviewer pass required before merge

This changes the control-channel approval semantics (#33/#537 hardening).

… the perimeter host-only)

The dashboard config editor now commits a small set of disruptive-but-recoverable
settings behind a type-to-confirm instead of hard-refusing them, while the security
perimeter stays host-only in every direction.

In scope (now confirm-gated, previously host-only DEST):
- monero/tari/p2pool/dashboard .data_dir moves (re-sync)
- p2pool.stratum_port (rigs repoint)
- monero/tari clearnet_initial_sync ENABLE (host IP exposed during IBD)
- monero.prune ENABLE (off->on); DISABLE stays host-only DEST

describe_change is the source of truth: it emits a new CONFIRM flag per in-scope
DIRECTION. The approval gate lets CONFIRM keys past the default-deny (a second
allowlist, CONTROL_DASHBOARD_CONFIRM_KEYS), still refuses every DEST perimeter row,
and requires a typed "APPLY" (carried on the commit intent) before a CONFIRM row
proceeds. A dashboard-confirmed change is audited as a distinct `commit-confirmed`
action. Perimeter guards left intact: wallets/view keys, dashboard auth, onion.*,
network.tor_egress_firewall, dashboard.control.enabled, stratum password,
workers.list[]/dashboard.workers[], node RPC creds, the closed-schema guard.

UI: _confirm_keys surfaces the confirm-gated paths; markEditable renders them
editable-with-confirm (not greyed); PreviewModal arms the type-APPLY box for CONFIRM
rows. Type-to-confirm is UX friction, not a security control.

Tests: gate unit (in-scope -> confirm-required not refused; wrong token refused;
APPLY applies + audits commit-confirmed; perimeter incl. tor_egress_firewall/view_key
/prune-disable refused even WITH the token), frontend markEditable + PreviewModal
confirm flow, _confirm_keys served + drift guard. Docs + CHANGELOG updated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
VijitSingh97 and others added 3 commits July 21, 2026 11:05
The two #719 perimeter cases in the control-gate suite (tor-egress-firewall
disable, monero.view_key set) committed WITHOUT a confirm token via gate_try,
so they only proved token-LESS refusal — which held before #719. The mandate
was to prove the perimeter stays refused EVEN WITH a valid confirm="APPLY"
present.

gate_try now takes an optional token arg; both perimeter cases pass "APPLY".
The refusal fires at the security-sensitive/DEST gate before the confirm check
consumes the token, so they stay rejected — proving the typed confirmation is
UX friction, not a perimeter unlock. All other gate_try calls stay token-less
(unchanged). make test-stack: 1560 passed, 0 failed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ol_service

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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