Bolt protocol: writes to a follower in Raft HA fail with "Cannot forward command to leader: no authenticated user in the current security context" (original) (raw)

Summary

In a Raft HA cluster, write queries (CREATE, MERGE, etc.) sent over the Bolt protocol succeed only when the load balancer happens to route the connection to the current leader pod. When the connection lands on a follower, the client receives:

{neo4j_code: Neo.DatabaseError.General.UnknownError}
{message: Cannot forward command to leader: no authenticated user in the current security context}

The REST/HTTP endpoint is unaffected. Reported against an EKS deployment using the Helm chart.

Root cause

The Bolt connection handler never binds the authenticated user onto the thread-local DatabaseContext.

BoltNetworkExecutor.ensureDatabase() resolves the database via server.getDatabase(targetName) and returns it, but never calls DatabaseContext.INSTANCE.init(...).setCurrentUser(...). The Bolt module has zero references to DatabaseContext / setCurrentUser (verified across bolt/src/main/java).

On a write to a follower, RaftReplicatedDatabase.command() forwards the statement to the leader via forwardCommandToLeaderViaRaft(...), which reads the current user to set the X-ArcadeDB-Forwarded-User header:

// ha-raft/.../RaftReplicatedDatabase.java:1523 final String proxiedUser = proxied.getCurrentUserName(); if (proxiedUser == null) throw new SecurityException( "Cannot forward command to leader: no authenticated user in the current security context"); // :1526 builder.header("X-ArcadeDB-Forwarded-User", proxiedUser); // :1527

Because the current user was never bound to the context, getCurrentUserName() returns null and the forward throws. The Bolt driver surfaces it as Neo.DatabaseError.General.UnknownError.

Why REST and Postgres work but Bolt does not

Affected components

Proposed fix

Mirror the Postgres/gRPC pattern in BoltNetworkExecutor.ensureDatabase(): after resolving / switching the database, bind the already-authenticated user:

DatabaseContext.INSTANCE.init((DatabaseInternal) database).setCurrentUser(user.getDatabaseUser(database));

This must run whenever the database is opened or switched (the ensureDatabase() switch path), and the Bolt executor runs on a dedicated per-connection thread, so the thread-local binding persists for the connection lifetime.

Steps to reproduce

  1. Start a 3-node ArcadeDB Raft HA cluster.
  2. Connect a Neo4j/Bolt driver to a follower node.
  3. Run a write query, e.g. CREATE (n:Person {name:'x'}).
  4. Observe the Neo.DatabaseError.General.UnknownError "no authenticated user in the current security context" failure. The same query against the leader succeeds.

Proposed regression coverage