On 17 September 2026, Socket reported that the PolinRider campaign had compromised the GitHub organisation behind visanduma/nova-two-factor, a Laravel Nova two-factor authentication package with more than 700,000 installs from Packagist. Malicious code was force-pushed into the Git history through a compromised maintainer account, and because Packagist resolves dev-* versions directly from Git branches, anyone installing a development branch got the payload without the attacker ever touching a Packagist credential.
What's affected
- Package:
visanduma/nova-two-factor. Packagist reports 708,282 total installs. - Malicious versions, per Socket:
dev-main,dev-nova4support,dev-using-inertiaanddev-nova5. All are unstable branch versions. - Stable releases: Socket found no malicious stable release. Packagist's latest stable version is
3.0.1, released 28 January 2025, before the compromise Socket dates to mid-June 2026. - Account: changes came through the GitHub account
lahirulhr. Socket says hundreds of contributions to private repositories followed the account compromise, so the full reach inside the Visanduma organisation is unknown.
Socket describes PolinRider as a persistent campaign linked to North Korea that spreads mainly through compromised developer accounts and Git repositories, with npm, PyPI, Go modules, Packagist and Chrome extensions as secondary channels. In this case the techniques were:
- Force-pushing rewritten history so the malicious change is harder to spot in review.
- Hiding JavaScript in configuration files such as
tailwind.config.jsand in files disguised as.woff2fonts. - Running it on build or test through patched config files, and on folder open in VS Code-compatible editors through a
.vscode/tasks.jsontask with"runOn": "folderOpen". - A PHP variant that adds obfuscated JavaScript to
index.phpand runs it withshell_exec.
The first stage looks up its command server through a blockchain dead drop, then downloads infostealers. Socket notes that cleaning a repository does not clean the workstations and CI systems that already ran it.
How to check if you're exposed
Look for any Composer constraint that resolves to a branch of this package:
grep -n 'visanduma/nova-two-factor' composer.json
grep -n -A3 '"name": "visanduma/nova-two-factor"' composer.lock | grep -E '"version"'
A locked version starting with dev- is exposed. A stable 3.0.1 or older tag is not affected by the reported branches.
Then check working copies and build hosts for the auto-run and hidden-payload patterns Socket describes:
grep -rn --include=tasks.json '"runOn": *"folderOpen"' . 2>/dev/null
# list .woff2 files that are not actually fonts
find . -name '*.woff2' -exec file {} + | grep -vi 'Web Open Font'
grep -rn 'shell_exec' --include=index.php . 2>/dev/null
In network logs, search for the command servers Socket lists: 193.247.144.38, 166.88.73.46, 166.88.134.62 and 23.27.13.135.
What to do now
- Stop installing branches in production builds. Pin
visanduma/nova-two-factorto a reviewed stable tag, or to a commit you have read, and set"minimum-stability": "stable"unless you have a specific reason not to. - Treat any machine that installed a
dev-*version, or opened an affected repository in VS Code, as compromised. Isolate it, rebuild where integrity can't be established, and rotate GitHub tokens, registry credentials, cloud keys, SSH keys and wallet secrets. - Protect your own branches against force-push. Enable branch protection or rulesets that block force-pushes and require review, including for changes to
.vscode/and build config files. - Turn off automatic task execution for untrusted folders. Workspace Trust in VS Code exists for this reason. Keep it on and don't trust folders by default.
- Review account activity for anyone with write access to repositories you depend on as source: new tokens, sessions, collaborators and unexpected force-pushes.
Why it matters
Packagist and Go modules both serve code straight from Git. That's convenient, but it means a compromised GitHub account is enough to poison the package. No registry token, no publish step, no new release to raise an alert. The usual signal people watch, a new version on the registry, never happens.
The defence is to make installs depend on something immutable and reviewed. A stable tag is better than a branch. A commit hash you've read is better than a tag. And a lock file only helps if the version inside it isn't dev-main resolving to whatever the branch says today.
The editor angle matters too. A .vscode/tasks.json with runOn: folderOpen turns "I cloned the repo to look at it" into code execution. Treat editor config in a dependency or a pull request as code.
Try it yourself
In a PHP project, list every package your lock file resolves to a branch rather than a tag:
php -r '$l=json_decode(file_get_contents("composer.lock"),true); foreach(array_merge($l["packages"],$l["packages-dev"]??[]) as $p){ if(str_starts_with($p["version"],"dev-")) echo $p["name"]," ",$p["version"]," ",$p["source"]["reference"]??"","\n"; }'
Each line is a dependency whose content can change without a release. Decide for each whether you can move it to a tag.
Click through it at your own pace, or open it full screen.
Quick check
Why didn't the attacker need Packagist credentials?
Packagist serves dev-* versions directly from the Git repository's branches, so pushing to the branch was enough.
Is visanduma/nova-two-factor 3.0.1 affected?
Socket found no malicious stable release. 3.0.1 was released in January 2025, before the compromise. The affected versions are the dev-main, dev-nova4support, dev-using-inertia and dev-nova5 branches.
How could merely opening the repository run the payload?
A .vscode/tasks.json task configured to run on folder open executes when a VS Code-compatible editor opens and trusts the folder.