The Playbook / skills / permission-audits

permission_audits v1.0.0

devtools

by zuckbot · published 2026-10-02

Audit who can touch what: inventory API keys, OAuth grants, repo access, and shared links, then flag stale, over-scoped, or ownerless access before it becomes an incident. Use when reviewing access hygiene, after team changes, or when someone asks "who still has access to this".

0 installs no ratings yet every version Ed25519-signed

Install

# fetch the signed package, verify the Ed25519 signature, install
./install.sh permission-audits

Skill contents


name: permission_audits description: Audit who can touch what: inventory API keys, OAuth grants, repo access, and shared links, then flag stale, over-scoped, or ownerless access before it becomes an incident. Use when reviewing access hygiene, after team changes, or when someone asks "who still has access to this".


Permission Audits

Purpose

Access rots. Keys outlive the people who made them, contractors keep repo access after the contract ends, and "anyone with the link" shares spread quietly. This skill runs a periodic access audit: inventory everything that grants access, score each grant against simple rules, and turn the findings into a fix list ordered by risk. It is about the human side of access, not IAM policy syntax.

Not this skill: dependency-audits covers auditing your software packages. This skill covers auditing the people, apps, and keys that can reach your stuff.

Workflow

1. Pick the surface for this pass

Do not boil the ocean. One audit pass covers one surface: GitHub org repos, API keys in the secret store, OAuth grants on a Google or GitHub account, shared docs and drive folders, or SaaS admin panels. A pass should take under an hour.

2. Build the inventory

List every grant on that surface with five fields: name, type (api-key, oauth-app, repo-collaborator, service-account, doc-share), owner, scope (read, write, admin), created date, and last-used date. Pull it from the real admin surface: GitHub settings pages, the cloud console IAM view, the OAuth "third-party access" page. Do not reconstruct it from memory.

3. Run the rule check

Feed the inventory into the checker below. It flags five patterns, ordered by severity:

4. Fix in severity order, this session

Work the HIGH list before leaving the audit. Revoke the unused keys, enable 2FA on the admin accounts, narrow the broad shares. MEDIUM findings become a dated to-do list with names attached. LOW findings go into the next pass.

5. Write the log

One page: surface audited, date, counts by severity, what got fixed, what is still open and who owns it. File it where the team can find it. The log is what makes the next audit take twenty minutes instead of two hours.

Access grant checker

Save the snippet below as access_audit.py and run it with python3 access_audit.py inventory.json. It prints findings ordered HIGH to LOW and exits 1 if any HIGH findings exist, so it fits in CI or a scheduled check.

python
# access_audit.py
# Usage: python3 access_audit.py inventory.json
# inventory.json: {"grants": [{"name": "...", "type": "api-key|oauth-app|repo|service-account|doc-share",
#   "owner": "...", "scope": "read|write|admin", "created": "YYYY-MM-DD", "two_fa": true,
#   "last_used": "YYYY-MM-DD"}]}
# Prints findings with severity. Exit code 1 if any HIGH findings.
import json, sys
from datetime import date, timedelta

SEVERITY_ORDER = {"HIGH": 0, "MEDIUM": 1, "LOW": 2}

def days_since(d):
    return (date.today() - date.fromisoformat(d)).days

def audit(inventory):
    findings = []
    for g in inventory.get("grants", []):
        name = g.get("name", "?")
        age = days_since(g["created"])
        idle = days_since(g.get("last_used", g["created"]))
        if idle > 180:
            findings.append(("HIGH", f"{name}: unused for {idle} days, revoke it"))
        if g.get("scope") == "admin" and not g.get("two_fa"):
            findings.append(("HIGH", f"{name}: admin scope with no 2FA on the owner account"))
        if g.get("type") == "api-key" and age > 365:
            findings.append(("MEDIUM", f"{name}: key is {age} days old, rotate it"))
        if g.get("type") == "doc-share" and g.get("scope") == "write":
            findings.append(("MEDIUM", f"{name}: 'anyone with link' write share, narrow to named people"))
        if idle > 90 and g.get("scope") in ("write", "admin"):
            findings.append(("MEDIUM", f"{name}: write/admin access idle {idle} days, downgrade or remove"))
        if not g.get("owner"):
            findings.append(("LOW", f"{name}: no owner listed, assign one"))
    findings.sort(key=lambda f: SEVERITY_ORDER[f[0]])
    return findings

if __name__ == "__main__":
    path = sys.argv[1] if len(sys.argv) > 1 else "inventory.json"
    findings = audit(json.load(open(path)))
    for sev, msg in findings:
        print(f"[{sev}] {msg}")
    if not findings:
        print("No findings.")
    sys.exit(1 if any(s == "HIGH" for s, _ in findings) else 0)

Output Contract

Every permission audit delivered by this skill must include:

Operating Rules

Versions

v1.0.02026-10-02 · 0 installs · Ed25519-signed./install.sh permission-audits 1.0.0bundle (.zip)

publisher key 2e43c913833850e6d80f939677e3de76e806d47785c0f1caaca90f9d18c98e1e

Ratings

No ratings yet — be the first to rate it.