Skip to content

PolicySleuth

Catch dangerous IAM permissions before they reach your cloud.

PolicySleuth is a lightweight static analysis tool for cloud IAM policies. It auto-detects AWS, GCP, and Azure policy formats and flags wildcard permissions, privilege escalation paths, risky role chaining, and over-permissive service accounts, before policies are applied to real infrastructure.

View on GitHub


Why

IAM policies are the access control layer for everything in a cloud environment. A single wildcard action or an iam:PassRole granted too broadly can hand an attacker everything they need. By the time a misconfigured policy surfaces in production, the blast radius is real. PolicySleuth runs in seconds against policy files during code review: no cloud credentials, no network calls, pure static analysis.


Features

  • Multi-cloud auto-detection: AWS, GCP, and Azure formats identified by heuristic, no flags required
  • Wildcard detection: flags * and service:* in actions and resources across all three clouds
  • Privilege escalation paths: catches known risky actions per cloud (e.g., iam:PassRole, roles/owner)
  • Role chaining detection: identifies broad AssumeRole, token creator, and identity assignment patterns
  • Over-permissive service accounts: flags high-privilege roles bound to service principals and managed identities
  • Severity threshold control: tune exit behavior for warnings vs. high severity findings independently
  • Structured JSON output: machine-readable findings for CI/CD pipelines and downstream tooling
  • Rich terminal output: color-coded summary table for local review

Installation

git clone https://github.com/Skellman-io/PolicySleuth.git
cd PolicySleuth
pip install .
pipx run --spec . policy-sleuth --help
git clone https://github.com/Skellman-io/PolicySleuth.git
cd PolicySleuth
python -m venv .venv
source .venv/bin/activate   # Windows: .venv\Scripts\activate
pip install -e .

Quick Start

# Lint a single policy file
policy-sleuth path/to/policy.json

# Lint multiple files and a directory tree
policy-sleuth policies/ more/*.json --format summary --fail-on any

# JSON output for CI/CD pipelines
policy-sleuth policies/ --format json --fail-on high > report.json

# Treat warnings as high severity
policy-sleuth policies/ --strict --fail-on any

Options

Flag Description Default
paths One or more policy files or directories (directories are recursively scanned for .json)
--format Output format: summary or json summary
--fail-on Exit non-zero when findings reach threshold: any high none any
--strict Treat warnings as high severity; combines with --fail-on any off

Rules

AWS

Rule What it catches Severity
wildcard-action * or service:* in Action or NotAction High
wildcard-resource * in Resource or NotResource High
priv-esc Risky actions: iam:PassRole, iam:CreatePolicyVersion, iam:AttachRolePolicy, iam:PutRolePolicy, sts:AssumeRole, iam:UpdateAssumeRolePolicy, lambda:UpdateFunctionCode High
role-chaining sts:AssumeRole granted on a wildcard resource High

GCP

Rule What it catches Severity
wildcard-action Wildcard actions in policy bindings High
wildcard-resource Wildcard resources in policy bindings High
priv-esc High-privilege roles (roles/owner, roles/editor, roles/iam.securityAdmin, roles/iam.serviceAccountAdmin) or risky permissions (iam.serviceAccounts.actAs, iam.serviceAccounts.getAccessToken, resourcemanager.projects.setIamPolicy) High
over-permissive-service-account Service account bound to roles/owner or roles/editor, or a wildcard member on a high-privilege role High
role-chaining roles/iam.serviceAccountTokenCreator or roles/iam.serviceAccountUser granted to a wildcard member (allUsers, allAuthenticatedUsers) High

Azure

Rule What it catches Severity
wildcard-action Actions ending in /* in role definitions High
priv-esc Microsoft.Authorization/roleAssignments/write, Microsoft.Authorization/roleDefinitions/write, Microsoft.ManagedIdentity/userAssignedIdentities/assign/action High
over-permissive-service-account Service principal or Managed Identity assigned the Owner role High
role-chaining Assignments that allow broadly assigning identities across the subscription High

Auto-Detection

PolicySleuth identifies the cloud provider using light heuristics with no flags required:

Signal Detected as
Statement or statement key present AWS IAM Policy
bindings key present GCP IAM Policy
permissions key or properties.roleDefinitionName present Azure Role Definition / Assignment
None of the above unrecognized-format warning

Multi-cloud directories are handled automatically; each file is evaluated independently and tagged with its detected provider in the output.


Output Formats

Rich terminal table with color-coded severity. Default for local review:

┌─────────────────────────────────────────────────────────────────────┐
│                       PolicySleuth Results                          │
├──────────────┬───────┬──────────┬────────────────┬─────────────────┤
│ File         │ Cloud │ Severity │ Rule           │ Message         │
├──────────────┼───────┼──────────┼────────────────┼─────────────────┤
│ policy.json  │ aws   │ high     │ wildcard-action │ Wildcard used  │
│ policy.json  │ aws   │ high     │ priv-esc       │ iam:PassRole...│
└──────────────┴───────┴──────────┴────────────────┴─────────────────┘
Summary: 2 findings (high=2, warn=0) in 1 files

Machine-readable output for CI/CD pipelines, SIEM ingestion, or custom reporting:

{
  "findings": [
    {
      "file": "policy.json",
      "cloud": "aws",
      "rule": "wildcard-action",
      "message": "Wildcard used in actions: *",
      "severity": "high",
      "context": {}
    },
    {
      "file": "policy.json",
      "cloud": "aws",
      "rule": "priv-esc",
      "message": "Potential privilege escalation via: ['iam:PassRole']",
      "severity": "high",
      "context": {}
    }
  ],
  "summary": {
    "files": 1,
    "total": 2,
    "high": 2,
    "warn": 0
  }
}

CI/CD Integration

GitHub Actions

name: PolicySleuth
on: [push, pull_request]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'
      - run: pip install .
      - run: |
          policy-sleuth policies/ --format json --fail-on any > iam_report.json
          echo "Findings: $(jq '.summary.total' iam_report.json)"

GitLab CI

policy-sleuth:
  image: python:3.11-slim
  script:
    - pip install .
    - policy-sleuth policies/ --format json --fail-on high

Pre-commit Hook

# .pre-commit-config.yaml
repos:
  - repo: local
    hooks:
      - id: policy-sleuth
        name: PolicySleuth
        entry: policy-sleuth
        args: [--fail-on, high]
        language: python
        files: \.json$
        pass_filenames: true

Exit Codes

Code Meaning
0 No findings, or all findings are below the --fail-on threshold
2 Findings at or above the threshold — pipeline should fail

No exit code 1

PolicySleuth uses 2 for findings (not 1) to distinguish tool-detected issues from generic shell errors. This makes exit code semantics unambiguous in pipeline scripts.


Requirements

  • Python 3.9+
  • Dependencies: rich>=13.7.0

Extending PolicySleuth

Detection rules live in policy_sleuth/detectors.py. The risky action and role lists at the top of the file are intentionally conservative starting points; extend them for your environment without touching the parser or core logic.


Project Structure

policy_sleuth/
├── __init__.py
├── cli.py
├── core.py
├── detectors.py
└── parsers/
    ├── __init__.py
    ├── aws.py
    ├── gcp.py
    └── azure.py

MIT License — Copyright © 2026 Skellman.io