@hasna/hooks 0.0.7 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/bin/index.js +240 -42
  2. package/dist/index.js +228 -30
  3. package/hooks/hook-autoformat/README.md +39 -0
  4. package/hooks/hook-autoformat/package.json +58 -0
  5. package/hooks/hook-autoformat/src/hook.ts +223 -0
  6. package/hooks/hook-autostage/README.md +70 -0
  7. package/hooks/hook-autostage/package.json +12 -0
  8. package/hooks/hook-autostage/src/hook.ts +167 -0
  9. package/hooks/hook-commandlog/README.md +45 -0
  10. package/hooks/hook-commandlog/package.json +12 -0
  11. package/hooks/hook-commandlog/src/hook.ts +92 -0
  12. package/hooks/hook-costwatch/README.md +61 -0
  13. package/hooks/hook-costwatch/package.json +12 -0
  14. package/hooks/hook-costwatch/src/hook.ts +178 -0
  15. package/hooks/hook-desktopnotify/README.md +50 -0
  16. package/hooks/hook-desktopnotify/package.json +57 -0
  17. package/hooks/hook-desktopnotify/src/hook.ts +112 -0
  18. package/hooks/hook-envsetup/README.md +40 -0
  19. package/hooks/hook-envsetup/package.json +58 -0
  20. package/hooks/hook-envsetup/src/hook.ts +197 -0
  21. package/hooks/hook-errornotify/README.md +66 -0
  22. package/hooks/hook-errornotify/package.json +12 -0
  23. package/hooks/hook-errornotify/src/hook.ts +197 -0
  24. package/hooks/hook-permissionguard/README.md +48 -0
  25. package/hooks/hook-permissionguard/package.json +58 -0
  26. package/hooks/hook-permissionguard/src/hook.ts +268 -0
  27. package/hooks/hook-promptguard/README.md +64 -0
  28. package/hooks/hook-promptguard/package.json +12 -0
  29. package/hooks/hook-promptguard/src/hook.ts +200 -0
  30. package/hooks/hook-protectfiles/README.md +62 -0
  31. package/hooks/hook-protectfiles/package.json +58 -0
  32. package/hooks/hook-protectfiles/src/hook.ts +267 -0
  33. package/hooks/hook-sessionlog/README.md +48 -0
  34. package/hooks/hook-sessionlog/package.json +12 -0
  35. package/hooks/hook-sessionlog/src/hook.ts +100 -0
  36. package/hooks/hook-slacknotify/README.md +62 -0
  37. package/hooks/hook-slacknotify/package.json +12 -0
  38. package/hooks/hook-slacknotify/src/hook.ts +146 -0
  39. package/hooks/hook-soundnotify/README.md +63 -0
  40. package/hooks/hook-soundnotify/package.json +12 -0
  41. package/hooks/hook-soundnotify/src/hook.ts +173 -0
  42. package/hooks/hook-taskgate/README.md +62 -0
  43. package/hooks/hook-taskgate/package.json +12 -0
  44. package/hooks/hook-taskgate/src/hook.ts +169 -0
  45. package/hooks/hook-tddguard/README.md +50 -0
  46. package/hooks/hook-tddguard/package.json +12 -0
  47. package/hooks/hook-tddguard/src/hook.ts +263 -0
  48. package/package.json +3 -3
@@ -0,0 +1,200 @@
1
+ #!/usr/bin/env bun
2
+
3
+ /**
4
+ * Claude Code Hook: promptguard
5
+ *
6
+ * UserPromptSubmit hook that validates user prompts before Claude processes them.
7
+ * Blocks prompts containing:
8
+ * - Known prompt injection patterns
9
+ * - Attempts to access credentials
10
+ * - Social engineering attempts
11
+ *
12
+ * All matching is case-insensitive.
13
+ */
14
+
15
+ import { readFileSync } from "fs";
16
+
17
+ interface HookInput {
18
+ session_id: string;
19
+ cwd: string;
20
+ tool_name: string;
21
+ tool_input: Record<string, unknown>;
22
+ }
23
+
24
+ interface HookOutput {
25
+ decision: "approve" | "block";
26
+ reason?: string;
27
+ }
28
+
29
+ /**
30
+ * Patterns that indicate prompt injection attempts
31
+ */
32
+ const INJECTION_PATTERNS: RegExp[] = [
33
+ /ignore\s+(all\s+)?previous\s+instructions/i,
34
+ /ignore\s+(all\s+)?prior\s+instructions/i,
35
+ /ignore\s+(all\s+)?above\s+instructions/i,
36
+ /disregard\s+(all\s+)?previous\s+instructions/i,
37
+ /disregard\s+(all\s+)?prior\s+instructions/i,
38
+ /forget\s+(all\s+)?previous\s+instructions/i,
39
+ /override\s+(all\s+)?previous\s+instructions/i,
40
+ /new\s+system\s+prompt/i,
41
+ /your\s+system\s+prompt/i,
42
+ /reveal\s+(your\s+)?system\s+prompt/i,
43
+ /show\s+(me\s+)?(your\s+)?system\s+prompt/i,
44
+ /print\s+(your\s+)?system\s+prompt/i,
45
+ /output\s+(your\s+)?system\s+prompt/i,
46
+ /what\s+(is|are)\s+your\s+instructions/i,
47
+ /you\s+are\s+now\b/i,
48
+ /from\s+now\s+on\s+you\s+are/i,
49
+ /you\s+have\s+been\s+reprogrammed/i,
50
+ /entering\s+(a\s+)?new\s+mode/i,
51
+ /switch\s+to\s+(\w+\s+)?mode/i,
52
+ /jailbreak/i,
53
+ /DAN\s+mode/i,
54
+ ];
55
+
56
+ /**
57
+ * Patterns that attempt to access credentials
58
+ */
59
+ const CREDENTIAL_PATTERNS: RegExp[] = [
60
+ /show\s+(me\s+)?(the\s+)?api\s*key/i,
61
+ /print\s+(the\s+)?api\s*key/i,
62
+ /reveal\s+(the\s+)?api\s*key/i,
63
+ /display\s+(the\s+)?api\s*key/i,
64
+ /output\s+(the\s+)?api\s*key/i,
65
+ /what\s+(is|are)\s+(the\s+)?api\s*key/i,
66
+ /show\s+(me\s+)?(the\s+)?token/i,
67
+ /print\s+(the\s+)?token/i,
68
+ /reveal\s+(the\s+)?token/i,
69
+ /display\s+(the\s+)?token/i,
70
+ /show\s+(me\s+)?(the\s+)?password/i,
71
+ /print\s+(the\s+)?password/i,
72
+ /reveal\s+(the\s+)?password/i,
73
+ /display\s+(the\s+)?password/i,
74
+ /show\s+(me\s+)?(the\s+)?secret/i,
75
+ /print\s+(the\s+)?secret/i,
76
+ /reveal\s+(the\s+)?secret/i,
77
+ /show\s+(me\s+)?(the\s+)?credentials/i,
78
+ /reveal\s+(the\s+)?credentials/i,
79
+ /dump\s+(all\s+)?credentials/i,
80
+ /dump\s+(all\s+)?secrets/i,
81
+ /dump\s+(all\s+)?tokens/i,
82
+ /extract\s+(the\s+)?credentials/i,
83
+ /read\s+\.env\b/i,
84
+ /cat\s+\.env\b/i,
85
+ /cat\s+\.secrets\//i,
86
+ ];
87
+
88
+ /**
89
+ * Patterns that indicate social engineering attempts
90
+ */
91
+ const SOCIAL_ENGINEERING_PATTERNS: RegExp[] = [
92
+ /pretend\s+(that\s+)?you\s+are/i,
93
+ /pretend\s+to\s+be/i,
94
+ /act\s+as\s+root/i,
95
+ /act\s+as\s+(an?\s+)?admin/i,
96
+ /act\s+as\s+(an?\s+)?administrator/i,
97
+ /sudo\s+mode/i,
98
+ /admin\s+mode/i,
99
+ /root\s+mode/i,
100
+ /god\s+mode/i,
101
+ /developer\s+mode/i,
102
+ /maintenance\s+mode/i,
103
+ /debug\s+mode/i,
104
+ /unrestricted\s+mode/i,
105
+ /bypass\s+(all\s+)?restrictions/i,
106
+ /bypass\s+(all\s+)?safety/i,
107
+ /bypass\s+(all\s+)?filters/i,
108
+ /disable\s+(all\s+)?safety/i,
109
+ /disable\s+(all\s+)?restrictions/i,
110
+ /remove\s+(all\s+)?restrictions/i,
111
+ /remove\s+(all\s+)?safety/i,
112
+ /turn\s+off\s+(all\s+)?safety/i,
113
+ /turn\s+off\s+(all\s+)?restrictions/i,
114
+ ];
115
+
116
+ /**
117
+ * Read and parse JSON from stdin
118
+ */
119
+ function readStdinJson(): HookInput | null {
120
+ try {
121
+ const input = readFileSync(0, "utf-8").trim();
122
+ if (!input) return null;
123
+ return JSON.parse(input);
124
+ } catch {
125
+ return null;
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Check a prompt against all pattern lists
131
+ */
132
+ function checkPrompt(prompt: string): { blocked: boolean; category?: string } {
133
+ for (const pattern of INJECTION_PATTERNS) {
134
+ if (pattern.test(prompt)) {
135
+ return { blocked: true, category: "prompt injection" };
136
+ }
137
+ }
138
+
139
+ for (const pattern of CREDENTIAL_PATTERNS) {
140
+ if (pattern.test(prompt)) {
141
+ return { blocked: true, category: "credential access attempt" };
142
+ }
143
+ }
144
+
145
+ for (const pattern of SOCIAL_ENGINEERING_PATTERNS) {
146
+ if (pattern.test(prompt)) {
147
+ return { blocked: true, category: "social engineering" };
148
+ }
149
+ }
150
+
151
+ return { blocked: false };
152
+ }
153
+
154
+ /**
155
+ * Output hook response
156
+ */
157
+ function respond(output: HookOutput): void {
158
+ console.log(JSON.stringify(output));
159
+ }
160
+
161
+ /**
162
+ * Main hook execution
163
+ */
164
+ export function run(): void {
165
+ const input = readStdinJson();
166
+
167
+ if (!input) {
168
+ respond({ decision: "approve" });
169
+ return;
170
+ }
171
+
172
+ // Extract prompt text from tool_input
173
+ const prompt =
174
+ (input.tool_input?.prompt as string) ||
175
+ (input.tool_input?.content as string) ||
176
+ (input.tool_input?.message as string) ||
177
+ "";
178
+
179
+ if (!prompt || typeof prompt !== "string") {
180
+ respond({ decision: "approve" });
181
+ return;
182
+ }
183
+
184
+ const result = checkPrompt(prompt);
185
+
186
+ if (result.blocked) {
187
+ console.error(`[hook-promptguard] Blocked: potential ${result.category}`);
188
+ respond({
189
+ decision: "block",
190
+ reason: "Blocked: potential prompt injection",
191
+ });
192
+ return;
193
+ }
194
+
195
+ respond({ decision: "approve" });
196
+ }
197
+
198
+ if (import.meta.main) {
199
+ run();
200
+ }
@@ -0,0 +1,62 @@
1
+ # hook-protectfiles
2
+
3
+ Claude Code hook that blocks access to sensitive files like `.env`, secrets, keys, and lock files.
4
+
5
+ ## Overview
6
+
7
+ Prevents Claude from reading or modifying files that contain secrets, credentials, or are auto-generated (lock files). Protects across all tool types — file operations and bash commands.
8
+
9
+ ## Hook Event
10
+
11
+ - **PreToolUse** (matcher: `Edit|Write|Read|Bash`)
12
+
13
+ ## Protected Files
14
+
15
+ ### Always Blocked (Read + Write)
16
+
17
+ | Pattern | Description |
18
+ |---------|-------------|
19
+ | `.env`, `.env.*` | Environment variable files |
20
+ | `.secrets/` | Secrets directory |
21
+ | `credentials.json` | Credential files |
22
+ | `*.pem`, `*.key`, `*.p12`, `*.pfx` | SSL/TLS certificates and keys |
23
+ | `id_rsa`, `id_ed25519`, `id_ecdsa` | SSH keys |
24
+ | `.ssh/` | SSH directory |
25
+ | `.aws/credentials` | AWS credentials |
26
+ | `.npmrc` | npm config (may contain tokens) |
27
+ | `.netrc` | Network credentials |
28
+ | `*.keystore`, `*.jks` | Java keystores |
29
+
30
+ ### Write-Only Block (Read is OK)
31
+
32
+ | Pattern | Description |
33
+ |---------|-------------|
34
+ | `package-lock.json` | npm lock file |
35
+ | `yarn.lock` | Yarn lock file |
36
+ | `bun.lock`, `bun.lockb` | Bun lock files |
37
+ | `pnpm-lock.yaml` | pnpm lock file |
38
+ | `Gemfile.lock` | Ruby lock file |
39
+ | `poetry.lock` | Poetry lock file |
40
+ | `Cargo.lock` | Rust lock file |
41
+ | `composer.lock` | PHP lock file |
42
+
43
+ ## Tool Coverage
44
+
45
+ | Tool | Check Method |
46
+ |------|-------------|
47
+ | `Read` | Checks `tool_input.file_path` against protected patterns |
48
+ | `Edit` | Checks `tool_input.file_path` against protected + lock patterns |
49
+ | `Write` | Checks `tool_input.file_path` against protected + lock patterns |
50
+ | `Bash` | Scans command string for references to protected files |
51
+
52
+ ## Bash Command Intelligence
53
+
54
+ For Bash commands, the hook:
55
+ - Allows git commands that naturally reference `.env` (e.g., `git add .gitignore` where `.env` appears)
56
+ - Blocks direct file access (`cat .env`, `cp .secrets/`, etc.)
57
+ - Blocks redirects to lock files (`> package-lock.json`)
58
+ - Blocks sed/awk modifications to lock files
59
+
60
+ ## License
61
+
62
+ MIT
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@hasna/hook-protectfiles",
3
+ "version": "0.1.0",
4
+ "description": "Claude Code hook that blocks access to sensitive files like .env, secrets, and keys",
5
+ "type": "module",
6
+ "bin": {
7
+ "hook-protectfiles": "./dist/cli.js"
8
+ },
9
+ "main": "./dist/hook.js",
10
+ "exports": {
11
+ ".": {
12
+ "import": "./dist/hook.js",
13
+ "types": "./dist/hook.d.ts"
14
+ },
15
+ "./cli": {
16
+ "import": "./dist/cli.js"
17
+ }
18
+ },
19
+ "files": [
20
+ "dist",
21
+ "README.md"
22
+ ],
23
+ "scripts": {
24
+ "build": "bun build ./src/hook.ts --outdir ./dist --target node",
25
+ "prepublishOnly": "bun run build",
26
+ "typecheck": "tsc --noEmit"
27
+ },
28
+ "keywords": [
29
+ "claude-code",
30
+ "claude",
31
+ "hook",
32
+ "security",
33
+ "protect",
34
+ "secrets",
35
+ "env",
36
+ "credentials",
37
+ "cli"
38
+ ],
39
+ "author": "Hasna",
40
+ "license": "MIT",
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "https://github.com/hasna/open-hooks.git"
44
+ },
45
+ "publishConfig": {
46
+ "access": "public",
47
+ "registry": "https://registry.npmjs.org/"
48
+ },
49
+ "engines": {
50
+ "node": ">=18",
51
+ "bun": ">=1.0"
52
+ },
53
+ "devDependencies": {
54
+ "@types/bun": "^1.3.8",
55
+ "@types/node": "^20",
56
+ "typescript": "^5.0.0"
57
+ }
58
+ }
@@ -0,0 +1,267 @@
1
+ #!/usr/bin/env bun
2
+
3
+ /**
4
+ * Claude Code Hook: protectfiles
5
+ *
6
+ * PreToolUse hook that blocks access to sensitive files:
7
+ *
8
+ * Always blocked (Edit/Write/Read/Bash):
9
+ * - .env, .env.local, .env.production, .env.*
10
+ * - .secrets/, credentials.json
11
+ * - *.pem, *.key
12
+ * - id_rsa, id_ed25519, .ssh/
13
+ *
14
+ * Blocked for Edit/Write only (Read is OK):
15
+ * - package-lock.json, yarn.lock, bun.lock, bun.lockb
16
+ *
17
+ * For Edit/Write/Read: checks tool_input.file_path
18
+ * For Bash: checks if the command references protected files
19
+ */
20
+
21
+ import { readFileSync } from "fs";
22
+ import { basename } from "path";
23
+
24
+ interface HookInput {
25
+ session_id: string;
26
+ cwd: string;
27
+ tool_name: string;
28
+ tool_input: Record<string, unknown>;
29
+ }
30
+
31
+ interface HookOutput {
32
+ decision: "approve" | "block";
33
+ reason?: string;
34
+ }
35
+
36
+ function readStdinJson(): HookInput | null {
37
+ try {
38
+ const input = readFileSync(0, "utf-8").trim();
39
+ if (!input) return null;
40
+ return JSON.parse(input);
41
+ } catch {
42
+ return null;
43
+ }
44
+ }
45
+
46
+ function respond(output: HookOutput): void {
47
+ console.log(JSON.stringify(output));
48
+ }
49
+
50
+ /**
51
+ * Sensitive file patterns that are always blocked (read + write).
52
+ */
53
+ const ALWAYS_PROTECTED_PATTERNS: Array<{ pattern: RegExp; description: string }> = [
54
+ // Environment files
55
+ { pattern: /(?:^|\/)\.env$/, description: ".env file" },
56
+ { pattern: /(?:^|\/)\.env\.[a-zA-Z0-9._-]+$/, description: ".env.* file" },
57
+
58
+ // Secrets directory
59
+ { pattern: /(?:^|\/)\.secrets(?:\/|$)/, description: ".secrets/ directory" },
60
+
61
+ // Credential files
62
+ { pattern: /(?:^|\/)credentials\.json$/, description: "credentials.json" },
63
+
64
+ // SSL/TLS keys and certificates
65
+ { pattern: /\.pem$/, description: ".pem file (certificate/key)" },
66
+ { pattern: /\.key$/, description: ".key file (private key)" },
67
+ { pattern: /\.p12$/, description: ".p12 file (certificate bundle)" },
68
+ { pattern: /\.pfx$/, description: ".pfx file (certificate bundle)" },
69
+
70
+ // SSH keys
71
+ { pattern: /(?:^|\/)id_rsa(?:\.pub)?$/, description: "SSH RSA key" },
72
+ { pattern: /(?:^|\/)id_ed25519(?:\.pub)?$/, description: "SSH Ed25519 key" },
73
+ { pattern: /(?:^|\/)id_ecdsa(?:\.pub)?$/, description: "SSH ECDSA key" },
74
+ { pattern: /(?:^|\/)id_dsa(?:\.pub)?$/, description: "SSH DSA key" },
75
+ { pattern: /(?:^|\/)\.ssh\//, description: ".ssh/ directory" },
76
+
77
+ // AWS credentials
78
+ { pattern: /(?:^|\/)\.aws\/credentials$/, description: "AWS credentials" },
79
+
80
+ // Token files
81
+ { pattern: /(?:^|\/)\.npmrc$/, description: ".npmrc (may contain tokens)" },
82
+ { pattern: /(?:^|\/)\.netrc$/, description: ".netrc (may contain credentials)" },
83
+
84
+ // Keystore files
85
+ { pattern: /\.keystore$/, description: "keystore file" },
86
+ { pattern: /\.jks$/, description: "Java keystore" },
87
+ ];
88
+
89
+ /**
90
+ * Lock file patterns — blocked for Edit/Write only, allowed for Read.
91
+ */
92
+ const LOCK_FILE_PATTERNS: Array<{ pattern: RegExp; description: string }> = [
93
+ { pattern: /(?:^|\/)package-lock\.json$/, description: "package-lock.json (auto-generated)" },
94
+ { pattern: /(?:^|\/)yarn\.lock$/, description: "yarn.lock (auto-generated)" },
95
+ { pattern: /(?:^|\/)bun\.lock$/, description: "bun.lock (auto-generated)" },
96
+ { pattern: /(?:^|\/)bun\.lockb$/, description: "bun.lockb (auto-generated binary)" },
97
+ { pattern: /(?:^|\/)pnpm-lock\.yaml$/, description: "pnpm-lock.yaml (auto-generated)" },
98
+ { pattern: /(?:^|\/)Gemfile\.lock$/, description: "Gemfile.lock (auto-generated)" },
99
+ { pattern: /(?:^|\/)poetry\.lock$/, description: "poetry.lock (auto-generated)" },
100
+ { pattern: /(?:^|\/)Cargo\.lock$/, description: "Cargo.lock (auto-generated)" },
101
+ { pattern: /(?:^|\/)composer\.lock$/, description: "composer.lock (auto-generated)" },
102
+ ];
103
+
104
+ type ToolCategory = "read" | "write" | "bash";
105
+
106
+ function getToolCategory(toolName: string): ToolCategory | null {
107
+ switch (toolName) {
108
+ case "Read":
109
+ return "read";
110
+ case "Edit":
111
+ case "Write":
112
+ return "write";
113
+ case "Bash":
114
+ return "bash";
115
+ default:
116
+ return null;
117
+ }
118
+ }
119
+
120
+ function checkFilePath(filePath: string, category: ToolCategory): { blocked: boolean; reason?: string } {
121
+ // Check always-protected files
122
+ for (const { pattern, description } of ALWAYS_PROTECTED_PATTERNS) {
123
+ if (pattern.test(filePath)) {
124
+ return {
125
+ blocked: true,
126
+ reason: `Blocked: access to ${description} (${basename(filePath)})`,
127
+ };
128
+ }
129
+ }
130
+
131
+ // Check lock files — only block writes, allow reads
132
+ if (category === "write") {
133
+ for (const { pattern, description } of LOCK_FILE_PATTERNS) {
134
+ if (pattern.test(filePath)) {
135
+ return {
136
+ blocked: true,
137
+ reason: `Blocked: writing to ${description} — this file is auto-generated`,
138
+ };
139
+ }
140
+ }
141
+ }
142
+
143
+ return { blocked: false };
144
+ }
145
+
146
+ function checkBashCommand(command: string): { blocked: boolean; reason?: string } {
147
+ // For Bash, check if the command references any protected file
148
+ // We check both always-protected and lock files (since bash could write to them)
149
+
150
+ for (const { pattern, description } of ALWAYS_PROTECTED_PATTERNS) {
151
+ // Extract the core pattern to search in the command string
152
+ if (pattern.test(command)) {
153
+ return {
154
+ blocked: true,
155
+ reason: `Blocked: command references ${description}`,
156
+ };
157
+ }
158
+ }
159
+
160
+ // Additional string-based checks for common patterns in bash commands
161
+ const sensitiveReferences: Array<{ pattern: RegExp; description: string }> = [
162
+ { pattern: /\b\.env\b(?!\.)/, description: ".env file" },
163
+ { pattern: /\.env\.[a-zA-Z]+/, description: ".env.* file" },
164
+ { pattern: /\.secrets\//, description: ".secrets/ directory" },
165
+ { pattern: /credentials\.json/, description: "credentials.json" },
166
+ { pattern: /\bid_rsa\b/, description: "SSH RSA key" },
167
+ { pattern: /\bid_ed25519\b/, description: "SSH Ed25519 key" },
168
+ { pattern: /\.ssh\//, description: ".ssh/ directory" },
169
+ { pattern: /\.aws\/credentials/, description: "AWS credentials" },
170
+ ];
171
+
172
+ for (const { pattern, description } of sensitiveReferences) {
173
+ if (pattern.test(command)) {
174
+ // Allow read-only commands that just check existence or list
175
+ // e.g., "test -f .env", "ls .secrets/", "cat .env" should still be caught
176
+ // But git commands that reference .env in .gitignore context are OK
177
+ if (/\bgit\s+(add|commit|diff|status|log)\b/.test(command)) {
178
+ continue;
179
+ }
180
+
181
+ return {
182
+ blocked: true,
183
+ reason: `Blocked: command references ${description}`,
184
+ };
185
+ }
186
+ }
187
+
188
+ // Check if bash command writes to lock files
189
+ const lockFileWritePatterns: Array<{ pattern: RegExp; description: string }> = [
190
+ { pattern: />\s*package-lock\.json/, description: "writing to package-lock.json" },
191
+ { pattern: />\s*yarn\.lock/, description: "writing to yarn.lock" },
192
+ { pattern: />\s*bun\.lock/, description: "writing to bun.lock" },
193
+ { pattern: /sed\s+.*package-lock\.json/, description: "modifying package-lock.json" },
194
+ { pattern: /sed\s+.*yarn\.lock/, description: "modifying yarn.lock" },
195
+ { pattern: /sed\s+.*bun\.lock/, description: "modifying bun.lock" },
196
+ ];
197
+
198
+ for (const { pattern, description } of lockFileWritePatterns) {
199
+ if (pattern.test(command)) {
200
+ return {
201
+ blocked: true,
202
+ reason: `Blocked: ${description} — this file is auto-generated`,
203
+ };
204
+ }
205
+ }
206
+
207
+ return { blocked: false };
208
+ }
209
+
210
+ export function run(): void {
211
+ const input = readStdinJson();
212
+
213
+ if (!input) {
214
+ respond({ decision: "approve" });
215
+ return;
216
+ }
217
+
218
+ const category = getToolCategory(input.tool_name);
219
+ if (!category) {
220
+ respond({ decision: "approve" });
221
+ return;
222
+ }
223
+
224
+ // For Edit/Write/Read: check file_path
225
+ if (category === "read" || category === "write") {
226
+ const filePath = input.tool_input?.file_path as string;
227
+ if (!filePath || typeof filePath !== "string") {
228
+ respond({ decision: "approve" });
229
+ return;
230
+ }
231
+
232
+ const result = checkFilePath(filePath, category);
233
+ if (result.blocked) {
234
+ console.error(`[hook-protectfiles] ${result.reason}`);
235
+ respond({ decision: "block", reason: result.reason });
236
+ return;
237
+ }
238
+
239
+ respond({ decision: "approve" });
240
+ return;
241
+ }
242
+
243
+ // For Bash: check command
244
+ if (category === "bash") {
245
+ const command = input.tool_input?.command as string;
246
+ if (!command || typeof command !== "string") {
247
+ respond({ decision: "approve" });
248
+ return;
249
+ }
250
+
251
+ const result = checkBashCommand(command);
252
+ if (result.blocked) {
253
+ console.error(`[hook-protectfiles] ${result.reason}`);
254
+ respond({ decision: "block", reason: result.reason });
255
+ return;
256
+ }
257
+
258
+ respond({ decision: "approve" });
259
+ return;
260
+ }
261
+
262
+ respond({ decision: "approve" });
263
+ }
264
+
265
+ if (import.meta.main) {
266
+ run();
267
+ }
@@ -0,0 +1,48 @@
1
+ # hook-sessionlog
2
+
3
+ Claude Code hook that logs every tool call to a session log file.
4
+
5
+ ## Overview
6
+
7
+ Every time Claude calls any tool, this hook appends a JSON line to `.claude/session-log-<date>.jsonl` in the project directory. Useful for auditing, debugging, and understanding what Claude did during a session.
8
+
9
+ ## Event
10
+
11
+ - **PostToolUse** (matches all tools)
12
+
13
+ ## Log Format
14
+
15
+ Each line in the `.jsonl` file is a JSON object:
16
+
17
+ ```json
18
+ {
19
+ "timestamp": "2026-02-14T10:30:00.000Z",
20
+ "tool_name": "Edit",
21
+ "tool_input": "{\"file_path\":\"src/index.ts\",\"old_string\":\"...\",\"new_string\":\"...\"}",
22
+ "session_id": "abc123"
23
+ }
24
+ ```
25
+
26
+ - `tool_input` is truncated to 500 characters to keep log files manageable
27
+ - One file per day: `.claude/session-log-2026-02-14.jsonl`
28
+
29
+ ## Behavior
30
+
31
+ - Creates `.claude/` directory if it does not exist
32
+ - Appends to the log file (never overwrites)
33
+ - Non-blocking: logging failures are logged to stderr but never interrupt Claude
34
+ - Outputs `{ "continue": true }` always
35
+
36
+ ## Log Location
37
+
38
+ ```
39
+ <project-root>/
40
+ └── .claude/
41
+ ├── session-log-2026-02-14.jsonl
42
+ ├── session-log-2026-02-15.jsonl
43
+ └── ...
44
+ ```
45
+
46
+ ## License
47
+
48
+ MIT
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "hook-sessionlog",
3
+ "version": "0.1.0",
4
+ "description": "Claude Code hook that logs every tool call to a session log file",
5
+ "type": "module",
6
+ "main": "./src/hook.ts",
7
+ "scripts": {
8
+ "typecheck": "tsc --noEmit"
9
+ },
10
+ "author": "Hasna",
11
+ "license": "MIT"
12
+ }