My agent guardrail only lived on the laptop. So I compiled it into AWS. A developer has released aegis-devops 0.3.0, an open-source guardrail that intercepts shell commands issued by AI coding agents such as Claude Code, Codex, Copilot, Cursor and Gemini CLI before they run kubectl, terraform or aws. The new version adds a compiler that translates the same signed policy into AWS Service Control Policies, closing a gap where agents could bypass the pre-execution hook by calling boto3 directly or using a stolen key, and it emits a rule-by-rule coverage report flagging rules that are only partially enforced. For the past few weeks I've been building Aegis-DevOps https://github.com/moneytool/aegis-devops , an open-source check that sits in front of the shell commands an AI coding agent runs. Before Claude Code, Codex, Copilot, Cursor or Gemini CLI runs kubectl , terraform or aws , a hook hands the command to Aegis, and Aegis blocks it if your signed policy says no. It works. It also has a hole I knew about from the start, and version 0.3.0 is my first attempt at closing it. Disclosure: I drafted this article with help from an AI assistant and reviewed it myself. The commands and output below are from running aegis-devops 0.3.0 against its example policy. A pre-execution hook sees the command string the agent asks its shell tool to run. It doesn't see: boto3 directly; In all three cases the destructive call reaches the AWS API without ever looking like aws s3 rb . Blocking on the client is the right first layer, because it's fast and it can explain itself to the model. It isn't the last layer. AWS already has a control that sits above everything an identity inside an account can do: a Service Control Policy . An SCP attached to an account in an AWS Organization caps what any role in that account may do, and an agent with IAM rights inside the account can't detach it. So 0.3.0 adds a compiler. It takes the same policy the hook enforces and writes it out as SCPs: bash $ aegis compile aws --account 111122223333 --out build/aegis-aws aegis: wrote 1 SCP s for account 111122223333 to build/aegis-aws exact=2, not-applicable=27, partial=1 aegis: agents.yaml is report-only: the SCPs are under report-only/ and are not for attaching; review coverage.md, then set enforcement: enforce The rule "never delete an S3 bucket or an RDS database" becomes an explicit Deny : { "Effect": "Deny", "Action": "rds:DeleteDBInstance", "rds:DeleteDBCluster", "rds:DeleteDBSnapshot", "rds:DeleteDBClusterSnapshot", "s3:DeleteBucket", "s3:DeleteObject", "s3:DeleteObjectVersion" , "Resource": " " , "Condition": { "ArnNotLike": { "aws:PrincipalArn": "arn:aws:iam::111122223333:role/BreakGlass", "arn:aws:iam::111122223333:role/Deploy" }, "StringNotEqualsIfExists": { "aws:SourceIdentity": "alice@example.com" } } } Now it doesn't matter whether the agent typed aws s3 rb , wrote a boto3 script, or found a key. If the call is made as an agent identity, AWS refuses it. An API call carries an identity, not a hint about who was typing. So the compiler needs an identity model, in a signed agents.yaml . The default is deny-by-default : you list the identities you trust people, your deploy role, CI bound to a specific workflow , plus at least one break-glass role, and everything else is treated as an agent. I went with deny-by-default because the other way round, listing the agents, fails open. Forget one, or let an agent mint a new role, and the new one isn't restricted. If the only thing between an agent and DeleteDBInstance is "is this call from the Deploy role?", the obvious move is to become the Deploy role. So every compile also adds statements that stop exactly that: sts:AssumeRole into a trusted or break-glass role, and no iam:PassRole of one; organizations:LeaveOrganization , since an account that leaves the organization sheds its SCPs. A guardrail that looks stronger than it is does more damage than no guardrail, because people stop checking. So every compile writes a coverage report, rule by rule: block-s3-bucket-delete: exact, mapping verified - enforced: s3/bucket delete s3:DeleteBucket, s3:DeleteObject, s3:DeleteObjectVersion - same effect, not covered: s3:PutLifecycleConfiguration: an expiration rule deletes every object - same effect, not covered: s3:PutBucketPolicy: a deny-all bucket policy locks everyone out block-rds-delete: partial, mapping verified - not enforced: 'rds/ ' also matches resource types the action map does not know - same effect, not covered: rds:ModifyDBInstance: BackupRetentionPeriod=0 deletes the automated backups "Same effect, not covered" is the list of other ways to reach the same outcome that the rule, as written, doesn't block. You can widen the rule, or accept the gap knowingly. A few more choices along the same lines: report-only/ and tells you not to attach them. You switch agents.yaml to enforce a signed change once you've reviewed who would be restricted. It's a preview. AWS is in the 0.3.0 release; a Kubernetes target ValidatingAdmissionPolicies scoped to agent identities is merged and waiting for the next release, and GCP and Azure are designed but not built. SCPs don't apply to an organization's management account, so agents shouldn't run there at all. Rules with a time window or a rate limit stay client-side, because an SCP can't express them. And it's a young project: I'd much rather hear "this mapping is wrong" now than after someone relies on it. pip install aegis-devops aegis init .aegis aegis compile aws --account