<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Suresh</title>
    <description>The latest articles on Forem by Suresh (@sureshmandalapu).</description>
    <link>https://forem.com/sureshmandalapu</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3685594%2Fc9b267ea-3265-4dc1-ae53-6af5ec0a39d4.png</url>
      <title>Forem: Suresh</title>
      <link>https://forem.com/sureshmandalapu</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/sureshmandalapu"/>
    <language>en</language>
    <item>
      <title>CleanCloud v0.4.0: How We Made Cloud Hygiene Scanning 10x Faster</title>
      <dc:creator>Suresh</dc:creator>
      <pubDate>Fri, 02 Jan 2026 06:15:44 +0000</pubDate>
      <link>https://forem.com/sureshmandalapu/cleancloud-v040-how-we-made-cloud-hygiene-scanning-10x-faster-with-benchmarkspublished-true-4png</link>
      <guid>https://forem.com/sureshmandalapu/cleancloud-v040-how-we-made-cloud-hygiene-scanning-10x-faster-with-benchmarkspublished-true-4png</guid>
      <description>&lt;p&gt;I just shipped CleanCloud v0.4.0 with major performance improvements through parallel scanning. Here's how we did it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's CleanCloud?
&lt;/h2&gt;

&lt;p&gt;If you missed the &lt;a href="https://dev.to/suresh_564529bdc18d6e32f4/i-built-a-read-only-awsazure-hygiene-scanner-because-auto-delete-is-too-risky-34go"&gt;original announcement&lt;/a&gt;, CleanCloud is a &lt;strong&gt;read-only&lt;/strong&gt; CLI tool that scans AWS/Azure for orphaned resources (unattached volumes, old snapshots, infinite CloudWatch log retention).&lt;/p&gt;

&lt;p&gt;Unlike aggressive cleanup tools, CleanCloud gives you conservative signals so you can review before taking action. No auto-delete, no risk.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Performance Problem
&lt;/h2&gt;

&lt;p&gt;v0.3.x had a bottleneck: &lt;strong&gt;sequential scanning&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Old approach (v0.3.x)
&lt;/span&gt;&lt;span class="n"&gt;findings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;region&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;regions_to_scan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;click&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;echo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;🔍 Scanning region &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;findings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;_scan_aws_region&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; Each region scanned one at a time. For accounts with resources in multiple regions, this added up quickly.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Solution: Parallel Scanning
&lt;/h2&gt;

&lt;p&gt;v0.4.0 introduces &lt;strong&gt;concurrent scanning&lt;/strong&gt; at two levels:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Parallel Region Scanning
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# New approach (v0.4.0)
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;concurrent.futures&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ThreadPoolExecutor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;as_completed&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;scan_aws_regions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;regions_to_scan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Finding&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;findings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;ThreadPoolExecutor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;regions_to_scan&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;futures&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_scan_aws_region&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;region&lt;/span&gt; 
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;region&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;regions_to_scan&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;future&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;as_completed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;futures&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;region&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;futures&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;future&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="n"&gt;click&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;echo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;✅ Completed region &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;findings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;future&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;result&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;findings&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key decisions:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;max_workers=min(5, len(regions_to_scan))&lt;/code&gt; - Limits parallelism to avoid rate limits&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;as_completed()&lt;/code&gt; - Shows progress as regions complete&lt;/li&gt;
&lt;li&gt;Thread-safe result collection&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Parallel Rule Execution
&lt;/h3&gt;

&lt;p&gt;Within each region, we also parallelized individual rules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;AWS_RULES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="n"&gt;find_unattached_ebs_volumes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;find_old_ebs_snapshots&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;find_inactive_cloudwatch_logs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;find_aws_untagged_resources&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_scan_aws_region&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Finding&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;create_aws_session&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;findings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;ThreadPoolExecutor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AWS_RULES&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;futures&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;rule&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;AWS_RULES&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;future&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;as_completed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;futures&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;rule_findings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;future&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;result&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="n"&gt;findings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rule_findings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="c1"&gt;# Never fail entire scan due to one rule
&lt;/span&gt;                &lt;span class="n"&gt;click&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;echo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;⚠️ Rule failed in &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;findings&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Benefits:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All 4 rules run concurrently per region&lt;/li&gt;
&lt;li&gt;Exception isolation (one failing rule doesn't break the scan)&lt;/li&gt;
&lt;li&gt;Better resource utilization&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Performance Improvements
&lt;/h2&gt;

&lt;p&gt;Real-world results from testing:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Single region scan:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Before: ~20-25 seconds&lt;/li&gt;
&lt;li&gt;After: ~15-18 seconds&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Improvement: ~30% faster&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Multi-region scan (5 regions):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Before: ~100-120 seconds (sequential)&lt;/li&gt;
&lt;li&gt;After: ~20-25 seconds (parallel)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Improvement: ~5x faster&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The key insight:&lt;/strong&gt; The more regions you scan, the bigger the improvement. Parallel execution shines when there's actual work to parallelize.&lt;/p&gt;




&lt;h2&gt;
  
  
  Azure Gets the Same Treatment
&lt;/h2&gt;

&lt;p&gt;Azure subscriptions are now scanned in parallel too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;scan_azure_subscriptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;subscription_ids&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;region_filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Finding&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;all_findings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;ThreadPoolExecutor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subscription_ids&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;futures&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;_scan_azure_subscription&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;subscription_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sub_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;credential&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;region_filter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;region_filter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;sub_id&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;sub_id&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;subscription_ids&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;future&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;as_completed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;futures&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;sub_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;futures&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;future&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="n"&gt;click&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;echo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;✅ Completed subscription &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;sub_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;all_findings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;future&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;result&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;click&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;echo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;⚠️ Subscription &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;sub_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; failed: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;all_findings&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Same benefits for Azure users with multiple subscriptions.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Other v0.4.0 Improvements
&lt;/h2&gt;

&lt;h3&gt;
  
  
  🔒 Safety Integration Tests
&lt;/h3&gt;

&lt;p&gt;We now have automated tests that verify CleanCloud's read-only guarantees:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_scan_is_read_only&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Ensure no write operations during scan.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="c1"&gt;# Run full scan
&lt;/span&gt;    &lt;span class="n"&gt;scan_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;scan_all_regions&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Check CloudTrail for write operations
&lt;/span&gt;    &lt;span class="n"&gt;cloudtrail_events&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_recent_events&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;write_events&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cloudtrail_events&lt;/span&gt; 
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;EventName&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;READ_ONLY_OPERATIONS&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;# Fail if ANY writes detected
&lt;/span&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;write_events&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Write operations detected: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;write_events&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These run in CI on every PR against real AWS/Azure accounts. If CleanCloud ever tries to write, the build fails.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why this matters:&lt;/strong&gt; You can trust that CleanCloud is truly read-only, not just claiming to be.&lt;/p&gt;

&lt;h3&gt;
  
  
  🩺 Enhanced Doctor Command
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;cleancloud doctor&lt;/code&gt; command now provides &lt;strong&gt;actionable IAM diagnostics&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cleancloud doctor &lt;span class="nt"&gt;--provider&lt;/span&gt; aws

&lt;span class="c"&gt;# Before (v0.3.x):&lt;/span&gt;
❌ Permission denied

&lt;span class="c"&gt;# After (v0.4.0):&lt;/span&gt;
❌ Missing IAM permission: ec2:DescribeVolumes

Suggested IAM policy:
&lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"Version"&lt;/span&gt;: &lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;,
  &lt;span class="s2"&gt;"Statement"&lt;/span&gt;: &lt;span class="o"&gt;[{&lt;/span&gt;
    &lt;span class="s2"&gt;"Effect"&lt;/span&gt;: &lt;span class="s2"&gt;"Allow"&lt;/span&gt;,
    &lt;span class="s2"&gt;"Action"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ec2:DescribeVolumes"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,
    &lt;span class="s2"&gt;"Resource"&lt;/span&gt;: &lt;span class="s2"&gt;"*"&lt;/span&gt;
  &lt;span class="o"&gt;}]&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Much more helpful for debugging permission issues.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  📊 Post-Scan Feedback
&lt;/h3&gt;

&lt;p&gt;After each scan, you'll see a feedback prompt (disabled in CI/CD with &lt;code&gt;--no-feedback&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--- Scan Summary ---
Total findings: 23

CleanCloud feedback
-------------------
If this scan surfaced useful findings, we'd love to hear about it.

Share feedback: https://github.com/cleancloud-io/cleancloud/discussions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This helps us improve detection rules based on real user feedback.&lt;/p&gt;




&lt;h2&gt;
  
  
  Real-World Impact
&lt;/h2&gt;

&lt;p&gt;Since launch, CleanCloud users have reported finding:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;💰 Cost Savings:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;$8K-12K/year in forgotten CloudWatch logs (infinite retention)&lt;/li&gt;
&lt;li&gt;$500-2K/year in unattached EBS volumes&lt;/li&gt;
&lt;li&gt;$300-1K/year in old snapshots&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;🎯 Common Findings:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;50-100 unattached volumes per account&lt;/li&gt;
&lt;li&gt;100-300 old snapshots from deleted instances&lt;/li&gt;
&lt;li&gt;20-50 log groups with infinite retention&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;⏱️ Time to Value:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scan time: 20-30 seconds (v0.4.0)&lt;/li&gt;
&lt;li&gt;Review time: 5-10 minutes&lt;/li&gt;
&lt;li&gt;First cleanup: Same day&lt;/li&gt;
&lt;li&gt;ROI: Immediate&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Installation &amp;amp; Usage
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install&lt;/span&gt;
pip &lt;span class="nb"&gt;install &lt;/span&gt;cleancloud

&lt;span class="c"&gt;# Scan all active AWS regions (auto-detects which have resources)&lt;/span&gt;
cleancloud scan &lt;span class="nt"&gt;--provider&lt;/span&gt; aws &lt;span class="nt"&gt;--all-regions&lt;/span&gt;

&lt;span class="c"&gt;# Check IAM permissions&lt;/span&gt;
cleancloud doctor &lt;span class="nt"&gt;--provider&lt;/span&gt; aws

&lt;span class="c"&gt;# Scan specific region&lt;/span&gt;
cleancloud scan &lt;span class="nt"&gt;--provider&lt;/span&gt; aws &lt;span class="nt"&gt;--region&lt;/span&gt; us-east-1

&lt;span class="c"&gt;# Scan Azure&lt;/span&gt;
cleancloud scan &lt;span class="nt"&gt;--provider&lt;/span&gt; azure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Example output:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;🔍 Starting CleanCloud scan...

Provider: aws

🔍 Auto-detecting regions with resources...
✓ Found 3 active regions: us-east-1, us-west-2, eu-west-1

✅ Completed region us-east-1
✅ Completed region us-west-2
✅ Completed region eu-west-1

--- Scan Summary ---
Total findings: 47
By confidence: {'HIGH': 12, 'MEDIUM': 23, 'LOW': 12}
Regions scanned: us-east-1, us-west-2, eu-west-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Technical Deep Dive: Threading Challenges
&lt;/h2&gt;

&lt;p&gt;Building the parallel scanning wasn't trivial. Here are some challenges we hit:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Thread Safety with boto3
&lt;/h3&gt;

&lt;p&gt;boto3 clients are &lt;strong&gt;not thread-safe&lt;/strong&gt;. We had to create separate sessions per thread:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_scan_aws_region&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Finding&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="c1"&gt;# Create NEW session per thread
&lt;/span&gt;    &lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;create_aws_session&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Now safe to use in this thread
&lt;/span&gt;    &lt;span class="n"&gt;findings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="c1"&gt;# ... scanning logic
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;findings&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; Never share boto3 clients across threads. Create new sessions per worker.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Rate Limiting
&lt;/h3&gt;

&lt;p&gt;Running 5 regions in parallel meant more concurrent API calls. We had to be smart about worker limits:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Limit parallelism to avoid throttling
&lt;/span&gt;&lt;span class="n"&gt;max_workers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;regions_to_scan&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# Cap at 5 workers
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Also:&lt;/strong&gt; boto3's built-in retry logic with adaptive mode handles most throttling gracefully.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Error Isolation
&lt;/h3&gt;

&lt;p&gt;One region failing shouldn't kill the entire scan:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;future&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;as_completed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;futures&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;rule_findings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;future&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;result&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;findings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rule_findings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Log error but continue
&lt;/span&gt;        &lt;span class="n"&gt;click&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;echo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;⚠️ Rule failed: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; Partial results if some regions fail. Trust-first means never failing the entire scan.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Progress Feedback
&lt;/h3&gt;

&lt;p&gt;Users need to know what's happening during parallel scans:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;future&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;as_completed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;futures&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;region&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;futures&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;future&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;click&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;echo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;✅ Completed region &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Better UX:&lt;/strong&gt; Show progress as regions complete, not just at the end.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Roadmap for v0.5.0:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🌐 &lt;strong&gt;GCP support&lt;/strong&gt; - Extend beyond AWS/Azure&lt;/li&gt;
&lt;li&gt;⚙️ &lt;strong&gt;Configurable thresholds&lt;/strong&gt; - Adjust age/confidence per environment&lt;/li&gt;
&lt;li&gt;💵 &lt;strong&gt;Cost calculations&lt;/strong&gt; - Show potential savings in dollars&lt;/li&gt;
&lt;li&gt;🔗 &lt;strong&gt;CI/CD templates&lt;/strong&gt; - GitHub Actions, GitLab CI examples&lt;/li&gt;
&lt;li&gt;📊 &lt;strong&gt;JSON export improvements&lt;/strong&gt; - Better integration with other tools&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Want to contribute?&lt;/strong&gt; We welcome PRs! Check out the &lt;a href="https://github.com/cleancloud-io/cleancloud/issues" rel="noopener noreferrer"&gt;issues&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Open Source?
&lt;/h2&gt;

&lt;p&gt;CleanCloud is &lt;strong&gt;MIT licensed&lt;/strong&gt; with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Zero telemetry&lt;/li&gt;
&lt;li&gt;✅ No phone-home&lt;/li&gt;
&lt;li&gt;✅ No tracking&lt;/li&gt;
&lt;li&gt;✅ All code visible&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Trust is critical for cloud security tools. Open source means you can verify CleanCloud is truly read-only. No need to trust my promises - read the code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Plus:&lt;/strong&gt; Building in public creates better software through community feedback.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try It Out
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;cleancloud
cleancloud scan &lt;span class="nt"&gt;--provider&lt;/span&gt; aws &lt;span class="nt"&gt;--all-regions&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📦 PyPI: &lt;a href="https://pypi.org/project/cleancloud" rel="noopener noreferrer"&gt;https://pypi.org/project/cleancloud&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💻 GitHub: &lt;a href="https://github.com/cleancloud-io/cleancloud" rel="noopener noreferrer"&gt;https://github.com/cleancloud-io/cleancloud&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📖 Docs: &lt;a href="https://github.com/cleancloud-io/cleancloud#readme" rel="noopener noreferrer"&gt;https://github.com/cleancloud-io/cleancloud#readme&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Feedback Welcome!
&lt;/h2&gt;

&lt;p&gt;What cloud hygiene checks would be useful? What other resources should CleanCloud scan?&lt;/p&gt;

&lt;p&gt;Drop a comment or open an issue on GitHub. Would love to hear what you find! 🚀&lt;/p&gt;

</description>
      <category>azure</category>
      <category>aws</category>
      <category>opensource</category>
      <category>devops</category>
    </item>
    <item>
      <title>I built a read-only AWS/Azure hygiene scanner (because auto-delete is too risky)</title>
      <dc:creator>Suresh</dc:creator>
      <pubDate>Tue, 30 Dec 2025 08:08:51 +0000</pubDate>
      <link>https://forem.com/sureshmandalapu/i-built-a-read-only-awsazure-hygiene-scanner-because-auto-delete-is-too-risky-34go</link>
      <guid>https://forem.com/sureshmandalapu/i-built-a-read-only-awsazure-hygiene-scanner-because-auto-delete-is-too-risky-34go</guid>
      <description>&lt;p&gt;After getting burned by an auto-cleanup tool that deleted a "test" database (it wasn't a test), I built CleanCloud.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Modern cloud environments are messy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Teams spin up resources constantly&lt;/li&gt;
&lt;li&gt;Deployments create and destroy infrastructure&lt;/li&gt;
&lt;li&gt;Resources get orphaned when instances are terminated&lt;/li&gt;
&lt;li&gt;Nobody knows what's safe to delete&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most cloud hygiene tools fall into two camps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Auto-delete everything&lt;/strong&gt; → Too dangerous for production&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flag everything&lt;/strong&gt; → Too noisy to be useful&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Both approaches fail when you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Elastic infrastructure (autoscaling, spot instances)&lt;/li&gt;
&lt;li&gt;Multiple teams with different ownership&lt;/li&gt;
&lt;li&gt;Resources that look unused but are actually important&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Auto-Delete Fails
&lt;/h2&gt;

&lt;p&gt;I learned this the hard way.&lt;/p&gt;

&lt;p&gt;A "smart" cleanup tool we tried:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Saw a database with no connections for 7 days&lt;/li&gt;
&lt;li&gt;Assumed it was orphaned&lt;/li&gt;
&lt;li&gt;Deleted it automatically&lt;/li&gt;
&lt;li&gt;Turned out it was a quarterly reporting database&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cost of that mistake:&lt;/strong&gt; 3 days of recovery, angry CFO, lost trust in automation.&lt;/p&gt;

&lt;p&gt;The blast radius of deleting the wrong resource is &lt;strong&gt;orders of magnitude higher&lt;/strong&gt; than leaving it running for a few more weeks.&lt;/p&gt;

&lt;h2&gt;
  
  
  CleanCloud's Approach: Signal First, Act Later
&lt;/h2&gt;

&lt;p&gt;Instead of automating cleanup, CleanCloud answers a safer question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Which resources deserve human review — and how confident are we?"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Core principles:&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Read-Only Always
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Required AWS permissions - notice no Delete* or Modify*&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"Action"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
    &lt;span class="s2"&gt;"ec2:DescribeVolumes"&lt;/span&gt;,
    &lt;span class="s2"&gt;"ec2:DescribeSnapshots"&lt;/span&gt;,
    &lt;span class="s2"&gt;"logs:DescribeLogGroups"&lt;/span&gt;,
    &lt;span class="s2"&gt;"s3:ListAllMyBuckets"&lt;/span&gt;
  &lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No write permissions. Ever. Safe to run in production.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Conservative Signals
&lt;/h3&gt;

&lt;p&gt;Not just "is this unattached?" but:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How long has it been unattached? (14+ days = HIGH confidence)&lt;/li&gt;
&lt;li&gt;Multiple signals required (age + state + tags)&lt;/li&gt;
&lt;li&gt;Explicit confidence levels: LOW, MEDIUM, HIGH&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;🔴 HIGH confidence: Volume unattached for 45 days
🟡 MEDIUM confidence: Volume unattached for 10 days  
🟢 LOW confidence: Volume unattached for 3 days (probably autoscaling)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Review-Only Recommendations
&lt;/h3&gt;

&lt;p&gt;CleanCloud never says "delete this." It says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"This volume has been unattached for 45 days, has no tags, and doesn't match any known deployment patterns. &lt;strong&gt;Worth reviewing.&lt;/strong&gt;"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Humans make the final call.&lt;/p&gt;

&lt;h2&gt;
  
  
  What It Detects
&lt;/h2&gt;

&lt;h3&gt;
  
  
  AWS Rules (4 currently)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unattached EBS volumes&lt;/strong&gt; (14+ days = HIGH confidence)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Old snapshots&lt;/strong&gt; (365+ days = HIGH confidence)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CloudWatch logs with infinite retention&lt;/strong&gt; (30+ days = HIGH confidence)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Untagged resources&lt;/strong&gt; (ownership unclear = MEDIUM confidence)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Azure Rules (4 currently)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unattached managed disks&lt;/strong&gt; (14+ days = HIGH confidence)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Old snapshots&lt;/strong&gt; (90+ days = HIGH confidence)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unused public IPs&lt;/strong&gt; (immediate = HIGH confidence)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Untagged resources&lt;/strong&gt; (MEDIUM confidence)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Week 1 Results
&lt;/h2&gt;

&lt;p&gt;Released last week. Here's what happened:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stats:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;300+ downloads (170 real users, rest are PyPI mirrors)&lt;/li&gt;
&lt;li&gt;0 production incidents (because read-only!)&lt;/li&gt;
&lt;li&gt;Most common finding: 15-30 unattached EBS volumes per AWS account&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;User feedback themes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Finally, a tool I can trust in production"&lt;/li&gt;
&lt;li&gt;"Found $2K/month in waste in first scan"&lt;/li&gt;
&lt;li&gt;"Love that it explains WHY something was flagged"&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Quick Start
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install&lt;/span&gt;
pip &lt;span class="nb"&gt;install &lt;/span&gt;cleancloud

&lt;span class="c"&gt;# Validate credentials&lt;/span&gt;
cleancloud doctor &lt;span class="nt"&gt;--provider&lt;/span&gt; aws

&lt;span class="c"&gt;# Scan single region&lt;/span&gt;
cleancloud scan &lt;span class="nt"&gt;--provider&lt;/span&gt; aws &lt;span class="nt"&gt;--region&lt;/span&gt; us-east-1

&lt;span class="c"&gt;# Scan all active regions&lt;/span&gt;
cleancloud scan &lt;span class="nt"&gt;--provider&lt;/span&gt; aws &lt;span class="nt"&gt;--all-regions&lt;/span&gt;

&lt;span class="c"&gt;# Output to JSON&lt;/span&gt;
cleancloud scan &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--provider&lt;/span&gt; aws &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--all-regions&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; json &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output-file&lt;/span&gt; results.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Example Output
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;cleancloud scan &lt;span class="nt"&gt;--provider&lt;/span&gt; aws &lt;span class="nt"&gt;--region&lt;/span&gt; us-east-1

🔍 Scanning region us-east-1

Found 12 findings:
  HIGH confidence: 8
  MEDIUM confidence: 4

Top findings:
  • vol-0abc123 - Unattached volume &lt;span class="o"&gt;(&lt;/span&gt;45 days, 100GB&lt;span class="o"&gt;)&lt;/span&gt; - ~&lt;span class="nv"&gt;$10&lt;/span&gt;/mo
  • snap-0def456 - Old snapshot &lt;span class="o"&gt;(&lt;/span&gt;120 days, 500GB&lt;span class="o"&gt;)&lt;/span&gt; - ~&lt;span class="nv"&gt;$25&lt;/span&gt;/mo
  • log-group-xyz - Infinite retention &lt;span class="o"&gt;(&lt;/span&gt;2.1GB stored&lt;span class="o"&gt;)&lt;/span&gt; - ~&lt;span class="nv"&gt;$6&lt;/span&gt;/mo

💰 Estimated monthly waste: ~&lt;span class="nv"&gt;$156&lt;/span&gt;

Review findings and decide what to delete.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  CI/CD Integration
&lt;/h2&gt;

&lt;p&gt;Built for pipelines with predictable exit codes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# GitHub Actions example&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run hygiene scan&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;pip install cleancloud&lt;/span&gt;
    &lt;span class="s"&gt;cleancloud scan \&lt;/span&gt;
      &lt;span class="s"&gt;--provider aws \&lt;/span&gt;
      &lt;span class="s"&gt;--all-regions \&lt;/span&gt;
      &lt;span class="s"&gt;--fail-on-confidence HIGH&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Exit codes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;0&lt;/code&gt; = Success (no policy violations)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;1&lt;/code&gt; = Configuration error&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;2&lt;/code&gt; = Policy violation (findings detected)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;3&lt;/code&gt; = Missing credentials&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Use cases:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Block PRs with HIGH confidence findings&lt;/li&gt;
&lt;li&gt;Generate weekly hygiene reports&lt;/li&gt;
&lt;li&gt;Enforce tagging standards&lt;/li&gt;
&lt;li&gt;Prevent resource leaks in development&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Authentication: OIDC First
&lt;/h2&gt;

&lt;p&gt;No long-lived credentials needed:&lt;/p&gt;

&lt;h3&gt;
  
  
  AWS (GitHub Actions)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Configure AWS credentials (OIDC)&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-actions/configure-aws-credentials@v4&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;role-to-assume&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;arn:aws:iam::ACCOUNT:role/CleanCloudReadOnly&lt;/span&gt;
    &lt;span class="na"&gt;aws-region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;us-east-1&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Scan&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cleancloud scan --provider aws&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Azure (GitHub Actions)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Azure Login (OIDC)&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;azure/login@v2&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;client-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AZURE_CLIENT_ID }}&lt;/span&gt;
    &lt;span class="na"&gt;tenant-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AZURE_TENANT_ID }}&lt;/span&gt;
    &lt;span class="na"&gt;subscription-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AZURE_SUBSCRIPTION_ID }}&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Scan&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cleancloud scan --provider azure&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No &lt;code&gt;AWS_SECRET_ACCESS_KEY&lt;/code&gt; or &lt;code&gt;AZURE_CLIENT_SECRET&lt;/code&gt; needed. ✅&lt;/p&gt;

&lt;h2&gt;
  
  
  What CleanCloud is NOT
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Not a cost optimization tool&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Doesn't access billing data&lt;/li&gt;
&lt;li&gt;Doesn't recommend rightsizing&lt;/li&gt;
&lt;li&gt;Focuses on hygiene, not savings&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Not a FinOps platform&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No dashboards&lt;/li&gt;
&lt;li&gt;No cost tracking&lt;/li&gt;
&lt;li&gt;Just clean signals&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Not an auto-remediation service&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Will never delete anything&lt;/li&gt;
&lt;li&gt;Will never modify resources&lt;/li&gt;
&lt;li&gt;Will never tag resources&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a &lt;strong&gt;strategic design choice&lt;/strong&gt;, not a limitation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Privacy &amp;amp; Telemetry
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;CleanCloud collects zero telemetry.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No analytics. No tracking. No phone-home.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Security tools shouldn't send data anywhere&lt;/li&gt;
&lt;li&gt;Works in air-gapped environments&lt;/li&gt;
&lt;li&gt;No opt-out flags needed&lt;/li&gt;
&lt;li&gt;Zero risk of leaking account info&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We improve based on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub issues&lt;/li&gt;
&lt;li&gt;Direct feedback&lt;/li&gt;
&lt;li&gt;Community contributions&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;v0.3.1&lt;/strong&gt; just shipped with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Complete documentation overhaul&lt;/li&gt;
&lt;li&gt;Smarter AWS region auto-detection&lt;/li&gt;
&lt;li&gt;Enhanced diagnostics with security grading&lt;/li&gt;
&lt;li&gt;Fixed region detection bugs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Coming soon:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GCP support&lt;/li&gt;
&lt;li&gt;Additional rules (unused Elastic IPs, old AMIs)&lt;/li&gt;
&lt;li&gt;Rule filtering (&lt;code&gt;--rules&lt;/code&gt; flag)&lt;/li&gt;
&lt;li&gt;Historical tracking&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Not planned:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Automated cleanup&lt;/li&gt;
&lt;li&gt;Cost optimization&lt;/li&gt;
&lt;li&gt;Billing data access&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;CleanCloud will remain focused on &lt;strong&gt;safe hygiene detection&lt;/strong&gt;, not automation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design Philosophy
&lt;/h2&gt;

&lt;p&gt;Three core principles:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Conservative by Default
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Age-based confidence thresholds&lt;/li&gt;
&lt;li&gt;Multiple signals required&lt;/li&gt;
&lt;li&gt;Prefer false negatives over false positives&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Read-Only Always
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;No Delete* permissions&lt;/li&gt;
&lt;li&gt;No Tag* permissions
&lt;/li&gt;
&lt;li&gt;No modification APIs&lt;/li&gt;
&lt;li&gt;Safe for production&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Review-Only Recommendations
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Findings are candidates for review, not automated action&lt;/li&gt;
&lt;li&gt;Clear reasoning for each finding&lt;/li&gt;
&lt;li&gt;Humans stay in control&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Who Is This For?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Primary users:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SRE teams&lt;/li&gt;
&lt;li&gt;Platform engineers&lt;/li&gt;
&lt;li&gt;Infrastructure teams&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Stakeholders:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Security (read-only = passes security reviews)&lt;/li&gt;
&lt;li&gt;Compliance (SOC2/ISO27001 friendly)&lt;/li&gt;
&lt;li&gt;FinOps (identifies waste without aggressive optimization)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Not for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Teams wanting auto-cleanup&lt;/li&gt;
&lt;li&gt;Cost optimization as primary goal&lt;/li&gt;
&lt;li&gt;Aggressive savings recommendations&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Real Talk: Why I Built This
&lt;/h2&gt;

&lt;p&gt;I've seen too many "smart" automation tools cause outages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Auto-scaler that scaled to zero during a traffic spike&lt;/li&gt;
&lt;li&gt;Cleanup tool that deleted "unused" security groups (broke production)&lt;/li&gt;
&lt;li&gt;Cost optimizer that downsized a database (performance disaster)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The pattern:&lt;/strong&gt; Automation is confident. Humans are cautious. Production requires caution.&lt;/p&gt;

&lt;p&gt;CleanCloud is designed for &lt;strong&gt;teams who value trust over automation&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/cleancloud-io/cleancloud" rel="noopener noreferrer"&gt;https://github.com/cleancloud-io/cleancloud&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Install:&lt;/strong&gt; &lt;code&gt;pip install cleancloud&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Docs:&lt;/strong&gt; Complete setup guides for AWS and Azure&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Looking for feedback:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What cloud hygiene tools do you currently use?&lt;/li&gt;
&lt;li&gt;Would read-only signals be useful for your team?&lt;/li&gt;
&lt;li&gt;What features would make this production-ready for you?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Open source, MIT license. Contributions welcome!&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;If you found this useful:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⭐ Star the repo&lt;/li&gt;
&lt;li&gt;💬 Share your cloud hygiene horror stories in the comments&lt;/li&gt;
&lt;li&gt;🐛 Report issues or suggest features&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Built for SRE teams who value trust over automation.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>azure</category>
      <category>devops</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
