Skip to content

Leaver access check

Who this is for: IT and security teams at a bank who need to prove that access is removed when someone leaves, not only that a ticket was filed.

Before you start: a Microsoft 365 connection named m365 (see Connect Microsoft 365), the connections github-org and service-desk, and the company variables github_org, entra_owner and github_owner.

Your HR system calls a webhook when someone leaves, with their employee id, their user principal name (UPN), and, if HR or IT tracks it, their GitHub handle. The workflow checks whether their Microsoft Entra ID account is still enabled and whether their GitHub handle is still a member of your organisation. For each system where access remains, it opens one service desk ticket and emails the system owner, so the removal is tracked, not only hoped for. When the Entra ID account is gone or already disabled, and the GitHub handle is no longer a member, it does nothing and sends nothing. If Microsoft refuses the check, for example because a permission is missing, the run fails instead of treating the leaver as gone.

If the GitHub handle isn’t in the event, the GitHub check is skipped rather than guessed at: better to check Entra ID alone than to check a wrong handle.

The workflow only reads. It never disables an account or removes anyone from a group itself: a person acts on the ticket. To remove Microsoft 365 access automatically, with a dry run first, see Offboard a leaver in Microsoft 365.

  • m365: a connection made from the Microsoft 365 (Microsoft Graph) template, read-only for this workflow. It uses only the ready-made action library/ms365.get-user@v1 and asks Microsoft only whether the account is enabled, so the application permission it needs is User.Read.All. Microsoft lists User.EnableDisableAccount.All together with User.Read.All as the least privileged way to read whether an account is enabled, so if the check can’t see it, grant that pair. Don’t grant any permission that changes data for this workflow.
  • github-org: a GitHub connection, read-only for this workflow. It only calls GET /orgs/{org}/memberships/{username}, so a fine-grained token with Members: Read-only on your organisation is enough.
  • service-desk: a generic REST connection with permission to create tickets (POST /api/tickets).
  • No secret value is ever in the workflow. Each connection holds its own credential; the workflow only names the connection.
  • It emails the owners named in entra_owner and github_owner, never the leaver.
  1. Create the Microsoft 365 connection from its template, name it m365, and grant its app only User.Read.All (see Connect Microsoft 365). Create the other two connections, named exactly github-org and service-desk (see Create a connection).
  2. Add the company variables github_org (your GitHub organisation’s login), entra_owner (who owns Entra ID account removal) and github_owner (who owns GitHub org membership) (see Use company variables).
  3. Point your HR or identity system’s leaver event at the workflow’s webhook endpoint, leaver-access (see Set up a webhook), sending at least employee_id and upn, and github_username when you track it.
  4. Take it into use from the Liitora library (see Take a library workflow into use), then read the test cases on your copy, run them and approve it.
  • More systems. Add a step per system next to check_entra and check_github, and add its result to the remaining step’s script. Keep each check read-only and gated the same way, so a system you can’t check yet is skipped, not assumed clear.
  • Remove the access instead of filing a ticket. Use the steps from Offboard a leaver in Microsoft 365. They need permissions that change data, so keep them in their own workflow, and start a dry run before you approve it.
  • A different ticket system. Change the service-desk connection’s address and the tickets step’s url and json to match your service desk’s API.
  • No GitHub handle in the event. If your HR system never sends github_username, look it up first with a step against your identity mapping, or remove the GitHub check and connection entirely.
  • Group instead of individual owners. Point entra_owner and github_owner at a shared mailbox or a distribution list instead of a person, so removal isn’t blocked by one person being away.

When the leaver-access webhook is called or when someone starts it by hand: check the leaver event has an employee id and a UPN, then check whether the Entra ID account is still enabled. If its condition is met, check whether the GitHub handle is still an org member. If its condition is met, list the accounts that still have access. If its condition is met, open a service desk ticket for each remaining account for each item. If its condition is met, email each system owner about the remaining access for each item.

What Details
Publisher Liitora
Version 1.0.0
Licence Apache-2.0
Content hash sha256:56ebe1168502e98b5279e5b107adee161d98aee6b1da63964d52dbb8ac68301c
Tests 7
Connections github-org, m365, service-desk
Company variables entra_owner, github_org, github_owner
Secrets None
Personal data The leaver’s employee id, user principal name and GitHub handle, sent to your service desk and to the system owners.
Saved state No
Runs on your own runner No
Actions library/ms365.get-user@v1, platform/email.send@v1, platform/evaluate@v1, platform/http.request@v1, platform/transform@v1

Liitora signs each version of this workflow. When your company takes it into use, Liitora checks the content hash and the signature again before it copies the workflow into your company. See How the library is kept safe.

# A Liitora library workflow for a bank CISO (see the guide, Library > Leaver access check).
# Connections used: m365 (from the Microsoft 365 template, read-only here),
# github-org (GitHub org membership), service-desk (generic REST). Company
# variables used: github_org, entra_owner, github_owner. To remove the access
# itself, see the guide, How-to > Offboard a leaver in Microsoft 365.
name: Leaver access check
description: When HR reports that someone has left, check Microsoft Entra ID and the GitHub organisation for access that is still there, open one service desk ticket per remaining account and email the system owner. Does nothing when no access remains.
on:
webhook:
endpoint: leaver-access
manual: true
steps:
- id: validate
name: Check the leaver event has an employee id and a UPN
uses: platform/evaluate@v1
with:
expression: ${{ trigger.body.employee_id != null && trigger.body.employee_id != '' && trigger.body.upn != null && trigger.body.upn != '' }}
fail_if_false: true
message: "The leaver event is missing employee_id or upn."
- id: check_entra
name: Check whether the Entra ID account is still enabled
needs: [validate]
uses: library/ms365.get-user@v1
connections:
graph: m365
with:
user: ${{ trigger.body.upn }}
select: [accountEnabled]
retry:
attempts: 3
backoff: exponential
on: [429, 5xx, timeout, network]
- id: check_github
name: Check whether the GitHub handle is still an org member
needs: [validate]
if: ${{ trigger.body.github_username != null && trigger.body.github_username != '' }}
uses: platform/http.request@v1
connections:
api: github-org
with:
method: GET
url: /orgs/${{ vars.github_org }}/memberships/${{ trigger.body.github_username }}
expect_status: [200, 404]
retry:
attempts: 3
backoff: exponential
on: [429, 5xx, timeout, network]
- id: remaining
name: List the accounts that still have access
needs: [check_entra, check_github]
if: ${{ always() && steps.check_entra.result == 'success' && (steps.check_github.result == 'success' || steps.check_github.result == 'skipped') }}
uses: platform/transform@v1
with:
input:
entra_active: ${{ steps.check_entra.outputs.found == true && steps.check_entra.outputs.user.accountEnabled == true }}
entra_owner: ${{ vars.entra_owner }}
github_username: ${{ trigger.body.github_username }}
github_member: ${{ steps.check_github.result == 'success' && steps.check_github.outputs.status == 200 }}
github_owner: ${{ vars.github_owner }}
script: |
const accounts = [];
if (input.entra_active) {
accounts.push({
system: "Microsoft Entra ID",
owner: input.entra_owner,
detail: "the Entra ID account is still enabled",
});
}
if (input.github_member) {
accounts.push({
system: "GitHub organisation",
owner: input.github_owner,
detail: `still an org member as ${input.github_username}`,
});
}
return accounts;
- id: tickets
name: Open a service desk ticket for each remaining account
needs: [remaining]
if: ${{ always() && steps.remaining.result == 'success' }}
uses: platform/http.request@v1
connections:
api: service-desk
strategy:
for_each: ${{ steps.remaining.outputs.result }}
max_parallel: 3
fail_fast: false
with:
method: POST
url: /api/tickets
json:
title: "Remove leaver access: ${{ each.item.system }}"
description: "${{ trigger.body.employee_id }} (${{ trigger.body.upn }}) left the company and ${{ each.item.detail }}. Remove the access in ${{ each.item.system }}."
requested_for: ${{ each.item.owner }}
- id: notify_owners
name: Email each system owner about the remaining access
needs: [tickets]
if: ${{ always() && steps.tickets.result == 'success' }}
uses: platform/email.send@v1
strategy:
for_each: ${{ steps.remaining.outputs.result }}
max_parallel: 3
fail_fast: false
with:
to: ["${{ each.item.owner }}"]
subject: "Leaver access remaining: ${{ each.item.system }}"
text: "${{ trigger.body.employee_id }} (${{ trigger.body.upn }}) left the company and ${{ each.item.detail }}. A service desk ticket has been opened to remove it from ${{ each.item.system }}."
  • 1.0.0 (2026-10-04): First version in the library.