A generated abstract illustration on a dark navy field: a stack of five thin cyan memory trays seen in cutaway, each holding rows of small grey blocks, with one small amber cube slipped between the third and fourth tray; from that cube a thin amber filament of particles arcs right across a dark void toward three small red-orange nodes arranged in a triangle. Behind the trays, dim grey tags hang from a keyring on thin loops. No text, people or organisation logos appear. The OrcaRouter logo is composited in the bottom-right corner.
AI Safety Incidents

sckit: MemTensor's AI Memory Packages Were Backdoored to Steal Agent Credentials and Prompts

Author

Rowan Sterling

Date Published

Latest models · 20View all models →
Benchmarks: Artificial Analysis · updated daily
Back to all posts

Four MemTensor releases published on 23 September 2026 carried the same stripped Go implant, and each of them was the newest version on its registry while it was live. npm's @memtensor/memos-cloud-openclaw-plugin went out as 0.1.21 at 02:23 UTC, 0.1.23 at 03:49 and 0.1.25 at 04:36; PyPI's MemoryOS went out as 2.0.34 at 05:25. All four shipped sckit, which starts when the OpenClaw agent gateway boots, starts again on every memory recall — handed the user's prompt text in an environment variable — and, in the Python package, starts on a plain import of the memos module. It then sweeps the home directory for npm, PyPI, GitHub, GitLab, AWS, Vault and SSH secrets and sends what it finds to servers under skyleen[.]fr. Socket, StepSecurity, SafeDep and Aikido documented the compromise independently on the day, and none of them reported evidence that the implant had run on a real host. The Orca AI Incident Archive holds this entry in full, alongside every other supply-chain incident it has logged, and each record carries its own confidence grade and harm status.

Four releases, and a public bug report in the middle of them

MemOS is MemTensor's open-source memory framework for LLM agents, and the npm package is the connector that lets an OpenClaw agent runtime read and write to a hosted memory service; StepSecurity notes that the same package also declares integration points for the Clawdbot and Moltbot runtimes. Socket measured the repository at about 11,500 stars and 1,100 forks when it published. That position is why the compromise drew four separate investigations: as StepSecurity put it, this "places the plugin inside a process that routinely handles user input and may inherit valuable credentials." The people exposed are the ones who installed either package — developers on their own machines, and CI runners and containers that only ran a test suite.

The npm sequence is the clearest part of the record because the registry timestamps are public and several firms captured them independently. Socket's write-up and SafeDep's timeline agree on every minute: 0.1.21 at 02:23:04, 0.1.22 at 03:45:44, 0.1.23 at 03:49:20, 0.1.24 at 04:33:30 and 0.1.25 at 04:36:58. Versions 0.1.22 and 0.1.24 were clean — Socket says they matched 0.1.20 apart from version strings — and the payload returned three and a half minutes after each of them.

The size jump is the tell. Socket measured 0.1.20 at about 272 KB unpacked and 0.1.21 at 43.6 MB, because the malicious releases bundled six statically linked platform binaries of roughly 7.4 MB each. PyPI shows the same pattern: the 2.0.34 wheel was 19,201,772 bytes against 951,210 for 2.0.33, according to Socket.

At 04:17 UTC — sixteen minutes before the clean 0.1.24 and nineteen before the payload came back as 0.1.25 — a GitHub user named omeridel opened issue #173 on the plugin repository, titled "Malicious versions 0.1.21 and 0.1.23 published to npm — publish token appears compromised." The issue notes that 0.1.23 was then the latest tag, that neither version matched any commit or tag in the repository, and that the most recent legitimate commit was dated 14 August. It remains open with no comments from the maintainers as of 26 October 2026.

A self-built HTML card headed 'Supply chain, 23 September 2026, all times UTC', titled 'Four and a half hours on npm and PyPI', subtitled 'Each malicious release was the registry's newest version while it was live'. Nine rows, each with a time, an event, a detail and a status chip: 00:48 attacker branch first pushed, pipeline; 02:23 npm 0.1.21 published, 43.6 MB unpacked up from 272 KB, malicious; 03:17 MemOS commit b52958f, six sckit binaries and a CI token stealer, malicious; 03:45 npm 0.1.22 published, matches 0.1.20 apart from version strings, clean; 03:49 npm 0.1.23 published, payload restored three minutes later, malicious; 04:17 GitHub issue 173 filed, disclosure; 04:33 npm 0.1.24 published, clean again and the latest tag for three minutes; 04:36 npm 0.1.25 published, payload back and this one takes the latest tag, malicious; 05:25 PyPI MemoryOS 2.0.34, 19.2 MB wheel against 951 KB in 2.0.33, malicious. A footer reads 'Timestamps from Socket and SafeDep, which agree to the minute. Sizes from Socket.' The OrcaRouter logo is composited in the bottom-right corner.

What sckit does once the package is loaded

Neither package used an install hook, which is why the usual advice about --ignore-scripts did not apply. StepSecurity's analysis of the npm variant describes a new file, lib/sckit.js, exporting a launcher that the plugin's existing startup and memory-recall code imports and calls. On startup it spawns a detached .sckit/<os>-<arch>/sckit process with stage0 --config64 and the parent's full environment; on recall it does the same and passes the stripped user prompt through an environment variable named SCKIT_EVENT_TEXT. Socket put the consequence plainly: the binary therefore runs again for every recall.

The PyPI variant is shorter and, in practice, harder to avoid. Socket found that memos/log.py calls a trigger function inside memos/_stage0.py when logging is configured, that 149 modules call the logger at module level, and that a plain import of the memos module is therefore enough to start the payload once per process. StepSecurity's analysis of the same package was purely static — it did not install, import, build or execute it.

The collection scope is the whole home directory. SafeDep lists the inventory root as $HOME and the targeted files as .npmrc, .pypirc, .git-credentials, .netrc, id_rsa, id_ecdsa, id_ed25519, .vault-token, msal_token_cache, access_tokens.json and access_tokens.db, plus environment variables whose names contain token, secret, password, API key or database URL, plus recognised token formats for GitHub, GitLab, npm, PyPI, Hugging Face, Slack, Stripe, AWS and SendGrid. The OSV records for the two packages, MAL-2026-16476 for npm and MAL-2026-16475 for PyPI, describe the same file list and add that the command-and-control protocol uses CBOR, X25519 key exchange and XChaCha20-Poly1305, with signed leases and modules delivered at runtime — so the modules' contents were never observed, only the loader that fetches them.

Two details shape what a defender can still recover. The binary carries self-deletion routines named scheduleSelfDelete and deleteExecutable, according to the OSV record, which is why the guidance is to hunt process and network artefacts rather than files. And 0.1.25 added lib/tls-trust.js, which sets SSL_CERT_FILE to a bundled 145-certificate authority file on minimal Linux systems, according to StepSecurity — a fix aimed at making its own traffic work on containers that lack a system trust store.

A generated abstract illustration on a dark navy field: at left, a dim cyan isometric tree of folders, several leaves carrying small grey key and badge silhouettes; from those keys a cluster of amber threads lifts away, braids into one bright cord and runs right through a narrow vertical cyan gateway frame that carries an amber keyhole, toward three small red-orange receivers with faint pulse rings on the far right. No text, people or organisation logos appear. The OrcaRouter logo is composited in the bottom-right corner.

The release pipeline was the way in

SafeDep's reconstruction is the most detailed account of initial access, and it points at GitHub Actions rather than at a stolen registry password. Between 00:48 and 02:03 UTC, a GitHub account operating as Memtensor-AI created, pushed and deleted a branch named sc/release-0.1.21-20260922-cloud five times. Commit 9b97ec6 modified the workflow script .github/scripts/validate-release-confirmation.mjs so that it appended a BASH_ENV value to $GITHUB_ENV, which made later non-interactive shell steps source an attacker-supplied sckit-publish-bridge.sh before running their own commands. That bridge checked for the package name and the publish step's 150-second timeout, called the token collector with NPM_TOKEN or NODE_AUTH_TOKEN, deleted itself and exited 1 — so the publish failed after the token had been taken. The first malicious npm release appeared twenty minutes after the final branch push.

The PyPI path used the same trick against a different pipeline. SafeDep reports that tag v2.0.34 pointed at unsigned commits that were not on main, and that commit b52958f, authored as "MemTensor CI Review", added the six binaries, a Python loader and a custom build backend called sckit_poetry_build. That backend wrote a BASH_ENV pointing at _pypi_bridge.sh into $GITHUB_ENV; the bridge acted only inside the twine upload wrapper, fetched a signed index from the skyleen[.]fr host, verified an emitter with an Ed25519 implementation, printed a marker line, unset the upload password and exited without uploading anything. About two hours later commit 41bf5c7 removed that call with the message "chore: allow native PyPI upload [skip ci]", the tag was deleted and recreated within ten seconds, and the PyPI upload followed 62 seconds after that. SafeDep reads the first commit as a token-capture run and the second as the run that let MemTensor's own workflow build and publish the malicious package with the project's real token.

Not every account lines up on this. Socket observed that the npm releases came from the same npm account as earlier legitimate versions and lacked a gitHead, which it read as a sign they were not produced by the project's CI, and stated that it could not confirm how the attacker gained publishing access. SC Media, reporting Socket's findings on 24 September, drew the same inference toward account compromise. The two versions agree that publishing credentials were taken from the release pipeline and differ on how the npm artefact itself was produced; nobody has published the workflow logs, because GitHub's Actions API showed no runs after 7 September and SafeDep infers the records were deleted. How the Memtensor-AI account obtained push access in the first place is unknown in both accounts; SafeDep calls a stolen account token likely but unconfirmed.

Established, inferred, and absent

The record's confidence grade is A and it is not disputed, which is worth being precise about, because the certainty attaches to what was published and not to what it did.

• Established: the contents of the four artefacts, reconstructed by static analysis at Socket, StepSecurity and SafeDep; the npm publish times and the latest tag on 0.1.25; the size jump in both ecosystems; the commits, tags and branch history in the two repositories; the skyleen[.]fr infrastructure and the campaign identifiers cloud-openclaw-semi-nuclear and memos-semi-nuclear; and the payload's configuration, which expires on 22–23 October 2026 according to Socket and SafeDep.

• Inferred: that the tool was written with the help of a language model, which SafeDep says directly and calls an inference; and that the stolen publish tokens were meant to be reused, which the configuration's channel string — "exact-ref-one-use-NPM_TOKEN" — supports but does not prove.

• Not established: who the attacker was; whether any copy of sckit ever executed outside a sandbox; whether any credential left a host; whether any prompt typed into an OpenClaw agent reached the attacker; and whether any downstream package was republished. Socket and StepSecurity both worked statically, and StepSecurity states that no process run, no secret transmission, no successful exfiltration, no workflow installation and no further package publication was observed in its analysis.

That gap is why the archive records this incident with real harm marked false. The exposure is real and broad — anyone who installed an affected version is told to treat the host as compromised — but harm, in the sense of a credential demonstrably used or a system demonstrably reached, has not been recorded by any of the four investigating firms.

A self-built HTML card headed 'What the record supports', titled 'Four investigating firms, no confirmed victim', subtitled 'Socket, StepSecurity, SafeDep and Aikido documented the same four artefacts on 23 September 2026'. The left column, headed 'Established by static analysis', lists: four malicious releases, npm 0.1.21, 0.1.23 and 0.1.25 and PyPI MemoryOS 2.0.34; one implant, the same stripped Go binary sckit in all four; the publish window 02:23 to 05:25 UTC with 0.1.25 tagged latest; package growth from 272 KB to 43.6 MB on npm and 951 KB to 19.2 MB for the PyPI wheel; six builds per package for Linux, macOS and Windows on amd64 and arm64; collection scoped to the whole home directory across registry, source-control, cloud, Vault and SSH secrets; exfiltration to servers under skyleen[.]fr; and OSV tracking as MAL-2026-16476 and MAL-2026-16475. The right column, headed 'Not established', lists: the attacker's identity; how Memtensor-AI obtained push access; whether any copy ever executed outside static analysis; whether a stolen secret reached the attacker; whether any prompt text was received; and whether any downstream package was republished. A footer reads 'Archive classification for this record: incident, severity high, confidence A, real harm false.' The OrcaRouter logo is composited in the bottom-right corner.

The worm that was never seen to spread

The binaries contain more than a stealer. SafeDep lists functions named recursivePublish, prepareRemoteNode, prepareRemotePython and prepareRemoteWorkflow, plus an embedded GitHub Actions template triggered on push and the ability to write a workflow file named runtime-update.yml into repositories the implant can reach. Socket found strings about encoding package manifests and installing repository files and assessed that the binaries might be able to republish packages using stolen registry tokens.

That is a worm design. It is not a worm that was observed working. StepSecurity found no evidence that the logic was ever used to propagate, and the Cloud Security Alliance's research note of 25 September reads the capability as limited self-propagation potential rather than confirmed worm behaviour. Several outlets described the case as the first supply-chain worm aimed at AI agent memory infrastructure; the description is fair as a statement about the design, and the reports do not support it as a statement about what happened.

What changed afterwards

The registries cleaned up the same day. Checked on 26 October 2026, the npm package metadata no longer lists 0.1.21, 0.1.23 or 0.1.25 among its versions, the latest tag points at 0.1.24, and the package was last modified at 11:53 UTC on 23 September. Two unusual dist-tags are still attached — clean-inverse-0-1-23 pointing at 0.1.22 and clean-inverse-0-1-25 pointing at 0.1.24 — mirroring the two clean releases that briefly interrupted the malicious ones. On PyPI, MemoryOS 2.0.34 is gone and the newest release is 2.0.33, uploaded on 3 September 2026; there has been no further PyPI release in the seven weeks since.

What has not changed is the project's public account. The repository lists no security advisories, the GitHub issue from 04:17 on the day of the incident is still open and unanswered, and none of the reporting surveyed here quotes a statement from MemTensor or from OpenClaw. Neither malicious package carried a provenance attestation, which SafeDep notes removed the one signal that the artefacts had been built from an unexpected commit.

The response that does exist is on the detection side: StepSecurity added the skyleen[.]fr command-and-control domains to its global blocklist, and the two packages are tracked in OSV as MAL-2026-16476 and MAL-2026-16475.

If an affected version was installed

Socket's guidance is the strictest and the simplest: if any listed version was loaded or imported, treat that host as compromised, including CI runners and containers that only ran tests. The concrete steps the four firms converge on:

• Remove the package and pin to a clean version — npm 0.1.20, PyPI 2.0.33. StepSecurity cautions that its analysis did not establish a clean PyPI baseline, while SafeDep and SC Media both name 2.0.33 as the last good release.

• Look for the process command line, since the executable may delete itself: sckit stage0 --config64, and the environment variable SCKIT_EVENT_TEXT.

• Delete the state directories ~/.openclaw/.cache/runtime and ~/.memos/.cache/runtime, and check repositories for an unexpected runtime-update.yml workflow or .sckit/ directories.

• Rotate every credential reachable from the home directory and the process environment, from a clean machine: npm, PyPI, GitHub, GitLab, AWS, Vault, SSH, Hugging Face, Slack, Stripe and SendGrid, and any .env values. Registry and source-control tokens go first.

• Review the releases of your own packages, in case a stolen publish token was used to ship versions you did not write.

• Block skyleen[.]fr and its subdomains, and read egress logs back to 23 September.

• Treat any prompt or pasted secret that passed through the plugin while it was active as exposed. This is the part of the incident specific to agent memory: the implant did not need to scrape a transcript, because the transcript was handed to it on every recall.