Dev.to AI πŸ€– Ai πŸ‘ 0 πŸ“– 6 min read

PHP2Shell to Node2Shell: Five Supply Chain Security Controls

The internet is on fire. πŸ”₯ PHP2Shell, Node2Shell, LiteLLM, Shai-Hulud. October (or should I call it Hacktober?) has been generous with incident names. So, let's celebrate the supply chain security awareness month. The

PHP2Shell to Node2Shell: Five Supply Chain Security Controls

The internet is on fire. πŸ”₯ PHP2Shell, Node2Shell, LiteLLM, Shai-Hulud. October (or should I call it Hacktober?) has been generous with incident names. So, let's celebrate the supply chain security awareness month.

RothΓΌtle drinking coffee in a burning server room: This Is Fine

These incidents showed that CVEs are around the corner and language-independent. We need to prepare ourselves for the next Java2Shell or Rust4Shell.

What the Hell Happened?

On October 21, PHP2Shell brought two critical PHP runtime vulnerabilities: CVE-2026-109618, involving out-of-memory corruption, and CVE-2026-109619, involving stale state between requests. Both can lead to remote code execution (RCE).

The same day's Node.js fixes included Node2Shell, the informal name used for the Brotli decoder issue, CVE-2026-111497. Crafted compressed input can trigger an out-of-bounds write and potentially remote code execution when decoded. Node.js rates it High. Exposure depends on whether an application processes that input through the affected decoder; merely running a Node.js server doesn't establish exploitability.

Meanwhile, LiteLLM's release pipeline was compromised. Its maintainers confirmed that litellm==1.107.0 wheels and official container images shipped credential-stealing malware and a remote-code loader. Malicious Python startup files could execute without an application importing LiteLLM. The legitimate source tag didn't contain those files.

Earlier, on October 8, Shai-Hulud reached [email protected]. Tensorlake's advisory describes a compromised maintainer account, a malicious preinstall hook, credential theft, and propagation through stolen tokens. The poisoned release went through the project's own workflow and carried valid npm provenance.

Two runtime flaws. Two malicious releases.

Five Controls, Applied Where They Matter

So, what should we do for the upcoming CVEs and worms?

My Security Immediate Plan (SIP) follows the path from local agents to dependencies, container builds, attestations, and release checks. It's a supply-chain security starter pack. Let's check it out.

1. Isolate Agents, Installs, and Builds

Run local AI agents in an isolated environment. For the juggernaut AI agents, I want a separate kernel and explicit controls over what they can reach. Docker Sandboxes provides that microVM boundary:

sbx run --name secure-agent claude

Sandbox Kits package the tools together with their network and credential requirements. In my Sandbox Kits tutorial, I also explain "Authority as Code". It basically means that you can define what the agent can do in the sandbox, and what it cannot, through written text:

capabilities:
  - type: com.docker.sandbox/network-policy@2
    config:
      runtime:
        allow:
          - github.com
          - hosts: [api.github.com]
            methods: [GET, HEAD, POST, PATCH, PUT, DELETE]
          - hosts: [uploads.github.com]
            methods: [POST, PUT]
        deny:
          - hosts: [api.github.com]
            methods: [DELETE]
            paths: [/repos/**]

Would this have kept you safe from LiteLLM?

  • Host credentials: The sandbox, by default, only mounts the project files. So, the host credentials were out of reach. Also, sbx keeps tokens out of the AI agent's reach and injects them into the outbound requests.
  • Exfiltration and remote loading: Even after the tokens are exfiltrated, the malicious application needs to drop them somewhere. A policy denying the attacker's destinations would block those connections.
  • Workspace and application secrets: Mounted .env files, credentials supplied directly, and accessible databases would remain exposed. Writable project files could also be poisoned for later execution on the host. Review changes before using them outside the sandbox.

2. Be Cool for Five Days

Xygeni's npm worm playbook mentions SIP and the 5-day cooldown as an effective defense against the Shai-Hulud worms. That still holds true.

On npm versions supporting min-release-age, put a five-day cooldown in the project's .npmrc:

min-release-age=5
ignore-scripts=true

If you need to exclude a package from the cooldown, e.g., in the case of a zero-day attack, you can exclude it from the min-release-age settings:

min-release-age-exclude=handlebars

Commit and review the lockfile, then use npm ci --ignore-scripts in CI. The flag --ignore-scripts protects you from lifecycle scripts running before or after the installation of packages. Most of the Shai-Hulud worms use these scripts to infest your machine.

Note. Not all worms use lifecycle scripts. One example is Phantom Gyp. Read The Shai-Hulud Family for more details.

Xygeni's Malware Early Warning monitors public registries for suspicious releases and notifies affected users; its CI/CD guardrails can block flagged components. That gives dependency review another signal alongside version pinning and cooldowns.

And yet again, if you run your development environment in a sandbox, the blast radius is limited.

3. Harden the Image. Patch the Runtime.

Use multi-stage builds to leave build tools out of production, and a minimal, non-root runtime image. Restrict capabilities, filesystem writes, and outbound traffic to what the service needs:

docker run --rm \
  --read-only \
  --tmpfs /tmp \
  --cap-drop ALL \
  --security-opt no-new-privileges:true \
  my-node-app:patched

The Docker Hardened Images (DHI, catalog at dhi.io) are a set of open-source secure-by-default base images that come in two shapes:

  • Prod images: They are non-root, with no package manager, and shellless. E.g., dhi.io/php:8.5.12.
  • Dev images: They are designed to be used in earlier stages, when you need a package manager and root access. E.g., dhi.io/php:8.5.12-dev.

The DHI also have near-zero CVEs by default. Near-zero CVEs don't mean near-zero zero-days.

And no shell doesn't mean no RCE. Compromised code still has the process's access to application data, credentials, and networking. Removing /bin/sh is useful. It is not an exorcism.

If something happens, you still need to patch the versions. It just reduces the damage.

So, if you have not patched them yet, do it now:

Incident Runtime branch Patched version Official image DHI runtime image
PHP2Shell PHP 8.2 8.2.35 php:8.2.35-fpm dhi.io/php:8.2.35-fpm
PHP2Shell PHP 8.3 8.3.36 php:8.3.36-fpm dhi.io/php:8.3.36-fpm
PHP2Shell PHP 8.4 8.4.27 php:8.4.27-fpm dhi.io/php:8.4.27-fpm
PHP2Shell PHP 8.5 8.5.12 php:8.5.12-fpm dhi.io/php:8.5.12-fpm
Node2Shell Node.js 22 22.23.4 node:22.23.4 dhi.io/node:22.23.4
Node2Shell Node.js 24 24.22.1 node:24.22.1 dhi.io/node:24.22.1
Node2Shell Node.js 26 26.12.1 node:26.12.1 dhi.io/node:26.12.1

4. Record What You Built, Then Check the Evidence

Generate SBOM and provenance attestations:

docker buildx build \
  --sbom=true \
  --provenance=mode=max \
  --push \
  -t registry.example.com/my-app:1.0 .

The SBOM attestations can help you answer, "Am I affected?"

In the case of PHP2Shell:

docker scout sbom my-php-app:latest --format list | grep -i php

If you have an SBOM attestation, Docker Scout picks it up and doesn't need to scan through your whole image.
And SBOM attestations are text files at the end of the day, so you can check them with your tool of choice, even jq!

5. Make Release Checks Enforceable

Scan the SBOM attached to the exact release image digest and make policy violations fail CI. SIP starts with blocking fixable Critical CVEs, but Node2Shell shows why that threshold alone is insufficient: Node.js rated the flaw High. So, better block High as well.

Conclusion

SIP step How it could have helped What it wouldn't do
1. Sandbox Contain LiteLLM and Tensorlake execution; protect unshared host credentials and block connections to denied destinations. Protect shared secrets or prevent abuse of permitted API access.
2. Freeze dependencies Delay the poisoned Tensorlake release; block its preinstall hook with --ignore-scripts. Stop LiteLLM's Python startup hooks with npm settings.
3. Harden runtimes Limit privileges, tools, writes, and egress after PHP2Shell or Node2Shell exploitation. Fix either runtime vulnerability. Patching is still required.
4. Generate attestations Locate affected components through SBOMs; check LiteLLM's artifact origins against the expected source and builder. Certify clean code: Tensorlake's malicious release had valid provenance.
5. Enforce release checks Block affected PHP and Node.js images once vulnerability data is available, including High severity; reject known malicious releases through malware checks or deny rules. Detect every malicious package through CVE scanning alone.

Limit the damage. Patch the bugs. SIP before you ship. β˜•πŸ³

πŸ“° Read the original article on Dev.to AI

Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes β€” full credit and traffic to the original publisher.