Build1 publisher3 min readPublished
Twenty-two read-only checks price idle AWS resources, though five findings cost nothing
A Python tool called zombiescan sweeps every enabled AWS region in about 20 seconds using only Describe, List and Get calls, and the one sample run its guide publishes returned 86 findings that all price at zero dollars a month.
The Engineer · Build desk

What happened
- A dev.to guide walks through zombiescan, a Python CLI that audits an AWS account for unused resources across 22 check classes and attaches a monthly dollar figure to each finding.
- A published all-regions sweep took about 20 seconds against a small account and returned 86 findings across 17 enabled regions.
- botocore[crt] is a hard dependency, because boto3's login credential provider raises MissingDependencyException at credential load without the CRT extra installed.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- constraint One tilde stands for three unrelated fallbacks, so anyone about to delete something on the strength of a figure has to open the JSON note to learn whether it is a price, a substituted us-east-1 price, or a storage ceiling.
- contradiction The guide's whole case is that a named per-resource cost moves a decision, and the only scan it shows totals nothing, so the pricing path is only described.
- cost Adoption costs a uv install and a read grant most organisations already hand out, which keeps the approval conversation about read scope instead of about giving a script delete rights in production.
- decision Because the price table ships with the installed version, teams have to decide how often to reinstall before a quoted monthly figure drifts away from what AWS is actually charging them.
In zombiescan's cost column a leading `~` means the monthly figure is an estimate or an upper bound, and three different conditions put it there: the region had no price entry so the figure fell back to us-east-1, the resource type was unrecognised, or the resource bills on what it stores instead of what it provisions [13]. The terminal table prints the same character for all three, so the tilde is the first thing to read. Each finding carries a `note` in the JSON output naming which one applied [14].
The one sweep the guide publishes reported 86 findings across 17 regions, and its summary rows are 67 log groups with no retention policy, 18 unused security groups and one empty VPC, each at $0.00 [10]. Those three numbers add to 86 [11]. Every finding in that account came from the zero-dollar checks, and the guide does not show a run containing a billable resource [11]. The five free checks are reported because those resources accumulate without limit and block deletions [3].
The read-only discipline is good engineering. Every call the scan makes is a Describe, a List or a Get, and the guide states the scan has no code path that deletes, terminates, modifies or releases anything [4]. The permissions match the behaviour: read access on ten services, all of it inside the managed `ReadOnlyAccess` policy [5]. The sample output shows the scan running as `arn:aws:iam::106059658660:root` [19]. Running as root means permissions were not what was being tested.
`botocore[crt]` is a hard dependency, because boto3 reads `aws login` sessions through its login credential provider, and that provider raises `MissingDependencyException` at credential load without the CRT extra installed [6]. The failure arrives before the first Describe call, during credential load [6]. That is the install detail that will cost someone an afternoon. Installation is `uv tool install git+https://github.com/xbill9/zombiescan`, with `uv` already on the path [7].
`--all-regions` asks EC2 which regions the account has enabled, so opted-out regions cost no calls [8]. Global services run once per scan, because Route 53 has no regions and a health check scanned in all seventeen would be reported seventeen times and multiply the total to match [12]. Details like those suggest this was run against a real account.
Twenty seconds is the figure least likely to transfer. Twenty-two checks across 17 regions is 374 check-region pairs, fewer in practice since the global checks run once. At the quoted runtime that is roughly 19 a second [20]. The guide qualifies the measurement as a small account [9]. For it to hold in yours, your enabled region count and your object count per check have to sit in the same range; an account with thousands of EBS snapshots is describing far more per check.
The guide's case is that Cost Explorer gives a total, Trusted Advisor gives a candidate list, and neither settles an argument the way one resource with one monthly figure does [18]. In my view that is right where a deletion has to be justified to whoever owns the workload. The price table ships bundled with the tool, generated from the AWS Price List API [17], so a finding's dollar figure is as current as the version installed. For the per-resource number to beat the total, the findings that matter have to come from the 17 billable checks [3]. In the published run, none did [11].
What to watch
- Whether a later version queries the AWS Price List API at scan time instead of shipping a table generated at build time.
- A published run against an account with billable findings, which would show whether the priced checks track a real invoice line.
- Whether the cleanup step arrives as a separate command needing write permissions, and which policy it then requires beyond ReadOnlyAccess.