coding-agents-hooks-authoring-456c45 — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited coding-agents-hooks-authoring-456c45 (Agent Skill) and scored it 96/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 1 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 1 flagged
The text {match} tells the agent to skip the normal "ask the user first" gate. Used adversarially it removes the human-in-the-loop check before destructive or sensitive actions, turning a normally-gated agent into a fire-and-forget executor.
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
<hooks_authoring>
Only files that export defineHook(…) AND call runAsCli(hook, module) belong directly in src/hooks/src/hooks/. Every .ts at the top level of that directory becomes a standalone CJS bundle distributed to all 5 IDEs (claude, codex, copilot, cursor, windsurf). Helper/data files without runAsCli belong in a named subdirectory: src/hooks/src/hooks/<feature>/.
src/hooks/src/hooks/<feature>/ (e.g. src/hooks/src/hooks/dangerous-actions/patterns.ts).src/hooks/src/runtime/.src/hooks/scripts/build-bundles.mjs:24 uses readdirSync(hooksDir).filter(f => f.endsWith('.ts')). There is no `{ recursive: true }`. Subdirectories are invisible to the bundler. Adding a top-level .ts without runAsCli produces a dead bundle for all 5 IDEs.
When a hook needs a new tool category (e.g. mcp-call):
TOOL_KINDS with all 5 IDE columns (null where the event doesn't exist). SemanticKind = keyof typeof TOOL_KINDS so TypeScript enforces coverage.mcp__.*), add a conditional branch at the top of lookupToolKind in the IDE-row file before the table loop. Table-driven lookup alone cannot handle open-ended tool name patterns.def.on.toolKinds.Order matters: run-hook.ts:98 gates on toolKinds before calling run(ctx). Matcher passes the event in; toolKinds must include the mapped kind or the call is dropped silently.
Every new hook must appear in every plugin's hooks.json. The canonical source is plugins/core-claude/hooks/hooks.json.tmpl (and equivalent templates in other plugins). Direct edits to generated hooks.json files are overwritten on the next npx rosettify-plugins@latest run.
Paths by plugin:
plugins/core-claude/hooks/hooks.json.tmpl → hooks.jsonplugins/core-copilot/hooks/hooks.json.tmpl → hooks/hooks.jsonplugins/core-cursor/.cursor/hooks/hooks.json.tmpl → hooks.jsonplugins/core-codex/.codex/hooks/hooks.json.tmpl → hooks.jsonplugins/core-windsurf/ — bundles are distributed but hooks.json registration is not covered by the regression test (hooks-registered.test.ts); register manually if needed.PreToolUse is absent on Copilot ('copilot': null in ide-registry.ts). If a hook uses a platform-exclusive event, add its name to CLAUDE_CODE_ONLY_HOOKS Set in src/hooks/tests/regression/hooks-registered.test.ts. Before adding a second scoped hook, refactor the Set to Map<string, Set<IdeName>>.
Co-locate tests in src/hooks/tests/<hook-name>.test.ts. The regression test (src/hooks/tests/regression/hooks-registered.test.ts) automatically discovers all .ts entries at src/hooks/src/hooks/ top level and asserts each is referenced in every plugin's hooks.json. A new hook without registration immediately fails the regression guard.
After any source change under src/hooks/src/ or instructions/r{2,3}/core/:
venv/bin/python scripts/pre_commit.pyThis builds CJS bundles, runs full test suite, and runs npx rosettify-plugins@latest to sync instructions/r{2,3}/core/ → all plugin directories.
isLibraryModule workaround. Fix: move to subdirectory.buildDenyMessage echoes evidence to transcript by default. Pass redact=true for DANGEROUS_CONTENT matches (AWS keys, PEM certs, SQL with row data).rm -rr and rm -ff. Require both flags with lookaheads: /\brm\s+-(?=[a-zA-Z]*[rR])(?=[a-zA-Z]*[fF])[a-zA-Z]+\b/./\.kube\/config$ fail when tested against filePath with trailing slash. Always test against normalizedPath = filePath.replace(/\/+$/, '').lookupToolKind and def.on.toolKinds is inert.src/hooks/scripts/build-bundles.mjs
src/hooks/src/runtime/ide-registry.ts
src/hooks/src/runtime/ide-rows/claude-code.ts
src/hooks/src/runtime/run-hook.ts:98
plugins/core-claude/hooks/hooks.json.tmpl
src/hooks/tests/regression/hooks-registered.test.ts</hooks_authoring>
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.