From acc403c536778c082fbdf0cb0bfedbfddbfeedab Mon Sep 17 00:00:00 2001 From: virtualian Date: Sun, 26 Apr 2026 22:20:45 +0100 Subject: [PATCH] Fix SecurityValidator yaml import crash via lazy dynamic load (#156) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Static `import { parse as parseYaml } from 'yaml'` crashed the hook at module load on every PreToolUse:Read because no node_modules resolves `yaml` from `~/.pai/hooks/`. Claude Code reported the error as non-blocking, but the warning fired on every Read, polluting session output. Replace with a lazy dynamic import wrapped in try/catch: - Type-only import via `import type { parse as YamlParse } from 'yaml'` (erased at parse time; cannot reintroduce the resolution warning) - `ensureYamlLoaded()` awaits `import('yaml')` once at the top of `main()` and silently sets `parseYaml = null` on failure - `loadPatterns()` short-circuits to a fail-open empty config when `parseYaml` is null, symmetric with the existing no-file and parse-error fail-open paths The same edit is applied to the runtime `~/.pai/hooks/SecurityValidator.hook.ts` so the warning stops in the current session; the runtime and shipped copies remain byte-identical. Verified empirically: hook returns `{"continue":true}` exit 0 with zero stderr for Read/Bash/Write inputs (vs pre-fix `Cannot find package 'yaml'` warning). Failing dynamic-import cost ~10ms total per invocation, well inside the 200ms stdin window. Audit confirms only `SecurityValidator.hook.ts` uses any third-party npm package across all 17 PAI hooks — no other hooks affected. Note: this fix silences the warning but does NOT restore SecurityValidator to functional state. The validator has been silently a no-op since the v3.0 → v4.x refactor stopped shipping `patterns.example.yaml`. Restoring real pattern matching requires a separate PR that (a) makes `yaml` resolvable from `~/.pai/hooks/` and (b) restores the patterns file under `Releases/v4.0.3+/.claude/PAISECURITYSYSTEM/`. Tracked in follow-up issue. Closes #156. --- .../.claude/hooks/SecurityValidator.hook.ts | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/Releases/v4.0.3+/.claude/hooks/SecurityValidator.hook.ts b/Releases/v4.0.3+/.claude/hooks/SecurityValidator.hook.ts index 9c302d47..a8cf0756 100755 --- a/Releases/v4.0.3+/.claude/hooks/SecurityValidator.hook.ts +++ b/Releases/v4.0.3+/.claude/hooks/SecurityValidator.hook.ts @@ -63,9 +63,22 @@ import { readFileSync, existsSync, writeFileSync, mkdirSync } from 'fs'; import { join } from 'path'; import { homedir } from 'os'; -import { parse as parseYaml } from 'yaml'; +import type { parse as YamlParse } from 'yaml'; import { configPath, codePath } from './lib/paths'; +// Lazy yaml load — static `import 'yaml'` crashes module load when the package +// isn't resolvable from this hook's directory. +let parseYaml: typeof YamlParse | null = null; + +async function ensureYamlLoaded(): Promise { + if (parseYaml) return; + try { + parseYaml = (await import('yaml')).parse; + } catch { + parseYaml = null; + } +} + // ======================================== // Security Event Logging // ======================================== @@ -220,6 +233,16 @@ function loadPatterns(): PatternsConfig { }; } + if (!parseYaml) { + return { + version: '0.0', + philosophy: { mode: 'permissive', principle: 'YAML parser unavailable - fail open' }, + bash: { trusted: [], blocked: [], confirm: [], alert: [] }, + paths: { zeroAccess: [], readOnly: [], confirmWrite: [], noDelete: [] }, + projects: {} + }; + } + try { const content = readFileSync(patternsPath, 'utf-8'); patternsCache = parseYaml(content) as PatternsConfig; @@ -551,6 +574,7 @@ function handleRead(input: HookInput): void { // ======================================== async function main(): Promise { + await ensureYamlLoaded(); let input: HookInput; try {