Skip to content

fix(dig-node): gate chat.send + chat.poll behind the control token - #168

Merged
MichaelTaylor3d merged 2 commits into
mainfrom
loop/1946-chat-control-token/fix
Aug 3, 2026
Merged

fix(dig-node): gate chat.send + chat.poll behind the control token#168
MichaelTaylor3d merged 2 commits into
mainfrom
loop/1946-chat-control-token/fix

Conversation

@MichaelTaylor3d

Copy link
Copy Markdown
Contributor

Closes dig_ecosystem#1946. Security follow-up F1 from the Lane B pre-merge audit (dig-node#133).

Why

chat.send and chat.poll classified as ungated WalletMethodClass::Other, routing through the ungated local plane — so ANY local process (not just the paired chat app) could invoke them:

  • chat.send makes the node seal + BLS-sign a message as its own 0x0010 identity to an arbitrary recipient — it wields the node's cryptographic identity (comparable to the token-gated control.* mutations).
  • chat.poll DRAINS (deletes) the inbound inbox — once the live receive path (#1947) lands, an unauthorized local process could steal/delete another app's queued ciphertext.

Bounded today (opaque app-supplied payload, attacker lacks the inner DIGCHAT1 key, loopback-bound, pre-release) — so it wasn't a #133 blocker, but it's the ranked-#1 non-gating follow-up and must land before the chat app is in real use.

What changed

  • Gate at the RPC dispatch, mirroring control.* / cache.fetchAndCache — a new is_gated_chat_method + chat_call_authorized (server.rs:~1108) requiring the master control token OR a valid paired controller token (control::ct_eq + pairing::is_paired_token), fail-closed on an empty master (the CSPRNG-failure sentinel, mirroring #2032). An unauthorized caller gets -32030 UNAUTHORIZED before any dispatch — no seal/send, no inbox drain.
  • HTTP POST / (rpc handler, before the passthrough-relay dispatch) — the live path: chat is dispatched only via handle_rpc from the HTTP path today.
  • WS /ws (ws_dispatch, before the wallet fallthrough) — chat is HTTP-only today (ws_dispatch doesn't reach handle_rpc), so this is bypass-proof defense-in-depth: if #793 later wires chat onto /ws, the gate is already in place.
  • Why the control token: chat wields the node identity / mutates the inbox, exactly the control.*/cache.fetchAndCache risk class — chat now mirrors that master-or-paired policy.

How verified (TDD)

  • chat_methods_over_http_require_the_control_token (integration) — for both chat.send + chat.poll: untokened → -32030 UNAUTHORIZED with no result (proves no side effect ran before the gate); wrong token → rejected; master token → clears the gate.
  • chat_gate_classifies_and_authorizes_like_a_control_mutation (unit) — only chat.send/poll gated; no/wrong token denied; master/paired allowed; empty-master fail-closed.
  • cargo fmt 0, clippy -p dig-node-service -D warnings clean, build ok, targeted tests pass. (The ~9 socket-bind sandbox failures are the known caveat; the new tests aren't among them.)

SPEC

Root SPEC.md §5.5.2 updated (the service's normative contract is the root SPEC — there is no crates/dig-node-service/SPEC.md): chat.send/chat.poll are control-token-gated on both transports, -32030 before any side effect.

Version

root [workspace.package].version 0.92.0 → 0.93.0 (dig-node-service inherits via version.workspace = true); Cargo.lock regenerated (dig-node-service=0.93.0). Minor (security behaviour change).

Notes for the gate

  • Chat is HTTP-only today; the WS gate is precautionary (defense-in-depth) — flagged for reviewer confirmation.
  • "No side effect on reject" is evidenced by reject-before-dispatch + no-result (matching the repo's cache.fetchAndCache gate-test pattern); the inbox drain() count isn't asserted directly (needs deeper node access).

Refs #133 (F1), #1947 (the live-receive path this lands ahead of), #2032 (the CSPRNG/empty-token control-plane work this mirrors).


Generated by Claude Code

claude added 2 commits August 3, 2026 11:42
Co-Authored-By: Claude <noreply@anthropic.com>
F1 (#1946): chat.send seals + BLS-signs a directed message as the node's own
0x0010 identity, and chat.poll drains the inbound inbox. Both previously classified
as WalletMethodClass::Other and routed through the ungated local plane, so any local
process could seal/send as the node or steal/delete another app's queued ciphertext.

Gate both behind the master/paired control token on BOTH transports (HTTP POST and
the /ws request plane), mirroring cache.fetchAndCache and control.* mutations. An
unauthorized caller gets -32030 UNAUTHORIZED before any seal/send or inbox drain runs.
Fail-closed on an empty master token. Updates SPEC §5.5.2 with the authz contract.

Co-Authored-By: Claude <noreply@anthropic.com>
@MichaelTaylor3d
MichaelTaylor3d merged commit 487fe3d into main Aug 3, 2026
15 checks passed
@MichaelTaylor3d
MichaelTaylor3d deleted the loop/1946-chat-control-token/fix branch August 3, 2026 12:21
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.

2 participants