free_tool
Who can do what on your GCP project?
An IAM policy quietly accretes broad grants: a primitive role here, a public binding there, a service account that ended up with admin. Paste yours and get a graded least-privilege report, with the tighter predefined role to use for each over-broad grant.
Runs entirely in your browser. Nothing is uploaded, sent to a server, or stored.
Least-privilege score
35/100
4 to fix · 2 warnings · 3 passed · 2 notes · 7 bindings
Grade F, score 35 out of 100, 4 to fix, 2 warnings, 3 passed. Top issue: No public access on non-public roles.No public access on non-public roles
criticalbinding 7Public principal(s) granted a non-public role: allAuthenticatedUsers on roles/viewer. allUsers exposes the resource to the entire internet and allAuthenticatedUsers to anyone with any Google account, so this hands real access to people you never named. Remove the binding and grant the specific users, groups, or service accounts that need it.
# Remove the public binding, then grant the specific principals:
# gcloud projects remove-iam-policy-binding PROJECT \
# --member=allUsers --role=ROLE
# gcloud projects add-iam-policy-binding PROJECT \
# --member=user:you@example.com --role=ROLEroles/owner is reserved, not handed out
criticalbinding 1roles/owner granted to: user:alex@example.com. Owner is a primitive role with full control over every resource plus the ability to change IAM, so it bypasses least privilege entirely. Keep owner to a tiny set of break-glass humans and replace day-to-day grants with predefined roles scoped to the job.
# Replace each day-to-day owner grant with predefined roles scoped to the job, e.g.:
# roles/run.developer, roles/storage.objectAdmin, roles/cloudsql.client
# or a service-specific admin role for the one service they manage.
# Keep roles/owner only for a small, named break-glass set, reviewed regularly:
# gcloud projects remove-iam-policy-binding PROJECT --member=user:you@example.com --role=roles/owner
# gcloud projects add-iam-policy-binding PROJECT --member=user:you@example.com --role=<scoped predefined role>Predefined roles used instead of roles/editor
highbinding 2roles/editor granted to: group:developers@example.com; serviceAccount:ci-deployer@my-project.iam.gserviceaccount.com. Editor is a primitive role with write access across almost every service in the project, far more than any one workload needs. Swap it for the predefined roles that cover the specific services the principal actually uses.
# Grant only the predefined roles the workload needs, e.g.:
# roles/run.developer, roles/storage.objectAdmin, roles/cloudsql.client
# instead of one blanket roles/editor.Service-account impersonation is tightly scoped
highbinding 4Impersonation granted without scoping: user:contractor@example.com via roles/iam.serviceAccountTokenCreator. roles/iam.serviceAccountTokenCreator lets a principal mint access tokens for a service account, and roles/iam.serviceAccountUser lets them deploy jobs that run as it, so either one lets the member inherit that service account's permissions. Granted at the project level it applies to every service account, which is a direct privilege-escalation path. Grant it on the one target service account, not the project.
# Grant impersonation on the specific service account resource, not the project:
# gcloud iam service-accounts add-iam-policy-binding TARGET_SA_EMAIL \
# --member=user:you@example.com \
# --role=roles/iam.serviceAccountTokenCreatorService accounts hold task-scoped, not admin, roles
mediumbinding 3Service account(s) granted a broad admin role: serviceAccount:image-uploader@my-project.iam.gserviceaccount.com on roles/storage.admin; serviceAccount:123456789-compute@developer.gserviceaccount.com on roles/secretmanager.admin. A workload service account usually reads or writes data, not manages the service, so an *.admin role (which includes setIamPolicy and delete) is more than it needs. If the key for that service account leaks, the blast radius is the admin role. Downgrade to the data-plane role: for example roles/storage.objectViewer or roles/storage.objectAdmin.
# Replace the admin role with the data-plane role the workload needs, e.g.:
# roles/storage.objectViewer or roles/storage.objectAdminDefault service accounts aren't broadly privileged
mediumbinding 6A default service account holds a privileged role: serviceAccount:123456789-compute@developer.gserviceaccount.com on roles/secretmanager.admin. The default Compute and App Engine service accounts get roles/editor on the project automatically, and anything you attach to a VM or function runs as them, so a compromised workload inherits that privilege. Create a dedicated service account per workload with only the roles it needs, and strip the broad grants off the default ones.
# Create a per-workload SA and run the workload as it:
# gcloud iam service-accounts create my-workload
# gcloud projects add-iam-policy-binding PROJECT \
# --member=serviceAccount:my-workload@PROJECT.iam.gserviceaccount.com \
# --role=<only the role it needs>IAM-admin and org-lifecycle roles aren't broadly granted
No principal holds an IAM-policy-editing or org-lifecycle admin role from the set checked, so nobody can quietly self-grant owner or mint impersonation keys. Good.
Service-scoped read instead of roles/viewer
No roles/viewer bindings. Read access is scoped to specific services rather than project-wide. Good.
Sensitive IAM-admin roles are condition-scoped
Sensitive IAM-admin roles either aren't granted or carry an IAM condition. That limits how far an admin grant can be stretched. Good.
Custom roles reviewed for scope
No custom roles in this policy. Note that predefined roles are maintained by Google as services add permissions, while a custom role's list is yours to keep current.
Inherited bindings aren't in this policy
IAM is additive down the resource hierarchy: a principal's effective access is this policy plus everything granted at the folder and organization above it. This analyzer only sees the policy you pasted, so a clean grade here means this level is sound, not that the principal's total access is. Check the parent folder and org bindings too, and use the Policy Analyzer in the console for the effective set.
A clean policy at one level is the floor, not the ceiling. The inherited folder and org bindings, the service-account key sprawl, and the workload identity wiring are where GCP access leaks. That's the kind of review I do.
Get your IAM tightened: book a callStatic analysis of the policy bindings you paste. It reads role names and members, not the permissions a custom role bundles or the bindings inherited from the parent folder and org, so a clean grade means this policy is sound, not that a principal's total effective access is. It accepts the JSON from gcloud projects get-iam-policy --format=json or one binding per line, runs entirely in your browser, and uploads nothing.
why_it_matters
Most GCP breaches are an over-broad grant, not a zero-day
The fastest way to lose a project is a public binding on a role that was never meant to be public, a human carrying roles/owner, or a service account that can impersonate another and inherit its permissions. None of those are exotic attacks. They are grants someone added to unblock a deploy and never walked back.
Least privilege means every principal holds the narrowest role that still lets it do its job, so a leaked key or a compromised workload has the smallest possible blast radius. This analyzer encodes the grants that actually escalate on GCP: public principals, the primitive roles, impersonation, and admin roles on workload service accounts, and names the predefined role to swap in for each one.
faq
Questions & answers
- What does the GCP IAM Least-Privilege Analyzer check?
- It parses your IAM policy and grades it on the grants that actually escalate privilege on Google Cloud: public principals (allUsers, allAuthenticatedUsers) on non-public roles, the primitive roles owner, editor and viewer, service-account impersonation via serviceAccountTokenCreator and serviceAccountUser, broad admin roles held by service accounts, privileged roles on the default Compute and App Engine service accounts, and whether IAM-admin roles carry a condition. Each finding names the offending role and member and suggests the tighter predefined role.
- What input does it accept?
- Either the JSON from gcloud projects get-iam-policy --format=json (the shape with a bindings array of role, members and an optional condition), or a simple list of one binding per line such as roles/owner -> user:you@example.com. It tries to parse the text as JSON first and falls back to the line form, so you can paste whichever you have.
- Why is a public binding on roles/run.invoker not flagged?
- Because some roles are public by design. A public unauthenticated Cloud Run service uses allUsers on roles/run.invoker, and a public bucket uses allUsers on roles/storage.objectViewer, so those are expected rather than mistakes. The analyzer only fails public principals on roles that were not meant to be open.
- Why does it treat service-account impersonation as a high finding?
- roles/iam.serviceAccountTokenCreator lets a principal mint access tokens for a service account, and roles/iam.serviceAccountUser lets them run jobs as it, so either one lets the member inherit that service account's permissions. Granted at the project level it applies to every service account, which is a direct privilege-escalation path. Grant it on the one target service account instead, ideally with an IAM condition, and the analyzer stops flagging it.
- Does it see roles inherited from the folder or organization?
- No. IAM is additive down the resource hierarchy, so a principal's effective access is the policy you paste plus everything granted at the parent folder and org. This analyzer only reads the bindings you give it, so a clean grade means that level is sound, not that the principal's total access is. Check the parent bindings too, and use the console's Policy Analyzer for the effective set.
- Is my IAM policy uploaded or stored anywhere?
- No. The analysis runs entirely in your browser. Nothing you paste is sent to a server or stored, so it is safe to check a policy that names real users, groups and service-account emails.
Want the whole IAM posture looked at?
A single policy is the floor. I'll review the inherited folder and org bindings, the service-account key sprawl, and the workload identity wiring that decide who can really reach what. Book a call, or leave your email.
Prefer proof first? See how this plays out in real case studies →