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).

FindingCount
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 accounts150 accounts, 1,375 ACEs
Deny ACEs (fragile pattern)606
Users whose access differs from same-title peers88
AD groups actually consumed by FS0127 of 375

Notable specifics:

  • The disabled MSP admin account wsadmin holds 540 explicit ACEs across most shares. wsadmin and the disabled Scan account are both members of Domain Admins.
  • Share-level permissions are not a control layer. Nearly every share is Everyone: Full or Everyone: Change at the SMB level — NTFS is the only real gate.
  • A hidden share Check$ grants Everyone: Full at 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

ComponentPurpose
sweep.ps1 / run-sweep.ps1Full-depth NTFS + SMB ACL inventory, run on FS01 as admin. Read-only.
install-sweep-schedule.ps1Registers the weekly sweep as a scheduled task running as SYSTEM — no stored credentials.
collect-ad.ps1AD groups, users, and membership edges via ADSI (no RSAT dependency).
build.pyProduces viewer.html and killlist.csv.
snapshot.py / drift.pyWeekly snapshot (~155 KB gzipped) and severity-tagged diff against the previous week.
pipeline.ps1The weekly run: collect → ingest sweep → build → snapshot → drift → push.
drift-triage.ymlGHE Actions: Claude groups drift findings into problems and files deduped issues.
mcp_server.pyMCP 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.py

Tools: 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

  1. Remove the 47 orphaned SIDs and 150 disabled-account grants (~2,200 ACEs). No functional impact; needs domain admin. Take icacls /save backups first.
  2. Review Check$ and the share-level Everyone grants.
  3. Re-home enabled direct-user grants into departmental groups, then remove the user ACEs.
  4. Extend consumer validation to GPOs, other DFS target servers, applications, and Entra app assignments.
  5. Deprecate groups from the kill list, highest score first, once consumers are mapped.