Pentest Copilot's agents ran 70 supported AWS attack-path checks from an EC2 role. We analyzed each run from source denial through proof, rollback, and retest.

An EC2 role can look dangerous on paper and still fail at runtime. An IAM permission alone is not a path. Reporting it as one can send defenders after access that never worked.
Pentest Copilot is organized into assessment modules. We used its Internal Assessment module for these cloud security checks.
Its agents began with the role attached to an EC2 instance.
Before each run, we approved one AWS account and its regions. We also set the safety rules. The agents stayed inside those boundaries. When a resource met a check's requirements, they ran it without waiting for an operator to choose each action.
For example, when the agents found iam:PassRole, they did not count it immediately. The permission became a path only after they started a workload with the passed role. That workload then completed an action denied to the source role.
We reviewed each complete run. Seventy AWS checks met the source-to-consequence evidence rule. We grouped them into 23 categories.
| Field | Result |
|---|---|
| Research question | When does control of one EC2 workload become a verified AWS attack path rather than a list of IAM permissions? |
| Starting condition | Code execution on an EC2 instance with access to its attached role |
| Unit of analysis | One identity-to-resource-to-runtime transition |
| Evidence threshold | The specific target accepts an action that the source role could not complete |
| Recovery threshold | Reversible state returns to the recorded baseline |
| Coverage | 70 attack paths across 23 vulnerability categories |
Because an operator session can carry unrelated authority, the agents measure identity from the instance itself. They use an IMDSv2 token to read the instance identity document. A separate STS call resolves the active role.
Metadata returns the instance account and region. STS returns the session account and active role. If the accounts differ, the agents stop. They also stop when the region is outside the approved scope. Either mismatch can mean the session belongs to an administrator or another workload.
| Boundary | Required observation | Failure meaning |
|---|---|---|
| Machine | IMDSv2 responds from the instance metadata endpoint | Workload context is not established |
| Account | Instance identity and STS caller resolve to the same account | Active session does not describe this machine |
| Region | Detected region is approved | Regional enumeration stops |
| Principal | STS returns the expected role session | Later actions can be attributed to the workload |
The same scope applies to large collections. Before listing objects or versions, the agents resolve the S3 bucket's region. They process every returned page until the configured collection ceiling is reached; a capped collection is recorded as partial rather than empty.
An inventory records objects. An attack-path model records how authority moves between them. Roles are linked to their trust policies, and instance profiles are linked to EC2 instances. Runtime roles are linked to functions, while event sources are linked to queues and functions. EKS access entries remain bound to specific clusters. The agents record a direct secret read as exposure. For example, a secret may contain a database password. The responsible agent tries it only against the named database. If the login succeeds, we record a separate edge from the secret to that consumer.
AWS evaluates requests across identity policies, resource policies, permissions boundaries, session policies, and organization controls. An allow in one policy is not the final decision; explicit denies and intersecting boundaries still apply.
A denied list operation is not an empty service. The agents distinguish an empty response from an authorization denial, incomplete pagination, or an unexpected request failure. These states prevent denied APIs from producing a clean-looking but false inventory.
The direct role path starts with an existing trust relationship or permission to change it. The agent records the trust document, assumes the target role, confirms the new identity with STS, and repeats a read-only action that the source role was denied.
| Step | Evidence |
|---|---|
| Baseline | Source role is denied the selected action |
| Transition | Existing trust or one temporary statement permits AssumeRole |
| Identity | STS resolves the exact destination role session |
| Consequence | Destination role completes the selected action |
| Recovery | Original trust document is restored exactly |
If the target role can be assumed and used without an approval step, MFA challenge, or recorded justification, the agents report that uncontrolled elevation as a separate workflow weakness.
iam:PassRole lets an AWS service receive a role; it does not create a caller session. The permission becomes useful only when the same principal can launch or modify a workload that accepts the stronger role.
The agents first tested EC2 as the receiving service. They then repeated the same proof through the other three supported service routes. Across all four routes, the destination had to start as the passed role and complete an action denied to the source.
Twelve escalation paths change AWS authorization directly. Some attach managed or inline policies. Others create policy versions or privileged group memberships. The remaining paths add authentication material or write an identity policy. Together, these mechanisms matter only when the target's effective permissions change. We therefore counted that permission change, not the policy name.
The agents did not return authentication material. The retained record identifies the affected principal, authorization change, accepted action, and restoration result.
| Candidate | Weak evidence | Required evidence |
|---|---|---|
| Secrets Manager or Parameter Store | API returned a value | Exact version is identified and its contents are withheld; consumer acceptance is also required when the claimed impact is access to another service |
| Systems Manager | SendCommand returned an identifier | Intended instance returns OS identity and a unique marker |
| EC2 startup execution | User data was changed | Intended boot path returns the marker and baseline is restored |
| EKS access entry | Access object was created | Selected cluster accepts administrator behavior only during the temporary mapping |
| Lambda event path | Trigger exists | Intended function processes the marker as its configured runtime role |
| Network control | Rule can be edited | Exact route becomes reachable and then returns to baseline |
| KMS impact | Key can be disabled | Non-sensitive canary becomes unavailable and decryptability returns |
These observations separate control-plane acceptance from functional impact. An API response can nominate a path, but it cannot finish one.
Across the AWS validation runs we reviewed, the agents exercised 70 source-to-consequence checks; they did not count sensitive API permissions as findings. The following families show how those checks reached different AWS boundaries.
A permissive trust policy can make a role in another account directly assumable. The useful question is not whether the trust document contains a wildcard or omits an external identifier. It is whether the workload can cross the account boundary and gain an authority it did not already possess.
The agent first calls one read-only action as the source role and requires a denial. It then assumes the destination role for a short session, confirms the destination account and role name through STS, and repeats the same action. The path is established only when the target session succeeds. Temporary credentials remain in memory and expire naturally.
This negative-then-positive comparison avoids two common false positives. A syntactically broad trust policy may still be blocked by organization controls or the caller's own policy. Conversely, a successful role assumption does not demonstrate escalation when the source could already perform the selected action.
Tags can participate in IAM conditions. A role that appears unable to reach an administrative policy may become eligible after a caller changes one of its tags. The resulting tag-controlled escalation route is easy to miss when policies and tags are reviewed separately.
The agent records the role's existing tags, changes one key used by the IAM condition, and repeats the previously denied action. Cleanup removes the temporary value or restores the exact previous value. A path is rejected if the caller cannot list, set, and remove the tag, or if the policy decision does not change after the mutation.
The defensive implication is broader than restricting policy writes. Any principal that can edit an attribute used by authorization effectively controls part of the policy decision.
Role passing becomes an attack path when the same caller can control a service that consumes the role. The agents exercised this conjunction across EC2, Lambda, CloudFormation, and managed processing workloads.
A controlled EC2 startup action reports the instance role and operating-system identity. Lambda invocation instead reports the function's assumed-role identity. A small CloudFormation stack performs a protected action through its service role. Because every route begins with a source denial and ends with an allowed runtime action, the result demonstrates a real privilege delta.
A report that lists only iam:PassRole is therefore incomplete. The exploitable edge is:
source role -> pass destination role -> control consuming service -> execute as destination role
Breaking either of the middle edges stops the path.
EKS has two important administration routes: access entries and the legacy aws-auth mapping. Both can turn cloud IAM authority into Kubernetes cluster administration, but only if the selected cluster uses the compatible authentication mode and accepts the exact principal.
The agent describes the intended cluster before the change, creates a temporary mapping for that cluster and principal, associates administrative access, and waits for AWS to report the new state. It then proves Kubernetes authority against that cluster. Rollback is ownership-bound: the temporary entry is removed only when its cluster, principal, policy, and unique ownership marker all still match the state created by the agent.
That last rule matters in shared environments. If another administrator changes the entry while the check is running, blind cleanup could delete legitimate state. The safe result is to stop and report drift.
Code-update permission on a function is valuable because the function may already run as a stronger role. Before changing the function, the agent records its identity, region, execution role, package digest, and revision. It then deploys a reproducible temporary package whose digest is known in advance, using the recorded revision to prevent overwriting a newer deployment.
Invocation must return a one-time marker, the expected account, and the stronger runtime role. It also performs one read-only follow-on action to show that the execution role is usable, not merely named in configuration. No credential value is returned.
Restoration is conditional. The original package is redeployed only if the current digest and revision still match the temporary package. The restored function is invoked again and must return its baseline marker. If the function changed independently, cleanup stops rather than overwriting the new revision.
A direct secret read proves exposure. The agents retain the version and a one-way digest rather than returning the value. If the claimed impact is access to another service, they also require that named service to accept the recovered authentication material.
For a queue message, the agent records a digest without acknowledging or deleting the message. For an event path, a unique value must travel through the configured source and reach the intended Lambda function. These checks distinguish data access from a configuration that merely suggests access.
A candidate does not become a finding when any of these conditions fail:
| Failure point | Result |
|---|---|
| Source baseline unexpectedly succeeds | No privilege delta was shown |
| Runtime resolves to another role, account, region, cluster, or function | Target binding failed |
| Control-plane change succeeds but the marker never executes | Configuration authority only |
| API denies enumeration | Visibility is unknown, not empty |
| Resource changes after the baseline | Mutation or rollback stops on drift |
| Original state cannot be verified after cleanup | Recovery is incomplete and must be reported |
| Proof would require returning a secret or destructive data | Use a fingerprint or approved canary instead |
These failure states keep denied, incomplete, or drifted checks out of the path count.
| Category | Paths | Measured result |
|---|---|---|
| Classic service authentication | 5 | FTP/SFTP, LDAP, MSSQL, MySQL, and SSH acceptance |
| Classic share exposure | 1 | Controlled FTP/SFTP read or write |
| Compute execution | 2 | Systems Manager or EC2 startup output from the intended machine |
| Delegation and trust | 7 | Four PassRole destinations, role assumption, trust edit, and workload-identity replacement |
| Discovery | 7 | Resource, permission, scope, secret, storage, metadata, and foothold context |
| Identity validation | 1 | AWS resolves the expected caller identity |
| Local secret discovery | 4 | Cloud identity file, token cache, kubeconfig, and SSH private key |
| Managed secrets and keys | 2 | Parameter Store and Secrets Manager reads with content withheld |
| Metadata identity access | 2 | Metadata token access and weak IMDSv1 protection |
| Permission enumeration | 1 | Effective reachable actions |
| Permission escalation | 12 | Policy attachment/write, policy version, key, profile, group, and password transitions |
| Network and exposure | 3 | Public administration, broad egress, and world-open ingress |
| Storage exposure | 3 | Bucket-policy bypass, managed share, and signed URL |
| Kubernetes and containers | 2 | EKS access entry and aws-auth administration |
| Serverless and event execution | 1 | Event-triggered Lambda execution |
| Audit and telemetry access | 3 | Audit read, telemetry change, and sensitive log values |
| Defense evasion | 4 | Log delivery, audit state, management path, and network protection |
| Impact | 1 | Customer-managed key disablement with recovery |
| Persistence | 5 | Role, user, access key, network ingress, and bootstrap path |
| OAuth token and session theft | 1 | Application-token replay against its intended endpoint |
| Cloud identity initial access | 1 | Application authentication material is accepted |
| Privileged access workflow | 1 | Direct uncontrolled temporary elevation |
| Serverless data mining | 1 | Readable function deployment package |
| Total | 70 | 23 categories |
For every mutating check, rollback is planned before the first write. The responsible agent records the account, region, resource identifier, current configuration, and concurrency value. It also records which temporary objects it expects to create and which observation must be repeated after restoration.
| Stage | AWS evidence |
|---|---|
| Baseline | Source action is denied or the original workload returns its baseline marker |
| Owned mutation | One policy, trust statement, tag, package, access entry, rule, or service state is changed |
| Functional proof | The intended destination role, runtime, cluster, service, or data boundary accepts the controlled action |
| Conditional rollback | Restoration proceeds only while the live revision, digest, policy state, and ownership marker still match the temporary change |
| Retest | The original configuration is read back, the temporary edge no longer works, and normal baseline behavior succeeds |
IAM paths restore the original policy document, managed-policy attachments, group membership, login state, tags, or trust relationship. Temporary users, keys, profiles, and policy versions are removed in dependency order. The agent then waits for IAM propagation and repeats the protected action. A rollback is incomplete until the temporary identity is denied and the original identity behaves as it did before the check.
Lambda uses the recorded package digest and revision. The temporary package is replaced only when both still identify the owned deployment. After restoration, the function must return the original marker. A revision mismatch means another deployment occurred, so automated rollback stops instead of overwriting it.
EKS restoration is bound to the selected account, region, cluster, principal, access policy, and ownership marker. The temporary access entry or mapping is removed, Kubernetes authority is tested again, and the original authentication state is re-read. Network, KMS, logging, and event paths follow the same rule: restore the exact rule or service state, then repeat the reachability, decryptability, delivery, or logging observation that originally demonstrated impact.
A mutating check is complete only after AWS matches the recorded state, the temporary access fails, and the original behavior still works.
An EC2 role should be reviewed through the services and workloads that make its permissions useful. Restricting trust, role passing, workload control, identity conditions, and secret consumers breaks the transitions that let one machine identity become another authority.
Once the AWS scope and safety rules were set, Pentest Copilot's agents carried out discovery, proof, rollback, and retesting without step-by-step operator control. We reviewed each complete flow, from the source-role baseline through the runtime consequence and final recovery check, and organized the supported results into the path model above. The published findings therefore describe executed identity-to-impact flows, not permissions that merely looked dangerous.