CtrlK
BlogDocsLog inGet started
Tessl Logo
SkillPublic

access-review

Conduct periodic access reviews and certifications. Implement access governance and recertification workflows. Use when managing access compliance.

Quality

74%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide
SecuritybySnyk

Low

Low-risk findings worth noting

Fix and improve this skill with Tessl

tessl review fix ./skills/antigravity-access-review/SKILL.md
SKILL.md
Quality
Evals
Security

Access Review

Implement periodic access review processes for AWS IAM, GitHub, Okta, and other identity providers, including automated reporting, certification workflows, and unused permission detection.

Access Review Process

access_review_workflow:
  1_scope:
    actions:
      - Define systems in scope for the review cycle
      - Identify review owners (managers, system owners)
      - Set review timeline and deadlines
      - Generate access inventory from all identity sources
    frequency:
      privileged_access: Quarterly
      standard_access: Semi-annually
      service_accounts: Quarterly
      api_keys: Monthly

  2_extract:
    actions:
      - Pull current access data from all systems
      - Correlate identities across platforms (SSO mapping)
      - Enrich with last login and activity data
      - Flag accounts for review (inactive, over-privileged, orphaned)

  3_review:
    actions:
      - Assign review items to appropriate managers
      - Manager certifies each user's access (approve/revoke/modify)
      - Risk-based prioritization (privileged users reviewed first)
      - Escalate non-responses after deadline
    decisions:
      approve: "Access is appropriate for current role"
      modify: "Access needs adjustment (reduce/change scope)"
      revoke: "Access is no longer needed"

  4_remediate:
    actions:
      - Revoke access flagged for removal
      - Modify access as directed by reviewers
      - Document exceptions with justification
      - Confirm changes with system owners
    sla:
      revocations: "Complete within 5 business days of decision"
      modifications: "Complete within 10 business days"
      exceptions: "Approved by security team, documented, time-limited"

  5_report:
    actions:
      - Generate completion metrics (% reviewed, % on time)
      - Document all decisions and actions taken
      - Archive evidence for compliance audits
      - Identify process improvements for next cycle

Review boundaries

Confirm exact tenant/accounts, read-only credentials, review period and authorized reviewers. Store reports privately with defined retention; proposed cadence/SLA numbers are examples, not compliance certification. Identity joins need verified immutable IDs, not matching display names. Owner approval and a recovery/break-glass plan are required before modifying access; inactivity heuristics never authorize revoking production service accounts automatically.

AWS IAM Access Review Scripts

Obtain the IAM credential report through a read-only scoped client, poll boundedly until complete, and decode it once into a private file. Failure/unsupported values are unknown, never zero/inactive. Use named CSV columns, not positional awk fields:

import csv

def review_console_mfa(report_path):
    candidates = []
    with open(report_path, newline="", encoding="utf-8") as source:
        for row in csv.DictReader(source):
            if row.get("password_enabled") == "true" and row.get("mfa_active") == "false":
                candidates.append({"user": row["user"], "finding": "review_console_mfa"})
    return candidates  # Private review data, not automatic disable actions.

Compare timezone-aware last-use/creation times and both access-key slots with an agreed threshold; missing history may mean never-used, unsupported or unavailable. Password last-use alone is not overall account inactivity. Effective privilege requires user/group/role/inline policies, boundaries, SCPs and resource policies; policy-name matching is only a hint. Cross-account trust needs structured principal and condition analysis, not grep or an AWS-account substring. No key or access is revoked solely because a report marks it stale.

GitHub Access Review

#!/usr/bin/env bash
# github-access-review.sh - Partial organization inventory
set -euo pipefail
umask 077

ORG="your-org"
OUTPUT_DIR="./access-review/github/$(date +%Y-%m)"
mkdir -p "$OUTPUT_DIR"

echo "=== GitHub Organization Access Review ==="

echo "--- Organization Members ---"
gh api orgs/$ORG/members --paginate \
  --jq '.[] | [.login, .site_admin] | @csv' \
  > "$OUTPUT_DIR/org-members.csv"

echo "--- Organization Owners ---"
gh api "orgs/$ORG/members?role=admin" --paginate \
  --jq '.[] | .login' \
  > "$OUTPUT_DIR/org-owners.txt"

echo "--- Outside Collaborators ---"
gh api orgs/$ORG/outside_collaborators --paginate \
  --jq '.[] | .login' \
  > "$OUTPUT_DIR/outside-collaborators.txt"

echo "--- Repository Access Per Repo ---"
for repo in $(gh api "orgs/$ORG/repos" --paginate --jq '.[].name'); do
  echo "Repo: $repo"
  gh api "repos/$ORG/$repo/collaborators" --paginate \
    --jq '.[] | [.login, .role_name] | @csv' \
    > "$OUTPUT_DIR/repo-$repo-access.csv"
done

echo "--- Team Memberships ---"
for team in $(gh api orgs/$ORG/teams --paginate --jq '.[].slug'); do
  echo "Team: $team"
  gh api "orgs/$ORG/teams/$team/members" --paginate \
    --jq '.[] | .login' \
    > "$OUTPUT_DIR/team-$team-members.txt"
done

echo "--- Pending Invitations ---"
gh api orgs/$ORG/invitations --paginate \
  --jq '.[] | [.login, .email, .role, .created_at] | @csv' \
  > "$OUTPUT_DIR/pending-invitations.csv"

echo "--- Deploy Keys ---"
for repo in $(gh api "orgs/$ORG/repos" --paginate --jq '.[].name'); do
  keys=$(gh api "repos/$ORG/$repo/keys" --jq '.[].title' 2>/dev/null)
  if [ -n "$keys" ]; then
    echo "$repo: $keys"
  fi
done > "$OUTPUT_DIR/deploy-keys.txt"

# Branch/ruleset enforcement needs a separate authorized read of each repository's
# actual default/protected branches, classic protection and repository/org rulesets.
# A 403/404 is unknown or absent for that API, not proof the branch is unprotected.
# site_admin is GitHub site administration, NOT an organization-owner indicator.

echo "Report generated in $OUTPUT_DIR"

Okta Access Review

Use a secret-aware SDK/client against the approved Okta HTTPS domain; do not put SSWS tokens in curl arguments. Disable unexpected redirects, set deadlines and bounded page/retry budgets, and follow only same-origin API Link: rel=next URLs. Enumerate users, factors, apps/assignments and admin roles with pagination; a single limit=200 response is incomplete. Iterate structured app IDs, not CSV rows containing quoted commas. Record permissions/errors/coverage explicitly. Factor enrollment is not proof that the current sign-on policy requires MFA or that a user's enrollment is usable. Compare current authenticators, policy and assignment data before recommendations. Keep names/email inventories restricted.

Unused Permission Detection

"""
Detect unused IAM permissions using CloudTrail and IAM Access Analyzer.
Generates recommendations for right-sizing access.
"""
import boto3
import json
import time
from datetime import datetime, timedelta, timezone


def analyze_iam_usage(days_lookback=90):
    """Analyze IAM user and role activity against granted permissions."""
    iam = boto3.client("iam")

    report = {
        "generated_at": datetime.now(timezone.utc).isoformat(),
        "lookback_days": days_lookback,
        "findings": [],
    }

    users = (user for page in iam.get_paginator("list_users").paginate() for user in page["Users"])
    for user in users:
        username = user["UserName"]

        # Get service last accessed data
        job_id = iam.generate_service_last_accessed_details(
            Arn=user["Arn"]
        )["JobId"]

        for attempt in range(60):
            result = iam.get_service_last_accessed_details(JobId=job_id)
            if result["JobStatus"] == "COMPLETED":
                break
            if result["JobStatus"] == "FAILED":
                raise RuntimeError("IAM last-access job failed")
            time.sleep(2)
        else:
            raise TimeoutError("IAM last-access job exceeded polling budget")
        services = list(result["ServicesLastAccessed"])
        while result.get("IsTruncated"):
            result = iam.get_service_last_accessed_details(JobId=job_id, Marker=result["Marker"])
            services.extend(result["ServicesLastAccessed"])

        threshold = datetime.now(timezone.utc) - timedelta(days=days_lookback)
        unused_services = []

        for service in services:
            last_accessed = service.get("LastAuthenticated")
            if last_accessed is None or last_accessed < threshold:
                unused_services.append({
                    "service": service["ServiceNamespace"],
                    "last_accessed": str(last_accessed) if last_accessed else "Unknown/no recorded activity",
                })

        if unused_services:
            report["findings"].append({
                "type": "unused_permissions",
                "user": username,
                "arn": user["Arn"],
                "unused_service_count": len(unused_services),
                "unused_services": unused_services[:10],
                "recommendation": "Review and remove unused service permissions",
            })

    return report


def list_external_access_candidates():
    """External-access findings are not a full effective-privilege/unused-access analysis."""
    analyzer = boto3.client("accessanalyzer")

    findings = analyzer.list_findings(
        analyzerArn="arn:aws:access-analyzer:us-east-1:123456789012:analyzer/org-analyzer",
        filter={
            "status": {"eq": ["ACTIVE"]},
            "resourceType": {"eq": ["AWS::IAM::Role"]},
        },
    )

    return [
        {
            "resource": f["resource"],
            "resource_type": f["resourceType"],
            "condition": f.get("condition", {}),
            "principal": f.get("principal", {}),
            "action": f.get("action", []),
            "created_at": str(f["createdAt"]),
        }
        for f in findings.get("findings", [])
    ]

Contents

  • Certification Workflow Automation
  • Access Review Checklist
  • Best Practices

When to Use

  • Conducting quarterly or annual access reviews for compliance (SOC 2, HIPAA, PCI DSS, ISO 27001)
  • Identifying and removing stale accounts and unused credentials
  • Certifying that current access levels match job responsibilities
  • Detecting excessive privileges and dormant service accounts
  • Generating evidence for auditor requests on access governance

Limitations

  • Guidance and checklists only; not legal advice and not a substitute for a qualified auditor.
  • Docs-only import: upstream templates and scripts not bundled.

Example

Map this skill's control checklist to our current evidence and list gaps.

Adapted from BagelHole/DevOps-Security-Agent-Skills (MIT); frontmatter, When to Use/Limitations, and safety boundaries added for upstream compliance. Docs-only import: helper scripts and templates not bundled.

Repository

boisenoise/skills-collections

Path

skills/antigravity-access-review/SKILL.md

Last updated
First committed

Also appears in

sickn33/agentic-awesome-skills
In sync

since Sep 24, 2026

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.