@posthog/wizard 2.38.2 → 2.40.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/README.md +70 -13
- package/dist/{add-mcp-server-to-clients-CHSzsAG8.js → add-mcp-server-to-clients-Dtvqw5DO.js} +5 -5
- package/dist/{add-mcp-server-to-clients-CHSzsAG8.js.map → add-mcp-server-to-clients-Dtvqw5DO.js.map} +1 -1
- package/dist/{agent-interface-DExARWay.js → agent-interface-BTVAlvLW.js} +46 -623
- package/dist/agent-interface-BTVAlvLW.js.map +1 -0
- package/dist/{agent-runner-CMfTmM3B.js → agent-runner-CqNL9x2D.js} +36 -19
- package/dist/agent-runner-CqNL9x2D.js.map +1 -0
- package/dist/analytics-D6RSYOPO.js +2 -0
- package/dist/{analytics-Ciob-x8a.js → analytics-__4kRqZr.js} +3 -2
- package/dist/analytics-__4kRqZr.js.map +1 -0
- package/dist/{api-T_WHs3qZ.js → api-D0RqJPWO.js} +3 -3
- package/dist/{api-T_WHs3qZ.js.map → api-D0RqJPWO.js.map} +1 -1
- package/dist/bin.js +387 -105
- package/dist/bin.js.map +1 -1
- package/dist/{ci-install-DJN5aT7W.js → ci-install-SXJrlScZ.js} +4 -4
- package/dist/{ci-install-DJN5aT7W.js.map → ci-install-SXJrlScZ.js.map} +1 -1
- package/dist/{debug-D1lORUFs.js → debug-D_mboDpg.js} +38 -11
- package/dist/debug-D_mboDpg.js.map +1 -0
- package/dist/{debug-Cne-cLxd.js → debug-dh-I1XvJ.js} +1 -1
- package/dist/{defaults-a0e6IRjP.js → defaults-BxiMLkQY.js} +1 -1
- package/dist/{defaults-a0e6IRjP.js.map → defaults-BxiMLkQY.js.map} +1 -1
- package/dist/{env-api-key-DEl3LJBv.js → env-api-key-DDVkPQmo.js} +1 -1
- package/dist/{env-api-key-DEl3LJBv.js.map → env-api-key-DDVkPQmo.js.map} +1 -1
- package/dist/{environment-BEsDCA2q.js → environment-4zKc7kWr.js} +3 -3
- package/dist/{environment-BEsDCA2q.js.map → environment-4zKc7kWr.js.map} +1 -1
- package/dist/{file-utils-Cn3dzlo-.js → file-utils-Cb6HLPhu.js} +2 -2
- package/dist/{file-utils-Cn3dzlo-.js.map → file-utils-Cb6HLPhu.js.map} +1 -1
- package/dist/{headless-ui-DGunpK-s.js → headless-ui-CjdRKkTG.js} +2 -2
- package/dist/{headless-ui-DGunpK-s.js.map → headless-ui-CjdRKkTG.js.map} +1 -1
- package/dist/{host-resolution-BnUJZKId.js → host-resolution-Czw5O33G.js} +6 -6
- package/dist/{host-resolution-BnUJZKId.js.map → host-resolution-Czw5O33G.js.map} +1 -1
- package/dist/{interactive-BB2IDSWP.js → interactive-C3Ty8H5M.js} +2 -2
- package/dist/{interactive-BB2IDSWP.js.map → interactive-C3Ty8H5M.js.map} +1 -1
- package/dist/{mcp-CgA5vyiS.js → mcp-B_utnKAq.js} +2 -2
- package/dist/{mcp-CgA5vyiS.js.map → mcp-B_utnKAq.js.map} +1 -1
- package/dist/{mcp-prompt-streaming-CGAkq9qR.js → mcp-prompt-streaming-BzFu8dP6.js} +5 -5
- package/dist/mcp-prompt-streaming-BzFu8dP6.js.map +1 -0
- package/dist/{non-interactive-Dko5F3vh.js → non-interactive-B2a00UId.js} +2 -2
- package/dist/{non-interactive-Dko5F3vh.js.map → non-interactive-B2a00UId.js.map} +1 -1
- package/dist/{package-manager-WKKQADHM.js → package-manager-CRYusNaP.js} +2 -2
- package/dist/{package-manager-WKKQADHM.js.map → package-manager-CRYusNaP.js.map} +1 -1
- package/dist/{playground-UtT6zbjE.js → playground-CVIGzLGh.js} +5 -5
- package/dist/{playground-UtT6zbjE.js.map → playground-CVIGzLGh.js.map} +1 -1
- package/dist/{posthog-BESrI6-T.js → posthog-BHbtnNQE.js} +1 -1
- package/dist/{posthog-BESrI6-T.js.map → posthog-BHbtnNQE.js.map} +1 -1
- package/dist/{posthog-integration-CWXSng0W.js → posthog-integration-D2S9mCAv.js} +16 -15
- package/dist/{posthog-integration-CWXSng0W.js.map → posthog-integration-D2S9mCAv.js.map} +1 -1
- package/dist/{provisioning-CgomM26T.js → provisioning-B1KoQFFK.js} +4 -4
- package/dist/{provisioning-CgomM26T.js.map → provisioning-B1KoQFFK.js.map} +1 -1
- package/dist/{registry-BQIX-25O.js → registry-CgrvMaYt.js} +5 -4
- package/dist/{registry-BQIX-25O.js.map → registry-CgrvMaYt.js.map} +1 -1
- package/dist/security-DX5ey9g8.js +204 -0
- package/dist/security-DX5ey9g8.js.map +1 -0
- package/dist/{setup-utils-DLGNk8T6.js → setup-utils-CUeGnxBk.js} +9 -9
- package/dist/{setup-utils-DLGNk8T6.js.map → setup-utils-CUeGnxBk.js.map} +1 -1
- package/dist/{start-tui-BjCKghQq.js → start-tui-BC3yp_wW.js} +29 -27
- package/dist/start-tui-BC3yp_wW.js.map +1 -0
- package/dist/{steps-XjV9CiqH.js → steps-5YGQK7la.js} +7 -7
- package/dist/{steps-XjV9CiqH.js.map → steps-5YGQK7la.js.map} +1 -1
- package/dist/{store-CDIxU0rr.js → store-DQ0-oE_Y.js} +4 -4
- package/dist/{store-CDIxU0rr.js.map → store-DQ0-oE_Y.js.map} +1 -1
- package/dist/{subagent-S5Yh16IJ.js → subagent-DkTSX2ew.js} +2 -2
- package/dist/{subagent-S5Yh16IJ.js.map → subagent-DkTSX2ew.js.map} +1 -1
- package/dist/{task-stream-rC4IEUHC.js → task-stream-d-R9J0g4.js} +2 -2
- package/dist/{task-stream-rC4IEUHC.js.map → task-stream-d-R9J0g4.js.map} +1 -1
- package/dist/{tasks-wbjOdifO.js → tasks-SbUmyQPS.js} +2 -2
- package/dist/{tasks-wbjOdifO.js.map → tasks-SbUmyQPS.js.map} +1 -1
- package/dist/{telemetry-BfnR7gtf.js → telemetry-_7NXwx7o.js} +3 -3
- package/dist/{telemetry-BfnR7gtf.js.map → telemetry-_7NXwx7o.js.map} +1 -1
- package/dist/{terminal-BvRwVeQp.js → terminal-NhR2Seu_.js} +13 -12
- package/dist/{terminal-BvRwVeQp.js.map → terminal-NhR2Seu_.js.map} +1 -1
- package/dist/{tools-CbtYlZV4.js → tools-WDPzlrXc.js} +4 -4
- package/dist/{tools-CbtYlZV4.js.map → tools-WDPzlrXc.js.map} +1 -1
- package/dist/types-evL_rPob.js +46 -0
- package/dist/types-evL_rPob.js.map +1 -0
- package/dist/{urls-CilgPbS-.js → urls-DYIMfv29.js} +2 -2
- package/dist/{urls-CilgPbS-.js.map → urls-DYIMfv29.js.map} +1 -1
- package/dist/warlock-smoke-test.ts +165 -0
- package/dist/{wizard-abort-Cp5Y7f8C.js → wizard-abort-CWaKUNMI.js} +3 -3
- package/dist/{wizard-abort-Cp5Y7f8C.js.map → wizard-abort-CWaKUNMI.js.map} +1 -1
- package/dist/{wizard-abort-DHrEGqaC.js → wizard-abort-DGM_sAA9.js} +1 -1
- package/dist/{wizard-tools-DQTfAzSV.js → wizard-tools-CrpRaYkE.js} +26 -50
- package/dist/wizard-tools-CrpRaYkE.js.map +1 -0
- package/dist/yara-hooks-BPQ3VE-l.js +635 -0
- package/dist/yara-hooks-BPQ3VE-l.js.map +1 -0
- package/dist/yara-hooks-yy8OLJ43.js +2 -0
- package/package.json +4 -3
- package/dist/agent-interface-DExARWay.js.map +0 -1
- package/dist/agent-runner-CMfTmM3B.js.map +0 -1
- package/dist/analytics-Ciob-x8a.js.map +0 -1
- package/dist/analytics-DbfiRvPS.js +0 -2
- package/dist/debug-D1lORUFs.js.map +0 -1
- package/dist/mcp-prompt-streaming-CGAkq9qR.js.map +0 -1
- package/dist/security-pJOYY5LO.js +0 -431
- package/dist/security-pJOYY5LO.js.map +0 -1
- package/dist/start-tui-BjCKghQq.js.map +0 -1
- package/dist/wizard-tools-DQTfAzSV.js.map +0 -1
|
@@ -0,0 +1,635 @@
|
|
|
1
|
+
import { f as getUI, s as logToFile, u as WIZARD_YARA_REPORT_FILE } from "./debug-D_mboDpg.js";
|
|
2
|
+
import { n as analytics } from "./analytics-__4kRqZr.js";
|
|
3
|
+
import { r as AUDIT_REPORT_FILE } from "./types-evL_rPob.js";
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import fg from "fast-glob";
|
|
7
|
+
import { createHash } from "crypto";
|
|
8
|
+
//#region src/lib/skill-install.ts
|
|
9
|
+
/**
|
|
10
|
+
* Check if command is a PostHog skill installation from MCP.
|
|
11
|
+
* We control the MCP server, so we only need to verify:
|
|
12
|
+
* 1. It installs to .claude/skills/
|
|
13
|
+
* 2. It downloads from our GitHub releases or localhost (dev)
|
|
14
|
+
*
|
|
15
|
+
* Extracted to its own module to avoid a circular dependency
|
|
16
|
+
* between agent-interface.ts and yara-hooks.ts.
|
|
17
|
+
*/
|
|
18
|
+
function isSkillInstallCommand(command) {
|
|
19
|
+
if (!command.startsWith("mkdir -p .claude/skills/")) return false;
|
|
20
|
+
const urlMatch = command.match(/curl -sL ['"]([^'"]+)['"]/);
|
|
21
|
+
if (!urlMatch) return false;
|
|
22
|
+
const url = urlMatch[1];
|
|
23
|
+
return url.startsWith("https://github.com/PostHog/context-mill/releases/") || /^http:\/\/localhost:\d+\//.test(url);
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region src/lib/programs/events-audit/constants.ts
|
|
27
|
+
/**
|
|
28
|
+
* Leaf-level constants for the events-audit program.
|
|
29
|
+
*
|
|
30
|
+
* Kept separate from `index.ts` so files like `yara-hooks.ts` can import
|
|
31
|
+
* the filename constants without dragging in `index.ts`'s heavier imports
|
|
32
|
+
* (agent-runner, audit/seed, etc.) — which can create import cycles.
|
|
33
|
+
*/
|
|
34
|
+
const SETUP_REPORT_FILE = "posthog-events-audit-report.md";
|
|
35
|
+
const EVENT_INVENTORY_FILE = ".posthog-events-inventory.json";
|
|
36
|
+
/** Per-part filename pattern emitted by events-audit subagents (e.g. `.posthog-events-inventory.part-3.json`). */
|
|
37
|
+
const EVENT_INVENTORY_PART_PATTERN = /^\.posthog-events-inventory\.part-\d+\.json$/;
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region src/lib/programs/posthog-integration/constants.ts
|
|
40
|
+
/**
|
|
41
|
+
* Leaf-level constants for the posthog-integration program.
|
|
42
|
+
*
|
|
43
|
+
* Kept separate from `index.ts` so files like `yara-hooks.ts` can import
|
|
44
|
+
* the filename constants without dragging in `index.ts`'s heavier imports
|
|
45
|
+
* (agent-interface, framework-config, etc.) — which would create an import
|
|
46
|
+
* cycle through agent-interface → yara-hooks.
|
|
47
|
+
*/
|
|
48
|
+
const EVENT_PLAN_FILE = ".posthog-events.json";
|
|
49
|
+
//#endregion
|
|
50
|
+
//#region src/lib/yara-hooks.ts
|
|
51
|
+
/**
|
|
52
|
+
* YARA hook wiring for the Claude Agent SDK.
|
|
53
|
+
*
|
|
54
|
+
* Creates PreToolUse and PostToolUse hook callback arrays that integrate the
|
|
55
|
+
* real @posthog/warlock security scanner (YARA-X via WASM) into the wizard's
|
|
56
|
+
* agent loop. These hooks are registered in the SDK's query() options alongside
|
|
57
|
+
* the existing Stop hook.
|
|
58
|
+
*
|
|
59
|
+
* PreToolUse hooks block dangerous commands before execution. PostToolUse hooks
|
|
60
|
+
* detect violations in written code and prompt injection in read content, and
|
|
61
|
+
* scan context-mill skill downloads.
|
|
62
|
+
*
|
|
63
|
+
* Warlock owns the rules; this file owns the *policy* — how a match maps to a
|
|
64
|
+
* block / revert / terminate response, plus the optional LLM triage pass that
|
|
65
|
+
* filters false positives before we act.
|
|
66
|
+
*
|
|
67
|
+
* Naming: "yara" is the technique — scanning content against YARA rules — so the
|
|
68
|
+
* wizard-side wiring keeps that name. "warlock" is the engine package that runs
|
|
69
|
+
* the rules. Both names showing up here is intentional.
|
|
70
|
+
*
|
|
71
|
+
* This is Layer 2 (L2) in the wizard's defense-in-depth model, complementing the
|
|
72
|
+
* prompt-based commandments (L0) and the canUseTool() allowlist (L1).
|
|
73
|
+
*/
|
|
74
|
+
let warlockModulePromise = null;
|
|
75
|
+
function getWarlock() {
|
|
76
|
+
if (!warlockModulePromise) warlockModulePromise = import("@posthog/warlock").catch((err) => {
|
|
77
|
+
warlockModulePromise = null;
|
|
78
|
+
throw err;
|
|
79
|
+
});
|
|
80
|
+
return warlockModulePromise;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Pay the WASM-init + rule-compile cost up front, off the hook path, so the
|
|
84
|
+
* first real tool-call scan doesn't eat cold-start under a hook timeout.
|
|
85
|
+
* Best-effort: a failure here is non-fatal — hooks still fail closed per scan.
|
|
86
|
+
*/
|
|
87
|
+
async function prewarmYaraScanner() {
|
|
88
|
+
try {
|
|
89
|
+
await (await getWarlock()).scan("");
|
|
90
|
+
logToFile("[YARA] warlock pre-warmed");
|
|
91
|
+
} catch (err) {
|
|
92
|
+
logToFile("[YARA] warlock pre-warm failed:", err);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
let scanCount = 0;
|
|
96
|
+
const scanViolations = [];
|
|
97
|
+
function recordScan() {
|
|
98
|
+
scanCount++;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Log a match to the run log + PostHog, and record it in the run's scan report.
|
|
102
|
+
* Single entry point so every violation is reported consistently.
|
|
103
|
+
*/
|
|
104
|
+
function recordMatch(phase, tool, match, action) {
|
|
105
|
+
const triage = match.triage;
|
|
106
|
+
logYaraMatch(phase, tool, match, action);
|
|
107
|
+
scanViolations.push({
|
|
108
|
+
rule: match.rule,
|
|
109
|
+
severity: match.metadata.severity ?? "unknown",
|
|
110
|
+
category: match.metadata.category ?? "unknown",
|
|
111
|
+
action,
|
|
112
|
+
phase,
|
|
113
|
+
tool,
|
|
114
|
+
description: match.metadata.description ?? "",
|
|
115
|
+
triageVerdict: triage?.verdict,
|
|
116
|
+
triageReason: triage?.reason
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Scan bookkeeping for harnesses that run a scanner outside these hooks (pi):
|
|
121
|
+
* counts the scan and routes violations through `recordMatch`, so they reach
|
|
122
|
+
* the run log, per-match telemetry, and the end-of-run scan report.
|
|
123
|
+
*/
|
|
124
|
+
function recordExternalScan(phase, tool, violations, action) {
|
|
125
|
+
recordScan();
|
|
126
|
+
for (const v of violations) recordMatch(phase, tool, {
|
|
127
|
+
rule: v.rule,
|
|
128
|
+
metadata: {
|
|
129
|
+
severity: v.severity,
|
|
130
|
+
category: v.category,
|
|
131
|
+
description: v.description
|
|
132
|
+
},
|
|
133
|
+
matchedStrings: []
|
|
134
|
+
}, action);
|
|
135
|
+
}
|
|
136
|
+
/** Format the scan report summary. Returns null if no scans occurred */
|
|
137
|
+
function formatScanReport() {
|
|
138
|
+
if (scanCount === 0) return null;
|
|
139
|
+
const lines = ["", "— YARA Scanner Summary —"];
|
|
140
|
+
const violationCount = scanViolations.length;
|
|
141
|
+
const cleanCount = scanCount - violationCount;
|
|
142
|
+
lines.push(`✓ ${scanCount} tool calls scanned, ${violationCount} violation${violationCount !== 1 ? "s" : ""} detected`);
|
|
143
|
+
if (violationCount > 0) {
|
|
144
|
+
lines.push("");
|
|
145
|
+
for (const v of scanViolations) {
|
|
146
|
+
const tag = v.action.toUpperCase();
|
|
147
|
+
lines.push(` [${tag}] ${v.rule} (${v.severity.toUpperCase()}) — ${v.phase}:${v.tool}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (cleanCount > 0) {
|
|
151
|
+
lines.push("");
|
|
152
|
+
lines.push(`No violations: ✓ ${cleanCount} clean scan${cleanCount !== 1 ? "s" : ""}`);
|
|
153
|
+
}
|
|
154
|
+
lines.push("");
|
|
155
|
+
return lines.join("\n");
|
|
156
|
+
}
|
|
157
|
+
/** Write the scan report to a JSON file. Returns the file path, or null if no scans occurred. */
|
|
158
|
+
function writeScanReport() {
|
|
159
|
+
if (scanCount === 0) return null;
|
|
160
|
+
const report = {
|
|
161
|
+
summary: {
|
|
162
|
+
totalScans: scanCount,
|
|
163
|
+
violations: scanViolations.length,
|
|
164
|
+
clean: scanCount - scanViolations.length
|
|
165
|
+
},
|
|
166
|
+
violations: scanViolations
|
|
167
|
+
};
|
|
168
|
+
try {
|
|
169
|
+
fs.writeFileSync(WIZARD_YARA_REPORT_FILE, JSON.stringify(report, null, 2));
|
|
170
|
+
} catch (err) {
|
|
171
|
+
logToFile("[YARA] Failed to write scan report:", err);
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
return WIZARD_YARA_REPORT_FILE;
|
|
175
|
+
}
|
|
176
|
+
const CATEGORY_DESCRIPTIONS = {
|
|
177
|
+
prompt_injection: "hit hidden instructions in a file it read, a possible attempt to hijack the setup",
|
|
178
|
+
exfiltration: "tried to send data to an outside location",
|
|
179
|
+
destructive_operations: "tried to run a destructive command",
|
|
180
|
+
supply_chain: "tried to pull in untrusted code",
|
|
181
|
+
posthog_pii: "tried to write personal data (PII) into your project",
|
|
182
|
+
posthog_hardcoded_key: "tried to hardcode a PostHog key into your project",
|
|
183
|
+
hardcoded_secret: "tried to hardcode a secret into your project"
|
|
184
|
+
};
|
|
185
|
+
/** Most recent violation that terminated the run, or null. */
|
|
186
|
+
function lastTerminatingViolation() {
|
|
187
|
+
for (let i = scanViolations.length - 1; i >= 0; i--) if (scanViolations[i].action === "aborted") return scanViolations[i];
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Build the user-facing copy for the security abort screen: what the scanner
|
|
192
|
+
* caught and what we did about it. This screen renders only because the run was
|
|
193
|
+
* stopped, so the "what we did" half is constant. Falls back to a generic line
|
|
194
|
+
* when no specific violation was recorded (e.g. the scanner itself errored and
|
|
195
|
+
* failed closed before a match was logged).
|
|
196
|
+
*/
|
|
197
|
+
function formatYaraAbortMessage() {
|
|
198
|
+
const v = lastTerminatingViolation();
|
|
199
|
+
return `Security check stopped the setup.
|
|
200
|
+
|
|
201
|
+
The agent ${(v && CATEGORY_DESCRIPTIONS[v.category]) ?? "did something our security scanner flagged"}. We stopped the run to keep your project safe.\nNo unsafe changes were left in your project.
|
|
202
|
+
|
|
203
|
+
If this looks wrong, let us know: wizard@posthog.com`;
|
|
204
|
+
}
|
|
205
|
+
/** Timeout for scan hooks (PreToolUse, PostToolUse Write/Edit/Read/Grep) */
|
|
206
|
+
const HOOK_TIMEOUT_MS = 3e4;
|
|
207
|
+
/** Timeout for the skill install hook (filesystem I/O + multiple scans) */
|
|
208
|
+
const SKILL_SCAN_HOOK_TIMEOUT_MS = 3e4;
|
|
209
|
+
/**
|
|
210
|
+
* Chunk size for scanning (100 KB). Oversized content is scanned in
|
|
211
|
+
* overlapping chunks so coverage is complete — nothing is silently truncated.
|
|
212
|
+
* The size also bounds what a single triage call pastes into the LLM prompt
|
|
213
|
+
* (~25K tokens), so triage always sees the chunk its matches came from.
|
|
214
|
+
*/
|
|
215
|
+
const SCAN_CHUNK_SIZE = 1e5;
|
|
216
|
+
/**
|
|
217
|
+
* Overlap between adjacent chunks so a pattern straddling a chunk boundary
|
|
218
|
+
* still lands whole inside at least one chunk. YARA rule strings are at most
|
|
219
|
+
* a few hundred bytes; 4 KB is generous.
|
|
220
|
+
*/
|
|
221
|
+
const SCAN_CHUNK_OVERLAP = 4096;
|
|
222
|
+
function logYaraMatch(phase, tool, match, action) {
|
|
223
|
+
const severity = match.metadata.severity ?? "unknown";
|
|
224
|
+
const category = match.metadata.category ?? "unknown";
|
|
225
|
+
const ruleAction = match.metadata.action ?? "unknown";
|
|
226
|
+
const description = match.metadata.description ?? "";
|
|
227
|
+
logToFile(`[YARA] ${phase}:${tool} [${action.toUpperCase()}] rule "${match.rule}" (severity: ${severity}, category: ${category}, action: ${ruleAction})\n Description: ${description}`);
|
|
228
|
+
analytics.wizardCapture("yara rule matched", {
|
|
229
|
+
rule: match.rule,
|
|
230
|
+
severity: match.metadata.severity,
|
|
231
|
+
category: match.metadata.category,
|
|
232
|
+
rule_action: match.metadata.action,
|
|
233
|
+
action,
|
|
234
|
+
phase,
|
|
235
|
+
tool,
|
|
236
|
+
description: match.metadata.description,
|
|
237
|
+
triage_verdict: match.triage?.verdict
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Send the run's full scan report to PostHog so maintainers can see why a run
|
|
242
|
+
* was flagged, warned, or aborted. This is the maintainer-facing report — it is
|
|
243
|
+
* NEVER shown to the user. No-op if nothing was scanned. The free-text triage
|
|
244
|
+
* reason is omitted to avoid sending scanned content off the user's machine.
|
|
245
|
+
*/
|
|
246
|
+
function captureScanReport() {
|
|
247
|
+
if (scanCount === 0) return;
|
|
248
|
+
analytics.wizardCapture("yara scan report", {
|
|
249
|
+
total_scans: scanCount,
|
|
250
|
+
violation_count: scanViolations.length,
|
|
251
|
+
clean_count: scanCount - scanViolations.length,
|
|
252
|
+
violations: scanViolations.map((v) => ({
|
|
253
|
+
rule: v.rule,
|
|
254
|
+
severity: v.severity,
|
|
255
|
+
category: v.category,
|
|
256
|
+
action: v.action,
|
|
257
|
+
phase: v.phase,
|
|
258
|
+
tool: v.tool,
|
|
259
|
+
description: v.description,
|
|
260
|
+
triage_verdict: v.triageVerdict
|
|
261
|
+
}))
|
|
262
|
+
});
|
|
263
|
+
scanCount = 0;
|
|
264
|
+
scanViolations.length = 0;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* End-of-run flush for the scan report. Wired once at the runner seam so every
|
|
268
|
+
* harness (linear, orchestrator, and any future shape) reports without having
|
|
269
|
+
* to know warlock exists — scanning is already harness-agnostic (it runs from
|
|
270
|
+
* SDK Pre/PostToolUse hooks), and this keeps reporting at a single shared seam.
|
|
271
|
+
*
|
|
272
|
+
* Order matters: write the local --yara-report file FIRST (it reads scanCount /
|
|
273
|
+
* scanViolations), THEN send telemetry. captureScanReport() zeroes scan state,
|
|
274
|
+
* which is what makes this whole function idempotent — a second call from
|
|
275
|
+
* another termination path (e.g. finally after an abort already flushed) finds
|
|
276
|
+
* scanCount === 0 and every step no-ops.
|
|
277
|
+
*/
|
|
278
|
+
function flushScanReport(session) {
|
|
279
|
+
if (session.yaraReport) {
|
|
280
|
+
const reportPath = writeScanReport();
|
|
281
|
+
if (reportPath) {
|
|
282
|
+
const summary = formatScanReport();
|
|
283
|
+
getUI().log.info(`YARA scan report: ${reportPath}${summary ?? ""}`);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
captureScanReport();
|
|
287
|
+
}
|
|
288
|
+
const WIZARD_DOC_BASENAMES = new Set([
|
|
289
|
+
EVENT_INVENTORY_FILE,
|
|
290
|
+
SETUP_REPORT_FILE,
|
|
291
|
+
AUDIT_REPORT_FILE,
|
|
292
|
+
EVENT_PLAN_FILE
|
|
293
|
+
]);
|
|
294
|
+
const WIZARD_DOC_PATTERNS = [EVENT_INVENTORY_PART_PATTERN];
|
|
295
|
+
function isWizardDocumentationPath(filePath) {
|
|
296
|
+
if (!filePath) return false;
|
|
297
|
+
const basename = path.basename(filePath);
|
|
298
|
+
if (WIZARD_DOC_BASENAMES.has(basename)) return true;
|
|
299
|
+
return WIZARD_DOC_PATTERNS.some((re) => re.test(basename));
|
|
300
|
+
}
|
|
301
|
+
/** Identical-payload attempt at which the agent is told to move on. */
|
|
302
|
+
const REPEAT_BLOCK_MOVE_ON_ATTEMPT = 3;
|
|
303
|
+
/** Per-run tracker of YARA-blocked payloads (keyed by tool + content hash). */
|
|
304
|
+
function createRepeatBlockTracker() {
|
|
305
|
+
const counts = /* @__PURE__ */ new Map();
|
|
306
|
+
return { attempt(tool, content) {
|
|
307
|
+
const key = `${tool} ${createHash("sha256").update(content).digest("hex")}`;
|
|
308
|
+
const next = (counts.get(key) ?? 0) + 1;
|
|
309
|
+
counts.set(key, next);
|
|
310
|
+
return next;
|
|
311
|
+
} };
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* The block reason for the given attempt: the plain reason the first time,
|
|
315
|
+
* an explicit "this exact retry can never work" on the second, and a
|
|
316
|
+
* "report it and move on" instruction from the third on.
|
|
317
|
+
*/
|
|
318
|
+
function repeatBlockReason(attempt, tool, baseReason) {
|
|
319
|
+
if (attempt <= 1) return baseReason;
|
|
320
|
+
if (attempt < REPEAT_BLOCK_MOVE_ON_ATTEMPT) return `${baseReason} This exact ${tool} content was ALREADY blocked — the scan is deterministic, so retrying identical content will always block again. Change the code to remove the flagged pattern instead of retrying.`;
|
|
321
|
+
return `${baseReason} This exact ${tool} content has now been blocked ${attempt} times. STOP retrying it. Note the blocked change in the setup report and move on to the next task.`;
|
|
322
|
+
}
|
|
323
|
+
const SEVERITY_RANK = {
|
|
324
|
+
critical: 4,
|
|
325
|
+
high: 3,
|
|
326
|
+
medium: 2,
|
|
327
|
+
low: 1
|
|
328
|
+
};
|
|
329
|
+
/** Return the highest-severity match from a list of matches. */
|
|
330
|
+
function highestSeverityMatch(matches) {
|
|
331
|
+
return matches.reduce((worst, m) => (SEVERITY_RANK[m.metadata.severity ?? ""] ?? 0) > (SEVERITY_RANK[worst.metadata.severity ?? ""] ?? 0) ? m : worst);
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Keep only matches whose rule targets this content surface. Rules carry a
|
|
335
|
+
* `scan_context` of 'command' | 'input' | 'output'. An undefined context is
|
|
336
|
+
* treated as "applies everywhere" (fail-safe — a rule that forgets the tag
|
|
337
|
+
* still gets enforced rather than silently skipped).
|
|
338
|
+
*/
|
|
339
|
+
function matchesForContext(matches, ctx) {
|
|
340
|
+
return matches.filter((m) => {
|
|
341
|
+
const c = m.metadata.scan_context;
|
|
342
|
+
return c === ctx || c === void 0;
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Drop false positives via warlock's LLM triage. Fail-closed: if no provider is
|
|
347
|
+
* available, or the triage call throws, every match is treated as real — we
|
|
348
|
+
* never silently suppress a flagged match.
|
|
349
|
+
*
|
|
350
|
+
* Every overruled match is reported to PostHog (rule metadata only, never the
|
|
351
|
+
* free-text reason) so maintainers can alert on triage-overrule patterns — the
|
|
352
|
+
* signal that someone is either tripping a noisy rule or trying to talk the
|
|
353
|
+
* triage model out of a real finding.
|
|
354
|
+
*/
|
|
355
|
+
async function triageFilter(content, matches, ctx, llmProvider) {
|
|
356
|
+
if (matches.length === 0) return [];
|
|
357
|
+
if (!llmProvider) {
|
|
358
|
+
logToFile(`[YARA] triage skipped (no provider) — treating ${matches.length} match(es) as real`);
|
|
359
|
+
return matches;
|
|
360
|
+
}
|
|
361
|
+
try {
|
|
362
|
+
const triaged = await (await getWarlock()).triageMatches(content, matches, llmProvider);
|
|
363
|
+
const kept = triaged.filter((m) => m.triage.verdict === "true_positive");
|
|
364
|
+
for (const m of triaged) {
|
|
365
|
+
if (m.triage.verdict === "true_positive") continue;
|
|
366
|
+
logToFile(`[YARA] triage overruled rule "${m.rule}" (${m.metadata.severity ?? "unknown"}) — not acting on it`);
|
|
367
|
+
analytics.wizardCapture("yara triage overruled", {
|
|
368
|
+
rule: m.rule,
|
|
369
|
+
severity: m.metadata.severity,
|
|
370
|
+
category: m.metadata.category,
|
|
371
|
+
scan_context: ctx
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
logToFile(`[YARA] triage: ${matches.length} flagged → ${kept.length} kept as real`);
|
|
375
|
+
return kept;
|
|
376
|
+
} catch (err) {
|
|
377
|
+
logToFile("[YARA] triage failed — treating all matches as real:", err);
|
|
378
|
+
return matches;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Split oversized content into overlapping chunks so the scanner covers all
|
|
383
|
+
* of it. Content that fits in one chunk is returned as-is.
|
|
384
|
+
*/
|
|
385
|
+
function chunkContent(content) {
|
|
386
|
+
if (content.length <= SCAN_CHUNK_SIZE) return [content];
|
|
387
|
+
const chunks = [];
|
|
388
|
+
const step = SCAN_CHUNK_SIZE - SCAN_CHUNK_OVERLAP;
|
|
389
|
+
for (let start = 0; start < content.length; start += step) {
|
|
390
|
+
chunks.push(content.slice(start, start + SCAN_CHUNK_SIZE));
|
|
391
|
+
if (start + SCAN_CHUNK_SIZE >= content.length) break;
|
|
392
|
+
}
|
|
393
|
+
return chunks;
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Scan content against warlock rules — chunked when oversized, so nothing is
|
|
397
|
+
* skipped — and keep only matches for this content surface. Chunks scan
|
|
398
|
+
* sequentially (the WASM engine is single-threaded; parallelism buys nothing).
|
|
399
|
+
* Returns each flagged chunk with its matches so triage can later judge every
|
|
400
|
+
* match against the exact content it came from.
|
|
401
|
+
*/
|
|
402
|
+
async function scanForContext(content, ctx) {
|
|
403
|
+
const warlock = await getWarlock();
|
|
404
|
+
const chunks = chunkContent(content);
|
|
405
|
+
if (chunks.length > 1) {
|
|
406
|
+
logToFile(`[YARA] content is ${content.length} chars — scanning ${chunks.length} overlapping chunks`);
|
|
407
|
+
analytics.wizardCapture("yara scan chunked", {
|
|
408
|
+
content_length: content.length,
|
|
409
|
+
chunk_count: chunks.length,
|
|
410
|
+
scan_context: ctx
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
const flagged = [];
|
|
414
|
+
for (const chunk of chunks) {
|
|
415
|
+
const result = await warlock.scan(chunk);
|
|
416
|
+
if (!result.matched) continue;
|
|
417
|
+
const matches = matchesForContext(result.matches, ctx);
|
|
418
|
+
if (matches.length === 0) continue;
|
|
419
|
+
flagged.push({
|
|
420
|
+
chunk,
|
|
421
|
+
matches
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
return flagged;
|
|
425
|
+
}
|
|
426
|
+
/** The overlap between chunks can surface the same rule twice; count it once. */
|
|
427
|
+
function dedupeByRule(matches) {
|
|
428
|
+
const seen = /* @__PURE__ */ new Set();
|
|
429
|
+
return matches.filter((m) => {
|
|
430
|
+
if (seen.has(m.rule)) return false;
|
|
431
|
+
seen.add(m.rule);
|
|
432
|
+
return true;
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Triage each flagged chunk against its own content — the evidence is always
|
|
437
|
+
* inside the window the LLM sees. Triage calls run in parallel (each is just
|
|
438
|
+
* an HTTP round trip) so N flagged chunks cost ~1× triage latency, not N×.
|
|
439
|
+
*/
|
|
440
|
+
async function triageFlagged(flagged, ctx, llmProvider) {
|
|
441
|
+
return dedupeByRule((await Promise.all(flagged.map(({ chunk, matches }) => triageFilter(chunk, matches, ctx, llmProvider)))).flat());
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Scan content, filter to the relevant context, triage. Returns real matches.
|
|
445
|
+
* Exported for harnesses that run their own hook loop (pi) so every harness
|
|
446
|
+
* scans through the same warlock engine, chunking, and triage policy.
|
|
447
|
+
*/
|
|
448
|
+
async function scanAndTriage(content, ctx, llmProvider) {
|
|
449
|
+
return triageFlagged(await scanForContext(content, ctx), ctx, llmProvider);
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Create PreToolUse hook matchers for YARA scanning.
|
|
453
|
+
* Scans Bash commands before execution for exfiltration, destructive
|
|
454
|
+
* operations, and supply chain violations ('command'-context rules).
|
|
455
|
+
*/
|
|
456
|
+
function createPreToolUseYaraHooks(_llmProvider) {
|
|
457
|
+
const repeatTracker = createRepeatBlockTracker();
|
|
458
|
+
return [{
|
|
459
|
+
hooks: [async (input) => {
|
|
460
|
+
try {
|
|
461
|
+
if (input.tool_name !== "Bash") return {};
|
|
462
|
+
const toolInput = input.tool_input;
|
|
463
|
+
const command = typeof toolInput?.command === "string" ? toolInput.command : "";
|
|
464
|
+
if (!command) return {};
|
|
465
|
+
recordScan();
|
|
466
|
+
const matches = await scanAndTriage(command, "command", void 0);
|
|
467
|
+
if (matches.length === 0) return {};
|
|
468
|
+
const match = highestSeverityMatch(matches);
|
|
469
|
+
recordMatch("PreToolUse", "Bash", match, "blocked");
|
|
470
|
+
return {
|
|
471
|
+
decision: "block",
|
|
472
|
+
reason: repeatBlockReason(repeatTracker.attempt("Bash", command), "Bash", `[YARA] ${match.rule}: ${match.metadata.description ?? "security policy violation"}. Command blocked for security.`)
|
|
473
|
+
};
|
|
474
|
+
} catch (error) {
|
|
475
|
+
logToFile("[YARA] PreToolUse hook error:", error);
|
|
476
|
+
return {
|
|
477
|
+
decision: "block",
|
|
478
|
+
reason: "[YARA] Scanner error — command blocked as a precaution."
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
}],
|
|
482
|
+
timeout: HOOK_TIMEOUT_MS
|
|
483
|
+
}];
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Create PostToolUse hook matchers for YARA scanning.
|
|
487
|
+
*
|
|
488
|
+
* Three matchers:
|
|
489
|
+
* 1. Write/Edit — scan written content ('output'-context rules: PII, secrets)
|
|
490
|
+
* 2. Read/Grep — scan read content ('input'-context rules: prompt injection)
|
|
491
|
+
* 3. Bash (skill install) — scan downloaded skill files for poisoned content
|
|
492
|
+
*
|
|
493
|
+
* `onTerminate` is invoked on the terminal paths (critical prompt injection in
|
|
494
|
+
* read content, a poisoned skill, or a scanner error under fail-closed). It is
|
|
495
|
+
* what ACTUALLY stops the run — returning `stopReason` from a PostToolUse hook
|
|
496
|
+
* is not honored by the SDK, so the caller wires this to the run's
|
|
497
|
+
* AbortController (see agent-interface).
|
|
498
|
+
*/
|
|
499
|
+
function createPostToolUseYaraHooks(llmProvider, onTerminate) {
|
|
500
|
+
return [
|
|
501
|
+
{
|
|
502
|
+
hooks: [async (input) => {
|
|
503
|
+
try {
|
|
504
|
+
const toolName = input.tool_name;
|
|
505
|
+
if (toolName !== "Write" && toolName !== "Edit") return {};
|
|
506
|
+
const toolInput = input.tool_input;
|
|
507
|
+
const content = toolName === "Write" ? toolInput?.content ?? "" : toolInput?.new_string ?? "";
|
|
508
|
+
if (!content) return {};
|
|
509
|
+
recordScan();
|
|
510
|
+
const flagged = await scanForContext(content, "output");
|
|
511
|
+
if (flagged.length === 0) return {};
|
|
512
|
+
const filePath = toolInput?.file_path;
|
|
513
|
+
const activeFlagged = isWizardDocumentationPath(filePath) ? flagged.map(({ chunk, matches }) => ({
|
|
514
|
+
chunk,
|
|
515
|
+
matches: matches.filter((m) => m.metadata.category !== "posthog_pii")
|
|
516
|
+
})).filter(({ matches }) => matches.length > 0) : flagged;
|
|
517
|
+
if (activeFlagged.length === 0) {
|
|
518
|
+
logToFile(`[YARA] posthog_pii match suppressed on wizard doc ${path.basename(filePath ?? "")} (rule: ${flagged[0]?.matches[0]?.rule})`);
|
|
519
|
+
return {};
|
|
520
|
+
}
|
|
521
|
+
const matches = await triageFlagged(activeFlagged, "output", llmProvider);
|
|
522
|
+
if (matches.length === 0) return {};
|
|
523
|
+
const match = highestSeverityMatch(matches);
|
|
524
|
+
recordMatch("PostToolUse", toolName, match, "reverted");
|
|
525
|
+
return { hookSpecificOutput: {
|
|
526
|
+
hookEventName: "PostToolUse",
|
|
527
|
+
additionalContext: `[YARA VIOLATION] ${match.rule}: ${match.metadata.description ?? ""}. You MUST revert this change immediately. The content you just wrote violates security policy.`
|
|
528
|
+
} };
|
|
529
|
+
} catch (error) {
|
|
530
|
+
logToFile("[YARA] PostToolUse Write/Edit hook error:", error);
|
|
531
|
+
const reason = "[YARA] Scanner error while scanning written content — session terminated as a precaution.";
|
|
532
|
+
onTerminate(reason);
|
|
533
|
+
return { stopReason: reason };
|
|
534
|
+
}
|
|
535
|
+
}],
|
|
536
|
+
timeout: HOOK_TIMEOUT_MS
|
|
537
|
+
},
|
|
538
|
+
{
|
|
539
|
+
hooks: [async (input) => {
|
|
540
|
+
try {
|
|
541
|
+
const toolName = input.tool_name;
|
|
542
|
+
if (toolName !== "Read" && toolName !== "Grep") return {};
|
|
543
|
+
const toolResponse = input.tool_response;
|
|
544
|
+
if (toolResponse == null) return {};
|
|
545
|
+
const content = typeof toolResponse === "string" ? toolResponse : JSON.stringify(toolResponse);
|
|
546
|
+
if (!content) return {};
|
|
547
|
+
recordScan();
|
|
548
|
+
const matches = await scanAndTriage(content, "input", llmProvider);
|
|
549
|
+
if (matches.length === 0) return {};
|
|
550
|
+
const match = highestSeverityMatch(matches);
|
|
551
|
+
if (match.metadata.severity === "critical" || match.metadata.action === "block") {
|
|
552
|
+
recordMatch("PostToolUse", toolName, match, "aborted");
|
|
553
|
+
const reason = `[YARA CRITICAL] ${match.rule}: Prompt injection detected in file content. Agent context is potentially poisoned. Session terminated for safety.`;
|
|
554
|
+
onTerminate(reason);
|
|
555
|
+
return { stopReason: reason };
|
|
556
|
+
}
|
|
557
|
+
recordMatch("PostToolUse", toolName, match, "warned");
|
|
558
|
+
return { hookSpecificOutput: {
|
|
559
|
+
hookEventName: "PostToolUse",
|
|
560
|
+
additionalContext: `[YARA WARNING] ${match.rule}: ${match.metadata.description ?? ""}`
|
|
561
|
+
} };
|
|
562
|
+
} catch (error) {
|
|
563
|
+
logToFile("[YARA] PostToolUse Read/Grep hook error:", error);
|
|
564
|
+
const reason = "[YARA] Scanner error while scanning read content — session terminated as a precaution.";
|
|
565
|
+
onTerminate(reason);
|
|
566
|
+
return { stopReason: reason };
|
|
567
|
+
}
|
|
568
|
+
}],
|
|
569
|
+
timeout: HOOK_TIMEOUT_MS
|
|
570
|
+
},
|
|
571
|
+
{
|
|
572
|
+
hooks: [async (input) => {
|
|
573
|
+
try {
|
|
574
|
+
if (input.tool_name !== "Bash") return {};
|
|
575
|
+
const toolInput = input.tool_input;
|
|
576
|
+
const command = typeof toolInput?.command === "string" ? toolInput.command : "";
|
|
577
|
+
if (!isSkillInstallCommand(command)) return {};
|
|
578
|
+
const dirMatch = command.match(/mkdir -p (.claude\/skills\/[^\s&]+)/);
|
|
579
|
+
if (!dirMatch) return {};
|
|
580
|
+
const skillDir = dirMatch[1];
|
|
581
|
+
const cwd = input.cwd ?? process.cwd();
|
|
582
|
+
recordScan();
|
|
583
|
+
const matches = await scanSkillFiles(cwd, skillDir, llmProvider);
|
|
584
|
+
if (matches.length === 0) return {};
|
|
585
|
+
const match = highestSeverityMatch(matches);
|
|
586
|
+
recordMatch("PostToolUse", "Bash (skill install)", match, "aborted");
|
|
587
|
+
const reason = `[YARA CRITICAL] Poisoned skill detected in ${skillDir}: ${match.rule}. The downloaded skill contains potential prompt injection. Session terminated for safety.`;
|
|
588
|
+
onTerminate(reason);
|
|
589
|
+
return { stopReason: reason };
|
|
590
|
+
} catch (error) {
|
|
591
|
+
logToFile("[YARA] PostToolUse skill install hook error:", error);
|
|
592
|
+
const reason = "[YARA] Scanner error while scanning skill files — session terminated as a precaution.";
|
|
593
|
+
onTerminate(reason);
|
|
594
|
+
return { stopReason: reason };
|
|
595
|
+
}
|
|
596
|
+
}],
|
|
597
|
+
timeout: SKILL_SCAN_HOOK_TIMEOUT_MS
|
|
598
|
+
}
|
|
599
|
+
];
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* Read and scan all text files in a skill directory for prompt injection.
|
|
603
|
+
* Scans each file sequentially (single-threaded WASM — parallelism buys
|
|
604
|
+
* nothing), unions the 'input'-context matches, then triages once across the
|
|
605
|
+
* combined content. Returns the real (post-triage) matches.
|
|
606
|
+
*/
|
|
607
|
+
async function scanSkillFiles(cwd, skillDir, llmProvider) {
|
|
608
|
+
const absoluteDir = path.resolve(cwd, skillDir);
|
|
609
|
+
if (!fs.existsSync(absoluteDir)) {
|
|
610
|
+
logToFile(`[YARA] Skill directory does not exist: ${absoluteDir}`);
|
|
611
|
+
return [];
|
|
612
|
+
}
|
|
613
|
+
const files = await fg("**/*.{md,txt,yaml,yml,json,js,ts,py,rb,sh}", {
|
|
614
|
+
cwd: absoluteDir,
|
|
615
|
+
absolute: true
|
|
616
|
+
});
|
|
617
|
+
const fileContents = [];
|
|
618
|
+
for (const filePath of files) try {
|
|
619
|
+
fileContents.push(fs.readFileSync(filePath, "utf-8"));
|
|
620
|
+
} catch (err) {
|
|
621
|
+
logToFile(`[YARA] Could not read skill file ${filePath}:`, err);
|
|
622
|
+
}
|
|
623
|
+
if (fileContents.length === 0) {
|
|
624
|
+
logToFile(`[YARA] No text files found in skill directory: ${absoluteDir}`);
|
|
625
|
+
return [];
|
|
626
|
+
}
|
|
627
|
+
logToFile(`[YARA] Scanning ${fileContents.length} files in skill directory: ${skillDir}`);
|
|
628
|
+
const flagged = [];
|
|
629
|
+
for (const content of fileContents) flagged.push(...await scanForContext(content, "input"));
|
|
630
|
+
return triageFlagged(flagged, "input", llmProvider);
|
|
631
|
+
}
|
|
632
|
+
//#endregion
|
|
633
|
+
export { flushScanReport as a, isWizardDocumentationPath as c, repeatBlockReason as d, scanAndTriage as f, SETUP_REPORT_FILE as h, createRepeatBlockTracker as i, prewarmYaraScanner as l, EVENT_PLAN_FILE as m, createPostToolUseYaraHooks as n, formatScanReport as o, writeScanReport as p, createPreToolUseYaraHooks as r, formatYaraAbortMessage as s, captureScanReport as t, recordExternalScan as u };
|
|
634
|
+
|
|
635
|
+
//# sourceMappingURL=yara-hooks-BPQ3VE-l.js.map
|