Initializing Enclave...

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 AssumeRole call is throwing InvalidSessionName or ValidationError because RoleSessionName contains 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.

Related Diagnostics

"Part of the Security Utility Matrix."

View all 140 Security Tools →