In-app reader
Arc Enterprise clustering accepts cluster join requests without authentication when cluster.enabled=true but cluster.shared_secret is not configured. The coordinator validates HMAC authentication only if a shared secret is non-empty; otherwise, a network attacker who can reach the coordinator port can send a join request with attacker-controlled node addresses and role. Accepted nodes are marked healthy, registered locally or added as Raft voters, and can be selected by the
cluster router for forwarded authenticated requests.
Cluster defaults include an empty shared secret and TLS disabled:
internal/config/config.go:943-950 defaults cluster.enabled=false, cluster.cluster_name="arc-cluster", and cluster.coordinator_addr=":9100".
internal/config/config.go:1001-1005 defaults cluster.shared_secret="" and cluster.tls_enabled=false.
Startup requires a shared secret only for file replication, not for all clustering/join/routing use:
cmd/arc/main.go:1258-1265 hard-fails without cluster.shared_secret only when cluster.replication_enabled is true.The join request contains attacker-supplied node identity, role, and addresses:
internal/cluster/protocol/messages.go:127-142 defines JoinRequest fields including node_id, role, raft_addr, api_addr, coord_addr, plus optional auth fields.The coordinator validates HMAC only when the configured shared secret is non-empty:
internal/cluster/coordinator.go:1066-1081 wraps all HMAC checks in if c.cfg.SharedSecret != "" { ... }.
If the secret is empty, the join request proceeds after only the cluster-name check.
An accepted join creates a healthy node from attacker-controlled fields and adds it to cluster trust state:
internal/cluster/coordinator.go:1101-1107 creates a node from request fields, sets attacker-provided coordinator/API addresses, and marks it healthy.
internal/cluster/coordinator.go:1108-1129 adds the attacker-provided raft_addr as a Raft voter and stores node info when Raft is configured.
internal/cluster/coordinator.go:1133-1138 registers the node locally when Raft is not configured.
The router uses healthy nodes from this registry and forwards authenticated requests to their advertised API addresses:
internal/cluster/registry.go:263-270 returns healthy writers/readers.
internal/cluster/router.go:154-177 routes writes to healthy writer nodes.
internal/cluster/router.go:203-227 routes queries to healthy readers, or writers if no readers exist.
internal/cluster/router.go:327-357 builds the forwarding target from node.APIAddress and copies all original request headers to the peer, including Authorization and x-api-key.
cmd/arc/main.go:1887-1897 wires the cluster router into MessagePack, line protocol, TLE, and query handlers when the cluster coordinator exists.
A related lower-severity issue is that heartbeat messages are also unauthenticated:
internal/cluster/protocol/messages.go:175-180 defines Heartbeat without HMAC fields.
internal/cluster/coordinator.go:1220-1232 records heartbeats and updates node state based only on supplied node_id and state.
Safe local lab reproduction only; do not target external infrastructure.
Prerequisites:
Enterprise clustering enabled in a lab deployment.
cluster.shared_secret intentionally left empty.
Network access to the coordinator TCP port, default 9100.
The attacker knows or guesses the cluster name; default is arc-cluster.
Steps:
[cluster]
enabled = true
cluster_name = "arc-cluster"
coordinator_addr = ":9100"
shared_secret = ""
tls_enabled = false
Discussion
Sign in to join the discussion.
Keep reading
Optional: create a free account to save items, track programs, and sync across web + app. Reading stays free.