Missing Conversation Ownership Check on SQL Driver Endpoint Allows Any Authenticated User to Execute Arbitrary SQL
Nobody has claimed this yet.
- Dominant language
- C#
- Stars
- 3.1k
- Forks
- 651
- Avg merge
- 15h 2m
- Merged PRs (30d)
- 36
Description
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.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start at the SqlDriverController ExecuteSqlQuery entry point and trace ConversationService.SetConversationId, then compare its authorization context with ConversationController.GetConversation. Review both the /execute and /result paths; done means authenticated users cannot bypass the intended role or conversation-ownership boundary when invoking SQL.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp, sql
- Domain
- authorization, backend-api-design, databases, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100