Skip to content
Security
Hook

Hooks

What repo-forensics runs automatically, and when. A hook is a command Claude Code fires at a fixed moment, without you asking for it.

From plugin
repo-forensics
2055 skills3 hooks

Claude Code

> /plugin marketplace add alexgreensh/repo-forensics
> /plugin install repo-forensics@alexgreensh-repo-forensics

What fires, and when

PreToolUse

  • MatchesBashbash "${CLAUDE_PLUGIN_ROOT}/hooks/run_pre_scan.sh"

PostToolUse

  • MatchesBashbash "${CLAUDE_PLUGIN_ROOT}/hooks/run_auto_scan.sh"

SessionStart

Fires once when a session begins, and again after a context compaction. It is where a plugin sets up its environment, or restores state the compaction dropped.

  • bash "${CLAUDE_PLUGIN_ROOT}/hooks/run_session_scan.sh"bash "${CLAUDE_PLUGIN_ROOT}/hooks/first-run-nudge.sh"
Read hooks/hooks.json

Where it lives

  • hooks/ensure_refresh_daemon.shGitHub
    Read the script
    #!/usr/bin/env bash
    # SessionStart adapter for the cross-platform refresh controller.
    
    set -u
    CALLER_PATH="${PATH:-}"
    # Trusted command roots for this wrapper's own utilities (dirname, cd, pwd):
    # FHS + NixOS system profiles (root-owned) + per-user Nix/XDG profile dirs
    # (same trust as Homebrew). NixOS has no /usr/bin or /bin coreutils.
    PATH="/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/opt/homebrew/bin:/run/current-system/sw/bin:/run/wrappers/bin:/nix/var/nix/profiles/default/bin"
    if [ -n "${HOME:-}" ]; then
        PATH="$PATH:$HOME/.nix-profile/bin:$HOME/.local/bin:$HOME/.local/state/nix/profile/bin"
    fi
    _rf_profile_user="${USER:-}"
    if [ -z "$_rf_profile_user" ] && [ -n "${HOME:-}" ]; then
        _rf_profile_user="${HOME##*/}"
    fi
    if [ -n "$_rf_profile_user" ]; then
        PATH="$PATH:/etc/profiles/per-user/$_rf_profile_user/bin"
    fi
    unset _rf_profile_user
    export PATH
    
    SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" || exit 0
    PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-${KIMI_PLUGIN_ROOT:-$(cd "$SCRIPT_DIR/.." && pwd)}}"
    CONTROLLER="$PLUGIN_ROOT/skills/repo-forensics/scripts/refresh_controller.py"
    LAUNCHER="$PLUGIN_ROOT/hooks/python-launcher.sh"
    
    [ -f "$CONTROLLER" ] || exit 0
    [ -f "$LAUNCHER" ] || exit 0
    
    # Preserve caller PATH only for the interpreter launcher: it independently
    # allowlists every Python location, including standard Windows installs. The
    # wrapper's own utilities above resolve exclusively through trusted paths.
    # Repair is detached so a slow/broken scheduler API cannot block SessionStart.
    PATH="${CALLER_PATH:+$CALLER_PATH:}$PATH" \
        "${BASH:-$(command -v bash || echo /bin/bash)}" "$LAUNCHER" "$CONTROLLER" ensure \
        </dev/null >/dev/null 2>&1 &
    exit 0
    
  • hooks/first-run-nudge.shRunsGitHub
    Read the script
    #!/usr/bin/env bash
    # repo-forensics - SessionStart First-Run Auto-Update Nudge
    #
    # Marketplace installs can go stale. This hook prints a one-time platform-aware
    # message telling users how to keep repo-forensics current so they get new IOCs,
    # detection rules, and critical security patches.
    #
    # For a security scanner specifically, stale installs are especially dangerous:
    # users running repo-forensics against known supply chain attacks need the IOC
    # list that was current at the time of the attack, not six weeks ago.
    #
    # Conditions (all must be true for the nudge to fire):
    #   - running from a plugin cache (marketplace install, not a dev checkout)
    #   - flag file absent (one-shot per user)
    #   - REPO_FORENSICS_NUDGE environment variable not set to 0 (kill switch)
    #
    # Copyright (C) 2026 Alex Greenshpun
    # SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
    
    SCRIPT_DIR="$(dirname "$0")"
    PLUGIN_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
    
    CODEX_ROOT="${CODEX_HOME:-${HOME}/.codex}"
    
    # If the user explicitly set CODEX_HOME, validate it exists before proceeding.
    # When unset we fall back to the default guess ($HOME/.codex), which may simply
    # not exist yet on Claude-only machines — that is normal and should not abort.
    if [ -n "${CODEX_HOME:-}" ] && [ ! -d "$CODEX_ROOT" ]; then
      echo "Warning: CODEX_ROOT not found: $CODEX_ROOT" >&2
      exit 0
    fi
    
    if [ -d "$CODEX_ROOT" ]; then
        CODEX_ROOT="$(cd "$CODEX_ROOT" && pwd)"
    fi
    CLAUDE_ROOT="${HOME}/.claude"
    if [ -d "$CLAUDE_ROOT" ]; then
        CLAUDE_ROOT="$(cd "$CLAUDE_ROOT" && pwd)"
    fi
    CURSOR_ROOT="${CURSOR_HOME:-${HOME}/.cursor}"
    if [ -d "$CURSOR_ROOT" ]; then
        CURSOR_ROOT="$(cd "$CURSOR_ROOT" && pwd)"
    fi
    KIMI_ROOT="${KIMI_CODE_HOME:-${HOME}/.kimi-code}"
    if [ -d "$KIMI_ROOT" ]; then
        KIMI_ROOT="$(cd "$KIMI_ROOT" && pwd)"
    fi
    
    PLATFORM="generic"
    STATE_ROOT="${HOME}/.repo-forensics"
    if [[ "$PLUGIN_ROOT" == "$CODEX_ROOT/"* || "$PLUGIN_ROOT" == *"/.codex/"* ]]; then
        PLATFORM="codex"
        STATE_ROOT="${CODEX_ROOT}/repo-forensics"
    elif [[ "$PLUGIN_ROOT" == "$CLAUDE_ROOT/"* || "$PLUGIN_ROOT" == *"/.claude/"* ]]; then
        PLATFORM="claude"
        STATE_ROOT="${CLAUDE_ROOT}/repo-forensics"
    elif [[ "$PLUGIN_ROOT" == "$CURSOR_ROOT/"* || "$PLUGIN_ROOT" == *"/.cursor/"* ]]; then
        PLATFORM="cursor"
        STATE_ROOT="${CURSOR_ROOT}/repo-forensics"
    elif [[ "$PLUGIN_ROOT" == "$KIMI_ROOT/"* || "$PLUGIN_ROOT" == *"/.kimi-code/"* ]]; then
        PLATFORM="kimi"
        STATE_ROOT="${KIMI_ROOT}/repo-forensics"
    fi
    NUDGE_FLAG="${STATE_ROOT}/.marketplace-nudge-shown"
    
    # Kill switch
    if [ "${REPO_FORENSICS_NUDGE:-1}" = "0" ]; then
        exit 0
    fi
    
    # Only fire for marketplace/cache installs. Dev-symlink or script-install users
    # have their own update paths and do not need this hint. Kimi Code installs
    # land in plugins/managed rather than plugins/cache.
    if [[ "$PLUGIN_ROOT" != *"/plugins/cache/"* && "$PLUGIN_ROOT" != *"/plugins/managed/"* ]]; then
        exit 0
    fi
    
    # One-shot: if we've already shown the nudge, stay silent.
    if [ -f "$NUDGE_FLAG" ]; then
        exit 0
    fi
    
    if [ "$PLATFORM" = "claude" ]; then
        cat <<'NUDGE'
    
      [repo-forensics] First-run tip: enable auto-update for this marketplace
      so you get new IOCs, detection rules, and critical security patches
      automatically. For a security scanner, stale installs are especially
      dangerous. In Claude Code:
    
          /plugin  ->  Marketplaces  ->  select your repo-forensics marketplace
                   ->  Enable auto-update
    
      Third-party marketplaces ship with auto-update off by default in Claude
      Code. This is not our choice. Opt out of this hint permanently with
      REPO_FORENSICS_NUDGE=0. This message will not show again.
    
    NUDGE
    elif [ "$PLATFORM" = "codex" ]; then
        cat <<'NUDGE'
    
      [repo-forensics] First-run tip: keep your Codex marketplace snapshot fresh
      so you get new IOCs, detection rules, and critical security patches.
    
          codex plugin marketplace upgrade
    
      If repo-forensics was already installed from that marketplace, reinstall it
      after refreshing the snapshot. Opt out of this hint permanently with
      REPO_FORENSICS_NUDGE=0. This message will not show again.
    
    NUDGE
    elif [ "$PLATFORM" = "cursor" ]; then
        cat <<'NUDGE'
    
      [repo-forensics] First-run tip: keep this install fresh so you get new IOCs,
      detection rules, and critical security patches. beforeShellExecution blocks
      known-malicious installs before they run, and it can only block what its
      IOC database knows about.
    
          python3 scripts/cursor_install.py --verify
    
      re-checks that the hooks are wired and current. Opt out of this hint
      permanently with REPO_FORENSICS_NUDGE=0. This message will not show again.
    
    NUDGE
    elif [ "$PLATFORM" = "kimi" ]; then
        cat <<'NUDGE'
    
      [repo-forensics] First-run tip: keep this plugin fresh so you get new
      IOCs, detection rules, and critical security patches. For a security
      scanner, stale installs are especially dangerous. In Kimi Code:
    
          /plugins  ->  Installed  ->  repo-forensics  ->  Enter (install update)
    
      Kimi Code does not auto-update plugins installed from GitHub; reinstall
      when a new version is available. Opt out of this hint permanently with
      REPO_FORENSICS_NUDGE=0. This message will not show again.
    
    NUDGE
    else
        cat <<'NUDGE'
    
      [repo-forensics] First-run tip: keep your plugin source fresh so you get
      new IOCs, detection rules, and critical security patches. Update or
      reinstall repo-forensics from your agent's plugin marketplace when a new
      version is available. Opt out of this hint permanently with
      REPO_FORENSICS_NUDGE=0. This message will not show again.
    
    NUDGE
    fi
    
    mkdir -p "$(dirname "$NUDGE_FLAG")" 2>/dev/null
    touch "$NUDGE_FLAG" 2>/dev/null
    exit 0
    
  • hooks/install_refresh_daemon.shGitHub
    Read the script
    #!/usr/bin/env bash
    # Compatibility entry point: install/repair native refresh automation.
    
    set -u
    CALLER_PATH="${PATH:-}"
    # Trusted command roots for this wrapper's own utilities (dirname, cd, pwd):
    # FHS + NixOS system profiles (root-owned) + per-user Nix/XDG profile dirs
    # (same trust as Homebrew). NixOS has no /usr/bin or /bin coreutils.
    PATH="/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/opt/homebrew/bin:/run/current-system/sw/bin:/run/wrappers/bin:/nix/var/nix/profiles/default/bin"
    if [ -n "${HOME:-}" ]; then
        PATH="$PATH:$HOME/.nix-profile/bin:$HOME/.local/bin:$HOME/.local/state/nix/profile/bin"
    fi
    _rf_profile_user="${USER:-}"
    if [ -z "$_rf_profile_user" ] && [ -n "${HOME:-}" ]; then
        _rf_profile_user="${HOME##*/}"
    fi
    if [ -n "$_rf_profile_user" ]; then
        PATH="$PATH:/etc/profiles/per-user/$_rf_profile_user/bin"
    fi
    unset _rf_profile_user
    export PATH
    SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" || exit 1
    PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-${KIMI_PLUGIN_ROOT:-$(cd "$SCRIPT_DIR/.." && pwd)}}"
    CONTROLLER="$PLUGIN_ROOT/skills/repo-forensics/scripts/refresh_controller.py"
    LAUNCHER="$PLUGIN_ROOT/hooks/python-launcher.sh"
    PATH="${CALLER_PATH:+$CALLER_PATH:}$PATH" \
        exec "${BASH:-$(command -v bash || echo /bin/bash)}" "$LAUNCHER" "$CONTROLLER" ensure --json
    
  • hooks/python-launcher.shGitHub
  • hooks/run_auto_scan.shRunsGitHub
    Read the script
    #!/usr/bin/env bash
    # Wrapper for PostToolUse auto-scan that fails loud if the target script
    # is missing instead of silently dying.
    #
    # Caught by torture-room security-sentinel Finding 6.
    #
    # Without this wrapper, if the canonical skills/repo-forensics/scripts/
    # auto_scan.py path is missing for any reason — plugin install corruption,
    # partial file restore after a tamper attempt, botched rename, tarball
    # extraction failure, or a future layout refactor that breaks the hook
    # path — the agent hook runner would silently swallow the "file not
    # found" failure and the user would have zero indication that their
    # security hook is no longer firing. For a security tool, silent failure
    # IS the worst failure mode.
    #
    # This wrapper:
    #   1. Checks that auto_scan.py exists at the expected canonical path
    #   2. If missing: prints a clear warning and exits 0 so the hook chain is not broken for the
    #      user's Bash command — graceful degradation over blocking every
    #      command
    #   3. If present: exec into python3 on auto_scan.py with argv forwarding
    #      preserved (the hook receives stdin from the agent hook runner)
    
    set -u
    
    PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-${KIMI_PLUGIN_ROOT:-}}"
    SCRIPT="$PLUGIN_ROOT/skills/repo-forensics/scripts/auto_scan.py"
    LAUNCHER="$PLUGIN_ROOT/hooks/python-launcher.sh"
    
    if [ ! -f "$SCRIPT" ]; then
        echo "[repo-forensics] WARNING: auto_scan.py not found at: $SCRIPT"
        echo "[repo-forensics] Plugin install may be corrupt, or the skill layout may have changed."
        echo "[repo-forensics] Auto-scan hook disabled for this command. Update or reinstall repo-forensics."
        # exit 0 so we don't break the user's Bash command chain. Hook is
        # PostToolUse — its failure should not retroactively fail the command.
        exit 0
    fi
    
    # Bound how often this scan may run (2.14.7). PreToolUse/PostToolUse fire on
    # every Bash command and each scan fans out into eight scanners, which stacked
    # into 16 concurrent trees and a load average of 175 before this guard existed.
    # A MISSING guard file degrades to the old unbounded behaviour on purpose: for a
    # security tool, silently not scanning is worse than scanning too often.
    GUARD="${CLAUDE_PLUGIN_ROOT}/hooks/scan_guard.sh"
    if [ -f "$GUARD" ]; then
        # shellcheck source=/dev/null
        . "$GUARD"
        if ! rf_scan_guard auto 90; then
            exit 0
        fi
    fi
    
    if [ -f "$LAUNCHER" ]; then
        exec "${BASH:-$(command -v bash || echo /bin/bash)}" "$LAUNCHER" "$SCRIPT"
    fi
    
    exec python3 "$SCRIPT"
    
  • hooks/run_pre_scan.shRunsGitHub
    Read the script
    #!/usr/bin/env bash
    # Wrapper for PreToolUse pre-scan that fails gracefully if the target script
    # is missing — same pattern as run_auto_scan.sh.
    #
    # IMPORTANT: This is a PreToolUse hook. If pre_scan.py is missing, we MUST
    # exit 0 (approve) to avoid silently blocking every Bash command. A broken
    # security hook that blocks all work is worse than a temporarily absent one.
    
    set -u
    
    PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-${KIMI_PLUGIN_ROOT:-}}"
    SCRIPT="$PLUGIN_ROOT/skills/repo-forensics/scripts/pre_scan.py"
    LAUNCHER="$PLUGIN_ROOT/hooks/python-launcher.sh"
    
    if [ ! -f "$SCRIPT" ]; then
        echo "[repo-forensics] WARNING: pre_scan.py not found at: $SCRIPT"
        echo "[repo-forensics] Plugin install may be corrupt, or the skill layout may have changed."
        echo "[repo-forensics] Pre-scan hook disabled for this command. Update or reinstall repo-forensics."
        # exit 0 = approve. NEVER exit 2 when the script is missing — that would
        # block every Bash command.
        exit 0
    fi
    
    # Every PreToolUse invocation must scan its own command. The shared guard is
    # only safe for advisory auto/session scans: skipping an overlapping pre-scan
    # would approve a different command without inspecting it.
    
    if [ -f "$LAUNCHER" ]; then
        _out="$("${BASH:-$(command -v bash || echo /bin/bash)}" "$LAUNCHER" "$SCRIPT")"
        _rc=$?
    else
        _out="$(python3 "$SCRIPT")"
        _rc=$?
    fi
    
    # Claude treats an interpreter error as non-blocking. Convert missing or
    # crashed scanners to a block rather than approving an uninspected command.
    case "$_rc" in
        0|2)
            if [ -n "$_out" ]; then
                printf '%s\n' "$_out"
                exit "$_rc"
            fi
            ;;
    esac
    echo '[repo-forensics] Pre-scan failed or returned no verdict; refusing this command.' >&2
    printf '%s\n' '{"decision":"block","reason":"repo-forensics pre-scan failed or returned no verdict. Repair the scanner before retrying."}'
    exit 2
    
  • hooks/run_session_scan.shRunsGitHub
    Read the script
    #!/usr/bin/env bash
    # Wrapper for SessionStart session_scan.py — same safety pattern as the
    # other hook wrappers.
    #
    # SessionStart hooks should NEVER prevent a session from starting.
    # If session_scan.py is missing, we log a warning and exit cleanly.
    
    set -u
    
    PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-${KIMI_PLUGIN_ROOT:-}}"
    SCRIPT="$PLUGIN_ROOT/skills/repo-forensics/scripts/session_scan.py"
    LAUNCHER="$PLUGIN_ROOT/hooks/python-launcher.sh"
    ENSURE_REFRESH="$PLUGIN_ROOT/hooks/ensure_refresh_daemon.sh"
    
    # Bootstrap or repair the background updater before checking freshness. This
    # stays silent and never blocks SessionStart if the platform scheduler fails.
    if [ -f "$ENSURE_REFRESH" ]; then
        "${BASH:-$(command -v bash || echo /bin/bash)}" "$ENSURE_REFRESH" || true
    fi
    
    if [ ! -f "$SCRIPT" ]; then
        echo "[repo-forensics] WARNING: session_scan.py not found at: $SCRIPT"
        echo "[repo-forensics] Session security scan disabled. Update or reinstall repo-forensics."
        exit 0
    fi
    
    # Bound how often this scan may run (2.14.7). PreToolUse/PostToolUse fire on
    # every Bash command and each scan fans out into eight scanners, which stacked
    # into 16 concurrent trees and a load average of 175 before this guard existed.
    # A MISSING guard file degrades to the old unbounded behaviour on purpose: for a
    # security tool, silently not scanning is worse than scanning too often.
    GUARD="${CLAUDE_PLUGIN_ROOT}/hooks/scan_guard.sh"
    if [ -f "$GUARD" ]; then
        # shellcheck source=/dev/null
        . "$GUARD"
        if ! rf_scan_guard session 60; then
            exit 0
        fi
    fi
    
    if [ -f "$LAUNCHER" ]; then
        exec "${BASH:-$(command -v bash || echo /bin/bash)}" "$LAUNCHER" "$SCRIPT"
    fi
    
    exec python3 "$SCRIPT"
    
  • hooks/scan_guard.shGitHub
  • hooks/uninstall_refresh_daemon.shGitHub

All 9 scripts are listed above. The source is inlined for 6 of them, starting with whatever hooks.json actually runs. See all of them in the repo.

Read the script before you install anything that runs on your machine. This is the one part of a plugin that acts without being asked.

@alexgreensh
Ships withrepo-forensics

Offline security scanner for AI-agent repos, skills, plugins, and MCP servers.

Stars on GitHub

Momentum

stars in total
205
Star history of alexgreensh/repo-forensics on GitHub, a chart by star-history.com

More plugins that ship hooks.

watermarks-remover

watermarks-remover

Agent skill + stdlib Python service to strip multi-vendor AI provenance marks from text and files, for privacy and hygiene on content you own. The skill is a thin client: it drives the machinery over HTTP, so the agent host needs no Python.

2 skills1 hook26k
@guillaumemeyer@guillaumemeyer
Install Plugin
agentic-bug-hunter

agentic-bug-hunter

Runs recon on a target, tests it for real, reportable vulnerabilities, checks each finding against a strict validation gate, and writes a report ready to submit to bug bounty platforms. It also remembers patterns across targets and resumes unfinished sessions.

19 skills+35.8k
@awarexone@awarexone
Install Plugin
agentic-bug-hunter

agentic-bug-hunter

A Claude Code plugin for bug bounty hunters, bundling skills for reconnaissance, vulnerability hunting such as IDOR, SSRF and XSS, black-box audits of deployed AI agents, and CI/CD pipeline security.

19 skills+35.8k
@shuvonsec@shuvonsec
Install Plugin
cc-safety-net

cc-safety-net

A pre-execution guard that blocks destructive Git and file system commands, and common attempts to read sensitive files, before a tool call runs. Works with several AI coding agents, including Claude Code.

2 skills1 hook1.9k
@kenryu42@kenryu42
Install Plugin
claude-code-safety-net

claude-code-safety-net

A pre-execution guard that blocks destructive Git and file system commands, plus common attempts to read sensitive files, before an AI coding agent runs a tool call. Works with Claude Code and several other agent CLIs.

2 skills1 hook1.9k
@kenryu42@kenryu42
Install Plugin
pentest-agents

pentest-agents

A bug bounty agent framework for Claude Code that automates reconnaissance, vulnerability hunting and exploit chaining, with skills for analyzing recon output, running autonomous hunt loops and correlating findings into attack chains.

39 skills+31.1k
@h-mmer@h-mmer
Install Plugin
vestige

vestige

Vestige is a fail-closed runtime firewall for AI agents. The model proposes, a deterministic gate decides, and every action leaves a receipt. When something breaks, causal_walk finds the real cause.

3 agents+1660
@samvallad33@samvallad33
Install Plugin
cti-expert

cti-expert

A cyber threat intelligence and OSINT analysis toolkit that runs structured investigations and produces analyst-style reports with sourced, trust-scored findings. Built for security analysts working OSINT and CTI cases in Claude Code.

1 skill+2624
@7onez@7onez
Install Plugin

Questions about the hooks in repo-forensics.

Which coding agents can use the hooks in repo-forensics?
The hooks in repo-forensics install in Claude Code and Cursor: each runs Claude Code hooks from .claude/settings.json, so copy the hooks there.