@goobits/sherpa 1.0.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 (55) hide show
  1. package/dist/chunk-3CILH2TO.js +387 -0
  2. package/dist/chunk-3CILH2TO.js.map +7 -0
  3. package/dist/chunk-5NF3BSD6.js +512 -0
  4. package/dist/chunk-5NF3BSD6.js.map +7 -0
  5. package/dist/chunk-IIU6U7TE.js +307 -0
  6. package/dist/chunk-IIU6U7TE.js.map +7 -0
  7. package/dist/chunk-LQZTKH3U.js +307 -0
  8. package/dist/chunk-LQZTKH3U.js.map +7 -0
  9. package/dist/cli.d.ts +11 -0
  10. package/dist/cli.d.ts.map +1 -0
  11. package/dist/cli.js +84 -0
  12. package/dist/cli.js.map +7 -0
  13. package/dist/commands/init.d.ts +7 -0
  14. package/dist/commands/init.d.ts.map +1 -0
  15. package/dist/commands/init.js +333 -0
  16. package/dist/commands/init.js.map +1 -0
  17. package/dist/commands/post.d.ts +20 -0
  18. package/dist/commands/post.d.ts.map +1 -0
  19. package/dist/commands/post.js +183 -0
  20. package/dist/commands/post.js.map +1 -0
  21. package/dist/commands/pre.d.ts +18 -0
  22. package/dist/commands/pre.d.ts.map +1 -0
  23. package/dist/commands/pre.js +102 -0
  24. package/dist/commands/pre.js.map +1 -0
  25. package/dist/commands/status.d.ts +5 -0
  26. package/dist/commands/status.d.ts.map +1 -0
  27. package/dist/commands/status.js +48 -0
  28. package/dist/commands/status.js.map +1 -0
  29. package/dist/daemon-V2QDZTUB.js +89 -0
  30. package/dist/daemon-V2QDZTUB.js.map +7 -0
  31. package/dist/daemon.d.ts +9 -0
  32. package/dist/daemon.d.ts.map +1 -0
  33. package/dist/daemon.js +112 -0
  34. package/dist/daemon.js.map +1 -0
  35. package/dist/index.d.ts +15 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +36 -0
  38. package/dist/index.js.map +7 -0
  39. package/dist/parser.d.ts +21 -0
  40. package/dist/parser.d.ts.map +1 -0
  41. package/dist/parser.js +152 -0
  42. package/dist/parser.js.map +1 -0
  43. package/dist/reviewer/index.js +544 -0
  44. package/dist/reviewer/index.js.map +7 -0
  45. package/dist/rules.d.ts +21 -0
  46. package/dist/rules.d.ts.map +1 -0
  47. package/dist/rules.js +165 -0
  48. package/dist/rules.js.map +1 -0
  49. package/dist/status-Q6Z4TFJZ.js +52 -0
  50. package/dist/status-Q6Z4TFJZ.js.map +7 -0
  51. package/dist/types.d.ts +69 -0
  52. package/dist/types.d.ts.map +1 -0
  53. package/dist/types.js +13 -0
  54. package/dist/types.js.map +1 -0
  55. package/package.json +52 -0
@@ -0,0 +1,512 @@
1
+ import {
2
+ EXIT,
3
+ countTokens,
4
+ loadConfig,
5
+ readHookInput,
6
+ writeHookOutput
7
+ } from "./chunk-3CILH2TO.js";
8
+
9
+ // src/types.ts
10
+ var DEFAULT_CONFIG = {
11
+ maxTokens: 2e3,
12
+ previewTokens: 500,
13
+ scratchDir: ".claude/scratch",
14
+ maxAgeMinutes: 60,
15
+ maxScratchSizeMB: 50,
16
+ socketPath: "/tmp/mcp-guard.sock"
17
+ };
18
+
19
+ // src/commands/post.ts
20
+ import { createHash } from "crypto";
21
+ import { existsSync, mkdirSync, readdirSync, statSync, unlinkSync, writeFileSync } from "fs";
22
+ import { join } from "path";
23
+ function loadGuardConfig() {
24
+ const searchPaths = [
25
+ join(process.cwd(), ".claude", "guard.json"),
26
+ join(process.cwd(), "guard.json")
27
+ ];
28
+ return loadConfig("guard.json", DEFAULT_CONFIG, searchPaths);
29
+ }
30
+ function cleanupScratch(scratchDir, maxAgeMinutes, maxSizeMB) {
31
+ try {
32
+ if (!existsSync(scratchDir)) {
33
+ return;
34
+ }
35
+ const files = readdirSync(scratchDir);
36
+ const now = Date.now();
37
+ const maxAge = maxAgeMinutes * 60 * 1e3;
38
+ const maxBytes = maxSizeMB * 1024 * 1024;
39
+ const fileInfos = [];
40
+ let totalSize = 0;
41
+ for (const file of files) {
42
+ if (!file.startsWith("out_")) {
43
+ continue;
44
+ }
45
+ const filepath = join(scratchDir, file);
46
+ try {
47
+ const stat = statSync(filepath);
48
+ fileInfos.push({
49
+ path: filepath,
50
+ size: stat.size,
51
+ mtime: stat.mtimeMs
52
+ });
53
+ totalSize += stat.size;
54
+ } catch {
55
+ }
56
+ }
57
+ for (const info of fileInfos) {
58
+ if (now - info.mtime > maxAge) {
59
+ try {
60
+ unlinkSync(info.path);
61
+ totalSize -= info.size;
62
+ } catch {
63
+ }
64
+ }
65
+ }
66
+ if (totalSize > maxBytes) {
67
+ const remaining = fileInfos.filter((f) => existsSync(f.path)).sort((a, b) => a.mtime - b.mtime);
68
+ for (const info of remaining) {
69
+ if (totalSize <= maxBytes) {
70
+ break;
71
+ }
72
+ try {
73
+ unlinkSync(info.path);
74
+ totalSize -= info.size;
75
+ } catch {
76
+ }
77
+ }
78
+ }
79
+ } catch {
80
+ }
81
+ }
82
+ function hashOutput(content) {
83
+ return createHash("md5").update(content).digest("hex").slice(0, 8);
84
+ }
85
+ function offloadOutput(output, exitCode, config) {
86
+ const tokens = countTokens(output);
87
+ const lines = output.split("\n");
88
+ if (tokens <= config.maxTokens) {
89
+ return { modified: false, result: output };
90
+ }
91
+ const scratchDir = join(process.cwd(), config.scratchDir);
92
+ mkdirSync(scratchDir, { recursive: true });
93
+ cleanupScratch(scratchDir, config.maxAgeMinutes, config.maxScratchSizeMB);
94
+ const hash = hashOutput(output);
95
+ const filename = `out_${hash}_exit${exitCode}.txt`;
96
+ const filepath = join(scratchDir, filename);
97
+ writeFileSync(filepath, output);
98
+ const charsPerToken = 4;
99
+ const targetChars = config.previewTokens * charsPerToken;
100
+ const previewLines = [];
101
+ let charCount = 0;
102
+ for (let i = lines.length - 1; i >= 0 && charCount < targetChars; i--) {
103
+ previewLines.unshift(lines[i]);
104
+ charCount += lines[i].length + 1;
105
+ }
106
+ const preview = previewLines.join("\n");
107
+ const sizeKB = (output.length / 1024).toFixed(1);
108
+ const result = [
109
+ `\u250C\u2500 Output offloaded (${lines.length} lines, ${sizeKB}KB, ~${tokens} tokens)`,
110
+ `\u2502 File: ${filepath}`,
111
+ `\u2502 Hint: grep <pattern> ${filepath}`,
112
+ `\u2514\u2500 Last ${previewLines.length} lines:`,
113
+ preview
114
+ ].join("\n");
115
+ return { modified: true, result };
116
+ }
117
+ function runPost() {
118
+ try {
119
+ const data = readHookInput();
120
+ if (data.tool_name !== "Bash") {
121
+ writeHookOutput(data);
122
+ return;
123
+ }
124
+ const stdout = data.tool_result?.stdout || "";
125
+ const stderr = data.tool_result?.stderr || "";
126
+ const exitCode = data.tool_result?.exit_code || 0;
127
+ const config = loadGuardConfig();
128
+ const stdoutResult = offloadOutput(stdout, exitCode, config);
129
+ const stderrResult = offloadOutput(stderr, exitCode, config);
130
+ if (!stdoutResult.modified && !stderrResult.modified) {
131
+ writeHookOutput(data);
132
+ return;
133
+ }
134
+ const result = {
135
+ ...data,
136
+ tool_result: {
137
+ ...data.tool_result,
138
+ stdout: stdoutResult.result,
139
+ stderr: stderrResult.modified ? stderrResult.result : stderr
140
+ }
141
+ };
142
+ writeHookOutput(result);
143
+ } catch (error) {
144
+ console.error("sherpa post error:", error.message);
145
+ try {
146
+ const data = readHookInput();
147
+ writeHookOutput(data);
148
+ } catch {
149
+ }
150
+ }
151
+ }
152
+
153
+ // src/parser.ts
154
+ import { homedir } from "os";
155
+ var regexCache = /* @__PURE__ */ new Map();
156
+ function getRegex(pattern) {
157
+ let regex = regexCache.get(pattern);
158
+ if (!regex) {
159
+ regex = new RegExp(pattern);
160
+ regexCache.set(pattern, regex);
161
+ }
162
+ return regex;
163
+ }
164
+ function normalizePath(inputPath) {
165
+ if (!inputPath) {
166
+ return {
167
+ original: inputPath,
168
+ normalized: inputPath,
169
+ hasTraversal: false,
170
+ isAbsolute: false
171
+ };
172
+ }
173
+ const original = inputPath;
174
+ let normalized = inputPath.replace(/^~/, homedir());
175
+ const isAbsolute = original.startsWith("/") || original.startsWith("~");
176
+ const segments = normalized.split("/");
177
+ const resolved = [];
178
+ for (const seg of segments) {
179
+ if (seg === "..") {
180
+ resolved.pop();
181
+ } else if (seg !== "." && seg !== "") {
182
+ resolved.push(seg);
183
+ }
184
+ }
185
+ normalized = isAbsolute ? `/${resolved.join("/")}` : resolved.join("/");
186
+ return {
187
+ original,
188
+ normalized,
189
+ hasTraversal: original.includes(".."),
190
+ isAbsolute
191
+ };
192
+ }
193
+ function isPathWithinAllowed(pathInfo, allowedPattern) {
194
+ const regex = getRegex(allowedPattern);
195
+ if (pathInfo.hasTraversal) {
196
+ return regex.test(pathInfo.normalized);
197
+ }
198
+ return regex.test(pathInfo.original);
199
+ }
200
+ function extractCommands(node) {
201
+ if (!node) {
202
+ return [];
203
+ }
204
+ const commands = [];
205
+ switch (node.type) {
206
+ case "Script":
207
+ case "Pipeline":
208
+ for (const cmd of node.commands || []) {
209
+ commands.push(...extractCommands(cmd));
210
+ }
211
+ break;
212
+ case "LogicalExpression":
213
+ if (node.left) {
214
+ commands.push(...extractCommands(node.left));
215
+ }
216
+ if (node.right) {
217
+ commands.push(...extractCommands(node.right));
218
+ }
219
+ break;
220
+ case "Command": {
221
+ const parsed = parseCommand(node);
222
+ if (parsed) {
223
+ commands.push(parsed);
224
+ }
225
+ break;
226
+ }
227
+ case "Subshell":
228
+ case "CompoundList":
229
+ for (const cmd of node.list || []) {
230
+ commands.push(...extractCommands(cmd));
231
+ }
232
+ break;
233
+ }
234
+ return commands;
235
+ }
236
+ function parseCommand(node) {
237
+ if (!node.name?.text) {
238
+ return null;
239
+ }
240
+ const cmdName = node.name.text;
241
+ const info = {
242
+ cmd: cmdName,
243
+ args: [],
244
+ flags: [],
245
+ paths: [],
246
+ raw: []
247
+ };
248
+ for (const part of node.suffix || []) {
249
+ const text = part.text;
250
+ if (!text) {
251
+ continue;
252
+ }
253
+ info.raw.push(text);
254
+ if (text.startsWith("--")) {
255
+ const flag = text.slice(2).split("=")[0];
256
+ info.flags.push(flag);
257
+ } else if (text.startsWith("-") && text.length > 1 && !/^-\d+\.?\d*$/.test(text)) {
258
+ const flags = text.slice(1);
259
+ for (const f of flags) {
260
+ info.flags.push(f);
261
+ }
262
+ } else {
263
+ info.args.push(text);
264
+ if (/^[/~$.]/.test(text)) {
265
+ info.paths.push(text);
266
+ }
267
+ }
268
+ }
269
+ if (cmdName === "git" && info.args.length > 0) {
270
+ info.subcommand = info.args[0];
271
+ info.subArgs = info.args.slice(1);
272
+ }
273
+ return info;
274
+ }
275
+
276
+ // src/rules.ts
277
+ var regexCache2 = /* @__PURE__ */ new Map();
278
+ function getRegex2(pattern) {
279
+ let regex = regexCache2.get(pattern);
280
+ if (!regex) {
281
+ regex = new RegExp(pattern);
282
+ regexCache2.set(pattern, regex);
283
+ }
284
+ return regex;
285
+ }
286
+ function matchesBlockRule(cmdInfo, rule) {
287
+ if (rule.cmd) {
288
+ const cmds = Array.isArray(rule.cmd) ? rule.cmd : [rule.cmd];
289
+ if (!cmds.includes(cmdInfo.cmd)) {
290
+ return false;
291
+ }
292
+ }
293
+ if (rule.subcommand && cmdInfo.subcommand !== rule.subcommand) {
294
+ return false;
295
+ }
296
+ if (rule.flags) {
297
+ const ruleFlags = Array.isArray(rule.flags) ? rule.flags : [rule.flags];
298
+ const mode = rule.flagMode || "all";
299
+ if (mode === "any") {
300
+ const hasAnyFlag = ruleFlags.some((f) => cmdInfo.flags.includes(f));
301
+ if (!hasAnyFlag) {
302
+ return false;
303
+ }
304
+ } else {
305
+ const hasAllFlags = ruleFlags.every((f) => cmdInfo.flags.includes(f));
306
+ if (!hasAllFlags) {
307
+ return false;
308
+ }
309
+ }
310
+ }
311
+ if (rule.pathPatterns) {
312
+ const patterns = Array.isArray(rule.pathPatterns) ? rule.pathPatterns : [rule.pathPatterns];
313
+ const pathsToCheck = cmdInfo.paths.length > 0 ? cmdInfo.paths : cmdInfo.args;
314
+ if (pathsToCheck.length === 0) {
315
+ return false;
316
+ }
317
+ const matchesPath = pathsToCheck.some((p) => {
318
+ const pathInfo = normalizePath(p);
319
+ return patterns.some((pattern) => {
320
+ const regex = getRegex2(pattern);
321
+ return regex.test(pathInfo.original) || regex.test(pathInfo.normalized);
322
+ });
323
+ });
324
+ if (!matchesPath) {
325
+ return false;
326
+ }
327
+ }
328
+ if (rule.argPatterns) {
329
+ const patterns = Array.isArray(rule.argPatterns) ? rule.argPatterns : [rule.argPatterns];
330
+ const rawStr = cmdInfo.raw.join(" ");
331
+ const matchesArg = patterns.some(
332
+ (pattern) => getRegex2(pattern).test(rawStr)
333
+ );
334
+ if (!matchesArg) {
335
+ return false;
336
+ }
337
+ }
338
+ return true;
339
+ }
340
+ function matchesAllowRule(cmdInfo, rule) {
341
+ if (rule.cmd) {
342
+ const cmds = Array.isArray(rule.cmd) ? rule.cmd : [rule.cmd];
343
+ if (!cmds.includes(cmdInfo.cmd)) {
344
+ return false;
345
+ }
346
+ }
347
+ if (rule.pathPatterns) {
348
+ const patterns = Array.isArray(rule.pathPatterns) ? rule.pathPatterns : [rule.pathPatterns];
349
+ const pathsToCheck = cmdInfo.paths.length > 0 ? cmdInfo.paths : cmdInfo.args;
350
+ if (pathsToCheck.length === 0) {
351
+ return false;
352
+ }
353
+ const matchesPath = pathsToCheck.some((p) => {
354
+ const pathInfo = normalizePath(p);
355
+ return patterns.some((pattern) => isPathWithinAllowed(pathInfo, pattern));
356
+ });
357
+ if (!matchesPath) {
358
+ return false;
359
+ }
360
+ }
361
+ return true;
362
+ }
363
+ function checkPipeline(ast, rules2) {
364
+ if (!ast || ast.type !== "Script") {
365
+ return null;
366
+ }
367
+ for (const cmd of ast.commands || []) {
368
+ if (cmd.type !== "Pipeline") {
369
+ continue;
370
+ }
371
+ const pipeCommands = (cmd.commands || []).map((c) => c.name?.text).filter((t) => Boolean(t));
372
+ for (const rule of rules2.block || []) {
373
+ if (!rule.pipeTo) {
374
+ continue;
375
+ }
376
+ const cmds = Array.isArray(rule.cmd) ? rule.cmd : [rule.cmd];
377
+ const pipes = Array.isArray(rule.pipeTo) ? rule.pipeTo : [rule.pipeTo];
378
+ for (let i = 0; i < pipeCommands.length; i++) {
379
+ if (cmds.includes(pipeCommands[i])) {
380
+ for (let j = i + 1; j < pipeCommands.length; j++) {
381
+ if (pipes.includes(pipeCommands[j])) {
382
+ return rule;
383
+ }
384
+ }
385
+ }
386
+ }
387
+ }
388
+ }
389
+ return null;
390
+ }
391
+ function checkCommand(cmdInfo, rules2) {
392
+ for (const rule of rules2.allow || []) {
393
+ if (matchesAllowRule(cmdInfo, rule)) {
394
+ return { blocked: false, reason: `Allowed: ${rule.name}` };
395
+ }
396
+ }
397
+ for (const rule of rules2.block || []) {
398
+ if (rule.pipeTo) {
399
+ continue;
400
+ }
401
+ if (matchesBlockRule(cmdInfo, rule)) {
402
+ return { blocked: true, rule };
403
+ }
404
+ }
405
+ return { blocked: false };
406
+ }
407
+
408
+ // src/commands/pre.ts
409
+ import parse from "bash-parser";
410
+ import { readFileSync } from "fs";
411
+ import { dirname, join as join2 } from "path";
412
+ import { fileURLToPath } from "url";
413
+ var __dirname = dirname(fileURLToPath(import.meta.url));
414
+ var rulesPath = join2(__dirname, "..", "..", "rules.json");
415
+ var rules = JSON.parse(readFileSync(rulesPath, "utf-8"));
416
+ var SAFE_COMMAND_PREFIXES = [
417
+ "echo ",
418
+ "echo ",
419
+ "printf ",
420
+ "ls ",
421
+ "ls ",
422
+ "ls\n",
423
+ "ls",
424
+ "pwd",
425
+ "date",
426
+ "whoami",
427
+ "id",
428
+ "cat ",
429
+ "head ",
430
+ "tail ",
431
+ "wc ",
432
+ "grep ",
433
+ "awk ",
434
+ "sed ",
435
+ "cd ",
436
+ "cd ",
437
+ "true",
438
+ "false",
439
+ ":"
440
+ ];
441
+ function isFastPathSafe(command) {
442
+ const trimmed = command.trim();
443
+ for (const prefix of SAFE_COMMAND_PREFIXES) {
444
+ if (trimmed === prefix.trim() || trimmed.startsWith(prefix)) {
445
+ if (!trimmed.includes("|") && !trimmed.includes("&&") && !trimmed.includes("||") && !trimmed.includes(";") && !trimmed.includes("$(") && !trimmed.includes("`")) {
446
+ return true;
447
+ }
448
+ }
449
+ }
450
+ return false;
451
+ }
452
+ function checkBashCommand(command) {
453
+ if (isFastPathSafe(command)) {
454
+ return { blocked: false };
455
+ }
456
+ let ast;
457
+ try {
458
+ ast = parse(command);
459
+ } catch {
460
+ return { blocked: false };
461
+ }
462
+ const pipeRule = checkPipeline(ast, rules);
463
+ if (pipeRule) {
464
+ return { blocked: true, rule: { name: pipeRule.name, reason: pipeRule.reason } };
465
+ }
466
+ const commands = extractCommands(ast);
467
+ for (const cmdInfo of commands) {
468
+ const result = checkCommand(cmdInfo, rules);
469
+ if (result.blocked && result.rule) {
470
+ return { blocked: true, rule: { name: result.rule.name, reason: result.rule.reason } };
471
+ }
472
+ }
473
+ return { blocked: false };
474
+ }
475
+ function runPre() {
476
+ try {
477
+ const data = readHookInput();
478
+ const command = data.tool_input?.command;
479
+ if (!command) {
480
+ process.exit(EXIT.ALLOW);
481
+ }
482
+ const result = checkBashCommand(command);
483
+ if (result.blocked && result.rule) {
484
+ console.error("BLOCKED by sherpa");
485
+ console.error(` Rule: ${result.rule.name}`);
486
+ console.error(` Reason: ${result.rule.reason}`);
487
+ console.error(` Command: ${command}`);
488
+ process.exit(EXIT.BLOCK);
489
+ }
490
+ process.exit(EXIT.ALLOW);
491
+ } catch (error) {
492
+ console.error("sherpa pre error:", error.message);
493
+ process.exit(EXIT.ALLOW);
494
+ }
495
+ }
496
+
497
+ export {
498
+ DEFAULT_CONFIG,
499
+ offloadOutput,
500
+ runPost,
501
+ normalizePath,
502
+ isPathWithinAllowed,
503
+ extractCommands,
504
+ parseCommand,
505
+ matchesBlockRule,
506
+ matchesAllowRule,
507
+ checkPipeline,
508
+ checkCommand,
509
+ checkBashCommand,
510
+ runPre
511
+ };
512
+ //# sourceMappingURL=chunk-5NF3BSD6.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/types.ts", "../src/commands/post.ts", "../src/parser.ts", "../src/rules.ts", "../src/commands/pre.ts"],
4
+ "sourcesContent": ["/**\n * Type definitions for guard\n */\n\n/** Parsed command info extracted from AST */\nexport interface CommandInfo {\n\tcmd: string;\n\targs: string[];\n\tflags: string[];\n\tpaths: string[];\n\traw: string[];\n\tsubcommand?: string;\n\tsubArgs?: string[];\n}\n\n/** Normalized path info for traversal detection */\nexport interface PathInfo {\n\toriginal: string;\n\tnormalized: string;\n\thasTraversal: boolean;\n\tisAbsolute: boolean;\n}\n\n/** Rule definition for blocking/allowing commands */\nexport interface Rule {\n\tname: string;\n\tcmd?: string | string[];\n\tsubcommand?: string;\n\tflags?: string | string[];\n\tflagMode?: 'all' | 'any';\n\tpathPatterns?: string | string[];\n\targPatterns?: string | string[];\n\tpipeTo?: string | string[];\n\treason: string;\n}\n\n/** Rules configuration */\nexport interface RulesConfig {\n\tblock: Rule[];\n\tallow: Rule[];\n}\n\n/** Check result */\nexport interface CheckResult {\n\tblocked: boolean;\n\trule?: Rule;\n\treason?: string;\n}\n\n/** Bash parser AST node types */\nexport interface ASTNode {\n\ttype: string;\n\tcommands?: ASTNode[];\n\tleft?: ASTNode;\n\tright?: ASTNode;\n\tlist?: ASTNode[];\n\tname?: { text: string };\n\tsuffix?: Array<{ text: string }>;\n}\n\n/** Guard configuration */\nexport interface GuardConfig {\n\tmaxTokens: number;\n\tpreviewTokens: number;\n\tscratchDir: string;\n\tmaxAgeMinutes: number;\n\tmaxScratchSizeMB: number;\n\tsocketPath: string;\n}\n\n/** Default guard configuration */\nexport const DEFAULT_CONFIG: GuardConfig = {\n\tmaxTokens: 2000,\n\tpreviewTokens: 500,\n\tscratchDir: '.claude/scratch',\n\tmaxAgeMinutes: 60,\n\tmaxScratchSizeMB: 50,\n\tsocketPath: '/tmp/mcp-guard.sock'\n}\n", "/**\n * sherpa post - PostToolUse hook that offloads large outputs to scratch files\n */\n\nimport {\n\tcountTokens,\n\tloadConfig,\n\ttype PostToolOutput,\n\treadHookInput,\n\twriteHookOutput\n} from '@goobits/sherpa-core'\nimport { createHash } from 'crypto'\nimport { existsSync,mkdirSync, readdirSync, statSync, unlinkSync, writeFileSync } from 'fs'\nimport { join } from 'path'\n\nimport { DEFAULT_CONFIG, type GuardConfig } from '../types.js'\n\n/**\n * Load guard config from .claude/guard.json or fallback locations\n */\nexport function loadGuardConfig(): GuardConfig {\n\tconst searchPaths = [\n\t\tjoin(process.cwd(), '.claude', 'guard.json'),\n\t\tjoin(process.cwd(), 'guard.json')\n\t]\n\treturn loadConfig<GuardConfig>('guard.json', DEFAULT_CONFIG, searchPaths)\n}\n\ninterface FileInfo {\n\tpath: string;\n\tsize: number;\n\tmtime: number;\n}\n\n/**\n * Clean up scratch files by age and size\n */\nfunction cleanupScratch(scratchDir: string, maxAgeMinutes: number, maxSizeMB: number): void {\n\ttry {\n\t\tif (!existsSync(scratchDir)) {return}\n\n\t\tconst files = readdirSync(scratchDir)\n\t\tconst now = Date.now()\n\t\tconst maxAge = maxAgeMinutes * 60 * 1000\n\t\tconst maxBytes = maxSizeMB * 1024 * 1024\n\n\t\t// Collect file info\n\t\tconst fileInfos: FileInfo[] = []\n\t\tlet totalSize = 0\n\n\t\tfor (const file of files) {\n\t\t\tif (!file.startsWith('out_')) {continue}\n\n\t\t\tconst filepath = join(scratchDir, file)\n\t\t\ttry {\n\t\t\t\tconst stat = statSync(filepath)\n\t\t\t\tfileInfos.push({\n\t\t\t\t\tpath: filepath,\n\t\t\t\t\tsize: stat.size,\n\t\t\t\t\tmtime: stat.mtimeMs\n\t\t\t\t})\n\t\t\t\ttotalSize += stat.size\n\t\t\t} catch {\n\t\t\t\t// Ignore errors on individual files\n\t\t\t}\n\t\t}\n\n\t\t// Delete files older than maxAge\n\t\tfor (const info of fileInfos) {\n\t\t\tif (now - info.mtime > maxAge) {\n\t\t\t\ttry {\n\t\t\t\t\tunlinkSync(info.path)\n\t\t\t\t\ttotalSize -= info.size\n\t\t\t\t} catch {\n\t\t\t\t\t// Ignore\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// If still over size limit, delete oldest files (LRU)\n\t\tif (totalSize > maxBytes) {\n\t\t\tconst remaining = fileInfos\n\t\t\t\t.filter(f => existsSync(f.path))\n\t\t\t\t.sort((a, b) => a.mtime - b.mtime) // Oldest first\n\n\t\t\tfor (const info of remaining) {\n\t\t\t\tif (totalSize <= maxBytes) {break}\n\t\t\t\ttry {\n\t\t\t\t\tunlinkSync(info.path)\n\t\t\t\t\ttotalSize -= info.size\n\t\t\t\t} catch {\n\t\t\t\t\t// Ignore\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} catch {\n\t\t// Ignore if directory doesn't exist yet\n\t}\n}\n\n/**\n * Generate a short hash for the output\n */\nfunction hashOutput(content: string): string {\n\treturn createHash('md5').update(content).digest('hex').slice(0, 8)\n}\n\n/**\n * Offload large output to a scratch file\n */\nexport function offloadOutput(\n\toutput: string,\n\texitCode: number,\n\tconfig: GuardConfig\n): { modified: boolean; result: string } {\n\tconst tokens = countTokens(output)\n\tconst lines = output.split('\\n')\n\n\t// Small output: pass through\n\tif (tokens <= config.maxTokens) {\n\t\treturn { modified: false, result: output }\n\t}\n\n\t// Create scratch directory\n\tconst scratchDir = join(process.cwd(), config.scratchDir)\n\tmkdirSync(scratchDir, { recursive: true })\n\n\t// Clean up old files and enforce size limit\n\tcleanupScratch(scratchDir, config.maxAgeMinutes, config.maxScratchSizeMB)\n\n\t// Save to scratch file\n\tconst hash = hashOutput(output)\n\tconst filename = `out_${ hash }_exit${ exitCode }.txt`\n\tconst filepath = join(scratchDir, filename)\n\twriteFileSync(filepath, output)\n\n\t// Create preview (last N tokens worth of lines)\n\t// Estimate ~4 chars per token to avoid expensive per-line token counting\n\tconst charsPerToken = 4\n\tconst targetChars = config.previewTokens * charsPerToken\n\tconst previewLines: string[] = []\n\tlet charCount = 0\n\tfor (let i = lines.length - 1; i >= 0 && charCount < targetChars; i--) {\n\t\tpreviewLines.unshift(lines[i])\n\t\tcharCount += lines[i].length + 1 // +1 for newline\n\t}\n\tconst preview = previewLines.join('\\n')\n\n\t// Build pointer message\n\tconst sizeKB = (output.length / 1024).toFixed(1)\n\tconst result = [\n\t\t`\u250C\u2500 Output offloaded (${ lines.length } lines, ${ sizeKB }KB, ~${ tokens } tokens)`,\n\t\t`\u2502 File: ${ filepath }`,\n\t\t`\u2502 Hint: grep <pattern> ${ filepath }`,\n\t\t`\u2514\u2500 Last ${ previewLines.length } lines:`,\n\t\tpreview\n\t].join('\\n')\n\n\treturn { modified: true, result }\n}\n\n/**\n * Main entry point for PostToolUse hook\n */\nexport function runPost(): void {\n\ttry {\n\t\tconst data = readHookInput<PostToolOutput>()\n\n\t\t// Only handle Bash tool\n\t\tif (data.tool_name !== 'Bash') {\n\t\t\twriteHookOutput(data)\n\t\t\treturn\n\t\t}\n\n\t\tconst stdout = data.tool_result?.stdout || ''\n\t\tconst stderr = data.tool_result?.stderr || ''\n\t\tconst exitCode = data.tool_result?.exit_code || 0\n\n\t\t// Load config from .claude/guard.json or defaults\n\t\tconst config = loadGuardConfig()\n\n\t\t// Check stdout\n\t\tconst stdoutResult = offloadOutput(stdout, exitCode, config)\n\n\t\t// Check stderr (usually smaller, but handle anyway)\n\t\tconst stderrResult = offloadOutput(stderr, exitCode, config)\n\n\t\t// If nothing was modified, pass through\n\t\tif (!stdoutResult.modified && !stderrResult.modified) {\n\t\t\twriteHookOutput(data)\n\t\t\treturn\n\t\t}\n\n\t\t// Return modified result\n\t\tconst result = {\n\t\t\t...data,\n\t\t\ttool_result: {\n\t\t\t\t...data.tool_result,\n\t\t\t\tstdout: stdoutResult.result,\n\t\t\t\tstderr: stderrResult.modified ? stderrResult.result : stderr\n\t\t\t}\n\t\t}\n\n\t\twriteHookOutput(result)\n\t} catch(error) {\n\t\t// On error, try to pass through original\n\t\tconsole.error('sherpa post error:', (error as Error).message)\n\t\ttry {\n\t\t\tconst data = readHookInput<PostToolOutput>()\n\t\t\twriteHookOutput(data)\n\t\t} catch {\n\t\t\t// Nothing we can do\n\t\t}\n\t}\n}\n", "/**\n * AST parsing utilities for bash commands\n */\n\nimport { homedir } from 'os'\n\nimport type { ASTNode, CommandInfo, PathInfo } from './types.js'\n\n// Cache compiled RegExp patterns\nconst regexCache = new Map<string, RegExp>()\n\nfunction getRegex(pattern: string): RegExp {\n\tlet regex = regexCache.get(pattern)\n\tif (!regex) {\n\t\tregex = new RegExp(pattern)\n\t\tregexCache.set(pattern, regex)\n\t}\n\treturn regex\n}\n\n/**\n * Normalize a path to prevent traversal attacks\n */\nexport function normalizePath(inputPath: string): PathInfo {\n\tif (!inputPath) {\n\t\treturn {\n\t\t\toriginal: inputPath,\n\t\t\tnormalized: inputPath,\n\t\t\thasTraversal: false,\n\t\t\tisAbsolute: false\n\t\t}\n\t}\n\n\tconst original = inputPath\n\n\t// Handle home directory\n\tlet normalized = inputPath.replace(/^~/, homedir())\n\n\tconst isAbsolute = original.startsWith('/') || original.startsWith('~')\n\n\t// Split into segments and resolve\n\tconst segments = normalized.split('/')\n\tconst resolved: string[] = []\n\n\tfor (const seg of segments) {\n\t\tif (seg === '..') {\n\t\t\tresolved.pop()\n\t\t} else if (seg !== '.' && seg !== '') {\n\t\t\tresolved.push(seg)\n\t\t}\n\t}\n\n\t// Only prefix with / for absolute paths\n\tnormalized = isAbsolute ? `/${ resolved.join('/') }` : resolved.join('/')\n\n\treturn {\n\t\toriginal,\n\t\tnormalized,\n\t\thasTraversal: original.includes('..'),\n\t\tisAbsolute\n\t}\n}\n\n/**\n * Check if a normalized path matches an allowed pattern\n */\nexport function isPathWithinAllowed(\n\tpathInfo: PathInfo,\n\tallowedPattern: string\n): boolean {\n\tconst regex = getRegex(allowedPattern)\n\n\t// If there's traversal, check the NORMALIZED path, not original\n\tif (pathInfo.hasTraversal) {\n\t\treturn regex.test(pathInfo.normalized)\n\t}\n\n\treturn regex.test(pathInfo.original)\n}\n\n/**\n * Extract all commands from AST (handles pipelines, lists, etc.)\n */\nexport function extractCommands(node: ASTNode | null): CommandInfo[] {\n\tif (!node) {return []}\n\n\tconst commands: CommandInfo[] = []\n\n\tswitch (node.type) {\n\t\tcase 'Script':\n\t\tcase 'Pipeline':\n\t\t\tfor (const cmd of node.commands || []) {\n\t\t\t\tcommands.push(...extractCommands(cmd))\n\t\t\t}\n\t\t\tbreak\n\n\t\tcase 'LogicalExpression':\n\t\t\tif (node.left) {commands.push(...extractCommands(node.left))}\n\t\t\tif (node.right) {commands.push(...extractCommands(node.right))}\n\t\t\tbreak\n\n\t\tcase 'Command': {\n\t\t\tconst parsed = parseCommand(node)\n\t\t\tif (parsed) {commands.push(parsed)}\n\t\t\tbreak\n\t\t}\n\n\t\tcase 'Subshell':\n\t\tcase 'CompoundList':\n\t\t\tfor (const cmd of node.list || []) {\n\t\t\t\tcommands.push(...extractCommands(cmd))\n\t\t\t}\n\t\t\tbreak\n\t}\n\n\treturn commands\n}\n\n/**\n * Parse a Command node into structured info\n */\nexport function parseCommand(node: ASTNode): CommandInfo | null {\n\tif (!node.name?.text) {return null}\n\n\tconst cmdName = node.name.text\n\n\tconst info: CommandInfo = {\n\t\tcmd: cmdName,\n\t\targs: [],\n\t\tflags: [],\n\t\tpaths: [],\n\t\traw: []\n\t}\n\n\t// Parse suffix (arguments and flags)\n\tfor (const part of node.suffix || []) {\n\t\tconst text = part.text\n\t\tif (!text) {continue}\n\n\t\tinfo.raw.push(text)\n\n\t\tif (text.startsWith('--')) {\n\t\t\t// Long flag: --force, --recursive\n\t\t\tconst flag = text.slice(2).split('=')[0]\n\t\t\tinfo.flags.push(flag)\n\t\t} else if (\n\t\t\ttext.startsWith('-') &&\n\t\t\ttext.length > 1 &&\n\t\t\t!/^-\\d+\\.?\\d*$/.test(text)\n\t\t) {\n\t\t\t// Short flags: -rf, -f, -r (but not negative numbers like -1)\n\t\t\tconst flags = text.slice(1)\n\t\t\tfor (const f of flags) {\n\t\t\t\tinfo.flags.push(f)\n\t\t\t}\n\t\t} else {\n\t\t\t// Regular argument (could be path)\n\t\t\tinfo.args.push(text)\n\t\t\tif (/^[/~$.]/.test(text)) {\n\t\t\t\tinfo.paths.push(text)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Handle git subcommands\n\tif (cmdName === 'git' && info.args.length > 0) {\n\t\tinfo.subcommand = info.args[0]\n\t\tinfo.subArgs = info.args.slice(1)\n\t}\n\n\treturn info\n}\n", "/**\n * Rule matching engine for guard\n */\n\nimport { isPathWithinAllowed,normalizePath } from './parser.js'\nimport type { ASTNode, CheckResult, CommandInfo, Rule, RulesConfig } from './types.js'\n\n// Cache compiled RegExp patterns to avoid recompilation on every check\nconst regexCache = new Map<string, RegExp>()\n\nfunction getRegex(pattern: string): RegExp {\n\tlet regex = regexCache.get(pattern)\n\tif (!regex) {\n\t\tregex = new RegExp(pattern)\n\t\tregexCache.set(pattern, regex)\n\t}\n\treturn regex\n}\n\n/**\n * Check if command matches a block rule\n */\nexport function matchesBlockRule(cmdInfo: CommandInfo, rule: Rule): boolean {\n\t// Check command name\n\tif (rule.cmd) {\n\t\tconst cmds = Array.isArray(rule.cmd) ? rule.cmd : [ rule.cmd ]\n\t\tif (!cmds.includes(cmdInfo.cmd)) {return false}\n\t}\n\n\t// Check subcommand (for git, etc.)\n\tif (rule.subcommand && cmdInfo.subcommand !== rule.subcommand) {\n\t\treturn false\n\t}\n\n\t// Check flags\n\tif (rule.flags) {\n\t\tconst ruleFlags = Array.isArray(rule.flags) ? rule.flags : [ rule.flags ]\n\t\tconst mode = rule.flagMode || 'all'\n\n\t\tif (mode === 'any') {\n\t\t\tconst hasAnyFlag = ruleFlags.some(f => cmdInfo.flags.includes(f))\n\t\t\tif (!hasAnyFlag) {return false}\n\t\t} else {\n\t\t\tconst hasAllFlags = ruleFlags.every(f => cmdInfo.flags.includes(f))\n\t\t\tif (!hasAllFlags) {return false}\n\t\t}\n\t}\n\n\t// Check path patterns (using normalized paths to catch traversal)\n\tif (rule.pathPatterns) {\n\t\tconst patterns = Array.isArray(rule.pathPatterns)\n\t\t\t? rule.pathPatterns\n\t\t\t: [ rule.pathPatterns ]\n\t\tconst pathsToCheck =\n\t\t\tcmdInfo.paths.length > 0 ? cmdInfo.paths : cmdInfo.args\n\t\tif (pathsToCheck.length === 0) {return false}\n\n\t\tconst matchesPath = pathsToCheck.some(p => {\n\t\t\tconst pathInfo = normalizePath(p)\n\t\t\t// Check both original AND normalized path for block rules\n\t\t\treturn patterns.some(pattern => {\n\t\t\t\tconst regex = getRegex(pattern)\n\t\t\t\treturn regex.test(pathInfo.original) || regex.test(pathInfo.normalized)\n\t\t\t})\n\t\t})\n\t\tif (!matchesPath) {return false}\n\t}\n\n\t// Check arg patterns\n\tif (rule.argPatterns) {\n\t\tconst patterns = Array.isArray(rule.argPatterns)\n\t\t\t? rule.argPatterns\n\t\t\t: [ rule.argPatterns ]\n\t\tconst rawStr = cmdInfo.raw.join(' ')\n\t\tconst matchesArg = patterns.some(pattern =>\n\t\t\tgetRegex(pattern).test(rawStr)\n\t\t)\n\t\tif (!matchesArg) {return false}\n\t}\n\n\treturn true\n}\n\n/**\n * Check if command matches an allow rule\n */\nexport function matchesAllowRule(cmdInfo: CommandInfo, rule: Rule): boolean {\n\tif (rule.cmd) {\n\t\tconst cmds = Array.isArray(rule.cmd) ? rule.cmd : [ rule.cmd ]\n\t\tif (!cmds.includes(cmdInfo.cmd)) {return false}\n\t}\n\n\tif (rule.pathPatterns) {\n\t\tconst patterns = Array.isArray(rule.pathPatterns)\n\t\t\t? rule.pathPatterns\n\t\t\t: [ rule.pathPatterns ]\n\t\tconst pathsToCheck =\n\t\t\tcmdInfo.paths.length > 0 ? cmdInfo.paths : cmdInfo.args\n\t\tif (pathsToCheck.length === 0) {return false}\n\n\t\tconst matchesPath = pathsToCheck.some(p => {\n\t\t\tconst pathInfo = normalizePath(p)\n\t\t\treturn patterns.some(pattern => isPathWithinAllowed(pathInfo, pattern))\n\t\t})\n\n\t\tif (!matchesPath) {return false}\n\t}\n\n\treturn true\n}\n\n/**\n * Check pipeline for dangerous pipe patterns (curl | bash)\n */\nexport function checkPipeline(ast: ASTNode, rules: RulesConfig): Rule | null {\n\tif (!ast || ast.type !== 'Script') {return null}\n\n\tfor (const cmd of ast.commands || []) {\n\t\tif (cmd.type !== 'Pipeline') {continue}\n\n\t\tconst pipeCommands = (cmd.commands || [])\n\t\t\t.map(c => c.name?.text)\n\t\t\t.filter((t): t is string => Boolean(t))\n\n\t\tfor (const rule of rules.block || []) {\n\t\t\tif (!rule.pipeTo) {continue}\n\n\t\t\tconst cmds = Array.isArray(rule.cmd) ? rule.cmd : [ rule.cmd ]\n\t\t\tconst pipes = Array.isArray(rule.pipeTo) ? rule.pipeTo : [ rule.pipeTo ]\n\n\t\t\t// Find if source command exists in pipeline\n\t\t\tfor (let i = 0; i < pipeCommands.length; i++) {\n\t\t\t\tif (cmds.includes(pipeCommands[i])) {\n\t\t\t\t\t// Check if ANY subsequent command is a dangerous target\n\t\t\t\t\tfor (let j = i + 1; j < pipeCommands.length; j++) {\n\t\t\t\t\t\tif (pipes.includes(pipeCommands[j])) {\n\t\t\t\t\t\t\treturn rule\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn null\n}\n\n/**\n * Check a command against all rules\n */\nexport function checkCommand(\n\tcmdInfo: CommandInfo,\n\trules: RulesConfig\n): CheckResult {\n\t// First check allow rules\n\tfor (const rule of rules.allow || []) {\n\t\tif (matchesAllowRule(cmdInfo, rule)) {\n\t\t\treturn { blocked: false, reason: `Allowed: ${ rule.name }` }\n\t\t}\n\t}\n\n\t// Then check block rules\n\tfor (const rule of rules.block || []) {\n\t\tif (rule.pipeTo) {continue} // Handled separately\n\t\tif (matchesBlockRule(cmdInfo, rule)) {\n\t\t\treturn { blocked: true, rule }\n\t\t}\n\t}\n\n\treturn { blocked: false }\n}\n", "/**\n * sherpa pre - PreToolUse hook that blocks dangerous bash commands\n */\n\nimport {\n\tEXIT,\n\ttype PreToolInput,\n\treadHookInput\n} from '@goobits/sherpa-core'\n// @ts-expect-error - bash-parser has no types\nimport parse from 'bash-parser'\nimport { readFileSync } from 'fs'\nimport { dirname, join } from 'path'\nimport { fileURLToPath } from 'url'\n\nimport { extractCommands } from '../parser.js'\nimport { checkCommand, checkPipeline } from '../rules.js'\nimport type { ASTNode, RulesConfig } from '../types.js'\n\n// Load rules from JSON config\nconst __dirname = dirname(fileURLToPath(import.meta.url))\nconst rulesPath = join(__dirname, '..', '..', 'rules.json')\nconst rules: RulesConfig = JSON.parse(readFileSync(rulesPath, 'utf-8'))\n\n// Fast-path: commands that are always safe (skip expensive parse)\nconst SAFE_COMMAND_PREFIXES = [\n\t'echo ', 'echo\\t', 'printf ',\n\t'ls ', 'ls\\t', 'ls\\n', 'ls',\n\t'pwd', 'date', 'whoami', 'id',\n\t'cat ', 'head ', 'tail ', 'wc ',\n\t'grep ', 'awk ', 'sed ',\n\t'cd ', 'cd\\t',\n\t'true', 'false', ':'\n]\n\nfunction isFastPathSafe(command: string): boolean {\n\tconst trimmed = command.trim()\n\t// Check if command starts with a known-safe prefix\n\tfor (const prefix of SAFE_COMMAND_PREFIXES) {\n\t\tif (trimmed === prefix.trim() || trimmed.startsWith(prefix)) {\n\t\t\t// Quick check: no compound commands, pipes, or dangerous chars\n\t\t\tif (\n\t\t\t\t!trimmed.includes('|') &&\n\t\t\t\t!trimmed.includes('&&') &&\n\t\t\t\t!trimmed.includes('||') &&\n\t\t\t\t!trimmed.includes(';') &&\n\t\t\t\t!trimmed.includes('$(') &&\n\t\t\t\t!trimmed.includes('`')\n\t\t\t) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\n/**\n * Run pre-guard check on a command\n */\nexport function checkBashCommand(command: string): { blocked: boolean; rule?: { name: string; reason: string } } {\n\t// Fast path: skip parsing for known-safe commands\n\tif (isFastPathSafe(command)) {\n\t\treturn { blocked: false }\n\t}\n\n\t// Parse command into AST\n\tlet ast: ASTNode\n\ttry {\n\t\tast = parse(command)\n\t} catch {\n\t\t// If parsing fails, allow (fail open)\n\t\treturn { blocked: false }\n\t}\n\n\t// Check pipeline patterns first (curl | bash)\n\tconst pipeRule = checkPipeline(ast, rules)\n\tif (pipeRule) {\n\t\treturn { blocked: true, rule: { name: pipeRule.name, reason: pipeRule.reason } }\n\t}\n\n\t// Extract and check each command\n\tconst commands = extractCommands(ast)\n\n\tfor (const cmdInfo of commands) {\n\t\tconst result = checkCommand(cmdInfo, rules)\n\t\tif (result.blocked && result.rule) {\n\t\t\treturn { blocked: true, rule: { name: result.rule.name, reason: result.rule.reason } }\n\t\t}\n\t}\n\n\treturn { blocked: false }\n}\n\n/**\n * Main entry point for PreToolUse hook\n */\nexport function runPre(): void {\n\ttry {\n\t\tconst data = readHookInput<PreToolInput>()\n\t\tconst command = data.tool_input?.command\n\n\t\tif (!command) {\n\t\t\tprocess.exit(EXIT.ALLOW)\n\t\t}\n\n\t\tconst result = checkBashCommand(command)\n\n\t\tif (result.blocked && result.rule) {\n\t\t\tconsole.error('BLOCKED by sherpa')\n\t\t\tconsole.error(` Rule: ${ result.rule.name }`)\n\t\t\tconsole.error(` Reason: ${ result.rule.reason }`)\n\t\t\tconsole.error(` Command: ${ command }`)\n\t\t\tprocess.exit(EXIT.BLOCK)\n\t\t}\n\n\t\tprocess.exit(EXIT.ALLOW)\n\t} catch(error) {\n\t\t// Graceful degradation: allow on error\n\t\tconsole.error('sherpa pre error:', (error as Error).message)\n\t\tprocess.exit(EXIT.ALLOW)\n\t}\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["regexCache", "getRegex", "rules", "join", "join"]
7
+ }