問題
使用者在手機 SSH 會話裡貼 rm -rf / 跟桌面一樣容易。可能更容易 —— 自動聯想、AI 建議、手忙腳亂誤觸。我們需要一層攔截,在按鍵到達伺服器**之前**生效。
最簡單的想法是建一個「危險字串黑名單」。但 shell 是一門語言,不是一組固定短語。rm -rf /、rm -fr /、rm --recursive --force / 和 r''m -rf / 表達同一個意圖,只是穿著不同的外衣。任何只比對字面文字的方案,第一個加引號的人就能繞過。
我們考慮過的三種方案
- 客戶端正則比對。傳送前比對危險模式。快,但極易繞過 ——
r''m -rf /就能逃過簡單比對。 - 伺服端包裝指令稿。在每臺伺服器上安裝包裝指令稿。可靠,但要修改每臺主機 —— 對於「專門連還沒預配置的伺服器」的行動客戶端來說,這條路根本走不通。
- 混合方案:客戶端解析 + 規範化。對命令分詞、規範化,再比對一套精心維護的規則集。這是我們最終上線的方案。
偵測流水線
偵測在裝置上分三階段執行:規範化、分詞、比對。最複雜的工作在規範化器裡 —— 展開引號、處理常見轉義、按命令分隔符切片,讓每段獨立判定。
def is_dangerous(command: str) -> tuple[bool, str | None]:
tokens = shlex.split(canonicalize(command))
for rule in DANGEROUS_RULES:
if rule.matches(tokens):
return True, rule.reason
return False, None canonicalize() 去掉成對引號(r''m → rm)、合併引號外的反斜線轉義、替換一小組已知安全的環境變數展開,並按 &&、;、| 切分複合命令,讓危險片段無處藏身。每段獨立過規則集。規則是資料,不是程式碼 —— 一個 YAML 包,記錄分詞模式和給使用者看的人類可讀理由。
為什麼這裡模式比對勝過大模型
我們也考慮過用 LLM 分類危險命令。可行 —— 但慢(每次按鍵 300-800ms 延遲)、呼叫量級下成本太高、過度保守:會因為「delete」看起來嚇人就把 find . -delete 攔下來。 手工調過的模式比對延遲在亞毫秒、完全離線、可稽核 —— 你能精確讀到一條命令為什麼被攔。所以我們分工:模式比對負責**偵測**,LLM 負責**解釋**。命令被攔後,AI 助手能用自然語言解釋為什麼,但它絕不在每次按鍵的熱路徑上。
Y/n 自動回應
ShellMon 解決的另一個獨立問題:apt upgrade 問 Do you want to continue? [Y/n],使用者希望它被自動回答而不必一直開著螢幕。難點不是「輸入 Y」 —— 而是怎麼知道會話**在等輸入**而不是還在工作。
# 每條我們啟動的命令都追加一個哨兵
cmd; __ec=$?; printf '\n__TERMAI_END_%s__%d__\n' "<hex>" "$__ec" 哨兵一物兩用。它出現在輸出裡,我們就知道命令結束了,能不解析提示符就拿到結束碼。它**沒**出現而輸出在某種已知提示符([Y/n]、(yes/no))上沉默,我們就有把握會話卡在輸入上 —— 只有這時自動回應規則才觸發。隨機化的 hex 讓哨兵跟程式輸出裡碰巧含「END」的字串不會衝突。
它抓不住什麼
誠實的局限:
- 自訂二進位。我們檢查不了
./my-script.sh裡在做什麼。只檢查呼叫,不檢查內容。 - 管道鏈。很長的管道中間嵌
grep/awk/jq,能把危險模式藏進去。明顯的我們攔得住,對抗式的攔不全。 - 有心人濫用。一個真的**想**抹掉伺服器的使用者,總能找到辦法。ShellMon 是安全網,不是存取控制。
我們發了什麼,接下來做什麼
ShellMon 在 TermAI v0.9 上線。之後我們加了可自訂規則包、Pro 等級的長任務推送/電郵通知、AI 助手的命令解釋整合。
下一步:snippet-aware 模式 —— 如果你跑的是自己片段庫裡的命令,ShellMon 信任度更高、噪音更少。同時我們把規則集開源到 GitHub,讓使用者稽核並貢獻我們遺漏的模式。
Free on iOS and Android. 3 SSH connections + 20 AI calls/day on the free tier.