@posthog/wizard 2.37.0 → 2.38.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/dist/{add-mcp-server-to-clients-CNn9br9l.js → add-mcp-server-to-clients-BB1_aH5M.js} +5 -5
  2. package/dist/{add-mcp-server-to-clients-CNn9br9l.js.map → add-mcp-server-to-clients-BB1_aH5M.js.map} +1 -1
  3. package/dist/{agent-interface-D8jnkEnB.js → agent-interface-kOuzXg84.js} +69 -1119
  4. package/dist/agent-interface-kOuzXg84.js.map +1 -0
  5. package/dist/{agent-runner-BxIYcxmA.js → agent-runner-DZhaiwD5.js} +2571 -1648
  6. package/dist/agent-runner-DZhaiwD5.js.map +1 -0
  7. package/dist/analytics-B1SC2jaf.js +2 -0
  8. package/dist/{analytics-Du8Z_3oX.js → analytics-Z_2kCTpB.js} +4 -3
  9. package/dist/{analytics-Du8Z_3oX.js.map → analytics-Z_2kCTpB.js.map} +1 -1
  10. package/dist/{api-BqKMQVhj.js → api-CwhOyprf.js} +3 -3
  11. package/dist/{api-BqKMQVhj.js.map → api-CwhOyprf.js.map} +1 -1
  12. package/dist/bin.js +64 -50
  13. package/dist/bin.js.map +1 -1
  14. package/dist/{ci-install-UlWb3u5t.js → ci-install-CWDY86uB.js} +4 -4
  15. package/dist/{ci-install-UlWb3u5t.js.map → ci-install-CWDY86uB.js.map} +1 -1
  16. package/dist/{debug-B4AH8Ahp.js → debug-BmdpjMub.js} +1 -1
  17. package/dist/{debug-CZwmzyDx.js → debug-CaRHQrDY.js} +91 -13
  18. package/dist/debug-CaRHQrDY.js.map +1 -0
  19. package/dist/{defaults-UswXMiTx.js → defaults-a0e6IRjP.js} +1 -1
  20. package/dist/{defaults-UswXMiTx.js.map → defaults-a0e6IRjP.js.map} +1 -1
  21. package/dist/{env-api-key-Db-mNPK2.js → env-api-key-DEl3LJBv.js} +1 -1
  22. package/dist/{env-api-key-Db-mNPK2.js.map → env-api-key-DEl3LJBv.js.map} +1 -1
  23. package/dist/{environment-BRXwshJO.js → environment-knibpXF2.js} +3 -3
  24. package/dist/{environment-BRXwshJO.js.map → environment-knibpXF2.js.map} +1 -1
  25. package/dist/{file-utils-D6CWLaOm.js → file-utils-DWYX_G1o.js} +2 -2
  26. package/dist/{file-utils-D6CWLaOm.js.map → file-utils-DWYX_G1o.js.map} +1 -1
  27. package/dist/{headless-ui-DvkogF0b.js → headless-ui-C4JCi9Zz.js} +2 -2
  28. package/dist/{headless-ui-DvkogF0b.js.map → headless-ui-C4JCi9Zz.js.map} +1 -1
  29. package/dist/{host-resolution-DrOzdKdY.js → host-resolution-C18FwVrM.js} +3 -3
  30. package/dist/{host-resolution-DrOzdKdY.js.map → host-resolution-C18FwVrM.js.map} +1 -1
  31. package/dist/{interactive-CXL8oJwb.js → interactive-46qgS-UA.js} +2 -2
  32. package/dist/{interactive-CXL8oJwb.js.map → interactive-46qgS-UA.js.map} +1 -1
  33. package/dist/mcp-QWL5Obf3.js +108 -0
  34. package/dist/mcp-QWL5Obf3.js.map +1 -0
  35. package/dist/{mcp-prompt-streaming-Dumivcgi.js → mcp-prompt-streaming-BpNkFksF.js} +5 -5
  36. package/dist/mcp-prompt-streaming-BpNkFksF.js.map +1 -0
  37. package/dist/{non-interactive-BI5b25U7.js → non-interactive-3_T1d0cj.js} +2 -2
  38. package/dist/{non-interactive-BI5b25U7.js.map → non-interactive-3_T1d0cj.js.map} +1 -1
  39. package/dist/{package-json-DCuoye-H.js → package-json-CDs1ZVKu.js} +1 -1
  40. package/dist/{package-json-DCuoye-H.js.map → package-json-CDs1ZVKu.js.map} +1 -1
  41. package/dist/{package-manager-9nhQBKAy.js → package-manager-C5VVQHQ-.js} +2 -2
  42. package/dist/{package-manager-9nhQBKAy.js.map → package-manager-C5VVQHQ-.js.map} +1 -1
  43. package/dist/{playground-DDYYs2ub.js → playground-CbzUpMuN.js} +783 -7
  44. package/dist/playground-CbzUpMuN.js.map +1 -0
  45. package/dist/{posthog-BrLFkaji.js → posthog-BESrI6-T.js} +1 -1
  46. package/dist/{posthog-BrLFkaji.js.map → posthog-BESrI6-T.js.map} +1 -1
  47. package/dist/{posthog-integration-Cx4RvC7E.js → posthog-integration-6vdi4wRB.js} +19 -18
  48. package/dist/posthog-integration-6vdi4wRB.js.map +1 -0
  49. package/dist/{provisioning-PosLaOt4.js → provisioning-DK0tS98H.js} +4 -4
  50. package/dist/{provisioning-PosLaOt4.js.map → provisioning-DK0tS98H.js.map} +1 -1
  51. package/dist/{registry-B43CNAAx.js → registry-DfXVjxsP.js} +5 -5
  52. package/dist/{registry-B43CNAAx.js.map → registry-DfXVjxsP.js.map} +1 -1
  53. package/dist/security-BMVH_bjk.js +431 -0
  54. package/dist/security-BMVH_bjk.js.map +1 -0
  55. package/dist/{setup-utils-CR8X8Fmf.js → setup-utils-v9I6PQyP.js} +192 -20
  56. package/dist/setup-utils-v9I6PQyP.js.map +1 -0
  57. package/dist/{start-tui-DeewAqJg.js → start-tui-CylEL6i_.js} +63 -38
  58. package/dist/start-tui-CylEL6i_.js.map +1 -0
  59. package/dist/{steps-CPvI-2Ms.js → steps-DQdpexNM.js} +7 -7
  60. package/dist/{steps-CPvI-2Ms.js.map → steps-DQdpexNM.js.map} +1 -1
  61. package/dist/{store-0uzey9Am.js → store-Db0X2V5D.js} +68 -6
  62. package/dist/store-Db0X2V5D.js.map +1 -0
  63. package/dist/subagent-Pk9jRXRf.js +108 -0
  64. package/dist/subagent-Pk9jRXRf.js.map +1 -0
  65. package/dist/{task-stream-DkbGqxZv.js → task-stream-rC4IEUHC.js} +3 -3
  66. package/dist/{task-stream-DkbGqxZv.js.map → task-stream-rC4IEUHC.js.map} +1 -1
  67. package/dist/tasks-B46phMJu.js +112 -0
  68. package/dist/tasks-B46phMJu.js.map +1 -0
  69. package/dist/{telemetry-DJNpvkYP.js → telemetry-BJAHymMq.js} +3 -3
  70. package/dist/{telemetry-DJNpvkYP.js.map → telemetry-BJAHymMq.js.map} +1 -1
  71. package/dist/{terminal-Bd6q58zn.js → terminal-BTQSAkC-.js} +143 -793
  72. package/dist/terminal-BTQSAkC-.js.map +1 -0
  73. package/dist/tools-C_m5knXt.js +122 -0
  74. package/dist/tools-C_m5knXt.js.map +1 -0
  75. package/dist/{urls-B1a2v93j.js → urls-BCmeQMIb.js} +2 -2
  76. package/dist/{urls-B1a2v93j.js.map → urls-BCmeQMIb.js.map} +1 -1
  77. package/dist/{wizard-abort-DyAGpw-u.js → wizard-abort-CMzW4LVM.js} +1 -1
  78. package/dist/{wizard-abort-BG8VNOI9.js → wizard-abort-DkngXmxN.js} +4 -4
  79. package/dist/{wizard-abort-BG8VNOI9.js.map → wizard-abort-DkngXmxN.js.map} +1 -1
  80. package/dist/{wizard-session-CqihOtk3.js → wizard-session-BoXAZEOf.js} +4 -1
  81. package/dist/wizard-session-BoXAZEOf.js.map +1 -0
  82. package/dist/{wizard-session-xzqycI7i.js → wizard-session-C8W_6X0j.js} +1 -1
  83. package/dist/wizard-tools-C8nVWLeI.js +1162 -0
  84. package/dist/wizard-tools-C8nVWLeI.js.map +1 -0
  85. package/dist/wizard-ui-WZ48rUgr.js.map +1 -1
  86. package/package.json +7 -2
  87. package/dist/agent-interface-D8jnkEnB.js.map +0 -1
  88. package/dist/agent-runner-BxIYcxmA.js.map +0 -1
  89. package/dist/debug-CZwmzyDx.js.map +0 -1
  90. package/dist/mcp-prompt-streaming-Dumivcgi.js.map +0 -1
  91. package/dist/playground-DDYYs2ub.js.map +0 -1
  92. package/dist/posthog-integration-Cx4RvC7E.js.map +0 -1
  93. package/dist/setup-utils-CR8X8Fmf.js.map +0 -1
  94. package/dist/start-tui-DeewAqJg.js.map +0 -1
  95. package/dist/store-0uzey9Am.js.map +0 -1
  96. package/dist/terminal-Bd6q58zn.js.map +0 -1
  97. package/dist/wizard-session-CqihOtk3.js.map +0 -1
@@ -0,0 +1,431 @@
1
+ import { s as logToFile } from "./debug-CaRHQrDY.js";
2
+ import { a as wizardCanUseTool, b as recordExternalScan, y as isWizardDocumentationPath } from "./agent-interface-kOuzXg84.js";
3
+ //#region src/lib/yara-scanner.ts
4
+ const POST_WRITE_EDIT = [{
5
+ phase: "PostToolUse",
6
+ tool: "Write"
7
+ }, {
8
+ phase: "PostToolUse",
9
+ tool: "Edit"
10
+ }];
11
+ const POST_READ_GREP = [{
12
+ phase: "PostToolUse",
13
+ tool: "Read"
14
+ }, {
15
+ phase: "PostToolUse",
16
+ tool: "Grep"
17
+ }];
18
+ const PRE_BASH = [{
19
+ phase: "PreToolUse",
20
+ tool: "Bash"
21
+ }];
22
+ const RULES = [
23
+ {
24
+ name: "pii_in_capture_call",
25
+ description: "Detects PII fields passed to posthog.capture() — violates 'NEVER send PII in capture()' commandment",
26
+ severity: "high",
27
+ category: "posthog_pii",
28
+ appliesTo: POST_WRITE_EDIT,
29
+ patterns: [
30
+ /\.capture\s*\([^)]{0,200}email/i,
31
+ /\.capture\s*\([^)]{0,200}phone/i,
32
+ /\.capture\s*\([^)]{0,200}full[_\s]?name/i,
33
+ /\.capture\s*\([^)]{0,200}first[_\s]?name/i,
34
+ /\.capture\s*\([^)]{0,200}last[_\s]?name/i,
35
+ /\.capture\s*\([^)]{0,200}(street|mailing|home|billing)[_\s]?address/i,
36
+ /\.capture\s*\([^)]{0,200}(ssn|social[_\s]?security)/i,
37
+ /\.capture\s*\([^)]{0,200}(date[_\s]?of[_\s]?birth|dob|birthday)/i,
38
+ /\.capture\s*\([^)]{0,200}\$ip/,
39
+ /\.identify\s*\([^)]{0,200}(ssn|social[_\s]?security)/i,
40
+ /\.identify\s*\([^)]{0,200}(card[_\s]?number|cvv|credit[_\s]?card)/i,
41
+ /\.identify\s*\([^)]{0,200}(date[_\s]?of[_\s]?birth|dob|birthday)/i,
42
+ /\.identify\s*\([^)]{0,200}(street|mailing|home|billing)[_\s]?address/i,
43
+ /\$set[^}]{0,200}email/i,
44
+ /\$set[^}]{0,200}phone/i
45
+ ]
46
+ },
47
+ {
48
+ name: "hardcoded_posthog_key",
49
+ description: "Detects hardcoded PostHog API keys in source — violates 'use environment variables' commandment",
50
+ severity: "high",
51
+ category: "posthog_hardcoded_key",
52
+ appliesTo: POST_WRITE_EDIT,
53
+ patterns: [
54
+ /phc_[a-zA-Z0-9]{20,}/,
55
+ /phx_[a-zA-Z0-9]{20,}/,
56
+ /apiKey\s*[:=]\s*['"][a-zA-Z0-9_]{20,}['"]/,
57
+ /api_key\s*[:=]\s*['"][a-zA-Z0-9_]{20,}['"]/,
58
+ /POSTHOG_PROJECT_TOKEN\s*[:=]\s*['"][a-zA-Z0-9_]{20,}['"]/
59
+ ]
60
+ },
61
+ {
62
+ name: "autocapture_disabled",
63
+ description: "Detects agent disabling autocapture — violates 'don't disable autocapture' commandment",
64
+ severity: "medium",
65
+ category: "posthog_autocapture",
66
+ appliesTo: POST_WRITE_EDIT,
67
+ patterns: [
68
+ /autocapture\s*:\s*false/,
69
+ /autocapture\s*:\s*'false'/,
70
+ /autocapture\s*:\s*"false"/,
71
+ /autocapture\s*=\s*False/,
72
+ /disable_autocapture\s*[:=]\s*(true|True|1)/
73
+ ]
74
+ },
75
+ {
76
+ name: "hardcoded_posthog_host",
77
+ description: "Detects hardcoded PostHog host URLs in source — should use environment variables",
78
+ severity: "high",
79
+ category: "posthog_hardcoded_key",
80
+ appliesTo: POST_WRITE_EDIT,
81
+ patterns: [/['"]https:\/\/(us|eu)\.i\.posthog\.com['"]/]
82
+ },
83
+ {
84
+ name: "session_recording_disabled",
85
+ description: "Detects agent disabling session recording",
86
+ severity: "medium",
87
+ category: "posthog_config",
88
+ appliesTo: POST_WRITE_EDIT,
89
+ patterns: [/disable_session_recording\s*:\s*true/i, /disable_session_recording\s*=\s*True/]
90
+ },
91
+ {
92
+ name: "opt_out_capturing",
93
+ description: "Detects agent opting out of PostHog capturing entirely",
94
+ severity: "medium",
95
+ category: "posthog_config",
96
+ appliesTo: POST_WRITE_EDIT,
97
+ patterns: [/opt_out_capturing\s*[:=]\s*(true|True|1)/i, /opted_out\s*[:=]\s*(true|True|1)/i]
98
+ },
99
+ {
100
+ name: "prompt_injection_wizard_override",
101
+ description: "Detects classic prompt injection attempting to override agent instructions",
102
+ severity: "critical",
103
+ category: "prompt_injection",
104
+ appliesTo: POST_READ_GREP,
105
+ patterns: [
106
+ /ignore previous instructions/i,
107
+ /disregard all/i,
108
+ /forget your instructions/i,
109
+ /override your rules/i,
110
+ /act as a different/i,
111
+ /new instructions:/i
112
+ ]
113
+ },
114
+ {
115
+ name: "prompt_injection_wizard_specific",
116
+ description: "Detects wizard-specific manipulation or tool abuse attempts in project files",
117
+ severity: "medium",
118
+ category: "prompt_injection",
119
+ appliesTo: POST_READ_GREP,
120
+ patterns: [
121
+ /skip posthog/i,
122
+ /do not install posthog/i,
123
+ /remove posthog/i,
124
+ /uninstall posthog/i,
125
+ /delete the posthog/i,
126
+ /run the following command/i,
127
+ /execute this shell command/i,
128
+ /you are now a\s/i
129
+ ]
130
+ },
131
+ {
132
+ name: "prompt_injection_base64",
133
+ description: "Detects suspicious base64-encoded blocks in file content that may contain obfuscated prompt injection",
134
+ severity: "critical",
135
+ category: "prompt_injection",
136
+ appliesTo: POST_READ_GREP,
137
+ patterns: [/(?:\/\/|#|\/\*)\s*[A-Za-z0-9+/]{100,}={0,2}/]
138
+ },
139
+ {
140
+ name: "secret_exfiltration_via_command",
141
+ description: "Detects shell commands attempting to exfiltrate secrets or credentials",
142
+ severity: "critical",
143
+ category: "exfiltration",
144
+ appliesTo: PRE_BASH,
145
+ patterns: [
146
+ /curl\s+.*\$\{?[A-Z_]*(KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL)/i,
147
+ /wget\s+.*\$\{?[A-Z_]*(KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL)/i,
148
+ /(\$\{?[A-Z_]*(KEY|TOKEN|SECRET|PASSWORD)|\.env|credentials)\S*.*\|\s*curl/i,
149
+ /(\$\{?[A-Z_]*(KEY|TOKEN|SECRET|PASSWORD)|\.env|credentials)\S*.*\|\s*wget/i,
150
+ /\|\s*nc\s/,
151
+ /\|\s*netcat\s/,
152
+ /base64.*\|\s*(curl|wget|nc\s)/i,
153
+ /cat\s+.*\.env.*\|\s*(curl|wget)/,
154
+ /curl.*phc_[a-zA-Z0-9]/,
155
+ /wget.*phc_[a-zA-Z0-9]/
156
+ ]
157
+ },
158
+ {
159
+ name: "destructive_rm",
160
+ description: "Detects rm -rf or rm -r commands that could mass-delete files",
161
+ severity: "critical",
162
+ category: "filesystem_safety",
163
+ appliesTo: PRE_BASH,
164
+ patterns: [
165
+ /\brm\s+(-[a-zA-Z]*r[a-zA-Z]*f|-[a-zA-Z]*f[a-zA-Z]*r)\b/,
166
+ /\brm\s+(-[a-zA-Z]*\s+)*-[a-zA-Z]*r[a-zA-Z]*\s+(-[a-zA-Z]*\s+)*-[a-zA-Z]*f\b/,
167
+ /\brm\s+(-[a-zA-Z]*\s+)*-[a-zA-Z]*f[a-zA-Z]*\s+(-[a-zA-Z]*\s+)*-[a-zA-Z]*r\b/
168
+ ]
169
+ },
170
+ {
171
+ name: "git_force_push",
172
+ description: "Detects git push --force which can overwrite remote history",
173
+ severity: "critical",
174
+ category: "filesystem_safety",
175
+ appliesTo: PRE_BASH,
176
+ patterns: [/git\s+push\s+.*--force/, /git\s+push\s+.*-f\b/]
177
+ },
178
+ {
179
+ name: "git_reset_hard",
180
+ description: "Detects git reset --hard which discards all uncommitted changes",
181
+ severity: "critical",
182
+ category: "filesystem_safety",
183
+ appliesTo: PRE_BASH,
184
+ patterns: [/git\s+reset\s+--hard/]
185
+ },
186
+ {
187
+ name: "wrong_posthog_package",
188
+ description: "Detects installing the wrong PostHog npm package — should be posthog-js or posthog-node",
189
+ severity: "high",
190
+ category: "supply_chain",
191
+ appliesTo: PRE_BASH,
192
+ patterns: [
193
+ /npm\s+install\s+(?:--save\s+|--save-dev\s+|-[SD]\s+)*posthog(?!\s*-)/,
194
+ /pnpm\s+(?:add|install)\s+(?:--save\s+|--save-dev\s+|-[SD]\s+)*posthog(?!\s*-)/,
195
+ /yarn\s+add\s+(?:--dev\s+|-D\s+)*posthog(?!\s*-)/,
196
+ /bun\s+(?:add|install)\s+(?:--dev\s+|-[dD]\s+)*posthog(?!\s*-)/
197
+ ]
198
+ },
199
+ {
200
+ name: "npm_install_global",
201
+ description: "Detects global npm installs — should never install packages globally",
202
+ severity: "high",
203
+ category: "supply_chain",
204
+ appliesTo: PRE_BASH,
205
+ patterns: [/npm\s+install\s+-g\b/, /npm\s+install\s+--global\b/]
206
+ }
207
+ ];
208
+ /** Maximum content length to scan (100 KB). Inputs beyond this are truncated. */
209
+ const MAX_SCAN_LENGTH = 1e5;
210
+ /**
211
+ * Scan content against rules applicable to a given hook phase and tool.
212
+ * Returns all matching rules (one match per rule, first pattern wins).
213
+ */
214
+ function scan(content, phase, tool) {
215
+ const scanContent = content.length > MAX_SCAN_LENGTH ? content.slice(0, MAX_SCAN_LENGTH) : content;
216
+ const applicableRules = RULES.filter((r) => r.appliesTo.some((a) => a.phase === phase && a.tool === tool));
217
+ const matches = [];
218
+ for (const rule of applicableRules) for (const pattern of rule.patterns) {
219
+ const match = pattern.exec(scanContent);
220
+ if (match) {
221
+ matches.push({
222
+ rule,
223
+ matchedText: match[0],
224
+ offset: match.index
225
+ });
226
+ break;
227
+ }
228
+ }
229
+ return matches.length > 0 ? {
230
+ matched: true,
231
+ matches
232
+ } : { matched: false };
233
+ }
234
+ //#endregion
235
+ //#region src/lib/agent/runner/harness/pi/security.ts
236
+ /**
237
+ * Fail-closed security for the pi backend (#525). pi has no built-in
238
+ * permission layer, so we attach an extension that intercepts every tool call
239
+ * — built-in (bash/read/edit/write/grep) AND custom — through pi's `tool_call`
240
+ * hook and reuses the EXACT anthropic policy: `wizardCanUseTool` (the bash
241
+ * allowlist + .env fencing) plus the YARA pre-scan. A `tool_result` hook
242
+ * post-scans output. Both fail closed: a scanner error blocks, and a critical
243
+ * post-scan violation latches so every subsequent tool call is blocked and the
244
+ * run terminates as a YARA violation.
245
+ *
246
+ * This is the one fence. Subagents run their own pi session with the SAME
247
+ * extension installed (see subagent.ts), so a child cannot escape it.
248
+ */
249
+ /** yara-scanner match → the report shape `recordExternalScan` expects. */
250
+ function toReportViolation(m) {
251
+ return {
252
+ rule: m.rule.name,
253
+ severity: m.rule.severity,
254
+ category: m.rule.category,
255
+ description: m.rule.description
256
+ };
257
+ }
258
+ const str = (v) => typeof v === "string" ? v : "";
259
+ /**
260
+ * Translate a pi tool name to the claude-cased name + input the shared policy
261
+ * expects. pi field names (from the live tool stream): bash{command},
262
+ * read/edit/write{path}, write adds {content}, edit adds {edits}, grep{path}.
263
+ */
264
+ function toClaudePolicyCall(toolName, input) {
265
+ switch (toolName) {
266
+ case "bash": return {
267
+ name: "Bash",
268
+ input: { command: str(input.command) }
269
+ };
270
+ case "read": return {
271
+ name: "Read",
272
+ input: { file_path: input.path }
273
+ };
274
+ case "write": return {
275
+ name: "Write",
276
+ input: { file_path: input.path }
277
+ };
278
+ case "edit": return {
279
+ name: "Edit",
280
+ input: { file_path: input.path }
281
+ };
282
+ case "grep": return {
283
+ name: "Grep",
284
+ input: { path: input.path }
285
+ };
286
+ default: return {
287
+ name: toolName,
288
+ input
289
+ };
290
+ }
291
+ }
292
+ /**
293
+ * YARA scan of the content a tool is about to act on, BEFORE it executes.
294
+ * - bash → scan the command (PreToolUse/Bash: exfiltration, destructive, force-push)
295
+ * - write/edit → scan the content being written (PostToolUse/Write|Edit:
296
+ * hardcoded keys, PII), with the same wizard-doc `posthog_pii` suppression the
297
+ * anthropic path uses so the agent's own event-plan files aren't blocked.
298
+ * Returns a block reason, or undefined to allow. Read/grep are post-scanned on
299
+ * their output (in the tool_result hook), not here.
300
+ */
301
+ function preExecutionYaraBlock(toolName, input) {
302
+ let content;
303
+ let target;
304
+ let phase;
305
+ switch (toolName) {
306
+ case "bash":
307
+ content = str(input.command);
308
+ target = "Bash";
309
+ phase = "PreToolUse";
310
+ break;
311
+ case "write":
312
+ content = str(input.content);
313
+ target = "Write";
314
+ phase = "PostToolUse";
315
+ break;
316
+ case "edit":
317
+ content = JSON.stringify(input.edits ?? "");
318
+ target = "Edit";
319
+ phase = "PostToolUse";
320
+ break;
321
+ default: return;
322
+ }
323
+ if (!content) return void 0;
324
+ const result = scan(content, phase, target);
325
+ let matches = result.matched ? result.matches : [];
326
+ if ((target === "Write" || target === "Edit") && isWizardDocumentationPath(str(input.path))) matches = matches.filter((m) => m.rule.category !== "posthog_pii");
327
+ recordExternalScan(phase, target, matches.map(toReportViolation), "blocked");
328
+ if (matches.length === 0) return void 0;
329
+ const m = matches[0];
330
+ return `[YARA] ${m.rule.name}: ${m.rule.description}. Blocked for security.`;
331
+ }
332
+ /**
333
+ * The pure gate decision for a single tool call. Reuses `wizardCanUseTool`
334
+ * (deny → block) then the YARA content scan (match → block). Fail-closed: any
335
+ * thrown error blocks.
336
+ */
337
+ function evaluateToolCall(toolName, input, ctx = {}) {
338
+ try {
339
+ const policy = toClaudePolicyCall(toolName, input);
340
+ const decision = wizardCanUseTool(policy.name, policy.input, {
341
+ disallowedTools: ctx.disallowedTools,
342
+ wizardAskPending: ctx.getWizardAskPending?.() ?? false
343
+ });
344
+ if (decision.behavior === "deny") return {
345
+ block: true,
346
+ reason: decision.message
347
+ };
348
+ const yaraReason = preExecutionYaraBlock(toolName, input);
349
+ if (yaraReason) return {
350
+ block: true,
351
+ reason: yaraReason
352
+ };
353
+ return { block: false };
354
+ } catch (err) {
355
+ logToFile("[pi-security] gate error — failing closed:", err);
356
+ return {
357
+ block: true,
358
+ reason: "Security check failed; tool blocked (fail-closed)."
359
+ };
360
+ }
361
+ }
362
+ /** pi result tool name → YARA target for the post-scan (skip the rest). */
363
+ function postScanTarget(toolName) {
364
+ switch (toolName) {
365
+ case "read": return "Read";
366
+ case "bash": return "Bash";
367
+ default: return;
368
+ }
369
+ }
370
+ /**
371
+ * Build the pi security extension + the shared state the backend inspects.
372
+ * Install the returned factory via `extensionFactories`; pass the same factory
373
+ * into every subagent session so the fence is inherited.
374
+ */
375
+ function createSecurityExtension(ctx = {}) {
376
+ const state = {
377
+ criticalViolation: false,
378
+ blockedCount: 0,
379
+ toolCalls: 0
380
+ };
381
+ const factory = (pi) => {
382
+ pi.on("tool_call", (event) => {
383
+ if (state.criticalViolation) return {
384
+ block: true,
385
+ reason: "Run terminated by a security violation."
386
+ };
387
+ state.toolCalls += 1;
388
+ if (state.toolCalls > 250) return {
389
+ block: true,
390
+ reason: `Stopped: exceeded 250 tool calls (runaway guard).`
391
+ };
392
+ const decision = evaluateToolCall(event.toolName, event.input ?? {}, ctx);
393
+ if (decision.block) {
394
+ state.blockedCount += 1;
395
+ logToFile(`[pi-security] BLOCK ${event.toolName}: ${decision.reason}`);
396
+ return {
397
+ block: true,
398
+ reason: decision.reason
399
+ };
400
+ }
401
+ return {};
402
+ });
403
+ pi.on("tool_result", (event) => {
404
+ const target = postScanTarget(event.toolName);
405
+ if (!target) return {};
406
+ const text = (event.content ?? []).map((c) => c && c.type === "text" ? c.text : "").join("\n");
407
+ if (!text) return {};
408
+ try {
409
+ const result = scan(text, "PostToolUse", target);
410
+ const matches = result.matched ? result.matches : [];
411
+ recordExternalScan("PostToolUse", target, matches.map(toReportViolation), "aborted");
412
+ if (matches.length > 0) {
413
+ state.criticalViolation = true;
414
+ logToFile(`[pi-security] POST-SCAN VIOLATION ${event.toolName}: ${matches[0].rule.name}`);
415
+ }
416
+ } catch (err) {
417
+ state.criticalViolation = true;
418
+ logToFile("[pi-security] post-scan error — failing closed:", err);
419
+ }
420
+ return {};
421
+ });
422
+ };
423
+ return {
424
+ factory,
425
+ state
426
+ };
427
+ }
428
+ //#endregion
429
+ export { createSecurityExtension };
430
+
431
+ //# sourceMappingURL=security-BMVH_bjk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"security-BMVH_bjk.js","names":[],"sources":["../src/lib/yara-scanner.ts","../src/lib/agent/runner/harness/pi/security.ts"],"sourcesContent":["/**\n * YARA content scanner for the PostHog wizard.\n *\n * This file is the single source of truth for all wizard YARA rules.\n *\n * Scans tool inputs (pre-execution) and outputs (post-execution) for\n * security violations including PII leakage, hardcoded secrets,\n * prompt injection, and secret exfiltration.\n *\n * We use YARA-style regex rules rather than the real YARA C library to\n * avoid native binary dependencies in an npx-distributed npm package.\n *\n * This is Layer 2 (L2) in the wizard's defense-in-depth model,\n * complementing the prompt-based commandments (L0) and the\n * canUseTool() allowlist (L1).\n */\n\n// ─── Types ───────────────────────────────────────────────────────\n\nexport type YaraSeverity = 'critical' | 'high' | 'medium' | 'low';\n\nexport type YaraCategory =\n | 'posthog_pii'\n | 'posthog_hardcoded_key'\n | 'posthog_autocapture'\n | 'posthog_config'\n | 'prompt_injection'\n | 'exfiltration'\n | 'filesystem_safety'\n | 'supply_chain';\n\nexport type HookPhase = 'PreToolUse' | 'PostToolUse';\nexport type ToolTarget = 'Bash' | 'Write' | 'Edit' | 'Read' | 'Grep';\n\nexport interface YaraRule {\n /** Rule name matching the .yar file (e.g. 'pii_in_capture_call') */\n name: string;\n description: string;\n severity: YaraSeverity;\n category: YaraCategory;\n /** Which hook+tool combinations this rule applies to */\n appliesTo: Array<{ phase: HookPhase; tool: ToolTarget }>;\n /** Compiled regex patterns — any match triggers the rule */\n patterns: RegExp[];\n}\n\nexport interface YaraMatch {\n rule: YaraRule;\n /** The matched substring */\n matchedText: string;\n /** Byte offset in the scanned content */\n offset: number;\n}\n\nexport type ScanResult =\n | { matched: false }\n | { matched: true; matches: YaraMatch[] };\n\n// ─── Rule Definitions ────────────────────────────────────────────\n//\n// Patterns are compiled once at module load time for performance.\n// Design spec: policies/yara/RULES.md\n\nconst POST_WRITE_EDIT: Array<{ phase: HookPhase; tool: ToolTarget }> = [\n { phase: 'PostToolUse', tool: 'Write' },\n { phase: 'PostToolUse', tool: 'Edit' },\n];\n\nconst POST_READ_GREP: Array<{ phase: HookPhase; tool: ToolTarget }> = [\n { phase: 'PostToolUse', tool: 'Read' },\n { phase: 'PostToolUse', tool: 'Grep' },\n];\n\nconst PRE_BASH: Array<{ phase: HookPhase; tool: ToolTarget }> = [\n { phase: 'PreToolUse', tool: 'Bash' },\n];\n\n// ── §1 PostHog API Violations ────────────────────────────────────\n\nconst pii_in_capture_call: YaraRule = {\n name: 'pii_in_capture_call',\n description:\n \"Detects PII fields passed to posthog.capture() — violates 'NEVER send PII in capture()' commandment\",\n severity: 'high',\n category: 'posthog_pii',\n appliesTo: POST_WRITE_EDIT,\n patterns: [\n // Direct PII field names in capture properties\n /\\.capture\\s*\\([^)]{0,200}email/i,\n /\\.capture\\s*\\([^)]{0,200}phone/i,\n /\\.capture\\s*\\([^)]{0,200}full[_\\s]?name/i,\n /\\.capture\\s*\\([^)]{0,200}first[_\\s]?name/i,\n /\\.capture\\s*\\([^)]{0,200}last[_\\s]?name/i,\n /\\.capture\\s*\\([^)]{0,200}(street|mailing|home|billing)[_\\s]?address/i,\n /\\.capture\\s*\\([^)]{0,200}(ssn|social[_\\s]?security)/i,\n /\\.capture\\s*\\([^)]{0,200}(date[_\\s]?of[_\\s]?birth|dob|birthday)/i,\n /\\.capture\\s*\\([^)]{0,200}\\$ip/,\n // identify() allows email/phone/name (standard PostHog user properties),\n // but highly sensitive PII is still blocked in identify().\n /\\.identify\\s*\\([^)]{0,200}(ssn|social[_\\s]?security)/i,\n /\\.identify\\s*\\([^)]{0,200}(card[_\\s]?number|cvv|credit[_\\s]?card)/i,\n /\\.identify\\s*\\([^)]{0,200}(date[_\\s]?of[_\\s]?birth|dob|birthday)/i,\n /\\.identify\\s*\\([^)]{0,200}(street|mailing|home|billing)[_\\s]?address/i,\n // PII in $set properties via capture (bound to same object)\n /\\$set[^}]{0,200}email/i,\n /\\$set[^}]{0,200}phone/i,\n ],\n};\n\nconst hardcoded_posthog_key: YaraRule = {\n name: 'hardcoded_posthog_key',\n description:\n \"Detects hardcoded PostHog API keys in source — violates 'use environment variables' commandment\",\n severity: 'high',\n category: 'posthog_hardcoded_key',\n appliesTo: POST_WRITE_EDIT,\n patterns: [\n // PostHog project API key (phc_ prefix, 20+ alphanumeric chars)\n /phc_[a-zA-Z0-9]{20,}/,\n // PostHog personal API key (phx_ prefix)\n /phx_[a-zA-Z0-9]{20,}/,\n // Hardcoded key assignment patterns\n /apiKey\\s*[:=]\\s*['\"][a-zA-Z0-9_]{20,}['\"]/,\n /api_key\\s*[:=]\\s*['\"][a-zA-Z0-9_]{20,}['\"]/,\n /POSTHOG_PROJECT_TOKEN\\s*[:=]\\s*['\"][a-zA-Z0-9_]{20,}['\"]/,\n ],\n};\n\nconst autocapture_disabled: YaraRule = {\n name: 'autocapture_disabled',\n description:\n \"Detects agent disabling autocapture — violates 'don't disable autocapture' commandment\",\n severity: 'medium',\n category: 'posthog_autocapture',\n appliesTo: POST_WRITE_EDIT,\n patterns: [\n /autocapture\\s*:\\s*false/,\n /autocapture\\s*:\\s*'false'/,\n /autocapture\\s*:\\s*\"false\"/,\n /autocapture\\s*=\\s*False/,\n /disable_autocapture\\s*[:=]\\s*(true|True|1)/,\n ],\n};\n\n// ── §1b Additional PostHog config rules ──────────────────────────\n\nconst hardcoded_posthog_host: YaraRule = {\n name: 'hardcoded_posthog_host',\n description:\n 'Detects hardcoded PostHog host URLs in source — should use environment variables',\n severity: 'high',\n category: 'posthog_hardcoded_key',\n appliesTo: POST_WRITE_EDIT,\n patterns: [/['\"]https:\\/\\/(us|eu)\\.i\\.posthog\\.com['\"]/],\n};\n\nconst session_recording_disabled: YaraRule = {\n name: 'session_recording_disabled',\n description: 'Detects agent disabling session recording',\n severity: 'medium',\n category: 'posthog_config',\n appliesTo: POST_WRITE_EDIT,\n patterns: [\n /disable_session_recording\\s*:\\s*true/i,\n /disable_session_recording\\s*=\\s*True/,\n ],\n};\n\nconst opt_out_capturing: YaraRule = {\n name: 'opt_out_capturing',\n description: 'Detects agent opting out of PostHog capturing entirely',\n severity: 'medium',\n category: 'posthog_config',\n appliesTo: POST_WRITE_EDIT,\n patterns: [\n // Match opt_out_capturing as a config property (in init options), not as a method call.\n // posthog.opt_out_capturing() is a legitimate GDPR-required API.\n /opt_out_capturing\\s*[:=]\\s*(true|True|1)/i,\n /opted_out\\s*[:=]\\s*(true|True|1)/i,\n ],\n};\n\n// ── §2 Prompt Injection ──────────────────────────────────────────\n\nconst prompt_injection_wizard_override: YaraRule = {\n name: 'prompt_injection_wizard_override',\n description:\n 'Detects classic prompt injection attempting to override agent instructions',\n severity: 'critical',\n category: 'prompt_injection',\n appliesTo: POST_READ_GREP,\n patterns: [\n // Direct instruction override — classic injection patterns\n /ignore previous instructions/i,\n /disregard all/i,\n /forget your instructions/i,\n /override your rules/i,\n // Role manipulation\n /act as a different/i,\n /new instructions:/i,\n ],\n};\n\nconst prompt_injection_wizard_specific: YaraRule = {\n name: 'prompt_injection_wizard_specific',\n description:\n 'Detects wizard-specific manipulation or tool abuse attempts in project files',\n severity: 'medium',\n category: 'prompt_injection',\n appliesTo: POST_READ_GREP,\n patterns: [\n // Wizard-specific manipulation\n /skip posthog/i,\n /do not install posthog/i,\n /remove posthog/i,\n /uninstall posthog/i,\n /delete the posthog/i,\n // Tool abuse via file content\n /run the following command/i,\n /execute this shell command/i,\n // Role hijacking — require \"you are now a\" to avoid false positives\n // on legitimate phrases like \"you are now ready to...\"\n /you are now a\\s/i,\n ],\n};\n\nconst prompt_injection_base64: YaraRule = {\n name: 'prompt_injection_base64',\n description:\n 'Detects suspicious base64-encoded blocks in file content that may contain obfuscated prompt injection',\n severity: 'critical',\n category: 'prompt_injection',\n appliesTo: POST_READ_GREP,\n patterns: [\n // Long base64 strings (100+ chars) in comments or string literals\n // that aren't typical data URIs or legitimate base64 content\n /(?:\\/\\/|#|\\/\\*)\\s*[A-Za-z0-9+/]{100,}={0,2}/,\n ],\n};\n\n// ── §3 Secret Exfiltration ───────────────────────────────────────\n\nconst secret_exfiltration_via_command: YaraRule = {\n name: 'secret_exfiltration_via_command',\n description:\n 'Detects shell commands attempting to exfiltrate secrets or credentials',\n severity: 'critical',\n category: 'exfiltration',\n appliesTo: PRE_BASH,\n patterns: [\n // curl/wget with environment variable secrets\n /curl\\s+.*\\$\\{?[A-Z_]*(KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL)/i,\n /wget\\s+.*\\$\\{?[A-Z_]*(KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL)/i,\n // Piping sensitive content to network tools\n /(\\$\\{?[A-Z_]*(KEY|TOKEN|SECRET|PASSWORD)|\\.env|credentials)\\S*.*\\|\\s*curl/i,\n /(\\$\\{?[A-Z_]*(KEY|TOKEN|SECRET|PASSWORD)|\\.env|credentials)\\S*.*\\|\\s*wget/i,\n /\\|\\s*nc\\s/,\n /\\|\\s*netcat\\s/,\n // Base64 encoding piped to network\n /base64.*\\|\\s*(curl|wget|nc\\s)/i,\n // Reading .env and sending\n /cat\\s+.*\\.env.*\\|\\s*(curl|wget)/,\n // PostHog key exfiltration specifically\n /curl.*phc_[a-zA-Z0-9]/,\n /wget.*phc_[a-zA-Z0-9]/,\n ],\n};\n\n// ── §4 Filesystem Safety ─────────────────────────────────────────\n\nconst destructive_rm: YaraRule = {\n name: 'destructive_rm',\n description: 'Detects rm -rf or rm -r commands that could mass-delete files',\n severity: 'critical',\n category: 'filesystem_safety',\n appliesTo: PRE_BASH,\n patterns: [\n // Combined flags: rm -rf, rm -fr, rm -rfi, etc.\n /\\brm\\s+(-[a-zA-Z]*r[a-zA-Z]*f|-[a-zA-Z]*f[a-zA-Z]*r)\\b/,\n // Separated flags: rm -r -f, rm -f -r (with optional other flags)\n /\\brm\\s+(-[a-zA-Z]*\\s+)*-[a-zA-Z]*r[a-zA-Z]*\\s+(-[a-zA-Z]*\\s+)*-[a-zA-Z]*f\\b/,\n /\\brm\\s+(-[a-zA-Z]*\\s+)*-[a-zA-Z]*f[a-zA-Z]*\\s+(-[a-zA-Z]*\\s+)*-[a-zA-Z]*r\\b/,\n ],\n};\n\nconst git_force_push: YaraRule = {\n name: 'git_force_push',\n description: 'Detects git push --force which can overwrite remote history',\n severity: 'critical',\n category: 'filesystem_safety',\n appliesTo: PRE_BASH,\n patterns: [/git\\s+push\\s+.*--force/, /git\\s+push\\s+.*-f\\b/],\n};\n\nconst git_reset_hard: YaraRule = {\n name: 'git_reset_hard',\n description:\n 'Detects git reset --hard which discards all uncommitted changes',\n severity: 'critical',\n category: 'filesystem_safety',\n appliesTo: PRE_BASH,\n patterns: [/git\\s+reset\\s+--hard/],\n};\n\n// ── §5 Supply Chain ──────────────────────────────────────────────\n\nconst wrong_posthog_package: YaraRule = {\n name: 'wrong_posthog_package',\n description:\n 'Detects installing the wrong PostHog npm package — should be posthog-js or posthog-node',\n severity: 'high',\n category: 'supply_chain',\n appliesTo: PRE_BASH,\n patterns: [\n // Match \"npm install posthog\" but not \"posthog-js\", \"posthog-node\", etc.\n /npm\\s+install\\s+(?:--save\\s+|--save-dev\\s+|-[SD]\\s+)*posthog(?!\\s*-)/,\n /pnpm\\s+(?:add|install)\\s+(?:--save\\s+|--save-dev\\s+|-[SD]\\s+)*posthog(?!\\s*-)/,\n /yarn\\s+add\\s+(?:--dev\\s+|-D\\s+)*posthog(?!\\s*-)/,\n /bun\\s+(?:add|install)\\s+(?:--dev\\s+|-[dD]\\s+)*posthog(?!\\s*-)/,\n ],\n};\n\nconst npm_install_global: YaraRule = {\n name: 'npm_install_global',\n description:\n 'Detects global npm installs — should never install packages globally',\n severity: 'high',\n category: 'supply_chain',\n appliesTo: PRE_BASH,\n patterns: [/npm\\s+install\\s+-g\\b/, /npm\\s+install\\s+--global\\b/],\n};\n\n// ─── Rule Registry ───────────────────────────────────────────────\n\nexport const RULES: YaraRule[] = [\n // §1 PostHog API violations\n pii_in_capture_call,\n hardcoded_posthog_key,\n autocapture_disabled,\n hardcoded_posthog_host,\n session_recording_disabled,\n opt_out_capturing,\n // §2 Prompt injection\n prompt_injection_wizard_override,\n prompt_injection_wizard_specific,\n prompt_injection_base64,\n // §3 Secret exfiltration\n secret_exfiltration_via_command,\n // §4 Filesystem safety\n destructive_rm,\n git_force_push,\n git_reset_hard,\n // §5 Supply chain\n wrong_posthog_package,\n npm_install_global,\n];\n\n// ─── Scan Engine ─────────────────────────────────────────────────\n\n/** Maximum content length to scan (100 KB). Inputs beyond this are truncated. */\nconst MAX_SCAN_LENGTH = 100_000;\n\n/**\n * Scan content against rules applicable to a given hook phase and tool.\n * Returns all matching rules (one match per rule, first pattern wins).\n */\nexport function scan(\n content: string,\n phase: HookPhase,\n tool: ToolTarget,\n): ScanResult {\n // Cap input length to prevent pathological regex performance\n const scanContent =\n content.length > MAX_SCAN_LENGTH\n ? content.slice(0, MAX_SCAN_LENGTH)\n : content;\n const applicableRules = RULES.filter((r) =>\n r.appliesTo.some((a) => a.phase === phase && a.tool === tool),\n );\n\n const matches: YaraMatch[] = [];\n for (const rule of applicableRules) {\n for (const pattern of rule.patterns) {\n const match = pattern.exec(scanContent);\n if (match) {\n matches.push({\n rule,\n matchedText: match[0],\n offset: match.index,\n });\n break; // One match per rule is sufficient\n }\n }\n }\n\n return matches.length > 0 ? { matched: true, matches } : { matched: false };\n}\n\n/**\n * Scan all files in a skill directory for prompt injection.\n * Used for context-mill scanning after skill installation.\n */\nexport function scanSkillDirectory(\n files: Array<{ path: string; content: string }>,\n): ScanResult {\n const allMatches: YaraMatch[] = [];\n for (const file of files) {\n const result = scan(file.content, 'PostToolUse', 'Read');\n if (result.matched) {\n allMatches.push(...result.matches);\n }\n }\n return allMatches.length > 0\n ? { matched: true, matches: allMatches }\n : { matched: false };\n}\n","/**\n * Fail-closed security for the pi backend (#525). pi has no built-in\n * permission layer, so we attach an extension that intercepts every tool call\n * — built-in (bash/read/edit/write/grep) AND custom — through pi's `tool_call`\n * hook and reuses the EXACT anthropic policy: `wizardCanUseTool` (the bash\n * allowlist + .env fencing) plus the YARA pre-scan. A `tool_result` hook\n * post-scans output. Both fail closed: a scanner error blocks, and a critical\n * post-scan violation latches so every subsequent tool call is blocked and the\n * run terminates as a YARA violation.\n *\n * This is the one fence. Subagents run their own pi session with the SAME\n * extension installed (see subagent.ts), so a child cannot escape it.\n */\n\nimport { wizardCanUseTool } from '@lib/agent/agent-interface';\nimport {\n scan,\n type HookPhase,\n type ToolTarget,\n type YaraMatch,\n} from '@lib/yara-scanner';\nimport { isWizardDocumentationPath, recordExternalScan } from '@lib/yara-hooks';\nimport { logToFile } from '@utils/debug';\n\n/** yara-scanner match → the report shape `recordExternalScan` expects. */\nfunction toReportViolation(m: YaraMatch) {\n return {\n rule: m.rule.name,\n severity: m.rule.severity,\n category: m.rule.category,\n description: m.rule.description,\n };\n}\n\n/** Runaway backstop: hard cap on tool calls per (sub)agent session. */\nexport const MAX_TOOL_CALLS = 250;\n\nexport interface ToolGateContext {\n disallowedTools?: readonly string[];\n /** True while a wizard_ask overlay is open (interactive); blocks Write/Edit. */\n getWizardAskPending?: () => boolean;\n}\n\nexport interface GateDecision {\n block: boolean;\n reason?: string;\n}\n\nconst str = (v: unknown): string => (typeof v === 'string' ? v : '');\n\n/**\n * Translate a pi tool name to the claude-cased name + input the shared policy\n * expects. pi field names (from the live tool stream): bash{command},\n * read/edit/write{path}, write adds {content}, edit adds {edits}, grep{path}.\n */\nfunction toClaudePolicyCall(\n toolName: string,\n input: Record<string, unknown>,\n): { name: string; input: Record<string, unknown> } {\n switch (toolName) {\n case 'bash':\n return { name: 'Bash', input: { command: str(input.command) } };\n case 'read':\n return { name: 'Read', input: { file_path: input.path } };\n case 'write':\n return { name: 'Write', input: { file_path: input.path } };\n case 'edit':\n return { name: 'Edit', input: { file_path: input.path } };\n case 'grep':\n return { name: 'Grep', input: { path: input.path } };\n default:\n // Custom tools (load_skill_menu, set_env_values, dispatch_agent, …) +\n // find/ls: no path/command, policy allows (their own handlers are fenced).\n return { name: toolName, input };\n }\n}\n\n/**\n * YARA scan of the content a tool is about to act on, BEFORE it executes.\n * - bash → scan the command (PreToolUse/Bash: exfiltration, destructive, force-push)\n * - write/edit → scan the content being written (PostToolUse/Write|Edit:\n * hardcoded keys, PII), with the same wizard-doc `posthog_pii` suppression the\n * anthropic path uses so the agent's own event-plan files aren't blocked.\n * Returns a block reason, or undefined to allow. Read/grep are post-scanned on\n * their output (in the tool_result hook), not here.\n */\nfunction preExecutionYaraBlock(\n toolName: string,\n input: Record<string, unknown>,\n): string | undefined {\n let content: string;\n let target: ToolTarget;\n let phase: HookPhase;\n switch (toolName) {\n case 'bash':\n content = str(input.command);\n target = 'Bash';\n phase = 'PreToolUse';\n break;\n case 'write':\n content = str(input.content);\n target = 'Write';\n phase = 'PostToolUse';\n break;\n case 'edit':\n content = JSON.stringify(input.edits ?? '');\n target = 'Edit';\n phase = 'PostToolUse';\n break;\n default:\n return undefined;\n }\n if (!content) return undefined;\n\n const result = scan(content, phase, target);\n let matches = result.matched ? result.matches : [];\n if (\n (target === 'Write' || target === 'Edit') &&\n isWizardDocumentationPath(str(input.path))\n ) {\n matches = matches.filter((m) => m.rule.category !== 'posthog_pii');\n }\n recordExternalScan(phase, target, matches.map(toReportViolation), 'blocked');\n if (matches.length === 0) return undefined;\n\n const m = matches[0];\n return `[YARA] ${m.rule.name}: ${m.rule.description}. Blocked for security.`;\n}\n\n/**\n * The pure gate decision for a single tool call. Reuses `wizardCanUseTool`\n * (deny → block) then the YARA content scan (match → block). Fail-closed: any\n * thrown error blocks.\n */\nexport function evaluateToolCall(\n toolName: string,\n input: Record<string, unknown>,\n ctx: ToolGateContext = {},\n): GateDecision {\n try {\n const policy = toClaudePolicyCall(toolName, input);\n const decision = wizardCanUseTool(policy.name, policy.input, {\n disallowedTools: ctx.disallowedTools,\n wizardAskPending: ctx.getWizardAskPending?.() ?? false,\n });\n if (decision.behavior === 'deny') {\n return { block: true, reason: decision.message };\n }\n\n const yaraReason = preExecutionYaraBlock(toolName, input);\n if (yaraReason) return { block: true, reason: yaraReason };\n\n return { block: false };\n } catch (err) {\n logToFile('[pi-security] gate error — failing closed:', err);\n return {\n block: true,\n reason: 'Security check failed; tool blocked (fail-closed).',\n };\n }\n}\n\n/** pi result tool name → YARA target for the post-scan (skip the rest). */\nfunction postScanTarget(toolName: string): ToolTarget | undefined {\n switch (toolName) {\n case 'read':\n return 'Read';\n case 'bash':\n return 'Bash';\n default:\n return undefined;\n }\n}\n\n/** Mutable state the backend reads after the run to classify the outcome. */\nexport interface SecurityState {\n criticalViolation: boolean;\n blockedCount: number;\n toolCalls: number;\n}\n\n/**\n * Build the pi security extension + the shared state the backend inspects.\n * Install the returned factory via `extensionFactories`; pass the same factory\n * into every subagent session so the fence is inherited.\n */\nexport function createSecurityExtension(ctx: ToolGateContext = {}): {\n factory: (pi: PiExtensionApiLike) => void;\n state: SecurityState;\n} {\n const state: SecurityState = {\n criticalViolation: false,\n blockedCount: 0,\n toolCalls: 0,\n };\n\n const factory = (pi: PiExtensionApiLike): void => {\n pi.on('tool_call', (event) => {\n // A latched post-scan violation blocks everything that follows.\n if (state.criticalViolation) {\n return {\n block: true,\n reason: 'Run terminated by a security violation.',\n };\n }\n state.toolCalls += 1;\n if (state.toolCalls > MAX_TOOL_CALLS) {\n return {\n block: true,\n reason: `Stopped: exceeded ${MAX_TOOL_CALLS} tool calls (runaway guard).`,\n };\n }\n const decision = evaluateToolCall(event.toolName, event.input ?? {}, ctx);\n if (decision.block) {\n state.blockedCount += 1;\n logToFile(`[pi-security] BLOCK ${event.toolName}: ${decision.reason}`);\n return { block: true, reason: decision.reason };\n }\n return {};\n });\n\n pi.on('tool_result', (event) => {\n const target = postScanTarget(event.toolName);\n if (!target) return {};\n const text = (event.content ?? [])\n .map((c) => (c && c.type === 'text' ? c.text : ''))\n .join('\\n');\n if (!text) return {};\n try {\n const result = scan(text, 'PostToolUse', target);\n const matches = result.matched ? result.matches : [];\n recordExternalScan(\n 'PostToolUse',\n target,\n matches.map(toReportViolation),\n 'aborted',\n );\n if (matches.length > 0) {\n state.criticalViolation = true;\n logToFile(\n `[pi-security] POST-SCAN VIOLATION ${event.toolName}: ${matches[0].rule.name}`,\n );\n }\n } catch (err) {\n // Fail closed: a scanner error on output latches a violation.\n state.criticalViolation = true;\n logToFile('[pi-security] post-scan error — failing closed:', err);\n }\n return {};\n });\n };\n\n return { factory, state };\n}\n\n/**\n * Minimal structural type for pi's ExtensionAPI — just the `on` overloads we\n * use. Kept local so this module has no value import from the pi SDK (so the\n * CommonJS unit tests can load it directly).\n */\nexport interface PiExtensionApiLike {\n on(\n event: 'tool_call',\n handler: (event: { toolName: string; input?: Record<string, unknown> }) => {\n block?: boolean;\n reason?: string;\n },\n ): void;\n on(\n event: 'tool_result',\n handler: (event: {\n toolName: string;\n content?: Array<{ type: string; text?: string }>;\n isError?: boolean;\n }) => Record<string, never>,\n ): void;\n}\n"],"mappings":";;;AA+DA,MAAM,kBAAiE,CACrE;CAAE,OAAO;CAAe,MAAM;CAAS,EACvC;CAAE,OAAO;CAAe,MAAM;CAAQ,CACvC;AAED,MAAM,iBAAgE,CACpE;CAAE,OAAO;CAAe,MAAM;CAAQ,EACtC;CAAE,OAAO;CAAe,MAAM;CAAQ,CACvC;AAED,MAAM,WAA0D,CAC9D;CAAE,OAAO;CAAc,MAAM;CAAQ,CACtC;AAmQD,MAAa,QAAoB;CA/PK;EACpC,MAAM;EACN,aACE;EACF,UAAU;EACV,UAAU;EACV,WAAW;EACX,UAAU;GAER;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GAGA;GACA;GACA;GACA;GAEA;GACA;GACD;EACF;CAEuC;EACtC,MAAM;EACN,aACE;EACF,UAAU;EACV,UAAU;EACV,WAAW;EACX,UAAU;GAER;GAEA;GAEA;GACA;GACA;GACD;EACF;CAEsC;EACrC,MAAM;EACN,aACE;EACF,UAAU;EACV,UAAU;EACV,WAAW;EACX,UAAU;GACR;GACA;GACA;GACA;GACA;GACD;EACF;CAIwC;EACvC,MAAM;EACN,aACE;EACF,UAAU;EACV,UAAU;EACV,WAAW;EACX,UAAU,CAAC,6CAA6C;EACzD;CAE4C;EAC3C,MAAM;EACN,aAAa;EACb,UAAU;EACV,UAAU;EACV,WAAW;EACX,UAAU,CACR,yCACA,uCACD;EACF;CAEmC;EAClC,MAAM;EACN,aAAa;EACb,UAAU;EACV,UAAU;EACV,WAAW;EACX,UAAU,CAGR,6CACA,oCACD;EACF;CAIkD;EACjD,MAAM;EACN,aACE;EACF,UAAU;EACV,UAAU;EACV,WAAW;EACX,UAAU;GAER;GACA;GACA;GACA;GAEA;GACA;GACD;EACF;CAEkD;EACjD,MAAM;EACN,aACE;EACF,UAAU;EACV,UAAU;EACV,WAAW;EACX,UAAU;GAER;GACA;GACA;GACA;GACA;GAEA;GACA;GAGA;GACD;EACF;CAEyC;EACxC,MAAM;EACN,aACE;EACF,UAAU;EACV,UAAU;EACV,WAAW;EACX,UAAU,CAGR,8CACD;EACF;CAIiD;EAChD,MAAM;EACN,aACE;EAC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"}