<?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: Prasanna Nattuthurai</title>
    <description>The latest articles on DEV Community by Prasanna Nattuthurai (@prasannavarshan).</description>
    <link>https://dev.to/prasannavarshan</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%2F4089306%2Fd5b8639c-5463-4925-b8ff-95307bb28ce8.png</url>
      <title>DEV Community: Prasanna Nattuthurai</title>
      <link>https://dev.to/prasannavarshan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/prasannavarshan"/>
    <language>en</language>
    <item>
      <title>Your Developer Toolchain Is a Supply Chain Attack Surface — Here's How I Hardened Mine</title>
      <dc:creator>Prasanna Nattuthurai</dc:creator>
      <pubDate>Sat, 22 Aug 2026 17:14:58 +0000</pubDate>
      <link>https://dev.to/prasannavarshan/your-developer-toolchain-is-a-supply-chain-attack-surface-heres-how-i-hardened-mine-4ghm</link>
      <guid>https://dev.to/prasannavarshan/your-developer-toolchain-is-a-supply-chain-attack-surface-heres-how-i-hardened-mine-4ghm</guid>
      <description>&lt;p&gt;&lt;em&gt;A practical guide to applying SLSA, Sigstore, and SBOM-based verification to the tools on your local machine.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem Nobody Talks About
&lt;/h2&gt;

&lt;p&gt;We obsess over dependency scanning in CI/CD. We pin versions in &lt;code&gt;package-lock.json&lt;/code&gt;. We run Dependabot. But what about the tools that &lt;em&gt;build&lt;/em&gt; your software?&lt;/p&gt;

&lt;p&gt;Every morning, you run &lt;code&gt;brew upgrade&lt;/code&gt;. npm updates your global packages. &lt;code&gt;rustup update&lt;/code&gt; refreshes your Rust toolchain. Each of these is an implicit trust decision:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"I trust that the binary I just downloaded is exactly what the publisher built, from the source code I'd expect, without tampering in transit."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For most developers, that trust is blind. I decided to make it explicit.&lt;/p&gt;




&lt;h2&gt;
  
  
  Threat Model: What Can Go Wrong
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Threat&lt;/th&gt;
&lt;th&gt;Real-World Example&lt;/th&gt;
&lt;th&gt;Likelihood&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Malicious package in registry&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;event-stream&lt;/code&gt; (npm, 2018), &lt;code&gt;ua-parser-js&lt;/code&gt; (npm, 2021)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;High&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compromised Homebrew tap&lt;/td&gt;
&lt;td&gt;Typosquatting tap overrides official formula&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;npm postinstall script RCE&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;eslint-scope&lt;/code&gt; credential theft (2018)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;High&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Binary replaced after install&lt;/td&gt;
&lt;td&gt;Compromised local system, PATH hijack&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DNS/BGP hijack on update channel&lt;/td&gt;
&lt;td&gt;PyPI CDN poisoning (hypothetical)&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dependency confusion&lt;/td&gt;
&lt;td&gt;Internal package name claimed on public registry&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The attack surface is your entire toolchain: &lt;strong&gt;compilers, linters, security scanners, CLI tools, language runtimes&lt;/strong&gt;. If an attacker compromises &lt;code&gt;trivy&lt;/code&gt;, they can hide their own CVEs from your scans.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Architecture: 6 Defense Layers
&lt;/h2&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiocvviu2qkcdw6dbdw6z.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiocvviu2qkcdw6dbdw6z.png" alt="Supply Chain Verification Architecture" width="800" height="853"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────────────────────────────────┐
│                    DEVELOPER WORKSTATION                             │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  Layer 1: TRANSPORT SECURITY                                        │
│  ┌──────────────────────────────────────────────────────────────┐  │
│  │  All channels: TLS 1.2+ (brew, npm, PyPI, crates.io, Go)    │  │
│  └──────────────────────────────────────────────────────────────┘  │
│                              │                                      │
│  Layer 2: CRYPTOGRAPHIC VERIFICATION                                │
│  ┌──────────────────────────────────────────────────────────────┐  │
│  │  brew: SHA256 bottles    │  uv: Sigstore cosign              │  │
│  │  rustup: signed manifest │  npm: ECDSA registry sigs         │  │
│  │  macOS: Apple code-sign  │  Go: sum.golang.org               │  │
│  └──────────────────────────────────────────────────────────────┘  │
│                              │                                      │
│  Layer 3: SOURCE ALLOWLISTING                                       │
│  ┌──────────────────────────────────────────────────────────────┐  │
│  │  Homebrew tap allowlist (BLOCK on untrusted)                  │  │
│  │  npm pinned package list + --ignore-scripts                   │  │
│  └──────────────────────────────────────────────────────────────┘  │
│                              │                                      │
│  Layer 4: SYSTEM INTEGRITY                                          │
│  ┌──────────────────────────────────────────────────────────────┐  │
│  │  SIP enabled │ Gatekeeper enabled │ No world-writable PATH   │  │
│  └──────────────────────────────────────────────────────────────┘  │
│                              │                                      │
│  Layer 5: AUDIT TRAIL                                               │
│  ┌──────────────────────────────────────────────────────────────┐  │
│  │  SHA256 pre/post snapshot │ Timestamped log │ Diff alerting   │  │
│  └──────────────────────────────────────────────────────────────┘  │
│                              │                                      │
│  Layer 6: SUPPLY-CHAIN VERIFICATION (post-update)                   │
│  ┌──────────────────────────────────────────────────────────────┐  │
│  │  ┌─────────────┐ ┌───────────────┐ ┌─────────────────────┐  │  │
│  │  │ brew verify │ │ npm audit sig │ │ gh attestation      │  │  │
│  │  │ (Sigstore)  │ │ (provenance)  │ │ verify (SLSA L3)    │  │  │
│  │  └─────────────┘ └───────────────┘ └─────────────────────┘  │  │
│  │  ┌─────────────┐ ┌───────────────┐ ┌─────────────────────┐  │  │
│  │  │ syft→grype  │ │ govulncheck   │ │ cargo audit         │  │  │
│  │  │ (SBOM+CVE)  │ │ (-mode=binary)│ │ (RustSec DB)        │  │  │
│  │  └─────────────┘ └───────────────┘ └─────────────────────┘  │  │
│  │  ┌─────────────┐ ┌───────────────┐                          │  │
│  │  │ codesign -v │ │ SHA diff alert│                          │  │
│  │  │ (macOS sig) │ │ (tampering)   │                          │  │
│  │  └─────────────┘ └───────────────┘                          │  │
│  └──────────────────────────────────────────────────────────────┘  │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Gap Analysis: Industry vs. Reality
&lt;/h2&gt;

&lt;p&gt;I benchmarked my existing toolchain updater against SLSA v1.0, NIST SP 800-218 (SSDF), and CISA's Secure Software Development guidance. Here's what I found:&lt;/p&gt;

&lt;h3&gt;
  
  
  What Most Developers Have (SLSA L1)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;✅ Lock files with checksums (&lt;code&gt;package-lock.json&lt;/code&gt;, &lt;code&gt;Cargo.lock&lt;/code&gt;, &lt;code&gt;go.sum&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;✅ TLS on all download channels&lt;/li&gt;
&lt;li&gt;✅ Package manager verifies checksums at install time&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What's Missing (SLSA L2/L3)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Gap&lt;/th&gt;
&lt;th&gt;Risk&lt;/th&gt;
&lt;th&gt;What's actually needed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;No independent provenance verification&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Don't just trust the package manager's internal check — verify Sigstore attestations yourself&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;npm audit&lt;/code&gt; ≠ &lt;code&gt;npm audit signatures&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;CVE scanning is different from provenance verification&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub Release downloads unattested&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;If you download a tarball from Releases, verify SLSA provenance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No SBOM of installed toolchain&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;You can't find vulns inside compiled binaries without an SBOM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SHA recorded but never compared&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;High&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A hash that's never diffed is security theater&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Go binaries never scanned&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Most security tools (trivy, gh, gitleaks) are Go — embedded deps have vulns too&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cargo binaries never audited&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;crates.io has zero Sigstore support — &lt;code&gt;cargo audit&lt;/code&gt; is all you have&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  The Implementation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Sigstore Provenance Verification (Homebrew)
&lt;/h3&gt;

&lt;p&gt;Homebrew bottles from &lt;code&gt;homebrew-core&lt;/code&gt; have had SLSA L3 Sigstore attestations since 2024. Most developers don't know this — or that you can verify them independently:&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;# Verify your security tools came from the real Homebrew CI&lt;/span&gt;
brew verify trivy
brew verify semgrep
brew verify gitleaks
brew verify gh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This checks the Sigstore transparency log and confirms the bottle was built by Homebrew's GitHub Actions workflow, from the expected source commit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real output from my system:&lt;/strong&gt;&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="go"&gt;[sec]   ✓ provenance verified: trivy
[sec]   ✓ provenance verified: semgrep
[sec]   ✓ provenance verified: gitleaks
[sec]   ✓ provenance verified: osv-scanner
[sec]   ✓ provenance verified: gh
[sec]   ✓ provenance verified: jq
[sec]   ✓ provenance verified: node
[sec]   ✓ provenance verified: python@3.13
[sec]   ✓ provenance verified: go
[ok]    Homebrew provenance: all 9 critical formulae verified
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. npm Provenance Signatures (Not Just &lt;code&gt;npm audit&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;npm audit&lt;/code&gt; checks for known CVEs. &lt;code&gt;npm audit signatures&lt;/code&gt; checks that packages were built from their claimed source repo via Sigstore:&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 checks CVEs:&lt;/span&gt;
npm audit

&lt;span class="c"&gt;# This checks provenance (different!):&lt;/span&gt;
npm audit signatures
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are complementary, not interchangeable. A package can have zero CVEs but be a complete forgery.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. SBOM + Binary Vulnerability Scanning
&lt;/h3&gt;

&lt;p&gt;Here's the insight most developers miss: &lt;strong&gt;compiled binaries embed their entire dependency tree.&lt;/strong&gt; A Go binary built with a vulnerable &lt;code&gt;net/http&lt;/code&gt; is vulnerable even after you've updated your system Go.&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;# Generate an SBOM of all your Homebrew binaries&lt;/span&gt;
syft &lt;span class="nb"&gt;dir&lt;/span&gt;:&lt;span class="si"&gt;$(&lt;/span&gt;brew &lt;span class="nt"&gt;--prefix&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;/bin &lt;span class="nt"&gt;-o&lt;/span&gt; cyclonedx-json &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; toolchain-sbom.json

&lt;span class="c"&gt;# Scan for embedded vulnerabilities&lt;/span&gt;
grype sbom:toolchain-sbom.json &lt;span class="nt"&gt;--only-fixed&lt;/span&gt; &lt;span class="nt"&gt;--fail-on&lt;/span&gt; high
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;My result:&lt;/strong&gt; &lt;code&gt;grype: no high/critical fixable vulns in toolchain binaries&lt;/code&gt; ✅&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Go Binary Vulnerability Scanning
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;govulncheck -mode=binary&lt;/code&gt; is the Go team's official tool for scanning compiled Go binaries. It reads the embedded module info and checks against the Go vulnerability database:&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;# Scan your security tools (they're all written in Go!)&lt;/span&gt;
govulncheck &lt;span class="nt"&gt;-mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;binary &lt;span class="si"&gt;$(&lt;/span&gt;which trivy&lt;span class="si"&gt;)&lt;/span&gt;
govulncheck &lt;span class="nt"&gt;-mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;binary &lt;span class="si"&gt;$(&lt;/span&gt;which gh&lt;span class="si"&gt;)&lt;/span&gt;
govulncheck &lt;span class="nt"&gt;-mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;binary &lt;span class="si"&gt;$(&lt;/span&gt;which gitleaks&lt;span class="si"&gt;)&lt;/span&gt;
govulncheck &lt;span class="nt"&gt;-mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;binary &lt;span class="si"&gt;$(&lt;/span&gt;which osv-scanner&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On a first run this surfaced a batch of Go stdlib DoS advisories in one of the scanners above — the binary had been compiled against an older Go toolchain than the one that fixed them. Low risk in a local CLI context, and resolved by a rebuild, but the point is that nothing else I ran would have told me.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. SHA Diff Alerting (The Most Important One)
&lt;/h3&gt;

&lt;p&gt;Recording SHA256 hashes is meaningless if you never compare them. The real value is &lt;strong&gt;detecting unexplained changes&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;# After every update run, record hashes&lt;/span&gt;
shasum &lt;span class="nt"&gt;-a&lt;/span&gt; 256 &lt;span class="si"&gt;$(&lt;/span&gt;brew &lt;span class="nt"&gt;--prefix&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;/bin/trivy &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; binary-shas.txt

&lt;span class="c"&gt;# On next run, compare:&lt;/span&gt;
&lt;span class="c"&gt;# - If SHA changed AND an update was logged → expected ✅&lt;/span&gt;
&lt;span class="c"&gt;# - If SHA changed WITHOUT a logged update → RED FLAG 🚨&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;My implementation detects this automatically:&lt;/strong&gt;&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="go"&gt;[sec]   Comparing runs: 2026-08-09 → 2026-08-18
[sec]   ✓ go: SHA changed (update logged)
[ok]    Binary diff: 1 changes, all explained by logged updates
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a binary ever changes without a corresponding update in the log, the script flags it as potential tampering.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Complete Flow
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────────┐
│   brew upgrade   │──→ SHA256 bottle verification (automatic)
│   npm install -g │──→ --ignore-scripts (kill postinstall RCE)
│   uv self update │──→ Sigstore cosign verification (automatic)
│   rustup update  │──→ Signed manifest verification (automatic)
└────────┬─────────┘
         │
         ▼
┌──────────────────────────────────────────────────────┐
│            POST-UPDATE VERIFICATION                   │
├──────────────────────────────────────────────────────┤
│                                                      │
│  1. brew verify (Sigstore provenance)                │
│  2. npm audit signatures (provenance ≠ CVE scan)    │
│  3. gh attestation verify (SLSA L3 for Releases)    │
│  4. syft → grype (SBOM-based binary vuln scan)      │
│  5. codesign -v (macOS signature validation)        │
│  6. SHA diff alerting (detect unexplained changes)   │
│  7. govulncheck -mode=binary (Go embedded deps)     │
│  8. cargo audit (RustSec advisory DB)               │
│                                                      │
└────────┬─────────────────────────────────────────────┘
         │
         ▼
┌──────────────────┐
│  SHA256 snapshot │──→ Baseline for next run's diff
│  SBOM archived   │──→ Audit trail
│  Log recorded    │──→ Evidence
└──────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  What This Catches (Real Examples)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Which layer detects it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;event-stream&lt;/code&gt; style npm hijack&lt;/td&gt;
&lt;td&gt;Layer 3 (&lt;code&gt;--ignore-scripts&lt;/code&gt;) blocks execution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Typosquatting Homebrew tap&lt;/td&gt;
&lt;td&gt;Layer 3 (tap allowlist blocks unknown taps)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compromised brew bottle&lt;/td&gt;
&lt;td&gt;Layer 6 (&lt;code&gt;brew verify&lt;/code&gt; — Sigstore attestation mismatch)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Binary replaced by attacker on disk&lt;/td&gt;
&lt;td&gt;Layer 5/6 (SHA diff alert — unexplained change)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vulnerable dependency inside &lt;code&gt;trivy&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Layer 6 (&lt;code&gt;govulncheck -mode=binary&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;npm package without provenance&lt;/td&gt;
&lt;td&gt;Layer 6 (&lt;code&gt;npm audit signatures&lt;/code&gt; flags it)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PATH hijack via world-writable dir&lt;/td&gt;
&lt;td&gt;Layer 4 (detected at script startup)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  The Ecosystem Reality Check
&lt;/h2&gt;

&lt;p&gt;Not everything is verifiable yet. Here's the honest state as of 2026:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Ecosystem&lt;/th&gt;
&lt;th&gt;Provenance Coverage&lt;/th&gt;
&lt;th&gt;What's available&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Homebrew&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ 100% (homebrew-core)&lt;/td&gt;
&lt;td&gt;Sigstore attestations on all bottles&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;npm&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;⚠️ ~7% of packages&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;npm audit signatures&lt;/code&gt; + registry ECDSA&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PyPI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;⚠️ ~17% of uploads&lt;/td&gt;
&lt;td&gt;PEP 740 attestations (growing fast)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Go modules&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Checksums (sum.golang.org)&lt;/td&gt;
&lt;td&gt;No Sigstore, but tamper-evident log&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;crates.io&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌ Checksums only&lt;/td&gt;
&lt;td&gt;No Sigstore, no provenance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GitHub Releases&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;⚠️ Opt-in&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;gh attestation verify&lt;/code&gt; (if publisher enables)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The strategy is &lt;strong&gt;defense in depth&lt;/strong&gt;: verify what you can cryptographically, scan what you can't verify, and detect tampering as a last resort.&lt;/p&gt;




&lt;h2&gt;
  
  
  Getting Started (15 Minutes)
&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;# 1. Install the verification tools&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;syft grype cosign
go &lt;span class="nb"&gt;install &lt;/span&gt;golang.org/x/vuln/cmd/govulncheck@latest
cargo &lt;span class="nb"&gt;install &lt;/span&gt;cargo-audit

&lt;span class="c"&gt;# 2. Verify your current Homebrew bottles&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;f &lt;span class="k"&gt;in &lt;/span&gt;trivy semgrep gh node python@3.13 go&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;brew verify &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; 2&amp;gt;/dev/null &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"✓ &lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&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;"✗ &lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;

&lt;span class="c"&gt;# 3. Check npm provenance&lt;/span&gt;
npm audit signatures

&lt;span class="c"&gt;# 4. Generate your first toolchain SBOM&lt;/span&gt;
syft &lt;span class="nb"&gt;dir&lt;/span&gt;:&lt;span class="si"&gt;$(&lt;/span&gt;brew &lt;span class="nt"&gt;--prefix&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;/bin &lt;span class="nt"&gt;-o&lt;/span&gt; cyclonedx-json &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ~/toolchain-sbom.json
grype sbom:~/toolchain-sbom.json &lt;span class="nt"&gt;--only-fixed&lt;/span&gt;

&lt;span class="c"&gt;# 5. Scan Go binaries&lt;/span&gt;
govulncheck &lt;span class="nt"&gt;-mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;binary &lt;span class="si"&gt;$(&lt;/span&gt;which trivy&lt;span class="si"&gt;)&lt;/span&gt;
govulncheck &lt;span class="nt"&gt;-mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;binary &lt;span class="si"&gt;$(&lt;/span&gt;which gh&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# 6. Create your SHA baseline&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;bin &lt;span class="k"&gt;in &lt;/span&gt;brew node npm go cargo trivy gh&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;shasum &lt;span class="nt"&gt;-a&lt;/span&gt; 256 &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;which &lt;span class="nv"&gt;$bin&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/binary-shas-baseline.txt
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Recording hashes is not monitoring.&lt;/strong&gt; A hash file that's never diffed is security theater. You need automated comparison with explainability (was there a logged update or not?).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Your security tools are attack surface.&lt;/strong&gt; &lt;code&gt;trivy&lt;/code&gt;, &lt;code&gt;semgrep&lt;/code&gt;, &lt;code&gt;gitleaks&lt;/code&gt; — if these are compromised, they can hide their own findings. Scan the scanners.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;npm audit&lt;/code&gt; is not &lt;code&gt;npm audit signatures&lt;/code&gt;.&lt;/strong&gt; One checks CVEs, the other checks provenance. You need both.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Bash 3.2 is still the default on macOS.&lt;/strong&gt; If your security script uses &lt;code&gt;mapfile&lt;/code&gt; or associative arrays, it silently fails on every Mac. Ship it or test it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Timeouts matter.&lt;/strong&gt; Any network call in a security script that can hang indefinitely will eventually hang indefinitely. I learned this the hard way when Sigstore transparency log queries hung for 90 minutes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;SLSA is a consumer spec too.&lt;/strong&gt; Most people think SLSA is for producers/builders. It's equally a spec for how consumers should verify artifacts.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A check that fires on a healthy machine is worse than no check.&lt;/strong&gt; My world-writable &lt;code&gt;PATH&lt;/code&gt; test matched any two &lt;code&gt;w&lt;/code&gt; characters in the permission string — so it flagged every &lt;em&gt;group&lt;/em&gt;-writable directory, which is Homebrew's own normal layout. It also read the wrong column, testing other-&lt;em&gt;read&lt;/em&gt; instead of other-write. Result: &lt;code&gt;PATH hijack risk!&lt;/code&gt; on a correctly configured Mac, every single run — a warning with no way to act on it, which is the kind you stop reading. A false positive doesn't just waste attention; it trains you to skip the output, and then the true positive scrolls past too. Prefer a false negative you'll notice missing over a false alarm you'll learn to filter.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;My tool was broken for every user but me, and only a second machine could tell me.&lt;/strong&gt; One line read the list of Rust-installed binaries:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   cargo &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--list&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s2"&gt;"^[a-z]"&lt;/span&gt; | &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; line&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Under &lt;code&gt;set -euo pipefail&lt;/code&gt;, &lt;code&gt;grep&lt;/code&gt; matching nothing exits 1, which aborts the entire script. On a machine with no cargo-installed binaries — a fresh Mac, most people's Mac — the dry run died silently a third of the way through. It worked flawlessly for me because I happen to have some. No amount of testing on my own laptop could have surfaced it; a CI runner found it on its first execution. If you're writing a tool that inspects an environment, the one environment you cannot test in is your own.&lt;/p&gt;




&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://slsa.dev/spec/v1.0/verifying-artifacts" rel="noopener noreferrer"&gt;SLSA v1.0 — Verification specification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://csrc.nist.gov/publications/detail/sp/800-218/final" rel="noopener noreferrer"&gt;NIST SP 800-218 (SSDF)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Homebrew/brew/issues/17019" rel="noopener noreferrer"&gt;Homebrew Sigstore attestations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.npmjs.com/generating-provenance-statements" rel="noopener noreferrer"&gt;npm provenance documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck" rel="noopener noreferrer"&gt;govulncheck binary mode&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/anchore/syft" rel="noopener noreferrer"&gt;Anchore syft + grype&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://rustsec.org/" rel="noopener noreferrer"&gt;RustSec Advisory Database&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;The full implementation is open source — a 1,300-line bash script with no&lt;br&gt;
runtime dependencies beyond Homebrew itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew tap prasannavarshan/tap
brew trust prasannavarshan/tap    &lt;span class="c"&gt;# Homebrew gates third-party taps&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;update-toolchain

update-toolchain &lt;span class="nt"&gt;--dry&lt;/span&gt;            &lt;span class="c"&gt;# report; change nothing&lt;/span&gt;
update-toolchain &lt;span class="nt"&gt;--audit&lt;/span&gt;          &lt;span class="c"&gt;# security checks only&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Source, threat model and known limitations:&lt;br&gt;
&lt;a href="https://github.com/prasannavarshan/update-toolchain" rel="noopener noreferrer"&gt;github.com/prasannavarshan/update-toolchain&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Allowlists are config files rather than code, so nothing updates unless you have&lt;br&gt;
named it — and the config is parsed, never sourced, because a tool that decides&lt;br&gt;
what to trust should not let its own config file execute anything.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Tags:&lt;/strong&gt; #SupplyChain #DevSecOps #SLSA #Sigstore #macOS #Homebrew #SBOM #SecurityEngineering&lt;/p&gt;

</description>
      <category>security</category>
      <category>supplychain</category>
      <category>devops</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
