Search This Blog

Powered by Blogger.

Blog Archive

Labels

Footer About

Footer About

Labels

Showing posts with label memOS. Show all posts

A Go Worm Stole MemTensor's CI Tokens and Shipped Backdoored Packages to npm and PyPI

 



On September 23, 2026, an attacker spent roughly five hours poisoning two packages belonging to MemTensor, the company behind the MemOS operating system for AI memory. By the time a researcher flagged the issue on GitHub at 4:17 AM UTC, malicious versions were already sitting at the top of the npm and PyPI registries, ready to install for any developer who ran a plain `npm install` or `pip install MemoryOS` that morning.

The packages hit were `@memtensor/memos-cloud-openclaw-plugin` on npm and `MemoryOS` on PyPI. Security firm SafeDep, which flagged the incident through its threat intelligence monitoring, found that three npm versions, `0.1.21`, `0.1.23`, and `0.1.25`, and one PyPI version, `2.0.34`, all contained the same Go binary: a credential-harvesting implant the attacker internally called `sckit`, built under the module path `supplychain.local/campaign`.


How the Attacker Got Inside the Pipeline

The attacker did not need a zero-day. Instead, they exploited a well-understood weakness in how GitHub Actions jobs share environment state.

The OpenClaw plugin publishes to npm through a GitHub Actions release workflow that reads its publish token from a repository secret. The attacker, operating through a GitHub account called `Memtensor-AI`, pushed a short-lived branch named `sc/release-0.1.21-20260922-cloud`, made a three-line change to a validation script that runs earlier in the same job, then deleted the branch. They repeated this process five times between 00:48 and 02:03 UTC.

The change was precise: it wrote a `BASH_ENV` entry into `$GITHUB_ENV`, which is GitHub's mechanism for passing environment variables between steps. Because Bash reads the file named in `BASH_ENV` before running any non-interactive script, this let the attacker's shell script execute silently before the real publish step. That script called `collectStageZero()` from within the package itself, passed the `NPM_TOKEN` to the `sckit` binary, then deleted itself and exited with a failure code. The publish step failed visibly, so nothing appeared on npm from that run. The token was already gone.

The PyPI compromise used the same `BASH_ENV` trick but through a different entry point. The attacker pushed an unsigned commit to the MemOS repository that replaced the standard build backend in `pyproject.toml` with a custom wrapper called `sckit_poetry_build`. On import, that wrapper injected its own bridge script into the CI environment. The bridge ran only inside the PyPI upload action's container, captured `INPUT_PASSWORD` (the PyPI token), sent it to a server at `10729e014d0e.skyleen[.]fr`, and then exited cleanly. Two hours later, a follow-up commit removed the capture code, and the next tag push uploaded the fully malicious wheel to PyPI using MemTensor's own legitimate credentials.


What the Package Does After Install

The implant activates at runtime, not at install time, so `--ignore-scripts` offers no protection. In the npm plugin, it fires when the OpenClaw gateway starts and again on every memory recall. In the Python library, it starts the first time `configure_logging()` runs, which happens on nearly every import path. The binary launches detached in the background with no output.

Once running, `sckit` scans the entire home directory for credentials. Its target list, visible in its strings and symbol names, covers `.npmrc`, `.pypirc`, `.git-credentials`, `.netrc`, SSH private keys, HashiCorp Vault tokens, and Microsoft MSAL token caches. Two compiled regular expressions recognize both secret-like variable names and token format patterns for AWS, GitHub, npm, PyPI, HuggingFace, Slack, and Stripe. Collected data goes to subdomains of `skyleen[.]fr`, the campaign's control infrastructure, over encrypted channels using X25519 key exchange and XChaCha20-Poly1305.

The binary also carries worm logic. Functions named `findRepositories`, `prepareRemoteNode`, `prepareRemotePython`, and `recursivePublish` describe how it uses stolen credentials to inject itself into other repositories. It plants a GitHub Actions workflow named `runtime-update.yml` and a `.sckit/` directory into reachable projects, turning each victim into a potential carrier. The campaign configuration encodes an expiry date of late October 2026, suggesting the attacker planned a defined window of operation.


Developers Need to Act Now

Anyone who ran an affected version should treat every credential in their home directory as stolen. That includes cloud CLI tokens, SSH keys, and any `.env` files. SafeDep recommends pinning to `0.1.20` for the npm plugin and `2.0.33` for `MemoryOS`, killing any running `sckit` process, deleting the state directories at `$HOME/.openclaw/.cache/runtime` and `$HOME/.memos/.cache/runtime`, and checking any repository with push access for the `runtime-update.yml` workflow file.

The attack sits inside a larger pattern. The first half of 2026 alone produced 37 supply chain attack campaigns and 497 indexed malicious packages, which is 4.5 times the package volume of the entire preceding year. What separates this incident is the operational sophistication: the attacker used the target project's own CI pipeline as the delivery mechanism, left no workflow run logs behind, and built self-propagation directly into the implant. For maintainers who publish from CI, PyPI's trusted publishing removes long-lived tokens from the job entirely. Required reviewers on release environments would have blocked the MemTensor runs before they started.