diff --git a/config/claude/settings.json b/config/claude/settings.json index 5325120bf..95053e0ad 100644 --- a/config/claude/settings.json +++ b/config/claude/settings.json @@ -232,7 +232,12 @@ }, { "type": "command", - "command": "$HOME/dotfiles/config/shared/hooks/block-push-main.sh", + "command": "$HOME/dotfiles/config/shared/hooks/block-git-push.sh", + "timeout": 5 + }, + { + "type": "command", + "command": "$HOME/dotfiles/config/shared/hooks/block-gh-settings.sh", "timeout": 5 }, { diff --git a/config/codex/hooks.json b/config/codex/hooks.json index 403abbe89..7c52cfd8d 100644 --- a/config/codex/hooks.json +++ b/config/codex/hooks.json @@ -35,7 +35,7 @@ }, { "type": "command", - "command": "$HOME/dotfiles/config/shared/hooks/block-push-main.sh", + "command": "$HOME/dotfiles/config/shared/hooks/block-git-push.sh", "timeout": 5 }, { diff --git a/config/shared/hooks/block-gh-settings.sh b/config/shared/hooks/block-gh-settings.sh new file mode 100755 index 000000000..aa30432a7 --- /dev/null +++ b/config/shared/hooks/block-gh-settings.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +# block-gh-settings.sh — Shared hook for Claude Code + Codex +# Blocks gh CLI commands that modify GitHub repository settings. +# Exit 2 = block (Codex), JSON decision output (Claude). +set -euo pipefail + +# Read tool input from stdin +input=$(cat) + +# Extract command +command=$(echo "$input" | jq -r '.tool_input.command // .command // empty' 2>/dev/null) +[[ -z $command ]] && exit 0 + +# Block: gh repo +if echo "$command" | grep -qE 'gh\s+repo\s+(delete|rename|archive|transfer|edit)\b'; then + subcommand=$(echo "$command" | grep -oE 'gh\s+repo\s+(delete|rename|archive|transfer|edit)' | awk '{print $3}') + msg="'gh repo $subcommand' is blocked. Repo settings must be changed manually." + echo "BLOCKED by block-gh-settings.sh: $msg" >&2 + exit 2 +fi + +# Block: gh api -X PATCH|DELETE|PUT targeting /repos/ +if echo "$command" | grep -qE 'gh\s+api'; then + if echo "$command" | grep -qE '\-X\s+(PATCH|DELETE|PUT)' && echo "$command" | grep -qE '/repos/'; then + method=$(echo "$command" | grep -oE '\-X\s+(PATCH|DELETE|PUT)' | awk '{print $2}') + msg="'gh api -X $method /repos/...' is blocked. Repo API mutations must be done manually." + echo "BLOCKED by block-gh-settings.sh: $msg" >&2 + exit 2 + fi +fi + +exit 0 diff --git a/config/shared/hooks/block-push-main.sh b/config/shared/hooks/block-git-push.sh similarity index 90% rename from config/shared/hooks/block-push-main.sh rename to config/shared/hooks/block-git-push.sh index c9e22ce8b..0229de510 100755 --- a/config/shared/hooks/block-push-main.sh +++ b/config/shared/hooks/block-git-push.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# block-push-main.sh — Shared hook for Claude Code + Codex +# block-git-push.sh - Shared hook for Claude Code + Codex # Blocks git push to main/master unless repo is in the allowlist. # Exit 2 = block (Codex), JSON decision output (Claude). set -euo pipefail @@ -37,5 +37,5 @@ done # Block the push msg="Push to main/master blocked in '$repo'. Use a feature branch + PR." -echo "BLOCKED by block-push-main.sh: $msg" >&2 +echo "BLOCKED by block-git-push.sh: $msg" >&2 exit 2 diff --git a/named-hosts/matic/default.nix b/named-hosts/matic/default.nix index 226d021aa..d23ef84ba 100644 --- a/named-hosts/matic/default.nix +++ b/named-hosts/matic/default.nix @@ -71,7 +71,34 @@ import ../../hosts/nixos { ]; # Networking - networking.networkmanager.wifi.powersave = true; + networking.networkmanager.wifi = { + powersave = true; + scanRandMacAddress = true; + macAddress = "stable-ssid"; + }; + + networking.firewall = { + enable = true; + trustedInterfaces = [ "tailscale0" ]; + allowedTCPPorts = [ ]; + allowedUDPPorts = [ ]; + logRefusedConnections = true; + }; + + # Audit logging + security.auditd.enable = true; + security.audit = { + enable = true; + rules = [ + "-a exit,always -F arch=b64 -S execve -k exec" + "-a exit,always -F arch=b32 -S execve -k exec" + "-a exit,always -F arch=b64 -S setuid,setgid,setresuid,setresgid -k priv_esc" + "-w /etc/sudoers -p wa -k sudoers" + "-w /etc/passwd -p wa -k identity" + "-w /etc/shadow -p wa -k identity" + "-w /etc/ssh -p wa -k ssh" + ]; + }; # Docker virtualisation.docker.enable = true; diff --git a/spec/block_gh_settings_spec.sh b/spec/block_gh_settings_spec.sh new file mode 100644 index 000000000..4c107ceaa --- /dev/null +++ b/spec/block_gh_settings_spec.sh @@ -0,0 +1,132 @@ +#!/usr/bin/env bash +# shellcheck disable=SC2329 + +Describe 'block-gh-settings.sh' +SCRIPT="$PWD/config/shared/hooks/block-gh-settings.sh" + +Describe 'non-modifying commands' + +It 'allows gh pr list' +Data '{"tool_input": {"command": "gh pr list"}}' +When run bash "$SCRIPT" +The status should be success +End + +It 'allows gh repo view' +Data '{"tool_input": {"command": "gh repo view"}}' +When run bash "$SCRIPT" +The status should be success +End + +It 'allows gh repo clone' +Data '{"tool_input": {"command": "gh repo clone owner/repo"}}' +When run bash "$SCRIPT" +The status should be success +End + +It 'allows gh api GET' +Data '{"tool_input": {"command": "gh api /repos/owner/repo"}}' +When run bash "$SCRIPT" +The status should be success +End + +It 'allows gh api -X POST to non-repo path' +Data '{"tool_input": {"command": "gh api -X POST /gists"}}' +When run bash "$SCRIPT" +The status should be success +End + +End + +Describe 'blocked gh repo subcommands' + +It 'blocks gh repo delete' +Data '{"tool_input": {"command": "gh repo delete owner/repo"}}' +When run bash "$SCRIPT" +The status should eq 2 +The stderr should include 'BLOCKED' +End + +It 'blocks gh repo rename' +Data '{"tool_input": {"command": "gh repo rename new-name"}}' +When run bash "$SCRIPT" +The status should eq 2 +The stderr should include 'BLOCKED' +End + +It 'blocks gh repo archive' +Data '{"tool_input": {"command": "gh repo archive owner/repo"}}' +When run bash "$SCRIPT" +The status should eq 2 +The stderr should include 'BLOCKED' +End + +It 'blocks gh repo transfer' +Data '{"tool_input": {"command": "gh repo transfer owner/repo new-owner"}}' +When run bash "$SCRIPT" +The status should eq 2 +The stderr should include 'BLOCKED' +End + +It 'blocks gh repo edit' +Data '{"tool_input": {"command": "gh repo edit --description new-desc"}}' +When run bash "$SCRIPT" +The status should eq 2 +The stderr should include 'BLOCKED' +End + +End + +Describe 'blocked gh api mutations on /repos/' + +It 'blocks gh api -X PATCH /repos/...' +Data '{"tool_input": {"command": "gh api -X PATCH /repos/owner/repo"}}' +When run bash "$SCRIPT" +The status should eq 2 +The stderr should include 'BLOCKED' +End + +It 'blocks gh api -X DELETE /repos/...' +Data '{"tool_input": {"command": "gh api -X DELETE /repos/owner/repo/branches/main/protection"}}' +When run bash "$SCRIPT" +The status should eq 2 +The stderr should include 'BLOCKED' +End + +It 'blocks gh api -X PUT /repos/...' +Data '{"tool_input": {"command": "gh api -X PUT /repos/owner/repo/collaborators/user"}}' +When run bash "$SCRIPT" +The status should eq 2 +The stderr should include 'BLOCKED' +End + +End + +Describe 'codex input format' + +It 'blocks codex-style input with .command key' +Data '{"command": "gh repo delete owner/repo"}' +When run bash "$SCRIPT" +The status should eq 2 +The stderr should include 'BLOCKED' +End + +End + +Describe 'edge cases' + +It 'passes with empty input' +Data '{}' +When run bash "$SCRIPT" +The status should be success +End + +It 'passes with empty command' +Data '{"tool_input": {"command": ""}}' +When run bash "$SCRIPT" +The status should be success +End + +End + +End diff --git a/spec/block_push_main_spec.sh b/spec/block_git_push_spec.sh similarity index 97% rename from spec/block_push_main_spec.sh rename to spec/block_git_push_spec.sh index c3ff66c69..0000e4edb 100644 --- a/spec/block_push_main_spec.sh +++ b/spec/block_git_push_spec.sh @@ -1,8 +1,8 @@ #!/usr/bin/env bash # shellcheck disable=SC2329 -Describe 'block-push-main.sh' -SCRIPT="$PWD/config/shared/hooks/block-push-main.sh" +Describe 'block-git-push.sh' +SCRIPT="$PWD/config/shared/hooks/block-git-push.sh" setup() { TEMP_REPO=$(mktemp -d) diff --git a/spec/coverage_spec.sh b/spec/coverage_spec.sh index d3d583a0a..1abe4e583 100644 --- a/spec/coverage_spec.sh +++ b/spec/coverage_spec.sh @@ -356,7 +356,8 @@ config/codex/hooks/notify.sh config/codex/hooks/pushover.sh config/codex/hooks/rtk-rewrite.sh config/codex/hooks/security.sh -config/shared/hooks/block-push-main.sh +config/shared/hooks/block-gh-settings.sh +config/shared/hooks/block-git-push.sh config/cursor/activate.sh config/gemini/activate.sh config/git-ai/activate.sh