> ## 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.

# Appendix B: Privacy and Security Checklist

> A one-page checklist to work through before handing sensitive data or a project to any AI tool. Covers training opt-out, credentials, supply chain, untrusted content, memory rotation, and maintenance.

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>
  This appendix is an operational checklist. For full explanations, threat model analysis, and background, see [03-3 Security, Privacy, and Supply Chain Risk](/en/code-agent/judgment/security-privacy-supply-chain). This page keeps only the criteria and the "why bother" rationale for each section.
</Info>

<Warning>
  This topic involves risks of irreversible data exfiltration and system compromise. Validate all executable configuration changes in a throwaway environment before applying them to a real workflow. Read the relevant section of [03-3](/en/code-agent/judgment/security-privacy-supply-chain) before acting; do not just tick boxes.
</Warning>

<CheckList
  lang="en"
  groups={[
{
  id: "onboarding",
  label: "1. Before you start (every new tool)",
  why: "Once you hand data over, the defaults are usually not in your favor. Training opt-out, retention limits, and isolation must be confirmed before the first piece of data is sent. After that point, your only option is to wait for the retention period to expire naturally.",
  items: [
    { id: "ob-1", label: "Locate and confirm the training opt-out switch (Claude: Help Improve Claude; ChatGPT: Improve the model for everyone; Gemini: Keep Activity).", why: "Not confirming means accepting the default. Free plans typically default to enabling training data use." },
    { id: "ob-2", label: "Confirm the conversation retention period and auto-delete settings (Free / Pro / Team / Enterprise differ).", why: "Longer retention means a wider exposure window. Enterprise plans typically have shorter retention than personal plans." },
    { id: "ob-3", label: "Know how to open a 'Temporary Chat' for one-off sensitive content.", why: "Temporary Chat is not stored in memory and is not used for training. It is the lowest-cost one-off isolation mechanism." },
    { id: "ob-4", label: "Understand the difference between Free plan and Team / Enterprise data terms (enterprise plans typically do not train on your data by default; personal plans typically do).", why: "Terms differ. Assuming enterprise defaults apply to personal accounts is the most common misunderstanding." },
    { id: "ob-5", label: "Record the current opt-out state of your account in CLAUDE.md or MEMORY.md to avoid confusion when switching accounts.", why: "Toggle state is configuration you will forget. Writing it down removes the dependency on memory." },
  ],
},
{
  id: "credentials",
  label: "2. Credentials and permissions",
  why: "Once an agent has your PAT or can read ~/.aws/credentials, it becomes the last link in any prompt injection attack chain. A dedicated, least-privilege credential for the agent is the firewall between 'attacker controls the agent' and 'attacker controls your entire account.'",
  items: [
    { id: "cr-1", label: "The agent uses a dedicated, minimum-scope credential (not your personal PAT or personal account).", why: "A personal PAT compromised through an agent means your account is compromised. All three must hold: agent-dedicated, short-lived, fine-grained scope." },
    { id: "cr-2", label: "The permission deny list covers secret-bearing paths (~/.ssh, ~/.aws, **/.env*, ~/.gnupg) and dangerous commands (curl * | bash, ssh *, wget * | bash).", why: "A static hard wall is more reliable than dynamic judgment. A deny list hit means instant rejection with no room for the safety model to deliberate." },
    { id: "cr-3", label: "Tool permissions are scoped to the task: MCP servers have the narrowest possible OAuth scope; Bash rules use 'ask' rather than 'allow' for unfamiliar commands.", why: "Principle of least privilege. Permissions beyond what the task requires are an attack surface." },
    { id: "cr-4", label: ".env is gitignored and .env.example exists (all keys present, values as placeholders).", why: ".env in git means public exposure. .env.example tells collaborators which keys to configure." },
    { id: "cr-5", label: "'Full permissions configured for my personal account' are never carried directly into automated loops or unattended sessions.", why: "Full permissions in an automated loop give an attacker unobstructed execution of arbitrary operations." },
  ],
},
{
  id: "supply-chain",
  label: "3. Supply chain (before installing any Skill / Plugin / rule / MCP server)",
  why: "Third-party assets mean trusting external code to run on your machine. The Snyk ToxicSkills February 2026 report found that roughly 36% of 3,984 public skills scanned contained prompt injection, accounting for 1,467 malicious payloads. Being listed in a marketplace does not mean safe; having a README does not mean auditable.",
  items: [
    { id: "sc-1", label: "Scan for hidden Unicode and bidi control characters (U+200B through U+FEFF, U+202A through U+202E).", why: "Humans cannot see them; models can. They are the carrier for hidden unicode / bidi attacks." },
    { id: "sc-2", label: "Scan for suspicious outbound calls and overrides (curl, wget, nc, ssh, ANTHROPIC_BASE_URL, enableAllProjectMcpServers).", why: "Overwriting ANTHROPIC_BASE_URL can redirect API traffic and exfiltrate API keys. enableAllProjectMcpServers can auto-approve unreviewed MCP servers." },
    { id: "sc-3", label: "Confirm traceable provenance (named author / official organization) and recency of maintenance (time since last commit).", why: "Unknown origin plus long inactivity equals high risk. Provenance is a trust signal; its absence is a warning." },
    { id: "sc-4", label: "Permission scope is reasonable: no writes outside the repository, no reading secret-bearing paths, no outbound network (unless the feature explicitly requires it).", why: "A legitimate tool does not need permissions beyond its stated function. Out-of-scope permissions are a red flag." },
    { id: "sc-5", label: "Read the SKILL.md / plugin.json / .mcp.json frontmatter: does the description, trigger conditions, and input/output match what the asset claims to do?", why: "Mismatch between claimed and actual behavior is a common technique for hiding malicious payloads." },
    { id: "sc-6", label: "Install and run once in a throwaway environment or container on the first use, observing for any outbound connections or unexpected file writes.", why: "Isolated testing is the cheapest supply chain verification. Discovering a problem in production costs far more." },
  ],
},
{
  id: "untrusted",
  label: "4. Handling untrusted content",
  why: "When 'private data + untrusted content + outbound channel' coexist in the same context, prompt injection escalates into a data exfiltration path (the Willison framework). Separating parsing from action is the cheapest mitigation: a restricted agent extracts facts only; a privileged agent receives only the clean summary.",
  items: [
    { id: "ut-1", label: "Before processing external PDFs / DOCX files / web pages, extract only the needed text and strip comments and hidden content.", why: "Raw files contain metadata and hidden content. Feeding them directly to a privileged agent expands the attack surface." },
    { id: "ut-2", label: "Separate parsing from action: a restricted agent parses (read-only, no outbound channel); a privileged agent handles only the clean summary.", why: "This is the direct countermeasure to the Willison triple threat. Separating agents means the three conditions can never coexist in one context." },
    { id: "ut-3", label: "Do not feed external links directly to a privileged agent.", why: "External links can be injection carriers. A privileged agent following them is actively inviting the attack." },
    { id: "ut-4", label: "Treat screenshots (OCR-extracted text) as untrusted content too: extract facts before feeding downstream.", why: "OCR output is functionally identical to direct input. Attackers can embed instructions inside screenshots." },
    { id: "ut-5", label: "Do not run write-capable or message-sending agents in the same context that receives external content.", why: "External input and sensitive execution capability in the same context is the perfect precondition for prompt injection." },
  ],
},
{
  id: "memory",
  label: "5. Memory and rotation",
  why: "Memory files accumulate across sessions. An attacker who plants a payload in one session can have it reassemble in a future session (a Microsoft February 2026 report identified 31 companies across 14 industries affected by memory poisoning). Keeping memory narrow and rotating it regularly is an effective break in that attack chain.",
  items: [
    { id: "mm-1", label: "Memory files stay narrow: no secrets, no personal data, no vendor NDA content.", why: "Memory poisoning lets attackers accumulate payloads across sessions. Narrow memory reduces the exploitable surface area." },
    { id: "mm-2", label: "After handling untrusted content, rotate / clean the project memory (~/.claude/projects/<repo>/memory/).", why: "Not rotating means leaving potential contamination in the starting state of the next session." },
    { id: "mm-3", label: "Review MEMORY.md and all repo memory directories in full on a quarterly basis.", why: "Memory accumulates useless or dangerous content over time. Periodic review is the minimum hygiene requirement." },
    { id: "mm-4", label: "After any high-risk workflow (mass web scraping, untrusted repo review), actively reset the project memory for that session.", why: "High-risk workflows have the highest contamination probability. Clearing after each run costs far less than discovering an incident later." },
  ],
},
{
  id: "maintenance",
  label: "6. Maintenance",
  why: "CVEs have an expiry date, or rather, an impact date. The patches for both critical Claude Code CVEs (CVE-2025-59536 and CVE-2026-21852) shipped in later versions (v1.0.111+ and v2.0.65+ respectively). An outdated version equals a known vulnerability equals an incident waiting to happen.",
  items: [
    { id: "mt-1", label: "Keep tools updated (monitor security advisories and CVEs; Claude Code recommended >= v2.0.65).", why: "CVE-2026-21852: an attacker-controlled app can overwrite ANTHROPIC_BASE_URL to redirect API traffic and exfiltrate the API key. Fixed in v2.0.65+." },
    { id: "mt-2", label: "Do not use --dangerously-skip-permissions or equivalent full-open flags in automated loops.", why: "Full permissions in an automated loop removes all brakes. A single injection means unobstructed execution." },
    { id: "mt-3", label: "Automated loops have a kill switch (process group kill + heartbeat dead-man switch).", why: "A compromised process will not terminate itself. An external mechanism must be able to kill the entire process group within 30 seconds." },
    { id: "mt-4", label: "Tool calls produce structured logs (timestamp, tool name, command summary, files touched, approval result).", why: "Attack traces look anomalous in logs before they look obviously malicious. No logs means no ability to trace back." },
    { id: "mt-5", label: "Quarterly: scan the full list of installed Skills / Plugins / MCP servers and remove any that are no longer in use.", why: "Unused components are a silent attack surface. Periodic removal is the cheapest way to shrink it." },
  ],
},
]}
/>

***

## 7. Scenario-specific checklists

<Tabs>
  <Tab title="Running an agent on a newly cloned repo">
    1. After cloning, inspect `.mcp.json`, `.github/workflows/`, `.claude/`, and `AGENTS.md` first. Unknown servers, unrecognized workflows, and unverified rules: disable them before proceeding.
    2. Running `--dangerously-skip-permissions` in a throwaway environment is the anti-pattern, not the recommendation: default to refusing it. Grant minimum permissions and add more only when a specific need arises.
    3. On the first run, execute all three `rg` scans from Section 3 and record every hit and its disposition.
    4. Confirm the account training opt-out switch before sending any data.
    5. If the run processed any untrusted content, rotate the project memory per Section 5.
  </Tab>

  <Tab title="Installing a popular-looking Skill">
    1. **Provenance**: Is the author named? What organization? When was the last commit?
    2. **Review**: Read `SKILL.md` and every script it references. Any suspicious commands or outbound calls in the first 50 lines?
    3. **Permissions**: Does it write files? Does it connect to the network? Does it require a token? Does the task actually need those capabilities?
    4. **Isolation**: Install inside a container first, run one empty task, and observe network and file behavior.
    5. **Upgrades**: Track the author's releases; repeat these 4 steps before every upgrade.
  </Tab>
</Tabs>

***

## 8. Related units

Factual claims are grounded in official documentation; fast-changing items are annotated as of 2026-05.

* Threat model, attack chains, and CVEs: [03-3 Security, Privacy, and Supply Chain Risk](/en/code-agent/judgment/security-privacy-supply-chain)
* Evaluation framework (beyond star counts): [03-2 Beyond GitHub Stars](/en/code-agent/judgment/beyond-github-stars)
* Personal benchmarking and verification habits: [03-4 Building a Personal Benchmark](/en/code-agent/judgment/personal-benchmark)
* Settings reference: [Appendix A: Settings Cheatsheet](/en/code-agent/appendix/settings-cheatsheet)
* MCP attack surface and minimum defenses: [04-9 Cross-Tool Integration and MCP](/en/code-agent/customization/mcp-integration)
* Plugin supply chain SOP: [04-7 Plugins](/en/code-agent/customization/plugins)
* Hooks as the enforcement layer: [04-6 Hooks](/en/code-agent/customization/hooks)
* Extended resources (security tools, Snyk, etc.): [Appendix D: Resources and Community List](/en/code-agent/appendix/resources)
