FrootAI — AmpliFAI your AI Ecosystem Get Started

FAI Hooks Deep Dive

10 security and governance hooks across 4 lifecycle events — the automated safety net for every Copilot session.

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": [
    {
      "event": "userPromptSubmitted",
      "command": "node scan-secrets.js",
      "description": "Scan user prompts for accidental secret inclusion",
      "env": {
        "HOOK_MODE": "block",
        "PATTERNS_FILE": "secret-patterns.json"
      }
    },
    {
      "event": "sessionEnd",
      "command": "node scan-session-log.js",
      "description": "Scan full session log for secrets before persistence"
    }
  ]
}
FieldTypeRequiredDescription
versionnumberYesSchema version (always 1)
hooks[].eventstringYesLifecycle event to listen for
hooks[].commandstringYesShell command to execute
hooks[].descriptionstringNoHuman-readable purpose
hooks[].envobjectNoEnvironment variables passed to script

The 4 Lifecycle Events

Hooks attach to one of four events in the Copilot session lifecycle. Each event has a specific timing and receives different context data from the runtime:

EventWhen It FiresInput (stdin)Use Cases
sessionStartWhen a Copilot chat session beginsSession metadata (workspace, user)Load context, verify credentials, log audit start
userPromptSubmittedAfter user sends a message, before LLM processesThe full user prompt textPII detection, secret scanning, prompt validation
preToolUseBefore a tool call is executedTool name + arguments JSONBlock dangerous commands, enforce tool policies
sessionEndWhen the chat session closesFull session logFinal secret scan, usage logging, cost tracking

All 10 FAI Hooks

FrootAI ships 10 pre-built hooks covering security, governance, cost, and quality. Each hook maps to specific WAF pillars:

#HookEventWAF PillarDescription
1secrets-scanneruserPromptSubmittedSecurityDetect API keys, tokens, connection strings in prompts
2tool-guardianpreToolUseSecurityBlock rm -rf, DROP TABLE, force-push, and dangerous commands
3governance-auditsessionEndOperationalLog all tool calls and decisions for compliance audit trail
4license-checkerpreToolUseSecurityVerify package licenses before npm/pip install
5waf-compliancesessionStartReliabilityVerify WAF pillar coverage meets play requirements
6session-loggersessionStart/EndOperationalStructured logging with correlation IDs for observability
7cost-guardianpreToolUseCostTrack token usage, enforce per-session cost budgets
8pii-redactoruserPromptSubmittedResponsible AIDetect and redact personal information before LLM processing
9token-budgetpreToolUseCostEnforce max_tokens limits per query tier
10output-validatorsessionEndResponsible AIValidate LLM outputs against groundedness and safety thresholds

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 node
const fs = require("fs");

// Read user prompt from stdin
let 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 node
let 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 10 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 folder
mkdir -p .github/hooks/my-custom-hook

# 2. Create hooks.json
cat > .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. Validate
npm run validate:primitives