AWS IAM Access Analyzer Deep Dive - External, Internal, and Unused Access Analyzers with Custom Policy Checks in CI

First Published:
Last Updated:

Most teams that run AWS at scale have solved the first half of the permissions problem. They know how to write a policy. They have review conventions, a naming scheme, maybe a module library. What they usually do not have is the second half: a mechanical answer to who can actually reach this resource right now, and which of the permissions we granted last year is anyone still using.

That second half does not scale on human attention. A bucket policy is easy to read and hard to reason about, because the answer depends on the interaction of a resource policy, an identity policy, a permission boundary, a service control policy, and a resource control policy, evaluated over every principal that exists. Reading them one at a time and forming an opinion is guesswork with good intentions. IAM Access Analyzer replaces the guess with automated reasoning, and it does so along three independent axes that many teams do not realize are separate products with separate lifecycles.

This guide is about operating those three axes deliberately: what each analyzer reads, what it emits, where its blind spots are, how the zone of trust changes the answer, and how to turn the same reasoning engine into a pull request gate that refuses to merge a privilege escalation.

Scope and honesty notes. This article covers IAM Access Analyzer's external, internal, and unused access analyzers, zone of trust design, findings lifecycle and archive rules, basic and custom policy checks, CI integration, policy generation from CloudTrail, and multi-account rollout. Every factual claim below was verified against AWS's official documentation on 2026-08-04; supported resource types, quotas, and API enumerations change, so confirm current values for your account and Region before you rely on them. No AWS API calls were executed to produce this article and no measurements were taken — nothing here is presented as a benchmark, and every code fragment follows AWS's published CLI reference rather than a tested deployment. This article does not discuss pricing. Which capabilities carry a charge is a genuine design input, and it belongs on AWS's pricing page rather than in a design guide that will outlive any figure quoted in it, so every recommendation below is argued from findings volume, review workload, coverage, and blast radius instead. Operations that are irreversible — deleting an analyzer, deleting an IAM role or access key, deactivating credentials — destroy findings or break workloads and are flagged with warnings wherever they appear.

Several adjacent topics are deliberately delegated to existing articles rather than repeated here:

  • The mechanics of how IAM decides whether a request is allowed — explicit deny, permission boundaries, session policies, evaluation order — belong to IAM Policy Evaluation Logic Step-by-Step. This guide assumes you already know that order and focuses on the tooling that verifies its outcome.
  • The catalog of policy shapes that are dangerous — wildcard actions, wildcard trust, confused deputy doors — belongs to IAM Anti-Patterns. The failure modes in Section 12 of this guide are about operating the analyzers badly, not about writing policies badly.
  • Service control policy and resource control policy design belongs to AWS Organization Guardrails. Here they appear only as inputs whose evaluation status is reported on an internal access finding.
  • Setting up federated access and permission sets belongs to the AWS IAM Identity Center Complete Setup Guide.
  • Application-level authorization with Cedar belongs to the AWS Verified Permissions and Cedar Policy Language Complete Guide. IAM Access Analyzer never evaluates Cedar policies.
  • Vocabulary — principal, permission boundary, ABAC, zone of trust — is defined in the AWS IAM Glossary.

Table of Contents

  1. 1. Introduction
  2. 2. Where Access Analyzer Sits in IAM Operations
  3. 3. The Three Analyzer Types
  4. 4. Zone of Trust Design
  5. 5. External Access Findings in Practice
  6. 6. Internal Access Findings
  7. 7. Unused Access Findings
  8. 8. Policy Validation and Custom Policy Checks
  9. 9. Blocking Privilege Escalation in CI
  10. 10. Policy Generation from CloudTrail
  11. 11. Multi-Account Operations
  12. 12. Failure Modes and Anti-Patterns
  13. 13. Frequently Asked Questions
  14. 14. Summary
  15. 15. References

1. Introduction

The audience for this guide is the engineer who owns permissions in a running AWS environment: security engineers, platform engineers, and cloud architects who can already write a policy and now need the operational layer around it. If your organization has more IAM roles than people who remember why each one exists, this is the layer you are missing.

The guide follows the shape of the work. Sections 2 through 4 establish what the service is and the single configuration choice — the zone of trust — that silently determines what every finding means. Sections 5 through 7 take each analyzer type in turn, because they have genuinely different inputs, different resource coverage, different lifecycles, and different failure modes. Sections 8 and 9 move from monitoring to prevention: the policy checks that run on a document before it is ever deployed, and how to wire them into a pull request so that a privilege escalation is rejected instead of detected. Section 10 covers policy generation, Section 11 covers organization-wide rollout, and Section 12 collects the ways this all goes wrong.

Three ideas run through all of it:

  • An analyzer is a question, not a scanner. Each analyzer type answers exactly one question, and it is easy to assume it answers a broader one. The external access analyzer does not tell you who inside your organization can read a bucket. The unused access analyzer does not tell you whether a resource is shared. Coverage gaps in practice come from assuming one analyzer covers the other's question.
  • The zone of trust is a semantic setting, not a scope setting. Choosing the account or the organization does not just widen or narrow the search. For internal access analysis it changes whether service control policies and resource control policies are folded into the computed permissions at all, which means the same resource can produce different action lists depending on a setting most teams pick without thinking.
  • Every suppression is a decision you will inherit. Archive rules, tag exclusions, and account exclusions are the mechanisms that make findings reviewable at all. They are also the mechanisms by which a real risk becomes permanently invisible. Section 12 is largely a list of suppressions that outlived their justification.

2. Where Access Analyzer Sits in IAM Operations

2.1 Two loops, not one

Permissions work has two loops running at different frequencies, and IAM Access Analyzer has a distinct capability for each.

The authoring loop runs at the speed of a pull request. Someone changes a policy document; something should decide whether the change is acceptable before it is applied. This is where policy validation and custom policy checks live: they take a JSON document as input, return a verdict, and never touch your account's live state.

The monitoring loop runs continuously against what is actually deployed. It answers questions about the environment as it exists, not as it was proposed. This is where analyzers and findings live: you create an analyzer, it watches supported resources, and it emits findings that have a status and a lifecycle.

Confusing the two is the most common structural mistake. Teams wire findings into a Slack channel and call it a control, but findings are a lagging signal — a finding means the risky configuration is already live. Conversely, teams add a CI check and assume the environment is now safe, but a CI check only sees documents that went through CI, and consoles and drifted stacks do not.

AWS documents six capabilities under the IAM Access Analyzer name: external access analyzers, internal access analyzers, unused access analyzers, policy validation against grammar and best practices, custom policy checks against your own standards, and policy generation from CloudTrail activity. The first three belong to the monitoring loop, the next two to the authoring loop, and policy generation sits between them as a refinement tool.

2.2 What automated reasoning proves, and what it does not

IAM Access Analyzer's findings are not pattern matches and not sampled tests. AWS describes the underlying approach as provable security, built on automated reasoning: the policy is translated into a mathematical formula and a solver proves or disproves properties about it. AWS's own explanation of Zelkova, the reasoning engine behind these checks, draws the distinction sharply — a policy simulator lets you test individual requests, while the reasoning engine makes statements about all possible requests.

That is a strong guarantee, and it is worth stating precisely what it covers, because the guarantee is about the policies that were analyzed, not about your security posture.

  • The proof is over the policies the analyzer reads. For external access, AWS states that IAM Access Analyzer analyzes only resource-based policies, and only for resources in the Region where the analyzer is enabled. A resource type that is not in the supported list produces no finding at all — not a clean finding, no finding.
  • The proof is over the state at the time of analysis. Findings are refreshed on policy change and on a periodic scan, and there are documented delays, including cases where a change does not trigger a rescan at all.
  • The proof does not cover what a principal will do with the access. It is an authorization question, not a behavioral one.
  • An analyzer that cannot read a resource does not silently skip it — it emits an error finding, which is a distinct thing from "no access found". Section 5 covers why treating error findings as noise is how coverage quietly disappears.

Absence of a finding, therefore, means "no external access was provable from the policies I could read, for a resource type I support, in this Region, as of the last analysis". That is a much more useful statement than most tools can make, and it is not the same as "this resource is safe".

3. The Three Analyzer Types

3.1 One service, three independent products

The most consequential thing to understand about IAM Access Analyzer is that "an analyzer" is not one object with three modes. Each type is created separately, has its own quota, its own resource coverage, its own findings, and its own archive rules. Turning on one gives you nothing from the others.

The three IAM Access Analyzer types and what each one reads and emits
The three IAM Access Analyzer types and what each one reads and emits
AWS's own summaries define them as follows. External access analyzers identify resource policies that grant access to an external principal, using logic-based reasoning over the resource-based policies in your environment, where an external principal can be another AWS account, a root user, an IAM user or role, a federated user, an AWS service, or an anonymous user. Internal access analyzers identify which principals within your organization or account have access to selected resources. Unused access analyzers identify unused IAM roles, unused access keys, unused console passwords, and IAM principals with unused service-level and action-level permissions.

The following comparison is drawn from the IAM User Guide's Using AWS Identity and Access Management Access Analyzer, IAM Access Analyzer findings, and IAM Access Analyzer supported resource types for external and internal access pages, and from the CreateAnalyzer API reference, all as of 2026-08-04. It is a summary of those pages, not an exhaustive specification.

* You can sort the table by clicking on the column name.

DimensionExternal accessInternal accessUnused access
Question answeredCan anyone outside the zone of trust reach this resource?Which principals inside the zone of trust can reach this resource?Which principals and permissions has nobody used?
Primary inputResource-based policies and access control lists on supported resourcesIdentity policies, resource policies, service control policies, and resource control policies for explicitly selected resourcesLast accessed information for all IAM roles and users
Resource types supported15 types (Section 5.1)6 types (Section 6.2)IAM users and roles only
Resource selectionAll supported resources in the Region, automaticallyExplicit — by account, by resource type, or by exact ARNAll IAM users and roles, minus exclusions
Regional behaviorPer Region; you must create one in every Region with supported resourcesPer Region; create one in every Region you want monitoredFindings do not change based on Region; one analyzer is enough
CreateAnalyzer type valuesACCOUNT, ORGANIZATIONACCOUNT_INTERNAL_ACCESS, ORGANIZATION_INTERNAL_ACCESSACCOUNT_UNUSED_ACCESS, ORGANIZATION_UNUSED_ACCESS
Finding types emittedPublic access, cross-account accessAccess paths, tagged INTRA_ACCOUNT or INTRA_ORGUnused role, unused access key, unused password, unused permission
Preview before deployYes — access previews for 14 resource configurationsNoNo

Two consequences of that table are worth stating explicitly, because they are the most common rollout mistakes.

An unused access analyzer must be created separately from an external access analyzer. They are different type values on the same API call and neither implies the other. A team that enabled IAM Access Analyzer in 2020 and never revisited it has external access coverage and zero unused access coverage.

External and internal are complements, not alternatives. AWS states this directly: the combination of external and internal access findings with the same zone of trust provides a comprehensive analysis of all possible access to a particular resource, both from within and outside your defined trust boundary. If you only run the external analyzer, an over-broad identity policy that lets the entire organization read a production database snapshot generates nothing at all, because nobody outside the zone of trust was granted anything.

3.2 Quotas that shape the design

Analyzer quotas are unusually load-bearing for a security service, because two of the most important ones cannot be raised. The following are from the IAM Access Analyzer quotas page as of 2026-08-04.

QuotaDefaultMaximumDesign consequence
Account-level analyzers per analyzer type per account per Region11You cannot run two differently-configured account analyzers of the same type side by side. Changing a tracking period or an exclusion set is an in-place change to the only analyzer you have.
Organization-level external or unused access analyzers per analyzer type per account per Region520You can run several organization analyzers with different configurations — for example, unused access analyzers with different tracking periods for different reviews.
Organization-level internal access analyzers per organization per Region11There is exactly one organization-wide internal access analyzer per Region. Its resource selection is a single shared list that every team has to negotiate.
Archive rules per analyzer1001,000Each rule accepts up to 20 values per criterion, so a rule set is a real budget, not an unlimited allowlist.
Access previews per analyzer per hour1,0001,000Enough for interactive use and modest CI use; not a bulk analysis mechanism.
CloudTrail time range per policy generation90 days90 daysPolicy generation cannot look further back than a quarter, which bounds how confidently it can capture rare workflows.
Concurrent policy generations11Policy generation is inherently serial; it is not a batch remediation tool.

The organization-level internal access quota of one per Region is the one that changes an operating model. Because there is a single organization-wide internal access analyzer and its resource list is explicit, somebody has to own that list. Section 6 covers how to structure it.

4. Zone of Trust Design

4.1 The definition, and why it is more than a scope

When you create an analyzer you choose an organization or an AWS account. AWS calls that choice the zone of trust, and defines it as determining the type of access that IAM Access Analyzer considers to be trusted. Every principal in the chosen organization or account is trusted; the resources and principals inside it comprise the zone.

For external access the consequence is straightforward. If you choose the organization, granting an S3 bucket in one member account to a principal in another member account produces no finding, because both are inside the zone. Grant the same permission to an account outside the organization and you get a finding. Choose a single account as the zone instead and the first case now generates a finding too, because the other member account is outside your zone.

Neither setting is more correct in the abstract. They answer different questions:

  • Organization as the zone of trust answers "what leaves the company". This is the right default for a central security team. It produces far fewer findings and each one is a real question about a third party.
  • Account as the zone of trust answers "what leaves this account". This is the right setting for an account that must not share with its siblings — a regulated workload, a customer-dedicated account, a production account that should never be readable from a sandbox. It produces many more findings, most of which are intended cross-account access, which is exactly what archive rules exist for.

Running both is legitimate and sometimes necessary, subject to the quota in Section 3.2: an account can have one account-level external analyzer of its own, and the organization's delegated administrator separately runs organization-level analyzers.

4.2 The setting that changes the arithmetic: SCPs and RCPs in internal access findings

For internal access analysis the zone of trust is not merely a filter. It changes what is folded into the computed permissions, and AWS documents this precisely in the InternalAccessDetails data type through two fields, serviceControlPolicyRestriction and resourceControlPolicyRestriction. Their values are defined as follows.

ValueMeaningWhen it applies
APPLIEDThe policy is present in the organization and IAM Access Analyzer included it when computing effective permissions. If an SCP blocks s3:DeleteObject, that action is not listed on the finding.Only for internal access findings with the organization as the zone of trust
APPLICABLEThe policy is present in the organization but IAM Access Analyzer does not include it in the evaluation. If an SCP blocks s3:DeleteObject, that action is still listed on the finding.Only for internal access findings with the account as the zone of trust
NOT_APPLICABLEThere was no such policy in the organization. For account-scoped findings this can also mean no policy applied to that principal or resource.Either scope
FAILED_TO_EVALUATE_SCP / FAILED_TO_EVALUATE_RCPThere was an error evaluating the policy.Either scope

Read that table twice, because the practical effect is easy to miss. An account-scoped internal access analyzer reports access that your organization's guardrails already block. The action list on the finding is the permission grant, not the effective permission. If your organization relies on service control policies to deny a class of actions everywhere — a very common design — an account-scoped internal analyzer will produce findings listing exactly those denied actions, and a reviewer who does not know this will spend the afternoon chasing access that cannot actually occur.

Conversely, an organization-scoped internal access analyzer folds the guardrails in and gives you the effective answer. That is usually what you want, and it is another reason the single organization-level internal analyzer per Region is the one to invest in. Use the account-scoped variant when you specifically want to see the raw grants regardless of guardrails — for example, when auditing whether the guardrail is the only thing standing between a principal and the data.

4.3 Delegated administrator, and what happens when you change it

For organization-wide analyzers, AWS supports designating a member account as the delegated administrator for IAM Access Analyzer. Only the management account can add, remove, or change it. In practice this is the security or audit account, which is where you want findings to land anyway.

The important operational fact is what happens when you change it, and it is more destructive than most people expect. AWS documents that when you switch the delegated administrator to a different account, the former delegated administrator account loses permission to all analyzers it created to analyze access across the organization. Those analyzers move to a disabled state, stop generating and updating findings, and their existing findings become inaccessible. They can be reached again only by configuring that same account as the delegated administrator once more. The same thing happens automatically if the delegated administrator account leaves the organization.

Warning: this is effectively an outage of your organization-wide access visibility, and the recovery path is to restore the old delegated administrator. If you know you will not reuse the account, AWS's guidance is to delete the analyzers first — which deletes all their findings — so that the new delegated administrator's analyzers regenerate equivalent findings cleanly rather than leaving orphaned, unreachable ones behind. Either way, plan a delegated administrator change as a maintenance activity with a communication to whoever consumes the findings, not as a settings tweak. The organization management account retains administrator permissions and can be used to reach findings in the interim.

5. External Access Findings in Practice

5.1 What is actually analyzed

For external access, AWS analyzes only resource-based policies, applied to resources in the Region where the analyzer is enabled. As of 2026-08-04, the IAM Access Analyzer supported resource types for external and internal access page lists the following 15 resource types for external access. This is a transcription of that page on that date and nothing more — AWS adds types, so re-read the page rather than trusting this list a year from now.

Resource typeWhat IAM Access Analyzer readsNotes that change the design
Amazon S3 bucketsBucket policy, ACL, and access points including multi-Region access pointsAccount-level Block Public Access settings are evaluated only once every 6 hours
Amazon S3 directory bucketsDirectory bucket policy including conditionsAccess point policies for cross-account access points are outside the analyzer account and are not analyzed
IAM rolesTrust policiesAn IAM role is a global resource, so one external grant produces a finding in every enabled Region
AWS KMS keysKey policies and grantsIf the key policy denies the analyzer's service-linked role, you get an ACCESS_DENIED error finding instead of an analysis
AWS Lambda functions and layersResource-based policies on functions and layersPolicies attached to aliases and specific versions invoked by qualified ARN are not reported
Amazon SQS queuesQueue policies including conditions
AWS Secrets Manager secretsResource policies including conditions
Amazon SNS topicsTopic policies including conditionsAddPermission is another way external access appears
Amazon EBS volume snapshotsCreateVolumePermissions sharing settings, not a policyUnencrypted snapshots can be shared publicly; encrypted ones only with accounts
Amazon RDS DB snapshotsSharing permissions on manual snapshots onlyAccess configured on the database itself is not analyzed
Amazon RDS DB cluster snapshotsSharing permissionsEncrypted cluster snapshots cannot be public; both kinds can be shared with up to 20 accounts
Amazon ECR repositoriesRepository policies including conditionsA principal must also hold ecr:GetAuthorizationToken through an identity policy to be considered externally available
Amazon EFS file systemsFile system policies including conditionsMounting from another account counts as external unless that account is in your zone of trust
Amazon DynamoDB streamsResource-based policiesA finding is generated if at least one cross-account action is allowed
Amazon DynamoDB tablesResource-based policiesCovers tables and indexes

The IAM role row deserves emphasis because it produces the single most confusing operational artifact in this service. Roles are global, external access analyzers are regional, and so one over-permissive trust policy generates one finding per enabled Region. A team that enables the analyzer in 17 Regions gets 17 findings for one role, all of which resolve when the trust policy is fixed, and all of which have to be archived separately if the access is intended — archive rules are per analyzer, and analyzers are per Region.

5.2 The findings lifecycle

An external or internal access finding has one of four states, and the transitions are not symmetric.

  1. Active — the access exists and has not been reviewed. This is the only state you should be paging on.
  2. Archived — you decided the access is intended. Archiving does not delete the finding; you can filter for archived findings and unarchive them at any time, which returns them to Active.
  3. Resolved — the access no longer exists, because the policy changed. IAM Access Analyzer sets this itself. Resolved findings are deleted 90 days after the last update to the finding status, so they are an audit trail with an expiry date, not a permanent record.
  4. Error — IAM Access Analyzer could not analyze the resource. Documented error types include INTERNAL_ERROR and ACCESS_DENIED, the latter typically when the service-linked role is denied access to the resource.

Two transition rules matter operationally.

Error findings do not become Resolved. AWS documents that when you fix the underlying problem, an error finding is removed completely rather than transitioning to Resolved. There is no closure record. If you want evidence that an error was addressed, capture it from the EventBridge stream, not from the findings list.

Changing a policy does not always mutate the existing finding. If your change leaves external access in place but for a different principal or a different permission, IAM Access Analyzer resolves the original finding and generates a new Active one. A remediation that "did not clear the finding" is often a remediation that cleared one finding and created another.

Timing is documented and worth internalizing, because it is the source of most "the analyzer is broken" tickets:

  • A new or updated policy is analyzed within about 30 minutes for external access.
  • For internal access, re-analysis after a change may take several minutes or hours.
  • All policies are rescanned automatically every 24 hours.
  • Amazon S3 account-level Block Public Access settings are evaluated only once every 6 hours, so turning on Block Public Access does not clear a public finding immediately. The Security Hub integration page documents these account-level changes as taking up to 12 hours to be reflected.
  • Multi-Region access point changes are also evaluated once every 6 hours.
  • A change to a resource control policy alone, without a change to the resource-based policy, does not trigger a rescan. The finding updates on the next periodic scan within 24 hours.
  • A CloudTrail log delivery problem can likewise prevent a rescan from being triggered.

When you need an answer now rather than on the next scan, the documented mechanism is the Rescan link on the finding details page, or the StartResourceScan API operation.

5.3 Archive rules, and the difference between suppression and expression

Archive rules automatically archive new findings matching criteria you define, and can optionally be applied retroactively to existing findings. That single sentence hides the most important governance decision in this service, because an archive rule is the only way to say "this access is intended" and it is also the only way to make a real risk invisible forever.

The mechanics, from the archive rules page as of 2026-08-04:

  • A rule archives new findings that match. Existing findings are not archived unless you explicitly choose Create and archive active findings (or Save and archive active findings when editing).
  • Each rule can include up to 20 values per criterion, and you can combine multiple criteria.
  • IAM Access Analyzer does not validate the values you put in a filter. AWS states this plainly: if you add a rule matching an AWS account, any value is accepted, even one that is not a valid account number. A typo produces a rule that silently never matches — or worse, a rule that matches more than you meant.
  • Rules are per analyzer and per Region. Deleting a rule deletes it only from the analyzer in the current Region; you must delete rules separately for every analyzer you created elsewhere.
  • Rules can be supplied inline at creation time through the archiveRules parameter of CreateAnalyzer, which is how you keep them in infrastructure as code rather than in console history.

The design guidance that follows from this is narrower than "use archive rules to reduce noise":

Write rules that describe an intent, not rules that describe a volume. A rule that archives all findings for a specific partner account is a statement your organization can defend and revisit. A rule that archives all findings of a resource type because there were too many of them is a decision to stop looking.

Never write an archive rule for public access without saying so. AWS documents a specific criterion for this: for external access findings you can include the criterion Public access set to false, which guarantees the rule cannot archive a finding for public access. Any archive rule that could match a public-access finding should either carry that criterion or be justified in writing.

Give every rule an owner and an expiry. Nothing in the service expires a rule. The tag on your analyzer and the rule name are the only metadata you get, so encode the reason and the review date in the rule name if you have nowhere better.

Distinguish suppression from expression. For intended cross-account sharing, the archive rule is the record that it was intended — the finding still exists, still appears in archived views, and unarchives if you change your mind. That is a defensible artifact for an auditor. Deleting the analyzer, by contrast, deletes all its findings and cannot be undone.

5.4 Previewing access before you deploy

Access previews let you evaluate a proposed resource configuration against an existing analyzer and see the findings it would produce, without applying anything. AWS exposes this through CreateAccessPreview, GetAccessPreview, ListAccessPreviews, and ListAccessPreviewFindings, and through the Amazon S3 console for buckets.

The Configuration union in the API reference accepts 14 resource configurations as of 2026-08-04: S3 buckets, S3 directory buckets, IAM roles, KMS keys, SQS queues, Secrets Manager secrets, SNS topics, EFS file systems, ECR repositories, EBS snapshots, RDS DB snapshots, RDS DB cluster snapshots, DynamoDB tables, and DynamoDB streams.

The property that makes previews useful in review is changeType on each preview finding, which tells you how the proposal compares to reality:

  • New — access that does not exist today and would be introduced.
  • Unchanged — an existing finding that would remain as it is.
  • Changed — an existing finding whose status would change. AWS's example is instructive: a Changed finding with preview status Resolved and existing status Active means the proposed change would fix an existing problem.

That third value is what makes access previews a review aid rather than just a linter. A pull request that produces three Changed-to-Resolved findings and zero New ones is a remediation, and you can say so mechanically.

Access previews are asynchronous. IAM Access Analyzer emits an Access Preview State Change event to EventBridge when the preview is created, completes, or fails, so a CI job can wait on the event rather than polling. The quota is 1,000 access previews per analyzer per hour.

6. Internal Access Findings

6.1 A different question entirely

External access analysis starts from the resource policy and asks who outside can get in. Internal access analysis starts from the resource and asks which principals inside the zone of trust have an access path to it. AWS describes it as generating findings when there is a possible access path between an IAM role or user within your organization and your specified resources, using automated reasoning to evaluate all IAM policies.

The practical difference is which policies are read. External access analysis reads resource-based policies. Internal access analysis has to consider the whole stack — identity policies as well as resource policies, and, depending on the zone of trust, service control policies and resource control policies too, as Section 4.2 described. AWS's What's New announcement for the capability describes it as using automated reasoning to evaluate identity policies, resource policies, service control policies, and resource control policies.

That is why the capability is scoped so tightly. You cannot ask this question about everything you own; you ask it about the resources that would matter if the answer were wrong.

6.2 Selection is explicit, and the selection is the design

Internal access analyzers do not sweep the account. You choose the resources, and there are three documented ways to do it:

  1. By account and resource type — add resources from selected accounts, choosing either all supported resource types or a specific subset.
  2. By exact ARN — paste account owner ID and resource ARN pairs, one per line.
  3. By CSV upload — AWS suggests using AWS Resource Explorer to search your accounts and export a CSV, then uploading it.

The ARN path carries a constraint that will bite anyone scripting it: ARNs must be exact matches and wildcards are not supported, and for Amazon S3 only bucket ARNs are accepted — object ARNs and prefixes are rejected.

Supported resource types for internal access, as of 2026-08-04, are six:

  • Amazon S3 buckets
  • Amazon S3 directory buckets
  • Amazon RDS DB snapshots
  • Amazon RDS DB cluster snapshots
  • Amazon DynamoDB streams
  • Amazon DynamoDB tables

There is a trap here for anyone reading the API reference rather than the user guide. The resourceTypes field of InternalAccessAnalysisRuleCriteria has a valid values enumeration of 17 types, inherited from the shared resource type enum, but the same page states in prose that only the six above are currently supported for internal access analyzers. A configuration that passes API validation with AWS::KMS::Key in the list is not a configuration that analyzes KMS keys. Validate against the prose, not the enum.

6.3 Reading an internal access finding

An internal access finding carries an accessType of either INTRA_ACCOUNT or INTRA_ORG, telling you whether the principal and the resource live in the same account. Alongside it you get the principal ARN, principalType of IAM_ROLE or IAM_USER, the principal's owning account, the actions the principal can perform, any conditions from the analyzed statement, and the SCP and RCP restriction status from Section 4.2.

Building a review standard out of that is mostly a matter of deciding in advance which access is structural and which is a question:

  • Structural, expected: the account's own administrative role, the backup role, the observability role, break-glass roles. These are the natural targets for an archive rule keyed on principal or on a role naming convention.
  • Structural, needs a second look: INTRA_ORG access from an account that has no business relationship with the data. This is the finding class the capability exists for — a role in a sandbox account with a broad identity policy and a resource whose policy never anticipated it.
  • Always a question: any principal with write or delete actions on a resource whose owner did not expect writers, and any access that appears with serviceControlPolicyRestriction of APPLICABLE in an account-scoped analyzer, which means the guardrail is the only reason it is not real.

There is one scale limit to plan against: IAM Access Analyzer cannot generate internal access findings for organizations that contain more than 70,000 principals (IAM users and roles combined). If your organization is near that number, the internal access analyzer is not the control you can rely on, and reducing principal count — which the unused access analyzer directly supports — becomes a prerequisite rather than a cleanup task.

6.4 Running external and internal together

AWS is explicit that the two combined, with the same zone of trust, give comprehensive analysis of all possible access to a resource. The operational pattern that follows:

  1. Run the external access analyzer everywhere, with the organization as the zone of trust, in every Region with supported resources. This is your perimeter.
  2. Run one organization-level internal access analyzer per Region, and curate its resource list to the assets where "who inside can read this" is a question you must be able to answer — the production data stores, the snapshots of them, the buckets holding regulated data.
  3. Review them together. The console supports selecting one external and one internal analyzer at a time and showing a combined summary, which is the intended workflow.

The resource list is the artifact that needs governance. Because there is exactly one organization-level internal analyzer per Region, its list is shared across the whole company. Maintain it in infrastructure as code, drive it from a tag convention on the resources themselves, and treat additions as a change with a reviewer rather than a console click.

7. Unused Access Findings

7.1 What it looks at

An unused access analyzer continuously monitors all IAM roles and users in the selected account or organization and emits four documented finding types:

  • Unused roles — roles with no access activity within the specified usage window.
  • Unused IAM user access keys — keys not used to access the account in the window.
  • Unused IAM user console passwords — passwords not used in the window.
  • Unused permissions — service-level and action-level permissions not used by a role in the window. IAM Access Analyzer determines what a role can do from its identity-based policies, and supports service-level review for all services; action-level coverage is limited to the services and actions listed in the IAM last accessed information reference.

Unlike the other two analyzer types, findings do not change based on Region. AWS states plainly that creating an unused access analyzer in each Region where you have resources is not required, and its guidance is to enable it in the single Region where you want to review and operate findings, to avoid duplicate findings. Since IAM roles and users are global, an analyzer in each of several Regions produces several copies of the same finding, each needing its own archive decision.

One coverage exclusion is documented and easy to miss: service-linked roles are not analyzed for unused access activity. If your inventory of roles and your findings count disagree, this is usually why.

7.2 The tracking period is two constraints, not one

The tracking period is where most teams misconfigure this analyzer, because the documented behavior is stricter than the obvious reading.

The obvious constraint is the one everybody knows: set 90 days and a role with no activity for 90 or more days generates a finding.

The constraint people miss is the other half. AWS's console procedure states it directly: the analyzer will only evaluate permissions for IAM entities that have existed for the entire tracking period. With a 90-day tracking period, only permissions that are at least 90 days old are analyzed at all. A role created six weeks ago and never used is not reported — it is not yet old enough to be evaluated.

That is the correct behavior, because a newly created role has not had a chance to be used. But it has two design consequences:

  • A long tracking period is a long blind spot for new grants. Set 365 days and permissions granted in the last year are outside the evaluation entirely. Detecting over-granting on new roles is not this analyzer's job; that is the authoring loop in Sections 8 and 9.
  • Two analyzers with different periods answer different questions. Because the organization-level quota permits several unused access analyzers per Region, a short-period analyzer (aggressive cleanup of things that were never used) and a long-period analyzer (conservative retirement of things that used to be used) are a legitimate pair, subject to the duplicate-findings consideration in Section 7.1.

Verify the accepted range in your own account before scripting it. The IAM User Guide's create procedure and the UnusedAccessConfiguration API reference both state a range of 1 to 365 days as of 2026-08-04, while AWS console help panel pages still document a maximum of 180 days. That is an inconsistency within AWS's own published documentation, not a subtlety of behavior, and the safe response is to test the value your automation intends to use rather than to trust either page.

7.3 Exclusions

Unused access analyzers support two exclusion mechanisms, both expressed through the analyzer's analysis rule.

Exclude AWS accounts (organization-level analyzers only). You can name account IDs directly or pick them from the organization hierarchy. The constraints: the excluded set cannot include the organization analyzer owner account, and the list cannot exceed 2,000 account IDs.

Exclude IAM users and roles by tag. Specify key-value pairs; entities matching them generate no findings. A tag key with no value specified applies the rule to all principals carrying that tag key, which is the mechanism you want for a convention like access-review-exempt.

There is one behavior here that will silently erode your coverage in the good direction and your exclusions in the bad one. New accounts added to your organization are not excluded, even if you previously chose to exclude all current accounts in an organizational unit. The exclusion is a snapshot of account IDs, not a rule about the organizational unit. If your intent was "the sandbox OU is out of scope", that intent is not what you configured, and every new sandbox account will start producing findings. Depending on your goals that is either a welcome default or a recurring surprise; either way, re-derive the exclusion list from the organizational unit on a schedule instead of assuming it tracks.

Finally, account removal has a documented lifecycle: if a member account leaves the organization, the unused access analyzer stops generating and updating findings for it after 24 hours, and findings associated with that account are permanently removed after 90 days.

7.4 Converging on least privilege without breaking production

Unused access findings are the only findings in this service whose remediation is inherently destructive. Resolving an external access finding removes a grant nobody was supposed to have. Resolving an unused access finding removes a grant somebody had and might still need on a schedule you cannot see.

IAM Access Analyzer helps with the "what should it become" half through finding recommendations: GenerateFindingRecommendation creates a recommendation for an unused permissions finding, and GetFindingRecommendation retrieves it. AWS's walkthrough shows the output as a proposed policy in which broad grants such as s3:Get* and s3:List* are scoped down to the actions actually observed, and unused actions are dropped.

A recommendation is a proposal derived from observed activity. It is not a proof that the removed actions are unnecessary, and adopting one without review has the same failure mode as adopting a generated policy without review (Section 10).

The sequence below is deliberately conservative. Every step from 4 onward is irreversible or breaks access if the analysis was wrong; none of it should be automated without a rollback plan, and none of the destructive steps were executed in preparing this article.

  1. Filter to what you can decide. Start with unused access keys and unused console passwords for IAM users. These are the highest-value, lowest-ambiguity findings, and they are also the ones that should be trending to zero anyway if you have moved to federated access.
  2. Archive what is expected. Break-glass and disaster-recovery roles are supposed to look unused. AWS's own example is an archive rule keyed to a DisasterRecoveryRole naming convention. Do this before you review anything else, so that the remaining list is genuinely a work queue.
  3. Corroborate before you touch a role. The analyzer sees last-accessed information. Cross-check against CloudTrail for the same window, and against whoever owns the workload, before concluding a role is dead.
  4. Deactivate before you delete. For access keys, make the key inactive and wait through at least one full business cycle. This is the only reversible step in the sequence, and skipping it converts a five-minute rollback into an incident.
  5. Remove permissions before you remove principals. Scoping a policy down is easier to reverse than recreating a role and everything that trusted it.
  6. Delete last, and expect to lose the finding history. Once the principal is gone, its findings resolve and are deleted 90 days later.

Warning: deleting the analyzer is not a step in this process. DeleteAnalyzer disables IAM Access Analyzer for the account or organization in that Region and deletes all findings that analyzer generated, with no undo. If you need to change a tracking period or an exclusion set, update the analyzer instead of recreating it.

7.5 The Security Hub service-linked analyzer

There is a second, newer path to unused access findings that changes the rollout calculus, and it is easy to end up with both without noticing.

AWS Security Hub can surface identity risks from unused access. When Security Hub is enabled for an organization, a service-linked IAM Access Analyzer is created in each member account with no additional configuration. The IAM Access Analyzer API exposes this as CreateServiceLinkedAnalyzer and DeleteServiceLinkedAnalyzer, operations that AWS documents as invokable only by authorized AWS services — direct customer invocation returns AccessDeniedException — with the analyzer's lifecycle managed by the calling service.

The behavior of that analyzer differs from one you create yourself in three ways that matter, per the Security Hub user guide as of 2026-08-04:

  • The lookback period is 90 days and is not configurable. You cannot make it stricter or more lenient.
  • Findings are re-evaluated every 24 hours, and a finding resolves automatically when a previously unused principal or permission becomes active.
  • The analyzer runs in US East (N. Virginia) because IAM is global, and Security Hub replicates findings to every Region where you have enabled Security Hub. You do not need Security Hub enabled in US East (N. Virginia) for it to run.

Two design implications. First, if you already operate an unused access analyzer with a deliberately chosen tracking period, enabling Security Hub gives you a second opinion at a fixed 90 days — useful as corroboration, confusing if two dashboards disagree and nobody knows why. Decide which one is the system of record. Second, if you have no unused access coverage and you already run Security Hub, you may have coverage you have not looked at.

8. Policy Validation and Custom Policy Checks

8.1 Basic policy checks

Policy validation runs whenever you author a policy in the IAM console's JSON editor, and on demand through validate-policy in the CLI and API. AWS describes it as validating against IAM policy grammar and AWS best practices, and IAM's own security best practices page states that IAM Access Analyzer provides more than 100 policy checks.

Findings come back in four categories, which map directly to how you should treat them in automation:

CategoryWhat it meansSuggested CI treatment
ERRORThe policy is wrong — invalid ARN format, invalid condition operator, unsupported element combination, JSON syntax error, policy size exceeding a quotaHard fail. These are defects regardless of security posture
SECURITY_WARNINGThe policy allows something that is usually unintended — Allow with NotPrincipal, pass-role with a wildcard resource, an OIDC principal missing its audience conditionHard fail by default, with a documented exception path
WARNING (general warning)Something that is legal but likely a mistake — a missing Version, a deprecated global condition key, a wildcard without a Like operator, a policy nearing a size quotaReport; fail selectively
SUGGESTIONRedundancy and hygiene — empty arrays, redundant statements, redundant resources, an empty SidReport only

The check reference is worth reading once end to end, because several checks encode organizational rules you would otherwise have to write yourself. Two of them target OIDC trust policies for source control providers: a security warning for a missing GitHub repository condition key, and a general warning recommending that a specific repository and branch be named. Two more, both errors, target policy variables that are too permissive in a wildcard or in a variable — mistakes that are close to impossible to spot by eye.

8.2 The three custom policy checks

Basic checks encode AWS's opinion. Custom policy checks encode yours, and they use the same automated reasoning to prove a property about a document rather than to pattern-match it. There are three, and they answer genuinely different questions.

API operationQuestion it answersRequired inputsPASS means
CheckNoNewAccessDoes this updated policy grant anything the previous version did not?existingPolicyDocument, newPolicyDocument, policyTypeNo new access is allowed by the updated policy
CheckAccessNotGrantedDoes this policy grant any of a specific set of actions or resources I consider critical?policyDocument, policyType, and an access object of actions and/or resourcesThe policy does not allow any of the specified permissions
CheckNoPublicAccessCould this resource policy grant public access for this resource type?policyDocument, resourceTypeThe policy does not allow public access to the specified resource type

All three return a result of PASS or FAIL, a human-readable message, and a reasons array of ReasonSummary objects carrying description, statementIndex, and statementId.

Four constraints shape how you use them.

policyType is a two-value enum. IDENTITY_POLICY or RESOURCE_POLICY — nothing more specific. Identity policies cover managed and inline policies for roles, users, and groups; resource policies cover trust policies and bucket policies.

CheckAccessNotGranted takes at most one Access object. The access array has a maximum of 1 item. Inside that single object, actions and resources are each arrays of up to 100 entries, with resource strings up to 2,048 characters and wildcards allowed only in the resource ID portion of an ARN. The semantics of the combination are specific and often misread: with only actions, the check asks whether the policy allows at least one of those actions on any resource; with only resources, whether it allows any action on at least one of those resources; with both, whether it allows at least one of the actions on at least one of the resources. If you want to check "these 300 actions", you need multiple calls, and if you want independent verdicts per action you need one call per action.

CheckNoPublicAccess requires a resource type from a fixed list. As of 2026-08-04 the resourceType enum has 27 values: AWS::DynamoDB::Table, AWS::DynamoDB::Stream, AWS::EFS::FileSystem, AWS::OpenSearchService::Domain, AWS::Kinesis::Stream, AWS::Kinesis::StreamConsumer, AWS::KMS::Key, AWS::Lambda::Function, AWS::S3::Bucket, AWS::S3::AccessPoint, AWS::S3Express::DirectoryBucket, AWS::S3::Glacier, AWS::S3Outposts::Bucket, AWS::S3Outposts::AccessPoint, AWS::SecretsManager::Secret, AWS::SNS::Topic, AWS::SQS::Queue, AWS::IAM::AssumeRolePolicyDocument, AWS::S3Tables::TableBucket, AWS::ApiGateway::RestApi, AWS::CodeArtifact::Domain, AWS::Backup::BackupVault, AWS::CloudTrail::Dashboard, AWS::CloudTrail::EventDataStore, AWS::S3Tables::Table, and AWS::S3Express::AccessPoint. Unsupported types return an error, so a CI job that maps its own resource inventory onto this enum needs a fallback branch rather than an assumption.

These checks read a document, not your account. None of them consult attached permission boundaries, service control policies, resource control policies, or the identity policies of the principals involved. CheckNoPublicAccess returning FAIL on a bucket policy in an account with Block Public Access enabled is correct — the policy could grant public access, and the account setting is a separate control that the check does not see. That is a feature for a merge gate, which should reject a policy that relies on a distant safety net, but it means a FAIL is a statement about the document, not a prediction about production.

8.3 Reference policies, and why Sid is not optional

CheckNoNewAccess compares two documents, and the "existing" document does not have to be the current version of the policy. AWS maintains a public IAM Access Analyzer custom policy checks samples repository on GitHub, and its API reference states that the reference policies in that repository are meant to be passed as the existingPolicyDocument parameter.

That inverts the tool's purpose in a useful way. Instead of "did this pull request add permissions", you can ask "does this policy grant anything beyond our organizational baseline" by passing a hand-written reference policy that expresses the maximum you are willing to allow. The check then proves the submitted policy is within that envelope.

Both usages are worth having:

  • Delta mode — existing version versus proposed version. Catches escalation in a specific change. Requires the CI job to fetch the deployed policy.
  • Baseline mode — reference policy versus proposed policy. Catches drift against a standard even in a brand-new policy. Requires no account access at all, which makes it the easier check to run on untrusted pull requests.

Whichever mode you use, the reasons array identifies the offending statement by statementIndex and, when present, by statementId. A policy whose statements have no Sid produces failure messages that identify only an array index, which is a poor experience for the developer reading the CI log and a fragile thing to reference in a code review comment. Requiring a Sid on every statement is a small convention that pays for itself the first time a check fails.

9. Blocking Privilege Escalation in CI

9.1 The gate

The goal is narrow: a pull request that changes an IAM policy document should not be mergeable if it grants access beyond the organization's standard. Everything in Section 8 exists to make that decision mechanical.

Custom policy checks running as a pull request gate
Custom policy checks running as a pull request gate
The shape is a fan-out and a fan-in. Every changed policy document goes through the basic checks and through whichever custom checks apply to its type, the results are aggregated, and the aggregate decides the merge.

The delta check compares the deployed policy against the proposed one:

aws accessanalyzer check-no-new-access \
    --existing-policy-document file://existing-policy.json \
    --new-policy-document file://new-policy.json \
    --policy-type IDENTITY_POLICY

AWS's published example for that command uses an existing policy allowing s3:GetObject and s3:ListBucket, and a new policy that adds s3:GetObjectAcl. The documented response is the one you want your gate to act on:

{
    "result": "FAIL",
    "message": "The modified permissions grant new access compared to your existing policy.",
    "reasons": [
        {
            "description": "New access in the statement with index: 0.",
            "statementIndex": 0
        }
    ]
}

The critical-action check asks a different question and needs no prior version:

aws accessanalyzer check-access-not-granted \
    --policy-document file://myfile.json \
    --access actions="s3:DeleteBucket","s3:GetBucketLocation" \
    --policy-type IDENTITY_POLICY

For resource policies, the public-access check is the one that maps most directly to an organizational rule:

aws accessanalyzer check-no-public-access \
    --policy-document file://bucket-policy.json \
    --resource-type AWS::S3::Bucket

Choose the action list for check-access-not-granted deliberately. The actions worth naming are the ones that let a principal grant itself more access or erase the evidence — the identity-mutating actions, the organization-level actions, and the ones that disable logging. Keep the list in version control next to the pipeline, review changes to it the way you would review a policy, and remember the 100-action ceiling per call.

9.2 Running it safely

Give the CI job the minimum it needs, and prefer baseline mode. The check APIs evaluate documents you send them; they do not read your account. In baseline mode the job needs only access-analyzer:CheckNoNewAccess, access-analyzer:CheckAccessNotGranted, access-analyzer:CheckNoPublicAccess, and access-analyzer:ValidatePolicy. Delta mode additionally needs read access to fetch the deployed policy — iam:GetPolicyVersion and iam:GetRolePolicy and their siblings — and nothing that writes. A policy gate should never hold write permissions on IAM. A job that can both approve and apply a policy change is not a gate.

Distinguish FAIL from failure. result: FAIL is a verdict and should block the merge. An HTTP error is an operational problem and should be handled differently. The documented error shapes are specific: ThrottlingException and InternalServerException both carry a retryAfterSeconds field, so retry with that value rather than a guessed backoff. ValidationException returns a fieldList of fields that did not validate. UnprocessableEntityException means the entity could not be processed — typically a malformed document, which for a policy gate is itself a reason to block. Do not treat an API error as a pass. A gate that fails open under throttling is a gate that disappears exactly when your organization is deploying the most.

Fail closed, and give the developer a route. The most common way a merge gate dies is that it becomes advisory. If a FAIL prints a warning and the merge proceeds, the check has been deleted with extra steps. Fail the build, and pair it with a documented exception path: a labeled approval from the security team, an entry in an exceptions file that the pipeline reads and that itself requires review. Whatever the mechanism, the exception should be visible in the repository, not in a person's memory of who said yes.

Make the failure message actionable. The reasons array gives you the statement index and, if you insisted on Sid values, the statement identifier. Render that into the CI output next to the statement text. A developer who sees "statement AllowArtifactRead grants new access" fixes it in a minute; a developer who sees FAIL opens a ticket.

Do not put the gate on the EventBridge path. Findings events are delivered on a best effort basis and arrive within roughly an hour, which is fine for notification and wrong for a control that must not be bypassed. The check APIs are synchronous and authoritative; use them for the gate and use events for everything downstream.

9.3 What the gate does not cover

A pull request gate covers policies that go through pull requests. It does not cover console edits, it does not cover policies embedded in third-party modules that were merged before the gate existed, and it does not cover the resource policies that AWS services create on your behalf.

That gap is precisely why the monitoring loop still matters. The two controls are complementary: the gate keeps the problem from being introduced through the paved road, and the analyzers catch what arrives by every other route. A team that has only the gate will be surprised; a team that has only the analyzers will always be remediating.

10. Policy Generation from CloudTrail

10.1 How it works

Policy generation analyzes CloudTrail events for an IAM user or role over a period you choose and produces a policy template based on the activity it found. You supply a service role that grants IAM Access Analyzer access to your CloudTrail trail and to service last accessed information, you specify the trail, and you receive a template you can review, customize, and save as a customer managed policy. The CLI operations are start-policy-generation, get-generated-policy, list-policy-generations, and cancel-policy-generation.

The output comes in two flavours, and knowing which one you got determines how much work remains:

  • Action-level information. For supported services, IAM Access Analyzer identifies the specific actions in your CloudTrail events and lists them in the generated policy.
  • Service-level information. For everything else, it uses last accessed information to produce a template naming the services that were used, and prompts you to add the actions yourself.

A generated policy is a starting point that still needs resource ARNs filled in. The template carries placeholder resource ARNs for actions that support resource-level permissions, and AWS recommends replacing them with the specific ARNs for your use case.

10.2 The limits that decide whether it is useful

AWS's Things to know about generating policies section is unusually candid, and reading it before you rely on the feature saves a great deal of confusion.

LimitConsequence
A CloudTrail trail must be enabled for the accountWithout a trail, there is nothing to generate from
Data events are not analyzed for action-level activityAmazon S3 object-level access will not appear as actions; a policy generated for a data-plane-heavy role will be badly incomplete
iam:PassRole is not tracked by CloudTrail and is not includedThe single most likely omission to break a workload that launches other AWS resources
Denied actions are includedGeneration reviews all CloudTrail events, including calls that were denied. Adopting the output verbatim can grant permissions the principal was previously refused
Do not use policy generation for auditingAWS says so explicitly; use CloudTrail for that
One generated policy at a time in the IAM console, retained for 7 daysGenerating another replaces the previous one
Time range of up to 90 daysAnything quarterly or annual is invisible
25 GB of CloudTrail data and 100,000 log files per generationBusy accounts hit these before they hit the day limit
One concurrent generation; 50 per day in most Regions, 5 in severalNot a bulk mechanism
AWS Control Tower trails are not supportedBecause the organization's CloudTrail data is logged in the Log Archive account and the bucket permissions cannot be reconfigured under Control Tower's service control policies

The Control Tower row is the one that surprises the most teams, because Control Tower is exactly the environment where a central team would want this. The documented workaround is the cross-account path: policy generation supports using CloudTrail data in another account, provided you can configure the trail's S3 bucket appropriately — which is precisely what Control Tower's guardrails prevent for the trails it creates. If you need generation in a Control Tower environment, plan a separate trail you control.

10.3 Where it belongs in the loop

Policy generation pairs naturally with unused access findings: the finding tells you a role has permissions nobody used, and generation proposes what the policy would look like if it only contained what was used. AWS's own guidance for unused access remediation makes this connection.

Treat the output as a proposal with three mandatory review steps: fill in the resource ARNs, add back anything that runs less often than your window, and remove anything that appears only because it was attempted and denied. Adopting a generated policy without that review is how a least-privilege exercise creates an outage on the first day of the next quarter — and, less obviously, how it can widen permissions, because denied attempts are part of the input.

11. Multi-Account Operations

11.1 A working topology

For an organization using AWS Organizations, the arrangement that follows from everything above is:

  1. Designate a delegated administrator from the management account — normally the security or audit account. Only the management account can do this, and changing it later is disruptive (Section 4.3).
  2. From the delegated administrator, create one organization-level external access analyzer in every Region where you have supported resources. This is the perimeter check and it must be regional because external access analysis only sees resources in its own Region.
  3. Create one organization-level internal access analyzer per Region you care about, and curate its explicit resource list. Remember the quota is one per organization per Region and cannot be raised.
  4. Create exactly one organization-level unused access analyzer, in the single Region where you will review findings. Do not replicate it; findings are Region-independent and duplicates create duplicate review work.
  5. Let individual accounts add their own account-level analyzers where the account's own trust boundary is narrower than the organization's — regulated workloads, customer-dedicated accounts. Each account gets one per type.
  6. Keep all of it in infrastructure as code, including archive rules, which CreateAnalyzer accepts inline through archiveRules. AWS notes there is no difference between using the IAM API and CloudFormation for configuring archive rules.

Creating an analyzer creates a service-linked role named AWSServiceRoleForAccessAnalyzer in the account. For an organization-level internal access analyzer, that role is created in each account of the organization.

11.2 Getting findings to the people who fix them

Two export paths exist, and they behave differently enough that most organizations end up using both.

Amazon EventBridge. IAM Access Analyzer emits four documented event types under the source aws.access-analyzer:

  • Access Analyzer Finding — external access findings
  • Internal Access Finding — internal access findings
  • Unused Access Finding for IAM entities — unused access findings
  • Access Preview State Change — preview created, completed, or failed

The operational properties matter more than the schema. Delivery is documented as best effort. Events for new findings and status changes arrive within about an hour. When a finding is deleted, the status is not changed — instead the isDeleted attribute is set to true, so a consumer that filters on status alone will never see deletions. Findings deleted because their analyzer was deleted are reported roughly 24 hours after the analyzer was deleted. Error findings arrive on the same detail types with an error attribute.

AWS Security Hub CSPM. IAM Access Analyzer sends findings in AWS Security Finding Format, normally within 30 minutes of creation, with six documented ASFF Types values covering public access, cross-account access, unused permissions, unused IAM roles, unused user passwords, and unused user access keys.

The Security Hub integration has one behavior that will mislead anyone treating it as the complete picture. For cross-account external access, IAM Access Analyzer sends a single finding for one external principal at a time. If a resource is shared with several external principals, you must resolve the Security Hub finding for the current one before the next is delivered. AWS states directly that for a full list of external principals with cross-account access outside the zone of trust, you must view the findings in IAM Access Analyzer. Security Hub is the right place to triage; it is not the right place to enumerate.

External access findings are also grouped per resource in Security Hub — the resource's finding stays active while any of its underlying findings are active, and archives when all of them are archived or resolved. Unused access findings are not grouped; they map one to one.

11.3 Who reviews what

The organizational question is which findings a central team owns and which belong to the account.

  • Public access findings are a central concern in every organization. They are the smallest set and the highest severity, and the dashboard separates public from cross-account for exactly this reason.
  • Cross-account external access findings are usually best triaged centrally and remediated locally, because the central team knows which partner accounts are approved and the account team knows why the share exists.
  • Internal access findings belong wherever the resource owner is. The central team owns the resource list; the account owns the answer.
  • Unused access findings are volume work and belong to the account, with the central team owning the tracking period, the exclusions, and the trend.

Routing this with EventBridge is straightforward because the finding carries the owning account — but note the field semantics differ by finding type. On an external access finding, accountId and region refer to the account and Region reported in the finding. On an internal access finding, accountId and principalOwnerAccount refer to the account of the principal, not necessarily the resource. A router that assumes one convention will misroute the other type.

For the aggregation layer itself — where these events land, how long they are retained, how they are queried alongside CloudTrail and Config — see the Centralized Logging and Audit Architecture on AWS guide. Where the resulting compliance state is evaluated and remediated as a rule set, see the AWS Config Rules and Conformance Packs guide.

12. Failure Modes and Anti-Patterns

Assuming one analyzer covers another's question. The most common coverage gap in this service is an organization that turned on external access analysis years ago and believes it has permissions visibility. It has perimeter visibility. Nothing in an external access analyzer reports an internal principal with excessive reach, and nothing in it reports a role nobody has used since 2023.

Silencing findings with archive rules instead of expressing intent. An archive rule keyed to "resource type is S3 bucket" is not a security decision, it is the end of one. Rules should name the principal, the account, or the specific resource whose access you have accepted, and they should carry the reason in the rule name because there is nowhere else to put it. And because IAM Access Analyzer does not validate filter values, a rule with a typo in an account ID looks identical in the console to one that works.

Archiving public access. Any archive rule that could match a public-access finding is a rule that can hide the highest-severity thing this service detects. AWS provides a Public access criterion set to false for exactly this reason; use it, or write down why you did not.

Choosing the zone of trust without understanding the SCP and RCP semantics. An account-scoped internal access analyzer lists actions your organization's guardrails already deny, because those policies are APPLICABLE but not applied. Reviewers who do not know this chase phantom access. Reviewers who do know it and use the account scope deliberately are asking a genuinely different and useful question — whether the guardrail is the only control — but that has to be a choice, not an accident.

Treating error findings as noise. An ACCESS_DENIED error finding means a resource is not being analyzed at all. The most common cause is a resource policy that denies the analyzer's service-linked role — most often on a KMS key, where a key policy that names only the key administrators leaves IAM Access Analyzer unable to read key metadata. The fix is to allow kms:DescribeKey, kms:GetKeyPolicy, and kms:List* to AWSServiceRoleForAccessAnalyzer, or to add it as an exception to a broad deny. Until then, that key has no coverage and the findings list looks clean.

Setting the unused access tracking period without accounting for entity age. A 365-day tracking period does not mean "thorough", it means permissions granted in the last year are not evaluated at all. Long periods are conservative about deletion and blind about recent over-granting.

Assuming account exclusions track the organizational unit. They do not. New accounts added to your organization are not excluded even if you previously excluded every account in that organizational unit. Re-derive the list, or accept that new accounts are in scope by design.

Replicating the unused access analyzer across Regions. Unused access findings do not vary by Region. Multiple analyzers produce multiple copies of the same finding, each requiring its own archive decision in its own Region, and archive rules do not cross Regions.

Deleting an analyzer to reconfigure it. DeleteAnalyzer deletes every finding the analyzer produced and cannot be undone. Every archive decision, every remediation record, gone. Use UpdateAnalyzer for configuration changes.

Changing the delegated administrator casually. The former delegated administrator's organization analyzers move to a disabled state, stop updating, and their findings become inaccessible until that same account is restored. Treat this as a planned migration.

Running the CI check in warning-only mode. A merge gate that prints a warning is documentation. If the organization is not ready to block merges, say so and set a date, rather than shipping a control that does nothing.

Failing open on API errors. ThrottlingException and InternalServerException are not verdicts. Retry using the retryAfterSeconds value the API returns, and block if the check cannot complete. A gate that disappears under load disappears during exactly the deployments that matter.

Gating on EventBridge events. Delivery is best effort with roughly an hour of latency. That is a notification pipeline, not a control plane. Use the synchronous check APIs for anything that must not be bypassed.

Adopting a generated policy without review. Policy generation includes denied actions in its input, omits iam:PassRole entirely, sees at most 90 days, and does not analyze data events. Each of those is a way for the output to be simultaneously too narrow to work and too broad to be least privilege.

Reading CheckNoPublicAccess as an account-level verdict. It evaluates a document. A FAIL on a bucket policy in an account with Block Public Access enabled is correct and still worth blocking, because the policy is relying on a control it does not name.

Deleting principals before deactivating credentials. Making an access key inactive is the only reversible step in the unused access remediation sequence. Skipping it turns a rollback into an incident.

13. Frequently Asked Questions

Do I need separate analyzers for external, internal, and unused access?

Yes. They are separate type values on CreateAnalyzerACCOUNT/ORGANIZATION for external, ACCOUNT_INTERNAL_ACCESS/ORGANIZATION_INTERNAL_ACCESS for internal, and ACCOUNT_UNUSED_ACCESS/ORGANIZATION_UNUSED_ACCESS for unused — and none implies the others. They also differ in regional behavior: external and internal analyzers must be created in every Region you want covered, while unused access findings do not change based on Region and one analyzer is sufficient.

Is IAM Access Analyzer the same thing as Network Access Analyzer?

No, and the naming collision causes real confusion. Network Access Analyzer is a feature of Amazon VPC that identifies unintended network access by analyzing the paths a packet can take, against a Network Access Scope you define. IAM Access Analyzer analyzes permissions. Both are built on AWS's automated reasoning work, which is why they sound related, but they share no configuration, no findings, and no console.

If the external access analyzer reports nothing, is my bucket safe?

It means no external access was provable from the resource-based policies IAM Access Analyzer could read, for a resource type it supports, in that Region, as of its last analysis. That is a strong statement and it is not "safe". It says nothing about which principals inside your zone of trust can read the bucket — that is the internal access analyzer's question — and it says nothing at all if an error finding exists for the resource.

Why does the same IAM role show up as a finding in several Regions?

Because IAM roles are global resources and external access analyzers are regional. AWS documents that if a role trust policy grants access to an external entity, a finding is generated in each enabled Region. Fixing the trust policy resolves all of them; accepting the access means archiving each one separately, in each Region, because archive rules belong to a single analyzer.

I enabled Block Public Access. Why is the public finding still there?

Because account-level Block Public Access settings are evaluated only once every 6 hours, while bucket-level settings are evaluated whenever a policy changes. AWS documents up to 6 hours for the finding to resolve, and the Security Hub integration page cites up to 12 hours for account-level changes to be reflected. If you need confirmation sooner, use the Rescan link on the finding or the StartResourceScan operation.

What tracking period should I set on the unused access analyzer?

There is no universal answer, but the constraint most people miss should drive the choice: the analyzer only evaluates permissions belonging to entities that have existed for the entire tracking period. A 90-day period is a common starting point because it is long enough to cover monthly processes and short enough that recent grants come into scope within a quarter. Also verify the accepted range against your own account first — the IAM User Guide and the UnusedAccessConfiguration API reference both state 1 to 365 days, while AWS console help panel pages still say 180.

Does enabling AWS Security Hub give me unused access coverage automatically?

It gives you a form of it. When Security Hub is enabled for an organization, a service-linked IAM Access Analyzer is created in each member account with no additional configuration, evaluating principals against a 90-day lookback that is not configurable, re-evaluating every 24 hours, running in US East (N. Virginia) and replicating findings to your Security Hub Regions. If you also operate your own unused access analyzer with a different tracking period, decide explicitly which one is the system of record before the two dashboards disagree.

Can custom policy checks replace human review of IAM changes?

No, and the boundary is precise. The checks prove properties of a document: whether it grants more than a previous version or a reference policy, whether it grants a named critical action, whether it could grant public access for a resource type. They do not consider permission boundaries, service control policies, resource control policies, or the identity policies of the principals involved, and they do not know what the change is for. They are excellent at answering "is this within the envelope we agreed" and incapable of answering "should we have agreed to that envelope".

Why does check-no-new-access fail on a change that only adds a read action?

Because it is doing what it says: any access allowed by the new document that was not allowed by the old one is new access. AWS's own example fails on the addition of s3:GetObjectAcl to a policy that already allowed s3:GetObject. That is not a false positive, it is the check's definition. If your standard is "no new privileged access" rather than "no new access at all", express it with check-access-not-granted against a list of privileged actions, or with a reference policy in check-no-new-access that describes the envelope you will accept.

Can I use policy generation in an AWS Control Tower environment?

Not with the trails Control Tower creates. AWS documents this explicitly: the organization's CloudTrail data is logged in the Log Archive account, and the permissions on that S3 bucket cannot be reconfigured because of the restrictions Control Tower's service control policies place on the logging bucket. Generating policies in such an environment requires a separate trail whose bucket permissions you control.

14. Summary

IAM Access Analyzer is three monitoring products and two authoring checks sharing a name and a reasoning engine. Almost every operational problem with it comes from treating it as one thing.

The decisions, condensed:

  • Create all three analyzer types deliberately. External for the perimeter, internal for the assets whose readers you must be able to name, unused for the inventory. None implies the others, and external and internal are complements that AWS explicitly recommends running together with the same zone of trust.
  • Get the Region model right. External and internal analyzers are per Region; unused findings are Region-independent and one analyzer is enough. Replicating the unused analyzer creates duplicate findings, and a global IAM role generates one external finding per enabled Region.
  • Treat the zone of trust as a semantic choice. Organization-scoped internal access findings fold service control policies and resource control policies into the computed permissions; account-scoped ones do not, and will list actions your guardrails already deny.
  • Curate the internal access resource list as a governed artifact. There is exactly one organization-level internal analyzer per Region, ARNs must be exact with no wildcards, only six resource types are supported despite a 17-value enum, and the whole capability stops working above 70,000 principals in the organization.
  • Set the unused access tracking period knowing it is also an entity-age filter. It excludes anything younger than the period from evaluation entirely. Exclusions by account are snapshots, not organizational-unit rules.
  • Make archive rules express intent, not volume. They are unvalidated, per-analyzer, per-Region, and permanent until someone removes them. Use the Public access criterion to guarantee a rule can never hide public access.
  • Move the decision left with the check APIs, and fail closed. CheckNoNewAccess, CheckAccessNotGranted, and CheckNoPublicAccess are synchronous and authoritative; EventBridge is best effort with about an hour of latency and belongs downstream, not in the gate. Retry on retryAfterSeconds, never on assumption.
  • Know what the checks cannot see. They evaluate a document, not your account. That is what makes them safe to run on an untrusted pull request and what makes their verdict a statement about the policy rather than about production.
  • Treat every removal as irreversible until proven otherwise. Deactivate keys before deleting them, scope policies before deleting roles, and never delete an analyzer to reconfigure it — DeleteAnalyzer takes every finding with it.

The through-line is that this service converts an unanswerable question into an answerable one, and then hands you a second problem: deciding what the answers mean. The reasoning engine is not the hard part. The archive rules, the exclusions, the tracking period, and the zone of trust are the hard part, because those are the places where your organization writes down what it considers acceptable — and where, if nobody revisits them, it quietly stops looking.

15. References

Related Articles



References:
Tech Blog with curated related content

Written by Hidekazu Konishi