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.
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
*andservice:*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¶
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