tldr: Azure DevOps can be accessed using multiple 1st party client ids, allowing anyone to pivot from a stolen session to access the repositories.
When we speak to people about how secure our solution, Attic, is, we always state that the best way to attack it would be via Azure DevOps. At the time, we didn’t know exactly how, but we knew it would be far easier than hacking a backend API and pivoting across multiple segments—all while dodging segmentation, honeypots, and monitoring rules.
Recently, we performed an Azure review for a client using Azure DevOps pipelines to create their entire infrastructure as code. This gave us the opportunity to think more deeply about access to DevOps. As we began the assessment, our friends at FalconForce published an excellent blog on the subject.
Midway through the assessment, we had the chance to attend the Outsider Security training, “Offensive Entra ID (Azure AD) and Hybrid AD Security.” One of the challenges required us to use the family of client IDs (FOCI) to switch between clients. As noted in Secureworks documentation: “Undocumented functionality in Azure Active Directory allows a group of Microsoft OAuth client applications to obtain special ‘family refresh tokens,’” enabling access from one M365 resource to another.
That’s when we began wondering about DevOps. If access to a Teams refresh token allows access to Azure PowerShell or Outlook, could it also grant access to DevOps?
Testing for access to the DevOps resource.
We wanted to know how to get access to the Azure DevOps resource. To do this, we needed to determine the resource identifier. Azure DevOps uses "499b84ac-1321-427f-aa17-267ca6975798/.default"
as its resource ID.
We were curious if, and which, FOCI client would give access to the DevOps resource. To investigate, we figured it would be a good start to take a list of client IDs, authenticate to them, and request access to the Azure DevOps resource. We used the Secureworks CSV of FOCI clients for this purpose. That way, we could pivot from Outlook or Teams refresh tokens to DevOps.
We built a Python script to automate the process of identifying Azure AD clients that have access to Azure DevOps. Here’s how it works:
- Reads a list of client IDs from a foci.csv file
- For each client ID:
- Refreshes the authentication token using the roadtx tool
- Loads the new access token from .roadtools_auth
- Tests access to Azure DevOps via API by attempting to list repositories.
As output it generates a text file called: devops_capable_clients.txt. A file containing all client IDs that successfully accessed DevOps.This is how we found the following Foci clients give access to Azure DevOPS:
- Microsoft Azure CLI (04b07795-8ddb-461a-bbee-02f9e1bf7b46)
- Microsoft Azure PowerShell (1950a258-227b-4e31-a9cf-717495945fc2)
- Microsoft Office (d3590ed6-52b3-4102-aeff-aad2292ab01c)
- Visual Studio (872cd9fa-d31f-45e0-9eab-6e460a02d1f1)
What this means is that if you phish a developer or steal their cookies. Access to DevOps is trivial. We can use the accesstoken to get a PAT token allowing us to use the repo without being bothered by conditional access.*
The client IDs that have access to the DevOps resource have been submitted to Roadtx. Additionally, Dirk-Jan has identified more client IDs. Roadtx can now be used to determine which clients can provide access to the resource:

Demo time
So how would this work? We can demo this by doing a simple device code phish using MS Authenticator (another foci client) as a lure. Step one is to start our device code phish asking for MsAuthenticator access:
roadtx auth --device-code -c 4813382a-8fa7-425e-ab75-3b753aab3abb
Then we switch to one of our newly found clients (in this cale Microsoft Azure PowerShell) and our DevOPS resourceid:
roadtx refreshtokento -c 1950a258-227b-4e31-a9cf-717495945fc2 -r 499b84ac-1321-427f-aa17-267ca6975798/.default
List all repositories we have access to:
python main.py --token $(jq -r '.accessToken' .roadtools_auth) repositories



Bonus: creating a PAT token using the accesstoken we’ve just acquired.

Initially, we wanted to build a tool to audit DevOps. However, during our research, we discovered that other parties are already working on this 😉. So, for now, the screenshots are all we have.
Mitigation
Theres lots going on in regards to conditional access policies handling device code phishing and preventing AiTM. These measures are important, but specifically, we would advise organizations to implement a conditional access rule with an IP filter for DevOps. If an attacker uses PAT tokens or accesses Git, any other conditonal access that an IP filter will not work.
You can find that via: organization > settings > policies. Enable.

For more information on hardening check out the FalconForce blog.
Why should you care
Well it depends. For us DevOPS is super important we store all our check and fix code there. For other organizations that employ pipelines or agent pools to deploy code it could be a way to gain quick access to identities by abusing those pipelines. While much of the focus in security is on protecting the production environment and ensuring stability, DevOps environments can be an attractive target. Attackers could use it to compromise developers and pivot into production systems from there.
Usefull trick
All DevOps API calls require you to know the organization name. If you have an accesstoken for the DevOps but dont know the name of the organization you can use the token to retrieve this:
1.get the user profile of the user we compromised:
https://app.vssps.visualstudio.com/_apis/profile/profiles/me?api-version=7.1
2.get the organizations that user belongs to:
https://app.vssps.visualstudio.com/_apis/accounts?memberId={UserID}?api-version=7.1
3.get the repositories inside of that organization:
https://dev.azure.com/{org_name}/_apis/projects?api-version=7.1

I’ve uploaded all my scripts to a repository. Its not done and ready to use. But it might be usefull for you furture development.
* Depends on the configuration.