Run gh-2fa-auditor from a workflow to list members of an organization (or a single repository) without 2FA, export the report as an artifact, and optionally fail the workflow when violators are detected.
Published as a Docker container action (action.yml + Dockerfile) — consumers only need a single uses: line.
name: 2FA Audit
on:
workflow_dispatch:
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: mingcheng/gh-2fa-auditor@v1
with:
command: audit-org
org: my-org
github-token: ${{ secrets.ADMIN_ORG_PAT }}By default the report is written to 2fa-report.json under $GITHUB_WORKSPACE, ready for actions/upload-artifact.
| Name | Required | Default | Description |
|---|---|---|---|
command |
yes | audit-org |
audit-org, audit-repo, or cache-clear. |
org |
for audit-* |
— | Organization login. |
repo |
for audit-repo |
— | Repository name (without owner/). |
format |
no | json |
table, json, or csv. |
output |
no | auto | Report path (relative paths resolve under $GITHUB_WORKSPACE). |
no-cache |
no | false |
Bypass the on-disk request cache. |
cache-ttl |
no | 300 |
Override cache TTL (seconds). |
cache-file |
no | — | Custom cache file for cache-clear. |
fail-on-violators |
no | false |
Exit 1 when ≥ 1 violator is found (requires format: json). |
github-token |
for audit-* |
— | PAT with admin:org. See Permissions & token. |
When format: table is used without output, the report is printed to the runner's log and no file is produced.
| Name | Description |
|---|---|
report-path |
Absolute path to the generated report file (empty when no file emitted). |
violator-count |
Number of 2FA violators. Populated only when format: json. |
- id: audit
uses: mingcheng/gh-2fa-auditor@v1
with:
command: audit-org
org: my-org
github-token: ${{ secrets.ADMIN_ORG_PAT }}
- run: |
echo "Found ${{ steps.audit.outputs.violator-count }} violator(s)."
echo "Report at ${{ steps.audit.outputs.report-path }}."The audit endpoints require an organization-admin identity. The default ${{ github.token }} is repo-scoped and does not carry admin:org — the action will fail fast if you try to use it.
Use one of:
- A classic PAT with the
admin:orgscope. - A fine-grained PAT with
Organization administration: Read(andMembers: Read) on the target org. - A GitHub App installation token with
Organization administrationpermission.
Pass it via github-token; it is exported as GITHUB_TOKEN inside the container and never logged.
name: Nightly 2FA Audit
on:
schedule: [{ cron: '0 2 * * *' }]
workflow_dispatch:
jobs:
audit:
runs-on: ubuntu-latest
steps:
- id: audit
uses: mingcheng/gh-2fa-auditor@v1
with:
command: audit-org
org: my-org
github-token: ${{ secrets.ADMIN_ORG_PAT }}
- uses: actions/upload-artifact@v4
if: steps.audit.outputs.report-path != ''
with:
name: 2fa-report-${{ github.run_id }}
path: ${{ steps.audit.outputs.report-path }}
retention-days: 30Block merges whenever one of a repo's collaborators is missing 2FA.
name: 2FA Gate
on:
pull_request:
branches: [main]
jobs:
gate:
runs-on: ubuntu-latest
steps:
- uses: mingcheng/gh-2fa-auditor@v1
with:
command: audit-repo
org: my-org
repo: ${{ github.event.repository.name }}
fail-on-violators: 'true'
github-token: ${{ secrets.ADMIN_ORG_PAT }}- id: audit
uses: mingcheng/gh-2fa-auditor@v1
with:
command: audit-repo
org: my-org
repo: payments
github-token: ${{ secrets.ADMIN_ORG_PAT }}
- if: steps.audit.outputs.violator-count != '0'
uses: slackapi/slack-github-action@v1
with:
payload: |
{ "text": ":warning: ${{ steps.audit.outputs.violator-count }} user(s) without 2FA have access to `payments`." }
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}- uses: actions/checkout@v4
- uses: mingcheng/gh-2fa-auditor@v1
with:
command: audit-org
org: my-org
format: csv
output: reports/2fa-latest.csv
github-token: ${{ secrets.ADMIN_ORG_PAT }}
- run: |
git config user.name 'github-actions[bot]'
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
git add reports/2fa-latest.csv
git diff --cached --quiet || git commit -m 'chore: refresh 2FA audit report'
git push| Symptom | Cause / fix |
|---|---|
Missing required scope: admin:org |
The token lacks admin:org. Use a classic PAT with that scope or an equivalent fine-grained PAT. |
Input 'github-token' is required |
Set github-token to a secret reference, e.g. ${{ secrets.ADMIN_ORG_PAT }}. |
Workflow succeeds but violator-count is empty |
The counter is only populated when format: json. |
| Action fails on a personal-account repo | GitHub does not expose 2FA outside Organizations — move the repo into an organization first. |
| Cache appears stale | The container cache is ephemeral and resets every run. Use no-cache: 'true' to make this explicit. |
docker: ... linux/amd64 |
Container actions require Linux runners (runs-on: ubuntu-latest). |