跳到主要內容

【2026 最新】本機 LLM 安全怎麼做?vLLM/SGLang 推論引擎硬化 7 招

最後更新: ·
本機 LLM 安全硬化實戰:vLLM 與 SGLang 的 token、parser 到主機權限防線

你把模型權重下載到自己的 GPU 主機,再用 vLLM 或 SGLang 開成 API,資料確實不必先送往雲端;但本機 LLM 安全不會因此自動成立。當模型輸出經由有漏洞的 parser/dispatcher 形成執行路徑,而 runtime 又位於高權限容器時,原本只是文字的 token 才可能進一步影響服務或主機。

這篇專為第一次維運推論服務的讀者寫:不公開攻擊 payload,也不假裝重現主機接管;我們會把已證實的漏洞、仍屬假說的情境分開,再用 vLLM/SGLang 的現行官方控制、Docker 最小權限與無破壞測例,做出一套能重跑的硬化流程。

先記住唯一的核心式:本機 LLM 安全=把模型輸出當不可信輸入 × 把推論 runtime 當低權限服務。前半段避免 token 被誤當指令;後半段在 parser 出錯時,把 process 可取得的檔案、網路與主機權限壓到最低。容器仍共用 host kernel/GPU driver,不能保證阻止 escape。

先說結論:你真正要守的是「解讀文字的程式」

  • 模型權重不必藏惡意程式,風險仍可能存在:外部 messages/tools 先經 chat template 形成模型輸入;生成 token 後,reasoning/tool-call parser 與應用程式 dispatcher 才處理輸出。任何一層把不可信字串交給危險函式,都可能成為執行路徑。
  • 已有具體漏洞,但證據邊界很重要:官方 advisory 證明的是特定舊版與設定下的執行路徑,並未證明模型曾自主規劃與利用該漏洞;它也不等於所有模型、所有 parser 或 SGLang 都會中招。
  • 容器不是安全貼紙:非 root、唯讀 root filesystem、能力移除、私網與資源上限要一起用;GPU container 仍共用 host kernel 與驅動。
  • API key 不是整台伺服器的防火牆:vLLM 官方明說內建 key 並不保護所有路徑;兩套引擎都應放在只開放必要 endpoint 的反向代理後面。
  • 測試不需要攻擊主機:先用截斷 JSON、未知工具與錯誤型別驗證 gateway 會拒絕,再在隔離環境對真正的引擎 parser 做有上限的穩定性、health 與告警測試。

本機 LLM 安全為什麼會從 token 一路碰到主機?

把 LLM 想成一位被綁在椅子上、只能說話的天才。外部 messages、角色與 tools 先由 chat template 整理成模型輸入;模型生成 token 後,推論引擎再把它還原成文字,reasoning/tool-call parser 抽出結構、工具名稱與參數,最後應用程式才決定是否真的呼叫檔案、網路或 Shell。token 是它說出的音節,本身沒有 Unix 權限;問題出在旁邊的翻譯員與執行員。

本機 LLM 從 messages 與 chat template、生成 token、parser、tool call 到推論主機權限的五段攻擊面
token 只有在後續程式把它解讀為控制訊號,並且該程式握有權限時,才會跨到主機。

2025 年公開的 vLLM CVE-2025-9141 官方公告提供一個清楚案例:受影響範圍是 >=0.10.0,<0.10.1.1,要同時啟用自動工具選擇、指定 qwen3_coder parser,並遇到未被辨認的參數型別,才會走到 parser 內不安全的 Python eval()。修補版是 0.10.1.1。

這個漏洞證明「模型產生的工具參數 → parser → 程式碼執行」在特定舊版與設定下是真實路徑;但公告描述的攻擊者是能誘導特製工具參數的已認證使用者,並沒有證明當代模型曾自主找到漏洞、規劃 payload 再接管主機。提出這個威脅模型的 Boyd Kane 分析也明確保留不確定性。正確態度不是恐慌,而是把「可能的路徑」變成可驗收的邊界。

先分清楚 4 種入口,別把所有風險都叫「惡意 token」

  1. 外部請求:使用者提供 prompt、圖片、音訊、影片或 URL;歷史上多媒體 decoder 與 URL fetcher 都出現過 RCE、SSRF 或資源耗盡問題。
  2. 模型供應鏈:模型 repository 可能帶自訂 Python、template、tokenizer 或舊格式權重。即使 trust_remote_code 關閉,也不能把未知模型與可寫 cache 當成可信程式庫。
  3. 模型輸出:token 經 reasoning/tool parser 轉成結構化資料;CVE-2025-9141 就位在這一段。
  4. 控制面與工具:分散式通訊、LoRA/權重更新、profiler、gRPC、插件與真正的工具 executor,往往比聊天 endpoint 更有權力,也更不該公開。

換句話說,「只跑純文字模型」可以縮小多媒體入口,卻沒有消除模型供應鏈、parser、管理 endpoint 與容器權限。若你還在選引擎,可先看 LLM 推論引擎完整比較;硬化前也要依 本機 LLM 顯存指南估算資源,避免把記憶體不足誤判成攻擊或 parser crash。

本機 LLM 安全硬化 7 層:從版本到復原

1. 版本層:鎖 release、image digest 與模型 commit

截至 2026 年 8 月 28 日,兩個專案最新穩定 release 分別是 vLLM v0.28.0(8 月 26 日)與 SGLang v0.5.18(8 月 22 日)。這只是查核快照,不是「最新版已無漏洞」的保證;上線前仍要重看 release、專案 advisory、CVE 與實際 image digest。

docker pull vllm/vllm-openai:v0.28.0
docker image inspect vllm/vllm-openai:v0.28.0 \
  --format '{{index .RepoDigests 0}}' > vllm-image-digest.txt

# 產出必須是 image@sha256:…;Production 改用這個不可變引用
grep -Eq '^(docker\.io/)?vllm/vllm-openai@sha256:[0-9a-f]{64}$' \
  vllm-image-digest.txt

SGLang 官方 image 依硬體有不同 tag;先從該版安裝文件選對 runtime,再用同一個 RepoDigests 方法保存收據。模型也要固定 repository revision/commit,先在下載區完成掃描,再以唯讀 mount 提供給推論服務。模型升級與退役的回歸方法,可接著用 Model Retirement Eval管理。

2. 容器層:非 root、唯讀、零額外 capability

vLLM 官方 CUDA image 預設仍以 root 執行,但提供 UID 2000--user 2000:0 使用。下面是純文字模型、host loopback Lab 的起始範本,不是每台機器都能直接照搬;先把 GPU-UUID-HERE、模型路徑與剛才保存的 digest 換掉,再在拋棄式主機做相容性測試。

docker run --detach --name vllm-contained --init \
  --gpus 'device=GPU-UUID-HERE' \
  --env NVIDIA_DRIVER_CAPABILITIES=compute,utility \
  --env VLLM_MAX_N_SEQUENCES=64 \
  --env VLLM_MEDIA_URL_ALLOW_REDIRECTS=0 \
  --env VLLM_PLUGINS= \
  --user 2000:0 \
  --read-only \
  --mount type=bind,src=/srv/models/verified-model,dst=/models/model,readonly \
  --tmpfs /home/vllm/.cache:rw,nosuid,nodev,size=16g,uid=2000,gid=0,mode=0770 \
  --tmpfs /tmp:rw,nosuid,nodev,size=1g,mode=1777 \
  --security-opt no-new-privileges=true \
  --security-opt seccomp=builtin \
  --cap-drop ALL \
  --pids-limit 512 --memory 64g --memory-swap 64g --cpus 16 \
  --shm-size 16g \
  --publish 127.0.0.1:8000:8000 \
  vllm/vllm-openai@sha256:REPLACE_WITH_VERIFIED_DIGEST \
  --model /models/model --host 0.0.0.0 --port 8000 \
  --no-trust-remote-code --disable-fastapi-docs

64g16g 與 CPU 數只是示例,要按模型與批次壓測調整;唯讀、ephemeral cache、seccomp 與 cap-drop 的完整組合也必須對該 digest 做啟動回歸,確認所有必要寫入路徑都可用。用指定 shared-memory 容量,是為了避免直接共用 host IPC;不要因啟動失敗就加入 --privileged、host PID/network/IPC,或掛載 /var/run/docker.sock

--user 2000:0 只代表 container 內非 root,不等於 Docker daemon 已用 rootless mode。CPU、host RAM 與 PID 上限也不會限制 GPU VRAM/算力;指定 GPU UUID 只縮小裝置可見範圍。SGLang image 的 Unix UID 與可寫路徑要逐版回讀,不能照抄 vLLM 的 2000:0

這個 Lab 的 proxy 要拒絕所有遠端媒體欄位;若業務真的需要多模態 URL,才在 vLLM 命令加入精確的 --allowed-media-domains media.example.com。不要把「旗標未設定」解讀成遠端媒體已被拒絕。

3. 網路層:入口只到 proxy,出口預設拒絕

上面的 127.0.0.1:8000:8000 只把 host-published port 綁在 loopback;同一 Docker network 的 container 仍可能直連服務,default bridge 出口也未受限。Production 應把 proxy 與引擎放在專用 backend network,由 nginx、Envoy 或 Gateway 明確 allowlist 需要的聊天/completion 路徑,再做身份驗證、request size、rate limit 與日誌。vLLM 的 官方安全指南明說 --api-key 不保護整個 server,分散式與 KV 通訊也應只存在隔離網路。

SGLang v0.5.18 的 HTTP 預設 host 是 127.0.0.1;若在 container 內為了 port forwarding 改成 0.0.0.0,host 端仍要只綁 loopback 或私網。內建 --api-key--admin-api-key 只能當內層控制;由於該版 /server_info 原始碼仍回傳完整啟動設定,代理層應拒絕這個路徑,以及權重更新、LoRA-from-tensors、profiler、dumper 等控制面,只放行實際使用的 inference API。

完全離線的 parser 單元測試可直接用 --network none,Docker 此時只建立 loopback。需要提供 API 時,則用 default-deny firewall/NetworkPolicy,逐條開放 proxy ingress;Kubernetes 的 NetworkPolicy 只有在 CNI 實際支援並執行時才有效。模型先在另一個受控流程下載,推論 container 不帶 Hugging Face token,也不需要任意網路出口。SGLang v0.5.18 的 SGLANG_USE_PICKLE_IPC 仍預設為 true;CERT/CC 建議在相容時設為 false,但分散式功能要先在固定版本做回歸。

4. Parser 層:不用的 tool 與自訂程式一律不開

如果服務只做文字 completion,就不要加入 vLLM 的 --enable-auto-tool-choice--tool-call-parser--tool-server;SGLang 維持 --tool-call-parser--tool-server 未設定,並確認 container 沒有意外繼承 EXA_API_KEY。這不是因為功能一定危險,而是每多一個 parser、插件或執行器,就多一段要更新與測試的可信程式碼。

真的要用工具時,把推論引擎與 executor 拆成兩個服務:引擎只能回傳結構化資料;gateway 只接受固定工具名稱與 JSON schema;executor 使用參數陣列,不把字串拼進 Shell,並在另一個低權限 sandbox 執行。這就是 AI Agent Harness 的 guardrail 概念;實作 dispatcher 前也可先看 最小 Agent Harness 偽代碼

5. 資料層:模型、cache、媒體與 Secret 分開

  • 模型:固定來源與 commit,預先下載,唯讀掛載;沒有明確需求就不開 remote code。
  • cache:視為可信程式資產,只讓服務帳號寫;不要與陌生使用者或多租戶共用。發生可疑 process RCE 後要銷毀 cache,不能把舊 volume 再接回乾淨容器。
  • 遠端媒體:vLLM 使用精確的 --allowed-media-domains,並關閉 redirect;SGLang v0.5.18 也提供 hostname allowlist 與檔案大小上限,但未設定時仍允許任意 domain 的 HTTP(S) 媒體。gateway 還要拒絕非預期 local path/data: URL;egress policy 只負責限制外連。
  • Secret:只交給真正需要的 proxy/executor,不放進模型 prompt、掛載的 home、crash dump 或高詳細度 request log。完整方法可參考 AI Agent 密鑰安全指南

6. 資源與觀測層:限制 request,也限制失敗方式

資源耗盡不必拿到 Shell,也能讓服務失效。vLLM 官方文件目前列出 VLLM_MAX_N_SEQUENCES 預設 16,384,並以 64 或 128 作為公開服務的調整示例;它只限制 completions/chat completions 的 n,不取代 body size、prompt token、concurrency 與 rate limit。不要盲抄數字,要依正常批次、延遲與 GPU 記憶體壓測。SGLang 可用 --max-running-requests--max-queued-requests--max-total-tokens 管理排程/token memory pool,再以 SGLANG_MAX_NEW_TOKENS_LIMIT 限制單次輸出;disaggregation mode 需另查哪些 queue 參數會被忽略。

至少監看 parser 拒絕率、未知 tool name、4xx/5xx、process restart、GPU OOM、延遲與 queue depth。SGLang 的 request logging 預設關閉;若為除錯開啟,要同時設定 --log-requests-level 0 才是 metadata-only,不要沿用會記部分 input/output 的預設 level 2。watchdog 能讓卡住的 process 結束,外部 supervisor 才負責退避重啟;兩者不能混為一談。

7. 復原層:準備「重建」,不要在可疑容器裡修補

保存 image digest、模型 commit、啟動參數、proxy route 與測試收據。若 parser 異常、來源不明的 tool call 或控制面存取觸發事件,先隔離節點、保全必要日誌、輪替可能接觸到的 key、銷毀可疑 cache,再從已驗證 digest 與空白 cache 重建。獨立 GPU node 可以縮小資料與憑證的爆炸半徑,但 container、MIG 或 GPU time-slicing 都不能替 host kernel/driver 提供完整隔離。

vLLM 與 SGLang 推論服務的版本、容器、網路、parser、資料、觀測與復原七層硬化架構
七層不是七個開關,而是七份可回讀的證據;任何一層失效,下一層仍要限制影響範圍。

無破壞測試:先驗證 gateway fail closed,引擎 parser 另做隔離測試

不要拿 Production GPU 主機做漏洞重現。最安全的第一輪,是在無 GPU、無 Secret、--network none、唯讀 source tree 的小 container,直接測你自己的 gateway/dispatcher。以下 Python 解析 raw JSON,以 spy 取代真實工具,驗證合法 call 會被路由,而截斷、未知、錯型別、非 object 與多餘欄位都會被拒絕:

import json

CASES = [
    ('{"name":"lookup_order","arguments":{"order_id":"A-100"}}', True),
    ('{"name":"lookup_order","arguments":{"order_id":', False),
    ('{"name":"unknown_tool","arguments":{}}', False),
    ('{"name":"lookup_order","arguments":{"order_id":["A-100"]}}', False),
    ('[]', False),
    ('{"name":"lookup_order","arguments":{"order_id":"A-100"},"extra":1}', False),
]

def validate_tool_call(call):
    if not isinstance(call, dict):
        return False
    if set(call) != {"name", "arguments"}:
        return False
    if call["name"] != "lookup_order":
        return False
    args = call["arguments"]
    return (isinstance(args, dict)
            and set(args) == {"order_id"}
            and isinstance(args["order_id"], str)
            and len(args["order_id"]) <= 64)

spy_calls = []
def dispatch(raw):
    try:
        call = json.loads(raw)
    except json.JSONDecodeError:
        return False
    if not validate_tool_call(call):
        return False
    spy_calls.append(call)  # 只記錄,不執行真實工具
    return True

for raw, expected in CASES:
    if dispatch(raw) is not expected:
        raise SystemExit(f"FAIL: {raw!r}")

if len(spy_calls) != 1:
    raise SystemExit(f"FAIL: spy_calls={len(spy_calls)}")

print("PASS: 1 routed to spy, 5 rejected, 0 real tools executed")

這不是 vLLM 或 SGLang 漏洞重現,也沒有測到引擎 crash handling;它只驗收「引擎回傳之後,gateway 不會把奇怪資料送進真實工具」。接著才在隔離 container 對部署版本的 parser 做 upstream unit test 或私有 fuzz:使用空字串、截斷 JSON、重複 delimiter、引號/括號不平衡、Unicode normalization、串流 chunk 邊界、未知欄位與有上限的 nesting;危險 sink 全部換成會立刻失敗並計數的 stub。

你要保存的 Pass/Fail 收據

docker inspect vllm-contained --format \
  'user={{.Config.User}} privileged={{.HostConfig.Privileged}} readonly={{.HostConfig.ReadonlyRootfs}} network={{.HostConfig.NetworkMode}} ipc={{.HostConfig.IpcMode}} pid={{.HostConfig.PidMode}} caps={{json .HostConfig.CapDrop}} security={{json .HostConfig.SecurityOpt}} memory={{.HostConfig.Memory}} pids={{.HostConfig.PidsLimit}} ports={{json .HostConfig.PortBindings}} mounts={{json .Mounts}} devices={{json .HostConfig.DeviceRequests}}'

docker exec vllm-contained sh -lc \
  'id -u; test ! -e /var/run/docker.sock; test -w /tmp; test -w /home/vllm/.cache'

curl --fail http://127.0.0.1:8000/health

backend health 通過後,還要從 proxy 外側做兩個負向測試:未帶身份呼叫允許的 inference 路徑應得到 401/403;呼叫刻意封鎖的 /version 或管理路徑應得到 proxy 產生的 403/404。這兩項才證明 route allowlist 真的生效,不能由 docker inspect 代替。

  • 隔離:UID 不是 0、root filesystem 為唯讀、capabilities 為 ALL drop、沒有 Docker socket,host 只在 loopback/私網監聽。
  • gateway 正確性:合法工具 call 只抵達 spy;截斷、未知、錯型別、非 object 與多餘欄位全部被拒絕。
  • 引擎穩定性:只有在同版本、同 parser 的 bounded integration test 跑完後,才能記錄「無 uncaught exception/hang,測後 health 通過」。
  • 可觀測性:只有實際接上 staging log/alert 後,才把 request ID、parser/schema 名稱與錯誤類別標為 Pass;不要記完整 prompt、output 或 Secret。
  • 復原:在 Lab 刻意停止 process,驗證 supervisor 的退避恢復與 proxy 503;沒有演練就保持未測。

若任何測例真的碰到 eval、pickle、Shell、subprocess、檔案或網路 sink,就停止公開測試、保存最小 reproducer,依專案的私有安全通報管道回報。公開文章只需留下 mutation 類別與修補後 regression hash,不需要散布可工作的 payload。

上線前 12 項本機 LLM 安全檢查表

  1. 重新確認 vLLM/SGLang,以及 Docker Engine、containerd/runc、Linux kernel、NVIDIA driver/Container Toolkit 的安全更新。
  2. image 以 digest 固定;模型以 commit/revision 固定,兩者都有來源收據。
  3. 推論 process 非 root;root filesystem 與模型 mount 唯讀。
  4. no-new-privileges、default seccomp、cap-drop ALL 與 CPU/RAM/PID 上限已回讀。
  5. 沒有 privileged、Docker socket、host PID、host network 或 host IPC。
  6. 外部只到反向代理;proxy 只 allowlist 必要 inference endpoint。
  7. 分散式、KV、gRPC 與管理 port 不可從不可信網路抵達。
  8. 不用的 parser、tool server、plugin、remote code 與 dynamic LoRA 不啟用;proxy 另行拒絕 profiler、LoRA、權重更新與其他控制面路徑。
  9. 模型 container 沒有下載 token、雲端憑證與任意 egress;媒體 domain、來源型別與大小有限制。
  10. 合法、截斷、未知、錯誤型別與串流邊界測例都留下 Pass/Fail。
  11. parser error、restart、OOM、queue 與拒絕率有告警,log 不保存敏感正文。
  12. 有隔離、輪替、銷毀 cache、重建與回滾流程;至少演練一次,不只寫在文件裡。

常見問題 FAQ

1. 只下載 Safetensors,就能排除本機 LLM 風險嗎?

不能。Safetensors 可縮小權重反序列化風險,但 tokenizer、template、自訂模型程式、cache、parser、媒體 decoder、插件與控制面仍是不同入口。

2. 一般 prompt 能直接讓 vLLM/SGLang 跑 Shell 嗎?

在正常資料流中,純文字 prompt 只是輸入資料。要讓主機執行 Shell,仍須存在額外執行路徑,例如 parser/decoder 漏洞、惡意模型資產、危險插件、可抵達的控制面,或應用程式盲目執行 tool call。這是架構條件,不是對所有版本零漏洞的保證。

3. CVE-2025-9141 代表 SGLang 也有同一個問題嗎?

不代表。該公告針對特定舊版 vLLM 的 Qwen3-Coder tool parser。SGLang 有自己的漏洞紀錄與 parser 實作,必須逐項查核,不能跨專案類推。

4. 用 Docker 跑,就算 parser RCE 也碰不到 host 嗎?

不能這樣保證。容器能限制 process 權限與 mount,但仍共用 host kernel、GPU driver 與部分 runtime。特權模式、Docker socket、host namespace 或過寬 mount 會大幅放大後果。

5. 只綁 127.0.0.1 就安全了嗎?

只解決一部分進站問題。同機其他 process 仍可能連線,容器也可能對外發 request;還需要身份、endpoint allowlist、egress、最小權限與更新。

6. vLLM 的 –api-key 可以取代 reverse proxy 嗎?

不可以。vLLM 官方文件明確指出它不涵蓋整個 HTTP server;proxy 還要負責 endpoint allowlist、額外身份驗證、rate limit、request validation 與日誌。

7. 可以在 Production 直接跑畸形輸出 fuzz 嗎?

不應該。先在無憑證、無網路、低資源、可拋棄的 parser test container 做;Production 只跑已知無破壞、已設上限的 regression case。

8. 獨立 GPU 主機是必要條件嗎?

依風險而定,但對不可信多租戶或會執行工具的服務很值得。它能把日常檔案、憑證與其他 workload 移出爆炸半徑;仍要做 container、網路、更新與監控,不能把獨立主機當成單一防線。

給新手的 5 個重點

  1. token 是資料;parser、dispatcher 與 executor 才把資料變成權限。
  2. 已知漏洞證明路徑存在,不等於模型已經自主接管主機。
  3. 版本固定只能回答「你跑哪一版」;最小權限回答「出錯時能做多大」。
  4. 內建 API key 是一層,不是 proxy、私網與 egress policy 的替代品。
  5. 先做 4 個無害 case:合法、截斷、未知工具、錯誤型別;能穩定拒絕再談更進階 fuzz。

接著閱讀

左右滑動查看更多推薦

結語:先讓文字永遠只是資料

回到開頭的式子:把模型輸出當不可信輸入 × 把推論 runtime 當低權限服務。第一項用嚴格 schema、工具 allowlist 與無害 parser regression 阻止 token 變成命令;第二項用非 root、唯讀、私網、資源上限與可重建映像,限制任何軟體錯誤的後果。

今天不要先追求「絕對安全」:先在拋棄式主機固定一個 image digest,關掉不用的 tool parser,跑完合法/截斷/未知/錯誤型別四個 case,再保存 docker inspect 與 health 收據。想把這套檢查擴充成可重跑的 AI 工程流程,可繼續瀏覽 AlphaLab AI 專區,或到 AlphaLab 課程建立自己的安全 Harness。

ALPHALAB 社群

有問題?來 Telegram 聊

和 Terry、編輯、其他網友一起討論這篇文章。提問、分享觀點,回覆更即時。

加入 Telegram 討論

📩 訂閱 AlphaLab 電子報

每週最多三封:一封 Weekly 週報與最多兩封關鍵 Alpha Signal。

我們不會 spam,隨時可退訂。