@hasna/hooks 0.0.6 → 0.1.0

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 (49) hide show
  1. package/.claude/settings.json +24 -0
  2. package/bin/index.js +758 -319
  3. package/dist/index.js +156 -1
  4. package/hooks/hook-autoformat/README.md +39 -0
  5. package/hooks/hook-autoformat/package.json +58 -0
  6. package/hooks/hook-autoformat/src/hook.ts +223 -0
  7. package/hooks/hook-autostage/README.md +70 -0
  8. package/hooks/hook-autostage/package.json +12 -0
  9. package/hooks/hook-autostage/src/hook.ts +167 -0
  10. package/hooks/hook-commandlog/README.md +45 -0
  11. package/hooks/hook-commandlog/package.json +12 -0
  12. package/hooks/hook-commandlog/src/hook.ts +92 -0
  13. package/hooks/hook-costwatch/README.md +61 -0
  14. package/hooks/hook-costwatch/package.json +12 -0
  15. package/hooks/hook-costwatch/src/hook.ts +178 -0
  16. package/hooks/hook-desktopnotify/README.md +50 -0
  17. package/hooks/hook-desktopnotify/package.json +57 -0
  18. package/hooks/hook-desktopnotify/src/hook.ts +112 -0
  19. package/hooks/hook-envsetup/README.md +40 -0
  20. package/hooks/hook-envsetup/package.json +58 -0
  21. package/hooks/hook-envsetup/src/hook.ts +197 -0
  22. package/hooks/hook-errornotify/README.md +66 -0
  23. package/hooks/hook-errornotify/package.json +12 -0
  24. package/hooks/hook-errornotify/src/hook.ts +197 -0
  25. package/hooks/hook-permissionguard/README.md +48 -0
  26. package/hooks/hook-permissionguard/package.json +58 -0
  27. package/hooks/hook-permissionguard/src/hook.ts +268 -0
  28. package/hooks/hook-promptguard/README.md +64 -0
  29. package/hooks/hook-promptguard/package.json +12 -0
  30. package/hooks/hook-promptguard/src/hook.ts +200 -0
  31. package/hooks/hook-protectfiles/README.md +62 -0
  32. package/hooks/hook-protectfiles/package.json +58 -0
  33. package/hooks/hook-protectfiles/src/hook.ts +267 -0
  34. package/hooks/hook-sessionlog/README.md +48 -0
  35. package/hooks/hook-sessionlog/package.json +12 -0
  36. package/hooks/hook-sessionlog/src/hook.ts +100 -0
  37. package/hooks/hook-slacknotify/README.md +62 -0
  38. package/hooks/hook-slacknotify/package.json +12 -0
  39. package/hooks/hook-slacknotify/src/hook.ts +146 -0
  40. package/hooks/hook-soundnotify/README.md +63 -0
  41. package/hooks/hook-soundnotify/package.json +12 -0
  42. package/hooks/hook-soundnotify/src/hook.ts +173 -0
  43. package/hooks/hook-taskgate/README.md +62 -0
  44. package/hooks/hook-taskgate/package.json +12 -0
  45. package/hooks/hook-taskgate/src/hook.ts +169 -0
  46. package/hooks/hook-tddguard/README.md +50 -0
  47. package/hooks/hook-tddguard/package.json +12 -0
  48. package/hooks/hook-tddguard/src/hook.ts +263 -0
  49. package/package.json +4 -3
@@ -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
+ }
@@ -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
+ }