AD / Entra Group Audit
Validates PSI’s AD and Entra groups against the systems that actually consume them, starting with the file server PS-GR-FS01. Produces an access viewer, a scored group kill list, weekly drift detection, and a natural-language query interface.
Repo: ProgressiveSurface/ad-group-audit (GHE, private)
Local: C:\git\ad-group-audit
Status: FS01 phase complete (2026-07-30). Continuous pipeline built; remediation pending.
Why it exists
PSI had 375 AD groups with no reliable answer to “who can reach this folder” or “is this group still used.” Group-based access control on the file server had eroded over roughly a decade into per-user ACL sprawl, including grants to accounts disabled since 2010.
What it found on PS-GR-FS01
Full admin sweep walked 2,153,333 directories; 5,884 carry explicit ACEs (13,330 ACE rows).
| Finding | Count |
|---|---|
| Orphaned SIDs (deleted principals still on ACLs) | 47 principals, 847 ACEs |
| Direct-user grants (should be group-based) | 237 users, 2,319 ACEs |
| …belonging to disabled accounts | 150 accounts, 1,375 ACEs |
| Deny ACEs (fragile pattern) | 606 |
| Users whose access differs from same-title peers | 88 |
| AD groups actually consumed by FS01 | 27 of 375 |
Notable specifics:
- The disabled MSP admin account
wsadminholds 540 explicit ACEs across most shares.wsadminand the disabledScanaccount are both members of Domain Admins. - Share-level permissions are not a control layer. Nearly every share is
Everyone: FullorEveryone: Changeat the SMB level — NTFS is the only real gate. - A hidden share
Check$grantsEveryone: Fullat share level; it was invisible to non-admin enumeration. - The outlier engine independently rediscovered the Non-Shop Group misassignment: Machine Operators carrying +568 paths versus their title peers, all via Non-Shop Group membership (30 active members of that group hold shop-floor AFTEC departments).
Components
| Component | Purpose |
|---|---|
sweep.ps1 / run-sweep.ps1 | Full-depth NTFS + SMB ACL inventory, run on FS01 as admin. Read-only. |
install-sweep-schedule.ps1 | Registers the weekly sweep as a scheduled task running as SYSTEM — no stored credentials. |
collect-ad.ps1 | AD groups, users, and membership edges via ADSI (no RSAT dependency). |
build.py | Produces viewer.html and killlist.csv. |
snapshot.py / drift.py | Weekly snapshot (~155 KB gzipped) and severity-tagged diff against the previous week. |
pipeline.ps1 | The weekly run: collect → ingest sweep → build → snapshot → drift → push. |
drift-triage.yml | GHE Actions: Claude groups drift findings into problems and files deduped issues. |
mcp_server.py | MCP server, 7 tools, zero dependencies. |
The viewer
viewer.html is self-contained — open it in a browser, no server needed. Three views:
folder tree → who has access (groups expanded to effective users); user/group tree,
branched by AD department with groups separated → what they can reach; and the kill list.
A fourth tab surfaces outliers.
Natural-language access queries
claude mcp add psi-access -s user -- python C:/git/ad-group-audit/mcp_server.pyTools: who_has_access, access_of, impact_of_removal, outliers, kill_candidates,
risk_summary, drift. impact_of_removal is the one to reach for before any cleanup —
it reports what a person would actually lose if removed from a group, accounting for
access they retain through other memberships.
Group kill list
Every group is scored: empty (3) + all-members-disabled (3) + duplicate membership of
another group (2) + not consumed on FS01 (1) + no description (1) + stale >3 years (1) +
name hint like test/old/a year (1). 129 groups score ≥4.
“Not consumed” is deliberately weighted at only 1 point. FS01 is the only consumer
validated so far — groups may still serve GPOs, the badge and door system (the B1_* /
B2_* groups are Niagara door groups), or applications. Map the remaining consumers
before deleting anything.
Remediation order
- Remove the 47 orphaned SIDs and 150 disabled-account grants (~2,200 ACEs). No functional
impact; needs domain admin. Take
icacls /savebackups first. - Review
Check$and the share-levelEveryonegrants. - Re-home enabled direct-user grants into departmental groups, then remove the user ACEs.
- Extend consumer validation to GPOs, other DFS target servers, applications, and Entra app assignments.
- Deprecate groups from the kill list, highest score first, once consumers are mapped.
Related
- Manufacturing — departments and shop-floor structure, the basis for the peer comparison
- Web App Compliance Standard — the auth and access posture new apps inherit