Photo by Maria Ziegler on Unsplash
To check AWS assumed role permissions, call STS GetCallerIdentity, take the role name from the assumed-role ARN it returns, then list the role’s attached and inline policies with IAM. That shows what the role grants. A permissions boundary, SCPs and session policies can still reduce it, so use iam:SimulatePrincipalPolicy to test specific actions.
“Which role am I, and what can it do?” comes up in every access-denied investigation, every SSO setup and every CI pipeline that assumes a role. The console answers it in several clicks across two services. This example is for developers and platform engineers who want to check AWS assumed role permissions from the terminal in one step: the identity, the role’s full ARN, every policy statement, the boundary, and an optional simulation of the actions you care about. To get into a role from code in the first place, the guide to assume an IAM role with STS AssumeRole in AWS SDK v3 covers the call and its temporary credentials.
It’s one of our hands-on AWS practical examples with SDK v3 scripts, and it’s the natural first step before you troubleshoot AWS IAM access denied errors step by step.
How do you get from a session to its role?
- Ask STS who you are
GetCallerIdentityreturns the account, a user ID and an ARN. It needs no IAM permission, and an explicit deny can’t block it, so it always works with valid credentials. - Parse the assumed-role ARNFor a role session the ARN looks like
arn:aws:sts::123456789012:assumed-role/DeployRole/ci-4821: resource type, role name, session name. IAM users and root showuser/...orrootinstead. - Resolve the real roleThe assumed-role ARN drops the role’s path, so the script calls
GetRoleby name to get the full IAM ARN, for examplearn:aws:iam::123456789012:role/aws-reserved/sso.amazonaws.com/AWSReservedSSO_ReadOnly_1a2b3c4d5e6f7a8bfor an IAM Identity Center permission set. - Read the policies
ListAttachedRolePoliciesplusGetPolicyandGetPolicyVersionfor managed policies;ListRolePoliciesplusGetRolePolicyfor inline ones. IAM returns documents URL-encoded, so they’re decoded before printing. - Optionally simulateWith
--check,SimulatePrincipalPolicyevaluates the listed actions against the role and reportsallowed,implicitDenyorexplicitDeny.
Why the attached policies aren’t the whole answer
A role’s identity policies say what it may do. Other policy types can only narrow that, and an explicit deny in any of them wins:
| Policy type | Can it grant? | Does the script see it? |
|---|---|---|
| Attached and inline identity policies | Yes | Yes, printed statement by statement |
| Permissions boundary | No, it caps the maximum | Yes, printed, and reflected in the simulation |
| Organizations SCPs | No, they cap the account | Only through the simulation’s OrganizationsDecisionDetail |
Session policies (passed to AssumeRole) |
No, they cap one session | No. They can’t be read back from a session. |
| Resource-based policies (bucket, key, queue) | Yes, for that resource | No, unless you pass them to a simulation |
The full order is laid out in the IAM policy evaluation logic reference. In short: if the script shows an allow but your call still fails, look at the boundary, SCPs, session policy and the resource’s own policy, in that order.
Prerequisites
- Node.js 18 or later, npm and
tsx. - The
@aws-sdk/client-stsand@aws-sdk/client-iampackages. - A profile that assumes a role (an SSO profile, a
role_arnprofile, or CI credentials) and any region set. IAM is global, but the SDK still needs a region to resolve the endpoint.
Which IAM permissions does it need?
The role has to be allowed to read itself. sts:GetCallerIdentity needs nothing. Scope the role statement to a single role ARN if you know it. Replace 123456789012 with your account ID.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ReadOwnRoleAndSimulate",
"Effect": "Allow",
"Action": [
"iam:GetRole",
"iam:ListAttachedRolePolicies",
"iam:ListRolePolicies",
"iam:GetRolePolicy",
"iam:SimulatePrincipalPolicy"
],
"Resource": "arn:aws:iam::123456789012:role/*"
},
{
"Sid": "ReadManagedPolicyDocuments",
"Effect": "Allow",
"Action": ["iam:GetPolicy", "iam:GetPolicyVersion"],
"Resource": [
"arn:aws:iam::aws:policy/*",
"arn:aws:iam::123456789012:policy/*"
]
}
]
}
AWS’s ReadOnlyAccess and IAMReadOnlyAccess managed policies include the read actions. To judge what you find, the checklist to review a generated IAM policy for least privilege flags wildcards and missing conditions. To see which actions your own code needs, paste it into the AI IAM policy generator for TypeScript code and compare its output with this report.
The script to check AWS assumed role permissions
// whoami-permissions.ts
// Shows who you are (STS GetCallerIdentity), resolves the IAM role behind an
// assumed-role session, and prints its attached and inline policies plus any
// permissions boundary. Optionally simulates specific actions.
// Read-only. Usage: npx tsx whoami-permissions.ts [--check s3:GetObject,ec2:DescribeInstances]
import { STSClient, GetCallerIdentityCommand } from "@aws-sdk/client-sts";
import {
IAMClient,
GetRoleCommand,
GetPolicyCommand,
GetPolicyVersionCommand,
GetRolePolicyCommand,
paginateListAttachedRolePolicies,
paginateListRolePolicies,
paginateSimulatePrincipalPolicy,
} from "@aws-sdk/client-iam";
type Statement = {
Effect: string;
Action?: string | string[];
NotAction?: string | string[];
Resource?: string | string[];
NotResource?: string | string[];
Condition?: unknown;
};
const iam = new IAMClient({});
const list = (v?: string | string[]) => (v === undefined ? [] : Array.isArray(v) ? v : [v]);
// IAM returns policy documents URL-encoded.
function printDocument(encoded: string | undefined): void {
if (!encoded) return;
const doc = JSON.parse(decodeURIComponent(encoded)) as { Statement: Statement | Statement[] };
const statements = Array.isArray(doc.Statement) ? doc.Statement : [doc.Statement];
for (const s of statements) {
const actions = s.Action ? list(s.Action) : list(s.NotAction).map((a) => `NOT ${a}`);
const resources = s.Resource ? list(s.Resource) : list(s.NotResource).map((r) => `NOT ${r}`);
const shown = actions.length > 6 ? [...actions.slice(0, 6), `…+${actions.length - 6} more`] : actions;
console.log(` ${s.Effect.padEnd(5)} ${shown.join(", ")}`);
console.log(` on ${resources.join(", ")}${s.Condition ? " (with conditions)" : ""}`);
}
}
async function printManagedPolicy(policyArn: string): Promise<void> {
const { Policy } = await iam.send(new GetPolicyCommand({ PolicyArn: policyArn }));
const version = await iam.send(
new GetPolicyVersionCommand({ PolicyArn: policyArn, VersionId: Policy?.DefaultVersionId }),
);
printDocument(version.PolicyVersion?.Document);
}
async function main(): Promise<void> {
const me = await new STSClient({}).send(new GetCallerIdentityCommand({}));
console.log(`Account: ${me.Account}\nCaller: ${me.Arn}\nUserId: ${me.UserId}`);
// arn:aws:sts::123456789012:assumed-role/RoleName/SessionName
const [kind, roleName, session] = (me.Arn?.split(":")[5] ?? "").split("/");
if (kind !== "assumed-role" || !roleName) {
console.log("\nThis identity is not an assumed role (IAM user or root), so there is no role to inspect.");
return;
}
console.log(`Session: ${session}`);
// The assumed-role ARN drops the role path; GetRole returns the full role ARN.
const { Role } = await iam.send(new GetRoleCommand({ RoleName: roleName }));
if (!Role) throw new Error(`Role ${roleName} not found`);
console.log(`Role: ${Role.Arn}`);
console.log(`Max session duration: ${(Role.MaxSessionDuration ?? 3600) / 3600} h`);
console.log("\nAttached managed policies:");
for await (const page of paginateListAttachedRolePolicies({ client: iam }, { RoleName: roleName })) {
for (const p of page.AttachedPolicies ?? []) {
console.log(` - ${p.PolicyName} (${p.PolicyArn})`);
if (p.PolicyArn) await printManagedPolicy(p.PolicyArn);
}
}
console.log("\nInline policies:");
for await (const page of paginateListRolePolicies({ client: iam }, { RoleName: roleName })) {
for (const name of page.PolicyNames ?? []) {
console.log(` - ${name}`);
const res = await iam.send(new GetRolePolicyCommand({ RoleName: roleName, PolicyName: name }));
printDocument(res.PolicyDocument);
}
}
const boundary = Role.PermissionsBoundary?.PermissionsBoundaryArn;
console.log(`\nPermissions boundary: ${boundary ?? "none"}`);
if (boundary) await printManagedPolicy(boundary);
// Optional: ask IAM to evaluate specific actions for this role.
const flag = process.argv.indexOf("--check");
const actions = flag > -1 ? (process.argv[flag + 1] ?? "").split(",").filter(Boolean) : [];
if (actions.length === 0) return;
console.log("\nSimulation (identity policies, boundary and SCPs; session policies are not included):");
for await (const page of paginateSimulatePrincipalPolicy(
{ client: iam },
{ PolicySourceArn: Role.Arn, ActionNames: actions },
)) {
for (const r of page.EvaluationResults ?? []) {
const scp = r.OrganizationsDecisionDetail?.AllowedByOrganizations === false ? " [blocked by SCP]" : "";
const pb = r.PermissionsBoundaryDecisionDetail?.AllowedByPermissionsBoundary === false ? " [blocked by boundary]" : "";
console.log(` ${r.EvalActionName?.padEnd(28)} ${r.EvalDecision}${scp}${pb}`);
}
}
}
main().catch((err: unknown) => {
console.error(err);
process.exit(1);
});
How do you run it?
npm install @aws-sdk/client-sts @aws-sdk/client-iam
npm install --save-dev tsx typescript
# Who am I, and what does my role grant?
AWS_PROFILE=deploy AWS_REGION=us-east-1 npx tsx whoami-permissions.ts
# Also simulate specific actions
AWS_PROFILE=deploy AWS_REGION=us-east-1 npx tsx whoami-permissions.ts \
--check s3:GetObject,s3:DeleteObject,ec2:TerminateInstances,lambda:UpdateFunctionCode
Actions passed to --check are simulated against "Resource": "*". For resource-specific questions, add ResourceArns to the SimulatePrincipalPolicy input.
Sample output
Account: 123456789012
Caller: arn:aws:sts::123456789012:assumed-role/DeployRole/ci-4821
UserId: AROA3XFRBF23EXAMPLE1:ci-4821
Session: ci-4821
Role: arn:aws:iam::123456789012:role/ci/DeployRole
Max session duration: 1 h
Attached managed policies:
- CiReadOnly (arn:aws:iam::123456789012:policy/CiReadOnly)
Allow s3:GetObject, s3:ListBucket, logs:FilterLogEvents, cloudwatch:GetMetricData, lambda:GetFunction, lambda:ListFunctions, …+4 more
on *
- DeployLambdaCode (arn:aws:iam::123456789012:policy/DeployLambdaCode)
Allow lambda:UpdateFunctionCode, lambda:PublishVersion
on arn:aws:lambda:us-east-1:123456789012:function:api-*
Inline policies:
- DenyDestructiveS3
Deny s3:DeleteObject, s3:DeleteBucket
on *
Permissions boundary: arn:aws:iam::123456789012:policy/CiBoundary
Allow s3:*, lambda:*, logs:*, cloudwatch:*
on *
Simulation (identity policies, boundary and SCPs; session policies are not included):
s3:GetObject allowed
s3:DeleteObject explicitDeny
ec2:TerminateInstances implicitDeny
lambda:UpdateFunctionCode implicitDeny
ARNs, policy names and results are illustrative. s3:DeleteObject shows explicitDeny because of the inline Deny; ec2:TerminateInstances is an implicit deny because nothing grants it. lambda:UpdateFunctionCode is an implicit deny only because the simulation used "Resource": "*" while the policy allows function:api-*; pass the real function ARN in ResourceArns to test it properly.
Troubleshooting
AccessDeniedoniam:GetRole. The role can’t read its own configuration, a common setup for tightly scoped CI roles. Run the script with an admin or audit profile and set the role name by hand, or ask your account admin to add the read statement above.- “This identity is not an assumed role”. You’re signed in as an IAM user or root. Use
ListAttachedUserPolicies,ListUserPoliciesandListGroupsForUserinstead, or switch to a role profile. - Simulation says
allowedbut the call fails. Check the session policy, the resource’s own policy, KMS key policies and VPC endpoint policies. The IAM access denied troubleshooting guide walks through each. - The wrong role shows up. The SDK used a different profile or environment credentials than you expected. Check
AWS_PROFILE,AWS_ACCESS_KEY_IDand the profile’srole_arn. The guide to connect ChatWithCloud to your AWS account with profiles, SSO and roles explains how profiles are resolved, and the guide to AWS SDK v3 credential providers and the default chain order shows which source the SDK picks first.
Ask ChatWithCloud instead
You can ask ChatWithCloud “Which role am I using, and what can it do?” and follow up with “Can it delete objects in the app-uploads bucket?” It writes AWS SDK for JavaScript v2 code, runs it on your machine with the profile you picked and explains the policies in plain English, as described in how ChatWithCloud answers AWS questions from your terminal. It uses one profile and region per session, so the answer is about exactly the identity it runs as. It runs changes without a confirmation step, so give it a read-only role, and use this script to confirm the role really is read-only. The ChatWithCloud security model explains what is sent for processing and what stays on your machine.
Frequently asked questions
How do I see which IAM role I’m using in the AWS CLI?
Run aws sts get-caller-identity. The Arn field contains assumed-role/ROLE_NAME/SESSION_NAME for a role session.
Can I list the permissions of an assumed role session directly?
No. AWS has no API that returns a session’s effective permissions. You read the role’s policies and boundary, then simulate or test specific actions.
Does SimulatePrincipalPolicy include SCPs?
For accounts in an organization, results include OrganizationsDecisionDetail, which shows whether SCPs allow the action. Session policies aren’t part of the simulation.
Why is GetCallerIdentity allowed when everything else is denied?
AWS exempts it from authorization, so it always works with valid credentials. That makes it a reliable first check that your credentials work at all.
Related guides
Ask your AWS account in plain English
Your first 15 runs are free, with no OpenAI key needed.
npx chatwithcloud