DEV Community

Cover image for The Tensorlake Worm Commits a .claude Folder So It Runs Again When You Open Claude Code
Gabriel Anhaia
Gabriel Anhaia

Posted on AI-assisted

The Tensorlake Worm Commits a .claude Folder So It Runs Again When You Open Claude Code


The commit message says chore: update dependencies. The author email is claude at GitHub's users.noreply.github.com domain, so in your log it reads like one more commit from a coding agent. It adds two config files. The next time someone opens that project in Claude Code or VS Code, the malware that wrote it runs again.

That is how StepSecurity describes the worm inside tensorlake@0.5.144, an npm release of the Tensorlake TypeScript SDK. Stealing tokens is standard for this campaign family. After that, it hides in the folders your coding agent and your editor read as configuration.

What tensorlake@0.5.144 did

The timeline, from StepSecurity's write-up of October 8, 2026: the first malicious commit landed on the repository's main branch on October 7 at 01:20 UTC, under a maintainer's name and not through a pull request. Seven more commits followed, one of them adding a preinstall line to package.json. On October 8 at 01:12 UTC the project's own release workflow published 0.5.144 to npm. Socket flagged it 11 minutes later and puts the package at about 12K weekly downloads.

Installing it was enough. You never had to import the SDK:

  1. The preinstall hook runs node lib/setup.mjs.
  2. The script skips itself on CI, so developer machines are the target.
  3. It downloads Bun and uses it to run lib/Math_Symbol.js, an obfuscated payload of about 856 KB.
  4. The payload collects GitHub and npm tokens, cloud keys, Kubernetes and Vault secrets, SSH keys, saved browser logins, and config files for AI tools "like Claude, Cursor and Windsurf", in StepSecurity's words (Cursor, Windsurf). Socket's analysis also lists configuration and MCP files for Kiro and Zed.
  5. It sends the haul to a public GitHub repo it creates in your account, described as "Shai-Hulud: Here We Go Again", or to an attacker-controlled server.

With your npm token it republishes your own packages with itself inside. The Hacker News ties it to ChainDrop, the Shai-Hulud-style campaign first documented in early August 2026 across hundreds of npm packages. The maintainers reverted every commit after the last clean one in PR #1016, merged October 8. That PR says npm has removed tensorlake@0.5.144 (it adds that six tensorlake-native-*@0.5.144 packages still needed unpublishing), and it moves every package to 0.5.145 as the first clean release.

The commit that waits for you to open the project

With your GitHub token, the worm goes past stealing. It commits .claude and .vscode files to the repos that token can reach. StepSecurity names .claude/settings.json and .vscode/tasks.json, and says the files run the malware again "when someone opens the project in Claude Code or VS Code."

StepSecurity doesn't publish the contents of those files. You don't need them to see how this works, because both tools document a way for a repository file to start a program.

In Claude Code, .claude/settings.json is the shared project settings file, the one the docs say "can be committed to the repo". It can hold hooks: shell commands Claude Code runs on events. A SessionStart hook with the startup matcher runs when a new session begins. This is the shape from the hooks docs, with a placeholder command. It is not the worm's file:

{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "startup",
        "hooks": [
          {
            "type": "command",
            "command": "node .claude/run.mjs"
          }
        ]
      }
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

A sealed parcel labelled only with a dotted folder icon sits on a doorstep, with a thin fuse running from it under the door of a small house
A commit that does nothing until someone opens the project.

Claude Code has a guard: the workspace trust dialog, shown in interactive sessions when you start in a folder you haven't trusted. But the worm writes to your repositories, which you have probably trusted already. The permissions docs say trust is keyed on the git repository root, and they describe no second prompt when a trusted repo's hooks change later. The same page says a claude -p run or an SDK session never shows the dialog and still uses hooks from the repository's settings files. A CI job that runs Claude Code headless on a fresh checkout would run them too.

VS Code's version is a task with "runOn": "folderOpen". Its guard is stronger by default. The tasks docs say task.allowAutomaticTasks is off, which means you are asked once per workspace, and automatic tasks "never run in an untrusted workspace". If you once clicked allow for that repo, though, the answer is already given.

Revoke the token and it deletes your home folder

The worm also plans for being found. When it holds a GitHub token, StepSecurity says, it installs a service called gh-token-monitor that checks the token against the GitHub API every 60 seconds, for up to 24 hours. If GitHub rejects the token, it runs rm -rf ~/ on Linux and macOS, or deletes the user profile with PowerShell on Windows. Socket found the intent spelled out in the code as a literal string: IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwner.

So the instinctive first move, revoking the token, is the trigger. This is StepSecurity's order, and Socket agrees on the part that matters, revoking last:

  1. Check whether you installed it: run npm ls tensorlake and search your lockfiles for 0.5.144.
  2. Back up what you need. Look in ~/.config/gh-token-monitor/ to see which token it holds.
  3. Stop the monitor. Socket's post gives the Linux command below. For macOS, both write-ups say to unload the com.user.gh-token-monitor.plist LaunchAgent, which is what the launchctl bootout line does. On Windows, delete the logon task in Task Scheduler that runs monitor.ps1.
  4. Delete ~/.config/gh-token-monitor/, ~/.local/bin/gh-token-monitor.sh and the service file: ~/.config/systemd/user/gh-token-monitor.service on Linux, ~/Library/LaunchAgents/com.user.gh-token-monitor.plist on macOS.
  5. Only then revoke the GitHub token, and rotate npm tokens, cloud keys, SSH keys, Kubernetes and Vault credentials, .env secrets, AI tool keys and saved browser passwords.
  6. Look for spread: public repos in your account described "Shai-Hulud: Here We Go Again", and .claude or .vscode commits you didn't make. The git checks below find those.
  7. If you can't confirm the machine is clean, rebuild it.
# Linux: stop the monitor before revoking anything
systemctl --user disable --now gh-token-monitor.service

# macOS: unload the LaunchAgent before revoking anything
launchctl bootout gui/$(id -u) \
  ~/Library/LaunchAgents/com.user.gh-token-monitor.plist
Enter fullscreen mode Exit fullscreen mode

A brass key hangs on a taut string tied to a tripwire, which runs to a lever poised over a stack of folders
The token is the tripwire: pull it first and the lever drops.

If the package only ran in CI, the hook skipped itself there. StepSecurity still advises rotating any secret that job could reach.

Find the commit with plain git

Start with the indicator StepSecurity published, then check by path, because the author line is free text and the next wave can change it:

# The worm's author address, every branch (--author is a regex)
git log --all --author='claude@users[.]noreply' \
  --format='%h %ad %an %s' --date=short

# Every commit that touched agent or editor config
git log --all --format='%h %ad %an %s' --date=short \
  -- .claude .vscode
Enter fullscreen mode Exit fullscreen mode

The second command is the one that keeps working. It also lists every legitimate change to those folders, so you read a short list instead of trusting a name.

Run the check before you open the project. Fetch first, then look at what the incoming commits do to config:

git fetch origin
git diff --stat HEAD...origin/main -- .claude .vscode
Enter fullscreen mode Exit fullscreen mode

The three dots show only what origin/main changed since your branch split from it. If you'd rather be warned on every pull, a local post-merge hook can do it. Git runs it after git merge, which is what a plain git pull does, and git merge sets ORIG_HEAD to the tip before the merge:

#!/bin/sh
# .git/hooks/post-merge  (make it executable)
changed=$(git diff --name-only ORIG_HEAD HEAD -- .claude .vscode)
if [ -n "$changed" ]; then
  echo "Pull changed agent/editor config. Read before opening:"
  echo "$changed"
fi
Enter fullscreen mode Exit fullscreen mode

Three limits. It lives in .git/hooks, which isn't part of the tracked files, so every clone needs its own copy. It doesn't run on git pull --rebase, where git calls post-rewrite instead. And it doesn't run when the merge stops on conflicts.

Inside Claude Code, /hooks opens a read-only browser of the hooks that are configured. Use it, but know its timing: by the time you can type it, a startup hook has already run. Read the file before you launch. If you run an org, Claude Code's managed setting allowManagedHooksOnly blocks user, project, local and plugin hooks, per the hooks docs.

On GitHub, protect the branches you open most. With a branch protection rule that requires pull requests, "collaborators can only push changes to a protected branch via a pull request". Add a CODEOWNERS entry for these paths and turn on "Require review from Code Owners" in the same rule, so changes to them need a named reviewer:

# .github/CODEOWNERS
/.claude/    @your-org/security
/.vscode/    @your-org/security
/.mcp.json   @your-org/security
Enter fullscreen mode Exit fullscreen mode

Branch protection has its own gaps. By default it doesn't apply to repository admins, so enable "Do not allow bypassing the above settings". And StepSecurity doesn't say which branch the worm commits to, so this covers only the branches you protect.

Where Hermes IDE fits, and where it doesn't

I build Hermes IDE, so weigh this section with that in mind. Hermes runs each agent in a real terminal, exactly as it would run anywhere else (README). So a SessionStart hook committed to your repo runs inside Hermes too. Each task you start with ⌘N gets its own git worktree, and a worktree checks out the same tracked .claude files, so a planted hook follows you into each one. Hermes doesn't sandbox the agent.

It does add a review step for the changes your agents make. The Review Desk (⌘G, Ctrl+Shift+G on Windows and Linux) shows everything an agent changed, by turn or by file, with risk flags (README). Every turn is saved, so if a turn touched .claude/settings.json, you can diff or restore that single turn, with undo, and keep the rest of the agent's work.

The Hermes Review Desk in By turn view for a Claude Code task in orders-api, with two turns, a diff of src/db.ts, the changed files and a drafted commit message
The Review Desk (⌘G) in By turn view for a Claude Code task in orders-api: two turns, turn 1's diff of src/db.ts, the changed-files list, and a commit message drafted from the turns.

The limit matters for this incident. The worm's commit doesn't come from an agent turn in Hermes. It was pushed to GitHub with a stolen token and reaches you through git pull. For that commit, the git checks above are your tool. Hermes is free to download, source-available under BSL 1.1, runs on macOS, Windows and Linux, and isn't affiliated with Nous Research, Anthropic or any agent vendor. Download it or read the source on GitHub.

Agent config is code now

If your team lets agents commit, a chore: update dependencies from a Claude-looking address blends right in. Review .claude/settings.json, .vscode/tasks.json and .mcp.json the way you review a CI workflow, because they can run commands on the next laptop that opens the repo. In an interactive session, Claude Code asks before connecting servers from a project's .mcp.json, per its security page. For hooks in a repo you already trusted, the docs describe no such prompt.

Does your team review changes to .claude/ at all today, or do they ride along in the same PR as the code? Tell me in the comments.


If this was useful

A committed hook is a tool call nobody asked for: a command that runs because a file said so. AI That Acts is about the side of that you control when you build agents in TypeScript: tool calling, functions, and a first working agent where you decide what it is allowed to run. If you run agents with Hermes, the Review Desk shows every change they make, turn by turn, with risk flags.

AI That Acts — AI in TypeScript

It is book 3 of AI in TypeScript, a five-book series that runs from your first LLM call through to agents you can leave running in production.

AI in TypeScript — the five-book series

Top comments (0)