GHSA-P378-JP5R-GPGW

Vulnerability from github – Published: 2026-08-28 20:32 – Updated: 2026-08-28 20:32
VLAI
Summary
arc has unauthenticated cluster node admission when `cluster.shared_secret` is unset
Details

Summary

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.

Details

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.

Proof of concept

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:

  1. Start an Arc cluster node with:
[cluster]
enabled = true
cluster_name = "arc-cluster"
coordinator_addr = ":9100"
shared_secret = ""
tls_enabled = false
  1. Start an attacker-controlled HTTP listener that records request method, path, and headers, for example on 127.0.0.1:18080.

  2. 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:

{
  "node_id": "evil-reader-1",
  "node_name": "evil-reader",
  "role": "reader",
  "cluster_name": "arc-cluster",
  "raft_addr": "127.0.0.1:19020",
  "api_addr": "127.0.0.1:18080",
  "coord_addr": "127.0.0.1:19010",
  "version": "lab",
  "core_count": 1
}

Expected vulnerable result: the coordinator accepts the join instead of rejecting it for missing authentication.

  1. Trigger a forwarded operation from a node that cannot handle the operation locally. Examples depend on cluster roles:

  2. Join as reader and trigger a query through a node that routes queries to readers.

  3. Join as writer and trigger ingestion through a non-writer node that routes writes to writers.

Expected 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.

  1. In a Raft-enabled lab, observe that the attacker-provided raft_addr is submitted to AddVoter, demonstrating unauthorized membership mutation.

Impact

In affected cluster deployments, an unauthenticated network attacker can become a trusted cluster node. Practical impacts include:

  • Interception of forwarded authenticated HTTP requests, including Authorization and x-api-key headers.
  • Exposure of query bodies, ingestion data, database/measurement names, and operational metadata.
  • Unauthorized cluster membership mutation, including attempted Raft voter addition when Raft is configured.
  • Potential data integrity impact if the rogue node returns forged query/write responses or accepts/diverts writes.
  • Potential availability impact by blackholing or delaying forwarded operations.

This 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.

Suggested fix

  • Fail startup when cluster.enabled=true and cluster.shared_secret is empty, not only when cluster.replication_enabled=true.
  • Reject all trust-mutating coordinator messages when no cluster authentication is configured, including join, heartbeat/state update, forward apply, and file replication messages.
  • Require HMAC or mutual TLS before processing any join/heartbeat message.
  • Bind authentication to node identity and advertised addresses to reduce replay and address-substitution risks.
  • Do not forward end-user Authorization/x-api-key headers to a peer unless the peer identity has been authenticated and authorized.
  • Add tests proving unauthenticated join and heartbeat requests fail when clustering is enabled.

References / evidence

  • internal/config/config.go:943-950
  • internal/config/config.go:1001-1005
  • cmd/arc/main.go:1258-1265
  • internal/cluster/protocol/messages.go:127-142
  • internal/cluster/coordinator.go:1066-1081
  • internal/cluster/coordinator.go:1101-1138
  • internal/cluster/router.go:154-177
  • internal/cluster/router.go:203-227
  • internal/cluster/router.go:327-357
  • cmd/arc/main.go:1887-1897
Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Go",
        "name": "github.com/basekick-labs/arc"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "0.0.0-20260615160325-38402ad2ebdd"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-55678"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-284",
      "CWE-287",
      "CWE-306"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-08-28T20:32:57Z",
    "nvd_published_at": null,
    "severity": "MODERATE"
  },
  "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`",
  "id": "GHSA-p378-jp5r-gpgw",
  "modified": "2026-08-28T20:32:57Z",
  "published": "2026-08-28T20:32:57Z",
  "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"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N",
      "type": "CVSS_V4"
    }
  ],
  "summary": "arc has unauthenticated cluster node admission when `cluster.shared_secret` is unset"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…

Loading…