az-costguard
Self-hosted Azure cost optimization scanner. Runs 204 rules across 12 FinOps domains — waste elimination, rightsizing, networking, storage, monitoring, and more — and reports findings with estimated monthly savings.
Prerequisites
| Requirement | Version |
|---|---|
| Python | 3.11+ |
| Azure CLI | Any (for az login) |
| Azure RBAC | Reader on target subscriptions |
Required Azure Permissions
The minimum IAM role is Reader assigned at the subscription scope (or management group for multi-subscription scans). No write permissions are required — az-costguard is read-only.
The following Azure APIs are called during a scan:
| API | Used for |
|---|---|
| Azure Resource Graph | Resource inventory queries (KQL) |
| Azure Monitor Metrics | CPU, memory, connection utilisation |
| Azure Advisor | Recommendation signals |
| Azure Cost Management | Commitment coverage and spend data |
| Azure Subscription API | Subscription name resolution |
For CI/CD or automated scans, create a Service Principal with the Reader role:
az ad sp create-for-rbac \
--name az-costguard \
--role Reader \
--scopes /subscriptions/<subscription-id>
Set the output values as environment variables AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET.
Installation
From PyPI (recommended)
pip install az-costguard
From source (development)
git clone https://github.com/ragnild/az-costguard.git
cd az-costguard
python -m venv .venv
# Windows
.venv\Scripts\activate
# macOS / Linux
source .venv/bin/activate
pip install -e ".[dev]"
Docker
git clone https://github.com/ragnild/az-costguard.git
cd az-costguard
docker build -t az-costguard:latest .
Run a scan using a service principal:
docker run --rm \
-e AZURE_TENANT_ID="<tenant-id>" \
-e AZURE_CLIENT_ID="<client-id>" \
-e AZURE_CLIENT_SECRET="<client-secret>" \
az-costguard:latest scan -s <subscription-id>
Save findings to a file on your host:
docker run --rm \
-e AZURE_TENANT_ID="<tenant-id>" \
-e AZURE_CLIENT_ID="<client-id>" \
-e AZURE_CLIENT_SECRET="<client-secret>" \
-v "$PWD/output:/output" \
az-costguard:latest scan -s <subscription-id> --format csv --output /output/findings.csv
Windows (PowerShell): replace
\line continuation with a backtick`and$PWDwith${PWD}.
Authentication
Option 1 — Azure CLI (recommended for local use)
az login
az account set --subscription <subscription-id>
Option 2 — Service Principal
Set these environment variables before running:
AZURE_TENANT_ID=<tenant-id>
AZURE_CLIENT_ID=<client-id>
AZURE_CLIENT_SECRET=<client-secret>
az-costguard automatically picks up the service principal when all three variables are present.
Running a Scan
Scan all accessible subscriptions
az-costguard scan
Scan a specific subscription
az-costguard scan -s <subscription-id>
Scan multiple subscriptions
az-costguard scan -s <sub-id-1> -s <sub-id-2>
Verbose mode — see every rule result
az-costguard scan -s <subscription-id> --verbose
Shows a Rule Execution Detail table with the status of every evaluated rule (PASS / FOUND / ERROR / SKIPPED), resource names, locations, and estimated savings.
Output Formats
Table (default)
Prints a Category Summary and Overall Summary to the terminal.
az-costguard scan -s <subscription-id>
Table + Rule Execution Detail
az-costguard scan -s <subscription-id> --verbose
JSON
Outputs the findings array as JSON to stdout. Pipe or redirect as needed.
az-costguard scan -s <subscription-id> --format json
# Save to file
az-costguard scan -s <subscription-id> --format json > findings.json
CSV
Outputs findings as a CSV file with a header row.
az-costguard scan -s <subscription-id> --format csv
# Save to file
az-costguard scan -s <subscription-id> --format csv > findings.csv
CSV columns: rule_id, rule_name, domain, severity, resource_name, resource_id, resource_group, subscription_id, location, monthly_savings, annual_savings, confidence, risk, evidence, description, recommendation, suppressed
HTML Executive Report
Generate a self-contained interactive HTML report from a CSV export. Opens in any browser — no server required.
# Step 1 — export findings to CSV
az-costguard scan -s <subscription-id> --format csv > findings.csv
# Step 2 — generate HTML report
az-costguard report --csv findings.csv --output report.html
For multiple subscriptions, pass --csv once per file:
az-costguard report --csv sub1.csv --csv sub2.csv --output report.html
Filtering
By domain
az-costguard scan -s <subscription-id> --domain "Waste Elimination"
Available domains:
Waste EliminationVM RightsizingDisk OptimizationAzure SQLApp Services & FunctionsAKSNetworkingStorage AccountsData ServicesMonitoring & BackupCommitmentsGovernance & FinOps Intelligence
By minimum severity
az-costguard scan -s <subscription-id> --severity HIGH
az-costguard scan -s <subscription-id> --severity MEDIUM
az-costguard scan -s <subscription-id> --severity LOW
Combining filters
az-costguard scan -s <subscription-id> --domain "Networking" --severity MEDIUM --format csv > network-findings.csv
Command Reference
Usage: az-costguard [OPTIONS] COMMAND [ARGS]...
Options:
-V, --version Show version and exit.
--help Show this message and exit.
Commands:
scan Scan Azure subscriptions for cost optimization findings.
report Generate an interactive HTML executive report from CSV exports.
scan
Usage: az-costguard scan [OPTIONS]
Options:
-s, --subscription TEXT Subscription ID to scan. Repeatable.
Defaults to all accessible subscriptions.
-d, --domain TEXT Limit to a single domain.
--severity TEXT Minimum severity: HIGH, MEDIUM, or LOW.
-f, --format TEXT Output format: table, json, or csv. [default: table]
-c, --config TEXT Path to az-costguard.yaml config file.
-v, --verbose Show rule execution detail for every evaluated rule.
--help Show this message and exit.
report
Usage: az-costguard report [OPTIONS]
Generate an interactive HTML executive report from one or more scan CSV files.
Options:
--csv PATH CSV file produced by 'scan --format csv'. Repeatable. [required]
--output PATH Output HTML file path. [default: az_costguard_report.html]
--help Show this message and exit.
Configuration File
Create az-costguard.yaml to set defaults or suppress known findings:
min_monthly_savings: 10.0 # Only report findings with >= $10/month savings
exceptions:
- rule_id: azure.publicip.unused
resource_id: /subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.Network/publicIPAddresses/<name>
Pass the config file with:
az-costguard scan -s <subscription-id> --config az-costguard.yaml
Example Output
Default mode
CATEGORY SUMMARY
Category Evaluated Triggered Findings Est. $/mo
──────────────────────────────────────────────────────────────────────
Monitoring & Backup 3 3 4 —
Networking 11 2 8 $8
Waste Elimination 12 2 2 —
──────────────────────────────────────────────────────────────────────
Total 26 7 14 $8
OVERALL SUMMARY
┌──────────────────────────────┬────────────────────────────────┐
│ Subscriptions scanned │ 1 │
│ Resources in scope │ 4,823 │
│ Rules evaluated │ 35 │
│ Total findings │ 13 │
│ Estimated monthly savings │ $7.30 │
├──────────────────────────────┼────────────────────────────────┤
│ Severity breakdown │ HIGH: 0 MEDIUM: 13 LOW: 0 │
├──────────────────────────────┼────────────────────────────────┤
│ Scan duration │ 18.3s │
└──────────────────────────────┴────────────────────────────────┘
Verbose mode — Rule Execution Detail (excerpt)
Rule ID Status Found Risk Est.$/mo Resource(s) Location Reason
──────────────────────────────────────────────────────────────────────────────────────────────────────────────
azure.vm.stopped_not_deallocated PASS 0 — — — — —
azure.vm.orphaned.nic FOUND 6 MEDIUM — nic-001, nic-002 +4 uaenorth Orphaned NIC not attached to any VM
azure.publicip.unused FOUND 2 MEDIUM $8 pip-001, pip-002 uaenorth PIP has no IP configuration. SKU: Standard
azure.bastion.idle FOUND 1 MEDIUM — bastion-dev uaenorth Verify session activity
azure.acr.unused SKIPPED — — — — — No executor yet
Rules evaluated : 29
Passed : 24
Triggered : 5
Errors : 0
Skipped : 6
Estimated savings from triggered rules : $7.30/month
Domains & Rule Count
| Domain | Rules |
|---|---|
| Waste Elimination | 35 |
| VM Rightsizing | 27 |
| Disk Optimization | 14 |
| Azure SQL | 14 |
| App Services & Functions | 11 |
| AKS | 24 |
| Networking | 9 |
| Storage Accounts | 15 |
| Data Services | 12 |
| Monitoring & Backup | 11 |
| Commitments | 12 |
| Governance & FinOps Intelligence | 20 |
| Total | 204 |
Version
az-costguard --version
Metadata
Release files for az-costguard 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| az_costguard-0.1.0.tar.gz | 124.2 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| az_costguard-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 264.5 kB
Release files / az_costguard-0.1.0.tar.gz
| Download URL | az_costguard-0.1.0.tar.gz |
|---|---|
| Size | 124.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a1b9f86b2d3595ed053fd1fe782245e59d47d50b6cdfb36dac49b39bc516825c
|
|
BLAKE2b-256 checksum How to use checksums |
e01645bb2af76cf9bbf9ef016d65869d7af0ff7161e355c49e1a17e00a30669f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Release files / az_costguard-0.1.0-py3-none-any.whl
| Download URL | az_costguard-0.1.0-py3-none-any.whl |
|---|---|
| Size | 140.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a3d0c4ef3a44da2eab1c71c54da6f4b3b02275c47832eda1517485a7f584844b
|
|
BLAKE2b-256 checksum How to use checksums |
3898aaa7c86ff3c25244055fb2b7678288f180da902ad4cf82a89fba9218ddf7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Security for az-costguard 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.