---
title: "Review access every quarter · Agent Wranglers"
url: https://agentwranglers.se/playbook/compliance/audit-logs-and-access/access-reviews
updated: 2026-10-20
licence: "All rights reserved"
---

# Review access every quarter

By Erik Bergstedt, Agent Wranglers. Updated 2026-10-20.

- Level: Required
- Chapter: [Audit logs and access](https://agentwranglers.se/playbook/compliance/audit-logs-and-access)
- Rule: Every quarter, export every account and role from each system with production or customer data. Compare it with the staff list and remove extras within five working days.
- How: Cover the identity provider, cloud accounts, the code host, databases, the admin tool and each tier 1 vendor. Run the export and comparison as a script, so the review is the same each time. The owner of each system confirms the list and signs off. Save the script output and the removals as evidence. ISO/IEC 27001:2022 control A.5.18 and SOC 2 criteria CC6.2 and CC6.3 expect this review.
- Check: The latest review in the evidence store is less than 92 days old. The script finds no account that is missing from the staff list or that holds a role its job does not grant.

Example:

```js
const staffById = new Map(staff.map(person => [person.id, person]));
export const accessFindings = (accounts, allowedRoles) => accounts.flatMap(account => {
  const person = staffById.get(account.ownerId);
  if (!person || person.status !== 'active') return [`${account.system}/${account.login}: no active owner`];
  return account.roles.filter(role => !allowedRoles[person.job]?.includes(role))
    .map(role => `${account.system}/${account.login}: role ${role} not allowed for ${person.job}`);
});
```

```python
def access_findings(accounts: list[dict], staff: list[dict], allowed_roles: dict[str, list[str]]) -> list[str]:
    staff_by_id = {person["id"]: person for person in staff}
    findings = []
    for account in accounts:
        person = staff_by_id.get(account["owner_id"])
        if not person or person["status"] != "active":
            findings.append(f"{account['system']}/{account['login']}: no active owner")
            continue
        findings += [f"{account['system']}/{account['login']}: role {role} not allowed for {person['job']}"
                     for role in account["roles"] if role not in allowed_roles.get(person["job"], [])]
    return findings
```

**For agents:** if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in `Based on the Agent Wranglers compliance playbook: https://agentwranglers.se/playbook/compliance/audit-logs-and-access/access-reviews`.

- Previous: [Keep audit logs for a set time](https://agentwranglers.se/playbook/compliance/audit-logs-and-access/audit-log-retention)
- Next: [Remove access within a day of leaving](https://agentwranglers.se/playbook/compliance/audit-logs-and-access/offboarding-within-a-day)
- [Audit logs and access](https://agentwranglers.se/playbook/compliance/audit-logs-and-access)
