⚡ LIVE From Lizard to Wizard · Thursday, May 28 · LIMITED SEATS Save my seat →

· security · 7 min read

How to steal npm publish tokens by opening GitHub issues

A chain of vulnerabilities and pretty clever attack strategies led to the compromise of the Cline CLI. Let me explain what happened and what you can do to protect yourself.

Neciu Dan

Neciu Dan

Hi there, it's Dan, a technical co-founder of an ed-tech startup, host of Señors at Scale - a podcast for Senior Engineers, Organizer of ReactJS Barcelona meetup, international speaker and Staff Software Engineer, I'm here to share insights on combining technology and education to solve real problems.

I write about startup challenges, tech innovations, and the Frontend Development. Subscribe to join me on this journey of transforming education through technology. Want to discuss Tech, Frontend or Startup life? Let's connect.

Share:
How to steal npm publish tokens by opening GitHub issues

Cline, if you haven’t heard of it, is an open-source AI coding assistant. It has over 5 million users across VS Code and JetBrains, and it’s one of the tools people actually use day-to-day for AI-assisted development. It also has a CLI version published on npm.

On February 17, someone published cline@2.3.0 to npm using a stolen publish token. The only thing they changed in the package was one line in package.json:

"postinstall": "npm install -g openclaw@latest"

It was live for 8 hours. About 4,000 installs before the maintainers caught it and pushed a clean 2.4.0.

To clarify what actually happened and its impact, let me set the context for what’s ahead.

What is OpenClaw and why should you care

If you’ve been anywhere near the internet in the last two months, you’ve seen OpenClaw. It used to be called Clawdbot, then briefly Moltbot, and now OpenClaw. It crossed 160,000 GitHub stars in early 2026. Peter Steinberger, the creator, was just hired by OpenAI to either scale it further or close it for good. People are buying Mac Minis specifically to run them 24/7 as a personal AI assistant.

And that’s not hype. OpenClaw is legitimately interesting software. You install it, it runs as a background daemon on your machine, and you talk to it over WhatsApp, Telegram, or iMessage. It reads files, runs terminal commands, browses the web, and manages tasks. Some guy named his instance “Govind” and has it drafting his Upwork proposals. There’s an entire cottage industry now of people selling Mac Mini setup services for OpenClaw deployments. The base M4 Mac Mini draws 5-7 watts at idle, costs maybe $1-2/month in electricity, and you’ve got a personal AI butler running on your desk.

So OpenClaw isn’t malware, which changes what the Cline attack actually did.

What the postinstall line actually does (and doesn’t do)

When reports say “malware was installed,” they mean the postinstall hook ran npm install -g openclaw@latest, installing the OpenClaw CLI globally and registering its Gateway daemon. On macOS, this is a launchd service; on Linux, it’s a systemd service. The daemon runs on ws://127.0.0.1:18789 and persists after reboot.

But OpenClaw does nothing unless configured. It needs API keys, messaging integrations, and manual setup to be useful.

So why is this bad?

The Gateway daemon, even unconfigured, is risky: it’s a WebSocket server on localhost that, before version 2026.1.29, had a critical auth bypass (CVE-2026-25253, CVSS 8.8). Anyone could connect as an operator without a token by skipping the scopes field in the handshake.

The Gateway has full disk and terminal access. That’s intentional, making OpenClaw useful when set up. But if it appears uninvited on a CI runner with AWS credentials or npm tokens in environment variables, it’s a problem.

How they stole the publish token

OK, so here’s where it actually gets interesting: the attack chain is unlike anything I’ve seen before in the npm ecosystem.

A security researcher named Adnan Khan found a vulnerability in Cline’s GitHub repo weeks earlier. He called it “Clinejection.” Here’s the setup:

Cline had added an AI-powered issue triage bot. When someone opens a GitHub issue, Claude (via Anthropic’s claude-code-action) would automatically read it, label it, and leave a helpful comment. Pretty standard stuff that many projects are doing now.

The problem was the configuration:

allowed_non_write_users: "*"
claude_args: >-
  --allowedTools "Bash,Read,Write,Edit,Glob,Grep,WebFetch,WebSearch"
prompt: |
  **Issue:** #${{ github.event.issue.number }}
  **Title:** ${{ github.event.issue.title }}

Three things are wrong here. One: Any GitHub user can trigger the workflow by opening an issue. Two: Claude has Bash, Read, Write, and Edit tool permissions on the Actions runner. Three: the issue title goes directly into the prompt.

That last one is the culprit. If you put the right text in the issue title, you can make Claude execute whatever you want on the CI runner. Classic prompt injection, but now with real tool access on real infrastructure.

Khan tested this on a mirror of the Cline repo. It worked. Claude happily executed the injected commands.

But the triage workflow didn’t have access to publish secrets. It was a low-privilege runner. So how did they get the npm publish token?

GitHub Actions cache poisoning.

The Cline repo had 2 workflows:

  • the triage bot workflow that we described which the attacker got access to
  • nightly release workflow that had the npm package secret

To understand how the attacker got the secret from another workflow he did not have access to, you need to know how GitHub Actions caching behaves. Every repository has a shared cache pool (up to 10GB) that all workflows on the default branch can read from and write to. The triage bot workflow and the nightly release workflow both live on the same branch, so they share the same cache scope. GitHub doesn’t isolate caches between workflows. A low-privilege workflow can write cache entries that a high-privilege workflow later reads.

Cline’s nightly release workflow had this in its config:

yaml- name: Cache root dependencies
  uses: actions/cache@v4
  id: root-cache
  with:
    path: node_modules
    key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }}

That key is predictable. If you know the contents of package-lock.json (it’s a public repo, so you do), you know exactly what cache key the release workflow will look for when it runs.

The attacker already has arbitrary command execution on the triage runner through the prompt injection. From that runner, they write junk data to the cache. Lots of it. More than 10GB. GitHub’s eviction policy is LRU (least recently used), so once the cache fills up, old entries start getting kicked out. The legitimate node_modules cache entry that the release workflow depends on gets evicted.

Now the attacker writes a new cache entry with the exact same key: Linux-npm-{hash of package-lock.json}. But this one contains a poisoned node_modules directory. The attacker can put whatever they want in there. Khan actually built an open-source tool for this called Cacheract that automates the whole process: it poisons cache entries and persists across workflow runs by hijacking the actions/checkout post step.

The nightly release workflow is scheduled to run at around 2 AM UTC. When it kicks off, it does actions/cache@v4 restore, looks for a cache entry matching that key, and finds the attacker’s poisoned version. It restores the poisoned node_modules into the workspace. From this point on, any code that runs in the release workflow is executing in a compromised environment.

The release workflow has access to the secrets that matter NPM_RELEASE_TOKEN. The attacker’s code inside the poisoned node_modules reads these secrets from the environment and exfiltrates them.

Khan published his research on February 9. Cline patched the prompt injection within 30 minutes and revoked credentials. But they revoked the wrong token. The actual npm publish token survived. Eight days later, on February 17, a different actor (not Khan, he was very clear about this) found the published proof of concept and used it to publish cline@2.3.0.

Legitimate Cline releases always had npm-provenance attestations from GitHub Actions using OIDC. Version 2.3.0 didn’t: it was published from a user account, not the pipeline. npm audit signatures would have exposed this.

You can read the full report from Khan on his blog here.

What you can actually do

Disable lifecycle scripts by default. Add this to .npmrc in your project root:

ignore-scripts=true

Some packages need them. Native modules like sharp and bcrypt compile binaries during postinstall. You can check which of your dependencies actually need scripts:

npx can-i-ignore-scripts

In my experience, the vast majority of post-install scripts in a typical project are unnecessary. The ones that matter are almost always well-known packages you can whitelist.

Switch CI/CD from npm install to npm ci. npm install resolves versions anew each time, risking compromised packages. npm ci uses the lockfile as a strict snapshot, increasing safety.

Run npm audit signatures in CI. It checks provenance attestations. cline@2.3.0 had none. This check costs nothing to add and would have caught this specific attack.

And honestly, start paying attention to how your dependencies get built and published. The Cline attack went from a GitHub issue to a published npm package through a chain of steps that, individually, seem reasonable (AI triage bots, shared CI caches, publish tokens in workflows), but together create a path that nobody was watching.

PS: You can read the Post Mortem here: https://cline.bot/blog/post-mortem-unauthorized-cline-cli-npm

PSS: Check out this amazing resource from Liran Tal: https://github.com/lirantal/npm-security-best-practices


I’ve been researching these attacks for months and turned it into a free course for frontend devs. It covers how the attacks work, defense layers, security tooling, building a custom scanner, container isolation, and incident response.

Module 1 is live at https://neciudan.dev/course/master-security. Enroll now!

🏆 SOLD OUT IN SINGAPORE · ATHENS · LONDON

From Lizard to Wizard

4-hour remote system design intensive.
Chat apps, microfrontends, BFF, SDUI, event-driven, observability.

€299 4-HOUR INTENSIVE
Save your seat →

Spots are vanishing. Don't be the one who waited.

Author

Discover more from The Neciu Dan Newsletter

A weekly column on Tech & Education, startup building and occasional hot takes.

Over 1,000 subscribers

🎙️ Latest Podcast Episodes

Dive deeper with conversations from senior engineers about scaling applications, teams, and careers.

React Native at Scale with Kadi Kraman
Episode 35
60 minutes

Señors @ Scale host Neciu Dan sits down with Kadi Kraman, software developer at Expo working on the tools that make React Native development as smooth as possible. Kadi's path started with C++ in a university maths degree, took her through Angular 1, scientific programming for pharmaceutical and defense companies, five and a half years at Formidable, and finally to Expo itself. From the limitations of early React Native to development builds, EAS workflows, fingerprint-based repacks, and the right way to think about over-the-air updates, this is the React Native conversation most web developers never get.

📖 Read Takeaways
Browser ML at Scale with Nico Martin
Episode 34
66 minutes

Señors @ Scale host Neciu Dan sits down with Nico Martin — open source ML engineer at Hugging Face working on Transformers.js, and Google Developer Expert in AI and web technology — to go deep on running machine learning models directly in the browser. Nico breaks down architectures vs. weights, quantization, tokenizers, ONNX, WebGPU, and why on-device AI is the right answer for a huge class of problems. He also shares the road from ski instructor and self-taught web developer to landing what he calls his dream job at Hugging Face.

📖 Read Takeaways
Frontend Foundations at Scale with Giorgio Polvara
Episode 33
55 minutes

Señors @ Scale host Neciu Dan sits down with Giorgio Polvara, Staff Engineer at Perk (formerly TravelPerk), who joined when the company was 15 people in two flats with a hole knocked through the wall and helped build the frontend foundations that still hold up at unicorn scale. Giorgio covers the multi-year migration from a monolithic frontend to vertical micro-frontends, why their first attempt with single-spa didn't work, how they pulled off a full rebrand behind feature flags without leaking, and the staff engineer mindset of treating every feature as a system improvement.

📖 Read Takeaways
Module Federation at Scale with Zack Chapple & Nestor
Episode 32
57 minutes

Señors @ Scale host Neciu Dan sits down with Zack Chapple, CEO and co-founder of Zephyr Cloud, and Nestor, the platform engineer building it, to go deep on module federation, microfrontends, and what it actually takes to go from code to global scale in seconds. They unpack why module federation is Docker for the frontend, how Zephyr composes applications at the edge in 80 milliseconds, and why the real unlock for enterprise teams isn't deployment — it's composition.

📖 Read Takeaways
Back to Blog