Repository navigation
fix(security): 実行時フロアの回避経路 2 件を塞ぐ (Phase 128) - #282
Conversation
外部 PR #246 のセキュリティレビュー中に、現在の main に実在する フロア回避経路 2 件を発見。いずれも Lead が実測で確認済み。 #246 の採否とは独立に単独修正すべき最優先事項として起票する。 回避経路 A (P0): AI が自力で secret-read hard floor を外せる。 3 段すべて実測で成立: 1. .claude-code-harness.config.json は deny リストに無く AI が書ける (deny 22 件中ヒット 0、hook 実測でも Write は「判定なし = 通る」) 2. configSecretAllowPatterns は絶対パス宣言にだけ境界チェックがあり、 相対パス宣言には無い 3. パス計算実測: ../../../../etc/a -> /etc/a と作業ツリー外へ出る 回避経路 B (P1): main への強制 push が確認をすり抜ける。 hook 実測: `git push origin main` -> ask だが `git push origin +main` / `+refs/heads/main` は判定なし (素通り)。 force refspec の先頭 + が正規表現照合前に剥がされていない。 A は 2 層で塞ぐ。境界チェック (128.1) だけでは宣言を worktree 内に 丸めるのみで「AI が自分で .env を宣言して読む」経路が残るため、 設定ファイル自体を保護面に載せる (128.3)。128.3 は helpers.go を 触るため Constitution 条項に該当し、operator adoption gate (128.4) を置く (Phase 112.19 / 126.6 の前例に従う)。 対象外は Plans.md に明示 (dependabot 9 件 / #246 の R16 再実装 / allow_rm_rf)。黙って落とさず理由付きで Reject 分類。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
外部 PR #246 のレビュー中に発見した、現在の main に実在する runtime floor 回避経路 2 件を修正する。変更はすべて締める方向のみ。 ## 128.1 — secretAllow の相対パス宣言が worktree を脱出できた configSecretAllowPatterns は絶対パス宣言にだけ pathUnderWorktree を 適用しており、相対パス宣言は filepath.Join した結果を無検証で許可 リストに載せていた。`../../../../etc/shadow` のような宣言で secret-read hard floor を設定ファイルから無効化できた。 spec.md は元から "Relative paths resolve under the project root" と 書いており、これは新しい制約の追加ではなく仕様どおりへの是正である。 両分岐を解決後の絶対パスで境界判定するよう統一し、さらに secretAllowSymlinkStaysInWorktree を追加して「テキスト上は worktree 内 だが symlink で外を指す」宣言も落とす。symlink 解決は pass/deny の 判定にのみ使い、許可リストには宣言由来のパスを保存する (実体パスを保存すると、宣言したパスが自分の宣言に一致しなくなる退行と、 宣言していない実体パスが許可される緩和が同時に起きる。Lead の probe で 両方を実測し差し戻した)。 ## 128.2 — main への強制 push が確認をすり抜けた protectedBranchRefPattern が force refspec 先頭の "+" を剥がさないため、 `git push origin +main` が R12 の ask をすり抜けていた。 normalizeGitToken で "+" を除去し、R11 (reset --hard) にも同時に効かせる。 ## 128.3 — 設定ファイルを AI が書き換えられた .claude-code-harness.config.{json,yaml} は runtimefloor.secretAllow を 通じて hard floor の適用範囲を決めるが、どの deny リストにも無く AI が自由に書けた。128.1 の境界チェックだけでは宣言を worktree 内に 丸めるのみで「AI が自分で宣言して読む」経路が残る。 protectedPathRules に deny レベルで追加し、selfaudit の deny surface baseline (R02/R03) を機械的に更新した。 ask ではなく deny を選んだ根拠: ask レベルのパターンは selfaudit の deny surface に含まれないため、将来この保護が削除されても機械的に 検知できない。deny なら指紋化され、劣化が検出される (Worker が両案を 実測して発見)。 ## 検証 (floor 免除 env を export したまま実行) hook 実測 (再ビルドした binary): git push origin +main 素通り -> ask git push origin +refs/heads/main 素通り -> ask git push origin +feature/x (保護外) 素通り -> 素通り .claude-code-harness.config.json 書込 通る -> deny .claude-code-harness.config.yaml 書込 通る -> deny claude-code-harness.config.example.json 通る -> 通る README.md 通る -> 通る - go test ./... 全パス / gofmt + vet clean - tests/validate-plugin.sh 131 pass / 0 fail - scripts/ci/check-consistency.sh 全 24 通過 - check-binary-source-drift.sh OK (4-platform 再ビルド済み) - 削除行は go/ 全体で 4 行のみ。内訳は (1) token 正規化の置換 (より厳しく)、(2)(3) deny 追加に伴う baseline hash 更新、 (4) 境界チェック付きへの置換 (より厳しく)。 deny/ask ルールの削除・弱体化はゼロ Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CHANGELOG [Unreleased] に Security 節を新設し、2 つの回避経路を before/after で記述した。実測表 (hook 判定の修正前後) も併記。 128.4 (operator 採択ゲート) を cc:done に更新。 2026-07-31 に operator が 7702ca4 を明示採択。 提示材料: 差分統計 / 削除行 4 行の全列挙 / hook 実測表 / 検証結果。 検証: - VERSION / plugin.json / harness.toml は未変更 - skill mirror in-sync (codex / opencode) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Walkthrough
Changesセキュリティ保護強化
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 golangci-lint (2.12.2)level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain modules listed in go.work or their selected dependencies" Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 476ea40304
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // floor by editing the declaration that scopes it. Does not match the | ||
| // checked-in template "claude-code-harness.config.example.json" (no leading | ||
| // dot, distinct ".example." infix). | ||
| {protectedPathDeny, "harness control-plane config", regexp.MustCompile(`(?:^|/)\.claude-code-harness\.config\.(?:json|ya?ml)$`)}, |
There was a problem hiding this comment.
Cover non-redirection writes to the control-plane config
Protecting this pathname does not prevent an AI from replacing it with commands such as cp /tmp/config .claude-code-harness.config.json or mv ...: R03 obtains destinations exclusively from shell redirections and tee in extractBashWriteTargets, so these common Bash writes return no protected target and are approved. The replacement can then enable runtimefloor.releaseAuto or expand secretAllow, leaving the self-modification route this change is intended to close.
Useful? React with 👍 / 👎.
| if err != nil { | ||
| return true |
There was a problem hiding this comment.
Resolve existing parent symlinks before accepting missing paths
Returning true for every EvalSymlinks error reopens the symlink escape for not-yet-created declarations. If config allows link/.env while link is absent, a single preflighted command such as ln -s /outside link && cat link/.env is approved because the initial resolution fails and the lexical path is allowlisted; execution then creates the escaping symlink before reading the outside file. Resolve the longest existing parent and append the missing suffix, as the policy package already does, rather than treating ENOENT as proof that the declaration stays inside the worktree.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@go/internal/policy/helpers.go`:
- Around line 73-80: Update the protectedPathDeny regexp for
.claude-code-harness.config files to use case-insensitive matching via an inline
(?i:...) group, preventing casing variants from bypassing R02/R03. Add
regression tests covering uppercase or mixed-case JSON/YAML/YML variants and
verify they are denied.
In `@go/internal/runtimefloor/runtimefloor.go`:
- Around line 391-419: Update secretAllowSymlinkStaysInWorktree and
isAllowlistedSecretPath so declared-but-nonexistent paths cannot bypass worktree
symlink checks after being replaced. Resolve and validate the nearest existing
ancestor when EvalSymlinks fails, or revalidate the declared path with the same
token during secret reads, while preserving allowlisting only for paths
confirmed to remain inside the worktree.
In `@Plans.md`:
- Around line 167-168: Update the 128.5 entry in Plans.md to reflect the actual
closeout status and results: mark its Status/result as complete only if the
listed validation, drift, consistency, version-surface, and PR closeout DoD
items are finished. If any remain incomplete, revise the PR objectives and
CHANGELOG wording to clearly identify the remaining work instead of reporting
completion.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 04fc5ac5-5191-473a-a9ac-8febd34b5ad0
⛔ Files ignored due to path filters (1)
bin/harness-windows-amd64.exeis excluded by!**/*.exe
📒 Files selected for processing (11)
CHANGELOG.mdPlans.mdbin/harness-darwin-amd64bin/harness-darwin-arm64bin/harness-linux-amd64go/internal/policy/helpers.gogo/internal/policy/rules_test.gogo/internal/policy/selfaudit.gogo/internal/runtimefloor/runtimefloor.gogo/internal/runtimefloor/runtimefloor_test.gospec.md
👮 Files not reviewed due to content moderation or server errors (1)
- bin/harness-darwin-arm64
| // Phase 128.3: .claude-code-harness.config.{json,yaml,yml} governs | ||
| // runtimefloor.secretAllow / runtimefloor.releaseAuto and other control-plane | ||
| // settings, the same governance tier as .claude/settings* and | ||
| // .claude-plugin/settings*. Denied so the AI cannot loosen its own hard | ||
| // floor by editing the declaration that scopes it. Does not match the | ||
| // checked-in template "claude-code-harness.config.example.json" (no leading | ||
| // dot, distinct ".example." infix). | ||
| {protectedPathDeny, "harness control-plane config", regexp.MustCompile(`(?:^|/)\.claude-code-harness\.config\.(?:json|ya?ml)$`)}, |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- candidate file outline ---'
ast-grep outline go/internal/policy/helpers.go --view compact || true
printf '%s\n' '--- protected path rules and nearby logic ---'
cat -n go/internal/policy/helpers.go | sed -n '1,110p'
printf '%s\n' '--- relevant tests and references ---'
rg -n -i 'R02|R03|protectedPathRules|claude-code-harness\.config|protectedPathDeny' goRepository: Chachamaru127/claude-code-harness
Length of output: 6575
🏁 Script executed:
#!/bin/bash
set -u
printf '%s\n' '--- helper structure ---'
ast-grep outline go/internal/policy/helpers.go --view signatures || true
printf '%s\n' '--- remaining helper implementation ---'
cat -n go/internal/policy/helpers.go | sed -n '95,180p'
printf '%s\n' '--- policy tests and exact references ---'
rg -n -i 'R02|R03|classifyProtectedPathPattern|normalizePathForGuardrail|protectedPath' go/internal go/pkg || true
printf '%s\n' '--- filesystem/path policy context ---'
rg -n -i 'filepath\.|os\.|runtime\.GOOS|GOOS|case.?insens|windows|darwin|protected path|guardrail' go/internal/policy go/internal go/pkg || trueRepository: Chachamaru127/claude-code-harness
Length of output: 7929
🏁 Script executed:
#!/bin/bash
set -u
printf '%s\n' '--- repository test files ---'
git ls-files | rg -i '(^|/)([^/]*(test|spec)[^/]*|tests?/.*)$' | head -200
printf '%s\n' '--- all R02/R03 references ---'
rg -n -i 'R0[23]|r02|r03' . --glob '!vendor/**' --glob '!node_modules/**' || true
printf '%s\n' '--- protected-path call sites ---'
rg -n 'classifyProtectedPath|classifyProtectedPathAtRoot|isProtectedPath|protectedPathRules' go --glob '*.go' || true
printf '%s\n' '--- standalone regexp behavior probe ---'
python3 - <<'PY'
import re
pattern = re.compile(r'(?:^|/)\.claude-code-harness\.config\.(?:json|ya?ml)$')
for path in [
'.claude-code-harness.config.json',
'.CLAUDE-CODE-HARNESS.CONFIG.JSON',
'dir/.CLAUDE-CODE-HARNESS.CONFIG.YAML',
'claude-code-harness.config.example.json',
]:
print(f'{path}: {bool(pattern.search(path))} / ignore-case: {bool(re.compile(pattern.pattern, re.I).search(path))}')
PYRepository: Chachamaru127/claude-code-harness
Length of output: 9342
🏁 Script executed:
#!/bin/bash
set -u
printf '%s\n' '--- policy helper tests ---'
cat -n go/internal/policy/helpers_test.go | sed -n '1,260p'
printf '%s\n' '--- policy regression tests ---'
cat -n go/internal/policy/cc2110_regression_test.go | sed -n '1,260p'
printf '%s\n' '--- config-specific tests ---'
rg -n -i -C 5 'harness.?config|control.?plane|runtimefloor|releaseAuto|R02|R03' go/internal/policy --glob '*_test.go' || true
printf '%s\n' '--- exact symbol references (fixed search) ---'
grep -RInE 'classifyProtectedPath|protectedPathRules|protectedPathDeny' go/internal/policy --include='*.go' || trueRepository: Chachamaru127/claude-code-harness
Length of output: 50389
🏁 Script executed:
#!/bin/bash
set -u
printf '%s\n' '--- R02/R03 implementation around protected paths ---'
rg -n -C 12 'classifyProtectedPathAtRoot|classifyProtectedPath\(|R02|R03' go/internal/policy/rules.go go/internal/policy/*.go | head -240
printf '%s\n' '--- existing harness-config tests only ---'
rg -n -C 4 'HarnessConfig|claude-code-harness\.config' go/internal/policy --glob '*_test.go'
printf '%s\n' '--- case-variant coverage in policy tests ---'
rg -n -i 'CLAUDE-CODE-HARNESS|CONFIG\.(JSON|YAML|YML)' go/internal/policy --glob '*_test.go' || true
printf '%s\n' '--- path normalization platform behavior ---'
rg -n -C 5 'normalizePathForGuardrail|filepath\.ToSlash|filepath\.Clean|EvalSymlinks' go/internal/policy/helpers.goRepository: Chachamaru127/claude-code-harness
Length of output: 30708
🏁 Script executed:
#!/bin/bash
set -u
cat -n go/internal/policy/helpers.go | sed -n '245,298p'Repository: Chachamaru127/claude-code-harness
Length of output: 2127
設定ファイルの大小文字違いによる deny 回避を防止してください。
regexp は大小文字を区別するため、ケース非依存ファイルシステムでは .CLAUDE-CODE-HARNESS.CONFIG.JSON が R02/R03 を回避します。パターンを (?i:...) で照合し、大小文字違いの R02/R03 回帰テストを追加してください。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@go/internal/policy/helpers.go` around lines 73 - 80, Update the
protectedPathDeny regexp for .claude-code-harness.config files to use
case-insensitive matching via an inline (?i:...) group, preventing casing
variants from bypassing R02/R03. Add regression tests covering uppercase or
mixed-case JSON/YAML/YML variants and verify they are denied.
| // secretAllowSymlinkStaysInWorktree reports whether a worktree-bound | ||
| // secretAllow declaration stays inside the worktree after symlinks are | ||
| // resolved. rootResolved must already be EvalSymlinks()-resolved (or its | ||
| // EvalSymlinks fallback) so both sides of the comparison are in the same | ||
| // coordinate space — see the comment in configSecretAllowPatterns about the | ||
| // macOS /var -> /private/var symlink. This closes an escape where a | ||
| // declaration names a path that is textually inside the worktree but is (or | ||
| // contains) a symlink pointing outside it (same class of escape as Phase | ||
| // 126.4's R05 use of EvalSymlinks). Symlink resolution is used ONLY to | ||
| // decide pass/deny here — the allowlist itself must keep storing the | ||
| // operator's DECLARED path (the caller appends its own `abs`/`joined` value, | ||
| // not this function's return), because isAllowlistedSecretPath() matches the | ||
| // token the command names against declared patterns via strings.HasPrefix. | ||
| // Substituting the resolved realpath into the allowlist would both (a) stop | ||
| // matching the operator's own declared path (regression) and (b) start | ||
| // matching the realpath even though the operator never declared it | ||
| // (widening) — the declaration and the allowlist entry must refer to the | ||
| // same path. If the symlink cannot be resolved (path does not exist yet, | ||
| // permission error, etc.) this reports true — fail-safe here means "do not | ||
| // widen the allowlist" for a resolved escape, not "silently drop a | ||
| // legitimate not-yet-created declaration". | ||
| func secretAllowSymlinkStaysInWorktree(path, rootResolved string) bool { | ||
| resolved, err := filepath.EvalSymlinks(path) | ||
| if err != nil { | ||
| return true | ||
| } | ||
| return pathUnderWorktree(filepath.Clean(resolved), rootResolved) | ||
| } | ||
|
|
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: isAllowlistedSecretPath の実装とその呼び出し箇所(実際の読み取り時の再検証有無)を確認
rg -n -C 8 'isAllowlistedSecretPath|secretAllowSymlinkStaysInWorktree' go/internal/runtimefloorRepository: Chachamaru127/claude-code-harness
Length of output: 171
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== files =="
git ls-files | rg '(^|/)runtimefloor\.go$|go/internal/runtimefloor|secretAllow|EvalSymlinks'
echo "== runtimefloor outline/stat =="
f=$(git ls-files | grep 'go/internal/runtimefloor/runtimefloor.go' | head -n 1)
if [ -n "${f:-}" ]; then
wc -l "$f"
ast-grep outline "$f" --view compact | sed -n '1,220p' || true
echo "== targeted search =="
rg -n -C 5 'secretAllowSymlinkStaysInWorktree|isAllowlistedSecretPath|EvalSymlinks|pathUnderWorktree|secretAllow' "$f" || true
echo "== surrounding lines =="
sed -n '360,440p' "$f" | cat -n
fiRepository: Chachamaru127/claude-code-harness
Length of output: 11008
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== relevant implementation sections =="
sed -n '450,510p' go/internal/runtimefloor/runtimefloor.go | cat -n
sed -n '563,590p' go/internal/runtimefloor/runtimefloor.go | cat -n
echo "== all secretRead-related locations =="
rg -n -C 4 'CategorySecretRead|secretAllow|hasSecretRead|secret|credential|EvalSymlinks' go/internal/runtimefloor/runtimefloor.go go/internal | sed -n '1,240p'
echo "== behavioral probe: filepath.EvalSymlinks vs declared allowlist matching =="
tmpdir="$(mktemp -d)"
trap 'rm -rf "$tmpdir"' EXIT
root("$tmpdir"/worktree)
rgx='strings\HasPrefix\("declared/path", patterns\)'
cat > /tmp/go_evalprobe.go <<'GO'
package main
import (
"fmt"
"path/filepath"
)
func main() {
tmp := "/tmp/go-floortmp123"
path := filepath.Join(tmp, "declared/path.log")
pattern := filepath.Join(tmp, "declared/path")
resolved, err := filepath.EvalSymlinks(path)
fmt.Printf("path=%q\npattern=%q\nerr=%v\nresolved=%q\n", path, pattern, err, resolved)
if err == nil {
fmt.Printf("prefix_match_after_resolve=%v\n", resolved == pattern || filepath.HasPrefix(resolved, pattern+string(filepath.Separator)))
}
fmt.Printf("prefix_match_on_declared=%v\n", path == pattern || filepath.HasPrefix(path, pattern+string(filepath.Separator)))
}
GO
if command -v go >/dev/null 2>&1; then
go run /tmp/go_evalprobe.go
fiRepository: Chachamaru127/claude-code-harness
Length of output: 16048
未存在宣言 path は fail-open を戻す必要がある
secretAllowSymlinkStaysInWorktree() は EvalSymlinks() がエラーだと true を返し、許可リストには宣言された未存在パスそのままで入る仕様です。その後で宣言パスにワークツリー外を指す symlink が差し替われば、isAllowlistedSecretPath() は宣言トークンと宣言パターンで一致するため、秘密読み取りでも worktree-escape が再導入されます。存在する直近の祖先 symlink をresolveして検査するか、秘密読み取り時に宣言パスと同じtokenで再検証してください。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@go/internal/runtimefloor/runtimefloor.go` around lines 391 - 419, Update
secretAllowSymlinkStaysInWorktree and isAllowlistedSecretPath so
declared-but-nonexistent paths cannot bypass worktree symlink checks after being
replaced. Resolve and validate the nearest existing ancestor when EvalSymlinks
fails, or revalidate the declared path with the same token during secret reads,
while preserving allowlisting only for paths confirmed to remain inside the
worktree.
| | 128.5 | `[lane:release]` `[tdd:skip:verification]` 検証 + 配布物 + closeout: `go/scripts/build-all.sh` で 4-platform binary 再生成 (`bin/harness` は shim のため `-o bin/harness` 禁止)、`scripts/ci/check-binary-source-drift.sh` ローカル green、新 test を `tests/validate-plugin.sh` へ配線、CHANGELOG `[Unreleased]` に Security 節として追記 (回避経路と塞ぎ方を before/after で) | (a) `bash tests/validate-plugin.sh` 0 failed (floor 免除 env を export したまま), (b) drift gate green, (c) `bash scripts/ci/check-consistency.sh` 全パス, (d) VERSION / `.claude-plugin/plugin.json` / `harness.toml` 非接触を `git diff --name-only` で確認, (e) PR closeout (事前承認済み: push + PR 作成 + CI green 確認 + merge。merge 前に 128.4 の採択を確認) | 128.4 | cc:TODO | | ||
| | 128.6 | `[lane:fast]` `[tdd:skip:no-code-change]` PR #246 の closeout: レビュー結論 (critical 3 件で as-is マージ不可、ただし問題提起は正当で脆弱性 2 件の発見に繋がった) を作者向けに説明してクローズする。採用した部分 (本 Phase の 2 件) と、再実装に回した部分 (`paths.protected` / R16、`git.protected_branches` 加算)、見送った部分 (`allow_rm_rf`) を切り分けて伝える。rebase 依頼はしない (117 commit 差 + Phase 126 が R05/R12 のコア実装を書き換えており、こちらで再実装する方が速く安全) | (a) #246 に日本語でなく英語のコメント (作者は英語話者) で 3 分類を明示, (b) 本 Phase の 2 件が #246 由来であることを明記して credit する, (c) #246 が CLOSED になる | 128.5 | cc:TODO | |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n '128\.5|cc:TODO|validate-plugin|check-consistency|binary-source-drift' Plans.md CHANGELOG.mdRepository: Chachamaru127/claude-code-harness
Length of output: 20981
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the exact Phase 128 table row and nearby context.
sed -n '150,180p' Plans.md | nl -ba -v133
# Show whether Plans.md has any explicit 128.5 closeout/result area or if Status is the only source.
python3 - <<'PY'
from pathlib import Path
text = Path('Plans.md').read_text()
for needle in ['Phase 128', '128.5', '128.4', '128.6', 'cc:TODO']:
print(needle, text.count(needle))
PYRepository: Chachamaru127/claude-code-harness
Length of output: 211
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '150,180p' Plans.md
python3 - <<'PY'
from pathlib import Path
text = Path('Plans.md').read_text().splitlines()
for i, line in enumerate(text[149:178], start=150):
if '128.5' in line:
cells = [c.strip().rstrip('|') for c in line.split('|')]
print(f'row: {i}')
print('last_two_nonempty_cells:', [c for c in cells if c][-2:])
PYRepository: Chachamaru127/claude-code-harness
Length of output: 8690
128.5 の Status を完了報告に合わせて更新してください。
DoD は検証・drift gate・整合性チェック・Version surface 不接触・PR closeout の完了を求めています。もし本 Phase の closeout が終了しているなら、128.5 | ... | 128.4 | cc:TODO を最新 Status/result に更新してください。未完了なら PR の範囲説明と一致させるため、PR objectives/CHANGELOG の表現を未完成タスクとして明確にしてください。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@Plans.md` around lines 167 - 168, Update the 128.5 entry in Plans.md to
reflect the actual closeout status and results: mark its Status/result as
complete only if the listed validation, drift, consistency, version-surface, and
PR closeout DoD items are finished. If any remain incomplete, revise the PR
objectives and CHANGELOG wording to clearly identify the remaining work instead
of reporting completion.
PR Chachamaru127#282 は squash merge されたため、Plans.md Phase 128 (128.1-128.5) が 記録していた作業ブランチ側の commit hash (7702ca4 / 476ea40) は main の 祖先から消えていた (git merge-base --is-ancestor で実測確認)。main に実際に 入った実体は 1f085a3 (親 1 個の squash commit、git log -1 --format=%p で 確認)。台帳を 1f085a3 に訂正し、なぜ変わったかの注記を Phase 128 見出し直下 に追加した。table の列数は編集前後で完全一致することを awk で確認済み。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ZBxNEtYJbtHkZcsAn8nsv
外部からの PR #246 をレビューする過程で、現在の
mainに実在する実行時フロアの回避経路 2 件を発見しました。いずれも実際に動かして確認しています。#246 の採否とは独立に、単独で修正すべき最優先事項として切り出したものです。修正はすべて制限を強める方向のみで、既存の拒否・確認ルールを緩めた箇所はありません。
回避経路 A — AI が自分で秘密ファイルの読み取り制限を外せた
3 段すべてが成立していました。
secretAllowの相対パス宣言だけ境界検査がないpathUnderWorktree)../../../../etc/a→/etc/aつまり AI が
{"runtimefloor":{"secretAllow":["../../../../etc/shadow"]}}と自分で書けば、仕様書が「いかなる設定・環境変数・permission mode でも上書きできない」と定める秘密読み取りフロアを自力で無効化できました。仕様書は元から正しかった点が重要です。
spec.mdは既に「相対パスはプロジェクトルート配下に解決される」と書いており、実装がそれを守っていませんでした。新しい制約の追加ではなく、仕様どおりへの是正です。2 層で塞ぎました
境界検査だけでは宣言を作業ツリー内に丸めるだけで、「AI が自分で
.envを宣言して読む」経路が残ります。設定ファイル自体を保護対象に載せることで、フロアを広げる操作に人間を戻しました。denyを選んだ根拠は実測です。askに降格すると self-audit の追跡対象から外れ、将来この保護が削除されても機械的に検知できません。denyなら指紋化され、劣化が検出されます。副作用としてreleaseAutoの切り替えは手動編集になります。回避経路 B —
mainへの強制 push が確認なしで通っていた保護ブランチの判定が、強制 push 短縮形の先頭
+を取り除かずに照合していました。実測(再ビルドした実行ファイル)
git push origin +maingit push origin +refs/heads/maingit push origin maingit push origin +feature/x(保護対象外).jsonへの書き込み.yamlへの書き込み...config.example.jsonREADME.md「緩める変更ゼロ」の機械的根拠
go/全体で削除は 4 行だけです。すべて、より厳しい版への置換です。return strings.Trim(token, "'\"")+も剥がす版に置換(厳しく)out = append(out, filepath.Join(...))拒否・確認ルールは 1 つも削除・弱体化していません。
Constitution ゲート
rules.go系は human-only 領域のため、Phase 112.19 / 126.6 と同じ手続きで operator の明示採択を経ています(2026-07-31、対象7702ca45)。差分統計・削除行の全列挙・実測表・検証結果を提示のうえ採択を得ました。selfaudit.goへの接触は Phase 126 ではありませんでした。変更は R02 / R03 のハッシュ 2 行のみで、これを更新しないと hook が「拒否面が変わった」として判定自体を拒否するため、機械的に必須です。検証(フロア免除の環境変数を設定したまま実行)
go test ./...tests/validate-plugin.shscripts/ci/check-consistency.shレビューで 1 度差し戻しています
128.1 の初版は許可リストに解決後の実体パスを保存していました。実測すると次の状態でした。
後者は本 Phase の絶対条件「緩める箇所ゼロ」に違反します。差し戻し、symlink の解決を許可・拒否の判定にのみ使う形へ是正しました。同じ probe で修正後は
declared=true/undeclared=falseを確認しています。副産物として、macOS の
/var→/private/varsymlink により、比較の片側だけを解決すると既存の宣言がすべて誤って拒否される問題も見つかり、同時に修正しました。非接触
.github/workflows/、.claude-plugin/settings*、.claude/settings*、deny-baseline.json、VERSION/plugin.json/harness.tomlはいずれも変更していません。🤖 Generated with Claude Code
https://claude.ai/code/session_01SkVve4dyhMqf9LMcQtsSyC
Summary by CodeRabbit