Typosquatting 賭的是你的拼字錯誤。Slopsquatting 賭的是你的 AI 助理。當模型捏造出一個不存在的套件時,攻擊者就把這個名字註冊下來,等你執行安裝。以下是完整攻擊鏈、為什麼你平常的防禦會漏掉它,以及在 npm、Composer 和 pip 上真正能阻止它的方法。
想像一下,一個程式助理剛剛叫你執行這個:
pip install requests-oauth2-helper
看起來很合理。你要的是把 OAuth2 token 掛到 requests 呼叫上的乾淨做法,而這個名稱讀起來正像會做這件事的套件。大小寫很符合慣例,連字號也符合該生態系,看起來就像 PyPI 上另外五十個真實的 helper 套件。於是你執行了。測試通過。你繼續往下做。
問題是,requests-oauth2-helper 在模型學會推薦它的時候根本不存在。是模型自己編出來的。要是攻擊者有在注意,這個名字現在就不是空的了。
這就是 slopsquatting:一種供應鏈攻擊,把 typosquatting 核心的「人為拼字錯誤」換成「機器幻覺」。這個詞由 Python Software Foundation 的 Developer-in-Residence Seth Larson 創造,並在 2025 年被 Ecosyste.ms 的 Andrew Nesbitt 推廣開來。它的概念很小,後果卻很糟:你不再需要手殘打成 reqeusts。你的 AI 會信心滿滿地端給你一個假的套件,而且你還會比相信自己的拼字更相信它。
Typosquatting 不是新東西。你註冊 expres 來搭上 express,註冊 python-dateutl 來搭上 python-dateutil,然後等別人手滑。這招有用,但本質上是機率遊戲,勝率不高。大多數人都會把 express 拼對。攻擊者是在釣那一點點會拼錯的人。
Slopsquatting 則完全不依賴人為失誤。開發者把名稱一字不差地打對了,因為他是從一個現在比自己更可信的來源抄下來:剛剛替他寫出周邊程式碼的助理。錯誤早在上游就發生了,發生在模型身上,而人只是忠實複製它。
關鍵差異在這裡。Typosquatting 是攻擊者猜你的手指可能會滑成什麼字。Slopsquatting 則完全不用猜。他們直接大規模讀取模型實際輸出了什麼名字,然後把常出現的名字註冊下來。模型幫他們做了目標選擇。
如果模型幻覺很少又隨機,slopsquatting 就只是註腳。註冊一個假名字,等到天荒地老,什麼也釣不到。這之所以是個真威脅,是因為這兩件事都不成立。
先看規模。USENIX Security 2025 的研究 We Have a Package for You!,在 Python 與 JavaScript 上針對 16 個 LLM 生成了 576,000 個程式碼樣本,並檢查模型推薦的每一個套件。19.7% 的推薦套件根本不存在。 這不是基準測試邊緣的四捨五入誤差,而是每五個就有一個。研究記錄到 205,474 個不同的幻覺套件名稱。商業模型表現比較好一些(平均至少 5.2%),開源模型則更糟(至少 21.7%),但沒有任何一個乾淨。
就算這些幻覺全都獨一無二,規模也還勉強可控,因為攻擊者不可能註冊二十萬個名字,也不知道哪些名字之後還會再次被人看到。真正致命的發現是:這些幻覺是可重現的。研究者拿 500 個曾產生假套件的提示詞,各自再跑十次。43% 的幻覺套件每一次都回來。58% 在多次執行中出現過。只有 39% 沒有再出現。
想想這件事。這些捏造的名稱有將近一半是穩定的模型行為,不是雜訊。這直接改變了攻擊成本。攻擊者不需要全部註冊,只要去挖模型輸出,把會重複出現的名字留下來,註冊那些最黏的名稱即可。可重現性本身就是偵察。模型一再告訴他們,未來開發者最可能被端上來的假名字是什麼。
警告
這些名字甚至不需要長得像真的套件。研究使用 Levenshtein distance 發現,只有 13% 的幻覺名稱只是和真實套件的簡單拼字錯誤相似。大約 38% 有中度相似度,而將近一半的差異非常大:完全是捏造,但在程式碼上下文中仍然可信。最後那一類,會直接穿過 typosquat 偵測,我們後面會再談為什麼。
這一切都不是理論。它是一條乾淨、可重複的序列,而每個環節都是你在正常工作日看過的事情。
1. 模型建議一個匯入。 你提出一個功能需求。助理寫出程式碼,並找上一個符合問題形狀的相依套件。它吐出一個名稱。這個名稱是捏造的,但語法上很像樣:符合該生態系慣例、大小寫正確、也很有「聽起來像真有這回事」的味道。
2. 你因為名稱看起來合理而信任它。 這是最關鍵的一步,而它是心理層面的,不是技術層面的。AI 自信的輸出會像一位資深工程師信心滿滿的 PR 一樣,輕鬆穿過你的防備。名稱很道地,沒有任何東西會讓你直覺覺得「危險」。你在審查邏輯,而不是檢查一個四個字組成的套件名稱到底是不是真的,因為誰會這樣做?
3. 安裝會執行程式碼。 在 npm 和 pip 裡,安裝套件可能會自動執行腳本:postinstall hook、setup.py build 步驟。攻擊者的 payload 不需要你去 import 什麼,也不需要你呼叫某個函式。install 一完成,程式碼就已經跑了。
4. 憑證被帶出門。 payload 讀取建置代理程式通常手上就會有的東西:環境變數、~/.aws/credentials、~/.npmrc token、GITHUB_TOKEN、.env 檔案。接著把它們用 POST 傳到攻擊者控制的端點。從外面看起來,像是套件在安裝時抓 metadata。從內部看,你的 CI 剛把鑰匙交給陌生人。

整個過程安靜得令人不安。沒有 exploit、沒有 CVE、也沒有什麼巧妙的記憶體破壞。只是開發者相信了一個名稱,然後照著他一天會執行五十次的標準安裝指令跑下去。整個攻擊就是這樣。
而且人們真的會在「信任」這一步上中招,這並不假。資安研究員 Bar Lanyado 在 Lasso Security 注意到模型一再幻覺出一個叫 huggingface-cli 的 Python 套件。他把這個名字 原封不動在 PyPI 上註冊了一個空套件 當作實驗。接下來三個月,這個空套件被下載超過 15,000 次,而 Alibaba 的 GraphTranslator 專案甚至在 README 裡推薦 pip install huggingface-cli,但真正的工具其實是用 pip install -U "huggingface_hub[cli]" 安裝。Lanyado 的套件是刻意做成無害的。slopsquatter 的可就不會了。
簡單講就是「JavaScript、PHP、Go 都會中」。精確地說,是每個生態系都會給攻擊者不同程度的施力空間,而理解差異就知道防禦預算該花在哪裡。
npm 給攻擊者的最多。 生命週期腳本(preinstall、install、postinstall)會在 npm install 時自動執行。這是最典型的攻擊向量,也正因如此,這個生態系終於開始改變。pnpm v10 在 2025 年初 預設停用相依套件的生命週期腳本,npm 也跟進了:npm v12 預設關閉自動腳本執行,並於 2026 年 6 月公布。在你還沒升到這些版本前,postinstall 就像一把上膛的槍對著你的 CI。
package.json(惡意相依套件)
{
"name": "requests-oauth2-helper",
"version": "1.0.3",
"scripts": {
"postinstall": "node ./collect.js"
}
}
pip 緊追在後。 來源分發套件會在安裝時執行 setup.py 來計算 metadata 和建置,也就是說在 pip install 時就能跑任意程式碼。wheel(.whl)不會以相同方式在安裝時執行程式碼,所以 --only-binary 不是小修小補,而是真正的硬化手段。
# 拒絕 source build;只接受預先建好的 wheel
pip install --only-binary :all: requests-oauth2-helper
Composer 在這三者中默默地最安全,這是設計使然。 Composer 只會執行 root package 的 composer.json 裡定義的腳本。相依套件自己的 scripts 區塊會被忽略。所以 slopsquatted 的 Composer 套件沒辦法像 npm 套件那樣,直接丟給你一個自動執行的 post-install-cmd。唯一要注意的是 plugin:惡意 Composer plugin 可以勾住安裝事件,這也是為什麼對不可信的專案樹來說,composer install --no-plugins --no-scripts 很重要。payload 必須等到你的程式真的去呼叫它才會發作,這比「安裝就執行」高了一個明顯門檻。
Go 沒有安裝腳本,所以攻擊鏈就換地方發生。 go get 和 go build 不會在你加入套件的瞬間執行它的任意設定碼。Go 模組中的惡意程式碼,會在你的程式第一次執行它時才跑,例如透過 import 時的 init() 函式,或在 go test 期間。比較晚,但不是永遠不會。
Go 還有自己的變形:模組 proxy。Socket 找到一個 被植入後門的 BoltDB typosquat:github.com/boltdb-go/bolt,它在 2021 年 11 月上傳,接著被 Go module mirror 快取,之後 Git tag 又被改回乾淨程式碼。但 proxy 還是繼續提供快取中的惡意版本。這件事整整過了三年多才被發現。原本為了讓建置可重現而設計的快取,也讓中毒版本更持久。
所以心智模型不是「一種攻擊,三種語言」。而是:npm 和 pip 在安裝時就會引爆,Composer 會等 plugin 或呼叫時才動作,Go 會等到執行時才發作,然後把壞版本永遠記住。slopsquatter 會挑那個最早、最安靜的觸發點。
令人不舒服的地方在這裡。你平常已經在跑的多數控制項,都是為了另一種威脅模型而設計的,而 slopsquatting 則會從它們之間的縫隙溜過去。
鎖定檔只對第一次安裝之後有用。 package-lock.json 或 composer.lock 會鎖定精確版本與雜湊,防止相依套件在你不知情下改變。這對你已經審核過並鎖定的套件很棒。但 slopsquatting 打的是第一次安裝一個全新名稱。鎖定檔裡根本還沒有這個東西,因為你從來沒裝過它。AI 只是三十秒前才把它推薦給你。鎖定檔忠實記錄的是你第一次拉下來的惡意版本,然後把它鎖住。鎖定檔保護的是延續性,不是第一次接觸。
掃描器看的是已知惡意,而這是從未見過的名字。 漏洞掃描器或惡意程式碼資料庫,匹配的是已經被人標記過的套件。昨天才註冊、目標是這季才開始重複出現的幻覺的 slopsquatted 套件,沒有 CVE、沒有通報、沒有名聲、沒有歷史。它只是因為還沒被人發現而顯得乾淨。掃描器沒壞,它只是回答了另一個問題:「這是不是已知威脅?」不等於「這是不是人類自己選的真套件?」
Typosquat 偵測看的是編輯距離,而這些名字有一半根本不像任何東西。 防 typosquatting 的標準做法是量字串相似度:expres 要改成 express 需要幾次編輯?把接近的都標出來。但還記得 Levenshtein 的結果嗎?將近一半的幻覺名稱和任何真實套件都高度不相似。requests-oauth2-helper 距離某個真實東西並不是只差一個字母。它根本不是既有名稱的腐化版本,而是剛捏造出來、只是剛好聽起來合理。既然沒有真正的鄰居可以量距離,偵測器就無從觸發。
把這三者放在一起,就能看出缺口的形狀。這些防禦都假設壞套件要嘛是變更過的(鎖定檔)、要嘛早就被標記過(掃描器)、要嘛像某個真東西(typosquat 偵測)。Slopsquatting 不是這三種。它是一個全新名字,從沒見過,不像任何東西,由一個聽起來很有信心的機器選出來。防禦不是弱,而是瞄錯地方了。
好訊息是,修法並不奇特。它就是你對任何相依套件都該有的習慣,只是現在要用在一個你已經開始過度信任的來源上。你不需要新的產品類別。你只需要停止把 AI 的套件建議當成引用來源。
在套件碰到你的機器前,先確認它真的存在而且有人在維護。 在安裝 AI 叫你用的東西之前,先查一下。它真的存在於 registry 嗎?下載量多少、版本多少、最後發布是什麼時候、是誰在發布、後面有沒有一個明顯由人維護的 repo?slopsquatted 套件通常只有幾天大,下載數卻有個可疑的整數,而且沒歷史可言。花三十秒查看,通常就能擋掉大部分。
# npm:是否存在、誰擁有、多久了?
npm view requests-oauth2-helper
# PyPI:查看專案頁、發布歷史與維護者
pip index versions requests-oauth2-helper
如果第一個指令回傳 404,代表模型沒有幫你找到寶藏,只是自己編了一個。
預設關閉安裝腳本。 這一個控制就能讓攻擊鏈最吵的版本失去作用。把自動執行關掉,讓真正需要的建置步驟成為明確例外,而不是預設:
:::tabs
npm
# 全域關閉生命週期腳本;只有極少數真的需要的套件再白名單放行
npm config set ignore-scripts true
pnpm
# pnpm v10+ 預設就會封鎖相依套件腳本。
# 只允許你真的信任的那些:
pnpm approve-builds
pip
# 優先使用預建 wheel,避免安裝時執行 setup.py
pip install --only-binary :all: <package>
composer
# 不可信的專案樹?不要 plugins、不要 scripts。
composer install --no-plugins --no-scripts
:::
只有大約 2% 的 npm 套件真的需要安裝腳本,這正是生態系決定把安全的預設設為關閉的原因。你幾乎不會失去什麼,卻關上了 payload 原本打算利用的大門。
把所有下載都透過帶白名單的私有 registry。 建置代理不該能直接連到公開 registry,然後把模型說的名字拉下來。前面加一層 proxy(Artifactory、Verdaccio、私有 PyPI、Composer 的 Satis),只允許已審核的套件進來。這樣一個幻覺名稱不會是「安裝後」才失敗,而是「事前」就失敗,因為它從來沒有被批准過。這是可擴展的控制,因為它把決策從「開發者有沒有注意到」移到「這是否在清單上」。
對新版本加成熟期延遲。 pnpm v11 推出了 minimumReleaseAge,預設 1440 分鐘,也就是 24 小時,會拒絕安裝一個版本,直到它在公開狀態夠久,讓社群有時間抓出明顯的惡意程式。追著新幻覺跑的 slopsquatted 套件,正是冷卻期最擅長擋下的東西。
最後,也是最重要的一條:不要因為 AI 建議了,就把相依套件放進 repo。 AI 的套件建議只是個假設,不是來源。把它當成你從沒看過的 Stack Overflow 回答:可能是對的,值得查核,但絕不能一眼就信。整個攻擊的核心,就在於第二步那一瞬間錯放的信任。只要先驗證名稱,攻擊就沒有立足之地。
令人不舒服但必須面對的事實是,模型短期內不太可能停止這種行為。幻覺是這些系統生成文字時的特性,而假的套件名稱從模型內部看起來,和真的套件名稱一模一樣。所以責任落在你身上:那個執行 install 的人。你助理剛給你的名稱,可能是真實函式庫,也可能是攻擊者用真函式庫名字包裝的套件。要分辨它們,唯一的方法就是在安裝前先看一眼;而 slopsquatting 的目的,就是讓你不會這麼做。
P.S. 謝謝你花時間閱讀這篇文章!文中的想法與觀點皆屬於我本人。英文不是我的母語,所以我會使用 AI 來協助修正文法,並讓我的寫作更清楚、更容易閱讀。如果仍有些地方讀起來稍微有點彆扭,還請見諒!
原文首次發表於 nazarboyko.com。
喜歡這篇嗎?歡迎保持聯繫——我在 LinkedIn 上,隨時都很樂意聊天、交換想法,或只是打聲招呼。👋