<?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: Yulo</title>
    <description>The latest articles on DEV Community by Yulo (yulo).</description>
    <link>https://dev.to/yulo</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%2Forganization%2Fprofile_image%2F14018%2F4bed3567-a5bb-4dc2-8848-f231e92b9b02.png</url>
      <title>DEV Community: Yulo</title>
      <link>https://dev.to/yulo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yulo"/>
    <language>en</language>
    <item>
      <title>Claude Code Skills for safe PHP and JS package updates</title>
      <dc:creator>Neo</dc:creator>
      <pubDate>Tue, 14 Jul 2026 15:28:18 +0000</pubDate>
      <link>https://dev.to/yulo/claude-code-skills-for-safe-php-and-js-package-updates-352a</link>
      <guid>https://dev.to/yulo/claude-code-skills-for-safe-php-and-js-package-updates-352a</guid>
      <description>&lt;p&gt;It's not abnormal for projects to go weeks, or dare I say months, between dependency updates. And when people finally do update, they do it in full force: everything at once, without checking anything. &lt;/p&gt;

&lt;p&gt;That habit has always carried risk, but in the new world of AI agents doing the updating, it collides head-on with a very real threat: supply chain attacks.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: &lt;code&gt;install&lt;/code&gt; is an arbitrary code execution feature
&lt;/h2&gt;

&lt;p&gt;The package ecosystems we all depend on have spent the last few years demonstrating exactly how bad this can get. In September 2025, &lt;a href="https://www.wiz.io/blog/widespread-npm-supply-chain-attack-breaking-down-impact-scope-across-debug-chalk" rel="noopener noreferrer"&gt;&lt;code&gt;chalk&lt;/code&gt; and &lt;code&gt;debug&lt;/code&gt;&lt;/a&gt;, part of a batch of eighteen packages with over two billion combined weekly downloads, started shipping a crypto-clipper after one maintainer's npm account was phished through a fake 2FA-reset email. &lt;/p&gt;

&lt;p&gt;Days later, the &lt;a href="https://unit42.paloaltonetworks.com/npm-supply-chain-attack/" rel="noopener noreferrer"&gt;Shai-Hulud worm&lt;/a&gt; chewed through hundreds of packages on its own: its post-install script stole npm tokens from every machine it landed on and used them to publish more infected versions of itself. And a couple of weeks before either, the &lt;a href="https://nx.dev/blog/s1ngularity-postmortem" rel="noopener noreferrer"&gt;Nx compromise&lt;/a&gt; put a post-install payload on developer machines that prompted locally installed AI coding CLIs like Claude and Gemini to hunt down wallets and credentials for exfiltration. That last one should make every agent owner sit up straight: our own agents, conscripted as burglars. The pattern is consistent: a malicious version goes live, does its damage for a few hours or days, then gets caught and pulled.&lt;/p&gt;

&lt;p&gt;Based on this, I decided, not to do updates till a set of rules have been met. These rules, I have decided to burn them into Claude skills and let my agents deal with them.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI Agent Skills: paranoia as a config file
&lt;/h2&gt;

&lt;p&gt;In Claude Code, a &lt;strong&gt;skill&lt;/strong&gt; is just a markdown file with instructions the agent loads when a task matches. This gives me way to encode my hard-won paranoia &lt;em&gt;once&lt;/em&gt; and have it applied &lt;em&gt;every single time&lt;/em&gt;, by something that never gets tired, never gets sloppy on a Friday afternoon, and never thinks "eh, it's probably fine."&lt;/p&gt;

&lt;p&gt;I wrote two of them, for now, &lt;code&gt;package-update-js&lt;/code&gt; and &lt;code&gt;package-update-php&lt;/code&gt; one for my TypeScript projects, one for my Laravel PHP ones. They differ in tooling, but they encode the same worldview. These are the rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rule 1: Research before touching anything
&lt;/h2&gt;

&lt;p&gt;The first phase isn't an update at all. The agent has to research supply chain incidents from the last six months: compromised packages, typosquatting campaigns, and suspicious maintainer handovers (the exact vector behind &lt;a href="https://blog.npmjs.org/post/180565383195/details-about-the-event-stream-incident" rel="noopener noreferrer"&gt;&lt;code&gt;event-stream&lt;/code&gt;&lt;/a&gt; in 2018, when a helpful stranger offered to take over maintenance and then quietly shipped a bitcoin-wallet thief to two million weekly downloads). It cross-references those against the project's actual dependency list, and runs the registry's own audit (&lt;code&gt;composer audit&lt;/code&gt;, the npm advisory data) on top. Every package gets a verdict: &lt;strong&gt;SAFE&lt;/strong&gt;, &lt;strong&gt;AFFECTED&lt;/strong&gt;, or &lt;strong&gt;INVESTIGATE&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Only after that report exists does anything get updated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rule 2: The seven-day quarantine
&lt;/h2&gt;

&lt;p&gt;This is the rule I'd tattoo on the ecosystem if I could: &lt;strong&gt;never install a version that's less than seven days old.&lt;/strong&gt; Some package managers, like pnpm and Bun, have added support for a minimum release age to make it easy to enforce.&lt;/p&gt;

&lt;p&gt;Compromised releases are almost always short-lived. The poisoned &lt;code&gt;chalk&lt;/code&gt; and &lt;code&gt;debug&lt;/code&gt; versions were live for about two hours before npm pulled them. The malicious Nx releases lasted four. The malicious version ships, someone notices, it gets yanked. A release age gate means the poisoned window passes you by entirely. Every incident named above would have sailed past me without ever touching a machine, not because I'm vigilant, but because the versions would have been too young to install.&lt;/p&gt;

&lt;p&gt;In my pnpm projects this is a single line in &lt;code&gt;pnpm-workspace.yaml&lt;/code&gt; (the value is in minutes, so 10080 is seven days):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;minimumReleaseAge&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10080&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since &lt;a href="https://pnpm.io/supply-chain-security" rel="noopener noreferrer"&gt;pnpm 11&lt;/a&gt; you even get a one-day quarantine by default, and Bun has the same idea as &lt;code&gt;minimumReleaseAge&lt;/code&gt; in &lt;code&gt;bunfig.toml&lt;/code&gt;. Composer has no equivalent, so the PHP skill enforces it manually: it reads the release date of every candidate version and anything younger than seven days gets skipped and flagged as "too fresh, revisit later."&lt;/p&gt;

&lt;p&gt;There's one exception, and it cuts the other way: a release that fixes a known CVE goes through immediately, quarantine or not. A vulnerability you're currently running is a bigger risk than a version that's three days old.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rule 3: Scripts don't run until I say so
&lt;/h2&gt;

&lt;p&gt;Every Composer update runs with &lt;code&gt;--no-scripts&lt;/code&gt;. Post-install scripts are the classic payload delivery mechanism that gives essentially full shell access, triggered automatically, on your machine. It's how Nx harvested SSH keys and wallets, and how Shai-Hulud found the npm tokens it used to spread. &lt;/p&gt;

&lt;p&gt;The skill installs the package, lets verification happen, and only regenerates autoload and runs scripts after the update has been looked at.&lt;/p&gt;

&lt;p&gt;The JS side is ahead of Composer here. pnpm refuses to run dependency build scripts unless you explicitly allowlist them, so my &lt;code&gt;pnpm-workspace.yaml&lt;/code&gt; names the only two packages allowed to execute anything at install time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;allowBuilds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;esbuild&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;sharp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything else gets installed but never runs. The same file sets &lt;code&gt;blockExoticSubdeps: true&lt;/code&gt;, which stops transitive dependencies from sneaking in from random git repos or tarball URLs instead of the registry. Bun takes a similar stance and doesn't run lifecycle scripts for arbitrary dependencies by default.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rule 4: Pin everything, exactly
&lt;/h2&gt;

&lt;p&gt;No &lt;code&gt;^&lt;/code&gt;, no &lt;code&gt;~&lt;/code&gt;, no ranges. Every dependency is pinned to an exact version, and updates happen through explicit &lt;code&gt;composer require package:1.2.3&lt;/code&gt; or &lt;code&gt;pnpm add package@1.2.3&lt;/code&gt;, never through a blanket update command. My &lt;code&gt;pnpm-workspace.yaml&lt;/code&gt; backs this up with &lt;code&gt;savePrefix: ""&lt;/code&gt;, which makes pnpm save exact versions instead of &lt;code&gt;^&lt;/code&gt; ranges by default. This does two things: it makes every version change visible in the diff (nothing drifts silently on the next lockfile regeneration), and it means the agent can never "accidentally" pull in something I didn't sign off on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rule 5: One at a time, verify after each
&lt;/h2&gt;

&lt;p&gt;The skills forbid updating everything at once. Each package — or ecosystem group, since things like the Laravel core packages or the TanStack family have to move together — gets updated individually, followed immediately by verification: type checks for TypeScript, PHPStan plus the full test suite for PHP. If something breaks, we know exactly which update did it. Major versions are never applied without presenting the breaking changes and getting an explicit yes from me.&lt;/p&gt;

&lt;h2&gt;
  
  
  The unreasonable effectiveness of writing rules down
&lt;/h2&gt;

&lt;p&gt;Here's the thing that surprised me: the agent following these skills does updates &lt;em&gt;more&lt;/em&gt; safely than I ever did by hand. I skipped changelogs when I was tired. I never once checked a package's release date before installing it. I definitely ran &lt;code&gt;composer update&lt;/code&gt; with scripts enabled my entire career.&lt;/p&gt;

&lt;p&gt;The skill doesn't skip steps, because skipping steps isn't in the file. My updates happen every week now, and now the checks for attacks are delegated.&lt;/p&gt;

&lt;p&gt;You can check out the &lt;a href="https://github.com/neoighodaro/claude-skills" rel="noopener noreferrer"&gt;skills&lt;/a&gt; here.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>security</category>
    </item>
  </channel>
</rss>
