Run XDRInternals as GitHub Action
When Nathan and I released XDRInternals one of the biggest shortcomings for me was the lack of workload identity support. Since we are using the native API of the Defender portal only delegated permissions are supported, which makes it very hard to automate things in a pipeline.
But the fact that it makes it very hard should not prevent you from doing it. Security considerations and common sense are the reasons you should not do it, but let’s throw them overboard for the fun of it.
Act 1 : Authenticate as a user, as a machine
It’s 2026 and agentic AI is the buzzword everybody on LinkedIn is posting about. This will be not about this technology. I was thinking about a way to sign-in as a “regular” user inside of a pipeline for a long time and tools like ROADtools Token eXchange or AADInternals allow you to automate those authentication flows including support for TOTP.
But on my way home from the family Christmas party I came up with an “even better” idea. Why not use the latest and greatest in security to authenticate:
Passkeys (or Huskys, as Google Assistant wrote in my voice memo)
Both of the before mentioned tools do not (as of now) support this flow natively and I thought it would be a nice coding task.
So I spun up Fiddler and authenticated using a Passkey to a Entra ID client, in this case the security portal. This already gave me a good idea of how the actual flow should look like.

Next I looked into options to export the key material. An easy task if you use a third passkey provider like KeePassXC, Bitwarden, you name it. With the native platform providers like Google and Apple this is much harder, as they don’t support any export capability.
I settled on KeePassXC and the browser plugin for my testing, but also validated an exported Bitwarden passkey in the process.

Take this serious! It’s not a good idea to export the private key to an unencrypted medium.

And what does a passkey look like on the inside, you might ask? Basically it’s just another JSON file that contains all the information you need to sign a challenge provided by the relying party (aka Microsoft Entra ID).

With this out of the way I explored the best options to sign a FIDO2 challenge programmatically without relying on custom PowerShell classes or DLLs. And when you restrict yourself to PowerShell 7 this is very easy, as Microsoft has the required functions and crypto built in.
I won’t go into the details, but I implemented everything into TokenTacticsV2. Take a look at the code yourself if you are curious.
After some trial and error with the actual flow I finally was able to implement the flow as a new function called Invoke-EntraIDPasskeyLogin and it supports native KeePassXC passkey exports or you can provide the information bit by bit yourself.
Let’s put it to the test locally and inside a GitHub action.
Act 2: Run little security issue, run!
On my local machine I tried my new toy by connecting to a tenant, as vanilla as they come, thanks to Nathan. And after the successful sign-in using the stored passkey credential I was able to use the resulting ESTSAUTH cookie to connect to the Defender portal using XDRInternals and query the Advanced Hunting settings. Neat!
Now I used the values from the passkey file and added them to a GitHub repo as secrets and variables.



Then I created a GitHub action that authenticates via those variables and extracts the same XDR Advanced Features as the local script
name: Authenticate and run XDRInternals for glamor and glory
on:
workflow_dispatch:
permissions:
contents: read
id-token: write
jobs:
# Build job
build:
runs-on: ubuntu-latest
steps:
- name: Checkout TokenTactics repository
uses: actions/checkout@v6
with:
repository: f-bader/TokenTacticsV2
ref: passkey
path: TokenTacticsV2
- name: Checkout XDRInternals repository
uses: actions/checkout@v6
with:
repository: MSCloudInternals/XDRInternals
path: XDRInternals
- name: Authenticate via passkey and run XDRInternals
shell: pwsh
run: |
Import-Module $env:GITHUB_WORKSPACE/XDRInternals/XDRInternals/XDRInternals.psd1
Import-Module $env:GITHUB_WORKSPACE/TokenTacticsV2/TokenTactics.psd1
$Parameters = @{
UserPrincipalName = "${{ vars.PASSKEY_USERNAME }}"
RelyingParty = "${{ vars.PASSKEY_RELYING_PARTY }}"
UserHandle = "${{ secrets.PASSKEY_USER_HANDLE }}"
CredentialId = "${{ secrets.PASSKEY_CREDENTIAL_ID }}"
PrivateKey = "${{ secrets.PASSKEY_PRIVATE_KEY_PEM }}"
}
# This will expose the ESTSAUTH cookie as a global variable $ESTSAUTH
Invoke-EntraIDPasskeyLogin @Parameters
Connect-XdrByEstsCookie -EstsAuthCookieValue $ESTSAUTH
Get-XdrEndpointAdvancedFeatures
And like all my GitHub actions, it worked on the first try ;)

Wonderful. This means I can now sign-in to Entra ID from inside a GitHub pipeline using a passkey and fulfil a strong authentication requirement. Still, if this is a good thing is not what we discuss today. Today we do fun stuff.
Act 3: Profit Build a community resource
Of course just running this inside of a GitHub action is not really that helpful. But the data provided inside of any XDR Defender tenant is. For example there is the full schema reference for all Advanced Hunting tables. To stay on top of the changes made to those tables can be an almost impossible task on your own. But now with the power of automation I can revive one of my projects from long ago:
xdrinternals.com - A website that tracks the changes to the schema like new ActionType values, new tables and updated descriptions.
The last manual update was done in late 2024, but as with most manual tasks, I never came around to do it on a regular schedule. Now I’m able to. After some minor changes to my script from back then I now have a fully working solution that updates itself on a daily basis.
Next steps will include updating the website in a way that it also incorporates the XDRinternals module documentation, update the theme and open-source the complete script.
If you should run this to automate real world tasks in your tenant? That’s for you to answer, but GitHub secrets are a safe way to store the private key. Of course you must make sure to never expose the secrets.