After compromising Lightning on PyPi earlier today, the same attackers compromised the intercom/intercom-php package version 5.0.2 on Packagist by overwriting the existing version with malicious code that converts it into a Composer plugin. The malicious plugin executes during package installation, downloading Bun JavaScript runtime and running an obfuscated credential-stealing payload. This represents an expansion of the Mini Shai-Hulud campaign from npm to the PHP ecosystem, using Composer's plugin system for install-time execution. Using the same payload from the npm campaign it steals various credentials including GitHub tokens, SSH keys, cloud provider credentials, and environment variables before encrypting and exfiltrating the data.
This comes after the npm package intercom-client, the official Node.js SDK for Intercom's API, was compromised earlier today. That package runs setup.mjs via a preinstall hook, meaning any machine that installed the malicious 7.0.4 was exposed before the package was ever imported or used. Intercom-client and intercom-php see roughly 400,000 weekly downloads and is commonly installed in backend services, developer environments, and CI/CD pipelines, making it a high-value target for credential theft.
For Semgrep Customers
Semgrep has an advisory and rule you can find to check your projects.
Trigger a new scan if you haven't recently on your projects.
Check the advisories page to see if any projects have installed these package versions recently: https://semgrep.dev/orgs/-/advisories
Check your dependency filter for matches. If you see “No matching dependencies” you are not actively using the malicious dependency in any of your projects. If you did match, additional advice on remediation and indicators of compromise are below.
If you matched: Audit your repositories for the injected files listed in the IOCs below.
How PHP Attacks Work Differently Compared to NPM and PyPI
PHP's package ecosystem runs on two pieces: Composer (the dependency manager) and Packagist (packagist.org), the default registry. The architecture is worth understanding since it shapes the attack surface in ways that differ from npm or PyPI.
Packagist isn't a file store, authors don't upload tarballs; they register a VCS repository URL, and Packagist crawls their composer.json, indexes tags, and serves that metadata to anyone running composer require. The actual code stays in GitHub, GitLab, or Bitbucket. Publishing is: submit a repo URL, authenticate via GitHub OAuth, add a webhook. Tag a release, webhook fires, Packagist re-indexes.
Given there is no pre-publish quarantine gate that becomes the attack surface.
Malware scanning pipelines have been introduced for PyPI or npm after many high profile incidents. If an attacker compromises a maintainer's GitHub account and pushes a malicious tag, the webhook fires within minutes and Packagist serves the new version to anyone running composer update. Transitive exposure compounds it: if a popular library takes a dependency on a compromised package, every downstream project picks it up without ever having explicitly required it. Replication isn't a feature of the attack and it's a feature of how Composer resolves dependencies.
Semgrep Supply Chain addresses this at two layers. For known vulnerabilities, it ingests security advisories and processes updates regularly, so when an incident surfaces a CVE, new rules get generated. PHP is fully supported with reachability analysis covering critical and high severity CVEs back to 2017, which means Semgrep doesn't stop at the version check. It checks whether your code actually calls the vulnerable function. Findings come back as reachable, unreachable, or conditionally reachable.
Malicious incidents like this one are a little bit different and require more proactive detection and rule creation in response to urgency.
For transitive dependencies, Semgrep scans them for known vulnerable versions across all supported languages but doesn't run code-level reachability analysis through the full dependency chain. The exception: if a vulnerability is exploitable through mere inclusion — no function call required — Semgrep flags it as reachable regardless of whether the dependency is direct or transitive. One PHP-specific gap worth knowing: lockfile-free scanning isn't supported, so composer.lock needs to be present and committed.
Indicators of Compromise
As this is a continuation of the Mini Shai-Hulud attack from today and earlier this week, you should refer to our previous blog posts for full analysis of the IoCs, however the key IoCs for this package are listed below.
Packages
- intercom/intercom-php@5.0.2
Domains / C2 Servers
- zero.masscan.cloud
Files / System Artifacts
- setup-intercom.sh
- router_runtime.js
- src/composerPlugin.php
- /tmp/tmp.987654321.lock
- .claude/router_runtime.js
- .claude/setup.mjs
- .claude/settings.json
- .vscode/setup.mjs
- .vscode/tasks.json
- results/results-*.json
- package-updated.tgz