From dd804a7fad045a11d4d368372b9ce1f97bfa4f45 Mon Sep 17 00:00:00 2001 From: Igor Date: Mon, 6 Apr 2026 11:39:20 +0200 Subject: [PATCH 1/9] chore(hooks): add ketchup plan for validator protection --- ketchup-plan.md | 26 +++++++------------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/ketchup-plan.md b/ketchup-plan.md index 63e5713..f923b87 100644 --- a/ketchup-plan.md +++ b/ketchup-plan.md @@ -1,23 +1,11 @@ -# Ketchup Plan: Remove Legacy NPX Installation +# Ketchup Plan: Protect Validators from Modification ## TODO -## DONE +- [ ] Burst 1: isProtectedPath returns true for files inside any validatorsDirs path [depends: none] +- [ ] Burst 2: isProtectedPath returns false for files outside validatorsDirs [depends: none] +- [ ] Burst 3: handlePreToolUse denies Edit/Write to validator files [depends: 1, 2] +- [ ] Burst 4: handlePreToolUse denies Bash commands that target validator files (rm, mv, cp, cat >) [depends: 3] +- [ ] Burst 5: handlePreToolUse still allows non-validator file operations [depends: 3] -- [x] Burst 1: Delete src/cli/ directory (all 26 files) -- [x] Burst 2: Delete bin/cli.ts -- [x] Burst 3: Delete templates/ directory (settings.json, settings.local.json) -- [x] Burst 4: Delete src/settings-merger.ts and src/settings-merger.test.ts -- [x] Burst 5: Delete src/settings-template.test.ts -- [x] Burst 6: Delete src/e2e.test.ts -- [x] Burst 7: Delete src/linker.ts and src/linker.test.ts -- [x] Burst 8: Delete src/gitignore-manager.ts and src/gitignore-manager.test.ts -- [x] Burst 9: Delete src/state-manager.ts and src/state-manager.test.ts -- [x] Burst 10: Delete src/root-finder.ts and src/root-finder.test.ts -- [x] Burst 11: Remove legacy fallback from path-resolver.ts, remove config-loader.ts, update tests -- [x] Burst 12: Clean up index.ts barrel exports -- [x] Burst 13: Remove commander/cosmiconfig/yaml deps, bin entry, legacy scripts from package.json -- [x] Burst 14: Update README.md and CLAUDE.md -- [x] Burst 15: Fix resolvePathsFromEnv for skills context (CLAUDE_PLUGIN_ROOT only) -- [x] Burst 16: Add explicit pluginRoot parameter to resolvePathsFromEnv, use in config.ts -- [x] Burst 17: Update all docs, delete npm-package-test.yml, delete install-local-spec.md, clean stale dist +## DONE From ca237da70b014f1f3ffe572224f74a706dcfe728 Mon Sep 17 00:00:00 2001 From: Igor Date: Mon, 6 Apr 2026 11:39:20 +0200 Subject: [PATCH 2/9] chore(global): add changeset --- .changeset/auto-ddd203e5ae4bfdd2.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .changeset/auto-ddd203e5ae4bfdd2.md diff --git a/.changeset/auto-ddd203e5ae4bfdd2.md b/.changeset/auto-ddd203e5ae4bfdd2.md new file mode 100644 index 0000000..ba32497 --- /dev/null +++ b/.changeset/auto-ddd203e5ae4bfdd2.md @@ -0,0 +1,9 @@ +--- +"claude-auto": minor +--- + +- Switched to plugin-only mode, removing the legacy npx/CLI installation system entirely +- Added plugin marketplace support for easier installation via BeOnAuto/auto-plugins +- Added runtime configuration skill for managing validators and reminders with overrides +- Fixed commit validation ignoring the "off" mode setting +- Updated all documentation for plugin-only workflow From 696542070db953f9ece25511346729da33db8be5 Mon Sep 17 00:00:00 2001 From: Igor Date: Mon, 6 Apr 2026 11:41:26 +0200 Subject: [PATCH 3/9] feat(hooks): add isProtectedPath for validator file detection --- src/hooks/pre-tool-use.test.ts | 18 +++++++++++++++++- src/hooks/pre-tool-use.ts | 4 ++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/hooks/pre-tool-use.test.ts b/src/hooks/pre-tool-use.test.ts index 0188af3..33cd335 100644 --- a/src/hooks/pre-tool-use.test.ts +++ b/src/hooks/pre-tool-use.test.ts @@ -5,7 +5,7 @@ import * as path from 'node:path'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import type { ResolvedPaths } from '../path-resolver.js'; -import { handlePreToolUse } from './pre-tool-use.js'; +import { handlePreToolUse, isProtectedPath } from './pre-tool-use.js'; const DEFAULT_AUTO_DIR = '.claude-auto'; @@ -322,6 +322,22 @@ Validate this commit`, } }); + describe('isProtectedPath', () => { + it('returns true for file inside a validatorsDirs path', () => { + const validatorsDirs = ['/plugin/validators', '/project/.claude-auto/validators']; + + expect(isProtectedPath('/project/.claude-auto/validators/burst-atomicity.md', validatorsDirs)).toBe(true); + expect(isProtectedPath('/plugin/validators/coverage-rules.md', validatorsDirs)).toBe(true); + }); + + it('returns false for file outside validatorsDirs', () => { + const validatorsDirs = ['/plugin/validators', '/project/.claude-auto/validators']; + + expect(isProtectedPath('/project/src/hooks/pre-tool-use.ts', validatorsDirs)).toBe(false); + expect(isProtectedPath('/project/.claude-auto/reminders/tcr.md', validatorsDirs)).toBe(false); + }); + }); + it('injects reminders matching PreToolUse hook and toolName', async () => { const remindersDir = path.join(autoDir, 'reminders'); fs.mkdirSync(remindersDir, { recursive: true }); diff --git a/src/hooks/pre-tool-use.ts b/src/hooks/pre-tool-use.ts index 65c7c5b..23af69e 100644 --- a/src/hooks/pre-tool-use.ts +++ b/src/hooks/pre-tool-use.ts @@ -13,6 +13,10 @@ import type { ResolvedPaths } from '../path-resolver.js'; import { loadReminders } from '../reminder-loader.js'; import { loadValidators } from '../validator-loader.js'; +export function isProtectedPath(filePath: string, validatorsDirs: string[]): boolean { + return validatorsDirs.some((dir) => filePath.startsWith(`${dir}/`)); +} + type ToolInput = Record; type HookResult = { From ddd58b6b8bae62ef1e4722219f4e8ffa1e648bce Mon Sep 17 00:00:00 2001 From: Igor Date: Mon, 6 Apr 2026 11:41:26 +0200 Subject: [PATCH 4/9] chore(global): add changeset --- .changeset/auto-a3c621b950c81fa3.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/auto-a3c621b950c81fa3.md diff --git a/.changeset/auto-a3c621b950c81fa3.md b/.changeset/auto-a3c621b950c81fa3.md new file mode 100644 index 0000000..8aae6f8 --- /dev/null +++ b/.changeset/auto-a3c621b950c81fa3.md @@ -0,0 +1,5 @@ +--- +"claude-auto": minor +--- + +- Added path protection utility for detecting validator files, enabling hooks to identify and safeguard validator-related paths From 558e8039f6f1b736730f4a85fc5250dc409317d8 Mon Sep 17 00:00:00 2001 From: Igor Date: Mon, 6 Apr 2026 11:45:04 +0200 Subject: [PATCH 5/9] feat(hooks): block Edit/Write to validator files in pre-tool-use --- dist/bundle/scripts/pre-tool-use.js | 16 +++++++++++++++- src/hooks/pre-tool-use.test.ts | 14 ++++++++++++++ src/hooks/pre-tool-use.ts | 15 ++++++++++++++- 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/dist/bundle/scripts/pre-tool-use.js b/dist/bundle/scripts/pre-tool-use.js index f49fb8f..aa9e0ba 100755 --- a/dist/bundle/scripts/pre-tool-use.js +++ b/dist/bundle/scripts/pre-tool-use.js @@ -6783,14 +6783,28 @@ function loadValidators(dirs, overrides) { } // src/hooks/pre-tool-use.ts +function isProtectedPath(filePath, validatorsDirs) { + return validatorsDirs.some((dir) => filePath.startsWith(`${dir}/`)); +} async function handlePreToolUse(paths, sessionId, toolInput, options2 = {}) { const command = toolInput.command; if (command && isCommitCommand(command)) { const gitCwd = options2.cwd ?? process.cwd(); return handleCommitValidation(paths, sessionId, command, options2, gitCwd); } - const patterns = loadDenyPatterns(paths.claudeDir); const filePath = toolInput.file_path; + if (filePath && isProtectedPath(filePath, paths.validatorsDirs)) { + activityLog(paths.autoDir, sessionId, "pre-tool-use", `blocked protected: ${filePath}`); + debugLog(paths.autoDir, "pre-tool-use", `${filePath} blocked (immutable validator)`); + return { + hookSpecificOutput: { + hookEventName: "PreToolUse", + permissionDecision: "deny", + permissionDecisionReason: `Validator files are immutable: ${filePath}` + } + }; + } + const patterns = loadDenyPatterns(paths.claudeDir); if (filePath && isDenied(filePath, patterns)) { activityLog(paths.autoDir, sessionId, "pre-tool-use", `blocked: ${filePath}`); debugLog(paths.autoDir, "pre-tool-use", `${filePath} blocked by deny-list`); diff --git a/src/hooks/pre-tool-use.test.ts b/src/hooks/pre-tool-use.test.ts index 33cd335..32ab724 100644 --- a/src/hooks/pre-tool-use.test.ts +++ b/src/hooks/pre-tool-use.test.ts @@ -322,6 +322,20 @@ Validate this commit`, } }); + it('denies Edit/Write to validator files', async () => { + const toolInput = { file_path: path.join(autoDir, 'validators', 'burst-atomicity.md') }; + + const result = await handlePreToolUse(resolvedPaths, 'session-protect', toolInput); + + expect(result).toEqual({ + hookSpecificOutput: { + hookEventName: 'PreToolUse', + permissionDecision: 'deny', + permissionDecisionReason: `Validator files are immutable: ${toolInput.file_path}`, + }, + }); + }); + describe('isProtectedPath', () => { it('returns true for file inside a validatorsDirs path', () => { const validatorsDirs = ['/plugin/validators', '/project/.claude-auto/validators']; diff --git a/src/hooks/pre-tool-use.ts b/src/hooks/pre-tool-use.ts index 23af69e..c4e5925 100644 --- a/src/hooks/pre-tool-use.ts +++ b/src/hooks/pre-tool-use.ts @@ -47,9 +47,22 @@ export async function handlePreToolUse( return handleCommitValidation(paths, sessionId, command, options, gitCwd); } - const patterns = loadDenyPatterns(paths.claudeDir); const filePath = toolInput.file_path as string; + if (filePath && isProtectedPath(filePath, paths.validatorsDirs)) { + activityLog(paths.autoDir, sessionId, 'pre-tool-use', `blocked protected: ${filePath}`); + debugLog(paths.autoDir, 'pre-tool-use', `${filePath} blocked (immutable validator)`); + return { + hookSpecificOutput: { + hookEventName: 'PreToolUse', + permissionDecision: 'deny', + permissionDecisionReason: `Validator files are immutable: ${filePath}`, + }, + }; + } + + const patterns = loadDenyPatterns(paths.claudeDir); + if (filePath && isDenied(filePath, patterns)) { activityLog(paths.autoDir, sessionId, 'pre-tool-use', `blocked: ${filePath}`); debugLog(paths.autoDir, 'pre-tool-use', `${filePath} blocked by deny-list`); From 4e489542f04ef4fb3837a0c2cd1eb4f36bf3e182 Mon Sep 17 00:00:00 2001 From: Igor Date: Mon, 6 Apr 2026 11:45:04 +0200 Subject: [PATCH 6/9] chore(global): add changeset --- .changeset/auto-55812bafc77921a8.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/auto-55812bafc77921a8.md diff --git a/.changeset/auto-55812bafc77921a8.md b/.changeset/auto-55812bafc77921a8.md new file mode 100644 index 0000000..573b866 --- /dev/null +++ b/.changeset/auto-55812bafc77921a8.md @@ -0,0 +1,5 @@ +--- +"claude-auto": minor +--- + +- Added protection for validator files by blocking direct edits and writes through the pre-tool-use hook From 38d75dffb70e4e4d4b38e34cec5851fe78dbbe94 Mon Sep 17 00:00:00 2001 From: Igor Date: Mon, 6 Apr 2026 11:48:41 +0200 Subject: [PATCH 7/9] feat(hooks): block Bash commands targeting validator files --- dist/bundle/scripts/pre-tool-use.js | 25 ++++++++++++++++ src/hooks/pre-tool-use.test.ts | 46 ++++++++++++++++++++++++++++- src/hooks/pre-tool-use.ts | 27 +++++++++++++++++ 3 files changed, 97 insertions(+), 1 deletion(-) diff --git a/dist/bundle/scripts/pre-tool-use.js b/dist/bundle/scripts/pre-tool-use.js index aa9e0ba..10aebaf 100755 --- a/dist/bundle/scripts/pre-tool-use.js +++ b/dist/bundle/scripts/pre-tool-use.js @@ -6786,12 +6786,37 @@ function loadValidators(dirs, overrides) { function isProtectedPath(filePath, validatorsDirs) { return validatorsDirs.some((dir) => filePath.startsWith(`${dir}/`)); } +function commandTargetsProtectedPath(command, validatorsDirs) { + for (const dir of validatorsDirs) { + if (command.includes(`${dir}/`)) { + const idx = command.indexOf(`${dir}/`); + const rest = command.slice(idx); + const match = rest.match(/^(\S+)/); + if (match) return match[1]; + } + } + return void 0; +} async function handlePreToolUse(paths, sessionId, toolInput, options2 = {}) { const command = toolInput.command; if (command && isCommitCommand(command)) { const gitCwd = options2.cwd ?? process.cwd(); return handleCommitValidation(paths, sessionId, command, options2, gitCwd); } + if (command) { + const targetedPath = commandTargetsProtectedPath(command, paths.validatorsDirs); + if (targetedPath) { + activityLog(paths.autoDir, sessionId, "pre-tool-use", `blocked protected: ${targetedPath}`); + debugLog(paths.autoDir, "pre-tool-use", `${targetedPath} blocked (immutable validator)`); + return { + hookSpecificOutput: { + hookEventName: "PreToolUse", + permissionDecision: "deny", + permissionDecisionReason: `Validator files are immutable: ${targetedPath}` + } + }; + } + } const filePath = toolInput.file_path; if (filePath && isProtectedPath(filePath, paths.validatorsDirs)) { activityLog(paths.autoDir, sessionId, "pre-tool-use", `blocked protected: ${filePath}`); diff --git a/src/hooks/pre-tool-use.test.ts b/src/hooks/pre-tool-use.test.ts index 32ab724..ba124bc 100644 --- a/src/hooks/pre-tool-use.test.ts +++ b/src/hooks/pre-tool-use.test.ts @@ -5,7 +5,7 @@ import * as path from 'node:path'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import type { ResolvedPaths } from '../path-resolver.js'; -import { handlePreToolUse, isProtectedPath } from './pre-tool-use.js'; +import { commandTargetsProtectedPath, handlePreToolUse, isProtectedPath } from './pre-tool-use.js'; const DEFAULT_AUTO_DIR = '.claude-auto'; @@ -322,6 +322,21 @@ Validate this commit`, } }); + it('denies Bash command targeting validator files', async () => { + const validatorPath = path.join(autoDir, 'validators', 'burst-atomicity.md'); + const toolInput = { command: `rm ${validatorPath}` }; + + const result = await handlePreToolUse(resolvedPaths, 'session-bash-protect', toolInput); + + expect(result).toEqual({ + hookSpecificOutput: { + hookEventName: 'PreToolUse', + permissionDecision: 'deny', + permissionDecisionReason: `Validator files are immutable: ${validatorPath}`, + }, + }); + }); + it('denies Edit/Write to validator files', async () => { const toolInput = { file_path: path.join(autoDir, 'validators', 'burst-atomicity.md') }; @@ -352,6 +367,35 @@ Validate this commit`, }); }); + describe('commandTargetsProtectedPath', () => { + it('returns matched path when command contains a validator path', () => { + const dirs = ['/project/.claude-auto/validators']; + + expect(commandTargetsProtectedPath('rm /project/.claude-auto/validators/test.md', dirs)).toBe( + '/project/.claude-auto/validators/test.md', + ); + }); + + it('returns undefined when command does not contain a validator path', () => { + const dirs = ['/project/.claude-auto/validators']; + + expect(commandTargetsProtectedPath('rm /project/src/file.ts', dirs)).toBe(undefined); + }); + }); + + it('allows Bash commands not targeting validator files', async () => { + const toolInput = { command: 'rm /project/src/file.ts' }; + + const result = await handlePreToolUse(resolvedPaths, 'session-bash-ok', toolInput); + + expect(result).toEqual({ + hookSpecificOutput: { + hookEventName: 'PreToolUse', + permissionDecision: 'allow', + }, + }); + }); + it('injects reminders matching PreToolUse hook and toolName', async () => { const remindersDir = path.join(autoDir, 'reminders'); fs.mkdirSync(remindersDir, { recursive: true }); diff --git a/src/hooks/pre-tool-use.ts b/src/hooks/pre-tool-use.ts index c4e5925..b3e3d63 100644 --- a/src/hooks/pre-tool-use.ts +++ b/src/hooks/pre-tool-use.ts @@ -17,6 +17,18 @@ export function isProtectedPath(filePath: string, validatorsDirs: string[]): boo return validatorsDirs.some((dir) => filePath.startsWith(`${dir}/`)); } +export function commandTargetsProtectedPath(command: string, validatorsDirs: string[]): string | undefined { + for (const dir of validatorsDirs) { + if (command.includes(`${dir}/`)) { + const idx = command.indexOf(`${dir}/`); + const rest = command.slice(idx); + const match = rest.match(/^(\S+)/); + if (match) return match[1]; + } + } + return undefined; +} + type ToolInput = Record; type HookResult = { @@ -47,6 +59,21 @@ export async function handlePreToolUse( return handleCommitValidation(paths, sessionId, command, options, gitCwd); } + if (command) { + const targetedPath = commandTargetsProtectedPath(command, paths.validatorsDirs); + if (targetedPath) { + activityLog(paths.autoDir, sessionId, 'pre-tool-use', `blocked protected: ${targetedPath}`); + debugLog(paths.autoDir, 'pre-tool-use', `${targetedPath} blocked (immutable validator)`); + return { + hookSpecificOutput: { + hookEventName: 'PreToolUse', + permissionDecision: 'deny', + permissionDecisionReason: `Validator files are immutable: ${targetedPath}`, + }, + }; + } + } + const filePath = toolInput.file_path as string; if (filePath && isProtectedPath(filePath, paths.validatorsDirs)) { From 072d3cc8cfdb513fd45e7395cb9f7d9a0845773a Mon Sep 17 00:00:00 2001 From: Igor Date: Mon, 6 Apr 2026 11:48:41 +0200 Subject: [PATCH 8/9] chore(global): add changeset --- .changeset/auto-140fe7085c0b5f28.md | 5 +++++ ketchup-plan.md | 11 ----------- 2 files changed, 5 insertions(+), 11 deletions(-) create mode 100644 .changeset/auto-140fe7085c0b5f28.md delete mode 100644 ketchup-plan.md diff --git a/.changeset/auto-140fe7085c0b5f28.md b/.changeset/auto-140fe7085c0b5f28.md new file mode 100644 index 0000000..c449f48 --- /dev/null +++ b/.changeset/auto-140fe7085c0b5f28.md @@ -0,0 +1,5 @@ +--- +"claude-auto": minor +--- + +- Added protection to block shell commands that attempt to modify or delete validator files diff --git a/ketchup-plan.md b/ketchup-plan.md deleted file mode 100644 index f923b87..0000000 --- a/ketchup-plan.md +++ /dev/null @@ -1,11 +0,0 @@ -# Ketchup Plan: Protect Validators from Modification - -## TODO - -- [ ] Burst 1: isProtectedPath returns true for files inside any validatorsDirs path [depends: none] -- [ ] Burst 2: isProtectedPath returns false for files outside validatorsDirs [depends: none] -- [ ] Burst 3: handlePreToolUse denies Edit/Write to validator files [depends: 1, 2] -- [ ] Burst 4: handlePreToolUse denies Bash commands that target validator files (rm, mv, cp, cat >) [depends: 3] -- [ ] Burst 5: handlePreToolUse still allows non-validator file operations [depends: 3] - -## DONE From b643541e24ab32336e964b1cc6449fd5ef3a87cc Mon Sep 17 00:00:00 2001 From: Igor Date: Mon, 20 Apr 2026 14:25:14 +0200 Subject: [PATCH 9/9] chore(global): add changeset --- .changeset/auto-272d242d3df04cd5.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/auto-272d242d3df04cd5.md diff --git a/.changeset/auto-272d242d3df04cd5.md b/.changeset/auto-272d242d3df04cd5.md new file mode 100644 index 0000000..d9c9b5d --- /dev/null +++ b/.changeset/auto-272d242d3df04cd5.md @@ -0,0 +1,7 @@ +--- +"claude-auto": minor +--- + +- Protected validator files from unauthorized modifications by blocking Edit and Write operations +- Blocked Bash commands that target validator files to prevent bypassing protections +- Added path detection to identify protected validator files across the hook system