MemTensorが公開しているnpmパッケージとPyPIパッケージに改ざん版が公開された。2026年9月25日時点で両レジストリの公開メタデータを直接取得したところ、npmの0.1.21と0.1.23、PyPIの2.0.34以降が消えていることが確認できた。レジストリから消えたということは新規インストールでは掴まないということだが、すでに入れた環境とロックファイルには残る。本記事では公開メタデータで「消えた版」を特定し、自分の環境を機械的に確認する手順を示す。確認コマンドは検体を置いた環境で実際にヒットすることを確かめてある。
まず自分の環境に残っていないかを見る。node_modules を検索対象から外さないことが要点で、npm側の実体は node_modules/@memtensor/ の下に memos-cloud-openclaw-plugin というディレクトリとして展開される。
git fetch --all --prune
find . -name 'memos-cloud-openclaw-plugin' -print
- ・対象:npm `@memtensor/memos-cloud-openclaw-plugin` とPyPI `MemoryOS`。本体のMemOSはstar 11.6kのApache-2.0プロジェクト
- ・実測:npmは0.1.21と0.1.23が404、PyPIは2.0.34以降が存在しない。いずれもレジストリから取り下げ済み
- ・効かない対策:現行npm版にinstall系フックは0個。postinstall監視や `--ignore-scripts` では止まらない類型
- ・やること:ロックファイルと実体の両方を確認し、CIの公開トークンを失効・再発行する
サプライチェーン攻撃の手口の全体像と防御ツールの比較はサプライチェーン攻撃とは|手口・防御ツール比較・npm 12の新機構まで実践解説にまとめている。
タイムライン——公開メタデータから復元できる範囲
レジストリの公開時刻は誰でも取得できるので、外から追える範囲の時系列はここまで確定できる。
| 日時(UTC) | 出来事 | 根拠 |
|---|---|---|
| 2026-08-03 06:46 | npm 0.1.20 公開 | registry のメタデータ |
| 2026-08-17 03:30 | npm 0.1.21-beta.0 公開。以後1か月以上、公開が止まる | 同上 |
| 2026-09-03 11:30 | PyPI MemoryOS 2.0.33 公開(現時点の最新) | PyPI のメタデータ |
| 9月下旬(報告) | npm 0.1.21・0.1.23、PyPI 2.0.34以降として改ざん版が公開 | 報告元 |
| 2026-09-23 03:45 | npm 0.1.22 公開(清浄版) | registry のメタデータ |
| 2026-09-23 04:33 | npm 0.1.24 公開(清浄版) | 同上 |
| 2026-09-25 | 当サイトで取得。該当版は全て404、dist-tags に後片付けの痕跡 |
本記事の実測 |
改ざん版そのものの公開日時は、削除済みのため公開メタデータからは読めない。確定しているのは「9月23日にまとめて清浄版が出た」ことと「その前後の版に穴が開いている」ことの2点で、改ざん版が出ていた期間の長さは報告元の記述に頼る必要がある。
何が起きたか——2つのレジストリで同じ攻撃者が動いた
対象になったのはMemTensorの2つの配布物だ。npmの @memtensor/memos-cloud-openclaw-plugin と、PyPIの MemoryOS である。
本体のMemOSはGitHubでstar 11.6k・fork 1.1kのApache-2.0プロジェクトで、説明文は「LLMとAIエージェントのための自己進化型メモリOS」。エージェントの長期記憶を持たせる部品で、テキストだけでなく画像やツールの実行履歴も扱うと説明されている。デプロイ手段としてマネージドのクラウドAPI、セルフホストのRESTサービス、そしてDeepSeek Harness・OpenClaw・Hermesといったエージェント向けのローカルプラグインが提供されている。npmパッケージの名前に openclaw-plugin が入っているのは、この3つ目の配布経路にあたるためだ。つまり、エージェントに常駐させる性質の部品が狙われている。
報告されている流れを図にすると次のようになる。侵入の入口が開発者の端末ではなくCIのリリース用ワークフローである点が、この型の特徴だ。
GitHub Actions が起動"] B --> C["ワークフローが持つ
npm / PyPI の公開トークンを取得"] C --> D["改ざん版を2つのレジストリへ公開"] D --> E["利用者がパッケージを導入"] E --> F["導入時ではなく
呼び出し時に検体が起動"] F --> G["資格情報を収集
AWS / GitHub / npm / PyPI など"] G --> H["得たトークンで
別のパッケージへ自己増殖"] H --> D
図の最後が最初に戻っているのが厄介なところで、盗んだ公開トークンがそのまま次の配布経路になる。報告によれば、攻撃者はMemTensor自身のGitHub Actionsのリリース用ワークフローにコミットを押し込み、ワークフローが保持していたnpmとPyPIの公開トークンを渡させて公開権限を得たとされる。実装された検体はGo製の多プラットフォーム対応バイナリで、Windows・Linux・macOSで動き、導入時ではなくパッケージが呼び出された時点で実行され、GitHub Actionsと直接公開の両方を使って自己増殖し、AWSキー・GitHubトークン・npmトークン・PyPIトークン・Stripeキーなどを収集する、と説明されている。
攻撃手法と検体の挙動に関する記述は、報告元(Aikidoの記事と、それを報じた海外メディア)に基づく。当サイトの検証環境からは報告元のドメインに到達できず、本文・IoCの原文を直接確認できていない。C2ドメイン・IPアドレス・ハッシュ値の一覧は報告元にあるため、遮断の影響を受けない環境から原典を参照してほしい。以降の「実測」と書いた部分は、当サイトがnpmとPyPIの公開メタデータを直接取得して確認した内容に限る。
レジストリに残った痕跡を実測する——消された版と残った版
報告の真偽を外から確かめる方法がある。レジストリの公開メタデータを見ることだ。改ざん版が取り下げられていれば、版の並びに穴が開く。
npmのメタデータを取得して版の一覧と公開時刻を並べると、次のようになっていた。
| 版 | 公開時刻(UTC) | 取得時の応答 |
|---|---|---|
| 0.1.20 | 2026-08-03T06:46:09Z | 200 |
| 0.1.21-beta.0 | 2026-08-17T03:30:38Z | 200 |
| 0.1.21 | 一覧に存在しない | 404 |
| 0.1.22 | 2026-09-23T03:45:44Z | 200 |
| 0.1.23 | 一覧に存在しない | 404 |
| 0.1.24 | 2026-09-23T04:33:30Z | 200 |
0.1.21と0.1.23だけが飛んでいる。前後の版は200で取得できるので、これは「まだ公開されていない版」ではなく「公開されたあと取り下げられた版」だ。報告にある該当版の範囲と一致する。
さらに dist-tags に後片付けの痕跡が残っていた。通常は latest や beta しか無いところに、次の2つが増えている。
"clean-inverse-0-1-23": "0.1.22"
"clean-inverse-0-1-25": "0.1.24"
タグ名に含まれる番号(0-1-23、0-1-25)と、割り当てられた版(0.1.22、0.1.24)が1つずつずれているのが特徴だ。「取り下げた版の代わりに、この版を使え」という対応関係を機械的に表現したものと読める。npmの dist-tags は本来 latest や beta のような配布チャネルを表すものなので、こうした名前が残っているのは通常運転ではない。
汚染された版に対して清浄な版を割り当てた運用上のタグと読める。0.1.22の公開が9月23日3時45分、0.1.24が同日4時33分と48分差で並んでいることからも、この日にまとめて後始末が行われたことが分かる。2026年8月17日のベータ版を最後に1か月以上公開が止まっていた沈黙期間も、時系列として押さえておきたい。
PyPI側も同じ方法で確認できる。MemoryOS の最新版は2.0.33(2026-09-03公開)で、2.0.34以降は存在しない。取得を試みると404が返る。注意すべきは、PyPIのメタデータで各リリースの yanked がすべて false だったことだ。つまりyank(取り下げフラグ)ではなく削除が行われている。yankなら版は残ってフラグだけが立つので後から履歴を追えるが、削除では何も残らない。外から経緯を追う手段が1つ減る。
なぜ install スクリプトの監視では止まらないのか
npmのサプライチェーン攻撃と聞いて多くの人がまず思い浮かべるのは postinstall だ。導入時に走るフックにダウンローダを仕込む手口は定番で、npm install --ignore-scripts という緩和策も広く知られている。この事案ではそれが効かない。
現行の清浄版(0.1.24)のメタデータを見ると、scripts に入っているのはテスト・版同期・公開補助のものだけで、preinstall も postinstall も prepare も1つも無い。ファイル数18・展開後271,704バイト、bin も main も未定義という構成だ。報告が「実行のきっかけはパッケージの呼び出し時であって導入時ではない」としているのと整合する。
この形の攻撃に対して導入時のフック監視は空振りする。効くのは次の2つだ。
・入っているかどうかを実体で確認する:ロックファイルの記述と、展開済みディレクトリの両方を見る
・公開権限を失効させる:CIが持つnpm・PyPIの公開トークンを再発行し、ワークフローがトークンを渡す経路を見直す
2つ目は自分が被害者側でなくても効く。今回の入口はMemTensorのリリース用ワークフローだったと報告されているが、同じ形のワークフローは無数のリポジトリにある。他人のインシデントを自分の設定を見直す機会として使うほうが、結果的に安上がりになる。
見直すときの軸は「CIに長期間有効なトークンを置かない」だ。npmとPyPIはどちらも、CIの実行そのものを身元として扱い、その都度短命の資格情報を発行する公開方式を用意している。リポジトリとワークフローの組み合わせを登録しておき、公開時に交換する仕組みなので、シークレットとして保管するトークンが存在しなくなる。トークンが無ければ、ワークフローに割り込んで盗むという今回の入口は塞がる。
すぐに切り替えられない場合でも、効く順に手当てできる。
・公開用ワークフローの起動条件を絞る:任意のコミットやプルリクエストから起動しないようにし、タグやリリースイベントに限定する
・公開ジョブを分離する:ビルドやテストと同じジョブにトークンを置かず、公開だけを別ジョブ・別環境に切り出す
・環境の承認を挟む:公開ジョブにレビュー必須の環境を割り当て、人の承認なしに公開が走らないようにする
・トークンの権限と寿命を絞る:公開対象のパッケージだけに限定し、定期的に再発行する
いずれも今回の事案に固有の対策ではない。パッケージを1つでも公開しているリポジトリなら、今日の午後にでも確認できる範囲の話だ。
自分の環境を確認する——ロックファイルと実体の両方を見る
確認は2系統で行う。まずリポジトリを最新にしてから、npm側とPyPI側をそれぞれ見る。
npmパッケージの実体は node_modules/@memtensor/ の下に memos-cloud-openclaw-plugin として展開される。過去に当サイトが公開した手順では node_modules を除外してしまい必ず空振りする欠陥があったため、ここを検索対象から外さないことを最優先にする。
git fetch --all --prune
find . -name 'memos-cloud-openclaw-plugin' -print
grep -rn "memos-cloud-openclaw-plugin" package-lock.json pnpm-lock.yaml yarn.lock 2>/dev/null
Python側は、インストールされると .venv/lib/python3.12/site-packages/ の下に memoryos-2.0.34.dist-info という版番号つきのディレクトリが残る。該当版が入っていればここで見つかる。
find . -name 'memoryos-2.0.34.dist-info' -print
grep -rn "MemoryOS" requirements.txt requirements-dev.txt poetry.lock uv.lock 2>/dev/null
なお、この2系統の検索は「該当版が入っていたか」までは判定しない。ディレクトリ名に版番号が入るPython側は版まで分かるが、npm側のディレクトリ名は版を含まないので、見つかったあとに node_modules/@memtensor/memos-cloud-openclaw-plugin/package.json の version を読むか、ロックファイルの記述を確認する必要がある。まず「在るか」を広く取り、次に「どの版か」を絞るという順序にしておくと、除外条件の書き間違いで見落とす事故が減る。
見つかった場合の対応は、削除して清浄版へ上げるだけでは足りない。報告されている検体は資格情報を収集するので、その環境から触れたトークンをすべて失効させる必要がある。AWSのアクセスキー、GitHubのトークン、npmとPyPIの公開トークン、決済系のキーが対象に挙がっている。CIで動いていたなら、CIのシークレットも同様だ。
検証環境:Linux(x86_64)/2026-09-25。npmとPyPIの公開メタデータをHTTPで直接取得し、版の一覧・公開時刻・応答コード・dist-tags・yanked を確認した。検体そのものは入手していない(両レジストリから削除済みのため)。したがって検体の挙動・C2・ハッシュ値は報告元の記述であり、当サイトの実測ではない。上記の確認コマンドは、検体を置いたサンドボックスでヒットし、置かないサンドボックスでヒットしないことを機械で確認している。
影響範囲と、いま取るべき対応
影響を受けるのは、該当版を実際に入れた環境だけだ。範囲を切り分けるとこうなる。
| 状況 | 影響 | 対応 |
|---|---|---|
| これから入れる | なし(該当版は削除済み) | npmは0.1.24、PyPIは2.0.33を使う |
| ロックファイルに該当版を固定している | 再現インストールで失敗するか、キャッシュから復元される | ロックを更新し、キャッシュを確認する |
| 該当版を導入済み | 資格情報の収集対象 | 実体を削除し、触れたトークンを全て失効・再発行 |
| CIで導入していた | CIのシークレットも対象 | CIのトークンを再発行し、公開経路を見直す |
見落としやすいのは2行目だ。レジストリから消えても、ロックファイルの記述とローカル・CIのキャッシュは残る。「新規インストールで掴まない」ことと「自分の環境に無い」ことは別である。
キャッシュの残り方はツールによって違う。npmはダウンロードしたtarballを内容ハッシュで保持し、pnpmは共有ストアに実体を置いてプロジェクト側はそこへリンクする。どちらもレジストリ側が削除されても手元のコピーは消えないので、ロックファイルが該当版を指したままだと再現インストールでそのコピーが使われうる。CIで依存関係のキャッシュを保存している場合も同じで、キャッシュキーが変わらない限り古い実体が復元される。
やるべき順序は、ロックファイルの更新、ローカルとCIのキャッシュの破棄、そして展開済みディレクトリの削除の3つだ。順序を逆にすると、削除した直後の再インストールでキャッシュから同じものが戻ってくる。
もう1点、本体リポジトリの状態も確認したほうがいい。2026年9月25日時点でMemOSのGitHubリポジトリを開いた限り、セキュリティアドバイザリや固定された告知は確認できなかった。star 11.6kの規模のプロジェクトでレジストリ側の後始末が先行している状態なので、利用者側は上流の告知を待たずに自分で確認する前提で動くのが安全だ。
上流が動くまでの間にできることもある。該当パッケージをこれから使うかどうかは別として、依存に入れているなら版を固定し、更新は内容を見てから取り込む運用に切り替えておくと、次に同じことが起きたときの露出時間が短くなる。自動更新をそのまま本番へ流している構成が、この型の攻撃では最も速く踏む。
同種の事案の確認手順は当サイトでも繰り返し扱っている。npmで悪性版が複数公開された事例は@7nohe/openapi-react-query-codegenに悪性版10種に、別のレジストリでの同型の事案はpub.dev汚染パッケージのXCSSET混入を実測にまとめてある。手口は違っても、確認の型は同じだ。
まとめ——消えた版は環境に残る
レジストリからの削除は攻撃の終了ではなく、外から見える証拠が減るだけだ。今回はnpmの版の並びに開いた2つの穴と、dist-tags に残った後片付けのタグ、PyPI側の版の断絶という形で痕跡が読めた。
入っていたら:削除と更新だけで終えず、その環境から触れたトークンをすべて失効・再発行する
入っていなくても:CIのリリース用ワークフローが公開トークンをどう扱っているかを見直す。今回の入口はそこだったと報告されている
本記事で当サイトが実測したのは、レジストリの公開メタデータから読み取れる範囲に限られる。検体の挙動・通信先・ハッシュ値は報告元の記述であり、検証環境からは報告元のドメインに到達できなかったため原文を確認していない。IoCを使った照合が必要なら、遮断の影響を受けない環境から原典を参照してほしい。
参照ソース
・npm registry: @memtensor/memos-cloud-openclaw-plugin — 版の一覧・公開時刻・dist-tags(2026-09-25取得)
・PyPI: MemoryOS — 版の一覧・yanked の状態(2026-09-25取得)
・MemTensor/MemOS(本体リポジトリ) — プロジェクトの説明・star・ライセンス(2026-09-25確認)