How to secure AI agent access to AWS without permanent keys

Choose the simplest safe access pattern, trace every run and prove that disabling your agent actually stops its AWS access.

31 October 2026 · 10 min read · Cloudline Consulting

Three engineers reviewing a laptop and a paper checklist in a naturally lit office.

Your AWS-connected agent works. Now someone asks who can disable it, which customer data it can reach, and whether yesterday’s credentials will still work after you switch it off. Those are production access questions, not prompting questions. Use a simple lifecycle framework—issue, constrain, attribute, deny—to choose the right access pattern and build a shutdown procedure you can demonstrate. Start with one read-only S3 task, then prove both that new sessions stop and that previously issued credentials lose access.

In this article
  1. Choose the simplest access pattern you can safely disable
  2. Issue credentials that can only read this task’s files
  3. Trace an AWS request back to one authorised run
  4. Prove the boundary before allowing autonomous writes
  5. Run a shutdown drill against credentials already issued
  6. Budget for evidence and ownership rather than unnecessary machinery

Choose the simplest access pattern you can safely disable

Two engineers drawing boxes and arrows on an office whiteboard.
Choose an access boundary your team can operate and shut down.

Start with the deployment boundary, not a credential broker. The simplest safe design keeps AI agent IAM permissions narrow and gives someone a tested way to stop access.

Use Issue → Constrain → Attribute → Deny as the access framework. Assign a named human owner at every stage, even if one engineer owns all four:

  • Issue: the platform owner decides which authenticated workloads can obtain credentials.
  • Constrain: the data owner approves permitted actions and resource boundaries.
  • Attribute: the service owner ensures requests map to an authorised run.
  • Deny: the on-call owner maintains and tests the shutdown procedure.

These are implementation choices, not competing credential types. All three can use temporary credentials. Add machinery when the trust boundary demands it, not simply because an agent exists.

PatternUse it whenBoundary and trade-off
Dedicated execution roleOne internal, read-only agent; one data boundary.An ECS task role or Lambda execution role has fixed, narrow permissions. Application-level run attribution is acceptable.
Per-task STSEach run needs distinct scope and attribution.Trusted orchestration assumes a target role separately for each run, applying task-specific scope and run identity.
Credential brokerUntrusted third-party runtimes, multiple tenants or centrally enforced approvals.An authenticated service outside the agent validates identity, tenant, approved tool and task. It issues scoped STS credentials or executes the operation itself.

AWS distinguishes agent identities from human identities and recommends attributable access. The architecture is: authenticated requester → trusted orchestrator or broker → STS → narrowly scoped role → approved AWS resource. CloudTrail records the access path; enable relevant data events for operations such as S3 object reads.

Deleting a worker is not a credential-revocation procedure. Issued credentials may remain usable elsewhere until expiry unless an effective deny blocks access.

Choosing the deployment boundary is an architectural decision; Cloudline’s AWS architecture and cloud security support covers that responsibility alongside platform design.

Temporary credentials are a lease, not a kill switch.

Share this quote

Issue credentials that can only read this task’s files

An engineer typing on a laptop at an office desk.
Keep the credential issuer separate from the agent’s reasoning loop. Photo: Nicola since 1972 (BY 2.0)

Separate three controls: who can assume the role, what the role permits, and what this task permits. AI agent least privilege needs all three.

Use account 111122223333, region eu-west-2, and a uniquely named sandbox bucket represented by BUCKET. It contains existing SSE-S3 objects support/run-001/brief.txt and support/run-002/brief.txt. Replace BUCKET in both policies with its actual name. Do not add bucket policies granting access directly to session principals.

Keep trust separate from permissions

Set this trust policy on AgentSupportRead. It trusts AgentIssuer, with explicit permission to set SourceIdentity, which AWS records for attribution.

{
  "Version":"2012-10-17",
  "Statement":[{
    "Effect":"Allow",
    "Principal":{"AWS":"arn:aws:iam::111122223333:role/AgentIssuer"},
    "Action":["sts:AssumeRole","sts:SetSourceIdentity"],
    "Condition":{
      "StringLike":{
        "sts:RoleSessionName":"agent-*",
        "sts:SourceIdentity":"run-*"
      }
    }
  }]
}

Add this identity-policy statement to AgentIssuer:

{
  "Effect":"Allow",
  "Action":["sts:AssumeRole","sts:SetSourceIdentity"],
  "Resource":"arn:aws:iam::111122223333:role/AgentSupportRead"
}

Give AgentSupportRead only these base permissions:

{
  "Version":"2012-10-17",
  "Statement":[{
    "Effect":"Allow",
    "Action":"s3:GetObject",
    "Resource":"arn:aws:s3:::BUCKET/support/*"
  }]
}

Save the task restriction as task-session.json:

{
  "Version":"2012-10-17",
  "Statement":[{
    "Effect":"Allow",
    "Action":"s3:GetObject",
    "Resource":"arn:aws:s3:::BUCKET/support/run-001/*"
  }]
}

ListBucket is deliberately unnecessary: this tool reads known keys. Session permissions normally intersect with role permissions; they do not add permissions.

Naming conditions do not require a session policy. A model-controlled issuer could omit it and read both runs. Trusted orchestration must construct scope from authorised task metadata, not model-supplied paths.

Pin one credential set for testing

Run this Bash snippet as AgentIssuer, with AWS CLI and jq installed. AssumeRole permits a minimum requested duration of 900 seconds; role chaining has a one-hour maximum.

set +x
umask 077
session_file=$(mktemp) || exit 1
trap 'rm -f "$session_file"' EXIT

aws sts assume-role \
  --region eu-west-2 \
  --role-arn arn:aws:iam::111122223333:role/AgentSupportRead \
  --role-session-name agent-run-001 \
  --source-identity run-001 \
  --duration-seconds 900 \
  --policy file://task-session.json \
  >"$session_file" || exit 1

agent_aws() (
  set +x
  values=$(jq -er \
    '.Credentials | [.AccessKeyId,.SecretAccessKey,.SessionToken] | @tsv' \
    "$session_file") || exit 1
  IFS=$'\t' read -r \
    AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN \
    <<<"$values"
  export AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN
  unset AWS_PROFILE AWS_DEFAULT_PROFILE
  export AWS_CONFIG_FILE=/dev/null AWS_SHARED_CREDENTIALS_FILE=/dev/null
  export AWS_PAGER=""
  aws --region eu-west-2 "$@"
)

The helper loads exactly that file’s three credential values in a subshell. Automatic refresh cannot silently replace them during later tests. Keep tracing disabled; never print the credential file.

In production, keep credentials inside the tool runner, outside prompts and model-visible files. Bring existing roles under reviewed Terraform ownership, rather than maintaining these boundaries through console edits.

Trace an AWS request back to one authorised run

An engineer seated at a desk looking at two monitors.
An incident investigation needs a run owner, not just a shared role name. Photo: jurvetson (BY 2.0)

Have the trusted issuer generate run-001, not the agent. Retain a mapping to the authenticated requester, agent version, tenant, approval ID, requested scope, issued access-key ID and expiration. Never record secret access keys or session tokens.

Set SourceIdentity to that run ID when issuing AWS agent temporary credentials. It is an attribution value, not proof of human identity. Trusted code must bind it to the authenticated request; an agent-supplied label proves nothing.

CloudTrail gives you two records to join operationally: AssumeRole captures the issuance request and calling identity. Subsequent resource calls carry the assumed-role identity and session context, including source identity.

Use Athena over a CloudTrail S3 trail. Start with AWS’s documented cloudtrail_logs table definition, adding sourceidentity STRING inside useridentity.sessioncontext. Retain its region/year/month/day partitions.

Register the fixture’s 2026/10/31 partitions in Glue for every relevant STS region and resource region. Include us-east-1 where global-endpoint STS events land. First, find issuance:

SELECT eventtime, useridentity.arn AS issuer,
       json_extract_scalar(requestparameters, '$.roleSessionName') AS session_name,
       json_extract_scalar(requestparameters, '$.sourceIdentity') AS run_id
FROM cloudtrail_logs
WHERE year='2026' AND month='10' AND day='31'
  AND eventsource='sts.amazonaws.com'
  AND eventname='AssumeRole'
  AND json_extract_scalar(requestparameters, '$.sourceIdentity')='run-001';

Then trace resource calls, restricted to the fixture’s target role:

SELECT eventtime, eventsource, eventname, useridentity.arn,
       useridentity.accesskeyid, errorcode
FROM cloudtrail_logs
WHERE year='2026' AND month='10' AND day='31'
  AND useridentity.sessioncontext.sourceidentity='run-001'
  AND useridentity.sessioncontext.sessionissuer.arn=
      'arn:aws:iam::123456789012:role/AgentTaskReader'
ORDER BY eventtime;

Example output — illustrative, not an executed query:

eventtime:    2026-10-31T10:02:14Z
eventsource:  s3.amazonaws.com
eventname:    GetObject
arn:          arn:aws:sts::123456789012:assumed-role/AgentTaskReader/run-001
accesskeyid:  ASIAIOSFODNN7EXAMPLE
errorcode:    NULL

Enable S3 object data events for the fixture prefix. Management-event history alone does not supply this object-access audit. Give the mapping and trail a named owner through monitoring and incident management; attribution is useless if nobody can retrieve it during an incident.

Prove the boundary before allowing autonomous writes

An engineer writing a checklist in a notebook beside a laptop.
A useful permission test proves what the agent cannot do. Photo: connors934 (BY 2.0)

Test the saved run-001 AWS agent temporary credentials against disposable, pre-created fixtures. An administrator must create both briefs, ./harmless-fixture.txt and a real sandbox escape-test role that does not trust this agent. Set ESCAPE_ROLE_ARN to that role’s ARN.

Run every table row in order, in the same harness shell. The positive control must succeed; every negative probe must return AccessDenied. A missing object is not evidence of isolation.

set -eu
export AWS_PAGER=""
LOG=$(mktemp)
trap 'rm -f "$LOG"' EXIT

must_deny() {
  if "$@" >"$LOG" 2>&1; then
    printf 'FAIL: unexpected success\n' >&2
    exit 1
  fi
  if ! grep -Fq 'An error occurred (AccessDenied)' "$LOG"; then
    cat "$LOG" >&2
    exit 1
  fi
}
ProbeCommandRequired result
Authorised readagent_aws s3api get-object --bucket "$BUCKET" --key support/run-001/brief.txt /tmp/agent-brief.txtSuccess
Other run’s existing filemust_deny agent_aws s3api get-object --bucket "$BUCKET" --key support/run-002/brief.txt /tmp/other-brief.txtAccessDenied, not NoSuchKey
Write within own prefixmust_deny agent_aws s3api put-object --bucket "$BUCKET" --key support/run-001/negative-test.txt --body ./harmless-fixture.txtAccessDenied
IAM discoverymust_deny agent_aws iam list-rolesAccessDenied
Role escapemust_deny agent_aws sts assume-role --role-arn "$ESCAPE_ROLE_ARN" --role-session-name escape-testAccessDenied

Clean up fixtures and any unexpected writes using a separate administrator identity. Never run destructive probes against production. These tests exercise this fixture’s intended boundary, not a universal security certification. Inspect resource-based policies too: their grants can alter effective permissions. The session policy is not the only control.

MCP AWS security needs an IAM boundary. Tool allowlists help, but the runner’s IAM rights remain decisive when shell access or another AWS client bypasses MCP. AWS’s 14 April 2026 guidance explicitly recognises this bypass.

Hypothetically, an agent selects production instead of staging. A tool allowlist cannot correct an authorised but wrongly targeted request. Keep IAM or trust changes, secrets access, destructive data operations, production networking and unbounded infrastructure creation outside autonomous execution.

For a narrow, reversible write, require a deterministic wrapper, fixed target, bounded retries, idempotency and rollback. Approval must bind the exact action and target outside the model; a chat message saying “approved” is not that control. Apply staged rollout and rollback thinking before widening access.

Run a shutdown drill against credentials already issued

A technician standing beside server racks in a data centre.
Prove shutdown at the AWS resource, not at the agent’s user interface. Photo: USDAgov (PDM 1.0)

Use an isolated AgentSupportRead test role and two terminals: saved agent credentials in A, a separate incident administrator identity in B. Never let the test helper refresh credentials. Record the credential expiration, UTC timestamps and original successful GetObject, including bucket and key.

Production containment must block issuance and deny current access together. The two-phase sequence below deliberately separates them to demonstrate the trap safely.

Compact shutdown runbook

  1. Freeze terminal A. Load the saved STS response from saved-session.json into environment credentials. These do not automatically refresh. Use the same object throughout.

    export AWS_ACCESS_KEY_ID="$(jq -r '.Credentials.AccessKeyId' saved-session.json)"
    export AWS_SECRET_ACCESS_KEY="$(jq -r '.Credentials.SecretAccessKey' saved-session.json)"
    export AWS_SESSION_TOKEN="$(jq -r '.Credentials.SessionToken' saved-session.json)"
    export AWS_EC2_METADATA_DISABLED=true
    jq -r '.Credentials.Expiration' saved-session.json
    
    date -u
    aws s3api get-object --bucket "$BUCKET" --key "$KEY" /tmp/agent-drill-object
  2. Phase A: block issuance. Stop the worker and broker issuance. Save this as blocked-trust.json, then apply it in terminal B using the incident administrator identity.

    {
      "Version": "2012-10-17",
      "Statement": [{
        "Effect": "Deny",
        "Principal": "*",
        "Action": "sts:AssumeRole"
      }]
    }
    aws iam update-assume-role-policy --role-name AgentSupportRead --policy-document file://blocked-trust.json

    Retry the issuer’s original AssumeRole request until denied. Repeat A’s successful GetObject. Changing trust does not itself remove an already issued session’s permissions.

  3. Phase B: deny existing use. In B, attach the deny policy:

    aws iam attach-role-policy --role-name AgentSupportRead --policy-arn arn:aws:iam::aws:policy/AWSDenyAll

    Record the application time. In A, poll the same GetObject every five seconds with the exact saved credentials until AccessDenied. Record elapsed time; expiration or another error is not proof.

Updated permissions apply to existing sessions, but policy propagation can take a few minutes. Do not promise instantaneous shutdown. Do not use GetCallerIdentity as the criterion: it can return identity despite an explicit deny.

Revoke older sessions on a reusable role

As an alternative to AWSDenyAll, save this as revoke-before.json. Replace the illustrative timestamp with the drill’s recorded UTC cutoff.

{"Version":"2012-10-17","Statement":[{"Effect":"Deny","Action":"*","Resource":"*","Condition":{"DateLessThan":{"aws:TokenIssueTime":"2026-10-31T12:00:00Z"}}}]}
aws iam put-role-policy --role-name AgentSupportRead --policy-name AWSRevokeOlderSessions --policy-document file://revoke-before.json

This denies every matching session of that role issued before the cutoff, not future sessions. Keep issuance blocked. Retain the deny until affected credentials expire; review restoration as a separate change.

To review your agent shutdown procedure, include offboarding: disable triggers and OAuth refresh paths, remove legacy static keys, and preserve audit evidence. Reconcile out-of-band IAM changes with Terraform; check downstream assumed roles and delegated jobs separately.

Your agent is not off until its old credentials fail.

Share this quote

Budget for evidence and ownership rather than unnecessary machinery

A calculator and open notebook beside a laptop on a desk.
Compare implementation effort separately from the cost of audit evidence. Photo: Camera Eye Photography (BY 2.0)

Budget for evidence and a named owner before building a credential broker. Tight AI agent IAM permissions, searchable run records and a tested shutdown path matter more than another service to maintain.

AWS charges: price the evidence you need

The examples below use AWS published USD rates, accessed 31 October 2026. Volumes are explicit workload assumptions, not typical agent traffic.

Logging optionRate and illustrative cost
Management-event historyFree.
First management-event trail copyNo CloudTrail delivery charge; S3 charges remain.
Selected S3 data events$0.10 per 100,000 events. Assumed one million monthly: $1.00 delivery.
Trail plus Athena SQL$1.00 delivery + $0.50 for an assumed 0.1 TB scan at $5/TB = $1.50.
CloudTrail Lake, one-year extendable option$0.75/GB ingestion; $0.005/GB scanned. Assumed 10 GB ingested + 100 GB scanned = $8.00, including the first year’s retention.

These are not all-in bills. Exclusions: VAT, foreign exchange, S3 storage and requests, KMS, Glue where applicable, additional copies, broker runtime and broader observability. Scan volume matters: querying every object repeatedly can dwarf event-delivery costs.

Engineering effort: buy the smallest defensible boundary

This explicitly hypothetical budget assumes £800 per engineer-day, existing workload identity and logging infrastructure, one AWS account and one tool. These are neither Cloudline prices nor measured benchmarks.

Access patternIllustrative effortBudget
Dedicated role0.5–1 day£400–£800
Per-task sessions1–2 days£800–£1,600
Small broker proof of concept3–5 days£2,400–£4,000

Estimates exclude production hardening, availability engineering and ongoing support. Assign ownership for policy changes, evidence retention and shutdown drills; a broker creates another production dependency.

For a second pair of eyes, talk to a cloud engineer about an agent-access security review. We can review issuer trust, effective permissions, per-run evidence, negative tests and observed shutdown behaviour.

Final thoughts

Choose the smallest access mechanism that fits your task, then make its lifecycle observable. Your release gate should include a successful authorised request, failed out-of-scope requests, a traceable run owner and a shutdown drill using credentials issued before the stop. Keep high-impact writes behind independent approval. Cloudline Consulting can help review the trust boundary, permission policies and offboarding procedure, so your team has evidence that the agent’s access ends when you intend it to.

Frequently asked questions

What IAM permissions should an AI agent have in AWS?

Give the agent a separate role containing only the actions and resources its tools require. Start with a fixed read-only task rather than a broad managed policy. Separate human and agent identities, then test both permitted and denied requests against the real resource boundary. Broader access needs a specific operational justification.

Can an AI agent use temporary AWS credentials?

Yes. A workload role can supply temporary credentials, or trusted orchestration can request a separate STS AssumeRole session for each task. An AssumeRole request can specify 900 seconds as its minimum duration. Keep credential issuance outside model-controlled code and pass credentials only to the tool runner that needs them.

Does changing an IAM role trust policy revoke existing sessions?

No. Blocking the trust relationship prevents new assumptions after the change propagates; it does not by itself deny requests signed with existing session credentials. Deny the role’s permissions or apply a timestamp-based revocation policy as well. Confirm the outcome by repeating a resource request that succeeded before shutdown.

Does an MCP server make AWS access secure?

Not by itself. MCP can expose narrow tools, but the server still needs an appropriately restricted AWS identity. A shell tool or another AWS client may bypass MCP entirely. Restrict the runner’s effective permissions, isolate credential issuance and test direct AWS requests as well as the advertised tool interface.

How can I see what an AI agent did in AWS?

Record a trusted run identifier when issuing the session and retain its authenticated owner in your application audit record. Query CloudTrail for the issuance event and subsequent requests. For S3 object reads and writes, enable the relevant data events; management-event history alone is not sufficient evidence of object access.

Sources and further reading

Found this useful? Share it or cite it: