diff --git a/.claude/commands/sync-template.md b/.claude/commands/sync-template.md index 1e4d5d0..83e80da 100644 --- a/.claude/commands/sync-template.md +++ b/.claude/commands/sync-template.md @@ -388,6 +388,7 @@ ## 注意事項 +- 本コマンドは一時ディレクトリ(`mktemp -d`)に clone したテンプレートを Read / `cp` / `rm -rf` する.`restrict_repo_access.py` フックはシステム一時ディレクトリを許可ゾーンとして例外扱いしており,本コマンドはそれに依存している(フックの例外を外すと本コマンドが動かなくなる) - テンプレートリポジトリへの push は行わない - コード修正はユーザーの確認なしに実行しない - マージ必須ファイル(`.gitignore`, `CLAUDE.md`, `docs/PROGRESS.md`, `.gitattributes`)は必ずステップ 5.4 の手順でマージする.盲目的な `cp` で上書きしない(フレームワーク固有の除外ルールやプロジェクト固有セクションが失われる) diff --git a/.claude/hooks/restrict_repo_access.py b/.claude/hooks/restrict_repo_access.py index 36bfe96..b326223 100644 --- a/.claude/hooks/restrict_repo_access.py +++ b/.claude/hooks/restrict_repo_access.py @@ -1,10 +1,20 @@ -"""PreToolUse hook: リポジトリ外のファイルアクセスをブロックする.""" +"""PreToolUse hook: リポジトリ外のファイルアクセスをブロックする. + +例外としてシステム一時ディレクトリ(/tmp・%TEMP% 等)は許可ゾーンとする: +- Read/Glob/Grep: 一時ディレクトリ配下なら許可 + (/sync-template・/set-mode がテンプレートを mktemp -d に clone して読むため) +- Bash の破壊的コマンド: 対象が一時ディレクトリ配下なら許可 + (cp "$TEMP_DIR/..." や rm -rf "$TEMP_DIR" の後片付けは正当な操作) +- Write/Edit: 一時ディレクトリでもリポジトリ外は従来どおりブロック +""" import json import os +import posixpath import re import shlex import sys +import tempfile # Bash で検出する破壊的コマンド DESTRUCTIVE_COMMANDS = {"rm", "rmdir", "mv", "cp", "chmod", "chown", "unlink"} @@ -27,6 +37,22 @@ return real_target == real_base or real_target.startswith(real_base + os.sep) +def is_temp_path(path: str) -> bool: + """path がシステム一時ディレクトリ配下かを判定する. + + OS ネイティブの一時ディレクトリ(tempfile.gettempdir())に加え, + Git Bash 等の POSIX 形式 /tmp も文字列正規化で判定する + (Windows では /tmp が実パスに解決できないため realpath に頼れない). + `..` は正規化してから判定するので /tmp/../etc のような脱出は温存されない. + """ + # POSIX 形式 /tmp の判定(.. を潰してから前方一致) + norm = posixpath.normpath(path.replace("\\", "/")) + if norm == "/tmp" or norm.startswith("/tmp/"): + return True + # OS ネイティブの一時ディレクトリの判定 + return is_within_directory(path, tempfile.gettempdir()) + + def check_bash_command(command: str, cwd: str) -> str | None: """Bash コマンド内の破壊的操作がリポジトリ外を対象としていないかチェックする. @@ -61,6 +87,10 @@ continue # 絶対パスまたは .. を含むパスをチェック if os.path.isabs(token) or ".." in token: + # 一時ディレクトリ配下への破壊的操作は許可 + # (テンプレート clone の cp・後片付けの rm -rf 等) + if is_temp_path(token): + continue resolved = os.path.realpath(os.path.join(cwd, token)) if not is_within_directory(resolved, cwd): return ( @@ -104,6 +134,12 @@ if target_path is None: sys.exit(0) + # 読み取り専用ツールは一時ディレクトリ配下なら許可 + # (/sync-template・/set-mode が mktemp -d に clone したテンプレートを読む) + # Write/Edit は一時ディレクトリでもブロックを維持する + if tool_name in ("Read", "Glob", "Grep") and is_temp_path(target_path): + sys.exit(0) + if not is_within_directory(target_path, cwd): deny( f"リポジトリ外のパスへのアクセスはブロックされました: {target_path}"