{"id":"CVE-2026-55678","aliases":[],"url":"https://o3.security/vulnerability/CVE-2026-55678","summary":"arc has unauthenticated cluster node admission when `cluster.shared_secret` is unset","details":"## Summary\n\nArc 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 \ncluster router for forwarded authenticated requests.\n\n## Details\n\nCluster defaults include an empty shared secret and TLS disabled:\n\n- `internal/config/config.go:943-950` defaults `cluster.enabled=false`, `cluster.cluster_name=\"arc-cluster\"`, and `cluster.coordinator_addr=\":9100\"`.\n- `internal/config/config.go:1001-1005` defaults `cluster.shared_secret=\"\"` and `cluster.tls_enabled=false`.\n\nStartup requires a shared secret only for file replication, not for all clustering/join/routing use:\n\n- `cmd/arc/main.go:1258-1265` hard-fails without `cluster.shared_secret` only when `cluster.replication_enabled` is true.\n\nThe join request contains attacker-supplied node identity, role, and addresses:\n\n- `internal/cluster/protocol/messages.go:127-142` defines `JoinRequest` fields including `node_id`, `role`, `raft_addr`, `api_addr`, `coord_addr`, plus optional auth fields.\n\nThe coordinator validates HMAC only when the configured shared secret is non-empty:\n\n- `internal/cluster/coordinator.go:1066-1081` wraps all HMAC checks in `if c.cfg.SharedSecret != \"\" { ... }`.\n- If the secret is empty, the join request proceeds after only the cluster-name check.\n\nAn accepted join creates a healthy node from attacker-controlled fields and adds it to cluster trust state:\n\n- `internal/cluster/coordinator.go:1101-1107` creates a node from request fields, sets attacker-provided coordinator/API addresses, and marks it healthy.\n- `internal/cluster/coordinator.go:1108-1129` adds the attacker-provided `raft_addr` as a Raft voter and stores node info when Raft is configured.\n- `internal/cluster/coordinator.go:1133-1138` registers the node locally when Raft is not configured.\n\nThe router uses healthy nodes from this registry and forwards authenticated requests to their advertised API addresses:\n\n- `internal/cluster/registry.go:263-270` returns healthy writers/readers.\n- `internal/cluster/router.go:154-177` routes writes to healthy writer nodes.\n- `internal/cluster/router.go:203-227` routes queries to healthy readers, or writers if no readers exist.\n- `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`.\n- `cmd/arc/main.go:1887-1897` wires the cluster router into MessagePack, line protocol, TLE, and query handlers when the cluster coordinator exists.\n\nA related lower-severity issue is that heartbeat messages are also unauthenticated:\n\n- `internal/cluster/protocol/messages.go:175-180` defines `Heartbeat` without HMAC fields.\n- `internal/cluster/coordinator.go:1220-1232` records heartbeats and updates node state based only on supplied `node_id` and `state`.\n\n## Proof of concept\n\nSafe local lab reproduction only; do not target external infrastructure.\n\nPrerequisites:\n\n- Enterprise clustering enabled in a lab deployment.\n- `cluster.shared_secret` intentionally left empty.\n- Network access to the coordinator TCP port, default `9100`.\n- The attacker knows or guesses the cluster name; default is `arc-cluster`.\n\nSteps:\n\n1. Start an Arc cluster node with:\n\n```toml\n[cluster]\nenabled = true\ncluster_name = \"arc-cluster\"\ncoordinator_addr = \":9100\"\nshared_secret = \"\"\ntls_enabled = false\n```\n\n2. Start an attacker-controlled HTTP listener that records request method, path, and headers, for example on `127.0.0.1:18080`.\n\n3. Send a framed cluster join request to the victim coordinator. The protocol uses a 4-byte big-endian length prefix, followed by a 1-byte message type (`MsgJoinRequest == 1`), followed by JSON. The payload should include attacker-controlled node fields and omit `auth_nonce`, `auth_timestamp`, and `auth_hmac`:\n\n```json\n{\n  \"node_id\": \"evil-reader-1\",\n  \"node_name\": \"evil-reader\",\n  \"role\": \"reader\",\n  \"cluster_name\": \"arc-cluster\",\n  \"raft_addr\": \"127.0.0.1:19020\",\n  \"api_addr\": \"127.0.0.1:18080\",\n  \"coord_addr\": \"127.0.0.1:19010\",\n  \"version\": \"lab\",\n  \"core_count\": 1\n}\n```\n\nExpected vulnerable result: the coordinator accepts the join instead of rejecting it for missing authentication.\n\n4. Trigger a forwarded operation from a node that cannot handle the operation locally. Examples depend on cluster roles:\n\n- Join as `reader` and trigger a query through a node that routes queries to readers.\n- Join as `writer` and trigger ingestion through a non-writer node that routes writes to writers.\n\nExpected vulnerable result: the attacker-controlled HTTP listener receives forwarded requests. Because `forwardRequest` copies all original headers, the listener can observe authentication headers such as bearer tokens or API keys along with request paths and bodies.\n\n5. In a Raft-enabled lab, observe that the attacker-provided `raft_addr` is submitted to `AddVoter`, demonstrating unauthorized membership mutation.\n\n## Impact\n\nIn affected cluster deployments, an unauthenticated network attacker can become a trusted cluster node. Practical impacts include:\n\n- Interception of forwarded authenticated HTTP requests, including `Authorization` and `x-api-key` headers.\n- Exposure of query bodies, ingestion data, database/measurement names, and operational metadata.\n- Unauthorized cluster membership mutation, including attempted Raft voter addition when Raft is configured.\n- Potential data integrity impact if the rogue node returns forged query/write responses or accepts/diverts writes.\n- Potential availability impact by blackholing or delaying forwarded operations.\n\nThis is not reachable in the default standalone configuration because `cluster.enabled=false`, but it is a critical trust-boundary issue for Enterprise cluster deployments where clustering is enabled without a shared secret. The code already treats `cluster.shared_secret` as mandatory for replication, which suggests unauthenticated cluster membership should also fail closed.\n\n\n## Suggested fix\n\n- Fail startup when `cluster.enabled=true` and `cluster.shared_secret` is empty, not only when `cluster.replication_enabled=true`.\n- Reject all trust-mutating coordinator messages when no cluster authentication is configured, including join, heartbeat/state update, forward apply, and file replication messages.\n- Require HMAC or mutual TLS before processing any join/heartbeat message.\n- Bind authentication to node identity and advertised addresses to reduce replay and address-substitution risks.\n- Do not forward end-user `Authorization`/`x-api-key` headers to a peer unless the peer identity has been authenticated and authorized.\n- Add tests proving unauthenticated join and heartbeat requests fail when clustering is enabled.\n\n## References / evidence\n\n- `internal/config/config.go:943-950`\n- `internal/config/config.go:1001-1005`\n- `cmd/arc/main.go:1258-1265`\n- `internal/cluster/protocol/messages.go:127-142`\n- `internal/cluster/coordinator.go:1066-1081`\n- `internal/cluster/coordinator.go:1101-1138`\n- `internal/cluster/router.go:154-177`\n- `internal/cluster/router.go:203-227`\n- `internal/cluster/router.go:327-357`\n- `cmd/arc/main.go:1887-1897`","published":"2026-08-28T20:32:57Z","modified":"2026-08-28T20:45:07.174641043Z","cvss":null,"epss":null,"cisaKev":null,"exploitsKnown":null,"affectedPackages":[{"ecosystem":"Go","name":"github.com/basekick-labs/arc","fixedVersion":"0.0.0-20260615160325-38402ad2ebdd"}],"fix":{"url":"https://github.com/Basekick-Labs/arc/pull/505","label":"Basekick-Labs/arc#505"},"references":[{"type":"WEB","url":"https://github.com/Basekick-Labs/arc/security/advisories/GHSA-p378-jp5r-gpgw"},{"type":"WEB","url":"https://github.com/Basekick-Labs/arc/pull/505"},{"type":"WEB","url":"https://github.com/Basekick-Labs/arc/commit/38402ad2ebddd32c15bf4a0fc9c22c920e5685df"},{"type":"PACKAGE","url":"https://github.com/Basekick-Labs/arc"},{"type":"WEB","url":"https://github.com/Basekick-Labs/arc/releases/tag/v26.06.2"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-08-28T20:45:07.174641043Z"}}