Every AssumeRole hop in AWS replaces the caller's identity with a role session, so an attacker who chains roles across accounts can leave a CloudTrail trail in which each record looks like legitimate automation. This guide is about detection only: how to rebuild a chain of role sessions from CloudTrail, which fields carry identity through each hop, how source identity and transitive session tags make chains attributable, and the alert logic that separates normal pipeline chains from abuse. It assumes you already know how STS, trust policies and session tags work, which the AWS STS and AssumeRole Guide covers. It is written for detection engineers, cloud security teams and incident responders working in multi-account AWS Organizations. Field names and behaviour come from AWS IAM and CloudTrail documentation, checked on 12 November 2026.
This is an independent guide from NHI Mgmt Group, based on AWS's public documentation as of November 2026. It is not sponsored or reviewed by AWS. Product features change, so confirm settings against AWS's current documentation.
Key takeaways
- A chain is a graph, not a log line. You rebuild it by joining each AssumeRole event's responseElements.credentials.accessKeyId to the userIdentity.accessKeyId of the next hop.
- Cross-account hops are logged twice. The caller's account holds the full caller identity and the target account holds only the caller's account and principal ID, matched by sharedEventID, so you need both sides centrally.
- Source identity is the only identity that survives every hop. It cannot be changed once set, and it appears in userIdentity.sessionContext.sourceIdentity on every later call, so a privileged chain without it is an alert in itself.
- Baselines beat signatures. Most chains are pipelines that take the same path every day; alert on new edges, new depths, new networks and chains that end in a privileged role.
- Denied cross-account attempts are invisible in the target account. Probing of your roles shows up only in the caller's account, which may be the attacker's own.
What role chaining abuse looks like in the logs
AWS defines role chaining as using one role's temporary credentials to assume a second role, and caps any chained session at one hour regardless of the role's maximum session duration; a chained AssumeRole call with DurationSeconds above 3,600 fails. Attackers chain roles for the same reasons pipelines do: to cross account boundaries, to reach a role with more permissions, and to move from a compromised workload into the accounts it deploys to. The mechanics, trust policy conditions and the one hour cap are explained in the AWS STS and AssumeRole Guide; this page picks up where that guide's logging section stops.
The detection problem is that each hop launders identity. After the first hop, calls are recorded with userIdentity.type AssumedRole, and the only identity shown is the role session: arn:aws:sts::<account>:assumed-role/<role>/<session-name>, with the role in sessionContext.sessionIssuer. Nothing in that record says which person, pipeline job or stolen key started the chain unless something carried that context forward.
In ATT&CK v20 terms, chain abuse usually combines T1078.004 Valid Accounts: Cloud Accounts (using the stolen session or key), T1550.001 Use Alternate Authentication Material: Application Access Token (reusing temporary credentials), T1548.005 Temporary Elevated Cloud Access (assuming a more privileged role) and often T1098.003 Additional Cloud Roles when the attacker edits a trust policy so a role they control can assume a privileged one. Discovery calls such as T1087.004 Cloud Account and T1580 Cloud Infrastructure Discovery tend to follow each new hop.
The CloudTrail fields that link hops together
STS calls are management events with eventSource sts.amazonaws.com. AssumeRole is a read-only event, but CloudTrail still records its responseElements (without the secret access key), which is what makes chain reconstruction possible.
| Field | Event | What it gives you |
|---|---|---|
| userIdentity.accessKeyId | Every call, including the AssumeRole itself | The key that signed the call: AKIA for long-term IAM user keys, ASIA for temporary credentials. The parent of this hop. |
| responseElements.credentials.accessKeyId | AssumeRole (caller side) | The new ASIA key issued for the target role. The child of this hop. |
| responseElements.assumedRoleUser.arn | AssumeRole | The session ARN that later calls will carry in userIdentity.arn |
| requestParameters.roleArn and roleSessionName | AssumeRole | Target role and the session name the caller chose |
| requestParameters.sourceIdentity | AssumeRole where it is first set, or re-passed | The original actor; echoed in responseElements.sourceIdentity |
| userIdentity.sessionContext.sourceIdentity | Every later call in the session and in chained sessions | The original actor carried through the chain |
| requestParameters.tags, transitiveTagKeys, incomingTransitiveTags | AssumeRole | Session tags set on this hop, which keys are transitive, and transitive tags inherited from the calling session |
| requestParameters.principalTags | AssumeRoleWithSAML and AssumeRoleWithWebIdentity | Tags supplied by the identity provider at the start of a federated chain |
| userIdentity.sessionContext.sessionIssuer | Every call made with a role session | The role (type Role, arn, accountId, userName) behind the session |
| userIdentity.sessionContext.attributes | Every role session call | creationDate and mfaAuthenticated |
| sharedEventID | Cross-account AssumeRole | Joins the caller-account record to the target-account record |
| additionalEventData.RequestDetails.endpointType | STS calls | global or regional; global endpoint calls are logged in US East (N. Virginia), regional calls in the serving Region |
Two caveats from the CloudTrail userIdentity reference matter for joins. First, userIdentity.accessKeyId may be absent or an empty string, so a join on keys alone will have gaps and needs the session ARN plus time window as a fallback. Second, for AssumedRole the userName field is not set at the top level; read sessionContext.sessionIssuer.userName for the role name.
Rebuilding a chain step by step
Chain reconstruction is the core investigative skill here, and the same logic drives the automated detections in the next sections. The steps assume an organisation trail or CloudTrail Lake event data store that collects management events from every account and every Region. See the AWS CloudTrail Guide for collection design.
- Start from the suspicious call. Take its userIdentity.accessKeyId (an ASIA key), userIdentity.arn and eventTime.
- Find the issuing AssumeRole. Search sts.amazonaws.com AssumeRole events where responseElements.credentials.accessKeyId equals that key. If the key is missing, match responseElements.assumedRoleUser.arn to the session ARN in a window of up to 12 hours before the call (one hour if the session was itself chained).
- Read the parent. The issuing event's userIdentity.accessKeyId is the parent credential. If it begins AKIA, you have reached a long-term IAM user key: the chain root. If it is ASIA, repeat step 2 for that key.
- Cross the account boundary. Where the issuing event sits in the target account with userIdentity.type AWSAccount, find the caller-side record in the calling account with the same sharedEventID. Only the caller-side record shows the full caller ARN and key.
- Handle federated and service roots. A chain that begins with AssumeRoleWithWebIdentity or AssumeRoleWithSAML has its root in the identity provider: record the federated subject, webIdFederationData and principalTags. A chain that begins with an EC2 instance profile, ECS task role or Lambda execution role has a workload root; record the instance, task or function.
- Record the path. For each hop keep account, role ARN, session name, source identity, transitive tags, source IP, user agent and creation time. The ordered list of role ARNs is the chain's signature, which you compare with the baseline.
- Fan out. From each ASIA key in the chain, list every other API call it made. Attackers often branch from an intermediate hop, so the damage is not only at the end of the chain.
For the starting point of an investigation from a leaked long-term key, see How to Investigate CloudTrail for a Compromised Access Key.
Making chains attributable with source identity and transitive tags
Reconstruction by key joins works, but it is slow and breaks when a hop's log is missing. Source identity and transitive session tags put the answer on every record, which is what turns chain analysis into a single field lookup.
- Source identity: set with the SourceIdentity parameter on AssumeRole, the SAML attribute https://aws.amazon.com/SAML/Attributes/SourceIdentity, or the JWT claim https://aws.amazon.com/source_identity. It is 2 to 256 characters, cannot begin with aws:, cannot be changed once set, and persists through role chaining; a chained AssumeRole that passes a different value is denied. It is visible in userIdentity.sessionContext.sourceIdentity on every later call. Setting it needs sts:SetSourceIdentity in both the caller's permissions and the target trust policy, including on every hop of a chain.
- Condition keys for enforcement: sts:SourceIdentity is present when the value is first set; aws:SourceIdentity is present on later requests in the session and in chained role requests. Requiring aws:SourceIdentity in the trust policy of a privileged role means no chain can reach it without a named actor.
- Transitive session tags: by default session tags are not passed to the next session. Tags whose keys are listed in TransitiveTagKeys persist through every later hop and appear in requestParameters.incomingTransitiveTags. Only session tags can be transitive, not role tags, and the trust policy's sts:TransitiveTagKeys condition caps which keys a caller may mark. A tag such as pipeline-run or change-ticket that is transitive gives every hop a correlation ID.
- Limits to plan for: AWS does not control the value of source identity; your identity provider or pipeline must set it from a trusted attribute. The IAM documentation also states that source identity is not captured when an AWS service or service-linked role acts on behalf of a federated or workforce identity, so those paths still need key-based reconstruction.
| Chain observation | Without source identity or tags | With both enforced |
|---|---|---|
| Who started a three-hop chain into production | Join three AssumeRole events across accounts by access key and sharedEventID | Read sessionContext.sourceIdentity on any production call |
| Which pipeline run made a change | Infer from session names, if the pipeline sets them consistently | Read the transitive run tag in incomingTransitiveTags |
| Was a privileged role reached by an unexpected actor | Compare chain paths with a baseline | Trust policy denies the hop; the caller side logs a failure |
| Did an attacker insert a hop | New edge in the chain graph | New edge plus a source identity that does not match the edge's usual actors |
Alert logic for unusual chains
Good chain detections compare each new hop with what that role normally does. Build the baseline from 30 to 90 days of AssumeRole events: for each target role, the set of calling roles (edges), the source identities and session-name patterns seen, the source IP ranges or VPC endpoints, and the usual depth at which the role is reached.
| Detection | Logic | Typical benign cause to tune out | Priority |
|---|---|---|---|
| New edge into a privileged role | AssumeRole where the pair (caller sessionIssuer.arn, requestParameters.roleArn) is not in the baseline and the target is tagged privileged | New pipeline or account onboarding with a change ticket | High |
| Privileged hop without source identity | AssumeRole into a privileged role where requestParameters.sourceIdentity and incoming aws:SourceIdentity are both empty | Roles not yet migrated; track as debt | High |
| Source identity mismatch | sessionContext.sourceIdentity on a session that is not among the actors usually seen for that role, or that names a disabled user | Staff changing teams | High |
| Unusual depth | Chain length above the role's baseline (for example, a role normally reached in one hop reached in three) | Break-glass procedures | Medium |
| Loop or revisit | The same role ARN appears twice in one chain, or a chain returns to its origin account | Rare; review each case | Medium |
| Session used from a new network | Calls with an ASIA key from an IP or ASN not seen for the issuing role, especially one that differs from the IP that called AssumeRole | Corporate VPN egress changes | High |
| Rapid fan-out | One session calls AssumeRole into many roles or accounts within minutes | Organisation-wide audit tooling, if it has its own baseline | Medium |
| Trust edit then hop | UpdateAssumeRolePolicy on a role followed by an AssumeRole into that role from a new caller | Infrastructure-as-code deploy that changed trust deliberately | High |
| Discovery after a hop | GetCallerIdentity, ListRoles or ListAccounts shortly after a new edge | Inventory tools | Low alone, raise with others |
| Failed hop burst in the caller account | Repeated AccessDenied on AssumeRole from one session across many role ARNs | Misconfigured deployment | Medium |
RoleSessionName is chosen by the caller (2 to 64 characters), so an attacker can copy a pipeline's naming pattern. Treat it as a weak signal that helps triage, never as attribution. If you want session names enforced, the trust policy can condition on sts:RoleSessionName, which turns a naming mismatch into a denied call on the caller side.
Where managed services help
Amazon GuardDuty's IAM finding types include UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration.OutsideAWS and .InsideAWS for EC2 instance credentials used elsewhere, ResourceCredentialExfiltration.OutsideAWS and .InsideAWS for ECS task and Lambda credentials, and a family of AnomalousBehavior findings such as PrivilegeEscalation:IAMUser/AnomalousBehavior and Discovery:IAMUser/AnomalousBehavior. The exfiltration findings are the most useful for chains that start from a workload role. GuardDuty does not reconstruct chain paths for you, so keep the custom graph logic above. See the Amazon GuardDuty Guide.
Rolling out chain detection
- Centralise both sides. Use an organisation trail covering all accounts and Regions, delivered to a log archive account the workloads cannot write to. Without the caller-side records, cross-account hops lose their caller identity.
- Tag privileged roles. Mark administrative, deployment, security tooling and break-glass roles with a consistent tag so detections can scope to them.
- Build the edge baseline. Extract every AssumeRole for 30 to 90 days into a table of edges with counts, actors, networks and depth. Review it with platform owners: unexpected edges found at this stage are often live risk, not noise.
- Deploy alerts in audit mode. Run the table's detections as reports for two to four weeks, measure volume per rule and tune exclusions to named pipelines and tickets, not to whole accounts.
- Introduce source identity. Configure the identity provider and pipelines to set it, add sts:SetSourceIdentity to every hop's trust policy, alert on privileged hops without it, then require aws:SourceIdentity on privileged roles. Pilot in one account first; a missing permission on any hop makes the chained call fail, and rollback is restoring the previous trust policy from version control.
- Add transitive correlation tags. Have pipelines pass a run or ticket tag marked transitive, constrained by sts:TransitiveTagKeys in trust policies.
- Promote to paging. Move high-priority rules to on-call once false positives are below an agreed rate, and link each alert to a chain reconstruction query so the responder starts with the full path.
Small versus large estates: a handful of accounts can baseline edges by hand and alert on any new edge into a privileged role. Organisations with hundreds of accounts should generate the edge baseline automatically, enforce source identity through trust policy templates in code, and use the AWS Organizations and SCPs Guide patterns to stop trust policies being edited outside the pipeline.
Measuring whether it works
The targets below are NHI Mgmt Group recommendations, not AWS figures.
- Privileged hops with source identity: share of AssumeRole events into privileged roles that carry a source identity; target 100 percent.
- Chain reconstruction time: median minutes for a responder to go from a suspicious call to the chain root; target under 15 minutes.
- Unbaselined edges: count of new caller-to-role edges per week, and the share explained by a change ticket.
- Log completeness: share of cross-account AssumeRole events whose sharedEventID has both caller-side and target-side records in the central store.
- Alert precision: share of high-priority chain alerts confirmed as true positives or policy breaches after triage.
Common failure patterns
- Collecting only the target account's logs: cross-account hops then show userIdentity.type AWSAccount with no caller ARN or key, and the chain stops at the account boundary.
- Collecting only us-east-1 or only your home Region: regional STS endpoint calls are logged in the Region that served them, so hops made through other Regions are missing.
- Treating session names as identity: callers choose them, and attackers reuse familiar names to blend in.
- Ignoring session reuse from new networks: in the Bybit hack, attackers reused a developer's active AWS session tokens from VPN addresses; an alert on a known session key appearing from a new network is the control that fits that pattern.
- Allowing trust policy edits outside change control: an attacker who can call UpdateAssumeRolePolicy can add their own edge to a privileged role, and the chain then looks authorised.
- Waiting for denied attempts in the target account: CloudTrail does not log denied cross-account STS requests in the target account, so failed probing is visible only from the calling side.
Framework mapping
| Framework | Reference | How chain detection supports it |
|---|---|---|
| NIST CSF 2.0 | DE.CM-03, DE.AE-02, PR.AA-05 | Monitoring of role session activity, analysis of chains, least privilege on trust edges |
| NIST SP 800-53 Rev. 5 | AU-2, AU-6, AU-12, SI-4, IA-4, AC-6 | STS event logging and review, monitoring, identifier management through source identity, least privilege |
| CIS Controls v8.1 | 8 Audit Log Management, 6 Access Control Management | Centralised CloudTrail, review of role access paths |
| ISO/IEC 27001:2022 Annex A | 8.15 Logging, 8.16 Monitoring activities, 8.2 Privileged access rights | Collection and monitoring of role sessions, control of privileged roles |
| MITRE ATT&CK v20 | T1078.004, T1550.001, T1548.005, T1098.003, T1087.004, T1580 | Stolen sessions, temporary elevation, trust policy changes and post-hop discovery |
Practitioner checklist
- Collect CloudTrail management events from every account and Region into one log archive.
- Confirm both caller-side and target-side AssumeRole records arrive for cross-account hops.
- Tag privileged roles consistently so detections can scope to them.
- Build and review a 30 to 90 day baseline of caller-to-role edges, actors and networks.
- Save a chain reconstruction query that joins issued and signing access keys.
- Alert on new edges into privileged roles and on privileged hops without source identity.
- Set source identity from the identity provider and pipelines, then require aws:SourceIdentity on privileged roles.
- Pass a transitive correlation tag from pipelines and constrain it with sts:TransitiveTagKeys.
- Alert on UpdateAssumeRolePolicy followed by a new edge into the same role.
- Measure source identity coverage and reconstruction time monthly.
Standards and references
- AWS: Logging IAM and AWS STS API calls with AWS CloudTrail
- AWS: CloudTrail userIdentity element
- AWS: Monitor and control actions taken with assumed roles
- AWS: Pass session tags in AWS STS
- AWS: IAM roles, terms and concepts
- AWS: GuardDuty IAM finding types
- MITRE ATT&CK: Abuse Elevation Control Mechanism: Temporary Elevated Cloud Access
- MITRE ATT&CK: Use Alternate Authentication Material: Application Access Token
Related NHI Mgmt Group resources: AWS STS and AssumeRole Guide · AWS CloudTrail Guide · How to Investigate CloudTrail for a Compromised Access Key · AWS IAM Policies and Roles Guide · Amazon GuardDuty Guide