From de43691d4ff3858b707aca0a0007297c848034f0 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 26 Mar 2026 12:57:44 +0000 Subject: [PATCH 1/3] Add SessionStart hook to enforce global MCP deny rules on fresh VMs --- .claude/session-start-global-deny.sh | 60 ++++++++++++++++++++++++++++ .claude/settings.json | 13 ++++++ 2 files changed, 73 insertions(+) create mode 100755 .claude/session-start-global-deny.sh diff --git a/.claude/session-start-global-deny.sh b/.claude/session-start-global-deny.sh new file mode 100755 index 0000000..2878d42 --- /dev/null +++ b/.claude/session-start-global-deny.sh @@ -0,0 +1,60 @@ +#!/bin/bash +# Session start hook: ensure global Claude Code deny rules exist +# In multi-repo sessions (cwd=/home/user), per-repo settings don't apply. +# This hook writes deny rules to ~/.claude/settings.json so mcp__github__ +# file-mutation tools are blocked globally. + +set -e + +GLOBAL_SETTINGS="$HOME/.claude/settings.json" + +# If global settings already has deny rules, skip +if [ -f "$GLOBAL_SETTINGS" ] && grep -q "mcp__github__push_files" "$GLOBAL_SETTINGS" 2>/dev/null; then + exit 0 +fi + +mkdir -p "$HOME/.claude" + +# If file exists, merge deny rules into it; otherwise create it +if [ -f "$GLOBAL_SETTINGS" ]; then + # File exists but lacks deny rules — add them + # Use a temp file to avoid partial writes + TMP_SETTINGS=$(mktemp) + if command -v python3 &>/dev/null; then + python3 -c " +import json, sys +with open('$GLOBAL_SETTINGS') as f: + settings = json.load(f) +perms = settings.setdefault('permissions', {}) +deny = perms.get('deny', []) +needed = [ + 'mcp__github__create_or_update_file', + 'mcp__github__push_files', + 'mcp__github__delete_file' +] +for rule in needed: + if rule not in deny: + deny.append(rule) +perms['deny'] = deny +with open('$TMP_SETTINGS', 'w') as f: + json.dump(settings, f, indent=2) + f.write('\n') +" && mv "$TMP_SETTINGS" "$GLOBAL_SETTINGS" + else + rm -f "$TMP_SETTINGS" + fi +else + # No global settings file — create one + cat > "$GLOBAL_SETTINGS" << 'EOF' +{ + "$schema": "https://json.schemastore.org/claude-code-settings.json", + "permissions": { + "deny": [ + "mcp__github__create_or_update_file", + "mcp__github__push_files", + "mcp__github__delete_file" + ] + } +} +EOF +fi diff --git a/.claude/settings.json b/.claude/settings.json index 5686477..73a5aae 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -22,5 +22,18 @@ "mcp__github__push_files", "mcp__github__delete_file" ] + }, + "hooks": { + "SessionStart": [ + { + "matcher": "", + "hooks": [ + { + "type": "command", + "command": ".claude/session-start-global-deny.sh" + } + ] + } + ] } } From a96e3b4a6ed7913e727a426f18078590addb401a Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 26 Mar 2026 13:04:12 +0000 Subject: [PATCH 2/3] Fix git proxy: rewrite github.com remote URLs to local proxy for native git push --- .claude/session-start-global-deny.sh | 78 ++++++++++++++++++++-------- 1 file changed, 55 insertions(+), 23 deletions(-) diff --git a/.claude/session-start-global-deny.sh b/.claude/session-start-global-deny.sh index 2878d42..a455093 100755 --- a/.claude/session-start-global-deny.sh +++ b/.claude/session-start-global-deny.sh @@ -1,28 +1,25 @@ #!/bin/bash -# Session start hook: ensure global Claude Code deny rules exist -# In multi-repo sessions (cwd=/home/user), per-repo settings don't apply. -# This hook writes deny rules to ~/.claude/settings.json so mcp__github__ -# file-mutation tools are blocked globally. +# Session start hook: ensure global Claude Code deny rules and git proxy config +# +# 1. Writes MCP deny rules to ~/.claude/settings.json so mcp__github__ +# file-mutation tools are blocked globally (even in cross-repo sessions). +# 2. Fixes git remote URLs to use the local git proxy when available, +# so native git push works instead of falling back to MCP tools. set -e -GLOBAL_SETTINGS="$HOME/.claude/settings.json" +# --- Part 1: Global MCP deny rules --- -# If global settings already has deny rules, skip -if [ -f "$GLOBAL_SETTINGS" ] && grep -q "mcp__github__push_files" "$GLOBAL_SETTINGS" 2>/dev/null; then - exit 0 -fi +GLOBAL_SETTINGS="$HOME/.claude/settings.json" -mkdir -p "$HOME/.claude" +if ! [ -f "$GLOBAL_SETTINGS" ] || ! grep -q "mcp__github__push_files" "$GLOBAL_SETTINGS" 2>/dev/null; then + mkdir -p "$HOME/.claude" -# If file exists, merge deny rules into it; otherwise create it -if [ -f "$GLOBAL_SETTINGS" ]; then - # File exists but lacks deny rules — add them - # Use a temp file to avoid partial writes - TMP_SETTINGS=$(mktemp) - if command -v python3 &>/dev/null; then - python3 -c " -import json, sys + if [ -f "$GLOBAL_SETTINGS" ]; then + TMP_SETTINGS=$(mktemp) + if command -v python3 &>/dev/null; then + python3 -c " +import json with open('$GLOBAL_SETTINGS') as f: settings = json.load(f) perms = settings.setdefault('permissions', {}) @@ -40,12 +37,11 @@ with open('$TMP_SETTINGS', 'w') as f: json.dump(settings, f, indent=2) f.write('\n') " && mv "$TMP_SETTINGS" "$GLOBAL_SETTINGS" + else + rm -f "$TMP_SETTINGS" + fi else - rm -f "$TMP_SETTINGS" - fi -else - # No global settings file — create one - cat > "$GLOBAL_SETTINGS" << 'EOF' + cat > "$GLOBAL_SETTINGS" << 'EOF' { "$schema": "https://json.schemastore.org/claude-code-settings.json", "permissions": { @@ -57,4 +53,40 @@ else } } EOF + fi +fi + +# --- Part 2: Fix git remote URLs to use local proxy --- +# Some sessions clone repos via github.com directly, which lacks push credentials. +# If the local git proxy is running, rewrite remote URLs to use it. + +# Detect local git proxy: look for the proxy in any sibling repo's remote URL +PROXY_BASE="" +for dir in /home/user/*/; do + if [ -d "$dir/.git" ]; then + url=$(git -C "$dir" remote get-url origin 2>/dev/null || true) + if echo "$url" | grep -q '127.0.0.1.*local_proxy'; then + # Extract base URL: http://local_proxy@127.0.0.1:PORT/git + PROXY_BASE=$(echo "$url" | sed 's|\(http://local_proxy@127\.0\.0\.1:[0-9]*/git\)/.*|\1|') + break + fi + fi +done + +if [ -n "$PROXY_BASE" ]; then + # Fix any repos with github.com remote URLs + for dir in /home/user/*/; do + if [ -d "$dir/.git" ]; then + url=$(git -C "$dir" remote get-url origin 2>/dev/null || true) + # Match github.com URLs (SSH or HTTPS) and rewrite to local proxy + if echo "$url" | grep -qE '(git@github\.com:|https?://github\.com/)'; then + # Extract org/repo from the URL + repo_path=$(echo "$url" | sed -E 's|.*github\.com[:/](.*)\.git$|\1|; s|.*github\.com[:/](.*)$|\1|') + if [ -n "$repo_path" ]; then + new_url="${PROXY_BASE}/${repo_path}" + git -C "$dir" remote set-url origin "$new_url" 2>/dev/null || true + fi + fi + fi + done fi From 0ae03a20737a79c44802e4e11e7b8a312e25569d Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 26 Mar 2026 13:08:29 +0000 Subject: [PATCH 3/3] Fix grep pattern in session-start hook: local_proxy comes before 127.0.0.1 in URL --- .claude/session-start-global-deny.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.claude/session-start-global-deny.sh b/.claude/session-start-global-deny.sh index a455093..84e5775 100755 --- a/.claude/session-start-global-deny.sh +++ b/.claude/session-start-global-deny.sh @@ -65,7 +65,7 @@ PROXY_BASE="" for dir in /home/user/*/; do if [ -d "$dir/.git" ]; then url=$(git -C "$dir" remote get-url origin 2>/dev/null || true) - if echo "$url" | grep -q '127.0.0.1.*local_proxy'; then + if echo "$url" | grep -q 'local_proxy.*127.0.0.1'; then # Extract base URL: http://local_proxy@127.0.0.1:PORT/git PROXY_BASE=$(echo "$url" | sed 's|\(http://local_proxy@127\.0\.0\.1:[0-9]*/git\)/.*|\1|') break