@reneturcios/hookcheck 0.0.0-stage → 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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Rene Turcios
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,109 @@
1
- # Temporary Holding Version
1
+ # hookcheck
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ **Find the hooks that run before you do. One scan, every auto-exec surface, one verdict.**
4
+
5
+ Modern supply chain attacks do not need you to run anything. They persist through hooks that run automatically: an npm `postinstall` script, a Claude Code `SessionStart` hook in `.claude/settings.json`, a VS Code task set to run on folder open. The Shai-Hulud npm worm used exactly this pattern, planting a preinstall dropper and then persisting through agent and editor hooks so it survived cleanup and stole credentials on the next open.
6
+
7
+ hookcheck scans a local repo for every auto-exec hook, classifies each one, and prints a single verdict: CLEAN, REVIEW, or COMPROMISED-PATTERN, with the file, command, rules, and evidence for every finding. Deterministic only. No LLM, no network in the core scan.
8
+
9
+ ## Why this exists
10
+
11
+ Reports in October 2026 described AI agents orchestrating attacks and a self-propagating npm worm that backdoored hundreds of packages. Its persistence layer was not exotic malware. It was ordinary config files that every developer and every coding agent already trusts: package.json install scripts, `.claude/settings.json` hooks, and `.vscode/tasks.json` folderOpen tasks. Defenders needed a fast local check that an agent itself can call before opening, installing, or trusting a repo. That is hookcheck.
12
+
13
+ ## What it checks
14
+
15
+ | surface | what it looks for |
16
+ | --- | --- |
17
+ | `package.json` | preinstall, install, postinstall, prepare scripts |
18
+ | `.claude/settings.json` and `settings.local.json` | SessionStart, PreToolUse, and other hook commands |
19
+ | `.vscode/tasks.json` | tasks with runOn folderOpen, plus task inventory |
20
+ | `.husky/` | every hook file and its full command content |
21
+ | `lefthook.yml` | run commands |
22
+ | `.pre-commit-config.yaml` | entry commands and remote repo URLs |
23
+ | `.cursor/` and `.codex/` | config commands and files |
24
+ | MCP configs | `.mcp.json`, `mcp.json`, `.vscode/mcp.json`, `.cursor/mcp.json` servers that autostart |
25
+ | `.github/workflows/` | run steps, as inventory |
26
+ | `.githooks/` | hook files |
27
+
28
+ Classification per hook:
29
+
30
+ - **SAFE**: matches known safe dev patterns such as test, lint, build, typecheck, husky, or lint-staged, and matches no risky rule.
31
+ - **RISKY**: matches at least one risky rule: network fetch, curl piped to a shell, credential store paths such as ~/.ssh or ~/.aws, environment secret access, base64 decoding or eval style obfuscation, shell spawning, inline code execution, or a remote URL.
32
+ - **UNKNOWN**: matches neither safe nor risky patterns. A local script name alone is UNKNOWN until a human reads it. When a hook runs a local file such as `node setup.mjs`, hookcheck reads that file and escalates the finding if the file itself matches risky rules.
33
+
34
+ Verdict rules:
35
+
36
+ - **CLEAN**: every finding is SAFE, or no hooks were found. Exit code 0.
37
+ - **REVIEW**: at least one UNKNOWN finding, or a RISKY finding without a remote fetch or credential rule. Exit code 1.
38
+ - **COMPROMISED-PATTERN**: at least one RISKY finding that fetches remote code or touches credential stores. This is the Shai-Hulud persistence shape. Treat the repo as untrusted until proven otherwise. Exit code 1.
39
+
40
+ Every finding reports file, hook id, command text, classification, rule ids, and evidence. Malformed JSON is reported as a warning, never silently ignored.
41
+
42
+ ## Usage
43
+
44
+ ### CLI
45
+
46
+ ```bash
47
+ npx @reneturcios/hookcheck scan
48
+ npx @reneturcios/hookcheck scan ./some-repo
49
+ npx @reneturcios/hookcheck scan ./some-repo --json
50
+ npx @reneturcios/hookcheck explain ./some-repo package.json:postinstall
51
+ ```
52
+
53
+ Example output:
54
+
55
+ ```
56
+ hookcheck ./some-repo: COMPROMISED-PATTERN
57
+ 2 hook(s): 0 SAFE, 2 RISKY, 0 UNKNOWN
58
+ [RISKY] package.json:postinstall (package.json)
59
+ command: node setup.mjs
60
+ rules: NET-FETCH, ENV-ACCESS
61
+ evidence: referenced local file setup.mjs is RISKY: NET-FETCH: performs a network fetch
62
+ COMPROMISED-PATTERN: at least one risky hook fetches remote code or touches credential stores.
63
+ ```
64
+
65
+ ### MCP server
66
+
67
+ Add to your MCP client config:
68
+
69
+ ```json
70
+ {
71
+ "mcpServers": {
72
+ "hookcheck": {
73
+ "command": "npx",
74
+ "args": ["-y", "@reneturcios/hookcheck", "mcp"]
75
+ }
76
+ }
77
+ }
78
+ ```
79
+
80
+ Tools:
81
+
82
+ - `scan_hooks(path)`: full scan with verdict, findings, counts, and warnings.
83
+ - `explain_hook(path, hookId or file)`: one hook explained in plain language with its evidence.
84
+
85
+ ### Docker
86
+
87
+ ```bash
88
+ docker run -i ghcr.io/cloudcorprecords/hookcheck mcp
89
+ docker run --rm -v "$PWD:/repo" ghcr.io/cloudcorprecords/hookcheck scan /repo
90
+ ```
91
+
92
+ ## The toolkit
93
+
94
+ hookcheck is part of a family of small, single-purpose developer tools by the same author, all built the same way: one bin that serves a CLI and an MCP server, deterministic checks with evidence, no invented answers.
95
+
96
+ - [greenlight](https://github.com/CloudCorpRecords/greenlight): pre-flight checks for open-source contributions: is this issue actually landable?
97
+ - [shipcheck](https://github.com/CloudCorpRecords/shipcheck): verify a release actually shipped on every surface, or NO-GO.
98
+
99
+ ## Development
100
+
101
+ ```bash
102
+ npm install
103
+ npm run build
104
+ npm test
105
+ ```
106
+
107
+ ## License
108
+
109
+ MIT
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * hookcheck CLI.
4
+ * hookcheck scan [path] [--json]
5
+ * hookcheck mcp
6
+ * hookcheck --version
7
+ * Exit codes: 0 CLEAN, 1 REVIEW or COMPROMISED-PATTERN, 2 usage error.
8
+ */
9
+ import { explainHook, renderReport, scanHooks, VERSION } from "./core.js";
10
+ function usage() {
11
+ console.error(`Usage:
12
+ hookcheck scan [path] [--json]
13
+ hookcheck explain [path] <hookId-or-file>
14
+ hookcheck mcp
15
+ hookcheck --version`);
16
+ process.exit(2);
17
+ }
18
+ async function main() {
19
+ const [cmd, ...rest] = process.argv.slice(2);
20
+ if (cmd === "--version" || cmd === "-v") {
21
+ console.log(VERSION);
22
+ return;
23
+ }
24
+ if (cmd === "mcp") {
25
+ await import("./mcp.js");
26
+ return;
27
+ }
28
+ if (!cmd)
29
+ usage();
30
+ try {
31
+ if (cmd === "scan") {
32
+ const asJson = rest.includes("--json");
33
+ const target = rest.find((a) => !a.startsWith("--")) ?? ".";
34
+ const report = scanHooks(target);
35
+ if (asJson)
36
+ console.log(JSON.stringify(report, null, 2));
37
+ else
38
+ console.log(renderReport(report));
39
+ process.exit(report.verdict === "CLEAN" ? 0 : 1);
40
+ }
41
+ else if (cmd === "explain") {
42
+ const args = rest.filter((a) => !a.startsWith("--"));
43
+ const asJson = rest.includes("--json");
44
+ let target = ".";
45
+ let id = "";
46
+ if (args.length === 1)
47
+ id = args[0];
48
+ else if (args.length >= 2) {
49
+ target = args[0];
50
+ id = args[1];
51
+ }
52
+ else
53
+ usage();
54
+ const result = explainHook(target, id);
55
+ if (asJson)
56
+ console.log(JSON.stringify(result, null, 2));
57
+ else
58
+ console.log(result.explanation);
59
+ process.exit(result.finding ? 0 : 1);
60
+ }
61
+ else {
62
+ usage();
63
+ }
64
+ }
65
+ catch (err) {
66
+ console.error(`error: ${err instanceof Error ? err.message : String(err)}`);
67
+ process.exit(2);
68
+ }
69
+ }
70
+ void main();
71
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
package/dist/core.d.ts ADDED
@@ -0,0 +1,28 @@
1
+ import { relative } from "node:path";
2
+ import type { Classification, HookFinding, ScanReport, Verdict } from "./types.js";
3
+ export declare const VERSION = "0.1.0";
4
+ interface RuleDef {
5
+ id: string;
6
+ pattern: RegExp;
7
+ evidence: string;
8
+ /** True when this rule alone indicates remote code fetch. */
9
+ remote?: boolean;
10
+ /** True when this rule indicates credential store access. */
11
+ credential?: boolean;
12
+ }
13
+ export declare const RISKY_RULES: RuleDef[];
14
+ export declare function classifyCommand(command: string): {
15
+ classification: Classification;
16
+ rules: string[];
17
+ evidence: string;
18
+ };
19
+ export declare function computeVerdict(findings: HookFinding[]): Verdict;
20
+ export declare function scanHooks(root: string): ScanReport;
21
+ export declare function explainHook(root: string, idOrFile: string): {
22
+ finding: HookFinding | null;
23
+ explanation: string;
24
+ report: ScanReport;
25
+ };
26
+ export declare function renderReport(report: ScanReport): string;
27
+ export { relative };
28
+ export type { Classification, HookFinding, ScanReport, Verdict };
package/dist/core.js ADDED
@@ -0,0 +1,471 @@
1
+ /**
2
+ * hookcheck core: deterministic local scan for auto-exec hooks.
3
+ *
4
+ * No network, no LLM. Every classification comes from explicit pattern
5
+ * rules so the same repo always produces the same report.
6
+ */
7
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
8
+ import { join, relative } from "node:path";
9
+ export const VERSION = "0.1.0";
10
+ export const RISKY_RULES = [
11
+ { id: "CURL-PIPE-SH", pattern: /\b(curl|wget)\b[^|]*\|\s*(sudo\s+)?(sh|bash|zsh)\b/, evidence: "pipes a network download straight into a shell", remote: true },
12
+ { id: "NET-FETCH", pattern: /\b(curl|wget)\b|\bfetch\s*\(|https?\s*\.get\s*\(|requests\.(get|post)\b|Invoke-WebRequest\b|Invoke-RestMethod\b/, evidence: "performs a network fetch", remote: true },
13
+ { id: "REMOTE-URL", pattern: /https?:\/\/[^\s"'`]+/, evidence: "references a remote URL", remote: true },
14
+ { id: "CRED-ACCESS", pattern: /~\/\.ssh|\$HOME\/\.ssh|\.aws\/|~\/\.aws|\.gnupg|id_rsa|id_ed25519|\.npmrc\b|\.netrc\b|credentials(\.json)?\b|\.config\/gh|keychain/i, evidence: "touches a credential store path", credential: true },
15
+ { id: "ENV-ACCESS", pattern: /process\.env|os\.environ|getenv\s*\(|\benv\b.*\b(print|export)|\$\{?\s*(AWS_SECRET|AWS_ACCESS|GITHUB_TOKEN|GH_TOKEN|NPM_TOKEN|OPENAI_API_KEY|ANTHROPIC_API_KEY)/, evidence: "reads environment variables that commonly hold secrets" },
16
+ { id: "ENV-EXFIL", pattern: /(process\.env|os\.environ)[\s\S]{0,80}(fetch|https?|request|post|send|upload)/i, evidence: "combines environment access with an outbound send pattern", credential: true, remote: true },
17
+ { id: "OBFUSCATION", pattern: /base64\s+(-d|--decode)|atob\s*\(|Buffer\.from\s*\([^)]*base64|\\x[0-9a-fA-F]{2}|eval\s*\(|Function\s*\(/, evidence: "uses decoding or dynamic evaluation often used to hide payloads" },
18
+ { id: "SHELL-EXEC", pattern: /\b(child_process|execSync|spawnSync|exec\s*\()\b|\/bin\/(sh|bash)\b|powershell(\.exe)?\b|cmd\.exe\b/, evidence: "spawns a shell or child process" },
19
+ { id: "WRITE-OUTSIDE", pattern: /(>>?|tee)\s+~\/|(>>?|tee)\s+\$HOME\/|chmod\s+\+x/, evidence: "writes outside the repo or changes executable bits" },
20
+ { id: "DOWNLOAD-EXEC", pattern: /\bnode\s+-e\b|\bpython3?\s+-c\b|\bdeno\s+eval\b|\bbun\s+-e\b/, evidence: "executes inline code supplied on the command line" },
21
+ ];
22
+ const SAFE_PATTERNS = [
23
+ /^(npm|npx|yarn|pnpm|bun)\s+(run\s+)?(test|lint|build|dev|start|typecheck|check|format|prepare:.*|prepublishOnly.*)$/i,
24
+ /^(npx\s+)?(tsc|eslint|prettier|vitest|jest|mocha|husky|lint-staged|lefthook|pre-commit)(\s|$)/i,
25
+ /^node\s+(--test|scripts\/|tools\/|build)/i,
26
+ /^(echo|true|exit 0)\b/i,
27
+ /^git\s+(config|rev-parse|status)/i,
28
+ /^patch-package$/i,
29
+ /^simple-git-hooks$/i,
30
+ ];
31
+ export function classifyCommand(command) {
32
+ const cmd = command.trim();
33
+ const fired = [];
34
+ for (const rule of RISKY_RULES) {
35
+ if (rule.pattern.test(cmd))
36
+ fired.push(rule);
37
+ }
38
+ if (fired.length > 0) {
39
+ return {
40
+ classification: "RISKY",
41
+ rules: fired.map((r) => r.id),
42
+ evidence: fired.map((r) => `${r.id}: ${r.evidence}`).join("; "),
43
+ };
44
+ }
45
+ for (const safe of SAFE_PATTERNS) {
46
+ if (safe.test(cmd)) {
47
+ return { classification: "SAFE", rules: [], evidence: "matches a known safe dev pattern (test, lint, build, dev tooling)" };
48
+ }
49
+ }
50
+ // A bare local script or binary with no risky indicators is UNKNOWN, not SAFE:
51
+ // we cannot prove what a local file does without reading it, and when the
52
+ // referenced local file itself contains risky patterns we escalate below.
53
+ return { classification: "UNKNOWN", rules: [], evidence: "no known safe pattern and no risky pattern matched; manual review needed" };
54
+ }
55
+ /** If a command runs a local file, inspect that file for risky patterns too. */
56
+ function escalateWithLocalFile(root, finding) {
57
+ const m = /\b(?:node|bash|sh|python3?|deno run|bun run)\s+([^\s;&|]+\.(?:mjs|cjs|js|ts|sh|py))\b/.exec(finding.command);
58
+ if (!m)
59
+ return finding;
60
+ const rel = m[1].replace(/^\.\//, "");
61
+ const abs = join(root, rel);
62
+ if (!existsSync(abs))
63
+ return finding;
64
+ let content = "";
65
+ try {
66
+ content = readFileSync(abs, "utf8").slice(0, 20000);
67
+ }
68
+ catch {
69
+ return finding;
70
+ }
71
+ const inner = classifyCommand(content.replace(/\n/g, " "));
72
+ if (inner.classification === "RISKY") {
73
+ const rules = Array.from(new Set([...finding.rules, ...inner.rules]));
74
+ return {
75
+ ...finding,
76
+ classification: "RISKY",
77
+ rules,
78
+ evidence: `${finding.evidence === "" ? "" : finding.evidence + "; "}referenced local file ${rel} is RISKY: ${inner.evidence}`,
79
+ };
80
+ }
81
+ return finding;
82
+ }
83
+ export function computeVerdict(findings) {
84
+ let review = false;
85
+ for (const f of findings) {
86
+ if (f.classification === "RISKY") {
87
+ const hasRemote = f.rules.some((id) => RISKY_RULES.find((r) => r.id === id)?.remote);
88
+ const hasCred = f.rules.some((id) => RISKY_RULES.find((r) => r.id === id)?.credential);
89
+ if (hasRemote || hasCred)
90
+ return "COMPROMISED-PATTERN";
91
+ review = true;
92
+ }
93
+ else if (f.classification === "UNKNOWN") {
94
+ review = true;
95
+ }
96
+ }
97
+ return review ? "REVIEW" : "CLEAN";
98
+ }
99
+ function makeFinding(root, file, source, hook, command, idSuffix) {
100
+ const c = classifyCommand(command);
101
+ const id = `${file}:${hook}${idSuffix !== undefined ? `:${idSuffix}` : ""}`;
102
+ const base = {
103
+ id, file, source, hook, command,
104
+ classification: c.classification, rules: c.rules,
105
+ evidence: c.evidence,
106
+ };
107
+ return escalateWithLocalFile(root, base);
108
+ }
109
+ function readText(abs) {
110
+ try {
111
+ return readFileSync(abs, "utf8");
112
+ }
113
+ catch {
114
+ return null;
115
+ }
116
+ }
117
+ function readJson(root, rel, warnings) {
118
+ const raw = readText(join(root, rel));
119
+ if (raw === null)
120
+ return null;
121
+ // Strip simple JSONC comments and trailing commas for VS Code style files.
122
+ const cleaned = raw
123
+ .replace(/^\s*\/\/.*$/gm, "")
124
+ .replace(/,\s*([}\]])/g, "$1");
125
+ try {
126
+ return JSON.parse(cleaned);
127
+ }
128
+ catch {
129
+ warnings.push(`Malformed JSON in ${rel}: file skipped for structured parsing`);
130
+ return null;
131
+ }
132
+ }
133
+ function scanPackageJson(root, findings, warnings) {
134
+ const rel = "package.json";
135
+ if (!existsSync(join(root, rel)))
136
+ return;
137
+ const data = readJson(root, rel, warnings);
138
+ if (!data) {
139
+ // readJson already warned when the file exists but is malformed. If it
140
+ // parsed to a non object, note it as well.
141
+ if (readText(join(root, rel)) !== null && !warnings.some((w) => w.includes(rel))) {
142
+ warnings.push(`Could not parse ${rel} as an object`);
143
+ }
144
+ return;
145
+ }
146
+ const watched = ["preinstall", "install", "postinstall", "prepare", "prepublish", "prepublishOnly"];
147
+ for (const name of watched) {
148
+ const cmd = data.scripts?.[name];
149
+ if (typeof cmd === "string" && cmd.trim()) {
150
+ findings.push(makeFinding(root, rel, "package.json", name, cmd));
151
+ }
152
+ }
153
+ }
154
+ function collectClaudeHooks(node, file, root, findings, prefix) {
155
+ if (!node || typeof node !== "object")
156
+ return;
157
+ const obj = node;
158
+ if (typeof obj.command === "string" && obj.command.trim()) {
159
+ findings.push(makeFinding(root, file, "claude-settings", prefix || "hook", obj.command, String(findings.length)));
160
+ return;
161
+ }
162
+ for (const [key, value] of Object.entries(obj)) {
163
+ if (Array.isArray(value)) {
164
+ value.forEach((item, i) => collectClaudeHooks(item, file, root, findings, prefix ? `${prefix}:${key}` : key));
165
+ }
166
+ else if (value && typeof value === "object") {
167
+ collectClaudeHooks(value, file, root, findings, prefix ? `${prefix}:${key}` : key);
168
+ }
169
+ }
170
+ }
171
+ function scanClaude(root, findings, warnings) {
172
+ for (const rel of [".claude/settings.json", ".claude/settings.local.json"]) {
173
+ if (!existsSync(join(root, rel)))
174
+ continue;
175
+ const data = readJson(root, rel, warnings);
176
+ if (!data)
177
+ continue;
178
+ if (data.hooks)
179
+ collectClaudeHooks(data.hooks, rel, root, findings, "");
180
+ else
181
+ warnings.push(`No hooks key in ${rel}`);
182
+ }
183
+ }
184
+ function scanVscode(root, findings, warnings) {
185
+ const rel = ".vscode/tasks.json";
186
+ if (!existsSync(join(root, rel)))
187
+ return;
188
+ const data = readJson(root, rel, warnings);
189
+ if (!data?.tasks)
190
+ return;
191
+ data.tasks.forEach((task, idx) => {
192
+ const runOptions = (task.runOptions ?? {});
193
+ const runOn = runOptions.runOn;
194
+ const isFolderOpen = runOn === "folderOpen" || (Array.isArray(runOn) && runOn.includes("folderOpen"));
195
+ const command = typeof task.command === "string" ? task.command : "";
196
+ const args = Array.isArray(task.args) ? task.args.filter((a) => typeof a === "string").join(" ") : "";
197
+ const full = [command, args].filter(Boolean).join(" ").trim();
198
+ if (!full)
199
+ return;
200
+ const label = typeof task.label === "string" ? task.label : `task-${idx}`;
201
+ if (isFolderOpen) {
202
+ findings.push(makeFinding(root, rel, "vscode-tasks", `folderOpen:${label}`, full, String(idx)));
203
+ }
204
+ else {
205
+ // Inventory non auto-run tasks at lower weight: still record them so
206
+ // explain can find them, classified by their command.
207
+ const f = makeFinding(root, rel, "vscode-tasks", label, full, String(idx));
208
+ findings.push(f);
209
+ }
210
+ });
211
+ // MCP config inside .vscode
212
+ scanMcpFile(root, ".vscode/mcp.json", findings, warnings);
213
+ }
214
+ function scanDirFiles(root, dirRel, source, findings) {
215
+ const abs = join(root, dirRel);
216
+ if (!existsSync(abs))
217
+ return;
218
+ let entries = [];
219
+ try {
220
+ entries = readdirSync(abs);
221
+ }
222
+ catch {
223
+ return;
224
+ }
225
+ for (const name of entries.sort()) {
226
+ const fileAbs = join(abs, name);
227
+ let st;
228
+ try {
229
+ st = statSync(fileAbs);
230
+ }
231
+ catch {
232
+ continue;
233
+ }
234
+ if (!st.isFile())
235
+ continue;
236
+ if (name === "_" || name.startsWith("."))
237
+ continue;
238
+ const content = readText(fileAbs);
239
+ if (content === null)
240
+ continue;
241
+ const firstMeaningful = content.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#") && !l.startsWith("set -")).join(" ");
242
+ const fileRel = `${dirRel}/${name}`;
243
+ // Entire file content is the command surface for husky style hooks.
244
+ findings.push(makeFinding(root, fileRel, source, name, content.trim().slice(0, 500)));
245
+ void firstMeaningful;
246
+ }
247
+ }
248
+ function scanLefthook(root, findings) {
249
+ for (const rel of ["lefthook.yml", "lefthook.yaml", ".lefthook.yml"]) {
250
+ if (!existsSync(join(root, rel)))
251
+ continue;
252
+ const raw = readText(join(root, rel)) ?? "";
253
+ const runRe = /^\s*run:\s*(.+)$/gm;
254
+ let m;
255
+ let i = 0;
256
+ while ((m = runRe.exec(raw)) !== null) {
257
+ findings.push(makeFinding(root, rel, "lefthook", "run", m[1].trim().replace(/^["']|["']$/g, ""), String(i++)));
258
+ }
259
+ }
260
+ }
261
+ function scanPreCommit(root, findings) {
262
+ const rel = ".pre-commit-config.yaml";
263
+ if (!existsSync(join(root, rel)))
264
+ return;
265
+ const raw = readText(join(root, rel)) ?? "";
266
+ const entryRe = /^\s*entry:\s*(.+)$/gm;
267
+ let m;
268
+ let i = 0;
269
+ while ((m = entryRe.exec(raw)) !== null) {
270
+ findings.push(makeFinding(root, rel, "pre-commit", "entry", m[1].trim().replace(/^["']|["']$/g, ""), String(i++)));
271
+ }
272
+ const repoRe = /^\s*-\s*repo:\s*(https?:\/\/\S+)$/gm;
273
+ while ((m = repoRe.exec(raw)) !== null) {
274
+ findings.push(makeFinding(root, rel, "pre-commit", "remote-repo", m[1].trim(), String(i++)));
275
+ }
276
+ }
277
+ function scanAgentDirs(root, findings, warnings) {
278
+ for (const [dir, source] of [[".cursor", "cursor"], [".codex", "codex"]]) {
279
+ const abs = join(root, dir);
280
+ if (!existsSync(abs))
281
+ continue;
282
+ let entries = [];
283
+ try {
284
+ entries = readdirSync(abs);
285
+ }
286
+ catch {
287
+ continue;
288
+ }
289
+ for (const name of entries.sort()) {
290
+ const fileAbs = join(abs, name);
291
+ let st;
292
+ try {
293
+ st = statSync(fileAbs);
294
+ }
295
+ catch {
296
+ continue;
297
+ }
298
+ if (!st.isFile())
299
+ continue;
300
+ const rel = `${dir}/${name}`;
301
+ if (name.endsWith(".json")) {
302
+ const data = readJson(root, rel, warnings);
303
+ if (data)
304
+ collectGenericCommands(data, rel, root, findings, source);
305
+ }
306
+ else {
307
+ const content = readText(fileAbs);
308
+ if (content && content.trim()) {
309
+ findings.push(makeFinding(root, rel, source, name, content.trim().slice(0, 500)));
310
+ }
311
+ }
312
+ if (name === "mcp.json")
313
+ scanMcpFile(root, rel, findings, warnings);
314
+ }
315
+ }
316
+ }
317
+ function collectGenericCommands(node, file, root, findings, source, prefix = "") {
318
+ if (!node || typeof node !== "object")
319
+ return;
320
+ const obj = node;
321
+ if (typeof obj.command === "string" && obj.command.trim()) {
322
+ const args = Array.isArray(obj.args) ? obj.args.filter((a) => typeof a === "string").join(" ") : "";
323
+ findings.push(makeFinding(root, file, source, prefix || "command", [obj.command, args].filter(Boolean).join(" "), String(findings.length)));
324
+ }
325
+ for (const [key, value] of Object.entries(obj)) {
326
+ if (value && typeof value === "object")
327
+ collectGenericCommands(value, file, root, findings, source, prefix ? `${prefix}:${key}` : key);
328
+ }
329
+ }
330
+ function scanMcpFile(root, rel, findings, warnings) {
331
+ if (!existsSync(join(root, rel)))
332
+ return;
333
+ const data = readJson(root, rel, warnings);
334
+ if (!data)
335
+ return;
336
+ const servers = (data.mcpServers ?? data.servers ?? {});
337
+ if (!servers || typeof servers !== "object")
338
+ return;
339
+ for (const [name, cfg] of Object.entries(servers)) {
340
+ if (!cfg || typeof cfg !== "object")
341
+ continue;
342
+ const c = cfg;
343
+ const command = typeof c.command === "string" ? c.command : "";
344
+ const args = Array.isArray(c.args) ? c.args.filter((a) => typeof a === "string").join(" ") : "";
345
+ const full = [command, args].filter(Boolean).join(" ").trim();
346
+ if (full)
347
+ findings.push(makeFinding(root, rel, "mcp-config", `mcp:${name}`, full));
348
+ if (typeof c.url === "string" && c.url) {
349
+ findings.push(makeFinding(root, rel, "mcp-config", `mcp:${name}:url`, c.url));
350
+ }
351
+ }
352
+ }
353
+ function scanWorkflows(root, findings) {
354
+ const dir = ".github/workflows";
355
+ const abs = join(root, dir);
356
+ if (!existsSync(abs))
357
+ return;
358
+ let entries = [];
359
+ try {
360
+ entries = readdirSync(abs);
361
+ }
362
+ catch {
363
+ return;
364
+ }
365
+ for (const name of entries.sort()) {
366
+ if (!/\.(yml|yaml)$/.test(name))
367
+ continue;
368
+ const rel = `${dir}/${name}`;
369
+ const raw = readText(join(root, rel)) ?? "";
370
+ const runRe = /^\s*-?\s*run:\s*(.+)$/gm;
371
+ let m;
372
+ let i = 0;
373
+ while ((m = runRe.exec(raw)) !== null) {
374
+ const cmd = m[1].trim();
375
+ if (cmd === "|" || cmd === ">" || cmd === "")
376
+ continue;
377
+ findings.push(makeFinding(root, rel, "github-workflow", "run", cmd, String(i++)));
378
+ }
379
+ }
380
+ }
381
+ function scanGitHooks(root, findings) {
382
+ scanDirFiles(root, ".githooks", "githooks", findings);
383
+ }
384
+ export function scanHooks(root) {
385
+ const findings = [];
386
+ const warnings = [];
387
+ if (!existsSync(root)) {
388
+ warnings.push(`Path does not exist: ${root}`);
389
+ return {
390
+ path: root, verdict: "REVIEW", findings, warnings,
391
+ counts: { total: 0, safe: 0, risky: 0, unknown: 0 },
392
+ scannedAt: new Date().toISOString(),
393
+ };
394
+ }
395
+ scanPackageJson(root, findings, warnings);
396
+ scanClaude(root, findings, warnings);
397
+ scanVscode(root, findings, warnings);
398
+ scanDirFiles(root, ".husky", "husky", findings);
399
+ scanLefthook(root, findings);
400
+ scanPreCommit(root, findings);
401
+ scanAgentDirs(root, findings, warnings);
402
+ for (const rel of [".mcp.json", "mcp.json", ".cursor/mcp.json"])
403
+ scanMcpFile(root, rel, findings, warnings);
404
+ scanWorkflows(root, findings);
405
+ scanGitHooks(root, findings);
406
+ // Dedupe identical ids (agent dir scan may double count mcp.json).
407
+ const seen = new Set();
408
+ const deduped = findings.filter((f) => {
409
+ const key = `${f.id}|${f.command}`;
410
+ if (seen.has(key))
411
+ return false;
412
+ seen.add(key);
413
+ return true;
414
+ });
415
+ const counts = {
416
+ total: deduped.length,
417
+ safe: deduped.filter((f) => f.classification === "SAFE").length,
418
+ risky: deduped.filter((f) => f.classification === "RISKY").length,
419
+ unknown: deduped.filter((f) => f.classification === "UNKNOWN").length,
420
+ };
421
+ return {
422
+ path: root,
423
+ verdict: computeVerdict(deduped),
424
+ findings: deduped,
425
+ counts,
426
+ warnings,
427
+ scannedAt: new Date().toISOString(),
428
+ };
429
+ }
430
+ export function explainHook(root, idOrFile) {
431
+ const report = scanHooks(root);
432
+ const finding = report.findings.find((f) => f.id === idOrFile || f.file === idOrFile || f.id.startsWith(idOrFile)) ?? null;
433
+ if (!finding) {
434
+ return { finding: null, explanation: `No hook matched "${idOrFile}". ${report.findings.length} hook(s) found in this repo.`, report };
435
+ }
436
+ const parts = [
437
+ `Hook ${finding.id} in ${finding.file} (${finding.source}, event ${finding.hook}).`,
438
+ `Command: ${finding.command}`,
439
+ `Classification: ${finding.classification}.`,
440
+ `Evidence: ${finding.evidence || "none"}.`,
441
+ finding.classification === "RISKY"
442
+ ? "This hook runs automatically and matched risky patterns. Treat the repo as untrusted until this hook is removed or proven benign."
443
+ : finding.classification === "SAFE"
444
+ ? "This hook matches known safe dev tooling patterns and matched no risky rules."
445
+ : "This hook matched neither safe nor risky patterns. Read the command and any file it invokes before trusting this repo.",
446
+ ];
447
+ return { finding, explanation: parts.join(" "), report };
448
+ }
449
+ export function renderReport(report) {
450
+ const lines = [];
451
+ lines.push(`hookcheck ${report.path}: ${report.verdict}`);
452
+ lines.push(` ${report.counts.total} hook(s): ${report.counts.safe} SAFE, ${report.counts.risky} RISKY, ${report.counts.unknown} UNKNOWN`);
453
+ for (const f of report.findings) {
454
+ lines.push(` [${f.classification}] ${f.id} (${f.source})`);
455
+ lines.push(` command: ${f.command.slice(0, 200)}`);
456
+ if (f.rules.length)
457
+ lines.push(` rules: ${f.rules.join(", ")}`);
458
+ lines.push(` evidence: ${f.evidence}`);
459
+ }
460
+ for (const w of report.warnings)
461
+ lines.push(` [warn] ${w}`);
462
+ if (report.verdict === "CLEAN")
463
+ lines.push("CLEAN: no risky or unknown auto-exec hooks found.");
464
+ else if (report.verdict === "REVIEW")
465
+ lines.push("REVIEW: at least one hook needs manual review.");
466
+ else
467
+ lines.push("COMPROMISED-PATTERN: at least one risky hook fetches remote code or touches credential stores. Treat as a Shai-Hulud style persistence pattern until proven otherwise.");
468
+ return lines.join("\n");
469
+ }
470
+ export { relative };
471
+ //# sourceMappingURL=core.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.js","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC1E,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAG3C,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC;AAY/B,MAAM,CAAC,MAAM,WAAW,GAAc;IACpC,EAAE,EAAE,EAAE,cAAc,EAAE,OAAO,EAAE,oDAAoD,EAAE,QAAQ,EAAE,gDAAgD,EAAE,MAAM,EAAE,IAAI,EAAE;IAC/J,EAAE,EAAE,EAAE,WAAW,EAAE,OAAO,EAAE,iHAAiH,EAAE,QAAQ,EAAE,0BAA0B,EAAE,MAAM,EAAE,IAAI,EAAE;IACnM,EAAE,EAAE,EAAE,YAAY,EAAE,OAAO,EAAE,sBAAsB,EAAE,QAAQ,EAAE,yBAAyB,EAAE,MAAM,EAAE,IAAI,EAAE;IACxG,EAAE,EAAE,EAAE,aAAa,EAAE,OAAO,EAAE,qIAAqI,EAAE,QAAQ,EAAE,iCAAiC,EAAE,UAAU,EAAE,IAAI,EAAE;IACpO,EAAE,EAAE,EAAE,YAAY,EAAE,OAAO,EAAE,iKAAiK,EAAE,QAAQ,EAAE,wDAAwD,EAAE;IACpQ,EAAE,EAAE,EAAE,WAAW,EAAE,OAAO,EAAE,gFAAgF,EAAE,QAAQ,EAAE,2DAA2D,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE;IACrN,EAAE,EAAE,EAAE,aAAa,EAAE,OAAO,EAAE,yGAAyG,EAAE,QAAQ,EAAE,iEAAiE,EAAE;IACtN,EAAE,EAAE,EAAE,YAAY,EAAE,OAAO,EAAE,qGAAqG,EAAE,QAAQ,EAAE,iCAAiC,EAAE;IACjL,EAAE,EAAE,EAAE,eAAe,EAAE,OAAO,EAAE,kDAAkD,EAAE,QAAQ,EAAE,oDAAoD,EAAE;IACpJ,EAAE,EAAE,EAAE,eAAe,EAAE,OAAO,EAAE,8DAA8D,EAAE,QAAQ,EAAE,mDAAmD,EAAE;CAChK,CAAC;AAEF,MAAM,aAAa,GAAa;IAC9B,sHAAsH;IACtH,gGAAgG;IAChG,2CAA2C;IAC3C,wBAAwB;IACxB,mCAAmC;IACnC,kBAAkB;IAClB,qBAAqB;CACtB,CAAC;AAEF,MAAM,UAAU,eAAe,CAAC,OAAe;IAC7C,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;IAC3B,MAAM,KAAK,GAAc,EAAE,CAAC;IAC5B,KAAK,MAAM,IAAI,IAAI,WAAW,EAAE,CAAC;QAC/B,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC;YAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC/C,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrB,OAAO;YACL,cAAc,EAAE,OAAO;YACvB,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7B,QAAQ,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;SAChE,CAAC;IACJ,CAAC;IACD,KAAK,MAAM,IAAI,IAAI,aAAa,EAAE,CAAC;QACjC,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YACnB,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,QAAQ,EAAE,mEAAmE,EAAE,CAAC;QAC9H,CAAC;IACH,CAAC;IACD,+EAA+E;IAC/E,0EAA0E;IAC1E,0EAA0E;IAC1E,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,EAAE,QAAQ,EAAE,0EAA0E,EAAE,CAAC;AACxI,CAAC;AAED,gFAAgF;AAChF,SAAS,qBAAqB,CAAC,IAAY,EAAE,OAAoB;IAC/D,MAAM,CAAC,GAAG,uFAAuF,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IACxH,IAAI,CAAC,CAAC;QAAE,OAAO,OAAO,CAAC;IACvB,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IACtC,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAC5B,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,OAAO,CAAC;IACrC,IAAI,OAAO,GAAG,EAAE,CAAC;IACjB,IAAI,CAAC;QAAC,OAAO,GAAG,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,OAAO,CAAC;IAAC,CAAC;IACtF,MAAM,KAAK,GAAG,eAAe,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC;IAC3D,IAAI,KAAK,CAAC,cAAc,KAAK,OAAO,EAAE,CAAC;QACrC,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,OAAO,CAAC,KAAK,EAAE,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QACtE,OAAO;YACL,GAAG,OAAO;YACV,cAAc,EAAE,OAAO;YACvB,KAAK;YACL,QAAQ,EAAE,GAAG,OAAO,CAAC,QAAQ,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,GAAG,IAAI,yBAAyB,GAAG,cAAc,KAAK,CAAC,QAAQ,EAAE;SAC9H,CAAC;IACJ,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,QAAuB;IACpD,IAAI,MAAM,GAAG,KAAK,CAAC;IACnB,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,IAAI,CAAC,CAAC,cAAc,KAAK,OAAO,EAAE,CAAC;YACjC,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC;YACrF,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,UAAU,CAAC,CAAC;YACvF,IAAI,SAAS,IAAI,OAAO;gBAAE,OAAO,qBAAqB,CAAC;YACvD,MAAM,GAAG,IAAI,CAAC;QAChB,CAAC;aAAM,IAAI,CAAC,CAAC,cAAc,KAAK,SAAS,EAAE,CAAC;YAC1C,MAAM,GAAG,IAAI,CAAC;QAChB,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC;AACrC,CAAC;AAED,SAAS,WAAW,CAAC,IAAY,EAAE,IAAY,EAAE,MAAkB,EAAE,IAAY,EAAE,OAAe,EAAE,QAAiB;IACnH,MAAM,CAAC,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;IACnC,MAAM,EAAE,GAAG,GAAG,IAAI,IAAI,IAAI,GAAG,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;IAC5E,MAAM,IAAI,GAAgB;QACxB,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO;QAC/B,cAAc,EAAE,CAAC,CAAC,cAAc,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK;QAChD,QAAQ,EAAE,CAAC,CAAC,QAAQ;KACrB,CAAC;IACF,OAAO,qBAAqB,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;AAC3C,CAAC;AAED,SAAS,QAAQ,CAAC,GAAW;IAC3B,IAAI,CAAC;QAAC,OAAO,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,IAAI,CAAC;IAAC,CAAC;AAClE,CAAC;AAED,SAAS,QAAQ,CAAC,IAAY,EAAE,GAAW,EAAE,QAAkB;IAC7D,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC;IACtC,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC9B,2EAA2E;IAC3E,MAAM,OAAO,GAAG,GAAG;SAChB,OAAO,CAAC,eAAe,EAAE,EAAE,CAAC;SAC5B,OAAO,CAAC,cAAc,EAAE,IAAI,CAAC,CAAC;IACjC,IAAI,CAAC;QAAC,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC;QACzC,QAAQ,CAAC,IAAI,CAAC,qBAAqB,GAAG,uCAAuC,CAAC,CAAC;QAC/E,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,eAAe,CAAC,IAAY,EAAE,QAAuB,EAAE,QAAkB;IAChF,MAAM,GAAG,GAAG,cAAc,CAAC;IAC3B,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QAAE,OAAO;IACzC,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,EAAE,GAAG,EAAE,QAAQ,CAAiD,CAAC;IAC3F,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,uEAAuE;QACvE,2CAA2C;QAC3C,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;YACjF,QAAQ,CAAC,IAAI,CAAC,mBAAmB,GAAG,eAAe,CAAC,CAAC;QACvD,CAAC;QACD,OAAO;IACT,CAAC;IACD,MAAM,OAAO,GAAG,CAAC,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,SAAS,EAAE,YAAY,EAAE,gBAAgB,CAAC,CAAC;IACpG,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;QAC3B,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,CAAC;QACjC,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC;YAC1C,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC;QACnE,CAAC;IACH,CAAC;AACH,CAAC;AAED,SAAS,kBAAkB,CAAC,IAAa,EAAE,IAAY,EAAE,IAAY,EAAE,QAAuB,EAAE,MAAc;IAC5G,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO;IAC9C,MAAM,GAAG,GAAG,IAA+B,CAAC;IAC5C,IAAI,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ,IAAI,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;QAC1D,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,IAAI,EAAE,iBAAiB,EAAE,MAAM,IAAI,MAAM,EAAE,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QAClH,OAAO;IACT,CAAC;IACD,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QAC/C,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YACzB,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,kBAAkB,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAChH,CAAC;aAAM,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC9C,kBAAkB,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QACrF,CAAC;IACH,CAAC;AACH,CAAC;AAED,SAAS,UAAU,CAAC,IAAY,EAAE,QAAuB,EAAE,QAAkB;IAC3E,KAAK,MAAM,GAAG,IAAI,CAAC,uBAAuB,EAAE,6BAA6B,CAAC,EAAE,CAAC;QAC3E,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;YAAE,SAAS;QAC3C,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,EAAE,GAAG,EAAE,QAAQ,CAA+B,CAAC;QACzE,IAAI,CAAC,IAAI;YAAE,SAAS;QACpB,IAAI,IAAI,CAAC,KAAK;YAAE,kBAAkB,CAAC,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC;;YACnE,QAAQ,CAAC,IAAI,CAAC,mBAAmB,GAAG,EAAE,CAAC,CAAC;IAC/C,CAAC;AACH,CAAC;AAED,SAAS,UAAU,CAAC,IAAY,EAAE,QAAuB,EAAE,QAAkB;IAC3E,MAAM,GAAG,GAAG,oBAAoB,CAAC;IACjC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QAAE,OAAO;IACzC,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,EAAE,GAAG,EAAE,QAAQ,CAAsD,CAAC;IAChG,IAAI,CAAC,IAAI,EAAE,KAAK;QAAE,OAAO;IACzB,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE;QAC/B,MAAM,UAAU,GAAG,CAAC,IAAI,CAAC,UAAU,IAAI,EAAE,CAA4B,CAAC;QACtE,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC;QAC/B,MAAM,YAAY,GAAG,KAAK,KAAK,YAAY,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC;QACtG,MAAM,OAAO,GAAG,OAAO,IAAI,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;QACrE,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACtG,MAAM,IAAI,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;QAC9D,IAAI,CAAC,IAAI;YAAE,OAAO;QAClB,MAAM,KAAK,GAAG,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,GAAG,EAAE,CAAC;QAC1E,IAAI,YAAY,EAAE,CAAC;YACjB,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,GAAG,EAAE,cAAc,EAAE,cAAc,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAClG,CAAC;aAAM,CAAC;YACN,qEAAqE;YACrE,sDAAsD;YACtD,MAAM,CAAC,GAAG,WAAW,CAAC,IAAI,EAAE,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;YAC3E,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACnB,CAAC;IACH,CAAC,CAAC,CAAC;IACH,4BAA4B;IAC5B,WAAW,CAAC,IAAI,EAAE,kBAAkB,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;AAC5D,CAAC;AAED,SAAS,YAAY,CAAC,IAAY,EAAE,MAAc,EAAE,MAAkB,EAAE,QAAuB;IAC7F,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC/B,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO;IAC7B,IAAI,OAAO,GAAa,EAAE,CAAC;IAC3B,IAAI,CAAC;QAAC,OAAO,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO;IAAC,CAAC;IACrD,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;QAClC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAChC,IAAI,EAAE,CAAC;QAAC,IAAI,CAAC;YAAC,EAAE,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC;YAAC,SAAS;QAAC,CAAC;QAC3D,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE;YAAE,SAAS;QAC3B,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAS;QACnD,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC;QAClC,IAAI,OAAO,KAAK,IAAI;YAAE,SAAS;QAC/B,MAAM,eAAe,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC5I,MAAM,OAAO,GAAG,GAAG,MAAM,IAAI,IAAI,EAAE,CAAC;QACpC,oEAAoE;QACpE,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;QACtF,KAAK,eAAe,CAAC;IACvB,CAAC;AACH,CAAC;AAED,SAAS,YAAY,CAAC,IAAY,EAAE,QAAuB;IACzD,KAAK,MAAM,GAAG,IAAI,CAAC,cAAc,EAAE,eAAe,EAAE,eAAe,CAAC,EAAE,CAAC;QACrE,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;YAAE,SAAS;QAC3C,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;QAC5C,MAAM,KAAK,GAAG,oBAAoB,CAAC;QACnC,IAAI,CAAyB,CAAC;QAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACzC,OAAO,CAAC,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;YACtC,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACjH,CAAC;IACH,CAAC;AACH,CAAC;AAED,SAAS,aAAa,CAAC,IAAY,EAAE,QAAuB;IAC1D,MAAM,GAAG,GAAG,yBAAyB,CAAC;IACtC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QAAE,OAAO;IACzC,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IAC5C,MAAM,OAAO,GAAG,sBAAsB,CAAC;IACvC,IAAI,CAAyB,CAAC;IAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACzC,OAAO,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QACxC,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,GAAG,EAAE,YAAY,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACrH,CAAC;IACD,MAAM,MAAM,GAAG,qCAAqC,CAAC;IACrD,OAAO,CAAC,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QACvC,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,GAAG,EAAE,YAAY,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC/F,CAAC;AACH,CAAC;AAED,SAAS,aAAa,CAAC,IAAY,EAAE,QAAuB,EAAE,QAAkB;IAC9E,KAAK,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAgC,EAAE,CAAC;QACxG,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QAC5B,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAS;QAC/B,IAAI,OAAO,GAAa,EAAE,CAAC;QAC3B,IAAI,CAAC;YAAC,OAAO,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC;YAAC,SAAS;QAAC,CAAC;QACvD,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;YAClC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;YAChC,IAAI,EAAE,CAAC;YAAC,IAAI,CAAC;gBAAC,EAAE,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC;YAAC,CAAC;YAAC,MAAM,CAAC;gBAAC,SAAS;YAAC,CAAC;YAC3D,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE;gBAAE,SAAS;YAC3B,MAAM,GAAG,GAAG,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;YAC7B,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC3B,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC;gBAC3C,IAAI,IAAI;oBAAE,sBAAsB,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;YACtE,CAAC;iBAAM,CAAC;gBACN,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC;gBAClC,IAAI,OAAO,IAAI,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;oBAC9B,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;gBACpF,CAAC;YACH,CAAC;YACD,IAAI,IAAI,KAAK,UAAU;gBAAE,WAAW,CAAC,IAAI,EAAE,GAAG,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;QACtE,CAAC;IACH,CAAC;AACH,CAAC;AAED,SAAS,sBAAsB,CAAC,IAAa,EAAE,IAAY,EAAE,IAAY,EAAE,QAAuB,EAAE,MAAkB,EAAE,MAAM,GAAG,EAAE;IACjI,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO;IAC9C,MAAM,GAAG,GAAG,IAA+B,CAAC;IAC5C,IAAI,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ,IAAI,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;QAC1D,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACpG,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,IAAI,SAAS,EAAE,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAC9I,CAAC;IACD,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QAC/C,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;YAAE,sBAAsB,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACzI,CAAC;AACH,CAAC;AAED,SAAS,WAAW,CAAC,IAAY,EAAE,GAAW,EAAE,QAAuB,EAAE,QAAkB;IACzF,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QAAE,OAAO;IACzC,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,EAAE,GAAG,EAAE,QAAQ,CAAmC,CAAC;IAC7E,IAAI,CAAC,IAAI;QAAE,OAAO;IAClB,MAAM,OAAO,GAAG,CAAC,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,OAAO,IAAI,EAAE,CAA4B,CAAC;IACnF,IAAI,CAAC,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ;QAAE,OAAO;IACpD,KAAK,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAClD,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ;YAAE,SAAS;QAC9C,MAAM,CAAC,GAAG,GAA8B,CAAC;QACzC,MAAM,OAAO,GAAG,OAAO,CAAC,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/D,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAChG,MAAM,IAAI,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;QAC9D,IAAI,IAAI;YAAE,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,GAAG,EAAE,YAAY,EAAE,OAAO,IAAI,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC;QACnF,IAAI,OAAO,CAAC,CAAC,GAAG,KAAK,QAAQ,IAAI,CAAC,CAAC,GAAG,EAAE,CAAC;YACvC,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,GAAG,EAAE,YAAY,EAAE,OAAO,IAAI,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAChF,CAAC;IACH,CAAC;AACH,CAAC;AAED,SAAS,aAAa,CAAC,IAAY,EAAE,QAAuB;IAC1D,MAAM,GAAG,GAAG,mBAAmB,CAAC;IAChC,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAC5B,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO;IAC7B,IAAI,OAAO,GAAa,EAAE,CAAC;IAC3B,IAAI,CAAC;QAAC,OAAO,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO;IAAC,CAAC;IACrD,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;QAClC,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,SAAS;QAC1C,MAAM,GAAG,GAAG,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;QAC7B,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;QAC5C,MAAM,KAAK,GAAG,yBAAyB,CAAC;QACxC,IAAI,CAAyB,CAAC;QAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACzC,OAAO,CAAC,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;YACtC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YACxB,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,EAAE;gBAAE,SAAS;YACvD,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,GAAG,EAAE,iBAAiB,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACpF,CAAC;IACH,CAAC;AACH,CAAC;AAED,SAAS,YAAY,CAAC,IAAY,EAAE,QAAuB;IACzD,YAAY,CAAC,IAAI,EAAE,WAAW,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC;AACxD,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,MAAM,QAAQ,GAAkB,EAAE,CAAC;IACnC,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACtB,QAAQ,CAAC,IAAI,CAAC,wBAAwB,IAAI,EAAE,CAAC,CAAC;QAC9C,OAAO;YACL,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ;YACjD,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE;YACnD,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACpC,CAAC;IACJ,CAAC;IACD,eAAe,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC1C,UAAU,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;IACrC,UAAU,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;IACrC,YAAY,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;IAChD,YAAY,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IAC7B,aAAa,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IAC9B,aAAa,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;IACxC,KAAK,MAAM,GAAG,IAAI,CAAC,WAAW,EAAE,UAAU,EAAE,kBAAkB,CAAC;QAAE,WAAW,CAAC,IAAI,EAAE,GAAG,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC5G,aAAa,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IAC9B,YAAY,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IAE7B,mEAAmE;IACnE,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE;QACpC,MAAM,GAAG,GAAG,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;QACnC,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,OAAO,KAAK,CAAC;QAChC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAAC,OAAO,IAAI,CAAC;IAC7B,CAAC,CAAC,CAAC;IAEH,MAAM,MAAM,GAAG;QACb,KAAK,EAAE,OAAO,CAAC,MAAM;QACrB,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,cAAc,KAAK,MAAM,CAAC,CAAC,MAAM;QAC/D,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,cAAc,KAAK,OAAO,CAAC,CAAC,MAAM;QACjE,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,cAAc,KAAK,SAAS,CAAC,CAAC,MAAM;KACtE,CAAC;IACF,OAAO;QACL,IAAI,EAAE,IAAI;QACV,OAAO,EAAE,cAAc,CAAC,OAAO,CAAC;QAChC,QAAQ,EAAE,OAAO;QACjB,MAAM;QACN,QAAQ;QACR,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACpC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,QAAgB;IACxD,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAC/B,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,QAAQ,IAAI,CAAC,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,IAAI,IAAI,CAAC;IAC3H,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,oBAAoB,QAAQ,MAAM,MAAM,CAAC,QAAQ,CAAC,MAAM,8BAA8B,EAAE,MAAM,EAAE,CAAC;IACxI,CAAC;IACD,MAAM,KAAK,GAAG;QACZ,QAAQ,OAAO,CAAC,EAAE,OAAO,OAAO,CAAC,IAAI,KAAK,OAAO,CAAC,MAAM,WAAW,OAAO,CAAC,IAAI,IAAI;QACnF,YAAY,OAAO,CAAC,OAAO,EAAE;QAC7B,mBAAmB,OAAO,CAAC,cAAc,GAAG;QAC5C,aAAa,OAAO,CAAC,QAAQ,IAAI,MAAM,GAAG;QAC1C,OAAO,CAAC,cAAc,KAAK,OAAO;YAChC,CAAC,CAAC,mIAAmI;YACrI,CAAC,CAAC,OAAO,CAAC,cAAc,KAAK,MAAM;gBACjC,CAAC,CAAC,+EAA+E;gBACjF,CAAC,CAAC,wHAAwH;KAC/H,CAAC;IACF,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC;AAC3D,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,MAAkB;IAC7C,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,CAAC,IAAI,CAAC,aAAa,MAAM,CAAC,IAAI,KAAK,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC;IAC1D,KAAK,CAAC,IAAI,CAAC,KAAK,MAAM,CAAC,MAAM,CAAC,KAAK,aAAa,MAAM,CAAC,MAAM,CAAC,IAAI,UAAU,MAAM,CAAC,MAAM,CAAC,KAAK,WAAW,MAAM,CAAC,MAAM,CAAC,OAAO,UAAU,CAAC,CAAC;IAC3I,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QAChC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,cAAc,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;QAC5D,KAAK,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;QACxD,IAAI,CAAC,CAAC,KAAK,CAAC,MAAM;YAAE,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACrE,KAAK,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC9C,CAAC;IACD,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,QAAQ;QAAE,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;IAC7D,IAAI,MAAM,CAAC,OAAO,KAAK,OAAO;QAAE,KAAK,CAAC,IAAI,CAAC,mDAAmD,CAAC,CAAC;SAC3F,IAAI,MAAM,CAAC,OAAO,KAAK,QAAQ;QAAE,KAAK,CAAC,IAAI,CAAC,gDAAgD,CAAC,CAAC;;QAC9F,KAAK,CAAC,IAAI,CAAC,wKAAwK,CAAC,CAAC;IAC1L,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,OAAO,EAAE,QAAQ,EAAE,CAAC"}
@@ -0,0 +1,2 @@
1
+ export * from "./types.js";
2
+ export * from "./core.js";
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export * from "./types.js";
2
+ export * from "./core.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC"}
package/dist/mcp.d.ts ADDED
@@ -0,0 +1 @@
1
+ export {};
package/dist/mcp.js ADDED
@@ -0,0 +1,26 @@
1
+ /**
2
+ * hookcheck MCP server. Tools: scan_hooks, explain_hook.
3
+ */
4
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+ import { z } from "zod";
7
+ import { explainHook, scanHooks, VERSION } from "./core.js";
8
+ const server = new McpServer({ name: "hookcheck", version: VERSION });
9
+ server.tool("scan_hooks", "Scan a local repo directory for auto-exec hooks: npm install scripts, Claude Code settings hooks, VS Code folderOpen tasks, husky, lefthook, pre-commit, Cursor and Codex configs, MCP autostart configs, and GitHub workflow run steps. Classifies each hook SAFE, RISKY, or UNKNOWN and returns a CLEAN, REVIEW, or COMPROMISED-PATTERN verdict with evidence per finding. Deterministic, local only, no network.", { path: z.string().describe("Local directory to scan, e.g. /home/user/project") }, async ({ path }) => {
10
+ const report = scanHooks(path);
11
+ return { content: [{ type: "text", text: JSON.stringify(report, null, 2) }] };
12
+ });
13
+ server.tool("explain_hook", "Explain one hook found by scan_hooks: its command, classification, rules that fired, and what the classification means. Pass the hook id (e.g. package.json:postinstall) or its file path.", {
14
+ path: z.string().describe("Local directory to scan"),
15
+ hookId: z.string().optional().describe("Hook id from a scan report, e.g. package.json:postinstall"),
16
+ file: z.string().optional().describe("File path of the hook as an alternative selector"),
17
+ }, async ({ path, hookId, file }) => {
18
+ const selector = hookId ?? file ?? "";
19
+ const result = explainHook(path, selector);
20
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
21
+ });
22
+ async function main() {
23
+ await server.connect(new StdioServerTransport());
24
+ }
25
+ void main();
26
+ //# sourceMappingURL=mcp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp.js","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAE5D,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;AAEtE,MAAM,CAAC,IAAI,CACT,YAAY,EACZ,qZAAqZ,EACrZ,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,kDAAkD,CAAC,EAAE,EACjF,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;IACjB,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAC/B,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;AACzF,CAAC,CACF,CAAC;AAEF,MAAM,CAAC,IAAI,CACT,cAAc,EACd,4LAA4L,EAC5L;IACE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,yBAAyB,CAAC;IACpD,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,2DAA2D,CAAC;IACnG,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,kDAAkD,CAAC;CACzF,EACD,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE;IAC/B,MAAM,QAAQ,GAAG,MAAM,IAAI,IAAI,IAAI,EAAE,CAAC;IACtC,MAAM,MAAM,GAAG,WAAW,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IAC3C,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;AACzF,CAAC,CACF,CAAC;AAEF,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,oBAAoB,EAAE,CAAC,CAAC;AACnD,CAAC;AAED,KAAK,IAAI,EAAE,CAAC"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * hookcheck types.
3
+ *
4
+ * A HookFinding is one auto-exec hook discovered in a repo: where it lives,
5
+ * the exact command text, how it was classified, and the rule ids plus
6
+ * evidence that produced that classification. Nothing is inferred silently.
7
+ */
8
+ export type Classification = "SAFE" | "RISKY" | "UNKNOWN";
9
+ export type Verdict = "CLEAN" | "REVIEW" | "COMPROMISED-PATTERN";
10
+ export type HookSource = "package.json" | "claude-settings" | "vscode-tasks" | "husky" | "lefthook" | "pre-commit" | "cursor" | "codex" | "mcp-config" | "github-workflow" | "githooks";
11
+ export interface HookFinding {
12
+ /** Stable id, e.g. "package.json:postinstall" or ".claude/settings.json:SessionStart:0". */
13
+ id: string;
14
+ /** Repo relative file path the hook was found in. */
15
+ file: string;
16
+ source: HookSource;
17
+ /** Hook name or event, e.g. postinstall, SessionStart, folderOpen. */
18
+ hook: string;
19
+ /** Exact command text that would auto-execute. */
20
+ command: string;
21
+ classification: Classification;
22
+ /** Rule ids that fired, e.g. ["NET-FETCH", "CRED-ACCESS"]. Empty for SAFE. */
23
+ rules: string[];
24
+ /** Human readable evidence for the classification. */
25
+ evidence: string;
26
+ }
27
+ export interface ScanReport {
28
+ path: string;
29
+ verdict: Verdict;
30
+ findings: HookFinding[];
31
+ counts: {
32
+ total: number;
33
+ safe: number;
34
+ risky: number;
35
+ unknown: number;
36
+ };
37
+ warnings: string[];
38
+ scannedAt: string;
39
+ }
40
+ export interface ExplainResult {
41
+ finding: HookFinding | null;
42
+ explanation: string;
43
+ report: ScanReport;
44
+ }
package/dist/types.js ADDED
@@ -0,0 +1,9 @@
1
+ /**
2
+ * hookcheck types.
3
+ *
4
+ * A HookFinding is one auto-exec hook discovered in a repo: where it lives,
5
+ * the exact command text, how it was classified, and the rule ids plus
6
+ * evidence that produced that classification. Nothing is inferred silently.
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
package/package.json CHANGED
@@ -1,6 +1 @@
1
- {
2
- "name": "@reneturcios/hookcheck",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
1
+ {"bin":{"hookcheck":"dist/cli.js"},"dependencies":{"@modelcontextprotocol/sdk":"^1","zod":"^3"},"description":"AI persistence hook auditor: scan a repo for auto-exec hooks (npm install scripts, Claude Code hooks, VS Code folderOpen tasks, husky, MCP autostart) and flag Shai-Hulud style persistence patterns.","devDependencies":{"@types/node":"^20","tsx":"^4","typescript":"^5"},"engines":{"node":">=20"},"files":["dist","server.json"],"homepage":"https://github.com/CloudCorpRecords/hookcheck#readme","license":"MIT","mcpName":"io.github.CloudCorpRecords/hookcheck","name":"@reneturcios/hookcheck","private":false,"publishConfig":{"access":"public"},"repository":{"type":"git","url":"git+https://github.com/CloudCorpRecords/hookcheck.git"},"scripts":{"build":"tsc","dev":"tsx src/cli.ts","test":"tsx --test test/*.test.ts"},"type":"module","version":"0.1.0"}
package/server.json ADDED
@@ -0,0 +1 @@
1
+ {"$schema":"https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json","description":"AI persistence hook auditor: scan a repo for auto-exec hooks and flag Shai-Hulud style persistence patterns.","name":"io.github.CloudCorpRecords/hookcheck","packages":[{"identifier":"@reneturcios/hookcheck","packageArguments":[{"type":"positional","value":"mcp"}],"registryBaseUrl":"https://registry.npmjs.org","registryType":"npm","runtimeHint":"npx","transport":{"type":"stdio"},"version":"0.1.0"},{"identifier":"ghcr.io/cloudcorprecords/hookcheck:0.1.0","packageArguments":[{"type":"positional","value":"mcp"}],"registryType":"oci","runtimeArguments":[{"name":"--rm","type":"named"},{"name":"-i","type":"named"}],"runtimeHint":"docker","transport":{"type":"stdio"}}],"repository":{"source":"github","url":"https://github.com/CloudCorpRecords/hookcheck"},"version":"0.1.0"}