From 9392dd34620ebf54d09d29708644f7febcb7a106 Mon Sep 17 00:00:00 2001 From: AIOSAI Date: Sat, 16 May 2026 12:00:59 -0700 Subject: [PATCH] =?UTF-8?q?fix(security):=20detect=20git=20commands=20insi?= =?UTF-8?q?de=20script=20files=20=E2=80=94=20reads=20file=20contents=20whe?= =?UTF-8?q?n=20bash/sh/source=20invoked=20(Issue=20#561)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .claude/hooks/git_gate.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.claude/hooks/git_gate.py b/.claude/hooks/git_gate.py index aa681145..66ebc2fe 100755 --- a/.claude/hooks/git_gate.py +++ b/.claude/hooks/git_gate.py @@ -165,6 +165,22 @@ def main(): if re.search(r"\bgit\b|\bgh\b", cmd): _block(GIT_REDIRECT) + # Script-file bypass detection — read file contents when bash/sh runs a script. + script_match = re.search(r"(?:^|[;&|]\s*)(?:bash|sh|source|\.)\s+([^\s;&|]+)", cmd) + if script_match: + script_path = script_match.group(1) + if not os.path.isabs(script_path): + script_path = os.path.join(cwd, script_path) + try: + content = Path(script_path).read_text(encoding="utf-8", errors="ignore") + if BLOCKED_GIT_RE.search(content) or BLOCKED_GIT_PATH_RE.search(content): + _block(GIT_REDIRECT) + if BLOCKED_GH_RE.search(content) or BLOCKED_GH_PATH_RE.search(content): + if not (_cwd_branch(cwd) in TRUSTED_HOOK_EDITORS or _is_project_owner(cwd)): + _block(GH_REDIRECT) + except (OSError, UnicodeDecodeError): + pass + # Strip quoted strings before matching — text inside "..." or '...' is data # (PR descriptions, commit messages, examples in docs), not code to enforce. scan = re.sub(r'"(?:[^"\\]|\\.)*"', '""', cmd)