<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Permission on Tarragon</title><link>https://tarrragon.github.io/blog/tags/permission/</link><description>Recent content in Permission on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Fri, 19 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/permission/index.xml" rel="self" type="application/rss+xml"/><item><title>6.2 tool use 與 MCP server 的權限模型</title><link>https://tarrragon.github.io/blog/llm/06-security/tool-use-permission-model/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/06-security/tool-use-permission-model/</guid><description>&lt;p>&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/tool-use/" data-link-title="Tool Use" data-link-desc="LLM 透過結構化呼叫外部工具（讀檔、查資料庫、發 API request）來擴展能力的設計、function calling 跟 MCP 是常見實作">Tool use&lt;/a> 跟 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/mcp/" data-link-title="MCP（Model Context Protocol）" data-link-desc="LLM application ↔ 外部 tool server 之間的標準化協議、複用 OpenAI 相容 API 的成功模式">MCP&lt;/a> server 是本地 LLM 對主機資源最大的副作用面。本章把「這個 tool 能做什麼」「MCP server 跑了會碰到什麼檔案」「能不能 rollback」整理成可操作的權限判讀。原理層的副作用範圍 spectrum、可逆性分級見 &lt;a href="https://tarrragon.github.io/blog/llm/04-applications/tool-use-principles/" data-link-title="4.3 Tool use 原理：LLM 跟外部世界互動" data-link-desc="Structured output 是 LLM 跨入工程系統的橋、function calling 取捨、為什麼本地小模型 tool use 表現崩潰">4.3 Tool use 原理&lt;/a>、agent 跟人類審查的協作模型見 &lt;a href="https://tarrragon.github.io/blog/llm/04-applications/agent-architecture/" data-link-title="4.4 Agent 架構原理" data-link-desc="Agent loop 結構、失敗模式、什麼任務適合 vs 不適合、跟人類審查的協作模型">4.4&lt;/a>；hands-on 驗證「LLM 自己沒 FS / shell 權限、wrapper 才有」見 &lt;a href="https://tarrragon.github.io/blog/llm/01-local-llm-services/hands-on/permission-boundary/" data-link-title="Hands-on：Ollama 改檔案 / 寫程式碼的權限邊界在哪" data-link-desc="四組對照實驗：Ollama 自己沒 FS / shell 權限、wrapper 才有；--dry-run / --confirm / --auto 三檔審查粒度的取捨">Ollama 改檔案的權限邊界&lt;/a>。隔離技術見 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/sandbox/" data-link-title="Sandbox" data-link-desc="把程式跑在受限制環境的隔離技術、限制檔案 / 網路 / 系統呼叫權限、是 tool use 跟 MCP server 副作用控制的基礎">sandbox&lt;/a> 卡、權限白名單見 backend &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/allowlist/" data-link-title="Allowlist" data-link-desc="說明如何用明確允許條件控制例外放行範圍">allowlist&lt;/a> 跟 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/least-privilege/" data-link-title="Least Privilege" data-link-desc="說明身份、服務與人員只應取得完成工作所需的最小權限">least-privilege&lt;/a> 卡。本章 framing 是個人 dev 視角；production agent 場景下 tool use 引發的 prompt injection 後果見 &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/llm-prompt-injection-in-agent/" data-link-title="LLM Agent Prompt Injection 後果治理" data-link-desc="production LLM agent 場景的 prompt injection 後果：tool spec 設計、agent loop 限制、review checkpoint、跟 incident workflow 的接合">backend/07 LLM agent prompt injection&lt;/a>。&lt;/p>
&lt;p>讀完本章後、你應該能對自己用的 tool / MCP server 回答：能讀寫哪些路徑、能跑哪些 shell command、能連哪些網路位址、副作用有沒有 dry-run / preview、出錯時怎麼回退。&lt;/p>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;ol>
&lt;li>認識 tool use 跟 MCP server 在三層架構中的位置。&lt;/li>
&lt;li>區分「讀取類 tool」跟「副作用類 tool」的權限判讀差異。&lt;/li>
&lt;li>知道個人 dev 場景下、第三方 MCP server 的信任邊界跟驗證流程。&lt;/li>
&lt;li>用「沙箱 / 白名單 / 副作用可逆性」三個維度評估具體 tool / MCP 的風險。&lt;/li>
&lt;li>認識常見的 tool use 副作用洩漏路徑跟對應的最低防護。&lt;/li>
&lt;/ol>
&lt;h2 id="tool-use-跟-mcp-server-在哪一層">tool use 跟 MCP server 在哪一層&lt;/h2>
&lt;p>tool use 跟 MCP server 同時跨&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/three-layer-architecture/" data-link-title="Three-Layer Architecture" data-link-desc="把本地 LLM 工具拆成介面層、推論伺服器層、模型權重層的基礎心智模型">三層架構&lt;/a> 的兩層、但跟模型本身的權限模型分離：&lt;/p></description><content:encoded><![CDATA[<p><a href="/blog/llm/knowledge-cards/tool-use/" data-link-title="Tool Use" data-link-desc="LLM 透過結構化呼叫外部工具（讀檔、查資料庫、發 API request）來擴展能力的設計、function calling 跟 MCP 是常見實作">Tool use</a> 跟 <a href="/blog/llm/knowledge-cards/mcp/" data-link-title="MCP（Model Context Protocol）" data-link-desc="LLM application ↔ 外部 tool server 之間的標準化協議、複用 OpenAI 相容 API 的成功模式">MCP</a> server 是本地 LLM 對主機資源最大的副作用面。本章把「這個 tool 能做什麼」「MCP server 跑了會碰到什麼檔案」「能不能 rollback」整理成可操作的權限判讀。原理層的副作用範圍 spectrum、可逆性分級見 <a href="/blog/llm/04-applications/tool-use-principles/" data-link-title="4.3 Tool use 原理：LLM 跟外部世界互動" data-link-desc="Structured output 是 LLM 跨入工程系統的橋、function calling 取捨、為什麼本地小模型 tool use 表現崩潰">4.3 Tool use 原理</a>、agent 跟人類審查的協作模型見 <a href="/blog/llm/04-applications/agent-architecture/" data-link-title="4.4 Agent 架構原理" data-link-desc="Agent loop 結構、失敗模式、什麼任務適合 vs 不適合、跟人類審查的協作模型">4.4</a>；hands-on 驗證「LLM 自己沒 FS / shell 權限、wrapper 才有」見 <a href="/blog/llm/01-local-llm-services/hands-on/permission-boundary/" data-link-title="Hands-on：Ollama 改檔案 / 寫程式碼的權限邊界在哪" data-link-desc="四組對照實驗：Ollama 自己沒 FS / shell 權限、wrapper 才有；--dry-run / --confirm / --auto 三檔審查粒度的取捨">Ollama 改檔案的權限邊界</a>。隔離技術見 <a href="/blog/llm/knowledge-cards/sandbox/" data-link-title="Sandbox" data-link-desc="把程式跑在受限制環境的隔離技術、限制檔案 / 網路 / 系統呼叫權限、是 tool use 跟 MCP server 副作用控制的基礎">sandbox</a> 卡、權限白名單見 backend <a href="/blog/backend/knowledge-cards/allowlist/" data-link-title="Allowlist" data-link-desc="說明如何用明確允許條件控制例外放行範圍">allowlist</a> 跟 <a href="/blog/backend/knowledge-cards/least-privilege/" data-link-title="Least Privilege" data-link-desc="說明身份、服務與人員只應取得完成工作所需的最小權限">least-privilege</a> 卡。本章 framing 是個人 dev 視角；production agent 場景下 tool use 引發的 prompt injection 後果見 <a href="/blog/backend/07-security-data-protection/llm-prompt-injection-in-agent/" data-link-title="LLM Agent Prompt Injection 後果治理" data-link-desc="production LLM agent 場景的 prompt injection 後果：tool spec 設計、agent loop 限制、review checkpoint、跟 incident workflow 的接合">backend/07 LLM agent prompt injection</a>。</p>
<p>讀完本章後、你應該能對自己用的 tool / MCP server 回答：能讀寫哪些路徑、能跑哪些 shell command、能連哪些網路位址、副作用有沒有 dry-run / preview、出錯時怎麼回退。</p>
<h2 id="本章目標">本章目標</h2>
<ol>
<li>認識 tool use 跟 MCP server 在三層架構中的位置。</li>
<li>區分「讀取類 tool」跟「副作用類 tool」的權限判讀差異。</li>
<li>知道個人 dev 場景下、第三方 MCP server 的信任邊界跟驗證流程。</li>
<li>用「沙箱 / 白名單 / 副作用可逆性」三個維度評估具體 tool / MCP 的風險。</li>
<li>認識常見的 tool use 副作用洩漏路徑跟對應的最低防護。</li>
</ol>
<h2 id="tool-use-跟-mcp-server-在哪一層">tool use 跟 MCP server 在哪一層</h2>
<p>tool use 跟 MCP server 同時跨<a href="/blog/llm/knowledge-cards/three-layer-architecture/" data-link-title="Three-Layer Architecture" data-link-desc="把本地 LLM 工具拆成介面層、推論伺服器層、模型權重層的基礎心智模型">三層架構</a> 的兩層、但跟模型本身的權限模型分離：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">介面層（VS Code / Continue.dev / CLI）
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">推論伺服器（Ollama / llama-server / LM Studio）
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">模型（GGUF 權重）
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">旁邊另一條：
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">MCP server（獨立 process、自己的權限）
</span></span><span class="line"><span class="ln">10</span><span class="cl">  └── 對檔案 / shell / 網路的具體 API</span></span></code></pre></div><p>關鍵特性：</p>
<ol>
<li><strong>模型本身不執行 tool</strong>：模型只生成 tool call JSON、實際執行由「LLM client」（如 Continue.dev、Claude Desktop）跟 MCP server 完成。</li>
<li><strong>MCP server 是獨立程式</strong>：可以是 Node / Python script、可以呼叫任何系統 API、權限上限是「跑該 server 的 user 的權限」。</li>
<li><strong>權限不是模型給的、是 OS / user 給的</strong>：模型再怎麼「同意」執行 <code>rm -rf /</code>、實際上能不能跑取決於 OS 的權限模型跟 MCP server 自己的 sandbox。</li>
</ol>
<blockquote>
<p><strong>事實查核註</strong>：<a href="https://modelcontextprotocol.io">Model Context Protocol（MCP）</a> 是 Anthropic 在 2024 年底發布的開放協議、各家 LLM client 跟 MCP server 實作的成熟度、權限粒度依版本演進。本章描述以 2026 年 5 月主流實作為基準、引用前以 MCP 官方規格跟各 client / server 的 README 為準。</p></blockquote>
<h2 id="讀取類跟副作用類tool-的權限差異">「讀取類」跟「副作用類」tool 的權限差異</h2>
<p>tool 可以粗分成兩類、權限判讀完全不同：</p>
<table>
  <thead>
      <tr>
          <th>類別</th>
          <th>例子</th>
          <th>主要風險</th>
          <th>個人 dev 場景的接受程度</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>讀取類</td>
          <td>read file、grep、search code、查 git log</td>
          <td>把私密內容讀進 prompt、prompt 被洩漏出去</td>
          <td>較高、但要注意 prompt 傳到哪個 LLM</td>
      </tr>
      <tr>
          <td>副作用類</td>
          <td>write file、run shell、git commit、發 HTTP request、操作資料庫</td>
          <td>不可逆改變、損毀檔案、發送請求、洩漏到外部</td>
          <td>較低、需要 preview / confirm / sandbox</td>
      </tr>
  </tbody>
</table>
<p>讀取類的判讀重點是「<strong>讀到的內容會被傳到哪</strong>」：</p>
<ol>
<li>讀到的 code 變 prompt 的一部分、prompt 送到本地模型→沒外洩</li>
<li>同樣 prompt 送到雲端 LLM→傳到雲端、跟雲端 LLM 的資料政策走（見 <a href="/blog/llm/06-security/cross-cloud-local-data-boundary/" data-link-title="6.4 跨雲端 / 本地的資料邊界" data-link-desc="個人 dev 場景下混用雲端 LLM 跟本地 LLM 時的 prompt 洩漏點：Continue.dev 多 provider 設定、隱私資料流、按敏感度分流的判讀">6.4 跨雲端 / 本地資料邊界</a>）</li>
<li>讀取會被 log→log 累積、需要管理</li>
</ol>
<p>副作用類的判讀重點是「<strong>可逆性</strong>」：</p>
<ol>
<li>write file 蓋掉原內容→可能無法回復（沒備份的話）</li>
<li>run shell <code>rm</code> / <code>git push</code>→不可逆或需要 force pull 才能還原</li>
<li>發 HTTP request、轉帳、call API→送出去就回不來</li>
<li>操作 production 資料庫→可能影響其他人</li>
</ol>
<h2 id="三個維度評估具體-tool--mcp-的風險">三個維度評估具體 tool / MCP 的風險</h2>
<p>對任何 tool / MCP server、可以用三個維度做初步評估：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">┌────────────────────────────────────────────────────┐
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">│ 維度一：沙箱                                       │
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">│   能做什麼 = 跑該 server 的 user 能做什麼          │
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">│   有沒有 chroot / Docker / namespace 隔離？        │
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">│                                                    │
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">│ 維度二：白名單                                     │
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">│   能讀寫的路徑、能跑的指令、能連的網址有沒有限定？  │
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">│   還是 &#34;all paths&#34; / &#34;any shell&#34; / &#34;any URL&#34;？     │
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">│                                                    │
</span></span><span class="line"><span class="ln">10</span><span class="cl">│ 維度三：副作用可逆性                               │
</span></span><span class="line"><span class="ln">11</span><span class="cl">│   出錯能不能 rollback？                            │
</span></span><span class="line"><span class="ln">12</span><span class="cl">│   有沒有 dry-run / preview / confirm？             │
</span></span><span class="line"><span class="ln">13</span><span class="cl">└────────────────────────────────────────────────────┘</span></span></code></pre></div><p>對應的判讀範例：</p>
<table>
  <thead>
      <tr>
          <th>Tool / MCP</th>
          <th>沙箱</th>
          <th>白名單</th>
          <th>副作用可逆性</th>
          <th>個人 dev 評估</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>read_file</code>（讀任意路徑）</td>
          <td>無、user 權限</td>
          <td>無、可讀 user 所有檔案</td>
          <td>N/A（讀取無副作用）</td>
          <td>注意 prompt 走向</td>
      </tr>
      <tr>
          <td><code>read_file</code> 限定 workspace</td>
          <td>無</td>
          <td>有、只讀 workspace</td>
          <td>N/A</td>
          <td>較安全</td>
      </tr>
      <tr>
          <td><code>run_shell</code>（任意指令）</td>
          <td>無</td>
          <td>無</td>
          <td>視指令、<code>rm</code> / <code>git push</code> 不可逆</td>
          <td>高風險</td>
      </tr>
      <tr>
          <td><code>apply_patch</code>（套 diff 到 file）</td>
          <td>無</td>
          <td>限定 workspace</td>
          <td>git stash 可逆、未 stash 不可逆</td>
          <td>中風險、值得用 git track</td>
      </tr>
      <tr>
          <td><code>fetch_url</code>（任意 URL）</td>
          <td>無</td>
          <td>無</td>
          <td>一般 GET 可逆、POST 不可逆</td>
          <td>看具體請求</td>
      </tr>
      <tr>
          <td><code>mcp-server-postgres</code>（直連 DB）</td>
          <td>無</td>
          <td>視 DB user 權限</td>
          <td>改 row 通常可逆、DROP TABLE 不可逆</td>
          <td>DB user 權限要設好</td>
      </tr>
  </tbody>
</table>
<p>實務上、社群常見的 MCP server 多半屬於「白名單較弱」「副作用直接套用」的設計、需要使用者自己加防護。</p>
<h2 id="第三方-mcp-server-的供應鏈信任">第三方 MCP server 的供應鏈信任</h2>
<p>MCP server 是可執行程式碼、信任邊界比 GGUF 模型權重高一個層級。常見的 MCP server 來源：</p>
<ol>
<li><strong>官方 reference server</strong>（如 Anthropic 維護的 <code>@modelcontextprotocol/server-*</code>）：相對較高信任、有官方 maintain。</li>
<li><strong>知名專案的 MCP server</strong>（如 GitHub、Notion、Slack 等公司自己出的）：跟該公司的軟體分發信任度一致。</li>
<li><strong>社群 MCP server</strong>：個人或小團隊維護、信任度視 maintainer 與 download 量、看 code 是基本動作。</li>
</ol>
<p>裝任何 MCP server 前的最低判讀：</p>
<ol>
<li><strong>看 source repo</strong>：是不是知名作者、stars 數、最後 commit 時間、issues 是否活躍。</li>
<li><strong>看實際做什麼</strong>：MCP server 的 README 通常列出提供的 tools、跑起來會碰到的權限。</li>
<li><strong>跑在最小權限環境</strong>：能用 Docker / chroot / <code>nice -n 19</code> 之類就用、不要直接用 root / admin。</li>
<li><strong>不要用 <code>curl | sh</code> 安裝</strong>：用 <code>npm install</code> / <code>pip install</code> / <code>go install</code> 等有 package manager 介入的方式、留下 install log。</li>
</ol>
<blockquote>
<p><strong>事實查核註</strong>：MCP server registry、套件管理工具的供應鏈安全機制依版本演進、Anthropic 跟其他主要 client 廠商可能引入官方 marketplace 或簽章機制、建議引用前以當前 MCP 官方狀態為準。</p></blockquote>
<h2 id="個人-dev-場景的最低防護建議">個人 dev 場景的最低防護建議</h2>
<p>對「我想用 tool use 但又怕 LLM 把檔案搞壞」的工作流、最低防護建議：</p>
<ol>
<li><strong>codebase 用 git track</strong>：所有寫入操作前確認 working tree clean、出問題能 <code>git checkout</code> 還原。<code>git stash</code> 是更輕的選擇。</li>
<li><strong>重要檔案 backup</strong>：dotfile、SSH key、雲端 API key 等不在 git track 範圍的、用 Time Machine / rsync / cloud sync 之類做日常 backup。</li>
<li><strong>跑 LLM agent 時用獨立 user / 容器</strong>：對「想試 agent 但怕」的場景、開個專用 macOS user 或 Docker container、user 沒 sudo、檔案存取限定 workspace。</li>
<li><strong>MCP server 的 config 加白名單</strong>：能設 allowed paths / allowed commands / allowed URLs 的 server 都先設、預設拒絕、按需開放。</li>
<li><strong>看不懂的 tool call 不要 confirm</strong>：Continue.dev / Claude Desktop 等 client 通常會 prompt 使用者確認 tool 執行、看不懂的 JSON 先別按。</li>
</ol>
<h2 id="tool-use-副作用洩漏的常見路徑">tool use 副作用洩漏的常見路徑</h2>
<p>個人 dev 場景常見的 tool use 副作用洩漏路徑：</p>
<ol>
<li><strong>LLM 誤把 secret 寫進 commit</strong>：tool use 帶 <code>git commit</code>、LLM 從 <code>.env</code> 讀到 API key 又寫進 commit message。對應防護：MCP server 加 <code>.env</code> 黑名單、commit hook 掃 secret。</li>
<li><strong>LLM 套用 broken patch 蓋掉檔案</strong>：<code>apply_patch</code> 失敗 / 部分套用、留下無法 compile 的狀態。對應防護：套 patch 前 <code>git stash</code> 或 <code>git add -p</code> 先存 working tree。</li>
<li><strong>LLM 從 issue / PR 內容引發指令</strong>：讀進 issue 的 prompt 內容包含 prompt injection、誘導跑非預期指令。對應防護：tool 跑前明確讓使用者確認（見 <a href="/blog/llm/06-security/prompt-injection-in-ide/" data-link-title="6.3 IDE 場景的 prompt injection" data-link-desc="個人 dev 場景下 IDE 寫 code 工作流的 prompt injection：codebase 內容、外部文件、剪貼簿作為攻擊面、跟雲端 LLM 場景的差異">6.3 prompt injection</a>）。</li>
<li><strong>LLM 觸發 production 操作</strong>：MCP server 連到 production DB、LLM 跑 <code>DROP TABLE</code>。對應防護：production credential 絕對不放在 tool use 可達的環境。</li>
</ol>
<h2 id="給讀者的-tool--mcp-評估清單">給讀者的 tool / MCP 評估清單</h2>
<p>每次裝新 MCP server / 啟用新 tool 之前、跑一次評估：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">[ ] 來源是知名作者 / 官方專案 / 我能 audit 的開源 repo
</span></span><span class="line"><span class="ln">2</span><span class="cl">[ ] README 列出的 tool 列表、跟我的使用情境匹配
</span></span><span class="line"><span class="ln">3</span><span class="cl">[ ] 該 server 跑在最小權限環境（user / sandbox / container）
</span></span><span class="line"><span class="ln">4</span><span class="cl">[ ] 副作用類 tool 有 confirm / preview 機制
</span></span><span class="line"><span class="ln">5</span><span class="cl">[ ] workspace 內容受 git track、能 rollback
</span></span><span class="line"><span class="ln">6</span><span class="cl">[ ] 不放 production credential / SSH key 在該 server 可達的環境
</span></span><span class="line"><span class="ln">7</span><span class="cl">[ ] 啟用後跑簡單測試、確認 tool call 行為符合預期</span></span></code></pre></div><h2 id="下一章">下一章</h2>
<p>下一章：<a href="/blog/llm/06-security/prompt-injection-in-ide/" data-link-title="6.3 IDE 場景的 prompt injection" data-link-desc="個人 dev 場景下 IDE 寫 code 工作流的 prompt injection：codebase 內容、外部文件、剪貼簿作為攻擊面、跟雲端 LLM 場景的差異">6.3 IDE 場景的 prompt injection</a>、處理 tool use 副作用最常見的觸發來源。</p>
]]></content:encoded></item><item><title>Permission 請求時機與措辭</title><link>https://tarrragon.github.io/blog/ux-design/02-gate-fallback/permission-request-timing/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/ux-design/02-gate-fallback/permission-request-timing/</guid><description>&lt;p>系統權限（相機、位置、通知、麥克風）的請求對話框由作業系統控制，app 只能決定「什麼時候觸發」和「觸發前顯示什麼說明」。使用者拒絕後，再次請求不會彈出系統對話框 — 必須引導使用者到系統設定手動開啟。這意味著第一次請求的時機和說明內容直接影響授權率。&lt;/p>
&lt;h2 id="請求時機">請求時機&lt;/h2>
&lt;h3 id="首次開啟時一次性請求">首次開啟時一次性請求&lt;/h3>
&lt;p>App 首次啟動時依序請求所有需要的權限。優點是使用者只被打斷一次；缺點是使用者尚未使用任何功能，不理解每個權限的用途，傾向拒絕。&lt;/p>
&lt;p>這個模式適合權限數量少（1-2 個）且和 app 核心功能直接相關的情境。相機 app 在首次開啟時請求相機權限，使用者能直覺理解原因。&lt;/p>
&lt;h3 id="功能使用時即時請求">功能使用時即時請求&lt;/h3>
&lt;p>使用者點擊需要權限的功能時才請求。優點是使用者在操作 context 中，能理解為什麼需要這個權限；缺點是操作流程被打斷。&lt;/p>
&lt;p>這個模式適合權限和特定功能綁定的情境。掃描 QR code 時請求相機權限，使用者正在嘗試掃描，理解為什麼需要相機。&lt;/p>
&lt;h3 id="推薦策略">推薦策略&lt;/h3>
&lt;p>功能使用時即時請求是多數場景的推薦策略。使用者有操作 context，授權率較高。打斷可以透過 pre-permission 說明畫面降低突兀感。&lt;/p>
&lt;h2 id="pre-permission-說明畫面">Pre-permission 說明畫面&lt;/h2>
&lt;p>在觸發系統權限對話框之前，app 先顯示自己的說明畫面，解釋為什麼需要這個權限和用途。&lt;/p>
&lt;p>說明畫面的設計要點：&lt;/p>
&lt;p>&lt;strong>說明用途而非技術細節&lt;/strong>。「需要相機來掃描裝置上的 QR code」比「app 需要存取 AVCaptureDevice」更有用。使用者關心的是「為什麼」，不是「用什麼 API」。&lt;/p>
&lt;p>&lt;strong>提供「稍後再說」選項&lt;/strong>。使用者可能想先了解 app 再決定是否授權。強制授權（沒有跳過選項）會讓使用者選擇拒絕。&lt;/p>
&lt;p>&lt;strong>視覺化說明&lt;/strong>。用截圖或圖示展示「授權後這個功能長什麼樣」，讓使用者預覽授權的價值。&lt;/p>
&lt;h2 id="拒絕後的處理">拒絕後的處理&lt;/h2>
&lt;p>使用者拒絕權限後，app 需要：&lt;/p>
&lt;p>&lt;strong>記住拒絕狀態&lt;/strong>。不要在每次使用者操作同一功能時都顯示 pre-permission 說明（使用者已經表達不想授權，反覆詢問是騷擾）。&lt;/p>
&lt;p>&lt;strong>提供功能降級&lt;/strong>。如果可能，提供不需要權限的替代方案。掃描 QR code 可以改成手動輸入配對碼。&lt;/p>
&lt;p>&lt;strong>在適當時機再提醒&lt;/strong>。使用者多次使用需要權限的功能但都因為沒有權限而失敗時，用非侵入式提示（Snackbar）說明「開啟相機權限可以使用掃描功能」加設定連結。&lt;/p>
&lt;p>&lt;strong>引導到系統設定&lt;/strong>。一旦使用者在系統對話框中選擇「不再詢問」（Android）或拒絕（iOS 拒絕後系統不再彈窗），唯一的路徑是引導使用者到系統設定手動開啟。提供直接跳轉到 app 設定頁面的按鈕。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>Gate 設計的通用方法論 → &lt;a href="https://tarrragon.github.io/blog/ux-design/02-gate-fallback/gate-three-questions/" data-link-title="Gate 分類與三問設計法" data-link-desc="每個 gate 設計時問三個問題：成功時做什麼、失敗時做什麼、使用者不知道發生什麼時做什麼">Gate 分類與三問設計法&lt;/a>&lt;/li>
&lt;li>網路 gate 的處理策略 → &lt;a href="https://tarrragon.github.io/blog/ux-design/02-gate-fallback/network-offline-ux/" data-link-title="網路斷線 UX 模式" data-link-desc="Offline-first / retry / degraded mode 三種網路 gate 的處理策略 — 取決於功能是否依賴即時連線">網路斷線 UX 模式&lt;/a>&lt;/li>
&lt;li>開發環境遮蔽 gate 問題 → &lt;a href="https://tarrragon.github.io/blog/ux-design/02-gate-fallback/dev-vs-real-gate-behavior/" data-link-title="開發環境 vs 真機的 gate 行為差異表" data-link-desc="模擬器、debug build、test 環境中的 gate 行為和真機 release build 不同 — 差異表讓開發者在上機前知道哪些 gate 還沒被真實驗證">開發環境 vs 真機的 gate 行為差異表&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>系統權限（相機、位置、通知、麥克風）的請求對話框由作業系統控制，app 只能決定「什麼時候觸發」和「觸發前顯示什麼說明」。使用者拒絕後，再次請求不會彈出系統對話框 — 必須引導使用者到系統設定手動開啟。這意味著第一次請求的時機和說明內容直接影響授權率。</p>
<h2 id="請求時機">請求時機</h2>
<h3 id="首次開啟時一次性請求">首次開啟時一次性請求</h3>
<p>App 首次啟動時依序請求所有需要的權限。優點是使用者只被打斷一次；缺點是使用者尚未使用任何功能，不理解每個權限的用途，傾向拒絕。</p>
<p>這個模式適合權限數量少（1-2 個）且和 app 核心功能直接相關的情境。相機 app 在首次開啟時請求相機權限，使用者能直覺理解原因。</p>
<h3 id="功能使用時即時請求">功能使用時即時請求</h3>
<p>使用者點擊需要權限的功能時才請求。優點是使用者在操作 context 中，能理解為什麼需要這個權限；缺點是操作流程被打斷。</p>
<p>這個模式適合權限和特定功能綁定的情境。掃描 QR code 時請求相機權限，使用者正在嘗試掃描，理解為什麼需要相機。</p>
<h3 id="推薦策略">推薦策略</h3>
<p>功能使用時即時請求是多數場景的推薦策略。使用者有操作 context，授權率較高。打斷可以透過 pre-permission 說明畫面降低突兀感。</p>
<h2 id="pre-permission-說明畫面">Pre-permission 說明畫面</h2>
<p>在觸發系統權限對話框之前，app 先顯示自己的說明畫面，解釋為什麼需要這個權限和用途。</p>
<p>說明畫面的設計要點：</p>
<p><strong>說明用途而非技術細節</strong>。「需要相機來掃描裝置上的 QR code」比「app 需要存取 AVCaptureDevice」更有用。使用者關心的是「為什麼」，不是「用什麼 API」。</p>
<p><strong>提供「稍後再說」選項</strong>。使用者可能想先了解 app 再決定是否授權。強制授權（沒有跳過選項）會讓使用者選擇拒絕。</p>
<p><strong>視覺化說明</strong>。用截圖或圖示展示「授權後這個功能長什麼樣」，讓使用者預覽授權的價值。</p>
<h2 id="拒絕後的處理">拒絕後的處理</h2>
<p>使用者拒絕權限後，app 需要：</p>
<p><strong>記住拒絕狀態</strong>。不要在每次使用者操作同一功能時都顯示 pre-permission 說明（使用者已經表達不想授權，反覆詢問是騷擾）。</p>
<p><strong>提供功能降級</strong>。如果可能，提供不需要權限的替代方案。掃描 QR code 可以改成手動輸入配對碼。</p>
<p><strong>在適當時機再提醒</strong>。使用者多次使用需要權限的功能但都因為沒有權限而失敗時，用非侵入式提示（Snackbar）說明「開啟相機權限可以使用掃描功能」加設定連結。</p>
<p><strong>引導到系統設定</strong>。一旦使用者在系統對話框中選擇「不再詢問」（Android）或拒絕（iOS 拒絕後系統不再彈窗），唯一的路徑是引導使用者到系統設定手動開啟。提供直接跳轉到 app 設定頁面的按鈕。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>Gate 設計的通用方法論 → <a href="/blog/ux-design/02-gate-fallback/gate-three-questions/" data-link-title="Gate 分類與三問設計法" data-link-desc="每個 gate 設計時問三個問題：成功時做什麼、失敗時做什麼、使用者不知道發生什麼時做什麼">Gate 分類與三問設計法</a></li>
<li>網路 gate 的處理策略 → <a href="/blog/ux-design/02-gate-fallback/network-offline-ux/" data-link-title="網路斷線 UX 模式" data-link-desc="Offline-first / retry / degraded mode 三種網路 gate 的處理策略 — 取決於功能是否依賴即時連線">網路斷線 UX 模式</a></li>
<li>開發環境遮蔽 gate 問題 → <a href="/blog/ux-design/02-gate-fallback/dev-vs-real-gate-behavior/" data-link-title="開發環境 vs 真機的 gate 行為差異表" data-link-desc="模擬器、debug build、test 環境中的 gate 行為和真機 release build 不同 — 差異表讓開發者在上機前知道哪些 gate 還沒被真實驗證">開發環境 vs 真機的 gate 行為差異表</a></li>
</ul>
]]></content:encoded></item><item><title>Hands-on：Ollama 改檔案 / 寫程式碼的權限邊界在哪</title><link>https://tarrragon.github.io/blog/llm/01-local-llm-services/hands-on/permission-boundary/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/01-local-llm-services/hands-on/permission-boundary/</guid><description>&lt;p>「Ollama 自己改檔案要不要 sudo？」「叫它寫 &lt;code>rm -rf&lt;/code> 會直接刪嗎？」這類問題的答案來自一個根本事實：&lt;strong>LLM 是 pure function、文字進、文字出、本身沒任何 file system / shell / network 副作用&lt;/strong>。改檔案、刪檔案、發網路請求、執行 shell command——全部由 &lt;strong>wrapper 或人類&lt;/strong>做。LLM 「以為」自己做了什麼、跟實際發生什麼是兩件事。&lt;/p>
&lt;p>本篇用四組對照實驗證明這個事實、再展開 wrapper 三檔審查粒度的設計取捨。這跟 &lt;a href="https://tarrragon.github.io/blog/llm/04-applications/tool-use-principles/" data-link-title="4.3 Tool use 原理：LLM 跟外部世界互動" data-link-desc="Structured output 是 LLM 跨入工程系統的橋、function calling 取捨、為什麼本地小模型 tool use 表現崩潰">4.3 副作用範圍設計&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/llm/04-applications/agent-architecture/" data-link-title="4.4 Agent 架構原理" data-link-desc="Agent loop 結構、失敗模式、什麼任務適合 vs 不適合、跟人類審查的協作模型">4.4 Agent 跟人類審查的協作模型&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/llm/00-foundations/privacy-data-flow/" data-link-title="0.7 隱私 / 資安的資料流原理" data-link-desc="從「位置」到「資料流」的思考升級：信任邊界、合約模型、零信任原則套用到 LLM 工作流">0.7 隱私資料流原理&lt;/a> 三個原則章節對應、實作層的權限與供應鏈判讀對應 &lt;a href="https://tarrragon.github.io/blog/llm/06-security/tool-use-permission-model/" data-link-title="6.2 tool use 與 MCP server 的權限模型" data-link-desc="個人 dev 場景下 tool use / MCP server 的副作用權限：檔案系統 / shell / 網路存取邊界、第三方 MCP 信任、副作用的可逆性">6.2 tool use 與 MCP server 的權限模型&lt;/a> 跟 &lt;a href="https://tarrragon.github.io/blog/llm/06-security/model-supply-chain-trust/" data-link-title="6.0 模型供應鏈與信任邊界" data-link-desc="個人 dev 用本地 LLM 時的模型權重來源信任：GGUF 完整性、Hugging Face / Ollama registry 信任、量化版本污染、檔案完整性檢查">6.0 模型供應鏈與信任邊界&lt;/a>。&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>驗證日期&lt;/strong>：2026-05-12
&lt;strong>環境&lt;/strong>：Ollama 0.23.2、&lt;code>gemma3:1b&lt;/code>、Python stdlib
&lt;strong>檔案位置&lt;/strong>：&lt;code>scripts/permission-demo/edit_with_llm.py&lt;/code>&lt;/p>&lt;/blockquote>
&lt;h2 id="為什麼這個問題重要">為什麼這個問題重要&lt;/h2>
&lt;p>直覺常見的誤判：&lt;/p>
&lt;ul>
&lt;li>「LLM 寫了 &lt;code>rm -rf&lt;/code> 我電腦會壞」——錯。LLM 寫指令不代表執行。&lt;/li>
&lt;li>「Ollama API 改我檔案要 sudo」——錯。Ollama API 根本碰不到檔案。&lt;/li>
&lt;li>「我跑 wrapper 就讓 LLM 改檔案、應該有 confirm 機制吧」——錯。Confirm 機制完全是 wrapper 開發者自己決定要不要寫、LLM 不知道、不在乎。&lt;/li>
&lt;/ul>
&lt;p>理解這個邊界、後續設計 LLM 應用的權限模型才有 ground truth。錯誤的 mental model 會導致兩種 failure：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>過度恐懼&lt;/strong>：因為怕 LLM「亂改」、把所有 LLM 互動關起來、放棄自動化收益。&lt;/li>
&lt;li>&lt;strong>過度信任&lt;/strong>：相信 LLM「不會做壞事」、給 wrapper 自動執行權限、結果小模型亂解 instruction 把資料毀掉。&lt;/li>
&lt;/ol>
&lt;p>實際上權限設計的判讀錨點是：&lt;strong>這個動作有沒有副作用、誰執行&lt;/strong>。LLM 永遠不執行、所以權限不在 LLM 層；wrapper 執行、所以權限完全在 wrapper 設計。&lt;/p>
&lt;h2 id="test-1直接-api-問改檔案看會發生什麼">Test 1：直接 API 問改檔案、看會發生什麼&lt;/h2>
&lt;p>挑一個檔案（token 卡片）、用 curl 送 chat completions、prompt 寫「修改這個檔案」、然後 check 檔案 mtime 跟 md5：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 修改前 snapshot&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">stat -f &lt;span class="s2">&amp;#34;%m %N&amp;#34;&lt;/span> content/llm/knowledge-cards/token.md
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">md5 -q content/llm/knowledge-cards/token.md
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="c1"># 用 system prompt「假裝你有 file 權限」、user 直接指明路徑&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">curl -s http://localhost:11434/v1/chat/completions &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="se">&lt;/span> -H &lt;span class="s2">&amp;#34;Content-Type: application/json&amp;#34;&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="se">&lt;/span> -d &lt;span class="s1">&amp;#39;{
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="s1"> &amp;#34;model&amp;#34;:&amp;#34;gemma3:1b&amp;#34;,
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="s1"> &amp;#34;messages&amp;#34;:[
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="s1"> {&amp;#34;role&amp;#34;:&amp;#34;system&amp;#34;,&amp;#34;content&amp;#34;:&amp;#34;You can modify files. The user provides a file. You modify it.&amp;#34;},
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="s1"> {&amp;#34;role&amp;#34;:&amp;#34;user&amp;#34;,&amp;#34;content&amp;#34;:&amp;#34;Please modify /Users/.../token.md to add a sentence...&amp;#34;}
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="s1"> ],
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">&lt;span class="s1"> &amp;#34;stream&amp;#34;:false
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">&lt;span class="s1"> }&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">&lt;span class="c1"># 修改後 snapshot&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">stat -f &lt;span class="s2">&amp;#34;%m %N&amp;#34;&lt;/span> content/llm/knowledge-cards/token.md
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">md5 -q content/llm/knowledge-cards/token.md&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>實測結果&lt;/strong>：&lt;/p></description><content:encoded><![CDATA[<p>「Ollama 自己改檔案要不要 sudo？」「叫它寫 <code>rm -rf</code> 會直接刪嗎？」這類問題的答案來自一個根本事實：<strong>LLM 是 pure function、文字進、文字出、本身沒任何 file system / shell / network 副作用</strong>。改檔案、刪檔案、發網路請求、執行 shell command——全部由 <strong>wrapper 或人類</strong>做。LLM 「以為」自己做了什麼、跟實際發生什麼是兩件事。</p>
<p>本篇用四組對照實驗證明這個事實、再展開 wrapper 三檔審查粒度的設計取捨。這跟 <a href="/blog/llm/04-applications/tool-use-principles/" data-link-title="4.3 Tool use 原理：LLM 跟外部世界互動" data-link-desc="Structured output 是 LLM 跨入工程系統的橋、function calling 取捨、為什麼本地小模型 tool use 表現崩潰">4.3 副作用範圍設計</a>、<a href="/blog/llm/04-applications/agent-architecture/" data-link-title="4.4 Agent 架構原理" data-link-desc="Agent loop 結構、失敗模式、什麼任務適合 vs 不適合、跟人類審查的協作模型">4.4 Agent 跟人類審查的協作模型</a>、<a href="/blog/llm/00-foundations/privacy-data-flow/" data-link-title="0.7 隱私 / 資安的資料流原理" data-link-desc="從「位置」到「資料流」的思考升級：信任邊界、合約模型、零信任原則套用到 LLM 工作流">0.7 隱私資料流原理</a> 三個原則章節對應、實作層的權限與供應鏈判讀對應 <a href="/blog/llm/06-security/tool-use-permission-model/" data-link-title="6.2 tool use 與 MCP server 的權限模型" data-link-desc="個人 dev 場景下 tool use / MCP server 的副作用權限：檔案系統 / shell / 網路存取邊界、第三方 MCP 信任、副作用的可逆性">6.2 tool use 與 MCP server 的權限模型</a> 跟 <a href="/blog/llm/06-security/model-supply-chain-trust/" data-link-title="6.0 模型供應鏈與信任邊界" data-link-desc="個人 dev 用本地 LLM 時的模型權重來源信任：GGUF 完整性、Hugging Face / Ollama registry 信任、量化版本污染、檔案完整性檢查">6.0 模型供應鏈與信任邊界</a>。</p>
<blockquote>
<p><strong>驗證日期</strong>：2026-05-12
<strong>環境</strong>：Ollama 0.23.2、<code>gemma3:1b</code>、Python stdlib
<strong>檔案位置</strong>：<code>scripts/permission-demo/edit_with_llm.py</code></p></blockquote>
<h2 id="為什麼這個問題重要">為什麼這個問題重要</h2>
<p>直覺常見的誤判：</p>
<ul>
<li>「LLM 寫了 <code>rm -rf</code> 我電腦會壞」——錯。LLM 寫指令不代表執行。</li>
<li>「Ollama API 改我檔案要 sudo」——錯。Ollama API 根本碰不到檔案。</li>
<li>「我跑 wrapper 就讓 LLM 改檔案、應該有 confirm 機制吧」——錯。Confirm 機制完全是 wrapper 開發者自己決定要不要寫、LLM 不知道、不在乎。</li>
</ul>
<p>理解這個邊界、後續設計 LLM 應用的權限模型才有 ground truth。錯誤的 mental model 會導致兩種 failure：</p>
<ol>
<li><strong>過度恐懼</strong>：因為怕 LLM「亂改」、把所有 LLM 互動關起來、放棄自動化收益。</li>
<li><strong>過度信任</strong>：相信 LLM「不會做壞事」、給 wrapper 自動執行權限、結果小模型亂解 instruction 把資料毀掉。</li>
</ol>
<p>實際上權限設計的判讀錨點是：<strong>這個動作有沒有副作用、誰執行</strong>。LLM 永遠不執行、所以權限不在 LLM 層；wrapper 執行、所以權限完全在 wrapper 設計。</p>
<h2 id="test-1直接-api-問改檔案看會發生什麼">Test 1：直接 API 問改檔案、看會發生什麼</h2>
<p>挑一個檔案（token 卡片）、用 curl 送 chat completions、prompt 寫「修改這個檔案」、然後 check 檔案 mtime 跟 md5：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 修改前 snapshot</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">stat -f <span class="s2">&#34;%m %N&#34;</span> content/llm/knowledge-cards/token.md
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">md5 -q content/llm/knowledge-cards/token.md
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"># 用 system prompt「假裝你有 file 權限」、user 直接指明路徑</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">curl -s http://localhost:11434/v1/chat/completions <span class="se">\
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="se"></span>  -H <span class="s2">&#34;Content-Type: application/json&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="se"></span>  -d <span class="s1">&#39;{
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s1">    &#34;model&#34;:&#34;gemma3:1b&#34;,
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s1">    &#34;messages&#34;:[
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s1">      {&#34;role&#34;:&#34;system&#34;,&#34;content&#34;:&#34;You can modify files. The user provides a file. You modify it.&#34;},
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s1">      {&#34;role&#34;:&#34;user&#34;,&#34;content&#34;:&#34;Please modify /Users/.../token.md to add a sentence...&#34;}
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s1">    ],
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s1">    &#34;stream&#34;:false
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s1">  }&#39;</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="c1"># 修改後 snapshot</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">stat -f <span class="s2">&#34;%m %N&#34;</span> content/llm/knowledge-cards/token.md
</span></span><span class="line"><span class="ln">19</span><span class="cl">md5 -q content/llm/knowledge-cards/token.md</span></span></code></pre></div><p><strong>實測結果</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">=== Before ===
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">1778508712 content/llm/knowledge-cards/token.md
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">d9f2d822f7458af62399076a94ef20f6
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">=== LLM response ===
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">Okay, here&#39;s the modified content of `/Users/.../token.md`...
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">=== After ===
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">1778508712 content/llm/knowledge-cards/token.md  ← mtime same
</span></span><span class="line"><span class="ln">10</span><span class="cl">d9f2d822f7458af62399076a94ef20f6                  ← md5 same</span></span></code></pre></div><p>mtime 沒變、md5 沒變、檔案內容完全沒動。但 LLM 用「Okay, here&rsquo;s the modified content」這種口氣回答——它<strong>以為</strong>自己改了、實際上只生成了一段 markdown 文字。</p>
<p><strong>結論</strong>：Ollama HTTP API 是 stateless、pure function。輸入 messages、輸出 message content。整個過程沒寫進 socket 以外的任何地方。</p>
<p>為什麼會這樣設計：</p>
<ul>
<li><strong>沙箱本來就在 API 邊界</strong>：HTTP server 接 request、跑 forward pass、回 response。期間沒呼叫 <code>fs.write()</code> / <code>subprocess.run()</code> / 任何 effectful API。</li>
<li><strong><a href="/blog/llm/knowledge-cards/system-prompt/" data-link-title="System Prompt" data-link-desc="LLM application 中由開發者預設、不直接顯示給使用者的指令層、定義模型的角色、行為規範、輸出格式">system prompt</a> 不是權限授予</strong>：「You can modify files」這句話對模型來說只是文字 context、不會真的給它 file access。Prompt 是「LLM 內部的 context」、不是「runtime capability」。</li>
<li><strong>訓練資料讓 LLM 「以為」自己有能力</strong>：LLM 訓練資料含大量「使用者問問題、AI 改檔案」的範例（如 GitHub Copilot agent traces、tool-use SFT 資料）、模型學會用「我已經改了」這種語氣回答——是 mimic、不是真正的 action。</li>
</ul>
<h2 id="test-2寫-wrapper-用-dry-run-模式安全處理">Test 2：寫 wrapper 用 &ndash;dry-run 模式安全處理</h2>
<p>權限不在 LLM、在 wrapper。寫一個 100 行的 wrapper、看怎麼設計 permission gates。完整檔案：<code>scripts/permission-demo/edit_with_llm.py</code>。</p>
<p>核心 architecture：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="c1"># 1. 讀檔（wrapper 用自己的 fs 權限）</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">original</span> <span class="o">=</span> <span class="n">args</span><span class="o">.</span><span class="n">file</span><span class="o">.</span><span class="n">read_text</span><span class="p">(</span><span class="n">encoding</span><span class="o">=</span><span class="s2">&#34;utf-8&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="c1"># 2. 送 LLM、拿回提議的新內容</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">response</span> <span class="o">=</span> <span class="n">chat</span><span class="p">([</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="p">{</span><span class="s2">&#34;role&#34;</span><span class="p">:</span> <span class="s2">&#34;system&#34;</span><span class="p">,</span> <span class="s2">&#34;content&#34;</span><span class="p">:</span> <span class="s2">&#34;You modify text files. Output ONLY ...&#34;</span><span class="p">},</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="p">{</span><span class="s2">&#34;role&#34;</span><span class="p">:</span> <span class="s2">&#34;user&#34;</span><span class="p">,</span> <span class="s2">&#34;content&#34;</span><span class="p">:</span> <span class="sa">f</span><span class="s2">&#34;File: </span><span class="si">{</span><span class="n">args</span><span class="o">.</span><span class="n">file</span><span class="si">}</span><span class="se">\n</span><span class="s2">Content:</span><span class="se">\n</span><span class="si">{</span><span class="n">original</span><span class="si">}</span><span class="se">\n</span><span class="s2">Instruction: </span><span class="si">{</span><span class="n">args</span><span class="o">.</span><span class="n">instruction</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">},</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="p">])</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">new_content</span> <span class="o">=</span> <span class="n">extract_code_block</span><span class="p">(</span><span class="n">response</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="c1"># 3. Diff（純讀、永遠 safe、不需 gate）</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="n">diff</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">difflib</span><span class="o">.</span><span class="n">unified_diff</span><span class="p">(</span><span class="n">original</span><span class="o">.</span><span class="n">splitlines</span><span class="p">(</span><span class="o">...</span><span class="p">),</span> <span class="n">new_content</span><span class="o">.</span><span class="n">splitlines</span><span class="p">(</span><span class="o">...</span><span class="p">)))</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="n">sys</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">writelines</span><span class="p">(</span><span class="n">diff</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="c1"># 4. PERMISSION GATE：wrapper 決定要不要 apply</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">auto</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="n">args</span><span class="o">.</span><span class="n">file</span><span class="o">.</span><span class="n">write_text</span><span class="p">(</span><span class="n">new_content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="k">elif</span> <span class="n">args</span><span class="o">.</span><span class="n">confirm</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="k">if</span> <span class="nb">input</span><span class="p">(</span><span class="s2">&#34;Apply? [y/N] &#34;</span><span class="p">)</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span> <span class="o">==</span> <span class="s2">&#34;y&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">            <span class="n">args</span><span class="o">.</span><span class="n">file</span><span class="o">.</span><span class="n">write_text</span><span class="p">(</span><span class="n">new_content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="k">else</span><span class="p">:</span>  <span class="c1"># --dry-run，預設</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="k">pass</span>  <span class="c1"># 不寫</span></span></span></code></pre></div><p><strong>為什麼這樣設計</strong>：</p>
<ul>
<li><strong><code>extract_code_block</code></strong>：嘗試 well-formed <code>```lang\n...\n```</code> regex、失敗 fallback 到 <code>```lang\n...$</code> 寬鬆版。小模型（1B）常忘記結尾 fence、寬鬆才能用。寫嚴格 regex 失敗時直接 abort、是另一種 permission gate（不應用 = 安全）。</li>
<li><strong>永遠先印 diff</strong>：diff 是純讀操作、無副作用、永遠 safe。讓使用者先看 LLM 提議了什麼、再決定要不要 apply。</li>
<li><strong><code>args.auto</code> 在 <code>elif</code> 鏈最前面、<code>dry-run</code> 預設</strong>：強迫使用者明示 opt-in 才會寫檔。預設不寫、是「safe default」設計原則。</li>
</ul>
<p>跑 <code>--dry-run</code> 預設、看實際發生：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">python3 scripts/permission-demo/edit_with_llm.py <span class="se">\
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="se"></span>  content/llm/knowledge-cards/token.md <span class="se">\
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="se"></span>  <span class="s2">&#34;把開頭第一段最後加一句『Token 是 embedding 的輸入單位』&#34;</span></span></span></code></pre></div><p>實測輸出（1B 模型）：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">[+] Asking gemma3:1b to: &#39;把開頭第一段最後加一句「Token 是 embedding 的輸入單位」&#39;
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">[+] Proposed diff:
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">--- a/token.md
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">+++ b/token.md
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">@@ -6,16 +6,4 @@
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"> tags: [&#34;llm&#34;, &#34;knowledge-cards&#34;]
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"> ---
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">-Token 的核心概念是「LLM 內部處理文字的最小單位」...（整段刪除）
</span></span><span class="line"><span class="ln">10</span><span class="cl">-
</span></span><span class="line"><span class="ln">11</span><span class="cl">-## 概念位置
</span></span><span class="line"><span class="ln">12</span><span class="cl">-...（整段刪除）
</span></span><span class="line"><span class="ln">13</span><span class="cl">-...（後面所有段落都刪除）
</span></span><span class="line"><span class="ln">14</span><span class="cl">+Token 是 embedding 的輸入單位。
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">[+] --dry-run: file unchanged. Use --confirm or --auto to apply.</span></span></code></pre></div><p><strong>驚悚發現</strong>：1B 模型完全沒理解「加一句」、把整篇刪掉只剩一行。但 <code>--dry-run</code> 不寫檔、檔案安全。</p>
<p><strong>重點</strong>：</p>
<ul>
<li>LLM 行為糟、但 wrapper 設計安全、結果 OK。</li>
<li>把同樣 instruction 餵 31B+ 模型結果會合理——模型能力決定 LLM 端品質、wrapper 設計決定<strong>最差情況的後果</strong>。</li>
<li>在 wrapper 端永遠假設 LLM 會亂改、設計 safe default、是 defensive programming。</li>
</ul>
<h2 id="test-3--confirm-模式step-by-step-審查">Test 3：<code>--confirm</code> 模式、step-by-step 審查</h2>
<p><code>--confirm</code> mode 印 diff、問 y/N、user 確認才寫：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">python3 scripts/permission-demo/edit_with_llm.py <span class="se">\
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="se"></span>  content/llm/knowledge-cards/token.md <span class="se">\
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="se"></span>  <span class="s2">&#34;加一句說明&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="se"></span>  --confirm</span></span></code></pre></div><p>互動流程：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">[+] Proposed diff:
</span></span><span class="line"><span class="ln">2</span><span class="cl">--- a/token.md
</span></span><span class="line"><span class="ln">3</span><span class="cl">+++ b/token.md
</span></span><span class="line"><span class="ln">4</span><span class="cl">@@ ... 整段刪除 ...
</span></span><span class="line"><span class="ln">5</span><span class="cl">
</span></span><span class="line"><span class="ln">6</span><span class="cl">[?] Apply this change to content/llm/.../token.md? [y/N] _</span></span></code></pre></div><p>使用者看 diff 發現「整篇被刪了」、按 N、檔案安全。</p>
<p><strong>這個 mode 對應的副作用範圍</strong>：<a href="/blog/llm/04-applications/tool-use-principles/" data-link-title="4.3 Tool use 原理：LLM 跟外部世界互動" data-link-desc="Structured output 是 LLM 跨入工程系統的橋、function calling 取捨、為什麼本地小模型 tool use 表現崩潰">4.3 工具的副作用範圍設計</a> 提的 spectrum：</p>
<table>
  <thead>
      <tr>
          <th>等級</th>
          <th>副作用</th>
          <th>適合 mode</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1</td>
          <td>純讀（grep、git status）</td>
          <td><code>--dry-run</code> 或無 gate</td>
      </tr>
      <tr>
          <td>2</td>
          <td>寫 sandbox / staging</td>
          <td><code>--dry-run</code> + 人類事後審</td>
      </tr>
      <tr>
          <td>3</td>
          <td>寫本地持久化（如 commit、edit 檔）</td>
          <td><code>--confirm</code></td>
      </tr>
      <tr>
          <td>4</td>
          <td>寫共享 / production（push、deploy）</td>
          <td><code>--confirm</code> 強制</td>
      </tr>
      <tr>
          <td>5</td>
          <td>操作真實世界（發 email、買股票）</td>
          <td><code>--confirm</code> + 額外 audit</td>
      </tr>
  </tbody>
</table>
<p>本 demo 改 markdown 是等級 3（寫本地檔）、<code>--confirm</code> 是合適粒度。改 production code 或 git push 是等級 4 / 5、<code>--confirm</code> 該強制不該 optional。</p>
<h2 id="test-4--auto-模式危險自動化">Test 4：<code>--auto</code> 模式、危險自動化</h2>
<p><code>--auto</code> 不問直接寫：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">cp /tmp/token-orig.md content/llm/knowledge-cards/token.md  <span class="c1"># 還原</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">python3 scripts/permission-demo/edit_with_llm.py <span class="se">\
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="se"></span>  content/llm/knowledge-cards/token.md <span class="se">\
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="se"></span>  <span class="s2">&#34;加一句說明&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="se"></span>  --auto</span></span></code></pre></div><p>實測：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">[!] --auto mode: writing without confirmation
</span></span><span class="line"><span class="ln">2</span><span class="cl">[+] wrote content/llm/knowledge-cards/token.md</span></span></code></pre></div><p>檔案內容變成：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">---
</span></span><span class="line"><span class="ln">2</span><span class="cl">title: &#34;Token&#34;
</span></span><span class="line"><span class="ln">3</span><span class="cl">...
</span></span><span class="line"><span class="ln">4</span><span class="cl">---
</span></span><span class="line"><span class="ln">5</span><span class="cl">
</span></span><span class="line"><span class="ln">6</span><span class="cl">Token 是 embedding 的輸入單位。</span></span></code></pre></div><p>整篇刪光、只剩一句。<strong>沒人 catch 到、commit + push 出去就是 production 災難</strong>。</p>
<p><strong><code>--auto</code> mode 適合什麼場景</strong>：</p>
<ul>
<li>LLM 任務範圍狹窄、可預測（如 format JSON、補 type annotation 給已有 type stub）。</li>
<li>配合 git workflow（每次 auto edit 都自動 commit、出問題 git revert）。</li>
<li>CI / batch processing、人類事後審 PR。</li>
</ul>
<p><strong><code>--auto</code> mode 不適合什麼場景</strong>：</p>
<ul>
<li>任務開放性高（「改寫這段讓它更清楚」）。</li>
<li>不可逆環境（直接寫 production DB / 發 email）。</li>
<li>用弱模型（&lt; 14B）跑、行為不穩。</li>
</ul>
<p>設計 wrapper 時、把 <code>--auto</code> 設成顯式 opt-in、預設保持 dry-run / confirm 等較保守模式。本 demo 的 mutually_exclusive 設計（<code>-g.add_mutually_exclusive_group()</code>）保證三種 mode 只能擇一、避免歧義。</p>
<h2 id="test-5llm-寫-shell-command誰執行">Test 5：LLM 寫 shell command、誰執行？</h2>
<p>改檔案是「直接副作用」、寫 shell command 是「間接副作用」——同樣的問題：誰真的執行？</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">curl -s http://localhost:11434/v1/chat/completions <span class="se">\
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="se"></span>  -H <span class="s2">&#34;Content-Type: application/json&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="se"></span>  -d <span class="s1">&#39;{
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="s1">    &#34;model&#34;:&#34;gemma3:1b&#34;,
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="s1">    &#34;messages&#34;:[{&#34;role&#34;:&#34;user&#34;,&#34;content&#34;:&#34;Give me a single shell command to find and delete all .log files in my home directory.&#34;}],
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="s1">    &#34;stream&#34;:false
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="s1">  }&#39;</span> <span class="p">|</span> python3 -c <span class="s2">&#34;import json,sys; print(json.load(sys.stdin)[&#39;choices&#39;][0][&#39;message&#39;][&#39;content&#39;])&#34;</span></span></span></code></pre></div><p>LLM 回：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">```bash
</span></span><span class="line"><span class="ln">2</span><span class="cl">find ~ -name &#34;*.log&#34; -delete
</span></span><span class="line"><span class="ln">3</span><span class="cl">```</span></span></code></pre></div><p>這是個有破壞性的指令。檢查 home 下 .log 還在不在：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">find ~ -maxdepth <span class="m">3</span> -name <span class="s2">&#34;*.log&#34;</span> 2&gt;/dev/null <span class="p">|</span> head -5
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"># /Users/tarragon/.npm/_logs/2026-05-11T15_33_34_348Z-debug-0.log</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"># /Users/tarragon/.npm/_logs/2026-05-11T11_58_08_827Z-debug-0.log</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># ...</span></span></span></code></pre></div><p>都還在。LLM「給了」rm 指令、但沒人執行。</p>
<p><strong>執行路徑只有兩種</strong>：</p>
<ol>
<li><strong>人類 paste 到 shell</strong>：人是執行者、權限是 user&rsquo;s shell session permission。Audit trail：terminal history。</li>
<li><strong>Wrapper 程式 <code>subprocess.run(...)</code></strong>：wrapper 是執行者、權限是 wrapper process 的 capability。Audit trail：wrapper 的 log。</li>
</ol>
<p>LLM 永遠不是執行者。所以「LLM 寫了 rm -rf」這個句子不能成立——它只能「生成了 rm -rf 字串」。</p>
<p><strong>Agent 場景的 stake</strong>：<a href="/blog/llm/04-applications/agent-architecture/" data-link-title="4.4 Agent 架構原理" data-link-desc="Agent loop 結構、失敗模式、什麼任務適合 vs 不適合、跟人類審查的協作模型">4.4 Agent 架構</a> 提到 agent loop = 「LLM 提議 → tool 執行 → 結果回 LLM → 下一輪」。Tool 執行那一步是 wrapper 做的、LLM 只看到結果。Agent 框架是否安全、完全看 tool 怎麼設計：</p>
<ul>
<li><strong>Tool 限制範圍</strong>：read-only file system access、不暴露 shell→ 即使 LLM 想跑 <code>rm -rf</code> 也沒對應 tool、無法執行。</li>
<li><strong>Tool 暴露 <code>bash</code> tool</strong>：給 LLM 一個「執行任意 shell command」的 tool。LLM 提議什麼 wrapper 都跑——這時 wrapper 設計失誤等同把鑰匙直接交給 LLM。</li>
<li><strong>Tool 暴露 <code>bash</code> tool + per-command confirm</strong>：每個 shell 呼叫前 wrapper 暫停、問人類「該不該執行」。對開發 / 探索環境合理、production 自動化流程會被互動卡住、不適用。</li>
</ul>
<h2 id="對照claude-code--cursor--aider-的權限模型">對照：Claude Code / Cursor / aider 的權限模型</h2>
<p>不同 LLM application 在權限 gate 上的設計選擇：</p>
<table>
  <thead>
      <tr>
          <th>Application</th>
          <th>File edit</th>
          <th>Shell exec</th>
          <th>預設審查粒度</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Claude Code（CLI）</td>
          <td>可、有 PreToolUse hook 可攔截</td>
          <td>可、有 hook</td>
          <td>中（部分自動、部分 prompt）</td>
      </tr>
      <tr>
          <td>Cursor</td>
          <td>可、agent mode</td>
          <td>可（agent terminal）</td>
          <td>中、agent 行為可調</td>
      </tr>
      <tr>
          <td>aider</td>
          <td>可、直接 diff + commit</td>
          <td>可（<code>--auto-commits</code> mode）</td>
          <td>中、預設 commit 前 diff</td>
      </tr>
      <tr>
          <td>Continue.dev</td>
          <td>inline edit（user 按 Cmd+;）</td>
          <td>不直接 exec</td>
          <td>高（user 必須 explicit）</td>
      </tr>
      <tr>
          <td>Open WebUI（純 chat）</td>
          <td>不</td>
          <td>不</td>
          <td>N/A（無 wrapper）</td>
      </tr>
      <tr>
          <td>自寫 wrapper（如本 demo）</td>
          <td>看設計</td>
          <td>看設計</td>
          <td>看設計</td>
      </tr>
  </tbody>
</table>
<p><strong>共通 pattern</strong>：所有「自動 edit / exec」的 app 都有某種 confirm 或 hook 機制。沒有 confirm 的 app 等於把寫 production 的鑰匙交給 LLM。</p>
<p><strong>選 application 時看的維度</strong>：</p>
<ul>
<li>預設 mode 是什麼？（auto / confirm / dry-run）</li>
<li>哪些動作會自動執行、哪些會 prompt？</li>
<li>有沒有 audit log、能不能 review LLM 改了什麼？</li>
<li>萬一 LLM 行為崩、怎麼 rollback？（git revert、snapshot、undo stack）</li>
</ul>
<h2 id="設計自家-wrapper-的權限模型">設計自家 wrapper 的權限模型</h2>
<p>如果你寫的是「LLM 自動處理 X」這種 wrapper、權限設計的 checklist：</p>
<ol>
<li><strong>副作用分級</strong>：把可能的動作分到 <a href="/blog/llm/04-applications/tool-use-principles/" data-link-title="4.3 Tool use 原理：LLM 跟外部世界互動" data-link-desc="Structured output 是 LLM 跨入工程系統的橋、function calling 取捨、為什麼本地小模型 tool use 表現崩潰">4.3 spectrum 等級 1-5</a>。</li>
<li><strong>預設 dry-run</strong>：不確定就不寫。Apply 必須 opt-in。</li>
<li><strong>永遠印 diff / preview</strong>：用戶才能 catch LLM 亂改。</li>
<li><strong>Confirm 在不可逆操作</strong>：等級 3+ 永遠 prompt、等級 4+ 強制 prompt + 額外 audit。</li>
<li><strong>Audit log</strong>：每個 wrapper 動作寫 log（時間、user、action、result）。出問題能追溯。</li>
<li><strong>Rollback path</strong>：git commit、backup、snapshot 任選一種、必有。</li>
<li><strong>限制 tool 範圍</strong>：給 LLM 暴露最少 tool、不暴露 shell。需要 shell 限制白名單。</li>
<li><strong>小模型加更保守 gate</strong>：1B 模型亂改機率高、保留 <code>--dry-run</code> 或 <code>--confirm</code> 即可、避免 <code>--auto</code>；31B+ 較穩、可給 auto + audit。</li>
</ol>
<h2 id="跑這份-demo-的完整指令">跑這份 demo 的完整指令</h2>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 前置：Ollama 跑著、gemma3:1b 已 pull</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">ollama list <span class="p">|</span> grep gemma3:1b
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># 備份要測試的檔案</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">cp content/llm/knowledge-cards/token.md /tmp/token-orig.md
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"># Mode 1：dry-run（預設、最安全）</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">python3 scripts/permission-demo/edit_with_llm.py <span class="se">\
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="se"></span>  content/llm/knowledge-cards/token.md <span class="se">\
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="se"></span>  <span class="s2">&#34;加一句說明&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"># Mode 2：confirm（互動審查、適合中等風險）</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">python3 scripts/permission-demo/edit_with_llm.py <span class="se">\
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="se"></span>  content/llm/knowledge-cards/token.md <span class="se">\
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="se"></span>  <span class="s2">&#34;加一句說明&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="se"></span>  --confirm
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="c1"># Mode 3：auto（無確認、危險、僅 batch 用）</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">python3 scripts/permission-demo/edit_with_llm.py <span class="se">\
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="se"></span>  content/llm/knowledge-cards/token.md <span class="se">\
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="se"></span>  <span class="s2">&#34;加一句說明&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="se"></span>  --auto
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="c1"># 還原</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">cp /tmp/token-orig.md content/llm/knowledge-cards/token.md</span></span></code></pre></div><h2 id="何時這篇會過時">何時這篇會過時</h2>
<p><strong>不會過時的部分</strong>：</p>
<ul>
<li>LLM HTTP API 是 pure function、無副作用——這個事實在所有「分離 inference server / wrapper / client」的架構都成立。</li>
<li>權限 gate 在 wrapper / application 層——是 software architecture invariant、不是 LLM 特性。</li>
<li>副作用範圍 spectrum 跟人類審查粒度的對應。</li>
<li><code>--dry-run</code> / <code>--confirm</code> / <code>--auto</code> 三檔的設計取捨。</li>
</ul>
<p><strong>會變的部分</strong>：</p>
<ul>
<li>具體 LLM application 的 default mode（Cursor / aider / Claude Code 都會持續調整）。</li>
<li>哪個模型「不會亂改」的 ranking（隨模型能力提升而變）。</li>
<li>MCP / tool spec 細節（會持續演化、但「tool 是 wrapper 暴露」的本質不變）。</li>
</ul>
<p>讀這篇若指令跑不過、可能是 wrapper script API 微調、但「測試 LLM 是不是 pure function」這個方法本身永遠成立——拿任何 LLM API、送任何 prompt、check 檔案 mtime / md5、就能驗證。</p>
<p>跟其他 hands-on 章節的關係：完整 hands-on 系列見 <a href="/blog/llm/01-local-llm-services/hands-on/" data-link-title="Hands-on：本地 AI 工具實作筆記" data-link-desc="Ollama / ComfyUI / Whisper / Piper TTS：實際安裝、驗證、跑通的紀錄。隨工具版本演化、跟 1.x 原理章節互補。">Hands-on 章節索引</a>、副作用範圍 spectrum 原理見 <a href="/blog/llm/04-applications/tool-use-principles/" data-link-title="4.3 Tool use 原理：LLM 跟外部世界互動" data-link-desc="Structured output 是 LLM 跨入工程系統的橋、function calling 取捨、為什麼本地小模型 tool use 表現崩潰">4.3 Tool use 原理</a>、Agent loop 跟人類審查的協作見 <a href="/blog/llm/04-applications/agent-architecture/" data-link-title="4.4 Agent 架構原理" data-link-desc="Agent loop 結構、失敗模式、什麼任務適合 vs 不適合、跟人類審查的協作模型">4.4 Agent 架構</a>、Tool use / MCP server 權限模型的個人 dev 視角見 <a href="/blog/llm/06-security/tool-use-permission-model/" data-link-title="6.2 tool use 與 MCP server 的權限模型" data-link-desc="個人 dev 場景下 tool use / MCP server 的副作用權限：檔案系統 / shell / 網路存取邊界、第三方 MCP 信任、副作用的可逆性">6.2</a>、術語見 <a href="/blog/llm/knowledge-cards/sandbox/" data-link-title="Sandbox" data-link-desc="把程式跑在受限制環境的隔離技術、限制檔案 / 網路 / 系統呼叫權限、是 tool use 跟 MCP server 副作用控制的基礎">Sandbox</a>。</p>
]]></content:encoded></item></channel></rss>