Skip to content

Latest commit

 

History

History
1649 lines (1425 loc) · 52.8 KB

File metadata and controls

1649 lines (1425 loc) · 52.8 KB

Example Outputs

Complete output examples for CleanCloud across AWS, Azure, and GCP — doctor validation, human-readable scan results, JSON output for CI/CD integration, and markdown for sharing in GitHub PRs or Slack.


Table of Contents


Doctor — AWS

cleancloud doctor --provider aws

Validates IAM credentials, authentication method, security grade, and all read-only permissions required for scanning.

Local Development (AWS CLI Profile)

Running CleanCloud doctor


======================================================================
CLEANCLOUD ENVIRONMENT DIAGNOSTICS
======================================================================

Providers to check: AWS


======================================================================
AWS ENVIRONMENT VALIDATION
======================================================================

Step 1: AWS Credential Resolution
----------------------------------------------------------------------
[OK] AWS session created successfully

Step 2: Authentication Method Detection
----------------------------------------------------------------------
Authentication Method: AWS CLI Profile (default)
  Boto3 Provider: shared-credentials-file
  Credential Type: Long-lived
  Lifetime: long-lived (access keys)
  Rotation Required: Yes (every 90 days)

[!] Security Grade: ACCEPTABLE
[!]   - Long-lived credentials
[!]   - Manual rotation required

  Recommendation for local development:
    Current setup is acceptable

CI/CD Ready: NO (Local development only)
AWS CLI profiles are not available in CI/CD

Compliance: Acceptable for development environments

Step 3: Identity Verification
----------------------------------------------------------------------
[OK] Account ID: 123456789012
[OK] User ID: AIDAURVBTGFZCW6JGFCG2
[OK] ARN: arn:aws:iam::123456789012:user/cleanclouduser
  IAM User: cleanclouduser

Region Scope
----------------------------------------------------------------------
Active Region: us-east-1
Doctor validates permissions for the active region only
Use 'cleancloud scan --provider aws --all-regions' to scan all active regions

Step 4: Read-Only Permission Validation
----------------------------------------------------------------------
[OK] ec2:DescribeVolumes
[OK] ec2:DescribeSnapshots
[OK] ec2:DescribeRegions
[OK] ec2:DescribeAddresses
[OK] ec2:DescribeNetworkInterfaces
[OK] ec2:DescribeImages
[OK] ec2:DescribeNatGateways
[OK] ec2:DescribeInstances
[OK] ec2:DescribeSecurityGroups
[OK] rds:DescribeDBInstances
[OK] rds:DescribeDBSnapshots
[OK] elasticloadbalancing:DescribeLoadBalancers
[OK] elasticloadbalancing:DescribeTargetGroups
[OK] logs:DescribeLogGroups
[OK] cloudwatch:GetMetricStatistics
[OK] s3:ListAllMyBuckets
[OK] s3:GetBucketTagging

======================================================================
VALIDATION SUMMARY
======================================================================
Authentication: AWS CLI Profile (default)
Security Grade: ACCEPTABLE
Permissions Tested: 19/19 passed

[OK] AWS ENVIRONMENT READY FOR CLEANCLOUD
======================================================================

CI/CD (OIDC — Recommended)

When running with OIDC authentication (e.g., GitHub Actions), the security grade upgrades to EXCELLENT:

Step 2: Authentication Method Detection
----------------------------------------------------------------------
Authentication Method: OIDC (AssumeRoleWithWebIdentity)
  Boto3 Provider: assume-role-with-web-identity
  Credential Type: Temporary
  Lifetime: 1 hour (temporary)
  Rotation Required: No (auto-rotated)

[OK] Security Grade: EXCELLENT
[OK]   - Temporary credentials
[OK]   - Auto-rotated
[OK]   - No secret storage required

[OK] CI/CD Ready: YES

Doctor — Azure

cleancloud doctor --provider azure

Validates Azure credentials, subscription access, and Reader role permissions across all accessible subscriptions.

Local Development (Service Principal)

Running CleanCloud doctor


======================================================================
CLEANCLOUD ENVIRONMENT DIAGNOSTICS
======================================================================

Providers to check: AZURE


======================================================================
AZURE ENVIRONMENT VALIDATION
======================================================================

Step 1: Azure Credential Resolution
----------------------------------------------------------------------
Authentication Method: Service Principal (Client Secret)
  Lifetime: long-lived (client secret)
  Rotation Required: Yes (every 90 days or per policy)
[!]   Uses Secret: Yes (stored credential)

[!] Security Grade: POOR
[!]   - Long-lived client secret
[!]   - Requires manual rotation
[!]   - High blast radius if compromised

  Recommendation for CI/CD:
    Switch to OIDC (Workload Identity Federation)
    See: https://docs.cleancloud.io/azure#oidc

[!] CI/CD Ready: NO
[!]   Client secrets not recommended for automated pipelines

[!] Compliance: May not meet enterprise security requirements

Client ID: d7b1a453-4182-41f1-aa1f-2b9c99956981
Tenant ID: a8d6813d-de25-4473-bf18-94658b348c7d
Subscription Filter: 29d91ee0-922f-483a-a81f-1a5eff4ecfa2

Step 2: Credential Acquisition
----------------------------------------------------------------------
[OK] Azure credentials acquired successfully
  Token expires in: ~59 minutes

Step 3: Subscription Access Validation
----------------------------------------------------------------------
[OK] Accessible subscriptions: 1
  • Azure subscription 1 (29d91ee0-922f-483a-a81f-1a5eff4ecfa2)

[OK] Subscription filter matched: Azure subscription 1

Step 4: Permission Validation
----------------------------------------------------------------------
[OK] Subscription read access confirmed
  Reader role provides all required permissions:
    - Microsoft.Compute/disks/read
    - Microsoft.Compute/snapshots/read
    - Microsoft.Compute/virtualMachines/read
    - Microsoft.Network/publicIPAddresses/read
    - Microsoft.Network/loadBalancers/read
    - Microsoft.Network/applicationGateways/read
    - Microsoft.Network/virtualNetworkGateways/read
    - Microsoft.Network/connections/read
    - Microsoft.Web/serverfarms/read
    - Microsoft.Web/serverfarms/sites/read
    - Microsoft.Web/sites/read
    - Microsoft.ContainerRegistry/registries/read
    - Microsoft.Sql/servers/read
    - Microsoft.Sql/servers/databases/read
    - Microsoft.Insights/metrics/read
    - Microsoft.Resources/subscriptions/read
    - Microsoft.Resources/resources/read

======================================================================
VALIDATION SUMMARY
======================================================================
Authentication: Service Principal (Client Secret)
Security Grade: POOR
Subscriptions: 1 accessible
Filtered to: 29d91ee0-922f-483a-a81f-1a5eff4ecfa2

[OK] AZURE ENVIRONMENT READY FOR CLEANCLOUD
======================================================================

CI/CD (OIDC — Recommended)

When running with Workload Identity Federation, the security grade upgrades to EXCELLENT:

Step 1: Azure Credential Resolution
----------------------------------------------------------------------
Authentication Method: OIDC (Workload Identity Federation)
  Lifetime: 1 hour (temporary)
  Rotation Required: No
[OK] Uses Secret: No (secretless)

[OK] Security Grade: EXCELLENT
[OK]   - No client secrets stored
[OK]   - Temporary credentials
[OK]   - Auto-rotated

[OK] CI/CD Ready: YES
[OK]   Suitable for production CI/CD pipelines

[OK] Compliance: SOC2/ISO27001 Compatible

Doctor — Multi-Account

cleancloud doctor --provider aws --multi-account accounts.yaml

Validates cross-account role access for every account in the config before committing to a full scan.

Running CleanCloud doctor

======================================================================
MULTI-ACCOUNT VALIDATION
======================================================================
Role name    : CleanCloudReadOnlyRole
External ID  : (none)
Accounts     : 3

Step 1: Hub Account Credentials
----------------------------------------------------------------------
[OK] Hub account: 000000000000  (arn:aws:sts::000000000000:assumed-role/CleanCloudCIReadOnly/github-actions)

Step 2: Hub Role Permissions
----------------------------------------------------------------------
[OK] organizations:ListAccounts  ✅  (--org flag will work)

Step 3: Cross-Account Role Validation
----------------------------------------------------------------------
[OK] production (111111111111)  →  arn:aws:sts::111111111111:assumed-role/CleanCloudReadOnlyRole/cleancloud-111111111111
[OK] staging (222222222222)     →  arn:aws:sts::222222222222:assumed-role/CleanCloudReadOnlyRole/cleancloud-222222222222
[!] dev (333333333333)          →  AccessDenied: role not found

======================================================================
MULTI-ACCOUNT SUMMARY
======================================================================
Accounts passed : 2/3
Accounts failed : 1
  dev (333333333333): AccessDenied: role not found

Expected role ARN format: arn:aws:iam::<ACCOUNT_ID>:role/CleanCloudReadOnlyRole
See docs/aws.md for cross-account IAM setup instructions
======================================================================

Doctor — Azure Multi-Subscription

cleancloud doctor --provider azure

When the service principal has Reader on multiple subscriptions, the doctor validates all of them automatically.

Running CleanCloud doctor


======================================================================
AZURE ENVIRONMENT VALIDATION
======================================================================

Step 1: Azure Credential Resolution
----------------------------------------------------------------------
Authentication Method: OIDC (Workload Identity Federation)
  Lifetime: 1 hour (temporary)
  Rotation Required: No
[OK] Uses Secret: No (secretless)

[OK] Security Grade: EXCELLENT
[OK]   - No client secrets stored
[OK]   - Temporary credentials
[OK]   - Auto-rotated

[OK] CI/CD Ready: YES
[OK]   Suitable for production CI/CD pipelines

[OK] Compliance: SOC2/ISO27001 Compatible

Client ID: d7b1a453-4182-41f1-aa1f-2b9c99956981
Tenant ID: a8d6813d-de25-4473-bf18-94658b348c7d

Step 2: Credential Acquisition
----------------------------------------------------------------------
[OK] Azure credentials acquired successfully
  Token expires in: ~59 minutes

Step 3: Subscription Access Validation
----------------------------------------------------------------------
[OK] Accessible subscriptions: 3
  • Production (a1b2c3d4-e5f6-7890-abcd-ef1234567890)
  • Staging    (f9e8d7c6-b5a4-3210-fedc-ba0987654321)
  • Dev        (c3d4e5f6-a7b8-9012-cdef-345678901234)

Step 4: Permission Validation
----------------------------------------------------------------------
[OK] Subscription read access confirmed
  Reader role provides all required permissions:
    - Microsoft.Compute/disks/read
    - Microsoft.Compute/snapshots/read
    - Microsoft.Compute/virtualMachines/read
    - Microsoft.Network/publicIPAddresses/read
    - Microsoft.Network/loadBalancers/read
    - Microsoft.Network/applicationGateways/read
    - Microsoft.Network/virtualNetworkGateways/read
    - Microsoft.Network/connections/read
    - Microsoft.Web/serverfarms/read
    - Microsoft.Web/serverfarms/sites/read
    - Microsoft.Web/sites/read
    - Microsoft.ContainerRegistry/registries/read
    - Microsoft.Sql/servers/read
    - Microsoft.Sql/servers/databases/read
    - Microsoft.Insights/metrics/read
    - Microsoft.Resources/subscriptions/read
    - Microsoft.Resources/resources/read

======================================================================
VALIDATION SUMMARY
======================================================================
Authentication: OIDC (Workload Identity Federation)
Security Grade: EXCELLENT
Subscriptions: 3 accessible

[OK] AZURE ENVIRONMENT READY FOR CLEANCLOUD
======================================================================

Assign Reader at the Management Group level to cover all subscriptions with a single role assignment — see docs/azure.md.


Doctor — GCP

cleancloud doctor --provider gcp --project my-project-123

Validates Application Default Credentials, project access, and all required IAM roles for scanning.

Local Development (gcloud ADC)

Running CleanCloud doctor


======================================================================
CLEANCLOUD ENVIRONMENT DIAGNOSTICS
======================================================================

Providers to check: GCP


======================================================================
GCP ENVIRONMENT VALIDATION
======================================================================

Step 1: GCP Credential Resolution
----------------------------------------------------------------------
Authentication Method: Application Default Credentials (gcloud)
  Credential Type: Temporary (OAuth2)
  Source: ~/.config/gcloud/application_default_credentials.json

[OK] Security Grade: EXCELLENT
[OK]   - Short-lived OAuth2 token
[OK]   - Auto-refreshed by gcloud

CI/CD Ready: NO (Local development only)
Use Workload Identity Federation for CI/CD pipelines

Step 2: Project Access Validation
----------------------------------------------------------------------
[OK] Project accessible: my-project-123 (My Project)

Step 3: Read-Only Permission Validation
----------------------------------------------------------------------
[OK] compute.disks.list        (roles/compute.viewer)
[OK] compute.instances.list    (roles/compute.viewer)
[OK] compute.addresses.list    (roles/compute.viewer)
[OK] compute.snapshots.list    (roles/compute.viewer)
[OK] cloudsql.instances.list   (roles/cloudsql.viewer)
[OK] monitoring.timeSeries.list (roles/monitoring.viewer)
[OK] resourcemanager.projects.get (roles/browser)

======================================================================
VALIDATION SUMMARY
======================================================================
Authentication: Application Default Credentials (gcloud)
Security Grade: EXCELLENT
Permissions Tested: 7/7 passed

[OK] GCP ENVIRONMENT READY FOR CLEANCLOUD
======================================================================

CI/CD (Workload Identity Federation — Recommended)

When running with Workload Identity Federation (e.g., GitHub Actions), there are no stored credentials:

Step 1: GCP Credential Resolution
----------------------------------------------------------------------
Authentication Method: Workload Identity Federation (OIDC)
  Credential Type: Temporary (exchanged from GitHub OIDC token)
  Lifetime: 1 hour (temporary)
  Rotation Required: No (auto-rotated)

[OK] Security Grade: EXCELLENT
[OK]   - No service account key stored
[OK]   - Temporary credentials
[OK]   - Auto-rotated

[OK] CI/CD Ready: YES
[OK]   Suitable for production CI/CD pipelines

Scan — AWS Multi-Account

cleancloud scan --provider aws --multi-account accounts.yaml --all-regions

Scans 3 accounts in parallel, aggregates findings, and shows a per-account breakdown.

Starting CleanCloud scan...

Provider: aws

Scanning 3 accounts (role: CleanCloudReadOnlyRole)...

[INFO] Scanning production (111111111111)...
[INFO] Scanning staging (222222222222)...
[INFO] Scanning dev (333333333333)...
[INFO] Completed dev in 14.2s — 8 findings  ~$420
[INFO] Completed staging in 18.7s — 31 findings  ~$4,200
[INFO] Completed production in 22.1s — 45 findings  ~$12,000

Found 84 hygiene issues:

1. [AWS] Unattached EBS Volume
   Account    : production (111111111111)
   Risk       : Low
   Confidence : High
   Resource   : aws.ebs.volume → vol-0a1b2c3d4e5f67890
   Region     : us-east-1
   Rule       : aws.ebs.unattached
   Reason     : Volume has been unattached for 47 days
   ...

--- Scan Summary ---
Total findings: 84

By risk:
  low: 61
  medium: 23

By confidence:
  high: 38
  medium: 46

Minimum estimated waste: ~$18,200/month
(71 of 84 findings costed)
Regions scanned: eu-west-1, us-east-1, us-west-2 (explicit)
Scanned at: 2026-03-17T08:00:00+00:00

Accounts scanned:     3 ✅

Per-account breakdown:
  dev                  (333333333333):  8 findings  ~$420/month
  production           (111111111111):  45 findings  ~$12,000/month
  staging              (222222222222):  31 findings  ~$4,200/month

JSON output includes accounts_scanned, per_account breakdown, and account_id/account_name on every finding — ideal for downstream processing in CI/CD pipelines.


Scan — AWS (Human-Readable)

cleancloud scan --provider aws --all-regions

Found 9 hygiene issues:

1. [AWS] Stopped EC2 Instance (30+ Days)
   Risk       : Medium
   Confidence : High
   Resource   : aws.ec2.instance → i-0abc1234567890def
   Region     : us-east-1
   Rule       : aws.ec2.instance.stopped
   Reason     : Instance has been stopped for 45 days (EBS charges continue)
   Detected   : 2026-02-08T14:32:01+00:00
   Details:
     - instance_type: t3.large
     - stopped_days: 45
     - ebs_volume_count: 2
     - ebs_total_gb: 120
     - estimated_monthly_cost_usd: 12.00
     - tags: {"Environment": "staging", "Owner": "backend-team"}

2. [AWS] Unattached EBS Volume
   Risk       : Low
   Confidence : High
   Resource   : aws.ebs.volume → vol-0a1b2c3d4e5f67890
   Region     : us-east-1
   Rule       : aws.ebs.unattached
   Reason     : Volume has been unattached for 47 days
   Detected   : 2026-02-08T14:32:02+00:00
   Details:
     - size_gb: 500
     - availability_zone: us-east-1a
     - state: available
     - tags: {"Project": "legacy-api", "Owner": "platform"}

3. [AWS] Old RDS Snapshot
   Risk       : Low
   Confidence : High
   Resource   : aws.rds.snapshot → rds:db-prod-2025-08-01-00-00
   Region     : us-east-1
   Rule       : aws.rds.snapshot.old
   Reason     : Manual RDS snapshot is 191 days old
   Detected   : 2026-02-08T14:32:03+00:00
   Details:
     - db_instance_identifier: db-prod
     - age_days: 191
     - allocated_storage_gb: 100
     - engine: postgres
     - estimated_monthly_cost_usd: 9.50

4. [AWS] Idle NAT Gateway
   Risk       : Medium
   Confidence : Medium
   Resource   : aws.ec2.nat_gateway → nat-0abcdef1234567890
   Region     : us-west-2
   Rule       : aws.ec2.nat_gateway.idle
   Reason     : No traffic detected for 21 days
   Detected   : 2026-02-08T14:32:04+00:00
   Details:
     - name: staging-nat
     - state: available
     - vpc_id: vpc-0abc123
     - total_bytes_out: 0
     - total_bytes_in: 0
     - estimated_monthly_cost_usd: 32.40
     - idle_threshold_days: 14

5. [AWS] Old AMI
   Risk       : Low
   Confidence : Medium
   Resource   : aws.ec2.ami → ami-0fedcba9876543210
   Region     : us-east-1
   Rule       : aws.ec2.ami.old
   Reason     : AMI is 243 days old with 3 associated snapshots (85.0 GB)
   Detected   : 2026-02-08T14:32:05+00:00
   Details:
     - ami_name: backend-v2.3.1-2025-06-10
     - age_days: 243
     - snapshot_count: 3
     - total_size_gb: 85.0
     - estimated_monthly_cost_usd: 4.25

6. [AWS] Unattached Elastic IP
   Risk       : Low
   Confidence : High
   Resource   : aws.ec2.elastic_ip → eipalloc-0a1b2c3d4e5f6
   Region     : eu-west-1
   Rule       : aws.ec2.elastic_ip.unattached
   Reason     : Elastic IP not associated with any instance or ENI (age: 92 days)
   Detected   : 2026-02-08T14:32:06+00:00
   Details:
     - public_ip: 52.18.xxx.xxx
     - domain: vpc
     - age_days: 92

7. [AWS] Unused Security Group
   Risk       : Low
   Confidence : High
   Resource   : aws.ec2.security_group → sg-0abc123def456789
   Region     : us-east-1
   Rule       : aws.ec2.security_group.unused
   Reason     : Security group has no ENI associations
   Detected   : 2026-02-08T14:32:07+00:00
   Details:
     - group_name: legacy-api-sg
     - vpc_id: vpc-0abc123
     - tags: {"Project": "legacy-api"}

8. [AWS] CloudWatch Log Group with Infinite Retention
   Risk       : Low
   Confidence : Medium
   Resource   : aws.cloudwatch.log_group → /aws/lambda/legacy-processor
   Region     : us-east-1
   Rule       : aws.cloudwatch.logs.infinite_retention
   Reason     : Log group has no retention policy (never expires)
   Detected   : 2026-02-08T14:32:08+00:00
   Details:
     - stored_bytes: 8745213952
     - retention_days: Never expires

9. [AWS] Untagged Resource
   Risk       : Low
   Confidence : Medium
   Resource   : aws.s3.bucket → company-temp-uploads-2024
   Region     : global
   Rule       : aws.resource.untagged
   Reason     : S3 bucket has no tags
   Detected   : 2026-02-08T14:32:09+00:00

--- Scan Summary ---
Total findings: 9

By risk:
  low: 7
  medium: 2

By confidence:
  high: 5
  medium: 4

Minimum estimated waste: ~$206/month
(6 of 9 findings costed)
Regions scanned: us-east-1, us-west-2, eu-west-1 (auto-detected)
Scanned at: 2026-02-08T14:32:09+00:00

Scan — Azure (Human-Readable)

cleancloud scan --provider azure

Found 7 hygiene issues:

1. [AZURE] Unattached Managed Disk
   Risk       : Low
   Confidence : Medium
   Resource   : azure.compute.disk → data-disk-legacy-api
   Region     : eastus
   Rule       : azure.unattached_disk
   Reason     : Managed disk not attached to any VM (age: 34 days)
   Detected   : 2026-02-08T14:45:12+00:00
   Details:
     - size_gb: 256
     - disk_state: Unattached
     - subscription: Production

2. [AZURE] Unused Public IP
   Risk       : Low
   Confidence : High
   Resource   : azure.network.public_ip → pip-old-gateway
   Region     : westeurope
   Rule       : azure.network.public_ip.unused
   Reason     : Public IP not associated with any resource
   Detected   : 2026-02-08T14:45:13+00:00
   Details:
     - ip_address: 20.82.xxx.xxx
     - allocation_method: Static
     - subscription: Staging

3. [AZURE] Load Balancer with No Backends
   Risk       : Medium
   Confidence : High
   Resource   : azure.network.load_balancer → lb-deprecated-service
   Region     : eastus
   Rule       : azure.lb_no_backends
   Reason     : Load balancer has no backend pools configured
   Detected   : 2026-02-08T14:45:14+00:00
   Details:
     - sku: Standard
     - subscription: Production

4. [AZURE] Empty App Service Plan
   Risk       : Low
   Confidence : High
   Resource   : azure.web.app_service_plan → plan-old-staging
   Region     : eastus2
   Rule       : azure.app_service_plan_empty
   Reason     : App Service Plan has no associated web apps
   Detected   : 2026-02-08T14:45:15+00:00
   Details:
     - sku: P1v3
     - subscription: Staging

5. [AZURE] Idle App Service (No Requests for 14+ Days)
   Risk       : Medium
   Confidence : High
   Resource   : azure.app_service → /subscriptions/.../sites/api-legacy-v1
   Region     : uksouth
   Rule       : azure.app_service.idle
   Reason     : App Service has zero HTTP requests for 14+ days
   Detected   : 2026-02-08T14:45:17+00:00
   Details:
     - app_name: api-legacy-v1
     - kind: app
     - sku_tier: Standard
     - days_idle_threshold: 14
     - subscription: Production

6. [AZURE] Unused Container Registry (90+ Days No Pulls or Pushes)
   Risk       : Low
   Confidence : High
   Resource   : azure.container_registry → /subscriptions/.../registries/acr-old-project
   Region     : eastus
   Rule       : azure.container_registry.unused
   Reason     : SuccessfulPullCount and SuccessfulPushCount both evaluated to ZERO over a 90-day window
   Detected   : 2026-02-08T14:45:18+00:00
   Details:
      - registry_name: acr-old-project
      - sku: Standard
      - created_at: 2025-08-01T00:00:00+00:00
      - days_unused_threshold: 90
      - subscription: Staging

7. [AZURE] Untagged Resource
   Risk       : Low
   Confidence : Medium
   Resource   : azure.compute.disk → temp-migration-disk
   Region     : eastus
   Rule       : azure.resource.untagged
   Reason     : Resource has no tags
   Detected   : 2026-02-08T14:45:19+00:00

--- Scan Summary ---
Total findings: 7

By risk:
  low: 5
  medium: 2

By confidence:
  high: 5
  medium: 2

Minimum estimated waste: ~$165/month
(5 of 7 findings costed)
Subscriptions scanned: Production, Staging (all accessible)
Scanned at: 2026-02-08T14:45:19+00:00

Scan — AWS (JSON)

cleancloud scan --provider aws --all-regions --output json --output-file results.json

{
  "schema_version": "1.3.0",
  "summary": {
    "total_findings": 9,
    "by_provider": { "aws": 9 },
    "by_risk": { "low": 7, "medium": 2 },
    "by_confidence": { "high": 5, "medium": 4 },
    "minimum_estimated_monthly_waste_usd": 206.15,
    "findings_with_cost_estimate": 6,
    "highest_confidence": "high",
    "high_conf_findings": 5,
    "regions_scanned": ["us-east-1", "us-west-2", "eu-west-1"],
    "region_selection_mode": "all-regions",
    "provider": "aws",
    "scanned_at": "2026-02-08T14:32:09+00:00"
  },
  "findings": [
    {
      "provider": "aws",
      "rule_id": "aws.ec2.instance.stopped",
      "resource_type": "aws.ec2.instance",
      "resource_id": "i-0abc1234567890def",
      "region": "us-east-1",
      "title": "Stopped EC2 Instance (30+ Days)",
      "summary": "EC2 instance has been stopped for 45 days — EBS charges continue accruing",
      "reason": "Instance has been stopped for 45 days (EBS charges continue)",
      "risk": "medium",
      "confidence": "high",
      "detected_at": "2026-02-08T14:32:01+00:00",
      "details": {
        "instance_type": "t3.large",
        "stopped_days": 45,
        "ebs_volume_count": 2,
        "ebs_total_gb": 120,
        "tags": { "Environment": "staging", "Owner": "backend-team" }
      },
      "estimated_monthly_cost_usd": 12.00,
      "evidence": {
        "signals_used": [
          "Instance state is 'stopped' for 45 days",
          "EBS volumes attached — storage charges continue",
          "2 attached EBS volumes totalling 120 GB"
        ],
        "signals_not_checked": [
          "Intentional pause for cost-saving during off-hours",
          "IaC-managed lifecycle",
          "Pending reactivation"
        ],
        "time_window": "30 days"
      }
    },
    {
      "provider": "aws",
      "rule_id": "aws.ebs.unattached",
      "resource_type": "aws.ebs.volume",
      "resource_id": "vol-0a1b2c3d4e5f67890",
      "region": "us-east-1",
      "title": "Unattached EBS Volume",
      "summary": "EBS volume has been unattached for 47 days",
      "reason": "Volume has been unattached for 47 days",
      "risk": "low",
      "confidence": "high",
      "detected_at": "2026-02-08T14:32:01+00:00",
      "details": {
        "size_gb": 500,
        "availability_zone": "us-east-1a",
        "state": "available",
        "tags": { "Project": "legacy-api", "Owner": "platform" }
      },
      "evidence": {
        "signals_used": [
          "Volume state is 'available' (not attached to any instance)",
          "Volume has been unattached for 47 days"
        ],
        "signals_not_checked": [
          "Application-level usage intent",
          "IaC-managed lifecycle",
          "Disaster recovery purpose"
        ],
        "time_window": "30 days"
      }
    },
    {
      "provider": "aws",
      "rule_id": "aws.ec2.nat_gateway.idle",
      "resource_type": "aws.ec2.nat_gateway",
      "resource_id": "nat-0abcdef1234567890",
      "region": "us-west-2",
      "title": "Idle NAT Gateway",
      "summary": "NAT Gateway with zero traffic for 21 days",
      "reason": "No traffic detected for 21 days",
      "risk": "medium",
      "confidence": "medium",
      "detected_at": "2026-02-08T14:32:04+00:00",
      "details": {
        "name": "staging-nat",
        "state": "available",
        "vpc_id": "vpc-0abc123",
        "total_bytes_out": 0,
        "total_bytes_in": 0,
        "estimated_monthly_cost_usd": 32.40,
        "idle_threshold_days": 14
      },
      "evidence": {
        "signals_used": [
          "Zero bytes transferred (in and out) over 14-day CloudWatch window",
          "NAT Gateway has been idle for 21 days"
        ],
        "signals_not_checked": [
          "Future provisioning intent",
          "IaC-managed lifecycle"
        ],
        "time_window": "14 days"
      }
    },
    {
      "provider": "aws",
      "rule_id": "aws.ec2.ami.old",
      "resource_type": "aws.ec2.ami",
      "resource_id": "ami-0fedcba9876543210",
      "region": "us-east-1",
      "title": "Old AMI",
      "summary": "AMI is 243 days old with 3 associated snapshots (85.0 GB)",
      "reason": "AMI is 243 days old with 3 associated snapshots (85.0 GB)",
      "risk": "low",
      "confidence": "medium",
      "detected_at": "2026-02-08T14:32:05+00:00",
      "details": {
        "ami_name": "backend-v2.3.1-2025-06-10",
        "age_days": 243,
        "snapshot_count": 3,
        "total_size_gb": 85.0,
        "estimated_monthly_cost_usd": 4.25
      },
      "evidence": {
        "signals_used": [
          "AMI age exceeds 180-day threshold",
          "AMI has associated snapshots incurring storage costs"
        ],
        "signals_not_checked": [
          "Whether AMI is referenced in launch templates",
          "IaC-managed lifecycle",
          "Disaster recovery or compliance retention"
        ],
        "time_window": "180 days"
      }
    },
    {
      "provider": "aws",
      "rule_id": "aws.ec2.elastic_ip.unattached",
      "resource_type": "aws.ec2.elastic_ip",
      "resource_id": "eipalloc-0a1b2c3d4e5f6",
      "region": "eu-west-1",
      "title": "Unattached Elastic IP",
      "summary": "Elastic IP not associated with any instance or ENI",
      "reason": "Elastic IP not associated with any instance or ENI (age: 92 days)",
      "risk": "low",
      "confidence": "high",
      "detected_at": "2026-02-08T14:32:06+00:00",
      "details": {
        "public_ip": "52.18.xxx.xxx",
        "domain": "vpc",
        "age_days": 92
      },
      "evidence": {
        "signals_used": [
          "Elastic IP has no association (no instance or ENI)",
          "EIP has been unattached for 92 days"
        ],
        "signals_not_checked": [
          "DNS records pointing to this IP",
          "IaC-managed lifecycle"
        ],
        "time_window": "30 days"
      }
    },
    {
      "provider": "aws",
      "rule_id": "aws.rds.snapshot.old",
      "resource_type": "aws.rds.snapshot",
      "resource_id": "rds:db-prod-2025-08-01-00-00",
      "region": "us-east-1",
      "title": "Old RDS Snapshot",
      "summary": "Manual RDS snapshot is 191 days old — no longer needed for point-in-time recovery",
      "reason": "Manual RDS snapshot is 191 days old",
      "risk": "low",
      "confidence": "high",
      "detected_at": "2026-02-08T14:32:07+00:00",
      "details": {
        "db_instance_identifier": "db-prod",
        "age_days": 191,
        "allocated_storage_gb": 100,
        "engine": "postgres"
      },
      "estimated_monthly_cost_usd": 9.50,
      "evidence": {
        "signals_used": [
          "Snapshot age (191 days) exceeds 90-day threshold",
          "Manual snapshot — not managed by automated backup retention",
          "Allocated storage: 100 GB (ceiling estimate; incremental snapshots may be smaller)"
        ],
        "signals_not_checked": [
          "Compliance or audit retention requirements",
          "Whether snapshot is referenced by a restore procedure"
        ],
        "time_window": "90 days"
      }
    },
    {
      "provider": "aws",
      "rule_id": "aws.ec2.security_group.unused",
      "resource_type": "aws.ec2.security_group",
      "resource_id": "sg-0abc123def456789",
      "region": "us-east-1",
      "title": "Unused Security Group",
      "summary": "Security group has no ENI associations",
      "reason": "Security group has no ENI associations",
      "risk": "low",
      "confidence": "high",
      "detected_at": "2026-02-08T14:32:08+00:00",
      "details": {
        "group_name": "legacy-api-sg",
        "vpc_id": "vpc-0abc123",
        "tags": { "Project": "legacy-api" }
      },
      "evidence": {
        "signals_used": [
          "Security group has zero network interface associations"
        ],
        "signals_not_checked": [
          "Referenced in launch templates or IaC",
          "Intentional placeholder for future use"
        ],
        "time_window": null
      }
    },
    {
      "provider": "aws",
      "rule_id": "aws.cloudwatch.logs.infinite_retention",
      "resource_type": "aws.cloudwatch.log_group",
      "resource_id": "/aws/lambda/legacy-processor",
      "region": "us-east-1",
      "title": "CloudWatch Log Group with Infinite Retention",
      "summary": "Log group has no retention policy (never expires)",
      "reason": "Log group has no retention policy (never expires)",
      "risk": "low",
      "confidence": "medium",
      "detected_at": "2026-02-08T14:32:07+00:00",
      "details": {
        "stored_bytes": 8745213952,
        "retention_days": null
      },
      "evidence": {
        "signals_used": [
          "Log group retention is set to 'Never expire'",
          "Log group has significant stored data (8.1 GB)"
        ],
        "signals_not_checked": [
          "Compliance or audit retention requirements",
          "Active log ingestion rate"
        ],
        "time_window": null
      }
    },
    {
      "provider": "aws",
      "rule_id": "aws.resource.untagged",
      "resource_type": "aws.s3.bucket",
      "resource_id": "company-temp-uploads-2024",
      "region": null,
      "title": "Untagged Resource",
      "summary": "S3 bucket has no tags",
      "reason": "S3 bucket has no tags",
      "risk": "low",
      "confidence": "medium",
      "detected_at": "2026-02-08T14:32:08+00:00",
      "details": {},
      "evidence": {
        "signals_used": [
          "Resource has zero tags attached"
        ],
        "signals_not_checked": [
          "Whether resource is managed by IaC with tags defined elsewhere",
          "Organizational tagging policy exceptions"
        ],
        "time_window": null
      }
    }
  ]
}

Scan — Azure (JSON)

cleancloud scan --provider azure --output json --output-file results.json

{
  "schema_version": "1.3.0",
  "summary": {
    "total_findings": 7,
    "by_provider": { "azure": 7 },
    "by_risk": { "low": 5, "medium": 2 },
    "by_confidence": { "high": 5, "medium": 2 },
    "minimum_estimated_monthly_waste_usd": 165.00,
    "findings_with_cost_estimate": 5,
    "highest_confidence": "high",
    "high_conf_findings": 5,
    "regions_scanned": ["eastus", "eastus2", "uksouth", "westeurope"],
    "subscriptions_scanned": [
      "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "f9e8d7c6-b5a4-3210-fedc-ba0987654321"
    ],
    "subscription_selection_mode": "all",
    "provider": "azure",
    "scanned_at": "2026-02-08T14:45:19+00:00"
  },
  "findings": [
    {
      "provider": "azure",
      "rule_id": "azure.unattached_disk",
      "resource_type": "azure.compute.disk",
      "resource_id": "/subscriptions/a1b2c3d4-e5f6-7890-abcd-ef1234567890/resourceGroups/rg-legacy/providers/Microsoft.Compute/disks/data-disk-legacy-api",
      "region": "eastus",
      "title": "Unattached Managed Disk",
      "summary": "Managed disk not attached to any VM",
      "reason": "Managed disk not attached to any VM (age: 34 days)",
      "risk": "low",
      "confidence": "medium",
      "detected_at": "2026-02-08T14:45:12+00:00",
      "details": {
        "size_gb": 256,
        "disk_state": "Unattached",
        "subscription": "Production"
      },
      "evidence": {
        "signals_used": [
          "Disk state is 'Unattached'",
          "Disk has been unattached for 34 days"
        ],
        "signals_not_checked": [
          "Application-level usage intent",
          "IaC-managed lifecycle",
          "Disaster recovery purpose"
        ],
        "time_window": "30 days"
      }
    },
    {
      "provider": "azure",
      "rule_id": "azure.network.public_ip.unused",
      "resource_type": "azure.network.public_ip",
      "resource_id": "/subscriptions/f9e8d7c6-b5a4-3210-fedc-ba0987654321/resourceGroups/rg-staging/providers/Microsoft.Network/publicIPAddresses/pip-old-gateway",
      "region": "westeurope",
      "title": "Unused Public IP",
      "summary": "Public IP not associated with any resource",
      "reason": "Public IP not associated with any resource",
      "risk": "low",
      "confidence": "high",
      "detected_at": "2026-02-08T14:45:13+00:00",
      "details": {
        "ip_address": "20.82.xxx.xxx",
        "allocation_method": "Static",
        "subscription": "Staging"
      },
      "evidence": {
        "signals_used": [
          "Public IP has no ip_configuration (not attached to any resource)"
        ],
        "signals_not_checked": [
          "DNS records pointing to this IP",
          "IaC-managed lifecycle"
        ],
        "time_window": null
      }
    },
    {
      "provider": "azure",
      "rule_id": "azure.lb_no_backends",
      "resource_type": "azure.network.load_balancer",
      "resource_id": "/subscriptions/a1b2c3d4-e5f6-7890-abcd-ef1234567890/resourceGroups/rg-prod/providers/Microsoft.Network/loadBalancers/lb-deprecated-service",
      "region": "eastus",
      "title": "Load Balancer with No Backends",
      "summary": "Load balancer has no backend pools configured",
      "reason": "Load balancer has no backend pools configured",
      "risk": "medium",
      "confidence": "high",
      "detected_at": "2026-02-08T14:45:14+00:00",
      "details": {
        "sku": "Standard",
        "subscription": "Production"
      },
      "evidence": {
        "signals_used": [
          "Load balancer has zero backend pools configured"
        ],
        "signals_not_checked": [
          "Whether backend pools are being provisioned",
          "IaC-managed lifecycle"
        ],
        "time_window": null
      }
    },
    {
      "provider": "azure",
      "rule_id": "azure.app_service_plan_empty",
      "resource_type": "azure.web.app_service_plan",
      "resource_id": "/subscriptions/f9e8d7c6-b5a4-3210-fedc-ba0987654321/resourceGroups/rg-staging/providers/Microsoft.Web/serverfarms/plan-old-staging",
      "region": "eastus2",
      "title": "Empty App Service Plan",
      "summary": "App Service Plan has no associated web apps",
      "reason": "App Service Plan has no associated web apps",
      "risk": "low",
      "confidence": "high",
      "detected_at": "2026-02-08T14:45:15+00:00",
      "details": {
        "sku": "P1v3",
        "subscription": "Staging"
      },
      "evidence": {
        "signals_used": [
          "App Service Plan has zero web apps associated"
        ],
        "signals_not_checked": [
          "Whether apps are being deployed to this plan",
          "IaC-managed lifecycle"
        ],
        "time_window": null
      }
    },
    {
      "provider": "azure",
      "rule_id": "azure.app_service.idle",
      "resource_type": "azure.app_service",
      "resource_id": "/subscriptions/a1b2c3d4-e5f6-7890-abcd-ef1234567890/resourceGroups/rg-prod/providers/Microsoft.Web/sites/api-legacy-v1",
      "region": "uksouth",
      "title": "Idle App Service (No Requests for 14+ Days)",
      "summary": "App Service 'api-legacy-v1' (Standard) has received zero HTTP requests for 14+ days but continues to accrue compute charges.",
      "reason": "App Service has zero HTTP requests for 14+ days",
      "risk": "medium",
      "confidence": "high",
      "detected_at": "2026-02-08T14:45:17+00:00",
      "details": {
        "app_name": "api-legacy-v1",
        "kind": "app",
        "sku_tier": "Standard",
        "location": "uksouth",
        "days_idle_threshold": 14
      },
      "estimated_monthly_cost_usd": 73.0,
      "evidence": {
        "signals_used": [
          "Zero HTTP requests for 14 days (Azure Monitor: Requests metric)",
          "App state: Running",
          "App Service Plan tier: Standard",
          "App Service Plan tier 'Standard' costs ~$73/month per instance"
        ],
        "signals_not_checked": [
          "Non-HTTP workloads (WebJobs, background services)",
          "Planned reactivation or seasonal use",
          "IaC-managed placeholder deployment",
          "Blue/green deployment staging slot"
        ],
        "time_window": "14 days"
      }
    },
    {
      "provider": "azure",
      "rule_id": "azure.container_registry.unused",
      "resource_type": "azure.container_registry",
      "resource_id": "/subscriptions/f9e8d7c6-b5a4-3210-fedc-ba0987654321/resourceGroups/rg-staging/providers/Microsoft.ContainerRegistry/registries/acr-old-project",
      "region": "eastus",
      "title": "Unused Container Registry (90+ Days No Pulls or Pushes)",
      "summary": "Container Registry 'acr-old-project' (Standard) has had no successful pulls or pushes for 90+ days.",
      "reason": "SuccessfulPullCount and SuccessfulPushCount both evaluated to ZERO over a 90-day window",
      "risk": "low",
      "confidence": "high",
      "detected_at": "2026-02-08T14:45:18+00:00",
      "details": {
        "registry_name": "acr-old-project",
        "sku": "Standard",
        "location": "eastus",
        "created_at": "2025-08-01T00:00:00+00:00",
        "days_unused_threshold": 90
      },
      "estimated_monthly_cost_usd": 20.0,
      "evidence": {
        "signals_used": [
          "Registry creation date satisfies properties.creationDate <= window_start",
          "SuccessfulPullCount and SuccessfulPushCount both evaluated to ZERO for the 90-day window",
          "Registry SKU: Standard",
          "ACR Standard tier costs ~$20/month plus storage"
        ],
        "signals_not_checked": [
          "Planned reactivation or migration intent",
          "Images referenced by stopped or undeployed workloads",
          "Failed pull or login attempts not treated as active use",
          "Storage charges not included in estimated base monthly cost"
        ],
        "time_window": "90 days"
      }
    },
    {
      "provider": "azure",
      "rule_id": "azure.resource.untagged",
      "resource_type": "azure.compute.disk",
      "resource_id": "/subscriptions/a1b2c3d4-e5f6-7890-abcd-ef1234567890/resourceGroups/rg-legacy/providers/Microsoft.Compute/disks/temp-migration-disk",
      "region": "eastus",
      "title": "Untagged Resource",
      "summary": "Resource has no tags",
      "reason": "Resource has no tags",
      "risk": "low",
      "confidence": "medium",
      "detected_at": "2026-02-08T14:45:19+00:00",
      "details": {},
      "evidence": {
        "signals_used": [
          "Resource has zero tags attached"
        ],
        "signals_not_checked": [
          "Whether resource is managed by IaC with tags defined elsewhere",
          "Organizational tagging policy exceptions"
        ],
        "time_window": null
      }
    }
  ]
}

Scan — AWS (Markdown)

cleancloud scan --provider aws --all-regions --output markdown

Produces a grouped, cost-sorted summary you can paste directly into a GitHub PR comment, Slack message, or issue. Use --output-file results.md to save to a file instead.

## CleanCloud Scan Results

**Provider:** AWS
**Regions:** us-east-1, us-west-2, eu-west-1
**Scanned:** 2026-02-08
**Estimated monthly waste:** ~$206

**Total findings:** 9

| Finding | Count | Est. Monthly Cost |
|---------|------:|------------------:|
| Idle NAT Gateway | 1 | ~$32 |
| Unattached EBS Volume | 2 | ~$115 |
| Old RDS Snapshot | 1 | ~$10 |
| Stopped EC2 Instance | 1 | ~$12 |
| Old AMI | 1 | ~$4 |
| Unattached Elastic IP | 1 | ~$0 |
| Unused Security Group | 1 ||
| CloudWatch Log Group: Infinite Retention | 1 ||
| Untagged Resource | 1 ||

**Confidence:** high: 5 · medium: 4

> Generated by [CleanCloud](https://github.com/cleancloud-io/cleancloud) — read-only cloud hygiene scanner for AWS, Azure, and GCP.

Findings are grouped by title (multiple instances of the same finding type are collapsed into one row with a count) and sorted by estimated cost descending.


Scan — Azure (Markdown)

cleancloud scan --provider azure --output markdown

## CleanCloud Scan Results

**Provider:** AZURE
**Subscriptions:** Production, Staging
**Scanned:** 2026-02-08
**Estimated monthly waste:** ~$165

**Total findings:** 7

| Finding | Count | Est. Monthly Cost |
|---------|------:|------------------:|
| Empty App Service Plan | 1 | ~$146 |
| Idle App Service | 1 | ~$73 |
| Load Balancer with No Backends | 1 | ~$18 |
| Unused Container Registry | 1 | ~$20 |
| Unused Public IP | 1 | ~$4 |
| Unattached Managed Disk | 1 ||
| Untagged Resource | 1 ||

**Confidence:** high: 5 · medium: 2

> Generated by [CleanCloud](https://github.com/cleancloud-io/cleancloud) — read-only cloud hygiene scanner for AWS, Azure, and GCP.

For Azure, the Subscriptions field is shown instead of Regions, reflecting how Azure scans are scoped.


Scan — GCP (Human-Readable)

cleancloud scan --provider gcp --all-projects

Found 5 hygiene issues:

1. [GCP] Stopped VM Instance (30+ Days)
   Risk       : Medium
   Confidence : High
   Resource   : gcp.compute.instance → projects/my-project/zones/us-central1-a/instances/old-worker-1
   Region     : us-central1-a
   Rule       : gcp.compute.vm.stopped
   Reason     : VM instance has been stopped for 52 days (disk charges continue)
   Detected   : 2026-02-08T15:00:01+00:00
   Details:
     - machine_type: n1-standard-4
     - stopped_days: 52
     - disk_count: 2
     - total_disk_gb: 200
     - estimated_monthly_cost_usd: 16.00
     - labels: {"env": "staging", "team": "data"}

2. [GCP] Unattached Persistent Disk
   Risk       : Low
   Confidence : High
   Resource   : gcp.compute.disk → projects/my-project/zones/us-central1-b/disks/data-disk-old
   Region     : us-central1-b
   Rule       : gcp.compute.disk.unattached
   Reason     : Persistent disk has been unattached for 38 days
   Detected   : 2026-02-08T15:00:02+00:00
   Details:
     - size_gb: 500
     - type: pd-ssd
     - status: READY
     - estimated_monthly_cost_usd: 85.00
     - labels: {"project": "legacy-pipeline"}

3. [GCP] Unused Reserved Static IP
   Risk       : Low
   Confidence : High
   Resource   : gcp.compute.address → projects/my-project/regions/us-central1/addresses/old-loadbalancer-ip
   Region     : us-central1
   Rule       : gcp.compute.ip.unused
   Reason     : Static IP is reserved but not attached to any resource
   Detected   : 2026-02-08T15:00:03+00:00
   Details:
     - address: 34.123.xxx.xxx
     - address_type: EXTERNAL
     - status: RESERVED
     - labels: {}

4. [GCP] Old Disk Snapshot
   Risk       : Low
   Confidence : High
   Resource   : gcp.compute.snapshot → projects/my-project/global/snapshots/disk-backup-2025-08-01
   Region     : global
   Rule       : gcp.compute.snapshot.old
   Reason     : Disk snapshot is 191 days old
   Detected   : 2026-02-08T15:00:04+00:00
   Details:
     - source_disk: projects/my-project/zones/us-central1-a/disks/app-disk
     - age_days: 191
     - storage_bytes: 53687091200
     - estimated_monthly_cost_usd: 2.15
     - labels: {}

5. [GCP] Idle Cloud SQL Instance
   Risk       : Medium
   Confidence : High
   Resource   : gcp.sql.instance → projects/my-project/instances/db-staging
   Region     : us-central1
   Rule       : gcp.sql.instance.idle
   Reason     : Cloud SQL instance has zero connections for 14+ days
   Detected   : 2026-02-08T15:00:05+00:00
   Details:
     - database_version: POSTGRES_14
     - tier: db-n1-standard-2
     - estimated_monthly_cost_usd: 100.00
     - labels: {"env": "staging"}

--- Scan Summary ---
Rules executed: 5/5
Total findings: 5

By risk:
  low: 3
  medium: 2

By confidence:
  high: 5

Minimum estimated waste: ~$203/month
(4 of 5 findings costed)
Projects scanned: my-project (all accessible)
Scanned at: 2026-02-08T15:00:05+00:00

For GCP, the Projects scanned field is shown instead of Regions, and findings use labels instead of tags in details.


Scan — GCP (JSON)

cleancloud scan --provider gcp --all-projects --output json --output-file results.json

{
  "schema_version": "1.3.0",
  "summary": {
    "total_findings": 5,
    "by_provider": { "gcp": 5 },
    "by_risk": { "low": 3, "medium": 2 },
    "by_confidence": { "high": 5 },
    "minimum_estimated_monthly_waste_usd": 203.15,
    "findings_with_cost_estimate": 4,
    "highest_confidence": "high",
    "high_conf_findings": 5,
    "projects_scanned": ["my-project"],
    "project_selection_mode": "all",
    "total_rules": 5,
    "provider": "gcp",
    "scanned_at": "2026-02-08T15:00:05+00:00"
  },
  "findings": [
    {
      "provider": "gcp",
      "rule_id": "gcp.compute.disk.unattached",
      "resource_type": "gcp.compute.disk",
      "resource_id": "projects/my-project/zones/us-central1-b/disks/data-disk-old",
      "region": "us-central1-b",
      "title": "Unattached Persistent Disk",
      "summary": "Persistent disk has been unattached for 38 days",
      "reason": "Persistent disk has been unattached for 38 days",
      "risk": "low",
      "confidence": "high",
      "detected_at": "2026-02-08T15:00:02+00:00",
      "details": {
        "size_gb": 500,
        "type": "pd-ssd",
        "status": "READY",
        "labels": { "project": "legacy-pipeline" }
      },
      "estimated_monthly_cost_usd": 85.00,
      "evidence": {
        "signals_used": [
          "Disk status is READY with no users (not attached to any instance)",
          "Disk has been unattached for 38 days"
        ],
        "signals_not_checked": [
          "Application-level usage intent",
          "IaC-managed lifecycle",
          "Disaster recovery purpose"
        ],
        "time_window": "30 days"
      }
    },
    {
      "provider": "gcp",
      "rule_id": "gcp.sql.instance.idle",
      "resource_type": "gcp.sql.instance",
      "resource_id": "projects/my-project/instances/db-staging",
      "region": "us-central1",
      "title": "Idle Cloud SQL Instance",
      "summary": "Cloud SQL instance has zero connections for 14+ days",
      "reason": "Cloud SQL instance has zero connections for 14+ days",
      "risk": "medium",
      "confidence": "high",
      "detected_at": "2026-02-08T15:00:05+00:00",
      "details": {
        "database_version": "POSTGRES_14",
        "tier": "db-n1-standard-2",
        "labels": { "env": "staging" }
      },
      "estimated_monthly_cost_usd": 100.00,
      "evidence": {
        "signals_used": [
          "Zero database connections over 14-day Cloud Monitoring window",
          "Instance state: RUNNABLE"
        ],
        "signals_not_checked": [
          "Application-level connection poolers that may suppress direct connections",
          "Planned reactivation or seasonal workload"
        ],
        "time_window": "14 days"
      }
    }
  ]
}

Scan — GCP (Markdown)

cleancloud scan --provider gcp --all-projects --output markdown

## CleanCloud Scan Results

**Provider:** GCP
**Projects:** my-project
**Scanned:** 2026-02-08
**Estimated monthly waste:** ~$203

**Total findings:** 5

| Finding | Count | Est. Monthly Cost |
|---------|------:|------------------:|
| Unattached Persistent Disk | 1 | ~$85 |
| Idle Cloud SQL Instance | 1 | ~$100 |
| Old Disk Snapshot | 1 | ~$2 |
| Stopped VM Instance | 1 | ~$16 |
| Unused Reserved Static IP | 1 | ~$0 |

**Confidence:** high: 5

> Generated by [CleanCloud](https://github.com/cleancloud-io/cleancloud) — read-only cloud hygiene scanner for AWS, Azure, and GCP.

For GCP, the Projects field is shown instead of Regions or Subscriptions.


JSON Schema Reference

CleanCloud uses a versioned JSON schema (current: 1.3.0). All JSON output includes a schema_version field for backward compatibility.

Key differences between AWS, Azure, and GCP JSON output:

Field AWS Azure GCP
region_selection_mode "explicit" or "all-regions" Not present Not present
regions_scanned Array of region strings Array of region strings Not present
subscription_selection_mode Not present "explicit", "all", or "management-group" Not present
subscriptions_scanned Not present Array of subscription IDs Not present
project_selection_mode Not present Not present "explicit" or "all"
projects_scanned Not present Not present Array of project IDs
resource_id Short ID (e.g., vol-0abc123) Full ARM resource ID Full resource path (e.g., projects/p/zones/z/disks/d)
details.tags Present Present Not present
details.labels Not present Not present Present