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.
What it does
Section titled “What it does”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.
What it can access
Section titled “What it can access”m365: a connection made from the Microsoft 365 (Microsoft Graph) template, read-only for this workflow. It uses only the ready-made actionlibrary/ms365.get-user@v1and asks Microsoft only whether the account is enabled, so the application permission it needs isUser.Read.All. Microsoft listsUser.EnableDisableAccount.Alltogether withUser.Read.Allas 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 callsGET /orgs/{org}/memberships/{username}, so a fine-grained token withMembers: Read-onlyon 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_ownerandgithub_owner, never the leaver.
What you need to set up
Section titled “What you need to set up”- Create the Microsoft 365 connection from its template, name it
m365, and grant its app onlyUser.Read.All(see Connect Microsoft 365). Create the other two connections, named exactlygithub-organdservice-desk(see Create a connection). - Add the company variables
github_org(your GitHub organisation’s login),entra_owner(who owns Entra ID account removal) andgithub_owner(who owns GitHub org membership) (see Use company variables). - Point your HR or identity system’s leaver event at the workflow’s webhook endpoint,
leaver-access(see Set up a webhook), sending at leastemployee_idandupn, andgithub_usernamewhen you track it. - 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.
How to adapt it
Section titled “How to adapt it”- More systems. Add a step per system next to
check_entraandcheck_github, and add its result to theremainingstep’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-deskconnection’s address and theticketsstep’surlandjsonto 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_ownerandgithub_ownerat a shared mailbox or a distribution list instead of a person, so removal isn’t blocked by one person being away.
What Liitora shows on the approval page
Section titled “What Liitora shows on the approval page”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.
Trust details
Section titled “Trust details”| 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.
The full YAML
Section titled “The full YAML”# 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 checkdescription: 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 }}."Changelog
Section titled “Changelog”- 1.0.0 (2026-10-04): First version in the library.