GitHub Action for CleanCloud — a read-only cloud hygiene scanner for AWS, Azure, and GCP that finds orphaned resources, detects idle AI/ML waste ($500–$23K/month per endpoint), and enforces policy-as-code in CI.
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::${{ vars.AWS_ACCOUNT_ID }}:role/CleanCloudCIReadOnly
aws-region: us-east-1
- uses: cleancloud-io/scan-action@v1
with:
provider: aws
all-regions: 'true'
fail-on-confidence: HIGH
fail-on-cost: '100'
output: json
output-file: scan-results.json- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::${{ vars.AWS_ACCOUNT_ID }}:role/CleanCloudCIReadOnly
aws-region: us-east-1
- uses: cleancloud-io/scan-action@v1
with:
provider: aws
org: 'true'
all-regions: 'true'
fail-on-confidence: HIGH
output: json
output-file: scan-results.json- uses: cleancloud-io/scan-action@v1
with:
provider: aws
accounts: '111111111111,222222222222,333333333333'
all-regions: 'true'
output: json
output-file: scan-results.json- uses: cleancloud-io/scan-action@v1
with:
provider: aws
multi-account: .cleancloud/accounts.yaml
all-regions: 'true'
output: json
output-file: scan-results.json- uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- uses: cleancloud-io/scan-action@v1
with:
provider: azure
fail-on-confidence: HIGH
fail-on-cost: '100'
output: json
output-file: scan-results.json- uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- uses: cleancloud-io/scan-action@v1
with:
provider: azure
management-group: my-management-group-id
fail-on-confidence: HIGH
output: json
output-file: scan-results.json- uses: cleancloud-io/scan-action@v1
with:
provider: azure
subscription: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy'
fail-on-confidence: HIGH
output: json
output-file: scan-results.json- uses: google-github-actions/auth@v2
with:
workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }}
service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }}
- uses: cleancloud-io/scan-action@v1
with:
provider: gcp
all-projects: 'true'
fail-on-confidence: HIGH
fail-on-cost: '100'
output: json
output-file: scan-results.json- uses: google-github-actions/auth@v2
with:
workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }}
service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }}
- uses: cleancloud-io/scan-action@v1
with:
provider: gcp
project: my-gcp-project-id
fail-on-confidence: HIGH
output: json
output-file: scan-results.json- uses: cleancloud-io/scan-action@v1
with:
provider: gcp
project: 'project-id-1, project-id-2, project-id-3'
fail-on-confidence: HIGH
output: json
output-file: scan-results.jsonDetect idle SageMaker endpoints with zero invocations. GPU-backed endpoints flagged HIGH risk ($500–$23K/month).
- uses: cleancloud-io/scan-action@v1
with:
provider: aws
all-regions: 'true'
category: ai
fail-on-confidence: HIGH
output: json
output-file: scan-results.json- uses: cleancloud-io/scan-action@v1
with:
provider: azure
category: ai
fail-on-confidence: HIGH
output: json
output-file: scan-results.json- uses: cleancloud-io/scan-action@v1
with:
provider: gcp
all-projects: 'true'
category: ai
fail-on-confidence: HIGH
output: json
output-file: scan-results.json- uses: cleancloud-io/scan-action@v1
with:
provider: aws
org: 'true'
all-regions: 'true'
category: all
fail-on-confidence: HIGH
fail-on-cost: '500'
output: json
output-file: scan-results.jsonCommit a cleancloud.yaml to your repo — exceptions, thresholds, and tag filtering are picked up automatically:
- uses: actions/checkout@v4 # required so cleancloud.yaml is available
- uses: cleancloud-io/scan-action@v1
with:
provider: aws
all-regions: 'true'
# config auto-detected from cleancloud.yaml in repo root
# or pass explicitly:
# config: configs/prod.yaml
output: json
output-file: scan-results.jsoncleancloud.yaml (committed to repo root):
defaults:
confidence: MEDIUM
min_cost: 10
exceptions:
- rule_id: aws.ec2.instance.stopped
resource_id: i-0abc1234567890def
reason: "Bastion host — started on demand"
expires_at: "2026-12-31"
thresholds:
fail_on_confidence: HIGH
fail_on_cost: 500See policy config reference for full options.
AWS:
- uses: cleancloud-io/scan-action@v1
with:
provider: aws
region: us-east-1
fail-on-confidence: HIGH
output: json
output-file: scan-results.jsonAzure (region is optional — omit to scan all locations):
- uses: cleancloud-io/scan-action@v1
with:
provider: azure
region: westeurope
fail-on-confidence: HIGH
output: json
output-file: scan-results.json| Input | Required | Default | Description |
|---|---|---|---|
provider |
Yes | — | aws, azure, or gcp |
category |
No | hygiene |
hygiene (default), ai (SageMaker / AML / Vertex AI — all clouds), or all |
region |
No | — | Specific region (AWS) or location filter (Azure, optional) |
fail-on-confidence |
No | — | Fail if findings at or above this level: LOW, MEDIUM, or HIGH |
fail-on-cost |
No | — | Fail if estimated monthly waste exceeds this USD amount |
fail-on-findings |
No | false |
Fail on any finding |
output |
No | human |
Output format: human, json, csv, or markdown |
output-file |
No | — | Path to write output file (required for json/csv, optional for markdown) |
artifact-name |
No | — | Upload the output file as a GitHub Actions artifact with this name. Leave empty to skip upload. |
config |
No | — | Path to cleancloud.yaml config file. Auto-detected from repo root if omitted (requires actions/checkout first). |
explain |
No | false |
Print suppression reason for each filtered finding. Useful for debugging policy config. |
skip |
No | — | Comma-separated rule IDs to skip. Example: aws.ec2.ami.old,aws.resource.untagged |
ignore-tag |
No | — | Ignore findings by tag. Comma-separated key or key:value pairs. Example: env:dev,temporary |
version |
No | latest | CleanCloud version to install (e.g. 1.7.2) |
| Input | Required | Default | Description |
|---|---|---|---|
all-regions |
One of all-regions or region required |
false |
Scan all active regions |
org |
No | false |
Auto-discover and scan all accounts in your AWS Organization. Requires organizations:ListAccounts on the hub role. |
accounts |
No | — | Comma-separated AWS account IDs to scan. Example: 111111111111,222222222222 |
multi-account |
No | — | Path to accounts config file. Example: .cleancloud/accounts.yaml |
role-name |
No | CleanCloudReadOnlyRole |
IAM role name to assume in each spoke account |
external-id |
No | — | External ID for cross-account role assumption, if required by the spoke trust policy |
concurrency |
No | 3 |
Number of accounts to scan in parallel. Keep low to avoid API throttling. |
timeout |
No | 3600 |
Total scan timeout in seconds across all accounts |
per-account-regions |
No | false |
Detect active regions per account instead of once on the hub. Slower but accurate if accounts use different regions. |
org,accounts, andmulti-accountare mutually exclusive — use only one.
| Input | Required | Default | Description |
|---|---|---|---|
subscription |
No | — | Comma-separated subscription IDs to scan. Omit to scan all accessible subscriptions. |
management-group |
No | — | Management Group ID — auto-discovers all subscriptions underneath. |
subscriptionandmanagement-groupare mutually exclusive — use only one.
| Input | Required | Default | Description |
|---|---|---|---|
project |
No | — | Comma-separated GCP project IDs to scan. Omit to use the default project from credentials. |
all-projects |
No | false |
Scan all accessible GCP projects. Requires roles/browser on the service account. |
projectandall-projectsare mutually exclusive — use only one.
| Code | Meaning |
|---|---|
0 |
No policy violations |
1 |
Configuration error or unexpected failure |
2 |
Policy violation — findings detected (when enforcement enabled) |
3 |
Missing credentials or insufficient permissions |
This action installs CleanCloud from PyPI and runs it directly on the runner. For Docker-based CI, use the Docker image directly instead of this action.
CleanCloud is read-only — it never creates, modifies, or deletes resources. Set up authentication before calling this action:
- AWS: Use
aws-actions/configure-aws-credentialswith OIDC. See AWS setup guide. - Azure: Use
azure/loginwith Workload Identity Federation. See Azure setup guide. - GCP: Use
google-github-actions/authwith Workload Identity Federation. See GCP setup guide.
This action installs the latest CleanCloud from PyPI by default. To pin a specific version:
- uses: cleancloud-io/scan-action@v1
with:
provider: aws
version: '1.9.0'- Korben 🇫🇷 — Major French tech publication
- Last Week in AWS #457 — Corey Quinn's weekly AWS newsletter