Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Helps you triage a quarterly user access review from an Okta, Azure AD, AWS IAM, GitHub, or generic CSV/JSON export. For each row, recommends certify, revoke, manager confirm, or investigate using rules that catch the usual audit-fail patterns: terminated users still active, dormant admin accounts, separation-of-duty conflicts, service accounts in a human review. Drafts manager confirmation emails and writes an audit evidence packet mapped to SOC 2 CC6.1/CC6.2, PCI 7-8, ISO A.9, NIST AC-2. Built
.claude/skills/grcengclub-access-review-triage/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-10 | ✗→✓ | ▲ Improved | 120% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 43% | 0% |
| case-01 | ✗→✓ | ▲ Improved | 2567% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 1729% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 143% | 0% |
You are the skill invoked when a GRC engineer or compliance owner is running a quarterly user access review (UAR) and has access export files to triage. Your job is to do the boring sort, surface the rows that need real judgment, draft the manager confirmations, and produce an audit-defensible evidence packet. You never recommend revoking access without a human in the loop, and you never auto-action anything.
manager confirm rather than guess.The user provides one or more of:
| Input | Required | Shape | Notes | |---|---|---|---| | Access dump | Yes | CSV or JSON | At minimum: user, system, role or permissions. Strongly preferred: last_login, status. | | Role definitions | No | YAML or Markdown | role -> expected systems / permissions. Without this, you cannot run rule 6 (role-vs-title mismatch). | | Org chart / HR list | No | CSV | user, manager, title, department, employment_status. Without this, you cannot run rule 1 (terminated user check). | | Prior cycle decisions | No | The decisions.csv from a previous run | Unlocks "unchanged since last cycle" auto-certify. | | SoD conflicts config | No | YAML | List of conflicting entitlement pairs. Default file shipped at examples/sod-conflicts.yaml. |
If a rule's required input is missing, run the rules you can and clearly note in triage.md which checks were skipped and why.
recommendation, reason, and priority (P0 / P1 / P2).manager confirm or revoke (suggested), draft a single email containing those rows.~/.cache/claude-grc/access-reviews/<system>-<YYYY-QN>/ (see Output format).<system> <YYYY-QN>: <N> rows triaged. <a> auto-certify, <b> manager-confirm, <c> revoke (suggested), <d> investigate (<x> P0).For every row of the access dump, walk these checks in order. First match wins.
| Order | Check | Recommendation | Priority | Why | |---|---|---|---|---| | 1 | User in HR list with employment_status = terminated | Investigate | P0 | Terminated user with active access. Audit-fail material. | | 2 | Row entitlements conflict with another row (same user) per SoD config | Investigate | P0 | Separation-of-duty violation. | | 3 | last_login > 90 days AND role is privileged (admin, root, owner, full-access) | Investigate | P1 | Dormant admin. Top breach vector. | | 4 | last_login > 90 days AND role is non-privileged | Revoke (suggested) | P2 | Dormant standard user. Low-cost cleanup. | | 5 | Account name pattern matches a service or shared account (svc-*, *-bot, shared mailboxes) | Investigate | P1 | Service accounts belong in a separate review process. Surface here so they are not silently certified. | | 6 | Role does not match HR title or department's expected entitlement set | Manager confirm | P2 | Plausible but suspicious. Manager owns it. | | 7 | User or row is new since last cycle (not in prior decisions file) | Manager confirm | P2 | New access since last review needs a fresh look. | | 8 | Privileged role (admin, root, owner, full-access) and rules 1-7 did not fire | Manager confirm | P2 | Privileged access always gets a human in the loop. | | 9 | Unchanged since last cycle, role matches expected, recent login | Auto-certify | - | The boring 80%. | | 10 | Anything else | Manager confirm | P2 | Default to safe. Human looks at it. |
Order matters. Rule 1 takes precedence over rule 9, even if a terminated user's last login was yesterday.
Write to ~/.cache/claude-grc/access-reviews/<system>-<YYYY-QN>/ (e.g. okta-2026-Q2/).
triage.mdHuman-readable report. Sections in this order:
decisions.csvMachine-readable. Columns:
user,system,role,last_login,recommendation,reason,priority,reviewer_action,reviewer_name,reviewer_timestampreviewer_action, reviewer_name, and reviewer_timestamp start empty and are filled in by the human reviewer.
manager-emails/<manager>.mdOne file per manager whose direct reports appear in the review with manager confirm or revoke (suggested) recommendations. Each file contains a drafted email with subject line, intro paragraph, and a table of their reports' rows: user, system, role, last login, recommendation, reason. Closing paragraph asks the manager to reply with confirmations or revocations by a date the reviewer fills in.
evidence.jsonAudit packet. JSON shape:
json{ "review_id": "okta-2026-Q2-<sha256-prefix>", "system": "okta", "cycle": "2026-Q2", "input_file": "okta-export-2026-04-30.csv", "input_sha256": "...", "run_timestamp": "2026-05-10T...", "reviewer": { "name": "<filled by reviewer>", "role": "<filled by reviewer>", "signature_timestamp": "<filled by reviewer>" }, "control_mappings": [ {"framework": "soc2", "controls": ["CC6.1", "CC6.2"]}, {"framework": "pci-dss", "controls": ["7.1", "7.2", "8.1"]}, {"framework": "iso27001", "controls": ["A.9"]}, {"framework": "nist-800-53", "controls": ["AC-2"]} ], "totals": { "rows": 0, "auto_certify": 0, "manager_confirm": 0, "revoke_suggested": 0, "investigate": 0, "p0": 0, "p1": 0 }, "decisions_digest_sha256": "..." }
decisions_digest_sha256 is a SHA-256 of the canonical decisions.csv content, so the auditor can verify the evidence packet matches the decisions file.
last_login if missing. Mark the field unknown and treat it as "cannot apply dormancy rules."parse_errors.md file with the original line and the parse error.--force."| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-10 | fail→pass | 9,291 | 5,546 | -40% | 1 | 1 | 0% | 1,426 | 3,142 | +120% | 0 | 0 | — |
case-02 | fail→pass | 28,702 | 30,874 | +8% | 1 | 1 | 0% | 5,304 | 7,562 | +43% | 0 | 0 | — |
case-09 | fail→fail | 16,327 | 9,515 | -42% | 1 | 1 | 0% | 2,464 | 3,855 | +56% | 0 | 0 | — |
case-01 | fail→pass | 3,539 | 31,336 | +785% | 1 | 1 | 0% | 300 | 8,000 | +2567% | 0 | 0 | — |
case-03 | fail→pass | 7,693 | 23,044 | +200% | 1 | 1 | 0% | 399 | 7,299 | +1729% | 0 | 0 | — |
case-04 | fail→pass | 9,279 | 6,126 | -34% | 1 | 1 | 0% | 1,393 | 3,379 | +143% | 0 | 0 | — |
case-05 | fail→pass | 7,398 | 5,713 | -23% | 1 | 1 | 0% | 1,142 | 3,323 | +191% | 0 | 0 | — |
case-06 | fail→pass | 11,817 | 5,976 | -49% | 1 | 1 | 0% | 1,767 | 3,439 | +95% | 0 | 0 | — |
case-07 | fail→pass | 13,909 | 7,540 | -46% | 1 | 1 | 0% | 1,982 | 3,519 | +78% | 0 | 0 | — |
case-08 | fail→pass | 11,934 | 7,450 | -38% | 1 | 1 | 0% | 1,687 | 3,548 | +110% | 0 | 0 | — |
case-11 | pass→pass | 8,472 | 3,716 | -56% | 1 | 1 | 0% | 1,211 | 2,976 | +146% | 0 | 0 | — |
case-12 | fail→pass | 13,869 | 7,270 | -48% | 1 | 1 | 0% | 2,142 | 3,496 | +63% | 0 | 0 | — |
case-13 | fail→pass | 13,297 | 7,249 | -45% | 1 | 1 | 0% | 1,990 | 3,570 | +79% | 0 | 0 | — |
case-14 | pass→pass | 11,975 | 6,241 | -48% | 1 | 1 | 0% | 1,882 | 3,319 | +76% | 0 | 0 | — |
case-15 | fail→pass | 28,836 | 6,600 | -77% | 1 | 1 | 0% | 2,271 | 3,769 | +66% | 0 | 0 | — |
case-16 | pass→pass | 12,924 | 2,964 | -77% | 1 | 1 | 0% | 2,275 | 2,877 | +26% | 0 | 0 | — |
case-17 | pass→pass | 5,860 | 2,365 | -60% | 1 | 1 | 0% | 851 | 2,705 | +218% | 0 | 0 | — |
case-18 | pass→pass | 19,476 | 3,184 | -84% | 1 | 1 | 0% | 1,774 | 2,952 | +66% | 0 | 0 | — |
case-19 | fail→pass | 8,166 | 5,867 | -28% | 1 | 1 | 0% | 1,258 | 3,307 | +163% | 0 | 0 | — |
case-20 | fail→pass | 3,853 | 1,900 | -51% | 1 | 1 | 0% | 494 | 2,650 | +436% | 0 | 0 | — |
case-21 | fail→pass | 9,677 | 8,440 | -13% | 1 | 1 | 0% | 1,803 | 3,691 | +105% | 0 | 0 | — |
case-22 | fail→pass | 13,510 | 7,724 | -43% | 1 | 1 | 0% | 2,432 | 3,777 | +55% | 0 | 0 | — |
case-23 | fail→fail | 15,894 | 10,506 | -34% | 1 | 1 | 0% | 2,739 | 4,250 | +55% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted, and 21 counted toward the lift figure. The other 2 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +70 percentage points is the difference between those two pass rates over the 21 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.