Skip to content

sec(iac/aws): role-prefix ARN scopes are satisfiable via the IAM path (arn:aws:iam::*:role/cudly-*) #197

Closed

Description

@cristim

Surfaced during the review of LeanerCloud/cloud-commitments-cli#1818 (which closes LeanerCloud/cloud-commitments-cli#1723). Pre-existing, not introduced by that PR, which changed no Resource, Condition or ARN line. Filed separately rather than widening LeanerCloud/cloud-commitments-cli#1818's scope.

What

terraform/environments/aws/ci-cd-permissions/policy_data.tf scopes several statements with a trailing-wildcard role ARN:

  • :61 arn:aws:iam::*:role/cudly-* (with policy/cudly-* and instance-profile/cudly-*)
  • :77 arn:aws:iam::*:role/cudly-*, the iam:PassRole statement, conditioned on iam:PassedToService

In an IAM resource ARN, * matches any sequence of characters including /, and the IAM role path is part of the ARN. So arn:aws:iam::123456789012:role/cudly-x/EvilRole satisfies arn:aws:iam::*:role/cudly-*. The prefix that is supposed to identify our roles can be carried in the path, which is a field the role's creator controls, rather than in the role name.

The same shape applies to arn:aws:iam::*:role/CUDly* in CrossAccountAssumeRoleCeiling. That one additionally has drift protection from TestBoundaryMatchesCrossAccountRolePrefix, but the path-matching property is identical. Note this is a different concern from LeanerCloud/cloud-commitments-cli#1628 item 4 / LeanerCloud/cloud-commitments-cli#1636, which is about the missing per-tenant account condition on sts:AssumeRole, not about path-bearing ARNs.

Why it matters

This is the classic IAM path wildcard bypass: an attacker satisfies a name-prefix guard by putting the required prefix in a segment they choose.

It is currently contained, and the containment is worth stating precisely so nobody over-reacts or under-reacts:

So this is not a live escape today. It is a guard whose correctness depends entirely on a separate control staying in place, which is exactly the kind of coupling that breaks silently later.

Fix direction

Prefer a form that cannot be satisfied by a path segment. Options, roughly in order of preference:

  1. Constrain the path explicitly, e.g. require arn:aws:iam::*:role/cudly-* and add an iam:ResourceTag or a path condition that pins the expected path, so the name prefix is not the only thing being checked.
  2. Pin the path in the ARN itself (role/cudly/…) and match on the full, expected shape rather than a trailing wildcard.
  3. At minimum, add a guard test asserting that a path-bearing ARN such as arn:aws:iam::123456789012:role/cudly-x/EvilRole does not satisfy the intended scope, so the residual is pinned rather than remembered.

Whatever is chosen, note the house rule: a security condition must be compared to its expected value. "Starts with the right prefix" is a shape check, and a shape check admits every value of that shape.

Verification bar

Both directions. Assert that a path-bearing impostor ARN is refused and that the real deploy roles still match, or the fix will pass its test by refusing everything and break the deploy.

Activity

  1. IAmJSD commented on Oct 16, 2026

    @IAmJSD
    Collaborator

    Claiming: working on this

  2. self-assigned this
    on Oct 16, 2026
  3. added a commit that references this issue on Oct 16, 2026
    eb1739a
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions