Fixing CodeBuild AccessDeniedException: Missing sts:TagSession Permission for AssumeRole
Threat/Impact Level: HIGH | Downtime Risk: HIGH | Time to Fix: 5–10 mins
TL;DR
- What broke: CodeBuild's service role (or the role it's trying to assume) is missing
sts:TagSession, causingAssumeRoleto hard-fail withAccessDeniedException. - How to fix it: Add
sts:TagSessionto the IAM policy attached to the principal initiating theAssumeRolecall — not the target role's trust policy. - Fast path: Use our Client-Side Sandbox below to auto-refactor your failing policy. Paste it in, get the corrected version without sending your ARNs to a third-party server.
The Incident (What Does the Error Mean?)
You hit this in your CodeBuild build logs or CloudTrail:
An error occurred (AccessDeniedException) when calling the AssumeRole operation:
User: arn:aws:sts::123456789012:assumed-role/CodeBuildServiceRole/session
is not authorized to perform: sts:AssumeRole on resource: arn:aws:iam::123456789012:role/TargetDeployRole
with an explicit deny or missing permission: sts:TagSession
Immediate consequence: The build pipeline stops cold. No deployment proceeds. If this role assumption is part of a cross-account deploy, staging or production pushes are completely blocked. The error message is deliberately vague — AWS bundles the sts:TagSession denial into the AssumeRole failure, which is why engineers waste 30+ minutes chasing the wrong permission.
Root cause: When session tags are passed during AssumeRole (either explicitly in your buildspec or implicitly by a service like CodePipeline passing PrincipalTag context), AWS requires the calling principal to have sts:TagSession on the target role resource. Without it, the entire AssumeRole call is rejected — not just the tagging.
The Attack Vector / Blast Radius
This is a least-privilege enforcement gap, not a vulnerability in the traditional sense — but misconfiguring the fix creates real risk.
The wrong fix engineers reach for: Granting sts:* or sts:AssumeRole with Resource: "*" to make the error go away. That's now a critical misconfiguration. Any principal with that policy can assume any role in the account that has a permissive trust policy, including roles with AdministratorAccess.
Blast radius of the lazy fix:
- Compromised CodeBuild execution environment (supply chain attack via a malicious dependency) can call
sts:AssumeRoleon high-privilege roles. - Lateral movement across accounts if cross-account roles exist with weak trust policies.
- Privilege escalation path:
CodeBuild role → sts:AssumeRole * → AdminRole → full account compromise.
The correct blast radius is zero — sts:TagSession scoped to the exact target role ARN adds no new attack surface.
How to Fix It (The Solution)
Basic Fix
Add sts:TagSession to the IAM policy attached to your CodeBuild service role, scoped to the specific target role ARN.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
- "sts:AssumeRole"
+ "sts:AssumeRole",
+ "sts:TagSession"
],
"Resource": "arn:aws:iam::123456789012:role/TargetDeployRole"
}
]
}
Critical: Resource must be the exact ARN of the role being assumed. Never "*".
Enterprise Best Practice (Condition Key Locking)
Scope sts:TagSession further using aws:RequestedRegion and sts:TransitiveTagKeys condition keys to prevent tag-based privilege escalation via transitive session tags.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowAssumeDeployRole",
"Effect": "Allow",
"Action": [
- "sts:AssumeRole"
+ "sts:AssumeRole",
+ "sts:TagSession"
],
"Resource": "arn:aws:iam::123456789012:role/TargetDeployRole",
+ "Condition": {
+ "StringEquals": {
+ "aws:RequestedRegion": "us-east-1"
+ },
+ "ForAllValues:StringEquals": {
+ "sts:TransitiveTagKeys": ["Project", "Environment"]
+ }
+ }
}
]
}
Why sts:TransitiveTagKeys matters: Without this condition, a compromised build could inject arbitrary session tags that propagate to subsequent AssumeRole chains, potentially satisfying ABAC conditions on high-privilege roles downstream.
Also verify the target role's trust policy allows tagged sessions from your CodeBuild role:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:role/CodeBuildServiceRole"
},
- "Action": "sts:AssumeRole"
+ "Action": [
+ "sts:AssumeRole",
+ "sts:TagSession"
+ ]
}
]
}
💡 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 (IaC scanning — catches this pre-merge):
Add a custom Checkov check or use the built-in CKV_AWS_110 / CKV_AWS_111 rules to flag any sts:AssumeRole statement missing a paired sts:TagSession when session tags are in use. Run in your PR pipeline:
checkov -d ./iam --framework cloudformation --check CKV_AWS_110
2. OPA / Conftest Policy (enforce in Terraform plan):
deny[msg] {
stmt := input.resource.aws_iam_policy.document.statement[_]
"sts:AssumeRole" == stmt.actions[_]
not contains_tag_session(stmt.actions)
msg := sprintf("IAM policy '%v' has sts:AssumeRole without sts:TagSession", [input.resource.aws_iam_policy.document])
}
contains_tag_session(actions) {
actions[_] == "sts:TagSession"
}
3. AWS Config Rule: Enable iam-policy-no-statements-with-admin-access and write a custom Config rule that flags CodeBuild service roles missing sts:TagSession when cross-role assumptions are detected in CloudTrail.
4. Terraform aws_iam_role_policy module pattern: Enforce a reusable module for all CodeBuild roles that always pairs sts:AssumeRole with sts:TagSession and mandates a non-wildcard Resource. Block direct aws_iam_policy_document usage outside the module via Sentinel (Terraform Cloud) or OPA.