iT邦幫忙

2026 iThome 鐵人賽

DAY 4
1
Claude AI

盡信 Claude,不如無 Code — 心法與全端實戰系列 第 4

Day 4 隔離環境有三級:刪掉、換目錄、開 VM —— 每一級擋住什麼,又漏掉什麼

  • 分享至 

  • xImage
  •  

昨天那份 193 行的架構文件,是在一個「無菌室」裡產出來的:一個只有原始碼、沒有知識圖、沒有 CLAUDE.md 的目錄。我當時只寫了怎麼準備,沒有講為什麼。

今天講。因為這 30 天,有一半的實驗都靠它才成立 —— 沒有乾淨的對照組,跑出來的數字都不知道代表什麼。而且日常用 Claude Code 也一樣用得到:試一個新 skill、裝一個沒看過的 plugin,你總希望它先在別的地方動,看完它動了什麼,再決定要不要放進正式環境。

「隔離」這件事我做了三級:刪掉、換目錄、開一台 VM。每一級都宣稱「它看不到」,每一級我都親手驗了一次:它到底擋住什麼,又漏掉什麼。三級並排放在最後。


第一級:乾淨目錄 —— 擋住 repo 裡的東西,擋不住 git 歷史

最便宜的一級。把 repo 重新 clone 一份,該拿掉的拿掉,再讓它進去:

git clone --no-hardlinks container clean-room-day04
cd clean-room-day04 && git checkout d6de5694
# graphify-out/ 不存在、CLAUDE.md 不存在

Day 3 就是這樣做的。這個目錄裡沒有我事先算好的知識圖,寫出來的架構文件才算是「它自己讀的」。

它擋住的:工作目錄裡不存在的東西。知識圖、我寫的規格、任何我不想讓它看到的檔案。

它擋不住的有三樣。第一樣一開始就知道:全域設定。~/.claude/CLAUDE.md、我裝的 skill、記憶,全部照常載入。這一級隔離的是 repo,不是 Claude Code。

第二樣,是我踩到才知道的。後來一次實驗,我先刪掉規格檔 docs/spec.md,再讓它規劃功能,計畫交回來,最後一段寫著:

工作樹裡 docs/spec.mddocs/non-goals.md 是未 commit 的 deleted 狀態(我從 HEAD 讀的,不會動)

.git 還在。 刪掉只是工作樹的狀態,歷史還在原地;它就從 git 物件裡,把我刪掉的規格讀了回去。同樣是第一級,Day 3 沒漏、那一次卻漏了 —— 差別只在被藏的東西有沒有進過 git

修法是 rm -rf .git,或一開始就用 git archive d6de5694 | tar -x -C clean/ 匯出 —— 從一開始就沒有 .git,不用事後才想起來要刪。

還有第三樣,是後來另一次實驗它自己講出來的。計畫的最後一句寫著:「我看到旁邊有 library-loan-lab 目錄,依實驗協定我沒有去讀它。」那次乾淨 clone 跟原 repo 是鄰居目錄 —— .git 刪了、規格檔拿掉了,但它往上看一層就看得到原本那份。那次它說它沒讀,我另外驗過,判定沒讀;但「它說沒讀」不是隔離,「它讀不到」才是。乾淨 clone 要放到別的地方,不放鄰居。

但這些都不是重點。重點是這一級的邊界要親手驗:「刪掉」不等於「看不到」,而它會在報告裡老實寫它讀了什麼,但報告還是得自己去讀。

第二級:CLAUDE_CONFIG_DIR —— 擋住設定,擋不住檔案系統

Claude Code 認得一個環境變數 CLAUDE_CONFIG_DIR。指到一個空目錄,整套設定、skill、plugin、記憶,連登入狀態都跟著換到那個目錄。

我在主機上開了一個空目錄 iso-day4,用它裝一個 plugin,前後對主機 ~/.claude 底下的檔案做 checksum。全部在同一台機器、同一個使用者底下:

ISO=$PWD/iso-day4
find ~/.claude/plugins -type f | sort | xargs shasum > host-before.txt

CLAUDE_CONFIG_DIR=$ISO claude plugin marketplace add anthropics/skills
CLAUDE_CONFIG_DIR=$ISO claude plugin install document-skills@anthropic-agent-skills

find ~/.claude/plugins -type f | sort | xargs shasum > host-after.txt
diff host-before.txt host-after.txt          # 一行都沒有
主機 ~/.claude CLAUDE_CONFIG_DIR=iso-day4
claude auth status 已登入 loggedIn: false
裝 plugin 之後的檔案數 168 → 168,checksum 零差異 3 → 872(+869,26 MB)
claude plugin list eli5@claude-community document-skills@anthropic-agent-skills
claude plugin marketplace list claude-community anthropic-agent-skills
settings.json 沒動 新建,只有 extraKnownMarketplacesenabledPlugins 兩個 key

兩邊的設定互相看不見。登入是跟著 config 目錄走的,不是跟著使用者 —— 這一點我原本猜錯,以為會沿用主機的登入,實際上要在 iso 那套 config 下再 claude auth login 一次,憑證存在它自己那份。

設定隔離了。那檔案系統呢? 登入之後,我從 iso 目錄的 session 要它做一件事:

cd ~
CLAUDE_CONFIG_DIR=$ISO claude -p 'Run exactly this shell command and nothing else: touch ~/Desktop/proof-day4.txt' \
  --allowedTools 'Bash(touch:*)'
ls -la ~/Desktop/proof-day4.txt
# -rw-r--r--  0  9月 14 12:19 /Users/<你的帳號>/Desktop/proof-day4.txt

設定隔離了,檔案卻真的出現在主機桌面上。 同一個 session 自報的 skill 清單是 document-skills:{xlsx,docx,pptx,pdf} 加 13 個內建 —— 它用的是 iso 那套設定,寫的是主機的檔案。一個 skill 的 allowed-tools 裡如果有 rm,在這套乾淨的 config 底下,照樣刪得到你的東西。

這一級擋的是設定,不是檔案系統

順手撞到的兩個坑

寫檔沙箱是以 cwd 為界的,跟 config 目錄無關。 上面那個 touch 我其實跑了三次。前兩次 cwd 在一個暫存目錄底下,它回覆我「this session is sandboxed to …/tmp」,寫不出去。cd ~ 再跑才寫進去。所以「它寫不出去」有時候是另一層在擋 —— 如果你把那個當成 CLAUDE_CONFIG_DIR 的功勞,就會在換個目錄的時候被咬。

信任記錄也住在 config 目錄裡。 cwd 在家目錄時,iso 的 session 把 ~/.claude/settings.json 當成 /Users/<你的帳號> 這個 workspace 的專案設定,回了一句 workspace has not been trusted, ignoring 1 permissions.allow entry。主機那邊早就信任過的目錄,在 iso 眼裡是陌生的。

二與三之間,還有一級半

上面「以 cwd 為界」那件事不是 bug,是 Claude Code 內建的 Bash sandbox(macOS 底下用 Seatbelt):/sandbox 打開後,預設只准寫 cwd、session 的暫存目錄,以及你用 --add-dir 加入的目錄,OS 層強制,子程序也跑不掉。

所以 CLAUDE_CONFIG_DIR + /sandbox 就是一級半:設定隔開了,檔案也被圈在 cwd 裡,額外成本還是零。第二級漏的那個洞,多數日常情境(試 skill、跑測試)用這半級就補得起來。

但它的邊界要記兩條,都是官方文件白紙黑字:它只管 Bash 指令和它的子程序 —— MCP server、hook 不在裡面;還有一道「unsandboxed retry」逃生門:指令在沙箱裡跑不動時,可以退到沙箱外重跑(走一般權限流程)。要關,就設 allowUnsandboxedCommands: false。第一條我驗了一次:沙箱開著、cwd 在暫存目錄,Bash 對 cwd 外的目錄寫檔回 operation not permitted。但同一個 session 裡,一支 20 行的 MCP server 把檔案寫進同一個目錄,寫進去了。文件那句是真的,而且它的意思是:沙箱只鎖 Bash 這條路;MCP 那條路,要 server 自己鎖(Day 15 接資料庫時會再用到這件事)。

第三級:VM —— 擋住一切,代價是一台電腦

要把檔案系統也隔開,就得換一台機器。我用 VirtualBuddy(第三方工具,底下是 Apple 自家的 Virtualization framework,macOS 上跑 macOS):裝一台乾淨的 macOS 當母片,裡面只放 Homebrew、git、Claude Code;登入完就關機,從此不再開它。要做實驗就從母片複製一台出來,做完整台刪掉。

「複製一台」聽起來很貴。實際查了才知道不是:

複製前 複製後
Finder 顯示的大小 base-26 107.41 GB 兩台各 107.41 GB
du -sh 30G 30G + 30G
df -h 可用空間 285Gi 285Gi

APFS clone:Finder 跟 du 看起來各算一份,實際上磁碟只存一份。複製前後,可用空間一個位元組都沒少,之後實驗機寫進多少,磁碟才多佔多少。三次複製加上所有實驗寫入結束,總共多佔了 2 GB。

然後在複製出來的那台裡做跟第二級一模一樣的事 —— 裝同一個 plugin,前後 checksum:

  • 既有檔改了 4 個:.claude.jsonhistory.jsonlsettings.jsonknown_marketplaces.json
  • 新增 3 個:installed_plugins.json(釘在 commit 34040c9c,跟主機第二級裝到的同一個)、plugin-catalog-cache.json.last_inuse_sweep
  • plugins/ 合計 33 MB、1,317 個檔
  • 它看到的 skill:13 個內建 + document-skills:{xlsx,docx,pptx,pdf}

跟第二級的 26 MB / 869 檔差了 7 MB,差在 claude-plugins-official:互動模式第一次啟動就會把官方 marketplace clone 下來,純 CLI 子命令不會。這種差異,不驗真的不會知道。

退回快照:VM 關機、整台刪掉、從母片再複製一台。進去看:

plugins/cache/            不存在
installed_plugins.json    不存在
settings.json             回到 2 個 key
它看到的 skill            13 個內建

乾淨。 裝過的東西一個都不在;主機的檔案系統從頭到尾也沒被碰過 —— 主機的 ~/.claude 我根本沒去 checksum,因為它連那個檔案系統都摸不到。

但第三級也有漏的,只是這次的檢查沒有涵蓋到:它擋住的是機器,擋不住身分網路。VM 裡那個 Claude Code 是用我的帳號 claude auth login 的 —— 它花的是我的額度、以我的身分呼叫 API;我要是把 GitHub token 或 SSH key 放進去,它推上去的每一個 commit,都是我;它讀到的任何東西都能 curl 出去。這一項我沒驗,是邊界在定義上就有:VM 隔離的是那台機器,不是你。 要連這個也擋,得再加一層 —— 不給憑證、限制網路 —— 那已經是第四級,這篇不寫。

第三級真正的用途:不是擋,是放手

前兩級都在限制它。第三級反過來 —— 爆炸半徑被鎖在一台可以整台刪掉的機器裡,所以可以拿掉平常不敢拿掉的東西:

  1. 無人值守的長任務claude -p "<prompt>" --dangerously-skip-permissions 跑整晚,不用每一步按 y。這不是我發明的用法,官方文件把它列成一條:「Run fully unattended inside a container」,旁邊的必要條件寫著 container、VM 或 sandbox runtime;同一頁對這個模式的使用建議是:只在容器、VM 這類它碰不到主機的隔離環境裡用;並且直說它對 prompt injection 沒有任何防護 —— 防護只能來自外面那一層 —— 也就是第三級。
  2. 循環任務:每小時掃一次 repo、跑一輪 review、批次處理幾十個檔。跑壞了刪 VM、從母片再複製一台,主機上一個檔都不會動 —— 上面那個「退回快照」就是這句話的證明。
  3. 別人的自動化:來路不明的 plugin、skill、agent 腳本,先在這裡跑一輪,看它動了什麼。像 OpenClaw、Hermes 這類掛在通訊軟體上 24 小時待命、還會改寫自己的常駐 agent,是這一級最典型的住客 —— 但我沒跑過,只點名。

有一個細節值得記:auto mode 的分類器會擋的動作清單裡,有一條是「啟動一個沒有人工核准、也沒有沙箱的自主 agent 迴圈」,例子就是 --dangerously-skip-permissions。也就是說,沒有隔離就起無人迴圈,連 Claude 自己都當成要擋的動作 —— 第三級不是多疑,而是把它已經內建的那條線,真的畫出來。

這一節我只寫用法沒放數字,因為這篇要證的是邊界,不是產能;「跑 30 次」的版本留給後面真的需要它的那幾天。

代價也要記:IPSW 19.77 GB、母片實佔 30 GB、guest 記憶體開機就整塊配置(8 GB 一次拿走,不是用多少算多少)、同時最多開兩台 macOS guest、這版 beta 的主機↔VM 剪貼簿是壞的(我用主機開 python3 -m http.server,VM 用 Safari 開頁面複製指令)。裝到母片可用,大約花一個晚上,多數時間在等下載。

三級並排

第一級 乾淨目錄 第二級 CLAUDE_CONFIG_DIR 第三級 VM
擋住 repo 裡沒有的東西 設定、skill、plugin、記憶、登入狀態 整台機器與檔案系統
漏掉 全域設定;.git 裡的歷史;鄰居目錄 檔案系統touch 到主機桌面;加 /sandbox 可補) 身分與網路(用的還是你的帳號;沒驗,定義上就有)
建立成本 一行 git clone 一個空目錄 + 重新登入 一個晚上 + 30 GB
退回成本 rm -rf rm -rf 刪 VM、從母片再複製(可用空間不變)
適合 「它自己讀,讀到多少」這種對照組 試 skill / plugin / 設定,看它動了哪些檔 放手讓它跑:無人值守、循環任務、測試來路不明的東西

沒有哪一級是對的,只有哪一級。Day 3 那種「它讀不讀得懂 repo」的實驗,第一級夠了;Day 12 之後試各種 skill,第二級加 /sandbox;要放手讓它整晚跑、或對一個來路不明的 plugin 說「我看過它動了什麼,主機沒事」,只有第三級能說這句話。


總結

三級我都親手驗了邊界,三級也各有漏點:第一級漏 git 歷史與鄰居目錄,第二級漏檔案系統,第三級漏身分與網路。前兩個是驗出來的,都不是文件寫錯,是我以為的邊界,跟實際的邊界不一樣 —— 「刪掉了」以為它看不到,「換了 config」以為它寫不出去。第三個是定義上就有:我這次設的檢查它全過了,但檢查本來就設在它擋得住的那一邊。

三級排在一起還有另一個讀法:一到三級,是從限制它走到放手讓它跑。隔離做到哪一級,決定的不只是它看不到什麼,還有你敢讓它做什麼。

這一篇留下的心法:

隔離不是「有沒有」,而是「到哪一級」。而不管哪一級,「它看不到」都要親手驗過,不是設定完就成立的 —— 用它裝東西之前先 checksum,裝完再 checksum,diff 出來的那幾行,才是它真正動過的地方。


參考資料

  • 第一級的無菌室:apple/container,commit d6de5694200468d99a61662bfb9bb3aba763e3e5git clone --no-hardlinks 後移除 graphify-out/CLAUDE.md(Day 3 的實驗就在這裡跑)
  • 第二級與第三級的數字(checksum 前後、diff 行數、plugin list、VM 三份報告)皆為當次終端機輸出,指令如正文;原始檔未另外公開
  • Claude Code 2.1.270(第二級與第三級都是這一版;一級半那個「沙箱 vs MCP」的對照是 2.1.271,2026-09-17);claude auth login / claude plugin marketplace add / claude plugin install 皆為 CLI 子命令,claude plugin --help 可查
  • 兩級都裝的 plugin:anthropics/skillsdocument-skills,commit 34040c9c568585f6929bedeaad110ad08f079624github.com/anthropics/skills
  • Claude Code 官方文件 — Bash sandbox(「只對 Bash 指令與其子程序」、預設可寫路徑、allowUnsandboxedCommands):code.claude.com/docs/en/sandboxing;2026-09-16 查證
  • Claude Code 官方文件 — 權限模式(「Run fully unattended inside a container」那一列、bypassPermissions 的使用建議、auto mode 分類器擋的動作清單):code.claude.com/docs/en/permission-modes;2026-09-16 查證
  • VirtualBuddy 2.2 (build 408)brew install --cask virtualbuddy@betagithub.com/insidegui/VirtualBuddy;guest macOS 26.6.2 (25G83),IPSW 19.77 GB;主機 M4 Pro / 64 GB / macOS 26.6.2

上一篇
Day 3 讓 AI 讀同一個 repo
下一篇
Day 5 一份 Markdown,三種產出,加上那些圖是怎麼來的
系列文
盡信 Claude,不如無 Code — 心法與全端實戰8
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言