-
Notifications
You must be signed in to change notification settings - Fork 0
feat: add copilot hooks and prometheus configuration #1794
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,107 @@ | ||||||||
| #!/usr/bin/env bash | ||||||||
| # rtk-hook-version: 3 | ||||||||
| # RTK auto-rewrite hook for Claude Code PreToolUse:Bash | ||||||||
| # Transparently rewrites raw commands to their RTK equivalents. | ||||||||
| # Uses `rtk rewrite` as single source of truth — no duplicate mapping logic here. | ||||||||
| # | ||||||||
| # To add support for new commands, update src/discover/registry.rs (PATTERNS + RULES). | ||||||||
| # | ||||||||
| # Exit code protocol for `rtk rewrite`: | ||||||||
| # 0 + stdout Rewrite found, no deny/ask rule matched → auto-allow | ||||||||
| # 1 No RTK equivalent → pass through unchanged | ||||||||
| # 2 Deny rule matched → pass through (Claude Code native deny handles it) | ||||||||
| # 3 + stdout Ask rule matched → rewrite but let Claude Code prompt the user | ||||||||
|
|
||||||||
| # --- Audit logging (opt-in via RTK_HOOK_AUDIT=1) --- | ||||||||
| _rtk_audit_log() { | ||||||||
| if [ "${RTK_HOOK_AUDIT:-0}" != "1" ]; then return; fi | ||||||||
| local action="$1" original="$2" rewritten="${3:--}" | ||||||||
| local dir="${RTK_AUDIT_DIR:-${HOME}/.local/share/rtk}" | ||||||||
| mkdir -p "$dir" | ||||||||
| printf '%s | %s | %s | %s\n' \ | ||||||||
| "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$action" "$original" "$rewritten" \ | ||||||||
| >> "${dir}/hook-audit.log" | ||||||||
| } | ||||||||
|
|
||||||||
| # Guards: skip silently if dependencies missing | ||||||||
| if ! command -v rtk &>/dev/null || ! command -v jq &>/dev/null; then | ||||||||
| _rtk_audit_log "skip:no_deps" "-" | ||||||||
| exit 0 | ||||||||
| fi | ||||||||
|
|
||||||||
| set -euo pipefail | ||||||||
|
|
||||||||
| INPUT=$(cat) | ||||||||
| CMD=$(echo "$INPUT" | jq -r '.tool_input.command // empty') | ||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The script is currently fail-closed on invalid JSON input. If
Suggested change
References
|
||||||||
|
|
||||||||
| if [ -z "$CMD" ]; then | ||||||||
| _rtk_audit_log "skip:empty" "-" | ||||||||
| exit 0 | ||||||||
| fi | ||||||||
|
|
||||||||
| # Skip heredocs (rtk rewrite also skips them, but bail early) | ||||||||
| case "$CMD" in | ||||||||
| *'<<'*) _rtk_audit_log "skip:heredoc" "$CMD"; exit 0 ;; | ||||||||
| esac | ||||||||
|
|
||||||||
| # Rewrite via rtk — single source of truth for all command mappings and permission checks. | ||||||||
| # Use "|| EXIT_CODE=$?" to capture non-zero exit codes without triggering set -e. | ||||||||
| EXIT_CODE=0 | ||||||||
| REWRITTEN=$(rtk rewrite "$CMD" 2>/dev/null) || EXIT_CODE=$? | ||||||||
|
|
||||||||
| case $EXIT_CODE in | ||||||||
| 0) | ||||||||
| # Rewrite found, no permission rules matched — safe to auto-allow. | ||||||||
| if [ "$CMD" = "$REWRITTEN" ]; then | ||||||||
| _rtk_audit_log "skip:already_rtk" "$CMD" | ||||||||
| exit 0 | ||||||||
| fi | ||||||||
| ;; | ||||||||
| 1) | ||||||||
| # No RTK equivalent — pass through unchanged. | ||||||||
| _rtk_audit_log "skip:no_match" "$CMD" | ||||||||
| exit 0 | ||||||||
| ;; | ||||||||
| 2) | ||||||||
| # Deny rule matched — let Claude Code's native deny rule handle it. | ||||||||
| _rtk_audit_log "skip:deny_rule" "$CMD" | ||||||||
| exit 0 | ||||||||
| ;; | ||||||||
| 3) | ||||||||
| # Ask rule matched — rewrite the command but do NOT auto-allow so that | ||||||||
| # Claude Code prompts the user for confirmation. | ||||||||
| ;; | ||||||||
| *) | ||||||||
| exit 0 | ||||||||
| ;; | ||||||||
| esac | ||||||||
|
|
||||||||
| _rtk_audit_log "rewrite" "$CMD" "$REWRITTEN" | ||||||||
|
|
||||||||
| # Build the updated tool_input with all original fields preserved, only command changed. | ||||||||
| ORIGINAL_INPUT=$(echo "$INPUT" | jq -c '.tool_input') | ||||||||
| UPDATED_INPUT=$(echo "$ORIGINAL_INPUT" | jq --arg cmd "$REWRITTEN" '.command = $cmd') | ||||||||
|
Comment on lines
+82
to
+83
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These two
Suggested change
|
||||||||
|
|
||||||||
| if [ "$EXIT_CODE" -eq 3 ]; then | ||||||||
| # Ask: rewrite the command, omit permissionDecision so Claude Code prompts. | ||||||||
| jq -n \ | ||||||||
| --argjson updated "$UPDATED_INPUT" \ | ||||||||
| '{ | ||||||||
| "hookSpecificOutput": { | ||||||||
| "hookEventName": "PreToolUse", | ||||||||
| "updatedInput": $updated | ||||||||
| } | ||||||||
| }' | ||||||||
| else | ||||||||
| # Allow: output the rewrite instruction in Claude Code hook format. | ||||||||
| jq -n \ | ||||||||
| --argjson updated "$UPDATED_INPUT" \ | ||||||||
| '{ | ||||||||
| "hookSpecificOutput": { | ||||||||
| "hookEventName": "PreToolUse", | ||||||||
| "permissionDecision": "allow", | ||||||||
| "permissionDecisionReason": "RTK auto-rewrite", | ||||||||
| "updatedInput": $updated | ||||||||
| } | ||||||||
| }' | ||||||||
| fi | ||||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,62 @@ | ||||||
| #!/usr/bin/env bash | ||||||
|
|
||||||
| # Codex/Copilot Security Hook | ||||||
| # Blocks dangerous Bash commands by checking against deny patterns. | ||||||
| # Returns exit code 2 to block, exit code 0 to allow. | ||||||
|
|
||||||
| set -euo pipefail | ||||||
|
|
||||||
| input=$(cat) | ||||||
|
|
||||||
| # Only process shell commands when the hook input includes a tool name. | ||||||
| tool_name=$(echo "$input" | jq -r '.tool.name // .tool_name // .toolName // empty' 2>/dev/null) | ||||||
| case "$tool_name" in | ||||||
| "" | Bash | bash | shell) ;; | ||||||
| *) exit 0 ;; | ||||||
| esac | ||||||
|
|
||||||
| command=$(echo "$input" | jq -r ' | ||||||
| .tool.input.command | ||||||
| // .tool_input.command | ||||||
| // (.toolArgs | if type == "object" then .command else empty end) | ||||||
| // (.toolArgs | if type == "string" then (fromjson? | .command) else empty end) | ||||||
| // .toolInput.command | ||||||
| // .command | ||||||
| // empty | ||||||
| ' 2>/dev/null) | ||||||
| [[ -z $command ]] && exit 0 | ||||||
|
|
||||||
| # Hardcoded deny patterns (mirrors claude settings.json deny list) | ||||||
| deny_patterns=( | ||||||
| "chmod -R 777" | ||||||
| "dd if=" | ||||||
| "docker system prune -a" | ||||||
| "docker system prune -f" | ||||||
| "git push --force origin main" | ||||||
| "git push --force origin master" | ||||||
| "git push --force-with-lease origin main" | ||||||
| "git push --force-with-lease origin master" | ||||||
| "git push -f origin main" | ||||||
| "git push -f origin master" | ||||||
| "mkfs" | ||||||
| "rm -rf /*" | ||||||
| "rm -rf ~/*" | ||||||
| "sudo" | ||||||
| ) | ||||||
|
|
||||||
| # Split command at logical operators | ||||||
| IFS=$'\n' read -r -d '' -a segments < <(echo "$command" | sed -E 's/[;&|]+/\n/g' && printf '\0') || true | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using
Suggested change
References
|
||||||
|
|
||||||
| for segment in "${segments[@]}"; do | ||||||
| segment=$(echo "$segment" | xargs 2>/dev/null) || continue | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||
| [[ -z $segment ]] && continue | ||||||
|
|
||||||
| for pattern in "${deny_patterns[@]}"; do | ||||||
| if [[ $segment == $pattern* ]]; then | ||||||
| echo "BLOCKED by security.sh: Command '$segment' matches deny pattern '$pattern'" >&2 | ||||||
| exit 2 | ||||||
| fi | ||||||
| done | ||||||
| done | ||||||
|
|
||||||
| exit 0 | ||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hook duplication invites drift. Before this PR,
source = ../codex/hooks/rtk-rewrite.sh;made the codex and copilot hooks literally the same file, so they could never diverge. Pointing at./hooks/rtk-rewrite.sh(and./hooks/security.shbelow) introduces a second copy that must be kept in sync viascripts/sync-{rtk-rewrite,codex-security}.sh. A few practical consequences:config/codex/hooks/security.sh(e.g. ad-hoc deny-pattern tweaks) silently won't reach copilot until someone remembers to runscripts/sync-codex-security.sh.rtk-rewrite.shandsecurity.share mirrored. Other codex hooks (atuin-history.sh,notify.sh,pushover.sh) won't be reachable from copilot at all if/when they're added to its config.If the intent is to let copilot's hooks evolve independently from codex, that's fine — otherwise consider either reverting the
sourcepaths to../codex/hooks/...or moving the shared hooks underconfig/shared/hooks/and pointing bothconfig/codex/default.nixandconfig/copilot/default.nixat that single location.