From f1b5d2d26710890519e25f6144daf11a395f0541 Mon Sep 17 00:00:00 2001 From: archipelago Date: Sat, 22 Aug 2026 03:10:53 -0400 Subject: [PATCH] fix(cuprate): stop publishing the unauthenticated unrestricted RPC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The manifest bound cuprated's unrestricted RPC (full node control) to 0.0.0.0 inside the container with i_know_what_im_doing_allow_public_unrestricted_rpc = true, relying on ports[].bind: 127.0.0.1 to keep it private. That only restricts the HOST side. Verified live on archi-dev-box 2026-08-22: a peer container got a valid unauthenticated get_info off container port 18081 — and still did after cuprate was moved to its own network, because podman bridges route to each other unless created with --opt isolate=true, which the orchestrator's auto-create does not pass. Every app on the node could therefore drive full node control with no credential. The PR justified this as the pattern bitcoin-knots already uses, but knots writes rpcuser/rpcpassword from generated secrets, so a 0.0.0.0 bind there still is not control without credentials. cuprated has no RPC authentication at all, so the two are not equivalent. Unrestricted RPC is now left at cuprated's own default — container loopback only, published nowhere, reachable by nothing — which is what upstream intends by refusing a non-local bind without an explicit override. Restricted RPC (the safe-for-public subset wallets use) and p2p are unchanged, and health_check moves to 18089 since 18184 is gone. Re-verified after the change: peer container gets connection refused on 18081 (exit 7), restricted RPC and the health endpoint still answer, the node still syncs, validator APPROVED, 76/76 container tests pass including the unauthenticated-port canary (still 28 — an auth: local port was removed, not an auth: none one). Co-Authored-By: Claude Opus 5 (1M context) --- apps/cuprate/manifest.yml | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/apps/cuprate/manifest.yml b/apps/cuprate/manifest.yml index d7727d4f..e087cca6 100644 --- a/apps/cuprate/manifest.yml +++ b/apps/cuprate/manifest.yml @@ -67,17 +67,18 @@ app: auth: none auth_rationale: >- Monero p2p gossip. Peers are anonymous by design and speak the Monero wire protocol, not HTTP. - # Unrestricted RPC: full node control (mining, wallet-adjacent calls). - # cuprated enables this by default bound to 127.0.0.1 and refuses to - # start if pointed at a non-local address without an explicit opt-in - # flag — this mirrors that intent at the gate too. Host port differs - # from Monero's canonical 18081 for the same reason as P2P above - # (nostr-rs-relay already holds 18081 on this fleet). - - host: 18184 - container: 18081 - protocol: tcp - bind: 127.0.0.1 - auth: local + # Unrestricted RPC (full node control) is deliberately NOT published. + # cuprated has no RPC authentication, and for a published port to reach + # it the service would have to bind 0.0.0.0 inside the container — at + # which point every other app can reach it directly on 18081, since + # ports[].bind only restricts the HOST side and podman bridges route to + # each other (verified live 2026-08-22: a peer container on archy-net + # got an unauthenticated get_info, from a *different* network). That is + # unlike bitcoin-knots, whose 0.0.0.0 RPC still demands the rpcuser / + # rpcpassword it writes from generated secrets. So unrestricted RPC is + # left at cuprated's own default — container loopback only, reachable by + # nothing — which is also what upstream intends by refusing a non-local + # bind without an explicit i_know_what_im_doing override. # Restricted RPC: Monero's own purpose-built safe-for-public subset — # what wallets use when connecting to a "remote node". Disabled by # cuprated's own default; enabled via files[] below. A dashboard login @@ -128,17 +129,14 @@ app: network = "Mainnet" target_max_memory = 3000000000 - [rpc.unrestricted] - address = "0.0.0.0" - i_know_what_im_doing_allow_public_unrestricted_rpc = true - [rpc.restricted] enable = true overwrite: false health_check: type: tcp - endpoint: localhost:18184 + # Restricted RPC — the only RPC surface published now. + endpoint: localhost:18089 interval: 30s timeout: 5s retries: 3