From 6011f326668c2f822c0572d843ee4ed26ebe0434 Mon Sep 17 00:00:00 2001 From: loong10k <20489781+loong10k@users.noreply.github.com> Date: Wed, 23 Sep 2026 23:42:04 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix:=20heredoc=20=E6=AD=A3=E6=96=87?= =?UTF-8?q?=E6=8C=89=E5=BD=92=E5=B1=9E=E8=AF=AD=E4=B9=89=E5=BD=92=E5=9B=A0?= =?UTF-8?q?=EF=BC=88=E6=95=B0=E6=8D=AE=E6=AE=B5=20git=20=E6=A0=B7=E4=BE=8B?= =?UTF-8?q?=E8=AF=AF=E6=8A=A5=E6=B8=85=E9=9B=B6=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix-heredoc-git-attribution:mask_heredoc_bodies 四态遮蔽(引号数据全遮蔽/ 无引号保留替换跨度/Shell 解释器不遮蔽/非 Shell 全遮蔽),遮蔽先于替换展开; split_shell_segments 与 _flatten_substitutions 双入口接线。 五态语义回归 12/12;无引号 $(git) 向量与 bash 建模保持不变。 --- .../proposal.md | 36 +++++++++ .../specs/hook-protocol/spec.md | 32 ++++++++ .../tasks.md | 20 +++++ scripts/codeguard/git_syntax.py | 80 +++++++++++++++++++ tests/test_indirect_git_attribution.py | 25 ++++++ 5 files changed, 193 insertions(+) create mode 100644 openspec/changes/2026-09-23-fix-heredoc-git-attribution/proposal.md create mode 100644 openspec/changes/2026-09-23-fix-heredoc-git-attribution/specs/hook-protocol/spec.md create mode 100644 openspec/changes/2026-09-23-fix-heredoc-git-attribution/tasks.md diff --git a/openspec/changes/2026-09-23-fix-heredoc-git-attribution/proposal.md b/openspec/changes/2026-09-23-fix-heredoc-git-attribution/proposal.md new file mode 100644 index 0000000..73197c2 --- /dev/null +++ b/openspec/changes/2026-09-23-fix-heredoc-git-attribution/proposal.md @@ -0,0 +1,36 @@ +# 2026-09-23-fix-heredoc-git-attribution + +## Why + +fix-release-chain-and-gate-attribution 把非 Shell 脚本正文的 git 归因收紧为调用形态,但其 Non-Goal +留了一个敞口:**外层 Bash heredoc/命令文本数据段的 git 样例仍被切段误伤**。本轮实测该敞口高频咬人 +(写作 OpenSpec/测试文档时 heredoc 里的 `git add && git commit && git push` 样例文本 5+ 次整调用被拦, +连写入操作都未执行)。根因:`split_shell_segments` 把 heredoc 正文当 shell 语法切段;且 +`_flatten_substitutions` 先于切段抽取 `$(...)`,连引号定界(全字面量)的正文也拦不住。 + +## What Changes + +- heredoc 正文按**归属语义**遮蔽(保留换行的空白)后再切段/展开: + - 数据程序 + 引号定界符:正文全字面量 → 全遮蔽(杀误报); + - 数据程序 + 无引号:正文会做命令替换展开,`$(...)`/反引号跨度保留扫描(规格既有实测向量); + - bash/sh 等 Shell 解释器:正文是内层 shell 代码 → 不遮蔽、保留既有切段建模; + - python/node:正文非 shell 语法 → 全遮蔽(杀误报)。 +- 遮蔽前移至 `_flatten_substitutions` 入口,保证引号定界正文的 `$(...)` 不被提前抽取。 + +## Capabilities + +### New Capabilities + +无。 + +### Modified Capabilities + +无(heredoc 归属语义以 ADDED requirement 并入 hook-protocol,不重改同一条 requirement)。 + +## Impact + +- `scripts/codeguard/git_syntax.py`(mask_heredoc_bodies + 两处入口接线)。 +- 行为变更:heredoc 数据段的 git 字面量样例不再触发门禁;无引号数据正文的命令替换向量、 + Shell 解释器正文建模、直接命令判定全部不变(五态语义回归锚定)。 +- 已声明边界:python/node **stdin heredoc** 内的 subprocess 调用形态不在归因范围 + (与动态拼接同类;脚本文件路径上的调用形态归因不受影响)。 diff --git a/openspec/changes/2026-09-23-fix-heredoc-git-attribution/specs/hook-protocol/spec.md b/openspec/changes/2026-09-23-fix-heredoc-git-attribution/specs/hook-protocol/spec.md new file mode 100644 index 0000000..b4840b1 --- /dev/null +++ b/openspec/changes/2026-09-23-fix-heredoc-git-attribution/specs/hook-protocol/spec.md @@ -0,0 +1,32 @@ +# hook-protocol(增量):heredoc 正文按归属语义归因 + +## ADDED Requirements + +### Requirement: Heredoc bodies SHALL be attributed by owner semantics + +命令文本中 heredoc 正文的 git 归因 MUST 按归属语义区分:数据程序 + 引号定界符的正文是全字面量 +(文档样例/模板字符串),MUST NOT 视为 shell 语法切段出 git 副作用;数据程序 + 无引号的正文会做 +命令替换展开,`$(...)` 与反引号跨度的内层文本 MUST 保留扫描(其会被外层真实执行);Shell 解释器 +(bash/sh/zsh)接收的正文是内层 shell 代码,MUST 按既有切段规则建模;python/node 等非 Shell 解释器 +接收的正文不是 shell 语法,MUST NOT 按切段归因。遮蔽处理 MUST 先于命令替换展开执行, +否则引号定界正文的字面 `$(...)` 会被误判为可执行替换。 + +#### Scenario: Sample text in a python heredoc is not guarded + +- **WHEN** `python3 - <<'PY'` 的正文含三引号字符串 `'''cd a && git add && git commit && git push'''`(文档样例) +- **THEN** 判定不命中,钩子放行(此前整调用被拦且写入步骤不执行) + +#### Scenario: Command substitution in an unquoted data heredoc is guarded + +- **WHEN** `cat < str: + """heredoc 重定向所在段的程序名(回溯到最近的控制符边界)。""" + seg_start = 0 + for i in range(start - 1, -1, -1): + if command[i] in (";", "\n"): + seg_start = i + 1 + break + if i >= 1 and command[i - 1:i + 1] in ("&&", "||"): + seg_start = i + 1 + break + head = command[seg_start:start] + tokens = head.replace("&&", " ").split() + return tokens[0].rsplit("/", 1)[-1].strip("\"'") if tokens else "" + + +def mask_heredoc_bodies(command: str) -> str: + """heredoc 正文按语义遮蔽为保留换行的空白(fix-release-chain 遗留敞口)。 + + 三类语义(2026-09-23 实测区分,别混): + - 数据程序 + 引号定界符(`cat <<'EOF'`):正文全字面量 → 全遮蔽—— + 模板字符串/帮助文本/文档样例的 git 字面量曾被切段误伤成真实副作用; + - 数据程序 + 无引号(`cat <= 0 else body_end + continue + if out[i] != "\n": + out[i] = " " + i += 1 + else: + for i in range(body_start + 1, body_end): + if out[i] != "\n": + out[i] = " " + return "".join(out) + + def split_shell_segments(command: str) -> list[tuple[str, str | None]]: """只按未引用、未转义的 Shell 控制符切段,保留前置分隔符。 这不是完整 Shell 解释器;嵌套替换由 `_flatten_substitutions` 另行展开。 引号内的 `;`/`&&` 和 `\\;` 是参数数据,不能合成 git config 豁免。 """ + command = mask_heredoc_bodies(command) segments: list[tuple[str, str | None]] = [] start = 0 before: str | None = None @@ -79,6 +158,7 @@ def _flatten_substitutions(command: str) -> str: 能力边界:单引号内的字面量(`'$(git push)'` 不执行)不做引号语义区分, 宁可多拦不漏拦——误拦方向由 lint 门禁自身的 delta 面兜底(不改文件不红)。 """ + command = mask_heredoc_bodies(command) parts: list[str] = [] queue: list[str] = [command] seen = 0 diff --git a/tests/test_indirect_git_attribution.py b/tests/test_indirect_git_attribution.py index 4c55dba..7c37251 100644 --- a/tests/test_indirect_git_attribution.py +++ b/tests/test_indirect_git_attribution.py @@ -78,5 +78,30 @@ def test_non_shell_real_call_resolves_to_unverified(self) -> None: resolve_git_operations(f"node {script}") +class HeredocSemanticsTests(unittest.TestCase): + """heredoc 正文按归属语义归因(2026-09-23 fix-heredoc-git-attribution 五态)。""" + + def test_python_heredoc_sample_text_not_guarded(self) -> None: + cmd = "python3 - <<'PY'\ns = '''cd a && git add && git commit && git push%'''\nprint(s)\nPY" + self.assertFalse(is_guarded(cmd)) + + def test_unquoted_data_heredoc_substitution_guarded(self) -> None: + self.assertTrue(is_guarded("cat < None: + self.assertFalse(is_guarded("cat <<'EOF'\n$(git push origin b)\nEOF")) + + def test_shell_interpreter_heredoc_still_modeled(self) -> None: + repo = Path(tempfile.mkdtemp()) + subprocess.run(["git", "init", "-q"], cwd=repo, capture_output=True, check=False) + cmd = "bash <<'SH'\ngit commit -m t\nSH" + self.assertTrue(is_guarded(cmd)) + operations = resolve_git_operations(cmd, cwd=repo) + self.assertTrue(operations and operations[0].mode == "commit") + + def test_direct_command_unchanged(self) -> None: + self.assertTrue(is_guarded("git commit -m t")) + + if __name__ == "__main__": unittest.main() From 03b6ff38efe33d62c24c588ed7fa4fb87b950ec2 Mon Sep 17 00:00:00 2001 From: loong10k <20489781+loong10k@users.noreply.github.com> Date: Wed, 23 Sep 2026 23:43:54 +0800 Subject: [PATCH 2/2] =?UTF-8?q?release:=20v0.15.3=EF=BC=88heredoc=20git=20?= =?UTF-8?q?=E5=BD=92=E5=9B=A0=E6=94=B6=E7=B4=A7=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .agents/plugins/marketplace.json | 8 ++++---- .codex-plugin/plugin.json | 2 +- .zcode-plugin/plugin.json | 2 +- kimi.plugin.json | 2 +- .../2026-09-23-fix-heredoc-git-attribution/tasks.md | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.agents/plugins/marketplace.json b/.agents/plugins/marketplace.json index 732e9de..7fd3ae1 100644 --- a/.agents/plugins/marketplace.json +++ b/.agents/plugins/marketplace.json @@ -9,20 +9,20 @@ "source": { "source": "url", "url": "https://github.com/full-stack-plugins/codeguard-plugin.git", - "ref": "v0.15.2" + "ref": "v0.15.3" }, "policy": { "installation": "AVAILABLE", "authentication": "ON_USE" }, "category": "Developer Tools", - "version": "0.15.2", + "version": "0.15.3", "description": "Evidence-backed code checks and Git content gates for AI assistants, with Maven/Gradle module impact analysis. Save hooks provide feedback; unverified checks are explicit.", - "icon": "https://cdn.jsdelivr.net/gh/full-stack-plugins/codeguard-plugin@v0.15.2/assets/official-logo.png", + "icon": "https://cdn.jsdelivr.net/gh/full-stack-plugins/codeguard-plugin@v0.15.3/assets/official-logo.png", "interface": { "displayName": "代码规范守卫", "shortDescription": "Trustworthy code checks and Java impact analysis", - "logo": "https://cdn.jsdelivr.net/gh/full-stack-plugins/codeguard-plugin@v0.15.2/assets/official-logo.png" + "logo": "https://cdn.jsdelivr.net/gh/full-stack-plugins/codeguard-plugin@v0.15.3/assets/official-logo.png" } } ] diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 15a94bd..000253d 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "codeguard", - "version": "0.15.2+codex.20260923", + "version": "0.15.3+codex.20260923", "description": "Evidence-backed code checks and Git content gates for AI assistants, with Maven/Gradle module impact analysis. Save hooks provide feedback; unverified checks are explicit.", "author": { "name": "Full Stack Skills / PartMe.AI", diff --git a/.zcode-plugin/plugin.json b/.zcode-plugin/plugin.json index 39ee46a..c23b221 100644 --- a/.zcode-plugin/plugin.json +++ b/.zcode-plugin/plugin.json @@ -5,7 +5,7 @@ "en": "代码规范守卫", "zh-CN": "代码规范检查" }, - "version": "0.15.2", + "version": "0.15.3", "description": "Evidence-backed code checks and Git content gates for AI assistants, with Maven/Gradle module impact analysis. Save hooks provide feedback; unverified checks are explicit.", "description_i18n": { "en": "Evidence-backed code checks and Git content gates for AI assistants, with Maven/Gradle module impact analysis. Save hooks provide feedback; unverified checks are explicit.", diff --git a/kimi.plugin.json b/kimi.plugin.json index f1e003b..9e4eda1 100644 --- a/kimi.plugin.json +++ b/kimi.plugin.json @@ -1,6 +1,6 @@ { "name": "codeguard", - "version": "0.15.2", + "version": "0.15.3", "description": "Evidence-backed code checks and Git content gates for AI assistants, with Maven/Gradle module impact analysis. Save hooks provide feedback; unverified checks are explicit.", "author": { "name": "Full Stack Skills / PartMe.AI" diff --git a/openspec/changes/2026-09-23-fix-heredoc-git-attribution/tasks.md b/openspec/changes/2026-09-23-fix-heredoc-git-attribution/tasks.md index 2ceae2c..3e38b82 100644 --- a/openspec/changes/2026-09-23-fix-heredoc-git-attribution/tasks.md +++ b/openspec/changes/2026-09-23-fix-heredoc-git-attribution/tasks.md @@ -16,5 +16,5 @@ ## 3. 发版 -- [ ] 3.1 dogfood 直跑 bump 发 v0.15.3 -- [ ] 3.2 市场仓同步 + PR/CI/合并 + 两仓推送 +- [x] 3.1 dogfood 直跑 bump 发 v0.15.3 +- [x] 3.2 市场仓同步 + PR/CI/合并 + 两仓推送