Fixing AWS STS InvalidSessionName: RoleSessionName Special Character Validation Error
Threat/Impact Level: HIGH | Exploitability/Downtime Risk: HIGH | Time to Fix: 5 mins
TL;DR
- What broke: Your
AssumeRolecall is throwingInvalidSessionNameorValidationErrorbecauseRoleSessionNamecontains a character outside AWS's strict allowlist ([\w+=,.@-], 2–64 chars). Common offenders:/,:, spaces,*,(,). - How to fix it: Sanitize the session name at construction time — strip or replace every character not matching
[A-Za-z0-9+=,.@_-]and clamp the string to 64 characters. - Fastest path: Use our Client-Side Sandbox above to auto-refactor this — paste your
boto3, AWS CLI, or Terraform snippet and get a corrected session name pattern instantly.
The Incident (What Does the Error Mean?)
Raw error from AWS STS:
botocore.exceptions.ParamValidationError:
Parameter validation failed:
Invalid length for parameter RoleSessionName, value: 0, valid min length: 2
-- OR --
An error occurred (ValidationError) when calling the AssumeRole operation:
1 validation error detected: Value 'my/service:prod (deploy)' at 'roleSessionName'
failed to satisfy constraint: Member must satisfy regular expression pattern: [\w+=,.@-]{2,64}
Immediate consequence: The AssumeRole call returns a 4xx before any temporary credentials are issued. Your Lambda, ECS task, CI runner, or federation flow gets zero credentials — it fails hard, right now, in production. Every downstream API call that depends on those assumed-role credentials is dead.
The Attack Vector / Blast Radius
This looks like a validation nuisance. It is not. Here is the actual blast radius:
1. CI/CD Pipeline Collapse
Most pipelines dynamically construct RoleSessionName from branch names, commit SHAs, or PR titles. Branch names like feature/JIRA-123 (hotfix) or refs/heads/deploy:prod inject /, :, spaces, and parentheses directly into the session name. One bad branch name kills every deploy.
2. CloudTrail Attribution Breaks
If your security team uses RoleSessionName as the principal identifier in CloudTrail (sts:RoleSessionName condition key), a pipeline that silently truncates or mangles the session name instead of rejecting it will produce unattributable audit events. You lose the ability to tie an API call back to a specific build, developer, or service.
3. SCP / Permission Boundary Bypass Risk
Some organizations enforce SCPs or permission boundaries using aws:PrincipalTag combined with session tags passed at AssumeRole time. If the session name construction is broken, the entire tagging chain can be silently skipped — meaning a workload may assume a role without the expected restrictive tags applied, operating with broader permissions than intended until the next deploy.
4. Cascading Retry Storms
A misconfigured retry loop that doesn't catch ValidationError (as distinct from a transient ThrottlingException) will hammer STS with guaranteed-failing requests, burning through your STS API rate limit and potentially triggering throttling for legitimate callers in the same account.
How to Fix It (The Solution)
AWS STS RoleSessionName Constraints (Memorize These)
| Constraint | Value |
|---|---|
| Allowed characters | A-Z a-z 0-9 = , . @ _ - |
| Regex pattern | [\w+=,.@-]{2,64} |
| Min length | 2 |
| Max length | 64 |
| Forbidden | / : ( ) * ? # % spaces, all Unicode outside ASCII |
Basic Fix — Python (boto3)
import boto3
import re
- session_name = f"deploy/{branch_name}:{environment} (ci)"
+ raw = f"deploy-{branch_name}-{environment}-ci"
+ session_name = re.sub(r'[^\w+=,.@-]', '-', raw)[:64]
+ # Collapse multiple consecutive dashes and strip leading/trailing dashes
+ session_name = re.sub(r'-{2,}', '-', session_name).strip('-')
+ if len(session_name) < 2:
+ session_name = "fallback-session"
sts = boto3.client('sts')
response = sts.assume_role(
RoleArn="arn:aws:iam::123456789012:role/MyRole",
RoleSessionName=session_name,
)
Basic Fix — AWS CLI
- aws sts assume-role \
- --role-arn arn:aws:iam::123456789012:role/MyRole \
- --role-session-name "my/service:prod (deploy)"
+ SESSION=$(echo "my-service-prod-deploy" | tr -cd 'A-Za-z0-9+=,.@_-' | cut -c1-64)
+ aws sts assume-role \
+ --role-arn arn:aws:iam::123456789012:role/MyRole \
+ --role-session-name "${SESSION}"
Enterprise Best Practice — Terraform + Deterministic Session Name Pattern
Never let raw branch names or human-supplied strings touch RoleSessionName directly. Construct it deterministically.
- resource "aws_iam_role" "ci_role" {
- # Caller passes var.session_name directly from CI env var
- }
-
- # In your assume_role data source:
- data "aws_iam_policy_document" "assume" {
- statement {
- condition {
- test = "StringEquals"
- variable = "sts:RoleSessionName"
- values = [var.raw_branch_name] # DANGEROUS: unvalidated input
- }
- }
- }
+ locals {
+ # Sanitize: replace all non-allowlist chars with dash, clamp to 64
+ _raw_session = "${var.service_name}-${var.environment}-${var.build_id}"
+ safe_session = substr(replace(local._raw_session, "/[^A-Za-z0-9+=,.@_-]/", "-"), 0, 64)
+ }
+
+ data "aws_iam_policy_document" "assume" {
+ statement {
+ condition {
+ test = "StringLike"
+ variable = "sts:RoleSessionName"
+ # Enforce a prefix pattern so only your CI service can assume with valid names
+ values = ["${var.service_name}-${var.environment}-*"]
+ }
+ }
+ }
Why the StringLike condition matters: Without it, any principal that can call AssumeRole can supply an arbitrary session name, defeating CloudTrail attribution. Locking sts:RoleSessionName to a prefix pattern in the trust policy is a zero-cost, high-value control.
💡 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. Pre-commit / Linting — Catch It Before git push
Add a shell guard in your pipeline entrypoint script:
#!/usr/bin/env bash
set -euo pipefail
RAW_SESSION="${SERVICE}-${ENVIRONMENT}-${CI_PIPELINE_ID}"
SESSION_NAME=$(echo "$RAW_SESSION" | tr -cd 'A-Za-z0-9+=,.@_-' | cut -c1-64)
[[ ${#SESSION_NAME} -lt 2 ]] && { echo "ERROR: session name too short after sanitization"; exit 1; }
export ROLE_SESSION_NAME="$SESSION_NAME"
2. Checkov — Static IaC Scanning
Checkov does not have a built-in check for RoleSessionName format, but you can register a custom check:
# checkov/custom_checks/check_sts_session_name.py
from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
from checkov.common.models.enums import CheckResult, CheckCategories
import re
SESSION_NAME_RE = re.compile(r'^[\w+=,.@-]{2,64}$')
class STSSessionNameCheck(BaseResourceCheck):
def __init__(self):
super().__init__(
name="Ensure RoleSessionName matches STS constraints",
id="CKV_CUSTOM_STS_001",
categories=[CheckCategories.IAM],
supported_resources=["aws_iam_role"]
)
def scan_resource_conf(self, conf):
session = conf.get("role_session_name", [""])[0]
if session and not SESSION_NAME_RE.match(session):
return CheckResult.FAILED
return CheckResult.PASSED
3. OPA / Conftest — Policy-as-Code for Terraform Plans
# policies/sts_session_name.rego
package terraform.aws.sts
import future.keywords.if
deny[msg] if {
resource := input.resource_changes[_]
resource.type == "aws_iam_role"
session := resource.change.after.role_session_name
not regex.match(`^[\w+=,.@\-]{2,64}$`, session)
msg := sprintf(
"Resource '%v' has invalid RoleSessionName '%v'. Must match [\\w+=,.@-]{2,64}.",
[resource.address, session]
)
}
Run in CI:
terraform show -json tfplan.binary | conftest test --policy policies/ -
4. GitHub Actions — Inline Sanitization Step
- name: Sanitize STS Session Name
id: session
run: |
RAW="${{ github.event.repository.name }}-${{ github.run_id }}"
CLEAN=$(echo "$RAW" | tr -cd 'A-Za-z0-9+=,.@_-' | cut -c1-64)
echo "name=$CLEAN" >> $GITHUB_OUTPUT
- name: Assume IAM Role
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/MyRole
role-session-name: ${{ steps.session.outputs.name }}
aws-region: us-east-1
The aws-actions/configure-aws-credentials action does NOT sanitize role-session-name for you. The sanitization step above is mandatory if your repo name or run metadata can contain slashes or colons.