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
| Ecosystem | Package | Malicious | Clean |
|---|---|---|---|
| npm | @memtensor/memos-cloud-openclaw-plugin | 0.1.21, 0.1.23, 0.1.25 | 0.1.20 (last release before the attack); 0.1.22 and 0.1.24 were clean releases the same day |
| PyPI | MemoryOS | 2.0.34 | 2.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 everyimport memospath 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):
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.
- Between 00:48 and 02:03 the GitHub account
Memtensor-AIcreated, pushed and deleted a branch namedsc/release-0.1.21-20260922-cloudfive times. - In commit
9b97ec6, three added lines in a validation script that runs early in the release job append aBASH_ENV=entry to$GITHUB_ENV. Every later step inherits it, and Bash runs the file named inBASH_ENVbefore any non-interactive script. - The file it names,
sckit-publish-bridge.sh, only acts inside the real publish step. It hands the token to the sckit binary asNPM_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". - 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
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 liketoken,secret,passwordandapi_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,prepareRemoteWorkflowandrecursivePublishsit beside loader templates for npm packages, Python packages and a GitHub Actions workflow saved asruntime-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_isoandja4_denysuggest the operator can filter by country and TLS fingerprint. The config'snot_aftervalue 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:
- Remove it and pin a clean version:
0.1.20for the plugin and2.0.33for MemoryOS, as SafeDep advises. - Kill any
sckitprocess (its command line issckit stage0 --config64 …), and delete$HOME/.openclaw/.cache/runtimeand$HOME/.memos/.cache/runtime. - Treat the machine as fully compromised, as the OSV advisory puts it. From a separate, clean system, rotate every credential reachable from
$HOMEand the process environment: npm and PyPI tokens, GitHub and GitLab tokens, SSH keys, cloud CLI credentials and.envsecrets. - Check every repository you can push to for an unexpected
runtime-update.ymlworkflow or a.sckit/directory, and blockskyleen[.]frand 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_ENVand take over the next step's shell throughBASH_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
- SafeDep: MemTensor npm and PyPI Packages Hit by a Go Worm
- Semgrep: The AI ecosystem has worms now: inside the MemTensor compromise
- The Hacker News: Compromised MemTensor packages deliver sckit credential stealer
- OSV: MAL-2026-16476 (npm)
- OSV: MAL-2026-16475 (PyPI)
- GitHub: MemOS-Cloud-OpenClaw-Plugin issue #173