Skip to content

Missing Conversation Ownership Check on SQL Driver Endpoint Allows Any Authenticated User to Execute Arbitrary SQL #1441

Description

@carfeii

Affected versions: confirmed against the current default branch as of 2026-09-16

Summary

POST /sql-driver/{conversationId}/execute accepts a raw SQL statement and executes it against a configured data source, gated only by [Authorize] (any logged-in user, no role check). It performs no check that the caller owns conversationId, and the underlying ConversationService.SetConversationId call has no ownership check either, silently creating a new conversation using the caller's own user id if the given id doesn't already exist. Any authenticated user, regardless of role, can therefore execute arbitrary SQL against any configured data source by supplying a conversation id of their own choosing.

Details

[Authorize]
[ApiController]
public class SqlDriverController : ControllerBase
{
    [HttpPost]
    [Route("/sql-driver/{conversationId}/execute")]
    public async Task<IActionResult> ExecuteSqlQuery([FromRoute] string conversationId, [FromBody] SqlQueryRequest sqlQueryRequest)
    {
        ...
        var conv = _services.GetRequiredService<IConversationService>();
        await conv.SetConversationId(conversationId, [...]);
        ...
        var result = await fn.InvokeFunction("execute_sql", msg);

ConversationService.SetConversationId:

public async Task SetConversationId(string conversationId, List<MessageState> states, bool isReadOnly = false)
{
    _conversationId = conversationId;
    await _state.Load(_conversationId, isReadOnly);
    states.ForEach(x => _state.SetState(...));
}

No ownership check exists here. By contrast, ConversationController.GetConversation (which only reads a conversation's own dialog, a far less sensitive operation) already enforces the caller is an admin or the conversation's owner:

var (isAdmin, user) = await userService.IsAdminUser(_user.Id);
var filter = new ConversationFilter
{
    Id = conversationId,
    UserId = !isAdmin ? user?.Id : null,
    ...
};

SqlDriverController has no equivalent check anywhere. The identical gap exists on POST /sql-driver/{conversationId}/result.

POC

(available upon request)

Impact

Any authenticated user of a BotSharp instance, regardless of intended role, can execute arbitrary SQL statements against any data source configured in SqlDriverSetting.Connections, by calling this endpoint with a conversation id of their own choosing (no leaked or real conversation id is required). In a deployment using this plugin's intended "chat with your data" feature with real database connections, this grants every logged-in chat user unrestricted database access, bypassing whatever scoping the AI agent's own system prompt was meant to enforce.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions