Self-hosted service
salesforce/policy_sentry avatar
salesforce/policy_sentry

Policy Sentry turns AWS documentation into least privilege IAM policies

IAM Least Privilege Policy Generator

2,173 stars158 forksPythonMIT

At a glance

What is it?
A Salesforce project that scrapes the AWS service reference into a local SQLite database, then generates IAM policies from a list of ARNs and the access level you actually need.
Who is it for?
Policy Sentry is at its best when you already know which resources a service account needs and want the policy written for you instead of by hand. The access level model is the part worth borrowing even if you never install it, because naming Read, Write, List, Tagging and Permissions Management as a requirement is more honest than naming actions.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 8 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 8, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Naming the access level instead of the API action

The argument the README makes is not really about security tooling, it is about vocabulary. Writing an IAM policy by hand means naming every API action a principal might call, and the AWS service reference is thousands of pages long. Policy Sentry asks for a smaller question instead: which resource, and which access level. The README puts it as a list of things a developer would say out loud, such as needing Read, Write and List access to one S3 bucket, or Permissions Management on one Secrets Manager secret.

Those words map onto the access levels AWS publishes per action, which is the whole trick. The README shows a snippet of the underlying documentation table, where `ssm:GetParameter` carries the access level Read, `ssm:DescribeParameters` carries List, and `secretsmanager:PutResourcePolicy` carries Permissions management. The project parses that documentation once, stores it, and reverses the lookup at policy generation time.

So the tool is really a database over scraped documentation, and the dependency list in `pyproject.toml` gives the mechanism away: beautifulsoup4 for parsing HTML, requests for fetching it, pyyaml for the data files, orjson for fast serialization, and click for the command line. There is no AWS SDK in that list, and no boto3. Nothing here talks to your account. It generates policy JSON from local data, which means it works offline and cannot silently query your organization.

Installing through three different package managers

Installation is a Homebrew tap, a pip install, or a container. The Homebrew route points at a tap hosted in the repository itself rather than at homebrew-core, which is the normal pattern for a project that wants its own tap:

bash
brew tap salesforce/policy_sentry https://github.com/salesforce/policy_sentry
brew install policy_sentry

The pip route installs as a user-level package:

bash
pip3 install --user policy_sentry

Both land the same `policy_sentry` executable. The console script is declared in `pyproject.toml` as `policy_sentry.bin.cli:main`, so the package is a plain Click application with no daemon and no service to keep running. That matters for how you would run it in CI: it is a step in a pipeline that writes a file, not something you deploy.

There is also shell completion, and the Bash version is a single eval line for your `.bashrc`:

bash
eval "$(_POLICY_SENTRY_COMPLETE=bash_source policy_sentry)"

That is the Click completion environment variable wired up to the project's own binary name, which is the standard way to ship completion for a Click app. The repository also carries a `Dockerfile` and a `terraform_module/` directory, so both the containerized workflow and the Terraform module are part of the distribution rather than afterthoughts. A Makefile is absent, replaced by a `justfile`, which matches the release notes for 0.15.1: the project moved from Invoke to just as part of its migration to uv.

The Dockerfile still targets a Python the project dropped

Here is a contradiction worth knowing about before you build the image. `pyproject.toml` declares `requires-python = ">=3.10"` and ships classifiers for Python 3.10 through 3.14. The `Dockerfile` opens with a build argument that defaults to an older interpreter:

dockerfile
ARG FROM_TAG=3.9-slim
FROM python:${FROM_TAG}

Release 0.15.1 is titled as a breaking change for exactly this: it drops Python 3.9 support. So the checked-in Dockerfile, as written, builds on an interpreter version the package no longer claims to support.

The same Dockerfile is stale in a second way. It installs dependencies from a file that the repository root no longer contains:

dockerfile
COPY requirements.txt /tmp/
RUN pip install -r /tmp/requirements.txt --no-cache-dir && \
    rm -rf /root/.cache/

There is no `requirements.txt` in the tree. The root has `pyproject.toml`, `uv.lock`, a `justfile` and a `.pre-commit-config.yaml`, which is the layout of a uv-managed project, and the 0.15.1 notes record the migration to uv explicitly. It also uses the `MAINTAINER` instruction, which Docker has deprecated in favour of `LABEL`. Both facts can be true at once: uv manages dependencies locally and in CI, and the Dockerfile was never updated to match. If you need the image, build it with a newer tag argument and expect to fix the dependency install step yourself.

The IAM database is a downloaded artifact, not a package constant

Everything the tool knows about AWS permissions arrives through a separate download step. The `utils/` directory holds `download_docs.py` and `copy_docs.py`, and the `justfile` wires them into named recipes:

bash
uv run ./utils/download_docs.py

The integration recipes in the same file run the CLI directly against that data, which is a decent window into what the tool actually does:

bash
uv run ./policy_sentry/bin/cli.py query action-table --service ram --access-level permissions-management
uv run ./policy_sentry/bin/cli.py query arn-table --service cloud9 --name environment

The first asks for every RAM action at a given access level. The second asks about the resource types a service supports. Those two tables, actions and resource types, are what policy generation joins against your ARN list.

The consequence is that a stale database produces a confidently wrong policy. It will not error when a service has added new actions or new resource types, because from the tool's point of view the data is simply what it has. The release history shows how this is handled: nearly every recent release includes a line reading Updates database, credited to the GitHub Actions bot, including several such entries in 0.16.0 alone. The database is refreshed on the project's schedule, not yours. Before you pin a version in a pipeline, find out when your local copy was last refreshed, because that timestamp, not the package version, decides whether the generated policy is complete.

What changed in 0.16.0, and why it can rewrite your existing policies

The 0.16.0 release, published 2026-04-14, contains two changes that alter generated output rather than just internals. One scopes dependent actions to matching resource types instead of a wildcard. The other adds support for the Deny effect in generated policies, which is a real capability gap closed, since an allow-only generator cannot express an explicit deny.

The wildcard change deserves attention from anyone already using the tool. When an action depends on another action, an older version would emit the dependency against a broad resource pattern. Version 0.16.0 narrows it to the resource types that actually match. Policies generated before and after this release are therefore not equivalent, and a diff between two runs can look alarming when it is actually a fix. Regenerate and review the diff rather than assuming the new output is wrong because it differs.

The same release replaced mypy with ty, and 0.15.1 replaced pre-commit with prek and Invoke with just. That is three build tooling substitutions in two releases, and it shows a small team consolidating on the uv ecosystem. It also means contributor setup instructions in older docs may not work as written. The repository is not archived and the last push was on 2026-09-01, so the project itself is active; the gap between that push and the April release is normal for a project whose visible releases follow database refreshes.

Where the README ends and the documentation takes over

The README is unusually honest about its own limits, and it makes the same point three times: walkthroughs and full documentation live on ReadTheDocs, the Salesforce engineering blog post covers the approach, and the cheat sheets for policy writing and for the IAM database query live as separate documents. The table of contents in the README lists sections for cheat sheets, commands, Python library usage, Docker and Terraform, all of which are entry points into documentation rather than instructions.

That split is appropriate for this project. Generating a correct policy needs the service reference knowledge, and that knowledge is large enough that keeping it in the README would be worse than linking to it. The useful thing the README does do is set expectations about the hard part, which is identifying resources rather than writing JSON. The blast radius argument in the overview is the real pitch: a scoped policy limits what a compromised credential can reach, and the time saved is time not spent reading the service reference.

The repository topics reinforce the audience: aws, aws-security, cloud, cloudsecurity, iam, iam-policy, salesforce and security, plus a leftover hacktoberfest tag from when the project was recruiting outside contributions. If you are evaluating it, the questions that matter are whether your resource list can be expressed as ARNs, whether your AWS account uses resource-based policies that need Deny, and how you will keep the database current. The docs answer the first two. The third is yours to solve.

Editorial conclusion

Policy Sentry is at its best when you already know which resources a service account needs and want the policy written for you instead of by hand. The access level model is the part worth borrowing even if you never install it, because naming Read, Write, List, Tagging and Permissions Management as a requirement is more honest than naming actions. What the repository does not settle is how fresh the bundled database is on your machine, and the release history shows that database is refreshed on its own schedule rather than on yours. Check the update command before trusting a generated policy in a deployment pipeline, and read the generated output once by hand, because a wildcard you did not ask for can still appear.

Frequently asked questions

Does Policy Sentry call the AWS API or need credentials?

No. The package dependencies are beautifulsoup4, requests, pyyaml, orjson, click and schema, with no AWS SDK in the list. The tool generates policy JSON from a local database built by scraping AWS documentation, so it needs no credentials and can run offline. The download step for refreshing that database is a separate command.

How do you keep the generated policies up to date with new AWS features?

The IAM database is refreshed on the project's schedule rather than yours, and recent releases include automated database updates attributed to the GitHub Actions bot. A stale copy will not fail loudly, because missing actions simply do not exist in the local data. Check when your copy was last downloaded before pinning a version in a deployment pipeline.

What is the difference between Policy Sentry and writing IAM JSON by hand?

The hand-written route requires naming each API action, while Policy Sentry takes an ARN plus an access level such as Read, Write, List, Tagging or Permissions Management, then looks up the matching actions in its database of AWS documentation. The access level vocabulary is the useful part even if you generate the JSON some other way, because it describes intent rather than implementation.

Can Policy Sentry generate explicit deny statements?

Yes, as of release 0.16.0 published on 2026-04-14, which added support for the Deny effect in generated IAM policies. That release also scoped dependent actions to matching resource types instead of emitting a wildcard, so policies generated by earlier versions can differ from current output without either being wrong.

Does the Policy Sentry Docker image work with the current Python requirement?

Not as checked in. The pyproject metadata requires Python 3.10 or newer and release 0.15.1 dropped Python 3.9, while the Dockerfile defaults to a 3.9 slim base image and installs from a requirements.txt that is no longer in the repository root. Build with a newer tag argument and expect to fix the dependency install step.

Official sources

  1. License: MIT
  2. Project website
  3. README
  4. Releases
  5. salesforce/policy_sentry on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/salesforce-policy-sentry.svg)](https://hysenlabs.com/projects/salesforce-policy-sentry)