75268 bc insight rpc enable admin false does not disable consensus admin rpc methods
Submitted on Apr 28th 2026 at 07:26:14 UTC by @silverologist for Audit Comp | Base Azul
Report ID: #75268
Report Type: Blockchain/DLT
Report severity: Insight
Target: https://github.com/base/base/tree/v0.8.0-rc.28
Impacts:
Network not being able to confirm new transactions (total network shutdown)
Description
Summary
The consensus node exposes the admin_* RPC namespace even when rpc.enable-admin is left disabled.
RpcArgs and RpcBuilder both model rpc.enable-admin as the flag that should enable the admin API. However, RpcActor::start() never checks that flag before merging AdminRpc into the live JSON-RPC module set. On Base nodes, the network_admin channel is wired unconditionally, and on sequencer nodes the sequencer admin client is also present. As a result, any reachable caller can invoke privileged admin_* methods such as admin_stopSequencer despite the operator never enabling the admin API.
Detailed Description
The CLI surface clearly models rpc.enable-admin as the switch for the consensus admin API.
In base/crates/client/cli/src/rpc.rs:30, the flag is documented as:
Enable the admin API.
That value is propagated into RpcBuilder.enable_admin in base/crates/client/cli/src/rpc.rs:72, and the consensus RPC config also documents the field the same way in base/crates/consensus/rpc/src/config.rs:12.
The problem is that the flag is never enforced when the RPC module set is built.
In base/crates/consensus/service/src/actors/rpc/actor.rs:119, RpcActor::start() merges AdminRpc whenever network_admin is present:
There is no self.config.enable_admin check anywhere in that path.
On real nodes, this condition is satisfied by default. In base/crates/consensus/service/src/service/node.rs:547, the node always starts the RPC actor with:
network_admin: Some(net_admin_rpc)
So the admin namespace is mounted whenever the RPC server is enabled, regardless of the flag.
On sequencer nodes, the impact is worse because a real sequencer admin client is also wired. AdminRpc::admin_stop_sequencer() in base/crates/consensus/rpc/src/admin.rs:107 forwards directly into the sequencer admin client, and stop_sequencer() in base/crates/consensus/service/src/actors/sequencer/admin_api_impl.rs:179 sets self.is_active = false, which stops sequencing.
The RPC server is also exposed on a public bind by default. RpcArgs.listen_addr defaults to 0.0.0.0 and listen_port defaults to 9545 in base/crates/client/cli/src/rpc.rs:24.
So the effective behavior is:
Operator leaves
rpc.enable-adminunset.Consensus RPC binds publicly on
0.0.0.0:9545.RpcActormounts theadmin_*namespace.A remote caller can invoke privileged methods that the operator did not intend to expose.
The critical manifestation is on sequencer nodes, where admin_stopSequencer can halt sequencing even though the operator never enabled the admin API, but the same issue applies to the other admin methods.
Concrete Exploit Sequence
The failure sequence is:
Deploy or run a Base sequencer node with RPC enabled and set
rpc.enable-adminto false in a setting where callers are not trusted.The node binds the consensus RPC server, by default on
0.0.0.0:9545.Because
RpcActor::start()keys only onnetwork_admin, it still mergesAdminRpcinto the live module set.An untrusted caller sends
admin_stopSequencerto the node.AdminRpc::admin_stop_sequencer()forwards to the sequencer admin client.stop_sequencer()setsis_active = falseand sequencing stops.
At that point the active sequencer can stop producing confirmations even though the operator did not enable the admin API.
Impact
A remote caller can invoke privileged consensus admin methods on a node whose operator never enabled the admin API.
On a sequencer node, the outcome is that admin_stopSequencer can halt sequencing. Under the bounty rules, this maps to:
Network not being able to confirm new transactions (total network shutdown)
Root Cause
The root cause is a mismatch between configuration intent and RPC module wiring:
RpcArgs/RpcBuilderdefineenable_adminas the admin API gate.RpcActor::start()never consults that field.Instead, admin RPC exposure is controlled only by whether
network_adminis present.Node::start()suppliesSome(net_admin_rpc)unconditionally.
So the code carries an enable_admin flag all the way into runtime config, but the live server ignores it when deciding whether to expose AdminRpc.
Recommended Fix
RpcActor::start() should gate AdminRpc on self.config.enable_admin before merging it into the RPC module set.
A minimal fix is to require both:
self.config.enable_admin == truenetwork_admin.is_some()
before the admin namespace is mounted.
Proof of Concept
Apply the following git diff to base/crates/consensus/service/src/actors/rpc/actor.rs and run as cargo test -p base-consensus-node test_admin_rpc_exposed_even_when_enable_admin_is_false -- --nocapture:
The test starts a real RpcActor with:
enable_admin = falsenetwork_admin = Some(...)a live HTTP RPC server
It then calls admin_stopSequencer over JSON-RPC and asserts that the call succeeds and that the sequencer admin stub was actually invoked.
Was this helpful?