sigild 0.0.1 → 0.0.3
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.
- package/README.md +142 -33
- package/THREAT_MODEL.md +47 -19
- package/dist/src/bin/sigil-hook-post.d.ts +3 -0
- package/dist/src/bin/sigil-hook-post.d.ts.map +1 -0
- package/dist/src/bin/sigil-hook-post.js +15 -0
- package/dist/src/bin/sigil-hook-post.js.map +1 -0
- package/dist/src/bin/sigil-hook-pre.d.ts +3 -0
- package/dist/src/bin/sigil-hook-pre.d.ts.map +1 -0
- package/dist/src/bin/sigil-hook-pre.js +18 -0
- package/dist/src/bin/sigil-hook-pre.js.map +1 -0
- package/dist/src/bin/sigil-mcp.d.ts +3 -0
- package/dist/src/bin/sigil-mcp.d.ts.map +1 -0
- package/dist/src/bin/sigil-mcp.js +90 -0
- package/dist/src/bin/sigil-mcp.js.map +1 -0
- package/dist/src/bin/sigil.d.ts +3 -0
- package/dist/src/bin/sigil.d.ts.map +1 -0
- package/dist/src/bin/sigil.js +9 -0
- package/dist/src/bin/sigil.js.map +1 -0
- package/dist/src/cli/args.d.ts +26 -0
- package/dist/src/cli/args.d.ts.map +1 -0
- package/dist/src/cli/args.js +36 -0
- package/dist/src/cli/args.js.map +1 -0
- package/dist/src/cli/index.d.ts +7 -0
- package/dist/src/cli/index.d.ts.map +1 -0
- package/dist/src/cli/index.js +7 -0
- package/dist/src/cli/index.js.map +1 -0
- package/dist/src/cli/main.d.ts +26 -0
- package/dist/src/cli/main.d.ts.map +1 -0
- package/dist/src/cli/main.js +197 -0
- package/dist/src/cli/main.js.map +1 -0
- package/dist/src/cli/paths.d.ts +18 -0
- package/dist/src/cli/paths.d.ts.map +1 -0
- package/dist/src/cli/paths.js +13 -0
- package/dist/src/cli/paths.js.map +1 -0
- package/dist/src/cli/portal.d.ts +59 -0
- package/dist/src/cli/portal.d.ts.map +1 -0
- package/dist/src/cli/portal.js +112 -0
- package/dist/src/cli/portal.js.map +1 -0
- package/dist/src/cli/status.d.ts +28 -0
- package/dist/src/cli/status.d.ts.map +1 -0
- package/dist/src/cli/status.js +59 -0
- package/dist/src/cli/status.js.map +1 -0
- package/dist/src/cli/unlock.d.ts +36 -0
- package/dist/src/cli/unlock.d.ts.map +1 -0
- package/dist/src/cli/unlock.js +77 -0
- package/dist/src/cli/unlock.js.map +1 -0
- package/dist/src/control/client.d.ts +26 -0
- package/dist/src/control/client.d.ts.map +1 -0
- package/dist/src/control/client.js +76 -0
- package/dist/src/control/client.js.map +1 -0
- package/dist/src/control/index.d.ts +4 -0
- package/dist/src/control/index.d.ts.map +1 -0
- package/dist/src/control/index.js +4 -0
- package/dist/src/control/index.js.map +1 -0
- package/dist/src/control/protocol.d.ts +54 -0
- package/dist/src/control/protocol.d.ts.map +1 -0
- package/dist/src/control/protocol.js +60 -0
- package/dist/src/control/protocol.js.map +1 -0
- package/dist/src/control/server.d.ts +52 -0
- package/dist/src/control/server.d.ts.map +1 -0
- package/dist/src/control/server.js +199 -0
- package/dist/src/control/server.js.map +1 -0
- package/dist/src/daemon/handles.d.ts +35 -6
- package/dist/src/daemon/handles.d.ts.map +1 -1
- package/dist/src/daemon/handles.js +83 -28
- package/dist/src/daemon/handles.js.map +1 -1
- package/dist/src/daemon/index.d.ts +2 -3
- package/dist/src/daemon/index.d.ts.map +1 -1
- package/dist/src/daemon/index.js +2 -3
- package/dist/src/daemon/index.js.map +1 -1
- package/dist/src/daemon/methods.d.ts +13 -0
- package/dist/src/daemon/methods.d.ts.map +1 -1
- package/dist/src/daemon/methods.js +50 -1
- package/dist/src/daemon/methods.js.map +1 -1
- package/dist/src/hooks/command-scanner.d.ts +5 -0
- package/dist/src/hooks/command-scanner.d.ts.map +1 -0
- package/dist/src/hooks/command-scanner.js +117 -0
- package/dist/src/hooks/command-scanner.js.map +1 -0
- package/dist/src/hooks/glob.d.ts +8 -0
- package/dist/src/hooks/glob.d.ts.map +1 -0
- package/dist/src/hooks/glob.js +98 -0
- package/dist/src/hooks/glob.js.map +1 -0
- package/dist/src/hooks/index.d.ts +9 -0
- package/dist/src/hooks/index.d.ts.map +1 -0
- package/dist/src/hooks/index.js +9 -0
- package/dist/src/hooks/index.js.map +1 -0
- package/dist/src/hooks/install.d.ts +29 -0
- package/dist/src/hooks/install.d.ts.map +1 -0
- package/dist/src/hooks/install.js +86 -0
- package/dist/src/hooks/install.js.map +1 -0
- package/dist/src/hooks/path-blocker.d.ts +29 -0
- package/dist/src/hooks/path-blocker.d.ts.map +1 -0
- package/dist/src/hooks/path-blocker.js +59 -0
- package/dist/src/hooks/path-blocker.js.map +1 -0
- package/dist/src/hooks/post-tool-use.d.ts +13 -0
- package/dist/src/hooks/post-tool-use.d.ts.map +1 -0
- package/dist/src/hooks/post-tool-use.js +45 -0
- package/dist/src/hooks/post-tool-use.js.map +1 -0
- package/dist/src/hooks/pre-tool-use.d.ts +8 -0
- package/dist/src/hooks/pre-tool-use.d.ts.map +1 -0
- package/dist/src/hooks/pre-tool-use.js +38 -0
- package/dist/src/hooks/pre-tool-use.js.map +1 -0
- package/dist/src/hooks/protocol.d.ts +41 -0
- package/dist/src/hooks/protocol.d.ts.map +1 -0
- package/dist/src/hooks/protocol.js +27 -0
- package/dist/src/hooks/protocol.js.map +1 -0
- package/dist/src/hooks/redactor.d.ts +19 -0
- package/dist/src/hooks/redactor.d.ts.map +1 -0
- package/dist/src/hooks/redactor.js +71 -0
- package/dist/src/hooks/redactor.js.map +1 -0
- package/dist/src/mcp/index.d.ts +4 -0
- package/dist/src/mcp/index.d.ts.map +1 -0
- package/dist/src/mcp/index.js +4 -0
- package/dist/src/mcp/index.js.map +1 -0
- package/dist/src/mcp/protocol.d.ts +98 -0
- package/dist/src/mcp/protocol.d.ts.map +1 -0
- package/dist/src/mcp/protocol.js +79 -0
- package/dist/src/mcp/protocol.js.map +1 -0
- package/dist/src/mcp/server.d.ts +46 -0
- package/dist/src/mcp/server.d.ts.map +1 -0
- package/dist/src/mcp/server.js +108 -0
- package/dist/src/mcp/server.js.map +1 -0
- package/dist/src/mcp/tools.d.ts +16 -0
- package/dist/src/mcp/tools.d.ts.map +1 -0
- package/dist/src/mcp/tools.js +117 -0
- package/dist/src/mcp/tools.js.map +1 -0
- package/dist/src/policy/evaluate.d.ts +13 -0
- package/dist/src/policy/evaluate.d.ts.map +1 -0
- package/dist/src/policy/evaluate.js +73 -0
- package/dist/src/policy/evaluate.js.map +1 -0
- package/dist/src/policy/index.d.ts +5 -0
- package/dist/src/policy/index.d.ts.map +1 -0
- package/dist/src/policy/index.js +5 -0
- package/dist/src/policy/index.js.map +1 -0
- package/dist/src/policy/loader.d.ts +33 -0
- package/dist/src/policy/loader.d.ts.map +1 -0
- package/dist/src/policy/loader.js +170 -0
- package/dist/src/policy/loader.js.map +1 -0
- package/dist/src/policy/template.d.ts +10 -0
- package/dist/src/policy/template.d.ts.map +1 -0
- package/dist/src/policy/template.js +69 -0
- package/dist/src/policy/template.js.map +1 -0
- package/dist/src/policy/types.d.ts +62 -0
- package/dist/src/policy/types.d.ts.map +1 -0
- package/dist/src/policy/types.js +10 -0
- package/dist/src/policy/types.js.map +1 -0
- package/package.json +9 -3
- package/dist/src/bin/sigild.d.ts +0 -3
- package/dist/src/bin/sigild.d.ts.map +0 -1
- package/dist/src/bin/sigild.js +0 -30
- package/dist/src/bin/sigild.js.map +0 -1
- package/dist/src/daemon/rpc.d.ts +0 -61
- package/dist/src/daemon/rpc.d.ts.map +0 -1
- package/dist/src/daemon/rpc.js +0 -76
- package/dist/src/daemon/rpc.js.map +0 -1
- package/dist/src/daemon/runtime.d.ts +0 -40
- package/dist/src/daemon/runtime.d.ts.map +0 -1
- package/dist/src/daemon/runtime.js +0 -61
- package/dist/src/daemon/runtime.js.map +0 -1
- package/dist/src/daemon/server.d.ts +0 -53
- package/dist/src/daemon/server.d.ts.map +0 -1
- package/dist/src/daemon/server.js +0 -103
- package/dist/src/daemon/server.js.map +0 -1
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// Tiny POSIX-style glob → regex translator. Just enough to support our
|
|
2
|
+
// blocklist patterns (~/.sigil/everything, *.pem, .env at any depth, etc.).
|
|
3
|
+
//
|
|
4
|
+
// Supported syntax:
|
|
5
|
+
// double-star (globstar): match anything including path separators
|
|
6
|
+
// single-star : match anything except path separators
|
|
7
|
+
// ? : match one char except separator
|
|
8
|
+
// [abc], [a-z] : char class (passed through to RegExp)
|
|
9
|
+
// everything else : literal
|
|
10
|
+
//
|
|
11
|
+
// `~` is expanded to the user's home directory before matching.
|
|
12
|
+
// Matches are case-sensitive; paths are normalised by collapsing repeated
|
|
13
|
+
// slashes and resolving `.` and `..` segments lexically.
|
|
14
|
+
import { homedir } from 'node:os';
|
|
15
|
+
export function expandTilde(path) {
|
|
16
|
+
if (path.startsWith('~/'))
|
|
17
|
+
return homedir() + path.slice(1);
|
|
18
|
+
if (path === '~')
|
|
19
|
+
return homedir();
|
|
20
|
+
return path;
|
|
21
|
+
}
|
|
22
|
+
function lexicalNormalize(path) {
|
|
23
|
+
// Collapse repeated slashes, resolve "." and ".." purely syntactically.
|
|
24
|
+
// Leading "/" preserved.
|
|
25
|
+
const isAbs = path.startsWith('/');
|
|
26
|
+
const parts = [];
|
|
27
|
+
for (const seg of path.split('/')) {
|
|
28
|
+
if (seg === '' || seg === '.')
|
|
29
|
+
continue;
|
|
30
|
+
if (seg === '..') {
|
|
31
|
+
if (parts.length > 0 && parts[parts.length - 1] !== '..')
|
|
32
|
+
parts.pop();
|
|
33
|
+
else if (!isAbs)
|
|
34
|
+
parts.push('..');
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
parts.push(seg);
|
|
38
|
+
}
|
|
39
|
+
return (isAbs ? '/' : '') + parts.join('/');
|
|
40
|
+
}
|
|
41
|
+
export function normalizePath(path) {
|
|
42
|
+
return lexicalNormalize(expandTilde(path));
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Convert a glob pattern to a RegExp. Anchored to the full string.
|
|
46
|
+
*/
|
|
47
|
+
export function globToRegex(glob) {
|
|
48
|
+
const expanded = expandTilde(glob);
|
|
49
|
+
let re = '^';
|
|
50
|
+
let i = 0;
|
|
51
|
+
while (i < expanded.length) {
|
|
52
|
+
const c = expanded[i];
|
|
53
|
+
if (c === '*') {
|
|
54
|
+
if (expanded[i + 1] === '*') {
|
|
55
|
+
// ** — match anything including slashes; consume optional trailing /
|
|
56
|
+
re += '.*';
|
|
57
|
+
i += 2;
|
|
58
|
+
if (expanded[i] === '/')
|
|
59
|
+
i++; // swallow `/` after ** so `a/**/b` matches `a/b`
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
re += '[^/]*';
|
|
63
|
+
i++;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
else if (c === '?') {
|
|
67
|
+
re += '[^/]';
|
|
68
|
+
i++;
|
|
69
|
+
}
|
|
70
|
+
else if (c === '[') {
|
|
71
|
+
// Pass character class through as-is; find matching ]
|
|
72
|
+
const end = expanded.indexOf(']', i + 1);
|
|
73
|
+
if (end === -1) {
|
|
74
|
+
// No close — treat literally
|
|
75
|
+
re += '\\[';
|
|
76
|
+
i++;
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
re += expanded.slice(i, end + 1);
|
|
80
|
+
i = end + 1;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
else if ('.+^$|()\\{}'.includes(c)) {
|
|
84
|
+
re += '\\' + c;
|
|
85
|
+
i++;
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
re += c;
|
|
89
|
+
i++;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
re += '$';
|
|
93
|
+
return new RegExp(re);
|
|
94
|
+
}
|
|
95
|
+
export function globMatch(path, pattern) {
|
|
96
|
+
return globToRegex(pattern).test(normalizePath(path));
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=glob.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"glob.js","sourceRoot":"","sources":["../../../src/hooks/glob.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { type BlockerOpts, type BlockDecision as PathBlockDecision, DEFAULT_PATH_PATTERNS, isBlockedPath, } from './path-blocker.js';
|
|
2
|
+
export { scanBashCommand } from './command-scanner.js';
|
|
3
|
+
export { type RedactionStat, type RedactionResult, redact, } from './redactor.js';
|
|
4
|
+
export { type HookEnvelope, type BlockDecision, type PostToolModification, readHookEnvelope, } from './protocol.js';
|
|
5
|
+
export { decidePreToolUse } from './pre-tool-use.js';
|
|
6
|
+
export { decidePostToolUse, walkAndRedact } from './post-tool-use.js';
|
|
7
|
+
export { type InitOpts, type InitScope, type InitResult, installInto, settingsPath, } from './install.js';
|
|
8
|
+
export { expandTilde, globMatch, globToRegex, normalizePath } from './glob.js';
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,aAAa,IAAI,iBAAiB,EACvC,qBAAqB,EACrB,aAAa,GACd,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,MAAM,GACP,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,oBAAoB,EACzB,gBAAgB,GACjB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACtE,OAAO,EACL,KAAK,QAAQ,EACb,KAAK,SAAS,EACd,KAAK,UAAU,EACf,WAAW,EACX,YAAY,GACb,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { DEFAULT_PATH_PATTERNS, isBlockedPath, } from './path-blocker.js';
|
|
2
|
+
export { scanBashCommand } from './command-scanner.js';
|
|
3
|
+
export { redact, } from './redactor.js';
|
|
4
|
+
export { readHookEnvelope, } from './protocol.js';
|
|
5
|
+
export { decidePreToolUse } from './pre-tool-use.js';
|
|
6
|
+
export { decidePostToolUse, walkAndRedact } from './post-tool-use.js';
|
|
7
|
+
export { installInto, settingsPath, } from './install.js';
|
|
8
|
+
export { expandTilde, globMatch, globToRegex, normalizePath } from './glob.js';
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,qBAAqB,EACrB,aAAa,GACd,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAGL,MAAM,GACP,MAAM,eAAe,CAAC;AACvB,OAAO,EAIL,gBAAgB,GACjB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACtE,OAAO,EAIL,WAAW,EACX,YAAY,GACb,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `sigil init` writes the hook config + MCP server registration into
|
|
3
|
+
* .claude/settings.json. Idempotent: re-running merges with whatever is
|
|
4
|
+
* there, preserving the user's unrelated settings.
|
|
5
|
+
*/
|
|
6
|
+
export type InitScope = 'project' | 'user';
|
|
7
|
+
export interface InitOpts {
|
|
8
|
+
scope: InitScope;
|
|
9
|
+
/** Root for project-scoped install (where .claude/ goes). Defaults to CWD. */
|
|
10
|
+
projectRoot?: string;
|
|
11
|
+
/** Home dir for user-scoped install. Defaults to os.homedir(). */
|
|
12
|
+
homeDir?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Path to the sigil-mcp binary to register. Defaults to looking up
|
|
15
|
+
* "sigil-mcp" on $PATH (which works if sigil was installed globally).
|
|
16
|
+
*/
|
|
17
|
+
mcpCommand?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface InitResult {
|
|
20
|
+
settingsPath: string;
|
|
21
|
+
changed: boolean;
|
|
22
|
+
}
|
|
23
|
+
export declare function settingsPath(opts: InitOpts): string;
|
|
24
|
+
/**
|
|
25
|
+
* Idempotently install sigil's MCP server registration + hook config.
|
|
26
|
+
* Existing settings (other MCP servers, other hook handlers) are preserved.
|
|
27
|
+
*/
|
|
28
|
+
export declare function installInto(opts: InitOpts): InitResult;
|
|
29
|
+
//# sourceMappingURL=install.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../../src/hooks/install.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AAEH,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,MAAM,CAAC;AAE3C,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,SAAS,CAAC;IACjB,8EAA8E;IAC9E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kEAAkE;IAClE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,UAAU;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;CAClB;AAiCD,wBAAgB,YAAY,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAOnD;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,QAAQ,GAAG,UAAU,CAoDtD"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join, resolve } from 'node:path';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
const SIGIL_MCP_NAME = 'sigil';
|
|
5
|
+
const SIGIL_HOOK_MARKER = 'sigil-hook-';
|
|
6
|
+
export function settingsPath(opts) {
|
|
7
|
+
if (opts.scope === 'user') {
|
|
8
|
+
const home = opts.homeDir ?? homedir();
|
|
9
|
+
return join(home, '.claude', 'settings.json');
|
|
10
|
+
}
|
|
11
|
+
const root = opts.projectRoot ? resolve(opts.projectRoot) : process.cwd();
|
|
12
|
+
return join(root, '.claude', 'settings.json');
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Idempotently install sigil's MCP server registration + hook config.
|
|
16
|
+
* Existing settings (other MCP servers, other hook handlers) are preserved.
|
|
17
|
+
*/
|
|
18
|
+
export function installInto(opts) {
|
|
19
|
+
const path = settingsPath(opts);
|
|
20
|
+
const before = readSettings(path);
|
|
21
|
+
const after = { ...before };
|
|
22
|
+
const mcpCommand = opts.mcpCommand ?? 'sigil-mcp';
|
|
23
|
+
// 1. MCP server registration — overwrite our entry; leave others alone.
|
|
24
|
+
const existingServers = after.mcpServers ?? {};
|
|
25
|
+
after.mcpServers = {
|
|
26
|
+
...existingServers,
|
|
27
|
+
[SIGIL_MCP_NAME]: { command: mcpCommand },
|
|
28
|
+
};
|
|
29
|
+
// 2. Hooks — remove any prior sigil entries (identified by the
|
|
30
|
+
// sigil-hook- prefix in the command path) then re-add fresh ones.
|
|
31
|
+
const stripSigil = (matchers) => {
|
|
32
|
+
if (!matchers)
|
|
33
|
+
return [];
|
|
34
|
+
return matchers
|
|
35
|
+
.map((m) => ({
|
|
36
|
+
...m,
|
|
37
|
+
hooks: m.hooks.filter((h) => !h.command.includes(SIGIL_HOOK_MARKER)),
|
|
38
|
+
}))
|
|
39
|
+
.filter((m) => m.hooks.length > 0);
|
|
40
|
+
};
|
|
41
|
+
const hooks = { ...(after.hooks ?? {}) };
|
|
42
|
+
hooks.PreToolUse = [
|
|
43
|
+
...stripSigil(hooks.PreToolUse),
|
|
44
|
+
{
|
|
45
|
+
matcher: 'Read|Bash',
|
|
46
|
+
hooks: [{ type: 'command', command: 'sigil-hook-pre' }],
|
|
47
|
+
},
|
|
48
|
+
];
|
|
49
|
+
hooks.PostToolUse = [
|
|
50
|
+
...stripSigil(hooks.PostToolUse),
|
|
51
|
+
{
|
|
52
|
+
matcher: '.*',
|
|
53
|
+
hooks: [{ type: 'command', command: 'sigil-hook-post' }],
|
|
54
|
+
},
|
|
55
|
+
];
|
|
56
|
+
after.hooks = hooks;
|
|
57
|
+
// Bail early if no change.
|
|
58
|
+
const beforeJson = JSON.stringify(before);
|
|
59
|
+
const afterJson = JSON.stringify(after);
|
|
60
|
+
if (beforeJson === afterJson && existsSync(path)) {
|
|
61
|
+
return { settingsPath: path, changed: false };
|
|
62
|
+
}
|
|
63
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
64
|
+
writeFileSync(path, JSON.stringify(after, null, 2) + '\n', { mode: 0o644 });
|
|
65
|
+
return { settingsPath: path, changed: true };
|
|
66
|
+
}
|
|
67
|
+
function readSettings(path) {
|
|
68
|
+
if (!existsSync(path))
|
|
69
|
+
return {};
|
|
70
|
+
try {
|
|
71
|
+
statSync(path);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return {};
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
const text = readFileSync(path, 'utf8');
|
|
78
|
+
if (text.trim() === '')
|
|
79
|
+
return {};
|
|
80
|
+
return JSON.parse(text);
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
throw new Error(`could not parse ${path}: ${err.message}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=install.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install.js","sourceRoot":"","sources":["../../../src/hooks/install.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in patterns we always block. These are not user-configurable; they
|
|
3
|
+
* represent paths where a key would conventionally live and that the agent
|
|
4
|
+
* should never read.
|
|
5
|
+
*/
|
|
6
|
+
export declare const DEFAULT_PATH_PATTERNS: readonly string[];
|
|
7
|
+
export interface BlockDecision {
|
|
8
|
+
blocked: boolean;
|
|
9
|
+
/** The pattern that matched, useful for surfacing in the error to the agent. */
|
|
10
|
+
matchedPattern?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface BlockerOpts {
|
|
13
|
+
/** Extra glob patterns to block, in addition to the built-ins. */
|
|
14
|
+
extraPatterns?: readonly string[];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Decide whether to block a path read. Both the resolved absolute path AND
|
|
18
|
+
* the raw user-supplied path are checked, because attackers (or confused
|
|
19
|
+
* agents) may pass paths with `..` or symlinks that resolve to a blocked
|
|
20
|
+
* location while looking innocent.
|
|
21
|
+
*
|
|
22
|
+
* Note: this does NOT resolve symlinks itself. Symlink resolution is best
|
|
23
|
+
* left to the caller (who has filesystem access). For sigil's hook context,
|
|
24
|
+
* the agent already has filesystem access and would do the symlink read
|
|
25
|
+
* before our hook fires; the meaningful protection is preventing direct
|
|
26
|
+
* reads through obvious paths.
|
|
27
|
+
*/
|
|
28
|
+
export declare function isBlockedPath(path: string, opts?: BlockerOpts): BlockDecision;
|
|
29
|
+
//# sourceMappingURL=path-blocker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path-blocker.d.ts","sourceRoot":"","sources":["../../../src/hooks/path-blocker.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,EAAE,SAAS,MAAM,EAuBjD,CAAC;AAEH,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,gFAAgF;IAChF,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,WAAW;IAC1B,kEAAkE;IAClE,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACnC;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,WAAgB,GAAG,aAAa,CAejF"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { basename } from 'node:path';
|
|
2
|
+
import { globMatch, normalizePath } from './glob.js';
|
|
3
|
+
/**
|
|
4
|
+
* Built-in patterns we always block. These are not user-configurable; they
|
|
5
|
+
* represent paths where a key would conventionally live and that the agent
|
|
6
|
+
* should never read.
|
|
7
|
+
*/
|
|
8
|
+
export const DEFAULT_PATH_PATTERNS = Object.freeze([
|
|
9
|
+
// sigil's own state
|
|
10
|
+
'**/.sigil/**',
|
|
11
|
+
// common key file extensions
|
|
12
|
+
'**/*.pem',
|
|
13
|
+
'**/*.key',
|
|
14
|
+
'**/*.keystore',
|
|
15
|
+
'**/*.jks',
|
|
16
|
+
'**/*.p12',
|
|
17
|
+
// SSH private key conventions
|
|
18
|
+
'**/.ssh/id_*',
|
|
19
|
+
'**/.ssh/*_rsa',
|
|
20
|
+
'**/.ssh/*_ed25519',
|
|
21
|
+
'**/.ssh/*_ecdsa',
|
|
22
|
+
// env files (no extension, dotted prefix variants like .env.local)
|
|
23
|
+
'**/.env',
|
|
24
|
+
'**/.env.*',
|
|
25
|
+
// ethereum/geth keystore directories
|
|
26
|
+
'**/keystore',
|
|
27
|
+
'**/keystore/**',
|
|
28
|
+
// GPG / pass(1) storage
|
|
29
|
+
'**/.gnupg/**',
|
|
30
|
+
'**/.password-store/**',
|
|
31
|
+
]);
|
|
32
|
+
/**
|
|
33
|
+
* Decide whether to block a path read. Both the resolved absolute path AND
|
|
34
|
+
* the raw user-supplied path are checked, because attackers (or confused
|
|
35
|
+
* agents) may pass paths with `..` or symlinks that resolve to a blocked
|
|
36
|
+
* location while looking innocent.
|
|
37
|
+
*
|
|
38
|
+
* Note: this does NOT resolve symlinks itself. Symlink resolution is best
|
|
39
|
+
* left to the caller (who has filesystem access). For sigil's hook context,
|
|
40
|
+
* the agent already has filesystem access and would do the symlink read
|
|
41
|
+
* before our hook fires; the meaningful protection is preventing direct
|
|
42
|
+
* reads through obvious paths.
|
|
43
|
+
*/
|
|
44
|
+
export function isBlockedPath(path, opts = {}) {
|
|
45
|
+
const patterns = [...DEFAULT_PATH_PATTERNS, ...(opts.extraPatterns ?? [])];
|
|
46
|
+
const normalized = normalizePath(path);
|
|
47
|
+
const base = basename(normalized);
|
|
48
|
+
for (const pattern of patterns) {
|
|
49
|
+
if (globMatch(normalized, pattern)) {
|
|
50
|
+
return { blocked: true, matchedPattern: pattern };
|
|
51
|
+
}
|
|
52
|
+
// Also test the basename so a pattern like `**/.env` catches relative `.env`.
|
|
53
|
+
if (globMatch(base, pattern.replace(/^\*\*\//, ''))) {
|
|
54
|
+
return { blocked: true, matchedPattern: pattern };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return { blocked: false };
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=path-blocker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path-blocker.js","sourceRoot":"","sources":["../../../src/hooks/path-blocker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAErD;;;;GAIG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAsB,MAAM,CAAC,MAAM,CAAC;IACpE,oBAAoB;IACpB,cAAc;IACd,6BAA6B;IAC7B,UAAU;IACV,UAAU;IACV,eAAe;IACf,UAAU;IACV,UAAU;IACV,8BAA8B;IAC9B,cAAc;IACd,eAAe;IACf,mBAAmB;IACnB,iBAAiB;IACjB,mEAAmE;IACnE,SAAS;IACT,WAAW;IACX,qCAAqC;IACrC,aAAa;IACb,gBAAgB;IAChB,wBAAwB;IACxB,cAAc;IACd,uBAAuB;CACxB,CAAC,CAAC;AAaH;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,aAAa,CAAC,IAAY,EAAE,OAAoB,EAAE;IAChE,MAAM,QAAQ,GAAG,CAAC,GAAG,qBAAqB,EAAE,GAAG,CAAC,IAAI,CAAC,aAAa,IAAI,EAAE,CAAC,CAAC,CAAC;IAC3E,MAAM,UAAU,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;IACvC,MAAM,IAAI,GAAG,QAAQ,CAAC,UAAU,CAAC,CAAC;IAElC,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,IAAI,SAAS,CAAC,UAAU,EAAE,OAAO,CAAC,EAAE,CAAC;YACnC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,CAAC;QACpD,CAAC;QACD,8EAA8E;QAC9E,IAAI,SAAS,CAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC;YACpD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,CAAC;QACpD,CAAC;IACH,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AAC5B,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { HookEnvelope, PostToolModification } from './protocol.js';
|
|
2
|
+
/**
|
|
3
|
+
* Walk the tool_response field of a PostToolUse envelope, redacting every
|
|
4
|
+
* string we find. Returns the modification envelope to emit, or null if
|
|
5
|
+
* nothing changed.
|
|
6
|
+
*/
|
|
7
|
+
export declare function decidePostToolUse(env: HookEnvelope): PostToolModification | null;
|
|
8
|
+
/**
|
|
9
|
+
* Recursively descend into objects, arrays, and strings, applying redact()
|
|
10
|
+
* to every string we encounter. Accumulates redaction counts.
|
|
11
|
+
*/
|
|
12
|
+
export declare function walkAndRedact(value: unknown, totals: Map<string, number>): unknown;
|
|
13
|
+
//# sourceMappingURL=post-tool-use.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"post-tool-use.d.ts","sourceRoot":"","sources":["../../../src/hooks/post-tool-use.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAExE;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,YAAY,GAAG,oBAAoB,GAAG,IAAI,CAchF;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAiBlF"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { redact } from './redactor.js';
|
|
2
|
+
/**
|
|
3
|
+
* Walk the tool_response field of a PostToolUse envelope, redacting every
|
|
4
|
+
* string we find. Returns the modification envelope to emit, or null if
|
|
5
|
+
* nothing changed.
|
|
6
|
+
*/
|
|
7
|
+
export function decidePostToolUse(env) {
|
|
8
|
+
const response = env.tool_response;
|
|
9
|
+
if (response === undefined)
|
|
10
|
+
return null;
|
|
11
|
+
const totals = new Map();
|
|
12
|
+
const redacted = walkAndRedact(response, totals);
|
|
13
|
+
if (totals.size === 0)
|
|
14
|
+
return null;
|
|
15
|
+
return {
|
|
16
|
+
hookSpecificOutput: {
|
|
17
|
+
hookEventName: 'PostToolUse',
|
|
18
|
+
updatedToolResponse: redacted,
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Recursively descend into objects, arrays, and strings, applying redact()
|
|
24
|
+
* to every string we encounter. Accumulates redaction counts.
|
|
25
|
+
*/
|
|
26
|
+
export function walkAndRedact(value, totals) {
|
|
27
|
+
if (typeof value === 'string') {
|
|
28
|
+
const r = redact(value);
|
|
29
|
+
for (const s of r.redactions)
|
|
30
|
+
totals.set(s.reason, (totals.get(s.reason) ?? 0) + s.count);
|
|
31
|
+
return r.text;
|
|
32
|
+
}
|
|
33
|
+
if (Array.isArray(value)) {
|
|
34
|
+
return value.map((v) => walkAndRedact(v, totals));
|
|
35
|
+
}
|
|
36
|
+
if (value !== null && typeof value === 'object') {
|
|
37
|
+
const out = {};
|
|
38
|
+
for (const [k, v] of Object.entries(value)) {
|
|
39
|
+
out[k] = walkAndRedact(v, totals);
|
|
40
|
+
}
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
return value;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=post-tool-use.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"post-tool-use.js","sourceRoot":"","sources":["../../../src/hooks/post-tool-use.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAwB,MAAM,eAAe,CAAC;AAG7D;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAiB;IACjD,MAAM,QAAQ,GAAG,GAAG,CAAC,aAAa,CAAC;IACnC,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IAExC,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IACzC,MAAM,QAAQ,GAAG,aAAa,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACjD,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAEnC,OAAO;QACL,kBAAkB,EAAE;YAClB,aAAa,EAAE,aAAa;YAC5B,mBAAmB,EAAE,QAA4C;SAClE;KACF,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,KAAc,EAAE,MAA2B;IACvE,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,MAAM,CAAC,GAAoB,MAAM,CAAC,KAAK,CAAC,CAAC;QACzC,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,UAAU;YAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;QAC1F,OAAO,CAAC,CAAC,IAAI,CAAC;IAChB,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;IACpD,CAAC;IACD,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAChD,MAAM,GAAG,GAA4B,EAAE,CAAC;QACxC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAgC,CAAC,EAAE,CAAC;YACtE,GAAG,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;QACpC,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type BlockerOpts } from './path-blocker.js';
|
|
2
|
+
import type { BlockDecision, HookEnvelope } from './protocol.js';
|
|
3
|
+
/**
|
|
4
|
+
* Decide whether a PreToolUse event should be blocked. Pure function; the
|
|
5
|
+
* bin entrypoint wraps this with stdin/stdout handling.
|
|
6
|
+
*/
|
|
7
|
+
export declare function decidePreToolUse(env: HookEnvelope, opts?: BlockerOpts): BlockDecision | null;
|
|
8
|
+
//# sourceMappingURL=pre-tool-use.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pre-tool-use.d.ts","sourceRoot":"","sources":["../../../src/hooks/pre-tool-use.ts"],"names":[],"mappings":"AACA,OAAO,EAAiB,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACpE,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAEjE;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,YAAY,EAAE,IAAI,GAAE,WAAgB,GAAG,aAAa,GAAG,IAAI,CA+BhG"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { scanBashCommand } from './command-scanner.js';
|
|
2
|
+
import { isBlockedPath } from './path-blocker.js';
|
|
3
|
+
/**
|
|
4
|
+
* Decide whether a PreToolUse event should be blocked. Pure function; the
|
|
5
|
+
* bin entrypoint wraps this with stdin/stdout handling.
|
|
6
|
+
*/
|
|
7
|
+
export function decidePreToolUse(env, opts = {}) {
|
|
8
|
+
const toolName = env.tool_name;
|
|
9
|
+
const input = env.tool_input ?? {};
|
|
10
|
+
if (toolName === 'Read') {
|
|
11
|
+
const path = typeof input['file_path'] === 'string' ? input['file_path'] : undefined;
|
|
12
|
+
if (path === undefined)
|
|
13
|
+
return null;
|
|
14
|
+
const d = isBlockedPath(path, opts);
|
|
15
|
+
if (d.blocked) {
|
|
16
|
+
return {
|
|
17
|
+
decision: 'block',
|
|
18
|
+
reason: `sigil ward: ${path} matches blocked pattern ${d.matchedPattern}. This path is on sigil's wardlist (~/.sigil/wards.toml + built-in defaults). If you need to read a file like this, edit the wardlist.`,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
if (toolName === 'Bash') {
|
|
24
|
+
const cmd = typeof input['command'] === 'string' ? input['command'] : undefined;
|
|
25
|
+
if (cmd === undefined)
|
|
26
|
+
return null;
|
|
27
|
+
const d = scanBashCommand(cmd, opts);
|
|
28
|
+
if (d.blocked) {
|
|
29
|
+
return {
|
|
30
|
+
decision: 'block',
|
|
31
|
+
reason: `sigil ward: bash command blocked — ${d.reason ?? 'matched blocked pattern'}.`,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
return null; // not a tool we ward
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=pre-tool-use.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pre-tool-use.js","sourceRoot":"","sources":["../../../src/hooks/pre-tool-use.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAoB,MAAM,mBAAmB,CAAC;AAGpE;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAAiB,EAAE,OAAoB,EAAE;IACxE,MAAM,QAAQ,GAAG,GAAG,CAAC,SAAS,CAAC;IAC/B,MAAM,KAAK,GAAG,GAAG,CAAC,UAAU,IAAI,EAAE,CAAC;IAEnC,IAAI,QAAQ,KAAK,MAAM,EAAE,CAAC;QACxB,MAAM,IAAI,GAAG,OAAO,KAAK,CAAC,WAAW,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACrF,IAAI,IAAI,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC;QACpC,MAAM,CAAC,GAAG,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QACpC,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;YACd,OAAO;gBACL,QAAQ,EAAE,OAAO;gBACjB,MAAM,EAAE,eAAe,IAAI,4BAA4B,CAAC,CAAC,cAAc,wIAAwI;aAChN,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,QAAQ,KAAK,MAAM,EAAE,CAAC;QACxB,MAAM,GAAG,GAAG,OAAO,KAAK,CAAC,SAAS,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAChF,IAAI,GAAG,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC;QACnC,MAAM,CAAC,GAAG,eAAe,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QACrC,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;YACd,OAAO;gBACL,QAAQ,EAAE,OAAO;gBACjB,MAAM,EAAE,sCAAsC,CAAC,CAAC,MAAM,IAAI,yBAAyB,GAAG;aACvF,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,IAAI,CAAC,CAAC,qBAAqB;AACpC,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code hook I/O protocol. Hooks are spawned as subprocesses; they
|
|
3
|
+
* receive a JSON envelope on stdin and may write a JSON decision on stdout.
|
|
4
|
+
*
|
|
5
|
+
* Reference: https://docs.claude.com/en/docs/claude-code/hooks (subject to
|
|
6
|
+
* change; the fields below are what sigil's hooks read and write).
|
|
7
|
+
*/
|
|
8
|
+
export interface HookEnvelope {
|
|
9
|
+
session_id?: string;
|
|
10
|
+
transcript_path?: string;
|
|
11
|
+
hook_event_name?: string;
|
|
12
|
+
tool_name?: string;
|
|
13
|
+
tool_input?: Record<string, unknown>;
|
|
14
|
+
tool_response?: Record<string, unknown> | string;
|
|
15
|
+
[key: string]: unknown;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Decision emitted by a PreToolUse hook. Returning `{ decision: "block" }`
|
|
19
|
+
* stops the tool call and surfaces `reason` to the model.
|
|
20
|
+
*/
|
|
21
|
+
export interface BlockDecision {
|
|
22
|
+
decision: 'block';
|
|
23
|
+
reason: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Output from a PostToolUse hook that wants to modify the tool's response
|
|
27
|
+
* (e.g., redact secrets). The exact shape Claude Code accepts is in flux;
|
|
28
|
+
* we emit a structure that has been stable in recent versions.
|
|
29
|
+
*/
|
|
30
|
+
export interface PostToolModification {
|
|
31
|
+
hookSpecificOutput: {
|
|
32
|
+
hookEventName: 'PostToolUse';
|
|
33
|
+
updatedToolResponse: Record<string, unknown> | string;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Read the entire stdin stream and parse it as JSON. Returns an empty
|
|
38
|
+
* object if stdin closes without data.
|
|
39
|
+
*/
|
|
40
|
+
export declare function readHookEnvelope(stdin?: NodeJS.ReadableStream): Promise<HookEnvelope>;
|
|
41
|
+
//# sourceMappingURL=protocol.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../../src/hooks/protocol.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,YAAY;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC;IAEjD,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACnC,kBAAkB,EAAE;QAClB,aAAa,EAAE,aAAa,CAAC;QAC7B,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC;KACvD,CAAC;CACH;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,KAAK,GAAE,MAAM,CAAC,cAA8B,GAAG,OAAO,CAAC,YAAY,CAAC,CAY1G"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code hook I/O protocol. Hooks are spawned as subprocesses; they
|
|
3
|
+
* receive a JSON envelope on stdin and may write a JSON decision on stdout.
|
|
4
|
+
*
|
|
5
|
+
* Reference: https://docs.claude.com/en/docs/claude-code/hooks (subject to
|
|
6
|
+
* change; the fields below are what sigil's hooks read and write).
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Read the entire stdin stream and parse it as JSON. Returns an empty
|
|
10
|
+
* object if stdin closes without data.
|
|
11
|
+
*/
|
|
12
|
+
export async function readHookEnvelope(stdin = process.stdin) {
|
|
13
|
+
const chunks = [];
|
|
14
|
+
for await (const c of stdin) {
|
|
15
|
+
chunks.push(typeof c === 'string' ? Buffer.from(c, 'utf8') : c);
|
|
16
|
+
}
|
|
17
|
+
const text = Buffer.concat(chunks).toString('utf8').trim();
|
|
18
|
+
if (text.length === 0)
|
|
19
|
+
return {};
|
|
20
|
+
try {
|
|
21
|
+
return JSON.parse(text);
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
throw new Error(`hook stdin was not valid JSON: ${err.message}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=protocol.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.js","sourceRoot":"","sources":["../../../src/hooks/protocol.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAkCH;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,QAA+B,OAAO,CAAC,KAAK;IACjF,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,KAAK,EAAE,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QAC5B,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAW,CAAC,CAAC;IAC5E,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;IAC3D,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IACjC,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAiB,CAAC;IAC1C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,kCAAmC,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;IAC9E,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Output redactor — the second line of defence behind the path blocker.
|
|
3
|
+
* If a key somehow ends up in tool output (because the agent read a file
|
|
4
|
+
* the blocker missed, or coaxed a key out of an API), redact it before it
|
|
5
|
+
* reaches the model's context.
|
|
6
|
+
*
|
|
7
|
+
* The rules are deliberately strict — false positives are tolerable
|
|
8
|
+
* (redacted noise in tool output is annoying; an exfiltrated key is fatal).
|
|
9
|
+
*/
|
|
10
|
+
export interface RedactionStat {
|
|
11
|
+
reason: string;
|
|
12
|
+
count: number;
|
|
13
|
+
}
|
|
14
|
+
export interface RedactionResult {
|
|
15
|
+
text: string;
|
|
16
|
+
redactions: RedactionStat[];
|
|
17
|
+
}
|
|
18
|
+
export declare function redact(text: string): RedactionResult;
|
|
19
|
+
//# sourceMappingURL=redactor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redactor.d.ts","sourceRoot":"","sources":["../../../src/hooks/redactor.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,aAAa,EAAE,CAAC;CAC7B;AAuDD,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,CAYpD"}
|