AI coding agent AWS permissions: the rules I enforce
AI coding agent AWS permissions done safely: session checks, approved IAM policies, secrets piped straight into GitHub, and the rules I give agents for deploys.
By Hamza Ghouri
Coding agents are now good enough to set up a deploy pipeline end to end: create an IAM user, write the policy, store the keys as GitHub secrets, push a build to Elastic Beanstalk. That is a lot of reach for a tool that runs commands on your machine with your credentials. So before I let an agent near a cloud account, I write down what it may and may not do.
This post is the set of AI coding agent AWS permissions rules I keep in the machine-readable conventions my agents read, with the reasoning behind each one. The rules are agent-neutral; they apply to any coding agent that can run a shell.
Should an AI coding agent have AWS credentials?
It can use yours, under rules. Let the agent run read commands freely, and require a human's explicit approval for anything that creates a billable resource, changes IAM, writes a secret or deletes something. Never let it go looking for other credentials when its session fails. Long-lived keys it creates should go straight into a secret store without ever being printed.
AI coding agent AWS permissions start with a session check
Every AWS task starts with four preflight commands:
aws --version
aws configure list-profiles
aws sts get-caller-identity
aws configure get region
The third one is the only real session check. Credentials can be fully configured and still be dead, with an expired SSO session or a revoked key, and only get-caller-identity actually asks AWS who you are.
If the session has expired, the agent stops and asks the human to log in. There is an explicit rule against hunting for keys in a file, an environment variable or another profile. An agent that is trying hard to finish a task will otherwise find the old admin key in a dotfile and carry on with it.
Billable and destructive commands need a human
Read commands are free to run. The two categories that always need a person:
- Anything billable. The agent never creates a billable resource without asking and naming the cost. RDS instances, Elastic Beanstalk environments, NAT gateways and load balancers all bill by the hour, and an agent retrying a failed setup can leave several behind.
- Anything destructive. The agent never runs a destructive command unprompted, "not even to clean up something the agent itself created". Cleanup feels harmless, and a mistaken resource ID makes it the most dangerous command in the session.
This matches the in-product guardrails I described in AI agent guardrails in production: reads flow, writes with consequences wait for a person.
Approve the exact IAM policy, then prove it
An agent may create the deploy IAM identity only after the human approves "the exact policy document rather than a description of it." A summary like "deploy permissions for Beanstalk" can hide "Action": "*". The JSON cannot.
The policy itself follows least privilege, as the AWS IAM security best practices recommend:
- Resources are scoped by ARN. The only wildcard resource is for the health-polling reads, because Elastic Beanstalk's describe actions do not support resource-level permissions.
- No managed admin policy and no wildcard action. "A deploy failing on a permission is telling you which one to add."
- The result is proven with
aws iam simulate-principal-policybefore anyone relies on it, so the policy is tested against the actions the deploy needs instead of reviewed by eye.
There are three ways to give a pipeline AWS access, and the conventions compare them so the human picks one on purpose. A user with an attached policy needs no workflow change. OIDC needs a workflow change and leaves no long-lived secret. A user that can only assume a role "still leaves a key lying around." My preference is OIDC, which I set up step by step in deploying to AWS from GitHub Actions with OIDC.
Keep the secret out of the transcript
aws iam create-access-key returns the secret once, in plaintext, on stdout. If the agent runs it plainly, a live credential is now in the session transcript and whatever logs the tool keeps. So the output is piped straight into gh secret set, and only the access key ID is shown, so the human can match it in the console:
aws iam create-access-key --user-name app-deploy \
--query 'AccessKey.[AccessKeyId,SecretAccessKey]' --output text |
{
read -r key_id secret
printf '%s' "$key_id" | gh secret set AWS_ACCESS_KEY_ID --repo your-org/your-app
printf '%s' "$secret" | gh secret set AWS_SECRET_ACCESS_KEY --repo your-org/your-app
echo "Stored access key $key_id"
}
The same goes for secrets the human provides. They are written as each answer arrives, never echoed back and never written to a file, and confirmed by name only. The agent asks for them one question at a time, each with a suggestion so a one-word reply is complete. The earlier version asked a numbered block of questions at once, which was "tiring to answer and half the answers arrive stale."
Always name the repository
gh secret set resolves the repository from the current directory's git remote. Run it one directory off and it writes the secret into the wrong repository without a warning. The gh secret set manual documents the --repo flag, and the rule is to use it every time:
- Ask the human which repository.
- Pass
--repoon every command. - Read back
gh repo view your-org/your-app --json viewerPermissionand stop unless it says ADMIN.
One more trap: with two GitHub accounts logged in, a repository under the other account fails with "Could not resolve to a Repository with the name ...". As my note put it, "That error reads like a typo and is not one." The fix is switching to the right account (gh auth switch), and an agent that treats it as a typo will start guessing names.
Deploys that report success early
Two Elastic Beanstalk details belong in the rules too. update-environment returns before the deploy finishes, so the agent polls describe-environments until the environment is healthy instead of reporting success on the first response. And bundles are named with the commit SHA, since a fixed build.zip overwrites the previous one and an older version cannot be redeployed. The full pipeline is in deploying Node.js to AWS Elastic Beanstalk with CI/CD.
Where I'd start
- Write the rules down where the agent reads them; I explained that setup in machine-readable engineering conventions for AI agents.
- Make
sts get-caller-identitythe first command, and stop on a dead session. - Require approval, with the cost named, for anything billable, and never allow unprompted deletes.
- Approve IAM policies as JSON, scope them by ARN, and prove them with the policy simulator.
- Pipe new keys into the secret store, pass
--repoevery time, and check for ADMIN first.