{"slug": "i-built-an-ai-agent-that-audits-aws-and-it-can-t-touch-anything", "title": "I Built an AI Agent That Audits AWS (And It Can't Touch Anything)", "summary": "A developer built an open-source AI agent that audits AWS accounts for security and cost issues, including publicly exposed S3 buckets, SSH open to 0.0.0.0/0, disabled GuardDuty, and idle Elastic IP charges. The agent runs on a read-only IAM identity using AWS-managed SecurityAudit and ViewOnlyAccess policies, so every write call is rejected at the permission layer before it can execute. The project, published on GitHub as aws-auditor-agent, is configured through a single JSON file and a Markdown checklist.", "body_md": "An AI agent just read my AWS account and told me a bucket was open to the internet, SSH was exposed to `0.0.0.0/0`, GuardDuty was off, and I was burning $3.65 a month on an Elastic IP attached to nothing.\n\nIt did all of that in about two minutes. And here is the part that counts: it physically could not have changed anything even if it tried.\n\nThat last sentence is the whole point of this build. Most \"give the AI access to my cloud\" ideas die on one fear: what if it deletes something, or a bad prompt tricks it into running a destructive command? We remove that fear at the permission layer, not with a polite instruction. The agent runs on a read-only IAM identity. Every write call it could imagine gets rejected by AWS before it happens.\n\nThis is a walkthrough of building that agent from scratch. It is one JSON file and one Markdown checklist. By the end you will have a working AWS auditor you can point at your own account, and you will understand every field that makes it work.\n\nThe full code is on GitHub: [github.com/simplynadaf/aws-auditor-agent](https://github.com/simplynadaf/aws-auditor-agent).\n\nYou use Kiro Crew or the Amazon Q Developer CLI. You know your way around AWS enough to have an account with a few things running. You have heard \"AI agent\" a hundred times and you want to see what one is built from, without a framework, without a vector database, without 400 lines of Python.\n\nIf you can edit a JSON file and write a checklist in Markdown, you can build this.\n\nStrip away the hype and an agent is one JSON file. The filename minus `.json` is the agent's name. The file describes a chat session: which model to use, what tools it can call, what it is allowed to do without asking you, what extra powers it plugs in, and what knowledge it carries.\n\nSix pieces. That is the entire mental model.\n\n| Piece | Field | Plain meaning | \n|---|---|---|\n| Identity | `name` ,`description` | What it is called | \n| The brain | `model` | Which LLM answers | \n| Instructions | `prompt` | Its personality and rules | \n| What it can do | `tools` | The toolbox | \n| What runs without asking | `allowedTools` | Pre-signed permission slips | \n| Extra powers | `mcpServers` | Plug in tool servers | \n| Its knowledge | `resources` | Attach skills and files | \n\nThe one distinction that trips up every beginner is `tools` versus `allowedTools`.\n\n`tools` answers \"what CAN this agent use?\" If a tool is not listed, it does not exist for the agent.\n\n`allowedTools` answers \"what runs WITHOUT stopping to ask me?\" A tool that is in `tools` but not in `allowedTools` still works, it just prompts you for approval each time it fires.\n\nToolbox versus permission slips. Keep that image and the rest is easy.\n\nBefore any config, we build the guardrail. This step is not optional and it is the reason the whole thing is trustworthy.\n\nWe attach two AWS-managed policies to the identity the agent uses:\n\n`arn:aws:iam::aws:policy/SecurityAudit`` arn:aws:iam::aws:policy/job-function/ViewOnlyAccess`\n`SecurityAudit` is the policy AWS designed for exactly this job: reading security-relevant configuration across services. `ViewOnlyAccess` fills the cost gaps, so the agent can see Elastic IPs, volumes, snapshots, and load balancers.\n\nUnderneath, both policies are `Get*`, `List*`, and `Describe*` only. There is no `Create`, no `Delete`, no `Put`, no `Modify` anywhere in them.\n\n```\naws iam create-user --user-name aws-auditor\n\naws iam attach-user-policy --user-name aws-auditor \\\n  --policy-arn arn:aws:iam::aws:policy/SecurityAudit\n\naws iam attach-user-policy --user-name aws-auditor \\\n  --policy-arn arn:aws:iam::aws:policy/job-function/ViewOnlyAccess\n\naws iam create-access-key --user-name aws-auditor\n# then paste the keys into: aws configure --profile aws-auditor\n```\n\nWhy go through this instead of just telling the model \"please do not change anything\"?\n\nBecause a prompt is a suggestion and IAM is a wall. If the model hallucinates a fix, IAM blocks it. If someone slips a \"now delete that bucket\" instruction into a file the agent reads, IAM blocks it. The blast radius is zero by construction. You are separating the act of detecting problems from the act of fixing them, which is a security best practice on its own.\n\nThe agent has read-only glasses, not a wrench.\n\nHere is the complete file. Save it as `~/.kiro/agents/aws-auditor.json`.\n\n```\n{\n  \"$schema\": \"https://raw.githubusercontent.com/aws/amazon-q-developer-cli/refs/heads/main/schemas/agent-v1.json\",\n  \"name\": \"aws-auditor\",\n  \"description\": \"Read-only agent that audits an AWS account for security risks and wasted spend, and reports prioritized, cited findings. Never changes anything.\",\n  \"model\": \"auto\",\n  \"mcpServers\": {\n    \"security\": {\n      \"command\": \"uvx\",\n      \"args\": [\"awslabs.well-architected-security-mcp-server@latest\"],\n      \"env\": { \"AWS_PROFILE\": \"default\", \"AWS_REGION\": \"us-east-1\", \"FASTMCP_LOG_LEVEL\": \"ERROR\" }\n    },\n    \"cloudtrail\": {\n      \"command\": \"uvx\",\n      \"args\": [\"awslabs.cloudtrail-mcp-server@latest\"],\n      \"env\": { \"AWS_PROFILE\": \"default\", \"AWS_REGION\": \"us-east-1\", \"FASTMCP_LOG_LEVEL\": \"ERROR\" }\n    },\n    \"pricing\": {\n      \"command\": \"uvx\",\n      \"args\": [\"awslabs.aws-pricing-mcp-server@latest\"],\n      \"env\": { \"AWS_PROFILE\": \"default\", \"AWS_REGION\": \"us-east-1\", \"FASTMCP_LOG_LEVEL\": \"ERROR\" }\n    },\n    \"awsdocs\": {\n      \"command\": \"uvx\",\n      \"args\": [\"awslabs.aws-documentation-mcp-server@latest\"],\n      \"env\": { \"FASTMCP_LOG_LEVEL\": \"ERROR\" }\n    }\n  },\n  \"tools\": [\"fs_read\", \"use_aws\", \"@security\", \"@cloudtrail\", \"@pricing\", \"@awsdocs\"],\n  \"allowedTools\": [\"fs_read\", \"use_aws\", \"@security\", \"@cloudtrail\", \"@pricing\", \"@awsdocs\"],\n  \"toolsSettings\": {\n    \"use_aws\": {\n      \"allowedServices\": [\"iam\", \"s3\", \"s3api\", \"ec2\", \"rds\", \"guardduty\", \"accessanalyzer\", \"securityhub\", \"elbv2\", \"elasticloadbalancing\", \"cloudwatch\", \"sts\"]\n    }\n  },\n  \"resources\": [\"skill://~/.kiro/skills/aws-audit/SKILL.md\"],\n  \"prompt\": \"You are AWS Auditor, a read-only cloud security and cost reviewer. You have ONLY read permissions (SecurityAudit + ViewOnlyAccess). You cannot and must not attempt to change anything, and you must not recommend that you run a fix yourself. Run the checks defined in your aws-audit skill using the available tools. Cite ONLY real resources you actually observed in tool output (real IDs, real names). NEVER invent a finding, a resource, or a number. If a check could not run, put it in the Gaps section as 'not verified' rather than implying it passed. Produce the report exactly in the format your skill defines. For every cost finding include an estimated monthly dollar impact computed from real pricing data, and state the assumption you used. Recommend fixes; never run them.\",\n  \"welcomeMessage\": \"AWS Auditor here (read-only). Point me at a region and I will report what is risky and what is wasteful, with real evidence. I cannot change anything.\"\n}\n```\n\nWalk through the fields that carry weight.\n\n`model` is set to `auto`, which lets the CLI pick the model. You can pin one (run `/model` in a session to see valid IDs) but `auto` is the sensible default.\n\n`prompt` is where the honesty rules live. Read it closely. \"Cite ONLY real resources you actually observed.\" \"NEVER invent a finding, a resource, or a number.\" \"If a check could not run, put it in the Gaps section as not verified rather than implying it passed.\" Those three lines are what separate a useful audit from a confident-sounding hallucination.\n\n`toolsSettings.use_aws.allowedServices` is a second wall on top of IAM. Even though IAM already blocks writes, this restricts the `use_aws` tool to a specific list of services it can even attempt to call. Two independent limits: the tool can only reach these services, and IAM only permits reads inside them. Defense in depth.\n\nNotice `tools` and `allowedTools` are identical here. That means the agent runs the audit end to end without stopping to ask permission for each read. That is a deliberate trade-off: convenience for a demo, and it is safe precisely because every one of those tools is read-only. If you were doing anything with write access, you would keep the risky tools out of `allowedTools` so they prompt you.\n\nThe agent config is the wiring. The skill is the brain of the audit. It is a plain Markdown file at `~/.kiro/skills/aws-audit/SKILL.md`, attached through the `resources` field, and the agent reads it on every run.\n\nThis is the file you will edit most. It holds the checks, the severity model, the framework mapping, and the exact output format.\n\nThe security checks, each backed by a specific read-only API:\n\n| Check | Read-only API | Severity | \n|---|---|---|\n| Public S3 bucket | `s3:GetPublicAccessBlock` ,`s3:GetBucketPolicyStatus` | CRITICAL | \n| Root account MFA off | `iam:GetAccountSummary` | CRITICAL | \n| Security group open to 0.0.0.0/0 on 22 / 3389 / DB | `ec2:DescribeSecurityGroups` | HIGH | \n| Unencrypted EBS or RDS | `ec2:DescribeVolumes` ,`rds:DescribeDBInstances` | HIGH | \n| IAM users without MFA | `iam:ListUsers` ,`iam:ListMFADevices` | HIGH | \n| GuardDuty disabled | `guardduty:ListDetectors` | HIGH | \n| Access Analyzer disabled | `accessanalyzer:ListAnalyzers` | MEDIUM | \n| Security Hub standards incomplete | `securityhub:GetEnabledStandards` | MEDIUM | \n\nThe cost checks, each of which must carry a real dollar figure:\n\n| Check | Read-only API | \n|---|---|\n| Unattached EBS volume | `ec2:DescribeVolumes` (state = available) | \n| Unassociated Elastic IP | `ec2:DescribeAddresses` | \n| Old or orphaned snapshot | `ec2:DescribeSnapshots` | \n| gp2 volume that should be gp3 | `ec2:DescribeVolumes` (VolumeType = gp2) | \n| Idle load balancer | `elbv2:DescribeLoadBalancers` plus target health | \n\nThe skill tells the agent how to think about severity (a traffic-light model), which frameworks to cite (Well-Architected Security Pillar for structure, CIS AWS Foundations Benchmark for authority, Trusted Advisor for the cost category), and the precise report shape: executive summary, a Top 3 list, findings grouped by Security and Cost, passing checks, and an honest gaps section.\n\nOne design choice worth calling out. The skill ships with a small \"demo scope\" block that tells the agent to only report resources tagged `demo-auditor=true`. That keeps a demo repeatable and stops it from surfacing anything real. To audit your whole account, you delete that one block. That is the entire difference between \"show me the demo\" and \"audit everything.\"\n\nThe agent needs eyes. MCP (Model Context Protocol) servers are how it sees AWS. Each server in the `mcpServers` block is a small program that exposes a set of tools, and you reference all of a server's tools with an `@` prefix, like `@pricing`.\n\nThis build uses four:\n\n`@security` (Well-Architected security MCP): checks GuardDuty, Security Hub, Access Analyzer.`@cloudtrail`: queries account activity.`@pricing`: pulls live rates from the AWS Price List API, which is how cost findings get real numbers instead of guesses.`@awsdocs`: reads AWS documentation when the agent needs to confirm a detail.\nThey run through `uvx`, so you need `uv` installed (`pip install uv`). The first time you launch the agent, `uvx` fetches each server. No manual install step, no Docker.\n\nThe general-purpose `use_aws` tool covers everything else with direct read-only API calls. Between `use_aws` and the four MCP servers, the agent can reach every check in the skill.\n\nInstall the two files, then start the agent:\n\n```\nmkdir -p ~/.kiro/skills/aws-audit ~/.kiro/agents\ncp skill/SKILL.md ~/.kiro/skills/aws-audit/SKILL.md\ncp agent/aws-auditor.json ~/.kiro/agents/aws-auditor.json\n\nkirocrew chat --agent aws-auditor\n```\n\nOn the Amazon Q Developer CLI the command is `q chat --agent aws-auditor`, and you put the agent in `~/.aws/amazonq/cli-agents/` with the `resources` entry changed from `skill://` to a `file://` path.\n\nThen ask it plainly:\n\n```\nAudit us-east-1. Actually call the tools and produce the report.\n```\n\nI ran this against five resources I created on purpose to be broken (the repo has a script for that, plus a teardown script that removes 100% of them). Here is the real output, trimmed. Nothing here is edited for effect.\n\n```\n# AWS Audit - us-east-1, 2026-09-02\n\n## Executive summary\nScoped to the 5 resources tagged demo-auditor=true, this audit found 8 findings:\n1 CRITICAL, 3 HIGH, 2 MEDIUM, and 3 cost items. The single most urgent problem is a\npublicly readable S3 bucket (demo-auditor-public-5403) that lets anyone on the\ninternet download its objects.\n\n## Top 3 - do these now\n1. Public S3 bucket demo-auditor-public-5403 - bucket policy grants s3:GetObject to\n   everyone (Principal: *). Enable Public Access Block and remove the public policy.\n2. Security group sg-055250cbcc6f3b37b - SSH port 22 is open to 0.0.0.0/0. Restrict\n   to a known admin IP or use SSM Session Manager.\n3. GuardDuty is disabled in us-east-1 - no threat detection is running.\n```\n\nThe cost section is where the pricing MCP earns its place:\n\n```\n### [COST] Unassociated Elastic IP - ~$3.65/month\n- Resource: eipalloc-000992c9956cfaaa5 (public IP 35.173.72.149, no association)\n- Estimated impact: $0.005/hr (USE1-PublicIPv4:IdleAddress, us-east-1) x 730 hrs\n  = $3.65/mo. Assumption: idle for a full month, on-demand.\n- Fix: Release the Elastic IP if not needed, or associate it with a running resource.\n```\n\nEvery resource ID is real. Every rate came from the live Price List API. The agent computed the numbers, it did not make them up.\n\nThe most convincing part was not a finding, though. It was the honesty. Three moments stood out:\n\nThe agent found a snapshot created that same day. The checklist looks for \"old\" snapshots. Instead of forcing it into the finding, the agent flagged it for completeness and refused to call a fresh snapshot old.\n\nRoot MFA was on. It reported that as a PASS. A tool that only ever finds problems just confirms its own bias. Reporting passes is how you know it looked.\n\nAnd the gaps section listed what it did not check and why: IAM per-user MFA, RDS encryption, and idle load balancers were out of scope for the demo, so it said \"not verified\" rather than implying those passed. \"Not verified\" is not \"passed.\" That line in the prompt did real work.\n\nThe whole run cost a few cents in demo resources and about two minutes of wall time.\n\nThe `SKILL.md` file is yours to own. It is a checklist, not code.\n\nAdd checks your team cares about. Each row names the read-only API that backs it, so extending it is a matter of adding a row and a line of guidance. Change the severities to match your risk appetite. Rewrite the report format if your manager wants it a certain way. Delete the demo-scope block to audit the entire region.\n\nA few natural next steps once it works:\n\nPoint it at more regions. The demo is `us-east-1` only. Loop the region in the prompt or run it per region.\n\nSchedule it. A read-only agent that runs every morning and mails you a diff of new findings is a genuinely useful thing, and it cannot break anything overnight because it cannot write.\n\nWiden the checklist toward a framework you report against, like the full CIS benchmark, one row at a time.\n\nFair question. There are mature tools in this space, and you should know when to reach for them instead.\n\n**Prowler** is the heavyweight: 600+ checks, mapped to CIS, PCI, HIPAA, and more. If you need exhaustive compliance coverage for an audit, use Prowler. The trade-off is that 600 findings with no narrative is a wall of text. It tells you everything and prioritizes nothing.\n\n**ScoutSuite** is read-only like our agent and produces a nice HTML report. It is excellent for a point-in-time config review. It does no cost analysis and it is static, not conversational. You cannot ask it a follow-up.\n\n**Trusted Advisor** has the best native cost checks (idle load balancers, unassociated Elastic IPs, underutilized EBS). The catch: the full cost category requires a **Business or Enterprise Support plan** ([AWS docs](https://aws.amazon.com/premiumsupport/technology/trusted-advisor/)). On a Basic plan you do not get them, which is exactly why an agent computing the same things from raw `Describe` calls is useful to a beginner.\n\n**Security Hub** is the central dashboard, but it must be configured. Our demo run caught the trap live: the standards were subscribed but reported `NO_AVAILABLE_CONFIGURATION_RECORDER`. Without an AWS Config recorder, most controls cannot evaluate ([AWS docs](https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-setup-prereqs.html)). The dashboard was on, the checks were off. A human skims a green dashboard and moves on. The agent read the actual status and flagged it.\n\nSo here is the decision framework:\n\n| Reach for | When | \n|---|---|\n| **Prowler** | You need exhaustive, framework-mapped compliance evidence for an audit | \n| **ScoutSuite** | You want a thorough static config snapshot, security only | \n| **Trusted Advisor** | You are on Business/Enterprise Support and want native cost checks | \n| **This agent** | You want security AND cost in one plain-language, prioritized report you can converse with, on any support plan, provably read-only | \n\nThe edge of the custom agent is not raw coverage. It is clarity, ruthless prioritization (a Top 3, not 600 rows), security and cost in one voice, detecting the \"configured but inert\" trap, and a provable read-only guarantee you can hand to a nervous manager.\n\n**When NOT to use it:** if you need certified compliance evidence, if you want continuous automated remediation (this agent only reads), or if your org already runs Prowler in CI and just needs the raw findings. This is a fast, human-friendly first look, not a compliance system of record.\n\nFour things I hit building this that are not in any single doc:\n\n**The `use_aws` service allowlist is a real second wall, and it is easy to forget `s3api`.** S3 read calls split between `s3` and `s3api` depending on the operation. Leave `s3api` out of `allowedServices` and the public-bucket check silently cannot run. Both belong in the list.\n\n**\"Not verified\" has to be forced in the prompt or the model will paper over gaps.** Without the explicit \"put it in the Gaps section as not verified\" instruction, models tend to imply a skipped check passed. That single sentence changed the behavior in testing.\n\n**Idle Elastic IP pricing hides behind a specific usage type.** The rate is not under a generic \"EIP\" filter. It is `USE1-PublicIPv4:IdleAddress` in us-east-1 (since the Feb 2024 public IPv4 charge). If your cost math comes back empty, you are querying the wrong usage type.\n\n**Clean up your demo resources.** If you use the demo scripts, the teardown deletes by recorded ID and then sweeps by tag, in this order: snapshot, Elastic IP, volume, security group, bucket. Run it right after, or you keep paying the few cents a month the audit just flagged. The irony writes itself.\n\nAn agent is not a mystery. It is a JSON file that names a model, lists some tools, plugs in a few MCP servers, and points at a Markdown checklist. The engineering that makes it trustworthy is not in the model at all. It is in the IAM boundary that makes destructive action impossible, and in a prompt that forbids inventing anything.\n\nBuild the guardrail first. Then the agent can be as capable as you like, because the worst it can do is tell you the truth about your account.\n\nGrab the two files, attach the two read-only policies, and run it against your own account: [github.com/simplynadaf/aws-auditor-agent](https://github.com/simplynadaf/aws-auditor-agent).\n\nWhat would you add to the checklist first, security or cost? I am curious which one bites people more in practice.\n\n*Follow me for more on AWS architecture, DevOps, and AI Infrastructure:*\n\n[Portfolio](https://sarvarnadaf.com) | [LinkedIn](https://www.linkedin.com/in/sarvar04/) | [Dev.to](https://dev.to/sarvar_04) | [YouTube](https://www.youtube.com/@sarvar-nadaf) | [Email](mailto:simplynadaf@gmail.com) | [AWS Builder Center](https://builder.aws.com/community/@sarvar) | [X](https://x.com/SarvarN_04)", "url": "https://wpnews.pro/news/i-built-an-ai-agent-that-audits-aws-and-it-can-t-touch-anything", "canonical_source": "https://dev.to/aws-builders/i-built-an-ai-agent-that-audits-aws-and-it-cant-touch-anything-4nip", "published_at": "2026-09-18 15:12:34+00:00", "updated_at": "2026-09-18 15:22:52.576681+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "ai-infrastructure", "developer-tools"], "entities": ["AWS", "GitHub", "Amazon Q Developer", "Kiro Crew", "GuardDuty", "IAM", "S3"], "alternates": {"html": "https://wpnews.pro/news/i-built-an-ai-agent-that-audits-aws-and-it-can-t-touch-anything", "markdown": "https://wpnews.pro/news/i-built-an-ai-agent-that-audits-aws-and-it-can-t-touch-anything.md", "text": "https://wpnews.pro/news/i-built-an-ai-agent-that-audits-aws-and-it-can-t-touch-anything.txt", "jsonld": "https://wpnews.pro/news/i-built-an-ai-agent-that-audits-aws-and-it-can-t-touch-anything.jsonld"}}