DEV Community

Derek Cai
Derek Cai

Posted on AI-assisted

LogCarverGuard, an MCP proxy between an AI agent and SQL Server

I am a SQL Server DBA and I also write C# and .NET. Many people now let an AI coding agent like Claude Code or Cursor run SQL on a real database. I wanted something between the agent and the server, so I built LogCarverGuard. This post covers the free edition: what it blocks, how it decides, and what it does not do.

It is early. The version is 0.1.

Setup

The agent talks MCP to the proxy, and the proxy talks to SQL Server.

AI agent --(MCP, API key)--> LogCarverGuard proxy --(EXECUTE AS the caller's own login)--> SQL Server
Enter fullscreen mode Exit fullscreen mode

There is one tool, execute_sql. For a SELECT you get the first 100 rows, and the rest is reported as a count. A write is executed and audited.

The agent does not get the password

The proxy has one service login. Each person gets an API key, and the key maps to their own SQL login. For each request the proxy impersonates that login with EXECUTE AS, so the GRANT and DENY you already set for that person still apply. The proxy does not add any permission.

The service login does not need sysadmin. It needs IMPERSONATE on each user's login and a database user in the target database. I have integration tests for this on a real SQL Server: a key runs as its mapped login, the login can use what it was granted, and it is denied on a table it was never granted.

How it decides

The checks use Microsoft's ScriptDom T-SQL parser. The guard looks at parsed statements, so text inside comments or strings is just text. A normal UPDATE with a comment like -- TRUNCATE TABLE dbo.T in front is not blocked. Same for a string literal that says "please truncate table later". Both are tested.

Some statements are always blocked: DROP TABLE, DROP DATABASE, RESTORE DATABASE and RESTORE LOG, ALTER DATABASE ... SET, sp_executesql, xp_cmdshell, and dynamic EXEC of a variable or a string. Identity switching is blocked too (REVERT, EXECUTE AS, SETUSER), and also GRANT, DENY, REVOKE, and the caller's own BEGIN TRAN, COMMIT, ROLLBACK, SAVE TRANSACTION.

Why REVERT: the proxy opens the connection as its service account and then switches to your login. If the agent sends REVERT, the session goes back to the service account and your grants no longer apply. The test case for this is REVERT; UPDATE dbo.Payroll SET Salary = 0; and it is blocked. SQL Server does not let the proxy lock the impersonation here, so the parser block is the only protection against this.

TRUNCATE TABLE, ALTER TABLE ... DROP COLUMN, and EXEC of a named stored procedure (including INSERT ... EXEC) are blocked unless the admin puts them on an allow-list. Entries are schema.name. If a statement names another database or a server, like OtherDb.dbo.T or [srv].db.dbo.P, it is never matched against the list.

Example

The agent sends this batch:

UPDATE dbo.T SET Name = 'x' WHERE Id = 1;
TRUNCATE TABLE dbo.T;
Enter fullscreen mode Exit fullscreen mode

The first line is OK. The second is a TRUNCATE on a table not on the allow-list. The guard checks every statement, so the whole batch is blocked, first line too. The agent gets the reason and the block goes into the audit log.

Transactions and audit log

When a batch has INSERT, UPDATE or DELETE, the proxy runs it in its own transaction. If one statement fails, the whole batch is rolled back. DDL, TRUNCATE and EXEC run as they are and are not rolled back with the batch. Because the proxy owns the transaction, the caller cannot send BEGIN TRAN or SET IMPLICIT_TRANSACTIONS ON.

The audit log is a local SQLite file. It has who, when, the SQL, and blocked or executed. Executed statements are written after commit. If SQL Server itself rejects a statement, for example permission denied, it is not recorded.

Local mode

There is also a local mode. Your AI tool starts it over stdio on your machine, with no server, Docker or API keys. Settings come only from environment variables. I tested it on Windows with integrated authentication.

With LOGCARVERGUARD_READ_ONLY=true it allows SELECT without INTO, plus SET, DECLARE and PRINT. Everything else is blocked with a reason. This includes EXEC even if allow-listed, NEXT VALUE FOR, and OPENQUERY, OPENROWSET, OPENDATASOURCE, because they can run a write on another server. SQL that does not parse is also blocked in read-only mode.

Local mode is a guardrail against accidents. It is not a security boundary. The connection string is on your machine and in your AI tool's config. An agent that can run shell commands or read that config can connect to SQL Server directly and skip the proxy. Every statement runs as the one login in the connection string.

What it does not do

The real security boundary is the grants on the SQL login. The blocked list is narrow on purpose. DROP INDEX, sp_rename, BACKUP DATABASE and a Linked Server OPENQUERY get no special handling. They run if the login has permission. Give each key an account with the least privilege you can live with, and never sysadmin or db_owner.

The free edition blocks. It does not undo a statement that already ran.

There is no mask-on-read. If the account can SELECT sensitive data, the agent can read it.

It only sees SQL that comes through this MCP connection. Files, APIs or a cloud console the agent can also use are outside it.

The API key is one static secret. If someone steals a key, they get that login's full access. The audit log has no tamper protection.

Testing

There are 200 test cases in five projects: Core, Server, Local, Cli, and the SQL Server integration tests. All passed on 2026-11-05. Most guard tests are parser-level unit tests. Only the integration and host tests use a real local SQL Server.

Some of the bypass cases tested:

  • INSERT ... EXEC variants, including INSERT INTO dbo.T EXEC('DROP TABLE dbo.X') and INSERT dbo.T EXEC xp_cmdshell 'dir'
  • TRUNCATE or EXEC with a database or server name, which must not match a local allow-list entry
  • xp_cmdshell and sp_executesql stay blocked even when an admin adds them to the allow-list
  • SET IMPLICIT_TRANSACTIONS ON
  • a dangerous statement after a normal one in the same batch

I wrote the tests myself. No one outside has done a penetration test. The tests show the rules work as written. They do not show the rules are complete.

The code and the full README are at https://github.com/caiderek/LogCarverGuard. It is MIT licensed.

I wrote this from my own notes in Chinese and drafted the English with AI help, and I checked the technical claims against the tests in the repo.

Top comments (0)