Skip to main content

FrootAI — AmpliFAI your AI Ecosystem Get Started

FAI learning resource

FAI Hooks Deep Dive

Eleven security and governance hooks across five shipped lifecycle events.

L7·14 min read·High

What Are Hooks?

Hooks are automated scripts that fire at specific points in a Copilot session lifecycle. They run shell commands or Node.js modules to enforce policies — scanning for secrets, blocking dangerous tool calls, redacting PII, or logging audit trails. Unlike agents (which respond to user prompts) or instructions (which shape code output), hooks operate silently in the background, intercepting events before they cause harm.

Each hook lives in its own folder under .github/hooks/ containing a hooks.json configuration and one or more executable scripts. The hooks.json file declares which lifecycle events trigger the hook and what command to run.

hooks.json Schema

Every hook folder must contain a hooks.json file with the following structure:

hooks/fai-secrets-scanner/hooks.json
{  "version": 1,  "hooks": {    "Stop": [      {        "type": "command",        "command": "bash hooks/fai-secrets-scanner/scan-secrets.sh",        "windows": "powershell -File hooks/fai-secrets-scanner/scan-secrets.ps1",        "cwd": ".",        "env": {          "SCAN_MODE": "warn",          "SCAN_SCOPE": "diff"        },        "timeout": 30      }    ]  }}
FieldTypeRequiredDescription
versionnumberYesSchema version (always 1)
hooks.<Event>arrayYesOne or more commands keyed by lifecycle event
typestringYesCommand entry type
commandstringYesDefault command to execute
windows / env / timeoutmixedNoPlatform override, environment, and execution bound

The 5 Shipped Lifecycle Events

The 11 repository manifests use five lifecycle events. Each event has distinct timing and runtime context:

EventWhen It FiresInput (stdin)Use Cases
SessionStartWhen a session beginsRuntime session contextInitialize audit, logging, and token-budget controls
UserPromptSubmitAfter prompt submissionPrompt event contextAudit prompts and session activity
PreToolUseBefore tool executionTool name and inputBlock dangerous operations before side effects
PostToolUseAfter tool executionTool input and outputRecord a redacted tool trace
StopWhen execution stopsRuntime stop contextValidate output, scan, audit, and record cost

All 11 FAI Hooks

The repository contains 11 hook manifests covering security, governance, cost, quality, and tool observability:

#HookEventWAF PillarDescription
1fai-secrets-scannerStopSecurityScan the completed interaction for secret patterns
2fai-tool-guardianPreToolUseSecurityBlock dangerous tool operations before execution
3fai-tool-observerPostToolUseOperationalWrite bounded, redacted tool traces
4fai-governance-auditMultipleOperationalAudit session start, prompts, and stop events
5fai-license-checkerStopSecurityCheck dependency licenses at stop
6fai-waf-complianceStopReliabilityCheck WAF evidence at stop
7fai-session-loggerMultipleOperationalLog start, prompt, and stop events
8fai-cost-trackerStopCostRecord usage cost when execution stops
9fai-pii-redactorStopResponsible AIApply configured PII redaction at stop
10fai-token-budget-enforcerSessionStartCostInitialize and enforce the session token budget
11fai-output-validatorStopResponsible AIValidate output against configured quality controls

Hook Script Structure

Hook scripts follow a simple contract: read input from stdin, process it, and exit with a status code. Exit 0 means pass (allow), exit 1 means block (reject). Any output to stdout is logged; output to stderr is shown to the user on block.

hooks/fai-secrets-scanner/scan-secrets.js
#!/usr/bin/env nodeconst fs = require("fs");
// Read user prompt from stdinlet input = "";process.stdin.on("data", (chunk) => (input += chunk));process.stdin.on("end", () => {  const patterns = [    /(?:sk|pk|api)[_-]?[a-zA-Z0-9]{20,}/g,          // API keys    /(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9_]{36,}/g,   // GitHub tokens    /DefaultEndpointsProtocol=https;Account/g,         // Azure conn strings    /-----BEGIN (?:RSA )?PRIVATE KEY-----/g,           // Private keys    /eyJ[A-Za-z0-9-_]+\.eyJ[A-Za-z0-9-_]+/g,        // JWT tokens  ];
  const mode = process.env.HOOK_MODE || "warn";  const found = patterns.some((p) => p.test(input));
  if (found) {    console.error("[secrets-scanner] Potential secret detected in prompt");    process.exit(mode === "block" ? 1 : 0);  }
  console.log("[secrets-scanner] Clean — no secrets found");  process.exit(0);});

Hook Execution Flow

When a lifecycle event fires, the runtime discovers all hooks registered for that event and executes them in a deterministic order:

  1. Event fires — Copilot runtime emits e.g. userPromptSubmitted
  2. Hook discovery — Runtime scans all .github/hooks/*/hooks.json for matching events
  3. Order resolution — Hooks execute alphabetically by folder name (use numeric prefixes to control order)
  4. Input piping — Event context (prompt text, tool args, session log) is piped to stdin
  5. Script execution — The command runs with declared env variables
  6. Exit code check — Exit 0 = pass, exit 1 = block (session continues or halts)
  7. Chain continues — If pass, next hook runs. If block, remaining hooks are skipped

Warn vs Block Mode

Every FAI hook supports two execution modes controlled by the HOOK_MODE environment variable:

ModeExit CodeBehaviorUse When
warnAlways 0Log the violation but allow the action to proceedDevelopment, testing, initial rollout
block1 on violationLog the violation and halt the actionProduction, compliance-required environments
Setting Hook Mode in hooks.json
{  "version": 1,  "hooks": [    {      "event": "preToolUse",      "command": "node guard-tools.js",      "description": "Block dangerous terminal commands",      "env": {        "HOOK_MODE": "block",        "BLOCKED_PATTERNS": "rm -rf,DROP TABLE,--force,--no-verify"      }    }  ]}

Hook Chaining Order

Multiple hooks can listen to the same event. They execute in alphabetical order by folder name. To control execution order, use numeric prefixes:

Recommended Hook Folder Naming
hooks/  fai-01-pii-redactor/       # Runs first — redact PII  fai-02-secrets-scanner/    # Runs second — scan for secrets  fai-03-tool-guardian/      # Runs third — check tool safety  fai-04-cost-guardian/      # Runs fourth — enforce budget  fai-05-output-validator/   # Runs last — validate response

If hook #2 returns exit 1 (block), hooks #3-5 are skipped. The chain short-circuits on the first blocking failure. This fail-fast behavior ensures that critical security hooks can prevent all subsequent processing.

WAF Pillar Mapping

Each hook enforces specific WAF pillars, making the governance model traceable from architecture decisions down to runtime enforcement:

WAF PillarHooksEnforcement
Securitysecrets-scanner, tool-guardian, license-checkerBlock secrets, dangerous commands, risky packages
Reliabilitywaf-complianceVerify all required WAF pillars are configured
Cost Optimizationcost-guardian, token-budgetTrack spend, enforce per-session token limits
Operational Excellencegovernance-audit, session-loggerAudit trails, structured logging, correlation IDs
Responsible AIpii-redactor, output-validatorPII protection, groundedness and safety gates

Example: Tool Guardian Hook

The tool guardian intercepts preToolUse events and blocks dangerous commands before they execute:

hooks/fai-tool-guardian/guard-tools.js
#!/usr/bin/env nodelet input = "";process.stdin.on("data", (chunk) => (input += chunk));process.stdin.on("end", () => {  const { tool, args } = JSON.parse(input);  const mode = process.env.HOOK_MODE || "block";  const blocked = (process.env.BLOCKED_PATTERNS || "").split(",");
  // Only inspect terminal commands  if (tool !== "run_in_terminal") {    process.exit(0);  }
  const command = args.command || "";  const match = blocked.find((p) => command.includes(p.trim()));
  if (match) {    console.error(      "[tool-guardian] BLOCKED: " + JSON.stringify(match) +      " found in command: " + command.substring(0, 80)    );    process.exit(mode === "block" ? 1 : 0);  }
  console.log("[tool-guardian] Allowed: " + tool);  process.exit(0);});

Wiring Hooks into Solution Plays

Hooks are listed in the primitives.hooks array of fai-manifest.json. Different plays can activate different hook combinations — a public-facing chatbot play might enable all hooks, while an internal analytics play might only use cost-guardian and session-logger.

fai-manifest.json — Hook Configuration
{  "play": "01-enterprise-rag",  "primitives": {    "hooks": [      "fai-secrets-scanner",      "fai-tool-guardian",      "fai-pii-redactor",      "fai-cost-guardian",      "fai-output-validator"    ]  }}

Creating Custom Hooks

Build your own hook in 3 steps:

Terminal — Scaffold a Custom Hook
# 1. Create the hook foldermkdir -p .github/hooks/my-custom-hook
# 2. Create hooks.jsoncat > .github/hooks/my-custom-hook/hooks.json << 'EOF'{  "version": 1,  "hooks": [    {      "event": "userPromptSubmitted",      "command": "node check.js",      "description": "My custom validation"    }  ]}EOF
# 3. Create the script (read stdin, exit 0 or 1)cat > .github/hooks/my-custom-hook/check.js << 'EOF'let input = "";process.stdin.on("data", (c) => (input += c));process.stdin.on("end", () => {  // Your validation logic here  const isValid = !input.includes("forbidden-word");  process.exit(isValid ? 0 : 1);});EOF
# 4. Validatenpm run validate:primitives