<?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>DEV Community: Bala Paranj</title>
    <description>The latest articles on DEV Community by Bala Paranj (@bala_paranj_059d338e44e7e).</description>
    <link>https://dev.to/bala_paranj_059d338e44e7e</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%2F3862804%2F7ea6c560-63cb-4daf-a713-450532280b0a.jpg</url>
      <title>DEV Community: Bala Paranj</title>
      <link>https://dev.to/bala_paranj_059d338e44e7e</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bala_paranj_059d338e44e7e"/>
    <language>en</language>
    <item>
      <title>Visual Regression Testing for CLIs with VHS</title>
      <dc:creator>Bala Paranj</dc:creator>
      <pubDate>Mon, 18 May 2026 12:06:31 +0000</pubDate>
      <link>https://dev.to/bala_paranj_059d338e44e7e/visual-regression-testing-for-clis-with-vhs-54gl</link>
      <guid>https://dev.to/bala_paranj_059d338e44e7e/visual-regression-testing-for-clis-with-vhs-54gl</guid>
      <description>&lt;p&gt;How to use Charm's VHS to create GIF-based visual regression tests for your CLI's terminal output — catching formatting bugs that unit tests miss.&lt;/p&gt;

&lt;p&gt;Your CLI's unit tests verify that the right data comes out. But they don't test what the user actually &lt;em&gt;sees&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;A missing newline. A table column that wraps at 80 characters. A progress spinner that bleeds into the output. An ANSI color code that renders as garbage on a light terminal theme. These are visual bugs that pass every unit test but make your CLI look broken.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/charmbracelet/vhs" rel="noopener noreferrer"&gt;VHS&lt;/a&gt; by Charm solves this by recording your terminal as a GIF from a script — and you can use those GIFs as visual regression tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using VHS
&lt;/h2&gt;

&lt;p&gt;VHS reads a &lt;code&gt;.tape&lt;/code&gt; file that describes terminal interactions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# demo.tape
Output demo.gif
Set Width 120
Set Height 40
Set Theme "Monokai"

Type "stave apply --controls ./controls --observations ./obs --format text"
Enter
Sleep 2s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vhs demo.tape
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output: &lt;code&gt;demo.gif&lt;/code&gt; — a pixel-perfect recording of what the terminal looks like when that command runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  How This Differs from Asciinema
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Asciinema (.cast)&lt;/th&gt;
&lt;th&gt;VHS (.gif/.png)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Output&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Text-based replay (NDJSON)&lt;/td&gt;
&lt;td&gt;Pixel-based image (GIF/PNG/WebM)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Renders&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;In a JavaScript player&lt;/td&gt;
&lt;td&gt;As a static image anywhere&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tests&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Text content correctness&lt;/td&gt;
&lt;td&gt;Visual formatting correctness&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Use case&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Documentation, interactive replay&lt;/td&gt;
&lt;td&gt;README badges, visual regression&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;File size&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Small (text)&lt;/td&gt;
&lt;td&gt;Large (image)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Searchable&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes (it's text)&lt;/td&gt;
&lt;td&gt;No (it's pixels)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Asciinema answers: "What text does the CLI produce?"&lt;br&gt;
VHS answers: "What does the CLI &lt;em&gt;look like&lt;/em&gt;?"&lt;/p&gt;

&lt;p&gt;Both are useful. They test different things.&lt;/p&gt;
&lt;h2&gt;
  
  
  Visual Regression Testing Pattern
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Step 1: Create a &lt;code&gt;.tape&lt;/code&gt; file per workflow
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# tapes/apply-violation.tape
Output testdata/screenshots/apply-violation.gif
Set Width 120
Set Height 40
Set FontSize 14
Set Theme "Catppuccin Mocha"

Type "stave apply --controls controls/s3 --observations observations --now 2026-01-15T00:00:00Z --format text"
Enter
Sleep 3s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Step 2: Generate the baseline
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vhs tapes/apply-violation.tape
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Commit &lt;code&gt;testdata/screenshots/apply-violation.gif&lt;/code&gt; as the golden file.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 3: Compare in CI
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .github/workflows/visual.yml&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;Generate screenshots&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;for tape in tapes/*.tape; do&lt;/span&gt;
      &lt;span class="s"&gt;vhs "$tape"&lt;/span&gt;
    &lt;span class="s"&gt;done&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;Check for visual changes&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;git diff --exit-code testdata/screenshots/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If any GIF changes, the diff catches it. The developer reviews the visual change and either updates the golden file or fixes the formatting bug.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 4: Review with PR comments
&lt;/h3&gt;

&lt;p&gt;For GitHub PRs, you can post the before/after GIF directly in a comment:&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="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;Post visual diff&lt;/span&gt;
  &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;failure()&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;echo "Visual regression detected. See the updated screenshots below."&lt;/span&gt;
    &lt;span class="s"&gt;# Upload artifacts or post to PR&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What Visual Tests Catch That Unit Tests Miss
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Table alignment
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CONTROL_ID          ASSET_ID              STATUS
CTL.S3.PUBLIC.001   my-very-long-bucket   NON_COMPLIANT
                    -name-that-wraps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A unit test checks that the data is correct. A visual test catches that the column wraps and breaks the alignment.&lt;/p&gt;

&lt;h3&gt;
  
  
  Color and formatting
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[PASS] CTL.S3.ENCRYPT.001 — Server-Side Encryption
[FAIL] CTL.S3.PUBLIC.001 — No Public Read Access
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A unit test sees &lt;code&gt;[PASS]&lt;/code&gt; and &lt;code&gt;[FAIL]&lt;/code&gt;. A visual test sees whether the ANSI color codes render correctly — green for pass, red for fail — or whether they produce &lt;code&gt;\033[32m[PASS]\033[0m&lt;/code&gt; garbage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Progress indicators
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Running: evaluating controls... ⠋
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A spinner that works in a real terminal but bleeds into piped output. A visual test with a fixed terminal size catches this.&lt;/p&gt;

&lt;h3&gt;
  
  
  Help text layout
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;Usage:
  stave apply [flags]

Flags:
  -i, --controls string   Path to control definitions (default "controls/s3")
  -o, --observations string
                          Path to observation snapshots (default "observations")
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Does the flag help wrap correctly? Are the defaults aligned? Is the long description properly indented? Unit tests don't check layout. VHS checks layout.&lt;/p&gt;

&lt;h2&gt;
  
  
  VHS &lt;code&gt;.tape&lt;/code&gt; Cheat Sheet
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Output file.gif              # Output file (gif, png, webm, mp4)
Set Width 120                # Terminal width
Set Height 40                # Terminal height
Set FontSize 14              # Font size in pixels
Set Theme "Dracula"          # Terminal theme
Set TypingSpeed 50ms         # Delay between keystrokes

Type "command"               # Type text (simulated keystrokes)
Enter                        # Press Enter
Sleep 2s                     # Wait for output
Ctrl+C                       # Send interrupt
Tab                          # Press Tab (for completion testing)
Backspace 5                  # Delete 5 characters

Hide                         # Stop recording (for setup commands)
Show                         # Resume recording
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Combining Both Tools
&lt;/h2&gt;

&lt;p&gt;For a complete CLI testing strategy:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Tests&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Unit tests&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;go test&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Data correctness, error handling, exit codes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;E2E golden files&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;go test&lt;/code&gt; + JSON comparison&lt;/td&gt;
&lt;td&gt;Full output correctness, determinism&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Text recordings&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Custom asciicast generator&lt;/td&gt;
&lt;td&gt;Documentation accuracy, demo freshness&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Visual regression&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;VHS&lt;/td&gt;
&lt;td&gt;Formatting, alignment, colors, layout&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each layer catches different bugs. Unit tests catch logic errors. Golden files catch output regressions. Asciicast recordings catch documentation drift. VHS catches visual formatting bugs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&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 VHS (macOS)&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;charmbracelet/tap/vhs

&lt;span class="c"&gt;# Install VHS (Linux)&lt;/span&gt;
go &lt;span class="nb"&gt;install &lt;/span&gt;github.com/charmbracelet/vhs@latest

&lt;span class="c"&gt;# Create your first tape&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; hello.tape &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
Output hello.gif
Set Width 80
Set Height 24
Type "echo 'Hello from VHS'"
Enter
Sleep 1s
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;&lt;span class="c"&gt;# Record&lt;/span&gt;
vhs hello.tape
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The GIF is your visual test. Commit it, compare it in CI, review it in PRs.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;&lt;a href="https://github.com/sufield/stave" rel="noopener noreferrer"&gt;Stave&lt;/a&gt; uses programmatic asciicast generation for documentation recordings and Go-based golden file testing for output correctness. VHS is the natural next step for visual regression testing of the text-formatted output.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>cli</category>
      <category>testing</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Z3 Can Prove Your Cloud is Unsafe. It Can't Tell You Why.</title>
      <dc:creator>Bala Paranj</dc:creator>
      <pubDate>Sun, 17 May 2026 11:35:08 +0000</pubDate>
      <link>https://dev.to/bala_paranj_059d338e44e7e/z3-can-prove-your-cloud-is-unsafe-it-cant-tell-you-why-48jh</link>
      <guid>https://dev.to/bala_paranj_059d338e44e7e/z3-can-prove-your-cloud-is-unsafe-it-cant-tell-you-why-48jh</guid>
      <description>&lt;p&gt;Z3 is one of the most powerful reasoning engines ever built. Microsoft Research created it to verify chip designs and flight software. It can take your cloud configuration, model it as a set of logical assertions, and mathematically prove whether an attack path exists.&lt;/p&gt;

&lt;p&gt;Z3 says when it finds one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three letters. No context. No explanation. No link to the configuration property that caused it. No fix. Just "sat" — which means "satisfiable," which means "the unsafe state you asked about is reachable," which means your cloud is vulnerable. Probably. If you encoded the question correctly. Which you can't verify from the output.&lt;/p&gt;

&lt;p&gt;This is the gap between a powerful engine and a useful tool. The engine answers the question. The tool explains the answer. This article explains why the explanation layer matters more than the engine, and what it looks like in practice.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Z3 outputs
&lt;/h2&gt;

&lt;p&gt;Let's say you want to check whether an anonymous internet user can read PHI data from an S3 bucket through a Cognito identity pool. You model the configuration as SMT-LIB assertions, write a query, and run it through Z3.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The input Z3 sees:&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;(set-logic ALL)
(declare-fun allows_unauthenticated (String String) Bool)
(declare-fun maps_unauth_to (String String) Bool)
(declare-fun has_action (String String) Bool)
(declare-fun has_tag (String String) Bool)

(assert (allows_unauthenticated "pool-abc" "true"))
(assert (maps_unauth_to "pool-abc" "role/AppUnauthRole"))
(assert (has_action "role/AppUnauthRole" "s3:GetObject"))
(assert (has_tag "bucket-prod-phi" "data_classification:phi"))

(declare-const principal String)
(declare-const action String)
(declare-const resource String)

(assert (allows_unauthenticated principal "true"))
(assert (has_action principal action))
(assert (has_tag resource "data_classification:phi"))

(check-sat)
(get-model)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The output Z3 produces:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="nv"&gt;sat&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;define-fun&lt;/span&gt; &lt;span class="nv"&gt;principal&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nv"&gt;String&lt;/span&gt; &lt;span class="s"&gt;"pool-abc"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;define-fun&lt;/span&gt; &lt;span class="nv"&gt;action&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nv"&gt;String&lt;/span&gt; &lt;span class="s"&gt;"s3:GetObject"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;define-fun&lt;/span&gt; &lt;span class="nv"&gt;resource&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nv"&gt;String&lt;/span&gt; &lt;span class="s"&gt;"bucket-prod-phi"&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;In SMT-LIB, &lt;code&gt;sat&lt;/code&gt; means the forbidden state is reachable. The model names the specific pool, action, and bucket. The proof is mathematically sound.&lt;/p&gt;

&lt;p&gt;If you're a security engineer who just wants to know whether your Cognito configuration is safe, this output is useless. You have three questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Is this result correct?&lt;/strong&gt; Did the input assertions match your configuration, or did the translation introduce a bug?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What does it mean?&lt;/strong&gt; Which specific settings in which specific files create the vulnerability?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What do I do about it?&lt;/strong&gt; What's the fix, what does it cost, and how do I verify it worked?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Z3 answers none of these. It answered the math question. The security question is still open.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two translation boundaries
&lt;/h2&gt;

&lt;p&gt;Between your cloud configuration and Z3's answer, there are two translation steps. Each can introduce bugs. Each is invisible if you only look at the solver's output.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;YOUR CLOUD CONFIG          THE MATH              YOUR ANSWER

  S3 bucket policy    →    SMT-LIB assertions    →    "sat"
  IAM role policy          (was the translation        (was the translation
  Cognito settings          correct?)                   back to cloud
                                                        terms correct?)

  ENCODING BOUNDARY        Z3 SOLVER              DECODING BOUNDARY
  (can have bugs)          (trusted)              (can have bugs)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Encoding bugs:&lt;/strong&gt; Your S3 bucket has &lt;code&gt;PublicAccessBlock.BlockPublicPolicy = true&lt;/code&gt;, but the translation emits &lt;code&gt;has_public_read "bucket" "true"&lt;/code&gt;. The assertion is wrong — the bucket is private, but Z3 thinks it's public. Z3 faithfully returns &lt;code&gt;sat&lt;/code&gt; based on the wrong input. The "vulnerability" doesn't exist. You can't tell from &lt;code&gt;sat&lt;/code&gt; that the encoding was wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Decoding bugs:&lt;/strong&gt; Z3 returns &lt;code&gt;sat&lt;/code&gt; with a model. You read the model and conclude "the bucket is directly accessible from the internet." But the actual path is through the Cognito identity pool, not direct access. The model told you which variables were assigned, but the interpretation of those variables — what they mean in cloud terms — is your responsibility. Misread the model, misunderstand the vulnerability.&lt;/p&gt;

&lt;p&gt;The solver is the most reliable component in the pipeline. The translation layers are where the bugs hide. And they're the layers that get the least attention because everyone focuses on the solver.&lt;/p&gt;

&lt;h2&gt;
  
  
  What an orchestration layer provides
&lt;/h2&gt;

&lt;p&gt;An orchestration layer wraps the solver with five capabilities that make the answer trustworthy, traceable, and actionable:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Encoding explanation — "Did the tool understand my configuration?"
&lt;/h3&gt;

&lt;p&gt;Before the solver runs, you see what the tool extracted from your configuration, in your language:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Configuration Summary: 7 assets, 43 facts extracted

Asset: arn:aws:s3:::prod-phi (S3 Bucket)
  ├── Public read access: ENABLED
  │   Source: prod-phi-bucket.obs.json → access.public_read
  │   Fact: a3f8c2e91b04
  │
  ├── Encryption: AES256 (SSE-S3, not KMS)
  │   Source: prod-phi-bucket.obs.json → encryption.algorithm
  │   Fact: b7d1e4f03c89
  │
  └── Data classification: PHI
      Source: prod-phi-bucket.obs.json → tags.data_classification
      Fact: c9e2a1f04d77

Asset: arn:aws:cognito-identity:...:identitypool/abc (Identity Pool)
  ├── Unauthenticated access: ALLOWED
  │   Source: cognito-pool.obs.json → identity.access.allow_unauthenticated
  │   Fact: d4e5f6a7b8c9
  │
  └── Maps unauthenticated users to: arn:aws:iam::111122223333:role/AppUnauthRole
      Source: cognito-pool.obs.json → identity.cognito.unauth_role_arn
      Fact: e5f6a7b8c9d0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No SMT-LIB. No predicate names. "Public read access: ENABLED" — the security engineer compares this to their mental model of the bucket. If the bucket is private, the encoding is wrong and the engineer knows before the solver runs.&lt;/p&gt;

&lt;p&gt;Every fact has a unique identifier and a traceable source showing which file and which property path produced it. This is the audit trail. When the solver's answer doesn't match expectations, the engineer traces the identifier back to the source and checks whether the encoding was correct.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Human verdict — "What does the result mean?"
&lt;/h3&gt;

&lt;p&gt;After the solver runs, you see the answer in security language:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VERDICT: UNSAFE

An anonymous internet user can read PHI data from the prod-phi
bucket through the Cognito identity pool.

The forbidden state is reachable because:
  1. Identity pool allows unauthenticated access
     (cognito-pool.obs.json → identity.access.allow_unauthenticated = true)
  2. Unauthenticated users receive credentials for AppUnauthRole
     (cognito-pool.obs.json → identity.cognito.unauth_role_arn)
  3. AppUnauthRole has s3:GetObject permission
     (iam-role.obs.json → policies.attached_policies[0].Action)
  4. Target bucket contains PHI
     (prod-phi-bucket.obs.json → tags.data_classification = phi)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not &lt;code&gt;sat&lt;/code&gt;. Not a model with &lt;code&gt;define-fun&lt;/code&gt; expressions. A four-step chain in plain English, each step linked to a specific file and property path. The security engineer reads it and knows exactly which settings create the vulnerability and where they live.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Fix guidance — "What do I do about it?"
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;FIX: Disable unauthenticated access on the identity pool.

  aws cognito-identity update-identity-pool &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--identity-pool-id&lt;/span&gt; us-east-1:abc123 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--no-allow-unauthenticated-identities&lt;/span&gt;

  Cost: &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt; Time: 30 seconds.
  Effect: Breaks the chain at step 1.

VERIFICATION: After applying the fix, re-run the analysis.
  Expected result: SAFE
  &lt;span class="o"&gt;(&lt;/span&gt;Z3 returns UNSAT — no assignment of principals, actions,
  and resources can satisfy the attack path conditions.&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fix is a shell command. The cost is quantified. The effect names the specific chain step that breaks. The verification tells the engineer what to expect and explains the solver's output in cloud terms.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Traceability — "Which property caused this?"
&lt;/h3&gt;

&lt;p&gt;Every step in the verdict traces back to a specific property in a specific file through a unique identifier:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# The verdict says step 1 caused the chain.&lt;/span&gt;
&lt;span class="c"&gt;# Trace identifier d4e5f6a7b8c9:&lt;/span&gt;

&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"d4e5f6a7b8c9"&lt;/span&gt; facts.jsonl

&lt;span class="c"&gt;# Returns:&lt;/span&gt;
&lt;span class="c"&gt;# fact_id: d4e5f6a7b8c9&lt;/span&gt;
&lt;span class="c"&gt;# subject: pool-abc&lt;/span&gt;
&lt;span class="c"&gt;# predicate: allows_unauthenticated = true&lt;/span&gt;
&lt;span class="c"&gt;# source: cognito-pool.obs.json&lt;/span&gt;
&lt;span class="c"&gt;# property: identity.access.allow_unauthenticated&lt;/span&gt;
&lt;span class="c"&gt;# captured: 2026-05-01T00:00:00Z&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One identifier. One grep. Full trace from the verdict to the configuration property, including when the snapshot was taken. No manual correlation across output files.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Encoding verification — "Can I trust the translation?"
&lt;/h3&gt;

&lt;p&gt;The orchestration layer verifies its own encoding by comparing each extracted fact against the raw configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Encoding verification: 43/43 facts verified ✓

Every extracted fact matches the corresponding property
in the observation file. The solver's input is consistent
with the configuration snapshot.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or, when there's a bug:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Encoding verification: 41/43 facts verified

MISMATCH:
  Fact d4e5f6a7b8c9:
    Extracted: allows_unauthenticated = "true"
    Observation: identity.access.allow_unauthenticated = "false"
    File: cognito-pool.obs.json
    → ENCODING BUG: fact says unauthenticated is allowed,
      but the configuration says it's disabled.
      The UNSAFE verdict may be incorrect.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No other tool in the market verifies its own translation layer. The security engineer doesn't need to read SMT-LIB to trust the result — the tool proves its encoding is correct, or reports where it is wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you get without orchestration vs. with it
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Capability&lt;/th&gt;
&lt;th&gt;Z3 alone&lt;/th&gt;
&lt;th&gt;With orchestration&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Prove an attack path exists&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sat&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;UNSAFE: anonymous user can read PHI data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Explain which settings cause it&lt;/td&gt;
&lt;td&gt;Read the SMT-LIB model&lt;/td&gt;
&lt;td&gt;Four-step chain with file names and property paths&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Verify the encoding is correct&lt;/td&gt;
&lt;td&gt;Manually review assertions&lt;/td&gt;
&lt;td&gt;Automated: 43/43 facts match observations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trace the finding to a file&lt;/td&gt;
&lt;td&gt;Grep through comments&lt;/td&gt;
&lt;td&gt;One identifier, one grep, full trace&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Get the fix&lt;/td&gt;
&lt;td&gt;Derive it from the model&lt;/td&gt;
&lt;td&gt;Shell command, cost, time, expected result&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Run multiple solvers&lt;/td&gt;
&lt;td&gt;Reformat per solver&lt;/td&gt;
&lt;td&gt;Same input, three solvers, consensus&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Explain to an auditor&lt;/td&gt;
&lt;td&gt;Show them SMT-LIB&lt;/td&gt;
&lt;td&gt;Show them the chain in English with evidence&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The right column is the product. The left column is a math library. Security engineers don't need a math library — they need trustworthy answers in their language.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters more for compound detection
&lt;/h2&gt;

&lt;p&gt;The orchestration layer is most valuable when the analysis spans multiple services. A single-service check ("is this bucket public?") is simple enough to verify manually. A cross-service check ("can an anonymous user reach PHI data through a chain of Cognito → IAM → S3?") involves three services, three configuration files, and dozens of properties.&lt;/p&gt;

&lt;p&gt;The encoding has more places to be wrong. The verdict has more steps to explain. The traceability has more links to follow.&lt;/p&gt;

&lt;p&gt;This is where raw solver output becomes dangerous. If Z3 returns &lt;code&gt;sat&lt;/code&gt; on a three-service chain and the encoding has a bug in the IAM layer, the finding is a false positive. The security team triages it as critical, burns two sprints investigating, and discovers it was caused by a property path error in the translation code. The encoding verification catches that error before the solver runs.&lt;/p&gt;

&lt;p&gt;If Z3 returns &lt;code&gt;unsat&lt;/code&gt; and the encoding has a bug — a property that should be &lt;code&gt;true&lt;/code&gt; was encoded as &lt;code&gt;false&lt;/code&gt; — the finding is a false negative. The team thinks they're safe. They're not. The encoding verification catches that too.&lt;/p&gt;

&lt;p&gt;The more complex the analysis, the more valuable the translation layer. Single-service checks can tolerate raw solver output. Cross-service compound detection cannot.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bottom line
&lt;/h2&gt;

&lt;p&gt;Don't evaluate formal verification tools by which solver they use. Z3, cvc5, and Yices all produce correct answers to the questions they're asked. The question is whether the question was asked correctly and whether the answer is translated back to your language.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't accept &lt;code&gt;sat&lt;/code&gt; as an answer.&lt;/strong&gt; Accept "UNSAFE: an anonymous internet user can read PHI data from the prod-phi bucket through the Cognito identity pool, caused by these four settings in these three files, fixable with this one command for $0 in 30 seconds, verified by three independent solvers, encoding confirmed correct against 43 observation properties."&lt;/p&gt;

&lt;p&gt;That's an answer. &lt;code&gt;sat&lt;/code&gt; is a data point.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The orchestration layer described in this article is implemented in &lt;a href="https://github.com/sufield/stave" rel="noopener noreferrer"&gt;Stave&lt;/a&gt;, an open-source static analysis tool that evaluates cloud configurations via CEL predicates and exports standardized facts for consumption by nine independent reasoning engines. The encoding explanation, verdict translation, traceability, and encoding verification are built on Stave's fact_id provenance chain — every fact carries a deterministic identifier linking the solver's input to the specific observation file and property path that produced it. All analysis runs on air-gapped snapshots with no cloud credentials required.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>cloud</category>
      <category>security</category>
      <category>aws</category>
      <category>go</category>
    </item>
    <item>
      <title>Proof, not prediction: where formal verification beats AI in cloud security</title>
      <dc:creator>Bala Paranj</dc:creator>
      <pubDate>Sat, 16 May 2026 11:33:09 +0000</pubDate>
      <link>https://dev.to/bala_paranj_059d338e44e7e/proof-not-prediction-where-formal-verification-beats-ai-in-cloud-security-1jf3</link>
      <guid>https://dev.to/bala_paranj_059d338e44e7e/proof-not-prediction-where-formal-verification-beats-ai-in-cloud-security-1jf3</guid>
      <description>&lt;p&gt;An AI scanner says 'this configuration looks unsafe' with 87% confidence. A formal verifier says 'this configuration IS unsafe, here is the exact principal, action, and resource that proves it.' One is a prediction. The other is evidence. The difference matters for insurance, for audits, and for the 80% of cloud security questions that have exact answers.&lt;/p&gt;

&lt;p&gt;A CISO walks into a renewal meeting with the cyber-insurance underwriter. The underwriter asks one question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Can your most sensitive S3 bucket be accessed by an unauthorized principal?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There are two ways to answer. An AI-powered cloud security tool gives you a something like "this bucket appears to have appropriate controls based on similar configurations in our training data, confidence: 92%." A formal verifier gives you a yes/no with a witness: "no — UNSAT against the following 17 constraints over the bucket policy, identity federation, IAM, and account-level Public Access Block."&lt;/p&gt;

&lt;p&gt;One is a prediction. The other is a proof. The underwriter knows which one survives a subrogation suit.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is a proof
&lt;/h2&gt;

&lt;p&gt;When a solver like Z3 says &lt;strong&gt;UNSAT&lt;/strong&gt;, it has shown that &lt;em&gt;no assignment of the free variables in the model satisfies the constraints&lt;/em&gt;. There is no principal, no action, no resource, no role assumption, no trust path, no policy condition — within the model — that violates the property. The answer is mathematically complete relative to the model.&lt;/p&gt;

&lt;p&gt;When the same solver says &lt;strong&gt;SAT&lt;/strong&gt;, it returns a &lt;em&gt;witness&lt;/em&gt;: the concrete assignment that satisfies the constraints. The witness names the exact principal, action, and resource that constitute the violation. The output is constructive. You didn't just learn the property is false; you got the counterexample that proves it.&lt;/p&gt;

&lt;p&gt;There is no confidence score. There is no probability. There is no temperature setting. There is no false-positive rate. There is no training data. There is no "this might be similar to a known bad pattern." There is a function from facts to verdicts, and the verdict is correct relative to the inputs it was given.&lt;/p&gt;

&lt;p&gt;The qualification &lt;em&gt;relative to the model&lt;/em&gt; is critical. If your model doesn't include SCP evaluation, the solver won't catch SCP issues. If your model doesn't include Cognito identity-pool trust, the solver won't catch identity-federation chains. But the model's limitations are &lt;em&gt;enumerable&lt;/em&gt;. You can list them. Operators know exactly what the verifier covers and what it doesn't.&lt;/p&gt;

&lt;p&gt;An AI tool's limitations are statistical and unknowable. You can't enumerate what the training data didn't cover. A configuration that doesn't match any training pattern gets missed silently. A configuration that superficially resembles a bad pattern gets flagged incorrectly. The miss is invisible until the breach.&lt;/p&gt;




&lt;h2&gt;
  
  
  Proof vs prediction
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Formal verification (Z3 / cvc5 / Yices)&lt;/th&gt;
&lt;th&gt;AI-based cloud-security tools&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Output shape&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Proof or counterexample&lt;/td&gt;
&lt;td&gt;Prediction with confidence score&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Correctness guarantee&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Mathematically complete relative to the model&lt;/td&gt;
&lt;td&gt;Statistically approximate relative to the training data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;False positives&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Zero relative to the model&lt;/td&gt;
&lt;td&gt;Non-zero, tunable via threshold&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;False negatives&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Zero relative to the model&lt;/td&gt;
&lt;td&gt;Non-zero, depends on training coverage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Explainability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Exact witness — "principal P, action A, resource R"&lt;/td&gt;
&lt;td&gt;This configuration resembles known-bad patterns&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Novel attack paths&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Discovers paths nobody has seen before, if the model captures them&lt;/td&gt;
&lt;td&gt;Recognises only patterns similar to training data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Reproducibility&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Deterministic — same input always produces the same answer&lt;/td&gt;
&lt;td&gt;Varies across model versions, temperatures, prompts&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The deterministic property makes the verifier auditable. Run it today, get UNSAT. Run it tomorrow against the same fact base, get UNSAT. Run it in three years when the auditor asks for evidence, get UNSAT with the same fact base. The proof is the artifact. It does not decay.&lt;/p&gt;

&lt;p&gt;An AI prediction at 92% confidence today is a 87% confidence prediction tomorrow because the model was updated, or a 79% confidence prediction next week because the prompt template changed. The prediction is not an artifact. It is an output of a service. The service can be re-priced, re-trained, or shut down. Your audit evidence cannot.&lt;/p&gt;




&lt;h2&gt;
  
  
  The cost structure that nobody mentions
&lt;/h2&gt;

&lt;p&gt;The accuracy story is the visible one. The cost story is the one that bends adoption.&lt;/p&gt;

&lt;p&gt;Z3 runs on a single CPU core. The binary is ~10 MB. Running it on Stave's full SMT-LIB export — 5,000 facts plus 90 closed-world axioms — completes in milliseconds:&lt;br&gt;
&lt;/p&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;&lt;span class="nb"&gt;time &lt;/span&gt;z3 facts.smt2
sat

real    0m0.131s
user    0m0.123s
sys     0m0.009s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;130 milliseconds. No GPU. No API call. No usage tier.&lt;/p&gt;

&lt;p&gt;An AI-based tool pays for every query. Inference cost, GPU hours, API fees, token pricing. The cost scales linearly with usage: more assets, more policies, more frequent runs, higher bill. Enterprise customers with thousands of resources across multiple AWS accounts pay proportionally.&lt;/p&gt;

&lt;p&gt;The cost ratio shows up most starkly in continuous-assessment scenarios. A cyber-insurance underwriter might require posture verification on every configuration change. For a company making hundreds of changes daily across multiple accounts, the AI-based tool runs hundreds of times per day per account. The formal verifier runs the same number of times — at a marginal cost of zero. The CPU was already paid for.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;AI-based tools&lt;/th&gt;
&lt;th&gt;Stave + Z3/cvc5&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Per-query cost&lt;/td&gt;
&lt;td&gt;API inference fee&lt;/td&gt;
&lt;td&gt;Zero&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Infrastructure&lt;/td&gt;
&lt;td&gt;GPU clusters&lt;/td&gt;
&lt;td&gt;Single CPU core&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scaling cost&lt;/td&gt;
&lt;td&gt;Linear with usage&lt;/td&gt;
&lt;td&gt;Flat&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Offline capability&lt;/td&gt;
&lt;td&gt;Requires API connectivity&lt;/td&gt;
&lt;td&gt;Runs fully offline (&lt;code&gt;STAVE_NO_NETWORK=1&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Predictability&lt;/td&gt;
&lt;td&gt;Variable pricing, usage-based&lt;/td&gt;
&lt;td&gt;Fixed — it's a binary&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The offline guarantee compounds with the cost guarantee. Air-gapped environments — defence, classified workloads, certain financial verticals — cannot ship configuration data to a third-party AI API. They must analyse locally. A formal verifier was designed for this constraint. AI-powered SaaS tools were designed for the opposite constraint. The trade-off is structural, not a UX choice.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why this matters for insurance and evidence
&lt;/h2&gt;

&lt;p&gt;Cyber-insurance underwriting is the canary for this entire shift. Underwriters today read CSPM-tool screenshots and trust the vendor's narrative. The narrative is statistical: "our customers have 92% lower breach rates." The screenshot is an opinion: "this bucket appears compliant."&lt;/p&gt;

&lt;p&gt;Underwriters are moving toward evidence. They want artifacts they can hand to legal, archive in their underwriting file, and produce in a subrogation suit if the breach happens anyway. A formal proof — &lt;code&gt;(check-sat)&lt;/code&gt; returning &lt;code&gt;unsat&lt;/code&gt; against a specific fact base on a specific date — is an artifact. It can be archived. It can be re-run. It can be independently verified by Z3, cvc5, or Yices to confirm the answer.&lt;/p&gt;

&lt;p&gt;The proof and the fact base are the audit evidence. The fact base is &lt;code&gt;obs.v0.1&lt;/code&gt; JSON, schema-validated, with provenance for every property. The proof is whatever the solver returns. Together they reconstruct the verdict exactly. The auditor does not need to trust the security vendor's model — they can run the solver themselves against the committed fact base and verify the verdict independently.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;A Z3 proof is evidence. An AI prediction is an opinion.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is also Stave's strongest positioning. When an insurer asks "prove your S3 bucket can't be accessed by unauthenticated principals," the formal answer is the artifact the underwriter is starting to demand.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where AI genuinely belongs
&lt;/h2&gt;

&lt;p&gt;Roughly 80–90% of cloud-security questions have exact answers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is MFA enabled on the root account?&lt;/li&gt;
&lt;li&gt;Does this bucket policy allow &lt;code&gt;Principal: *&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;Can an unauthenticated Cognito identity assume this role?&lt;/li&gt;
&lt;li&gt;Is CloudTrail logging multi-region?&lt;/li&gt;
&lt;li&gt;Are these three controls all violated on the same asset?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these requires a language model. None of them benefits from a confidence score. They are predicates over structured facts. The deterministic verifier answers them in milliseconds with mathematical completeness relative to the model. Paying AI pricing for "is MFA enabled?" is a category error.&lt;/p&gt;

&lt;p&gt;The remaining 10–20% genuinely needs AI:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Natural-language policy interpretation&lt;/strong&gt;: parsing a vendor's English SOC 2 attestation and asking "does this commit them to encrypting backups?" The text is ambiguous; the intent must be inferred.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anomaly detection in behavioural data&lt;/strong&gt;: "is this CloudTrail access pattern unusual for this principal?" The baseline is statistical, the answer is probabilistic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Intent inference from incomplete documentation&lt;/strong&gt;: "does the team that owns this bucket consider its contents public-by-design, or did the public ACL get added by mistake?" The signal is in commit messages, ticket history, and Slack threads; the answer is judgment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AI tools are valuable on those. They are also expensive on those. The cost is justified because the alternative is human review at twice the price.&lt;/p&gt;




&lt;h2&gt;
  
  
  The foundation-layer architecture
&lt;/h2&gt;

&lt;p&gt;The right shape is composition. Deterministic verification at the bottom; AI judgement at the top:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+-------------------------------------------------------+
|  AI tools (10-20% of work)                            |
|    Policy interpretation, anomaly detection,          |
|    intent inference from English / behavioural data   |
+-------------------------------------------------------+
|  Stave + Z3/cvc5 (80-90% of work)                     |
|    Compound chain detection, formal proofs,           |
|    deterministic verdicts on structured facts         |
+-------------------------------------------------------+
|  Snapshot facts (obs.v0.1)                            |
|    Schema-validated, provenance-tracked, offline      |
+-------------------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The AI bill drops 80–90% because trivial deterministic checks no longer route through an inference endpoint. The remaining AI workload has a much better signal-to-noise ratio because it sees only the hard cases — the ones where human judgement is the alternative. The AI tool gets better, not worse, when the deterministic layer is in place underneath it.&lt;/p&gt;

&lt;p&gt;This is also the partnership angle. An AI-powered cloud-security vendor benefits from Stave handling the deterministic layer: their costs drop because the volume drops, their accuracy improves because the inputs are cleaner, and their pricing model survives the next round of GPU price increases because they're no longer paying inference cost for questions like "is encryption enabled?"&lt;/p&gt;

&lt;p&gt;Stave does not replace AI tools. It makes them economically viable by removing the work they shouldn't be doing.&lt;/p&gt;




&lt;h2&gt;
  
  
  A 130-millisecond demo
&lt;/h2&gt;

&lt;p&gt;The repository ships a working pipeline. Clone, build, and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/sufield/stave
&lt;span class="nb"&gt;cd &lt;/span&gt;stave &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; make build

&lt;span class="c"&gt;# Pick a real fixture — HackerOne #1021906, the Shopify "tag says internal,&lt;/span&gt;
&lt;span class="c"&gt;# policy says everyone" case. The fixture is a reconstructed snapshot from&lt;/span&gt;
&lt;span class="c"&gt;# the public disclosure.&lt;/span&gt;
&lt;span class="nv"&gt;FIXTURE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;testdata/e2e/e2e-h1-shopify-1021906

&lt;span class="c"&gt;# Export the snapshot as SMT-LIB v2 — facts only, no query.&lt;/span&gt;
./stave export-sir &lt;span class="nt"&gt;--format&lt;/span&gt; smt2 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--controls&lt;/span&gt; &lt;span class="nv"&gt;$FIXTURE&lt;/span&gt;/controls &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--observations&lt;/span&gt; &lt;span class="nv"&gt;$FIXTURE&lt;/span&gt;/observations &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--now&lt;/span&gt; 2026-01-15T00:00:00Z &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /tmp/facts.smt2

&lt;span class="c"&gt;# Append the satisfiability query.&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"(check-sat)"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /tmp/facts.smt2

&lt;span class="c"&gt;# Two independent solvers.&lt;/span&gt;
z3 /tmp/facts.smt2
cvc5 &lt;span class="nt"&gt;--lang&lt;/span&gt; smt2 /tmp/facts.smt2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;z3&lt;/code&gt; returns &lt;code&gt;sat&lt;/code&gt; on 302 lines of SMT-LIB in 132 milliseconds. &lt;code&gt;cvc5&lt;/code&gt; runs over the same input as an independent cross-check; on this particular fact base it returns &lt;code&gt;unknown&lt;/code&gt;, which is itself an honest verdict — the second solver could not establish the answer with its default tactic. Two engines returning the same &lt;code&gt;sat&lt;/code&gt;/&lt;code&gt;unsat&lt;/code&gt; is the strongest possible cross-validation; one returning &lt;code&gt;unknown&lt;/code&gt; is the signal to either widen the timeout, switch tactics, or refine the model. The point is that the &lt;em&gt;protocol&lt;/em&gt; — facts plus query plus solver — is reproducible. The verdict is auditable regardless of which solver produced it.&lt;/p&gt;

&lt;p&gt;No GPU, no API key, no per-query cost. The binary that produced the export does not call any cloud API. The solver runs locally. The audit evidence is the file at &lt;code&gt;/tmp/facts.smt2&lt;/code&gt; plus the verdict — both reproducible byte-for-byte at any point in the future against the same &lt;code&gt;obs.v0.1&lt;/code&gt; snapshot.&lt;/p&gt;




&lt;h2&gt;
  
  
  What this is not
&lt;/h2&gt;

&lt;p&gt;It is not "AI is bad for security." AI is excellent for the 10–20% of problems that genuinely require judgement, anomaly detection, or natural-language interpretation. The framing is &lt;em&gt;use the right tool for each class of problem&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;It is not "formal verification solves everything." The verifier is correct relative to the model. If your model doesn't include the Cognito identity-pool trust pattern, the solver will not flag a Cognito identity-pool attack. But model coverage is enumerable; you can list what is and isn't covered. AI coverage is statistical; you cannot.&lt;/p&gt;

&lt;p&gt;It is not "Z3 replaces your CSPM." CSPM and formal verification are complementary. CSPM tells you &lt;em&gt;what's in the cloud right now&lt;/em&gt;. Formal verification tells you &lt;em&gt;whether what's there satisfies your stated invariants&lt;/em&gt;. The CSPM dashboard is the inventory; the formal verifier is the auditor with the proof.&lt;/p&gt;




&lt;h2&gt;
  
  
  The structural shift
&lt;/h2&gt;

&lt;p&gt;Cloud security has spent a decade paying language-model prices for boolean questions. The market matured around AI because no deterministic alternative existed that handled compound risk across services. That gap is closed. Stave's open-source policy library plus the SMT-LIB export plus a $0 solver replaces the deterministic-check layer at zero marginal cost.&lt;/p&gt;

&lt;p&gt;The AI vendors who survive will be the ones who specialise in genuinely judgemental questions and welcome a free, deterministic foundation beneath them. The CSPM vendors who survive will be the ones who emit &lt;code&gt;obs.v0.1&lt;/code&gt;-compatible inventories and let any open-source verifier consume them. The customers who survive are the ones who stop paying API fees for &lt;code&gt;Principal: *&lt;/code&gt; checks.&lt;/p&gt;

&lt;p&gt;When the insurance underwriter asks for proof, the customer hands them the SMT-LIB file and the solver verdict. The verifier ran on a laptop. The cost was zero. The evidence is independently reproducible.&lt;/p&gt;

&lt;p&gt;That is what formal verification looks like in cloud security in 2026. The technique has been validated at scale by Microsoft Research (SecGuru, 2015) on Azure's datacentre network. Applying it to cloud service configurations is the only step that was missing. It is no longer missing.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;&lt;a href="https://github.com/sufield/stave" rel="noopener noreferrer"&gt;Stave&lt;/a&gt; is an open-source intent verification engine for cloud infrastructure with 2,650+ controls, compound risk detection, and nine independent reasoning engines.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>aws</category>
      <category>cloud</category>
      <category>ai</category>
    </item>
    <item>
      <title>Zero-cost abstractions in Go: deleting your way to better code</title>
      <dc:creator>Bala Paranj</dc:creator>
      <pubDate>Fri, 15 May 2026 12:09:37 +0000</pubDate>
      <link>https://dev.to/bala_paranj_059d338e44e7e/zero-cost-abstractions-in-go-deleting-your-way-to-better-code-35ad</link>
      <guid>https://dev.to/bala_paranj_059d338e44e7e/zero-cost-abstractions-in-go-deleting-your-way-to-better-code-35ad</guid>
      <description>&lt;p&gt;The most impactful refactoring in a Go CLI wasn't adding code — it was deleting pass-through layers, thin wrappers, and premature frameworks. Here's how to recognize abstractions that cost more than they save.&lt;/p&gt;

&lt;p&gt;Over 60 refactorings on a security CLI, the highest-ROI changes were deletions. Deletions of abstractions that existed "just in case" and cost every reader cognitive overhead with zero runtime benefit.&lt;/p&gt;

&lt;p&gt;Here are the abstractions we removed and why.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Pass-through packages
&lt;/h2&gt;

&lt;p&gt;A package that exists only to forward calls to another package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// internal/app/workflow/evaluate.go&lt;/span&gt;
&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;workflow&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="n"&gt;EvalInput&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&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;eval&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// just forwards&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every command imported &lt;code&gt;workflow&lt;/code&gt; instead of &lt;code&gt;eval&lt;/code&gt; directly. The package had no logic, no transformation, no error handling. It was a phantom layer — it appeared in import paths, confused grep results, and added one more package to understand.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; Delete the package. Rewire all callers to import &lt;code&gt;eval&lt;/code&gt; directly. One commit, zero behavior change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The rule:&lt;/strong&gt; If a package only forwards calls, it shouldn't exist.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Thin wrapper functions
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;RenderJSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Writer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&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;jsonutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteIndented&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;RenderText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Writer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;report&lt;/span&gt; &lt;span class="n"&gt;Report&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&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;textReporter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;report&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;These wrappers added names but no behavior. Every caller could call the underlying function directly. The wrappers existed because "we might add logging later" — but we never did.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; Inline the call at every call site. Delete the wrapper.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The principle:&lt;/strong&gt; Don't create a function to wrap a single function call. The call itself is already readable.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Anemic files
&lt;/h2&gt;

&lt;p&gt;25 Go files with fewer than 20 lines of logic. Each contained a single type or a single function that belonged in its neighboring file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;types.go          → 1 type, 0 methods
constants.go      → 3 constants
helpers.go        → 1 helper function
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every file is a navigation decision. 25 anemic files means 25 wrong guesses when searching for code. Merging &lt;code&gt;types.go&lt;/code&gt; into &lt;code&gt;policy.go&lt;/code&gt; means the type lives next to the logic that uses it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; Merge into the logical neighbor. 25 files became 0 additional files — the types moved into the files that used them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The rule:&lt;/strong&gt; Name files after the primary type they contain. Don't create &lt;code&gt;types.go&lt;/code&gt;, &lt;code&gt;utils.go&lt;/code&gt;, or &lt;code&gt;helpers.go&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Premature generic frameworks
&lt;/h2&gt;

&lt;p&gt;The most expensive deletion: a 500-line &lt;code&gt;Pipeline[T]&lt;/code&gt; generic framework.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Pipeline&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;stages&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;Stage&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Pipeline&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&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;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stage&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stages&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;
        &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&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;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
        &lt;span class="p"&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;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This was used in only one place — the evaluation pipeline. The stages were three sequential function calls. The framework added generics, interfaces, registration, and error handling for a problem that looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;controls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;loadControls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&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;err&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;controls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;snapshots&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&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;err&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;writeOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three lines of sequential Go. No generics. No interfaces. No framework.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; Delete the pipeline package. Replace with three sequential calls.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The principle:&lt;/strong&gt; Three lines of sequential Go beats a 500-line generic fluent API. Every time.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Backward-compatibility aliases
&lt;/h2&gt;

&lt;p&gt;After renaming &lt;code&gt;invariant&lt;/code&gt; to &lt;code&gt;control&lt;/code&gt; across 60 files, we kept type aliases for safety:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Deprecated: use ControlID.&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;InvariantID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ControlID&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The aliases created confusion about which name was canonical. Grep returned both. Autocompletion showed both. New code used both names randomly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; Delete all aliases in the same commit as the rename. No transition period. The codebase has no external consumers — there is nobody to break.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The rule:&lt;/strong&gt; If you have no external consumers, backward compatibility is debt.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Dead methods
&lt;/h2&gt;

&lt;p&gt;After the hexagonal migration, &lt;code&gt;deadcode&lt;/code&gt; analysis found 207 unreachable functions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;deadcode &lt;span class="nt"&gt;-test&lt;/span&gt; ./...
&lt;span class="go"&gt;internal/core/controldef.(*Operand).AsBool
internal/core/controldef.(*Operand).AsString
internal/core/controldef.(*Operand).AsNumber
internal/core/controldef.(*Operand).IsZero
internal/core/evaluation.(*EvalContext).GetLogger
&lt;/span&gt;&lt;span class="c"&gt;...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Methods that were written speculatively, methods left behind after a refactoring, methods duplicated across packages during a migration. All dead. All deleted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; Run &lt;code&gt;deadcode -test ./...&lt;/code&gt; after every structural change. Delete what it finds. No exceptions.&lt;/p&gt;

&lt;h2&gt;
  
  
  The cost model
&lt;/h2&gt;

&lt;p&gt;Every abstraction has a cost:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Abstraction&lt;/th&gt;
&lt;th&gt;Cost per reader&lt;/th&gt;
&lt;th&gt;Runtime benefit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Pass-through package&lt;/td&gt;
&lt;td&gt;Import confusion, grep noise&lt;/td&gt;
&lt;td&gt;Zero&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Thin wrapper&lt;/td&gt;
&lt;td&gt;Extra indirection to read&lt;/td&gt;
&lt;td&gt;Zero&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Anemic file&lt;/td&gt;
&lt;td&gt;Navigation overhead&lt;/td&gt;
&lt;td&gt;Zero&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unused generic framework&lt;/td&gt;
&lt;td&gt;500 lines to understand&lt;/td&gt;
&lt;td&gt;Zero&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Type alias&lt;/td&gt;
&lt;td&gt;Namespace pollution&lt;/td&gt;
&lt;td&gt;Zero&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dead method&lt;/td&gt;
&lt;td&gt;"Is this used?" investigation&lt;/td&gt;
&lt;td&gt;Zero&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If the runtime benefit column is zero, the abstraction is not zero-cost. It's negative-cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to delete
&lt;/h2&gt;

&lt;p&gt;After every structural refactoring, ask:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Does this package forward calls without adding logic? &lt;strong&gt;Delete it.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Does this function wrap a single function call? &lt;strong&gt;Inline it.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Does this file contain fewer than 20 lines? &lt;strong&gt;Merge it.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Does this framework serve one use case? &lt;strong&gt;Replace with sequential code.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Does this type alias exist for transition safety? &lt;strong&gt;Delete it now.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Does &lt;code&gt;deadcode&lt;/code&gt; find anything? &lt;strong&gt;Delete it all.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Real Zero Cost Abstraction
&lt;/h2&gt;

&lt;p&gt;Go doesn't have zero-cost abstractions in the Rust sense. Every interface adds a vtable lookup. Every package adds compile time. Every file adds navigation cost. Every line adds reading time.&lt;/p&gt;

&lt;p&gt;The only truly zero-cost abstraction in Go is the one you deleted.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;&lt;a href="https://github.com/sufield/stave" rel="noopener noreferrer"&gt;Stave&lt;/a&gt; is an open-source intent verification engine for cloud infrastructure with 2,650+ controls, compound risk detection, and nine independent reasoning engines.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>refactoring</category>
      <category>architecture</category>
      <category>security</category>
    </item>
    <item>
      <title>The $0 cloud infrastructure security stack</title>
      <dc:creator>Bala Paranj</dc:creator>
      <pubDate>Thu, 14 May 2026 11:02:07 +0000</pubDate>
      <link>https://dev.to/bala_paranj_059d338e44e7e/the-0-cloud-infrastructure-security-stack-2pdi</link>
      <guid>https://dev.to/bala_paranj_059d338e44e7e/the-0-cloud-infrastructure-security-stack-2pdi</guid>
      <description>&lt;p&gt;Maya Kaczorowski documented Oblique's $0 security stack for code, email, logs, and devices. This is the companion piece: the $0 stack for cloud infrastructure — intent verification, compound risk detection, and formal safety proofs for AWS configurations, with nine independent reasoning engines.&lt;/p&gt;

&lt;h2&gt;
  
  
  Inspired by a $0 stack
&lt;/h2&gt;

&lt;p&gt;Maya Kaczorowski recently wrote about &lt;a href="https://oblique.security/blog/security-stack/" rel="noopener noreferrer"&gt;Oblique's $0 security stack&lt;/a&gt; — world-class security tooling at zero cost. Semgrep for code analysis, TruffleHog for secret scanning, RunReveal for SIEM, Sublime for email, Apple Business for device management. All free or free-tier. All solving real problems. Her point is important: the excuse that security costs too much no longer holds.&lt;/p&gt;

&lt;p&gt;Her article covers application security, email security, log aggregation, and device management. This article covers a different domain: cloud infrastructure security — verifying whether your AWS resources are configured safely, not just correctly.&lt;/p&gt;

&lt;p&gt;The distinction matters. A configuration can be correct by every checklist and still be unsafe. Three individually-correct settings — an unauthenticated identity pool, a scoped IAM role, and a private PHI-tagged bucket — can compose into a path that lets anonymous users reach patient data. No individual check catches it because the vulnerability exists in the composition, not in any single setting. This stack solves that problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  The infrastructure security pipeline
&lt;/h2&gt;

&lt;p&gt;Commercial cloud security posture management — Wiz, Orca, Prisma Cloud — starts at five figures annually. The open-source alternative costs nothing, and it does something commercial tools structurally cannot: verify that your configurations don't contradict your own declared intent, with mathematical proofs from engines built to verify flight software.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Input              Evaluation           Reasoning              Downstream
─────              ──────────           ─────────              ──────────
Steampipe    →     Stave          →     9 External Engines →   Neo4j GDS
(cloud SQL)        (CEL predicates      Z3 / cvc5 / Yices      SIEM
                    + compound chains)  Soufflé / Clingo       SARIF
                                        Prolog / PySAT         Evidence bundles
                                        Risk / Game Theory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each layer is independent. Replace any piece without touching the others.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://steampipe.io" rel="noopener noreferrer"&gt;Steampipe&lt;/a&gt;&lt;/strong&gt; is the input layer. It queries your cloud APIs like a database — AWS, Azure, and GCP resources become SQL tables. Steampipe produces the inventory. It doesn't evaluate it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/sufield/stave" rel="noopener noreferrer"&gt;Stave&lt;/a&gt;&lt;/strong&gt; is the evaluation layer. It reads observation snapshots and evaluates them against 2,650+ controls across 74 AWS service domains. CEL predicates detect individual misconfigurations. Compound chains compose multiple findings into named attack paths. The evaluation is deterministic — same snapshot, same findings, every time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nine reasoning engines&lt;/strong&gt; consume Stave's fact export independently. Z3 proves whether forbidden states are mathematically reachable. Soufflé enumerates blast radius and reachability paths. Clingo fires declarative violation rules. Each engine adds a reasoning dimension that CEL predicates structurally cannot express — quantification, graph traversal, satisfiability. The engines are external consumers, not internal components. Stave exports facts as JSONL triples or SMT-LIB assertions. The engines read them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Downstream systems&lt;/strong&gt; consume what Stave and the engines produce. Neo4j Community Edition provides graph analysis — centrality, shortest paths, choke points. SARIF output feeds IDEs. JSONL output feeds SIEMs. Signed evidence bundles feed compliance audits.&lt;/p&gt;

&lt;h2&gt;
  
  
  What scanners check vs. what Stave verifies
&lt;/h2&gt;

&lt;p&gt;A scanner asks: "Is this setting correct?" It checks attributes on individual resources — encryption enabled, public access blocked, MFA enforced. These are necessary checks. They verify that individual nodes meet a baseline.&lt;/p&gt;

&lt;p&gt;Stave asks a different question: "Do your configurations contradict what you declared?"&lt;/p&gt;

&lt;p&gt;When you tag a bucket &lt;code&gt;data_classification: phi&lt;/code&gt;, you're declaring intent: this contains patient records. When a Bedrock knowledge base indexes that bucket and a customer-facing agent serves the results without a guardrail, your infrastructure contradicts your declaration. Three individually-correct configurations compose into a violation of your own stated intent.&lt;/p&gt;

&lt;p&gt;Scanners check 10 attributes per resource. With five layers of AWS security — IAM, SCPs, resource policies, VPC endpoints, trust relationships — a single bucket can exist in over 200 possible effective-access states. The other 190 stay unexamined. Stave collapses that state space through invariants: define which states are forbidden, prove they're unreachable.&lt;/p&gt;

&lt;p&gt;The findings reflect this difference:&lt;/p&gt;

&lt;p&gt;A scanner says: "S3 bucket is publicly accessible. Severity: High."&lt;/p&gt;

&lt;p&gt;Stave says:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CHAIN: bedrock_rag_phi_exposure (CRITICAL)

  CTL.COGNITO.UNAUTH.ACCESS.001
    Identity pool allows unauthenticated access

  CTL.IAM.ROLE.MAPPED.BROAD.001
    Mapped role has s3:GetObject on PHI bucket

  CTL.BEDROCK.AGENT.GUARDRAIL.001
    Agent has no content-filtering guardrail

  Compound: anonymous internet user can reach
  patient health records through the RAG pipeline.
  Three settings, three clean individual checks,
  one CRITICAL attack path.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The scanner found zero issues. Stave found the composition that makes the system unsafe.&lt;/p&gt;

&lt;h2&gt;
  
  
  What 2,650 controls cover
&lt;/h2&gt;

&lt;p&gt;The catalog spans 74 AWS service domains. The controls that matter most are the families that detect structural patterns no individual check can see.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;32 AI agent identity controls&lt;/strong&gt; cover Bedrock agents, SageMaker pipelines, and Lambda tool chains. Agent execution role overprivilege, missing guardrails, ghost action groups referencing deleted Lambda functions, shadow agents created outside IaC, knowledge base data boundary violations. Five compound chains compose these into attack paths: agent-to-PHI exposure through RAG pipelines, cross-account training data access, shadow agent credential theft.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shadow admin detection&lt;/strong&gt; catches IAM roles that accumulated permissions beyond their declared scope. A role named &lt;code&gt;S3-ReadOnly&lt;/code&gt; that can retrieve secrets, invoke Lambda functions, and enumerate the full IAM inventory. Five controls check permission drift (unused service ratio via Access Advisor), category mixing (data access combined with IAM write), and intent mismatch (permissions contradict the declared &lt;code&gt;role-type&lt;/code&gt; tag). Two compound chains fire when the pattern is systemic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vendor delegation governance&lt;/strong&gt; checks whether vendors with access to your S3 buckets have exceeded their declared scope, whether their access review is overdue, whether they can make your bucket public, and whether you can revoke their access. Five controls, one compound chain at threshold 3-of-5 — a single overdue review is a reminder, three concurrent failures is a systemic governance breakdown.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;23 ghost reference controls&lt;/strong&gt; detect policies that reference resources absent from the snapshot — dangling IAM trust policies pointing at deleted accounts, Cognito triggers referencing deleted Lambda functions, S3 policies granting access to buckets that no longer exist. An attacker who recreates the deleted resource under the same name inherits every permission the dangling policy still grants.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;47 credential TTL controls&lt;/strong&gt; across IAM rotation, token expiry, certificate lifecycle, Secrets Manager rotation, and KMS key management. The Time-Bound Credential Invariant checks not just that a TTL is declared, but that the TTL hasn't elapsed — the difference between "rotation is configured" and "rotation actually happened."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Temporal analysis&lt;/strong&gt; treats time as a built-in dimension. Drift detection compares snapshots and flags configuration changes. Duration tracking measures how long a misconfiguration has persisted — the same public bucket at 6 hours and 6 months tells different stories. Observation freshness detects stale snapshots — findings based on data that's 90 days old need different urgency than findings based on data from today.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nine engines, one fact export
&lt;/h2&gt;

&lt;p&gt;Stave evaluates controls with CEL predicates — the primary detection mechanism. But CEL has expressivity limits: it can't quantify over lists, traverse reachability graphs, or prove satisfiability of combined assertions. The nine external engines fill those gaps.&lt;/p&gt;

&lt;p&gt;The fact export is the interface. Stave projects observation properties into JSONL triples (subject-predicate-object) and SMT-LIB assertions. 44 scalar predicates and 6 per-element array projectors cover AI agents, VPC peering, EC2 instance profiles, IAM role drift, and S3 delegation. Every engine reads the same facts and produces independent analysis.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Z3 / cvc5 / Yices&lt;/strong&gt; (SMT solvers) prove whether forbidden states are reachable. "Can an anonymous user reach PHI data through any combination of identity pool, role mapping, and bucket policy?" The answer is &lt;code&gt;sat&lt;/code&gt; (reachable — unsafe) or &lt;code&gt;unsat&lt;/code&gt; (mathematically impossible — proven safe). These are the solvers Microsoft Research built for verifying flight control software and CPU designs. The proof is deterministic and independently reproducible. Microsoft has used the same approach to solve firewall rules verification for Azure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Soufflé&lt;/strong&gt; (Datalog) enumerates reachability paths and counts blast radius. "How many resources can this compromised role reach?" "Which vendor principals have excessive delegation reach on this bucket?" Per-element facts — &lt;code&gt;has_unused_service(role, "lambda")&lt;/code&gt;, &lt;code&gt;has_delegated_principal(bucket, principal_arn)&lt;/code&gt; — enable queries that name specific services and specific principals, not just booleans.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Clingo&lt;/strong&gt; (Answer Set Programming) fires declarative violation rules. Shipped rules cover AI agent patterns (broad Lambda + no guardrail), delegation violations (unknown principal, scope exceeded, irrevocable access), shadow admin signals (incompatible categories + unused services), VPC peering exposure, and shadow EC2 lateral movement. Every remediated fixture produces zero violations — verified end-to-end.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prolog&lt;/strong&gt; derives proof trees showing step-by-step reasoning: attacker → shadow account → trusted role → production data. &lt;strong&gt;PySAT&lt;/strong&gt; checks boolean satisfiability on multi-control compounds. &lt;strong&gt;Risk model&lt;/strong&gt; computes exploitation probability. &lt;strong&gt;Game theory&lt;/strong&gt; quantifies attacker cost vs. defender remediation ROI. &lt;strong&gt;TLA+&lt;/strong&gt; checks temporal safety — how many configuration changes separate the current state from an unsafe state.&lt;/p&gt;

&lt;p&gt;Each engine finds a different class of issue on the same fact set. That's breadth without tool sprawl — nine reasoning dimensions from one data export.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this fits in a security program
&lt;/h2&gt;

&lt;p&gt;A security program has multiple layers. Each addresses a different domain. Each can be $0 independently:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Layer                    Domain                  $0 tools
─────                    ──────                  ────────
Application security     Code + dependencies     Semgrep, TruffleHog
Infrastructure security  Cloud configuration     Steampipe, Stave, Neo4j CE
Detection &amp;amp; response     Logs + runtime events   RunReveal
Email security           Phishing + BEC          Sublime Security
Device management        Endpoints               Apple Business
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The infrastructure layer is the one most startups skip because commercial CSPM starts at five figures. The pipeline above is $0 and covers 2,650+ controls with formal verification from engines designed for safety-critical systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting it up
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Install Steampipe and the AWS plugin.&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;brew &lt;span class="nb"&gt;install &lt;/span&gt;turbot/tap/steampipe
steampipe plugin &lt;span class="nb"&gt;install &lt;/span&gt;aws
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Steampipe reads your AWS credentials from the standard locations. No additional configuration needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Verify your inventory.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="n"&gt;name&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;bucket_policy_is_public&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;aws_s3_bucket&lt;/span&gt;
&lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;bucket_policy_is_public&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If this returns results, you have public S3 buckets. Steampipe makes cloud inventory queryable in seconds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Try the demo (30 seconds, no AWS account needed).&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;git clone https://github.com/sufield/stave.git
&lt;span class="nb"&gt;cd &lt;/span&gt;stave
bash examples/demo-ai-security/run.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The demo shows the full pipeline on built-in fixtures: 5 AI agent findings → 3 CRITICAL compound chains → remediation → clean. No cloud credentials required.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4: Run against your own snapshots.&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;stave apply &lt;span class="nt"&gt;--observations&lt;/span&gt; ./my-snapshots
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Stave evaluates every observation against 2,650+ controls. Compound chains fire when multiple findings compose into attack paths on the same resource.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5: Export to reasoning engines.&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;&lt;span class="c"&gt;# JSONL triples for Soufflé / Clingo&lt;/span&gt;
stave export-sir &lt;span class="nt"&gt;--format&lt;/span&gt; jsonl &lt;span class="nt"&gt;--observations&lt;/span&gt; ./my-snapshots &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; facts.jsonl

&lt;span class="c"&gt;# SMT-LIB assertions for Z3 / cvc5&lt;/span&gt;
stave export-sir &lt;span class="nt"&gt;--format&lt;/span&gt; smt2 &lt;span class="nt"&gt;--observations&lt;/span&gt; ./my-snapshots &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; facts.smt2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 6: Track drift over time.&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;stave diff &lt;span class="nt"&gt;--before&lt;/span&gt; ./snapshot-march &lt;span class="nt"&gt;--after&lt;/span&gt; ./snapshot-april
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compares snapshots and flags configuration changes with before-and-after state.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 7: Export graph for Neo4j.&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;stave graph &lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt; graphml &lt;span class="nt"&gt;--observations&lt;/span&gt; ./my-snapshots &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; graph.graphml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Load into Neo4j Community Edition for centrality analysis, shortest-path computation, and effective permission reasoning.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you get for $0
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The input layer (Steampipe)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SQL queries over AWS, Azure, and GCP resources&lt;/li&gt;
&lt;li&gt;150+ plugins covering every major cloud provider and SaaS service&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The evaluation layer (Stave)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;2,650+ controls across 74 AWS service domains&lt;/li&gt;
&lt;li&gt;30+ compound chains composing individual findings into named attack paths&lt;/li&gt;
&lt;li&gt;32 AI agent identity controls (Bedrock, SageMaker, Lambda tool chains)&lt;/li&gt;
&lt;li&gt;Shadow admin detection (permission drift + category mixing + intent mismatch)&lt;/li&gt;
&lt;li&gt;Vendor delegation governance (scope, lifecycle, revocability, escalation)&lt;/li&gt;
&lt;li&gt;23 ghost reference controls detecting dangling policies after resource deletion&lt;/li&gt;
&lt;li&gt;47 credential TTL controls with elapsed-TTL verification&lt;/li&gt;
&lt;li&gt;Temporal analysis: drift detection, duration tracking, observation freshness&lt;/li&gt;
&lt;li&gt;Intent verification: findings fire when configurations contradict declared tags, role-type taxonomies, and vendor registries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The reasoning layer (9 engines)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Z3 / cvc5 / Yices: mathematical proofs of safety or reachability&lt;/li&gt;
&lt;li&gt;Soufflé: blast radius enumeration and reachability path counting&lt;/li&gt;
&lt;li&gt;Clingo: declarative violation rules across 5 attack pattern families&lt;/li&gt;
&lt;li&gt;Prolog: step-by-step proof trees for attack path derivation&lt;/li&gt;
&lt;li&gt;PySAT / Risk / Game Theory / TLA+: satisfiability, probability, cost, temporal safety&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The insights layer (downstream)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Neo4j GDS: graph centrality, shortest paths, choke point identification&lt;/li&gt;
&lt;li&gt;SARIF: IDE integration for developer-visible findings in pull requests&lt;/li&gt;
&lt;li&gt;JSONL: SIEM ingestion for correlation with runtime events&lt;/li&gt;
&lt;li&gt;Evidence bundles: signed compliance archives&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The commercial equivalent of this pipeline costs $25,000–$100,000+ annually. The open-source version costs nothing, includes formal verification from SMT solvers designed for flight software, and verifies intent — not just configuration — across 74 service domains.&lt;/p&gt;

&lt;h2&gt;
  
  
  Infrastructure security doesn't have to cost anything
&lt;/h2&gt;

&lt;p&gt;The reason most startups skip cloud security posture management is that the tools cost more than their entire infrastructure spend. A startup paying $500/month for AWS can't justify $50,000/year for a CSPM tool. So they don't. Their S3 buckets stay public, their IAM roles accumulate permissions nobody reviews, their deleted resources leave ghost references nobody detects, and their AI agents serve PHI through RAG pipelines nobody verified.&lt;/p&gt;

&lt;p&gt;The open-source pipeline removes this barrier. Steampipe + Stave + nine reasoning engines + Neo4j costs nothing and provides capabilities commercial CSPM tools structurally cannot: intent verification against operator declarations, compound risk detection across service boundaries, mathematical safety proofs from SMT solvers, and temporal analysis that tracks how configurations evolve over time.&lt;/p&gt;

&lt;p&gt;Maya Kaczorowski showed that application security, email security, log aggregation, and device management can all be $0. This article shows the same is true for cloud infrastructure security — and that the $0 version doesn't just match the commercial tools. On compound risk detection, intent verification, and formal safety proofs, it goes beyond them.&lt;/p&gt;

&lt;p&gt;Three tools. Nine engines. Zero dollars. A pipeline, not a product.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;&lt;a href="https://github.com/sufield/stave" rel="noopener noreferrer"&gt;Stave&lt;/a&gt; is an open-source intent verification engine for cloud infrastructure with 2,650+ controls, compound risk detection, and nine independent reasoning engines. &lt;a href="https://steampipe.io" rel="noopener noreferrer"&gt;Steampipe&lt;/a&gt; is an open-source tool for querying cloud APIs via SQL. Together with &lt;a href="https://neo4j.com/product/community/" rel="noopener noreferrer"&gt;Neo4j&lt;/a&gt; for graph insights, they form the $0 infrastructure security pipeline.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>aws</category>
      <category>cloud</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Your Go Golden Tests Don't Need to Regenerate Everything</title>
      <dc:creator>Bala Paranj</dc:creator>
      <pubDate>Wed, 13 May 2026 10:55:51 +0000</pubDate>
      <link>https://dev.to/bala_paranj_059d338e44e7e/your-go-golden-tests-dont-need-to-regenerate-everything-2o6g</link>
      <guid>https://dev.to/bala_paranj_059d338e44e7e/your-go-golden-tests-dont-need-to-regenerate-everything-2o6g</guid>
      <description>&lt;p&gt;A practical pattern for targeted golden file regeneration in Go projects — from minutes to 0.27 seconds.&lt;/p&gt;

&lt;p&gt;I have 5,810 golden files in my project. Every time I changed one test, I was regenerating all of them. It took minutes. Now it takes 0.27 seconds.&lt;/p&gt;

&lt;p&gt;The fix was just organizing the regeneration path so you could aim it at one file instead of firing at everything.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with regenerate everything
&lt;/h2&gt;

&lt;p&gt;Golden tests are great. You capture known-good output, save it to a file, and compare against it on every run. When the output changes intentionally, you regenerate the golden file.&lt;/p&gt;

&lt;p&gt;Most Go projects start with a simple approach: a Makefile target that regenerates all golden files at once.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;make regenerate-goldens
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works when you have 20 golden files. When you have 5,810, it doesn't. You change one test, you wait for the tool to process every fixture directory, and most of the time nothing else changed. You're wasting time to update one file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two kinds of golden tests
&lt;/h2&gt;

&lt;p&gt;Before fixing anything, I audited how golden files worked in the codebase. I found two completely different mechanisms hiding behind the same word.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In-process goldens&lt;/strong&gt; — a test function renders output, compares it against a &lt;code&gt;.golden&lt;/code&gt; or &lt;code&gt;golden.json&lt;/code&gt; file in &lt;code&gt;testdata/&lt;/code&gt;. The test itself can write the file if you ask it to. I had 3 of these.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;E2e fixture goldens&lt;/strong&gt; — an external tool runs the compiled binary against fixture directories and captures stdout into &lt;code&gt;expected.*&lt;/code&gt; files. The test reads those files and compares. I had 5,807 of these.&lt;/p&gt;

&lt;p&gt;These need different regeneration strategies. Trying to unify them under one mechanism would either duplicate the external tool inside the test (pointless) or force the external tool to understand in-process test output (brittle).&lt;/p&gt;

&lt;h2&gt;
  
  
  The in-process pattern: UPDATE_GOLDEN env var
&lt;/h2&gt;

&lt;p&gt;For the 3 in-process golden tests, I added a small helper to the existing test utilities package.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;testutil&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"bytes"&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;
    &lt;span class="s"&gt;"path/filepath"&lt;/span&gt;
    &lt;span class="s"&gt;"testing"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/google/go-cmp/cmp"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;UpdateGolden&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;bool&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;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"UPDATE_GOLDEN"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;AssertGolden&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;got&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Helper&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;UpdateGolden&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;writeIfChanged&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;got&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;want&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"read golden file %s: %v&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Run with UPDATE_GOLDEN=1 to create it"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;diff&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;cmp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Diff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;want&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;got&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="n"&gt;diff&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"golden mismatch %s (-want +got):&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;%s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Run with UPDATE_GOLDEN=1 to update"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;writeIfChanged&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Helper&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;old&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;old&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MkdirAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filepath&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Dir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="m"&gt;0755&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"create golden dir: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0644&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"write golden file %s: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Logf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"updated golden file: %s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&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;Usage in a test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;TestTextReporter_Golden&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;got&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;renderReport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buildFixture&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;testutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AssertGolden&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"testdata/reports/hipaa_golden.txt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;got&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;Regenerate just that one test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;UPDATE_GOLDEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 go &lt;span class="nb"&gt;test&lt;/span&gt; ./internal/profile/reporter &lt;span class="nt"&gt;-run&lt;/span&gt; TestTextReporter_Golden
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;0.27 seconds.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why an env var instead of a flag
&lt;/h3&gt;

&lt;p&gt;My first version used &lt;code&gt;flag.Bool("update", ...)&lt;/code&gt;. The problem: Go test flags are per-package. If you define &lt;code&gt;-update&lt;/code&gt; in one package and run &lt;code&gt;go test ./... -update&lt;/code&gt;, every other package fails because it doesn't recognize the flag.&lt;/p&gt;

&lt;p&gt;An environment variable works across all packages without any registration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# This works for any package, any test&lt;/span&gt;
&lt;span class="nv"&gt;UPDATE_GOLDEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 go &lt;span class="nb"&gt;test&lt;/span&gt; ./path/to/package &lt;span class="nt"&gt;-run&lt;/span&gt; TestWhatever
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why writeIfChanged matters
&lt;/h3&gt;

&lt;p&gt;Without it, every &lt;code&gt;UPDATE_GOLDEN=1 go test ./...&lt;/code&gt; run touches every golden file's timestamp, even if the content didn't change. Your git status fills up with phantom changes. &lt;code&gt;writeIfChanged&lt;/code&gt; reads the file first, compares bytes, and skips the write if nothing changed. Five lines that keep your diffs clean.&lt;/p&gt;

&lt;h2&gt;
  
  
  The e2e pattern: wrap what already exists
&lt;/h2&gt;

&lt;p&gt;For the 5,807 fixture goldens, I already had a regeneration tool (&lt;code&gt;regengoldens&lt;/code&gt;) that accepted a &lt;code&gt;-filter&lt;/code&gt; regex. The problem was discoverability. Nobody remembered the flag syntax.&lt;/p&gt;

&lt;p&gt;I added a Makefile target:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="nl"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;golden-fixture&lt;/span&gt;
&lt;span class="nl"&gt;golden-fixture&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;&lt;span class="s2"&gt;FILTER&lt;/span&gt;&lt;span class="p"&gt;)&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;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Usage: make golden-fixture FILTER=&amp;lt;regex&amp;gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;$(&lt;/span&gt;MAKE&lt;span class="p"&gt;)&lt;/span&gt; regenerate-goldens &lt;span class="nv"&gt;ARGS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'-filter &lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;&lt;span class="s1"&gt;FILTER&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now regenerating one fixture set:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;make golden-fixture &lt;span class="nv"&gt;FILTER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;hipaa
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No syntax to remember. Tab-completable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The full Makefile surface
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="c"&gt;# In-process goldens
&lt;/span&gt;&lt;span class="nl"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;golden-update-all&lt;/span&gt;
&lt;span class="nl"&gt;golden-update-all&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="nv"&gt;UPDATE_GOLDEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 go &lt;span class="nb"&gt;test&lt;/span&gt; ./...

&lt;span class="nl"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;golden-update&lt;/span&gt;
&lt;span class="nl"&gt;golden-update&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;&lt;span class="s2"&gt;PKG&lt;/span&gt;&lt;span class="p"&gt;)&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;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Usage: make golden-update PKG=./path/to/pkg/..."&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nv"&gt;UPDATE_GOLDEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 go &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;PKG&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nl"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;golden-one&lt;/span&gt;
&lt;span class="nl"&gt;golden-one&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;&lt;span class="s2"&gt;PKG&lt;/span&gt;&lt;span class="p"&gt;)&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;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Usage: make golden-one PKG=./path/to/pkg/... RUN=TestName"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;&lt;span class="s2"&gt;RUN&lt;/span&gt;&lt;span class="p"&gt;)&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;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Usage: make golden-one PKG=./path/to/pkg/... RUN=TestName"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nv"&gt;UPDATE_GOLDEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 go &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;PKG&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;-run&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;&lt;span class="s1"&gt;RUN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;

&lt;span class="c"&gt;# E2e fixture goldens
&lt;/span&gt;&lt;span class="nl"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;golden-fixture&lt;/span&gt;
&lt;span class="nl"&gt;golden-fixture&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;&lt;span class="s2"&gt;FILTER&lt;/span&gt;&lt;span class="p"&gt;)&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;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Usage: make golden-fixture FILTER=&amp;lt;regex&amp;gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;$(&lt;/span&gt;MAKE&lt;span class="p"&gt;)&lt;/span&gt; regenerate-goldens &lt;span class="nv"&gt;ARGS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'-filter &lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;&lt;span class="s1"&gt;FILTER&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two mechanisms, one discoverable surface. &lt;code&gt;grep golden Makefile&lt;/code&gt; tells you everything.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I didn't do
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;I didn't unify the two mechanisms.&lt;/strong&gt; The in-process tests and e2e fixture tests have different architectures. Forcing them into one pattern would mean either duplicating the external regeneration tool inside Go tests or making the tool understand in-process rendering. Both are worse than having two clear paths.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I didn't add subtests where they weren't needed.&lt;/strong&gt; The original plan called for converting flat tests to subtests for &lt;code&gt;-run&lt;/code&gt; targeting. In practice, my 3 in-process golden tests were either single-case or already subtested. Converting for the sake of a pattern would have been churn.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I didn't migrate one test that would have changed golden content.&lt;/strong&gt; One golden file was a hand-ordered JSON map. &lt;code&gt;json.MarshalIndent&lt;/code&gt; sorts keys alphabetically, so running &lt;code&gt;AssertGolden&lt;/code&gt; with &lt;code&gt;UPDATE_GOLDEN=1&lt;/code&gt; would have silently reordered the file. The rule I set was: the migration changes the mechanism, not the content. If any golden file changes, something is wrong. I left that test alone and documented why.&lt;/p&gt;

&lt;h2&gt;
  
  
  The verification that matters
&lt;/h2&gt;

&lt;p&gt;After the migration, this is the check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;UPDATE_GOLDEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 go &lt;span class="nb"&gt;test&lt;/span&gt; ./...
git diff &lt;span class="nt"&gt;--name-only&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="s1"&gt;'*.golden'&lt;/span&gt; &lt;span class="s1"&gt;'*.golden.*'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The diff must be empty. If any golden file changed during migration, the helper introduced a difference — a trailing newline, an encoding change, a key reordering. That's a bug in the migration, not a legitimate update.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;Before: change one test, run &lt;code&gt;make regenerate-goldens&lt;/code&gt;, wait minutes.&lt;/p&gt;

&lt;p&gt;After: change one test, run &lt;code&gt;UPDATE_GOLDEN=1 go test ./pkg/foo -run TestBar&lt;/code&gt;, wait 0.27 seconds.&lt;/p&gt;

&lt;p&gt;The approach is boring. Env var, a 40-line helper, a few Makefile targets. Nothing novel. But the development loop went from avoid touching golden tests to golden tests are free to change.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This speed up was applied to &lt;a href="https://github.com/sufield/stave" rel="noopener noreferrer"&gt;Stave&lt;/a&gt; codebase, an offline configuration safety evaluator.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>testing</category>
      <category>devops</category>
      <category>productivity</category>
    </item>
    <item>
      <title>9 Go Performance Patterns That Don't Need a Profiler to Find</title>
      <dc:creator>Bala Paranj</dc:creator>
      <pubDate>Tue, 12 May 2026 11:26:16 +0000</pubDate>
      <link>https://dev.to/bala_paranj_059d338e44e7e/9-go-performance-patterns-that-dont-need-a-profiler-to-find-iid</link>
      <guid>https://dev.to/bala_paranj_059d338e44e7e/9-go-performance-patterns-that-dont-need-a-profiler-to-find-iid</guid>
      <description>&lt;p&gt;Pre-allocated slices, bitmask operations, index-based iteration, strings.Builder with Grow, switch-over-map hot paths, defensive cloning, sync. Once caching, WalkDir over Walk, and defined types avoiding string conversion — patterns visible in code review.&lt;/p&gt;

&lt;p&gt;Most performance advice starts with run a profiler. That's correct for micro-optimization. But the patterns in this article are visible in code review. You don't need a flamegraph to know that copying a 408-byte struct in a loop 10,000 times is slower than taking a pointer to it.&lt;/p&gt;

&lt;p&gt;These are the patterns we applied across a 50,000-line Go security CLI. Each one has a reason that doesn't require benchmarks to justify.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Pre-Allocate Slices and Maps
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Cost of Growth
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// BAD: append grows the backing array 5+ times for 100 elements&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;Finding&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;asset&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;assets&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;isViolation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buildFinding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;))&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;Each time &lt;code&gt;append&lt;/code&gt; exceeds the slice capacity, Go allocates a new backing array (typically 2x the current size), copies all existing elements, and lets the old array be garbage collected. For 100 elements starting from zero, that's approximately 7 allocations and 7 copies.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Fix
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// GOOD: one allocation, zero copies&lt;/span&gt;
&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&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="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;assets&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c"&gt;// cap = upper bound&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;asset&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;assets&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;isViolation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buildFinding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;))&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;Same pattern for maps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// BAD: map grows through multiple rehashes&lt;/span&gt;
&lt;span class="n"&gt;baseMap&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&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="c"&gt;// GOOD: pre-sized to avoid rehashing&lt;/span&gt;
&lt;span class="n"&gt;baseMap&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&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="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;baseline&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The capacity hint doesn't need to be exact. It's an upper bound. &lt;code&gt;make([]T, 0, len(input))&lt;/code&gt; is correct even if the filter removes 90% of elements. The wasted capacity (a few KB of pointers) is cheaper than the allocation churn.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where to Apply
&lt;/h3&gt;

&lt;p&gt;Pre-allocate when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You know the upper bound (input length, map size)&lt;/li&gt;
&lt;li&gt;The collection is built in a loop&lt;/li&gt;
&lt;li&gt;The collection survives the function (returned, stored in a struct)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Skip when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The collection is tiny (&amp;lt; 8 elements — Go's small-object optimization handles it)&lt;/li&gt;
&lt;li&gt;The upper bound is unknown or enormous&lt;/li&gt;
&lt;li&gt;You're building a result from streaming data&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. Index-Based Iteration for Large Structs
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Cost of Range-Value Copy
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// BAD: copies 408 bytes per iteration&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;findings&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&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;Go's &lt;code&gt;for _, v := range&lt;/code&gt; copies the element into &lt;code&gt;v&lt;/code&gt; on every iteration. For a &lt;code&gt;Finding&lt;/code&gt; struct (408 bytes), iterating over 1,000 findings copies 408 KB of data — just to read each element.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Fix
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// GOOD: zero copy — pointer to element in existing slice memory&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;findings&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;findings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&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;&lt;code&gt;&amp;amp;findings[i]&lt;/code&gt; takes a pointer to the element in the slice's backing array. No copy. The pointer is 8 bytes regardless of struct size.&lt;/p&gt;

&lt;h3&gt;
  
  
  When It Matters
&lt;/h3&gt;

&lt;p&gt;The Go compiler may optimize small structs (&amp;lt; 64 bytes) to registers. For structs above 128 bytes, the copy is measurable. We set the &lt;code&gt;rangeValCopy&lt;/code&gt; gocritic threshold to 128 bytes and fixed 70 loops across the codebase.&lt;/p&gt;

&lt;p&gt;Types we fixed:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;Loop Count&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;remediation.Finding&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;408B&lt;/td&gt;
&lt;td&gt;15 loops&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;policy.ControlDefinition&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;304B&lt;/td&gt;
&lt;td&gt;12 loops&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;remediation.RemediationFinding&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;352B&lt;/td&gt;
&lt;td&gt;8 loops&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;securityaudit.Finding&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;160B&lt;/td&gt;
&lt;td&gt;5 loops&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;risk.Item&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;152B&lt;/td&gt;
&lt;td&gt;4 loops&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;s3/policy.Statement&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;128B&lt;/td&gt;
&lt;td&gt;3 loops&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Do NOT change &lt;code&gt;[]T&lt;/code&gt; to &lt;code&gt;[]*T&lt;/code&gt;.&lt;/strong&gt; Pointer slices destroy cache locality — each element is a separate heap allocation that the CPU prefetcher can't predict. Keep contiguous &lt;code&gt;[]T&lt;/code&gt; memory; access individual elements by index.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Switch Over Map for Hot-Path Dispatch
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Cost of Map + Closures
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// BAD: map lookup + closure allocation on every call&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;operators&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Operator&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;operatorFunc&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;OpEq&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="n"&gt;handled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exists&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&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;exists&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;EqualValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="n"&gt;OpNe&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="n"&gt;handled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exists&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;exists&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;EqualValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="c"&gt;// ... 14 operators&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;EvaluateOperator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt; &lt;span class="n"&gt;Operator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exists&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;compare&lt;/span&gt; &lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;operators&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;false&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;fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;compare&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c"&gt;// 2 levels of function indirection&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three costs: (1) hash the operator string and probe the map, (2) call through a &lt;code&gt;handled()&lt;/code&gt; wrapper that allocates a closure, (3) call through the inner closure. On the hot path (43 controls × 50 assets × 10 snapshots), that's 21,500 map lookups with closure calls.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Fix
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// GOOD: compiler-generated jump table, zero allocation&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;EvaluateOperator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt; &lt;span class="n"&gt;Operator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exists&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;compare&lt;/span&gt; &lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handled&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;handled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;OpEq&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;exists&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;EqualValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;compare&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;OpNe&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;exists&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;EqualValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;compare&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;OpGt&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;exists&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;GreaterThan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;compare&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c"&gt;// ... 14 cases&lt;/span&gt;
    &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;false&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;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The switch compiles to a jump table — same O(1) dispatch as the map, but without hashing, without closure allocation, and without function pointer indirection. The CPU branch predictor can optimize switch patterns better than indirect function calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Bitmask Operations for Permission Analysis
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Cost of Slice-Based Tracking
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// BAD: O(n) contains check for each permission&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Permissions&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="n"&gt;Permissions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;HasRead&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;bool&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;slices&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"s3:GetObject"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;slices&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"s3:*"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;slices&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"*"&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;Three linear scans per check. For a policy with 20 actions, that's 60 string comparisons to answer "does this grant read access?"&lt;/p&gt;

&lt;h3&gt;
  
  
  The Fix
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// GOOD: O(1) bitmask check&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;ActionMask&lt;/span&gt; &lt;span class="kt"&gt;uint8&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ActionRead&lt;/span&gt;    &lt;span class="n"&gt;ActionMask&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;iota&lt;/span&gt;  &lt;span class="c"&gt;// s3:GetObject&lt;/span&gt;
    &lt;span class="n"&gt;ActionWrite&lt;/span&gt;                           &lt;span class="c"&gt;// s3:PutObject&lt;/span&gt;
    &lt;span class="n"&gt;ActionDelete&lt;/span&gt;                          &lt;span class="c"&gt;// s3:DeleteObject&lt;/span&gt;
    &lt;span class="n"&gt;ActionList&lt;/span&gt;                            &lt;span class="c"&gt;// s3:ListBucket&lt;/span&gt;
    &lt;span class="n"&gt;actionACLWrite&lt;/span&gt;                        &lt;span class="c"&gt;// s3:PutBucketAcl (unexported)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="n"&gt;ActionMask&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flag&lt;/span&gt; &lt;span class="n"&gt;ActionMask&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&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;m&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;flag&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="n"&gt;Statement&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;ResolveActions&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ActionMask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;mask&lt;/span&gt; &lt;span class="n"&gt;ActionMask&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Action&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ToLower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"*"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"s3:*"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;mask&lt;/span&gt; &lt;span class="o"&gt;|=&lt;/span&gt; &lt;span class="n"&gt;ActionRead&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;ActionWrite&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;ActionDelete&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;ActionList&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;actionACLWrite&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"s3:getobject"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;mask&lt;/span&gt; &lt;span class="o"&gt;|=&lt;/span&gt; &lt;span class="n"&gt;ActionRead&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"s3:putobject"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;mask&lt;/span&gt; &lt;span class="o"&gt;|=&lt;/span&gt; &lt;span class="n"&gt;ActionWrite&lt;/span&gt;
        &lt;span class="c"&gt;// ...&lt;/span&gt;
        &lt;span class="p"&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;mask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bits&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OnesCount8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Check: one bitwise AND, constant time&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="n"&gt;Statement&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;GrantsReadAccess&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResolveActions&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;mask&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ActionRead&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;Parse the action list once into a bitmask. Every subsequent permission check is a single bitwise AND — constant time, zero allocation, fits in a register.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. strings.Builder with Pre-Allocation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Cost of String Concatenation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// BAD: each += allocates a new string&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;finding&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;findings&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;finding&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ControlID&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;": "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;finding&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Evidence&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TemporalRisk&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Go strings are immutable. Each &lt;code&gt;+=&lt;/code&gt; allocates a new backing array and copies the accumulated string. For 100 findings with 50-character lines, that's 100 allocations copying an average of 2.5 KB each.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Fix
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// GOOD: one allocation, zero copies&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Builder&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Grow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&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="o"&gt;*&lt;/span&gt; &lt;span class="m"&gt;64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c"&gt;// estimate: 64 bytes per finding&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;findings&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;findings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ControlID&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;": "&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Evidence&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TemporalRisk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteByte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;'\n'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;strings.Builder&lt;/code&gt; writes to a growing byte buffer. &lt;code&gt;Grow(n)&lt;/code&gt; pre-allocates &lt;code&gt;n&lt;/code&gt; bytes so the buffer never needs to resize if your estimate is close. The final &lt;code&gt;String()&lt;/code&gt; converts the buffer to a string without copying (Go 1.10+).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;WriteString&lt;/code&gt; over &lt;code&gt;fmt.Fprintf&lt;/code&gt;:&lt;/strong&gt; &lt;code&gt;WriteString&lt;/code&gt; is a direct memcpy. &lt;code&gt;Fprintf&lt;/code&gt; parses a format string, allocates for the format args, and calls reflection for &lt;code&gt;%v&lt;/code&gt;. For simple concatenation, &lt;code&gt;WriteString&lt;/code&gt; is 5-10x faster.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Grow Estimate
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Grow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="m"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c"&gt;// 5 KB for a markdown report&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Grow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;256&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;        &lt;span class="c"&gt;// 256 bytes for a diagnostic error message&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Grow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c"&gt;// 80 bytes per line estimate&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Over-estimating is fine — the wasted capacity is a few KB. Under-estimating triggers a reallocation (still better than &lt;code&gt;+=&lt;/code&gt;). The rule: estimate the total output size from the input size and allocate once.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. strings.Cut Over Split+Index
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Cost of Split
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// BAD: allocates a []string, splits entire string&lt;/span&gt;
&lt;span class="n"&gt;parts&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"="&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&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;&lt;code&gt;strings.Split&lt;/code&gt; allocates a slice and all the substrings. For &lt;code&gt;"KEY=value=with=equals"&lt;/code&gt;, it allocates 4 strings when you only need 2.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Fix
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// GOOD: zero allocation, stops at first separator&lt;/span&gt;
&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cut&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"="&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// key = "KEY", value = "value=with=equals"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;strings.Cut&lt;/code&gt; (Go 1.18+) returns substrings of the original string — no allocation. It stops at the first separator, so &lt;code&gt;"KEY=value=with=equals"&lt;/code&gt; correctly splits into &lt;code&gt;"KEY"&lt;/code&gt; and &lt;code&gt;"value=with=equals"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For prefix checking:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// BAD: allocates trimmed string&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HasPrefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"severity:"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;rest&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"severity:"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// GOOD: CutPrefix returns substring, no allocation&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CutPrefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"severity:"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// rest is a substring, zero allocation&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  7. Defined Types Avoiding Conversion
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Cost of String Conversion
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// BAD: string() creates a new string from the typed value&lt;/span&gt;
&lt;span class="n"&gt;assetID&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;asset&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"bucket-1"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;assetID&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;observations&lt;/span&gt;  &lt;span class="c"&gt;// allocates new string on every call&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;assetID.String()&lt;/code&gt; returns &lt;code&gt;string(assetID)&lt;/code&gt; — which in Go allocates a new string and copies the bytes. In a loop over 1,000 assets × 10 snapshots, that's 10,000 string allocations.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Fix
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// GOOD: use the typed value directly as map key&lt;/span&gt;
&lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;assetID&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;observations&lt;/span&gt;  &lt;span class="c"&gt;// asset.ID IS a string — no conversion&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;asset.ID&lt;/code&gt; is &lt;code&gt;type ID string&lt;/code&gt;. Go allows defined string types as map keys. The map uses the underlying string bytes directly. No conversion, no allocation.&lt;/p&gt;

&lt;p&gt;The same principle applies to function parameters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// BAD: convert to string for a function that accepts string&lt;/span&gt;
&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;controlID&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c"&gt;// GOOD: use the String() method (or let fmt call it)&lt;/span&gt;
&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;controlID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c"&gt;// fmt calls String() automatically&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  When Conversion is Needed
&lt;/h3&gt;

&lt;p&gt;Sometimes you need &lt;code&gt;string()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// String conversion required: joining typed values into a single string&lt;/span&gt;
&lt;span class="n"&gt;joinedPath&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vendor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"/"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;assetType&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But within the same type system — passing &lt;code&gt;ControlID&lt;/code&gt; to a function that accepts &lt;code&gt;ControlID&lt;/code&gt;, using it as a map key, comparing it — no conversion is needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. sync.Once and sync.Map for Initialization
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Cost of Repeated Initialization
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// BAD: check-then-init has a race condition&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;ExemptionConfig&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;prepared&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;
    &lt;span class="n"&gt;exactMap&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Rule&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ExemptionConfig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Prepare&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prepared&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;  &lt;span class="c"&gt;// RACE: two goroutines can both see false&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exactMap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;buildIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Assets&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prepared&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Fix
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// GOOD: sync.Once is atomic and runs exactly once&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;ExemptionConfig&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;once&lt;/span&gt;     &lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Once&lt;/span&gt;
    &lt;span class="n"&gt;exactMap&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Rule&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ExemptionConfig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Prepare&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;once&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Do&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exactMap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;buildIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Assets&lt;/span&gt;&lt;span class="p"&gt;)&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;For caching values keyed by pointer identity (TTY detection results):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Cache TTY detection result per file descriptor&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ttyCache&lt;/span&gt; &lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Map&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;CanColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Writer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;reflect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ValueOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pointer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ttyCache&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;bool&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;v&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;enabled&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;detectTTY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ttyCache&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;enabled&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;enabled&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;sync.Map&lt;/code&gt; is optimized for the "write-once, read-many" pattern — exactly what caching needs. No mutex contention on the read path after the first call.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. WalkDir Over Walk
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Cost of Extra Syscalls
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// BAD: filepath.Walk calls Lstat on every entry&lt;/span&gt;
&lt;span class="n"&gt;filepath&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Walk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;info&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FileInfo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// info is from Lstat — an extra syscall per entry&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;filepath.Walk&lt;/code&gt; calls &lt;code&gt;os.Lstat&lt;/code&gt; on every directory entry to populate &lt;code&gt;os.FileInfo&lt;/code&gt;. For a directory with 10,000 files, that's 10,000 extra syscalls — each one a kernel context switch.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Fix
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// GOOD: filepath.WalkDir uses cached DirEntry&lt;/span&gt;
&lt;span class="n"&gt;filepath&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WalkDir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="n"&gt;fs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DirEntry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// d.IsDir() and d.Name() are cached from readdir — no extra syscall&lt;/span&gt;
    &lt;span class="c"&gt;// Only call d.Info() when you actually need size/mode/modtime&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;filepath.WalkDir&lt;/code&gt; (Go 1.16+) uses &lt;code&gt;fs.DirEntry&lt;/code&gt;, which caches the entry type from the &lt;code&gt;readdir&lt;/code&gt; syscall. &lt;code&gt;d.IsDir()&lt;/code&gt; and &lt;code&gt;d.Name()&lt;/code&gt; are free. &lt;code&gt;d.Info()&lt;/code&gt; calls &lt;code&gt;Lstat&lt;/code&gt; only when you explicitly need it — most filters don't (skip hidden dirs, match extensions).&lt;/p&gt;

&lt;h2&gt;
  
  
  The Checklist
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pattern&lt;/th&gt;
&lt;th&gt;Cost of Not Doing It&lt;/th&gt;
&lt;th&gt;Fix Effort&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Pre-allocate slices&lt;/td&gt;
&lt;td&gt;O(log n) allocations + copies&lt;/td&gt;
&lt;td&gt;One &lt;code&gt;make()&lt;/code&gt; parameter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Index-based iteration&lt;/td&gt;
&lt;td&gt;128-408B copy per iteration&lt;/td&gt;
&lt;td&gt;Change &lt;code&gt;_, v&lt;/code&gt; to &lt;code&gt;i := range&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Switch over map&lt;/td&gt;
&lt;td&gt;Hash + closure + indirect call per dispatch&lt;/td&gt;
&lt;td&gt;Replace map with switch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bitmask operations&lt;/td&gt;
&lt;td&gt;O(n) contains per check&lt;/td&gt;
&lt;td&gt;One-time parse + O(1) checks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;strings.Builder + Grow&lt;/td&gt;
&lt;td&gt;O(n²) concatenation&lt;/td&gt;
&lt;td&gt;Replace &lt;code&gt;+=&lt;/code&gt; with &lt;code&gt;WriteString&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;strings.Cut&lt;/td&gt;
&lt;td&gt;Allocate full split slice&lt;/td&gt;
&lt;td&gt;Replace &lt;code&gt;Split&lt;/code&gt; with &lt;code&gt;Cut&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Defined type as key&lt;/td&gt;
&lt;td&gt;String conversion per use&lt;/td&gt;
&lt;td&gt;Remove &lt;code&gt;string()&lt;/code&gt; casts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sync.Once / sync.Map&lt;/td&gt;
&lt;td&gt;Race condition or mutex contention&lt;/td&gt;
&lt;td&gt;Replace bool flag with Once&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WalkDir over Walk&lt;/td&gt;
&lt;td&gt;Extra Lstat syscall per entry&lt;/td&gt;
&lt;td&gt;Change &lt;code&gt;Walk&lt;/code&gt; to &lt;code&gt;WalkDir&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;None of these require benchmarks to justify. They're visible in code review, mechanical to apply, and eliminate entire categories of unnecessary work. The profiler is for finding the 1% improvements after these 9 patterns are already in place.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;These 9 performance patterns were applied across &lt;a href="https://github.com/sufield/stave" rel="noopener noreferrer"&gt;Stave&lt;/a&gt;, a Go CLI for offline security evaluation. The index-based iteration alone fixed 70 loops copying structs up to 408 bytes. The switch-over-map change eliminated closure allocations on the predicate evaluation hot path.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>programming</category>
      <category>performance</category>
      <category>cli</category>
    </item>
    <item>
      <title>Two Problems, Two Tools: Why AI-Assisted Scanning and Configuration Verification Solve Different Things</title>
      <dc:creator>Bala Paranj</dc:creator>
      <pubDate>Mon, 11 May 2026 14:00:56 +0000</pubDate>
      <link>https://dev.to/bala_paranj_059d338e44e7e/two-problems-two-tools-why-ai-assisted-scanning-and-configuration-verification-solve-different-7e2</link>
      <guid>https://dev.to/bala_paranj_059d338e44e7e/two-problems-two-tools-why-ai-assisted-scanning-and-configuration-verification-solve-different-7e2</guid>
      <description>&lt;p&gt;There's growing confusion in cloud security about what AI-assisted tools can do. Some of the confusion comes from inflated claims about AI-powered vulnerability discovery. Some comes from genuine uncertainty about where different tools fit. But most of it comes from treating security as one problem when it's actually two. The two problems require fundamentally different approaches.&lt;/p&gt;

&lt;p&gt;Before evaluating any tool, separate the problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two classes of security problems
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Class 1: Pattern Recognizable Problems
&lt;/h3&gt;

&lt;p&gt;SQL injection is a vulnerability regardless of the operator. Unsanitized user input concatenated into a SQL query is dangerous in every application, every deployment, every organization. The operator's intent doesn't change the verdict. Nobody intends for their application to be injectable.&lt;/p&gt;

&lt;p&gt;The same applies to XSS, buffer overflows, command injection, insecure deserialization, and most of the OWASP Top 10 for web applications. These are universal patterns. The vulnerability exists because of how the code works, not because of what the operator intended. A function that passes user input to &lt;code&gt;eval()&lt;/code&gt; is unsafe whether the application is a healthcare portal or a recipe blog.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key property:&lt;/strong&gt; The verdict is independent of the operator's intent. The pattern generalizes across deployments. If you've seen one SQL injection, you can recognize the next one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Class 2: Intent Dependent Problems
&lt;/h3&gt;

&lt;p&gt;A public S3 bucket is correct for a static website hosting CSS files. The same configuration is a HIPAA violation when the bucket contains patient records. The bucket settings are identical. The verdict depends entirely on what the operator declared about the data inside.&lt;/p&gt;

&lt;p&gt;A Bedrock agent with &lt;code&gt;lambda:InvokeFunction&lt;/code&gt; on &lt;code&gt;Resource: *&lt;/code&gt; is appropriate for an internal developer tool that needs to orchestrate arbitrary workflows. The same permission is catastrophic when the agent serves customer-facing queries and one of those Lambda functions reads from a PHI-tagged bucket. The IAM policy is identical. The verdict depends on the interaction between the policy, the agent's purpose, and the data classification of the resources the tool chain can reach.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key property:&lt;/strong&gt; The verdict depends on the operator's declared intent. The pattern does not generalize across deployments. Every organization's tags, policies, and trust relationships are unique. Seeing a thousand examples of "this configuration is unsafe" doesn't tell you whether the next configuration is unsafe — because the next operator's intent is different.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why this distinction matters
&lt;/h3&gt;

&lt;p&gt;Mixing the two classes leads to the wrong tool for the problem, and the wrong expectations for the results.&lt;/p&gt;

&lt;p&gt;Using an AI-trained scanner on Class 2 problems produces the IDOR false positive: the scanner flags a critical IDOR on a public endpoint because it doesn't know the endpoint is supposed to be open. It's applying pattern recognition to a problem where the pattern doesn't generalize. Because the verdict depends on intent that only this operator knows.&lt;/p&gt;

&lt;p&gt;Using a configuration consistency checker on Class 1 problems is equally wrong. It can't find SQL injection because SQL injection isn't a configuration contradiction — it's a code defect that exists regardless of what the operator declared.&lt;/p&gt;

&lt;p&gt;The tools aren't interchangeable. They solve different problem classes.&lt;/p&gt;

&lt;h2&gt;
  
  
  What solves each class
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Class 1: Pattern recognition
&lt;/h3&gt;

&lt;p&gt;AI-assisted vulnerability discovery — LLM-powered pen testing, fuzzing, SAST, agentic security scanners — excels at Class 1 problems. The scanner learns patterns from training data: "this code shape leads to injection," "this API behavior indicates a broken access control." The patterns generalize because the vulnerabilities are universal.&lt;/p&gt;

&lt;p&gt;The growing critique that AI-assisted scanning produces inflated findings is valid within Class 1. Some findings are in low-tier targets, some bugs aren't exploitable, some results are source-assisted. The community is right to demand validation. But the approach itself is sound: pattern recognition works when patterns generalize.&lt;/p&gt;

&lt;p&gt;Where the approach breaks down is at the boundary between Class 1 and Class 2. The IDOR example sits on that boundary. Some IDORs are universal (accessing another user's private data is always wrong). Some are intent-dependent (accessing a public record through a predictable ID is by design). When the scanner can't distinguish the two, it produces noise. The fix isn't better AI. It's recognizing that the problem has crossed into Class 2, where the operator's intent determines the verdict.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why more training doesn't fix Class 2
&lt;/h3&gt;

&lt;p&gt;The reason is structural. In Class 1, the verdict is a function of the code: &lt;code&gt;verdict = f(code)&lt;/code&gt;. The function is the same for every deployment. Train on enough examples and the model approximates it well.&lt;/p&gt;

&lt;p&gt;In Class 2, intent is a variable: &lt;code&gt;verdict = f(configuration, intent)&lt;/code&gt;. The configuration is visible. It's the IAM policy, the S3 tag, the Bedrock agent setup. But intent is supplied by the operator, and its value changes for every scenario. A public bucket is correct when intent is "serve static assets." The same public bucket is a violation when intent is "store patient records." The configuration is identical. The intent is different. The verdict flips.&lt;/p&gt;

&lt;p&gt;Because intent is a variable in that function, it cannot be baked into a model during training. It is unknown before training — no dataset contains this operator's future decisions. It is unknown during training — the model learns patterns from other operators' configurations, none of which carry this operator's intent. And it is unknown after training — when the model encounters a new deployment, the intent variable has a value it has never seen because that value was created when this specific operator tagged this specific bucket and configured this specific agent. The variable doesn't exist yet when the model is trained. It comes into existence when the operator makes a deployment decision.&lt;/p&gt;

&lt;p&gt;An AI scanner sees the configuration. It does not see the intent. It's solving an equation with a variable that has no value at any stage of the model's lifecycle. No amount of training data fills that gap because the gap isn't a data problem — it's a timing problem. The value is created after the model is deployed, by an operator the model has never observed, for a purpose the model cannot infer from the configuration alone.&lt;/p&gt;

&lt;p&gt;This is why the false-positive IDOR is unfixable by training: the scanner sees the endpoint and the response. It cannot see whether the operator intended that data to be public. That intent didn't exist when the model was trained. It came into existence when this operator designed this application. The model will encounter this value for the first time at inference — with no prior example to generalize from.&lt;/p&gt;

&lt;h3&gt;
  
  
  Class 2: Constraint satisfaction
&lt;/h3&gt;

&lt;p&gt;Configuration consistency checking asks: "Given what the operator declared — what's sensitive, what's public, who can reach what — do those declarations contradict each other?"&lt;/p&gt;

&lt;p&gt;This is a constraint satisfaction problem. The operator's declarations are constraints: "this bucket contains PHI," "this agent serves public queries," "this knowledge base indexes this bucket." The tool checks whether those constraints are simultaneously satisfiable without creating a forbidden state. That's a satisfiability query — the exact problem SMT solvers like Z3 have been solving since 2007 for flight software, CPU verification, and compiler correctness.&lt;/p&gt;

&lt;p&gt;The solver doesn't need training data. It doesn't need to generalize across organizations. It reads THIS operator's assertions and checks whether THEY contradict each other. The logic is the same for every deployment. The facts are different. That's what constraint solvers are built for — same engine, different inputs, deterministic answers.&lt;/p&gt;

&lt;p&gt;A trained model answering the same question would need thousands of labeled examples of "this configuration is safe" and "this configuration is unsafe." It would produce a probability, not a proof. It would struggle with novel compositions it hadn't seen in training. And crucially, it would fail on intent — because every organization's tags, policies, and classifications are unique. You can show a model a thousand IDOR findings and it still can't determine whether endpoint &lt;code&gt;/api/records/123&lt;/code&gt; is supposed to be open, because that depends on what THIS operator intended for THIS application, and that intent isn't in any training corpus. It's in the tags, policies, and configurations that only this operator controls.&lt;/p&gt;

&lt;h3&gt;
  
  
  The right tool for each class
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Property&lt;/th&gt;
&lt;th&gt;Class 1 (universal patterns)&lt;/th&gt;
&lt;th&gt;Class 2 (intent-dependent)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Example&lt;/td&gt;
&lt;td&gt;SQL injection, XSS, buffer overflow&lt;/td&gt;
&lt;td&gt;PHI bucket indexed by public RAG pipeline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Verdict depends on operator intent?&lt;/td&gt;
&lt;td&gt;No — always a vulnerability&lt;/td&gt;
&lt;td&gt;Yes — depends on declared data classification&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pattern generalizes?&lt;/td&gt;
&lt;td&gt;Yes — one SQLi resembles the next&lt;/td&gt;
&lt;td&gt;No — every operator's intent is different&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Training data helps?&lt;/td&gt;
&lt;td&gt;Yes — more examples improve detection&lt;/td&gt;
&lt;td&gt;No — each deployment's intent is unique&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Right approach&lt;/td&gt;
&lt;td&gt;Pattern recognition (AI/ML)&lt;/td&gt;
&lt;td&gt;Constraint satisfaction (SMT solvers)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Right tool&lt;/td&gt;
&lt;td&gt;SAST, DAST, fuzzing, AI scanners&lt;/td&gt;
&lt;td&gt;Configuration consistency verification&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What the scanner sees vs. what consistency verification sees
&lt;/h2&gt;

&lt;p&gt;Now that the classes are separated, the gap becomes visible.&lt;/p&gt;

&lt;p&gt;Consider a Bedrock agent in AWS. A component-level scanner checks each resource individually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Bedrock encryption:     ✅ PASS
Bedrock VPC:            ✅ PASS
Bedrock model access:   ✅ PASS
S3 encryption:          ✅ PASS
S3 public access:       ✅ PASS
Lambda encryption:      ✅ PASS

6 checks. 6 passes. COMPLIANT.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are Class 1 checks applied to configuration: "is encryption on?" is a universal pattern. The answer doesn't depend on the operator's intent. Encryption should be on.&lt;/p&gt;

&lt;p&gt;But the agent's execution role has &lt;code&gt;lambda:InvokeFunction&lt;/code&gt; on &lt;code&gt;Resource: *&lt;/code&gt;. It can invoke any Lambda in the account. One of those Lambda functions reads from a bucket tagged &lt;code&gt;data_classification: phi&lt;/code&gt;. The knowledge base indexes the same PHI bucket for RAG retrieval. These are Class 2 problems — the verdict depends on the interaction between the agent's permissions, the Lambda's access, and the bucket's data classification. All three are intentional configurations. The contradiction is in their composition.&lt;/p&gt;

&lt;p&gt;Consistency verification extracts facts from all three services, checks whether they compose into a forbidden state, and reports: "Your customer-facing chatbot can return patient records through its Lambda tool chain." Five individual findings compose into three CRITICAL compound chains. The scanner's 6/6 PASS and the consistency checker's 3 CRITICAL chains are both correct — they're answering different questions about different problem classes.&lt;/p&gt;

&lt;h2&gt;
  
  
  What consistency verification provides
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Compound detection across services.&lt;/strong&gt; Individual checks can't find cross-service compositions. When the agent's role is overpermissioned AND the Lambda reads PHI AND the knowledge base indexes PHI, the compound fires as a single chain with a description naming the specific attack path.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Intent-aware evaluation.&lt;/strong&gt; The IDOR problem doesn't exist here because the operator's intent is encoded in the configuration. Tags declare sensitivity. Policies declare access. The tool doesn't guess whether data is sensitive — the operator tagged it. "Your PHI-tagged bucket is indexed by your public-facing knowledge base" isn't a guess. Both the tag and the configuration are explicit operator decisions. The finding resolves into a concrete decision (remove the tag or change the data source), not a triage exercise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mathematical proof.&lt;/strong&gt; Z3 returns &lt;code&gt;sat&lt;/code&gt; (the forbidden state is reachable) or &lt;code&gt;unsat&lt;/code&gt; (mathematically impossible). Not a confidence score. Not a probability. A proof that can be verified by running the same query again, or by a different solver. For compliance evidence, proofs beat confidence scores.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Traceability.&lt;/strong&gt; Every step in the compound chain traces back to a specific property in a specific configuration file through a deterministic identifier. One grep returns the observation file, the property path, and the timestamp. The team doesn't search for the root cause — the tool names it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Air-gapped operation.&lt;/strong&gt; No credentials, no API calls, no network access. Static snapshot analysis on a laptop. For organizations under GDPR, HIPAA, or FedRAMP, this isn't a feature — it's a prerequisite.&lt;/p&gt;

&lt;h2&gt;
  
  
  What consistency verification does NOT do
&lt;/h2&gt;

&lt;p&gt;It doesn't find SQL injection. It doesn't discover XSS. It doesn't fuzz API endpoints. It doesn't scan dependencies for CVEs. It doesn't evaluate models for prompt injection. These are Class 1 problems. They need Class 1 tools.&lt;/p&gt;

&lt;p&gt;Consistency verification catches what sits between the Class 1 tools: the compound configuration errors that exist in the interaction between services, where every individual check passes and the aggregate state is unsafe.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's missing: your intent
&lt;/h2&gt;

&lt;p&gt;There's a prerequisite that consistency verification cannot work without: the tool cannot read the security engineer's mind. It reads configurations. If the engineer's intent isn't expressed in the configuration, the tool has nothing to reason about.&lt;/p&gt;

&lt;p&gt;If your PHI buckets aren't tagged &lt;code&gt;data_classification: phi&lt;/code&gt;, the compound chain "knowledge base indexes PHI data" cannot fire. No tag, no finding. The engineer has to declare what's sensitive before the tool can check whether the declaration is violated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tags are intent declarations.&lt;/strong&gt; &lt;code&gt;data_classification: phi&lt;/code&gt; is not metadata for a dashboard. It's a machine-readable statement: "this bucket contains patient records." Without it, the tool treats the bucket like any other bucket. The compound detection that joins "this knowledge base indexes this bucket" with "this bucket contains PHI" requires both declarations to exist.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Policy absence is an intent signal.&lt;/strong&gt; When a Bedrock agent has no guardrail configured, the tool reads that absence as a fact: "guardrail is not present." The configuration is the expressed state — the tool reads what's there and what's missing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chain definitions encode security judgment.&lt;/strong&gt; When a control author writes a compound chain that says "if the agent's role is overpermissioned AND the knowledge base indexes PHI AND there's no guardrail, that's CRITICAL" — they're encoding a security team's judgment about which interactions matter. The engine doesn't decide what's dangerous. The control author decides. &lt;/p&gt;

&lt;p&gt;This is similar to how you write a unit test: You know the expected value of the right result for a given input, you encode that human judgment in the unit test.&lt;/p&gt;

&lt;p&gt;Tagging discipline is the prerequisite. An organization that doesn't tag its sensitive data, doesn't classify its environments, and doesn't label its IAM roles by purpose will get fewer findings — not because their infrastructure is safer, but because they haven't expressed enough intent for the tool to reason about.&lt;/p&gt;

&lt;p&gt;That's the correct design. The alternative is guessing. Guessing produces inflated findings, false-positive IDORs, and the critical-severity noise that the security community is rightly pushing back on. Consistency verification doesn't guess. It requires the engineer to say what matters, and then checks whether the infrastructure respects what they said.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to think about your tooling stack
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;Problem class&lt;/th&gt;
&lt;th&gt;Right tool&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;"Does my code have bugs?"&lt;/td&gt;
&lt;td&gt;Class 1 — universal&lt;/td&gt;
&lt;td&gt;SAST, DAST, fuzzing, AI-assisted pen testing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"Does my model have vulnerabilities?"&lt;/td&gt;
&lt;td&gt;Class 1 — universal&lt;/td&gt;
&lt;td&gt;Model security scanners, red teaming&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"Are my components configured correctly?"&lt;/td&gt;
&lt;td&gt;Class 1 — universal&lt;/td&gt;
&lt;td&gt;CSPM, CIS benchmarks, component scanners&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"Do my configurations contradict each other?"&lt;/td&gt;
&lt;td&gt;Class 2 — intent-dependent&lt;/td&gt;
&lt;td&gt;Compound risk / consistency verification&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The first three rows are mature. The fourth is new. It doesn't replace the others — it catches what they structurally cannot.&lt;/p&gt;

&lt;p&gt;The critical mindset the security community applies to AI-assisted scanner findings — "is this finding real? is the bug exploitable? is the target meaningful?" — is right. Apply it to consistency verification too. But ask the Class 2 version: "does this configuration really say what the tool claims?" (every fact is verified against the raw configuration file). "Is the compound really a risk?" (the chain description names specific services and data classifications from the operator's own declarations). "Can I verify it independently?" (the solver is open source — run the query yourself).&lt;/p&gt;

&lt;p&gt;The findings aren't inflated because they aren't heuristic. They're logical consequences of the operator's own declarations. The operator said "this is PHI." The operator said "this agent can invoke any Lambda." The tool says "those two facts compose into a breach path." That's not an opinion. That's arithmetic on expressed intent.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The consistency verification approach described in this article is implemented in &lt;a href="https://github.com/sufield/stave" rel="noopener noreferrer"&gt;Stave&lt;/a&gt;, an open-source static analysis tool for cloud infrastructure configurations. Stave evaluates 2,650+ controls across 74 AWS service domains, exports facts to nine independent reasoning engines — including SMT solvers used to verify flight software — and detects compound risk chains across services. 32 AI agent identity controls and 5 AI-specific compound chains cover Bedrock, SageMaker, and Lambda. All analysis runs on air-gapped snapshots with no cloud credentials required.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>cloud</category>
      <category>security</category>
      <category>ai</category>
      <category>aws</category>
    </item>
    <item>
      <title>go:embed in a CLI: Schemas, Controls, Templates, and Pack Registries</title>
      <dc:creator>Bala Paranj</dc:creator>
      <pubDate>Sun, 10 May 2026 13:59:23 +0000</pubDate>
      <link>https://dev.to/bala_paranj_059d338e44e7e/goembed-in-a-cli-schemas-controls-templates-and-pack-registries-4402</link>
      <guid>https://dev.to/bala_paranj_059d338e44e7e/goembed-in-a-cli-schemas-controls-templates-and-pack-registries-4402</guid>
      <description>&lt;p&gt;How embedding JSON schemas, YAML controls, Go templates, and a pack registry index into the binary eliminates external file dependencies — critical for air-gapped deployment and deterministic evaluation.&lt;/p&gt;

&lt;p&gt;A security CLI that depends on external schema files to validate input is a CLI that breaks when deployed to a Docker container, a CI runner, or an air-gapped server where those files don't exist.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;go:embed&lt;/code&gt; solves this by compiling files into the binary at build time. The binary carries its own schemas, controls, templates, and registry index. No installation step. No file path configuration. No "schema file not found" errors in production.&lt;/p&gt;

&lt;p&gt;Here are four ways to use, each solving a different embed problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. JSON Schemas — Validation Without a Registry
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;//go:embed embedded/*/*/*.json&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;embeddedFS&lt;/span&gt; &lt;span class="n"&gt;embed&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FS&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The directory structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;internal/contracts/schema/embedded/
├── control/v1/control.schema.json
├── diagnose/v1/diagnose.schema.json
└── output/v1/output.schema.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The validator loads schemas from the embedded filesystem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Validator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;getSchema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kind&lt;/span&gt; &lt;span class="n"&gt;Kind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;jsonschema&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RLock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compiled&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;cacheKey&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RUnlock&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;cached&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RUnlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c"&gt;// Load from embedded FS — no disk access&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;embeddedFS&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;schemaPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"load schema %s/%s: %w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c"&gt;// ... compile and cache&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;Why embed, not bundle separately:&lt;/strong&gt; The schema version must match the binary version. If schemas are external files, a user could upgrade the binary but keep old schemas — leading to validation that passes when it shouldn't or fails when it should pass. Embedding guarantees schema-binary consistency.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. YAML Controls — Built-In Security Policies
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;//go:embed embedded/s3/**/*.yaml&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;controlFS&lt;/span&gt; &lt;span class="n"&gt;embed&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FS&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;43 S3 security controls embedded in the binary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;internal/controldata/embedded/s3/
├── access/
│   ├── CTL.S3.PUBLIC.001.yaml
│   ├── CTL.S3.ACL.001.yaml
│   └── ...
├── encryption/
│   ├── CTL.S3.ENC.001.yaml
│   └── ...
└── governance/
    └── ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The loader accepts any &lt;code&gt;fs.FS&lt;/code&gt; — embedded for production, &lt;code&gt;fstest.MapFS&lt;/code&gt; for tests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;BuiltinLoader&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;fsys&lt;/span&gt; &lt;span class="n"&gt;fs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FS&lt;/span&gt;
    &lt;span class="n"&gt;mu&lt;/span&gt;   &lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RWMutex&lt;/span&gt;
    &lt;span class="n"&gt;once&lt;/span&gt; &lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Once&lt;/span&gt;
    &lt;span class="n"&gt;controls&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ControlDefinition&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Production: uses embedded FS&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;EmbeddedFS&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;fs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FS&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;controlFS&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Tests: use any fs.FS&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;NewBuiltinLoader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fsys&lt;/span&gt; &lt;span class="n"&gt;fs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;BuiltinLoader&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;BuiltinLoader&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;fsys&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;fsys&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;&lt;strong&gt;Why embed controls:&lt;/strong&gt; Users can run &lt;code&gt;stave apply --controls controls/s3&lt;/code&gt; to use the built-in controls without copying YAML files to their project. The controls ship with the binary. For custom controls, users point &lt;code&gt;--controls&lt;/code&gt; at their own directory — the built-in ones are a starting point, not a lock-in.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Go Templates — Customizable Output
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;//go:embed templates/prompt_default.tmpl&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;DefaultTemplate&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The LLM prompt template is embedded as a string (not &lt;code&gt;embed.FS&lt;/code&gt; — it's a single file):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;RenderPrompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="n"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tmpl&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&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;ui&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExecuteTemplate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tmpl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&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;For the default template, &lt;code&gt;tmpl&lt;/code&gt; is &lt;code&gt;DefaultTemplate&lt;/code&gt; (the embedded string). For custom templates, the user provides a path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Default embedded template&lt;/span&gt;
stave prompt from-finding &lt;span class="nt"&gt;--evaluation-file&lt;/span&gt; eval.json

&lt;span class="c"&gt;# Custom template from disk&lt;/span&gt;
stave prompt from-finding &lt;span class="nt"&gt;--evaluation-file&lt;/span&gt; eval.json &lt;span class="nt"&gt;--prompt-template&lt;/span&gt; ./my-template.tmpl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why string, not embed.FS:&lt;/strong&gt; A single template doesn't need a filesystem abstraction. &lt;code&gt;string&lt;/code&gt; is simpler — it can be passed directly to &lt;code&gt;template.Parse()&lt;/code&gt;. The &lt;code&gt;embed.FS&lt;/code&gt; pattern is for collections of files.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Pack Registry — Index of Built-In Control Packs
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;//go:embed embedded/index.yaml&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;embeddedRegistryFS&lt;/span&gt; &lt;span class="n"&gt;embed&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FS&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The registry index maps pack names to control IDs:&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;# embedded/index.yaml&lt;/span&gt;
&lt;span class="na"&gt;packs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;s3&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AWS&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;S3&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;bucket&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;security&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;controls"&lt;/span&gt;
    &lt;span class="na"&gt;controls&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;CTL.S3.PUBLIC.001&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;CTL.S3.ACL.001&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;CTL.S3.ENC.001&lt;/span&gt;
      &lt;span class="c1"&gt;# ... 43 controls&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The registry validates its own integrity at startup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;ValidateStrict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fsys&lt;/span&gt; &lt;span class="n"&gt;embed&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Verify every control referenced in index.yaml exists as a YAML file&lt;/span&gt;
    &lt;span class="c"&gt;// Verify every YAML file in the embedded FS is referenced in index.yaml&lt;/span&gt;
    &lt;span class="c"&gt;// No orphans. No phantom references.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This catches build-time mistakes: adding a control YAML file but forgetting to register it in the index, or removing a file but leaving the index entry.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Build Pipeline
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="nl"&gt;sync-schemas&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;SCHEMA_DST&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;SCHEMA_DST&lt;span class="p"&gt;)&lt;/span&gt;/&lt;span class="k"&gt;*&lt;/span&gt;
    &lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;SCHEMA_SRC&lt;span class="p"&gt;)&lt;/span&gt;/&lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;SCHEMA_DST&lt;span class="p"&gt;)&lt;/span&gt;/

&lt;span class="nl"&gt;build&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;sync-schemas&lt;/span&gt;
    go build &lt;span class="nt"&gt;-o&lt;/span&gt; stave ./cmd/stave
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Schemas are synced from a canonical source directory into the embed directory before building. The canonical schemas are human-editable; the embedded copies are build artifacts. This means &lt;code&gt;go build&lt;/code&gt; alone won't work after a fresh clone — &lt;code&gt;make build&lt;/code&gt; is required (documented in CLAUDE.md).&lt;/p&gt;

&lt;h2&gt;
  
  
  When NOT to Embed
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;User-authored files&lt;/strong&gt; (observations, custom controls): these change per project — don't embed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Large binary assets&lt;/strong&gt; (images, videos): bloats the binary unnecessarily&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Files that change between deployments&lt;/strong&gt; (config, secrets): must be external&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Files &amp;gt; 10MB&lt;/strong&gt;: Go's embed has no compression — large files inflate the binary&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Embed when the file is part of the tool's identity (schemas, built-in policies, default templates) and must be available in every deployment environment including air-gapped.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;These 4 embed patterns are used in &lt;a href="https://github.com/sufield/stave" rel="noopener noreferrer"&gt;Stave&lt;/a&gt;, a Go CLI for offline security evaluation. The embedded schemas, controls, templates, and pack registry ensure the binary is self-contained — deployable to air-gapped environments with no external file dependencies.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>programming</category>
      <category>cli</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Your Private API is Currently Safe. One Developer Change Away From Unsafe.</title>
      <dc:creator>Bala Paranj</dc:creator>
      <pubDate>Sat, 09 May 2026 11:44:13 +0000</pubDate>
      <link>https://dev.to/bala_paranj_059d338e44e7e/your-private-api-is-currently-safe-one-developer-change-away-from-unsafe-pcb</link>
      <guid>https://dev.to/bala_paranj_059d338e44e7e/your-private-api-is-currently-safe-one-developer-change-away-from-unsafe-pcb</guid>
      <description>&lt;p&gt;A 2023 Medium tutorial walks through restricting a private API Gateway to a single EC2 host in a single VPC. The author's intent — read literally — is correct: make the API reachable only from inside the VPC, only through the VPC endpoint they create, only by the EC2 they specify. The configuration they publish does this &lt;em&gt;almost&lt;/em&gt;. It also has two active gaps and one latent gap. The active gaps are visible to a careful reader of AWS documentation. The latent gap is invisible until a future change activates it.&lt;/p&gt;

&lt;p&gt;Z3 from Microsoft Research runs four queries against the published configuration and proves all three. The two active gaps return SAT with concrete witnesses. The latent gap returns UNSAT on the published configuration &lt;em&gt;but&lt;/em&gt; SAT on a one-line variant — the kind of variant a developer would introduce while adding a new method or stage. That asymmetry is the article's central argument: a configuration's current safety status and its &lt;em&gt;structural fragility&lt;/em&gt; are different questions. Heuristic scanners only check the first. Formal verification can answer both.&lt;/p&gt;

&lt;h2&gt;
  
  
  The configuration
&lt;/h2&gt;

&lt;p&gt;The author's resource policy:&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&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;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Deny"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Principal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"execute-api:Invoke"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"execute-api:/prod/GET/*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Condition"&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;span class="nl"&gt;"StringNotEquals"&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;span class="nl"&gt;"aws:sourceVpc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vpc-0b52ca08e7db8531f"&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;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;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Principal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"execute-api:Invoke"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"execute-api:/prod/GET/*"&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;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;The API uses &lt;code&gt;--authorization-type NONE&lt;/code&gt;. The resource policy is the only access control on the API itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three claims, three Z3 queries
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Claim 1: Allow/Deny resource pattern alignment
&lt;/h3&gt;

&lt;p&gt;Both statements scope to &lt;code&gt;execute-api:/prod/GET/*&lt;/code&gt;. Today, that means every request the Allow admits is also covered by the Deny condition. The Deny applies whenever the request comes from a VPC other than &lt;code&gt;vpc-0b52ca08e7db8531f&lt;/code&gt;; the Allow is unconditional but scoped to the same resource pattern. The two statements together restrict &lt;code&gt;/prod/GET/*&lt;/code&gt; to traffic from the VPC.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Z3 query:&lt;/strong&gt; is there a (stage, method, resource) witness that the Allow admits but the Deny does not?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verdict on the published config:&lt;/strong&gt; &lt;strong&gt;UNSAT&lt;/strong&gt;. Every witness the Allow admits is also blocked by the Deny. The current design is &lt;em&gt;aligned&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;But the design is &lt;em&gt;fragile&lt;/em&gt;: there's no structural guarantee that the Allow and Deny patterns stay aligned under future edits. To demonstrate, consider what a developer would do when asked to "add a &lt;code&gt;dev&lt;/code&gt; stage so QA can test against it." The natural change is to broaden the Allow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt; {
   "Effect": "Allow",
   "Principal": "*",
   "Action": "execute-api:Invoke",
&lt;span class="gd"&gt;-  "Resource": "execute-api:/prod/GET/*"
&lt;/span&gt;&lt;span class="gi"&gt;+  "Resource": "execute-api:/*"
&lt;/span&gt; }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Deny stays at &lt;code&gt;/prod/GET/*&lt;/code&gt; because nobody noticed it needed to change too. Re-run Z3 against this variant:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verdict on the broadened-allow variant:&lt;/strong&gt; &lt;strong&gt;SAT&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;verdict: SAT — witness: stage=prod method=POST resource=execute-api:/prod/POST/users
         (Allow widened to execute-api:/* — Deny still scoped to
          /prod/GET/*, so this resource is allowed without any VPC
          restriction)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The witness is concrete. &lt;code&gt;execute-api:/prod/POST/users&lt;/code&gt; is admitted by the broadened Allow (matches &lt;code&gt;execute-api:/*&lt;/code&gt;) and not blocked by the Deny (does not match &lt;code&gt;execute-api:/prod/GET/*&lt;/code&gt;). The Deny condition's VPC restriction &lt;em&gt;does not apply&lt;/em&gt; to this resource at all. The new POST endpoint is publicly invocable — without any VPC restriction — even though the API is still configured as a private REST API.&lt;/p&gt;

&lt;p&gt;The published configuration is currently safe because the Allow and Deny patterns happen to be identical. That equality is structural. It depends on a human remembering, every time either pattern changes, to update the other. Z3 can detect when that invariant breaks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Claim 2: aws:sourceVpc admits every endpoint in the VPC
&lt;/h3&gt;

&lt;p&gt;The Deny condition uses &lt;code&gt;aws:sourceVpc&lt;/code&gt;:&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="nl"&gt;"Condition"&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;span class="nl"&gt;"StringNotEquals"&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;span class="nl"&gt;"aws:sourceVpc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vpc-0b52ca08e7db8531f"&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;This restricts to traffic &lt;em&gt;from any source&lt;/em&gt; in &lt;code&gt;vpc-0b52ca08e7db8531f&lt;/code&gt;. The author's narrative says "restrict to the VPC endpoint we created" — but the condition restricts to the entire VPC, not the specific endpoint.&lt;/p&gt;

&lt;p&gt;In a shared enterprise VPC, multiple teams create multiple VPC endpoints. Each endpoint is a separate network path; each has its own security group and its own service. The author's intended endpoint (&lt;code&gt;vpce-0abc123def456789&lt;/code&gt;) is one of them. &lt;em&gt;Any&lt;/em&gt; other interface endpoint in the same VPC, with permissive security-group rules, is a path to this private API that the author did not intend.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Z3 query:&lt;/strong&gt; is there a VPC endpoint in &lt;code&gt;vpc-0b52ca08e7db8531f&lt;/code&gt; that is &lt;em&gt;not&lt;/em&gt; &lt;code&gt;vpce-0abc123def456789&lt;/code&gt;, but reaches the API anyway?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verdict on the published config:&lt;/strong&gt; &lt;strong&gt;SAT&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;verdict: SAT — witness: vpce-0999888777666555
         (in vpc-0b52ca08e7db8531f, NOT vpce-0abc123def456789)
         (Deny condition uses aws:sourceVpc which matches every
          endpoint in the VPC, not just the intended one)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The witness is the exact failure mode: a different VPC endpoint in the same VPC, created by another team for another service, can route traffic to this API. The security group on the &lt;em&gt;intended&lt;/em&gt; endpoint restricts to one EC2 IP, but the resource policy doesn't care which endpoint a request came through — it only checks the source VPC.&lt;/p&gt;

&lt;p&gt;The fix is one word in the condition key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt; "Condition": {
   "StringNotEquals": {
&lt;span class="gd"&gt;-    "aws:sourceVpc": "vpc-0b52ca08e7db8531f"
&lt;/span&gt;&lt;span class="gi"&gt;+    "aws:sourceVpce": "vpce-0abc123def456789"
&lt;/span&gt;   }
 }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;aws:sourceVpce&lt;/code&gt; matches the specific endpoint ID, not the entire VPC. After this change, the Deny applies to every endpoint except the named one; the witness from above is denied.&lt;/p&gt;

&lt;h3&gt;
  
  
  Claim 3: No authorization compounds the gap
&lt;/h3&gt;

&lt;p&gt;The API method uses &lt;code&gt;--authorization-type NONE&lt;/code&gt;. There is no IAM authorization, no Cognito user pool, no Lambda authorizer. The resource policy is the only gate. If the resource policy has a gap (claim 2: it does), no second layer catches it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Z3 query:&lt;/strong&gt; is there a request that reaches the API with &lt;code&gt;auth_type=NONE&lt;/code&gt; AND a non-intended VPC endpoint?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verdict on the published config:&lt;/strong&gt; &lt;strong&gt;SAT&lt;/strong&gt; — the same witness as claim 2, plus &lt;code&gt;auth_type: NONE&lt;/code&gt;. Any workload in the VPC, on any endpoint, with any identity (or no identity), can invoke the API.&lt;/p&gt;

&lt;p&gt;This is not a discrete vulnerability so much as a defense-in-depth failure. AWS provides four authorization options at the method level (&lt;code&gt;NONE&lt;/code&gt;, &lt;code&gt;AWS_IAM&lt;/code&gt;, &lt;code&gt;COGNITO_USER_POOLS&lt;/code&gt;, &lt;code&gt;CUSTOM&lt;/code&gt;). Three of the four require the request be authenticated by some mechanism; the resource policy is then a second layer that further restricts based on source. With &lt;code&gt;NONE&lt;/code&gt;, the resource policy is the &lt;em&gt;only&lt;/em&gt; layer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt; aws apigateway put-method \
   --rest-api-id timegateway-id \
   --resource-id root-id \
   --http-method GET \
&lt;span class="gd"&gt;-  --authorization-type NONE
&lt;/span&gt;&lt;span class="gi"&gt;+  --authorization-type AWS_IAM
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After this change, requests must be SigV4-signed by an authorized IAM principal. The resource policy still applies; the IAM check is a second layer that catches resource-policy gaps that creep in over time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is a logic question
&lt;/h2&gt;

&lt;p&gt;CEL evaluates a state predicate. Stave's existing &lt;code&gt;CTL.APIGATEWAY.NETWORK.PRIVATE.POLICY.001&lt;/code&gt; checks one boolean: &lt;code&gt;resource_policy_restricts_vpc&lt;/code&gt;. The published configuration sets this to &lt;code&gt;true&lt;/code&gt; (the policy &lt;em&gt;does&lt;/em&gt; restrict by VPC, just by the wrong condition key). The control reports clean.&lt;/p&gt;

&lt;p&gt;Z3 doesn't pattern-match. It runs SAT searches over witness sets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;exists witness:
  resource_policy_admits(witness)
  AND witness != intended
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Claim 1, the witness set is a few stage/method tuples. The query asks whether any tuple is admitted by the Allow but not blocked by the Deny. On the published config: no — the patterns happen to match. On the broadened-allow variant: yes — &lt;code&gt;prod/POST/users&lt;/code&gt; is admitted but not blocked.&lt;/p&gt;

&lt;p&gt;For Claim 2, the witness set is several VPC endpoint ARNs in the same VPC. The query asks whether the Deny fails to apply for any non-intended endpoint. On the published config: yes for every non-intended endpoint — the condition's &lt;code&gt;StringNotEquals&lt;/code&gt; on &lt;code&gt;aws:sourceVpc&lt;/code&gt;&lt;br&gt;
is &lt;em&gt;false&lt;/em&gt; (the source IS in the VPC), so the Deny doesn't fire, so the Allow takes over.&lt;/p&gt;

&lt;p&gt;For Claim 3, the query compounds Claim 2 with the &lt;code&gt;auth_type=NONE&lt;/code&gt; field. On the published config: yes — same witness as Claim 2 plus no auth check.&lt;/p&gt;

&lt;p&gt;CEL's predicate vocabulary doesn't include "find a witness." Z3's does.&lt;/p&gt;
&lt;h2&gt;
  
  
  The reframe
&lt;/h2&gt;

&lt;p&gt;Earlier version of this example series refuted a suspicion about KMS &lt;code&gt;Resource: "*"&lt;/code&gt; — the pattern looked unsafe to a heuristic scanner; Z3 proved it was correctly scoped because of KMS-specific semantics. The lesson was: &lt;strong&gt;formal verification is right when you expect it to be right and right when you expect it to be wrong.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Later it demonstrates the converse case. Finding 1a returns UNSAT — the published configuration is &lt;em&gt;currently&lt;/em&gt; aligned. A heuristic might stop there and declare the configuration safe. Z3 doesn't stop there. The same query, against a one-line variant that represents a common developer change, returns SAT.&lt;/p&gt;

&lt;p&gt;The article's claim is not "your config is broken right now." It's "your config is one developer change away from being broken, and the change you'd most naturally make is the one that breaks it." That's a stronger statement than a heuristic can make because it requires reasoning about &lt;em&gt;the configuration under perturbation&lt;/em&gt; — which is exactly what an SMT solver gives you.&lt;/p&gt;
&lt;h2&gt;
  
  
  The remediation
&lt;/h2&gt;

&lt;p&gt;Three changes. None requires architectural rework:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt; {
   "Version": "2012-10-17",
   "Statement": [
     {
       "Effect": "Deny",
       "Principal": "*",
       "Action": "execute-api:Invoke",
&lt;span class="gd"&gt;-      "Resource": "execute-api:/prod/GET/*",
&lt;/span&gt;&lt;span class="gi"&gt;+      "Resource": "execute-api:/*",
&lt;/span&gt;       "Condition": {
         "StringNotEquals": {
&lt;span class="gd"&gt;-          "aws:sourceVpc": "vpc-0b52ca08e7db8531f"
&lt;/span&gt;&lt;span class="gi"&gt;+          "aws:sourceVpce": "vpce-0abc123def456789"
&lt;/span&gt;         }
       }
     },
     {
       "Effect": "Allow",
       "Principal": "*",
       "Action": "execute-api:Invoke",
&lt;span class="gd"&gt;-      "Resource": "execute-api:/prod/GET/*"
&lt;/span&gt;&lt;span class="gi"&gt;+      "Resource": "execute-api:/*"
&lt;/span&gt;     }
   ]
 }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Plus the method-level change to &lt;code&gt;authorization-type AWS_IAM&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;After remediation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Finding 1: UNSAT (patterns aligned at   &lt;code&gt;execute-api:/*&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Finding 2: UNSAT (&lt;code&gt;aws:sourceVpce&lt;/code&gt; scopes to the specific endpoint).&lt;/li&gt;
&lt;li&gt;Finding 3: UNSAT (IAM auth gates the request before the resource policy is consulted).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The pattern alignment is now structural — both statements use the same &lt;code&gt;execute-api:/*&lt;/code&gt; — not an accident of the author choosing the same literal twice.&lt;/p&gt;

&lt;h2&gt;
  
  
  The prevention lesson
&lt;/h2&gt;

&lt;p&gt;Three layers, in priority order:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resource-policy template review.&lt;/strong&gt; Whoever publishes infrastructure tutorials should include the alignment invariant ("Allow.Resource ⊆ Deny.Resource union with the inverse condition") as a checked property, not an implicit one. Z3 can check this automatically. Humans cannot reliably check it under deadline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;aws:sourceVpce&lt;/code&gt; is the scoping condition for private API restrictions, not &lt;code&gt;aws:sourceVpc&lt;/code&gt;.&lt;/strong&gt; Treat any AWS tutorial that uses the latter for "endpoint-specific" restriction as outdated, and update the policy when copy-pasting. The &lt;code&gt;Vpc&lt;/code&gt; form admits every endpoint in the VPC — usually not what the author intended.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Authorization-type &lt;code&gt;NONE&lt;/code&gt; is a deliberate choice, not a default.&lt;/strong&gt; If the API serves authenticated traffic, use &lt;code&gt;AWS_IAM&lt;/code&gt;. The resource policy then becomes a &lt;em&gt;second&lt;/em&gt; layer, and the resource-policy alignment fragility above stops being a single point of failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Allow and Deny statements in API Gateway resource policies use the same Resource pattern (or the Deny is broader)&lt;/li&gt;
&lt;li&gt;VPC-restriction conditions use &lt;code&gt;aws:sourceVpce&lt;/code&gt; (specific endpoint), not &lt;code&gt;aws:sourceVpc&lt;/code&gt; (entire VPC)&lt;/li&gt;
&lt;li&gt;Private REST APIs use &lt;code&gt;--authorization-type AWS_IAM&lt;/code&gt; (or another non-&lt;code&gt;NONE&lt;/code&gt; type) so the resource policy is a second layer behind authentication&lt;/li&gt;
&lt;li&gt;Resource-policy templates run through formal verification at publish time; tutorials include the verification step in the walkthrough&lt;/li&gt;
&lt;li&gt;Pre-merge CI runs the equivalent of this iteration's Z3 prover against the post-deploy observation snapshot&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The article author's published configuration is currently safe. It works. The EC2 reaches the API. The intended VPC endpoint is the only path. As infrastructure documentation, the tutorial does its job. As a security artefact, it's one well-meant developer change away from an open API. Z3 can tell you that. Pattern matchers can't.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The example shipped with this article can be found at &lt;a href="https://github.com/sufield/stave/tree/main/examples/apigw-private-api-scoped-deny" rel="noopener noreferrer"&gt;&lt;code&gt;stave/examples/apigw-private-api-scoped-deny/&lt;/code&gt;&lt;/a&gt; — two binaries side by side: a CEL foil that runs Stave's &lt;code&gt;CTL.APIGATEWAY.NETWORK.PRIVATE.POLICY.001&lt;/code&gt; control against the writeup, broadened-allow, and remediated configurations and reports COMPLIANT on all three (the existing control fires only when the private API has no VPC restriction at all), and a Z3 SAT prover that runs the four queries described in this article. The Z3 binary lives in a sibling Go module so its libz3 link stays out of Stave's main vendored tree. &lt;a href="https://github.com/sufield/stave" rel="noopener noreferrer"&gt;Stave&lt;/a&gt; detects this pattern and 31 other H1-grounded scenarios from local AWS configuration snapshots, with no cloud credentials.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>aws</category>
      <category>cloud</category>
      <category>appsec</category>
    </item>
    <item>
      <title>Global State in Go: 5 Kinds We Found, 3 We Eliminated, 2 We Kept</title>
      <dc:creator>Bala Paranj</dc:creator>
      <pubDate>Fri, 08 May 2026 10:58:34 +0000</pubDate>
      <link>https://dev.to/bala_paranj_059d338e44e7e/global-state-in-go-5-kinds-we-found-3-we-eliminated-2-we-kept-2afb</link>
      <guid>https://dev.to/bala_paranj_059d338e44e7e/global-state-in-go-5-kinds-we-found-3-we-eliminated-2-we-kept-2afb</guid>
      <description>&lt;p&gt;A mutable registry populated by init(), a session field leaking between evaluations, a validation limit set by a setter, an immutable singleton, and a cached detection result — how to tell which globals are dangerous and which are acceptable.&lt;/p&gt;

&lt;p&gt;Not all global state is bad. A &lt;code&gt;var ErrNotFound = errors.New("not found")&lt;/code&gt; is global state. So is &lt;code&gt;var controlIDPattern = regexp.MustCompile(...)&lt;/code&gt;. Nobody argues these should be parameters.&lt;/p&gt;

&lt;p&gt;The problem is when global state is &lt;strong&gt;mutable&lt;/strong&gt; and &lt;strong&gt;shared across execution boundaries&lt;/strong&gt; — when one function writes it and another reads it without explicit coordination. In a CLI, this means two evaluations in the same process see each other's side effects. In tests, it means parallel tests corrupt each other's state.&lt;/p&gt;

&lt;p&gt;We found 5 kinds of global state in a Go security CLI. Three were dangerous. Two were acceptable. Here's how we decided.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dangerous: Mutable Global Populated by init()
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Problem
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// 14 init() functions across 14 files, all writing to this:&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ControlRegistry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;NewRegistry&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c"&gt;// access_block_public.go&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ControlRegistry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MustRegister&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;accessBlockPublic&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Definition&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;NewDefinition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;WithID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ACCESS.001"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;WithSeverity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SeverityCritical&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// audit_server_logging.go&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ControlRegistry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MustRegister&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;auditServerLogging&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// ... 12 more files&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;14 &lt;code&gt;init()&lt;/code&gt; functions write to a package-level &lt;code&gt;ControlRegistry&lt;/code&gt; during import. The consequences:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No test isolation.&lt;/strong&gt; Every test that imports &lt;code&gt;compliance&lt;/code&gt; gets all 14 controls loaded. Can't test with a subset.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No parallel safety.&lt;/strong&gt; Two parallel tests share the same registry. If one test modifies the registry (registering a test-only control), the other sees the modification.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No ordering control.&lt;/strong&gt; Go doesn't guarantee &lt;code&gt;init()&lt;/code&gt; execution order across files. Adding a control that depends on another being registered first is fragile.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hidden side effects.&lt;/strong&gt; Importing a package changes global state. The import statement &lt;code&gt;import _ "pkg/compliance"&lt;/code&gt; has invisible consequences.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Fix: Factory Closures + On-Demand Catalogs
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ControlRegistry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;NewRegistry&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;allControlConstructors&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;Control&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;RegisterControl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;factory&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;Control&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;allControlConstructors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;allControlConstructors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ControlRegistry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MustRegister&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Test-only: create an isolated catalog from the same factories&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;NewTestCatalog&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ControlCatalog&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;NewRegistry&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;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;allControlConstructors&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MustRegister&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;allControlConstructors&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&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;cat&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Test-only: create a catalog with specific controls only&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;NewCatalogWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;controls&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="n"&gt;Control&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ControlCatalog&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;NewRegistry&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;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ctl&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;controls&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MustRegister&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctl&lt;/span&gt;&lt;span class="p"&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;cat&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each &lt;code&gt;init()&lt;/code&gt; now registers a factory closure instead of a concrete instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;RegisterControl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;Control&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;accessBlockPublic&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Definition&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;NewDefinition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;WithID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ACCESS.001"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;WithSeverity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SeverityCritical&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;}&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;&lt;strong&gt;Production is unchanged:&lt;/strong&gt; &lt;code&gt;ControlRegistry&lt;/code&gt; is still populated at import time. Existing code that reads &lt;code&gt;ControlRegistry.Lookup("ACCESS.001")&lt;/code&gt; works as before.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tests are now isolated:&lt;/strong&gt; &lt;code&gt;NewTestCatalog()&lt;/code&gt; creates a fresh catalog by calling each factory — new instances, no shared state. &lt;code&gt;NewCatalogWith(myControl)&lt;/code&gt; creates a catalog with just one control for focused testing. Two parallel tests get two independent catalogs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dangerous: Session State on a Long-Lived Config Object
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Problem
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Runner&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Configuration (long-lived, set once)&lt;/span&gt;
    &lt;span class="n"&gt;Controls&lt;/span&gt;     &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ControlDefinition&lt;/span&gt;
    &lt;span class="n"&gt;Clock&lt;/span&gt;        &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt;
    &lt;span class="n"&gt;SLAThreshold&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;

    &lt;span class="c"&gt;// Session state (per-evaluation, mutable) — DANGER&lt;/span&gt;
    &lt;span class="n"&gt;StaveVersion&lt;/span&gt;     &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;InputHashes&lt;/span&gt;      &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;evaluation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InputHashes&lt;/span&gt;
    &lt;span class="n"&gt;identitiesByTime&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;][]&lt;/span&gt;&lt;span class="n"&gt;asset&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CloudIdentity&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;identitiesByTime&lt;/code&gt; is a mutable map set during &lt;code&gt;Evaluate()&lt;/code&gt;. If the &lt;code&gt;Runner&lt;/code&gt; is reused for a second evaluation, the identity map from run 1 leaks into run 2. The second evaluation sees identities from snapshots it didn't load.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;StaveVersion&lt;/code&gt; and &lt;code&gt;InputHashes&lt;/code&gt; are per-evaluation metadata on a struct that's supposed to be reusable configuration. Setting them before evaluation and reading them during report assembly creates a temporal coupling — the fields must be set in the right order.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Fix: Separate by Lifetime
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Configuration (reusable, immutable after construction)&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Assessor&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Controls&lt;/span&gt;     &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ControlDefinition&lt;/span&gt;
    &lt;span class="n"&gt;Clock&lt;/span&gt;        &lt;span class="n"&gt;ports&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Clock&lt;/span&gt;
    &lt;span class="n"&gt;SLAThreshold&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Per-call parameters (passed as argument, not stored)&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;AssessmentOptions&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;StaveVersion&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;InputHashes&lt;/span&gt;  &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;evaluation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InputHashes&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Per-evaluation state (created in Assess, garbage collected after)&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;assessmentSession&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;assessor&lt;/span&gt;  &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Assessor&lt;/span&gt;
    &lt;span class="n"&gt;idIndex&lt;/span&gt;   &lt;span class="n"&gt;IdentityIndex&lt;/span&gt;         &lt;span class="c"&gt;// built fresh per evaluation&lt;/span&gt;
    &lt;span class="n"&gt;collector&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;AssessmentCollector&lt;/span&gt;   &lt;span class="c"&gt;// accumulates per evaluation&lt;/span&gt;
    &lt;span class="n"&gt;auditTime&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt;             &lt;span class="c"&gt;// computed per evaluation&lt;/span&gt;
    &lt;span class="n"&gt;opts&lt;/span&gt;      &lt;span class="n"&gt;AssessmentOptions&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;identitiesByTime&lt;/code&gt; became &lt;code&gt;IdentityIndex&lt;/code&gt; — a value type constructed fresh inside &lt;code&gt;Assess()&lt;/code&gt; and stored on the session. When &lt;code&gt;Assess()&lt;/code&gt; returns, the session is garbage collected. No leaking between evaluations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The principle:&lt;/strong&gt; separate by lifetime. Configuration lives for the process. Options live for one call. Session state lives for one evaluation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dangerous: Set-Once Global With No Coordination
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Problem
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;maxValidationErrors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DefaultMaxValidationErrors&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;SetMaxValidationErrors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;maxValidationErrors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;n&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;A package-level variable set by a &lt;code&gt;Set*&lt;/code&gt; function during bootstrap. The intent is "configure once at startup, read during validation." But nothing enforces the "once" part:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If two tests call &lt;code&gt;SetMaxValidationErrors(5)&lt;/code&gt; and &lt;code&gt;SetMaxValidationErrors(10)&lt;/code&gt;, the last one wins for both tests.&lt;/li&gt;
&lt;li&gt;If validation runs concurrently with &lt;code&gt;SetMaxValidationErrors&lt;/code&gt;, the read is racy.&lt;/li&gt;
&lt;li&gt;The function name doesn't communicate that it's a one-time bootstrap operation.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Fix: Document the Contract
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// maxValidationErrors controls how many errors are shown before truncating.&lt;/span&gt;
&lt;span class="c"&gt;// Set via SetMaxValidationErrors at startup. This is a process-level display&lt;/span&gt;
&lt;span class="c"&gt;// preference, not domain state — acceptable as a package variable since it's&lt;/span&gt;
&lt;span class="c"&gt;// set once during bootstrap and read during validation.&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;maxValidationErrors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DefaultMaxValidationErrors&lt;/span&gt;

&lt;span class="c"&gt;// SetMaxValidationErrors overrides the validation error display cap.&lt;/span&gt;
&lt;span class="c"&gt;// Must be called during process initialization (e.g., bootstrap), not&lt;/span&gt;
&lt;span class="c"&gt;// concurrently with validation calls.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;SetMaxValidationErrors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;maxValidationErrors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;n&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;We chose documentation over refactoring here because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;There's only one caller (&lt;code&gt;cmd/bootstrap_limits.go&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;The variable is a display preference, not domain logic&lt;/li&gt;
&lt;li&gt;Moving it to a parameter would thread it through 5 functions that don't care about it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When this becomes a problem:&lt;/strong&gt; If a second caller appears, or if tests need different values, refactor to pass the limit through the function call chain or via a &lt;code&gt;ValidationConfig&lt;/code&gt; struct.&lt;/p&gt;

&lt;h2&gt;
  
  
  Acceptable: Immutable Singletons
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Pattern
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;SafeResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ComplianceReport&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Exposed&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;GlobalScope&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;AuditScope&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;global&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ErrZeroTimestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"record observation: time must not be zero"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;controlIDPattern&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;regexp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MustCompile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;`^CTL\.[A-Z][A-Z0-9]*(\.[A-Z][A-Z0-9]*){1,}\.\d{3}$`&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;Why these are fine:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Immutable.&lt;/strong&gt; No &lt;code&gt;Set*&lt;/code&gt; function. No mutation after package initialization. &lt;code&gt;SafeResult&lt;/code&gt; is a value type — copying it creates an independent instance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No side effects.&lt;/strong&gt; Reading &lt;code&gt;GlobalScope&lt;/code&gt; doesn't change state anywhere.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compile-time constant behavior.&lt;/strong&gt; &lt;code&gt;ErrZeroTimestamp&lt;/code&gt; always returns the same error. &lt;code&gt;controlIDPattern&lt;/code&gt; always matches the same strings.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The litmus test:&lt;/strong&gt; If you can replace the global with a &lt;code&gt;const&lt;/code&gt; (conceptually, even if Go doesn't support &lt;code&gt;const&lt;/code&gt; for the type), it's safe. &lt;code&gt;SafeResult&lt;/code&gt; is conceptually &lt;code&gt;const ComplianceReport{Exposed: false}&lt;/code&gt;. &lt;code&gt;ErrZeroTimestamp&lt;/code&gt; is conceptually &lt;code&gt;const error("...")&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Acceptable: Cached Detection Results
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Pattern
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ttyCache&lt;/span&gt; &lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Map&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;CanColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Writer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;reflect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ValueOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pointer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ttyCache&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;bool&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;v&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;enabled&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;detectTTY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ttyCache&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;enabled&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;enabled&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;Why this is fine:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Idempotent.&lt;/strong&gt; &lt;code&gt;detectTTY(f)&lt;/code&gt; returns the same result for the same file descriptor. Caching doesn't change behavior — just avoids redundant syscalls.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Thread-safe.&lt;/strong&gt; &lt;code&gt;sync.Map&lt;/code&gt; handles concurrent reads and writes correctly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No test pollution.&lt;/strong&gt; Two tests using &lt;code&gt;os.Stdout&lt;/code&gt; get the same cached result — which is correct, because &lt;code&gt;os.Stdout&lt;/code&gt; has the same TTY status in both tests. The &lt;code&gt;IsTTY *bool&lt;/code&gt; override on &lt;code&gt;Runtime&lt;/code&gt; allows tests to bypass the cache entirely.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;globalNoColor&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;SetNoColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;globalNoColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v&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;Similar reasoning for &lt;code&gt;globalNoColor&lt;/code&gt;:&lt;/strong&gt; set once during bootstrap, read many times. The &lt;code&gt;--no-color&lt;/code&gt; flag is process-level state — it doesn't change during execution. Tests that need different behavior use the &lt;code&gt;Runtime.NoColor&lt;/code&gt; field, not the global.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Classification Framework
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Kind&lt;/th&gt;
&lt;th&gt;Mutable?&lt;/th&gt;
&lt;th&gt;Set When?&lt;/th&gt;
&lt;th&gt;Read When?&lt;/th&gt;
&lt;th&gt;Verdict&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;ControlRegistry&lt;/code&gt; + &lt;code&gt;init()&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;YES&lt;/td&gt;
&lt;td&gt;Import time (14 sites)&lt;/td&gt;
&lt;td&gt;Runtime (many)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Eliminate&lt;/strong&gt; — factory pattern&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;identitiesByTime&lt;/code&gt; on Runner&lt;/td&gt;
&lt;td&gt;YES&lt;/td&gt;
&lt;td&gt;Per-evaluation&lt;/td&gt;
&lt;td&gt;Same evaluation&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Extract&lt;/strong&gt; — session struct&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;maxValidationErrors&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;YES&lt;/td&gt;
&lt;td&gt;Bootstrap (1 site)&lt;/td&gt;
&lt;td&gt;Validation (few)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Document&lt;/strong&gt; — set-once contract&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;SafeResult&lt;/code&gt;, &lt;code&gt;ErrZeroTimestamp&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;NO&lt;/td&gt;
&lt;td&gt;Package init&lt;/td&gt;
&lt;td&gt;Anywhere&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Keep&lt;/strong&gt; — immutable singleton&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ttyCache&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;YES (append-only)&lt;/td&gt;
&lt;td&gt;First access&lt;/td&gt;
&lt;td&gt;Subsequent access&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Keep&lt;/strong&gt; — idempotent cache&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  The Three Questions
&lt;/h3&gt;

&lt;p&gt;For each package-level &lt;code&gt;var&lt;/code&gt;, ask:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Can two callers see different values?&lt;/strong&gt;&lt;br&gt;
If yes → eliminate. &lt;code&gt;ControlRegistry&lt;/code&gt; shows 14 controls to test A and 14 controls to test B, but test A might want only 1.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Does mutation in one context leak into another?&lt;/strong&gt;&lt;br&gt;
If yes → extract. &lt;code&gt;identitiesByTime&lt;/code&gt; from evaluation 1 leaked into evaluation 2.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Is the value set once and never changed?&lt;/strong&gt;&lt;br&gt;
If yes → keep (or document). &lt;code&gt;globalNoColor&lt;/code&gt;, &lt;code&gt;maxValidationErrors&lt;/code&gt;, compiled regexps.&lt;/p&gt;

&lt;p&gt;The last question has a trap: set once is only safe if you can guarantee it's called before any reads. In a CLI, bootstrap runs before any command — safe. In a library used by multiple goroutines, set once is a race condition. Know your execution model.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;These 5 global state patterns were found in &lt;a href="https://github.com/sufield/stave" rel="noopener noreferrer"&gt;Stave&lt;/a&gt;, a Go CLI for offline security evaluation. The ControlRegistry factory pattern enabled parallel test isolation for 14 security controls. The session extraction fixed a state leak between sequential evaluations. The immutable singletons and caches remain — they're the right tool for process-level state that doesn't change.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>programming</category>
      <category>refactoring</category>
      <category>architecture</category>
    </item>
    <item>
      <title>The dog that didn't bark: finding security holes in what's missing, not what's misconfigured</title>
      <dc:creator>Bala Paranj</dc:creator>
      <pubDate>Thu, 07 May 2026 11:30:32 +0000</pubDate>
      <link>https://dev.to/bala_paranj_059d338e44e7e/the-dog-that-didnt-bark-finding-security-holes-in-whats-missing-not-whats-misconfigured-3d03</link>
      <guid>https://dev.to/bala_paranj_059d338e44e7e/the-dog-that-didnt-bark-finding-security-holes-in-whats-missing-not-whats-misconfigured-3d03</guid>
      <description>&lt;p&gt;Every security scanner examines resources that exist. Nobody checks whether the resources your IAM policies reference actually exist. A deleted S3 bucket name referenced in an active policy is a structural hole — the permission is live, the resource is gone, and the name is reclaimable by any attacker. The absence is the evidence.&lt;/p&gt;

&lt;p&gt;In Arthur Conan Doyle's Silver Blaze, a prize racehorse is stolen from a guarded stable. Scotland Yard investigates the crime scene, interviews witnesses, examines evidence. They focus on what happened — what they can see, measure, and catalog.&lt;/p&gt;

&lt;p&gt;Sherlock Holmes solves the case by noticing what didn't happen.&lt;/p&gt;

&lt;p&gt;Is there any point to which you would wish to draw my attention?&lt;br&gt;
To the curious incident of the dog in the night-time.&lt;br&gt;
The dog did nothing in the night-time.&lt;br&gt;
That was the curious incident.&lt;/p&gt;

&lt;p&gt;The guard dog should have barked at an intruder entering the stable. It didn't. Therefore the person who took the horse wasn't a stranger. The dog knew them. Every investigator examined the evidence that was present. Holmes noticed the evidence that was absent.&lt;/p&gt;

&lt;p&gt;Cloud security scanners examine resources that exist. They check properties, configurations, and permissions on things that are there:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is this S3 bucket public? Check the bucket.&lt;/li&gt;
&lt;li&gt;Is this RDS instance encrypted? Check the instance.&lt;/li&gt;
&lt;li&gt;Is this IAM role over-privileged? Check the role.&lt;/li&gt;
&lt;li&gt;Is this security group open? Check the security group.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every check follows the same pattern: find a resource, read its configuration, evaluate a predicate. The resource exists. The scanner examines it. The finding describes what's wrong with it.&lt;/p&gt;

&lt;p&gt;This is Scotland Yard examining the crime scene. Thorough, methodical, and looking at everything that's there.&lt;/p&gt;

&lt;p&gt;What scanners don't look at:&lt;/p&gt;

&lt;p&gt;An IAM policy says: "Allow PutObject to arn:aws:s3:::prod-audit-logs."&lt;/p&gt;

&lt;p&gt;Every scanner checks the policy's permissions. Is it too broad? Does it grant admin access? Does it follow least privilege? They examine the policy — the thing that exists.&lt;/p&gt;

&lt;p&gt;Nobody checks whether &lt;code&gt;prod-audit-logs&lt;/code&gt; exists.&lt;/p&gt;

&lt;p&gt;The bucket was deleted six months ago during a migration. The policy wasn't updated. The permission is active. The resource is gone. The ARN in the policy points at nothing.&lt;/p&gt;

&lt;p&gt;This is the dog that didn't bark. The resource should exist. It doesn't. The absence is the evidence.&lt;/p&gt;

&lt;p&gt;S3 bucket names are globally unique across all AWS accounts. When a bucket is deleted, its name becomes available for registration by anyone. Any AWS account, anywhere in the world, can create a bucket with that exact name.&lt;/p&gt;

&lt;p&gt;The IAM policy still says "Allow PutObject to prod-audit-logs." The Lambda function that writes audit logs still runs every hour. It calls PutObject on prod-audit-logs. If nobody owns that bucket, the write fails silently. If an attacker registers the bucket name, the write succeeds — to the attacker's bucket.&lt;/p&gt;

&lt;p&gt;The organization's audit logs — potentially containing PHI, financial records, user activity, or compliance evidence — begin flowing to an attacker-controlled destination. The Lambda function doesn't error. The CloudWatch metrics look normal. The data delivery succeeds. It just goes to the wrong place.&lt;/p&gt;

&lt;p&gt;The organization is generating compliance evidence and delivering it to an adversary.&lt;/p&gt;

&lt;p&gt;Not every missing resource is equally dangerous. The risk depends on what the policy allows and whether the resource name is reclaimable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tier 1: Any dangling reference.&lt;/strong&gt; An IAM policy references a resource ARN that doesn't exist in the current inventory. The permission is active, the resource is absent. This is a structural hole — the policy's intent no longer matches reality. Maybe the resource was deleted intentionally and the policy cleanup was forgotten. Maybe the resource was renamed. Maybe it never existed (typo in the policy). Regardless, the permission points at nothing. Severity: high.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tier 2: Write permission to a reclaimable name.&lt;/strong&gt; The policy grants PutObject, SendMessage, Publish, or other write actions to a resource name that an attacker can claim. This isn't a latent risk. The organization's systems are actively trying to send data to this destination. The attacker claims the name and the data starts arriving. Severity: critical. This is the exfiltration tier.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tier 3: KMS key reference to a deleted key.&lt;/strong&gt; The policy grants kms:Decrypt or kms:Encrypt on a key that's been scheduled for deletion or already deleted. KMS key ARNs include random IDs and can't be reclaimed by another account — the risk is operational, not exfiltration. Systems configured to encrypt with a deleted key either fail or fall back to unencrypted writes. The policy maintains the illusion that encryption is active. An auditor reads the policy and sees encryption permissions. The key behind those permissions is gone. Severity: high.&lt;/p&gt;

&lt;p&gt;The structural limitation isn't that scanners are poorly built. It's that the finding doesn't live on any single resource.&lt;/p&gt;

&lt;p&gt;A per-resource scanner iterates through resources: for each S3 bucket, check its configuration. For each IAM role, check its policies. For each EC2 instance, check its security group.&lt;/p&gt;

&lt;p&gt;A ghost reference finding lives in the gap &lt;em&gt;between&lt;/em&gt; two inventories: the set of ARNs in IAM policies and the set of resources that actually exist. The finding is the difference between these two sets. No single resource carries it. The bucket doesn't exist to be scanned. The policy exists but looks normal — it has valid JSON, well-formed ARNs, and reasonable permissions. The problem is only visible when you compare the policy's ARNs against the resource inventory and notice an ARN that doesn't resolve.&lt;/p&gt;

&lt;p&gt;This is cross-inventory reasoning. The scanner must compare two datasets and notice what's in one but not the other. Per-resource scanners don't do this because their architecture processes one resource at a time. The dog that didn't bark is invisible to any investigator who only examines witnesses who showed up.&lt;/p&gt;

&lt;p&gt;The detection requires three inputs:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Input 1: Policy ARN extraction.&lt;/strong&gt; Parse every Allow statement in every IAM policy (user policies, group policies, role policies, managed and inline). Extract every fully-qualified, non-wildcard ARN. Wildcard ARNs (arn:aws:s3:::prod-*) can't be cross-referenced against the inventory — they match a pattern, not a specific resource.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Input 2: Resource inventory.&lt;/strong&gt; Every resource the extractor collected: S3 buckets, SQS queues, SNS topics, Lambda functions, KMS keys, DynamoDB tables. Each with its ARN.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Input 3: Cross-reference.&lt;/strong&gt; For every ARN in a policy, check whether a resource with that ARN exists in the inventory. If it doesn't, the policy references a ghost.&lt;/p&gt;

&lt;p&gt;The finding output tells the operator what they need to act:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Finding: CTL.IAM.POLICY.GHOSTREF.002 [CRITICAL]

  DEFECT:
    IAM policy "AuditLogWriter" grants s3:PutObject
    to arn:aws:s3:::prod-audit-logs which does not
    exist in the resource inventory. The bucket name
    is globally reclaimable.

  INFECTION:
    The Lambda function "WriteAuditLogs" executes
    hourly with this policy's permissions. It calls
    PutObject on prod-audit-logs. If an attacker
    registers this bucket name, the function's
    writes succeed — to the attacker's bucket. The
    function sees no errors. The data delivery
    appears normal.

  FAILURE:
    Silent data exfiltration. Audit logs containing
    PHI are delivered to an attacker-controlled S3
    bucket. The organization continues generating
    compliance evidence and delivering it to an
    adversary.

  DELTA:
    Remove arn:aws:s3:::prod-audit-logs from the
    AuditLogWriter policy, or recreate the bucket
    and verify ownership.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The operator reads this and understands: the policy is pointing at a bucket that doesn't exist, the bucket name can be claimed by anyone, and an active Lambda function is trying to write to it right now. The fix is immediate: either remove the dangling reference from the policy or recreate the bucket.&lt;/p&gt;

&lt;p&gt;The ghost reference alone is dangerous. Combined with missing logging, it's invisible.&lt;/p&gt;

&lt;p&gt;If CloudTrail data-write logging is enabled, the PutObject calls to the ghost bucket are logged — even when they fail. A security team reviewing CloudTrail can see "PutObject to prod-audit-logs failed with NoSuchBucket" and investigate. The ghost reference is discoverable through log analysis, even without the cross-inventory detection.&lt;/p&gt;

&lt;p&gt;But if data-write logging is also disabled, the PutObject calls generate no log entries. The writes fail silently or succeed to an attacker's bucket with no observable signal. The ghost reference is invisible to the scanner AND invisible in the logs.&lt;/p&gt;

&lt;p&gt;When the ghost reference finding co-occurs with missing write logging, the compound risk is total: the exfiltration path is open and there is no forensic trail to detect it. This is the compound chain — two independent findings that together produce a risk neither captures alone.&lt;/p&gt;

&lt;p&gt;Ghost references aren't created by malice. They are created by mundane operational workflows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Team provisions an S3 bucket for audit logs&lt;/li&gt;
&lt;li&gt;IAM policy is written granting Lambda write access to the bucket&lt;/li&gt;
&lt;li&gt;Lambda function runs for months, writing logs&lt;/li&gt;
&lt;li&gt;Team migrates logging to a new architecture — new bucket name, new function&lt;/li&gt;
&lt;li&gt;Old bucket is deleted&lt;/li&gt;
&lt;li&gt;Old Lambda function is deleted&lt;/li&gt;
&lt;li&gt;Old IAM policy is... still there&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Step 7 is the failure. Steps 1-6 are normal operations. The migration was successful. The new architecture works. Nobody noticed that the old policy still exists because the old policy isn't attached to anything that's actively used — or is it? Maybe a different Lambda function in a different team's account inherited the old policy through a role that was shared. Maybe a CI/CD pipeline still references the old role. Maybe the policy is attached to a group that 40 developers belong to.&lt;/p&gt;

&lt;p&gt;The gap between resource deleted and policy updated grows with organizational complexity. In small teams, one person manages both the resource and the policy. In large organizations, different teams manage infrastructure, IAM, and applications. The resource owner deletes the resource. The IAM team doesn't know the resource was deleted. The policy persists.&lt;/p&gt;

&lt;p&gt;Over months and years, the ghost reference count grows. Each one is a structural hole. Most are dormant. The resource type isn't globally reclaimable, or no active system references the ghost. But the write-permission ghosts pointing at reclaimable S3 bucket names are live exfiltration paths waiting for someone to notice.&lt;/p&gt;

&lt;p&gt;Holmes didn't just notice the dog didn't bark. He deduced &lt;em&gt;why&lt;/em&gt; it didn't bark and that deduction solved the case. The absence wasn't just a curiosity. It was the key evidence that every other investigator missed because they were looking at what was present.&lt;/p&gt;

&lt;p&gt;Ghost resource detection works the same way. The absence of a resource behind an active IAM permission isn't a cleanup task. It's evidence of a structural security gap that no amount of per-resource scanning will find. The policy looks correct. The permissions are well-scoped. The JSON is valid. Everything present is fine. What's missing is the problem.&lt;/p&gt;

&lt;p&gt;Every scanner on the market examines what's there. The most dangerous findings are in what isn't.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Ghost resource detection — cross-inventory reasoning about IAM policies referencing non-existent resources — is implemented in &lt;a href="https://github.com/sufield/stave" rel="noopener noreferrer"&gt;Stave&lt;/a&gt;, an open-source security CLI. Three controls detect dangling ARN references at escalating severity: general ghost references, write-permission exfiltration paths, and orphaned KMS key references. The finding lives in the gap between two inventories. No single resource carries it.&lt;/em&gt;&lt;/p&gt;

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