> For the complete documentation index, see [llms.txt](https://reports.immunefi.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://reports.immunefi.com/base/75032-bc-insight-rpc-enable-admin-false-is-a-dead-flag-admin-namespace-registered-regardless-on-ever.md).

# 75032 bc insight rpc enable admin false is a dead flag admin namespace registered regardless on every base consensus binary

**Submitted on Apr 26th 2026 at 19:59:11 UTC by @gzeon for** [**Audit Comp | Base Azul**](https://immunefi.com/audit-competition/audit-comp-base-azul)

* **Report ID:** #75032
* **Report Type:** Blockchain/DLT
* **Report severity:** Insight
* **Target:** <https://github.com/base/base/tree/v0.8.0-rc.24>
* **Impacts:**
  * Temporary freezing of network transactions by delaying one block by 500% or more of the average block time of the preceding 24 hours beyond standard difficulty adjustments

## Description

## Brief

The `base-consensus` CLI flag `--rpc.enable-admin` is parsed (`crates/client/cli/src/rpc.rs:32`), stored on `RpcBuilder.enable_admin` (`crates/consensus/rpc/src/config.rs:13`), and never read by the runtime gate at `crates/consensus/service/src/actors/rpc/actor.rs:120-123`. The gate is `if let Some(network_admin) = network_admin`, where `network_admin` is unconditionally `Some(_)` for full-node mode (`crates/consensus/service/src/service/node.rs:552`). The admin namespace is registered on every `base-consensus` listener regardless of the operator's `enable_admin` selection, including `admin_stopSequencer`, which halts block production by setting `self.is_active = false` (`crates/consensus/service/src/actors/sequencer/admin_api_impl.rs:191`).

The default consensus RPC binding is `0.0.0.0:9545` (`crates/client/cli/src/rpc.rs:25`). The middleware stack is timeout + concurrency + load-shed + healthz (`actors/rpc/actor.rs:67-89`); no authentication. Any caller able to send an HTTP POST to the bound port can call any registered admin method. The defect ships in every `base-consensus` binary distributed to validators, third-party operators, and HA cluster deployments — not Coinbase-operated infrastructure.

## Vulnerability Details

The flag's lifecycle:

```rust
// crates/client/cli/src/rpc.rs:31-32 — CLI parsing
#[arg(long = "rpc.enable-admin", env = "BASE_NODE_RPC_ENABLE_ADMIN")]
pub enable_admin: bool,

// crates/client/cli/src/rpc.rs:72 — wired into RpcBuilder
enable_admin: args.enable_admin,

// crates/consensus/rpc/src/config.rs:13 — field on RpcBuilder
pub enable_admin: bool,
```

The runtime gate that decides whether to register the admin module:

```rust
// crates/consensus/service/src/actors/rpc/actor.rs:120-123
if let Some(network_admin) = network_admin {
    modules
        .merge(AdminRpc::new(self.sequencer_admin_rpc_client, network_admin).into_rpc())?;
}
// self.config.enable_admin is NEVER consulted.
```

`network_admin` is unconditionally `Some(_)` in full-node mode at `crates/consensus/service/src/service/node.rs:552`. `--rpc.enable-admin=false` has no runtime effect.

## Impact

Severity: **HIGH** (network-transaction freezing exceeding 500% of average block time).

Methods reachable unauthenticated on the bound listener (mutating only):

An unauthenticated caller who reaches the consensus RPC of a sequencer-mode node can invoke `admin_stopSequencer` even when the operator did not enable admin RPC. This sets `is_active = false`; the sequencer build loop only emits new payloads while `is_active` is true (`actor.rs` sequencer loop), so block production remains stopped until an authorized operational action restarts sequencing. On Base's 2 s block time, any stop lasting over 10 s maps directly to the 500% one-block-delay HIGH bracket. The bug also exposes `admin_overrideLeader` (HA conductor leader override), `admin_setRecoverMode`, `admin_resetDerivationPipeline`, and `admin_startSequencer` (the latter is partially gated downstream — it requires the supplied head to match the engine's current unsafe head per `admin_api_impl.rs:158-170`, so it is restart-only, not arbitrary-head injection).

The `--rpc.enable-admin` flag is the documented mechanism to separate the admin namespace from the read namespaces (`optimism_*` always, `ws_*` when `ws_enabled`) on the same listener. The HA Conductor Cluster topology distributed in this codebase requires inter-node admin RPC for leader election (`admin_overrideLeader` exists for this purpose). Operators who deploy mixed-purpose listeners — public read endpoints for downstream consumers + internal admin for HA — rely on the flag to gate the namespaces independently. The dead gate breaks that separation: the admin namespace registers on every listener regardless of operator intent.

The defect is in published binary code, code-review-discoverable, and independent of any operator's deployment. The "Base-operated infrastructure" exclusion does not apply.

## References

* Dead-flag chain: `crates/client/cli/src/rpc.rs:32, 72`; `crates/consensus/rpc/src/config.rs:13`; `crates/consensus/service/src/actors/rpc/actor.rs:120-123`; `crates/consensus/service/src/service/node.rs:552`.
* `admin_stopSequencer` flips `is_active` at `crates/consensus/service/src/actors/sequencer/admin_api_impl.rs:191`.
* HTTP middleware stack (no auth): `crates/consensus/service/src/actors/rpc/actor.rs:67-89`.
* Default `0.0.0.0` binding: `crates/client/cli/src/rpc.rs:25`.

## Bug introduction

`4d7d12db chore(cli): full cli arg port (#475)` (2026-01-14) introduced the `RpcArgs.enable_admin` flag. `c9758ee1 feat(devnet): Three-Node HA Conductor Cluster (#1542)` (2026-03-21) introduced `crates/consensus/service/src/actors/rpc/actor.rs` with the runtime gate that never reads the flag. Present in v0.7.0-rc.1 through v0.8.0-rc.24; never patched.

## Proof of Concept

The test below replicates the runtime gating logic from `RpcActor::start` (`crates/consensus/service/src/actors/rpc/actor.rs:120-123`) verbatim, with `RpcBuilder.enable_admin: false` (the operator's documented opt-out) and `network_admin: Some(_)` (full-node mode). It launches the same jsonrpsee server `RpcActor` uses, then probes every admin method. Any response other than `-32601 Method not found` proves the method is registered on the listener despite `enable_admin: false`. A control method that genuinely does not exist returns `-32601`, isolating the registration outcome to the dead gate.

Append to the existing `mod tests` block of `crates/consensus/service/src/actors/rpc/actor.rs`. Add `serde_json.workspace = true` to `crates/consensus/service/Cargo.toml` under `[dev-dependencies]`.

```rust
use alloy_primitives::B256;
use base_consensus_rpc::{AdminApiServer, AdminRpc, NetworkAdminQuery};
use jsonrpsee::{core::client::ClientT, http_client::HttpClientBuilder, rpc_params};
use tokio::sync::mpsc;

use super::*;
use crate::actors::rpc::sequencer_rpc_client::QueuedSequencerAdminAPIClient;

#[tokio::test]
async fn poc_enable_admin_false_is_a_dead_flag() {
    let config = RpcBuilder {
        socket: SocketAddr::from(([127, 0, 0, 1], 0)),
        no_restart: true,
        enable_admin: false,                              // documented opt-out
        admin_persistence: None,
        ws_enabled: false,
        dev_enabled: false,
        http_timeout: Duration::from_secs(10),
        max_concurrent_requests: NonZeroUsize::new(1024).unwrap(),
    };

    let mut modules = RpcModule::new(());

    // network_admin is unconditionally Some(_) in full-node mode (node.rs:552).
    let (admin_tx, _admin_rx) = mpsc::channel::<NetworkAdminQuery>(1);
    let network_admin: Option<mpsc::Sender<NetworkAdminQuery>> = Some(admin_tx);
    let sequencer_admin_rpc_client: Option<QueuedSequencerAdminAPIClient> = None;

    // Verbatim copy of the buggy gate (actor.rs:120-123).
    if let Some(network_admin) = network_admin {
        modules
            .merge(AdminRpc::new(sequencer_admin_rpc_client, network_admin).into_rpc())
            .unwrap();
    }

    let server = jsonrpsee::server::Server::builder()
        .build(config.socket).await.unwrap();
    let addr = server.local_addr().unwrap();
    let _handle = server.start(modules);
    let client = HttpClientBuilder::default()
        .build(format!("http://{addr}")).unwrap();

    // Probe every admin method. Any response other than -32601 proves registration.
    let admin_methods = [
        ("admin_sequencerActive",         rpc_params![]),
        ("admin_startSequencer",          rpc_params![B256::ZERO]),
        ("admin_stopSequencer",           rpc_params![]),
        ("admin_conductorEnabled",        rpc_params![]),
        ("admin_adminRecoverMode",        rpc_params![]),
        ("admin_setRecoverMode",          rpc_params![true]),
        ("admin_overrideLeader",          rpc_params![]),
        ("admin_resetDerivationPipeline", rpc_params![]),
        ("admin_postUnsafePayload",       rpc_params![serde_json::json!({})]),
    ];
    for (method, params) in admin_methods {
        let res: Result<serde_json::Value, _> = client.request(method, params).await;
        let s = match &res { Ok(_) => "Ok(_)".to_string(), Err(e) => e.to_string() };
        assert!(
            !s.contains("-32601") && !s.contains("Method not found"),
            "BUG: {method} should be REGISTERED despite enable_admin=false; got: {s}",
        );
    }

    // Control: a fabricated method must return -32601.
    let res: Result<(), _> = client
        .request("admin_thisMethodDoesNotExist", rpc_params![]).await;
    let err = res.expect_err("expected method-not-found").to_string();
    assert!(err.contains("-32601") || err.contains("Method not found"));
}
```

Verbatim cargo output (against v0.8.0-rc.24 HEAD `819ea306`):

```
$ cargo test -p base-consensus-node --lib \
    actors::rpc::actor::tests::poc_enable_admin_false_is_a_dead_flag

    Finished `test` profile [unoptimized + debuginfo] target(s) in 5.24s
     Running unittests src/lib.rs (target/debug/deps/base_consensus_node-b4f4d32717b80070)

running 1 test
test actors::rpc::actor::tests::poc_enable_admin_false_is_a_dead_flag ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 183 filtered out;
finished in 0.01s
```

All nine admin methods are registered with `enable_admin: false`; the control returns `-32601`.

## Fix

```diff
-        if let Some(network_admin) = network_admin {
+        if let Some(network_admin) = network_admin.filter(|_| self.config.enable_admin) {
             modules.merge(AdminRpc::new(self.sequencer_admin_rpc_client, network_admin).into_rpc())?;
         }
```

After this change, the PoC's admin-method probes return `-32601` and the control matches. Admin methods become reachable only when the operator explicitly sets `--rpc.enable-admin`.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://reports.immunefi.com/base/75032-bc-insight-rpc-enable-admin-false-is-a-dead-flag-admin-namespace-registered-regardless-on-ever.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
