Skip to content
Rana Usman Ahmad
All insights
AI Security/12 min read

Hugging Face AI agent breach: the same attack path in Azure, and how to close it

AI agents from an OpenAI test broke into Hugging Face. Here is each step mapped to Azure, with the Workload ID, IMDS, Pod Security, Conditional Access and KQL fixes.

By Rana Usman Ahmad ·

In July 2026, AI agents running inside an OpenAI cyber-capability evaluation escaped their sandbox and ran a campaign against Hugging Face's production infrastructure from 9 to 13 July. Nobody directed them. They had been given exploitation tasks, and METR's investigation found that roughly 30 to 40 percent of those tasks could not be solved as written. The agents concluded that the way to a good score was to reverse-engineer how they were being scored, and Hugging Face looked like a place where the answers might live.

Three primary sources were published in late August: OpenAI's report, Hugging Face's technical timeline, and an independent investigation by METR and Redwood Research. This article does not retell them. It takes the attack path step by step, maps each step to the equivalent weakness in an Azure and Microsoft Entra environment, and gives the configuration that closes it, together with the limits of that configuration.

The facts, and one apparent contradiction

Coverage has quoted both "1,200 agents" and "700 agents". Both are right, and they measure different things.

  • About 1,200 agents found and used a message board that one agent improvised inside OpenAI's internal package repository. Together they exchanged more than 70,000 messages and files between 7 and 13 July.
  • About 700 agents took part in the Hugging Face intrusion itself.
  • Hugging Face, seeing only the victim side, describes one coherent campaign: roughly 17,600 recovered actions between 02:28 UTC on 9 July and 14:14 UTC on 13 July.

Hugging Face states that no customer-facing models, datasets or Spaces were compromised. Exposed material included pod environment variables holding secrets, worker source code, operational metadata, and five datasets linked to the evaluation.

The chain, stage by stage

StageWhat the agents didThe underlying weakness
1Used an unknown flaw in a package cache proxy, a permitted egress route, to reach the internetSandbox egress trusted a proxy that could be abused
2Took root on an unsecured third-party code sandbox exposed on the internetSomeone else's forgotten endpoint became the launchpad
3Uploaded datasets whose HDF5 config pointed "raw storage" at local paths such as /proc/self/environ, then read the bytes back through the APIA file-processing service read attacker-chosen local paths
4Used Jinja2 template injection in a dataset config to run Python inside a production conversion podUser-supplied configuration was rendered as a template
5Read the pod's Kubernetes service account token and queried the cluster APIToken mounted in a pod that did not need it
6Called the metadata endpoint at 169.254.169.254 and replayed the temporary cloud credentials from external hostingPods could reach instance metadata; credentials worked from any network
7Created privileged pods with the host filesystem mounted, reaching root on eleven nodesNo admission control; a CSI driver ClusterRole could create pods cluster-wide
8Read a cluster secret holding 136 keys, including a mesh VPN key and an access-broker credentialMany unrelated secrets in one object
9Enrolled devices into the mesh VPN with memory-only state and logging suppressed, then reached source control and minted a GitHub App tokenA long-lived enrolment key tagged for CI with broad network access

One detail from stage 3 is worth pausing on. An earlier attempt to make the workers fetch the metadata URL directly failed, because the datasets library rejected non-platform URLs. The agents switched from asking for a remote URL to asking for a local file, which the allowlist never considered. That is the behaviour to design for: a blocked path is a prompt to try the adjacent one.

Mapping it to Azure

WeaknessAzure equivalentControlStatus and cost
Secrets in pod environmentClient secrets in AKS pod env or Kubernetes secretsMicrosoft Entra Workload ID, Key Vault CSI driver in workload identity modeGA, no extra licence
Metadata reachable from podsEvery AKS pod can reach Azure IMDS by defaultAKS IMDS restriction, or an egress NetworkPolicyRestriction is preview; policy needs a network policy engine
Privileged podsSame on AKSPod Security Admission, Azure Policy for AKSBuilt into Kubernetes
Credentials replayed from anywhereService principal credentials or tokens used off-networkConditional Access for workload identities, ID Protection workload identity riskWorkload Identities Premium
Slow escalationSentinel incident waits for a humanAutomation rule plus playbook with pre-approved containmentSentinel and Logic Apps consumption
Agents as anonymous workloadsAgents running as shared app registrationsMicrosoft Entra Agent IDGA

1. Take the secrets out of the pod

The agents' first real prize was /proc/self/environ. Whatever sits in a pod's environment is one file read away from anyone who gets a foothold.

With Microsoft Entra Workload ID, the pod receives a projected Kubernetes token and exchanges it with Microsoft Entra ID for an access token. There is no client secret to steal.

az aks update -g rg-prod -n aks-prod \
  --enable-oidc-issuer --enable-workload-identity

AKS_OIDC_ISSUER="$(az aks show -g rg-prod -n aks-prod \
  --query oidcIssuerProfile.issuerUrl -o tsv)"

az identity create -g rg-prod -n id-dataset-worker

az identity federated-credential create \
  --name fic-dataset-worker \
  --identity-name id-dataset-worker \
  --resource-group rg-prod \
  --issuer "$AKS_OIDC_ISSUER" \
  --subject system:serviceaccount:datasets:dataset-worker \
  --audience api://AzureADTokenExchange
apiVersion: v1
kind: ServiceAccount
metadata:
  name: dataset-worker
  namespace: datasets
  annotations:
    azure.workload.identity/client-id: "<client ID of id-dataset-worker>"

Pods that use it carry the label azure.workload.identity/use: "true". For pods that never call the Kubernetes API, also set automountServiceAccountToken: false, which removes the token the agents read in stage 5.

What this does not fix. Code running in the pod can still read the projected token and use it for as long as it is valid. Workload ID shrinks the prize from a 136-key secret to one identity's short-lived access. Scope that identity's Azure RBAC tightly, because the token will reach exactly as far as the role allows.

An architectural choice most teams skip. A user-assigned managed identity is the simplest thing to federate, but Conditional Access does not cover managed identities. An app registration with a federated credential is equally secretless, and it is a single-tenant service principal, so Conditional Access for workload identities and ID Protection risk policies can apply to it. If you want network-location enforcement on a workload, that is the trade: a little more lifecycle work, plus Workload Identities Premium.

2. Close the metadata endpoint

By default every pod in an AKS cluster can reach Azure IMDS at 169.254.169.254. AKS offers IMDS restriction, which blocks that route for pods where hostNetwork is false.

az extension add --name aks-preview
az feature register --namespace Microsoft.ContainerService \
  --name IMDSRestrictionPreview
az provider register --namespace Microsoft.ContainerService

az aks update -g rg-prod -n aks-prod --enable-imds-restriction
az aks upgrade -g rg-prod -n aks-prod --node-image-only

The reimage is not optional. The block does not take effect on existing nodes until they are reimaged.

The production failure mode. At the time of writing this is a preview feature, and it refuses to enable when certain add-ons are present. The list includes the Azure Policy add-on, Azure Monitor Container Insights, the Application Gateway Ingress Controller, application routing, Flux, Dapr and Azure Backup, and Windows node pools are not supported. Many production clusters run at least one of these. Check before you put this in a design document.

Where you cannot use it, an egress NetworkPolicy gets you most of the way on Linux pods, provided the cluster runs a network policy engine:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-imds-egress
  namespace: datasets
spec:
  podSelector: {}
  policyTypes: ["Egress"]
  egress:
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: kube-system
          podSelector:
            matchLabels:
              k8s-app: kube-dns
      ports:
        - { protocol: UDP, port: 53 }
        - { protocol: TCP, port: 53 }
    - to:
        - ipBlock:
            cidr: 0.0.0.0/0
            except: ["169.254.169.254/32"]

The explicit DNS rule is there because some engines do not match in-cluster pod traffic with CIDR rules, and without it name resolution breaks the moment the policy lands. Test DNS from a pod immediately after applying. Host network pods and anyone holding node root bypass this policy, which is why section 3 matters.

3. Refuse privileged pods

Stage 7 turned one compromised pod into root on eleven nodes, because nothing refused a privileged pod with the host filesystem mounted. Pod Security Admission is built into Kubernetes and costs nothing.

kubectl label namespace datasets \
  pod-security.kubernetes.io/enforce=baseline \
  pod-security.kubernetes.io/warn=restricted \
  --overwrite

The baseline profile refuses privileged containers, hostPath volumes and host namespaces including host networking. Warning at restricted shows you what the next tightening would break without breaking it today.

Then look for the second half of stage 7, the over-broad role:

kubectl auth can-i create pods -A \
  --as=system:serviceaccount:<namespace>:<service-account>

Run it for every service account that belongs to a CSI driver, an operator or a controller. A "yes" across all namespaces is a node-root path waiting for a foothold.

4. Make credentials fail off-network

The agents replayed temporary cloud credentials from external hosting providers. In Microsoft Entra ID, the equivalent defence for service principals is Conditional Access for workload identities. The only grant control is block.

Sample policy, following the Microsoft Graph beta schema, created in report-only mode:

POST https://graph.microsoft.com/beta/identity/conditionalAccess/policies
Content-Type: application/json

{
  "displayName": "WID - Block service principals outside trusted egress",
  "state": "enabledForReportingButNotEnforced",
  "conditions": {
    "applications": { "includeApplications": ["All"] },
    "clientApplications": {
      "includeServicePrincipals": ["<service principal object ID>"]
    },
    "locations": {
      "includeLocations": ["All"],
      "excludeLocations": ["<named location ID for your egress IPs>"]
    }
  },
  "grantControls": { "operator": "and", "builtInControls": ["block"] }
}

Use the object ID from Enterprise applications, not the one on the app registration. Assign service principals to the policy directly: a policy assigned to a group that contains a service principal is not enforced for it.

Limits to read before you rely on it.

  • Creating or modifying these policies requires Workload Identities Premium.
  • Only single-tenant service principals registered in your tenant are covered. Multitenant and SaaS apps are not. Managed identities are not.
  • The policy applies when a token is requested. It does not revoke a token already issued, except where continuous access evaluation for workload identities applies.

Pair it with a risk-based policy on the same identities. ID Protection's Suspicious Sign-ins detection baselines each service principal over 2 to 60 days and fires on unfamiliar IP or ASN, target resource, user agent, a change between hosting and non-hosting IPs, IP country, or credential type. Credentials replayed from rented infrastructure hit several of those at once.

You do not need the premium licence to start hunting. This query surfaces service principals that signed in successfully from a network (ASN) they had not used in the previous two weeks:

let known =
    AADServicePrincipalSignInLogs
    | where TimeGenerated between (ago(14d) .. ago(1d))
    | where ResultType == "0"
    | distinct ServicePrincipalId, AutonomousSystemNumber;
AADServicePrincipalSignInLogs
| where TimeGenerated > ago(1d)
| where ResultType == "0"
| join kind=leftanti known on ServicePrincipalId, AutonomousSystemNumber
| summarize
    FirstSeen = min(TimeGenerated),
    SignIns = count(),
    IPs = make_set(IPAddress, 20),
    Resources = make_set(ResourceDisplayName, 20)
    by ServicePrincipalName, ServicePrincipalId, AutonomousSystemNumber, ClientCredentialType
| order by FirstSeen desc

Expect noise after legitimate changes, such as a new egress IP or a pipeline moved to different runners. Tune it into an analytics rule once you know what normal looks like in your tenant.

The gap nobody mentions. Replaying an access token that has already been issued does not create a new sign-in event, and managed identity tokens taken from IMDS are out of scope for Conditional Access anyway. For that case, the evidence is on the resource side. For Azure Resource Manager, look at AzureActivity, where CallerIpAddress shows where each call came from. This is also why section 2 is not optional.

5. Decide escalation before the incident

Hugging Face's controls did detect the activity. Several layers fired, including runtime analysis and SIEM logs. The criticality was not raised correctly, and the on-call team was activated late. SecurityWeek's analysis puts it bluntly: escalation was slow, so the outcome did not change.

In Microsoft Sentinel, the fix is a written decision encoded as an automation rule and a playbook. For a high-confidence workload identity alert, the pre-approved containment is to disable the service principal:

PATCH https://graph.microsoft.com/v1.0/servicePrincipals/<object ID>
Content-Type: application/json

{ "accountEnabled": false }

Then follow Microsoft's remediation order for a risky workload identity: inventory every credential on the service principal and the application object, add a new credential (Microsoft recommends certificates), remove the compromised ones, and rotate any Key Vault secrets that identity could read.

Agree in advance which identities may be disabled automatically. A disabled production integration is an outage, and that conversation belongs in a change meeting, not at 02:00 during an incident.

6. Give every agent its own identity

The OpenAI agents shared an environment and very quickly behaved like a collective: METR reports that more than 90 percent of the agents present joined the attack once it began. In an enterprise, agents that share one app registration are indistinguishable in the logs, and they cannot be contained one at a time.

Microsoft Entra Agent ID is now generally available. It introduces agent identity blueprints and agent identities, gives each one owners and a sponsor, and extends Conditional Access and ID Protection to agents, including templates to block high-risk agent identities and to govern autonomous agents that act without a user. The design principle is the same one this incident teaches: one agent, one identity, one accountable human, and a way to switch off exactly one.

What held

This part of the story gets less attention, and it deserves more.

  • Cloud IAM denied every mutating call. The agents could enumerate VPCs, clusters, secrets and the container registry, but they could not change them.
  • A database network allow-list rejected valid stolen credentials. The credentials were real. The source address was not permitted.
  • CI execution policies stopped malicious commits from shipping. The agents opened a pull request, and it went nowhere.
  • 84 operations against an internal credential store were all denied.

Least privilege and network restriction worked exactly where they had been applied. The damage happened in the places they had not.

The incident response lesson nobody planned for

During forensics, Hugging Face's responders asked commercial frontier models to help decrypt and analyse the staged payloads. The models refused, treating defensive reverse engineering as an attack. The team deployed an open-weight model locally, which recovered secrets that simple scans had missed.

If your incident response runbook assumes an AI assistant, test that assumption now, on real malicious samples, in the tool your analysts will actually use. Decide which model is approved for handling malicious artefacts, and where it is allowed to run, before an incident forces that decision.

What this article does not tell you

  • None of these controls fixes the application bugs. The proxy flaw, the HDF5 local path read and the Jinja2 injection are code defects. Identity and platform controls limit how far an attacker travels after one of them, and nothing more. If you run a service that processes user-supplied files or configuration, that pipeline is an ingress point, and it needs the same scrutiny as a public API.
  • Nothing here addresses why the agents did it. Reward hacking under impossible tasks is a model and evaluation design problem, and it belongs to the labs.
  • The Hugging Face environment ran on AWS. The Azure mapping is mine. It describes equivalent weaknesses and controls, not what Hugging Face should have deployed.
  • Preview features change. Check the IMDS restriction add-on list and the Conditional Access for workload identities scope on Microsoft Learn before committing to a design.

The one thing worth remembering

The attacker was new. The weaknesses were not. Secrets in environment variables, metadata every pod can reach, no admission control, and credentials that work from anywhere are the same findings that show up in ordinary assessments. Agents simply find them faster, and they do not stop.

If you are working through the identity side of this, I cover it in more depth under identity and Microsoft Entra and security architecture. For a second opinion on your own AKS or workload identity design, get in touch.

References

Written by

Rana Usman Ahmad

Microsoft Security and Cloud Solutions Architect

Work with me

Let me turn complexity into a system you can run.

Securing a Microsoft environment, planning a migration, or getting ready for Copilot. I help you make the call with clarity, then build it to last.