diff --git a/.claude/commands/new-hook.md b/.claude/commands/new-hook.md index ec2ebf1..4b86ad9 100644 --- a/.claude/commands/new-hook.md +++ b/.claude/commands/new-hook.md @@ -86,7 +86,16 @@ Hooks can return structured responses: suppressOutput?: boolean } -// Other hooks can return: +// Stop/SubagentStop can return: +{ + decision?: 'block', + reason?: string, + continue?: boolean, + stopReason?: string, + suppressOutput?: boolean +} + +// Notification can return: { continue?: boolean, stopReason?: string, @@ -139,22 +148,31 @@ export { blockDangerousCommands } from './hooks/blockDangerousCommands'; - Each hook type has its own specific input type - Share common logic between related hooks when appropriate -### 5. Update Documentation +### 5. Update Exports + +After creating a new hook, update the `src/index.ts` file to export it: + +```typescript +// Add your export to src/index.ts +export { myHookName } from './hooks/myHookName'; +``` + +### 6. Update Documentation -After creating a new hook, update the README.md file to document it: +After creating and exporting the hook, update the README.md file to document it: 1. Add the new hook to the "Predefined Hook Utilities" section 2. Include usage examples showing how to import and use the hook 3. Document any configuration options 4. Show example output if the hook produces logs or other artifacts -### 6. Using the Hook +### 7. Using the Hook Users can then import and use the hook: ```typescript // .claude/hooks/hooks.ts -import { defineHooks, blockDangerousCommands } from 'define-claude-code-hooks'; +import { defineHooks, blockDangerousCommands } from '@timoaus/define-claude-code-hooks'; export default defineHooks({ PreToolUse: [ @@ -164,7 +182,7 @@ export default defineHooks({ }); ``` -### 7. Common Patterns and Best Practices +### 8. Common Patterns and Best Practices **File I/O in hooks:** - Use `process.cwd()` to get the current working directory for file paths @@ -198,7 +216,7 @@ export default defineHooks({ - Clean up test artifacts after running - Test edge cases like file size limits and error conditions -### 8. Advanced Hook Patterns +### 9. Advanced Hook Patterns **Tool hooks with matchers:** - Tool hooks (PreToolUse, PostToolUse) require both `matcher` and `handler` diff --git a/README.md b/README.md index 5e5a99b..217aeef 100644 --- a/README.md +++ b/README.md @@ -91,13 +91,15 @@ The CLI will automatically detect which hook files exist and update the correspo The library includes several predefined hook utilities for common logging scenarios: -| Hook Function | Options | -| ------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **`logPreToolUseEvents`**
Logs tool uses before execution | • Optional first param: `matcher` (regex pattern, defaults to '.\*' for all tools)
• `maxEventsStored` (default: 100)
• `logFileName` (default: 'hook-log.tool-use.json')
• `includeToolInput` (default: true) | -| **`logPostToolUseEvents`**
Logs tool uses after execution | • Optional first param: `matcher` (regex pattern, defaults to '.\*' for all tools)
• `maxEventsStored` (default: 100)
• `logFileName` (default: 'hook-log.tool-use.json')
• `includeToolInput` (default: true)
• `includeToolResponse` (default: true) | -| **`logStopEvents`**
Logs main agent stop events | • `maxEventsStored` (default: 100)
• `logFileName` (default: 'hook-log.stop.json') | -| **`logSubagentStopEvents`**
Logs subagent stop events | • `maxEventsStored` (default: 100)
• `logFileName` (default: 'hook-log.stop.json') | -| **`logNotificationEvents`**
Logs notification messages | • `maxEventsStored` (default: 100)
• `logFileName` (default: 'hook-log.notification.json') | + +| Hook Function | Options | +|--------------|---------| +| **`logPreToolUseEvents`**
Logs tool uses before execution | • Optional first param: `matcher` (regex pattern, defaults to '.*' for all tools)
• `maxEventsStored` (default: 100)
• `logFileName` (default: 'hook-log.tool-use.json')
• `includeToolInput` (default: true) | +| **`logPostToolUseEvents`**
Logs tool uses after execution | • Optional first param: `matcher` (regex pattern, defaults to '.*' for all tools)
• `maxEventsStored` (default: 100)
• `logFileName` (default: 'hook-log.tool-use.json')
• `includeToolInput` (default: true)
• `includeToolResponse` (default: true) | +| **`logStopEvents`**
Logs main agent stop events | • `maxEventsStored` (default: 100)
• `logFileName` (default: 'hook-log.stop.json') | +| **`logSubagentStopEvents`**
Logs subagent stop events | • `maxEventsStored` (default: 100)
• `logFileName` (default: 'hook-log.stop.json') | +| **`logNotificationEvents`**
Logs notification messages | • `maxEventsStored` (default: 100)
• `logFileName` (default: 'hook-log.notification.json') | +| **`blockEnvFiles`**
Blocks access to .env files | No options - blocks all .env file variants except example files | All predefined hooks: @@ -333,6 +335,27 @@ export default defineHooks({ }); ``` +### Environment File Protection + +```typescript +import { + defineHooks, + blockEnvFiles, +} from "@timoaus/define-claude-code-hooks"; + +export default defineHooks({ + PreToolUse: [ + blockEnvFiles, // Blocks access to .env files while allowing .env.example + ], +}); +``` + +The `blockEnvFiles` hook: +- Blocks reading or writing to `.env` files and variants (`.env.local`, `.env.production`, etc.) +- Allows access to example env files (`.env.example`, `.env.sample`, `.env.template`, `.env.dist`) +- Works with `Read`, `Write`, `Edit`, and `MultiEdit` tools +- Provides clear error messages when access is blocked + ### Combining Multiple Hooks ```typescript @@ -341,10 +364,12 @@ import { logStopEvents, logPreToolUseEvents, logPostToolUseEvents, + blockEnvFiles, } from "@timoaus/define-claude-code-hooks"; export default defineHooks({ PreToolUse: [ + blockEnvFiles, // Security: prevent .env file access logPreToolUseEvents({ logFileName: "hook-log.tool-use.json" }), // Add your custom hooks here { diff --git a/src/hooks/blockEnvFiles.ts b/src/hooks/blockEnvFiles.ts new file mode 100644 index 0000000..fefa490 --- /dev/null +++ b/src/hooks/blockEnvFiles.ts @@ -0,0 +1,59 @@ +import { defineHook } from '../index'; +import { PreToolUseInput } from '../types'; + +/** + * Blocks reading or writing of .env files and variants (e.g., .env.local, .env.production) + * while allowing example env files (e.g., .env.example, .env.sample) + * + * @example + * ```typescript + * import { defineHooks, blockEnvFiles } from 'define-claude-code-hooks'; + * + * export default defineHooks({ + * PreToolUse: [ + * blockEnvFiles, + * // other hooks... + * ] + * }); + * ``` + */ +export const blockEnvFiles = defineHook('PreToolUse', { + matcher: 'Read|Write|Edit|MultiEdit', + handler: async (input: PreToolUseInput) => { + const toolName = input.tool_name; + let filePath: string | undefined; + + // Extract file path based on tool + if (toolName === 'Read' || toolName === 'Write') { + filePath = input.tool_input.file_path; + } else if (toolName === 'Edit' || toolName === 'MultiEdit') { + filePath = input.tool_input.file_path; + } + + if (!filePath) { + return; // No file path to check + } + + // Normalize the file path for consistent checking + const normalizedPath = filePath.toLowerCase(); + + // Check if this is an example env file (allowed) + const isExampleFile = /\.env\.(example|sample|template|dist)$/i.test(filePath) || + /\.env\..*\.(example|sample|template|dist)$/i.test(filePath); + + if (isExampleFile) { + return; // Allow example env files + } + + // Check if this is a .env file or variant (blocked) + const isEnvFile = /\.env$/i.test(filePath) || // .env + /\.env\.[^.]+$/i.test(filePath); // .env.local, .env.production, etc. + + if (isEnvFile) { + return { + decision: 'block' as const, + reason: `Access to .env files is not allowed. File: ${filePath}. If you need to show an example, use .env.example or .env.sample instead.` + }; + } + } +}); \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index ca1127e..2230ab4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,6 +9,7 @@ export { logPreToolUseEvents, logPostToolUseEvents } from './hooks/logToolUseEvents'; +export { blockEnvFiles } from './hooks/blockEnvFiles'; /** * Define a typed hook handler for Claude Code