<?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: bruce way</title>
    <description>The latest articles on Forem by bruce way (@abestdev).</description>
    <link>https://forem.com/abestdev</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%2F3876021%2F75c0fe1d-a655-455c-af8c-514d64de6cfe.jpg</url>
      <title>Forem: bruce way</title>
      <link>https://forem.com/abestdev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/abestdev"/>
    <language>en</language>
    <item>
      <title>How I built a pure client-side sanitizer to stop leaking Stripe tokens to ChatGPT.</title>
      <dc:creator>bruce way</dc:creator>
      <pubDate>Sat, 16 May 2026 14:26:02 +0000</pubDate>
      <link>https://forem.com/abestdev/how-i-built-a-pure-client-side-sanitizer-to-stop-leaking-stripe-tokens-to-chatgpt-2i4b</link>
      <guid>https://forem.com/abestdev/how-i-built-a-pure-client-side-sanitizer-to-stop-leaking-stripe-tokens-to-chatgpt-2i4b</guid>
      <description>&lt;p&gt;Like most developers, my go-to debugging strategy often involves dumping massive Nginx configs or React error traces straight into AI tools like Claude and ChatGPT. A few weeks ago, I made a classic mistake: I accidentally included a production DB URI and a Stripe token in my prompt. It made me realize how dangerously easy it is to leak credentials when you're moving fast and frustrated by a bug.&lt;/p&gt;

&lt;p&gt;I searched around for a lightweight client-side scrubber to sanitize my text, but came up empty. The tools I found either choked on multi-line secrets (like RSA private keys) or completely failed on greedy regex traps (like the @ symbol in database credentials).&lt;/p&gt;

&lt;p&gt;So I spent the last couple of weekends building &lt;strong&gt;GhostSanitizer&lt;/strong&gt; — a pure TypeScript/Regex engine that runs entirely in the browser. It intercepts your text, tokenizes any high-entropy secrets (AWS keys, JWTs, URIs) into dummy tokens (e.g., &lt;strong&gt;STACK_SEC_1&lt;/strong&gt;), and only sends the safe structure to the LLM. When the LLM replies with the refactored code, the browser locally maps the tokens back to your real secrets so you can 1-click copy it.&lt;/p&gt;

&lt;p&gt;I open-sourced the core sanitizer logic here if anyone wants to use it for their own AI wrappers:&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/abests/ghost-sanitizer-js" rel="noopener noreferrer"&gt;https://github.com/abests/ghost-sanitizer-js&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Live Sandbox:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
To actually make it useful for myself, I wrapped it into a UI and used a Python script to map out and generate specific pre-loaded prompts for about 500 annoying WebDev/DevOps error scenarios (mostly React hydration errors, Nginx 502s/CORS issues). Yes, it's a generated matrix, but it forces the LLM to stay highly context-specific instead of giving generic advice.&lt;/p&gt;

&lt;p&gt;You can test the live decryption theater (press F12 to watch it redact before the network request) here:  &lt;strong&gt;&lt;a href="https://stackengine.dev" rel="noopener noreferrer"&gt;stackengine.dev&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
(Link to a specific issue like &lt;a href="https://stackengine.dev/postgres-deadlock-detected-sharelock-transaction" rel="noopener noreferrer"&gt;PostgreSQL Deadlock ShareLock&lt;/a&gt; if you want to see the specific system prompts in action).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Full transparency on the model:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
I threw in a tiny free tier (3 uses) routed through my own Claude 3.5 Haiku key just so people can test the decryption UI. After that, you have to drop in your own OpenAI/Anthropic key (BYOK). When you use your own key, the requests go straight from your browser to the LLM provider. I have zero backend database and literally store nothing.&lt;/p&gt;

&lt;p&gt;Let me know if you manage to break the local regex mask. I'm actively trying to find edge cases to patch in the repo. Cheers!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>devops</category>
      <category>postgres</category>
      <category>aws</category>
    </item>
    <item>
      <title>IAM Access Analyzer nuked our prod hotfix because I fundamentally misunderstood how Zelkova evaluates wildcards</title>
      <dc:creator>bruce way</dc:creator>
      <pubDate>Sat, 02 May 2026 13:30:49 +0000</pubDate>
      <link>https://forem.com/abestdev/iam-access-analyzer-nuked-our-prod-hotfix-because-i-fundamentally-misunderstood-how-zelkova-384d</link>
      <guid>https://forem.com/abestdev/iam-access-analyzer-nuked-our-prod-hotfix-because-i-fundamentally-misunderstood-how-zelkova-384d</guid>
      <description>&lt;p&gt;TL;DR: Spent 6 hours debugging why our GitOps pipeline kept blocking a critical deployment. Turns out IAM Access Analyzer doesn't care about your Permission Boundaries when evaluating trust policies. &lt;code&gt;Principal: "AWS: *"&lt;/code&gt; + a &lt;code&gt;StringLike&lt;/code&gt; ARN condition is still globally exploitable. Fixed it with &lt;code&gt;aws:PrincipalOrgID&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Incident
&lt;/h2&gt;

&lt;p&gt;Our zero-critical-finding security gate hard-blocked a hotfix for our order processing engine. The Terraform pipeline died during validation with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[FATAL] IAM Access Analyzer finding [RESOURCE_PUBLICLY_ACCESSIBLE] 
detected on aws_iam_role.cross_account_event_bus. 
Trust policy allows Principal 'AWS:*'. Deployment halted.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The False Leads (aka me being an idiot)
&lt;/h2&gt;

&lt;p&gt;First, I assumed IAM eventual consistency was screwing with us. Forced a state refresh, manually triggered aws accessanalyzer start-resource-scan, finding came right back.&lt;/p&gt;

&lt;p&gt;Second hypothesis: I had a strict Permission Boundary on the role with aws:SourceVpc and aws:SourceIp conditions. I thought Zelkova (the automated reasoning engine behind Access Analyzer) would be smart enough to calculate the intersection of the trust policy + boundary and realize no external actor could satisfy the network requirements.&lt;/p&gt;

&lt;p&gt;Wrong. Access Analyzer evaluates trust policies in complete isolation. It doesn't aggregate Permission Boundaries or SCPs when determining if something is publicly accessible.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Root Cause
&lt;/h2&gt;

&lt;p&gt;The role's trust policy had Principal: { "AWS": "*" } to support dynamic cross-account access for worker nodes across sub-accounts. To "secure" it, I added:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;Condition:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;StringLike:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"aws:PrincipalArn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:iam::*:role/worker-node-*"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looks safe, right? Nope. Zelkova uses formal logic. Since AWS account IDs are globally addressable, any attacker could create a role named worker-node-exploit in their own AWS account. That ARN would match the StringLike condition. Zelkova correctly flagged this as exploitable by the entire AWS universe.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fix
&lt;/h2&gt;

&lt;p&gt;Replaced the non-deterministic ARN wildcard with &lt;code&gt;aws:PrincipalOrgID&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;condition&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;test&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"StringEquals"&lt;/span&gt;
  &lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"aws:PrincipalOrgID"&lt;/span&gt;
  &lt;span class="nx"&gt;values&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"o-xyz123abc9"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This mathematically proves to Zelkova that &lt;code&gt;AWS: *&lt;/code&gt; is bounded to our AWS Organization. Finding cleared instantly.&lt;/p&gt;

&lt;p&gt;Honestly, I got so annoyed debugging IAM policy logic at 2 AM that I refuse to paste our configs into ChatGPT or third-party linters because of compliance. So over the weekend, I just hacked together a pure client-side WASM utility that runs locally in the browser and redacts secrets before checking for this exact issue. Put it up here if anyone else wants to validate their trust policies without leaking data to the cloud:&lt;br&gt;
&lt;a href="https://stackengine.dev/aws-iam-access-analyzer-publicly-accessible" rel="noopener noreferrer"&gt;IAM Access Analyzer Public Principal Auditor&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>security</category>
      <category>devops</category>
      <category>terraform</category>
    </item>
  </channel>
</rss>
