<?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: StopThatSlop</title>
    <description>The latest articles on DEV Community by StopThatSlop (@stopthatslop).</description>
    <link>https://dev.to/stopthatslop</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%2F4092837%2F2fae4806-b383-43b9-8684-b8adcd3b9cda.png</url>
      <title>DEV Community: StopThatSlop</title>
      <link>https://dev.to/stopthatslop</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/stopthatslop"/>
    <language>en</language>
    <item>
      <title>We benchmarked a security detector against 500k lines of Go. It found a real bug.</title>
      <dc:creator>StopThatSlop</dc:creator>
      <pubDate>Mon, 24 Aug 2026 19:05:48 +0000</pubDate>
      <link>https://dev.to/stopthatslop/we-benchmarked-a-security-detector-against-500k-lines-of-go-it-found-a-real-bug-3pj2</link>
      <guid>https://dev.to/stopthatslop/we-benchmarked-a-security-detector-against-500k-lines-of-go-it-found-a-real-bug-3pj2</guid>
      <description>&lt;p&gt;We're building deslop — rule packs that stop coding agents from&lt;br&gt;
re-introducing bugs that were already fixed. One half of that is&lt;br&gt;
detection: if a rule claims agents keep writing a certain kind of bug,&lt;br&gt;
there should be a detector that catches it, and that detector should be&lt;br&gt;
measured before it's allowed to fail anyone's build.&lt;/p&gt;

&lt;p&gt;So we benchmarked one of our security detectors against roughly 503,000&lt;br&gt;
lines of Go across eight well-known open-source projects.&lt;/p&gt;

&lt;p&gt;It produced exactly one finding. The finding was real.&lt;/p&gt;
&lt;h2&gt;
  
  
  The detector
&lt;/h2&gt;

&lt;p&gt;The rule is simple to state: secrets and tokens must be compared in&lt;br&gt;
constant time. A plain string equality check (&lt;code&gt;==&lt;/code&gt;) against a secret leaks&lt;br&gt;
information through timing — on shared infrastructure, an attacker who can&lt;br&gt;
measure how long a comparison takes can, in principle, recover the secret&lt;br&gt;
piece by piece. The standard fix is &lt;code&gt;crypto/subtle.ConstantTimeCompare&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Writing a detector that fires on every &lt;code&gt;==&lt;/code&gt; next to a variable named&lt;br&gt;
&lt;code&gt;token&lt;/code&gt; is easy and useless. Real code compares all kinds of things to all&lt;br&gt;
kinds of things, and most of them are fine. Our detector only fires when&lt;br&gt;
an operand has a &lt;strong&gt;traceable credential source&lt;/strong&gt; — a value that came from&lt;br&gt;
&lt;code&gt;r.Header.Get(...)&lt;/code&gt;, &lt;code&gt;os.Getenv(...)&lt;/code&gt;, or an assignment chained from one&lt;br&gt;
of those. It skips test files and vendored code. It resolves named helper&lt;br&gt;
functions one level deep, because &lt;code&gt;CheckOrigin: isValidOrigin&lt;/code&gt; and&lt;br&gt;
&lt;code&gt;CheckOrigin: func(...) { return true }&lt;/code&gt; are the same bug wearing&lt;br&gt;
different clothes.&lt;/p&gt;
&lt;h2&gt;
  
  
  The benchmark
&lt;/h2&gt;

&lt;p&gt;Eight repositories, pinned commits, ~503k lines of Go (counted as&lt;br&gt;
non-blank lines, tests and vendor excluded):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Repo&lt;/th&gt;
&lt;th&gt;What it is&lt;/th&gt;
&lt;th&gt;LOC&lt;/th&gt;
&lt;th&gt;Findings&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;caddy&lt;/td&gt;
&lt;td&gt;web server / reverse proxy&lt;/td&gt;
&lt;td&gt;104,660&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;lazygit&lt;/td&gt;
&lt;td&gt;TUI git client&lt;/td&gt;
&lt;td&gt;141,754&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;restic&lt;/td&gt;
&lt;td&gt;backup / storage infra&lt;/td&gt;
&lt;td&gt;88,525&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;goreleaser&lt;/td&gt;
&lt;td&gt;release automation&lt;/td&gt;
&lt;td&gt;92,357&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fzf&lt;/td&gt;
&lt;td&gt;CLI fuzzy finder&lt;/td&gt;
&lt;td&gt;33,306&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;task&lt;/td&gt;
&lt;td&gt;task runner&lt;/td&gt;
&lt;td&gt;23,162&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;chi&lt;/td&gt;
&lt;td&gt;HTTP router&lt;/td&gt;
&lt;td&gt;12,082&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;viper&lt;/td&gt;
&lt;td&gt;configuration&lt;/td&gt;
&lt;td&gt;7,194&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Seven clean repos matter as much as the hit. A detector that cries wolf on&lt;br&gt;
&lt;code&gt;if key == "border-native"&lt;/code&gt; (fzf compares lexer tokens, not credentials)&lt;br&gt;
or &lt;code&gt;if setting.Key == "vcs.modified"&lt;/code&gt; (task dispatches on config keys)&lt;br&gt;
is a detector nobody will leave enabled. Our first version produced&lt;br&gt;
exactly those false positives — twenty-two of them. We hardened the&lt;br&gt;
operand analysis (require the credential source, not the name) and&lt;br&gt;
re-ran. One finding.&lt;/p&gt;
&lt;h2&gt;
  
  
  The finding
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;goreleaser&lt;/code&gt;, &lt;code&gt;internal/client/gitlab.go&lt;/code&gt;, in &lt;code&gt;checkUseJobToken&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="n"&gt;ciToken&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;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CI_JOB_TOKEN"&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;ciToken&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;return&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c"&gt;// ...&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GitLabURLs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UseJobToken&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;token&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;ciToken&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;token&lt;/code&gt; is the user-configured GitLab API token. &lt;code&gt;ciToken&lt;/code&gt; is the CI job&lt;br&gt;
token from the environment. The comparison decides which API client to&lt;br&gt;
use — and it's done with plain string equality.&lt;/p&gt;

&lt;p&gt;To be clear about severity: this is &lt;strong&gt;hardening, not a zero-day&lt;/strong&gt;.&lt;br&gt;
Exploiting a timing side channel over a network on a comparison like this&lt;br&gt;
is largely theoretical, and &lt;code&gt;ConstantTimeCompare&lt;/code&gt; still leaks length&lt;br&gt;
(though &lt;code&gt;CI_JOB_TOKEN&lt;/code&gt; lengths are fixed per environment). We said exactly&lt;br&gt;
that in the PR. The maintainers' response treated it as a reasonable&lt;br&gt;
correctness improvement — which is the right way to think about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix
&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;import&lt;/span&gt; &lt;span class="s"&gt;"crypto/subtle"&lt;/span&gt;
&lt;span class="c"&gt;// ...&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;subtle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConstantTimeCompare&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;token&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;ciToken&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two hunks, one line changed. The PR is here:&lt;br&gt;
&lt;a href="https://github.com/goreleaser/goreleaser/pull/6813" rel="noopener noreferrer"&gt;goreleaser#6813&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we take from this
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Zero findings on 430k lines is the feature.&lt;/strong&gt; The hard part of
static analysis isn't finding things — it's not finding things that
aren't there. Every clean repo in that table is what makes the single
finding worth a maintainer's time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source tracking beats name matching.&lt;/strong&gt; &lt;code&gt;token == "giteatoken"&lt;/code&gt; in
goreleaser (a placeholder comparison) looks identical to a credential
check if you match on identifiers. Trace where the value came from
instead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;This is the bar for rules that gate CI.&lt;/strong&gt; If a rule can't run clean
across half a million lines of real code, it has no business failing
anyone's PR.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;deslop is open source: &lt;a href="https://github.com/Amaresh/deslop" rel="noopener noreferrer"&gt;github.com/Amaresh/deslop&lt;/a&gt;.&lt;br&gt;
The detector that found this, and the benchmark that measured it, are part&lt;br&gt;
of the repo.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Found a false positive in your own Go codebase? That's data — the issue&lt;br&gt;
tracker is open.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>security</category>
      <category>opensource</category>
      <category>devtools</category>
    </item>
  </channel>
</rss>
