A small CLI that lists members of a GitHub organization (or a single repository) who have not enabled two-factor authentication (2FA).
GitHub only exposes 2FA status through the org-level filter ?filter=2fa_disabled. This tool wraps that filter and — for audit-repo — intersects it with the repo's collaborator list, answering:
"Who with access to this repo is still running without 2FA?"
audit-org— every org member and outside collaborator without 2FA.audit-repo— narrowed to a single repository (collaborators ∩ no-2FA list).cache-clear— wipe the on-disk request cache.- Output as terminal table (default),
json, orcsv; optional--output <file>. - Each row carries a
sourcefield (org:<org>/repo:<org>/<repo>) so merged reports stay traceable. - Token-scope self-check, full pagination, automatic retry/throttling.
- Node.js >= 18
- A GitHub token with the
admin:orgscope (classic PAT) or equivalent organization-administration permission (fine-grained PAT / GitHub App), exported asGITHUB_TOKEN. - The target repository must belong to an organization — personal-account repos are not supported by GitHub's API.
git clone <this-repo>
cd gh-2fa-auditor
pnpm install
pnpm run build
pnpm link --global # optional: expose `gh-2fa-auditor` on $PATHexport GITHUB_TOKEN="ghp_xxxxxxxx"
# Audit a whole organization
gh-2fa-auditor audit-org --org my-org
# Audit a single repository
gh-2fa-auditor audit-repo --org my-org --repo my-critical-repo
# Export the report
gh-2fa-auditor audit-repo \
--org my-org --repo my-critical-repo \
--format json --output ./report.json| Option | Applies to | Description |
|---|---|---|
--org <org> |
audit-* |
Organization login. Required. |
--repo <repo> |
audit-repo |
Repository name (without owner prefix). Required. |
-f, --format |
audit-* |
table (default), json, or csv. |
-o, --output |
audit-* |
Write the report to a file instead of stdout. |
--no-cache |
audit-* |
Skip the on-disk request cache for this run. |
--cache-ttl <seconds> |
audit-* |
Override cache TTL (default 300s, env: GH_2FA_AUDITOR_CACHE_TTL). |
--cache-file <path> |
cache-clear |
Wipe a non-default cache file. |
API list responses are cached at ${XDG_CACHE_HOME:-$HOME/.cache}/gh-2fa-auditor/cache.json with a default TTL of 5 minutes. The cache key includes the endpoint and arguments, so different orgs / repos never collide. Setting the TTL to 0 or passing --no-cache disables caching.
gh-2fa-auditor cache-clear # wipe default cache
gh-2fa-auditor cache-clear --cache-file ./cache.json # wipe a custom fileA Docker container action is published from the repository root. See docs/github-action.md for the full reference.
- uses: mingcheng/gh-2fa-auditor@v1
with:
command: audit-org
org: my-org
github-token: ${{ secrets.ADMIN_ORG_PAT }}table
2FA violators in repo "acme/payments"
┌───┬─────────────┬─────────┬──────┐
│ # │ Login │ Type │ Org │
├───┼─────────────┼─────────┼──────┤
│ 1 │ alice │ member │ acme │
│ 2 │ contractor1 │ outside │ acme │
└───┴─────────────┴─────────┴──────┘
Total violators: 2
csv
login,type,source,org,profile_url,audited_at
alice,member,repo:acme/payments,acme,https://github.com/alice,2026-05-21T12:34:56.000Zjson
[
{
"login": "alice",
"type": "member",
"source": "repo:acme/payments",
"org": "acme",
"profileUrl": "https://github.com/alice",
"auditedAt": "2026-05-21T12:34:56.000Z"
}
]pnpm install
pnpm run dev -- audit-org --org my-org # run TS directly via tsx
pnpm run typecheck
pnpm run lint
pnpm run test
pnpm run buildA Husky + lint-staged pre-commit hook auto-formats and lints staged files. See CONTRIBUTING.md for the full workflow.
src/
├── index.ts # CLI entry / global error handling
├── cli.ts # commander program & sub-commands
├── cache/store.ts # File-based TTL cache for API responses
├── github/client.ts # Octokit wrapper (auth, pagination, caching)
├── commands/ # audit-org, audit-repo, cache-clear
└── output/formatter.ts # table / json / csv renderers
tests/ # Vitest unit tests
| Code | Meaning |
|---|---|
0 |
Audit succeeded (regardless of how many violators) |
1 |
An error occurred (auth, network, missing scope…) |
Can I audit a repo under my personal account? No. GitHub only exposes 2FA status within Organizations/Enterprises. Move the repo into a (free) organization first.
Why does audit-repo fetch the whole org's no-2FA list?
The REST API has no per-repo 2FA filter, so the only viable approach is computing repo collaborators ∩ org-level no-2FA users in memory.
My fine-grained PAT skips the scope check — is that safe?
Fine-grained PATs do not return the x-oauth-scopes header, so the check is bypassed. If the token is under-privileged, GitHub will return a 403 at the audit endpoint and you'll see a descriptive error.
MIT — see LICENSE.