@testrelic/playwright-analytics 2.13.0 is malicious: check now

@testrelic/playwright-analytics 2.13.0 shipped an obfuscated postinstall loader (OSV MAL-2026-15565). Live ~3.5h on 27 Aug 2026. How to check your lockfiles, what to rotate, and how to upgrade.

playwright-analytics 2.13.0 shipped a hidden postinstall loader.

Short answer: Version 2.13.0 of @testrelic/playwright-analytics shipped an obfuscated code loader inside its postinstall script. It was on npm for at most about three and a half hours on 27 August 2026 before a clean 2.13.1 replaced it, and OSV.dev published advisory MAL-2026-15565 on 29 August. If your lockfile never pinned 2.13.0, you are not affected. If it did, treat the machine that ran npm install as compromised and rotate its credentials.

This is a small package (roughly 2,400 weekly downloads), so the blast radius is nothing like the keyv worm. But the technique matters: a legitimate vendor package, a single bad version, a payload hidden behind 7 KB of whitespace in a file that already had a reason to exist. This guide covers what was published, how to check whether you pulled it, and what to do if you did.

What happened to @testrelic/playwright-analytics?

@testrelic/playwright-analytics is a Playwright test reporter from TestRelic AI. It has been on npm since February 2026 with over a hundred releases, all published by the testrelic-ai account. It legitimately ships a scripts/postinstall.cjs that scaffolds a .testrelic/testrelic-config.json file into your project when you install it.

Here is the timeline from the npm registry's own publish metadata (all times UTC):

  • 27 Aug 2026, 06:18 - 2.12.1-next.88 published. This version has since been removed from the registry.
  • 27 Aug 2026, 16:04 - 2.13.0 published. This is the version flagged as malicious.
  • 27 Aug 2026, 17:20 and 17:34 - 2.12.1-next.90 and next.93 published, clean.
  • 27 Aug 2026, 19:33 - 2.13.1 published by testrelic-ai, clean, becomes latest.
  • 29 Aug 2026, 03:00 - OSV.dev publishes MAL-2026-15565, credited to Amazon Inspector. GitHub mirrors it as GHSA-73rf-vqvh-pc36 half an hour later.
  • 30 Aug 2026, 13:36 - 2.14.0 published, clean.

As of this writing, 2.13.0 no longer resolves on the registry - the tarball returns 404 and the version has no metadata - and TestRelic has not published a statement about how the version got out. We do not know whether this was a compromised publish token, a compromised CI runner, or something else. The one thing the evidence does say is that the same maintainer account pushed a clean release three and a half hours later, which is consistent with a vendor noticing and cleaning up.

What did the malicious postinstall script do?

According to the OpenSSF malicious-packages record (source: Amazon Inspector), the 2.13.0 tarball's scripts/postinstall.cjs looked like this:

  1. The legitimate config-scaffolding code that every prior version shipped - the part a reviewer would recognise and stop reading at.
  2. Roughly 7 KB of whitespace padding, so the malicious part sits far below the fold in any editor or diff view.
  3. An obfuscated loader that rebuilds a large string using a custom Fisher-Yates shuffle, fetches Function via the String constructor property (so the literal tokens Function and eval never appear in the file), exposes require, module, __dirname and __filename on the global object, and then invokes the decoded body.

Exposing require globally before calling the decoded code is the tell: it means the payload was designed to load arbitrary Node built-ins - filesystem, network, child processes - without shipping them in plain sight. The report also notes the bundled dist/index.cjs, dist/reporter-entry.cjs and dist/cli.cjs contained require('child_process') alongside outbound HTTP POST calls and ping invocations.

Two honest caveats. First, because the tarball has been pulled, we could not independently decode the payload, so we cannot tell you exactly what it stole or where it sent it. The advisory describes a dynamic-code loader; assume the worst-case for a postinstall payload, which is credential theft from the installing machine. Second, the clean versions' dist bundles also reference child_process and POST to platform.testrelic.ai - that is what a test-analytics uploader does - so those references alone are not proof of anything. The postinstall loader is the finding.

One detail worth knowing when you assess exposure: the legitimate scaffolding block exits early with process.exit(0) when CI=true, when INIT_CWD is unset, or when installing inside node_modules. If the attacker left that block untouched and simply appended the payload below it, CI installs may have exited before reaching the loader and the primary victims would be developer laptops. Do not rely on this - we cannot confirm the block was unmodified - but it tells you where to look first.

How do you check if you installed 2.13.0?

You are only exposed if 2.13.0 was actually resolved and installed with lifecycle scripts enabled. Three checks, in order of speed.

1. Search your lockfiles

Run this from each repo root that uses the package, including monorepo subpackages:

grep -rn "playwright-analytics-2.13.0.tgz\|playwright-analytics@2.13.0" \
  package-lock.json yarn.lock pnpm-lock.yaml 2>/dev/null

# Or match the exact tarball integrity hash from the advisory:
grep -rn "sdKejYN3QLQHkj0HILr+UU9uFT60clNsKk33e0B85ODjySausezaa2ypIJ6Mo26j45y+02J/zIJei50jxMo1nQ==" \
  package-lock.json yarn.lock pnpm-lock.yaml 2>/dev/null

If a lockfile currently pins 2.13.0, you installed it. If your lockfile pins 2.13.1 or later but was updated between 27 and 30 August, check git history for the previous pin:

git log -p --since="2026-08-27" -- package-lock.json | grep -n "playwright-analytics"

2. Check what is on disk

npm ls @testrelic/playwright-analytics
cat node_modules/@testrelic/playwright-analytics/package.json | grep '"version"'

Do this on developer machines too, not just in the repo. A laptop that ran npm install on 27 August with a floating ^2.12.0 range and no lockfile would have picked up 2.13.0 for those three and a half hours.

3. Check CI and install logs

Look for any job that installed dependencies between 2026-08-27 16:04 UTC and roughly 19:33 UTC (and, to be safe, until the version was removed from the registry). Search the install output for playwright-analytics@2.13.0 or for the [testrelic] Created .testrelic/testrelic-config.json line, which the postinstall prints before the padded section.

Not sure which of your repos even depend on this package? A free InstallSafe scan reads your lockfile and flags any version that appears in OSV.dev, including MAL-2026-15565.

What should you do if you were exposed?

If 2.13.0 was installed with scripts enabled, work through this list on every affected machine. Do not skip to step 4.

  1. Treat the host as compromised. A postinstall loader with global require access ran as your user. Anything that user could read - ~/.npmrc, ~/.ssh, ~/.aws, .env files, git credentials, browser session data - is in scope.
  2. Rotate credentials. Start with npm tokens (revoke and reissue at npmjs.com), GitHub personal access tokens and SSH keys, cloud provider keys, and any API keys in .env or .testrelic/testrelic-config.json, including TESTRELIC_API_KEY. If the machine held CI secrets, rotate those too.
  3. Check for persistence. Look for new launch agents, cron entries, shell rc modifications, and unexpected entries in ~/.config created on or after 27 August. If the machine is a shared CI runner, rebuild it from a known-good image rather than cleaning it.
  4. Upgrade and re-lock. Move to 2.13.1 or later (2.14.0 is current), commit the lockfile, and run npm ci so every environment resolves the same pinned version.
  5. Audit downstream. If the affected machine had publish rights to your own npm packages or write access to your repos, review anything published or pushed from it since 27 August.

If you only pulled 2.13.0 with --ignore-scripts, the postinstall never ran, and the exposure is limited to whatever the bundled dist code did when your test suite executed. Upgrade anyway and review your test runner logs for unexpected outbound requests.

How does a registry firewall help with incidents like this?

The uncomfortable truth about this incident is that it was invisible for the entire window that mattered. The malicious version was live for about three and a half hours, and the advisory landed about 35 hours after it was pulled. No scanner that depends on advisory data could have blocked it at publish time - and that includes InstallSafe, which uses OSV.dev data and does not claim otherwise.

What a registry firewall does change is everything after the advisory exists, and the shape of the exposure before it:

  • Retroactive blocking without touching every repo. Once MAL-2026-15565 is in OSV.dev, a proxy registry refuses to serve 2.13.0 to any client pointed at it - developer laptops, CI runners, AI coding agents that install dependencies on their own. You do not have to find every lockfile first.
  • One install boundary to log. When the question is "did anything in the org pull this version on 27 August", a registry proxy has the answer in one place. Without it, you are grepping CI logs across every pipeline and asking every developer to check their laptop.
  • Byte-for-byte tarballs. A proxy that serves exact upstream tarballs (rather than rewriting them) means the integrity hash in your lockfile still matches, so a lockfile grep for the advisory's hash keeps working as forensics.

Pairing a registry firewall with behavioural analysis from tools like Socket or Aikido closes more of the gap: those tools flag obfuscated postinstall scripts on publish, before any advisory exists. Neither approach alone covers everything, and anyone telling you their tool would have caught this at minute zero should show you the alert timestamp.

To route installs through InstallSafe:

npm config set registry https://r.installsafe.io

Or start with the free scan to see whether any lockfile in your org currently references an OSV-flagged version.

Frequently asked questions

Is @testrelic/playwright-analytics safe to use now?

Versions 2.13.1 and 2.14.0 do not contain the loader described in the advisory - we diffed 2.12.1 against 2.14.0 and the postinstall script is the same 1.9 KB config scaffolder. Only 2.13.0 is flagged. Whether to keep trusting the package depends on the vendor's explanation of how 2.13.0 was published, which has not been issued yet.

Is @testrelic/core affected?

No advisory exists for @testrelic/core as of 30 August 2026. The flagged package depends on it, but the malicious code was in @testrelic/playwright-analytics itself.

Does npm audit catch this?

Yes, now. npm audit reads the GitHub Advisory Database, which includes GHSA-73rf-vqvh-pc36 as of 29 August. It would not have flagged anything during the window the version was live.

I have 2.13.0 in my lockfile but always install with --ignore-scripts. Am I safe?

The postinstall loader would not have run. Upgrade anyway, because the advisory also notes outbound network calls in the bundled reporter code, and you should review test-run logs from that period.

How long was the malicious version available?

From 16:04 UTC on 27 August 2026 until at most 19:33 UTC the same day, when 2.13.1 became latest. The exact unpublish time is not recorded in the registry metadata.

Who reported it?

Amazon Inspector, via the OpenSSF malicious-packages repository. The OSV.dev entry is MAL-2026-15565.