Blog / Threat Analysis

Key Eater Is Not a Worm. It Is Axiom X in a Loop.

Stolen app secrets, 27 organisations, MSPs and their customers, and no exploit or malware anywhere. Todyl’s report on the multi-MSP Entra ID campaign of 1–3 November 2026, classified step by step with TLCTC v2.7.

BK
Bernhard Kreinz
• • ~10 min read • TLCTC v2.7

At 08:45 UTC on 1 November 2026, someone signed in to an MSP’s Microsoft Entra ID tenant as one of its applications, using that application’s client secret. Over the next two and a half days the same operator signed in as more than 33 applications at about 18 organisations, searched about 25 tenants, downloaded 81 files, accessed about 2,279 mailbox items and swept 15 customer tenants of one MSP. In one case it downloaded a connector archive and signed in as that connector’s app about two and a half minutes later.

Todyl, whose MXDR team saw the campaign in its customers’ logs, published the investigation on 3 November and calls the actor “Key Eater”: “An identity attack that moves like a worm. Here’s why it isn’t one.”[1] Cyber Security News picked it up a day later.[2] Todyl’s explanation of why it is not a worm is operational: automation, a person steering it, no self-spreading code. TLCTC gives the same answer as a classification, and the classification also shows how the campaign spreads.

Everything below rests on Todyl’s report. The investigation is ongoing, the victims are not named, and Todyl’s figures cover only organisations that send it logs. Where the evidence stops, I say so.

What Todyl reported

All confirmed malicious activity comes from one address, 199.19.77.84, which Todyl attributes with high confidence to a single operator working from one VPS. The operator is not attributed. How it got the first secrets is not known.[1]

When (UTC) What happened
Before 1 Nov A stockpile of stolen app secrets is assembled. Several had only ever been used from staff devices and office networks, and some arrived as current-and-expired pairs, which in Todyl’s reading points to a file store or live backend rather than configurations crawled from the internet.
1 Nov, 08:45 First sign-in as an application with a stolen client secret. For nearly every credential the operator then requests an Azure Resource Manager token and a Key Vault token, often followed within seconds by Graph, Storage and Cognitive Services tokens.
1 Nov, 14:39 An Azure DevOps federated workload identity is used from the operator’s address.
2 Nov, 10:39 First replay of a user’s Azure DevOps session token. Two users’ sessions are replayed that day.
2 Nov, 17:40 First Microsoft Graph search inside a victim tenant: 29 single-wildcard (*) searches across about 25 tenants in all.
2 Nov, 20:36 App-only SharePoint downloads begin at about one file per second, aimed at file names related to credentials and configuration. 81 files in total; about 2,279 mailbox items accessed.
1–3 Nov Harvested material is used to sign in to further apps within minutes, in three MSP families. One connector archive is downloaded, and its app signs in about 2.5 minutes later.
3 Nov, 07:56 One MSP’s multi-tenant app sweeps 15 client tenants in loops of about ten seconds. Detections fire at 07:57; the operator switches from curl and Python to spoofed browser user agents. One MSP’s app obtains a Partner Center token; its GDAP automation app is tried against client tenants and fails.
3 Nov, 15:16 Last successful action: app-only downloads at one MSP.
3 Nov, 18:12–21:31 Several users’ refresh tokens replayed with scripted clients; all blocked by Conditional Access. The latest attempt, at 21:31, is blocked too.

Todyl also reports what it did not see. No Microsoft vulnerability is involved. No persistence was set up: no new consents, credentials, roles or users. And although integration credentials of security tools were among those abused, including Todyl’s own integration apps in two MSP tenants, there is no sign that any vendor platform was compromised.[1]

The path: two clusters and a gap

Written in TLCTC notation, with the intervals the report supports:

…
 →[Δt=?]     #4 ||[auth][@External→@MSP]||  [fanout=~33]
 →[Δt≤33h]   #1 + [DRE: C]
 →[Δt≈2.5m]  #4  [propagation=recursive]
 →[Δt=?]     #4 ||[auth][@External→@Customer]||  [fanout=15]
Step What happened Why this cluster
… The seed secrets were obtained Something happened before 1 November, but neither the number of steps nor their clusters can be defended yet. That is what the unresolved-gap operator is for: an open analytical task, excluded from statistics, carrying no DRE.[3] See “What is still open”.
#4 Sign-in as an app with a client secret; an Azure DevOps federated credential; replayed Azure DevOps session tokens Presenting a credential to authenticate is always #4, however it was obtained (R-CRED). A secret, a federated assertion and a session token are three kinds of credential, not three clusters. The token requests that follow (ARM, Key Vault, Graph, Storage) are part of the same authentication: each is the identity platform issuing a token to the identity that just authenticated.
#1 + [DRE: C] Wildcard Graph searches, then app-only downloads at machine speed; mailbox items read Search and download are designed functions of Graph and SharePoint, used as built with the permissions the app holds. Not Abuse of Rights: a stolen credential never confers a grant, so the scope question is answered before it is asked (R-SCOPE).[3] The files and mail are disclosed: [DRE: C]. So are the secrets inside them, and that is where the loop begins.
#4 A harvested secret is used within minutes The connector’s secret was acquired by the #1 step that downloaded it, and its use is a new #4 (Axiom X). The new identity can search and download again.
#4 across the boundary One MSP’s multi-tenant app signs in to 15 customer tenants Still #4, not #10. The boundary crossing is annotated, not reclassified. See “A trusted relationship without a supply chain attack”.

The [fanout=…] and [propagation=recursive] annotations are the interim convention of the replication notation proposal: write the sub-path once and say how often it repeats (more than 33 apps at about 18 organisations, 15 customer tenants), instead of writing 33 sign-ins or pretending the recursion has a known depth. They annotate the path; they do not classify anything.

Counted over the path, the campaign uses two generic vulnerabilities: credentials that work for whoever presents them, and functions that do what the identity behind them is allowed to do. No #2 or #3, because no flaw was exploited. No #7, because no foreign code ran in any victim’s environment. No #10, because no trust artifact was subverted.

Why it is not a worm, and why it spreads anyway

A worm, in TLCTC terms, is a path that repeats a step in which foreign executable content runs in the target, and that content carries the next round itself. R-EXEC places a #7 at every such execution. In Key Eater there is none. The curl and Python clients run on the operator’s VPS, outside every victim’s domain; they are the attacker’s tools, not steps. What reaches the victims is a stream of authenticated API calls.

The spreading comes from Axiom X. Credentials are control elements with two lives: acquiring one is data leaving the system, a [DRE: C] on the step that obtained it; applying one is always #4.[3] When the data that a #1 step discloses are credentials, every turn of the path produces the material for the next turn:

#4 → #1 + [DRE: C] →[Δt≈2.5m] #4 → #1 + [DRE: C] →[Δt≈minutes] #4 → …

That is self-propagation through a credential lifecycle, not through code. It is why the campaign reaches downstream tenants without anything installed, why a worm label would send defenders looking for the wrong artefact, and why the decisive control is the one that makes harvested material worthless (below). Todyl’s diagram draws exactly this: “New secrets” as an asset box, an arrow back to “Sign in as app”, and the label “Reused within minutes”.[1]

Todyl holds AI involvement at low confidence: the speed and consistency fit automated, possibly agentic tooling, while multi-hour idle gaps and the user-agent switch after detection show a person steering it. For the classification this does not matter. Whether a script, an agent or a person sends the request, the request authenticates with a stolen secret and calls a designed function. AI changes the tempo of the loop, not its clusters.

A trusted relationship without a supply chain attack

Todyl lists seven ATT&CK techniques; its attack-chain diagram adds two. Mapped to TLCTC, with this case’s evidence:[5]

ATT&CK Here Note
T1078.004 Cloud Accounts #4 Sign-in as a service principal.
T1550.001 Application Access Token
T1550.004 Web Session Cookie
#4 Replayed Azure DevOps sessions and refresh tokens. A replayed token is a presented credential.
T1580 Cloud Infrastructure Discovery
T1526 Cloud Service Discovery
no separate step yet The evidence is ARM tokens being issued. Discovery becomes a #1 step when the record shows an inventory being read.
T1555.006 Cloud Secrets Management Stores #1 + [DRE: C] [conf=low] Key Vault tokens were issued; that secrets were read needs the Key Vault audit logs, which Todyl says are required to confirm it.
T1213.002 SharePoint
T1530 Data from Cloud Storage
#1 + [DRE: C] Wildcard search and 81 downloads.
T1199 Trusted Relationship #4, not #10 The MSP’s multi-tenant app, presented in customer tenants.

Nine technique IDs, two clusters. The last row needs an argument, because the TLCTC ATT&CK mapping starts T1199 at #10, and because an incident that runs from MSPs into their customers will be called a supply chain attack.

Since v2.6, #10 requires that the trust artifact, or the third party issuing it, was subverted before the target accepted it (R-SUPPLY). For federation the core paper is explicit: presenting a credential or assertion to authenticate as another identity is #4, wherever it is presented and however it was obtained; a party honouring an assertion from an issuer that was not subverted is the trust link working as designed, not a step.[3] That is the situation here. The customer tenants accepted tokens that Entra ID issued, correctly, to the MSP’s app, because the attacker presented that app’s secret. Neither Entra ID nor the MSP’s authority to administer its customers was subverted; one of the MSP’s credentials was stolen. The same holds for the security-tool integrations: Todyl found no sign that any vendor platform was compromised.

So the crossing into the customer is #4, annotated with the boundary it crosses: ||[auth][@External→@Customer]||. Internal clusters may cross domain boundaries in multi-tenant and partner contexts; the crossing goes into the notation, the cluster stays the same.[3] The difference has practical weight. A #10 would send the response to the supplier’s build and distribution. A #4 sends it to the credential: where the secret was stored, who could read it, and whether it works for anyone who holds it.

The failures are evidence too

Todyl’s diagram has a legend most incident graphics lack: a dashed red box for a failed path (the GDAP attempt) and a dashed blue box for assessed, not directly confirmed (a password spray from a neighbouring address). TLCTC makes the same two distinctions in its notation.

A cluster assignment asserts that the step succeeded, so failed attempts are not path steps. They are not discarded either. TLCTC+ v0.9 records them as near misses, written [Attempt: …], with no SRE, DRE or BRE:[4]

// GDAP automation app against client tenants
[Attempt: #4 ||[auth][@External→@Customer]||]
// refresh-token replays, 18:12–21:31, blocked by CA
[Attempt: #4]
// retries with expired or rotated secrets
[Attempt: #4]
// Azure CLI password spray, 16 accounts, all failed
[Attempt: #4] [conf=low]

The password spray carries low confidence because its link to the operator is only shared hosting: Todyl rates it low to moderate, and the pattern also fits generic spraying. The application companion counts near misses per cluster as Key Risk Indicators, next to effective compromises.[3] Todyl’s advice points the same way: “Failed attempts matter alongside successful ones”, and detection logic keyed on successful sign-ins will miss the failures. Here the failures are a #4 signal: AADSTS7000215 (invalid secret) and AADSTS7000222 (expired secret) from a hosting provider mean that someone holds your app’s secret history, not only its current secret.

The edge that cannot be detected in time

Each causal transition has an interval, Δt, and the interval bounds the window for detection and response.[3] The edges in this path have very different windows.

From the first #4 to the first collection: up to 33 hours. The first app sign-in was at 08:45 on 1 November, the first Graph search at 17:40 the next day. An alert on service-principal sign-ins from a hosting provider, triaged by a human, fits easily into that window.

From a harvested secret to its use: about 2.5 minutes. Measured as DCSd = TTDP90 / Δt, a SOC whose 90th-percentile time to detect is one hour scores 24 on this edge; anything above 1 means detection arrives after the transition. This edge cannot be detected and answered by people. It can be cut by automated response, or prevented by making the harvested material useless, and the second is more robust.

From containment to effect: up to the token lifetime. Removing a secret does not revoke what it already obtained: “Issued tokens otherwise stay valid until expiry,” in Todyl’s words. As in the Binance case, containment has to reach where the authority lives.[6] Here that is in two places: the credential, and every token already issued against it.

Break the loop at the acquisition, not at the use

A secret that works for whoever holds it turns every [DRE: C] on a document store into a potential #4. A workload identity that cannot be presented from anywhere else — a managed identity, workload identity federation bound to a specific issuer and subject, a certificate held in a vault — turns the same disclosure into a dead end. That is the control that removes the 2.5-minute edge instead of racing it.

Business events: none in the record yet

The victims are not named, and Todyl’s report describes what happened in their tenants, not what followed in their businesses. On the evidence, the path therefore carries no Business Risk Event. The first one is not hard to name: the MSPs held, in their document stores, the credentials and configurations with which they run their customers’ security — MFA and RMM API keys, backup-storage credentials, firewall and VPN configurations, in Todyl’s list of what such files contain. That they have left the MSP is an event in the managed service itself, and it is that service’s BRE1. In the TLCTC+ catalogue it takes two codes, one per role the material plays: it is held for the customers, and it controls their systems.

What will follow almost certainly is listed here, with TLCTC+ catalogue codes, as expected rather than recorded:[4]

Expected BRE Edge Why it is expected
(BRE-DATA.11 Entrusted Data Exposed + BRE-DATA.13 Control Material Exposed) causal, from [DRE: C] BRE1, once each MSP confirms what the documents the apps read contained: customer data held in trust (.11), and the credentials, keys and configurations that run the customers’ security (.13). Using that material would be a further #4 step; the code does not assert that it happened.
BRE-ORG.14 Internal Investigation Opened causal At each affected MSP and customer. Todyl’s own investigation is reported; the victims’ are not.
BRE-REG.11 Mandatory Notification Obligation Triggered normative, no Δt About 2,279 mailbox items and client documents almost certainly contain personal data, and several downstream victims are in government, energy and transport. Todyl advises treating confirmed access as a potential data-exposure event and involving legal and privacy early. The duty sits on BRE-DATA.11, not directly on the DRE.
BRE-LGL.16 Evidence Preservation Obligation normative, no Δt Todyl tells MSPs to preserve audit logs.
BRE-3P.12 Managed Service Disruption Observed causal At the customers, once MSPs disable affected apps and rotate secrets, as Todyl recommends. The containment itself produces this one: the abused apps are the integrations the service runs on.
BRE-ORG.16 Control Remediation Program Opened causal Retiring client secrets and rotating everything in every vault and document the apps could reach is a programme, not a ticket.
BRE-LGL.13 Contractual Claim by Counterparty causal Possible between customers and MSPs. Todyl tells MSPs to notify customers under their agreements; whether claims follow is open.

One lever per step, and who holds it

Todyl’s response guidance is thorough. Sorted by the step each measure acts on, it also shows that the levers sit with different owners, and that one of them is not where a customer would look for it:

Step Lever Typical owner Examples
… (seed) Where secrets exist at all App owners; endpoint security Retire client secrets: managed identities first, then workload identity federation, then vault-held certificates. App management policies that block new secrets and cap lifetimes. Infostealer defence on developer and admin endpoints.
#4 (MSP tenant) Who may present the credential, from where Identity team, MSP Conditional Access for workload identities, assigned directly to service principals; alerts on service-principal sign-ins from hosting providers and on one address authenticating across unrelated tenants.
#1 + [DRE: C] What the app may read Tenant admin, with the app owner Least-privilege application permissions: site-scoped rather than tenant-wide file access, mailbox access restricted to the mailboxes the integration needs. Key Vault and Storage limited to selected networks or private endpoints. Detection: wildcard search followed by app-only downloads at one per second.
#1 → #4 (loop edge) Whether disclosed material can be used App owners; whoever runs the document stores No credentials in SharePoint, runbooks or connector archives; secret scanning over document stores. Where a secret must exist, it should not work from outside its workload.
#4 (customer tenant) Which MSP apps may act in the tenant, with which rights The MSP, and the customer’s tenant admin for consents and GDAP Inventory of multi-tenant apps and GDAP relationships, least-privilege and time-bound roles, unused consents removed.

The last row is the uncomfortable one. Todyl notes that workload-identity Conditional Access requires a separate licence and covers only single-tenant apps registered in your own tenant: not managed identities, not multi-tenant apps, not third-party SaaS. The app that swept 15 customer tenants was a multi-tenant app. The customer can decide which MSP apps exist in its tenant and with what rights; it cannot decide from where the MSP’s app may sign in. That control lives in the MSP’s home tenant and in how the MSP keeps its secrets. A customer’s risk register should therefore name the MSP as the owner of that #4, and the contract should say what the MSP does about it.

What is still open

Three points depend on evidence Todyl does not have yet, and the record should change as it arrives:

  • The seed acquisition. Todyl names candidates without tying any of them to the campaign. Secrets taken from a file store or backend with a stolen credential would be #4 → #1 + [DRE: C]; secrets taken by an infostealer would be #7 + [DRE: C], after whatever delivered it. Todyl also mentions tampered builds of a coding-assistant extension served for about eleven hours in Cursor’s extension marketplace on 29 October; if an affected organisation had installed one, that start would be #10 → #7 + [DRE: C]. Todyl has not tied any affected organisation to an install. Until one of these is evidenced, the gap stays ….
  • Key Vault. The ARM-then-Key Vault token pattern, followed within minutes by apps that had never been used, suggests a second loop through Key Vault. Whether secrets were actually read there needs the Key Vault audit logs. Until then that step is #1 + [DRE: C] [conf=low], and the confirmed loop runs through SharePoint.
  • AI-service tokens. Three Cognitive Services tokens and one Azure ML token were issued at two MSPs. If they were used to consume the victims’ AI resources, that is a further #1. A token being issued, as Todyl notes, does not show that it was used.

One point is settled, although an early update left it open. Todyl first listed the October 2026 ConnectWise Cloud Backup compromise attributed to Storm-0940 as a possible connection, and in its later update assesses it as unrelated.[1]

A secret that works for anyone

Key Eater needs no new category. It is #4 and #1, repeated, with a gap at the start that the investigation has yet to fill. What makes it behave like a worm is a property of the credentials, not of any code: a client secret authenticates whoever presents it, and the documents that the abused apps can read are full of them. Each disclosure feeds the next sign-in, which is Axiom X turned into a loop. The loop stops where acquired credentials stop working for the person who acquired them.

You cannot detect your way past a 2.5-minute edge. You can make sure there is nothing worth taking on the other side of it.

Sources

  1. [1] Andrew Scott (Todyl), “Stolen App Credentials, Real Access: An Actor Is Signing Into Entra ID and Azure DevOps as Your Workloads”, Todyl blog, published 3 November 2026, updated 4 November 2026, including the investigation updates of 3 November and the “Observed Attack Chain” diagram. All facts, times and figures in this post are taken from it.
  2. [2] Guru Baran, “Hackers Use Stolen Microsoft Entra ID and Azure DevOps Credentials to Access 27 Organizations”, Cyber Security News, 4 November 2026.
  3. [3] TLCTC v2.7 core paper: §3.3 (boundary crossings by internal clusters), §3.4 (consequence chain, Δt), §3.5 (cause-side partition and R-SCOPE), §4 (#1, #4, #10 and the subversion test), §5 (Axiom X), §6 (R-CRED, R-EXEC, R-SUPPLY), §7.2 (Δt and DCS), §7.7 (unresolved steps); application companion §10 (KRI, near misses). Glossary: credential acquisition, credential application, R-SUPPLY, KRI. The classifications in this post are my application of TLCTC to Todyl’s statements, not classifications made by Todyl.
  4. [4] TLCTC+ v0.9: near-miss records [Attempt: …], BRE catalogue v0.4 (BRE-DATA family). See also The Cyber Bow-Tie.
  5. [5] TLCTC mapping of MITRE ATT&CK Enterprise v20; T1199 is mapped there as #10 → #7 by default, with the presentation of a partner’s credentials as #4.
  6. [6] Binance Stopped Withdrawals. The Theft Wasn’t Using Them. (TLCTC blog, 4 November 2026). On fan-out and recursion without new clusters, see also A Swarm Is a Shape, Not an Actor.

TLCTC Framework · v2.7 · CC BY 4.0