昨天那條攻擊從擋不住變成擋得住,中間那個模型是假的。我用一支腳本冒充模型,回答寫死在裡面。
它撐了 25 天,讓整套流程跑得動,不用連網也不用花錢。代價是它說什麼都不算數。
今天換真的。
前面二十五天用的都是雲端模型,所以有一件事沒變過:你每次想讓它看一段程式碼,那段程式碼就得先傳出去。
第 4 天我們為此列了一份清單,寫下哪些東西不可以貼出去。那份清單當時只有一個答案,就是不要貼。
結果最該有人幫忙看一眼的那幾個檔案,剛好是最沒人幫得上忙的。
最後五天把模型搬回你自己的機器上。清單上那些檔案從今天起可以拿去掃,因為推論就在這台機器裡跑完。

圖上兩條路的終點差很多。往外那條到的是一個看得懂你業務邏輯的模型,留在本機這條到的是一份候選清單,後面還得你自己看過。
本機那顆弱很多。換到的好處是推論在自己的機器上跑完,而且我用的腳本不會把待掃的程式碼送去任何模型服務。這五天所有的取捨都是從這裡來的。
這句話的範圍就到這裡。你電腦上還有別的工具,它們會不會傳東西出去是另一回事,真的敏感的程式碼還是要在斷網或受控的環境裡驗。
我用的是 Cisco 放出來的 Antares-1B,Apache 2.0,訓練目標就是在一堆程式碼裡指出弱點在哪個檔案(2026-08 查證)。
它的官方成績很漂亮。在 VLoc Bench 這個評測上,它的 File F1 是 0.209,而 753B 的 GLM-5.2 只有 0.186。
1B 打贏 753B。我看到這行的時候也想直接開始裝。
先講清楚 File F1 是什麼,不然這個數字很容易被讀成準確率。論文的定義是「檔案層級精確率與召回率的調和平均」:它交出一份候選檔案清單,跟真正被修補的那些檔案比對重疊程度。它量的不是「找到了 20.9% 的漏洞」,是「挑檔案挑得準不準」。
但那張表要往下看完。同一張表上有三個成績比它好:GPT-5.5 的高階模式 0.229、它自己的 3B 版本 0.223、GPT-5.5 一般模式 0.221。
這張表上最高的是 0.229,整張表的分數都很低。
所以我不會因為它贏了一個大模型就覺得 0.209 很好用。那個數字說的是這件事很難。
我知道 0.209 算低。但我說不出低長什麼樣子。
第一次自己跑的時候你就會知道差在哪。輸出裡誤報、漏報、錯的行號同時出現,你心裡冒出來的第一句話是「這東西是不是在唬人」,然後就不想再碰了。
所以我打算先讓你看它掃出來的東西,看完再決定要不要用。
先裝起來。以下這組指令只適用 Apple Silicon 的 Mac,因為 MLX 只跑在那上面。Windows、Intel Mac 與 Linux 照貼不會得到下面的結果。官方模型頁另外列了 Transformers 與 vLLM 這幾條路,社群也有人轉了 GGUF(那不是官方發的)。那幾條我都沒有實跑過,連結放在文末的 recipe 裡。
我這次的環境:M4 Pro、24 GB 記憶體、macOS 26.5.2、Python 3.11.8、mlx-lm 0.31.3(2026-08-25)。
pip install mlx-lm
hf download fdtn-ai/antares-1b
python3 -m mlx_lm convert --hf-path fdtn-ai/antares-1b \
--mlx-path ./antares-1b-mlx -q --q-bits 4
跑第二行之前要先去模型頁上填一份表、勾同意條款,欄位是姓名、電子郵件、國家、任職單位、職稱。表單上寫明你填的資料要屬實。
表單文字說申請會經過人工審核,不過那個倉庫的核准模式設定是自動核准(2026-08-25 查 HuggingFace 的 API)。我很早以前就申請過了,沒辦法替你測第一次申請要等多久。
第三行是把權重量化成 4-bit,轉換後的目錄約 1.03 GB。
怎麼確認成功了?打開 config.json,裡面會多一個 quantization 欄,值是 {"group_size": 64, "bits": 4, "mode": "affine"}。沒有那一欄就代表 -q 沒生效,你手上那份是沒壓過的:官方那個 model.safetensors 是 3.67 GB,壓完 1.03 GB,差 3.6 倍。
接著隨便給它一句很短的輸入,看它載得起來、也生得出字。下面這支腳本在文末那份 recipe 裡,3 是跑幾次:
export ANTARES_MLX=./antares-1b-mlx
python3 smoke.py "$ANTARES_MLX" 3
它量的是三個東西:權重載進記憶體花多久、生 20 個 token 花多久,以及 MLX 自己回報的峰值配置量。三次的結果:
回合 載入秒 生成秒 載入後峰值GB 全程峰值GB
1 0.49 0.20 1.034 1.234
2 0.47 0.17 1.034 1.234
3 0.38 0.17 1.034 1.234
為什麼要跑三次?因為我第一次是 2.2 秒,後面幾次都不到 0.5 秒。差別在作業系統的檔案快取,跟模型無關。只寫一個數字給你,你對不上自己的機器,又會以為是自己裝壞了。
到這裡模型跑得動了。這一段你可以整段丟給自己的 AI 工具,套件版本、Python 環境、路徑那些都是環境差異,卡住問它比自己查快。
接下來那段是判斷這個模型的輸出能信到哪裡,那件事不能讓模型代勞。
範例專案裡有七個 JavaScript 檔案,其中五個埋了六個問題,另外兩個是乾淨的,故意留著看它會不會亂報。我給它的指示寫得很白:講出每個具體的問題,一行一條,格式是編號、行號、一句話。
七個檔掃完 33.1 秒,單檔 4.3 到 6.3 秒,過程中記憶體最高吃到 1.49 GB。
先看乾淨的那個。src/format.js 裡面只有金額格式化、日期格式化、字串截斷,什麼都沒埋:
I'll scan the file for obvious security issues: unsafe template literals,
missing validation of untrusted input, use of eval/Function, DOM/JSX sinks,
or improper input handling. The file uses Intl with trusted ISO and currency
codes; no obvious unsafe code. I'll look for any direct use of eval or new
Function, or direct DOM usage. None found.
</think>
CWE-420 | line 3 | Uses Intl NumberFormat to format user input cents; no obvious unsafe code.
你看最後一行。它照我要的格式報了一條,前面掛著編號,後半句自己說「no obvious unsafe code」,翻成中文是「沒看到不安全的地方」。一邊報問題一邊說沒問題。
上面那段自言自語我也原樣留著。我沒叫它把思考過程寫出來,它自己寫了一段,最後補上一個沒有開頭的 </think> 就結束了。
再看它抓對的。src/render.js 把模型回傳的內容直接塞進 innerHTML,五個有問題的檔案裡就屬這個最好認:
CWE-79 | line 4 | Sets innerHTML to user-supplied content without sanitization.
CWE-90 | line 9 | Calls an external API (ask) without validating or sanitizing its output.
第一行的描述完全正確,編號 CWE-79 也對。只有行號寫錯:innerHTML 在第 11 行,第 4 行是在取表單元素。
第二行講的還是上面那個問題,ask() 拿回來的東西沒消毒。但 CWE-90 是 LDAP 注入的編號,這個檔案裡連資料庫都沒有;第 9 行也不是它,那行是寫死的「思考中」。
路徑穿越那個檔案,描述跟編號差得更遠:
CWE-1021 | line 6 | User-controlled path name used to access a file without proper traversal checks
這句話講得很準,path.join 接使用者輸入確實會被 ../ 跳出目錄。但 CWE-1021 是點擊劫持,跟檔案讀寫沒關係;path.join 也不在第 6 行,那行是寫死的上傳目錄。
後來覺得這是這一天最該想清楚的地方。
換大模型或改提示,也許會少錯幾條。但我先去看了它是怎麼訓練出來的。
訓練時的場景是這樣:丟給它一個 CWE 類別的描述,讓它自己下指令翻一整個專案,總共只准下 15 個指令,正確答案取自那些修補安全問題的合併請求。
也就是說,它練了很久的那件事是「給我一個類別,我去幾百個檔案裡挑出值得看的」。
這不是我猜的。剛才那三種錯,正好落在官方跑分沒有涵蓋的範圍。
行號。論文寫得比我狠:
line-level localization and remediation remain outside the current system’s scope.
行號層級的定位,作者寫明不在這套系統的範圍內。
CWE 編號。它的評測是「給一個 CWE 描述,去找出有問題的檔案」。編號是題目給的,不是它要答的。所以它報出來的那個編號,從來沒有被評分過。
乾淨的檔案。這個評測分兩階段:Phase A 給你一個有洞的專案,要你指出哪些檔案;Phase B 給你修補過的程式碼,要模型說出「這裡沒有問題」。而論文寫著:
The experiments in this report focus on Phase A localization; Phase B is included in the benchmark specification but is not evaluated here.
0.209 全部來自 Phase A,而 Phase A 每一題都保證有洞。「認出一個檔案是乾淨的」這件事,這個分數一次都沒有量過。
而我剛才叫它做的,正好是逐檔逐行判斷。換更大的模型也許會少錯幾條,但改不掉這個落差:我要測的是逐檔判斷有沒有問題,官方跑分測的是依已知 CWE 去挑候選檔案。
跑分量的是它練過的那件事。那件事跟你要它做的對不上的時候,分數再高也不能拿來推薦它。
我把六個問題一條一條對過去,指到三個,漏掉三個。兩個乾淨的檔案裡,有一個被它亂報。
這種對照最容易出錯的地方是憑印象。那些輸出你讀過一次,隔一個小時回頭填表格,就變成「它好像有講到吧」。
所以我規定自己:表格每填一列,都要附一句它原話裡的句子,一個字都不能改。我另外寫了支腳本一列一列去存檔裡找,找不到就不讓我過。
今天真的被擋了一次。我在那份能力說明裡寫「行號一條都沒對」,腳本不讓我過:那條被它當成 SQL 注入的,行號寫 6,而那個檔案真正有問題的就是第 6 行。它位置指對了,只是講錯了原因。
我當下的處理方式是把話講小一點,改成「三條指到的裡面,行號一條都沒對」。這樣就過了。
這個處理方式是錯的。我把敘述的範圍縮窄,剛好把對我不利的那條排除在外,剩下的當然全對。昨天那張漂亮的成績單也是這樣來的。
我自己完全沒發現,是找人幫忙審這份程式碼的時候被抓出來的。而且不只這一處,表格裡還漏記了一條它多報出來的東西。
改過的版本是這樣寫的:五條有得對照的候選裡,只有一條行號對,而那一條把原因講錯了。難聽多了,可是不用再多加一句話去圈範圍。
那支腳本只能檢查引文有沒有真的出現在輸出裡。我把敘述範圍縮窄的時候,它不知道我在避開反例。這件事最後是審稿的人看出來的。
環境裝好只是準備工作。今天要留下來的是一份寫成檔案的說明,我自己一個字一個字打的,不讓模型碰:
候選產生器,不是掃描器。它的工作是把該看的檔案從幾百個縮到幾個,判斷是我做。
底下分四段:它做得到什麼、它做不到什麼(每一條都標明是對照表的哪幾列)、我打算怎麼用它、什麼情況下這份說明要重寫。
最後那段是我特別加的。上面每一句話都出自今天這次實測,所以只要換了模型、換了壓縮的位元數、換了提示,或者拿去掃別的語言,這份說明就作廢。
為什麼非得寫成檔案?因為三個月後你不會記得那些誤報長什麼樣子。到時候你只記得自己裝過這個東西,記不得它哪裡不能信。
今天這三個都沒動,因為沒有新的攻擊案例。
另外多了一份本機模型的 9 列人工核對紀錄。3 列有指到已知問題(但編號、行號或原因還是可能錯),3 列該講沒講,1 列在乾淨的檔案上亂講,1 列重複講前面那個問題,剩下 1 列是它連一句「沒問題」都沒交出來。
第 19 天我親手修過一個命令注入的洞。那一類的編號是 CWE-78。
明天我把那個編號、那一類的描述、還有整個專案丟給它,問一句話:同一類的洞,還有沒有漏掉的?
它列出來的每一條都只算候選,我會逐條打開來確認,不會把它的輸出直接當成掃描結果。今天寫下的那句「判斷是我做」,明天就是照這個規矩執行。
這篇引到的來源:Antares 論文 arXiv:2608.02407(跑分、File F1 定義、行號不在範圍那句都出自這裡)|官方模型頁(權重、授權、取得方式)