<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Claude-Code on PHP Boy Scout</title><link>https://blog-570662.gitlab.io/tags/claude-code/</link><description>Recent content in Claude-Code on PHP Boy Scout</description><generator>Hugo -- gohugo.io</generator><language>en-gb</language><copyright>Matt Cockayne</copyright><lastBuildDate>Fri, 19 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://blog-570662.gitlab.io/tags/claude-code/index.xml" rel="self" type="application/rss+xml"/><item><title>The interpreter we forgot to sandbox</title><link>https://blog-570662.gitlab.io/the-interpreter-we-forgot-to-sandbox/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://blog-570662.gitlab.io/the-interpreter-we-forgot-to-sandbox/</guid><description>&lt;img src="https://blog-570662.gitlab.io/the-interpreter-we-forgot-to-sandbox/cover-the-interpreter-we-forgot-to-sandbox.png" alt="Featured image of post The interpreter we forgot to sandbox" /&gt;&lt;p&gt;I write a &lt;code&gt;CLAUDE.md&lt;/code&gt; for every project I work on, and a small pile of other markdown
files besides. They&amp;rsquo;re how I keep an AI agent on the rails: what the project is, what
the conventions are, what it must never do. I lean on them heavily, I change them constantly,
and&amp;hellip; here&amp;rsquo;s the uncomfortable bit&amp;hellip; I don&amp;rsquo;t always give a change to one the same hard
look I&amp;rsquo;d give a change to the code. They look like notes. They feel like docs.&lt;/p&gt;
&lt;p&gt;Somebody worked out that they&amp;rsquo;re not.&lt;/p&gt;
&lt;p&gt;In May, a supply-chain campaign researchers named
&lt;a class="link" href="https://thehackernews.com/2026/05/trapdoor-supply-chain-attack-spreads.html" target="_blank" rel="noopener"
 &gt;TrapDoor&lt;/a&gt;
pushed 384 malicious versions of 34 packages across npm, PyPI and Crates.io. The bytes
did the usual nasty things, hunting out SSH keys, AWS credentials, GitHub tokens and
crypto wallets. The new trick was where it hid the &lt;em&gt;instructions&lt;/em&gt;. The packages shipped
poisoned &lt;code&gt;.cursorrules&lt;/code&gt; and &lt;code&gt;CLAUDE.md&lt;/code&gt; files, and the attackers also opened pull
requests against real projects, LangChain, LangFlow, LlamaIndex, MetaGPT and OpenHands,
under titles as innocent as &amp;ldquo;docs: add .cursorrules with dev standards and build
verification&amp;rdquo;. The payload was a plain-English instruction telling your AI assistant to
run a helpful-sounding &amp;ldquo;security scan&amp;rdquo; that quietly shipped your secrets to a stranger.
And it was written into the file in zero-width Unicode, characters that render as
nothing, so you wouldn&amp;rsquo;t see it even if you looked. Which, on a file marked &amp;ldquo;docs&amp;rdquo;, you
probably didn&amp;rsquo;t.&lt;/p&gt;
&lt;h2 id="not-a-new-attack-a-new-doorway"&gt;Not a new attack, a new doorway
&lt;/h2&gt;&lt;p&gt;I want to be careful not to oversell this, because the loud version, &amp;ldquo;a terrifying new
class of AI threat&amp;rdquo;, isn&amp;rsquo;t true. It&amp;rsquo;s a supply-chain attack, the same shape we&amp;rsquo;ve had for
years on npm and PyPI: social engineering, plus a victim who didn&amp;rsquo;t quite do enough due
diligence. I wrote a while back that
&lt;a class="link" href="https://blog-570662.gitlab.io/nobody-is-coming-to-clean-your-supply-chain/" &gt;nobody is coming to clean your supply chain&lt;/a&gt;,
and nothing about TrapDoor changes that. The package is still the package.&lt;/p&gt;
&lt;p&gt;What&amp;rsquo;s different, and worth the words, is &lt;em&gt;where&lt;/em&gt; it goes off. A classic supply-chain
payload waits for CI, or for production. This one detonates the moment you open the
repository in your editor, on the one machine in the whole chain that nobody audits: your
laptop.&lt;/p&gt;
&lt;p&gt;Think about what sits on a developer&amp;rsquo;s machine. Tokens in environment variables. Cloud
credentials. An SSH agent holding the keys to your git forge. A logged-in CLI for your
package registry. And now an AI agent running with all of it, at your full permissions,
and almost none of the guard-rails a CI runner gets. It&amp;rsquo;s the least sandboxed, most
credentialed box you own, and we&amp;rsquo;ve just pointed an interpreter at it that will read and
act on a file an attacker can write. Pop that one machine and you haven&amp;rsquo;t popped a machine,
you&amp;rsquo;ve been handed the whole keyring and left alone in the building.&lt;/p&gt;
&lt;h2 id="markdown-is-a-programming-language-now"&gt;Markdown is a programming language now
&lt;/h2&gt;&lt;p&gt;Here&amp;rsquo;s the framing I keep coming back to, and I can&amp;rsquo;t unsee it now. A &lt;code&gt;CLAUDE.md&lt;/code&gt; is to an AI agent exactly what a
&lt;code&gt;.py&lt;/code&gt; is to Python, a &lt;code&gt;.js&lt;/code&gt; to Node, a &lt;code&gt;.rb&lt;/code&gt; to Ruby. It is source code. The agent is the
interpreter. You hand it a file of instructions and it executes them.&lt;/p&gt;
&lt;p&gt;And I don&amp;rsquo;t say that as a complaint. That an agent will read a paragraph of plain English
and just &lt;em&gt;do&lt;/em&gt; it, no compiler, no ceremony, no forty lines of glue, is one of the more
remarkable things to happen to this craft in my working life, and I lean on it every day.
The catch is that the very thing that makes it marvellous, that it does what the
instructions tell it, is the thing that makes a poisoned instruction file so dangerous.
The power and the exposure are the same property.&lt;/p&gt;
&lt;p&gt;The only real difference is that the language interpreters have spent decades growing
rules to protect you: scopes, permissions, sandboxes, a standard library that asks before
it does anything irreversible. The AI interpreter has almost none of that. It reads your
prose and does what the prose says, with whatever access you happen to have, and the prose
can come from anywhere. We&amp;rsquo;ve quietly built the most powerful interpreter in the stack,
given it the fewest rules, and filed its source code under &amp;ldquo;documentation&amp;rdquo;.&lt;/p&gt;
&lt;h2 id="you-cant-just-read-it-more-carefully"&gt;You can&amp;rsquo;t just read it more carefully
&lt;/h2&gt;&lt;p&gt;The obvious answer is &amp;ldquo;review the file like code&amp;rdquo;, and it&amp;rsquo;s right, but TrapDoor is the
reason it isn&amp;rsquo;t enough on its own. The instructions were written in zero-width Unicode.
You can open the diff, read every visible word, approve it in good conscience, and merge
something you were never able to see. &amp;ldquo;Docs: add dev standards&amp;rdquo; is precisely the pull
request you nod through on a Friday afternoon.&lt;/p&gt;
&lt;p&gt;So reading carefully is necessary and insufficient. You also need tooling that treats
these files as executable: that flags invisible characters, diffs them as code, and
refuses to let an agent act on a changed instruction file until a human has actually
cleared it. I run a crude version of this already. In CI, if one of my prompt or rules
files changes, no AI step is allowed to run until I&amp;rsquo;ve reviewed it by hand. It isn&amp;rsquo;t
clever, but it closes the worst of the gap. Locally it&amp;rsquo;s much harder, and right now my
real defence is that I&amp;rsquo;m the only contributor to most of my projects, so the audit is
just me, usually noticing after the horse has bolted.&lt;/p&gt;
&lt;h2 id="signing-wont-save-you-here"&gt;Signing won&amp;rsquo;t save you here
&lt;/h2&gt;&lt;p&gt;This is the part that stings, because I&amp;rsquo;ve spent a good chunk of this year
&lt;a class="link" href="https://blog-570662.gitlab.io/sign-your-own-binaries-with-go-tool-base/" &gt;building signing and provenance into my tools&lt;/a&gt;.
A signature proves &lt;em&gt;who&lt;/em&gt; published something. It says nothing about &lt;em&gt;whether it&amp;rsquo;s safe&lt;/em&gt;.
That was already true for poisoned-but-signed packages, and it lands twice as hard here:
you can sign a release flawlessly, with a key the platform can&amp;rsquo;t forge, and still ship a
&lt;code&gt;CLAUDE.md&lt;/code&gt; inside it that tells the reader&amp;rsquo;s agent to rob them. A merged pull request is
&amp;ldquo;signed&amp;rdquo; by the very act of merging, with perfect provenance, and the instruction in it
is still hostile. Provenance is necessary. It was never sufficient, and it&amp;rsquo;s no defence at
all against a payload made of sentences. A signature is only ever as good as the trust you
place in the publisher.&lt;/p&gt;
&lt;h2 id="so-whose-job-is-it"&gt;So whose job is it?
&lt;/h2&gt;&lt;p&gt;Primarily, still ours. I said it in the supply-chain piece and I&amp;rsquo;ll stand on it: the
responsibility sits with the developer doing the consuming, to pin, to read, to gate, to
not run a stranger&amp;rsquo;s instructions with the keys to the kingdom in their pocket. And that
gets harder, not easier, as we start consuming each other&amp;rsquo;s agent setups wholesale. The
Claude skills marketplace and the things like it turn &amp;ldquo;borrow someone&amp;rsquo;s &lt;code&gt;CLAUDE.md&lt;/code&gt;&amp;rdquo; into
a one-click habit, and every one of those is unreviewed code from a stranger. Each skill
needs vetting like the dependency it is.&lt;/p&gt;
&lt;p&gt;But it isn&amp;rsquo;t &lt;em&gt;only&lt;/em&gt; on us, and TrapDoor is the argument for better tooling. We have CVE
databases, scanners and scorecards for packages, for all
&lt;a class="link" href="https://blog-570662.gitlab.io/anything-under-an-8/" &gt;their flaws&lt;/a&gt;. We have nothing
equivalent for an instruction file: no scoring, no advisory feed, no scanner that knows
what a poisoned &lt;code&gt;CLAUDE.md&lt;/code&gt; looks like. That&amp;rsquo;s a gap the ecosystem has to close, and it
will, eventually. The catch is that the agent vendors will be slow about it. Sandboxing a
feature people love precisely because it gets out of your way is a hard, unpopular,
multi-quarter job, and I wouldn&amp;rsquo;t hold my breath.&lt;/p&gt;
&lt;h2 id="the-most-dangerous-machine-is-the-one-on-your-desk"&gt;The most dangerous machine is the one on your desk
&lt;/h2&gt;&lt;p&gt;Which is why I&amp;rsquo;m not waiting for them&amp;hellip; and nor should you.&lt;/p&gt;
&lt;p&gt;The most dangerous machine in your supply chain isn&amp;rsquo;t a build server or a registry. It&amp;rsquo;s
the laptop you&amp;rsquo;re reading this on, and we&amp;rsquo;ve handed an AI the keys to it. The good news is
that nearly everything you can do about that, you can do today, with nobody shipping you a
feature first. Treat your &lt;code&gt;CLAUDE.md&lt;/code&gt; and your rules files as source code, because they
are: diff them, scan them for what you can&amp;rsquo;t see, and gate any agent run on a human
clearing the change. Get your secrets out of plaintext environment variables and into
something an opportunistic script can&amp;rsquo;t just read, which is exactly why go-tool-base
&lt;a class="link" href="https://blog-570662.gitlab.io/where-should-a-cli-keep-your-api-keys/" &gt;keeps its credentials in the OS keychain&lt;/a&gt;.
And vet a borrowed skill or rules file the way you&amp;rsquo;d vet any dependency, because that&amp;rsquo;s
what it is.&lt;/p&gt;
&lt;p&gt;None of that is new advice. It&amp;rsquo;s the same diligence the supply chain has always demanded.
We just have to extend it to a file we&amp;rsquo;d decided was only documentation, running on an
interpreter we forgot to sandbox.&lt;/p&gt;</description></item></channel></rss>