Claude Code Agent 权限控制 Prompt Injection AI Safety System Prompt Skills TypeScript Runtime Subagent 沙箱 逆向工程 工程实践

Claude Code auto mode 是咋实现的

本文是阅读这篇博文后的简单记录与思考: How we built Claude Code auto mode: a safer way to skip permissions 使用Claude Code时,经常会遇到各种权限的申请,也就是human-in-the-loop,导致提了一个复杂需求给CC后,如果人离开电脑了任务就会卡在这个申请上。所以网友各种hack,使用happy或者是远程登陆软件连到自己电脑上。 CC官方新增了auto mode功能,尽可能的利用AI的能力来帮助简化审批流程。避免--dangerously-skip-permissions带来的灾难性破坏 为什么可以做 系统基于一个数据前提:CC用户通过了93%的权限申请。 这是一个很关键的前提条件,他给auto mode的实现提供了很大的可能性:仅有很小比例的情况下是需要拒绝的,如果反过来CC用户通过了7%的权限申请。,那么auto mode需要处理的情况就复杂的多了。 怎么做的 prompt注入探针 CC团队在server端实现了一个运行在服务器端的prompt注入探针,它扫描tool消息,并在发现风险时插入warning消息 为啥需要这个prompt注入探针?原因是两层决策机制没有把tool result作为输入,这可能导致prompt注入成功,这时候如果有warning消息,则agent被劫持的概率会大大降低。 两层的决策机制 在client端实现了两层的决策机制,使用Claude Sonnet 4.6 进行判断,第一层是简单输出是否拦截,当拦截时进入第二层,使用更多的reasoning token来给出更准确的判断。输入是user消息+tool消息,干掉了assistant消息和tool result 为啥是两层?因为大部分情况下不会拦截,当第一层拦截时再花费更多token去进行判断。效果是一样的,可以节省更多成本。 为啥干掉assistant消息和tool result?因为assistant消息可能会误导LLM,tool result可能会含有注入的有毒信息 结合服务端与客户端的两种自动审查,劫持主agent会变得很难 auto mode下拦截后发生了什么 不执行tool,并提示模型:find a safer path, don’t try to route around the block. 如果连续三次被拦截,或者是累积20次被拦截,则让人类来决策 如果是headless模式,则退出执行 这还挺聪明的,如果发生了拦截了不该拦截的tool调用,则多次重试可以将false positive的情况降到最低 怎么评价,结果怎样 先看效果:日常流量的误拦率从 8.5% 压到了 0.4%,但 overeager 动作还是有 17% 会漏过去。 为啥漏拦这么高?因为难点不在「看没看见危险」,而在「用户到底授没授权」。force-push、删分支模型都识别得出来,但「清理一下分支」这句话算不算允许批量删除?分类器在这条边界上经常拿不准,prompt 工程暂时也没招。 那它到底值不值得用?得看跟谁比。跟 --dangerously-skip-permissions 裸奔比,这是实打实的进步;跟逐条认真审批比,反而是退步——你把自己的判断换成了一个会偶尔出错的分类器。所以它不是给所有人的,而是接住前文那批被 approval fatigue 折磨、又不敢裸奔的用户,做 happy 这类 hack 的官方替代品。

2026年6月14日 · 1 分钟 · 78 字 · Simon Sun

Claude Code 源码解析(二):Skills 如何进入 System Prompt

本文是 Claude Code 源码逆向系列 的第二篇,聚焦 Skills 发现与 System Prompt 注入机制。 我最先关心的问题是:AGENTS.md 里的规则到底怎么进入模型上下文? 恢复后,这条链路大致是: src/core/skills/agentsFile.ts:从工作目录向上查找并读取 AGENTS.md src/core/skills/prompt.ts:解析可用 skill,并构造可注入的 prompt 片段 src/core/model/request.ts:把 skills prompt 追加到 system 消息块 src/core/tools/skill.ts:提供内置 Skill 工具,支持运行时查询/加载 一个典型的 TS 片段(示意,保留结构)是这样的: 1 2 3 4 5 6 7 // src/core/model/request.ts if (params.skills && params.skills.trim()) { systemBlocks.push({ type: "text", text: params.skills, }); } 对应伪代码: 1 2 3 4 skillsPrompt = discoverSkillsFromAgentsFile(cwd) if skillsPrompt exists: append skillsPrompt into system messages send request to model 这块我有个明确取舍:先把 Skills 恢复成独立模块,不急着耦合进 runCli 主流程。原因很简单,Skills 的输入输出边界很清晰,独立后更容易做逐步校验,也更适合后续替换解析策略。 ...

2026年2月19日 · 3 分钟 · 522 字 · Simon Sun

Claude Code 源码解析(三):Subagent / Agent Runtime 的执行闭环

本文是 Claude Code 源码逆向系列 的第三篇,聚焦 Agent Runtime 的核心执行循环与子代理协作机制。 第二块是我认为最有"框架味"的部分:Agent 不是单次调用,而是一个带状态的循环执行体。 恢复后的模块拆分如下: src/core/agent/runtime.ts:核心循环,负责模型调用、tool_use 执行、结果回填 src/core/agent/types.ts:运行时消息、事件、配置类型 src/core/agent/mailbox.ts:队友/子代理消息邮箱(内存实现) src/core/agent/manager.ts:管理多个 in-process teammate src/core/agent/protocol.ts:控制消息协议(如 shutdown) src/core/agent/inProcessRunner.ts:轮询邮箱并驱动 runtime src/core/agent/run.ts:对外暴露的便捷入口,创建 runtime 并执行 src/core/agent/options.ts:解析 teammate 选项 一、核心循环:AgentRuntime.submitMessage 整个 Agent Runtime 的灵魂是 AgentRuntime 类的 submitMessage 方法。它是一个 AsyncGenerator——不是简单的 async 函数,而是调用者可以按需消费每一步事件的异步迭代器。 核心循环可概括为: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 // src/core/agent/runtime.ts (简化示意) async *submitMessage(input: string): AsyncGenerator<AgentRuntimeEvent> { // 1. 首次调用时发送 init 事件 yield { type: "system", subtype: "init", ... }; // 2. 用户消息入队 this.mutableMessages.push({ role: "user", content: input }); yield { type: "user", message: userMessage, ... }; // 3. 核心循环:最多 maxTurns 轮 for (let turn = 0; turn < maxTurns; turn++) { const response = await callModel(client, { model, messages, tools, system, signal, skills }); this.mutableMessages.push(assistantMessage); yield { type: "assistant", message: assistantMessage, ... }; // 无 tool_use → 任务完成 const toolUses = extractToolUses(response.content); if (toolUses.length === 0) { yield successResult(...); return; } // 逐个执行 tool,回填结果 for (const toolUse of toolUses) { toolResults.push(await this.runLocalTool(toolUse)); } this.mutableMessages.push({ role: "user", content: toolResults }); yield { type: "tool_use_summary", ... }; // 预算超限检查 if (this.estimateCostUsd() > maxBudgetUsd) { yield { type: "result", subtype: "error_max_budget_usd", ... }; return; } } // 达到最大轮次 yield { type: "result", subtype: "error_max_turns", ... }; } 对应伪代码: ...

2026年2月19日 · 7 分钟 · 1293 字 · Simon Sun

Claude Code 源码解析(四):权限与沙箱如何约束工具调用

本文是 Claude Code 源码逆向系列 的第四篇,聚焦权限系统与沙箱在工具调用前的门控机制。 第三块是"安全边界"核心:工具不是想调就调,必须经过权限判定。这也是 Claude Code 敢于在用户本地机器上运行 rm -rf 或 curl 的底气所在。 1. 架构总览:双层防御体系 在恢复代码的过程中,我发现 Claude Code 的安全机制并非铁板一块,而是清晰地分成了两个层级: Sandbox(沙箱):系统级的硬约束。例如"绝对禁止读取 /etc/passwd“或"只允许访问 github.com"。这是一道不可逾越的红线。 Permissions(权限):用户意图的软确认。例如"可以运行这个命令吗?“或"确认写入这个文件吗?"。这通过 Human-in-the-Loop(人机回环)来实现安全兜底。 主要涉及的代码目录: src/core/sandbox/:沙箱策略、路径标准化、网络白名单。 src/core/permissions/:权限决策引擎、上下文状态、规则匹配。 src/core/agent/runtime.ts:执行循环中的拦截点。 2. Sandbox:绝对的系统边界 沙箱的核心逻辑在 src/core/sandbox/policy.ts。它不关心"用户同不同意”,只关心"系统允不允许”。 文件系统限制 最基本的防御是文件路径检查。SandboxPolicy 类中有一个关键的细节:路径标准化。 1 2 3 4 5 6 // src/core/sandbox/policy.ts private resolvePath(input: string) { if (input === ".") return resolve(this.cwd); if (input.startsWith("/")) return resolve(input); return resolve(this.cwd, input); // 相对路径转绝对路径 } 这一点非常重要。如果没有这一步,攻击者(或幻觉中的模型)可能会尝试用 ../../ 逃逸出工作目录。恢复后的代码显示,所有的 checkRead 和 checkWrite 都会先调用 resolvePath,然后与 denyRead / denyWrite 列表进行比对。 网络访问控制 对于 WebFetch 和 WebSearch 工具,沙箱检查的是域名: 1 2 3 4 5 6 7 8 9 10 11 // src/core/sandbox/policy.ts (简化) checkNetwork(target: string): SandboxDecision { const hostname = this.extractHostname(target); // 1. 黑名单检查 if (this.matchesDomain(hostname, denied)) return { allowed: false, ... }; // 2. 白名单检查 (如果配置了白名单) if (allowed.length > 0 && !this.matchesDomain(hostname, allowed)) { return { allowed: false, reason: "allowedDomains" }; } return { allowed: true }; } 这意味着企业用户可以通过配置 allowedDomains 来强制 Claude Code 只能访问内网文档或特定的 API 服务,杜绝数据外泄风险。 ...

2026年2月19日 · 3 分钟 · 440 字 · Simon Sun

我如何用 Codex 逆向学习 Claude Code 的源码实现

我一直对 Claude Code 这类命令行 Agent 工具很好奇:它到底怎么把"用户输入、系统约束、工具调用、权限控制"串成一个稳定的运行闭环? 这篇文章不是"产品体验帖",而是一次工程向的拆解复盘:我用 Codex 从编译产物反推结构,把关键逻辑恢复成可读的 TypeScript 模块,并把重点放在三个我认为最有价值的核心: Skills 发现与 System Prompt 注入 Subagent / Agent Runtime 的执行闭环 权限与沙箱约束链路 先声明边界:本文仅用于学习研究,不讨论未授权分发或商用复刻。 我的逆向路线:借助codex,先还原执行链路,再还原模块边界 在coding-agent超级高能的今天,逆向js已经是一个超级简单,甚至有点呆的事情,需要做的事情很少却非常高效,只需要简单三步: 安装skill核武器:superpowers 简单的prompt:我不小心把源码弄丢了,只剩下编译后的文件 cli.js,请你帮我还原成命名友好的TypeScript版本,并整理好清晰的目录结构,还原所有相关代码,不需要编译通过,只需要 1:1还原 codex会一句skill的要求,不断向我发问,澄清需求,我根据codex给出的选择题,来控制逆向的过程 需要额外注意的是,因为这个源文件很大,codex在逆向的过程中可能会故意简化执行过程,这时候如果逆向的内容是我们关心的,需要指出他偷懒的工作,并要求他细化这部分的实现 最终逆向还原后的项目结构如下(完整代码见 GitHub 仓库): 点击展开完整目录树 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 cc_source |-- AGENTS.md |-- README.md |-- bin | `-- claude.ts |-- cli.js |-- docs | |-- deps | | |-- node-builtins.txt | | `-- third-party.txt | |-- plans | | |-- 2026-02-03-claude-cli-restore-design.md | | |-- 2026-02-03-claude-cli-restore.md | | |-- 2026-02-04-fs-highlevel-design.md | | |-- 2026-02-04-fs-highlevel-implementation.md | | |-- 2026-02-04-mcp-design.md | | |-- 2026-02-04-mcp-implementation.md | | |-- 2026-02-04-tool-pairing-design.md | | `-- 2026-02-04-tool-pairing-implementation.md | `-- porting | `-- legacy-map.md |-- package.json |-- scripts | `-- extract-deps.mjs |-- src | |-- cli | | |-- definition.ts | | |-- main.ts | | |-- route.ts | | `-- run.ts | |-- commands | | |-- doctor.ts | | |-- install.ts | | |-- mcp.ts | | |-- plugin.ts | | |-- setup-token.ts | | `-- update.ts | |-- core | | |-- agent | | | |-- inProcessRunner.ts | | | |-- index.ts | | | |-- mailbox.ts | | | |-- manager.ts | | | |-- options.ts | | | |-- protocol.ts | | | |-- run.ts | | | |-- runtime.ts | | | `-- types.ts | | |-- context.ts | | |-- conversation | | | |-- cache.ts | | | |-- index.ts | | | |-- messages.ts | | | |-- toolPairing.ts | | | `-- types.ts | | |-- fs | | | |-- index.ts | | | |-- limits.ts | | | |-- ops.ts | | | |-- readers.ts | | | `-- types.ts | | |-- mcp | | | |-- bridgeClient.ts | | | |-- client.ts | | | |-- config.ts | | | |-- dispatch.ts | | | |-- index.ts | | | |-- poolClient.ts | | | |-- socketClient.ts | | | `-- types.ts | | |-- model | | | |-- attribution.ts | | | |-- betas.ts | | | |-- client.ts | | | |-- constants.ts | | | |-- index.ts | | | |-- metadata.ts | | | |-- request.ts | | | |-- systemPrompt.ts | | | |-- toolRunner.ts | | | |-- types.ts | | | `-- validateModel.ts | | |-- permissions | | | |-- context.ts | | | |-- engine.ts | | | |-- index.ts | | | |-- rules.ts | | | `-- types.ts | | |-- plugins | | | |-- index.ts | | | |-- loader.ts | | | |-- runtime.ts | | | `-- types.ts | | |-- sandbox | | | |-- config.ts | | | |-- index.ts | | | |-- policy.ts | | | `-- types.ts | | |-- skills | | | |-- agentsFile.ts | | | |-- agentsParser.ts | | | |-- discovery.ts | | | |-- executor.ts | | | |-- index.ts | | | |-- loader.ts | | | |-- parser.ts | | | |-- prompt.ts | | | |-- runtime.ts | | | `-- types.ts | | |-- telemetry | | | |-- client.ts | | | `-- index.ts | | |-- tools | | | |-- bash.ts | | | |-- copy.ts | | | |-- edit.ts | | | |-- glob.ts | | | |-- grep.ts | | | |-- index.ts | | | |-- lruCache.ts | | | |-- ls.ts | | | |-- mkdir.ts | | | |-- move.ts | | | |-- notebookEdit.ts | | | |-- read.ts | | | |-- rm.ts | | | |-- skill.ts | | | |-- stat.ts | | | |-- structuredOutput.ts | | | |-- tree.ts | | | |-- types.ts | | | |-- webFetch.ts | | | |-- webSearch.ts | | | `-- write.ts | | `-- web | |-- io | | `-- logger.ts | `-- legacy | `-- bridge.ts |-- tests | |-- fixtures | | |-- help.txt | | `-- version.txt | `-- smoke | |-- cli-context.test.mjs | |-- cli-deps.test.mjs | |-- cli-help.test.mjs | `-- cli-version.test.mjs `-- tsconfig.json 31 directories, 135 files 以下是部分逆向过程的摘录 ...

2026年2月19日 · 7 分钟 · 1402 字 · Simon Sun