AI coding agent admin guide·verified October 21, 2026

force ai coding agents to use a scoped aws sso profile

Keep agents off admin and on-call AWS profiles. Require an approved SSO profile on every AWS, Terraform and IaC call an agent makes.

the short answer

Create a scoped IAM Identity Center permission set for agent work, give it a named profile, and enforce it at the agent’s tool boundary: deny AWS, Terraform and IaC calls that name an admin profile or no profile, and tell the agent which profile to use. Enforce it on every call; session-start events are observational on every supported harness, so they cannot gate a session.

Credential layer
An IAM Identity Center permission set and named profile for agents
Policy layer
Deny calls that use an unapproved profile or none
Where to gate
PreToolUse on every call; UserPromptSubmit where it blocks
Session start
Observational on every supported harness
Login
aws sso login --profile agent-dev

Why Profile Enforcement Is the Highest-Leverage AWS Control

A developer’s shell often has several AWS profiles configured: a sandbox, a dev role, and for admins an AdministratorAccess or on-call permission set. Agents use whichever one the environment points at, usually whatever AWS_PROFILE was last exported. Requiring a scoped profile for agent work turns every other AWS policy from “the only thing in the way” into a second layer.

Step 1: Create the Agent Profile

  1. In IAM Identity Center, create a permission set for agent work, for example read-only plus the specific write actions your developers need in dev accounts.
  2. Set a short session duration. Permission sets allow 1 to 12 hours; agent work rarely needs the maximum.
  3. Add a named profile to developers’ ~/.aws/config, for example agent-dev, via your onboarding script or MDM.
  4. Developers sign in with aws sso login --profile agent-dev before starting agent sessions.

Step 2: Enforce the Profile on Every Call

Requiring an explicit profile is stricter than inspecting the environment, and that is the point: policies receive the tool call, not the shell’s exported variables, so “whatever was exported last” in an earlier session cannot silently become an admin role. Each call is checked on its own, including calls behind sudo, env, bash -c or inside $( ), so one profiled call cannot cover an unprofiled one; an export AWS_PROFILE=… earlier in the same command counts. Calls that need no credentials, such as aws --version or terraform fmt, are let through.

import { customPolicies, allow, deny } from "failproofai";

// An AWS-credentialed tool in command position: start of a segment, inside
// $( ) or backticks, after sudo/env/time/xargs or bash -c, optionally after
// VAR=value assignments.
const AWS_CALL = /(?:^|[(`]|\s-c\s+["']?|\b(?:sudo|env|time|exec|nohup|xargs)\s+(?:-\S+\s+)*)\s*(?:[A-Z_][A-Z0-9_]*=\S+\s+)*["']?(aws|terraform|tofu|cdk|pulumi|sam|eksctl)["']?\s+([\w-]+)/g;
const NO_CREDENTIALS = /^(--version|version|fmt|validate|help|--help)$/;
const APPROVED = new Set(["agent-dev", "agent-readonly"]);
const NAMED = /(?:--profile[=\s]+|AWS_PROFILE=)([\w.-]+)/g;
const EXPORTED = /^export\s+AWS_PROFILE=([\w.-]+)/;

customPolicies.add({
  name: "agents-use-approved-aws-profile",
  description: "Every AWS, Terraform and IaC call from an agent must name an approved profile",
  match: { events: ["PreToolUse"] },
  fn: async (ctx) => {
    if (ctx.toolName !== "Bash") return allow();
    const cmd = String(ctx.toolInput?.command ?? "");
    let exported = null;
    // Check each segment on its own, so one profiled call cannot cover another.
    for (const segment of cmd.split(/\n|;|&&|\|\||\|/).map((s) => s.trim())) {
      const set = segment.match(EXPORTED);
      if (set) exported = set[1];
      const calls = [...segment.matchAll(AWS_CALL)].filter((m) => !NO_CREDENTIALS.test(m[2]));
      if (calls.length === 0) continue;
      const profiles = [...segment.matchAll(NAMED)].map((m) => m[1]);
      const bad = [...profiles, exported].find((p) => p && !APPROVED.has(p));
      if (bad) {
        return deny(`Profile "${bad}" is not approved for agent sessions. Use agent-dev, or ask a human to run this.`);
      }
      if (!exported && profiles.length < calls.length) {
        return deny("Name the AWS profile on every call: prefix it with AWS_PROFILE=agent-dev or pass --profile agent-dev.");
      }
    }
    return allow();
  },
});

Test Cases

CommandExpected
aws s3 lsdeny
aws s3 ls --profile agent-devallow
AWS_PROFILE=agent-dev terraform planallow
aws s3 ls --profile admindeny
aws --versionallow
terraform fmtallow
git commit -m 'update aws docs'allow
cd infra && AWS_PROFILE=oncall-admin terraform applydeny
sudo aws s3 lsdeny
env aws s3 lsdeny
bash -c "aws s3 ls"deny
AWS_PROFILE=agent-dev aws s3 ls; aws s3 ls s3://prod-bucketdeny
aws ec2 terminate-instances --instance-ids $(aws ec2 describe-instances --query x --output text) --profile agent-devdeny
export AWS_PROFILE=agent-dev && aws s3 ls && terraform planallow

Step 3: Why per Call and Not at Session Start

It is tempting to check once at session start and let the session run. Failproof’s harness capability matrix lists session lifecycle events as observational on every supported harness, so a session-start check cannot stop anything. The profile can also change mid-session when the agent exports a new one.

Use UserPromptSubmit, which blocks on Claude Code, Codex, Copilot CLI, Cursor, Pi, OpenClaw, Factory Droid and Devin CLI, if you want to refuse a task outright, and the per-call policy above for everything else.

GateBlocks onUse it for
SessionStartNo supported harnessRecording session-level state
UserPromptSubmitClaude Code, Codex, Copilot CLI, Cursor, Pi, OpenClaw, Factory Droid, Devin CLIRefusing a task before it starts
PreToolUseAll twelve supported harnessesEnforcing the approved profile on every call

SDK Scripts and Terraform Providers

  • A Python script that builds a boto3.Session(profile_name="admin") names the profile in the file. Extend the policy to Write and Edit content, as in the bypass guide.
  • Terraform providers can hardcode profile = "admin" in provider "aws" blocks. Grep your repos; prefer the environment so the policy is the source of truth.
  • An SDK that reads the default credential chain with no profile named falls back to the environment. The IAM permission set is the only control there.

FAQ

Can the policy read `AWS_PROFILE` from the agent’s environment?

No. Policies receive the normalized tool call, session metadata (such as the working directory) and the source harness, not the agent process environment. That is why the pattern requires the profile to be named in the command.

Will developers hate typing the profile?

The agent types it, not the developer. A clear deny reason teaches the agent the convention after one refusal.

Does this replace least privilege?

No. It routes agent work to the least-privileged profile you created. Without that profile there is nothing safe to route to.

Sources

Checked against the sources below on October 21, 2026. Harness settings, hook capabilities and cloud features change between versions.

  1. AWS docs: Set session duration for AWS accounts (IAM Identity Center)
  2. AWS docs: Configuring IAM Identity Center authentication with the AWS CLI
  3. Failproof AI docs: Custom policies (policy SDK)
  4. Failproof AI docs: Agent harnesses and enforcement capability
  5. Failproof AI docs: Test a policy
  6. Claude Code docs: Hooks reference