How to Fix AWS CLI v2 AssumeRole 'The security token included in the request is invalid' After Session Expiry
Threat/Impact Level: HIGH | Downtime Risk: HIGH | Time to Fix: 5–15 mins
TL;DR
- What broke: Your STS-issued session token (via
AssumeRole) hit itsMaxSessionDurationor the 1-hour default TTL. Every subsequent CLI call using that cached token returnsInvalidClientTokenIdorExpiredTokenException. - How to fix it: Force a token refresh via
aws sts assume-rolemanually, or configure your~/.aws/configprofile withcredential_process/role_arn+source_profileso the CLI auto-refreshes. - Fast path: Use our Client-Side Sandbox above to auto-refactor your credentials config or shell wrapper — paste your config, get corrected output instantly.
The Incident (What Does the Error Mean?)
You ran a CLI command and got this:
An error occurred (InvalidClientTokenId) when calling the GetCallerIdentity operation:
The security token included in the request is invalid.
or this variant:
An error occurred (ExpiredTokenException) when calling the AssumeRole operation:
Token included in the request is expired.
Immediate consequence: Every AWS API call authenticated via that profile is dead. If this is a deployment pipeline, your CI/CD run is blocked. If it's a production ops script, your on-call engineer cannot execute remediation commands. The credentials cache at ~/.aws/cli/cache/ or the AWS_SESSION_TOKEN env var is holding a stale token that STS is rejecting at the signature validation layer.
The Attack Vector / Blast Radius
This is not just an inconvenience — stale token handling is a security boundary failure with real blast radius:
Hardcoded or long-lived tokens in CI/CD env vars — When engineers "fix" this by extending
MaxSessionDurationto 12 hours or exporting staticAWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY/AWS_SESSION_TOKENinto environment variables, those tokens become long-lived secrets. A compromised CI runner, a leaked.envfile, or aprintenvin build logs exposes a fully-authenticated AWS session.Cache poisoning window — The CLI cache directory (
~/.aws/cli/cache/*.json) stores plaintext JSON withAccessKeyId,SecretAccessKey, andSessionToken. If an attacker has filesystem read access, they can exfiltrate a valid token before expiry. Extending TTL directly extends the exfiltration window.Cascading pipeline failures — In multi-stage pipelines where stage 2 assumes a role from stage 1's token, a single expiry mid-pipeline causes all downstream stages to fail with cryptic errors, often misdiagnosed as permission errors rather than token expiry.
Silent failures in SDKs — Boto3 and other SDKs may silently retry and surface a generic
ClientError, masking the real cause and delaying diagnosis by 20–40 minutes in a production incident.
How to Fix It (The Solution)
Basic Fix — Force Refresh the Token Manually
Nuke the CLI cache and re-assume the role:
# Clear stale cached credentials
rm -f ~/.aws/cli/cache/*.json
# Re-assume the role and export fresh tokens
OUTPUT=$(aws sts assume-role \
--role-arn arn:aws:iam::123456789012:role/MyRole \
--role-session-name refresh-session \
--duration-seconds 3600)
export AWS_ACCESS_KEY_ID=$(echo $OUTPUT | jq -r '.Credentials.AccessKeyId')
export AWS_SECRET_ACCESS_KEY=$(echo $OUTPUT | jq -r '.Credentials.SecretAccessKey')
export AWS_SESSION_TOKEN=$(echo $OUTPUT | jq -r '.Credentials.SessionToken')
This gets you unblocked in under 2 minutes. It is not a permanent fix.
Enterprise Best Practice — Auto-Refreshing Profile via ~/.aws/config
The correct fix is to let the AWS CLI v2 credential provider chain handle refresh automatically using profile chaining. Never hardcode session tokens.
# ~/.aws/config
- [profile broken-profile]
- aws_access_key_id = ASIAXXXXXXXXXXXXXXXXXXX
- aws_secret_access_key = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
- aws_session_token = AQoXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX # HARDCODED — EXPIRES AND BREAKS
+ [profile base-identity]
+ credential_process = aws-vault exec base-identity --json
+ # OR if using IAM user long-term keys as source:
+ # aws_access_key_id = AKIAIOSFODNN7EXAMPLE
+ # aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
+
+ [profile my-role]
+ role_arn = arn:aws:iam::123456789012:role/MyRole
+ source_profile = base-identity
+ role_session_name = cli-auto-refresh
+ duration_seconds = 3600
+ # CLI v2 will auto-refresh this token before expiry when using this profile
Usage after fix:
aws s3 ls --profile my-role
# CLI v2 automatically calls sts:AssumeRole and caches/refreshes the token
For CI/CD (GitHub Actions / GitLab CI) — use OIDC, not static tokens:
- env:
- AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
- AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- AWS_SESSION_TOKEN: ${{ secrets.AWS_SESSION_TOKEN }} # Rotates every N hours, breaks pipeline
+ permissions:
+ id-token: write
+ contents: read
+
+ - name: Configure AWS Credentials
+ uses: aws-actions/configure-aws-credentials@v4
+ with:
+ role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsRole
+ aws-region: us-east-1
+ # OIDC — no static secrets, no expiry surprises
💡 Tired of pasting proprietary configs into ChatGPT? Generic AI tools log your company's ARNs, DB strings, and private keys. StackEngine is a zero-backend, pure Client-Side WASM utility. Drop your failing config into the sandbox above. We redact your secrets locally in the browser and auto-generate the refactored code using your own API key.
Prevention in CI/CD
1. Checkov — Detect Hardcoded Session Tokens in IaC
Checkov rule CKV_AWS_46 flags hardcoded credentials. Add to your pipeline:
- name: Checkov IaC Scan
run: checkov -d . --check CKV_AWS_46 --compact
2. OPA / Conftest Policy — Block Static Token Injection
# policy/no_static_session_token.rego
package main
deny[msg] {
input.env[key]
key == "AWS_SESSION_TOKEN"
msg := "Static AWS_SESSION_TOKEN in pipeline env is forbidden. Use OIDC or instance role."
}
conftest test pipeline.yaml --policy policy/
3. aws-vault for Local Development
Replace all local ~/.aws/credentials static key usage with aws-vault, which stores credentials in the OS keychain and auto-rotates STS tokens:
brew install aws-vault
aws-vault add base-identity
aws-vault exec my-role -- aws s3 ls
4. Set a Token Expiry Monitor (CloudWatch + EventBridge)
For long-running assumed-role sessions in ECS tasks or EC2, configure a CloudWatch alarm on aws.sts GetCallerIdentity 4xx error rates. An uptick in InvalidClientTokenId errors at a predictable interval is a leading indicator of token refresh logic failure — catch it before it pages your on-call.
5. Enforce MaxSessionDuration via SCP
Do not set MaxSessionDuration above 4 hours without a documented justification. Use an SCP to enforce this org-wide:
{
"Effect": "Deny",
"Action": "iam:UpdateRole",
"Resource": "*",
"Condition": {
"NumericGreaterThan": {
"iam:MaxSessionDuration": "14400"
}
}
}
Longer sessions are not a fix for broken refresh logic — they are a security liability masquerading as a convenience.