Supply Chain Attack · Supply Chain

MemTensor's OpenClaw Plugin and MemoryOS Shipped the sckit Credential Worm

Data graphic: a timeline of 23 September 2026 UTC headlined "MemTensor's own CI shipped a worm". After an attacker branch was pushed and deleted five times between 00:48 and 02:03, malicious npm releases 0.1.21 02:23 , 0.1.23 03:49 and 0.1.25 04:36 alternate with clean 0.1.22 03:45 and 0.1.24 04:33 , issue #173 opens at 04:17, and PyPI MemoryOS 2.0.34 follows at 05:25: four poisoned releases, all carrying the sckit worm. Pin 0.1.20 and 2.0.33.
AK

Threat intelligence editor · Updated Oct 2, 2026, 8:05 AM EDT

Pushed commits made MemTensor's own GitHub Actions jobs leak npm and PyPI tokens. Four releases then shipped sckit, a Go worm that steals developer secrets.

On 23 September 2026 an attacker published malicious releases of two packages from MemTensor, the company behind the MemOS memory layer for AI agents. The npm package @memtensor/memos-cloud-openclaw-plugin, which gives the OpenClaw agent long-term memory, shipped poisoned versions 0.1.21, 0.1.23 and 0.1.25. The Python library MemoryOS shipped a poisoned 2.0.34 on PyPI. All four carry the same Go implant, called sckit. It steals the credentials it finds on the machine, sends them to servers under skyleen[.]fr, and carries the templates it needs to republish itself through any npm, PyPI or GitHub account those credentials unlock.

Nobody phished a maintainer to get the publish tokens. The attacker pushed commits that made MemTensor's own GitHub Actions release jobs hand the npm and PyPI tokens over, mid-run, before anything was published. That is the part every team that publishes an agent plugin from CI should read twice.

SafeDep, Aikido, Socket, StepSecurity and Semgrep all reported on the compromise. OSV tracks it as MAL-2026-16476 (GHSA-mhjf-v53x-7p87) for the npm plugin and MAL-2026-16475 (PYSEC-2026-3987) for MemoryOS. No CVE has been assigned, which is normal for malicious-package incidents.

Who is affected

EcosystemPackageMaliciousClean
npm@memtensor/memos-cloud-openclaw-plugin0.1.21, 0.1.23, 0.1.250.1.20 (last release before the attack); 0.1.22 and 0.1.24 were clean releases the same day
PyPIMemoryOS2.0.342.0.33

When SafeDep pulled the data, 0.1.25 carried npm's latest tag and 2.0.34 was the newest MemoryOS release, so a plain npm install or pip install MemoryOS got the backdoor. The Hacker News reports that MemoryOS is now quarantined on PyPI. SafeDep found no new versions of any other @memtensor/* package that day, but warns the list can grow, because the payload is built to spread.

The malicious code does not use an install hook. It runs when the package loads, so npm install --ignore-scripts does not help:

  • The OpenClaw plugin starts the binary when the OpenClaw gateway starts, and again on every memory recall. The user's prompt text is passed to the implant in an environment variable, SCKIT_EVENT_TEXT.
  • MemoryOS starts it the first time the library configures logging, through memos/log.py, which almost every import memos path triggers.

How the attacker took the npm token

The OpenClaw plugin publishes from a GitHub Actions workflow whose publish step reads NPM_TOKEN from a repository secret. SafeDep reconstructed the theft from repository events and the registry APIs (all times UTC on 23 September):

Data graphic: how a pushed commit stole MemTensor's npm token. A branch pushed by Memtensor-AI makes the validation script write BASH_ENV to $GITHUB_ENV, so a bridge script runs inside the npm publish step; NPM_TOKEN is handed to sckit and the step exits 1, the token is sent to skyleen . fr, and malicious 0.1.21 is published 20 minutes after the attacker's last push. Side notes: five branch pushes from 00:48 to 02:03 UTC, commit 9b97ec6, and the same BASH_ENV trick on PyPI for MemoryOS 2.0.34.

How the attacker took the npm publish token from MemTensor's own release job: the poisoned 0.1.21 was on npm 20 minutes after the last branch push. Source: SafeDep.

  1. Between 00:48 and 02:03 the GitHub account Memtensor-AI created, pushed and deleted a branch named sc/release-0.1.21-20260922-cloud five times.
  2. In commit 9b97ec6, three added lines in a validation script that runs early in the release job append a BASH_ENV= entry to $GITHUB_ENV. Every later step inherits it, and Bash runs the file named in BASH_ENV before any non-interactive script.
  3. The file it names, sckit-publish-bridge.sh, only acts inside the real publish step. It hands the token to the sckit binary as NPM_TOKEN, deletes itself and exits with code 1. The publish fails, nothing reaches npm from that run, and the attacker now holds the token. The campaign config names the trick outright: "channel": "exact-ref-one-use-NPM_TOKEN".
  4. Twenty minutes after the last push, at 02:23, malicious 0.1.21 appeared on npm.

The registry then flip-flopped: clean 0.1.22 at 03:45, malicious 0.1.23 at 03:49, clean 0.1.24 at 04:33, malicious 0.1.25 at 04:36. All five came from the same npm account that published the legitimate 0.1.20. SafeDep cannot tell from the registry who published the clean ones. Semgrep reads the swaps as the attacker testing within hours.

SafeDep also found that the GitHub Actions API returns no workflow runs for the repository after 7 September, and infers that someone deleted them. How the attacker got push access as Memtensor-AI is not known. A stolen token for that account is the most likely explanation, SafeDep says, but it could not confirm it.

The PyPI route used the same trick

MemOS publishes to PyPI when a GitHub Release with a v* tag is created. Commit b52958f, created at 03:17 under the author name "MemTensor CI Review", added the six sckit binaries and swapped in a custom Poetry build backend. On import, that backend writes the same kind of BASH_ENV entry, pointing the PyPA publish action's upload script at a bridge that ships the PyPI token to 10729e014d0e.skyleen[.]fr and then exits without uploading. A deleted tag named v2.0.34-capture-1 fits that capture run.

About two hours later, commit 41bf5c7 ("chore: allow native PyPI upload [skip ci]") removed the one line that registered the bridge. Memtensor-AI re-created tag v2.0.34 on it and published a Release. MemoryOS 2.0.34 hit PyPI at 05:25, 62 seconds later, built and uploaded by MemTensor's own workflow with the project's real token. Neither commit is on main.

What sckit does on a machine

Data graphic: the sckit Go implant at the centre takes npm, PyPI, GitHub, GitLab, AWS and SSH credentials plus Hugging Face, Vault, Slack and JWTs , sends them to skyleen . fr and spreads into npm and PyPI packages, with a WORM badge for the GitHub Actions file runtime-update.yml. Side panel: the MemoryOS 2.0.34 wheel is 19 MB, up from 951 KB in 2.0.33; it runs on every load, ships in six builds for Linux, macOS and Windows on amd64 and arm64, and uses X25519 and XChaCha20-Poly1305.

What sckit steals from a developer machine, where it sends it, and how it tries to spread. The poisoned MemoryOS wheel grew from 951 KB to 19 MB. Source: SafeDep, OSV MAL-2026-16476.

sckit ships as six Go binaries, for Linux, macOS and Windows on amd64 and arm64. The MemoryOS wheel grew from 951 KB in 2.0.33 to 19 MB in 2.0.34 to carry them. From SafeDep's and OSV's analysis:

  • It takes credentials from the whole home directory. It reads files such as .npmrc, .pypirc, .git-credentials, .netrc, SSH keys and .vault-token, and environment variables with names like token, secret, password and api_key. It matches token formats for AWS, GitHub, GitLab, npm, PyPI, Hugging Face, HashiCorp Vault, Slack, Stripe and SendGrid, plus generic JWTs.
  • It sends them to skyleen[.]fr. C2 traffic uses CBOR with X25519 key exchange and XChaCha20-Poly1305 encryption, and the server can push further modules at run time.
  • It is built to spread. Functions named findRepositories, prepareRemoteNode, prepareRemotePython, prepareRemoteWorkflow and recursivePublish sit beside loader templates for npm packages, Python packages and a GitHub Actions workflow saved as runtime-update.yml. Semgrep summed up the loop: compromise a developer, steal the token, publish the worm from their account, compromise their users, repeat. It found no evidence yet that this malware has spread beyond MemTensor.
  • It can pick its victims. Fields named geo_iso, deny_iso and ja4_deny suggest the operator can filter by country and TLS fingerprint. The config's not_after value decodes to 22 and 23 October 2026, which looks like a built-in expiry.

For OpenClaw users there is an extra sting. The plugin hands every recall prompt to the implant, so whatever a user asked their agent to remember went along for the ride. TF's OpenClaw vs Hermes Agent security comparison covers the wider plugin risk.

What to do

If you installed an affected version:

  1. Remove it and pin a clean version: 0.1.20 for the plugin and 2.0.33 for MemoryOS, as SafeDep advises.
  2. Kill any sckit process (its command line is sckit stage0 --config64 …), and delete $HOME/.openclaw/.cache/runtime and $HOME/.memos/.cache/runtime.
  3. Treat the machine as fully compromised, as the OSV advisory puts it. From a separate, clean system, rotate every credential reachable from $HOME and the process environment: npm and PyPI tokens, GitHub and GitLab tokens, SSH keys, cloud CLI credentials and .env secrets.
  4. Check every repository you can push to for an unexpected runtime-update.yml workflow or a .sckit/ directory, and block skyleen[.]fr and all its subdomains.

If you publish packages from CI, this attack is the one to design against:

  • Any script that runs before your publish step can write to $GITHUB_ENV and take over the next step's shell through BASH_ENV. Do not let the job run code from the commit it is building with the publish token in scope.
  • Move to trusted publishing (OIDC) on npm and PyPI, so there is no long-lived token to steal.
  • Protect release tags and require a reviewer on the release environment. Both MemTensor workflows built commits that were never merged to main, and either control would have stopped those runs.
  • Publish provenance attestations. Neither MemTensor package had them, so nothing flagged that the artifacts came from an unexpected commit.

Sources