Win32 classic static controls come with a bunch of possible styles to describe what it renders and how. Maybe it’s text. Maybe it’s an image. Maybe it’s just a solid color.
What happens if you create the control with one style and then change the style later?
Don’t do that.
The Win32 classic static control was originally written in the early 1980s for Windows 1.0, and back then, people didn’t play funny games like changing a static control from text to bitmap. They barely had enough memory to add two numbers together!
The Win32 static control decides what it is when it is created, and it doesn’t listen to the WM_STYLECHANGED message to learn that somebody told it to change its clothes. Mostly because the WM_STYLECHANGED message wouldn’t exist for another decade. It assumes that the style doesn’t change for the lifetime of the control.
If you change the style on the fly, say, from image to text, what happens is that the control starts up and realizes that it’s an image, so it loads the image. And then when it is destroyed, it checks what it is, and hopefully it still says “You’re an image,” so it frees the image. But if you secretly changed it to “You’re text”, it says “Well, in that case, I don’t have to free an image,” and the image gets leaked.
This behavior has remained unchanged in the static control for backward compatibility. There are almost certainly programs that “know” about this behavior when they change an image static control to a text static control, so they “fix” the problem by deleting the leaked image. If the leak were fixed in the static control, this would result in a double-delete bug.
So just stay away from the electrified wire. If you need to change the style of a static control, destroy it and create a new one with the new style, or create two static controls (say, one image and one text) and hide one and show the other depending on which one you need.
This hotfix release improves File Explorer thumbnail generation, fixes Mouse Without Borders pairing failures, and restores optional diagnostic event reporting. See the v0.102.2933.0 release notes for the full list of changes in PowerToys v0.102.
This patch addresses three areas since the previous stable release.
File Explorer
Improved handling of stalled thumbnail renderers, isolated concurrent SVG requests, and preserved supported SVG formatting and UTF-8 STL names, in #49950.
General
Updated telemetry to version 2.0.5 and restored optional diagnostic event reporting from native and managed components, without changing diagnostic-data preferences, in #51896 and #51940.
Mouse Without Borders
Fixed Mouse Without Borders pairing failures that could show an untrusted-signature error in signed PowerToys builds, in #51789 by @mvanhorn.
SQL Server 2025 features, ranked the way the music magazines rank albums. By whether they changed anything, not by how loud the launch was. Every entry below was tried on build 17.0.4085.5 before it earned a place. Three function probes came back unrecognised with this lab’s settings, and they’re listed too.
Twenty-four probes, each a TRY block. Twenty worked. Four came back no, and one of those four was a switch that is simply off. The ranking is mine. The measurements aren’t opinions.
10. Optimized locking
The column is_optimized_locking_on exists on every database. It was off on mine, because it needs accelerated database recovery switched on first.
Off is not the same as unavailable. Ranked low because I didn’t switch it on for a lab and can’t say I measured it. Ask me again after a month in production.
9. REGEXP functions
REGEXP_LIKE, REGEXP_REPLACE, REGEXP_COUNT and friends. They work and they’re correct.
Measured: a prefix match over 20,000 customer names took 2 ms with LIKE and 39 ms with REGEXP_LIKE. Use them for the patterns LIKE can’t express, not as a replacement for it.
8. The fuzzy string functions this lab couldn’t reach
EDIT_DISTANCE and JARO_WINKLER_SIMILARITY both returned “not a recognized built-in function name” with this lab’s settings. So did ANY_VALUE.
The fuzzy functions are documented as needing the preview features switch, which this lab left off. So this says nothing about whether they exist on the build. It says they aren’t reachable with the settings most instances have. If your plan depends on one, check your build and your settings before you write the code.
7. Vector data type and VECTOR_DISTANCE
You can declare VECTOR(3), insert into it, and compute cosine distance. All three worked first time.
Ranked in the middle because it’s real and it’s early. It changes what the database can hold. It hasn’t changed my week yet, because my week doesn’t have embeddings in it.
6. DOP feedback and CE feedback, on by default
Both database scoped configurations read 1 on a fresh database at level 170. I verified the switches, not the effect, so this entry is a note rather than a review.
Ranked sixth for the same reason optimized locking is tenth. Being on is not the same as being measured.
5. sp_invoke_external_rest_endpoint
It exists on the box. I didn’t call anything with it. My lab has no network access, and I won’t write about a thing I didn’t run.
It ranks fifth on shape alone. Calling a service from inside the engine is either the best or worst idea in this list. It depends entirely on who’s holding it.
4. The JSON data type
This one I expected to rank higher. A native JSON column, stored in a binary form.
Measured against the old way, 20,000 documents in an NVARCHAR(MAX) column. The same JSON_VALUE filter took 55 ms on nvarchar and 67 ms on the JSON type. Storage was 3,848 KB against 3,976 KB.
Neither faster nor smaller for that query on that data. The value is elsewhere: validation on write, and the functions that only take the new type. Adopt it for those reasons, not for speed you’ll measure on day one.
3. The small T-SQL things
|| for concatenation. UNISTR. BASE64_ENCODE and DECODE. PRODUCT as an aggregate. IS NOT DISTINCT FROM. DATE_BUCKET. GENERATE_SERIES.
Every one worked first time. None is a headline. Together they remove about a dozen small workarounds from my code. That adds up to a better week than any single big feature.
2. Parameter Sensitive Plan optimization
On by default at compatibility level 170, and it works. I proved that by finding the skew at which it starts producing a dispatcher plan. That’s a separate post.
The short version: it did nothing for a giant customer at 60 percent of the rows. In my test cases it fired only above a ratio between 90,000 and 122,500. That’s the heavy value’s rows against the estimate for the compiled one. It’s an observation, not a documented threshold. Useful, and narrower than the slide suggests.
1. Memory grant feedback with percentile and persistence
A sort spilled to tempdb on its first run. It got the right grant on its second, with no change from me. Executions two to twenty-nine reported the grant as stable and never spilled. Execution thirty ran on a freshly compiled plan.
That’s the feature that most changed a real week, because spills are the failure people don’t see. What the run shows is memory grant feedback working. It doesn’t isolate the persistence or the percentile mode, because I never compared them with those modes off.
Which SQL Server 2025 features to ignore for now
Anything that returned “not a recognized function” until you’ve checked whether a setting was the reason. Anything that needs a network path you haven’t secured. Anything you can’t switch on in a lab and measure yourself.
Upgrade for the sort that stops spilling and the dozen small workarounds you can delete. Everything else can wait for a cumulative update or two.
If you want the same twenty-four probes run against your build before you upgrade, that’s an afternoon’s work.
A feature list is not a reason to upgrade, it is a list of things to test before you do.
Part six of ten. AI-written SQL fails in a way that plain bad code never does. It runs, it returns a number, and the number is wrong. Here is one that came out 28 times too big.
The number
While writing chapter 6 of my new book, I asked an assistant for sales by territory. Nothing exotic. Total sales and order count for 2024, from the header table, with product detail from the line items.
It wrote a clean query. It joined the header to the detail and summed the header total.
On AdventureWorks2025, the Northwest territory has 2,061 orders. The query reported 7,775. The 2024 total went from 6,763,100.58 to 190,392,567.96.
Every order had been counted once per line item. No error. No warning. A number that was ready to paste into a slide.
Four questions an interviewer will ask
These are from chapter 6. Answers short enough to say out loud.
1. Where do AI coding assistants struggle?
They struggle with what they can’t see: your schema, your data volumes, your business rules and your team’s unwritten conventions. They also struggle with long, tangled code and anything newer than their training. Their mistakes look like working code.
Why it matters. Most data bugs live in business rules. “Active customer” means something specific at your company, and no model knows it unless someone writes it down.
What the interviewer wants. Gaps named as missing context, not as the model being dumb. Missing context is fixable, and that’s the point.
2. What is text to SQL?
Text to SQL turns a question in plain language into a SQL query. “Top ten customers by sales last quarter” becomes a SELECT with joins, filters and an ORDER BY. The query runs, and the result comes back as a table or a sentence.
Why it matters. It opens data to people who don’t write SQL. It also moves the risk, because now someone who can’t read the query is trusting its result.
Watch out. Ambiguous questions produce confident queries. “Last quarter” can mean calendar or fiscal, and the model picks one without asking unless you tell it to ask.
3. How do you test that an AI-written query returns the right rows?
Compare it with something you trust. Run it next to a known-good query or a report total, and compare the results with EXCEPT in both directions. Then check row counts, totals and a few rows by hand.
Why it matters. A wrong query that runs is more dangerous than one that fails. The 28 times too big number above is what that looks like.
Watch out. Test the edges too: empty results, NULLs, the first and last day of a period, and duplicate keys. EXCEPT ignores repeated rows, so group by every column and compare the counts when repeats matter.
4. Why can AI-written code be correct and still slow?
Because correctness and performance are different problems. The model writes a query that returns the right rows without seeing your indexes, row counts or plans. It can wrap a column in a function, or pick a pattern that scans a billion rows.
Why it matters. On a small test table, everything is fast. The same query on production data can time out or block other work.
What the interviewer wants. The execution plan and a test on realistic volumes. Name one trap, such as a filter that can’t use an index.
Do this in five minutes
Reproduce the mistake yourself, so you can describe it from memory rather than from a blog post.
Paste this into any AI assistant:
Write a SQL Server query for total sales and order count by territory for 2024. Use Sales.SalesOrderHeader and include product detail from Sales.SalesOrderDetail.
Check the reply. Does it join the detail table and still use COUNT and SUM from the header? If so, it made the mistake.
Then ask: does this join repeat any rows? Watch whether it fixes the problem or defends it. That reaction tells you more than the first answer did.
One fix to refuse, by the way. SUM(DISTINCT TotalDue) looks clever and is wrong, because two orders can have the same total and DISTINCT throws one away.
Why the book helps here
The pattern above is one of ten questions in one chapter. The chapter also covers reviewing AI code, giving a model your schema, and semantic layers. It covers SQL injection from generated code, and what to do when an AI-written change breaks production.
Every answer carries the part most cheat sheets skip. What the interviewer is listening for, and where the neat answer stops being true.
Microsoft Threat Intelligence has observed threat actors using an agentic AI offensive framework to search public GitHub repositories for exposed secrets, including credentials, application credentials, and AI inference-related API keys across multiple applications and cloud services. These threat actors can then use the secrets for further malicious activity against the compromised organization.
Searching open code repositories for exposed credentials, secrets, keys, and other authentication material is a well-documented technique that has historically been limited by the time required for human operators to find code repositories, review the data in the repositories, and leverage authentication material for further action. However, agentic tooling like ARTEX allows threat actors to not only scan for secrets across multiple repositories at a quicker pace but also use the agents to analyze the output and conduct follow-on attacks, cutting down on the need for the threat actor to have hands-on-keyboard interactions throughout the attack chain. In at least one case, Microsoft observed an AI agent advance from scanning GitHub for secrets to exfiltrating data from Azure Key Vault in just five minutes.
Microsoft has also investigated follow-on activity involving the misuse of valid application credentials. As early as October 28, 2026, Microsoft began observing successful service principal sign-ins from the IP address 199.19.77[.]84 involving multiple application IDs across some impacted customers, including cases where a multitenant application credential was compromised at the source (vendor-owned application) and used to access multiple downstream tenants. The threat actor used the compromised credentials for cloud resource discovery, and credential and data exfiltration, including from Azure Key Vaults and Storage Accounts, and abuse of Azure AI services consistent with the activity described above. Additionally, Microsoft observed malicious activity originating from two IP addresses potentially associated with this campaign. We identified malicious operations from 65.87.223[.]26, including indications of vulnerability exploitation by embedded shell commands in a user agent and attempts to obtain App Service authentication tokens. A similar campaign involving the same IPs addressed was also reported by Todyl. Separately, Microsoft observed reconnaissance operations from 157.22.126[.]128 targeting AI workloads deployed by victims on Azure AI services.
Microsoft encourages organizations to scan their code in GitHub and determine where any secrets are present. In addition to removing the secrets from the code base, affected organizations should immediately rotate any exposed keys or credentials. GitHub monitors public repositories by automatically scanning for secrets. In addition to scanning their own repositories, organizations can enable public monitoring through GitHub to detect secrets leaked in secondary public repositories across GitHub. This extends secret scanning beyond the repositories an enterprise owns to follow potential secret exposure across the platform. GitHub Advanced Security for Azure DevOps scans for credentials and other sensitive content in source code. Push protection can also block supported secrets before they’re pushed. Organizations need either GitHub Advanced Security for Azure DevOps or, for organizations using the standalone experience, GitHub Secret Protection for Azure DevOps enabled.
Secrets are exposed in public repositories when code containing the secret is committed to GitHub. Developers might store secrets in configuration files, scripts, or other code to quickly operationalize the OAuth application or avoid configuring more modern authentication flows. Organizations can reduce exposure by protecting workload identities and secrets, enforcing least privilege, safeguarding recovery resources, and enabling relevant Microsoft Defender for Cloud protections. Publicly exposed credentials remain usable until revoked or rotated; removing the original disclosure alone does not remediate the exposure. Microsoft recommends that developers migrate OAuth applications away from secret-based authentication. While storing long-lived application client secrets in code is a common practice, using managed identities for application authentication eliminates the possibility of secret exposure in code, manual processes associated with secret management, and barriers to securely scaling applications.
This activity illustrates an ongoing trend of threat actors accelerating all stages of the attack chain with agentic AI penetration testing frameworks, including ARTEX. As these capabilities evolve, defenders must similarly use AI to investigate and respond across large environments.
From exposed secrets to cloud compromise
Starting in August 2026, Microsoft observed threat actors using an agentic AI offensive framework to submit large volumes of queries to GitHub for secrets. The queries submitted by the AI framework often included the name of a target organization, keywords for secrets, a keyword linked to cloud services, or a keyword linked to another AI service provider. Organizations that find their secrets exposed in public repositories must assume that the exposed secrets and associated applications are compromised.
Follow-on actions
Credential abuse via Entra ID
In one follow-on attack that began as early as October 28, 2026, an agentic framework used valid leaked or stolen application credentials to access Microsoft Entra ID tenants of certain organizations and connected Azure and Microsoft 365 resources. The sign-ins primarily came from two IP addresses: 199.19.77[.]84 and 199.19.77[.]18. The latter IP address is a Tor exit node, which might be associated with unrelated malicious activity, including from unrelated sources. The agent successfully conducted service principal sign-ins on dozens of associated applications across multiple tenants.. Service principals are the identities that applications use to authenticate and give direct, programmatic access to whatever the application is authorized to reach.
The threat actor targeted operations that return credentials, including storage account keys, App Service settings, connection strings and publishing credentials, AI and machine learning (ML) connections and datastores, and Azure Kubernetes Service (AKS) user credentials. The compromised applications subsequently accessed SharePoint and OneDrive in the targeted organization tenants, including file downloads and mailbox-item access. In some tenants, the threat actor sent Microsoft Graph API requests, which were mostly searches to find content of interest. The threat actor enumerated subscriptions, resource groups, permissions, AI model deployments, and service configurations in Azure to map the environment.
Additionally, the threat actor attempted to validate stolen Azure resources, including compromised Azure AI services, as identified using Defender for Cloud’s AI threat protection. Observed activity suggests that the threat actor might have attempted to abuse some of these compromised AI resources to facilitate their broader attack activity.
Figure 1. Tactics, techniques, and procedures (TTPs) observed across the credential abuse attack chain
Cloud API adjustments indicative of adaptive behavior
Additionally in this attack the agent’s operations on a victim’s Azure Resource Manager (ARM) instance originating from 199.19.77[.]84 demonstrated sequences consistent with adaptive troubleshooting, where failed cloud API requests were followed by changes to HTTP methods or API versions and subsequent HTTP 200 responses:
A batch of ARM requests attempting to enumerate secret metadata across multiple Azure Key Vault resources returned HTTP 400 (Bad Request). These requests specified a version associated with Key Vault’s data-plane APIs against ARM (control-plane) APIs. Approximately 28 seconds after the first failed response, a batch rerun began against the same vault resource paths using the ARM API version 2023-07-01 and returned HTTP 200.
A GET request to enumerate an Azure SQL server’s firewall rules specified an outdated API version and returned HTTP 400. Approximately 60 seconds after that request, a matching GET began using a supported version and subsequently returned HTTP 200. The URL changed only in its api-version parameter.
An ARM POST request to an Application Insights query endpoint returned HTTP 400 because an outdated API version was specified. Approximately 80 seconds after the failed request, another POST began against the same endpoint, specifying a supported API version, and subsequently returned HTTP 200.
OAuth application abuse
In multiple cases, Microsoft observed the same agentic AI tools subsequently obtain a token for a single-tenant OAuth application in the same target organization, then use the compromised OAuth application to enumerate other cloud resources within the organization. In some cases, the agents exfiltrated secrets from the organization’s Azure Key Vault. The agents once authenticated have also searched Azure DevOps for secrets. Until the secrets are revoked or rotated, they can allow a threat actor to maintain persistence in the organization, compromise additional identities, or hijack resources. In one observed case, an AI agent progressed from searching for the client secrets in GitHub to exfiltrating data from Azure Key Vault in five minutes.
The agents also used the compromised OAuth application to use the Run Command, which runs scripts in Azure VMs. With access to this functionality on a VM, a threat actor can access unsecured credentials and other stored data, compromise associated workload identities, or run code that provides persistence or lateral movement.
During this activity, the AI agents repeatedly demonstrated a focus on credentials that provide access to AI services, such as API keys for major AI service providers and OAuth applications with access to Foundry Tools (previously known as Azure Cognitive Services). Multiple public reports have documented threat actors using agentic AI tools to compromise credentials for AI services, sometimes to run the same agentic AI workflow.
Mitigating application credential abuse activity
Microsoft recommends the following mitigation steps to contain, detect, and investigate this threat.
Containment
Block 199.19.77[.]84/32 as a named location in a Conditional Access policy targeting workload identities, assigned directly to service principals, and in a user-scoped block policy to cover replayed sessions. Note that workload identity policies require Workload ID Premium licensing and cover only single-tenant apps registered in your tenant. Block the exact IP rather than the whole ASN.
For affected users, revoke Entra sessions, Azure DevOps PATs and SSH keys.
For any affected application, remove all client secrets, certificates and federated credentials, including those added by the threat actor and disable the service principal if needed. Tokens already issued remain valid until expiry.
Rotate everything stored in any Key Vault the affected application could read, and any credentials found in accessed documents.
MSPs: Review Partner Center access, GDAP relationships, and customer tenants, and notify affected customers per your agreements.
Detection and hunting
Review Entra service principal and non-interactive sign-in logs for 199.19.77[.]84 and 199.19.77[.]18 including failed attempts. Sign-in errors AADSTS7000215 (invalid secret) or AADSTS7000222 (expired secret) indicate someone holds a credential for your app; a failure followed by a success is urgent.
Review Azure Activity, Key Vault and Storage diagnostics, Entra audit, Azure DevOps audit and Microsoft 365 unified audit logs for the address, including secret reads, blob and file downloads, and changes to apps, permissions or pipelines.
In Azure DevOps auditing, review PAT creation and first use, federated service connections, pipeline runs and edits, and unfamiliar user agents. Enable auditing if it is off.
Look for behavior as well as the IP: one address authenticating to three or more service principals with mixed success, or one app requesting tokens across Azure Resource Manager, Key Vault, Storage and Graph in a short window. Search as far back as retention allows.
Defending against agentic AI attacks
Threat actors can and do change the source IP address, user agent strings, and other fingerprints left by the agentic AI penetration testing tools they use. This can make identifying and blocking malicious network traffic early difficult for defenders.
Several behavioral signals can help organizations determine that they are being targeted by threat actors using agentic AI. Agentic AI frameworks scanning a target send a wide variety of requests in a brief time and retry variations on the same technique or move on to next-stage actions at machine speed. Agentic AI frameworks use existing penetration testing tools. For example, ARTEX by default uses Nmap, curl, wget, Python HTTP clients, Norma SDK WebFetch, and headless Chromium through Playwright, among others. These tools may send network traffic with distinctive characteristics, such as default user agent strings. While blocking network traffic with these characteristics will not stop an attack by an AI agent, which will quickly change them, identifying them can alert defenders to the activity.
Defensive honeypots, decoys, tarpits, and fake wins can pull autonomous AI attacks away from legitimate targets. Honeypots and decoy surfaces such as Microsoft Sentinel Deception give the agent attractive but worthless targets to pursue. Tarpits consume the agent’s time and budget. Fake wins present convincing evidence that the agent’s objective is complete, which can cause it to stop on a false success. In Microsoft’s testing, these controls helped reduce the attack traffic reaching real assets and produced high-confidence, attribution-quality signal.
Microsoft recommends the following mitigations to reduce the impact of this threat.
Protect and continuously assess application credentials and secrets. Avoid storing service principal credentials, storage keys, connection strings, and other secrets in source code, configuration files, public repositories, issues, or other locations where they might be inadvertently exposed. To find credentials in source code, consider using tools like GitHub Advanced Security or other OWASP source code analysis tools.
Rotate compromised or exposed credentials immediately and establish credential lifecycle practices. Treat credentials that have been publicly exposed as compromised, even if the original location has subsequently been edited or deleted. Removing the content does not invalidate the credential or eliminate copies retained in edit history, caches, archives, logs, or other systems. Immediately revoke or rotate the affected credentials and investigate their historical use. Where supported, organizations should favor mechanisms that reduce reliance on long-lived credentials. Learn more about protecting secrets with Defender for Cloud.
Enable appropriate Microsoft Defender for Cloud plans for critical Azure workloads. Consider enabling workload protections relevant to the resources in your environment, including Defender for Resource Manager, Defender for Storage, Defender for Key Vault, Defender for App Service, and Defender for Databases. Learn more in the Microsoft Defender for Cloud overview.
Apply least privilege to service principals and other workload identities. Review the Azure role-based access control (RBAC) permissions assigned to service principals and restrict their privileges to the resources and operations required by their applications. Learn more about best practices for Azure RBAC.
Protect backup and recovery infrastructure as part of resilience. Restrict access to backup and recovery resources and closely monitor attempts to modify or remove their protection controls. Learn more about Azure Backup security best practices.
Hardening OAuth applications against compromise involves managing the permissions of the applications in your organization and securing application secrets. To triage applications in your environment, enforce security policies with new applications as they are created, and address existing applications over time beginning with the most recent ones.
Identify and catalog applications running in your cloud environment. The Cloud app catalog page in Defender for Cloud Apps is one service that can help accomplish this.
Use Microsoft Defender for Cloud Apps Cloud Discovery tools to identify unmanaged apps, and prioritize the most frequently used apps to migrate and integrate.
Set permission restrictions to limit who can create an application in the tenant. This can be achieved by setting the ability for users to register apps to “No” and limiting the application developer role to privileged users. Review the list of least privileged roles by task to help scope role-based access control in your environment.
Microsoft Defender and Microsoft Sentinel customers can refer to the list of applicable detections below. Microsoft Defender coordinates detection, prevention, investigation, and response across endpoints, identities, email, and apps to provide integrated protection against attacks like the threat discussed in this blog.
Tactic
Observed activity
Microsoft Defender and Sentinel coverage
Initial access
Multiple identity-protection alerts identified authentication attempts from anonymized or high-risk infrastructure. This pattern can indicate attempted access through proxy, VPN, or previously reported malicious network sources.
Microsoft Entra ID Protection – Anonymous IP address
Initial access
Attack-analysis detections identified OAuth application activity consistent with compromise. This type of detection is useful when evaluating whether an app identity, rather than only a user identity, may have been abused.
Microsoft Defender XDR – Potentially compromised OAuth application identified through attack activity analysis
Initial access, Discovery
Unattributed network indicators related to ARTEX use are flagged by Microsoft Threat Intelligence research and surfaced across multiple alerts. Suspected ARTEX use can include activity beyond the GitHub repository scanning activity discussed in this blog.
Microsoft Sentinel – Abnormal Deny Rate for Source IP – Abnormal Port to Protocol – Inbound Network Attack detected – Malformed user agent – Password spray attack against Azure AD application
Microsoft Defender XDR – Account compromised following a password-spray attack – Anomalous OAuth device code authentication activity – Multiple Service Principal Sign-ins from Single IP – Graph API activity from a Microsoft threat intelligence IP address
Microsoft Defender for Cloud Apps – Activities from suspicious user agents – Multiple failed login attempts – Multiple failed user log on attempts to an app – OAuth application activity from an uncommonly visited ISP – Suspicious OAuth app registration
Microsoft Defender for Cloud – Key vault unusual access denied – user/application accessing high volume of denied key vaults – Access from an unusual location to a sensitive blob container – Azure Resource Manager operation from suspicious IP address
Microsoft Entra ID Protection – Atypical travelMalicious IP addressPassword Spray – Verified threat actor IP
Credential access
Identity-protection detections observed password spray behavior. This indicates repeated authentication attempts across accounts or tenants using a common source pattern.
Microsoft Entra ID Protection – Password spray
Microsoft Defender for Identity – Suspicious service principal sign-in following credential addition
Credential access
Azure Key Vault access
Microsoft Defender for Cloud (Defender for Key Vault) – Unusual operation pattern in a key vault – High volume of operations in a key vault – Unusual application accessed a key vault – Suspicious secret listing and query in a key vault – Unusual access to the key vault from a suspicious IP (Non-Microsoft or External)
Command and control
Suspicious Microsoft Graph API usage was observed from campaign infrastructure. The activity involved Graph requests that can support discovery, collection, or follow-on cloud operations.
Microsoft Defender for Identity – Suspicious Entra Graph API query observed
Advanced hunting queries
The following hunting queries are related to the October 28 Entra ID service principal sign-in activity.
Find successful and failed application sign-ins from the suspicious IP addresses
let startTime = datetime(2026-10-28T00:00:00Z);
let endTime = now();
EntraIdSpnSignInEvents
| where Timestamp between (startTime .. endTime)
| where IPAddress in ("199.19.77.84")
| project Timestamp, TenantId, Application, ApplicationId, ServicePrincipalName, ServicePrincipalId, ResourceDisplayName, ResourceId, ResourceTenantId, IPAddress, ErrorCode, UserAgent, CorrelationId, RequestId
| order by Timestamp asc
Find Microsoft Graph API requests from the two IP addresses, including searches and response status
let startTime = datetime(2026-10-28T00:00:00Z);
let endTime = now();
GraphAPIAuditEvents
| where Timestamp between (startTime .. endTime)
| where IpAddress in ("199.19.77.84")
| project Timestamp, TenantId, ApplicationId, ServicePrincipalId, AccountObjectId, IpAddress,
RequestMethod, RequestUri, ResponseStatusCode, ResponseSize, Scopes, EntityType,
TargetWorkload, UniqueTokenIdentifier, ClientRequestId, OperationId, RequestId
| order by Timestamp asc
Find available Microsoft 365 and Microsoft Azure cloud activity details from the suspicious IP addresses
let startTime = datetime(2026-10-28T00:00:00Z);
let endTime = now();
CloudAppEvents
| where Timestamp between (startTime .. endTime)
| where IPAddress in ("199.19.77.84")
| project Timestamp, Application, CloudAppId = ApplicationId, ActionType, ActivityType,
AccountType, AccountDisplayName, AccountId, AccountObjectId, IPAddress, UserAgent,
ObjectName, ObjectType, ObjectId, ActivityObjects, ReportId, RawEventData
| order by Timestamp asc
CloudAuditEvents table to identify suspicious controlplane operations
CloudAuditEvents
| where IPAddress has_any ('199.19.77.84','65.87.223.26')
CloudStorageEvents table to identify possible exfiltration from Storage Accounts.
CloudStorageAggregatedEvents
| where IPAddress in ('199.19.77.84','65.87.223.26')
| where SuccessfulOperationsCount > 0
Microsoft Security Copilot
Microsoft Security Copilot is embedded in Microsoft Defender and provides security teams with AI-powered capabilities to summarize incidents, analyze files and scripts, summarize identities, use guided responses, and generate device summaries, hunting queries, and incident reports.
Security Copilot is also available as a standalone experience where customers can perform specific security-related tasks, such as incident investigation, user analysis, and vulnerability impact assessment. In addition, Security Copilot offers developer scenarios that allow customers to build, test, publish, and integrate AI agents and plugins to meet unique security needs.
Threat intelligence reports
Microsoft Defender XDR customers can use threat analytics reports in the Defender portal (requires license for at least one Defender XDR product) to get the most up-to-date information about the activity and techniques discussed in this blog. These reports provide intelligence, protection information, and recommended actions to prevent, mitigate, or respond to associated threats found in customer environments.
Microsoft Security Copilot customers can also use the Microsoft Security Copilot integration in Microsoft Defender Threat Intelligence, either in the Security Copilot standalone portal or in the embedded experience in the Microsoft Defender portal to get more information about this threat.
To hear stories and insights from the Microsoft Threat Intelligence community about the ever-evolving threat landscape, listen to the Microsoft Threat Intelligence podcast.
If you’ve used Docker for a while, “Docker Sandboxes” might read as “a container with an AI agent inside.” Containers have been the unit of nearly everything Docker ships for over a decade, and a sandbox might sound like one more way to package a workload.
They’re actually different technologies. A container is an isolated process that shares the host kernel with everything else on the machine. Docker Sandboxes are dedicated microVMs with their own Linux kernel, filesystem, network, and Docker Engine, built for autonomous coding agents that need a whole development machine to work in. A sandbox can run containers, which is part of why the two are easy to conflate.
In his WeAreDevelopers keynote, Docker President Mark Cavage said containers are doing exactly the job they were designed to do, but we’re asking them to do a different job.
Containers were built to isolate applications, which they still do well. But a coding agent is an actor: it makes decisions and operates on your systems the way a developer uses a laptop. It clones repositories, installs packages, starts databases, builds images, and tries again for hours. It can also act on instructions from files, web pages, and tool output that nobody reviewed.
That different job needs a different boundary, and where that boundary sits determines how far an agent’s mistake can travel. This post covers how Docker Sandboxes and containers isolate workloads, what changes beyond the kernel, what a sandbox costs you in setup, and how to pick.
Key takeaways
A container is an isolated process sharing the host kernel. Docker Sandboxes are microVMs with their own kernel, filesystem, network, and Docker Engine.
Containers stay the right choice for packaged applications, CI jobs, and short-lived execution.
Docker Sandboxes fit autonomous agents that need sudo, a private Docker Engine, long-lived state, and host-enforced network and credential controls.
Containers can be part of an agent’s containment, and a sandbox can run them. Sandbox the agent, containerize the application it works on.
What is a container?
A container is a process, or a group of processes, running on the host with its own view of the filesystem, process tree, and network. Linux namespaces provide that view, and cgroups meter CPU, memory, and I/O. Every container on the machine also shares the host kernel, which is why containers start quickly, pack densely, and carry so little overhead.
This design has more than a decade of tooling behind it. Compose, Kubernetes, OCI registries, image scanners, and nearly every CI system treat the container as the common primitive. If the job is to run a service, execute a code snippet, process one request, or run an evaluation and disappear, a container is usually the obvious pick, and it will stay that way.
ⓘ Containers share one kernel, even inside a VM. On macOS and Windows, Docker Desktop runs your Linux containers inside a virtual machine, which keeps them off your host operating system. Every container in that VM still shares its single kernel, so the isolation between one container and the next is unchanged.
What are Docker Sandboxes?
Docker Sandboxes run an AI coding agent inside a microVM that has its own Linux kernel, its own filesystem and network stack, and a private Docker Engine. The agent gets sudo inside the isolated environment. It can install packages, run services, build images, and bring up an entire Compose application without reaching the host Docker daemon, the host filesystem outside its workspace, or other sandboxes on the same machine.
Getting one running takes a single command, using sbx, the Docker Sandboxes CLI:
sbx run claude
That creates the sandbox and starts the agent. You can stop it, resume it, and come back to everything the agent installed until you remove the sandbox.
Why do coding agents need a full machine?
Coding agents need a full machine because their sessions build up state over hours and they act on unvetted instructions. A container is built for a process with a known set of dependencies, and an agent’s work doesn’t fit that shape.
An agent needs the authority of a whole machine and a boundary designed for one. The capabilities that make an agent useful, such as running code, installing packages, and reaching the network, are the same ones that wear at the walls around it. An agent probes the edges of its environment because the job requires changing that environment.
Agents accumulate state for hours
A coding agent’s session looks like a developer’s afternoon. It clones a repository, installs packages, starts PostgreSQL, runs migrations, builds images, launches the app with Compose, reads the logs, changes the code, and tries again. That loop can run for hours, and each step depends on what the previous one left behind.
A container is designed to be rebuilt from an image and a few explicit volumes. The packages, databases, and build output an agent creates outside those volumes are lost when the container is recreated. That’s less than ideal for a session where every step builds on the last.
Box by ASCII describes this distinction neatly: “It uses the sandbox the way a developer uses a laptop, so the sandbox has to behave like one.”
Agents act on untrusted instructions
The program deciding which command to run is probabilistic, and it can read instructions from many places at once, including source files, issue descriptions, web pages, package metadata, and MCP tool output. Some of that input is untrusted, and a malicious instruction planted in a README or a tool description can steer the agent on purpose. The code it generates may also be wrong or actively hostile.
We’ve already seen an agent that deleted a production database after misreading its task. Once the agent is running unattended in YOLO mode, there’s no permission prompt left to catch it. A workload with that much authority and that little predictability needs a boundary designed for it.
Put these facts together and you get the reason why we built Docker Sandboxes. An autonomous coding agent needs machine-shaped capabilities without access to everything on the developer’s machine, which comes down to four things:
Containment: a boundary the model can’t rewrite
Control: observability into what the agent is doing and the ability to stop it
Choice: the freedom to swap agents, models, and tools without rebuilding the setup
Capacity: a setup other developers can reproduce and scale past one machine.
The next sections show what each looks like, starting with containment.
Containment: How isolation differs between containers and Docker Sandboxes
The short version is that a container’s boundary is the host kernel it shares, and Docker Sandboxes have a hypervisor boundary around a kernel the agent has to itself. That one difference decides how far a compromise can travel, and it also settles the awkward question of how an agent could get access to Docker.
This is where containers and containment part ways. A container can be part of containment, and often should be, but containment covers the whole environment around the agent. It starts with a boundary the model can’t rewrite, which is why Docker uses a microVM with its own kernel for autonomous agents.
Shared kernel vs. dedicated kernel
A container’s boundary is enforced by the kernel it shares, so escaping it requires a kernel exploit or a side-channel attack. Those aren’t everyday finds. Past that bar, though, sits the entire host kernel, every other process on the machine, and any credentials mounted alongside them.
ⓘ This scenario is specific to Linux environments. On Docker Desktop (macOS and Windows), the shared kernel is the Docker Desktop VM’s kernel, not the host kernel, so an escape would land in that VM rather than the operating system.
Docker Sandboxes give the agent its own kernel behind a hypervisor boundary. If an agent escapes its container, it lands in a disposable microVM, and the host stays out of reach. And you can delete the sandbox and start another. Each agent also gets its own microVM, so two agents running on the same machine can’t reach each other’s sandboxes either. However, the agent can still reach whatever you chose to share with it, including the workspace, allowed network destinations, MCP servers, and skills.
The Docker socket problem
Mounting /var/run/docker.sock into a container is the long-standing way to give it Docker access, and it turns up in Compose setups, CI pipelines, and observability tools. It also hands the container control of the host Docker daemon, which Docker limits to trusted users because it can mount host paths and start privileged containers.
An agent doesn’t need an exploit to use that. Put one in a container started with the socket mounted, and it probes its surroundings, works out that it’s in a container, and finds the hole in the configuration. In Mark’s keynote demo, that was enough for an agent to reach a secrets file on the host with no zero-day involved.
The socket is one hole among several, and agents find them because probing their environment is part of the work. Docker-in-Docker, mounted credentials, and broad host mounts are others. Containers were built to isolate an application that behaves as configured. But an agent keeps testing that configuration, so if there’s a gap, it’s likely to find it.
Where the microVM boundary goes
Docker Sandboxes put the missing boundary in the right place. The agent talks to a private Docker Engine inside the guest. It can build images, run Testcontainers, and start a whole Compose stack, and the host daemon and its socket stay outside the microVM the entire time. Containers keep doing what they do best, inside a boundary the agent can’t widen.
Control: What you can see and stop beyond the kernel
Containment draws the boundary. Control is what you can see and stop inside it: what the agent is allowed to reach, and the ability to block it when it heads somewhere it shouldn’t. In a sandbox, those controls are enforced by the host, outside the agent’s reach, so the agent can’t switch them off or reason its way around them. Outbound traffic, credentials, and organization-wide policy all work this way. Control also means knowing what the agent can still touch, because a microVM can’t hide what you choose to share with it.
Agent in a standard container
Agent in Docker Sandboxes
Kernel
Shares the host kernel
Runs its own Linux kernel in a microVM
Docker
Needs the host socket or a nested daemon
Uses a private Docker Engine inside the microVM
Workspace
Sees the host paths you mount
Uses a direct read-write mount or an in-VM private clone
Network
Uses the container network and whatever controls its operator supplies
Sends outbound traffic through a host proxy with deny-by-default policy
Credentials
Commonly receives environment variables, files, or mounted sockets
Uses host-side credential injection for supported HTTP services
State
Usually rebuilt from an image and explicit volumes
Keeps packages, images, containers, and agent history until sbx rm
Orchestration
Fits Compose, Kubernetes, and most CI systems
Uses the sbx lifecycle and experimental environment files
The host enforces the controls
These controls hold because they run outside the agent process. A rule the agent can reach is a rule it can work around, so the rules that matter live on the host. Outbound traffic goes through a host-side proxy with a deny-by-default policy, which means a destination the agent wants stays blocked until someone adds it to the list. Credentials follow the same logic. For supported HTTP services, the proxy injects them into outbound requests, so the agent can use a credential without the raw value ever entering the VM.
When one developer’s setup needs to become a team standard, Docker AI Governance can move network, filesystem, and MCP policy into organization-wide controls.
What the agent can still reach
A microVM can’t hide what you choose to share with it, so the shared parts deserve the most attention.
The workspace is the largest of them. It’s mounted read-write by default, so the agent can edit or delete files in your project. Clone mode gives the agent a private in-VM clone to work in, which protects the host working tree, though the original repository stays readable inside the sandbox.
MCP servers and skills are shared too. A local stdio MCP server runs on the host and shared agent skills use a host-side store, so the agent’s reach extends to both.
So whichever mode you pick, be sure to read the diff before you run modified code.
Choice and capacity: What Docker Sandboxes cost you in setup
Docker Sandboxes may cost some configuration and a supported hypervisor. But Kits and environment files account for the configuration, and they’re also where you get choice over agents and tools and the capacity to reproduce a setup across a team.
Kits and environment files
Kits make setup repeatable by packaging tools, files, commands, network rules, credentials, and agent instructions. They’re published for different agents, tools, and IDEs, so you can swap what runs inside the sandbox without rebuilding the environment around it. An sbxenv.yaml file records one project’s sandbox configuration, and committing it to the repo lets a teammate recreate the same sandbox with one command, so every developer works from the same setup. (Kits are in early access and environment files are experimental, so plan for changes to both.)
The host-enforced controls take some setup too. Network policy requires someone to decide which destinations belong in the workflow, credential mediation adds service bindings and approved domains, and clone mode adds a fetch step before changes land in the host repository.
Hypervisor and resource requirements
Running a sandbox takes more from your machine than running a container does. It needs a supported hypervisor, which today means Apple silicon on macOS 14 or later, Windows Hypervisor Platform, or KVM on supported Ubuntu systems, and running inside another VM requires nested virtualization. The separate kernel and Docker Engine also consume more memory and disk than one additional container would.
If your machine is the limiting factor, Docker Cloud Sandboxes run the same microVM isolation on Docker-managed compute, and one command moves a sandbox between your laptop and the cloud. Local and cloud sandboxes keep separate secrets and network policies.
How do you choose between a container and Docker Sandboxes?
Choose a container when you control the code and its dependencies, and choose Docker Sandboxes when the workload generates or fetches code at runtime and needs its own boundary.
Reach for a container when
Reach for Docker Sandboxes when
You control the code and its dependencies
The workload generates or fetches code at runtime
Execution is short-lived and rebuilt from an image
The session accumulates state for hours
The workload doesn’t need Docker, or runs in CI that already provides it
The workload needs its own Docker Engine with no path to the host socket
You want maximum density and a fit with Compose or Kubernetes
You need host-enforced network and credential policy the workload can’t switch off
The kernel boundary is enough for the code you’re running
You want a hypervisor between the workload and your kernel
If any of the signals below apply to your project, you should start with Docker Sandboxes.
The agent runs unattended, or its permission prompts are auto-approved
The agent needs to build images or run Compose or Testcontainers
The agent reads input you didn’t write, such as issues, web pages, package metadata, or MCP output
The agent requires API keys you don’t want inside its environment
The session lasts longer than a single task
Start with sbx run claude. Add clone mode, kits, network policy, credential mediation, and organization governance as your workflow grows.
Sandbox the agent, containerize the application
Containers aren’t going anywhere. For services, CI jobs, and anything you package to ship, they draw the right boundary, and the ecosystem around them is the reason they’ll keep doing that work. A coding agent needs more than a container to be trusted with real work. It needs containment, meaning a boundary the model can’t rewrite, controls it can’t reason its way around, and the freedom to choose its tools and scale its setup.
Docker Sandboxes provide that containment in a microVM that can still run your containers. The boundary is also what lets the agent move faster. With it in place, you can stop approving every command and let the agent get to work.
You can have a sandbox running with one command. Start with sbx run claude and the Docker Sandboxes quickstart. Once one developer’s setup needs to become a team standard, Docker AI Governance extends the same controls across your organization.
Frequently asked questions
Are Docker Sandboxes just containers?
No. A container is an isolated process that shares the host kernel. Docker Sandboxes are microVMs with their own kernel, filesystem, network, and Docker Engine. A sandbox can run containers, which is why the two are easy to confuse.
Can I run Docker inside Docker Sandboxes?
Yes. Each sandbox has a private Docker Engine, so the agent can build images, run Testcontainers, and start Compose applications without the host socket or Docker-in-Docker.
Do Docker Sandboxes replace containers?
No. Containers remain the right tool for packaged applications, CI jobs, and short-lived execution. Sandboxes add a stronger boundary for autonomous agents, and containers keep running inside them.
What’s the difference between a container and containment?
A container isolates a workload. Containment involves the whole environment around an agent: the boundary it runs in, what it can reach, which tools it uses, and how you reproduce it. A container can be part of containment, and Docker Sandboxes add the microVM boundary an autonomous agent needs.
What does an AI agent need from its environment?
Docker describes four requirements: containment (a boundary the model can’t rewrite), control (visibility into what the agent is doing and the ability to stop it), choice (swapping models, agents, and tools without rebuilding), and capacity (a setup other developers can reproduce and scale past one machine).
What do I need to run Docker Sandboxes?
A supported hypervisor, which means Apple silicon on macOS 14 or later, Windows Hypervisor Platform, or KVM on supported Ubuntu systems. Running inside another VM requires nested virtualization.