<?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: Cheikh Seck</title>
    <description>The latest articles on DEV Community by Cheikh Seck (@cheikh_seck_01).</description>
    <link>https://dev.to/cheikh_seck_01</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2625724%2F231e80f8-3459-455a-b23a-781596328434.png</url>
      <title>DEV Community: Cheikh Seck</title>
      <link>https://dev.to/cheikh_seck_01</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/cheikh_seck_01"/>
    <language>en</language>
    <item>
      <title>Static Single Assignment in Go: From Compiler IR to Taint Analysis</title>
      <dc:creator>Cheikh Seck</dc:creator>
      <pubDate>Sat, 17 Oct 2026 13:07:00 +0000</pubDate>
      <link>https://dev.to/cheikh_seck_01/static-single-assignment-in-go-from-compiler-ir-to-taint-analysis-2lo9</link>
      <guid>https://dev.to/cheikh_seck_01/static-single-assignment-in-go-from-compiler-ir-to-taint-analysis-2lo9</guid>
      <description>&lt;p&gt;&lt;strong&gt;Subtitle:&lt;/strong&gt; A 1988 compiler optimization turns out to be the cleanest way to trace untrusted data through Go.&lt;/p&gt;

&lt;p&gt;When you write 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;x&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you have written one name, &lt;code&gt;x&lt;/code&gt;, that holds three different values at three different moments. That is convenient for humans and miserable for machines. Any tool that wants to reason about &lt;code&gt;x&lt;/code&gt; must first answer a question your code never asked: &lt;em&gt;which&lt;/em&gt; &lt;code&gt;x&lt;/code&gt; do you mean, at this line?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Static Single Assignment (SSA) form&lt;/strong&gt; is the answer. In SSA, every variable is assigned exactly once. The same program becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;x1 := 1
x2 := x1 + 1
x3 := x2 * 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three names, three values, no ambiguity. Each definition has exactly one use-site chain, and each use has exactly one definition. The data-flow graph of the program becomes explicit instead of implied.&lt;/p&gt;

&lt;p&gt;This is not a new idea. SSA was developed at IBM in the 1980s; Rosen, Wegman, and Zadeck named it and introduced Φ-functions in their 1988 paper &lt;em&gt;"Global Value Numbers and Redundant Computations."&lt;/em&gt; It made numerous compiler optimizations easier — constant folding, dead-code elimination, global value numbering — because when you look at a use of a variable, there is only one place where that variable may have received a value. SSA became the default internal representation for LLVM, GCC, and most production compilers.&lt;/p&gt;

&lt;p&gt;Go adopted it too. The Go 1.7 release notes describe a new compiler back end based on SSA that "generates more compact, more efficient code" with optimizations like bounds check elimination and common subexpression elimination. The Go compiler's own SSA package documents the core idea plainly: "a value is defined exactly once, but it may be used any number of times."&lt;/p&gt;

&lt;p&gt;The part that matters for this article is the side effect. &lt;strong&gt;A representation that makes data flow explicit for an optimizer also makes data flow explicit for an auditor.&lt;/strong&gt; If you want to know whether a value that came from an HTTP request can reach a SQL query, SSA has already done the hard part: it has told you, unambiguously, which value is which.&lt;/p&gt;

&lt;p&gt;Go exposes this representation directly. The &lt;code&gt;golang.org/x/tools/go/ssa&lt;/code&gt; package builds an SSA IR for arbitrary Go packages, and its documentation states that "the level of abstraction of the SSA form is intentionally close to the source language to facilitate construction of source analysis tools." It is the same machinery that powers &lt;code&gt;go vet&lt;/code&gt;, &lt;code&gt;staticcheck&lt;/code&gt;, &lt;code&gt;gopls&lt;/code&gt;, and &lt;code&gt;gosec&lt;/code&gt;. You do not need to be writing a compiler to use it. You need to be writing a question about data flow.&lt;/p&gt;

&lt;p&gt;This article is about one such question, and about a small tool that answers it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Guard That Only Looks Like a Guard
&lt;/h2&gt;

&lt;p&gt;Here is the shape of a whole class of security bugs, in four lines:&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;name&lt;/span&gt; &lt;span class="o"&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;FormValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c"&gt;// attacker-controlled&lt;/span&gt;
&lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;"SELECT * FROM users WHERE name = '"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"'"&lt;/span&gt;
&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                   &lt;span class="c"&gt;// injection&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The data enters at a &lt;strong&gt;source&lt;/strong&gt; (&lt;code&gt;FormValue&lt;/code&gt;), travels through the program, and exits at a &lt;strong&gt;sink&lt;/strong&gt; (&lt;code&gt;db.Query&lt;/code&gt;). If nothing in between removes the danger, you have a vulnerability. This is &lt;strong&gt;taint analysis&lt;/strong&gt;: mark the untrusted values, propagate the mark, and complain if a marked value reaches a dangerous place.&lt;/p&gt;

&lt;p&gt;The naive version of this is a grep. The naive version is also wrong, in both directions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It &lt;strong&gt;misses&lt;/strong&gt; flows that pass through helper functions, string concatenation, or a struct field.&lt;/li&gt;
&lt;li&gt;It &lt;strong&gt;invents&lt;/strong&gt; flows that a guard already neutralized, because a regex cannot tell whether &lt;code&gt;SanitizeHTML(name)&lt;/code&gt; actually sanitizes anything.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That second failure is the interesting one. Consider:&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;ValidateAndEscape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&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;string&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;s&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;100&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&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;s&lt;/span&gt; &lt;span class="c"&gt;// ← returns the input unchanged&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The name promises validation &lt;em&gt;and&lt;/em&gt; escaping. The body does neither. A human reviewer skimming a call site sees &lt;code&gt;ValidateAndEscape(name)&lt;/code&gt; and moves on. A regex-based scanner sees a function call and either flags it (noisy) or trusts it (dangerous). Neither one reads the body.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A tool that reads the body can tell the difference.&lt;/strong&gt; And SSA is what makes reading the body tractable.&lt;/p&gt;

&lt;p&gt;Picatz's write-up on static taint analysis for Go makes the same point from the other direction: SSA provides "the foundation for performing taint analysis," transforming the program into a value graph where each node is a value or instruction, then into a call graph where edges are calls — the structure you need to identify data flow from source to sink.&lt;/p&gt;




&lt;h2&gt;
  
  
  Five Steps From Source to Sink
&lt;/h2&gt;

&lt;p&gt;The project is a single Go module called &lt;code&gt;leakcheck&lt;/code&gt;. It does five things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Loads a set of Go packages and builds SSA for each.&lt;/li&gt;
&lt;li&gt;Classifies every project function that &lt;em&gt;looks&lt;/em&gt; like a guard into one of eight classes.&lt;/li&gt;
&lt;li&gt;Walks the SSA instructions, propagating taint from sources to sinks.&lt;/li&gt;
&lt;li&gt;Probes every sink rule's argument index against the callee's real declared signature.&lt;/li&gt;
&lt;li&gt;Runs the whole thing twice — once trusting guards, once treating every guard as a no-op — and reports the difference.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That last step is the point of the whole exercise. The delta between the two passes is the set of flows that are silent &lt;em&gt;only because someone wrote a reassuring function name&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Loading and building SSA
&lt;/h3&gt;

&lt;p&gt;The entry point is the standard &lt;code&gt;packages&lt;/code&gt; + &lt;code&gt;ssa&lt;/code&gt; pairing:&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;cfg&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;packages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Mode&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LoadAllSyntax&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Dir&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;pkgs&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;packages&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;cfg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;patterns&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;prog&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ssa&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewProgram&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ssa&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SanityCheckFunctions&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;p&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;pkgs&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ssapkg&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;prog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreatePackage&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;Types&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;Syntax&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;TypesInfo&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="n"&gt;ssapkg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Build&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;ssa.NewProgram&lt;/code&gt; gives you a container; &lt;code&gt;CreatePackage&lt;/code&gt; turns a type-checked package into SSA; &lt;code&gt;Build()&lt;/code&gt; does the actual translation. After that, every function body is a list of basic blocks, and every basic block is a list of instructions.&lt;/p&gt;

&lt;h3&gt;
  
  
  The instruction walk
&lt;/h3&gt;

&lt;p&gt;The core of the analysis is a switch over instruction types. This is where SSA earns its keep — the cases are few and each one is unambiguous:&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;switch&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;instr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ssa&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Call&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&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;stepCall&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;v&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;Common&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ssa&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Defer&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&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;stepCall&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;v&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;Common&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ssa&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Go&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&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;stepCall&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;v&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;Common&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ssa&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BinOp&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="c"&gt;// taint flows from either operand to the result&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ssa&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Store&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="c"&gt;// taint flows from the value into the address&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ssa&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FieldAddr&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="c"&gt;// taint flows from the struct into the field&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three things are worth noticing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First, &lt;code&gt;Call&lt;/code&gt;, &lt;code&gt;Defer&lt;/code&gt;, and &lt;code&gt;Go&lt;/code&gt; share a shape.&lt;/strong&gt; All three carry a &lt;code&gt;*ssa.CallCommon&lt;/code&gt;, which holds the callee, the arguments, and the receiver. A deferred call and a goroutine-launched call are the same data-flow event as a direct call, just scheduled differently. One helper handles all three.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second, &lt;code&gt;BinOp&lt;/code&gt; is where string concatenation lives.&lt;/strong&gt; &lt;code&gt;"SELECT ... " + name&lt;/code&gt; is a &lt;code&gt;BinOp&lt;/code&gt; with a &lt;code&gt;+&lt;/code&gt; token. Taint flows from either operand to the result. This is the single most common propagation step in real injection bugs, and in SSA it is one case in a switch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Third, &lt;code&gt;Store&lt;/code&gt; and &lt;code&gt;FieldAddr&lt;/code&gt; are why SSA beats the AST here.&lt;/strong&gt; If taint enters a struct field and is read back three functions later, the AST gives you a syntax tree with no edges between the write and the read. SSA gives you a value graph where the field address is a node and the store is an edge into it.&lt;/p&gt;

&lt;h3&gt;
  
  
  The guard classifier
&lt;/h3&gt;

&lt;p&gt;This is the part that does something a regex cannot. Every project function is inspected and sorted into one of eight classes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Class&lt;/th&gt;
&lt;th&gt;Cuts taint?&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;effective&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;yes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Delegates to a recognized sanitizer and does nothing else&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;constant&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;yes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Takes no input, so it cannot forward taint&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;non-forwarding&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;yes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Returns only &lt;code&gt;error&lt;/code&gt;/&lt;code&gt;bool&lt;/code&gt;; the input never comes back out&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;passthrough&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;Returns its argument unchanged&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rewrites&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;Transforms the input without calling a recognized sanitizer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;conditional&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;Branches on the input; the analyzer cannot correlate branch with sink&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;unknown&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;No rule matched&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The classification is decided by the SSA body, not the name. &lt;code&gt;SanitizeHTML&lt;/code&gt; is &lt;code&gt;effective&lt;/code&gt; because it calls a known escaper and does nothing else. &lt;code&gt;ValidateAndEscape&lt;/code&gt; is &lt;code&gt;conditional&lt;/code&gt; because it branches. &lt;code&gt;CleanInput&lt;/code&gt; is &lt;code&gt;passthrough&lt;/code&gt; because it returns its argument. &lt;code&gt;IsSafePath&lt;/code&gt; is &lt;code&gt;non-forwarding&lt;/code&gt; because it returns a &lt;code&gt;bool&lt;/code&gt; and the string never leaves.&lt;/p&gt;

&lt;p&gt;Only three of the eight classes cut taint. That ratio is the finding.&lt;/p&gt;

&lt;h3&gt;
  
  
  The argument-layout probe
&lt;/h3&gt;

&lt;p&gt;Every sink rule names an argument index. &lt;code&gt;os.Open&lt;/code&gt; is index 0. &lt;code&gt;db.Query&lt;/code&gt; is index 1 (the query string, after the receiver). &lt;code&gt;Header.Set&lt;/code&gt; is index 2.&lt;/p&gt;

&lt;p&gt;Those indices are guesses about someone else's function signature. And there is a genuine trap here, because Go's SSA representation is asymmetric about receivers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;static&lt;/strong&gt; method call puts the receiver in &lt;code&gt;Args[0]&lt;/code&gt;, so &lt;code&gt;Header.Set(key, value)&lt;/code&gt; has the value at index 2.&lt;/li&gt;
&lt;li&gt;An &lt;strong&gt;invoke-mode&lt;/strong&gt; call (through an interface) puts the receiver in &lt;code&gt;CallCommon.Value&lt;/code&gt; and &lt;em&gt;excludes&lt;/em&gt; it from &lt;code&gt;Args&lt;/code&gt;, so the indices shift by one.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A tool that assumes one convention will silently mis-index half its rules. So &lt;code&gt;leakcheck&lt;/code&gt; measures instead of assuming: for every sink rule, it looks up the callee's declared signature and checks that the named index exists and has a plausible type.&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;if&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;&amp;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;sig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Params&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// rule is out of range for this callee — report it&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The probe prints a table and exits non-zero if anything is suspicious. It is the difference between a confidently wrong report and a report you can check.&lt;/p&gt;

&lt;h3&gt;
  
  
  The two-pass delta
&lt;/h3&gt;

&lt;p&gt;The whole analysis runs twice. Pass 2 is byte-identical except for one flag:&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;guardsNoop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that flag set, every project guard is treated as a no-op — taint flows straight through it. The difference between the two passes is the set of flows that a guard is currently suppressing.&lt;/p&gt;

&lt;p&gt;If that number is zero, your guards are decorative. If it is large, your guards are load-bearing and you should look at exactly which ones.&lt;/p&gt;




&lt;h2&gt;
  
  
  Nine Seconds, Ten Findings
&lt;/h2&gt;

&lt;p&gt;Here is the actual output, formatted as if from a Linux machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cheikh@dev:~/ssa-taint$ go run .
==============================================================================
  LEAKCHECK — SSA taint analysis for Go
==============================================================================

TIMING
  load + SSA build           8.6312159s
  guard classification       534.2515ms
  summary fixpoint           4.9699ms
  pass 1 (guards trusted)    0s
  pass 2 (guards no-op)      0s
  layout probe               0s
  total                      9.1704373s

PROGRAM
  packages analysed          2
  functions                  80
  SSA instructions           3011
  guards classified          14
  callees indexed            17035
  summaries that taint       19

FINDINGS (pass 1 — guards trusted) — 10
  #   SEVERITY KIND                FUNCTION                       ARG   ORIGIN
  1   HIGH     sql-injection       corpus.H01Query                1     request.form
  2   HIGH     sql-injection       corpus.H02EnvQuery             1     environment
  3   HIGH     path-traversal      corpus.H05Open                 0     request.form
  4   HIGH     ssrf                corpus.H06Fetch                0     request.form
  5   HIGH     path-traversal      corpus.H10TwoHop               0     request.form
  6   HIGH     path-traversal      corpus.H11OneHop               0     request.form
  7   HIGH     sql-injection       corpus.H13FilteredSQL          1     request.form
  8   HIGH     path-traversal      corpus.H14CleanGuard           0     request.form
  9   HIGH     path-traversal      corpus.H16PathHandler          0     request.form
  10  MEDIUM   output-sink         corpus.H09Header               2     request.form

TAINT PATHS
  [1] corpus.H01Query  (handlers.go:24)
      origin : request.form
      sink   : database/sql.DB.Query  arg 1  (sql-injection)
      path   : http.Request.FormValue
  [2] corpus.H02EnvQuery  (handlers.go:30)
      origin : environment
      sink   : database/sql.DB.Query  arg 1  (sql-injection)
      path   : os.Getenv
  [3] corpus.H05Open  (handlers.go:48)
      origin : request.form
      sink   : os.Open  arg 0  (path-traversal)
      path   : http.Request.FormValue
  [4] corpus.H06Fetch  (handlers.go:54)
      origin : request.form
      sink   : net/http.Get  arg 0  (ssrf)
      path   : http.Request.FormValue
  [5] corpus.H10TwoHop  (handlers.go:78)
      origin : request.form
      sink   : os.Open  arg 0  (path-traversal)
      path   : http.Request.FormValue
  [6] corpus.H11OneHop  (handlers.go:84)
      origin : request.form
      sink   : os.Open  arg 0  (path-traversal)
      path   : http.Request.FormValue
  [7] corpus.H13FilteredSQL  (handlers.go:99)
      origin : request.form
      sink   : database/sql.DB.Query  arg 1  (sql-injection)
      path   : http.Request.FormValue
  [8] corpus.H14CleanGuard  (handlers.go:106)
      origin : request.form
      sink   : os.Open  arg 0  (path-traversal)
      path   : http.Request.FormValue
  [9] corpus.H16PathHandler  (handlers.go:123)
      origin : request.form
      sink   : os.Open  arg 0  (path-traversal)
      path   : http.Request.FormValue
  [10] corpus.H09Header  (handlers.go:72)
      origin : request.form
      sink   : net/http.Header.Set  arg 2  (output-sink)
      path   : http.Request.FormValue

GUARD EFFECTIVENESS — 14 classified
  FUNCTION                             CLASS            CUTS   REASON
  leakcheck.trimFn                     conditional      no     branches on the input; the analyser cannot correlate the branch with the sink
  corpus.CleanGuard                    unknown          no     no rule matched
  corpus.CleanInput                    passthrough      no     returns its argument unchanged
  corpus.EscapeIfNeeded                conditional      no     calls a sanitizer but also branches or transforms
  corpus.FilterQuery                   rewrites         no     transforms the input without calling a recognised sanitizer
  corpus.IsSafePath                    non-forwarding   yes    returns only error/bool values; the input is not forwarded
  corpus.NormalizeName                 rewrites         no     transforms the input without calling a recognised sanitizer
  corpus.SafeToken                     constant         yes    takes no input, so it cannot forward taint
  corpus.SanitizeHTML                  effective        yes    delegates to a recognised sanitizer and does not transform otherwise
  corpus.SanitizeSQLLike               effective        yes    delegates to a recognised sanitizer and does not transform otherwise
  corpus.SanitizeURLComponent          effective        yes    delegates to a recognised sanitizer and does not transform otherwise
  corpus.StripTags                     unknown          no     no rule matched
  corpus.ValidateAndEscape             conditional      no     branches on the input; the analyser cannot correlate the branch with the sink
  corpus.ValidatePath                  non-forwarding   yes    returns only error/bool values; the input is not forwarded

ARGUMENT LAYOUT (probe) — 29 rule(s), 0 suspicious
  CALLEE                                   RULE   PARAMS  DECLARED TYPE        NOTE
  database/sql.DB.Exec                     1      2       string               
  database/sql.DB.ExecContext              2      3       string               
  database/sql.DB.Query                    1      2       string               
  database/sql.DB.QueryContext             2      3       string               
  fmt.Fprint                               any    2       -                    any argument (variadic rule)
  fmt.Fprintf                              any    3       -                    any argument (variadic rule)
  fmt.Fprintln                             any    2       -                    any argument (variadic rule)
  fmt.Print                                any    1       -                    any argument (variadic rule)
  fmt.Printf                               any    2       -                    any argument (variadic rule)
  fmt.Println                              any    1       -                    any argument (variadic rule)
  log.Print                                any    1       -                    any argument (variadic rule)
  log.Printf                               any    2       -                    any argument (variadic rule)
  log.Println                              any    1       -                    any argument (variadic rule)
  net.Dial                                 1      2       string               
  net.DialTimeout                          2      3       time.Duration        
  net/http.Get                             any    1       -                    any argument (variadic rule)
  net/http.Header.Add                      2      2       string               
  net/http.Header.Set                      2      2       string               
  net/http.Post                            any    3       -                    any argument (variadic rule)
  net/http.ResponseWriter.Write            any    1       -                    any argument (variadic rule)
  os.Create                                0      1       string               
  os.File.Write                            any    1       -                    any argument (variadic rule)
  os.Open                                  0      1       string               
  os.OpenFile                              0      3       string               
  os.ReadFile                              0      1       string               
  os.Setenv                                any    2       -                    any argument (variadic rule)
  os.WriteFile                             0      3       string               
  os/exec.Command                          any    2       -                    any argument (variadic rule)
  os/exec.CommandContext                   any    3       -                    any argument (variadic rule)

SECOND PASS — every project guard treated as a no-op
  findings with guards               10
  findings without guards            10
  flows that depend on a guard       0

CAVEATS
  - Sanitizers are not checked against sink kind. url.QueryEscape stops taint at a SQL sink, which is wrong. This is planted false negative FN-1.
  - Variadic sinks use arg == -1, so every argument is treated as dangerous. exec.Command("echo", name) is reported even though the command is constant. Documented false positive.
  - filepath.Base and friends are propagators, not sanitizers, so CleanGuard is reported. Documented false positive.
  - Predicate guards are not correlated with sinks. ValidatePath(name) protects only the branch that consults it; the analyser does not track that, so a handler that reads the raw value is still reported.
  - Interprocedural summaries are one bit per function: 'some parameter taints some result'. They cannot say which parameter or which result.
  - Only functions in the analysed packages are summarised. A taint path through a dependency is invisible unless the dependency is in the load patterns.

cheikh@dev:~/ssa-taint$
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Three of Fourteen
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Ten findings, and the table that explains them
&lt;/h3&gt;

&lt;p&gt;Ten flows reach a sink. The guard table tells you which of them are &lt;em&gt;supposed&lt;/em&gt; to be safe and aren't.&lt;/p&gt;

&lt;p&gt;Look at &lt;code&gt;corpus.H14CleanGuard&lt;/code&gt;. It calls &lt;code&gt;CleanGuard&lt;/code&gt;, which sounds like it cleans. The classifier says &lt;code&gt;unknown&lt;/code&gt; — no rule matched — so taint flows through. The caveat section names the reason: &lt;code&gt;filepath.Base&lt;/code&gt; is a &lt;strong&gt;propagator&lt;/strong&gt;, not a sanitizer. It strips directory components, which is not the same as making a path safe. &lt;code&gt;../../etc/passwd&lt;/code&gt; becomes &lt;code&gt;passwd&lt;/code&gt;; &lt;code&gt;..%2f..%2fetc%2fpasswd&lt;/code&gt; does not.&lt;/p&gt;

&lt;p&gt;That is a real bug class, and it is invisible to a name-based scanner.&lt;/p&gt;

&lt;h3&gt;
  
  
  The delta is zero, and that is the honest result
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;findings with guards               10
findings without guards            10
flows that depend on a guard       0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Zero. Not one of the ten findings is currently being suppressed by a guard. Every guard in the corpus either doesn't cut taint (the &lt;code&gt;no&lt;/code&gt; rows) or isn't on a path to a sink.&lt;/p&gt;

&lt;p&gt;This is a &lt;strong&gt;negative result&lt;/strong&gt;, and it is worth stating plainly rather than dressing up. The corpus was built to exercise the classifier, not to demonstrate a dramatic delta. What the zero tells you is that the classifier's &lt;code&gt;yes&lt;/code&gt; verdicts — &lt;code&gt;IsSafePath&lt;/code&gt;, &lt;code&gt;SafeToken&lt;/code&gt;, &lt;code&gt;ValidatePath&lt;/code&gt;, and the three &lt;code&gt;effective&lt;/code&gt; sanitizers — are not load-bearing for any of the ten reported flows. The guards that &lt;em&gt;would&lt;/em&gt; matter are the ones classified &lt;code&gt;no&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The interesting number is not the delta. It is &lt;strong&gt;3 out of 14&lt;/strong&gt;. Three guards cut taint; eleven do not. If you shipped this corpus and assumed the guard names meant something, you would be wrong eleven times out of fourteen.&lt;/p&gt;

&lt;h3&gt;
  
  
  Zero suspicious rows is a real check
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ARGUMENT LAYOUT (probe) — 29 rule(s), 0 suspicious
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Twenty-nine sink rules, every argument index verified against the callee's declared signature, zero mismatches. That includes the receiver-shift cases: &lt;code&gt;Header.Set&lt;/code&gt; at index 2, &lt;code&gt;QueryContext&lt;/code&gt; at index 2, &lt;code&gt;DialTimeout&lt;/code&gt; at index 2.&lt;/p&gt;

&lt;p&gt;Zero suspicious is the outcome you want, but it is only meaningful because the probe &lt;em&gt;could&lt;/em&gt; have failed. The check exists because the indices are guesses, and a guess that is never tested is a bug waiting for a quiet afternoon.&lt;/p&gt;

&lt;h3&gt;
  
  
  The caveats are the most valuable part
&lt;/h3&gt;

&lt;p&gt;Six documented limitations, and they are not hedging — they are the boundary of what the tool knows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sanitizers aren't checked against sink kind.&lt;/strong&gt; &lt;code&gt;url.QueryEscape&lt;/code&gt; stops taint at a SQL sink. It shouldn't. This is a planted false negative, and it is the single most important line in the output: it says the tool's notion of "sanitized" is context-free, and context-free sanitization is not a real thing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Variadic sinks use &lt;code&gt;arg == -1&lt;/code&gt;&lt;/strong&gt;, so every argument is dangerous. &lt;code&gt;exec.Command("echo", name)&lt;/code&gt; gets reported even though the command is constant.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Predicate guards aren't correlated with sinks.&lt;/strong&gt; &lt;code&gt;ValidatePath(name)&lt;/code&gt; protects only the branch that consults it. The analyzer doesn't track that, so a handler that reads the raw value is still reported.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interprocedural summaries are one bit.&lt;/strong&gt; "Some parameter taints some result." Not which parameter, not which result.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependencies aren't summarized.&lt;/strong&gt; A taint path through a library is invisible unless the library is in the load patterns.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A tool that lists its own false positives and false negatives is a tool you can reason about. A tool that reports "10 issues" with no caveats is a tool you have to reverse-engineer.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Generalizes
&lt;/h2&gt;

&lt;p&gt;SSA was built to make compilers fast. It ended up being the cleanest substrate for asking &lt;em&gt;where does this value go&lt;/em&gt; — because that is the same question an optimizer asks, just with a different goal.&lt;/p&gt;

&lt;p&gt;The tool in this article is small: about 3,000 SSA instructions analyzed, 80 functions, 14 guards classified, 29 sink rules probed. It found ten real flows and, more usefully, it told you which of its own answers to distrust.&lt;/p&gt;

&lt;p&gt;Three things generalize beyond this codebase:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Read the body, not the name.&lt;/strong&gt; The single highest-value thing an SSA-based analyzer does that a regex cannot is decide whether a function called &lt;code&gt;ValidateAndEscape&lt;/code&gt; actually validates or escapes. Eleven of fourteen guards in this corpus did not cut taint. Names are documentation, and documentation drifts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Measure your assumptions.&lt;/strong&gt; Every sink rule is a claim about someone else's signature. The probe turns those claims into a table you can check. Zero suspicious rows is a result; an unverified index is a liability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Publish the caveats.&lt;/strong&gt; The six limitations are the most honest part of the output. A security tool that doesn't state its false-positive and false-negative modes is asking you to trust it blindly, which is exactly the failure mode it was built to catch.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Takeaway
&lt;/h2&gt;

&lt;p&gt;If you take one thing from this: &lt;strong&gt;taint analysis is not a scanner, it is a data-flow question, and SSA is the representation that makes the question answerable.&lt;/strong&gt; The &lt;code&gt;golang.org/x/tools/go/ssa&lt;/code&gt; package is public, documented, and already powering the tools you use every day. The barrier to writing your own analysis is not the IR — it is deciding what question to ask.&lt;/p&gt;

&lt;p&gt;Ask a question whose answer you can verify. Then verify it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Code
&lt;/h2&gt;

&lt;p&gt;The full source — the loader, the guard classifier, the fixpoint walk, the argument-layout probe, the corpus, and the tests — is at:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/cheikh2shift/go-snippets/tree/main/ssa-taint" rel="noopener noreferrer"&gt;https://github.com/cheikh2shift/go-snippets/tree/main/ssa-taint&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Clone it, run &lt;code&gt;go run .&lt;/code&gt;, and compare your output against the run above. If a corpus case gets the wrong verdict, that is a finding too.&lt;/p&gt;




&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Rosen, B. K., Wegman, M. N., &amp;amp; Zadeck, F. K. (1988). &lt;em&gt;Global Value Numbers and Redundant Computations.&lt;/em&gt; IBM Research Report RC 13588. The paper that named static single-assignment form and introduced Φ-functions.&lt;/li&gt;
&lt;li&gt;Wikipedia. &lt;em&gt;Static single-assignment form.&lt;/em&gt; &lt;a href="https://en.wikipedia.org/wiki/Static_single-assignment_form" rel="noopener noreferrer"&gt;https://en.wikipedia.org/wiki/Static_single-assignment_form&lt;/a&gt; — SSA definition, history, and the compiler optimizations it enables.&lt;/li&gt;
&lt;li&gt;Go Team. &lt;em&gt;Go 1.7 is released.&lt;/em&gt; &lt;a href="https://go.dev/blog/go1.7" rel="noopener noreferrer"&gt;https://go.dev/blog/go1.7&lt;/a&gt; — announcement of Go's SSA-based compiler back end and its observed speedups.&lt;/li&gt;
&lt;li&gt;Go Team. &lt;em&gt;&lt;code&gt;golang.org/x/tools/go/ssa&lt;/code&gt; package documentation.&lt;/em&gt; &lt;a href="https://pkg.go.dev/golang.org/x/tools/go/ssa" rel="noopener noreferrer"&gt;https://pkg.go.dev/golang.org/x/tools/go/ssa&lt;/a&gt; — the API used throughout this article; notes that SSA is "intentionally close to the source language to facilitate construction of source analysis tools."&lt;/li&gt;
&lt;li&gt;Go Team. &lt;em&gt;Introduction to the Go compiler's SSA backend.&lt;/em&gt; &lt;a href="https://github.com/golang/go/blob/master/src/cmd/compile/internal/ssa/README.md" rel="noopener noreferrer"&gt;https://github.com/golang/go/blob/master/src/cmd/compile/internal/ssa/README.md&lt;/a&gt; — values defined exactly once, memory type, basic blocks, and functions in Go's compiler SSA.&lt;/li&gt;
&lt;li&gt;Picatz. &lt;em&gt;Static Taint Analysis for Go&lt;/em&gt; (2023). &lt;a href="https://picatz.github.io/blog/2023/1/2/taint" rel="noopener noreferrer"&gt;https://picatz.github.io/blog/2023/1/2/taint&lt;/a&gt; — taint analysis as source-to-sink tracking on an SSA value graph; &lt;code&gt;taint.Check&lt;/code&gt;, backwards data flow, and the &lt;code&gt;*ssa.Const&lt;/code&gt; prepared-statement check.&lt;/li&gt;
&lt;li&gt;Picatz. &lt;em&gt;Go Static Analysis with SSA&lt;/em&gt; (2022). &lt;a href="https://picatz.github.io/blog/2022/12/4/ssa" rel="noopener noreferrer"&gt;https://picatz.github.io/blog/2022/12/4/ssa&lt;/a&gt; — practical introduction to building analyses on Go's SSA IR.&lt;/li&gt;
&lt;li&gt;Securego. &lt;em&gt;gosec — Golang security checker.&lt;/em&gt; &lt;a href="https://github.com/securego/gosec" rel="noopener noreferrer"&gt;https://github.com/securego/gosec&lt;/a&gt; — production static analyzer for Go that uses SSA-based taint analysis (rules G701–G706).&lt;/li&gt;
&lt;li&gt;Wikipedia. &lt;em&gt;Taint checking.&lt;/em&gt; &lt;a href="https://en.wikipedia.org/wiki/Taint_checking" rel="noopener noreferrer"&gt;https://en.wikipedia.org/wiki/Taint_checking&lt;/a&gt; — the general technique of tracking untrusted data from sources to sinks.&lt;/li&gt;
&lt;li&gt;Wikipedia. &lt;em&gt;Data-flow analysis.&lt;/em&gt; &lt;a href="https://en.wikipedia.org/wiki/Data-flow_analysis" rel="noopener noreferrer"&gt;https://en.wikipedia.org/wiki/Data-flow_analysis&lt;/a&gt; — the broader category of analyses that SSA enables.&lt;/li&gt;
&lt;/ul&gt;




</description>
      <category>ai</category>
      <category>go</category>
      <category>programming</category>
    </item>
    <item>
      <title>I built a Docker Image Factory From Scratch in Go</title>
      <dc:creator>Cheikh Seck</dc:creator>
      <pubDate>Thu, 30 Jul 2026 15:21:11 +0000</pubDate>
      <link>https://dev.to/cheikh_seck_01/i-built-a-docker-image-factory-from-scratch-in-go-301e</link>
      <guid>https://dev.to/cheikh_seck_01/i-built-a-docker-image-factory-from-scratch-in-go-301e</guid>
      <description>&lt;p&gt;Most teams treat container images as something a build tool produces on their behalf. You write a Dockerfile, hand it to BuildKit, and a few hundred megabytes later you have an image. The build process itself is a black box — a daemon, a cache, a base image you didn’t author, and a format you rarely inspect.&lt;/p&gt;

&lt;p&gt;This post takes the opposite stance: an image builder is just a program. Given a tarball and a few JSON blobs, any code that can compute a SHA-256 and write files can manufacture a valid OCI image. So instead of reaching for Docker, we’ll build an image factory — a small Go program that ingests layer tarballs, content-addresses them, assembles a manifest, and emits a spec-compliant OCI layout. The same factory can push to a registry and pull images back.&lt;/p&gt;

&lt;p&gt;The result is a reusable tool, not a one-off trick. As a bonus, the images it produces are tiny — around 8MB for a Go server — because the factory adds nothing you didn’t ask for. But the real value is ownership: you understand and control every byte of the image format.&lt;/p&gt;

&lt;p&gt;Read the rest here: &lt;a href="https://cheikhhseck.medium.com/building-a-docker-image-factory-from-scratch-in-go-e3c2e7127106?sk=42cbfc6c5981a5acc6ac1e617bf932be" rel="noopener noreferrer"&gt;https://cheikhhseck.medium.com/building-a-docker-image-factory-from-scratch-in-go-e3c2e7127106?sk=42cbfc6c5981a5acc6ac1e617bf932be&lt;/a&gt;&lt;/p&gt;

</description>
      <category>docker</category>
      <category>containers</category>
      <category>go</category>
    </item>
    <item>
      <title>eBPF vs Traditional Observability: Why Kernel-Level Debugging Wins for AI Services</title>
      <dc:creator>Cheikh Seck</dc:creator>
      <pubDate>Fri, 26 Jun 2026 09:13:01 +0000</pubDate>
      <link>https://dev.to/cheikh_seck_01/ebpf-vs-traditional-observability-why-kernel-level-debugging-wins-for-ai-services-3kaj</link>
      <guid>https://dev.to/cheikh_seck_01/ebpf-vs-traditional-observability-why-kernel-level-debugging-wins-for-ai-services-3kaj</guid>
      <description>&lt;h2&gt;
  
  
  eBPF vs Traditional Observability: Why Kernel-Level Debugging Wins for AI Services
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Originally published on Medium: &lt;a href="https://cheikhhseck.medium.com/ebpf-in-go-observability-for-ai-generated-services-9aae7573b823" rel="noopener noreferrer"&gt;https://cheikhhseck.medium.com/ebpf-in-go-observability-for-ai-generated-services-9aae7573b823&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;The Problem:&lt;/strong&gt; Your AI-generated service is running in production. Latency spikes from 40ms to 4 seconds. Your application logs show nothing unusual. Traditional observability tools (APM, logs, metrics) are blind to what's happening.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Solution:&lt;/strong&gt; eBPF gives you kernel-level visibility without modifying your code or restarting services.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why eBPF Matters for AI-Generated Code
&lt;/h3&gt;

&lt;p&gt;AI-generated services often lack context about kernel interactions. When things go wrong at the system level, you need observability that goes beyond application-level metrics.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real-world scenario:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;P95 latency jumped from 40ms to 4s&lt;/li&gt;
&lt;li&gt;No errors in application logs&lt;/li&gt;
&lt;li&gt;Traditional tools showed nothing&lt;/li&gt;
&lt;li&gt;eBPF revealed the root cause: syscall overhead from AI-generated file operations&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Key Takeaways from the Original Article
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Installation &amp;amp; Setup&lt;/strong&gt; - Getting started with Cilium's eBPF library in Go&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Writing eBPF Programs&lt;/strong&gt; - Using C for kernel-level tracing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Loading from Go&lt;/strong&gt; - Practical patterns for loading and attaching programs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tracing syscalls, network events, and file I/O&lt;/strong&gt; - Real examples you can run&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compilation tips&lt;/strong&gt; - Making it work in production environments&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The Code Example
&lt;/h3&gt;

&lt;p&gt;The original article includes a working example you can run in 10 minutes. It demonstrates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Attaching kprobes to kernel functions&lt;/li&gt;
&lt;li&gt;Using perf-event ringbuffers for efficient data collection&lt;/li&gt;
&lt;li&gt;Tracing file I/O and network events from Go&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://cheikhhseck.medium.com/ebpf-in-go-observability-for-ai-generated-services-9aae7573b823" rel="noopener noreferrer"&gt;Link to the full tutorial with code&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Why This Matters Now
&lt;/h3&gt;

&lt;p&gt;As AI-generated code becomes more prevalent, we need observability tools that can debug at the system level. eBPF provides that capability without requiring changes to existing applications.&lt;/p&gt;

</description>
      <category>go</category>
      <category>ai</category>
      <category>observability</category>
    </item>
    <item>
      <title>Godex: AI assistance in the terminal</title>
      <dc:creator>Cheikh Seck</dc:creator>
      <pubDate>Fri, 01 May 2026 10:35:55 +0000</pubDate>
      <link>https://dev.to/cheikh_seck_01/godex-ai-assistance-in-the-terminal-ihj</link>
      <guid>https://dev.to/cheikh_seck_01/godex-ai-assistance-in-the-terminal-ihj</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjgpqsdswxq3p90tcqlau.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjgpqsdswxq3p90tcqlau.png" alt=" " width="800" height="586"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hi folks,&lt;/p&gt;

&lt;p&gt;I recently wrote an article I feel has some insight in how LLMs work and the best way to apply it to your day-2-day work. "AI will not take your job, but somebody using it will"&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/dev-genius/godex-ai-assisted-coding-in-the-terminal-1d5182edecde?sk=b850044334e8b97720277dfdf458a17f" rel="noopener noreferrer"&gt;https://medium.com/dev-genius/godex-ai-assisted-coding-in-the-terminal-1d5182edecde?sk=b850044334e8b97720277dfdf458a17f&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>cli</category>
      <category>agents</category>
      <category>llm</category>
    </item>
    <item>
      <title>Don't pay for an AI sandbox — build one locally with Docker</title>
      <dc:creator>Cheikh Seck</dc:creator>
      <pubDate>Thu, 26 Mar 2026 11:32:21 +0000</pubDate>
      <link>https://dev.to/cheikh_seck_01/dont-pay-for-an-ai-sandbox-build-one-locally-with-docker-3840</link>
      <guid>https://dev.to/cheikh_seck_01/dont-pay-for-an-ai-sandbox-build-one-locally-with-docker-3840</guid>
      <description>&lt;p&gt;Don't pay for an AI sandbox. Build one locally that's secure with Docker.&lt;/p&gt;

&lt;p&gt;Here's a practical guide that spins up a locked-down environment with a TUI, a persistent config volume, and a proxy to your local Ollama:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/cheikh2shift/godex/tree/main#running-securely-with-docker" rel="noopener noreferrer"&gt;https://github.com/cheikh2shift/godex/tree/main#running-securely-with-docker&lt;/a&gt;&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Runs locally (no vendor lock-in)&lt;/li&gt;
&lt;li&gt;Sandboxed tools (bash, filesystem, webscraper)&lt;/li&gt;
&lt;li&gt;Config persisted in a Docker volume&lt;/li&gt;
&lt;li&gt;Works from any project directory via &lt;code&gt;WORKSPACE_DIR&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Quick start&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="nv"&gt;UID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;GID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;WORKSPACE_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PWD&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nv"&gt;$HOME&lt;/span&gt;/godex/docker-compose.yml up &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; docker attach godex &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nv"&gt;$HOME&lt;/span&gt;/godex/docker-compose.yml down
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Ollama note&lt;/strong&gt;&lt;br&gt;
If you're using a host Ollama instance via the nginx proxy, make sure Ollama listens on &lt;code&gt;0.0.0.0:11434&lt;/code&gt; (not just &lt;code&gt;127.0.0.1&lt;/code&gt;).&lt;/p&gt;

&lt;h1&gt;
  
  
  security #docker #opensource
&lt;/h1&gt;

</description>
      <category>ai</category>
      <category>docker</category>
      <category>security</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Go 1.23’s Iter package</title>
      <dc:creator>Cheikh Seck</dc:creator>
      <pubDate>Sat, 28 Dec 2024 13:48:00 +0000</pubDate>
      <link>https://dev.to/cheikh_seck_01/go-123s-iter-package-24kc</link>
      <guid>https://dev.to/cheikh_seck_01/go-123s-iter-package-24kc</guid>
      <description>&lt;p&gt;A post about writing a custom iterator that returns a map’s values in a constant order.&lt;/p&gt;

&lt;p&gt;Maps are notoriously known for changing their iteration order during each range loop. “When iterating over a map with a range loop, the iteration order is not specified and is not guaranteed to be the same from one iteration to the next.” The solution proposed by the Go team was to define a separate data structure that explicitly specifies the order of iteration; this may be a slice or array with the keys in the desired order.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdnyb45hjsxrim05ncpff.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdnyb45hjsxrim05ncpff.png" alt=" " width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this post, we’ll look at a recent approach to solving the iteration order problem; as well as understand how to define a really basic iterator with the iter package.&lt;/p&gt;

&lt;p&gt;You can read the rest here: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://cheikhhseck.medium.com/go-1-23s-iter-package-f6b44dfb9b7b?source=friends_link&amp;amp;sk=1cbef6905c4acaa7b816e661068f967a" rel="noopener noreferrer"&gt;https://cheikhhseck.medium.com/go-1-23s-iter-package-f6b44dfb9b7b?source=friends_link&amp;amp;sk=1cbef6905c4acaa7b816e661068f967a&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>go</category>
    </item>
  </channel>
</rss>
