iam-deceptive-escalation-auditor skill
Audit the union of every IAM policy attached to one principal for privilege-escalation paths that no single statement reveals, and for apparent escalations that are already neutralised. Resolves the effective permission set across all attached policies (Allow minus blanket Deny), then checks the cross-statement escalation combos (iam:PassRole + a compute-launch action, policy-rewrite-in-place, function-code hijack, self-attach admin, trust-policy rewrite + assume, credential minting for another identity), the wildcard grants (Action '*' on Resource '*', service-level wildcards, Allow+NotAction), and the trust-policy exposure. Its discipline is symmetric: it does NOT flag a PassRole combo killed by an explicit Deny, an Action '*' pinned to one bucket, an sts:AssumeRole whose target does not trust back, a mutation kit capped by a permissions-boundary Deny, or a cross-account assume sealed by an unsatisfiable Condition. Reports findings with severity and a fix, then names what a single principal's policies cannot answer (the privileges of a passed/assumed role, the permissions boundary, the org SCPs). Use when asked to audit an IAM policy, role, or user for escalation, over-broad grants, or "can this principal become admin." Vendor-neutral; runs offline against the policy JSON with no Anyshift account.
Is the iam-deceptive-escalation-auditor skill safe?
Clean: nothing in its files matched our rules. We read 42 files in the folder on 2026-09-28.
- low
SKILL.md:1The description is over 1,024 characters, the limit agents read.
1320 characters
Install the iam-deceptive-escalation-auditor skill
A skill is a folder. Copy it into your agent's skills folder and the agent loads it when the task matches its description.
git clone --depth 1 https://github.com/anyshift-io/sre-skills.git /tmp/sre-skills mkdir -p ~/.claude/skills cp -r /tmp/sre-skills/skills/iam-deceptive-escalation-auditor ~/.claude/skills/iam-deceptive-escalation-auditor
In the Claude apps, zip the folder and upload it from the Skills settings. The folder on GitHub
The instructions your agent would load
SKILL.md as published, without the frontmatter. Read it on GitHub
iam-deceptive-escalation-auditor
Privilege-escalation audit skill for one AWS IAM principal. Takes every permissions policy attached to a role or user (plus the trust policy and permissions boundary if supplied), resolves the effective permission set across all of them, and answers one question a per-statement read cannot: can this principal escalate to a privilege it was not granted, and is an apparent escalation real or already neutralised. It returns findings with severity and a fix, then names exactly where a single principal's policy documents stop being able to answer the question.
The escalation combinations this skill exists to catch are precisely the ones that span two statements or two attached policies, so that no single statement looks guilty on its own. iam:PassRole in one policy and sagemaker:CreateTrainingJob in another are each routine; together they let the principal launch compute with any role attached and inherit it. A per-statement read clears every statement and misses the union. The other half of the skill is the inverse discipline: an explicit Deny, a resource scope, a broken trust, or an unsatisfiable Condition can neutralise an escalation that still reads as critical, and the audit must not fabricate a finding the effective permissions do not support.
When to invoke
grants, or "can this principal become administrator."
- An agent is asked to audit an IAM role or user for privilege escalation, over-broad
grants combine into an escalation.
- A policy is being shipped or reviewed and the question is whether two individually-fine
and the claim "but it's capped / scoped / denied" needs to be confirmed against the effective permissions, not taken on trust.
- A policy looks dangerous (a full mutation kit, a cross-account assume, an Action '*')
- An incident assumes a principal is compromised and the question is what it can escalate to.
What this skill reads, and what it does not
It reads the static policy documents attached to one principal: every permissions policy, plus the trust policy (AssumeRolePolicyDocument) and the permissions boundary if supplied. That is the entire input. The audit is correct and complete for the effective permissions those documents express, and it is explicit about the rest. Every audit ends by naming the joins it cannot make:
Effective permissions are the union of every managed and inline policy. Join: principal to its full set of attached policies.
- It does not see the principal's other attached policies if only some were supplied.
what any Allow can actually grant. Join: principal to its permissions boundary.
- It does not know the permissions boundary unless one is supplied. A boundary caps
Allows and is invisible from the account. Join: account to its organization's SCPs.
- It does not see org SCPs. A Service Control Policy can Deny actions this policy
assumes, or hijacks a role only matters if that role is more privileged than this principal, and those privileges live in other documents. Join: this policy to the roles and resources it references.
- It does not contain the privileges of a targeted role. An escalation that passes,
A clean (neutralised) policy still gets a boundary section, because a capped policy is not a proven-safe principal.
The model
Build the effective permission set across all attached policies. An action is granted when some Allow statement matches it (by case-insensitive glob on Action, or by NotAction) and no blanket Deny (on Resource "*") matches it. Deny wins over Allow, always. The escalation checks then run against this resolved set, not against any single statement, because the combos are unions and the neutralisations are denies.
Deny handling is a conservative approximation: a Deny on Resource "*" kills the action;
resource-specific denies are behind the boundary (the audit does not enumerate the
account's ARNs). This never under-reports a grant on a wildcard resource, which is the
case the skill cares about.
The methodology, in order
1. Resolve the effective permission set
Before any judgment, union the statements and apply Deny:
policies, and the escalation combos are exactly the ones that span them.
- Load every policy*.json for the principal. A principal can have several attached
no blanket Deny does. Read Effect: Deny as a hard constraint, not noise — it is the single most common neutraliser in this corpus.
- Split into Allow and Deny statements. An action is granted only if an Allow matches it and
grants, so a wildcard is judged by what it contains, not skimmed as "broad."
- Expand a wildcard Action ( or svc:) into the concrete sensitive permissions it
(suppresses the "no boundary provided" note and may itself be the Deny that caps a kit).
- Read the trust policy (enables the trust-exposure check) and the permissions boundary
2. Check the cross-statement escalation combos (E1-E6)
These are the flagship. Each spans statements so no single one looks guilty. Run them against the resolved set:
ec2:RunInstances, lambda:CreateFunction, ecs:RunTask, sagemaker:CreateTrainingJob, cloudformation:CreateStack, etc.: launch compute with a more-privileged role attached, then use that compute's credentials. Critical when PassRole is on Resource "" (any role, including admin); high when scoped (the escalation is real only if that scoped role is more privileged — a boundary question). The launch action must actually bind a role*: Start/Invoke on existing compute take no PassRole argument and do not arm E1.
- E1 (critical/high) — iam:PassRole + a compute-launch action. Pair PassRole with
iam:SetDefaultPolicyVersion: mint a new admin version of an attached policy, or flip the default back to a permissive one. No second action needed; the policy ARN is unchanged.
- E2 (critical) — rewrite a managed policy in place. iam:CreatePolicyVersion /
overwrite an existing function's code to run attacker code with that function's role. No PassRole required (it reuses an attached role).
- E3 (critical) — hijack a function's execution role. lambda:UpdateFunctionCode:
AttachRolePolicy / PutRolePolicy etc.: a single attach call turns a scoped identity into an administrator.
- E4 (critical) — attach an admin policy to a principal. iam:AttachUserPolicy /
iam:UpdateAssumeRolePolicy (+ sts:AssumeRole = critical): rewrite a privileged role's trust to trust this principal, then assume it.
- E5 (critical/high) — rewrite a role's trust policy, then assume it.
CreateLoginProfile / AddUserToGroup etc.: a sideways takeover that never touches the caller's own policies, so a review of this principal's permissions looks clean.
- E6 (high) — mint credentials for another identity. iam:CreateAccessKey /
What is NOT an escalation (do not flag these): A standalone sts:AssumeRole grant is not an in-account privilege escalation on its own. Escalation-via-assume is E5 and requires iam:UpdateAssumeRolePolicy to rewrite a role's trust so it trusts this principal. Without that rewrite capability, an sts:AssumeRole grant only does anything if the target role already trusts this principal back, and even then it is lateral movement to whatever that role can do, not self-escalation, scored as the boundary question of "is the target more privileged." A cross-account sts:AssumeRole narrowed by an aws:PrincipalOrgID / sts:ExternalId condition, with no UpdateAssumeRolePolicy to relax either side, is inert: report no escalation. Do not debate whether the condition is "satisfiable" or call the path "live" — that is the wrong frame and produces a false positive. The grant is unused and removable; the correct recommendation is "no fix needed (optionally remove the inert grant)", never "harden / pin / monitor it."
3. Classify the wildcard grants (W1-W5)
Each Allow statement gets at most one wildcard finding (W1 > W3 > W2 > W4 > W5):
privesc combo is a subset of this one grant, so report it as the single headline rather than enumerating a dozen restatements.
- W1 (critical) — Action '' on Resource ''. Full administrator by value. Every
"allow these few." It reads narrow and is one of the broadest possible shapes. The safe form is Deny + NotAction.
- W3 (high) — Allow + NotAction. This is "allow everything except a short list," not
More skills from anyshift-io/sre-skills
- Akubectl-investigatorInvestigate a live or recent incident in a Kubernetes cluster. Anchor the window, bisect the change surface (rollouts, ConfigMaps/Secrets, RBAC, HPA/cluster changes, CronJobs), classify against four reference failure paths (OOM, DNS, cascading-failure, deploy-correlator), confirm the hypothesis with three independent signals, quantify blast radius, and propose mitigation before root cause. Use whenever an agent is asked "what is breaking in the cluster right now", "why did this pod/Deployment just page", "did the rollout cause Z", or to triage an active Kubernetes incident. Vendor-neutral by default (works with kubectl, kube-state-metrics, and whatever telemetry you have); an opt-in Anyshift integration is documented separately.
- As3-estate-calibration-auditorAudit an estate of AWS S3 buckets for the one bucket that is genuinely publicly or cross-account exposed, without over-flagging the many buckets that READ as exposed but are neutralised. Resolves each bucket's EFFECTIVE verdict by composing four layers (Block Public Access x bucket policy x bucket ACL x access points), never one layer alone, then rolls the per-bucket verdicts up into an estate verdict. Its discipline is symmetric: BPA (RestrictPublicBuckets / BlockPublicPolicy) neutralises a Principal '*' policy but NOT a cross-account grant; IgnorePublicAcls kills a public-group ACL grant but NOT a cross-account canonical-user grant; a narrowing Condition (org id, ExternalId, SourceIp, access-point delegation) scopes a Principal '*' so it is not public. On a needle estate it names the ONE live bucket as the primary finding; on a clean estate it reports NO live exposure and does not manufacture findings. Then it states what the bucket configs alone cannot answer (per-object ACLs, CloudFront/CDN fronting, the trusted principals' identity policies, account-level BPA dependency, data sensitivity). Use when asked to review an S3 bucket fleet for public exposure, cross-account access, or whether the estate is clean. Vendor-neutral; runs offline against describe-bucket / get-bucket-policy / get-bucket-acl / list-access-points JSON with no Anyshift account.
- Asg-deceptive-reachability-auditorAudit a fleet of AWS security groups for the multi-hop lateral-movement path that no single ingress rule reveals. Builds a directed reachability graph from the SG-to-SG references (an ingress rule on SG B naming SG A means a host in A can reach B), adds an internet edge for every 0.0.0.0/0 rule, then composes those edges into the transitive closure from a named entry point (the internet, or a compromised host). Reports the shortest reachable path to the crown-jewel tier, the blast radius, and any pivot/hub SG that bridges otherwise-isolated regions, each ranked by severity with a fix. Its discipline is symmetric: on a segmented or orphaned fleet where the chain does NOT reach the crown jewel, it reports clean and names the boundary instead of fabricating a path. Then it states what the SG graph alone cannot answer (live host membership, route tables, NACLs, app-layer auth). Use when asked to review a security-group fleet for lateral movement, blast radius, or whether the internet can reach a sensitive tier. Vendor-neutral; runs offline against describe-security-groups + describe-instances JSON with no Anyshift account.
- Asqs-queue-auditorAudit a single AWS SQS queue's configuration for the misconfigurations that silently drop or re-deliver messages while every attribute reads as fine. Parses the GetQueueAttributes output (and the referenced dead-letter queue), checks the redrive path (DLQ present, maxReceiveCount band, DLQ-vs-source retention ordering), the message lifecycle (poison messages aging out before they reach the DLQ, default visibility timeout, short retention), and exposure (open resource policy, encryption at rest, FIFO dedup contract). Reports findings with severity and a recommendation, then names the boundary: the questions a single queue's config cannot answer (consumer processing time, live behaviour, the IAM union, the producers and consumers on either side). Use when asked to review, harden, or sanity-check an SQS queue, or to explain why messages are going missing. Vendor-neutral; runs offline against the queue attributes with no Anyshift account.