@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,197 @@
1
+ #!/usr/bin/env bun
2
+
3
+ /**
4
+ * Claude Code Hook: errornotify
5
+ *
6
+ * PostToolUse hook that detects tool failures and logs errors.
7
+ * Checks tool output for error indicators (non-zero exit codes,
8
+ * error messages) and logs warnings to stderr. Optionally writes
9
+ * to a .claude/errors.log file for persistent error tracking.
10
+ *
11
+ * Never blocks — always outputs { continue: true }.
12
+ */
13
+
14
+ import { readFileSync, appendFileSync, mkdirSync, existsSync } from "fs";
15
+ import { join } from "path";
16
+
17
+ interface HookInput {
18
+ session_id: string;
19
+ cwd: string;
20
+ tool_name: string;
21
+ tool_input: Record<string, unknown>;
22
+ tool_output?: Record<string, unknown>;
23
+ }
24
+
25
+ interface HookOutput {
26
+ continue: true;
27
+ }
28
+
29
+ /**
30
+ * Read and parse JSON from stdin
31
+ */
32
+ function readStdinJson(): HookInput | null {
33
+ try {
34
+ const input = readFileSync(0, "utf-8").trim();
35
+ if (!input) return null;
36
+ return JSON.parse(input);
37
+ } catch {
38
+ return null;
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Check if the tool output indicates a failure
44
+ */
45
+ function detectError(input: HookInput): { isError: boolean; message: string } {
46
+ const output = input.tool_output || {};
47
+
48
+ // Check for explicit exit code
49
+ const exitCode = output.exit_code ?? output.exitCode ?? output.code;
50
+ if (exitCode !== undefined && exitCode !== null && exitCode !== 0) {
51
+ const stderr = (output.stderr as string) || (output.output as string) || "unknown error";
52
+ return {
53
+ isError: true,
54
+ message: `Exit code ${exitCode}: ${truncate(stderr, 200)}`,
55
+ };
56
+ }
57
+
58
+ // Check for error field
59
+ if (output.error && typeof output.error === "string") {
60
+ return {
61
+ isError: true,
62
+ message: `Error: ${truncate(output.error, 200)}`,
63
+ };
64
+ }
65
+
66
+ // Check output text for common error indicators
67
+ const outputText =
68
+ (output.stderr as string) ||
69
+ (output.output as string) ||
70
+ (output.content as string) ||
71
+ (output.text as string) ||
72
+ "";
73
+
74
+ if (typeof outputText === "string" && outputText.length > 0) {
75
+ // Check for common error patterns in output
76
+ const errorPatterns = [
77
+ /^error:/im,
78
+ /^fatal:/im,
79
+ /^panic:/im,
80
+ /command not found/i,
81
+ /permission denied/i,
82
+ /no such file or directory/i,
83
+ /segmentation fault/i,
84
+ /killed/i,
85
+ /ENOENT/,
86
+ /EACCES/,
87
+ /EPERM/,
88
+ /ENOMEM/,
89
+ /TypeError:/,
90
+ /ReferenceError:/,
91
+ /SyntaxError:/,
92
+ /ModuleNotFoundError:/,
93
+ /ImportError:/,
94
+ /FileNotFoundError:/,
95
+ /PermissionError:/,
96
+ /traceback \(most recent call last\)/i,
97
+ ];
98
+
99
+ for (const pattern of errorPatterns) {
100
+ if (pattern.test(outputText)) {
101
+ // Extract the first relevant line
102
+ const lines = outputText.split("\n").filter((l: string) => l.trim());
103
+ const errorLine = lines.find((l: string) => pattern.test(l)) || lines[0] || "";
104
+ return {
105
+ isError: true,
106
+ message: truncate(errorLine.trim(), 200),
107
+ };
108
+ }
109
+ }
110
+ }
111
+
112
+ return { isError: false, message: "" };
113
+ }
114
+
115
+ /**
116
+ * Truncate a string to a maximum length
117
+ */
118
+ function truncate(str: string, maxLen: number): string {
119
+ if (str.length <= maxLen) return str;
120
+ return str.slice(0, maxLen) + "...";
121
+ }
122
+
123
+ /**
124
+ * Get a human-readable description of what was being executed
125
+ */
126
+ function getToolContext(input: HookInput): string {
127
+ const toolName = input.tool_name;
128
+ const toolInput = input.tool_input || {};
129
+
130
+ switch (toolName) {
131
+ case "Bash":
132
+ return `Bash: ${truncate((toolInput.command as string) || "unknown command", 100)}`;
133
+ case "Write":
134
+ case "Edit":
135
+ return `${toolName}: ${(toolInput.file_path as string) || "unknown file"}`;
136
+ case "Read":
137
+ return `Read: ${(toolInput.file_path as string) || "unknown file"}`;
138
+ default:
139
+ return toolName;
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Write error to .claude/errors.log
145
+ */
146
+ function writeErrorLog(cwd: string, toolContext: string, errorMessage: string, sessionId: string): void {
147
+ try {
148
+ const claudeDir = join(cwd, ".claude");
149
+ if (!existsSync(claudeDir)) {
150
+ mkdirSync(claudeDir, { recursive: true });
151
+ }
152
+
153
+ const logFile = join(claudeDir, "errors.log");
154
+ const timestamp = new Date().toISOString();
155
+ const entry = `[${timestamp}] [session:${sessionId.slice(0, 8)}] ${toolContext} — ${errorMessage}\n`;
156
+ appendFileSync(logFile, entry);
157
+ } catch {
158
+ // Silently fail — logging should never cause issues
159
+ }
160
+ }
161
+
162
+ /**
163
+ * Output hook response
164
+ */
165
+ function respond(): void {
166
+ const output: HookOutput = { continue: true };
167
+ console.log(JSON.stringify(output));
168
+ }
169
+
170
+ /**
171
+ * Main hook execution
172
+ */
173
+ export function run(): void {
174
+ const input = readStdinJson();
175
+
176
+ if (!input) {
177
+ respond();
178
+ return;
179
+ }
180
+
181
+ const { isError, message } = detectError(input);
182
+
183
+ if (isError) {
184
+ const toolContext = getToolContext(input);
185
+ console.error(`[hook-errornotify] FAILURE in ${toolContext}`);
186
+ console.error(`[hook-errornotify] ${message}`);
187
+
188
+ // Write to persistent error log
189
+ writeErrorLog(input.cwd, toolContext, message, input.session_id);
190
+ }
191
+
192
+ respond();
193
+ }
194
+
195
+ if (import.meta.main) {
196
+ run();
197
+ }
@@ -0,0 +1,48 @@
1
+ # hook-permissionguard
2
+
3
+ Claude Code hook that auto-approves safe read-only commands and blocks dangerous patterns.
4
+
5
+ ## Overview
6
+
7
+ Reduces permission prompts for safe commands while blocking truly dangerous operations. Commands that don't match either list pass through normally.
8
+
9
+ ## Hook Event
10
+
11
+ - **PreToolUse** (matcher: `Bash`)
12
+
13
+ ## Auto-Approved Commands
14
+
15
+ Read-only commands that are always safe:
16
+
17
+ | Category | Commands |
18
+ |----------|----------|
19
+ | Git (read-only) | `git status`, `git log`, `git diff`, `git branch`, `git show`, `git tag` |
20
+ | File reading | `ls`, `cat`, `head`, `tail`, `wc`, `find`, `grep`, `rg`, `pwd` |
21
+ | Testing | `npm test`, `bun test`, `pytest`, `cargo test`, `go test`, `jest`, `vitest` |
22
+ | Package listing | `npm list`, `bun pm ls`, `pip list`, `cargo tree` |
23
+ | Version checks | `node -v`, `bun -v`, `python --version`, `cargo --version`, etc. |
24
+
25
+ **Note**: Piped commands (`cmd | cmd`), chained commands (`cmd && cmd`), and semicolon-separated commands (`cmd; cmd`) are never auto-approved, even if individual parts are safe.
26
+
27
+ ## Blocked Commands
28
+
29
+ Dangerous patterns that are always blocked:
30
+
31
+ | Pattern | Reason |
32
+ |---------|--------|
33
+ | `rm -rf /`, `rm -rf ~`, `rm -rf $HOME` | Destructive deletion |
34
+ | `:(){ :\|:& };:` | Fork bomb |
35
+ | `dd if=`, `mkfs.`, `fdisk` | Disk destruction |
36
+ | `curl \| sh`, `wget \| sh` | Remote code execution |
37
+ | `chmod 777`, `chmod -R 777` | Insecure permissions |
38
+ | `shutdown`, `reboot` | System control |
39
+
40
+ ## Behavior
41
+
42
+ 1. Checks command against dangerous patterns — blocks if matched
43
+ 2. Checks command against safe allowlist — auto-approves if matched
44
+ 3. Everything else: approves (passes through to Claude's normal permission flow)
45
+
46
+ ## License
47
+
48
+ MIT
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@hasna/hook-permissionguard",
3
+ "version": "0.1.0",
4
+ "description": "Claude Code hook that auto-approves safe commands and blocks dangerous ones",
5
+ "type": "module",
6
+ "bin": {
7
+ "hook-permissionguard": "./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
+ "permission",
33
+ "guard",
34
+ "safety",
35
+ "allowlist",
36
+ "blocklist",
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,268 @@
1
+ #!/usr/bin/env bun
2
+
3
+ /**
4
+ * Claude Code Hook: permissionguard
5
+ *
6
+ * PreToolUse hook that auto-approves safe read-only commands and
7
+ * blocks dangerous patterns. Everything else passes through.
8
+ *
9
+ * Safe commands (auto-approve):
10
+ * - git status, git log, git diff, git branch
11
+ * - ls, cat, head, tail, wc, find, grep
12
+ * - npm test, bun test, pytest, cargo test
13
+ * - npm list, bun pm ls, pip list
14
+ * - node --version, bun --version, python --version
15
+ *
16
+ * Dangerous patterns (auto-block):
17
+ * - rm -rf / or ~ or $HOME
18
+ * - Fork bombs
19
+ * - dd if=, mkfs., fdisk
20
+ * - curl|sh, wget|sh (pipe to shell)
21
+ * - chmod 777
22
+ */
23
+
24
+ import { readFileSync } from "fs";
25
+
26
+ interface HookInput {
27
+ session_id: string;
28
+ cwd: string;
29
+ tool_name: string;
30
+ tool_input: Record<string, unknown>;
31
+ }
32
+
33
+ interface HookOutput {
34
+ decision: "approve" | "block";
35
+ reason?: string;
36
+ }
37
+
38
+ function readStdinJson(): HookInput | null {
39
+ try {
40
+ const input = readFileSync(0, "utf-8").trim();
41
+ if (!input) return null;
42
+ return JSON.parse(input);
43
+ } catch {
44
+ return null;
45
+ }
46
+ }
47
+
48
+ function respond(output: HookOutput): void {
49
+ console.log(JSON.stringify(output));
50
+ }
51
+
52
+ /**
53
+ * Patterns for safe read-only commands that can be auto-approved.
54
+ * These match the beginning of a command (after trimming).
55
+ */
56
+ const SAFE_COMMAND_PATTERNS: RegExp[] = [
57
+ // Git read-only
58
+ /^git\s+status(\s|$)/,
59
+ /^git\s+log(\s|$)/,
60
+ /^git\s+diff(\s|$)/,
61
+ /^git\s+branch(\s|$)/,
62
+ /^git\s+show(\s|$)/,
63
+ /^git\s+remote\s+-v(\s|$)/,
64
+ /^git\s+tag(\s|$)/,
65
+
66
+ // File reading
67
+ /^ls(\s|$)/,
68
+ /^cat\s/,
69
+ /^head\s/,
70
+ /^tail\s/,
71
+ /^wc\s/,
72
+ /^find\s/,
73
+ /^grep\s/,
74
+ /^rg\s/,
75
+ /^file\s/,
76
+ /^stat\s/,
77
+ /^du\s/,
78
+ /^df\s/,
79
+ /^which\s/,
80
+ /^type\s/,
81
+ /^pwd$/,
82
+ /^echo\s/,
83
+
84
+ // Testing
85
+ /^npm\s+test(\s|$)/,
86
+ /^npm\s+run\s+test(\s|$)/,
87
+ /^bun\s+test(\s|$)/,
88
+ /^bun\s+run\s+test(\s|$)/,
89
+ /^pytest(\s|$)/,
90
+ /^python\s+-m\s+pytest(\s|$)/,
91
+ /^cargo\s+test(\s|$)/,
92
+ /^go\s+test(\s|$)/,
93
+ /^jest(\s|$)/,
94
+ /^vitest(\s|$)/,
95
+
96
+ // Package listing
97
+ /^npm\s+list(\s|$)/,
98
+ /^npm\s+ls(\s|$)/,
99
+ /^bun\s+pm\s+ls(\s|$)/,
100
+ /^pip\s+list(\s|$)/,
101
+ /^pip\s+show\s/,
102
+ /^cargo\s+tree(\s|$)/,
103
+
104
+ // Version checks
105
+ /^node\s+--version$/,
106
+ /^node\s+-v$/,
107
+ /^bun\s+--version$/,
108
+ /^bun\s+-v$/,
109
+ /^python\s+--version$/,
110
+ /^python3\s+--version$/,
111
+ /^pip\s+--version$/,
112
+ /^cargo\s+--version$/,
113
+ /^go\s+version$/,
114
+ /^rustc\s+--version$/,
115
+ /^ruby\s+--version$/,
116
+ /^java\s+--version$/,
117
+ /^java\s+-version$/,
118
+ ];
119
+
120
+ /**
121
+ * Dangerous patterns that should always be blocked.
122
+ */
123
+ const DANGEROUS_PATTERNS: Array<{ pattern: RegExp; description: string }> = [
124
+ // Destructive rm commands
125
+ {
126
+ pattern: /rm\s+(-[a-zA-Z]*r[a-zA-Z]*f|--recursive\s+--force|-[a-zA-Z]*f[a-zA-Z]*r)\s+[/~]/,
127
+ description: "rm -rf on root or home directory",
128
+ },
129
+ {
130
+ pattern: /rm\s+(-[a-zA-Z]*r[a-zA-Z]*f|--recursive\s+--force|-[a-zA-Z]*f[a-zA-Z]*r)\s+\$HOME/,
131
+ description: "rm -rf $HOME",
132
+ },
133
+ {
134
+ pattern: /rm\s+(-[a-zA-Z]*r[a-zA-Z]*f|--recursive\s+--force|-[a-zA-Z]*f[a-zA-Z]*r)\s+\/\s*$/,
135
+ description: "rm -rf /",
136
+ },
137
+
138
+ // Fork bomb
139
+ {
140
+ pattern: /:\(\)\s*\{\s*:\s*\|\s*:\s*&\s*\}\s*;\s*:/,
141
+ description: "fork bomb",
142
+ },
143
+
144
+ // Disk destruction
145
+ {
146
+ pattern: /\bdd\s+if=/,
147
+ description: "dd command (raw disk write)",
148
+ },
149
+ {
150
+ pattern: /\bmkfs\./,
151
+ description: "mkfs (filesystem format)",
152
+ },
153
+ {
154
+ pattern: /\bfdisk\b/,
155
+ description: "fdisk (partition manipulation)",
156
+ },
157
+
158
+ // Pipe to shell (remote code execution)
159
+ {
160
+ pattern: /curl\s+.*\|\s*(ba)?sh/,
161
+ description: "curl piped to shell (remote code execution)",
162
+ },
163
+ {
164
+ pattern: /wget\s+.*\|\s*(ba)?sh/,
165
+ description: "wget piped to shell (remote code execution)",
166
+ },
167
+ {
168
+ pattern: /curl\s+.*\|\s*sudo\s+(ba)?sh/,
169
+ description: "curl piped to sudo shell (remote code execution)",
170
+ },
171
+ {
172
+ pattern: /wget\s+.*\|\s*sudo\s+(ba)?sh/,
173
+ description: "wget piped to sudo shell (remote code execution)",
174
+ },
175
+
176
+ // Insecure permissions
177
+ {
178
+ pattern: /chmod\s+(-R\s+)?777\b/,
179
+ description: "chmod 777 (world-writable permissions)",
180
+ },
181
+ {
182
+ pattern: /chmod\s+-R\s+777\b/,
183
+ description: "chmod -R 777 (recursive world-writable permissions)",
184
+ },
185
+
186
+ // Additional dangerous patterns
187
+ {
188
+ pattern: /\bshutdown\b/,
189
+ description: "system shutdown",
190
+ },
191
+ {
192
+ pattern: /\breboot\b/,
193
+ description: "system reboot",
194
+ },
195
+ {
196
+ pattern: />\s*\/dev\/sda/,
197
+ description: "writing to raw disk device",
198
+ },
199
+ ];
200
+
201
+ function isSafeCommand(command: string): boolean {
202
+ const trimmed = command.trim();
203
+
204
+ // Check each line of a multi-line or piped command
205
+ // If the FIRST command in a pipeline is safe and there are no pipes, approve
206
+ // For piped commands, don't auto-approve (could pipe safe command to dangerous one)
207
+ if (trimmed.includes("|") || trimmed.includes("&&") || trimmed.includes(";")) {
208
+ return false;
209
+ }
210
+
211
+ for (const pattern of SAFE_COMMAND_PATTERNS) {
212
+ if (pattern.test(trimmed)) {
213
+ return true;
214
+ }
215
+ }
216
+
217
+ return false;
218
+ }
219
+
220
+ function isDangerousCommand(command: string): { dangerous: boolean; reason?: string } {
221
+ for (const { pattern, description } of DANGEROUS_PATTERNS) {
222
+ if (pattern.test(command)) {
223
+ return { dangerous: true, reason: `Blocked: ${description}` };
224
+ }
225
+ }
226
+ return { dangerous: false };
227
+ }
228
+
229
+ export function run(): void {
230
+ const input = readStdinJson();
231
+
232
+ if (!input) {
233
+ respond({ decision: "approve" });
234
+ return;
235
+ }
236
+
237
+ if (input.tool_name !== "Bash") {
238
+ respond({ decision: "approve" });
239
+ return;
240
+ }
241
+
242
+ const command = input.tool_input?.command as string;
243
+ if (!command || typeof command !== "string") {
244
+ respond({ decision: "approve" });
245
+ return;
246
+ }
247
+
248
+ // Check for dangerous patterns first (highest priority)
249
+ const dangerCheck = isDangerousCommand(command);
250
+ if (dangerCheck.dangerous) {
251
+ console.error(`[hook-permissionguard] ${dangerCheck.reason}`);
252
+ respond({ decision: "block", reason: dangerCheck.reason });
253
+ return;
254
+ }
255
+
256
+ // Check for safe commands (auto-approve without prompting)
257
+ if (isSafeCommand(command)) {
258
+ respond({ decision: "approve" });
259
+ return;
260
+ }
261
+
262
+ // Everything else: approve (pass through to Claude's normal permission flow)
263
+ respond({ decision: "approve" });
264
+ }
265
+
266
+ if (import.meta.main) {
267
+ run();
268
+ }
@@ -0,0 +1,64 @@
1
+ # hook-promptguard
2
+
3
+ Claude Code hook that blocks prompt injection, credential extraction, and social engineering attempts.
4
+
5
+ ## Event
6
+
7
+ **UserPromptSubmit** — fires before Claude processes a user prompt.
8
+
9
+ ## What It Does
10
+
11
+ Scans user prompts for malicious patterns and blocks them before Claude sees them:
12
+
13
+ ### Prompt Injection
14
+ - "ignore previous instructions", "disregard prior instructions"
15
+ - "new system prompt", "reveal system prompt", "what are your instructions"
16
+ - "you are now", "from now on you are", "entering new mode"
17
+ - "jailbreak", "DAN mode"
18
+
19
+ ### Credential Access
20
+ - "show me the api key", "print the token", "reveal password"
21
+ - "dump credentials", "dump secrets", "extract credentials"
22
+ - "read .env", "cat .secrets/"
23
+
24
+ ### Social Engineering
25
+ - "pretend you are", "act as root", "act as admin"
26
+ - "sudo mode", "god mode", "developer mode", "unrestricted mode"
27
+ - "bypass restrictions", "disable safety", "remove restrictions"
28
+
29
+ All matching is **case-insensitive**.
30
+
31
+ ## Installation
32
+
33
+ Add to your `.claude/settings.json`:
34
+
35
+ ```json
36
+ {
37
+ "hooks": {
38
+ "UserPromptSubmit": [
39
+ {
40
+ "matcher": "",
41
+ "hooks": [
42
+ {
43
+ "type": "command",
44
+ "command": "bun run hooks/hook-promptguard/src/hook.ts"
45
+ }
46
+ ]
47
+ }
48
+ ]
49
+ }
50
+ }
51
+ ```
52
+
53
+ ## Output
54
+
55
+ - `{ "decision": "approve" }` — prompt is safe
56
+ - `{ "decision": "block", "reason": "Blocked: potential prompt injection" }` — prompt blocked
57
+
58
+ ## Customization
59
+
60
+ Add or remove patterns in the `INJECTION_PATTERNS`, `CREDENTIAL_PATTERNS`, or `SOCIAL_ENGINEERING_PATTERNS` arrays in `src/hook.ts`.
61
+
62
+ ## License
63
+
64
+ MIT
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "hook-promptguard",
3
+ "version": "0.1.0",
4
+ "description": "Claude Code hook that blocks prompt injection and social engineering attempts",
5
+ "type": "module",
6
+ "main": "./src/hook.ts",
7
+ "scripts": {
8
+ "typecheck": "tsc --noEmit"
9
+ },
10
+ "author": "Hasna",
11
+ "license": "MIT"
12
+ }