> ## Documentation Index
> Fetch the complete documentation index at: https://felimet-hub.jmcores.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 附錄 B：隱私與安全設定 checklist

> 一頁可勾選清單，在把敏感資料或專案交給任何 AI 工具之前逐項確認。涵蓋訓練退出、憑證、供應鏈、不可信內容、記憶輪換與維護六大區。

export const CheckList = ({lang = "zh", groups = []}) => {
  const UI = lang === "en" ? {
    progress: (n, t) => `${n} / ${t} checked`,
    progressFull: "All done",
    whyLabel: "Why",
    expandHint: "Tap for details",
    resetBtn: "Reset",
    groupWhy: "Context"
  } : {
    progress: (n, t) => `已勾選 ${n} / ${t} 項`,
    progressFull: "全部完成",
    whyLabel: "為什麼",
    expandHint: "點查說明",
    resetBtn: "清除",
    groupWhy: "背景"
  };
  const ACCENT = "#bf7551";
  const ACCENT_L = "#cf8a68";
  const ACCENT_BG = "rgba(191,117,81,0.08)";
  const ACCENT_BDR = "rgba(191,117,81,0.35)";
  const GREEN = "#4a7c59";
  const GREEN_BG = "#eaf4ec";
  const GREEN_D = "#7ecf96";
  const GREEN_BG_D = "#1a3328";
  const safeGroups = Array.isArray(groups) ? groups : [];
  const allItems = safeGroups.flatMap(g => g.items || []);
  const total = allItems.length;
  const [checked, setChecked] = useState({});
  const [expandedWhy, setExpandedWhy] = useState(null);
  const [isDark, setIsDark] = useState(false);
  useEffect(() => {
    const detect = () => setIsDark(document.documentElement.classList.contains("dark"));
    detect();
    const obs = new MutationObserver(detect);
    obs.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ["class"]
    });
    return () => obs.disconnect();
  }, []);
  const count = Object.values(checked).filter(Boolean).length;
  const toggleItem = id => setChecked(prev => ({
    ...prev,
    [id]: !prev[id]
  }));
  const toggleWhy = id => setExpandedWhy(prev => prev === id ? null : id);
  const reset = () => {
    setChecked({});
    setExpandedWhy(null);
  };
  const pct = total > 0 ? Math.round(count / total * 100) : 0;
  const isDone = count === total && total > 0;
  const css = `
  .cl-root {
    --cl-bg:         #FAF8F3;
    --cl-surface:    rgba(0,0,0,0.025);
    --cl-border:     rgba(0,0,0,0.09);
    --cl-text:       #2b2722;
    --cl-dim:        #6f6a62;
    --cl-faint:      #8a8378;
    --cl-accent:     ${ACCENT};
    --cl-accent-l:   ${ACCENT_L};
    --cl-accent-bg:  ${ACCENT_BG};
    --cl-accent-bdr: ${ACCENT_BDR};
    --cl-item-bg:    #ffffff;
    --cl-item-hover: #f6f2ed;
    --cl-code-bg:    rgba(0,0,0,0.055);
    font-size: 14px;
    border: 1px solid var(--cl-border);
    border-radius: 14px;
    background: var(--cl-bg);
    color: var(--cl-text);
    overflow: hidden;
  }
  .dark .cl-root {
    --cl-bg:         #1b1a18;
    --cl-surface:    rgba(255,255,255,0.03);
    --cl-border:     rgba(255,255,255,0.08);
    --cl-text:       #e7e3da;
    --cl-dim:        #a8a299;
    --cl-faint:      #706b64;
    --cl-accent:     ${ACCENT_L};
    --cl-accent-bg:  rgba(207,138,104,0.09);
    --cl-accent-bdr: rgba(207,138,104,0.35);
    --cl-item-bg:    #242220;
    --cl-item-hover: #2c2a27;
    --cl-code-bg:    rgba(255,255,255,0.08);
  }

  /* ── 頂部進度區 ── */
  .cl-progress-bar {
    padding: 14px 18px 12px;
    border-bottom: 1px solid var(--cl-border);
    background: var(--cl-surface);
  }
  .cl-progress-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 8px;
    gap: 10px;
  }
  .cl-progress-label {
    font-size: 13px;
    font-weight: 600;
    color: var(--cl-dim);
  }
  .cl-progress-done {
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: ${GREEN};
    background: ${GREEN_BG};
    border-radius: 4px;
    padding: 2px 8px;
  }
  .dark .cl-progress-done {
    color: ${GREEN_D};
    background: ${GREEN_BG_D};
  }
  .cl-reset-btn {
    font: inherit;
    font-size: 12px;
    color: var(--cl-faint);
    background: transparent;
    border: 1px solid var(--cl-border);
    border-radius: 6px;
    padding: 3px 10px;
    cursor: pointer;
    transition: color 0.13s, border-color 0.13s;
  }
  .cl-reset-btn:hover {
    color: var(--cl-dim);
    border-color: var(--cl-accent-bdr);
  }
  .cl-track {
    height: 5px;
    border-radius: 3px;
    background: var(--cl-border);
    overflow: hidden;
  }
  .cl-fill {
    height: 100%;
    border-radius: 3px;
    background: var(--cl-accent);
    transition: width 0.25s ease;
  }

  /* ── 群組 ── */
  .cl-groups {
    display: flex;
    flex-direction: column;
  }
  .cl-group {
    border-bottom: 1px solid var(--cl-border);
    padding: 14px 18px;
  }
  .cl-group:last-child { border-bottom: none; }
  .cl-group-header {
    display: flex;
    align-items: baseline;
    gap: 10px;
    margin-bottom: 10px;
    flex-wrap: wrap;
  }
  .cl-group-label {
    font-size: 11.5px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--cl-accent);
  }
  .cl-group-why {
    font-size: 12.5px;
    color: var(--cl-dim);
    line-height: 1.55;
    margin-bottom: 10px;
    padding: 8px 11px;
    border-left: 2px solid var(--cl-accent-bdr);
    background: var(--cl-accent-bg);
    border-radius: 0 6px 6px 0;
  }

  /* ── 清單項目 ── */
  .cl-items {
    display: flex;
    flex-direction: column;
    gap: 5px;
  }
  .cl-item {
    border-radius: 8px;
    border: 1px solid var(--cl-border);
    background: var(--cl-item-bg);
    overflow: hidden;
    transition: border-color 0.13s;
  }
  .cl-item-checked {
    border-color: var(--cl-accent-bdr);
    background: var(--cl-accent-bg);
  }
  .cl-item-main {
    display: flex;
    align-items: flex-start;
    gap: 9px;
    padding: 9px 11px;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    user-select: none;
  }
  .cl-item-main:hover { background: var(--cl-item-hover); }
  .cl-item-checked .cl-item-main:hover { background: rgba(191,117,81,0.06); }

  /* 核取框 */
  .cl-checkbox {
    flex-shrink: 0;
    width: 17px;
    height: 17px;
    border-radius: 5px;
    border: 1.5px solid var(--cl-accent-bdr);
    background: var(--cl-code-bg);
    display: flex;
    align-items: center;
    justify-content: center;
    margin-top: 1px;
    transition: background 0.14s, border-color 0.14s;
  }
  .cl-checkbox-on {
    background: var(--cl-accent);
    border-color: var(--cl-accent);
  }

  .cl-item-text {
    flex: 1 1 0;
    font-size: 13.5px;
    line-height: 1.48;
    color: var(--cl-text);
  }
  .cl-item-checked .cl-item-text {
    color: var(--cl-dim);
    text-decoration: line-through;
    text-decoration-color: var(--cl-accent-bdr);
  }

  /* why 展開按鈕 */
  .cl-why-btn {
    flex-shrink: 0;
    margin-top: 2px;
    background: transparent;
    border: 1px solid var(--cl-border);
    border-radius: 4px;
    padding: 2px 7px;
    font: inherit;
    font-size: 11px;
    color: var(--cl-faint);
    cursor: pointer;
    white-space: nowrap;
    transition: border-color 0.12s, color 0.12s;
  }
  .cl-why-btn:hover {
    border-color: var(--cl-accent-bdr);
    color: var(--cl-accent);
  }
  .cl-why-btn-open {
    border-color: var(--cl-accent-bdr);
    color: var(--cl-accent);
    background: var(--cl-accent-bg);
  }

  /* why 展開內容 */
  .cl-why-panel {
    padding: 8px 11px 10px 36px;
    font-size: 13px;
    line-height: 1.6;
    color: var(--cl-dim);
    border-top: 1px dashed var(--cl-border);
    background: var(--cl-surface);
  }

  /* ── 手機 ── */
  @media (max-width: 600px) {
    .cl-group { padding: 12px 14px; }
    .cl-progress-bar { padding: 12px 14px 10px; }
    .cl-item-main { padding: 8px 10px; }
    .cl-why-panel { padding-left: 28px; }
  }
  `;
  return <div className="cl-root">
      <style>{css}</style>

      {}
      <div className="cl-progress-bar">
        <div className="cl-progress-row">
          <span className="cl-progress-label">
            {isDone ? UI.progressFull : UI.progress(count, total)}
          </span>
          <span style={{
    display: "flex",
    gap: "8px",
    alignItems: "center"
  }}>
            {isDone && <span className="cl-progress-done">✓</span>}
            {count > 0 && <button type="button" className="cl-reset-btn" onClick={reset}>
                {UI.resetBtn}
              </button>}
          </span>
        </div>
        <div className="cl-track">
          <div className="cl-fill" style={{
    width: pct + "%"
  }} />
        </div>
      </div>

      {}
      <div className="cl-groups">
        {safeGroups.map(group => <div key={group.id} className="cl-group">
            <div className="cl-group-header">
              <span className="cl-group-label">{group.label}</span>
            </div>
            {group.why && <div className="cl-group-why">{group.why}</div>}
            <div className="cl-items">
              {(group.items || []).map(item => {
    const isOn = !!checked[item.id];
    const whyOpen = expandedWhy === item.id;
    return <div key={item.id} className={"cl-item" + (isOn ? " cl-item-checked" : "")}>
                    <div className="cl-item-main" role="checkbox" aria-checked={String(isOn)} tabIndex={0} onClick={() => toggleItem(item.id)} onKeyDown={e => (e.key === " " || e.key === "Enter") && toggleItem(item.id)}>
                      {}
                      <div className={"cl-checkbox" + (isOn ? " cl-checkbox-on" : "")}>
                        {isOn && <svg width="10" height="10" viewBox="0 0 10 10" fill="none" stroke="white" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
                            <polyline points="1.5,5 4,7.5 8.5,2.5" />
                          </svg>}
                      </div>
                      {}
                      <span className="cl-item-text">{item.label}</span>
                      {}
                      {item.why && <button type="button" className={"cl-why-btn" + (whyOpen ? " cl-why-btn-open" : "")} onClick={e => {
      e.stopPropagation();
      toggleWhy(item.id);
    }} aria-expanded={String(whyOpen)}>
                          {UI.whyLabel}
                        </button>}
                    </div>
                    {}
                    {whyOpen && item.why && <div className="cl-why-panel">{item.why}</div>}
                  </div>;
  })}
            </div>
          </div>)}
      </div>
    </div>;
};

<Info>
  本附錄是操作性清單，完整說明、見解與威脅模型見 [03-3 安全、隱私與供應鏈風險](/code-agent/judgment/security-privacy-supply-chain)；此處只留判準與「為什麼要勾」。
</Info>

<Warning>
  涉及不可逆的資料外洩與系統破壞風險。所有可執行設定變更請先在 throwaway 環境驗證，再上正式工作流。動手前先讀 [03-3](/code-agent/judgment/security-privacy-supply-chain) 對應節，不要只照清單打勾。
</Warning>

<CheckList
  lang="zh"
  groups={[
{
  id: "onboarding",
  label: "1. 上手前（每個新工具）",
  why: "把資料丟進去之後，預設值多半對你不利；要在第一筆資料送達前確認訓練退出、保留期限、隔離機制，否則事後只能等保留期自然消滅。",
  items: [
    { id: "ob-1", label: "找到並確認訓練退出開關狀態（Claude：Help Improve Claude；ChatGPT：Improve the model for everyone；Gemini：Keep Activity）。", why: "不確認就是預設接受；Free 方案通常預設開啟訓練資料使用。" },
    { id: "ob-2", label: "確認對話保留期限與自動刪除設定（Free / Pro / Team / Enterprise 各自不同）。", why: "保留期越長，暴露窗口越大；Enterprise 通常短於個人方案。" },
    { id: "ob-3", label: "知道如何開「暫時對話 / Temporary Chat」處理一次性敏感內容。", why: "暫時對話不進記憶、不用於訓練，是一次性隔離的最低成本手段。" },
    { id: "ob-4", label: "區分 Free 方案與 Team / Enterprise 的資料條款（企業預設多不訓練，個人預設多會）。", why: "條款不同；把企業方案的預設套到個人帳號上是最常見的認知錯誤。" },
    { id: "ob-5", label: "把當前帳號的開關狀態寫進 CLAUDE.md 或 MEMORY.md，避免下次切帳號時混淆。", why: "狀態是易忘的配置，寫下來才不靠記憶。" },
  ],
},
{
  id: "credentials",
  label: "2. 憑證與權限",
  why: "代理一旦擁有你的 PAT 或讀到 ~/.aws/credentials，任何 prompt injection 攻擊鏈的最後一里路就是它。獨立、最小範圍的代理專用憑證，是把「攻擊者控制代理」與「攻擊者控制你的全部帳號」之間的防火牆。",
  items: [
    { id: "cr-1", label: "代理使用專用、最小範圍憑證（非個人 PAT / 個人帳號）。", why: "個人 PAT 代理被入侵 = 你的帳號被入侵。代理專用 + 短效 + 細粒度 scope 三項都要成立。" },
    { id: "cr-2", label: "權限 deny list 含密鑰路徑（~/.ssh、~/.aws、**/.env*、~/.gnupg）與危險指令（curl * | bash、ssh *、wget * | bash）。", why: "靜態硬牆比動態判斷可靠；清單命中即拒絕，不給 safety model 留判斷空間。" },
    { id: "cr-3", label: "工具權限對齊任務範圍：MCP server 給最窄 OAuth scope；Bash 規則用 ask 而非 allow 處理未知指令。", why: "最小權限原則；超出任務需要的權限是潛在的攻擊面。" },
    { id: "cr-4", label: ".env 已 gitignore，且有 .env.example（含所有 key、value 為佔位符）。", why: ".env 進 git 就是公開；.env.example 讓協作者知道要設哪些 key。" },
    { id: "cr-5", label: "任何「個人帳號設定的全開權限」不直接套到自動化迴圈或無人監控的 session。", why: "全開權限在自動迴圈裡相當於讓攻擊者無阻礙地執行任意操作。" },
  ],
},
{
  id: "supply-chain",
  label: "3. 供應鏈（安裝 Skill / Plugin / 規則 / MCP server 前）",
  why: "第三方資產是「信任外部程式碼在你的機器上跑」。Snyk ToxicSkills 2026-02 報告指出，當時掃的 3,984 個公開 skill 中約 36% 含 prompt injection，總計 1,467 個惡意 payload。收錄在市集不等於安全；有 README 不等於可審查。",
  items: [
    { id: "sc-1", label: "掃描隱藏 Unicode 與 bidi 控制字元（U+200B 至 U+FEFF、U+202A 至 U+202E）。", why: "人類看不到，模型看得到；是 hidden unicode / bidi 攻擊的載體。" },
    { id: "sc-2", label: "掃描可疑外連與覆寫（curl、wget、nc、ssh、ANTHROPIC_BASE_URL、enableAllProjectMcpServers）。", why: "覆寫 ANTHROPIC_BASE_URL 可重定向 API 流量外洩 API key；enableAllProjectMcpServers 可自動核准未審查的 MCP server。" },
    { id: "sc-3", label: "確認來源可溯（具名作者 / 官方組織）與維護近度（最後 commit 距今多久）。", why: "來源不明 + 久未維護 = 高風險。provenance 是信任訊號，缺乏就是警告。" },
    { id: "sc-4", label: "權限範圍合理：無倉庫外寫入、無讀取密鑰路徑、無對外網路（除非功能明確需要）。", why: "合理的工具不需要超出功能範圍的權限；超範圍就是紅旗。" },
    { id: "sc-5", label: "看 SKILL.md / plugin.json / .mcp.json 的 frontmatter：描述欄、觸發條件、輸入輸出是否與宣稱一致。", why: "宣稱與實際不符是常見的惡意 payload 隱藏手法。" },
    { id: "sc-6", label: "首次安裝在 throwaway 環境或容器內跑一次，觀察是否有對外連線或非預期的檔案寫入。", why: "隔離測試是最便宜的供應鏈驗證；在正式環境跑才發現問題代價高很多。" },
  ],
},
{
  id: "untrusted",
  label: "4. 不可信內容處理",
  why: "當「私密資料 + 不可信內容 + 對外通道」三個條件同存，提示注入就會升級成資料外洩工具（Willison 框架）。解析與行動分離是最便宜的解方：受限的代理只負責抽事實，擁有權限的代理只接乾淨摘要。",
  items: [
    { id: "ut-1", label: "處理外部 PDF / DOCX / 網頁前，先抽取所需文字、去除註解與隱藏內容。", why: "原始檔含 metadata 與隱藏內容，直接餵給有權限的代理是擴大攻擊面。" },
    { id: "ut-2", label: "解析與行動分離：受限代理解析（read-only、無對外通道），有權限代理只處理乾淨摘要。", why: "這是 Willison triple threat 的直接對策；兩個代理分開就無法同時滿足三條件。" },
    { id: "ut-3", label: "不把外部連結直接餵給有權限的代理。", why: "外部連結可能是注入載體；有權限代理直接跟進等於主動引狼入室。" },
    { id: "ut-4", label: "對截圖（OCR 出的文字）也視為不可信內容，先抽事實再餵。", why: "OCR 出的文字與直接輸入沒有本質差異，攻擊者可在截圖裡藏指令。" },
    { id: "ut-5", label: "接收外部內容的 session 不在同一個 context 跑可寫檔、可發訊的代理。", why: "同一 context 同時有外部輸入與敏感操作能力，是 prompt injection 的完美前提。" },
  ],
},
{
  id: "memory",
  label: "5. 記憶與輪換",
  why: "記憶檔是跨 session 累積的，攻擊者只要在某次 session 寫進一條載荷，未來 session 組裝時就會被觸發（Microsoft 2026-02 報告指出 31 家公司 / 14 個產業受記憶污染波及）。記憶狹窄、定期輪換，是污染攻擊鏈的有效斷點。",
  items: [
    { id: "mm-1", label: "記憶檔保持狹窄，不含密鑰、不含個資、不含供應商 NDA 內容。", why: "記憶污染讓攻擊者跨 session 累積載荷；狹窄記憶降低可利用的表面積。" },
    { id: "mm-2", label: "處理不可信內容後輪換 / 清理專案記憶（~/.claude/projects/<repo>/memory/）。", why: "不輪換就是讓潛在的污染留在下一個 session 的起始狀態。" },
    { id: "mm-3", label: "定期（建議每季）全審視一次 MEMORY.md 與各 repo 記憶目錄。", why: "記憶會隨時間累積無用或危險的內容；定期清理是維護衛生的最低要求。" },
    { id: "mm-4", label: "高風險工作流（mass web scraping、untrusted repo 審查）跑完後，主動 reset 該 session 的專案記憶。", why: "高風險工作流污染機率最高；每次跑完就清，比事後發現再清代價低。" },
  ],
},
{
  id: "maintenance",
  label: "6. 維護",
  why: "CVE 是有時效的。Claude Code 的兩個關鍵 CVE（CVE-2025-59536 與 CVE-2026-21852）的修補都在較新版（v1.0.111+ 與 v2.0.65+）。過期版本 = 已知漏洞 = 早晚出事。",
  items: [
    { id: "mt-1", label: "工具保持更新（留意安全公告與 CVE；Claude Code 建議 >= v2.0.65）。", why: "CVE-2026-21852（CVSS -）：攻擊者控制 app 可覆寫 ANTHROPIC_BASE_URL 外洩 API key；v2.0.65+ 已修補。" },
    { id: "mt-2", label: "不在自動化迴圈使用 --dangerously-skip-permissions 或同等的全開旗標。", why: "全開權限在自動迴圈裡是切除了所有剎車；一旦被注入就是無阻礙執行。" },
    { id: "mt-3", label: "自動迴圈具備 kill switch（process group kill + heartbeat dead-man switch）。", why: "被入侵的 process 不會自殺；必須靠外部機制在 30 秒內終止整個 process group。" },
    { id: "mt-4", label: "工具呼叫有結構化日誌（時間戳、工具名、command 摘要、檔案觸及、審批結果）。", why: "入侵痕跡在 trace 裡會顯得異常；沒有日誌就沒有溯源能力。" },
    { id: "mt-5", label: "每季掃一次已安裝的 Skill / Plugin / MCP server 清單，把不用的 remove 掉。", why: "不用的元件是靜默的攻擊面；定期清理是最省事的縮小攻擊面手段。" },
  ],
},
]}
/>

***

## 7. 一次性應用情境

<Tabs>
  <Tab title="新裝 repo 跑 agent">
    1. clone 完先看 `.mcp.json`、`.github/workflows/`、`.claude/`、`AGENTS.md` 這幾個檔。陌生的 server、不明的 workflow、來路不明的 rule，先關掉再說。
    2. 在 throwaway 環境或容器內跑的反例是使用 `--dangerously-skip-permissions`：預設拒絕。給最小權限，缺什麼再開。
    3. 第一次跑就用第 3 節的三條 `rg` 掃一次，記錄所有 hit 與處置。
    4. 確認帳號的訓練退出開關（在第一次送資料前）。
    5. 跑完若有處理不可信內容，依第 5 節輪換專案記憶。
  </Tab>

  <Tab title="裝看起來很熱門的 Skill">
    1. **來源**：作者具名？組織是誰？最後 commit 是什麼時候？
    2. **審查**：把 `SKILL.md` 與其引用的腳本讀完；前 50 行內有沒有可疑指令或外連？
    3. **權限**：要寫檔嗎？要連網嗎？要 token 嗎？對應到任務「真的需要嗎」？
    4. **隔離**：先在容器內裝、跑一次空任務、觀察網路與檔案行為。
    5. **升版**：追作者的 release；升版前重跑這 4 步。
  </Tab>
</Tabs>

***

## 8. 對應單元

事實主張依官方文件，快變動項標註截至 2026-05。

* 威脅模型、攻擊鏈與 CVE：[03-3 安全、隱私與供應鏈風險](/code-agent/judgment/security-privacy-supply-chain)
* 評估框架（不要只看 star）：[03-2 跳脫 star 數迷思](/code-agent/judgment/beyond-github-stars)
* 個人 benchmark 與驗證習慣：[03-4 建立個人 benchmark](/code-agent/judgment/personal-benchmark)
* 設定速查：[附錄 A 各工具設定速查表](/code-agent/appendix/settings-cheatsheet)
* MCP 攻擊面與最小防護：[04-9 跨工具整合與 MCP](/code-agent/customization/mcp-integration)
* Plugin 的供應鏈 SOP：[04-7 Plugin](/code-agent/customization/plugins)
* Hook 作為「強制執行」層：[04-6 Hooks](/code-agent/customization/hooks)
* 延伸資源（安全工具、Snyk 等）：[附錄 D 延伸資源與社群清單](/code-agent/appendix/resources)
