☰
gsd-core 安全加固实战:面向 LLM 工作流的提示注入防御与路径验证体系
2026/10/10 9:03:55 网站建设 项目流程

【免费下载链接】gsd-core

Git. Ship. Done - Core

项目地址:https://gitcode.com/gh_mirrors/ge/gsd-core
点击查看免费下载

本指南围绕 gsd-core 的 Security Hardening 特性展开,系统讲解这套以"纵深防御(defense-in-depth)"为原则的安全体系:它为何存在、由哪些组件构成、每个组件在源码中如何实现、如何配置与验证。读完你将掌握 GSD 项目中防止路径穿越、检测提示注入、防御 Shell 与 JSON 注入的完整技术方案,并能独立运行 CI 注入扫描器检验自己的变更。

为什么 GSD 需要安全加固

gsd-core(Git. Ship. Done)是一款面向 LLM 的规划与执行工作流工具:它生成大量 Markdown 文件(agent 指令、workflow 状态、phase 计划),而这些文件最终会成为 LLM 的系统提示词(system prompts)。这意味着一个关键的安全推论:

任何用户可控文本一旦流入这些文件,都可能成为**间接提示注入(indirect prompt injection)**的载体——攻击者把看似普通的文本(如 PRD、issue、任务描述)嵌入其中,让模型误以为这是系统指令并执行恶意操作。

这一威胁模型在 src/security.cts 的模块头注释中被明确列出,共五类:

#威胁说明
1路径穿越(Path traversal)用户提供的文件路径逃逸出项目目录
2提示注入(Prompt injection)参数/PRD 中的恶意文本内嵌 LLM 指令
3Shell 元字符注入(Shell metacharacter injection)用户文本被 Shell 解释执行
4JSON 注入(JSON injection)畸形 JSON 导致崩溃或状态损坏
5正则 DoS(Regex DoS)精心构造的输入引发灾难性回溯

针对上述模型,该特性拆分为四个组件:集中式安全模块、提示注入守卫 Hook、工作流守卫 Hook,以及 CI 就绪的注入扫描器。下面逐一展开。

组件一:集中式安全模块(security.cts)

这是整套体系的"中央引擎"。注意一个历史沿革:原文档中它被称为security.cjs,但根据 ADR-457(build-at-publish),手写的bin/lib/security.cjs已折叠为 TypeScript 单一事实源 src/security.cts,构建时生成gsd-core/bin/lib/security.cjs,行为逐字节保持一致。本文以下分析均针对src/security.cts。

1.1 路径穿越防护:唯一归属判定 + 双重解析策略

路径安全的核心是**"已解析路径必须落在项目目录内"**这一条铁律,由 isContainedIn 实现,它被注释为"本仓库判定路径归属的唯一规范位置"(ADR-4650):

  • 对分隔符敏感:比较两侧都先补上尾部分隔符再做前缀匹配,避免<root>-evil这种仅共享前缀的兄弟目录被误判为包含关系;
  • target === root视为包含;
  • 支持注入path.win32/path.posix,使 Windows 分隔符语义可在非 Windows 平台测试。

在其之上,validatePath 完成完整的输入校验流程,处理了五类细节:

  1. 空值与类型检查:非字符串、空串直接拒绝;
  2. NUL 字节拒绝:含\0的路径立即失败;
  3. 绝对路径策略:默认拒绝绝对路径,仅当显式传入allowAbsolute时才放行(且仍需通过包含性校验);
  4. 符号链接双保险:先fs.realpathSync解析;若失败则用lstatSync判别"悬空符号链接"(链接存在但目标不存在)并拒绝——这一区分防止悬空链接指向外部不存在路径时被回退逻辑重新放行,也堵死了"存在性预言机"(existence oracle)侧信道;
  5. 祖先回溯规范化:若目标本身尚不存在,则向上逐层找到最近存在的祖先并 realpath 之,再拼接剩余段。这正是 REQ-SEC-05 的实现基础——macOS 上/var是/private/var的符号链接,若不做规范化,/var/...与/private/var/...的字符串前缀比较会误判(详见源码 L107-L132 的注释)。

对外暴露的四组 API 分工明确:

  • 抛异常版:assertWithinRoot(CLI 命令使用)、requireSafePath(委托前者);
  • 返回 null 版:tryWithinRoot——不安全时严格返回null而非逃逸路径,因为旧版validatePath在穿越分支仍会填充resolved字段,直接返回它会复现"看似可用实则危险"的缺陷;
  • 类型品牌(branded type):ContainedPath 用unique symbol标记,普通string不可赋值给它,持有ContainedPath即证明包含性检查已通过;
  • 纯词法版:tryWithinRootLexical / assertWithinRootLexical——只做path.resolve不做任何文件系统访问,适用于"目标尚不存在"(如 mkdir 前校验目的地)或必须保留符号链接的场景,但源码明确警告:词法检查"看不见符号链接",依赖它做写约束的调用方必须自带符号链接拒绝逻辑。

此外,PathAcceptance 用命名策略取代了旧的{ allowAbsolute: true }布尔标志:RelativeOnly只接受相对路径;AbsoluteInsideRoot表示"绝对路径可参与判定,但包含性绝不放松——逃出根目录的绝对路径与穿越一样被拒绝"。

与路径安全配套的是可信全局根白名单加载器 loadTrustedGlobalRoots,读取配置config.agent_skills_security.trusted_global_roots,规则包括:展开~、拒绝非绝对路径(项目相对路径视为越界)、对每个条目做 realpath 规范化并去重,且拒绝文件系统根与 home 目录本身作为信任根(防止 macOS APFS 大小写不敏感的/users/alicevs/Users/alice绕过)。

1.2 提示注入检测:多层级模式扫描

scanForInjection 是代码库级扫描引擎,返回{ clean, findings, structuredFindings }结构化结果。它综合了三类检测源:

  • INJECTION_PATTERNS:核心正则集,按攻击手法分组——指令覆盖(ignore all previous instructions等)、角色操控(you are now a...、act as a...,其中act as显式排除了plan|phase|wave以减少误报)、系统提示词提取(print/reveal your system prompt)、伪造边界(</system>、[SYSTEM]、<<SYS>>)、外泄(curl/wget https://、base64 ... send)、工具操控(run bash tool)。注释特别说明:<instructions>被有意排除,因为它是 GSD 的合法提示结构;
  • MARKDOWN_LINK_PATTERNS:Markdown 链接攻击面,含javascript:协议、data:URI(仅放行白名单 MIME:image/png|jpe?g|gif|webp...与font/*,SVG 被有意排除因其可内嵌<script>)、含用户信息的 URL、查询串中的令牌泄露(token、api_key、secret等);
  • OBFUSCATION_PATTERN_ENTRIES:混淆手法——字符间空格混淆(i g n o r e)、定界符注入标签、长十六进制序列。

strict模式额外开启三道检查(L491-L510):零宽/不可见 Unicode(\u200B-\u200F、RTL 覆盖、软连字符等)、Unicode 标签块 U+E0000–U+E007F(2025 年供应链攻击向量,不可见字符可内嵌隐藏指令)、以及超长文本(CRLF 规范化后 > 50000 字符视为 prompt stuffing 嫌疑)。

1.3 净化与展示安全

检测之外,模块还提供三层净化函数:

  • sanitizeForPrompt:面向"将嵌入 agent 提示词"的文本——剥离零宽字符、把<system>/<human>等边界标签中和为全角<system-text>、[SYSTEM]/[INST]改写为[SYSTEM-TEXT]、<<SYS>>改写为«SYS-TEXT»;
  • sanitizeForDisplay:面向回显给用户的文本,额外按行过滤协议泄漏标记(如assistant to=...、<|assistant|>);
  • sanitizeLabel:面向"必须渲染为单行"的文件系统名(phase 目录名、归档里程碑标签)。它把 C0 控制字符(含 ESC\x1b、CR、LF)、DEL 与 C1 区间转义为可见表示(\n、\x1b)而非静默剥离——因为 #3458 的复现中,一个名为zz\n0 open items require decisions.\n\x1b[2K...FORGED的 phase 目录会借助换行伪造报告行、借助 ESC 字节直达终端;转义让审查者能看到篡改痕迹而非被悄悄抹平。

1.4 Shell 与 JSON 安全

  • validateShellArg:Shell 参数校验——拒绝空值/非字符串/NUL 字节;检测$(、反引号等命令替换特征并抛错;
  • safeJsonParse:安全 JSON 解析——默认 1 MiB(1048576 字节)大小上限,返回{ ok, value?, error? }结构而非抛异常,畸形输入在破坏状态前被优雅捕获(对应 REQ-SEC-04);
  • validateFieldName:字段名校验,正则^[A-Za-z][A-Za-z0-9 _.\-/]{0,60}$,防止经配置字段名注入正则;
  • validatePhaseNumber:phase 编号参数校验,支持123A、1.2.3与MANIFOLD-64-auth两类合法形态。

1.5 结构化 Schema 校验

validatePromptStructure 对 agent/workflow 两类提示文件的 XML 结构做白名单校验:仅允许objective、process、step、success_criteria、critical_rules、available_agent_types、purpose、required_reading这组已知标签,未知标签记入 violations。这为"提示文件结构不可被注入篡改"提供了又一层保障。

组件二:Prompt Injection Guard Hook(gsd-prompt-guard.js)

hooks/gsd-prompt-guard.js 是注册在PreToolUse事件上的守卫:拦截Write / Edit工具调用,且只扫描目标位于.planning/(agent 上下文文件所在目录)的写入操作。

几个关键实现事实:

  1. Advisory-only 设计(对应 REQ-SEC-03):钩子头注释与崩溃策略HOOK_ON_CRASH.ALLOW都明确"检测但不阻断",目的是让编排者感知可疑内容而不是因误报造成死锁;即使自身崩溃也绝不开始阻断(#3911);
  2. 共享模式集:hooks/lib/injection-patterns.js 是gsd-prompt-guard.js与gsd-read-injection-scanner.js(PostToolUse 扫描 Read/WebFetch/WebSearch 内容)的单一模式来源(#3504),两处表面不再可能各自漂移。该文件头还说明:它刻意不与security.cts的scanForInjection集合统一——Hook 必须能在不加载编译产物树的情况下独立加载,两套集合是"不同表面"而非漂移。其 #4016 超集模式用一个容错命令式覆盖正则取代了五个窄模式,并配套 describePattern 把约 280 字符的正则源码压缩为 ≤50 字符的可读标签用于告警文案;
  3. Kimi 负载归一化:normalizeKimiPayload 处理 kimi-cli 的工具词表差异(WriteFile → Write、StrReplaceFile → Edit),并修复了字段遮蔽漏洞(#2547/#2595):path权威覆盖file_path、无条件重建old_string/new_string、对edit数组做带守卫的 String 强制转换——这些修复堵住了"模型伪造空字段使守卫读到空串而放行"的绕过路径;
  4. 告警输出:命中时输出结构化hookSpecificOutput,含additionalContext与findings数组(规则 IDINJECTION-PATTERN/INVISIBLE-UNICODE),提醒内容将进入 agent 上下文,请审查是否有内嵌指令;若内容是合法的注入讨论文档则正常放行。

在 hooks/hooks.json 中,它注册于PreToolUse的Write|Editmatcher 下,超时 120 秒。

组件三:Workflow Guard Hook(gsd-workflow-guard.js)

hooks/gsd-workflow-guard.js 解决另一类问题:模型绕过 GSD 工作流直接编辑文件,导致变更不被 STATE.md 跟踪、不产生 SUMMARY.md。

  • 启用方式:通过项目配置hooks.workflow_guard开启(默认 false)。读取位置是项目根下.planning/config.json中的config.hooks?.workflow_guard(见 workflowGuardEnabled);
  • 触发面:仅拦截 Write / Edit / MultiEdit 且目标不在.planning/的调用;.planning/文件(GSD 状态管理)直接放行;.gitignore、.env、CLAUDE.md、AGENTS.md、GEMINI.md、settings.json等常规配置/文档也放行(allowedPatterns);
  • 子代理豁免:is_subagent或session_type === 'task'时放行(编排场景);
  • 告警语义:触发时注入WORKFLOW_ADVISORY建议文案,引导使用/gsd:fast(琐碎修复)或/gsd:quick(较大改动)以维持状态跟踪;用户显式要求直接编辑时可忽略;
  • 唯一的硬阻断:在 agent 分支(/^(worktree-)?agent-/)上执行git add -f/--force被硬性拒绝(WORKTREE_AGENT_FORCE_ADD_FORBIDDEN),且该阻断在内部出错时失败关闭(fail-closed)(#3504):通过 failClosedBlockContext 从负载重新推导阻断上下文,无法确证 agent 分支时才退化为放行。advisory 分支则始终失败开放——一个坏掉的建议绝不能卡死每一次工具调用。

组件四:CI 就绪的注入扫描器

这一组件分两条线:Node 测试套件与 Shell 脚本。

4.1 测试套件:prompt-injection-scan.security.test.cjs

tests/prompt-injection-scan.security.test.cjs 是 CI 对整个代码库的注入体检:

  • 扫描范围(SCAN_DIRS):agents、commands、gsd-core/workflows、gsd-core/bin/lib、hooks,文件类型.md/.cjs/.js/.json——即所有会成为 LLM agent 上下文的文件;
  • 分组断言:agent 定义文件必须无注入模式且 ≤ 100K(源码文件天然更大,阈值放宽);workflow / command / hook / lib 文件必须通过scanForInjection(workflow 与 command 走strict: true严格模式);全库不可见 Unicode 扫描;.js/.cjs源文件不得出现伪造的<system>/<assistant>/<human>边界标签;
  • 白名单治理:ALLOWLIST 只豁免"合法讨论注入"的文件(安全文档、测试自身、守卫源码);SIZE_ONLY_WORKFLOWS 只豁免 50K 尺寸告警(如execute-phase/steps/code-review-disposition.md),注入检测照跑不误——注释明确警告"不得把合法引用注入模式的文件放进尺寸豁免表";
  • 回归向量测试(L386-L470):覆盖 frontmatter 注入、commit message 中的[SYSTEM]、PRD 中的<system>标签、phase 描述中的角色操控、系统提示词提取,并含 #2295 的双向回归——fact as an/artifact as a不再误报,而真实的act as an administrator仍必须命中("边界修复不得静默放过真负载");
  • Shell 扫描器专项测试(L483-L620):直接驱动scripts/prompt-injection-scan.sh,验证 #3175 左边界修复——impact/contract/artifact/interact/transact/redact/abstract as a...、reprint the instructions、retrieval('...')、medieval('...')、Jordan mode全部扫描干净,而exec('...')的各种拼写(含child_process.exec、require("child_process").exec)与eval('...')单引号形式、new Function('...'); return ...、DAN mode必须仍然命中。注释特别指出["\x27]是 GNU-grep 专属转义,BSD/macOS grep 不识别,故用["'"'"'"]拼写以保证跨平台可移植性。

4.2 Shell 扫描器:prompt-injection-scan.sh

scripts/prompt-injection-scan.sh 是 CI 工作流实际调用的独立实现(与scanForInjection的 Node 模式集相互独立,二者是 #3175 中刻意区分的两条线)。三种调用模式:

scripts/prompt-injection-scan.sh --diff origin/main # CI 模式:扫描变更的 .md 等文件 scripts/prompt-injection-scan.sh --file path/to/file # 扫描单个文件 scripts/prompt-injection-scan.sh --dir agents/ # 扫描目录下所有文件

其模式集(PATTERNS)按指令覆盖、角色操控、系统提示词提取、伪造边界、工具调用注入(eval(/exec(/Function(...return)、越狱(DAN mode、jailbreak)分组,全部为 POSIX 扩展正则并用(^|[^[:alnum:]])显式书写左边界——因为\b是 GNU 扩展,脚本须兼容 BSD/macOS grep。exec(刻意保持"接收者盲"(receiver-blind):若给.加左边界排除,require('child_process').exec('...')这一最常见的 Node 攻击拼写也会被放过。

退出码契约(ADR-3889、#3908,值从 gsd-core/bin/shared/exit-codes.sh 引入,绝不硬编码):

退出码含义
0已扫描,无发现(clean)
1发现注入模式
64(USAGE)参数错误(未知模式、--file/--dir目标不存在)
66(NO_INPUT)范围已建立但确实为空(如纯文档 PR)——非失败
69(UNAVAILABLE)无法建立扫描范围(bad ref、不在 git 仓库、目录不可读)——从未真正执行

四条退出码全部非零的设计意图是:if ! scanner; then风格的调用方对"干净扫描"与"扫描失败"行为一致——"假绿"只会变红,绝不会把"红"变绿。CI 中66视为通过、69视为失败,杜绝"扫了个寂寞还报绿"。

需求到实现的映射

原文档定义的五个安全需求均已落地,映射关系如下:

需求实现位置
REQ-SEC-01:用户路径必须针对项目目录校验validatePath / assertWithinRoot 的 realpath + 祖先回溯 +isContainedIn归属判定
REQ-SEC-02:注入模式必须在进入规划产物前被检测gsd-prompt-guard.js 的 PreToolUse 扫描 + scanForInjection
REQ-SEC-03:安全 Hook 必须仅咨询(advisory-only)两个 Hook 的HOOK_ON_CRASH.ALLOW与"检测不阻断"策略
REQ-SEC-04:JSON 解析必须优雅捕获畸形数据safeJsonParse 的{ ok, value?, error? }结构
REQ-SEC-05:macOS/var→/private/var符号链接必须处理validatePath 的最近祖先 realpath 回溯逻辑

配置与部署速览

  1. Hook 注册:hooks/hooks.json已预置注册——gsd-prompt-guard.js挂在PreToolUse的Write|Edit(120s 超时),gsd-read-injection-scanner.js挂在PostToolUse的Read|WebFetch|WebSearch,gsd-worktree-path-guard.js挂在Write|Edit|MultiEdit。安装时这些脚本按GSD_HOOK_LIB_FILES白名单随hooks/lib/一起 staging(gsd-prompt-guard.js头注释与 hooks/lib/injection-patterns.js 有说明);
  2. 开启工作流守卫:在项目.planning/config.json中写入{ "hooks": { "workflow_guard": true } }(默认关闭);
  3. 信任根白名单(可选):在配置的agent_skills_security.trusted_global_roots数组中列出绝对路径,仅当确有跨目录读取需求时使用;
  4. 本地自检:提交前运行scripts/prompt-injection-scan.sh --diff origin/main,并执行node --test tests/prompt-injection-scan.security.test.cjs验证全库健康;
  5. 延伸阅读:该特性的姊妹文档 docs/features/improved-prompt-injection-scanner.md 记录了后续演进(invisible Unicode 检测、退出码四态契约、scanEntropyAnomalies熵分析因零调用方被移除的 #2198 决策);完整的安全态势说明见 SECURITY.md 与 docs/security/baseline.md。

设计要点回顾

  • 纵深防御而非单一防线:集中式模块负责"输入验证引擎",Hook 负责"运行期拦截",CI 扫描器负责"变更门禁",净化函数负责"输出收口"——四层互相独立、各有侧重;
  • Advisory-only 是刻意选择:阻断合法操作会制造误报死锁,检测并上抛上下文让编排者决策,是 LLM 工作流安全 Hook 的务实姿态(唯一的硬阻断git add -f例外,且失败关闭);
  • 单一事实源与防漂移:isContainedIn是全仓库唯一的归属判定;hooks/lib/injection-patterns.js是双 Hook 共享的模式来源;退出码来自注册表而非脚本字面量——三者共同防止"修了一处、漏了另一处"的漂移类缺陷。

【免费下载链接】gsd-core

Git. Ship. Done - Core

项目地址:https://gitcode.com/gh_mirrors/ge/gsd-core
点击查看免费下载
上一篇:OpenProject 6.1.0 版本技术解析:成员管理、工作包关系与 API v3 全面升级
下一篇:Refly PTC 模式详解:在 Sandbox 中以 Python SDK 方式编排工具调用

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询