A single POST to management.azure.com, with a request path full of %2e%2e sequences, returned the plaintext value of a secret stored in a different tenant's Key Vault. The request went through a connection the attacker owned, but the token that actually read the secret belonged to the victim. That is one of five critical cross-tenant elevation-of-privilege bugs a researcher found in Azure API Connections, the layer underneath Azure Logic Apps connectors. The set paid out $200,000 and was presented at Blue Hat Asia 2026.
How API Connections work
An API Connection stores credentials for a backend: a Key Vault, an Azure SQL database, Jira, Salesforce, anything a Logic App connector can talk to. When a Logic App runs an action, it calls a shared Azure API Management (APIM) instance. APIM checks the connector's OpenAPI definition to confirm the action is valid, swaps the caller's token for the token configured on the connection, and makes the real call to the backend with that second token.
The list of backends is effectively unbounded, and that is where the trouble starts. Whatever credential sits on a connection is usually the full-privilege one. For OAuth connections it is the token of whoever created the connection. For API key connections, the researcher's bet is that almost nobody scopes the key down. Microsoft's own connectors don't.
The attack surface opens because you can reach APIM directly through the Azure Resource Manager REST API, via the DynamicInvoke endpoint on a connection resource. ARM, as the resource manager, has rights to every connection regardless of which one the request names. Make the request resolve to a connection you don't own and you get the victim's configured token applied against their backend.
Five bugs, one root cause
The first bug was plain path traversal through DynamicInvoke. Microsoft marked it fixed, but the fix restricted the allowed paths rather than addressing the token reuse underneath.
The researcher then provisioned a Standard Logic App, which gets a dedicated host, SSHed in, and pulled the relevant ARM resource-type code off the machine. The code showed the fix, and also showed that DynamicInvoke was one of four sibling functions along the same path. The others, including DynamicList, were undocumented and reachable. Since the path blacklist covered only DynamicInvoke, DynamicList still carried the traversal. A crafted DynamicList call ran an arbitrary INSERT against a victim's Azure SQL server through their connection.
The researcher reported DynamicList on its own, expecting a targeted fix that would leave the remaining siblings to report later. Microsoft instead blocked all of those endpoints and added no other mitigation. If they ever come back, the traversal comes back with them.
That closed the ARM route, so the next way in was the Logic Apps themselves. Consumption Logic Apps, which are co-located, create Version 1 connections; Standard Logic Apps, which run on a dedicated host, create Version 2. A Standard app authorizes itself to a backend using its managed identity. A Consumption app has no identity to authorize, which means every Consumption Logic App has implicit access to every Version 1 connection. The only thing in the way is input validation.
The workflow GUI won't let you point an action at a connection you don't own; the runtime validates ownership. The code view and the API accept more parameters than the GUI shows, though, and one of them is host.api.RuntimeUrl. After validating the connection named in host.connection, the runtime lets you override the runtime URL with something else. As long as the host ends in azure-apihub.net, it still attaches the required Authorization header. Point it at a victim's connection and the victim's token goes along.
Microsoft's fix required the runtime URL to match the connection's expected endpoint exactly. That is a reasonable rule, except the check runs before the path is normalized, so the validator sees a URL pointing at your connection while APIM resolves it to the victim's. The same cross-tenant read came back as a separate bug. When Microsoft fixed that normalization gap, their change stopped validating the original RuntimeUrl path, which reopened the first RuntimeUrl attack.
What to take from it
Every one of these is the same underlying issue reached a different way. By the researcher's own guess the connection's stored credential rarely gets scoped down, and the only barrier between an attacker and that credential is input validation that has to be perfect across path traversal, undocumented sibling endpoints, parameter overrides, and validation ordering. Four rounds of fixes all targeted the symptom.
The one control in your hands is the caveat the researcher keeps flagging. Scope the credential on every API Connection to the least privilege the connector needs. An OAuth connection inherits the creator's access, so build connections with a dedicated, narrowly permissioned identity rather than an admin's account. An API key connection is only as dangerous as the key, so provision keys that can do one thing. None of that stops the traversal, but it bounds what a successful traversal gets.
Top comments (0)