Limitations of Role Chaining in STS AssumeRole Sessions: Understanding the 1-Hour Hard Limit

AWS STS imposes a hard 1-hour maximum session duration on role chaining, silently truncating any DurationSeconds request that would exceed this limit regardless of individual role configurations.

When working with the Agent Toolkit for AWS repository, understanding how role chaining in STS AssumeRole sessions operates is critical for designing secure, long-running workflows. The toolkit's IAM skill documentation explicitly warns that chained assumptions cannot extend beyond a one-hour boundary, a constraint that impacts everything from CI/CD pipelines to multi-account data processing architectures.

What Is Role Chaining in STS?

Role chaining occurs when a principal uses temporary credentials from one AssumeRole operation to execute another AssumeRole call to a different IAM role. This pattern appears in cross-account access scenarios, privilege escalation workflows, and bootstrap-to-service architectures where intermediate roles provide transitional permissions.

According to the source code in skills/core-skills/aws-iam/SKILL.md, AWS treats these sequential assumptions as a single logical session with a cumulative lifetime ceiling.

The 1-Hour Hard Limit Explained

Regardless of how many roles exist in the chain or what DurationSeconds value (up to 12 hours) you specify for individual calls, the combined lifetime of a role-chaining session cannot exceed 1 hour. This limitation is documented in the Agent Toolkit's AWS IAM skill as "Role chaining: max 1-hour session" in skills/core-skills/aws-iam/SKILL.md.

Why AWS Enforces This Constraint

Chaining amplifies the risk of credential leakage and complicates expiration enforcement. By imposing a single-hour ceiling, AWS guarantees that compromised temporary credentials cannot persist indefinitely, even if an attacker obtains downstream role credentials from memory logs or process dumps.

Impact on DurationSeconds

When you request DurationSeconds=43200 (12 hours) on the final AssumeRole call in a chain, STS silently truncates the expiration to the 1-hour boundary established by the first assumption. The first role in the chain determines the absolute expiry timestamp, and subsequent roles cannot extend it.

This often surfaces as an ExpiredTokenException after approximately 55 minutes, even when the original token remains valid.

Practical Code Examples

The following examples demonstrate the limitation and mitigation strategies using the AWS CLI and boto3.

Demonstrating the Limitation (Bash)

This script chains two roles and reveals that the second assumption's expiration never exceeds one hour from the initial call:


# Assume role A (bootstrap role)

aws sts assume-role \
  --role-arn arn:aws:iam::111122223333:role/BootstrapRole \
  --role-session-name bootstrap \
  --duration-seconds 3600 \
  --output json > /tmp/credsA.json

export AWS_ACCESS_KEY_ID=$(jq -r .Credentials.AccessKeyId /tmp/credsA.json)
export AWS_SECRET_ACCESS_KEY=$(jq -r .Credentials.SecretAccessKey /tmp/credsA.json)
export AWS_SESSION_TOKEN=$(jq -r .Credentials.SessionToken /tmp/credsA.json)

# Now assume role B (service role) **requesting 12 h**

aws sts assume-role \
  --role-arn arn:aws:iam::444455556666:role/ServiceRole \
  --role-session-name service \
  --duration-seconds 43200 \
  --output json > /tmp/credsB.json

# Inspect expiry – note it never exceeds 1 hour from the *first* call

jq .Credentials.Expiration /tmp/credsB.json

Running the final jq command shows an expiration timestamp capped at approximately one hour after the original AssumeRole call, ignoring the 12-hour request.

Proactive Credential Refresh (Python)

To avoid service interruptions, calculate remaining time from the first assumption and schedule refreshes before the 45-minute mark:

import boto3, time, datetime

def assume_role(chain_arn, session_name, duration):
    client = boto3.client('sts')
    resp = client.assume_role(
        RoleArn=chain_arn,
        RoleSessionName=session_name,
        DurationSeconds=duration
    )
    return resp['Credentials']

# First hop

creds_a = assume_role('arn:aws:iam::111122223333:role/BootstrapRole',
                      'bootstrap', 3600)

# Second hop (cannot exceed the remaining time)

remaining = (creds_a['Expiration'] - datetime.datetime.utcnow()).seconds
creds_b = assume_role('arn:aws:iam::444455556666:role/ServiceRole',
                      'service', remaining)

# Schedule a refresh 45 min after the first assumption

time.sleep(45 * 60)

# Re-run the first hop to get a fresh 1-hour window, then repeat the chain.

This approach computes the exact remaining seconds from the initial credentials and uses that value for the second call, ensuring the final credentials respect the 1-hour chain ceiling.

Eliminating Chains via Direct Access (IAM Policy)

The most reliable workaround involves granting the original principal direct access to the target role, bypassing the intermediate assumption entirely:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "sts:AssumeRole",
      "Resource": "arn:aws:iam::444455556666:role/ServiceRole"
    }
  ]
}

By attaching this policy to the original principal (user, EC2 instance, or Lambda execution role), you eliminate the intermediate bootstrap role and the associated 1-hour restriction. This pattern is validated by the toolkit's consistency checks in tools/validate.py, which enforces best practices for IAM advice across skill files.

Mitigation Strategies and Best Practices

When role chaining in STS AssumeRole sessions cannot be avoided, implement these safeguards from the plugins/aws-core/skills/aws-iam/SKILL.md documentation:

  • Avoid deep chaining – Limit chains to a single AssumeRole operation when possible.
  • Use session tags and policies – Embed required permissions in the first assumed role rather than delegating to secondary roles.
  • Refresh proactively – Schedule credential regeneration every 45 minutes to prevent ExpiredTokenException errors.
  • Leverage AWS Organizations – Grant target account roles directly to original principals using cross-account trusts, eliminating intermediate hops.
  • Monitor with CloudTrail – Enable logging for AssumeRole events and alert when chains exceed two hops or approach the 1-hour limit.

Summary

  • Role chaining in STS AssumeRole sessions is limited to a hard 1-hour maximum duration regardless of DurationSeconds requests.
  • The first assumption in the chain establishes the absolute expiration timestamp; subsequent roles cannot extend this boundary.
  • STS silently truncates long-duration requests (up to 12 hours) to fit within the 1-hour chain window.
  • The limitation is documented in skills/core-skills/aws-iam/SKILL.md and plugins/aws-core/skills/aws-iam/SKILL.md within the Agent Toolkit for AWS repository.
  • Mitigation strategies include direct role assumption, proactive 45-minute refresh cycles, and eliminating intermediate roles through AWS Organizations cross-account trusts.

Frequently Asked Questions

What is the maximum session duration for role chaining in STS?

The maximum session duration for any role chain is 1 hour, regardless of individual role settings or DurationSeconds parameters. This limit applies to the cumulative lifetime from the first AssumeRole call through all subsequent chained assumptions.

Why does AWS limit role chaining to 1 hour?

AWS imposes this limit to minimize the blast radius of credential compromise. Chaining increases complexity and attack surface; a 1-hour ceiling ensures that leaked temporary credentials from any point in the chain expire quickly, preventing indefinite unauthorized access even if downstream credentials are exfiltrated.

How can I work around the 1-hour role chaining limit?

Eliminate the chain by granting the original principal direct permission to assume the target role via IAM policy, bypassing intermediate roles entirely. If chaining is unavoidable, implement proactive credential refresh logic that re-assumes the first role every 45 minutes to reset the 1-hour window, as demonstrated in the Python example above.

Does requesting a longer DurationSeconds extend the chain lifetime?

No. Requesting DurationSeconds=43200 (12 hours) on a secondary role in a chain will not extend the session beyond 1 hour from the initial assumption. STS silently truncates the expiration to the 1-hour boundary established by the first role in the chain.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →