Skip to content
Security
Hook

Hooks

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

From plugin
find-cve-agent
5121 skills5 agents7 commands3 hooks
Install
$ npx -y skills add ByamB4/find-cve-agent --agent claude-code

Ships with find-cve-agent. Installing the plugin gets these hooks.

What fires, and when

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.

  • Matchesstartup|resume|clear|compactnode "hooks/session-start-context.mjs"

PreToolUse

  • MatchesBashnode "hooks/pretooluse-clone-dedup.mjs"
  • MatchesWrite|Editnode "hooks/pretooluse-finding-selfcheck.mjs"

PostToolUse

  • MatchesReadnode "hooks/posttooluse-version-check.mjs"
Read hooks/hooks.json

Where it lives

  • hooks/posttooluse-version-check.mjsRunsGitHub
    Read the script
    #!/usr/bin/env node
    
    /**
     * Post-Tool-Use: Version Check Hook
     *
     * After reading package.json, requirements.txt, go.mod, or similar:
     * - Reminds to check the exact version against NVD/OSV
     * - Reminds to verify this is the latest version
     */
    
    import { readFileSync } from "fs";
    
    // Read the tool input/output from stdin
    let input = "";
    try {
      input = readFileSync("/dev/stdin", "utf-8");
    } catch {
      process.exit(0);
    }
    
    // Check if we're reading a dependency/version file
    const versionFiles = [
      "package.json",
      "package-lock.json",
      "requirements.txt",
      "setup.py",
      "setup.cfg",
      "pyproject.toml",
      "go.mod",
      "go.sum",
      "Gemfile",
      "Gemfile.lock",
      "composer.json",
      "Cargo.toml",
      "pom.xml",
    ];
    
    const isVersionFile = versionFiles.some((f) => input.includes(f));
    
    if (!isVersionFile) {
      process.exit(0);
    }
    
    const reminders = [];
    
    reminders.push("VERSION CHECK REMINDER:");
    reminders.push("  - Note the EXACT version number from this file");
    reminders.push("  - Check NVD/OSV for existing CVEs on this exact version:");
    reminders.push(
      '    curl -s "https://api.osv.dev/v1/query" -d \'{"package":{"name":"<pkg>","ecosystem":"<eco>"}}\''
    );
    reminders.push(
      "  - Verify this is the LATEST release (not an old, already-patched version):"
    );
    reminders.push("    npm view <package> version");
    reminders.push("    pip3 index versions <package>");
    reminders.push(
      "  - If the version is outdated, check the changelog for security fixes"
    );
    reminders.push(
      "  - A patched version = DUPLICATE, not a new finding. Check before proceeding."
    );
    
    console.log(reminders.join("\n"));
    
    process.exit(0);
    
  • hooks/pretooluse-clone-dedup.mjsRunsGitHub
    Read the script
    #!/usr/bin/env node
    
    /**
     * Pre-Tool-Use: Clone Deduplication Hook
     *
     * Before a Bash tool call that contains "git clone":
     * - Checks if the target repo is already in REGISTRY.md
     * - Warns if the target has already been investigated
     * - Checks if the directory already exists locally
     */
    
    import { readFileSync, existsSync } from "fs";
    import { join } from "path";
    
    const projectRoot = process.env.PROJECT_DIR || process.cwd();
    const registryPath = join(projectRoot, "REGISTRY.md");
    
    // Read the tool input from stdin
    let input = "";
    try {
      input = readFileSync("/dev/stdin", "utf-8");
    } catch {
      process.exit(0);
    }
    
    // Only act on git clone commands
    if (!input.includes("git clone")) {
      process.exit(0);
    }
    
    // Extract the repo URL or name from the clone command
    const cloneMatch = input.match(/git clone\s+(?:--[^\s]+\s+)*(?:["']?)([^\s"']+)/);
    if (!cloneMatch) {
      process.exit(0);
    }
    
    const repoUrl = cloneMatch[1];
    
    // Extract repo name from URL
    const repoName = repoUrl
      .replace(/\.git$/, "")
      .split("/")
      .pop();
    
    if (!repoName) {
      process.exit(0);
    }
    
    const warnings = [];
    
    // Check REGISTRY.md for duplicates
    if (existsSync(registryPath)) {
      try {
        const registry = readFileSync(registryPath, "utf-8");
        const lowerRegistry = registry.toLowerCase();
        const lowerName = repoName.toLowerCase();
    
        if (lowerRegistry.includes(lowerName)) {
          // Determine which section it's in
          const sections = [
            { name: "IN PROGRESS", pattern: /## IN PROGRESS([\s\S]*?)(?=##|$)/ },
            { name: "SUBMITTED", pattern: /## SUBMITTED([\s\S]*?)(?=##|$)/ },
            { name: "FALSE POSITIVES", pattern: /## FALSE POSITIVES([\s\S]*?)(?=##|$)/ },
            { name: "SKIP", pattern: /## SKIP([\s\S]*?)(?=##|$)/ },
            { name: "DUPLICATE", pattern: /## DUPLICATE([\s\S]*?)(?=##|$)/ },
          ];
    
          for (const section of sections) {
            const match = registry.match(section.pattern);
            if (match && match[1].toLowerCase().includes(lowerName)) {
              warnings.push(
                `REGISTRY WARNING: "${repoName}" found in ${section.name} section of REGISTRY.md. Check before proceeding.`
              );
              break;
            }
          }
        }
      } catch {
        // Registry couldn't be read - not critical
      }
    }
    
    // Check if target directory already exists
    const targetsDir = join(projectRoot, "targets", repoName);
    if (existsSync(targetsDir)) {
      warnings.push(
        `DIRECTORY EXISTS: targets/${repoName}/ already exists locally. The repo may already be cloned.`
      );
    }
    
    if (warnings.length > 0) {
      console.log(warnings.join("\n"));
    }
    
    process.exit(0);
    
  • hooks/pretooluse-finding-selfcheck.mjsRunsGitHub
    Read the script
    #!/usr/bin/env node
    
    /**
     * Pre-Tool-Use: Finding Self-Check Hook
     *
     * When writing to findings.md or verdict.md:
     * - Injects the self-criticism checklist as a reminder
     * - Reminds about common false positive patterns
     */
    
    import { readFileSync } from "fs";
    
    // Read the tool input from stdin
    let input = "";
    try {
      input = readFileSync("/dev/stdin", "utf-8");
    } catch {
      process.exit(0);
    }
    
    // Check if we're writing to a findings or verdict file
    const isFindings = input.includes("findings.md");
    const isVerdict = input.includes("verdict.md");
    const isPoc = input.includes("poc_");
    
    if (!isFindings && !isVerdict && !isPoc) {
      process.exit(0);
    }
    
    const reminders = [];
    
    if (isFindings) {
      reminders.push("SELF-CHECK REMINDER (findings.md):");
      reminders.push("  1. Did you trace the FULL data flow from source to sink?");
      reminders.push("  2. Did you verify the source is attacker-controlled (not internal)?");
      reminders.push("  3. Did you check for validation/sanitization between source and sink?");
      reminders.push("  4. Did you read the README for 'untrusted input' warnings?");
      reminders.push("  5. Is this a real security bug, or intended behavior?");
    }
    
    if (isVerdict) {
      reminders.push("SELF-CHECK REMINDER (verdict.md):");
      reminders.push("  1. Did the PoC succeed 3/3 times?");
      reminders.push("  2. Is this the LATEST version of the package?");
      reminders.push("  3. Does exploitation require permissions that already grant equivalent access?");
      reminders.push("  4. Are there runtime/framework protections you haven't checked?");
      reminders.push("  5. Am I hallucinating? LLMs are biased toward seeing bugs.");
      reminders.push("  6. Did I actually READ the validation code, or assume it works?");
      reminders.push("  7. For DoS: OOM crash or just a caught RangeError?");
    }
    
    if (isPoc) {
      reminders.push("POC REMINDER:");
      reminders.push("  - Did the Director approve this PoC plan?");
      reminders.push("  - Does the PoC run locally only (no remote targets)?");
      reminders.push("  - Does it use the exact version from the target's lockfile?");
      reminders.push("  - Does it produce concrete evidence (not 'it might crash')?");
    }
    
    if (reminders.length > 0) {
      console.log(reminders.join("\n"));
    }
    
    process.exit(0);
    
  • hooks/session-start-context.mjsRunsGitHub
    Read the script
    #!/usr/bin/env node
    
    /**
     * Session Start Context Hook
     *
     * On session start/resume/clear/compact:
     * - Reads REGISTRY.md if it exists and injects current research state
     * - Reads any active target brief for context continuity
     * - Provides a summary of what's in progress
     */
    
    import { readFileSync, existsSync, readdirSync, statSync } from "fs";
    import { join, resolve } from "path";
    
    const projectRoot = process.env.PROJECT_DIR || process.cwd();
    const registryPath = join(projectRoot, "REGISTRY.md");
    const targetsDir = join(projectRoot, "targets");
    
    const lines = [];
    
    // Read REGISTRY.md summary
    if (existsSync(registryPath)) {
      try {
        const content = readFileSync(registryPath, "utf-8");
        const inProgressMatches = content.match(/^\|[^|]+\|[^|]+\|[^|]+\|[^|]+\|$/gm);
        const submittedMatches = content.match(
          /## SUBMITTED[\s\S]*?(?=##|$)/
        );
    
        let inProgressCount = 0;
        let submittedCount = 0;
    
        if (inProgressMatches) {
          // Subtract header row
          inProgressCount = Math.max(0, inProgressMatches.length - 5);
        }
    
        // Count submitted entries
        if (submittedMatches) {
          const submittedLines = submittedMatches[0]
            .split("\n")
            .filter((l) => l.startsWith("|") && !l.includes("---") && !l.includes("Repo"));
          submittedCount = submittedLines.length;
        }
    
        // Count sections
        const sections = {
          in_progress: (content.match(/## IN PROGRESS([\s\S]*?)(?=##|$)/)?.[1] || "")
            .split("\n")
            .filter((l) => l.startsWith("|") && !l.includes("---") && !l.includes("Repo")).length,
          submitted: (content.match(/## SUBMITTED([\s\S]*?)(?=##|$)/)?.[1] || "")
            .split("\n")
            .filter((l) => l.startsWith("|") && !l.includes("---") && !l.includes("Repo")).length,
          false_positives: (content.match(/## FALSE POSITIVES([\s\S]*?)(?=##|$)/)?.[1] || "")
            .split("\n")
            .filter((l) => l.startsWith("|") && !l.includes("---") && !l.includes("Repo")).length,
          skip: (content.match(/## SKIP([\s\S]*?)(?=##|$)/)?.[1] || "")
            .split("\n")
            .filter((l) => l.startsWith("|") && !l.includes("---") && !l.includes("Repo")).length,
        };
    
        lines.push("REGISTRY STATUS:");
        lines.push(`  In Progress: ${sections.in_progress}`);
        lines.push(`  Submitted: ${sections.submitted}`);
        lines.push(`  False Positives: ${sections.false_positives}`);
        lines.push(`  Skipped: ${sections.skip}`);
      } catch {
        // Registry exists but couldn't be parsed - not critical
      }
    } else {
      lines.push(
        "No REGISTRY.md found. Run /registry or /hunt to initialize."
      );
    }
    
    // Check for active targets with briefs
    if (existsSync(targetsDir)) {
      try {
        const targets = readdirSync(targetsDir).filter((d) => {
          const fullPath = join(targetsDir, d);
          return statSync(fullPath).isDirectory();
        });
    
        if (targets.length > 0) {
          lines.push("");
          lines.push("ACTIVE TARGETS:");
    
          for (const target of targets) {
            const briefPath = join(targetsDir, target, "brief.md");
            const findingsPath = join(targetsDir, target, "findings.md");
            const verdictPath = join(targetsDir, target, "verdict.md");
            const pocFiles = existsSync(join(targetsDir, target))
              ? readdirSync(join(targetsDir, target)).filter((f) =>
                  f.startsWith("poc_")
                )
              : [];
    
            let status = "recon";
            if (existsSync(verdictPath)) {
              const verdict = readFileSync(verdictPath, "utf-8");
              if (verdict.includes("CONFIRMED")) status = "CONFIRMED";
              else if (verdict.includes("FALSE_POSITIVE"))
                status = "false_positive";
              else status = "validating";
            } else if (pocFiles.length > 0) {
              status = "poc_ready";
            } else if (existsSync(findingsPath)) {
              status = "findings_ready";
            } else if (existsSync(briefPath)) {
              status = "brief_ready";
            }
    
            lines.push(`  ${target}: ${status}`);
          }
        }
      } catch {
        // Targets dir exists but couldn't be read - not critical
      }
    }
    
    if (lines.length > 0) {
      console.log(lines.join("\n"));
    }
    

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.

Ships withfind-cve-agent

Open Source CVE Hunting Harness for Claude Code A Claude Code plugin that systematically finds real CVEs in open source packages through coordinated multi-agent security research.

Get the whole plugin