<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>John Saigle</title>
    <description>Software engineering, security, and technical insights</description>
    <link>https://johnsaigle.com</link>
    <atom:link href="https://johnsaigle.com/feed.xml" rel="self" type="application/rss+xml"/>
    <language>en-us</language>
    <lastBuildDate>Thu, 16 Apr 2026 19:26:09 +0000</lastBuildDate>
    <pubDate>Thu, 16 Apr 2026 19:26:09 +0000</pubDate>
    <ttl>60</ttl>
    
    <item>
      <title><![CDATA[Solana Vulnerabilities That Aren&#39;t]]></title>
      <description><![CDATA[A few bug classes that auditors keep reporting, but which don&#39;t actually exist.]]></description>
      <pubDate>Thu, 12 Mar 2026 00:00:00 +0000</pubDate>
      <link>https://johnsaigle.com/posts/solana-vulnerabilities-that-arent</link>
      <guid isPermaLink="true">https://johnsaigle.com/posts/solana-vulnerabilities-that-arent</guid>
      <content:encoded><![CDATA[
        <div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; max-width: 680px;">
          <p>I published a post with Asymmetric Research about bug classes that keep showing up in audit reports, security courses, and LLM outputs—but which aren’t actually vulnerabilities.</p>

<p>Topics include:</p>

<ul>
  <li>Reentrancy (not a thing in Solana)</li>
  <li>Closed account discriminators (removed from Anchor years ago)</li>
  <li>Float non-determinism (emulated, not hardware-dependent)</li>
  <li>Self-transfers always succeeding (they don’t)</li>
  <li>Partial state commitment (transactions are atomic)</li>
  <li>Unchecked CPI return values (they revert automatically)</li>
</ul>

<p>Getting these right helps everyone focus on real issues. The post explains what changed, why the old advice persists, and how the Solana ecosystem continues to evolve.</p>

<p><a href="https://blog.asymmetric.re/solana-vulnerabilities-that-arent-unpacking-common-misreports/">Read it on the Asymmetric Research blog</a></p>

        </div>
      ]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[Getting Roasted by my LLM Style Editor]]></title>
      <description><![CDATA[My own style-editing agent delivered unexpectedly harsh feedback. It stung, and it was exactly what I needed.]]></description>
      <pubDate>Sun, 30 Nov 2025 00:00:00 +0000</pubDate>
      <link>https://johnsaigle.com/posts/getting-roasted-by-my-llm-style-editor</link>
      <guid isPermaLink="true">https://johnsaigle.com/posts/getting-roasted-by-my-llm-style-editor</guid>
      <content:encoded><![CDATA[
        <div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; max-width: 680px;">
          <p>I got caught being lazy. I wanted to experiment by writing a brief, fragmented post at the end of the day. The goal was simply to explain-brag that I’d found a neat trick to get an LLM to do a post-processing step that increased its output quality somewhat.</p>

<p>As is my routine, I sent the rough draft through my “Style Editor” <a href="https://opencode.ai/docs/agents/#subagents">subagent</a>. That’s when it got personal.</p>

<p>Here are some selected lines from the LLM’s review of my prose:</p>
<ul>
  <li><em>“… reads like jargon explaining jargon”</em></li>
  <li><em>“tepid business-speak”</em></li>
  <li><em>“undermines its own point”</em></li>
  <li><em>“the unfinished structure introduces sloppiness (ironically)”</em></li>
</ul>

<p>But this was the kicker:</p>

<div class="language-md highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gu">### Does This Need to Be Published?</span>

Your "Sloppy Syntax" post already catalogs LLM output problems. This post adds the <span class="ge">*action*</span> of fixing them, which is valuable, but ask yourself: is this additive to your existing work, or redundant?

If you proceed, make it either comprehensively practical (show the full prompt, give more examples, make it a resource) or philosophically interesting (what does the need for deslopification tell us?). The current draft falls in the exhausting middle ground that you eloquently criticized in "Bernoulli Coding."
</code></pre></div></div>

<p><strong>Damn</strong>. This is a far cry from the fulsome you’re-absolutely-right style feedback I’m used to. Honestly, it stung a little.</p>

<p>It’s also hilarious. I was not expecting to get roasted by the machine. I was unconsciously relying on a bit of the ol’ sycophancy to get me through to the next draft.</p>

<p>I get that this is me playing chess against myself: I’m using my own words to critique my own words. I attempted to approximate my aesthetic preferences, programming a strange machine to tell myself when I don’t like something that I’m doing.</p>

<p>There’s value in ruthless self-assessment, and in coming back to a piece in a different frame of mind from the one you were in during the initial creation. “Write drunk, edit sober.”</p>

<p>I’m used to this way of thinking while writing or making music, but this experience was new. Instead of coming back to the work with a different mindset, I’ve <em>approximated</em> the critical mindset. The shock came from having that receiving the editorial feedback while I was in a lazy mode of the creative mindset.</p>

<p>I’m thankful for it. The Style Editor shook me out of a complacent mindset and genuinely challenged me to put more effort into writing something worthwhile.</p>

<p>The trick, apparently, is stating your aesthetic preferences clearly enough that the system delivers tough love on its own. I told it what good writing looks like, fed it my existing posts, and it somehow decided I needed to be called out. The harshness emerged organically, which makes it more interesting than if I’d explicitly programmed it to roast me.</p>

<p>I’m still entertained by moments like this. It’s fun when these things surprise you, and it’s this feeling of playful<br />
exploration that keeps me motivated to blend LLMs into my writing and coding practices.</p>

        </div>
      ]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[Stop Running `npm install`]]></title>
      <description><![CDATA[The sha1-hulud attack compromised 830 packages and leaked 11,000 secrets. MFA stops account takeovers. `npm ci` stops propagation.]]></description>
      <pubDate>Wed, 26 Nov 2025 00:00:00 +0000</pubDate>
      <link>https://johnsaigle.com/posts/stop-running-npm-install</link>
      <guid isPermaLink="true">https://johnsaigle.com/posts/stop-running-npm-install</guid>
      <content:encoded><![CDATA[
        <div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; max-width: 680px;">
          <p>This week, I watched the <a href="https://socket.dev/blog/sha1-hulud-november-2025">sha1-hulud attack</a> spread through the JavaScript ecosystem in real time. Over 830 npm packages compromised. 28,000 repositories infected. More than 11,000 unique secrets leaked, with over 2,000 still valid and publicly exposed as of November 24.</p>

<p>The response focused on securing accounts: enable MFA, audit dependencies, rotate secrets. Necessary, but reactive. There’s a prevention strategy that gets overlooked: stop running <code>npm install</code>.</p>

<h2 id="the-problem">The Problem</h2>

<p><code>npm install</code> pulls whatever version satisfies your semver range. Without a lockfile, you get the latest compatible version published in the last few seconds. With a lockfile, you’re still trusting that maintainer accounts haven’t been compromised, that transitive dependencies are clean, that post-install scripts aren’t malicious.</p>

<p>Every <code>npm install</code> is a bet that nothing malicious has been published since your last install. Sha1-hulud proved that bet loses.</p>

<h2 id="version-pinning-as-defense">Version Pinning as Defense</h2>

<p><a href="https://blog.yossarian.net/2025/11/21/We-should-all-be-using-dependency-cooldowns">Dependency cooldowns</a> are a valuable concept. (I encountered it on <a href="https://simonwillison.net/2025/Nov/21/dependency-cooldowns/">Simon Willison</a>’s blog this week.) Wait a configurable period after a package is published before allowing it into your project. If a malicious version gets published, you have time to catch it before it reaches production.</p>

<p>For projects with serious security requirements, treat dependency updates as security events. They deserve dedicated review time and careful examination of changelogs. Staying several versions behind might actually reduce risk when attacks exploit the usual advice to patch quickly.</p>

<p>After the last major attack, we added explicit rules to <a href="https://github.com/wormhole-foundation/wormhole/blob/main/CONTRIBUTING.md#supply-chain-security">Wormhole’s Guardian node CONTRIBUTING.md</a>:</p>

<p>For existing projects, use <code>npm ci</code>. While <code>npm install</code> updates dependencies to newer versions within the semver range and might pull down a compromised package published five minutes ago, <code>npm ci</code> installs exactly what’s in <code>package-lock.json</code>.</p>

<p>When adding packages, pin versions. Use <code>npm i package@version</code> to specify exact versions. Specific versions can’t be overwritten after release, which blocks an entire class of attacks.</p>

<p>The same principles apply to Docker containers. Copy <code>package.json</code> and <code>package-lock.json</code> first, then run <code>npm ci</code>. For global packages, specify versions: <code>npm install -g somepackage@1.2.3</code>. Pin base images with SHA-256 hashes: <code>FROM node:18.19.0-alpine@sha256:12345...</code>.</p>

<h2 id="building-lockfile-guard">Building lockfile-guard</h2>

<p>Manual discipline doesn’t scale. Developers will continue using the unsafe default workflow unless something stops them. I wanted a tool that would catch these mistakes before they reached production, so I wrote <a href="https://github.com/johnsaigle/lockfile-guard">lockfile-guard</a>.</p>

<p>It’s a GitHub Action that scans your repository for dangerous package manager commands. It checks Dockerfiles, markdown documentation, and shell scripts. When it finds violations, it fails the build.</p>

<p>The rules:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># ✅ Safe patterns</span>
<span class="s">npm ci</span>
<span class="s">npm i package@version</span>
<span class="s">pnpm install --frozen-lockfile</span>
<span class="s">yarn install --immutable</span>
<span class="s">bun install --frozen-lockfile</span>

<span class="c1"># ❌ Dangerous patterns</span>
<span class="s">npm install</span>
<span class="s">npm i package</span>
<span class="s">pnpm install</span>
<span class="s">yarn install</span>
<span class="s">bun install</span>
</code></pre></div></div>

<p>The output:</p>

<pre><code>✗ ./Dockerfile
  Line 15: Use 'npm ci' instead of 'npm install' for lockfile-based installations
  &gt; npm install

✗ ./.github/workflows/deploy.yml
  Line 42: Use 'pnpm install --frozen-lockfile' to respect lockfile
  &gt; pnpm install
</code></pre>

<p>Nothing advanced here, just pattern matching in files that matter. Simple tools are valuable when they encode protections that might otherwise depend on institutional memory.</p>

<h2 id="trust-is-not-a-security-model">Trust Is Not a Security Model</h2>

<p>We trust that packages do what they claim, that maintainer accounts are protected, that the registry enforces security boundaries. Sha1-hulud exploited that trust. A single compromise rippled through thousands of projects in hours.</p>

<p>Use <code>npm ci</code>. Pin your versions. Audit your dependencies. Harden your CI/CD pipelines with least-privilege access. When the next supply chain attack arrives, and it will, you’ll be ready.</p>

        </div>
      ]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[Slop In A Bucket]]></title>
      <description><![CDATA[LLM-generated code, linters, and the security problems we can and can&#39;t solve.]]></description>
      <pubDate>Sun, 23 Nov 2025 00:00:00 +0000</pubDate>
      <link>https://johnsaigle.com/posts/slop-in-a-bucket</link>
      <guid isPermaLink="true">https://johnsaigle.com/posts/slop-in-a-bucket</guid>
      <content:encoded><![CDATA[
        <div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; max-width: 680px;">
          <p>LLM-generated code is everywhere. The speed is seductive, the YOLO mode irresistible. Despite the horrible security problems<br />
this entails, <a href="https://www.youtube.com/watch?v=b6_IZK-1naY">I also like to live <code>--dangerously</code>.</a></p>

<p>I’ve been thinking about whether you can vibe code complex applications and still ship secure code. Here are some lines of<br />
thought I’ve been exploring.</p>

<h2 id="fragment-1-linters-as-llm-guardrails">Fragment 1: Linters as LLM Guardrails</h2>

<p>The main defense is linters and static analysis tools. They’re deterministic, which matters enormously. You can integrate them into CI or commit hooks to block code that doesn’t meet standards. Better yet, high-quality tools often include suggested fixes or even auto-fix flags that can be applied directly to the codebase. With reasonably high-signal information like that, you can run the linter, have the LLM look at the output, and watch it fix its own mistakes.</p>

<p>Ask an LLM to code an app from scratch and run it against common linting tools. You’ll find dozens of errors. The LLM can look at those reports and fix the code it just made. Suddenly, even though it used dangerous patterns, it’s able to correct the ones we can lint for. It’s like having a sloppy chef who’ll clean up after themselves if you point at the spills.</p>

<p><a href="/posts/adding-slopsquatting-packages-to-scary-strings-detection">SlopSquatting</a> is interesting here because it’s a new bug class introduced by LLMs while also being a problem we can address via linting.</p>

<h2 id="fragment-2-the-limits-of-linting">Fragment 2: The Limits of Linting</h2>

<p>We need lint rules with high signal-to-noise ratios and suggested fixes. The more patterns we can detect and auto-correct, the better we can constrain generated code. But we get no security guarantees for unlintable problems. Logic errors, for instance, or authentication flows that span multiple files.</p>

<p>Linters raise the floor of quality on a codebase generated by an LLM, but vibe coding is going to remain an insecure process. The main problem is keeping context and intent across several distinct files. It’s easy to mess up an authentication system in a web app because you have to track state across user endpoints, authorization and routes, database schemas, refresh tokens, lockouts, and so on. It’s a bunch of complicated mechanisms glued together, and it’s easy to miss one line or make changes in one set of commits that undoes work in another set.</p>

<h2 id="fragment-3-the-problem-of-the-missing-mind">Fragment 3: The Problem of the Missing Mind</h2>

<p>During a security review of a complex codebase, it’s common to go in and not understand how things work. But you can usually piece together the complex features with enough time and with the implicit trust that someone got this to work well at some point. There’s some sense of order lurking behind even a chaotic codebase. It may be that no single person on the team knows how it all works, but you get the blind men touching the elephant thing. Talk to enough people, read enough documentation, and you can usually assemble the whole from the parts, even if no one person sees the complete picture.</p>

<p>This won’t be the case with LLM-generated code. There is no intent creating a through-line in the implementation. No original architect who made deliberate choices, no accumulated wisdom from code reviews, no debugging sessions that revealed why something had to be done a particular way. Just slop in a bucket, tokens arranged in plausible patterns. The linters catch what they can, but the ghost in the machine isn’t haunted by understanding—just by training data.</p>

<h2 id="fragment-4-everything-is-perl-now">Fragment 4: Everything is Perl Now</h2>

<p>I have a soft spot for Perl despite its shortcomings. Maybe I love it because of what other people consider its shortcomings.<br />
It’s fun to develop your own little dialect and completely forget about it ten minutes later. It’s infamous as a write-only language because no one can read its arcane symbols or figure out what the hell anyone else is doing.</p>

<p>But now a ton of new code is getting generated this way. The harder you vibe, the less you read the code. I have<br />
entire applications where I haven’t yet <em>opened</em> all of the files. Code gets written, not read. Vibe-coding is a continuum,<br />
and I expect the ratio of vibe-to-artisanal code to increase as the LLMs get better and as more people get comfortable with<br />
the tools. Everything is Perl now.</p>

        </div>
      ]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[Sloppy Syntax]]></title>
      <description><![CDATA[A personal collection of totally radioactive syntax often generated by LLMs.]]></description>
      <pubDate>Sat, 22 Nov 2025 00:00:00 +0000</pubDate>
      <link>https://johnsaigle.com/posts/sloppy-syntax</link>
      <guid isPermaLink="true">https://johnsaigle.com/posts/sloppy-syntax</guid>
      <content:encoded><![CDATA[
        <div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; max-width: 680px;">
          <p>A personal collection of totally radioactive syntax often generated by LLMs.</p>

<h2 id="stale-rhetorical-devices">Stale Rhetorical Devices</h2>
<ul>
  <li>Contrastive negation, i.e. any variation on: <em>“It’s not X – it’s Y”</em>.</li>
  <li>Unnecessary meta-commentary (<em>“So here’s the question,” “here’s the interesting part”</em>)</li>
  <li><em>“The math is simple”</em></li>
  <li>Developing ideas in essays via <a href="https://en.wikipedia.org/wiki/Anaphora_(rhetoric)">anaphora</a></li>
  <li>Obvious alliteration</li>
</ul>

<h2 id="seo-and-algorithm-gamification">SEO and Algorithm Gamification</h2>
<ul>
  <li>New markdown headings that interject prose that already flows well</li>
  <li>Posts consisting of individual sentences separated by more than one new line</li>
  <li>Emojis as punctuation</li>
  <li>Emojis in markdown headings</li>
  <li>Ending pieces with “The Bottom Line”; superfluous summary pontification</li>
  <li>Calls-to-action outside of a marketing context</li>
</ul>

<h2 id="sloppy-syntax">Sloppy Syntax</h2>
<ul>
  <li>Em dashes (other than rare occurrences)</li>
  <li>Bolded text</li>
</ul>

<h2 id="phrases-id-never-use">Phrases I’d never use</h2>
<ul>
  <li>AI assistant</li>
  <li>pair programmer</li>
</ul>

        </div>
      ]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[Security is PvPvE]]></title>
      <description><![CDATA[Security work isn&#39;t just about outsmarting attackers. You&#39;re also fighting the computers themselves.]]></description>
      <pubDate>Mon, 17 Nov 2025 00:00:00 +0000</pubDate>
      <link>https://johnsaigle.com/posts/security-is-pvpve</link>
      <guid isPermaLink="true">https://johnsaigle.com/posts/security-is-pvpve</guid>
      <content:encoded><![CDATA[
        <div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; max-width: 680px;">
          <p>Like much of the gaming world lately, I’ve been playing a lot of Arc Raiders. The game is <br />
described as PvPvE: Player vs. Player vs. Environment. Players explore a shared <br />
post-apocalyptic landscape to scavenge rare materials. They can cooperate or attack <br />
each other, stealing whatever loot their opponents have collected. But patrolling these <br />
abandoned space ports and half-buried cities are killer robots, who attack everyone <br />
regardless of alliance or intent.</p>

<p>The field of computer security works the same way, but most people think of it as <br />
exclusively Player vs. Player: will the security engineer outsmart the hacker, or vice-versa?<br />
We define our philosophies as white hat or black hat and describe our activity as<br />
red or blue teaming.</p>

<p>But security work also has a PvE element. When securing systems, we struggle not just against <br />
agents with opposed intentions, but against the environment itself. The computers are trying to <br />
kill us. We don’t need to ascribe intent here; it’s observable from their behavior.</p>

<p>This makes sense when you realize that computer security is, for the most part, a subset of software quality. It shouldn’t be <br />
possible to hack software of the highest quality. (Social engineering and wrench attacks <br />
sidestep the software entirely, so they don’t count.)</p>

<p>Problems like denial-of-service, technical debt, or poorly-designed software are indistinguishable <br />
from “real” security bugs. Unreliable, calcified systems are extremely difficult to secure because <br />
it’s not always possible to test their behavior and prove their security properties.</p>

<p>The history of computing is full of these environmental failures. Early networks weren’t encrypted <br />
because they assumed trusted users on trusted infrastructure. Multi-user operating systems shipped <br />
without password protection because designers couldn’t imagine adversarial access. The environment <br />
changed, but the systems couldn’t adapt without heavy intervention.</p>

<p>The same pattern plays out in modern systems. Flashloan attacks in Solidity weren’t possible until <br />
DeFi protocols composed in ways the original developers never anticipated. Invariant-based design <br />
could have prevented this—not by predicting flashloans specifically, but by encoding constraints <br />
that hold regardless of the execution environment.</p>

<p>Why do these environmental failures keep happening? Because the environment actively fights you. <br />
Brittle code, legacy systems, poor abstractions, missing tests—a codebase with little in the way of test coverage <br />
or proactive assertions of invariants can’t be refactored safely. A system built on fragile assumptions breaks under novel inputs. A service with ten dependency layers fails in ways no one can predict. These aren’t just obstacles <br />
to security work. They <em>are</em> security problems.</p>

<p>Yet this isn’t how most people think about security, because environmental issues are less obvious <br />
and sexy than a major compromise. It also doesn’t fit well into the offensive security mindset oriented <br />
toward time-boxed evaluations: pentests, code audits, contests, and bug bounties. These <br />
problems are more like a disease that slowly spreads and only becomes visible when it’s <br />
already lethal.</p>

<p>The PvPvE frame lacks the narrative drama of hacker-versus-hacker showdowns, but it better <br />
explains the actual work. You’re not just fighting attackers. You’re fighting the computers themselves.</p>

<p>And if you ignore the environment, you’re already halfway to losing the game.</p>

        </div>
      ]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[Bernoulli Coding]]></title>
      <description><![CDATA[The middle ground in software development is dead. I now write code in just two modes: fortress-grade security or complete disposable chaos.]]></description>
      <pubDate>Tue, 02 Sep 2025 00:00:00 +0000</pubDate>
      <link>https://johnsaigle.com/posts/bernoulli-coding</link>
      <guid isPermaLink="true">https://johnsaigle.com/posts/bernoulli-coding</guid>
      <content:encoded><![CDATA[
        <div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; max-width: 680px;">
          <p>The middle ground in software development is dead.</p>

<p>After a decade of building systems, reviewing code, and hunting vulnerabilities, I’ve found myself writing software in exactly two modes. There’s the fortress: ultra-secure, thoroughly tested, peer-reviewed, and bulletproof. Then there’s the sketch: completely disposable, built for speed, meant to break.</p>

<p>This approach eliminates the exhausting middle tier where projects hover in development purgatory. Those half-robust side projects that might work, might scale, might be worth the CI setup and security audit. The ones that consume mental energy without clarity of purpose.</p>

<h2 id="the-two-extremes">The Two Extremes</h2>

<p><strong>Fortress Mode</strong> applies to anything handling sensitive data or critical workflows. These projects get the full treatment: linters cranked to maximum sensitivity, comprehensive test suites, security reviews, and deployment pipelines. Every dependency is audited. Every function is documented.</p>

<p>This rigor exists because most software frustrations stem from cutting corners. Web apps that constantly break, services that leak data, systems that crumble under load. When something matters, it deserves relentless attention.</p>

<p>This is how I code for work, or in any situation where someone is counting on me to do something properly.</p>

<p><strong>Sketch Mode</strong> covers scripts, proof-of-concepts, and experimental tools. Here, I deliberately turn off the security reviewer part of my brain. No Rust type system enforcement, no memory safety concerns, no linting. The goal is pure velocity: get the idea out of your head and into working form.</p>

<p>These tools exist to answer a simple question: is this concept worth pursuing? Most aren’t. They’re incomplete, abandoned after the initial burst of curiosity, or require significant rework to become truly useful. Starting with heavy tooling would kill the experimentation entirely.</p>

<p>This goes all the way down to language choice. I won’t vibe-code Rust, and I won’t try to write secure, reliable software<br />
in an interpreted language. Getting clear on the actual use of the software allows one to play to the respective<br />
strengths of the tools used to build it.</p>

<h2 id="the-excluded-middle">The Excluded Middle</h2>

<p>Even though both of the above approaches are extreme in their own ways, it’s actually the mid-tier projects that I find<br />
most exhausting to work with. Fortress-style should be lovely to read and should work every time. Sketch mode code<br />
should never, ever be read and is almost something close to an aesthetic, playful experience than to something like<br />
engineering.</p>

<p>Most of the software out there isn’t truly fortress-grade or unapologetically sketchy. Instead, it’s fragile code pretending to be robust—a proof-of-concept stretched far past its expiration date. Most software should be thought of as<br />
slop until proven otherwise, just because doing things right ends up being beyond the interest, ability, or resources<br />
of most people and organizations. Maybe all of the LLM-generated code will make more people realize and/or admit this.</p>

<h2 id="putting-out-slop">Putting Out Slop</h2>

<p>I treat the sketch projects like hazardous materials, clearly labeled from the start. The README gets a prominent warning: “This is vibe-coded software. Use at your own risk.” No security guarantees or reliability promises, just an idea I had that someone<br />
else might find interesting too.</p>

<p>More broadly, I’m sympathetic to the idea of <a href="https://www.youtube.com/watch?v=vagyIcmIGOQ&amp;t=20195">FOSS as a gift economy</a><br />
rather than as an open-ended service contract. If I was stuck in that mindset, I wouldn’t be able to make and share these<br />
projects knowing that they almost certainly have ridiculous problems as well as security issues. It’s not even so<br />
much that I’m genuinely worried that someone will get hacked because of something that I wrote, but more that<br />
it takes more mental energy to write code securely, and that’s not the point of these projects.</p>

<p>Keeping these two mindsets totally separate has felt deeply liberating. I hope others do the same, so that we can<br />
all be a little bit more clear about what we’re getting ourselves into when we run something off of GitHub.</p>

        </div>
      ]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[Slopsquatting detection added to Scary Strings]]></title>
      <description><![CDATA[Preventing AIs from installing sketchy packages]]></description>
      <pubDate>Sat, 30 Aug 2025 00:00:00 +0000</pubDate>
      <link>https://johnsaigle.com/posts/adding-slopsquatting-packages-to-scary-strings-detection</link>
      <guid isPermaLink="true">https://johnsaigle.com/posts/adding-slopsquatting-packages-to-scary-strings-detection</guid>
      <content:encoded><![CDATA[
        <div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; max-width: 680px;">
          <p>Vibe-coding sessions can go awry in many ways. One way is for the agent to offer to install a weird package. It will frequently pull an obscure GitHub repo over an official SDK. AI training data is biased toward whatever packages were popular or well-documented during training, not necessarily what’s safe or well-maintained.</p>

<p>This happened to me recently: I was trying to connect Go with OpenAI when the agent suggested installing an unofficial implementation over OpenAI’s own SDK. Nothing obviously wrong with that particular package, but it got me thinking about how this could be prevented. We’re automating ourselves toward new vulnerabilities. As we allow these tools to become more autonomous, we need defenses that work automatically too.</p>

<p>So I added a new <a href="https://github.com/johnsaigle/scary-strings/tree/main/wordlists/llm/slopsquat"><code>slopsquat</code> directory</a> to <a href="https://github.com/johnsaigle/scary-strings/">Scary Strings</a> that tracks risky packages.</p>

<p>My immediate motivation is to use it to track and prevent strange packages that AI tools suggest in the wild,<br />
though of course it is also simply a collection of weird libraries that go against sensible defaults.</p>

<p>Developers can then wire this list into their package managers (like <a href="https://golangci-lint.run/docs/linters/configuration/#depguard"><code>depguard</code> in Go</a>) to automatically reject these suggestions before they make it into production.</p>

<p>Using this list to block packages in CI protects against both human mistakes and AI agents running wild. Whether you’re a dev who is too busy to investigate every package, or an autonomous agent in full YOLO mode, the safety net catches both.</p>

        </div>
      ]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[Browser Use is Solving the Wrong Problem]]></title>
      <description><![CDATA[The browser has been the everything app for a long time, and much hype surrounds the idea of automating browser use with LLMs. But in the age of AI agents, ...]]></description>
      <pubDate>Tue, 26 Aug 2025 00:00:00 +0000</pubDate>
      <link>https://johnsaigle.com/posts/browser-use-is-solving-the-wrong-problem</link>
      <guid isPermaLink="true">https://johnsaigle.com/posts/browser-use-is-solving-the-wrong-problem</guid>
      <content:encoded><![CDATA[
        <div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; max-width: 680px;">
          <p><em>The browser has been the everything app for a long time, and much hype surrounds the idea of automating browser use with LLMs. But in the age of AI agents, the CLI + LLM combo could be the superior choice for getting anything done.</em></p>

<hr />

<p>Anthropic announced their new <a href="https://www.anthropic.com/news/claude-for-chrome">Piloting Claude for Chrome</a> feature today, unleashing Claude into the wild web where it will click, scroll, and type its way through the digital maze we’ve built around our simplest tasks.</p>

<p>Browser automation leaves me cold. Every browser tab feels like a small defeat, a task that I couldn’t solve in a simple, repeatable way. Where possible, I will always reach for the command-line first so that I can make the most out of my programming<br />
skills and the speed that comes with adroit keyboard navigation.</p>

<p>That said, that’s not most people’s experience, so the wider appeal of browser automation is obvious. And unfortunately<br />
there’s not a good CLI-based solution for most “real life”, non-coding domains: filing taxes, ordering groceries, booking appointments, managing subscriptions.</p>

<p>These tasks are trapped behind interfaces designed for human eyeballs, which makes their execution extremely inefficient.</p>

<p>Consider what we’re actually asking LLMs to do. Navigate to TurboTax, wait for the page to load, dismiss the promotional popup, click through the cookie consent dialog, find the right form field among dozens of others, enter data, wait for validation, handle inevitable JavaScript errors, and repeat this dance hundreds of times.</p>

<p>Then, of course, are the <a href="https://simonwillison.net/2025/Aug/26/piloting-claude-for-chrome/">massive security implications</a> of giving AI agents access to our browsers.</p>

<p>All this to say: we’re solving the wrong problem with the wrong tool.</p>

<p>Instead of teaching AIs to navigate human interfaces, what if we built machine-readable interfaces for the tasks that matter?</p>

<p>Text-based interfaces play to LLM strengths rather than their weaknesses. Instead of parsing visual layouts and guessing click targets, they could process structured data and generate precise commands. An AI that struggles to find the “Submit” button on a cluttered webpage could effortlessly compose complex command pipelines.</p>

<p>Consider the UNIX philosophy applied to daily life. <code>grocery-order --store=whole-foods --list=weekly.txt --delivery="Tuesday 6pm"</code>.<br />
Recurring deliveries could be done with a cronjob. Workflows could be shared with family and friends: along with recommending a recipe, you could share the script that places a delivery order for all of the ingredients. No stock photos of produce, no upselling widgets, no loading spinners. The same could be done for filing taxes, booking appointments, and so on.</p>

<p>This could do a lot to educate people about programming and help them to feel more empowered too. Instead of losing<br />
all of one’s know-how each time a GUI undergoes a rework, scripting knowledge could accumulate and compound. The whole<br />
consumer world could be converted into script-kiddies, and some of them would graduate into full-fledged hackers.</p>

<p>Given that innumerable person-hours and lines of code have been devoted to making the web functional<br />
for commerce, I don’t know if any of this will come to pass. It would mean a huge shift in how people think<br />
about using their computers. For companies it might be more difficult to deploy the dark arts of advertising and surveillance<br />
outside of the browser context, and this alone might ensure that the CLI-based future does not come to pass.</p>

<p>But as for sheer efficiency, composability, and ease-of-use, I think there’s an excellent argument to be made<br />
for shifting the paradigm.</p>

        </div>
      ]]></content:encoded>
    </item>
  </channel>
</rss>
