RAPID INCIDENT TRIAGE & ACTION PLAN•Security
01. Error Signature
AWS CLI v2 AssumeRole Session Expiry Fix
02. Root Cause Vector

Expired STS session tokens cause AWS CLI v2 AssumeRole calls to fail with 'invalid security token', blocking all IAM-authenticated operat...

03. Rapid Prescription

Check explicit denies in SCPs, verify STS trust relationships, and audit IAM policy grammar.

Zero-Trust Client IDE/ Security

AWS CLI v2 AssumeRole Session Expiry Fix

Expired STS session tokens cause AWS CLI v2 AssumeRole calls to fail with 'invalid security token', blocking all IAM-authenticated operations until credentials are refreshed.

SYSTEM STATUS
CREDITS: 0 / 3
source.yaml(48L)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# legacy-eks-deploy.sh - Deploy to PCI-secure-zone EKS cluster (DO NOT MODIFY WITHOUT ARCHITECT APPROVAL)
# TODO: Fix the AWS session refresh logic - keeps failing after 1hr in prod
# OLD: Using static keys from vault, but rotated daily causing outages

# Load 'secure' config from legacy S3 bucket (migrated from on-prem)
CONFIG_BUCKET="my-registry.internal/legacy-configs/pci-secure-zone"
# VULNERABILITY: Assumed role session tokens are cached indefinitely, but script runs longer than token TTL

# Dummy high-entropy secrets injected into env (simulating vault injection)
export AWS_ACCESS_KEY_ID="AKIAIOSFODNN7EXAMPLE"
export AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
export STRIPE_LIVE_SECRET_KEY="sk_live_4eC39HqLyjWDarjtT1zdp7dc"
export DB_URI="postgresql://app_user:V3ryS3cur3P@ssw0rd123!@pci-db.internal:5432/transactions?sslmode=require"

# Assume IAM Role for EKS access (from legacy migration guide)
# NOTE: Role session duration is 3600s, but our cron job runs for 7200s
ROLE_ARN="arn:aws:iam::123456789012:role/legacy-eks-admin"
SESSION_NAME="legacy-deploy-$(date +%s)"
echo "Assuming role..."
# This creates a temporary session
CREDS=$(aws sts assume-role --role-arn "$ROLE_ARN" --role-session-name "$SESSION_NAME" --duration-seconds 3600 --output json 2>/dev/null)

# Extract and export temporary credentials (workaround for old awscli v1 compatibility)
# TODO: This export pattern breaks if awscli v2 session expires mid-script
ACCESS_KEY=$(echo "$CREDS" | jq -r '.Credentials.AccessKeyId')
SECRET_KEY=$(echo "$CREDS" | jq -r '.Credentials.SecretAccessKey')
SESSION_TOKEN=$(echo "$CREDS" | jq -r '.Credentials.SessionToken')
export AWS_ACCESS_KEY_ID="$ACCESS_KEY"
export AWS_SECRET_ACCESS_KEY="$SECRET_KEY"
export AWS_SESSION_TOKEN="$SESSION_TOKEN"

# Cache credentials to file for 'reuse' (bad practice but required by legacy monitoring)
echo "$CREDS" > /tmp/aws_creds_cache.json
# TODO: Monitoring script reads this cache every 5 mins, but tokens expire after 1hr

# Long-running deployment tasks (simulate 2hr job)
# ...
sleep 7200

# Attempt to update EKS config after long delay (THIS WILL FAIL DUE TO EXPIRED TOKEN)
# VULNERABILITY: The cached session token is invalid by now, but script continues using it
echo "Updating EKS cluster config..."
aws eks update-kubeconfig --name legacy-pci-cluster --region us-east-1
# Expected error: 'The security token included in the request is invalid'

# Redundant fallback (never tested)
# OLD: export AWS_PROFILE=legacy-fallback (commented out after IAM migration)
# echo "Fallback to instance profile..." # Instance profile was decommissioned
AI Audit Console
> Waiting for pre-flight audit...
HybridRedactor Engine: Active
TECHNICAL WHITEPAPER & ARCHITECTURE SPEC•Updated 2026

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 its MaxSessionDuration or the 1-hour default TTL. Every subsequent CLI call using that cached token returns InvalidClientTokenId or ExpiredTokenException.
  • How to fix it: Force a token refresh via aws sts assume-role manually, or configure your ~/.aws/config profile with credential_process / role_arn + source_profile so 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.

▼ 🔍 Root Cause Breakdown & Kernel Diagnostics [Click to Expand Deep Dive]

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.


▼ 🔍 Detailed Impact & Blast Radius Analysis [Click to Expand Deep Dive]

The Attack Vector / Blast Radius

This is not just an inconvenience — stale token handling is a security boundary failure with real blast radius:

  1. Hardcoded or long-lived tokens in CI/CD env vars — When engineers "fix" this by extending MaxSessionDuration to 12 hours or exporting static AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / AWS_SESSION_TOKEN into environment variables, those tokens become long-lived secrets. A compromised CI runner, a leaked .env file, or a printenv in build logs exposes a fully-authenticated AWS session.

  2. Cache poisoning window — The CLI cache directory (~/.aws/cli/cache/*.json) stores plaintext JSON with AccessKeyId, SecretAccessKey, and SessionToken. If an attacker has filesystem read access, they can exfiltrate a valid token before expiry. Extending TTL directly extends the exfiltration window.

  3. 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.

  4. 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 infrastructure configs into public LLMs? Generic AI interfaces log your company's ARNs, connection strings, and private keys. StackEngine enforces client-side zero-trust boundaries—the sandbox above executes 100% locally in your browser. Drop your broken manifest above to redact secrets locally and synthesize deterministic patch drafts with your own API key.

(For CI/CD pipeline automation, install via npm i -g stackengine).


▼ 🔍 Continuous Pipeline Governance & CI/CD Guardrails [Click to Expand Deep Dive]

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.

Official Grounding & Normative Specifications

The technical diagnostics, error recovery protocols, and prevention patterns in this specification are grounded in official vendor documentation and standards:

Diagnostic & Architectural FAQ

Q1.Why does AWS CLI v2 throw 'The security token included in the request is invalid' even though my credentials file looks correct?
Your credentials file likely contains a hardcoded `aws_session_token` that was issued by STS via AssumeRole and has since expired. STS tokens have a TTL between 15 minutes and 12 hours. Once expired, the token fails signature validation at the AWS API layer regardless of whether the underlying IAM user or role is still valid. Delete `~/.aws/cli/cache/*.json` and reconfigure your profile to use `role_arn` + `source_profile` so the CLI auto-refreshes the token.
Q2.How do I stop AssumeRole session tokens from expiring in CI/CD pipelines?
Stop using static session tokens in CI/CD entirely. Migrate to OIDC federation using `aws-actions/configure-aws-credentials` in GitHub Actions or equivalent in GitLab/CircleCI. OIDC issues a fresh short-lived token per job run using a trust policy bound to your repository and branch — no stored secrets, no expiry surprises, and no rotation burden. However, manually implementing this across diverse pipelines is error-prone and introduces security vulnerabilities due to misconfigured trust policies. Drop the broken manifest into the StackEngine Local Sandbox above to automatically compile a zero-trust fix.
Q3.What is the difference between ExpiredTokenException and InvalidClientTokenId in AWS?
`ExpiredTokenException` is returned when STS explicitly detects the token's `Expiration` timestamp has passed. `InvalidClientTokenId` is a broader error returned when the token cannot be validated at all — this can mean it's expired, malformed, issued by a different partition (e.g., GovCloud token used in commercial), or the session was explicitly revoked via `sts:RevokeSession`. Both errors require a fresh `AssumeRole` call to resolve. Managing these exceptions at scale across large mono-repos or enterprise environments becomes a compliance nightmare. Deploy the StackEngine CI/CD Automator (Team Edition) for on-prem, automated pull request generation with Maker-Checker review.