Blog

min read

Lose Control Flow: Unauthenticated Tool Execution in Dolt MCP

By

Ariel Fogel

and

August 13, 2026

min read

A missing return statement let unauthenticated callers execute Dolt MCP tools

Executive summary

Pillar Security discovered a critical authentication bypass in DoltHub’s remote Dolt MCP server. The vulnerability affected versions 0.3.1 through 0.3.6 and was fixed in version 0.3.7. It affected the remote HTTP transport when JWT authentication was enabled; local stdio use was not affected.

Dolt, a SQL database marketed as “the Database for Agents”, is used frequently to help agents coordinate across tasks in multi-agent systems. Dolt MCP exposes its database and version-control operations to AI agents through the Model Context Protocol.

Two flaws made the bypass possible:

  1. Requests with invalid tokens received a 401 response but continued processing.
  2. Session validation accepted attacker-generated IDs that matched the expected format without checking that the server had created them.

An unauthenticated attacker could therefore submit an invalid forged credentials and a locally generated session ID, reach tool dispatch, and execute Dolt MCP operations. The response still carried a 401 status, but the requested operation ran, and its result appeared in the response body.

Impact depended on the MCP server account’s privileges. An unauthenticated attacker could exercise any database privileges exposed through the MCP server’s tools. Tool calls ran with the server’s database credentials, allowing an unauthenticated attacker to read accessible data, modify, or delete it.

DoltHub confirmed the vulnerability hours after reporting, patched its hosted infrastructure, and coordinated a GitHub Security Advisory. Self-hosted users should upgrade to Dolt MCP 0.3.7 or later.

The broader lesson is that returning an authentication error is not enough: rejection must terminate execution. For every downstream effect, an MCP server must preserve the connection between the authenticated caller, the authorized operation, the associated state, and the credential used to perform it.

Dolt, DoltHub, and the agent ecosystem

DoltHub builds Dolt, a SQL database marketed as “the Database for Agents”, with Git-style version control. Dolt supports familiar database operations while adding branches, commits, diffs, merges, and history directly to the database model.

That combination is particularly useful for systems in which multiple humans or agents need to modify shared state without losing provenance. Instead of treating every update as an opaque overwrite, Dolt can retain who changed what, when it changed, and how competing versions were reconciled.

Dolt also powers Beads, a distributed, graph-based issue tracker designed to provide persistent structured memory for multi-agent systems. Beads is an integral building block in Steve Yegge’s Gas Town and Gas City approach to coordinating multi-agent systems, where agents need durable work state, dependency tracking, and a shared operational ledger.

Dolt MCP connects this database to MCP clients. An agent can use its tools to inspect schemas, query data, and perform supported database operations without implementing the Dolt protocol itself. That makes the MCP server a security boundary. It does not merely return information about a database. It receives instructions from an AI-facing client and turns them into operations performed with credentials held by the server.

Following a request through the broker

A remote Dolt MCP request passed through several stages:

Authentication was meant to verify the caller, session handling to validate request state, and tool dispatch to convert an authorized request into a database operation under the server’s account.

Impact

The attacker could fabricate the session identifier needed to reach tool dispatch despite failing JWT authentication, and cause the MCP server to exercise its own authority on their behalf. The available actions depended on the database privileges granted to the server account. Potential consequences included:

  • Reading database schemas and accessible records.
  • Executing supported queries.
  • Modifying or deleting data when the server account had write access.
  • Invoking other Dolt MCP tools available to the deployment.

The vulnerability affected the remote HTTP transport when JWT authentication was enabled. Local stdio use was not affected by this authentication path.

The first defect: authentication failed, but execution continued

Dolt MCP’s JWT middleware treated missing and invalid credentials differently. A missing token produced a 401 and terminated processing. An invalid token produced the same response, but execution continued because the branch omitted return:

Code Block
None
valid, _, err := validateJWT(logger, pr, token, time.Now())

if err != nil || !valid {
    logger.Info("unable to authorize jwt",
        zap.Bool("valid", valid),
        zap.Error(err))
    http.Error(w, "unauthorized", http.StatusUnauthorized)
}

next.ServeHTTP(w, r)

In Go, http.Error writes the response but does not terminate the handler. Execution therefore continued to next.ServeHTTP, passing the rejected request to MCP handling.

The enabling condition: attacker-generated session IDs were accepted

The missing return allowed the request to reach the MCP handler, but Dolt MCP was using a session-based version of the protocol. A non-initialization request still needed an Mcp-Session-Id.

A typical stateful implementation should have limited the bug’s impact. Although an attacker might reach initialization, a fabricated ID would not correspond to a server-created session and could not reach arbitrary tool execution. An attacker could reach the initialization path, but a fabricated session ID would be rejected because it did not correspond to a session created by the server.

The version of mark3labs/mcp-go used by Dolt MCP behaved differently. Its default InsecureStatefulSessionIdManager verified only that the supplied value began with mcp-session- and ended with a valid UUID:

Code Block
None
if !strings.HasPrefix(sessionID, idPrefix) {
    return false, fmt.Errorf("invalid session id")
}

if _, err := uuid.Parse(sessionID[len(idPrefix):]); err != nil {
    return false, fmt.Errorf("invalid session id")
}

return false, nil

The manager never checked a registry of issued sessions. As a result, the attacker-generated mcp-session-<uuid> passed validation: the check proved syntax, not server issuance.

The complete exploit


Video demonstration:
An invalid JWT and attacker-generated session ID reach tool dispatch. Although the server returns 401 Unauthorized, the database operation executes and its result appears in the response.

The two defects composed into a single unauthenticated request:

Code Block
None
POST /mcp HTTP/1.1

Authorization: Bearer not.a.valid.jwt
Mcp-Session-Id: mcp-session-<attacker-generated-uuid>
Content-Type: application/json

The request then followed this path:

  1. JWT validation rejected the token and wrote 401 Unauthorized, but processing continued.
  2. mcp-go accepted the attacker-generated session ID.
  3. The server dispatched the requested tool using its configured Dolt credentials.
  4. The tool result was appended to the 401 response body.

The response could therefore look like this:

Code Block
None
HTTP/1.1 401 Unauthorized

unauthorized
{"jsonrpc":"2.0","id":10,"result":{...}}

Monitoring that recorded only the status code could report that the attack had failed. At the database, however, the operation had already occurred.

How authority changed hands

The defects opened a path to the tool; the server’s database credential supplied the authority behind it. The attacker neither authenticated nor possessed a Dolt credential, yet could perform database operations because three boundaries failed to preserve the request’s provenance:

  1. Authentication rejected the caller, but did not prevent dispatch.
  2. Session validation checked only that the ID looked valid, not that the server had created it or that it belonged to the caller.
  3. Tool execution used the server’s database credential without establishing that the caller was entitled to exercise its authority.

By the time Dolt received the query, the rejected caller had disappeared from the authorization model; only the server’s database identity remained.

What generalizes beyond Dolt MCP

The fabricated session ID was specific to the affected version of mark3labs/mcp-go, whose default manager checked format without confirming server issuance. The official MCP SDKs we reviewed generally verified session IDs against server-created state, so this finding does not imply that MCP sessions were broadly self-mintable.

The general failure was earlier in the chain: an authentication decision that did not control dispatch. The missing return was an ordinary middleware bug, but its consequence was amplified at an MCP boundary, where reaching the dispatcher can cause tools to exercise credentials and modify systems beyond the HTTP server itself.

The 2026-07-28 MCP revision removed protocol-level sessions and the Mcp-Session-Id header. On a sessionless server, a request that passes through failed authentication may reach tool dispatch without encountering a second protocol gate. This does not make sessionless MCP inherently less secure, but it makes authentication’s control over dispatch critical. Tests must prove not only that rejected requests return 401 or 403, but also that no tool ran and no downstream effect occurred.

For requests that pass authentication, the verified caller must continue to govern which operations are allowed, which state may be used, and which downstream authority may be exercised.

From caller identity to downstream authority

An MCP server does more than expose an API. It turns one principal’s instruction into an operation on another system, often using a credential the caller does not possess. It is therefore an authority-bearing intermediary.

A verified identity in request context does not constrain behavior by itself. If a tool ignores that identity, a state handle is transferable, or every caller can exercise the full authority of a shared service account, the server may know who called without limiting what the request can cause.

The core rule is:

Every action must trace back to a verified caller who was allowed to perform it, access the state it used, and exercise the required downstream permissions.

Servers can enforce this through delegated credentials, token exchange, caller-specific authorization, narrowly scoped service accounts, or a combination of these controls. The approach may vary, but the link between the caller and the resulting action must remain intact.

DoltHub’s response

DoltHub had an exemplary response, confirming the bypass and protecting its hosted infrastructure within a day. The fix entailed enforcing the authentication in the JWT middleware. The urgency with which the team responded matters: Cyber Unit estimates that the average disclosure-to-exploit window shrank from 56 days in 2024 to roughly 10 hours in 2026. Whatever the precise industry average, vendors increasingly have little time to validate critical reports and protect exposed systems.

Disclosure timeline

  • August 3, 2026: Pillar Security reported the authentication bypass privately to DoltHub with reproduction material and evidence from an authorized deployment.
  • August 4, 2026: DoltHub confirmed the vulnerability and its exposure on hosted instances where MCP was enabled, patched its hosted infrastructure, began investigating potentially affected users, and issued GHSA-mgpq-fxxc-h49q.
  • August 13, 2026: CVE-2026-73554 is issued
  • August 14, 2026: Pillar discloses the vulnerability in their blog.

Mitigation

Self-hosted users should upgrade to Dolt MCP 0.3.7 or later.

Administrators should also review the permissions assigned to the MCP server’s database account. The account should have only the capabilities required by the tools and workloads being exposed.

Lessons for MCP server developers

The server reached the correct authentication decision. It returned the correct status code. Neither prevented the rejected request from becoming an authorized-looking database operation under the server’s identity.

The attacker supplied no valid identity and possessed no database credential. The instruction was theirs. The authority was the server’s. The vulnerability was the broken chain between them.

Subscribe and get the latest security updates

Back to blog

MAYBE YOU WILL FIND THIS INTERSTING AS WELL

Deadbugz: Currently Active MCP Supply-Chain Campaign

By

Ariel Fogel

and

August 12, 2026

Research
Pillar launches Red Graph Suite: version-controlled, contextual and continuous AI red teaming

By

Dor Sarig

and

August 6, 2026

News
ChainDrop: When Opening a Repository Becomes Execution

By

Ariel Fogel

and

August 4, 2026

Research