Claude Code 源码分析第 23 课 · 第 08
第 23 课

沙箱与安全

Claude Code 如何通过操作系统级隔离来限制 AI 生成的 shell 命令,以及如何通过平台本机钥匙串存储来保护凭据。

01 Overview

原则上,Claude 运行的每个 Bash 命令都可以读取您的 SSH 密钥、窃取文件或写入您的设置。 沙箱系统在操作系统级别阻止这种情况发生——在任何进程可以打开文件或进行网络调用之前。 本课涵盖:

A部分

沙箱适配器

Bridges @anthropic-ai/sandbox-runtime Claude Code 的设置和工具系统。

B部分

网络和文件系统控制

域允许/拒绝列表、文件系统允许/拒绝路径、权限规则如何映射到沙箱配置。

C部分

安全存储

macOS 钥匙串 + OAuth 令牌和 API 密钥的明文后备,具有错误时过时缓存。

覆盖源文件
utils/sandbox/sandbox-adapter.ts · utils/sandbox/sandbox-ui-utils.ts · commands/sandbox-toggle/index.ts · commands/sandbox-toggle/sandbox-toggle.tsx · components/sandbox/SandboxSettings.tsx · components/sandbox/SandboxDependenciesTab.tsx · components/sandbox/SandboxOverridesTab.tsx · utils/secureStorage/index.ts · utils/secureStorage/macOsKeychainStorage.ts · utils/secureStorage/macOsKeychainHelpers.ts · utils/secureStorage/keychainPrefetch.ts · utils/secureStorage/fallbackStorage.ts · utils/secureStorage/plainTextStorage.ts
02 沙盒架构

Claude Code 本身不实现沙箱原语。 它包裹着 @anthropic-ai/sandbox-runtime - 这 BaseSandboxManager - 通过一个瘦适配器,添加设置集成、工作树感知和 Claude 特定的安全规则。 公共表面是一个单一的 SandboxManager object.

flowchart TD CC["Claude Code\n(sandbox-adapter.ts)"] --> |"convertToSandboxRuntimeConfig()"| CFG["SandboxRuntimeConfig\n{ network, filesystem, ripgrep ... }"] CFG --> BSM["BaseSandboxManager\n(@anthropic-ai/sandbox-runtime)"] BSM -->|macOS| SB["seatbelt\n(sandbox-exec)"] BSM -->|Linux/WSL2| BW["bubblewrap (bwrap)\n+ seccomp filter\n+ socat proxy"] SB --> PROC["sandboxed\nprocess"] BW --> PROC CC --> |"wrapWithSandbox(cmd)"| BSM PROC --> |"violation event"| VS["SandboxViolationStore"] VS --> UI["SandboxDoctorSection\nstderr annotation"] style CC fill:#22201d,stroke:#7d9ab8,color:#b8b0a4 style BSM fill:#1a1816,stroke:#8e82ad,color:#b8b0a4 style SB fill:#1f241d,stroke:#6e9468,color:#b8b0a4 style BW fill:#1f241d,stroke:#6e9468,color:#b8b0a4 style PROC fill:#2a201b,stroke:#c47a50,color:#b8b0a4

平台后端

沙箱后端在运行时自动选择:

PlatformBackendDependencies
macOS 苹果安全带(sandbox-exec) 内置 — 零安装。 仅有的 ripgrep needed.
Linux / WSL2 气泡膜(bwrap) + seccomp + 震惊 apt install bubblewrap socat + seccomp BPF 滤波器来自 @anthropic-ai/sandbox-runtime
WSL1 / Windows 不支持 isSupportedPlatform() 返回假; /sandbox 命令隐藏
平台检测和依赖检查代码

平台支持和依赖关系健康状况都会在进程生命周期内被记住 - isSupportedPlatform() and checkDependencies() 每次通话 memoize() 来自 lodash-es,因此后续调用会命中缓存,而不是子进程。

// sandbox-adapter.ts — memoized dependency check
const checkDependencies = memoize((): SandboxDependencyCheck => {
  const { rgPath, rgArgs } = ripgrepCommand()
  return BaseSandboxManager.checkDependencies({
    command: rgPath,
    args: rgArgs,
  })
})

// memoized platform check
const isSupportedPlatform = memoize((): boolean => {
  return BaseSandboxManager.isSupportedPlatform()
})

在 Linux 上,检查了三个单独的依赖项: bwrap (容器化), socat (网络代理管道)和 seccomp BPF 过滤器。 Seccomp 是一个 warning (不是错误)——沙箱可以在没有它的情况下运行,但 Unix 域套接字阻塞不可用。

// SandboxDependenciesTab.tsx — Linux dep check display
// bwrap: bubblewrap (required)
// socat: network proxy (required)
// seccomp filter: optional — blocks unix domain sockets
bwrapMissing   && "apt install bubblewrap"
socatMissing   && "apt install socat"
seccompMissing && "npm install -g @anthropic-ai/sandbox-runtime"
03 三种沙盒模式

用户通过选择沙箱行为 /sandbox,这呈现 SandboxSettings 有三个选择:

模式1

无沙箱(已禁用)

所有 Bash 命令都在未沙盒的情况下运行。 克劳德必须为每个新的命令模式征求许可。 默认状态。

模式2

常规(沙盒)

命令在操作系统隔离内运行。 克劳德在执行预先批准的列表之外的命令之前仍然会询问。

模式3

自动允许(沙盒)

由于沙箱提供了安全保证,因此命令会自动批准,无需提示。 最大生产力。

模式源自两个布尔设置: sandbox.enabled and sandbox.autoAllowBashIfSandboxed。 还有一个 sandbox.allowUnsandboxedCommands 控制命令是否明确从沙箱中排除的标志(sandbox.excludedCommands)仍然允许在沙箱外运行。

SandboxSettings.tsx 中的模式选择逻辑
// SandboxSettings.tsx
type SandboxMode = 'auto-allow' | 'regular' | 'disabled'

const getCurrentMode = (): SandboxMode => {
  if (!currentEnabled) return "disabled"
  if (currentAutoAllow) return "auto-allow"
  return "regular"
}

当用户选择一种模式时, setSandboxSettings() 被称为与组合 enabled, autoAllowBashIfSandboxed,并且可选地 allowUnsandboxedCommands — 全部写给 localSettings (i.e. .claude/settings.local.json 在项目目录中)。

// sandbox-adapter.ts — mapping mode → settings
// auto-allow:  { enabled: true,  autoAllowBashIfSandboxed: true  }
// regular:     { enabled: true,  autoAllowBashIfSandboxed: false }
// disabled:    { enabled: false }
async function setSandboxSettings(options: {
  enabled?: boolean
  autoAllowBashIfSandboxed?: boolean
  allowUnsandboxedCommands?: boolean
}): Promise<void> {
  updateSettingsForSource('localSettings', {
    sandbox: {
      ...existingSettings?.sandbox,
      ...options,
    },
  })
}

排除特定命令

某些工具(例如 Docker 守护进程管理或硬件接触命令)无法在沙箱内运行。 这 /sandbox exclude "pattern" 命令将模式附加到 sandbox.excludedCommands 在本地设置中。 适配器还暴露了 addToExcludedCommands() 用于沙箱违规事件后的程序排除。

// commands/sandbox-toggle/sandbox-toggle.tsx — /sandbox exclude subcommand
if (subcommand === 'exclude') {
  const cleanPattern = commandPattern.replace(/^["']|["']$/g, '')
  addToExcludedCommands(cleanPattern)
  // writes to localSettings: sandbox.excludedCommands
}
策略锁定:企业管理的沙箱设置

When flagSettings or policySettings 显式设置任何沙箱键,这些设置优先于 localSettings 并且用户无法在本地覆盖它们。 areSandboxSettingsLockedByPolicy() 检测到这一点和 /sandbox 命令显示错误:

// sandbox-adapter.ts — policy lock detection
function areSandboxSettingsLockedByPolicy(): boolean {
  const overridingSources = ['flagSettings', 'policySettings'] as const
  for (const source of overridingSources) {
    const settings = getSettingsForSource(source)
    if (
      settings?.sandbox?.enabled !== undefined ||
      settings?.sandbox?.autoAllowBashIfSandboxed !== undefined ||
      settings?.sandbox?.allowUnsandboxedCommands !== undefined
    ) return true
  }
  return false
}

还有一种无证的 sandbox.enabledPlatforms array——为 NVIDIA 企业部署而添加——允许管理员将沙箱限制到特定平台(例如 ["macos"])而不全局禁用它。

04 网络控制

沙箱在操作系统级别强制执行网络访问。 Claude Code 将其权限规则和设置转换为 NetworkRestrictionConfig that BaseSandboxManager 通过平台后端强制执行。

域允许/拒绝列表

允许的域来自合并在一起的两个来源:

  1. sandbox.network.allowedDomains — 设置 JSON 中的显式列表
  2. permissions.allow 形式规则 WebFetch(domain:example.com)

被拒绝的域名来自 permissions.deny 遵循相同的规则 WebFetch(domain:...) pattern.

// sandbox-adapter.ts — convertToSandboxRuntimeConfig()
for (const ruleString of permissions.allow || []) {
  const rule = permissionRuleValueFromString(ruleString)
  if (rule.toolName === WEB_FETCH_TOOL_NAME &&
      rule.ruleContent?.startsWith('domain:')) {
    allowedDomains.push(rule.ruleContent.substring('domain:'.length))
  }
}
// Same pattern for deniedDomains using permissions.deny
allowedManagedDomainsOnly — 企业网络锁定

When policySettings.sandbox.network.allowManagedDomainsOnly is true, only 域名来自 policySettings 被使用 - 用户级允许规则 localSettings 被完全忽略。 沙箱询问回调也被包装以静默阻止未经批准的出站连接而不提示:

// sandbox-adapter.ts — initialize()
const wrappedCallback: SandboxAskCallback = async (hostPattern) => {
  if (shouldAllowManagedSandboxDomainsOnly()) {
    logForDebugging(
      `[sandbox] Blocked network request to ${hostPattern.host} (allowManagedDomainsOnly)`
    )
    return false  // silently deny
  }
  return sandboxAskCallback(hostPattern)
}

附加网络设置

设定键TypePurpose
sandbox.network.allowUnixSockets string[] 允许的特定 Unix 域套接字路径
sandbox.network.allowAllUnixSockets boolean 允许所有 Unix 套接字(禁用 seccomp 套接字过滤器)
sandbox.network.allowLocalBinding boolean 允许绑定到本地端口(例如用于开发服务器)
sandbox.network.httpProxyPort number 覆盖沙箱使用的 HTTP 代理端口
sandbox.network.socksProxyPort number 覆盖沙箱使用的 SOCKS 代理端口
05 文件系统控制

文件系统配置由四个列表构建 - allowWrite, denyWrite, allowRead, denyRead - 从多个来源组装并传递给 BaseSandboxManager.

始终允许的写入路径

无论用户设置如何,两个路径都是无条件可写的:当前工作目录('.') 和 Claude 临时目录。 这是任何有用工作所需的最低限度。

// sandbox-adapter.ts — baseline writable paths
const allowWrite: string[] = ['.', getClaudeTempDir()]

始终拒绝写入路径(安全硬编码)

无条件拒绝多个路径,以防止通过设置操作进行沙箱逃逸:

  • All settings.json and settings.local.json 每个设置源的文件
  • 托管设置放置目录
  • .claude/skills 在原始的 Cwd 和当前的 Cwd 中 - 技能在沙盒中运行并且具有完整的 Claude 功能
  • 裸 git repo 哨兵文件 (HEAD, objects, refs, hooks, config) — 阻止 git core.fsmonitor 逃逸向量(CVE 模式,作为问题 #29316 进行跟踪)
// sandbox-adapter.ts — settings escape prevention
const settingsPaths = SETTING_SOURCES.map(source =>
  getSettingsFilePathForSource(source)
).filter((p): p is string => p !== undefined)
denyWrite.push(...settingsPaths)

// skills escape prevention
denyWrite.push(resolve(originalCwd, '.claude', 'skills'))
裸 git repo 沙箱逃逸 — Issue #29316

Git's is_git_directory() 处理包含以下内容的任何目录 HEAD + objects/ + refs/ 作为裸存储库。 如果沙盒命令植入了这些文件,那么下一个就是 git unsandboxed 运行可以加载一个 core.fsmonitor 来自攻击者控制的钩子 config 文件,完全逃离沙箱。

适配器通过两方面的防御来处理这个问题:

  1. 如果是哨兵文件 已经存在 在 cwd 处,将其添加到 denyWrite (bubbblewrap ro-bind 真实文件就位 - 无存根)。
  2. 如果哨兵文件确实 not 存在,将其路径添加到 bareGitRepoScrubPaths 并打电话 scrubBareGitRepoFiles() 每次命令删除植入的任何内容后。
// sandbox-adapter.ts — post-command scrub
function scrubBareGitRepoFiles(): void {
  for (const p of bareGitRepoScrubPaths) {
    try {
      rmSync(p, { recursive: true })
    } catch {
      // ENOENT is the expected case — nothing was planted
    }
  }
}

路径解析:权限规则与 sandbox.filesystem

两种不同的路径解析约定并存,并且将它们混合在一起是一个有记录的脚枪(问题#30067):

Source/path meansResolver
权限规则(Edit(…), Read(…)) 相对于设置文件的目录 resolvePathPatternForSandbox()
sandbox.filesystem.allowWrite etc. 绝对(如书面所示), ~ expanded resolveSandboxFilesystemPath()
Both //path → 绝对 /path (旧版兼容) 两个解析器都处理 // prefix
// resolvePathPatternForSandbox — permission-rule convention
// "/foo/**" → "${settingsRootDir}/foo/**"
if (pattern.startsWith('/') && !pattern.startsWith('//')) {
  const root = getSettingsRootPathForSource(source)
  return resolve(root, pattern.slice(1))
}

// resolveSandboxFilesystemPath — sandbox.filesystem convention
// "/Users/foo/.cargo" → "/Users/foo/.cargo" (absolute, as written)
return expandPath(pattern, getSettingsRootPathForSource(source))
Git 工作树写访问权限

在 git 工作树中, .git 是一个指向主存储库的文件(不是目录) .git/worktrees/name。 在工作树中运行的 Bash 命令需要对 main repo's .git 目录 index.lock 和类似的文件。

initialize() calls detectWorktreeMainRepoPath() 一次并缓存会话结果。 然后将该路径添加到 allowWrite:

// sandbox-adapter.ts
if (worktreeMainRepoPath && worktreeMainRepoPath !== cwd) {
  allowWrite.push(worktreeMainRepoPath)
}

实时配置刷新

沙箱配置不是静态的。 每次用户更新权限或设置文件时, settingsChangeDetector 订阅火爆 refreshConfig(),这称为 BaseSandboxManager.updateConfig(newConfig) 同步地。 这意味着授予新的文件编辑权限会立即生效 - 无需重新启动。

// sandbox-adapter.ts — live settings subscription
settingsSubscriptionCleanup = settingsChangeDetector.subscribe(() => {
  const settings = getSettings_DEPRECATED()
  const newConfig = convertToSandboxRuntimeConfig(settings)
  BaseSandboxManager.updateConfig(newConfig)
})
06 安全存储

Claude Code 通过 OAuth 存储 OAuth 令牌(和旧版 API 密钥) SecureStorage 具有两个具体实现的接口:macOS 钥匙串和明文后备。 适配器在运行时选择正确的实现并将它们与 createFallbackStorage() combinator.

flowchart LR GS["getSecureStorage()"] -->|"darwin"| FS["createFallbackStorage(\n macOsKeychainStorage,\n plainTextStorage\n)"] GS -->|"linux/other"| PT["plainTextStorage\n~/.config/claude/.credentials.json\nchmod 0o600"] FS --> KC["macOsKeychainStorage\nsecurity(1) CLI\nadd-generic-password\nfind-generic-password"] FS --> PT2["plainTextStorage\n(fallback if keychain unavailable)"] style GS fill:#22201d,stroke:#7d9ab8,color:#b8b0a4 style FS fill:#1a1816,stroke:#8e82ad,color:#b8b0a4 style KC fill:#1f241d,stroke:#6e9468,color:#b8b0a4 style PT fill:#2a201b,stroke:#c47a50,color:#b8b0a4 style PT2 fill:#2a201b,stroke:#b8965e,color:#b8b0a4

macOS 钥匙串存储

凭据使用 Apple 的通用密码项存储在 macOS 钥匙串中 security(1) 命令行工具。 服务名称源自配置目录路径的稳定哈希,因此多个 Claude 实例具有不同的 CLAUDE_CONFIG_DIR 值获取单独的钥匙串条目:

// macOsKeychainHelpers.ts — service name derivation
export function getMacOsKeychainStorageServiceName(
  serviceSuffix: string = '',
): string {
  const isDefaultDir = !process.env.CLAUDE_CONFIG_DIR
  const dirHash = isDefaultDir
    ? ''
    : `-${createHash('sha256').update(configDir).digest('hex').substring(0, 8)}`
  return `Claude Code${OAUTH_FILE_SUFFIX}${serviceSuffix}${dirHash}`
}

凭证 JSON 在写入之前会序列化为十六进制。 这避免了逃避问题 security(1)的参数解析器,并且还击败了企业端点安全工具(例如 CrowdStrike)中的朴素纯文本 grep 规则:

// macOsKeychainStorage.ts — write path
const hexValue = Buffer.from(jsonString, 'utf-8').toString('hex')

// Prefer stdin so process monitors see only "security -i", not the payload
const command =
  `add-generic-password -U -a "${username}" -s "${serviceName}" -X "${hexValue}"\n`

if (command.length <= SECURITY_STDIN_LINE_LIMIT) {
  result = execaSync('security', ['-i'], { input: command })
} else {
  // Payload too large for stdin buffer (4032B limit): fall back to argv
  result = execaSync('security', ['add-generic-password', '-U', ...])
}
标准输入缓冲区限制
Apple's security -i 使用 4096 字节缓冲区读取 stdin。 大于约 4032 字节(有 64 字节余量)的有效负载会默默地破坏写入 — 前 4096 字节被用作格式错误的命令,溢出将被忽略。 Claude Code 检测到这一点并回退到 argv,它对 macOS 没有实际大小限制(ARG_MAX 为 1 MB)。
钥匙串缓存:错误时失效和生成跟踪

Each security(1) 同步生成时间约为 500 毫秒。 由于启动时有 50 多个 MCP 连接器进行身份验证,简单的实现会使事件循环停止 25 秒以上。 Claude Code 通过 30 秒 TTL 缓存和 stale-while-error 语义解决了这个问题:

// macOsKeychainHelpers.ts
export const KEYCHAIN_CACHE_TTL_MS = 30_000

export const keychainCacheState: {
  cache: { data: SecureStorageData | null; cachedAt: number }
  generation: number  // incremented on every invalidation
  readInFlight: Promise<SecureStorageData | null> | null
} = { cache: { data: null, cachedAt: 0 }, generation: 0, readInFlight: null }
  • Stale-while-error: 如果一个 security 调用失败,但缓存已经有数据,提供陈旧数据而不是返回 null(这将显示为“未登录”)。
  • 生成计数器: 每增加一次 clearKeychainCache()。 异步读取在生成之前捕获生成; 如果完成时存在较新的一代,则结果将被丢弃 - 防止过时的子进程覆盖并发写入的新数据 update().
  • readInFlight 去重: concurrent readAsync() TTL 未命中期间的调用共享一个子进程,而不是 N 个子进程。

启动预取

keychainPrefetch.ts 开火两 security 子进程在最顶层并行运行 main.tsx,在模块评估之前约 65ms。 当应用程序的异步初始化程序需要凭据时,结果已被缓存并且同步 security 完全避免了生成。

// keychainPrefetch.ts — parallel startup reads
export function startKeychainPrefetch(): void {
  if (process.platform !== 'darwin' || prefetchPromise || isBareMode()) return

  // Fire both subprocesses immediately — they run in parallel with
  // each other AND with main.tsx import evaluation (~65ms saved)
  const oauthSpawn  = spawnSecurity(getMacOsKeychainStorageServiceName(CREDENTIALS_SERVICE_SUFFIX))
  const legacySpawn = spawnSecurity(getMacOsKeychainStorageServiceName())

  prefetchPromise = Promise.all([oauthSpawn, legacySpawn]).then(([oauth, legacy]) => {
    if (!oauth.timedOut)  primeKeychainCacheFromPrefetch(oauth.stdout)
    if (!legacy.timedOut) legacyApiKeyPrefetch = { stdout: legacy.stdout }
  })
}

明文后备存储

在 Linux 上,或当 macOS 钥匙串不可用时(例如 SSH 会话),凭据将回退到 ~/.config/claude/.credentials.json 权限设置为 0o600。 每次写入时都会发出警告:

// plainTextStorage.ts — write path
writeFileSync_DEPRECATED(storagePath, jsonStringify(data), { encoding: 'utf8' })
chmodSync(storagePath, 0o600)  // owner-only read/write
return {
  success: true,
  warning: 'Warning: Storing credentials in plaintext.',
}

后备存储组合器

在macOS, createFallbackStorage(primary, secondary) 用精心排序的迁移逻辑包装这两种实现:

迁移和陈旧主清理逻辑
// fallbackStorage.ts — update() with migration
update(data: SecureStorageData) {
  const primaryDataBefore = primary.read()
  const result = primary.update(data)

  if (result.success) {
    // First-time migration to keychain: delete secondary (plaintext)
    // so the stale .credentials.json doesn't shadow the fresh keychain entry
    if (primaryDataBefore === null) secondary.delete()
    return result
  }

  // Keychain write failed — use plaintext fallback
  const fallbackResult = secondary.update(data)
  if (fallbackResult.success) {
    // Best-effort: remove stale keychain entry so it can't shadow
    // the fresh plaintext data (login loop fix, issue #30337)
    if (primaryDataBefore !== null) primary.delete()
    return { success: true, warning: fallbackResult.warning }
  }

  return { success: false }
}

关键不变量: read() 每当它返回非空时就首选primary。 如果钥匙串持有一个过时的令牌,而明文持有一个新的令牌,则过时的令牌获胜,从而导致 /login 循环。 组合器通过在成功回退写入后删除陈旧的主条目来防止这种情况发生。

07 沙盒 UI 和违规报告

当沙盒命令被阻止时,违规事件存储在 SandboxViolationStore。 两个 UI 表面消耗这个:

  • 标准错误注释: annotateStderrWithSandboxFailures() 当违规匹配时,将人类可读的解释附加到命令的 stderr。
  • SandboxDoctorSection: 在助手的响应区域中呈现,显示哪个路径或域被阻止,并提供有关如何允许它的建议。

在 UI 中显示错误消息时还需要剥离违规标签 - <sandbox_violations> 原始 stderr 中出现的 XML 块被删除以供显示:

// sandbox-ui-utils.ts — strip violation XML from display text
export function removeSandboxViolationTags(text: string): string {
  return text.replace(
    /<sandbox_violations>[\s\S]*?<\/sandbox_violations>/g,
    '',
  )
}
沙箱配置错误的启动警告
如果用户设置 sandbox.enabled: true 但缺少依赖项, getSandboxUnavailableReason() 返回人类可读的原因字符串。 Claude Code 在启动时显示一次(不会默默地忽略损坏的设置),因为用户配置 allowedDomains 期待强制执行——无声的失败是安全的步枪。 (修复问题#34044。)

要点

  • 沙箱适配器是 细桥 — Claude Code 将其设置/权限系统转换为 SandboxRuntimeConfig 并将所有实际进程隔离委托给 @anthropic-ai/sandbox-runtime.
  • macOS 使用 Apple 安全带(零安装); Linux/WSL2 使用 bubblewrap + seccomp + socat。 不支持 WSL1 和 Windows。
  • 三种模式: disabled, regular (沙盒,请求许可), auto-allow (沙盒,无提示)。 在本地设置中存储为两个布尔值。
  • 网络访问是通过以下方式强制执行的 allowedDomains / deniedDomains,由两者组装而成 WebFetch 权限规则和明确 sandbox.network settings.
  • 设置文件和 .claude/skills are 总是被拒绝写访问 防止通过配置注入或技能注入进行沙箱逃逸。
  • 两种路径解析约定共存:权限规则对待 /path 作为相对设置; sandbox.filesystem treats /path 一样绝对。 将它们混合在一起会导致无声损坏(问题#30067)。
  • 每个命令后都会清理裸 git repo 文件以阻止 core.fsmonitor 沙箱逃逸向量。
  • 在 macOS 上,凭证位于以十六进制序列化的钥匙串中。 具有 stale-while-error 和生成跟踪功能的 30 秒 TTL 缓存可避免 500ms-per-spawn 事件循环停顿。
  • 回退存储组合器会在成功回退写入后删除过时的主条目,以防止 /login 循环(问题 #30337)。
  • 钥匙串读取在进程启动时与模块评估并行进行预取 - 每次 macOS 启动时可节省约 65 毫秒。

知识检查

1. Claude Code 使用哪种操作系统级别机制在 macOS 上进行沙箱处理?
2. “自动允许”沙箱模式与“常规”模式有何不同?
3. 为什么是 settings.json 文件无条件添加到 denyWrite?
4. 之间有什么显着差异 resolvePathPatternForSandbox() and resolveSandboxFilesystemPath() 当给定一条路径时 /Users/foo/.cargo?
5. 为什么会 macOsKeychainStorage.update() 在写入钥匙串之前将凭证序列化为十六进制?
6. 发生了什么 createFallbackStorage 当钥匙串写入失败但钥匙串已经保存过时数据并且明文后备写入成功时?