On October 30, 2024, three versions of @lottiefiles/lottie-player (2.0.5, 2.0.6, 2.0.7) were published to npm with injected code that prompted users to connect a crypto wallet, then drained it. The package is pulled roughly 4 million times per month and is embedded in a huge number of marketing sites, dashboards, and landing pages. Because the Lottie player is typically loaded via a CDN <script> tag or bundled into frontends, the malicious versions reached end-user browsers within minutes of publication.
Unlike a typosquat, this attack targeted a trusted, heavily used package using a stolen publish token. The interesting question is how the detection and response worked, and what it tells us about frontend supply chain assumptions.
How did the attacker get publish rights?
A developer-associated npm access token was phished and used to publish new versions under legitimate package ownership.
LottieFiles disclosed that the attacker obtained an npm token belonging to a contributor and used it to run npm publish against the existing package. The token had publish privileges on the @lottiefiles organization scope. Two-factor authentication was configured, but the token itself had a scope that allowed automated publishing without interactive 2FA, which is a common tradeoff for CI-driven release pipelines.
The attacker published three versions in rapid succession, likely to test which one would propagate through CDN caches fastest and to hedge against early takedowns. Each version contained the same injected payload wrapped around the legitimate minified bundle.
What did the payload do in the browser?
The payload waited for page load, injected a wallet-connect modal styled to look like a legitimate UI element, and drained whatever wallet the user approved.
The injected code hooked into the Lottie player's initialization path so it ran early in the page lifecycle. It then called out to an attacker-controlled script host and loaded a drainer script. The drainer used window.ethereum to request wallet connection, then prompted the user to sign transactions that transferred ERC-20 and native tokens to attacker addresses. The UI mimicked a generic "connect wallet to view content" modal, which is common enough on Web3 sites that users clicked through without suspicion.
Public post-incident analysis reported a relatively small number of successful drains (a few dozen wallets, totaling well under $100K), which is lower than typical for this attack class. The likely reason is that most sites embedding Lottie Player are not Web3 sites, so users without a wallet plugin saw no prompt and the payload failed silently.
Why did the CDN make this worse?
Most sites load Lottie Player from unpkg or jsDelivr, which resolve latest-tag requests to new versions automatically.
A <script src="https://unpkg.com/@lottiefiles/lottie-player@latest"> pattern is extremely common in marketing copy and documentation. It resolves to whatever version was most recently published, with aggressive edge caching. When 2.0.5 shipped, every page using this pattern began serving the malicious version to new visitors within the CDN's TTL window. Even after LottieFiles yanked the versions from npm, CDN edges continued serving cached malicious files for hours.
Pinning to @2.0.4 in a script tag would have eliminated the exposure. Using a subresource integrity hash (integrity="sha384-...") would have caused browsers to refuse the mutated file and fail closed. Neither of these defenses is the default in documentation or tutorials, which creates a systemic gap.
How was the attack detected?
A user with wallet connection prompts on an unrelated site noticed, traced the script, and reported it within hours.
The detection chain went: user sees unexpected wallet prompt on a non-Web3 site, opens devtools, identifies the script origin as Lottie Player, checks the file contents, spots the injected drainer, and posts on X and GitHub. LottieFiles staff confirmed within about two hours and coordinated takedown with npm. The compromised versions were unpublished, CDN caches were purged, and a clean 2.0.8 was released.
Automated detection via npm's malware scanning did not flag this. The injected code was obfuscated enough to evade signature matching, and the overall package shape (including the legitimate Lottie bundle) looked normal. Behavioral sandboxing of npm install-time and first-import actions also did not fire because the malicious logic only executed in a browser with window.ethereum present.
What should frontend teams actually change?
Three concrete changes, in priority order.
First, never load third-party JavaScript via unpinned @latest or major-version CDN tags. Use exact version pins in script tags, lockfiles for bundled dependencies, and SRI hashes for any <script src> from an external origin. If a CDN cannot serve an SRI-validated file, host a copy yourself.
Second, enforce content security policy restrictions on third-party scripts. A strict CSP with script-src allowlists limits where injected code can fetch additional payloads from. In the Lottie incident, the drainer was loaded from an attacker-controlled domain, which a properly configured CSP would have blocked outright.
Third, apply the same SBOM and dependency review process to frontend assets as to backend code. Many teams track their server-side npm dependencies in SCA but treat the CDN-loaded versions of the same packages as invisible. A unified SBOM across server and frontend is the minimum for detecting which customer-facing properties are exposed when a package like Lottie Player is compromised.
For Web3-adjacent sites specifically, adding wallet transaction simulation (using tools that preview the state changes of a signed transaction before the user confirms) converts a drainer attack from "one click and it is gone" into a user-facing warning.
What is the broader lesson for frontend supply chains?
Frontend supply chains have been treated as a second-class security concern for too long, and incidents like Lottie Player show why that is no longer tenable.
The mental model most security programs apply is: server-side code is production, frontend code is a view layer. That model made sense when frontends were mostly rendered server-side and scripts were minimal. It has not made sense for a decade. A modern frontend bundle executes tens of thousands of lines of third-party code in the customer's browser, on the customer's device, with access to the customer's session, cookies, and (for Web3) signing authority. A compromised frontend script is functionally equivalent to a compromised production service for blast radius, except that it fans out to every visitor rather than requiring server access.
The tooling gap is real. Most SCA tools trace server-side dependencies with high fidelity and treat frontend dependencies as an afterthought. Most SBOMs exclude minified bundles and CDN-loaded scripts. Most incident response plans do not include "rotate what the visitor's browser might have been exposed to." Closing this gap means pulling frontend dependencies into the same inventory, the same alerting, the same review gates as backend code. Lottie Player is a small incident in dollar terms, but the shape of the attack is the shape of the next several years of supply chain incidents.
How Safeguard.sh Helps
Safeguard.sh's reachability analysis would have identified @lottiefiles/lottie-player as customer-facing code with browser execution context, applying the 60-80% noise reduction that prioritizes this class of finding over internal-only dependencies. Griffin AI flags behavioral deltas in frontend bundles (new outbound fetch targets, new interaction with window.ethereum, new DOM injection patterns) and would have surfaced the wallet drainer independent of npm advisory timing. The SBOM pipeline covers frontend assets at 100-level dependency depth, including CDN-loaded scripts, so teams can answer "which sites served the poisoned version" within minutes rather than hours. TPRM workflows gate frontend suppliers under review, and container self-healing rolls back affected builds automatically once the upstream incident is confirmed.