Here is a piece that many people miss when they wire up GitHub Copilot in SSMS: when Copilot runs a query, it runs as you. Same login, same permissions, and same blast radius. Connect as sysadmin and your helpful AI assistant is also sysadmin — db_owner in means db_owner out.
That is fine when you are poking at a dev box, but it's a different thing entirely when a DBA with db_owner in production asks Copilot to 'clean up some old rows', and Copilot happily obliges without any safety checks. SSMS 22.7 finally gives us a way to fence that in.
What changed in 22.7
Starting in SSMS 22.7, you can give Copilot its own execution context — a dedicated identity that is separate from the user driving it. You point Copilot at a database user or SQL login, and from then on every query Copilot runs executes as that principal instead of the human at the keyboard. The DBA keeps their own access in the Query Editor and Object Explorer, but what Copilot executes on their behalf is now restricted to only what Copilot's identity can access. See Microsoft's writeup, Introducing execution context for GitHub Copilot in SSMS.
I believe this is a remarkable step forward in the AI realm that we now exist in.
The setting is called agentExecuteAsUser, and it lives in the frontmatter of that database's CONSTITUTION.md, which is the markdown instruction block SSMS stores as a database-level extended property. It is configured per database. When Copilot connects, it reads the constitution, and if it finds agentExecuteAsUser, Copilot then executes database actions under that user context.
Set it up
Here is a self-contained walk-through with two tables: one Copilot is allowed to touch and one that it isn't.
USE DBA; GO CREATE TABLE dbo.Orders (OrderID int IDENTITY PRIMARY KEY, Customer nvarchar(50), Total money); CREATE TABLE dbo.Payroll (EmpID int IDENTITY PRIMARY KEY, EmpName nvarchar(50), Salary money); GO INSERT dbo.Orders (Customer, Total) VALUES (N'Acme', 100.00), (N'Globex', 250.00); INSERT dbo.Payroll (EmpName, Salary) VALUES (N'Employee A', 90000), (N'Employee B', 120000); GO
Now we create the identity Copilot will run as, and grant it only the permission it needs -- SELECT on Orders, nothing else. No rights on Payroll at all.
USE DBA; GO -- The least-privilege identity Copilot executes as CREATE USER CopilotExec WITHOUT LOGIN; GO -- Only what Copilot needs. SELECT on one table, full stop. GRANT SELECT ON dbo.Orders TO CopilotExec; GO
Now store the CONSTITUTION.md as the database's extended property. The agentExecuteAsUser line goes in the frontmatter: the block at the very top of the CONSTITUTION.md file, opened and closed by its own '---' line, as the code below shows. Everything after that closing '---' is the body, where the behavioral guidelines go.
USE DBA;
GO
DECLARE @DBUser NVARCHAR(128) = N'CopilotExec';
DECLARE @constitution NVARCHAR(4000) = N'---' + CHAR(10)
+ N'agentExecuteAsUser: ' + @DBUser + CHAR(10)
+ N'---' + CHAR(10)
+ N'# Database Constitution' + CHAR(10)
+ CHAR(10)
+ N'## Coding Guidelines' + CHAR(10)
+ N'- Never suggest DROP statements' + CHAR(10)
+ N'- Always include a WHERE clause on UPDATE or DELETE' + CHAR(10)
+ N'- Avoid SELECT * in production code';
IF EXISTS (SELECT 1 FROM sys.extended_properties
WHERE class = 0 AND name = N'CONSTITUTION.md')
EXEC sp_updateextendedproperty @name = N'CONSTITUTION.md', @value = @constitution;
ELSE
EXEC sp_addextendedproperty @name = N'CONSTITUTION.md', @value = @constitution;
GO
One last step that is easy to forget. The human using Copilot must be able to impersonate CopilotExec, or the engine will not let Copilot switch context. Grant IMPERSONATE to your Copilot user — here, the database user is me.
USE DBA; GO GRANT IMPERSONATE ON USER::CopilotExec TO [sqlfingers\rlewis]; GO
First, let's verify the SQL Server boundary manually. This simulates the execution context SSMS Copilot should use when agentExecuteAsUser is active.
USE DBA;
GO
EXECUTE AS USER = 'CopilotExec';
GO
SELECT
USER_NAME() AS DatabaseUser,
ORIGINAL_LOGIN() AS OriginalLogin,
HAS_PERMS_BY_NAME('dbo.Orders', 'OBJECT', 'SELECT') AS CanSelectOrders,
HAS_PERMS_BY_NAME('dbo.Payroll', 'OBJECT', 'SELECT') AS CanSelectPayroll;
SELECT * FROM dbo.Orders; -- returns rows
SELECT * FROM dbo.Payroll; -- Msg 229, denied
GO
REVERT;
GO
We see results from the top two SELECTs but that call to Payroll fails with Msg 229. This proves the database wall we just put up works. CopilotExec can read Orders but the call to Payroll fails with Msg 229.
Now that we've proven the SQL Server boundary manually, let's verify the permissions available to the execution context itself.
EXECUTE AS USER = 'CopilotExec';
GO
SELECT
USER_NAME() AS DatabaseUser,
HAS_PERMS_BY_NAME('dbo.Orders', 'OBJECT', 'SELECT') AS CanSelectOrders,
HAS_PERMS_BY_NAME('dbo.Payroll', 'OBJECT', 'SELECT') AS CanSelectPayroll;
REVERT;
GO
As intended, CopilotExec can read Orders but not Payroll. With that established, I then asked Copilot to return the top 10 rows from dbo.Payroll:
There it is. Copilot can only query what CopilotExec is privileged for. It sees that it doesn't have SELECT on Payroll and very kindly gives us a script to do it ourselves.
Even better, Copilot did not simply obey the prompt. It recognized the execution context defined in the database constitution and determined that the current context did not have permission to access the requested table. To me, THAT is the type of governance we need in AI to manage data safely, legally, and ethically.
Two boundaries, and only one of them is real
It is worth being precise about what is actually protecting you here, because the constitution does two different jobs:
| Mechanism | Type | Enforced by |
|---|---|---|
| agentExecuteAsUser | Hard boundary | SQL Server permissions on the impersonated user |
| Constitution body (ie., 'never DROP') |
Soft boundary | The model choosing to behave |
The guidelines in the body steer behavior. They do not stop anything. Microsoft is blunt about this in the Agent mode docs: "The security boundary is SQL Server's permission enforcement, not Copilot's approval system." The approval prompts and the READ_ONLY default are seatbelts. The permissions on agentExecuteAsUser are the wall. You need to build the wall AND enforce seatbelt usage.
Gotchas worth knowing before you ship it
| Gotcha | What happens |
|---|---|
| Missing IMPERSONATE grant | Copilot cannot assume the configured agentExecuteAsUser identity, so the execution context never activates |
| Per database | It is not server-wide. Set this on every database you care about |
| Lazy account choice | Do not point it at sa, dbo, or a broad admin login because it is convenient. That defeats the feature |
Version requirements
Requires SSMS 22.7 or later with the AI Assistance workload installed (it is a separate component in the Visual Studio Installer), plus a GitHub account with Copilot access. SSMS 22.7.0 shipped June 9, 2026 and 22.7.1 followed on June 16 with a query-editor fix for large scripts. 22.7.1 is the most current SSMS 22 build.
Again, I think this is a real step forward for better controls with AI and SQL Server, but it still requires a common-sensical approach with best practices. Follow the least privilege principle with your agentExecuteAsUser just like you would any other user. Grant the minimum permissions needed for the tasks at hand. Just like Erin Stellato said, "The tighter the permissions on this user, the more confidence you have in what GitHub Copilot in SSMS can and can't do." Read more on the Copilot best practices here.
More to Read
Introducing execution context for GitHub Copilot in SSMS
Announcing the Release of SSMS 22.7.0 - and many previews
GitHub Copilot Agent Mode (Preview) - Microsoft Learn
Database Instructions (CONSTITUTION.md / AGENTS.md) - Microsoft Learn
Best Practices for GitHub Copilot in SSMS - Microsoft Learn




No comments:
Post a Comment