@posthog/wizard 2.84.0 → 2.84.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.
- package/dist/{agent-interface-BNssNetq.js → agent-interface-D8mrarFD.js} +10 -10
- package/dist/{agent-interface-BNssNetq.js.map → agent-interface-D8mrarFD.js.map} +1 -1
- package/dist/{agent-prompt-loader-iV11XVEF.js → agent-prompt-loader-Bt6mrV7q.js} +6 -6
- package/dist/{agent-prompt-loader-iV11XVEF.js.map → agent-prompt-loader-Bt6mrV7q.js.map} +1 -1
- package/dist/{analytics-Drox5LSW.js → analytics-CX2kvq0R.js} +2 -2
- package/dist/{analytics-Drox5LSW.js.map → analytics-CX2kvq0R.js.map} +1 -1
- package/dist/analytics-DgLAjlIK.js +2 -0
- package/dist/{api-Dfu655Qt.js → api-BOP8gF35.js} +2 -2
- package/dist/{api-Dfu655Qt.js.map → api-BOP8gF35.js.map} +1 -1
- package/dist/api-BphZo4f2.js +2 -0
- package/dist/api-key-login-BEi3wymJ.js +2 -0
- package/dist/{api-key-login-BgwfAXfv.js → api-key-login-DbrZknHp.js} +5 -5
- package/dist/{api-key-login-BgwfAXfv.js.map → api-key-login-DbrZknHp.js.map} +1 -1
- package/dist/bin.js +1 -1
- package/dist/{bounded-fs-TRL6IsoU.js → bounded-fs-BENh-Jzc.js} +3 -3
- package/dist/{bounded-fs-TRL6IsoU.js.map → bounded-fs-BENh-Jzc.js.map} +1 -1
- package/dist/{ci-install-nc1IKuX2.js → ci-install-DnjuAl1l.js} +4 -4
- package/dist/{ci-install-nc1IKuX2.js.map → ci-install-DnjuAl1l.js.map} +1 -1
- package/dist/{debug-BfO_Txo8.js → debug-C_9xDg9j.js} +3 -3
- package/dist/{debug-BfO_Txo8.js.map → debug-C_9xDg9j.js.map} +1 -1
- package/dist/{gateway-CLQQb96F.js → gateway-C6_zPLyx.js} +5 -5
- package/dist/{gateway-CLQQb96F.js.map → gateway-C6_zPLyx.js.map} +1 -1
- package/dist/{headless-DMO8N_FD.js → headless-BIQpNAHc.js} +2 -2
- package/dist/{headless-DMO8N_FD.js.map → headless-BIQpNAHc.js.map} +1 -1
- package/dist/{headless-CHGNEzO3.js → headless-sWzDbxAu.js} +1 -1
- package/dist/{host-resolution-DftfSjJD.js → host-resolution-C3ctTKNg.js} +4 -4
- package/dist/{host-resolution-DftfSjJD.js.map → host-resolution-C3ctTKNg.js.map} +1 -1
- package/dist/{install-MWoo1hXL.js → install-CDhljVka.js} +1 -1
- package/dist/{install-C9FPM37m.js → install-Dayhuaun.js} +5 -5
- package/dist/{install-C9FPM37m.js.map → install-Dayhuaun.js.map} +1 -1
- package/dist/{interactive-DOnolJHB.js → interactive-CjbttghI.js} +3 -3
- package/dist/{interactive-DOnolJHB.js.map → interactive-CjbttghI.js.map} +1 -1
- package/dist/{links-CatXPRxH.js → links-BOOGSL_G.js} +3 -3
- package/dist/{links-CatXPRxH.js.map → links-BOOGSL_G.js.map} +1 -1
- package/dist/{main-CINmZtoW.js → main-MzQaWN8w.js} +20 -20
- package/dist/{main-CINmZtoW.js.map → main-MzQaWN8w.js.map} +1 -1
- package/dist/{mcp-KVV6Z_bq.js → mcp-BwlD260T.js} +3 -3
- package/dist/{mcp-KVV6Z_bq.js.map → mcp-BwlD260T.js.map} +1 -1
- package/dist/{mcp-prompt-streaming-M6UnLgGX.js → mcp-prompt-streaming-CoplNPI8.js} +5 -5
- package/dist/{mcp-prompt-streaming-M6UnLgGX.js.map → mcp-prompt-streaming-CoplNPI8.js.map} +1 -1
- package/dist/{models-B0huHItG.js → models-HwzgIr2E.js} +2 -2
- package/dist/{models-B0huHItG.js.map → models-HwzgIr2E.js.map} +1 -1
- package/dist/{oauth-session-9EU3ky0v.js → oauth-session-C4JV-yA9.js} +2 -2
- package/dist/{oauth-session-9EU3ky0v.js.map → oauth-session-C4JV-yA9.js.map} +1 -1
- package/dist/{orchestrator-tools-QWyDLu_7.js → orchestrator-tools-Dr4w80Tn.js} +3 -3
- package/dist/{orchestrator-tools-QWyDLu_7.js.map → orchestrator-tools-Dr4w80Tn.js.map} +1 -1
- package/dist/{package-manager-B-9I_fAT.js → package-manager-Zw0JN1Kh.js} +4 -4
- package/dist/{package-manager-B-9I_fAT.js.map → package-manager-Zw0JN1Kh.js.map} +1 -1
- package/dist/{pi-BSnSk6rv.js → pi-qzRJcTgu.js} +12 -12
- package/dist/{pi-BSnSk6rv.js.map → pi-qzRJcTgu.js.map} +1 -1
- package/dist/{playground-Drz1qszs.js → playground-B0SpcGYQ.js} +4 -4
- package/dist/{playground-Drz1qszs.js.map → playground-B0SpcGYQ.js.map} +1 -1
- package/dist/{posthog-integration-mIQGBc1f.js → posthog-integration-4vQjbU-f.js} +21 -21
- package/dist/{posthog-integration-mIQGBc1f.js.map → posthog-integration-4vQjbU-f.js.map} +1 -1
- package/dist/{programs-mxhRXwP-.js → programs-7aIrstFy.js} +15 -15
- package/dist/{programs-mxhRXwP-.js.map → programs-7aIrstFy.js.map} +1 -1
- package/dist/programs-B9Iu3Mye.js +3 -0
- package/dist/provisioning-BG_lh4hk.js +2 -0
- package/dist/{provisioning-CHUjae9V.js → provisioning-BMJ3J0Vb.js} +4 -4
- package/dist/{provisioning-CHUjae9V.js.map → provisioning-BMJ3J0Vb.js.map} +1 -1
- package/dist/{queue-tools-PFG6H93Q.js → queue-tools-D8HPaGck.js} +4 -4
- package/dist/{queue-tools-PFG6H93Q.js.map → queue-tools-D8HPaGck.js.map} +1 -1
- package/dist/{results-5yCKf2cI.js → results-8xNiBbaP.js} +2 -2
- package/dist/{results-5yCKf2cI.js.map → results-8xNiBbaP.js.map} +1 -1
- package/dist/{run-D5TKJuSL.js → run-DUcJFhzP.js} +7 -7
- package/dist/{run-D5TKJuSL.js.map → run-DUcJFhzP.js.map} +1 -1
- package/dist/{run-W03b6jNM.js → run-Dte7AuRg.js} +10 -10
- package/dist/{run-W03b6jNM.js.map → run-Dte7AuRg.js.map} +1 -1
- package/dist/run-tool-BXpuefYa.js +2 -0
- package/dist/{run-tool-DTQF8mmI.js → run-tool-sdhoPp9H.js} +13 -13
- package/dist/{run-tool-DTQF8mmI.js.map → run-tool-sdhoPp9H.js.map} +1 -1
- package/dist/{runners-B_84kPyf.js → runners-DxbNGuKH.js} +8 -8
- package/dist/{runners-B_84kPyf.js.map → runners-DxbNGuKH.js.map} +1 -1
- package/dist/{security-pApVawYb.js → security-BMqK129i.js} +5 -5
- package/dist/{security-pApVawYb.js.map → security-BMqK129i.js.map} +1 -1
- package/dist/{setup-utils-CQY8PHjR.js → setup-utils-DBOw3lnu.js} +3 -3
- package/dist/{setup-utils-CQY8PHjR.js.map → setup-utils-DBOw3lnu.js.map} +1 -1
- package/dist/{start-playground-DAw1GbLt.js → start-playground-Uf3nte-P.js} +7 -7
- package/dist/{start-playground-DAw1GbLt.js.map → start-playground-Uf3nte-P.js.map} +1 -1
- package/dist/{subagent-YLGSvgoZ.js → subagent-DYza5kN6.js} +3 -3
- package/dist/{subagent-YLGSvgoZ.js.map → subagent-DYza5kN6.js.map} +1 -1
- package/dist/{task-3hRSovmK.js → task-CiStV2CD.js} +14 -14
- package/dist/{task-3hRSovmK.js.map → task-CiStV2CD.js.map} +1 -1
- package/dist/{telemetry-BPBzqsb_.js → telemetry-DLLuQiCt.js} +2 -2
- package/dist/{telemetry-BPBzqsb_.js.map → telemetry-DLLuQiCt.js.map} +1 -1
- package/dist/{terminal-DZsYFbm9.js → terminal-B4EXuZra.js} +18 -18
- package/dist/{terminal-DZsYFbm9.js.map → terminal-B4EXuZra.js.map} +1 -1
- package/dist/{tools-D_AtrhbO.js → tools-BTAi_RrA.js} +13 -13
- package/dist/{tools-D_AtrhbO.js.map → tools-BTAi_RrA.js.map} +1 -1
- package/dist/{tools-DyzRi_dQ.js → tools-M5ENAiPc.js} +8 -8
- package/dist/{tools-DyzRi_dQ.js.map → tools-M5ENAiPc.js.map} +1 -1
- package/dist/{tui-UI6ctBQJ.js → tui-DU9MmsAK.js} +1 -1
- package/dist/{tui-CRp0iCuT.js → tui-DigZ-Q1i.js} +4 -4
- package/dist/{tui-CRp0iCuT.js.map → tui-DigZ-Q1i.js.map} +1 -1
- package/dist/{upload-step-DrYXnpzI.js → upload-step-VNWq-3aL.js} +3 -3
- package/dist/{upload-step-DrYXnpzI.js.map → upload-step-VNWq-3aL.js.map} +1 -1
- package/dist/{urls-WeqfBLP4.js → urls-Bgngk5HI.js} +2 -2
- package/dist/{urls-WeqfBLP4.js.map → urls-Bgngk5HI.js.map} +1 -1
- package/dist/{wizard-abort-BwEefm6P.js → wizard-abort-CdKk1lpi.js} +4 -4
- package/dist/{wizard-abort-BwEefm6P.js.map → wizard-abort-CdKk1lpi.js.map} +1 -1
- package/dist/{yara-hooks-MDtFbuoM.js → yara-hooks-DCgSsXFP.js} +1 -1
- package/dist/{yara-hooks-aDbdsitJ.js → yara-hooks-DPqy6eVo.js} +4 -4
- package/dist/{yara-hooks-aDbdsitJ.js.map → yara-hooks-DPqy6eVo.js.map} +1 -1
- package/package.json +6 -1
- package/dist/analytics-Ix9HEra3.js +0 -2
- package/dist/api-H2UaPgjv.js +0 -2
- package/dist/api-key-login-Cow3UDAN.js +0 -2
- package/dist/programs-Bv0_YHzE.js +0 -3
- package/dist/provisioning-BVsxSNve.js +0 -2
- package/dist/run-tool-DjERhu7G.js +0 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { it as WIZARD_USER_AGENT } from "./analytics-
|
|
1
|
+
import { it as WIZARD_USER_AGENT } from "./analytics-CX2kvq0R.js";
|
|
2
2
|
import axios from "axios";
|
|
3
3
|
//#region src/shared/utils/urls.ts
|
|
4
4
|
/**
|
|
@@ -64,4 +64,4 @@ const getIntegrationAuthorizeUrl = (appHost, projectId, kind) => `${appHost.repl
|
|
|
64
64
|
//#endregion
|
|
65
65
|
export { getOAuthUrl as a, getIntegrationAuthorizeUrl as i, getCloudUrl as n, getUiHostFromHost as o, getHost as r, resolveBaseUrl as s, detectRegion as t };
|
|
66
66
|
|
|
67
|
-
//# sourceMappingURL=urls-
|
|
67
|
+
//# sourceMappingURL=urls-Bgngk5HI.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"urls-
|
|
1
|
+
{"version":3,"file":"urls-Bgngk5HI.js","names":[],"sources":["../src/shared/utils/urls.ts"],"sourcesContent":["import axios from 'axios';\nimport { IS_DEV, WIZARD_USER_AGENT } from '@shared/constants';\nimport type { CloudRegion } from './types';\n\n/**\n * Resolve a pinned PostHog base URL from an optional override. When present, it\n * becomes the single source of truth for every PostHog origin — the\n * API/ingestion host, the cloud/app URL, and the OAuth server — bypassing\n * region resolution entirely.\n *\n * The override lives on the session (`session.baseUrl`, set by `--base-url`) and\n * is threaded in by callers, so there is no hidden module-level state. A `--base-url`\n * value wins; otherwise a dev/test build implies `localhost:8010`; otherwise the\n * result is `undefined`, meaning region-based resolution applies.\n *\n * This is the runtime equivalent of the `IS_DEV` localhost routing: `IS_DEV` is a\n * build-time constant that tsdown compiles out of production builds, so it can't\n * point a shipped wizard at a local stack — `--base-url` can. Both feed this one\n * resolver so every URL helper only asks the question once.\n */\nexport const resolveBaseUrl = (override?: string): string | undefined => {\n return override?.trim() || (IS_DEV ? 'http://localhost:8010' : undefined);\n};\n\nexport const getUiHostFromHost = (host: string) => {\n if (host.includes('us.i.posthog.com')) {\n return 'https://us.posthog.com';\n }\n\n if (host.includes('eu.i.posthog.com')) {\n return 'https://eu.posthog.com';\n }\n\n return host;\n};\n\nexport const getHost = (region: CloudRegion, baseUrl?: string) => {\n const override = resolveBaseUrl(baseUrl);\n if (override) {\n return override;\n }\n\n if (region === 'eu') {\n return 'https://eu.i.posthog.com';\n }\n\n return 'https://us.i.posthog.com';\n};\n\nexport const getCloudUrl = (region: CloudRegion, baseUrl?: string) => {\n const override = resolveBaseUrl(baseUrl);\n if (override) {\n return override;\n }\n\n if (region === 'eu') {\n return 'https://eu.posthog.com';\n }\n\n return 'https://us.posthog.com';\n};\n\nexport async function detectRegion(\n accessToken: string,\n baseUrl?: string,\n): Promise<CloudRegion> {\n // With a pinned base URL there is only one instance to talk to — skip the\n // us/eu probe and default the region label to 'us'. (Covers IS_DEV too, via\n // resolveBaseUrl.) The URLs themselves come from the override, not this label.\n if (resolveBaseUrl(baseUrl)) {\n return 'us';\n }\n\n const headers = {\n Authorization: `Bearer ${accessToken}`,\n 'User-Agent': WIZARD_USER_AGENT,\n };\n\n const [usResult, euResult] = await Promise.allSettled([\n axios.get('https://us.posthog.com/api/users/@me/', { headers }),\n axios.get('https://eu.posthog.com/api/users/@me/', { headers }),\n ]);\n\n if (usResult.status === 'fulfilled') return 'us';\n if (euResult.status === 'fulfilled') return 'eu';\n\n throw new Error(\n 'Could not determine cloud region from access token. Please check your PostHog account.',\n );\n}\n\n/** Region-agnostic prod OAuth server. Resolves to the right region server-side. */\nconst PROD_OAUTH_URL = 'https://oauth.posthog.com';\n\n/**\n * OAuth server URL. Follows the base-URL override (and thus IS_DEV → localhost),\n * otherwise the region-agnostic prod OAuth host.\n */\nexport const getOAuthUrl = (baseUrl?: string): string =>\n resolveBaseUrl(baseUrl) ?? PROD_OAUTH_URL;\n\n/**\n * One-click integration install/authorize deep-link. Opening it in the user's\n * logged-in browser runs the install directly — no settings-page hunting. The\n * one link covers fresh install, link-existing, and re-auth.\n */\nexport const getIntegrationAuthorizeUrl = (\n appHost: string,\n projectId: number,\n kind: string,\n): string =>\n `${appHost.replace(\n /\\/$/,\n '',\n )}/api/environments/${projectId}/integrations/authorize?kind=${kind}`;\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAoBA,MAAa,kBAAkB,aAA0C;AACvE,QAAO,UAAU,MAAM,IAAwC,KAAA;;AAGjE,MAAa,qBAAqB,SAAiB;AACjD,KAAI,KAAK,SAAS,mBAAmB,CACnC,QAAO;AAGT,KAAI,KAAK,SAAS,mBAAmB,CACnC,QAAO;AAGT,QAAO;;AAGT,MAAa,WAAW,QAAqB,YAAqB;CAChE,MAAM,WAAW,eAAe,QAAQ;AACxC,KAAI,SACF,QAAO;AAGT,KAAI,WAAW,KACb,QAAO;AAGT,QAAO;;AAGT,MAAa,eAAe,QAAqB,YAAqB;CACpE,MAAM,WAAW,eAAe,QAAQ;AACxC,KAAI,SACF,QAAO;AAGT,KAAI,WAAW,KACb,QAAO;AAGT,QAAO;;AAGT,eAAsB,aACpB,aACA,SACsB;AAItB,KAAI,eAAe,QAAQ,CACzB,QAAO;CAGT,MAAM,UAAU;EACd,eAAe,UAAU;EACzB,cAAc;EACf;CAED,MAAM,CAAC,UAAU,YAAY,MAAM,QAAQ,WAAW,CACpD,MAAM,IAAI,yCAAyC,EAAE,SAAS,CAAC,EAC/D,MAAM,IAAI,yCAAyC,EAAE,SAAS,CAAC,CAChE,CAAC;AAEF,KAAI,SAAS,WAAW,YAAa,QAAO;AAC5C,KAAI,SAAS,WAAW,YAAa,QAAO;AAE5C,OAAM,IAAI,MACR,yFACD;;;AAIH,MAAM,iBAAiB;;;;;AAMvB,MAAa,eAAe,YAC1B,eAAe,QAAQ,IAAI;;;;;;AAO7B,MAAa,8BACX,SACA,WACA,SAEA,GAAG,QAAQ,QACT,OACA,GACD,CAAC,oBAAoB,UAAU,+BAA+B"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { o as logToFile } from "./debug-
|
|
2
|
-
import { n as analytics } from "./analytics-
|
|
1
|
+
import { o as logToFile } from "./debug-C_9xDg9j.js";
|
|
2
|
+
import { n as analytics } from "./analytics-CX2kvq0R.js";
|
|
3
3
|
import { a as WizardError } from "./errors-BXlWujEw.js";
|
|
4
|
-
import { m as runCleanups } from "./agent-interface-
|
|
4
|
+
import { m as runCleanups } from "./agent-interface-D8mrarFD.js";
|
|
5
5
|
//#region src/host/wizard-abort.ts
|
|
6
6
|
/**
|
|
7
7
|
* Single exit point for the wizard. Use instead of process.exit() directly.
|
|
@@ -81,4 +81,4 @@ async function wizardAbort(host, options) {
|
|
|
81
81
|
//#endregion
|
|
82
82
|
export { startHostExit as n, wizardAbort as r, registerShutdown as t };
|
|
83
83
|
|
|
84
|
-
//# sourceMappingURL=wizard-abort-
|
|
84
|
+
//# sourceMappingURL=wizard-abort-CdKk1lpi.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wizard-abort-
|
|
1
|
+
{"version":3,"file":"wizard-abort-CdKk1lpi.js","names":[],"sources":["../src/host/wizard-abort.ts"],"sourcesContent":["/**\n * Single exit point for the wizard. Use instead of process.exit() directly.\n *\n * Sequence: cleanup -> error capture (optional) -> analytics shutdown -> outro -> the host's exit\n *\n * WizardError (from `@shared/errors`) is a data carrier passed to wizardAbort() for analytics context, never thrown.\n */\nimport { analytics } from '@utils/analytics';\nimport { logToFile } from '@utils/debug';\nimport { OutroKind, type OutroData } from '@shared/outro';\nimport type { ErrorCode } from '@shared/errors';\nimport { WizardError } from '@shared/errors';\nimport { clearCleanups, runCleanups } from '@utils/cleanup';\n\nexport interface WizardAbortOptions {\n message?: string;\n /** Structured error data for the outro; built from `message` when absent. */\n outroData?: OutroData;\n error?: Error | WizardError;\n exitCode?: number;\n code?: ErrorCode;\n detail?: Record<string, unknown>;\n /** Terminal analytics status. Defaults from whether `error` is set. */\n status?: 'error' | 'cancelled';\n}\n\n/**\n * Shows an abort's outro, waits for the user to dismiss it, and emits the\n * machine-readable error line where the host calls for one. Each caller passes\n * its host's presenter and exit to `wizardAbort`, so nothing looks either up.\n */\nexport type AbortPresenter = (\n outro: OutroData,\n report: {\n code?: ErrorCode;\n message: string;\n detail?: Record<string, unknown>;\n },\n) => Promise<void>;\n\n/** A host's end: the first `end` or `fail` wins, and `exited` settles with it. */\nexport interface HostExit {\n readonly exited: Promise<number>;\n end(code: number): void;\n fail(error: unknown): void;\n readonly ended: boolean;\n}\n\n/** What `wizardAbort` ends: how the host shows the outro, and the exit that takes the code. */\nexport interface AbortHost {\n present: AbortPresenter;\n exit: Pick<HostExit, 'end'>;\n}\n\n/** Start a host's exit; the abort that ends it is handed this exit. */\nexport function startHostExit(): HostExit {\n let resolve!: (code: number) => void;\n let reject!: (error: unknown) => void;\n const exited = new Promise<number>((res, rej) => {\n resolve = res;\n reject = rej;\n });\n let ended = false;\n const exit: HostExit = {\n exited,\n end(code) {\n if (ended) return;\n ended = true;\n resolve(code);\n },\n fail(error) {\n if (ended) return;\n ended = true;\n reject(error);\n },\n get ended() {\n return ended;\n },\n };\n return exit;\n}\n\nconst shutdownFns = new Set<\n (outcome: 'failed' | 'cancelled') => Promise<void>\n>();\n\nexport function registerShutdown(\n fn: (outcome: 'failed' | 'cancelled') => Promise<void>,\n): () => void {\n shutdownFns.add(fn);\n return () => {\n shutdownFns.delete(fn);\n };\n}\n\nexport function clearCleanup(): void {\n clearCleanups();\n shutdownFns.clear();\n}\n\nfunction resolveErrorCode(\n options: WizardAbortOptions,\n error: Error | WizardError | undefined,\n): ErrorCode | undefined {\n if (options.code) return options.code;\n if (error instanceof WizardError) return error.code;\n return undefined;\n}\n\nexport async function wizardAbort(\n host: AbortHost,\n options?: WizardAbortOptions,\n): Promise<never> {\n const {\n message = 'Wizard setup cancelled.',\n outroData,\n error,\n exitCode = 1,\n } = options ?? {};\n\n const code = resolveErrorCode(options ?? {}, error);\n const detail = options?.detail;\n\n logToFile(\n `[wizard-abort] exitCode=${exitCode}, code=${\n code ?? 'none'\n }, message: ${message}`,\n );\n if (error) {\n logToFile('[wizard-abort] error:', error);\n }\n\n // 1. Run registered cleanup functions\n runCleanups();\n const status = options?.status ?? (error ? 'error' : 'cancelled');\n await Promise.allSettled(\n [...shutdownFns].map((fn) =>\n fn(status === 'cancelled' ? 'cancelled' : 'failed'),\n ),\n );\n\n // 2. Capture error in analytics. An 'error' ending with no Error object\n // is captured as its code and message.\n const captured =\n error ??\n (status === 'error'\n ? new WizardError(message, undefined, code)\n : undefined);\n if (captured) {\n analytics.captureException(captured, {\n ...((captured instanceof WizardError && captured.context) || {}),\n ...(code ? { error_code: code } : {}),\n });\n }\n\n // 3. Shutdown analytics\n await analytics.shutdown(status);\n\n // 4. Show the error outro through the host's presenter. Synthesize OutroData\n // from `message` when the caller didn't provide structured data.\n const resolvedOutroData: OutroData = outroData ?? {\n kind: OutroKind.Error,\n message,\n };\n if (code && resolvedOutroData.kind === OutroKind.Error) {\n resolvedOutroData.errorCode ??= code;\n if (detail) resolvedOutroData.errorDetail ??= detail;\n }\n await host.present(resolvedOutroData, { code, message, detail });\n\n // 5. Hand the code to the host and never settle, so nothing after an abort runs.\n host.exit.end(exitCode);\n return new Promise<never>(() => undefined);\n}\n"],"mappings":";;;;;;;;;;;;;AAuDA,SAAgB,gBAA0B;CACxC,IAAI;CACJ,IAAI;CACJ,MAAM,SAAS,IAAI,SAAiB,KAAK,QAAQ;AAC/C,YAAU;AACV,WAAS;GACT;CACF,IAAI,QAAQ;AAiBZ,QAhBuB;EACrB;EACA,IAAI,MAAM;AACR,OAAI,MAAO;AACX,WAAQ;AACR,WAAQ,KAAK;;EAEf,KAAK,OAAO;AACV,OAAI,MAAO;AACX,WAAQ;AACR,UAAO,MAAM;;EAEf,IAAI,QAAQ;AACV,UAAO;;EAEV;;AAIH,MAAM,8BAAc,IAAI,KAErB;AAEH,SAAgB,iBACd,IACY;AACZ,aAAY,IAAI,GAAG;AACnB,cAAa;AACX,cAAY,OAAO,GAAG;;;AAS1B,SAAS,iBACP,SACA,OACuB;AACvB,KAAI,QAAQ,KAAM,QAAO,QAAQ;AACjC,KAAI,iBAAiB,YAAa,QAAO,MAAM;;AAIjD,eAAsB,YACpB,MACA,SACgB;CAChB,MAAM,EACJ,UAAU,2BACV,WACA,OACA,WAAW,MACT,WAAW,EAAE;CAEjB,MAAM,OAAO,iBAAiB,WAAW,EAAE,EAAE,MAAM;CACnD,MAAM,SAAS,SAAS;AAExB,WACE,2BAA2B,SAAS,SAClC,QAAQ,OACT,aAAa,UACf;AACD,KAAI,MACF,WAAU,yBAAyB,MAAM;AAI3C,cAAa;CACb,MAAM,SAAS,SAAS,WAAW,QAAQ,UAAU;AACrD,OAAM,QAAQ,WACZ,CAAC,GAAG,YAAY,CAAC,KAAK,OACpB,GAAG,WAAW,cAAc,cAAc,SAAS,CACpD,CACF;CAID,MAAM,WACJ,UACC,WAAW,UACR,IAAI,YAAY,SAAS,KAAA,GAAW,KAAK,GACzC,KAAA;AACN,KAAI,SACF,WAAU,iBAAiB,UAAU;EACnC,GAAK,oBAAoB,eAAe,SAAS,WAAY,EAAE;EAC/D,GAAI,OAAO,EAAE,YAAY,MAAM,GAAG,EAAE;EACrC,CAAC;AAIJ,OAAM,UAAU,SAAS,OAAO;CAIhC,MAAM,oBAA+B,aAAa;EAChD,MAAA;EACA;EACD;AACD,KAAI,QAAQ,kBAAkB,SAAA,SAA0B;AACtD,oBAAkB,cAAc;AAChC,MAAI,OAAQ,mBAAkB,gBAAgB;;AAEhD,OAAM,KAAK,QAAQ,mBAAmB;EAAE;EAAM;EAAS;EAAQ,CAAC;AAGhE,MAAK,KAAK,IAAI,SAAS;AACvB,QAAO,IAAI,cAAqB,KAAA,EAAU"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { d as redactSecretLines, l as prewarmYaraScanner, m as scanInstalledSkill } from "./yara-hooks-
|
|
1
|
+
import { d as redactSecretLines, l as prewarmYaraScanner, m as scanInstalledSkill } from "./yara-hooks-DPqy6eVo.js";
|
|
2
2
|
export { prewarmYaraScanner, redactSecretLines, scanInstalledSkill };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { o as logToFile, u as WIZARD_YARA_REPORT_FILE } from "./debug-
|
|
2
|
-
import { _ as EVENT_INVENTORY_FILE, f as CONTEXT_MILL_URL, g as EVENTS_AUDIT_REPORT_FILE, n as analytics, u as AWS_SKILLS_BASE_URL, v as EVENT_INVENTORY_PART_PATTERN, y as EVENT_PLAN_FILE } from "./analytics-
|
|
3
|
-
import { r as readFileHead } from "./bounded-fs-
|
|
1
|
+
import { o as logToFile, u as WIZARD_YARA_REPORT_FILE } from "./debug-C_9xDg9j.js";
|
|
2
|
+
import { _ as EVENT_INVENTORY_FILE, f as CONTEXT_MILL_URL, g as EVENTS_AUDIT_REPORT_FILE, n as analytics, u as AWS_SKILLS_BASE_URL, v as EVENT_INVENTORY_PART_PATTERN, y as EVENT_PLAN_FILE } from "./analytics-CX2kvq0R.js";
|
|
3
|
+
import { r as readFileHead } from "./bounded-fs-BENh-Jzc.js";
|
|
4
4
|
import fs from "fs";
|
|
5
5
|
import path from "path";
|
|
6
6
|
import fg from "fast-glob";
|
|
@@ -1031,4 +1031,4 @@ async function scanSkillFiles(cwd, skillDir, llmProvider) {
|
|
|
1031
1031
|
//#endregion
|
|
1032
1032
|
export { AUDIT_REPORT_FILE as C, AUDIT_CHECKS_FILE as S, scanVerdict as _, flushScanReport as a, fetchSkillMenu as b, isWizardDocumentationPath as c, redactSecretLines as d, repeatBlockReason as f, publishBlockingMatch as g, writeScanReport as h, createRepeatBlockTracker as i, prewarmYaraScanner as l, scanInstalledSkill as m, createPostToolUseYaraHooks as n, formatScanReport as o, scanAndTriage as p, createPreToolUseYaraHooks as r, formatYaraAbortMessage as s, captureScanReport as t, recordExternalScan as u, downloadSkill as v, coerceAuditChecks as w, fetchWithRetry as x, installSkillById as y };
|
|
1033
1033
|
|
|
1034
|
-
//# sourceMappingURL=yara-hooks-
|
|
1034
|
+
//# sourceMappingURL=yara-hooks-DPqy6eVo.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"yara-hooks-aDbdsitJ.js","names":[],"sources":["../src/shared/audit-ledger.ts","../src/shared/fetch-retry.ts","../src/shared/skill-menu.ts","../src/shared/skill-install.ts","../src/agent/yara-policy.ts","../src/agent/yara-hooks.ts"],"sourcesContent":["/**\n * The audit ledger's contract: the file the audit tools write into the user's\n * project, the check shape inside it, and the read-side coercion. Shared by\n * the agent's ledger tools (both harnesses), the scanner's documentation\n * allowlist and the audit program that watches the file.\n */\n\nexport type AuditStatus =\n | 'pending'\n | 'pass'\n | 'error'\n | 'warning'\n | 'suggestion';\n\nexport interface AuditCheck {\n id: string;\n area: string;\n label: string;\n status: AuditStatus;\n file?: string;\n details?: string;\n}\n\nexport const AUDIT_CHECKS_FILE = '.posthog-audit-checks.json';\nexport const AUDIT_REPORT_FILE = 'posthog-audit-report.md';\n\n/**\n * Read the audit checks ledger off disk. Validation lives at write time —\n * every writer (`audit_seed_checks` / `audit_add_checks` / `audit_resolve_checks`\n * MCP tools, `seedAuditLedger`) zod-parses entries before the atomic write,\n * so by the time the file watcher fires we trust the shape and only guard\n * against the file not being a JSON array (corrupted / hand-edited / not yet\n * seeded).\n */\nexport function coerceAuditChecks(parsed: unknown): AuditCheck[] {\n return Array.isArray(parsed) ? (parsed as AuditCheck[]) : [];\n}\n","/**\n * One retry + failover policy for every critical-path fetch. GitHub is primary,\n * AWS is the fallback. A status a retry can't heal (404, 403) spends no retry\n * budget at the origin that returned it — but it still costs the other origin\n * one try, because the two are published separately and GitHub 403s expired\n * asset redirects and blocked regions where the asset is present.\n */\n\nimport { logToFile } from '@utils/debug';\nimport { analytics } from '@utils/analytics';\nimport { CONTEXT_MILL_URL, AWS_SKILLS_BASE_URL } from './constants';\n\nconst DEFAULT_TIMEOUT_MS = 60000; // per attempt\nconst DEFAULT_MAX_ATTEMPTS = 3;\nconst DEFAULT_BACKOFF_MS = 500; // doubles each retry\n\nconst GITHUB_LATEST_PREFIX = `${CONTEXT_MILL_URL}/releases/latest/download/`;\nconst GITHUB_PINNED_PREFIX = `${CONTEXT_MILL_URL}/releases/download/`;\n/** Version-pinned URLs name their own prefix, so they need the host alone. */\nconst AWS_ORIGIN = new URL(AWS_SKILLS_BASE_URL).origin;\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Maps a context-mill release URL onto AWS; null when there is no second origin.\n *\n * .../releases/latest/download/x.zip -> <aws>/latest/x.zip\n * .../releases/download/v1.50.0/x.zip -> <aws>/v1.50.0/x.zip\n */\nexport function awsUrlFor(url: string): string | null {\n if (url.startsWith(GITHUB_LATEST_PREFIX)) {\n return `${AWS_SKILLS_BASE_URL}/${url.slice(GITHUB_LATEST_PREFIX.length)}`;\n }\n if (url.startsWith(GITHUB_PINNED_PREFIX)) {\n return `${AWS_ORIGIN}/${url.slice(GITHUB_PINNED_PREFIX.length)}`;\n }\n return null;\n}\n\n/** The two origins context-mill releases are published to. */\nexport type SkillsOrigin = 'github' | 'aws';\n\n/** Sticky: without it every asset re-pays GitHub's retry budget during an outage. */\nlet preferredOrigin: SkillsOrigin = 'github';\n\n/** Test seam — the preference deliberately outlives a single call. */\nexport function resetOriginPreference(): void {\n preferredOrigin = 'github';\n}\n\n/** A response status that retrying can't fix; propagates past the retry loop. */\nclass NonRetryableFetchError extends Error {}\n\n/**\n * 5xx are transient; 408 (request timeout) and 429 (rate limit) explicitly ask\n * to retry. Every other 4xx is a client error a retry won't heal (404 missing\n * skill, 403 bad token) — retrying only burns the backoff before failing.\n */\nfunction isRetryableStatus(status: number): boolean {\n return status >= 500 || status === 408 || status === 429;\n}\n\nexport interface RetryOpts {\n fetchImpl?: typeof fetch;\n sleepImpl?: (ms: number) => Promise<void>;\n timeoutMs?: number;\n maxAttempts?: number;\n backoffMs?: number;\n /** Set false to fail rather than reach for AWS. */\n failover?: boolean;\n /** Test seam for the failover/exhausted events. */\n onEvent?: (event: string, props: Record<string, unknown>) => void;\n}\n\nfunction messageOf(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\n/** Try one origin to exhaustion. */\nasync function fetchOrigin(\n url: string,\n opts: Required<Pick<RetryOpts, 'timeoutMs' | 'maxAttempts' | 'backoffMs'>> & {\n fetchImpl: typeof fetch;\n sleepImpl: (ms: number) => Promise<void>;\n },\n): Promise<Response> {\n const failures: string[] = [];\n for (let attempt = 1; attempt <= opts.maxAttempts; attempt++) {\n try {\n const resp = await opts.fetchImpl(url, {\n signal: AbortSignal.timeout(opts.timeoutMs),\n });\n if (resp.ok) return resp;\n const msg = `HTTP ${resp.status} ${resp.statusText}`.trim();\n if (!isRetryableStatus(resp.status))\n throw new NonRetryableFetchError(msg);\n throw new Error(msg);\n } catch (err) {\n if (err instanceof NonRetryableFetchError) throw err;\n failures.push(`attempt ${attempt}: ${messageOf(err)}`);\n if (attempt < opts.maxAttempts) {\n await opts.sleepImpl(opts.backoffMs * 2 ** (attempt - 1));\n }\n }\n }\n throw new Error(failures.join('; '));\n}\n\n/**\n * Retry with backoff, then fail over. Throws once every origin is exhausted.\n */\nexport async function fetchWithRetry(\n url: string,\n opts: RetryOpts = {},\n): Promise<Response> {\n const {\n fetchImpl = fetch,\n sleepImpl = sleep,\n timeoutMs = DEFAULT_TIMEOUT_MS,\n maxAttempts = DEFAULT_MAX_ATTEMPTS,\n backoffMs = DEFAULT_BACKOFF_MS,\n failover = true,\n onEvent = (event, props) => analytics.wizardCapture(event, props),\n } = opts;\n const originOpts = {\n fetchImpl,\n sleepImpl,\n timeoutMs,\n maxAttempts,\n backoffMs,\n };\n\n const awsUrl = failover ? awsUrlFor(url) : null;\n if (!awsUrl) {\n try {\n return await fetchOrigin(url, originOpts);\n } catch (err) {\n throw new Error(`fetch ${url} failed — ${messageOf(err)}`);\n }\n }\n\n // Sticky: whichever origin last worked is tried first for the rest of the run.\n const order: Array<{ origin: SkillsOrigin; target: string }> =\n preferredOrigin === 'aws'\n ? [\n { origin: 'aws', target: awsUrl },\n { origin: 'github', target: url },\n ]\n : [\n { origin: 'github', target: url },\n { origin: 'aws', target: awsUrl },\n ];\n\n const failures: string[] = [];\n for (const [index, candidate] of order.entries()) {\n try {\n const resp = await fetchOrigin(candidate.target, originOpts);\n if (candidate.origin !== preferredOrigin) {\n // The transition is the interesting event, not every later fetch.\n preferredOrigin = candidate.origin;\n logToFile(`fetchWithRetry: failed over to ${candidate.origin}`);\n onEvent('skills fetch failed over', {\n origin: candidate.origin,\n url: candidate.target,\n });\n }\n return resp;\n } catch (err) {\n // Non-retryable here still means \"ask the other origin\" — they are\n // published separately, and a 403 is as often about us as the asset.\n failures.push(`${candidate.origin}: ${messageOf(err)}`);\n if (index === order.length - 1) {\n onEvent('skills fetch failed', { url, failures: failures.length });\n }\n }\n }\n throw new Error(`fetch ${url} failed — ${failures.join(' | ')}`);\n}\n","/**\n * The skill menu: what context-mill publishes as `skill-menu.json` and how the\n * wizard reads it. Skill-backed CLI commands, the family picker, the skill\n * source screen and the agent's install path all resolve skills through this\n * one fetch, so it lives in shared and depends on nothing in the agent.\n */\n\nimport { logToFile } from '@utils/debug';\nimport { fetchWithRetry, type RetryOpts } from '@shared/fetch-retry';\n\nexport type SkillEntry = {\n id: string;\n name: string;\n downloadUrl: string;\n /** The hyphenated skill-group prefix of `id` (e.g. `posthog-integration-install`). */\n group?: string;\n /** The detection id this variant serves (e.g. `rails`, `react-router`). */\n framework?: string;\n /** The variant a bare framework id resolves to when its family has several. */\n default?: boolean;\n /** This entry's download is a bundle JSON of every variant, not a single skill's zip. */\n bundle?: boolean;\n /** Menu-only: the variants inside a bundle, expanded into entries of their own on fetch. */\n variants?: { id: string; framework?: string; default?: boolean }[];\n};\n\n/**\n * Entry in the wizard's runtime CLI registry. Mirrors the shape context-mill\n * publishes under `cliEntries` inside `skill-menu.json`. The wizard uses these\n * to register skill-backed subcommands at runtime instead of from a baked\n * build-time snapshot.\n */\nexport type CliEntry = {\n skillId: string;\n role: 'command' | 'skill' | 'internal';\n command?: string;\n parentCommand?: string;\n default?: boolean;\n displayName: string;\n description: string;\n};\n\nexport interface SkillMenu {\n categories: Record<string, SkillEntry[]>;\n /**\n * Skills exposed as CLI commands. Optional because context-mill releases\n * older than the runtime-resolver cutover don't emit this field.\n */\n cliEntries?: CliEntry[];\n}\n\n/** Expand a bundle entry into one entry per variant, so the menu reads the same whether a group ships bundled or as zips. */\nexport function expandBundleEntry(entry: SkillEntry): SkillEntry[] {\n if (!entry.bundle || !entry.variants) return [entry];\n return entry.variants.map((variant) => ({\n ...variant,\n name: entry.name,\n group: entry.group,\n bundle: true,\n downloadUrl: entry.downloadUrl,\n }));\n}\n\n/**\n * Fetch the skill menu from the skills server.\n * Returns parsed data on success, `null` on failure.\n */\nexport async function fetchSkillMenu(\n skillsBaseUrl: string,\n opts: RetryOpts = {},\n): Promise<SkillMenu | null> {\n const menuUrl = `${skillsBaseUrl}/skill-menu.json`;\n try {\n logToFile(`fetchSkillMenu: fetching from ${menuUrl}`);\n const resp = await fetchWithRetry(menuUrl, opts);\n const data = (await resp.json()) as SkillMenu;\n for (const [category, entries] of Object.entries(data.categories)) {\n data.categories[category] = entries.flatMap(expandBundleEntry);\n }\n logToFile(\n `fetchSkillMenu: loaded (${\n Object.keys(data.categories).length\n } categories)`,\n );\n return data;\n } catch (err: any) {\n logToFile(`fetchSkillMenu: error: ${err.message}`);\n return null;\n }\n}\n","// Download a context-mill skill into a project, and recognise the agent's own skill-install command.\n\nimport path from 'path';\nimport fs from 'fs';\nimport { unzipSync } from 'fflate';\nimport { logToFile } from '@utils/debug';\nimport { analytics } from '@utils/analytics';\nimport { fetchWithRetry, type RetryOpts } from '@shared/fetch-retry';\nimport { fetchSkillMenu, type SkillEntry } from '@shared/skill-menu';\n\n/** A bundle's files, keyed by variant short id then path. */\nexport type SkillBundle = {\n id: string;\n variants: Record<string, Record<string, string>>;\n};\n\n/** Extract a zip buffer, refusing entries that escape destDir (zip-slip). */\nfunction extractZipArchive(zip: Uint8Array, destDir: string): number {\n const root = path.resolve(destDir);\n let written = 0;\n for (const [entryPath, data] of Object.entries(unzipSync(zip))) {\n const target = path.resolve(root, entryPath);\n if (target !== root && !target.startsWith(root + path.sep)) {\n throw new Error(`zip entry escapes destination: ${entryPath}`);\n }\n if (entryPath.endsWith('/')) {\n fs.mkdirSync(target, { recursive: true });\n continue;\n }\n fs.mkdirSync(path.dirname(target), { recursive: true });\n fs.writeFileSync(target, data);\n written++;\n }\n return written;\n}\n\n/** Unpack the one variant this entry names out of a bundle; the rest is noise and never hits disk. */\nfunction extractBundle(\n bundle: SkillBundle,\n destDir: string,\n entryId: string,\n): number {\n if (\n typeof bundle?.id !== 'string' ||\n typeof bundle?.variants !== 'object' ||\n bundle.variants === null\n ) {\n throw new Error('malformed bundle: expected { id, variants }');\n }\n const files = bundle.variants[entryId.slice(bundle.id.length + 1)];\n if (!files) {\n throw new Error(`bundle ${bundle.id} has no variant \"${entryId}\"`);\n }\n const root = path.resolve(destDir);\n let written = 0;\n for (const [entryPath, contents] of Object.entries(files)) {\n const target = path.resolve(root, entryPath);\n if (target !== root && !target.startsWith(root + path.sep)) {\n throw new Error(`bundle entry escapes destination: ${entryPath}`);\n }\n fs.mkdirSync(path.dirname(target), { recursive: true });\n fs.writeFileSync(target, contents);\n written++;\n }\n return written;\n}\n\n/** Download a URL to a buffer, retrying transient failures with backoff. */\nasync function downloadWithRetry(\n url: string,\n opts: RetryOpts = {},\n): Promise<Uint8Array> {\n const resp = await fetchWithRetry(url, opts);\n return new Uint8Array(await resp.arrayBuffer());\n}\n\n/** Scans an extracted skill directory; a returned reason means it is poisoned. Injected because @shared cannot import @agent. */\nexport type SkillScanner = (skillDir: string) => Promise<string | null>;\n\n/** How to place a skill and what scans it — `scan` is stated by every caller so none inherits a silent default. */\nexport interface SkillInstallOptions {\n /** Base directory override, e.g. `.posthog/skills`. Default `.claude/skills`. */\n skillsRoot?: string;\n scan: SkillScanner;\n}\n\n/**\n * Download and extract a skill.\n * By default installs to `<installDir>/.claude/skills/<id>/`.\n */\nexport async function downloadSkill(\n skillEntry: SkillEntry,\n installDir: string,\n { skillsRoot, scan }: SkillInstallOptions,\n): Promise<{ success: boolean; error?: string }> {\n const skillDir = skillsRoot\n ? path.join(installDir, skillsRoot, skillEntry.id)\n : path.join(installDir, '.claude', 'skills', skillEntry.id);\n let step: 'download' | 'extract' | 'scan' = 'download';\n\n try {\n fs.mkdirSync(skillDir, { recursive: true });\n const data = await downloadWithRetry(skillEntry.downloadUrl);\n step = 'extract';\n const fileCount = skillEntry.bundle\n ? extractBundle(\n JSON.parse(Buffer.from(data).toString('utf8')) as SkillBundle,\n skillDir,\n skillEntry.id,\n )\n : extractZipArchive(data, skillDir);\n fs.writeFileSync(path.join(skillDir, '.posthog-wizard'), '');\n\n // Same scan the Bash-install hook runs — TS-path installs (linear\n // pre-install, MCP/pi install_skill, orchestrator cache + reference)\n // must not skip it.\n //\n // The scan is its own step: it runs the YARA-X WASM engine, and an engine\n // that fails to load throws from here. Left as `extract` that lands on the\n // event as an unzip failure, which the pure-JS unzip cannot produce.\n step = 'scan';\n const poisonReason = await scan(skillDir);\n if (poisonReason) {\n fs.rmSync(skillDir, { recursive: true, force: true });\n logToFile(`downloadSkill: ${poisonReason}`);\n analytics.wizardCapture('skill install failed', {\n skill_id: skillEntry.id,\n step: 'scan',\n platform: process.platform,\n error: poisonReason.slice(0, 500),\n });\n return { success: false, error: poisonReason };\n }\n\n logToFile(\n `downloadSkill: installed ${skillEntry.id} from ${skillEntry.downloadUrl} (${fileCount} files)`,\n );\n // The installed variant is a skill program's identity dimension in analytics.\n analytics.wizardCapture('skill installed', {\n skill_id: skillEntry.id,\n platform: process.platform,\n });\n return { success: true };\n } catch (err: any) {\n logToFile(`downloadSkill: error: ${err.message}`);\n // A skill-less run still reports success — keep the failure visible.\n analytics.wizardCapture('skill install failed', {\n skill_id: skillEntry.id,\n step,\n platform: process.platform,\n error: String(err.message).slice(0, 500),\n });\n return { success: false, error: err.message };\n }\n}\n\n/**\n * Structured result for installSkillById.\n * - `ok`: the skill was fetched and extracted; `path` is where it lives\n * relative to installDir.\n * - `menu-fetch-failed`: couldn't fetch or parse the skill menu.\n * - `skill-not-found`: the menu didn't contain a skill with this id.\n * - `download-failed`: found the skill but download/extract failed;\n * `message` has the underlying error.\n */\nexport type InstallSkillResult =\n | { kind: 'ok'; path: string }\n | { kind: 'menu-fetch-failed' }\n | { kind: 'skill-not-found'; skillId: string }\n | { kind: 'download-failed'; message: string };\n\n/**\n * High-level \"install a skill by ID\" helper. Fetches the skill menu,\n * finds the skill, downloads and extracts it. Programs should use this\n * instead of composing fetchSkillMenu + downloadSkill themselves.\n */\nexport async function installSkillById(\n skillId: string,\n installDir: string,\n skillsBaseUrl: string,\n options: SkillInstallOptions,\n): Promise<InstallSkillResult> {\n const menu = await fetchSkillMenu(skillsBaseUrl);\n if (!menu) return { kind: 'menu-fetch-failed' };\n\n const skill = Object.values(menu.categories)\n .flat()\n .find((s) => s.id === skillId);\n if (!skill) return { kind: 'skill-not-found', skillId };\n\n const result = await downloadSkill(skill, installDir, options);\n if (!result.success) {\n return { kind: 'download-failed', message: result.error ?? 'unknown' };\n }\n\n const relPath = options.skillsRoot\n ? `${options.skillsRoot}/${skillId}`\n : `.claude/skills/${skillId}`;\n return { kind: 'ok', path: relPath };\n}\n\n/**\n * Check if command is a PostHog skill installation from MCP.\n * We control the MCP server, so we only need to verify:\n * 1. It installs to .claude/skills/\n * 2. It downloads from a context-mill release origin (GitHub Releases or the\n * AWS mirror) or localhost (dev)\n *\n * Extracted to its own module to avoid a circular dependency\n * between agent-interface.ts and yara-hooks.ts.\n */\nexport function isSkillInstallCommand(command: string): boolean {\n if (!command.startsWith('mkdir -p .claude/skills/')) return false;\n\n const urlMatch = command.match(/curl -sL ['\"]([^'\"]+)['\"]/);\n if (!urlMatch) return false;\n\n // Literal prefixes rather than the constants in `@shared/constants`: an\n // allow-list is easier to audit when it reads as the URLs themselves.\n const url = urlMatch[1];\n return (\n url.startsWith('https://github.com/PostHog/context-mill/releases/') ||\n url.startsWith('https://context-mill.posthog.com/') ||\n /^http:\\/\\/localhost:\\d+\\//.test(url)\n );\n}\n\n// ---------------------------------------------------------------------------\n// Test-only exports\n// ---------------------------------------------------------------------------\n\nexport const __test = {\n extractZipArchive,\n extractBundle,\n downloadWithRetry,\n};\n","/**\n * Shared YARA scan policy: the vocabulary every scan surface speaks, and the\n * one decision they all make — does a set of matches terminate the session, or\n * only warn. Both fences (the anthropic PostToolUse hooks in `yara-hooks.ts`\n * and the pi tool_result extension in `harness/pi/security.ts`) resolve a\n * verdict here rather than each re-deriving it from severity and action.\n */\n\nimport type { ScanMatch } from '@posthog/warlock';\n\n/** Which content surface a warlock rule applies to (from rule `scan_context`). */\nexport type ScanContext = 'command' | 'input' | 'output';\n\n/** What a fence did about a match, as recorded in the scan report. */\nexport type ScanAction = 'blocked' | 'reverted' | 'warned' | 'aborted';\n\n/** Severity order, highest first, for picking the match worth reporting. */\nconst SEVERITY_RANK: Record<string, number> = {\n critical: 4,\n high: 3,\n medium: 2,\n low: 1,\n};\n\n/** Severities that end the session. Critical only — nothing below it is worth killing a run over. */\nconst TERMINAL_SEVERITIES = new Set(['critical']);\n\n/**\n * The match that ends the session rather than just warning. Severity alone\n * decides: a rule's `action: 'block'` is a recommendation about the *operation*\n * (PreToolUse still refuses the command), not a licence to end the run. Medium\n * and high `block` rules fire on first-party content often enough that\n * terminating on them costs more than it prevents.\n */\nexport function isTerminalMatch(match: ScanMatch): boolean {\n return TERMINAL_SEVERITIES.has(match.metadata.severity ?? '');\n}\n\n/** Return the highest-severity match from a list of matches. */\nexport function highestSeverityMatch(matches: ScanMatch[]): ScanMatch {\n return matches.reduce((worst, m) =>\n (SEVERITY_RANK[m.metadata.severity ?? ''] ?? 0) >\n (SEVERITY_RANK[worst.metadata.severity ?? ''] ?? 0)\n ? m\n : worst,\n );\n}\n\n/**\n * The match that refuses a publish, or null to allow. Critical only — every\n * 'output' rule asks for `remediate`, and the handoff has no second channel.\n */\nexport function publishBlockingMatch(matches: ScanMatch[]): ScanMatch | null {\n if (matches.length === 0) return null;\n const match = highestSeverityMatch(matches);\n return isTerminalMatch(match) ? match : null;\n}\n\nexport interface ScanVerdict {\n /** The match worth reporting: the highest-severity one. */\n match: ScanMatch;\n /** Session ends now; anything else is advisory. */\n terminal: boolean;\n /** How the scan report records it. */\n action: ScanAction;\n}\n\n/** Resolve what a fence should do about a set of matches. Null when clean. */\nexport function scanVerdict(matches: ScanMatch[]): ScanVerdict | null {\n if (matches.length === 0) return null;\n const match = highestSeverityMatch(matches);\n const terminal = isTerminalMatch(match);\n return { match, terminal, action: terminal ? 'aborted' : 'warned' };\n}\n","/**\n * YARA hook wiring for the Claude Agent SDK.\n *\n * Creates PreToolUse and PostToolUse hook callback arrays that integrate the\n * real @posthog/warlock security scanner (YARA-X via WASM) into the wizard's\n * agent loop. These hooks are registered in the SDK's query() options alongside\n * the existing Stop hook.\n *\n * PreToolUse hooks block dangerous commands before execution. PostToolUse hooks\n * detect violations in written code and prompt injection in read content, and\n * scan context-mill skill downloads.\n *\n * Warlock owns the rules; this file owns the *policy* — how a match maps to a\n * block / revert / terminate response, plus the optional LLM triage pass that\n * filters false positives before we act.\n *\n * Naming: \"yara\" is the technique — scanning content against YARA rules — so the\n * wizard-side wiring keeps that name. \"warlock\" is the engine package that runs\n * the rules. Both names showing up here is intentional.\n *\n * This is Layer 2 (L2) in the wizard's defense-in-depth model, complementing the\n * prompt-based commandments (L0) and the canUseTool() allowlist (L1).\n */\n\nimport fs from 'fs';\nimport path from 'path';\nimport { createHash } from 'crypto';\nimport fg from 'fast-glob';\nimport type {\n ScanMatch,\n TriageMatch,\n LLMProvider,\n Category,\n} from '@posthog/warlock';\nimport { logToFile } from '@utils/debug';\nimport { readFileHead } from '@utils/bounded-fs';\nimport { analytics } from '@utils/analytics';\nimport { isSkillInstallCommand } from '@shared/skill-install';\nimport {\n highestSeverityMatch,\n publishBlockingMatch,\n scanVerdict,\n} from './yara-policy';\nimport type { ScanAction, ScanContext } from './yara-policy';\nimport { WIZARD_YARA_REPORT_FILE } from '@utils/paths';\n// TODO(wizard#594): invert this dependency. The document names are shared\n// constants now, so nothing here imports a program, but this file still knows\n// which programs write PII-shaped reports: every new one has to be added.\n// Proper fix is inverted — programs declare their own doc paths and the hooks\n// read a generic registry. Land that before adding a fourth entry.\nimport {\n EVENTS_AUDIT_REPORT_FILE,\n EVENT_INVENTORY_FILE,\n EVENT_INVENTORY_PART_PATTERN,\n EVENT_PLAN_FILE,\n} from '@shared/constants';\nimport { AUDIT_REPORT_FILE } from '@shared/audit-ledger';\n\n// ─── Warlock module accessor ─────────────────────────────────────\n// Warlock is ESM-only and lazily inits its WASM engine + compiles rules on the\n// first scan() call. We import it once and cache the module promise so the\n// warmed engine (a warlock-internal singleton) is shared across every hook.\n// Mirrors the lazy `await import(...)` pattern used for the agent SDK.\n\ntype WarlockModule = typeof import('@posthog/warlock');\n\nlet warlockModulePromise: Promise<WarlockModule> | null = null;\n\nfunction getWarlock(): Promise<WarlockModule> {\n if (!warlockModulePromise) {\n // Clear the cache on rejection so a transient failure (flaky disk, ESM\n // resolver hiccup) doesn't poison the cache for the rest of the process\n // lifetime. Without this, one rejected import permanently locks every\n // hook into fail-closed-terminate until restart.\n warlockModulePromise = import('@posthog/warlock').catch((err) => {\n warlockModulePromise = null;\n throw err;\n });\n }\n return warlockModulePromise;\n}\n\n/**\n * Pay the WASM-init + rule-compile cost up front, off the hook path, so the\n * first real tool-call scan doesn't eat cold-start under a hook timeout.\n * Best-effort: a failure here is non-fatal — hooks still fail closed per scan.\n */\nexport async function prewarmYaraScanner(): Promise<void> {\n try {\n const warlock = await getWarlock();\n await warlock.scan('');\n logToFile('[YARA] warlock pre-warmed');\n } catch (err) {\n logToFile('[YARA] warlock pre-warm failed:', err);\n }\n}\n\n// ─── Types ───────────────────────────────────────────────────────\n// Loose hook types to avoid tight coupling to the SDK version.\n// The SDK hook types are: HookCallbackMatcher[], where each matcher\n// has { matcher?: string, hooks: HookCallback[], timeout?: number }\n\ntype HookInput = Record<string, unknown>;\ntype HookOutput = Record<string, unknown>;\ntype HookCallback = (\n input: HookInput,\n toolUseID: string | undefined,\n options: { signal: AbortSignal },\n) => Promise<HookOutput>;\n\nexport interface HookCallbackMatcher {\n matcher?: string;\n hooks: HookCallback[];\n timeout?: number;\n}\n\nexport type { ScanAction, ScanContext } from './yara-policy';\n\n// ─── Scan Report Accumulator ─────────────────────────────────────\n\ninterface ScanReportEntry {\n rule: string;\n severity: string;\n category: string;\n action: ScanAction;\n phase: string;\n tool: string;\n /** Rule description — the human-readable \"why\". Rule-static, safe to report. */\n description: string;\n /** Triage outcome, when triage ran. */\n triageVerdict?: string;\n /**\n * Free-text triage rationale. May quote scanned content, so it is written to\n * the local report file only — NEVER sent to PostHog (see captureScanReport).\n */\n triageReason?: string;\n}\n\nlet scanCount = 0;\nconst scanViolations: ScanReportEntry[] = [];\n\nfunction recordScan(): void {\n scanCount++;\n}\n\n// recordMatch is the single entry point for a violation (log + telemetry +\n// report). scanViolations is appended only there.\n\n/**\n * Log a match to the run log + PostHog, and record it in the run's scan report.\n * Single entry point so every violation is reported consistently.\n */\nfunction recordMatch(\n phase: string,\n tool: string,\n match: ScanMatch,\n action: ScanAction,\n): void {\n const triage = (match as TriageMatch).triage;\n logYaraMatch(phase, tool, match, action);\n scanViolations.push({\n rule: match.rule,\n severity: match.metadata.severity ?? 'unknown',\n category: match.metadata.category ?? 'unknown',\n action,\n phase,\n tool,\n description: match.metadata.description ?? '',\n triageVerdict: triage?.verdict,\n triageReason: triage?.reason,\n });\n}\n\n/** Reset counters (for testing) */\nexport function resetScanReport(): void {\n scanCount = 0;\n scanViolations.length = 0;\n}\n\n/**\n * Scan bookkeeping for harnesses that run a scanner outside these hooks (pi):\n * counts the scan and routes violations through `recordMatch`, so they reach\n * the run log, per-match telemetry, and the end-of-run scan report.\n */\nexport function recordExternalScan(\n phase: string,\n tool: string,\n violations: Array<{\n rule: string;\n severity: string;\n category: string;\n description: string;\n }>,\n action: ScanAction,\n): void {\n recordScan();\n for (const v of violations) {\n recordMatch(\n phase,\n tool,\n {\n rule: v.rule,\n metadata: {\n severity: v.severity as ScanMatch['metadata']['severity'],\n category: v.category as ScanMatch['metadata']['category'],\n description: v.description,\n },\n matchedStrings: [],\n },\n action,\n );\n }\n}\n\n/** Format the scan report summary. Returns null if no scans occurred */\nexport function formatScanReport(): string | null {\n if (scanCount === 0) return null;\n\n const lines: string[] = ['', '— YARA Scanner Summary —'];\n const violationCount = scanViolations.length;\n const cleanCount = scanCount - violationCount;\n\n lines.push(\n `✓ ${scanCount} tool calls scanned, ${violationCount} violation${\n violationCount !== 1 ? 's' : ''\n } detected`,\n );\n\n if (violationCount > 0) {\n lines.push('');\n for (const v of scanViolations) {\n const tag = v.action.toUpperCase();\n lines.push(\n ` [${tag}] ${v.rule} (${v.severity.toUpperCase()}) — ${v.phase}:${\n v.tool\n }`,\n );\n }\n }\n\n if (cleanCount > 0) {\n lines.push('');\n lines.push(\n `No violations: ✓ ${cleanCount} clean scan${cleanCount !== 1 ? 's' : ''}`,\n );\n }\n\n lines.push('');\n return lines.join('\\n');\n}\n\n/** Write the scan report to a JSON file. Returns the file path, or null if no scans occurred. */\nexport function writeScanReport(): string | null {\n if (scanCount === 0) return null;\n\n const report = {\n summary: {\n totalScans: scanCount,\n violations: scanViolations.length,\n clean: scanCount - scanViolations.length,\n },\n violations: scanViolations,\n };\n\n try {\n fs.writeFileSync(WIZARD_YARA_REPORT_FILE, JSON.stringify(report, null, 2));\n } catch (err) {\n logToFile('[YARA] Failed to write scan report:', err);\n return null;\n }\n return WIZARD_YARA_REPORT_FILE;\n}\n\n// ─── User-facing abort copy ──────────────────────────────────────\n//\n// The abort screen is the one security surface a user actually sees, so it must\n// say what was caught and that we protected them, in plain language, instead of\n// a bare \"Security violation detected.\" Warlock owns the rules and categories;\n// this map is the wizard's *presentation* of a category it already detected. We\n// deliberately do not surface warlock's own rule `description` here: it is\n// written for maintainers (it ships to telemetry) and reads as technical. The\n// `Category` type is a stable, append-only warlock API, so a new category just\n// falls through to the generic line below rather than breaking the build.\n\nconst CATEGORY_DESCRIPTIONS: Record<Category, string> = {\n prompt_injection:\n 'hit hidden instructions in a file it read, a possible attempt to hijack the setup',\n exfiltration: 'tried to send data to an outside location',\n destructive_operations: 'tried to run a destructive command',\n supply_chain: 'tried to pull in untrusted code',\n posthog_pii: 'tried to write personal data (PII) into your project',\n posthog_hardcoded_key: 'tried to hardcode a PostHog key into your project',\n hardcoded_secret: 'tried to hardcode a secret into your project',\n};\n\n/** Most recent violation that terminated the run, or null. */\nfunction lastTerminatingViolation(): ScanReportEntry | null {\n for (let i = scanViolations.length - 1; i >= 0; i--) {\n if (scanViolations[i].action === 'aborted') return scanViolations[i];\n }\n return null;\n}\n\n/**\n * Build the user-facing copy for the security abort screen: what the scanner\n * caught and what we did about it. This screen renders only because the run was\n * stopped, so the \"what we did\" half is constant. Falls back to a generic line\n * when no specific violation was recorded (e.g. the scanner itself errored and\n * failed closed before a match was logged).\n */\nexport function formatYaraAbortMessage(): string {\n const v = lastTerminatingViolation();\n const what =\n (v && CATEGORY_DESCRIPTIONS[v.category as Category]) ??\n 'did something our security scanner flagged';\n\n return (\n 'Security check stopped the setup.\\n\\n' +\n `The agent ${what}. We stopped the run to keep your project safe.\\n` +\n 'No unsafe changes were left in your project.\\n\\n' +\n 'If this looks wrong, let us know: wizard@posthog.com'\n );\n}\n\n// ─── Hook Timeouts (ms) ─────────────────────────────────────────\n// Generous because triage adds an LLM round-trip, and because a *fired* hook\n// timeout means the protective action is dropped (fail-open). The triage HTTP\n// call has its own shorter internal timeout (see agent-interface), so a hung\n// triage fails inside our catch (fail-closed) before these fire.\n\n/** Timeout for scan hooks (PreToolUse, PostToolUse Write/Edit/Read/Grep) */\nconst HOOK_TIMEOUT_MS = 30_000;\n/** Timeout for the skill install hook (filesystem I/O + multiple scans) */\nconst SKILL_SCAN_HOOK_TIMEOUT_MS = 30_000;\n\n/**\n * Chunk size for scanning (100 KB). Oversized content is scanned in\n * overlapping chunks so coverage is complete — nothing is silently truncated.\n * The size also bounds what a single triage call pastes into the LLM prompt\n * (~25K tokens), so triage always sees the chunk its matches came from.\n */\nconst SCAN_CHUNK_SIZE = 100_000;\n\n// A skill file is read at most this far; the rest is head-scanned and logged.\nconst SKILL_FILE_SCAN_BYTES = 10 * 1024 * 1024;\n/**\n * Overlap between adjacent chunks so a pattern straddling a chunk boundary\n * still lands whole inside at least one chunk. YARA rule strings are at most\n * a few hundred bytes; 4 KB is generous.\n */\nconst SCAN_CHUNK_OVERLAP = 4_096;\n\n// ─── Logging ─────────────────────────────────────────────────────\n\nfunction logYaraMatch(\n phase: string,\n tool: string,\n match: ScanMatch,\n action: ScanAction,\n): void {\n const severity = match.metadata.severity ?? 'unknown';\n const category = match.metadata.category ?? 'unknown';\n const ruleAction = match.metadata.action ?? 'unknown';\n const description = match.metadata.description ?? '';\n logToFile(\n `[YARA] ${phase}:${tool} [${action.toUpperCase()}] rule \"${match.rule}\" ` +\n `(severity: ${severity}, category: ${category}, action: ${ruleAction})\\n` +\n ` Description: ${description}`,\n );\n analytics.wizardCapture('yara rule matched', {\n rule: match.rule,\n severity: match.metadata.severity,\n category: match.metadata.category,\n rule_action: match.metadata.action,\n action,\n phase,\n tool,\n // Rule-static description — the \"why\". The free-text triage reason is\n // deliberately omitted (it can quote scanned content; stays local-only).\n description: match.metadata.description,\n triage_verdict: (match as TriageMatch).triage?.verdict,\n });\n}\n\n/**\n * Send the run's full scan report to PostHog so maintainers can see why a run\n * was flagged, warned, or aborted. This is the maintainer-facing report — it is\n * NEVER shown to the user. No-op if nothing was scanned. The free-text triage\n * reason is omitted to avoid sending scanned content off the user's machine.\n */\nexport function captureScanReport(): void {\n if (scanCount === 0) return;\n analytics.wizardCapture('yara scan report', {\n total_scans: scanCount,\n violation_count: scanViolations.length,\n clean_count: scanCount - scanViolations.length,\n violations: scanViolations.map((v) => ({\n rule: v.rule,\n severity: v.severity,\n category: v.category,\n action: v.action,\n phase: v.phase,\n tool: v.tool,\n description: v.description,\n triage_verdict: v.triageVerdict,\n })),\n });\n // Reset after sending so a second captureScanReport on this same run\n // (e.g. success-path call followed by an abort during shutdown) can't\n // ship duplicate telemetry. Also protects future chained-program runs.\n scanCount = 0;\n scanViolations.length = 0;\n}\n\n/**\n * End-of-run flush for the scan report. Wired once at the runner seam so every\n * harness (linear, orchestrator, and any future shape) reports without having\n * to know warlock exists — scanning is already harness-agnostic (it runs from\n * SDK Pre/PostToolUse hooks), and this keeps reporting at a single shared seam.\n *\n * Order matters: write the local --yara-report file FIRST (it reads scanCount /\n * scanViolations), THEN send telemetry. captureScanReport() zeroes scan state,\n * which is what makes this whole function idempotent — a second call from\n * another termination path (e.g. finally after an abort already flushed) finds\n * scanCount === 0 and every step no-ops.\n *\n * Returns the user-facing report line when a report was written; the caller\n * decides where it goes (the runner emits it as progress).\n */\nexport function flushScanReport(options: {\n yaraReport: boolean;\n}): string | undefined {\n let line: string | undefined;\n if (options.yaraReport) {\n const reportPath = writeScanReport();\n if (reportPath) {\n const summary = formatScanReport();\n line = `YARA scan report: ${reportPath}${summary ?? ''}`;\n }\n }\n captureScanReport();\n return line;\n}\n\n// ─── Wizard-documentation allowlist ───────────────────────────────\n//\n// Files the wizard's own programs write to describe events the user's\n// codebase already captures, or events the integration program is\n// proposing to add. When the agent copies a literal\n// `posthog.capture('event', { email: ... })` snippet (or a property\n// list including PII-shaped keys) into one of these files, the\n// `pii_in_capture_call` rule (category: posthog_pii) fires even though\n// the wizard is documenting / planning, not introducing, the pattern.\n// Suppress posthog_pii matches on these paths only; every other rule\n// (secrets, prompt injection, supply chain, destructive ops) still\n// fires normally so the file cannot be used as a smuggling vector for\n// actual violations.\n\nconst WIZARD_DOC_BASENAMES = new Set([\n EVENT_INVENTORY_FILE,\n EVENTS_AUDIT_REPORT_FILE,\n AUDIT_REPORT_FILE,\n EVENT_PLAN_FILE,\n]);\n\nconst WIZARD_DOC_PATTERNS: RegExp[] = [EVENT_INVENTORY_PART_PATTERN];\n\nexport function isWizardDocumentationPath(\n filePath: string | undefined,\n): boolean {\n if (!filePath) return false;\n const basename = path.basename(filePath);\n if (WIZARD_DOC_BASENAMES.has(basename)) return true;\n return WIZARD_DOC_PATTERNS.some((re) => re.test(basename));\n}\n\n// ─── Repeat-block tracker ─────────────────────────────────────────\n//\n// Field data (switchboard YARA tile): an agent whose edit gets blocked often\n// retries the identical payload — the scan is deterministic, so every retry\n// blocks again and the run burns turns (~4.2 blocks per affected run). The\n// runtime notes already say \"don't retry\"; the data shows the prompt alone\n// doesn't hold. This is the code-side guard: remember the exact payloads we\n// blocked, and when the same content comes back, escalate the block reason —\n// first to \"change the code, not the retry\", then to \"report it and move on\".\n// Used by both fences: the anthropic PreToolUse Bash hook below, and the pi\n// security extension's pre-execution scan (bash/write/edit).\n\n/** Identical-payload attempt at which the agent is told to move on. */\nconst REPEAT_BLOCK_MOVE_ON_ATTEMPT = 3;\n\nexport interface RepeatBlockTracker {\n /**\n * Record a YARA block of `content` on `tool`; returns the attempt count\n * for that exact payload (1 = first time blocked).\n */\n attempt(tool: string, content: string): number;\n}\n\n/** Per-run tracker of YARA-blocked payloads (keyed by tool + content hash). */\nexport function createRepeatBlockTracker(): RepeatBlockTracker {\n const counts = new Map<string, number>();\n return {\n attempt(tool: string, content: string): number {\n const key = `${tool} ${createHash('sha256')\n .update(content)\n .digest('hex')}`;\n const next = (counts.get(key) ?? 0) + 1;\n counts.set(key, next);\n return next;\n },\n };\n}\n\n/**\n * The block reason for the given attempt: the plain reason the first time,\n * an explicit \"this exact retry can never work\" on the second, and a\n * \"report it and move on\" instruction from the third on.\n */\nexport function repeatBlockReason(\n attempt: number,\n tool: string,\n baseReason: string,\n): string {\n if (attempt <= 1) return baseReason;\n if (attempt < REPEAT_BLOCK_MOVE_ON_ATTEMPT) {\n return (\n `${baseReason} This exact ${tool} content was ALREADY blocked — the scan is ` +\n 'deterministic, so retrying identical content will always block again. ' +\n 'Change the code to remove the flagged pattern instead of retrying.'\n );\n }\n return (\n `${baseReason} This exact ${tool} content has now been blocked ${attempt} times. ` +\n 'STOP retrying it. Note the blocked change in the setup report and move on to the next task.'\n );\n}\n\n// ─── Severity helpers ────────────────────────────────────────────\n// Severity vocabulary and the terminate-or-warn decision live in yara-policy.\n\n// ─── Scan + triage core ──────────────────────────────────────────\n\n/**\n * Keep only matches whose rule targets this content surface. Rules carry a\n * `scan_context` of 'command' | 'input' | 'output'. An undefined context is\n * treated as \"applies everywhere\" (fail-safe — a rule that forgets the tag\n * still gets enforced rather than silently skipped).\n */\nfunction matchesForContext(\n matches: ScanMatch[],\n ctx: ScanContext,\n): ScanMatch[] {\n return matches.filter((m) => {\n const c = m.metadata.scan_context as string | undefined;\n return c === ctx || c === undefined;\n });\n}\n\n/**\n * Drop false positives via warlock's LLM triage. Fail-closed: if no provider is\n * available, or the triage call throws, every match is treated as real — we\n * never silently suppress a flagged match.\n *\n * Every overruled match is reported to PostHog (rule metadata only, never the\n * free-text reason) so maintainers can alert on triage-overrule patterns — the\n * signal that someone is either tripping a noisy rule or trying to talk the\n * triage model out of a real finding.\n */\nasync function triageFilter(\n content: string,\n matches: ScanMatch[],\n ctx: ScanContext,\n llmProvider: LLMProvider | undefined,\n): Promise<ScanMatch[]> {\n if (matches.length === 0) return [];\n if (!llmProvider) {\n logToFile(\n `[YARA] triage skipped (no provider) — treating ${matches.length} match(es) as real`,\n );\n return matches;\n }\n try {\n const warlock = await getWarlock();\n const triaged = await warlock.triageMatches(content, matches, llmProvider);\n const kept = triaged.filter((m) => m.triage.verdict === 'true_positive');\n for (const m of triaged) {\n if (m.triage.verdict === 'true_positive') continue;\n logToFile(\n `[YARA] triage overruled rule \"${m.rule}\" (${\n m.metadata.severity ?? 'unknown'\n }) — not acting on it`,\n );\n analytics.wizardCapture('yara triage overruled', {\n rule: m.rule,\n severity: m.metadata.severity,\n category: m.metadata.category,\n scan_context: ctx,\n // The free-text triage reason is deliberately omitted — it can quote\n // scanned content, which must never leave the user's machine.\n });\n }\n logToFile(\n `[YARA] triage: ${matches.length} flagged → ${kept.length} kept as real`,\n );\n return kept;\n } catch (err) {\n logToFile('[YARA] triage failed — treating all matches as real:', err);\n return matches;\n }\n}\n\n/** A chunk of scanned content together with the matches found inside it. */\ninterface FlaggedChunk {\n chunk: string;\n matches: ScanMatch[];\n}\n\n/**\n * Split oversized content into overlapping chunks so the scanner covers all\n * of it. Content that fits in one chunk is returned as-is.\n */\nfunction chunkContent(content: string): string[] {\n if (content.length <= SCAN_CHUNK_SIZE) return [content];\n const chunks: string[] = [];\n const step = SCAN_CHUNK_SIZE - SCAN_CHUNK_OVERLAP;\n for (let start = 0; start < content.length; start += step) {\n chunks.push(content.slice(start, start + SCAN_CHUNK_SIZE));\n if (start + SCAN_CHUNK_SIZE >= content.length) break;\n }\n return chunks;\n}\n\n/**\n * Scan content against warlock rules — chunked when oversized, so nothing is\n * skipped — and keep only matches for this content surface. Chunks scan\n * sequentially (the WASM engine is single-threaded; parallelism buys nothing).\n * Returns each flagged chunk with its matches so triage can later judge every\n * match against the exact content it came from.\n */\nasync function scanForContext(\n content: string,\n ctx: ScanContext,\n): Promise<FlaggedChunk[]> {\n const warlock = await getWarlock();\n const chunks = chunkContent(content);\n if (chunks.length > 1) {\n logToFile(\n `[YARA] content is ${content.length} chars — scanning ${chunks.length} overlapping chunks`,\n );\n // Alertable signal: oversized content should be rare; a spike could mean\n // someone is padding content to probe the scanner.\n analytics.wizardCapture('yara scan chunked', {\n content_length: content.length,\n chunk_count: chunks.length,\n scan_context: ctx,\n });\n }\n const flagged: FlaggedChunk[] = [];\n for (const chunk of chunks) {\n const result = await warlock.scan(chunk);\n if (!result.matched) continue;\n const matches = matchesForContext(result.matches, ctx);\n if (matches.length === 0) continue;\n flagged.push({ chunk, matches });\n }\n return flagged;\n}\n\n/** Categories whose matches are dropped from content sent to a model outside an agent run. */\nconst REDACTED_CATEGORIES: ReadonlySet<string> = new Set([\n 'hardcoded_secret',\n 'posthog_hardcoded_key',\n 'posthog_pii',\n]);\n\n/**\n * Replace every line that holds a secret or PII match with `[redacted]`. A\n * whole line goes, since a match's string can be length-capped. Rejects when\n * the scanner can't load, so callers fail closed.\n */\nexport async function redactSecretLines(\n content: string,\n): Promise<{ text: string; redactedLines: number }> {\n const warlock = await getWarlock();\n const flagged: string[] = [];\n for (const chunk of chunkContent(content)) {\n const result = await warlock.scan(chunk);\n if (!result.matched) continue;\n for (const match of result.matches) {\n if (!REDACTED_CATEGORIES.has(String(match.metadata.category))) continue;\n flagged.push(...match.matchedStrings.filter(Boolean));\n }\n }\n if (flagged.length === 0) return { text: content, redactedLines: 0 };\n let redactedLines = 0;\n const lines = content.split('\\n').map((line) => {\n if (!flagged.some((s) => line.includes(s))) return line;\n redactedLines += 1;\n return '[redacted]';\n });\n return { text: lines.join('\\n'), redactedLines };\n}\n\n/** The overlap between chunks can surface the same rule twice; count it once. */\nfunction dedupeByRule(matches: ScanMatch[]): ScanMatch[] {\n const seen = new Set<string>();\n return matches.filter((m) => {\n if (seen.has(m.rule)) return false;\n seen.add(m.rule);\n return true;\n });\n}\n\n/**\n * Triage each flagged chunk against its own content — the evidence is always\n * inside the window the LLM sees. Triage calls run in parallel (each is just\n * an HTTP round trip) so N flagged chunks cost ~1× triage latency, not N×.\n */\nasync function triageFlagged(\n flagged: FlaggedChunk[],\n ctx: ScanContext,\n llmProvider: LLMProvider | undefined,\n): Promise<ScanMatch[]> {\n const triaged = await Promise.all(\n flagged.map(({ chunk, matches }) =>\n triageFilter(chunk, matches, ctx, llmProvider),\n ),\n );\n return dedupeByRule(triaged.flat());\n}\n\n/**\n * Scan content, filter to the relevant context, triage. Returns real matches.\n * Exported for harnesses that run their own hook loop (pi) so every harness\n * scans through the same warlock engine, chunking, and triage policy.\n */\nexport async function scanAndTriage(\n content: string,\n ctx: ScanContext,\n llmProvider: LLMProvider | undefined,\n): Promise<ScanMatch[]> {\n const flagged = await scanForContext(content, ctx);\n return triageFlagged(flagged, ctx, llmProvider);\n}\n\n// ─── PreToolUse Hooks ────────────────────────────────────────────\n\n/**\n * Create PreToolUse hook matchers for YARA scanning.\n * 1. Bash — scans commands before execution for exfiltration, destructive\n * operations, and supply chain violations ('command'-context rules).\n * 2. publish_handoff — scans the report content with the same 'output'-context\n * rules + triage as the PostToolUse Write/Edit hook, but BEFORE the MCP\n * tool runs: the report is agent-authored and is persisted to a\n * host-designated file (POSTHOG_HANDOFF_OUTPUT_PATH) as well as pushed to\n * the task stream. Only a CRITICAL match refuses the publish; below that\n * the report goes out with the match recorded.\n */\nexport function createPreToolUseYaraHooks(\n // Used only by the publish_handoff matcher below (output-context triage,\n // same fail-closed semantics as Write/Edit). The Bash matcher still passes\n // undefined — any flagged command blocks regardless of triage verdict, so\n // the LLM call would be wasted.\n llmProvider?: LLMProvider,\n // Ends the run when a handoff scan errors — without it that path just blocks,\n // which is the old behavior, so it stays optional.\n onTerminate?: (reason: string) => void,\n): HookCallbackMatcher[] {\n const repeatTracker = createRepeatBlockTracker();\n return [\n {\n hooks: [\n async (input: HookInput): Promise<HookOutput> => {\n try {\n const toolName = input.tool_name as string;\n if (toolName !== 'Bash') return {};\n\n const toolInput = input.tool_input as Record<string, unknown>;\n const command =\n typeof toolInput?.command === 'string' ? toolInput.command : '';\n\n if (!command) return {};\n\n recordScan();\n // Skip triage on PreToolUse Bash: any flagged command is blocked\n // regardless of triage verdict, so the LLM call would be wasted.\n const matches = await scanAndTriage(command, 'command', undefined);\n if (matches.length === 0) return {};\n\n const match = highestSeverityMatch(matches);\n recordMatch('PreToolUse', 'Bash', match, 'blocked');\n\n const attempt = repeatTracker.attempt('Bash', command);\n return {\n decision: 'block',\n reason: repeatBlockReason(\n attempt,\n 'Bash',\n `[YARA] ${match.rule}: ${\n match.metadata.description ?? 'security policy violation'\n }. Command blocked for security.`,\n ),\n };\n } catch (error) {\n logToFile('[YARA] PreToolUse hook error:', error);\n // Fail closed: block the command if scanning fails\n return {\n decision: 'block',\n reason: '[YARA] Scanner error — command blocked as a precaution.',\n };\n }\n },\n ],\n timeout: HOOK_TIMEOUT_MS,\n },\n {\n // ── publish_handoff content scanning ──\n hooks: [\n async (input: HookInput): Promise<HookOutput> => {\n try {\n const toolName = input.tool_name as string;\n // The wizard's publish_handoff MCP tool, fully qualified by the\n // SDK (e.g. mcp__wizard-tools__publish_handoff). Matched by\n // suffix so a server rename can't silently reopen the gap — the\n // exact FQN lives in WIZARD_TOOL_NAMES (wizard-tools/tools.ts).\n if (\n typeof toolName !== 'string' ||\n !toolName.endsWith('__publish_handoff')\n ) {\n return {};\n }\n\n const toolInput = input.tool_input as Record<string, unknown>;\n const content =\n typeof toolInput?.content === 'string' ? toolInput.content : '';\n\n if (!content) return {};\n\n recordScan();\n // Same 'output'-context scan + triage as the Write/Edit hook:\n // the report is agent-authored, quotes the user's code, and now\n // reaches a second persisted consumer (the host file + the cloud\n // worker's PR body) — so it must clear the bar of any other\n // agent-driven write BEFORE the tool executes.\n const matches = await scanAndTriage(content, 'output', llmProvider);\n if (matches.length === 0) return {};\n\n // Below critical: record it, don't cost the run its only report.\n const match = publishBlockingMatch(matches);\n if (!match) {\n const worst = highestSeverityMatch(matches);\n recordMatch('PreToolUse', 'publish_handoff', worst, 'warned');\n logToFile(\n `[YARA] publish_handoff allowed with a ${\n worst.metadata.severity ?? 'unknown'\n } match (${worst.rule}) — below the critical publish bar`,\n );\n return {};\n }\n\n recordMatch('PreToolUse', 'publish_handoff', match, 'blocked');\n\n const attempt = repeatTracker.attempt('publish_handoff', content);\n return {\n decision: 'block',\n reason: repeatBlockReason(\n attempt,\n 'publish_handoff',\n `[YARA] ${match.rule}: ${\n match.metadata.description ?? 'security policy violation'\n }. Handoff blocked for security — remove the flagged content from the report.`,\n ),\n };\n } catch (error) {\n logToFile('[YARA] PreToolUse publish_handoff hook error:', error);\n // Fail closed, but end the run: a silent block would just leave\n // the agent rewording a report that can never publish.\n const reason =\n '[YARA] Scanner error while scanning the handoff report — session terminated as a precaution.';\n onTerminate?.(reason);\n return { decision: 'block', reason };\n }\n },\n ],\n timeout: HOOK_TIMEOUT_MS,\n },\n ];\n}\n\n// ─── PostToolUse Hooks ───────────────────────────────────────────\n\n/**\n * Create PostToolUse hook matchers for YARA scanning.\n *\n * Three matchers:\n * 1. Write/Edit — scan written content ('output'-context rules: PII, secrets)\n * 2. Read/Grep — scan read content ('input'-context rules: prompt injection)\n * 3. Bash (skill install) — scan downloaded skill files for poisoned content\n *\n * `onTerminate` is invoked on the terminal paths (critical prompt injection in\n * read content, a poisoned skill, or a scanner error under fail-closed). It is\n * what ACTUALLY stops the run — returning `stopReason` from a PostToolUse hook\n * is not honored by the SDK, so the caller wires this to the run's\n * AbortController (see agent-interface).\n */\nexport function createPostToolUseYaraHooks(\n llmProvider: LLMProvider | undefined,\n // REQUIRED, not optional: the SDK ignores `stopReason` from PostToolUse\n // hooks, so terminal paths (critical prompt injection, poisoned skill,\n // fail-closed scanner error) depend entirely on this callback firing.\n // Making it required at compile time prevents a future caller from\n // silently regressing every terminal path to a no-op.\n onTerminate: (reason: string) => void,\n): HookCallbackMatcher[] {\n return [\n // ── Write/Edit content scanning ──\n {\n hooks: [\n async (input: HookInput): Promise<HookOutput> => {\n try {\n const toolName = input.tool_name as string;\n if (toolName !== 'Write' && toolName !== 'Edit') return {};\n\n const toolInput = input.tool_input as Record<string, unknown>;\n // For Write, scan the content being written.\n // For Edit, scan the new_string (replacement text).\n const content =\n toolName === 'Write'\n ? (toolInput?.content as string) ?? ''\n : (toolInput?.new_string as string) ?? '';\n\n if (!content) return {};\n\n recordScan();\n const flagged = await scanForContext(content, 'output');\n if (flagged.length === 0) return {};\n\n // Wizard-documentation paths: suppress posthog_pii matches that\n // come from the agent verbatim-copying the user's existing\n // capture calls into an inventory / report, or planning new\n // events with PII-shaped property keys. Every other category\n // still triggers the revert. Suppression runs BEFORE triage so\n // we never pay an LLM round trip for a match we're about to\n // discard anyway.\n // TODO(warlock#33): once warlock PR #33 lands with its\n // more-precise PII rules, the wizard-side suppression below\n // should become unnecessary — remove `WIZARD_DOC_BASENAMES`,\n // `WIZARD_DOC_PATTERNS`, and `isWizardDocumentationPath` and\n // verify the noisy-PII issue stays fixed.\n const filePath = toolInput?.file_path as string | undefined;\n const activeFlagged = isWizardDocumentationPath(filePath)\n ? flagged\n .map(({ chunk, matches }) => ({\n chunk,\n matches: matches.filter(\n (m) => m.metadata.category !== 'posthog_pii',\n ),\n }))\n .filter(({ matches }) => matches.length > 0)\n : flagged;\n if (activeFlagged.length === 0) {\n logToFile(\n `[YARA] posthog_pii match suppressed on wizard doc ${path.basename(\n filePath ?? '',\n )} (rule: ${flagged[0]?.matches[0]?.rule})`,\n );\n return {};\n }\n\n const matches = await triageFlagged(\n activeFlagged,\n 'output',\n llmProvider,\n );\n if (matches.length === 0) return {};\n\n const match = highestSeverityMatch(matches);\n recordMatch('PostToolUse', toolName, match, 'reverted');\n\n return {\n hookSpecificOutput: {\n hookEventName: 'PostToolUse',\n additionalContext:\n `[YARA VIOLATION] ${match.rule}: ${\n match.metadata.description ?? ''\n }. ` +\n `You MUST revert this change immediately. The content you just wrote violates security policy.`,\n },\n };\n } catch (error) {\n logToFile('[YARA] PostToolUse Write/Edit hook error:', error);\n // Fail closed: if scanning is broken on a Write/Edit, the next\n // Read/skill-install scan is also going to be broken — terminate\n // cleanly instead of looping the agent through \"please revert\"\n // until a Read finally aborts. Matches Read/Grep + skill-install\n // catch behavior; consistent fail-closed across all PostToolUse.\n const reason =\n '[YARA] Scanner error while scanning written content — session terminated as a precaution.';\n onTerminate(reason);\n return { stopReason: reason };\n }\n },\n ],\n timeout: HOOK_TIMEOUT_MS,\n },\n\n // ── Read/Grep prompt injection scanning ──\n {\n hooks: [\n async (input: HookInput): Promise<HookOutput> => {\n try {\n const toolName = input.tool_name as string;\n if (toolName !== 'Read' && toolName !== 'Grep') return {};\n\n const toolResponse = input.tool_response;\n // Guard before stringify: `JSON.stringify(undefined ?? '')`\n // returns the 2-char string '\"\"', which is truthy and slips\n // past the empty-content check below. Exit early when the SDK\n // gave us no response payload.\n if (toolResponse == null) return {};\n const content =\n typeof toolResponse === 'string'\n ? toolResponse\n : JSON.stringify(toolResponse);\n\n if (!content) return {};\n\n recordScan();\n const matches = await scanAndTriage(content, 'input', llmProvider);\n const verdict = scanVerdict(matches);\n if (!verdict) return {};\n\n const { match } = verdict;\n recordMatch('PostToolUse', toolName, match, verdict.action);\n\n if (verdict.terminal) {\n const reason =\n `[YARA CRITICAL] ${match.rule}: Prompt injection detected in file content. ` +\n `Agent context is potentially poisoned. Session terminated for safety.`;\n onTerminate(reason);\n return { stopReason: reason };\n }\n\n return {\n hookSpecificOutput: {\n hookEventName: 'PostToolUse',\n additionalContext: `[YARA WARNING] ${match.rule}: ${\n match.metadata.description ?? ''\n }`,\n },\n };\n } catch (error) {\n logToFile('[YARA] PostToolUse Read/Grep hook error:', error);\n // Fail closed: terminate session if scanning fails on read content\n const reason =\n '[YARA] Scanner error while scanning read content — session terminated as a precaution.';\n onTerminate(reason);\n return { stopReason: reason };\n }\n },\n ],\n timeout: HOOK_TIMEOUT_MS,\n },\n\n // ── Context-mill skill install scanning ──\n {\n hooks: [\n async (input: HookInput): Promise<HookOutput> => {\n try {\n const toolName = input.tool_name as string;\n if (toolName !== 'Bash') return {};\n\n const toolInput = input.tool_input as Record<string, unknown>;\n const command =\n typeof toolInput?.command === 'string' ? toolInput.command : '';\n\n // Only scan after skill install commands\n if (!isSkillInstallCommand(command)) return {};\n\n // Extract skill directory from command\n const dirMatch = command.match(\n /mkdir -p (.claude\\/skills\\/[^\\s&]+)/,\n );\n if (!dirMatch) return {};\n\n const skillDir = dirMatch[1];\n const cwd = (input.cwd as string) ?? process.cwd();\n recordScan();\n const matches = await scanSkillFiles(cwd, skillDir, llmProvider);\n\n const verdict = scanVerdict(matches);\n if (!verdict) return {};\n\n recordMatch(\n 'PostToolUse',\n 'Bash (skill install)',\n verdict.match,\n verdict.action,\n );\n // Same verdict as every other surface: critical terminates, the\n // rest warn. These rules fire on first-party skill prose often\n // enough that terminating costs more than it prevents.\n if (!verdict.terminal) return {};\n\n const reason =\n `[YARA CRITICAL] Poisoned skill detected in ${skillDir}: ${verdict.match.rule}. ` +\n `The downloaded skill contains potential prompt injection. Session terminated for safety.`;\n onTerminate(reason);\n return { stopReason: reason };\n } catch (error) {\n logToFile('[YARA] PostToolUse skill install hook error:', error);\n // Fail closed: terminate if skill scanning fails\n const reason =\n '[YARA] Scanner error while scanning skill files — session terminated as a precaution.';\n onTerminate(reason);\n return { stopReason: reason };\n }\n },\n ],\n timeout: SKILL_SCAN_HOOK_TIMEOUT_MS,\n },\n ];\n}\n\n// ─── Skill File Scanner ──────────────────────────────────────────\n\n/**\n * Scan a freshly installed skill directory (any root — .claude/skills or the\n * orchestrator's run cache) and return a terminate reason when it is poisoned,\n * else null. The choke point for TS-path installs (downloadSkill); agent Bash\n * installs are covered by the PostToolUse matcher above. Runs the same LLM\n * triage as the tool-use scans; fail-closed to treating every match as real when\n * no provider is configured, so a missing key never weakens the check.\n * `llmProvider` is explicit — pi and the orchestrator never set the env fallback.\n *\n * Terminates on the same verdict as every other surface (critical only). A\n * non-terminal match is recorded and the skill is kept: these rules fire on\n * first-party skill prose, and deleting the skill leaves the agent working blind\n * on the very content it needed.\n */\nexport async function scanInstalledSkill(\n absoluteSkillDir: string,\n llmProvider: LLMProvider | undefined,\n): Promise<string | null> {\n recordScan();\n const matches = await scanSkillFiles(absoluteSkillDir, '.', llmProvider);\n const verdict = scanVerdict(matches);\n if (!verdict) return null;\n recordMatch(\n 'skill-install',\n 'installSkillById',\n verdict.match,\n verdict.action,\n );\n if (!verdict.terminal) {\n logToFile(\n `[YARA] ${verdict.match.rule} (${\n verdict.match.metadata.severity ?? 'unknown'\n }) in ${absoluteSkillDir} — non-terminal, keeping the skill`,\n );\n return null;\n }\n return `Poisoned skill detected: ${verdict.match.rule} (${\n verdict.match.metadata.severity ?? 'unknown'\n }) in ${absoluteSkillDir}`;\n}\n\n/**\n * Read and scan all text files in a skill directory for prompt injection.\n * Scans each file sequentially (single-threaded WASM — parallelism buys\n * nothing), unions the 'input'-context matches, then triages once across the\n * combined content. Returns the real (post-triage) matches.\n */\nasync function scanSkillFiles(\n cwd: string,\n skillDir: string,\n llmProvider: LLMProvider | undefined,\n): Promise<ScanMatch[]> {\n const absoluteDir = path.resolve(cwd, skillDir);\n\n if (!fs.existsSync(absoluteDir)) {\n logToFile(`[YARA] Skill directory does not exist: ${absoluteDir}`);\n return [];\n }\n\n const files = await fg('**/*.{md,txt,yaml,yml,json,js,ts,py,rb,sh}', {\n cwd: absoluteDir,\n absolute: true,\n });\n\n if (files.length === 0) {\n logToFile(`[YARA] No text files found in skill directory: ${absoluteDir}`);\n return [];\n }\n\n logToFile(\n `[YARA] Scanning ${files.length} files in skill directory: ${skillDir}`,\n );\n\n // Pass 1 (sequential): read and scan ONE file at a time through the\n // chunk-aware path. Oversized files are scanned up to the cap and logged —\n // never silently skipped. Flagged chunks keep a reference to the exact\n // content their matches came from.\n const flagged: FlaggedChunk[] = [];\n for (const filePath of files) {\n let content: string | null;\n try {\n if (fs.statSync(filePath).size > SKILL_FILE_SCAN_BYTES) {\n logToFile(\n `[YARA] Skill file over ${SKILL_FILE_SCAN_BYTES} bytes; scanning its head only: ${filePath}`,\n );\n content = readFileHead(filePath, SKILL_FILE_SCAN_BYTES);\n } else {\n content = fs.readFileSync(filePath, 'utf-8');\n }\n } catch (err) {\n logToFile(`[YARA] Could not read skill file ${filePath}:`, err);\n continue;\n }\n if (content) {\n flagged.push(...(await scanForContext(content, 'input')));\n }\n }\n\n // Pass 2 (parallel, inside triageFlagged): triage each flagged chunk\n // against its own content concurrently. Triage is just an axios call —\n // running them in parallel cuts total wall-clock from N × triage-timeout\n // to ~1×. Important because the hook's HOOK_TIMEOUT_MS is 30s; serial\n // triage on 2+ matched files could blow the hook timeout under a slow\n // Haiku.\n return triageFlagged(flagged, 'input', llmProvider);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"yara-hooks-DPqy6eVo.js","names":[],"sources":["../src/shared/audit-ledger.ts","../src/shared/fetch-retry.ts","../src/shared/skill-menu.ts","../src/shared/skill-install.ts","../src/agent/yara-policy.ts","../src/agent/yara-hooks.ts"],"sourcesContent":["/**\n * The audit ledger's contract: the file the audit tools write into the user's\n * project, the check shape inside it, and the read-side coercion. Shared by\n * the agent's ledger tools (both harnesses), the scanner's documentation\n * allowlist and the audit program that watches the file.\n */\n\nexport type AuditStatus =\n | 'pending'\n | 'pass'\n | 'error'\n | 'warning'\n | 'suggestion';\n\nexport interface AuditCheck {\n id: string;\n area: string;\n label: string;\n status: AuditStatus;\n file?: string;\n details?: string;\n}\n\nexport const AUDIT_CHECKS_FILE = '.posthog-audit-checks.json';\nexport const AUDIT_REPORT_FILE = 'posthog-audit-report.md';\n\n/**\n * Read the audit checks ledger off disk. Validation lives at write time —\n * every writer (`audit_seed_checks` / `audit_add_checks` / `audit_resolve_checks`\n * MCP tools, `seedAuditLedger`) zod-parses entries before the atomic write,\n * so by the time the file watcher fires we trust the shape and only guard\n * against the file not being a JSON array (corrupted / hand-edited / not yet\n * seeded).\n */\nexport function coerceAuditChecks(parsed: unknown): AuditCheck[] {\n return Array.isArray(parsed) ? (parsed as AuditCheck[]) : [];\n}\n","/**\n * One retry + failover policy for every critical-path fetch. GitHub is primary,\n * AWS is the fallback. A status a retry can't heal (404, 403) spends no retry\n * budget at the origin that returned it — but it still costs the other origin\n * one try, because the two are published separately and GitHub 403s expired\n * asset redirects and blocked regions where the asset is present.\n */\n\nimport { logToFile } from '@utils/debug';\nimport { analytics } from '@utils/analytics';\nimport { CONTEXT_MILL_URL, AWS_SKILLS_BASE_URL } from './constants';\n\nconst DEFAULT_TIMEOUT_MS = 60000; // per attempt\nconst DEFAULT_MAX_ATTEMPTS = 3;\nconst DEFAULT_BACKOFF_MS = 500; // doubles each retry\n\nconst GITHUB_LATEST_PREFIX = `${CONTEXT_MILL_URL}/releases/latest/download/`;\nconst GITHUB_PINNED_PREFIX = `${CONTEXT_MILL_URL}/releases/download/`;\n/** Version-pinned URLs name their own prefix, so they need the host alone. */\nconst AWS_ORIGIN = new URL(AWS_SKILLS_BASE_URL).origin;\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Maps a context-mill release URL onto AWS; null when there is no second origin.\n *\n * .../releases/latest/download/x.zip -> <aws>/latest/x.zip\n * .../releases/download/v1.50.0/x.zip -> <aws>/v1.50.0/x.zip\n */\nexport function awsUrlFor(url: string): string | null {\n if (url.startsWith(GITHUB_LATEST_PREFIX)) {\n return `${AWS_SKILLS_BASE_URL}/${url.slice(GITHUB_LATEST_PREFIX.length)}`;\n }\n if (url.startsWith(GITHUB_PINNED_PREFIX)) {\n return `${AWS_ORIGIN}/${url.slice(GITHUB_PINNED_PREFIX.length)}`;\n }\n return null;\n}\n\n/** The two origins context-mill releases are published to. */\nexport type SkillsOrigin = 'github' | 'aws';\n\n/** Sticky: without it every asset re-pays GitHub's retry budget during an outage. */\nlet preferredOrigin: SkillsOrigin = 'github';\n\n/** Test seam — the preference deliberately outlives a single call. */\nexport function resetOriginPreference(): void {\n preferredOrigin = 'github';\n}\n\n/** A response status that retrying can't fix; propagates past the retry loop. */\nclass NonRetryableFetchError extends Error {}\n\n/**\n * 5xx are transient; 408 (request timeout) and 429 (rate limit) explicitly ask\n * to retry. Every other 4xx is a client error a retry won't heal (404 missing\n * skill, 403 bad token) — retrying only burns the backoff before failing.\n */\nfunction isRetryableStatus(status: number): boolean {\n return status >= 500 || status === 408 || status === 429;\n}\n\nexport interface RetryOpts {\n fetchImpl?: typeof fetch;\n sleepImpl?: (ms: number) => Promise<void>;\n timeoutMs?: number;\n maxAttempts?: number;\n backoffMs?: number;\n /** Set false to fail rather than reach for AWS. */\n failover?: boolean;\n /** Test seam for the failover/exhausted events. */\n onEvent?: (event: string, props: Record<string, unknown>) => void;\n}\n\nfunction messageOf(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\n/** Try one origin to exhaustion. */\nasync function fetchOrigin(\n url: string,\n opts: Required<Pick<RetryOpts, 'timeoutMs' | 'maxAttempts' | 'backoffMs'>> & {\n fetchImpl: typeof fetch;\n sleepImpl: (ms: number) => Promise<void>;\n },\n): Promise<Response> {\n const failures: string[] = [];\n for (let attempt = 1; attempt <= opts.maxAttempts; attempt++) {\n try {\n const resp = await opts.fetchImpl(url, {\n signal: AbortSignal.timeout(opts.timeoutMs),\n });\n if (resp.ok) return resp;\n const msg = `HTTP ${resp.status} ${resp.statusText}`.trim();\n if (!isRetryableStatus(resp.status))\n throw new NonRetryableFetchError(msg);\n throw new Error(msg);\n } catch (err) {\n if (err instanceof NonRetryableFetchError) throw err;\n failures.push(`attempt ${attempt}: ${messageOf(err)}`);\n if (attempt < opts.maxAttempts) {\n await opts.sleepImpl(opts.backoffMs * 2 ** (attempt - 1));\n }\n }\n }\n throw new Error(failures.join('; '));\n}\n\n/**\n * Retry with backoff, then fail over. Throws once every origin is exhausted.\n */\nexport async function fetchWithRetry(\n url: string,\n opts: RetryOpts = {},\n): Promise<Response> {\n const {\n fetchImpl = fetch,\n sleepImpl = sleep,\n timeoutMs = DEFAULT_TIMEOUT_MS,\n maxAttempts = DEFAULT_MAX_ATTEMPTS,\n backoffMs = DEFAULT_BACKOFF_MS,\n failover = true,\n onEvent = (event, props) => analytics.wizardCapture(event, props),\n } = opts;\n const originOpts = {\n fetchImpl,\n sleepImpl,\n timeoutMs,\n maxAttempts,\n backoffMs,\n };\n\n const awsUrl = failover ? awsUrlFor(url) : null;\n if (!awsUrl) {\n try {\n return await fetchOrigin(url, originOpts);\n } catch (err) {\n throw new Error(`fetch ${url} failed — ${messageOf(err)}`);\n }\n }\n\n // Sticky: whichever origin last worked is tried first for the rest of the run.\n const order: Array<{ origin: SkillsOrigin; target: string }> =\n preferredOrigin === 'aws'\n ? [\n { origin: 'aws', target: awsUrl },\n { origin: 'github', target: url },\n ]\n : [\n { origin: 'github', target: url },\n { origin: 'aws', target: awsUrl },\n ];\n\n const failures: string[] = [];\n for (const [index, candidate] of order.entries()) {\n try {\n const resp = await fetchOrigin(candidate.target, originOpts);\n if (candidate.origin !== preferredOrigin) {\n // The transition is the interesting event, not every later fetch.\n preferredOrigin = candidate.origin;\n logToFile(`fetchWithRetry: failed over to ${candidate.origin}`);\n onEvent('skills fetch failed over', {\n origin: candidate.origin,\n url: candidate.target,\n });\n }\n return resp;\n } catch (err) {\n // Non-retryable here still means \"ask the other origin\" — they are\n // published separately, and a 403 is as often about us as the asset.\n failures.push(`${candidate.origin}: ${messageOf(err)}`);\n if (index === order.length - 1) {\n onEvent('skills fetch failed', { url, failures: failures.length });\n }\n }\n }\n throw new Error(`fetch ${url} failed — ${failures.join(' | ')}`);\n}\n","/**\n * The skill menu: what context-mill publishes as `skill-menu.json` and how the\n * wizard reads it. Skill-backed CLI commands, the family picker, the skill\n * source screen and the agent's install path all resolve skills through this\n * one fetch, so it lives in shared and depends on nothing in the agent.\n */\n\nimport { logToFile } from '@utils/debug';\nimport { fetchWithRetry, type RetryOpts } from '@shared/fetch-retry';\n\nexport type SkillEntry = {\n id: string;\n name: string;\n downloadUrl: string;\n /** The hyphenated skill-group prefix of `id` (e.g. `posthog-integration-install`). */\n group?: string;\n /** The detection id this variant serves (e.g. `rails`, `react-router`). */\n framework?: string;\n /** The variant a bare framework id resolves to when its family has several. */\n default?: boolean;\n /** This entry's download is a bundle JSON of every variant, not a single skill's zip. */\n bundle?: boolean;\n /** Menu-only: the variants inside a bundle, expanded into entries of their own on fetch. */\n variants?: { id: string; framework?: string; default?: boolean }[];\n};\n\n/**\n * Entry in the wizard's runtime CLI registry. Mirrors the shape context-mill\n * publishes under `cliEntries` inside `skill-menu.json`. The wizard uses these\n * to register skill-backed subcommands at runtime instead of from a baked\n * build-time snapshot.\n */\nexport type CliEntry = {\n skillId: string;\n role: 'command' | 'skill' | 'internal';\n command?: string;\n parentCommand?: string;\n default?: boolean;\n displayName: string;\n description: string;\n};\n\nexport interface SkillMenu {\n categories: Record<string, SkillEntry[]>;\n /**\n * Skills exposed as CLI commands. Optional because context-mill releases\n * older than the runtime-resolver cutover don't emit this field.\n */\n cliEntries?: CliEntry[];\n}\n\n/** Expand a bundle entry into one entry per variant, so the menu reads the same whether a group ships bundled or as zips. */\nexport function expandBundleEntry(entry: SkillEntry): SkillEntry[] {\n if (!entry.bundle || !entry.variants) return [entry];\n return entry.variants.map((variant) => ({\n ...variant,\n name: entry.name,\n group: entry.group,\n bundle: true,\n downloadUrl: entry.downloadUrl,\n }));\n}\n\n/**\n * Fetch the skill menu from the skills server.\n * Returns parsed data on success, `null` on failure.\n */\nexport async function fetchSkillMenu(\n skillsBaseUrl: string,\n opts: RetryOpts = {},\n): Promise<SkillMenu | null> {\n const menuUrl = `${skillsBaseUrl}/skill-menu.json`;\n try {\n logToFile(`fetchSkillMenu: fetching from ${menuUrl}`);\n const resp = await fetchWithRetry(menuUrl, opts);\n const data = (await resp.json()) as SkillMenu;\n for (const [category, entries] of Object.entries(data.categories)) {\n data.categories[category] = entries.flatMap(expandBundleEntry);\n }\n logToFile(\n `fetchSkillMenu: loaded (${\n Object.keys(data.categories).length\n } categories)`,\n );\n return data;\n } catch (err: any) {\n logToFile(`fetchSkillMenu: error: ${err.message}`);\n return null;\n }\n}\n","// Download a context-mill skill into a project, and recognise the agent's own skill-install command.\n\nimport path from 'path';\nimport fs from 'fs';\nimport { unzipSync } from 'fflate';\nimport { logToFile } from '@utils/debug';\nimport { analytics } from '@utils/analytics';\nimport { fetchWithRetry, type RetryOpts } from '@shared/fetch-retry';\nimport { fetchSkillMenu, type SkillEntry } from '@shared/skill-menu';\n\n/** A bundle's files, keyed by variant short id then path. */\nexport type SkillBundle = {\n id: string;\n variants: Record<string, Record<string, string>>;\n};\n\n/** Extract a zip buffer, refusing entries that escape destDir (zip-slip). */\nfunction extractZipArchive(zip: Uint8Array, destDir: string): number {\n const root = path.resolve(destDir);\n let written = 0;\n for (const [entryPath, data] of Object.entries(unzipSync(zip))) {\n const target = path.resolve(root, entryPath);\n if (target !== root && !target.startsWith(root + path.sep)) {\n throw new Error(`zip entry escapes destination: ${entryPath}`);\n }\n if (entryPath.endsWith('/')) {\n fs.mkdirSync(target, { recursive: true });\n continue;\n }\n fs.mkdirSync(path.dirname(target), { recursive: true });\n fs.writeFileSync(target, data);\n written++;\n }\n return written;\n}\n\n/** Unpack the one variant this entry names out of a bundle; the rest is noise and never hits disk. */\nfunction extractBundle(\n bundle: SkillBundle,\n destDir: string,\n entryId: string,\n): number {\n if (\n typeof bundle?.id !== 'string' ||\n typeof bundle?.variants !== 'object' ||\n bundle.variants === null\n ) {\n throw new Error('malformed bundle: expected { id, variants }');\n }\n const files = bundle.variants[entryId.slice(bundle.id.length + 1)];\n if (!files) {\n throw new Error(`bundle ${bundle.id} has no variant \"${entryId}\"`);\n }\n const root = path.resolve(destDir);\n let written = 0;\n for (const [entryPath, contents] of Object.entries(files)) {\n const target = path.resolve(root, entryPath);\n if (target !== root && !target.startsWith(root + path.sep)) {\n throw new Error(`bundle entry escapes destination: ${entryPath}`);\n }\n fs.mkdirSync(path.dirname(target), { recursive: true });\n fs.writeFileSync(target, contents);\n written++;\n }\n return written;\n}\n\n/** Download a URL to a buffer, retrying transient failures with backoff. */\nasync function downloadWithRetry(\n url: string,\n opts: RetryOpts = {},\n): Promise<Uint8Array> {\n const resp = await fetchWithRetry(url, opts);\n return new Uint8Array(await resp.arrayBuffer());\n}\n\n/** Scans an extracted skill directory; a returned reason means it is poisoned. Injected because @shared cannot import @agent. */\nexport type SkillScanner = (skillDir: string) => Promise<string | null>;\n\n/** How to place a skill and what scans it — `scan` is stated by every caller so none inherits a silent default. */\nexport interface SkillInstallOptions {\n /** Base directory override, e.g. `.posthog/skills`. Default `.claude/skills`. */\n skillsRoot?: string;\n scan: SkillScanner;\n}\n\n/**\n * Download and extract a skill.\n * By default installs to `<installDir>/.claude/skills/<id>/`.\n */\nexport async function downloadSkill(\n skillEntry: SkillEntry,\n installDir: string,\n { skillsRoot, scan }: SkillInstallOptions,\n): Promise<{ success: boolean; error?: string }> {\n const skillDir = skillsRoot\n ? path.join(installDir, skillsRoot, skillEntry.id)\n : path.join(installDir, '.claude', 'skills', skillEntry.id);\n let step: 'download' | 'extract' | 'scan' = 'download';\n\n try {\n fs.mkdirSync(skillDir, { recursive: true });\n const data = await downloadWithRetry(skillEntry.downloadUrl);\n step = 'extract';\n const fileCount = skillEntry.bundle\n ? extractBundle(\n JSON.parse(Buffer.from(data).toString('utf8')) as SkillBundle,\n skillDir,\n skillEntry.id,\n )\n : extractZipArchive(data, skillDir);\n fs.writeFileSync(path.join(skillDir, '.posthog-wizard'), '');\n\n // Same scan the Bash-install hook runs — TS-path installs (linear\n // pre-install, MCP/pi install_skill, orchestrator cache + reference)\n // must not skip it.\n //\n // The scan is its own step: it runs the YARA-X WASM engine, and an engine\n // that fails to load throws from here. Left as `extract` that lands on the\n // event as an unzip failure, which the pure-JS unzip cannot produce.\n step = 'scan';\n const poisonReason = await scan(skillDir);\n if (poisonReason) {\n fs.rmSync(skillDir, { recursive: true, force: true });\n logToFile(`downloadSkill: ${poisonReason}`);\n analytics.wizardCapture('skill install failed', {\n skill_id: skillEntry.id,\n step: 'scan',\n platform: process.platform,\n error: poisonReason.slice(0, 500),\n });\n return { success: false, error: poisonReason };\n }\n\n logToFile(\n `downloadSkill: installed ${skillEntry.id} from ${skillEntry.downloadUrl} (${fileCount} files)`,\n );\n // The installed variant is a skill program's identity dimension in analytics.\n analytics.wizardCapture('skill installed', {\n skill_id: skillEntry.id,\n platform: process.platform,\n });\n return { success: true };\n } catch (err: any) {\n logToFile(`downloadSkill: error: ${err.message}`);\n // A skill-less run still reports success — keep the failure visible.\n analytics.wizardCapture('skill install failed', {\n skill_id: skillEntry.id,\n step,\n platform: process.platform,\n error: String(err.message).slice(0, 500),\n });\n return { success: false, error: err.message };\n }\n}\n\n/**\n * Structured result for installSkillById.\n * - `ok`: the skill was fetched and extracted; `path` is where it lives\n * relative to installDir.\n * - `menu-fetch-failed`: couldn't fetch or parse the skill menu.\n * - `skill-not-found`: the menu didn't contain a skill with this id.\n * - `download-failed`: found the skill but download/extract failed;\n * `message` has the underlying error.\n */\nexport type InstallSkillResult =\n | { kind: 'ok'; path: string }\n | { kind: 'menu-fetch-failed' }\n | { kind: 'skill-not-found'; skillId: string }\n | { kind: 'download-failed'; message: string };\n\n/**\n * High-level \"install a skill by ID\" helper. Fetches the skill menu,\n * finds the skill, downloads and extracts it. Programs should use this\n * instead of composing fetchSkillMenu + downloadSkill themselves.\n */\nexport async function installSkillById(\n skillId: string,\n installDir: string,\n skillsBaseUrl: string,\n options: SkillInstallOptions,\n): Promise<InstallSkillResult> {\n const menu = await fetchSkillMenu(skillsBaseUrl);\n if (!menu) return { kind: 'menu-fetch-failed' };\n\n const skill = Object.values(menu.categories)\n .flat()\n .find((s) => s.id === skillId);\n if (!skill) return { kind: 'skill-not-found', skillId };\n\n const result = await downloadSkill(skill, installDir, options);\n if (!result.success) {\n return { kind: 'download-failed', message: result.error ?? 'unknown' };\n }\n\n const relPath = options.skillsRoot\n ? `${options.skillsRoot}/${skillId}`\n : `.claude/skills/${skillId}`;\n return { kind: 'ok', path: relPath };\n}\n\n/**\n * Check if command is a PostHog skill installation from MCP.\n * We control the MCP server, so we only need to verify:\n * 1. It installs to .claude/skills/\n * 2. It downloads from a context-mill release origin (GitHub Releases or the\n * AWS mirror) or localhost (dev)\n *\n * Extracted to its own module to avoid a circular dependency\n * between agent-interface.ts and yara-hooks.ts.\n */\nexport function isSkillInstallCommand(command: string): boolean {\n if (!command.startsWith('mkdir -p .claude/skills/')) return false;\n\n const urlMatch = command.match(/curl -sL ['\"]([^'\"]+)['\"]/);\n if (!urlMatch) return false;\n\n // Literal prefixes rather than the constants in `@shared/constants`: an\n // allow-list is easier to audit when it reads as the URLs themselves.\n const url = urlMatch[1];\n return (\n url.startsWith('https://github.com/PostHog/context-mill/releases/') ||\n url.startsWith('https://context-mill.posthog.com/') ||\n /^http:\\/\\/localhost:\\d+\\//.test(url)\n );\n}\n\n// ---------------------------------------------------------------------------\n// Test-only exports\n// ---------------------------------------------------------------------------\n\nexport const __test = {\n extractZipArchive,\n extractBundle,\n downloadWithRetry,\n};\n","/**\n * Shared YARA scan policy: the vocabulary every scan surface speaks, and the\n * one decision they all make — does a set of matches terminate the session, or\n * only warn. Both fences (the anthropic PostToolUse hooks in `yara-hooks.ts`\n * and the pi tool_result extension in `harness/pi/security.ts`) resolve a\n * verdict here rather than each re-deriving it from severity and action.\n */\n\nimport type { ScanMatch } from '@posthog/warlock';\n\n/** Which content surface a warlock rule applies to (from rule `scan_context`). */\nexport type ScanContext = 'command' | 'input' | 'output';\n\n/** What a fence did about a match, as recorded in the scan report. */\nexport type ScanAction = 'blocked' | 'reverted' | 'warned' | 'aborted';\n\n/** Severity order, highest first, for picking the match worth reporting. */\nconst SEVERITY_RANK: Record<string, number> = {\n critical: 4,\n high: 3,\n medium: 2,\n low: 1,\n};\n\n/** Severities that end the session. Critical only — nothing below it is worth killing a run over. */\nconst TERMINAL_SEVERITIES = new Set(['critical']);\n\n/**\n * The match that ends the session rather than just warning. Severity alone\n * decides: a rule's `action: 'block'` is a recommendation about the *operation*\n * (PreToolUse still refuses the command), not a licence to end the run. Medium\n * and high `block` rules fire on first-party content often enough that\n * terminating on them costs more than it prevents.\n */\nexport function isTerminalMatch(match: ScanMatch): boolean {\n return TERMINAL_SEVERITIES.has(match.metadata.severity ?? '');\n}\n\n/** Return the highest-severity match from a list of matches. */\nexport function highestSeverityMatch(matches: ScanMatch[]): ScanMatch {\n return matches.reduce((worst, m) =>\n (SEVERITY_RANK[m.metadata.severity ?? ''] ?? 0) >\n (SEVERITY_RANK[worst.metadata.severity ?? ''] ?? 0)\n ? m\n : worst,\n );\n}\n\n/**\n * The match that refuses a publish, or null to allow. Critical only — every\n * 'output' rule asks for `remediate`, and the handoff has no second channel.\n */\nexport function publishBlockingMatch(matches: ScanMatch[]): ScanMatch | null {\n if (matches.length === 0) return null;\n const match = highestSeverityMatch(matches);\n return isTerminalMatch(match) ? match : null;\n}\n\nexport interface ScanVerdict {\n /** The match worth reporting: the highest-severity one. */\n match: ScanMatch;\n /** Session ends now; anything else is advisory. */\n terminal: boolean;\n /** How the scan report records it. */\n action: ScanAction;\n}\n\n/** Resolve what a fence should do about a set of matches. Null when clean. */\nexport function scanVerdict(matches: ScanMatch[]): ScanVerdict | null {\n if (matches.length === 0) return null;\n const match = highestSeverityMatch(matches);\n const terminal = isTerminalMatch(match);\n return { match, terminal, action: terminal ? 'aborted' : 'warned' };\n}\n","/**\n * YARA hook wiring for the Claude Agent SDK.\n *\n * Creates PreToolUse and PostToolUse hook callback arrays that integrate the\n * real @posthog/warlock security scanner (YARA-X via WASM) into the wizard's\n * agent loop. These hooks are registered in the SDK's query() options alongside\n * the existing Stop hook.\n *\n * PreToolUse hooks block dangerous commands before execution. PostToolUse hooks\n * detect violations in written code and prompt injection in read content, and\n * scan context-mill skill downloads.\n *\n * Warlock owns the rules; this file owns the *policy* — how a match maps to a\n * block / revert / terminate response, plus the optional LLM triage pass that\n * filters false positives before we act.\n *\n * Naming: \"yara\" is the technique — scanning content against YARA rules — so the\n * wizard-side wiring keeps that name. \"warlock\" is the engine package that runs\n * the rules. Both names showing up here is intentional.\n *\n * This is Layer 2 (L2) in the wizard's defense-in-depth model, complementing the\n * prompt-based commandments (L0) and the canUseTool() allowlist (L1).\n */\n\nimport fs from 'fs';\nimport path from 'path';\nimport { createHash } from 'crypto';\nimport fg from 'fast-glob';\nimport type {\n ScanMatch,\n TriageMatch,\n LLMProvider,\n Category,\n} from '@posthog/warlock';\nimport { logToFile } from '@utils/debug';\nimport { readFileHead } from '@utils/bounded-fs';\nimport { analytics } from '@utils/analytics';\nimport { isSkillInstallCommand } from '@shared/skill-install';\nimport {\n highestSeverityMatch,\n publishBlockingMatch,\n scanVerdict,\n} from './yara-policy';\nimport type { ScanAction, ScanContext } from './yara-policy';\nimport { WIZARD_YARA_REPORT_FILE } from '@utils/paths';\n// TODO(wizard#594): invert this dependency. The document names are shared\n// constants now, so nothing here imports a program, but this file still knows\n// which programs write PII-shaped reports: every new one has to be added.\n// Proper fix is inverted — programs declare their own doc paths and the hooks\n// read a generic registry. Land that before adding a fourth entry.\nimport {\n EVENTS_AUDIT_REPORT_FILE,\n EVENT_INVENTORY_FILE,\n EVENT_INVENTORY_PART_PATTERN,\n EVENT_PLAN_FILE,\n} from '@shared/constants';\nimport { AUDIT_REPORT_FILE } from '@shared/audit-ledger';\n\n// ─── Warlock module accessor ─────────────────────────────────────\n// Warlock is ESM-only and lazily inits its WASM engine + compiles rules on the\n// first scan() call. We import it once and cache the module promise so the\n// warmed engine (a warlock-internal singleton) is shared across every hook.\n// Mirrors the lazy `await import(...)` pattern used for the agent SDK.\n\ntype WarlockModule = typeof import('@posthog/warlock');\n\nlet warlockModulePromise: Promise<WarlockModule> | null = null;\n\nfunction getWarlock(): Promise<WarlockModule> {\n if (!warlockModulePromise) {\n // Clear the cache on rejection so a transient failure (flaky disk, ESM\n // resolver hiccup) doesn't poison the cache for the rest of the process\n // lifetime. Without this, one rejected import permanently locks every\n // hook into fail-closed-terminate until restart.\n warlockModulePromise = import('@posthog/warlock').catch((err) => {\n warlockModulePromise = null;\n throw err;\n });\n }\n return warlockModulePromise;\n}\n\n/**\n * Pay the WASM-init + rule-compile cost up front, off the hook path, so the\n * first real tool-call scan doesn't eat cold-start under a hook timeout.\n * Best-effort: a failure here is non-fatal — hooks still fail closed per scan.\n */\nexport async function prewarmYaraScanner(): Promise<void> {\n try {\n const warlock = await getWarlock();\n await warlock.scan('');\n logToFile('[YARA] warlock pre-warmed');\n } catch (err) {\n logToFile('[YARA] warlock pre-warm failed:', err);\n }\n}\n\n// ─── Types ───────────────────────────────────────────────────────\n// Loose hook types to avoid tight coupling to the SDK version.\n// The SDK hook types are: HookCallbackMatcher[], where each matcher\n// has { matcher?: string, hooks: HookCallback[], timeout?: number }\n\ntype HookInput = Record<string, unknown>;\ntype HookOutput = Record<string, unknown>;\ntype HookCallback = (\n input: HookInput,\n toolUseID: string | undefined,\n options: { signal: AbortSignal },\n) => Promise<HookOutput>;\n\nexport interface HookCallbackMatcher {\n matcher?: string;\n hooks: HookCallback[];\n timeout?: number;\n}\n\nexport type { ScanAction, ScanContext } from './yara-policy';\n\n// ─── Scan Report Accumulator ─────────────────────────────────────\n\ninterface ScanReportEntry {\n rule: string;\n severity: string;\n category: string;\n action: ScanAction;\n phase: string;\n tool: string;\n /** Rule description — the human-readable \"why\". Rule-static, safe to report. */\n description: string;\n /** Triage outcome, when triage ran. */\n triageVerdict?: string;\n /**\n * Free-text triage rationale. May quote scanned content, so it is written to\n * the local report file only — NEVER sent to PostHog (see captureScanReport).\n */\n triageReason?: string;\n}\n\nlet scanCount = 0;\nconst scanViolations: ScanReportEntry[] = [];\n\nfunction recordScan(): void {\n scanCount++;\n}\n\n// recordMatch is the single entry point for a violation (log + telemetry +\n// report). scanViolations is appended only there.\n\n/**\n * Log a match to the run log + PostHog, and record it in the run's scan report.\n * Single entry point so every violation is reported consistently.\n */\nfunction recordMatch(\n phase: string,\n tool: string,\n match: ScanMatch,\n action: ScanAction,\n): void {\n const triage = (match as TriageMatch).triage;\n logYaraMatch(phase, tool, match, action);\n scanViolations.push({\n rule: match.rule,\n severity: match.metadata.severity ?? 'unknown',\n category: match.metadata.category ?? 'unknown',\n action,\n phase,\n tool,\n description: match.metadata.description ?? '',\n triageVerdict: triage?.verdict,\n triageReason: triage?.reason,\n });\n}\n\n/** Reset counters (for testing) */\nexport function resetScanReport(): void {\n scanCount = 0;\n scanViolations.length = 0;\n}\n\n/**\n * Scan bookkeeping for harnesses that run a scanner outside these hooks (pi):\n * counts the scan and routes violations through `recordMatch`, so they reach\n * the run log, per-match telemetry, and the end-of-run scan report.\n */\nexport function recordExternalScan(\n phase: string,\n tool: string,\n violations: Array<{\n rule: string;\n severity: string;\n category: string;\n description: string;\n }>,\n action: ScanAction,\n): void {\n recordScan();\n for (const v of violations) {\n recordMatch(\n phase,\n tool,\n {\n rule: v.rule,\n metadata: {\n severity: v.severity as ScanMatch['metadata']['severity'],\n category: v.category as ScanMatch['metadata']['category'],\n description: v.description,\n },\n matchedStrings: [],\n },\n action,\n );\n }\n}\n\n/** Format the scan report summary. Returns null if no scans occurred */\nexport function formatScanReport(): string | null {\n if (scanCount === 0) return null;\n\n const lines: string[] = ['', '— YARA Scanner Summary —'];\n const violationCount = scanViolations.length;\n const cleanCount = scanCount - violationCount;\n\n lines.push(\n `✓ ${scanCount} tool calls scanned, ${violationCount} violation${\n violationCount !== 1 ? 's' : ''\n } detected`,\n );\n\n if (violationCount > 0) {\n lines.push('');\n for (const v of scanViolations) {\n const tag = v.action.toUpperCase();\n lines.push(\n ` [${tag}] ${v.rule} (${v.severity.toUpperCase()}) — ${v.phase}:${\n v.tool\n }`,\n );\n }\n }\n\n if (cleanCount > 0) {\n lines.push('');\n lines.push(\n `No violations: ✓ ${cleanCount} clean scan${cleanCount !== 1 ? 's' : ''}`,\n );\n }\n\n lines.push('');\n return lines.join('\\n');\n}\n\n/** Write the scan report to a JSON file. Returns the file path, or null if no scans occurred. */\nexport function writeScanReport(): string | null {\n if (scanCount === 0) return null;\n\n const report = {\n summary: {\n totalScans: scanCount,\n violations: scanViolations.length,\n clean: scanCount - scanViolations.length,\n },\n violations: scanViolations,\n };\n\n try {\n fs.writeFileSync(WIZARD_YARA_REPORT_FILE, JSON.stringify(report, null, 2));\n } catch (err) {\n logToFile('[YARA] Failed to write scan report:', err);\n return null;\n }\n return WIZARD_YARA_REPORT_FILE;\n}\n\n// ─── User-facing abort copy ──────────────────────────────────────\n//\n// The abort screen is the one security surface a user actually sees, so it must\n// say what was caught and that we protected them, in plain language, instead of\n// a bare \"Security violation detected.\" Warlock owns the rules and categories;\n// this map is the wizard's *presentation* of a category it already detected. We\n// deliberately do not surface warlock's own rule `description` here: it is\n// written for maintainers (it ships to telemetry) and reads as technical. The\n// `Category` type is a stable, append-only warlock API, so a new category just\n// falls through to the generic line below rather than breaking the build.\n\nconst CATEGORY_DESCRIPTIONS: Record<Category, string> = {\n prompt_injection:\n 'hit hidden instructions in a file it read, a possible attempt to hijack the setup',\n exfiltration: 'tried to send data to an outside location',\n destructive_operations: 'tried to run a destructive command',\n supply_chain: 'tried to pull in untrusted code',\n posthog_pii: 'tried to write personal data (PII) into your project',\n posthog_hardcoded_key: 'tried to hardcode a PostHog key into your project',\n hardcoded_secret: 'tried to hardcode a secret into your project',\n};\n\n/** Most recent violation that terminated the run, or null. */\nfunction lastTerminatingViolation(): ScanReportEntry | null {\n for (let i = scanViolations.length - 1; i >= 0; i--) {\n if (scanViolations[i].action === 'aborted') return scanViolations[i];\n }\n return null;\n}\n\n/**\n * Build the user-facing copy for the security abort screen: what the scanner\n * caught and what we did about it. This screen renders only because the run was\n * stopped, so the \"what we did\" half is constant. Falls back to a generic line\n * when no specific violation was recorded (e.g. the scanner itself errored and\n * failed closed before a match was logged).\n */\nexport function formatYaraAbortMessage(): string {\n const v = lastTerminatingViolation();\n const what =\n (v && CATEGORY_DESCRIPTIONS[v.category as Category]) ??\n 'did something our security scanner flagged';\n\n return (\n 'Security check stopped the setup.\\n\\n' +\n `The agent ${what}. We stopped the run to keep your project safe.\\n` +\n 'No unsafe changes were left in your project.\\n\\n' +\n 'If this looks wrong, let us know: wizard@posthog.com'\n );\n}\n\n// ─── Hook Timeouts (ms) ─────────────────────────────────────────\n// Generous because triage adds an LLM round-trip, and because a *fired* hook\n// timeout means the protective action is dropped (fail-open). The triage HTTP\n// call has its own shorter internal timeout (see agent-interface), so a hung\n// triage fails inside our catch (fail-closed) before these fire.\n\n/** Timeout for scan hooks (PreToolUse, PostToolUse Write/Edit/Read/Grep) */\nconst HOOK_TIMEOUT_MS = 30_000;\n/** Timeout for the skill install hook (filesystem I/O + multiple scans) */\nconst SKILL_SCAN_HOOK_TIMEOUT_MS = 30_000;\n\n/**\n * Chunk size for scanning (100 KB). Oversized content is scanned in\n * overlapping chunks so coverage is complete — nothing is silently truncated.\n * The size also bounds what a single triage call pastes into the LLM prompt\n * (~25K tokens), so triage always sees the chunk its matches came from.\n */\nconst SCAN_CHUNK_SIZE = 100_000;\n\n// A skill file is read at most this far; the rest is head-scanned and logged.\nconst SKILL_FILE_SCAN_BYTES = 10 * 1024 * 1024;\n/**\n * Overlap between adjacent chunks so a pattern straddling a chunk boundary\n * still lands whole inside at least one chunk. YARA rule strings are at most\n * a few hundred bytes; 4 KB is generous.\n */\nconst SCAN_CHUNK_OVERLAP = 4_096;\n\n// ─── Logging ─────────────────────────────────────────────────────\n\nfunction logYaraMatch(\n phase: string,\n tool: string,\n match: ScanMatch,\n action: ScanAction,\n): void {\n const severity = match.metadata.severity ?? 'unknown';\n const category = match.metadata.category ?? 'unknown';\n const ruleAction = match.metadata.action ?? 'unknown';\n const description = match.metadata.description ?? '';\n logToFile(\n `[YARA] ${phase}:${tool} [${action.toUpperCase()}] rule \"${match.rule}\" ` +\n `(severity: ${severity}, category: ${category}, action: ${ruleAction})\\n` +\n ` Description: ${description}`,\n );\n analytics.wizardCapture('yara rule matched', {\n rule: match.rule,\n severity: match.metadata.severity,\n category: match.metadata.category,\n rule_action: match.metadata.action,\n action,\n phase,\n tool,\n // Rule-static description — the \"why\". The free-text triage reason is\n // deliberately omitted (it can quote scanned content; stays local-only).\n description: match.metadata.description,\n triage_verdict: (match as TriageMatch).triage?.verdict,\n });\n}\n\n/**\n * Send the run's full scan report to PostHog so maintainers can see why a run\n * was flagged, warned, or aborted. This is the maintainer-facing report — it is\n * NEVER shown to the user. No-op if nothing was scanned. The free-text triage\n * reason is omitted to avoid sending scanned content off the user's machine.\n */\nexport function captureScanReport(): void {\n if (scanCount === 0) return;\n analytics.wizardCapture('yara scan report', {\n total_scans: scanCount,\n violation_count: scanViolations.length,\n clean_count: scanCount - scanViolations.length,\n violations: scanViolations.map((v) => ({\n rule: v.rule,\n severity: v.severity,\n category: v.category,\n action: v.action,\n phase: v.phase,\n tool: v.tool,\n description: v.description,\n triage_verdict: v.triageVerdict,\n })),\n });\n // Reset after sending so a second captureScanReport on this same run\n // (e.g. success-path call followed by an abort during shutdown) can't\n // ship duplicate telemetry. Also protects future chained-program runs.\n scanCount = 0;\n scanViolations.length = 0;\n}\n\n/**\n * End-of-run flush for the scan report. Wired once at the runner seam so every\n * harness (linear, orchestrator, and any future shape) reports without having\n * to know warlock exists — scanning is already harness-agnostic (it runs from\n * SDK Pre/PostToolUse hooks), and this keeps reporting at a single shared seam.\n *\n * Order matters: write the local --yara-report file FIRST (it reads scanCount /\n * scanViolations), THEN send telemetry. captureScanReport() zeroes scan state,\n * which is what makes this whole function idempotent — a second call from\n * another termination path (e.g. finally after an abort already flushed) finds\n * scanCount === 0 and every step no-ops.\n *\n * Returns the user-facing report line when a report was written; the caller\n * decides where it goes (the runner emits it as progress).\n */\nexport function flushScanReport(options: {\n yaraReport: boolean;\n}): string | undefined {\n let line: string | undefined;\n if (options.yaraReport) {\n const reportPath = writeScanReport();\n if (reportPath) {\n const summary = formatScanReport();\n line = `YARA scan report: ${reportPath}${summary ?? ''}`;\n }\n }\n captureScanReport();\n return line;\n}\n\n// ─── Wizard-documentation allowlist ───────────────────────────────\n//\n// Files the wizard's own programs write to describe events the user's\n// codebase already captures, or events the integration program is\n// proposing to add. When the agent copies a literal\n// `posthog.capture('event', { email: ... })` snippet (or a property\n// list including PII-shaped keys) into one of these files, the\n// `pii_in_capture_call` rule (category: posthog_pii) fires even though\n// the wizard is documenting / planning, not introducing, the pattern.\n// Suppress posthog_pii matches on these paths only; every other rule\n// (secrets, prompt injection, supply chain, destructive ops) still\n// fires normally so the file cannot be used as a smuggling vector for\n// actual violations.\n\nconst WIZARD_DOC_BASENAMES = new Set([\n EVENT_INVENTORY_FILE,\n EVENTS_AUDIT_REPORT_FILE,\n AUDIT_REPORT_FILE,\n EVENT_PLAN_FILE,\n]);\n\nconst WIZARD_DOC_PATTERNS: RegExp[] = [EVENT_INVENTORY_PART_PATTERN];\n\nexport function isWizardDocumentationPath(\n filePath: string | undefined,\n): boolean {\n if (!filePath) return false;\n const basename = path.basename(filePath);\n if (WIZARD_DOC_BASENAMES.has(basename)) return true;\n return WIZARD_DOC_PATTERNS.some((re) => re.test(basename));\n}\n\n// ─── Repeat-block tracker ─────────────────────────────────────────\n//\n// Field data (switchboard YARA tile): an agent whose edit gets blocked often\n// retries the identical payload — the scan is deterministic, so every retry\n// blocks again and the run burns turns (~4.2 blocks per affected run). The\n// runtime notes already say \"don't retry\"; the data shows the prompt alone\n// doesn't hold. This is the code-side guard: remember the exact payloads we\n// blocked, and when the same content comes back, escalate the block reason —\n// first to \"change the code, not the retry\", then to \"report it and move on\".\n// Used by both fences: the anthropic PreToolUse Bash hook below, and the pi\n// security extension's pre-execution scan (bash/write/edit).\n\n/** Identical-payload attempt at which the agent is told to move on. */\nconst REPEAT_BLOCK_MOVE_ON_ATTEMPT = 3;\n\nexport interface RepeatBlockTracker {\n /**\n * Record a YARA block of `content` on `tool`; returns the attempt count\n * for that exact payload (1 = first time blocked).\n */\n attempt(tool: string, content: string): number;\n}\n\n/** Per-run tracker of YARA-blocked payloads (keyed by tool + content hash). */\nexport function createRepeatBlockTracker(): RepeatBlockTracker {\n const counts = new Map<string, number>();\n return {\n attempt(tool: string, content: string): number {\n const key = `${tool} ${createHash('sha256')\n .update(content)\n .digest('hex')}`;\n const next = (counts.get(key) ?? 0) + 1;\n counts.set(key, next);\n return next;\n },\n };\n}\n\n/**\n * The block reason for the given attempt: the plain reason the first time,\n * an explicit \"this exact retry can never work\" on the second, and a\n * \"report it and move on\" instruction from the third on.\n */\nexport function repeatBlockReason(\n attempt: number,\n tool: string,\n baseReason: string,\n): string {\n if (attempt <= 1) return baseReason;\n if (attempt < REPEAT_BLOCK_MOVE_ON_ATTEMPT) {\n return (\n `${baseReason} This exact ${tool} content was ALREADY blocked — the scan is ` +\n 'deterministic, so retrying identical content will always block again. ' +\n 'Change the code to remove the flagged pattern instead of retrying.'\n );\n }\n return (\n `${baseReason} This exact ${tool} content has now been blocked ${attempt} times. ` +\n 'STOP retrying it. Note the blocked change in the setup report and move on to the next task.'\n );\n}\n\n// ─── Severity helpers ────────────────────────────────────────────\n// Severity vocabulary and the terminate-or-warn decision live in yara-policy.\n\n// ─── Scan + triage core ──────────────────────────────────────────\n\n/**\n * Keep only matches whose rule targets this content surface. Rules carry a\n * `scan_context` of 'command' | 'input' | 'output'. An undefined context is\n * treated as \"applies everywhere\" (fail-safe — a rule that forgets the tag\n * still gets enforced rather than silently skipped).\n */\nfunction matchesForContext(\n matches: ScanMatch[],\n ctx: ScanContext,\n): ScanMatch[] {\n return matches.filter((m) => {\n const c = m.metadata.scan_context as string | undefined;\n return c === ctx || c === undefined;\n });\n}\n\n/**\n * Drop false positives via warlock's LLM triage. Fail-closed: if no provider is\n * available, or the triage call throws, every match is treated as real — we\n * never silently suppress a flagged match.\n *\n * Every overruled match is reported to PostHog (rule metadata only, never the\n * free-text reason) so maintainers can alert on triage-overrule patterns — the\n * signal that someone is either tripping a noisy rule or trying to talk the\n * triage model out of a real finding.\n */\nasync function triageFilter(\n content: string,\n matches: ScanMatch[],\n ctx: ScanContext,\n llmProvider: LLMProvider | undefined,\n): Promise<ScanMatch[]> {\n if (matches.length === 0) return [];\n if (!llmProvider) {\n logToFile(\n `[YARA] triage skipped (no provider) — treating ${matches.length} match(es) as real`,\n );\n return matches;\n }\n try {\n const warlock = await getWarlock();\n const triaged = await warlock.triageMatches(content, matches, llmProvider);\n const kept = triaged.filter((m) => m.triage.verdict === 'true_positive');\n for (const m of triaged) {\n if (m.triage.verdict === 'true_positive') continue;\n logToFile(\n `[YARA] triage overruled rule \"${m.rule}\" (${\n m.metadata.severity ?? 'unknown'\n }) — not acting on it`,\n );\n analytics.wizardCapture('yara triage overruled', {\n rule: m.rule,\n severity: m.metadata.severity,\n category: m.metadata.category,\n scan_context: ctx,\n // The free-text triage reason is deliberately omitted — it can quote\n // scanned content, which must never leave the user's machine.\n });\n }\n logToFile(\n `[YARA] triage: ${matches.length} flagged → ${kept.length} kept as real`,\n );\n return kept;\n } catch (err) {\n logToFile('[YARA] triage failed — treating all matches as real:', err);\n return matches;\n }\n}\n\n/** A chunk of scanned content together with the matches found inside it. */\ninterface FlaggedChunk {\n chunk: string;\n matches: ScanMatch[];\n}\n\n/**\n * Split oversized content into overlapping chunks so the scanner covers all\n * of it. Content that fits in one chunk is returned as-is.\n */\nfunction chunkContent(content: string): string[] {\n if (content.length <= SCAN_CHUNK_SIZE) return [content];\n const chunks: string[] = [];\n const step = SCAN_CHUNK_SIZE - SCAN_CHUNK_OVERLAP;\n for (let start = 0; start < content.length; start += step) {\n chunks.push(content.slice(start, start + SCAN_CHUNK_SIZE));\n if (start + SCAN_CHUNK_SIZE >= content.length) break;\n }\n return chunks;\n}\n\n/**\n * Scan content against warlock rules — chunked when oversized, so nothing is\n * skipped — and keep only matches for this content surface. Chunks scan\n * sequentially (the WASM engine is single-threaded; parallelism buys nothing).\n * Returns each flagged chunk with its matches so triage can later judge every\n * match against the exact content it came from.\n */\nasync function scanForContext(\n content: string,\n ctx: ScanContext,\n): Promise<FlaggedChunk[]> {\n const warlock = await getWarlock();\n const chunks = chunkContent(content);\n if (chunks.length > 1) {\n logToFile(\n `[YARA] content is ${content.length} chars — scanning ${chunks.length} overlapping chunks`,\n );\n // Alertable signal: oversized content should be rare; a spike could mean\n // someone is padding content to probe the scanner.\n analytics.wizardCapture('yara scan chunked', {\n content_length: content.length,\n chunk_count: chunks.length,\n scan_context: ctx,\n });\n }\n const flagged: FlaggedChunk[] = [];\n for (const chunk of chunks) {\n const result = await warlock.scan(chunk);\n if (!result.matched) continue;\n const matches = matchesForContext(result.matches, ctx);\n if (matches.length === 0) continue;\n flagged.push({ chunk, matches });\n }\n return flagged;\n}\n\n/** Categories whose matches are dropped from content sent to a model outside an agent run. */\nconst REDACTED_CATEGORIES: ReadonlySet<string> = new Set([\n 'hardcoded_secret',\n 'posthog_hardcoded_key',\n 'posthog_pii',\n]);\n\n/**\n * Replace every line that holds a secret or PII match with `[redacted]`. A\n * whole line goes, since a match's string can be length-capped. Rejects when\n * the scanner can't load, so callers fail closed.\n */\nexport async function redactSecretLines(\n content: string,\n): Promise<{ text: string; redactedLines: number }> {\n const warlock = await getWarlock();\n const flagged: string[] = [];\n for (const chunk of chunkContent(content)) {\n const result = await warlock.scan(chunk);\n if (!result.matched) continue;\n for (const match of result.matches) {\n if (!REDACTED_CATEGORIES.has(String(match.metadata.category))) continue;\n flagged.push(...match.matchedStrings.filter(Boolean));\n }\n }\n if (flagged.length === 0) return { text: content, redactedLines: 0 };\n let redactedLines = 0;\n const lines = content.split('\\n').map((line) => {\n if (!flagged.some((s) => line.includes(s))) return line;\n redactedLines += 1;\n return '[redacted]';\n });\n return { text: lines.join('\\n'), redactedLines };\n}\n\n/** The overlap between chunks can surface the same rule twice; count it once. */\nfunction dedupeByRule(matches: ScanMatch[]): ScanMatch[] {\n const seen = new Set<string>();\n return matches.filter((m) => {\n if (seen.has(m.rule)) return false;\n seen.add(m.rule);\n return true;\n });\n}\n\n/**\n * Triage each flagged chunk against its own content — the evidence is always\n * inside the window the LLM sees. Triage calls run in parallel (each is just\n * an HTTP round trip) so N flagged chunks cost ~1× triage latency, not N×.\n */\nasync function triageFlagged(\n flagged: FlaggedChunk[],\n ctx: ScanContext,\n llmProvider: LLMProvider | undefined,\n): Promise<ScanMatch[]> {\n const triaged = await Promise.all(\n flagged.map(({ chunk, matches }) =>\n triageFilter(chunk, matches, ctx, llmProvider),\n ),\n );\n return dedupeByRule(triaged.flat());\n}\n\n/**\n * Scan content, filter to the relevant context, triage. Returns real matches.\n * Exported for harnesses that run their own hook loop (pi) so every harness\n * scans through the same warlock engine, chunking, and triage policy.\n */\nexport async function scanAndTriage(\n content: string,\n ctx: ScanContext,\n llmProvider: LLMProvider | undefined,\n): Promise<ScanMatch[]> {\n const flagged = await scanForContext(content, ctx);\n return triageFlagged(flagged, ctx, llmProvider);\n}\n\n// ─── PreToolUse Hooks ────────────────────────────────────────────\n\n/**\n * Create PreToolUse hook matchers for YARA scanning.\n * 1. Bash — scans commands before execution for exfiltration, destructive\n * operations, and supply chain violations ('command'-context rules).\n * 2. publish_handoff — scans the report content with the same 'output'-context\n * rules + triage as the PostToolUse Write/Edit hook, but BEFORE the MCP\n * tool runs: the report is agent-authored and is persisted to a\n * host-designated file (POSTHOG_HANDOFF_OUTPUT_PATH) as well as pushed to\n * the task stream. Only a CRITICAL match refuses the publish; below that\n * the report goes out with the match recorded.\n */\nexport function createPreToolUseYaraHooks(\n // Used only by the publish_handoff matcher below (output-context triage,\n // same fail-closed semantics as Write/Edit). The Bash matcher still passes\n // undefined — any flagged command blocks regardless of triage verdict, so\n // the LLM call would be wasted.\n llmProvider?: LLMProvider,\n // Ends the run when a handoff scan errors — without it that path just blocks,\n // which is the old behavior, so it stays optional.\n onTerminate?: (reason: string) => void,\n): HookCallbackMatcher[] {\n const repeatTracker = createRepeatBlockTracker();\n return [\n {\n hooks: [\n async (input: HookInput): Promise<HookOutput> => {\n try {\n const toolName = input.tool_name as string;\n if (toolName !== 'Bash') return {};\n\n const toolInput = input.tool_input as Record<string, unknown>;\n const command =\n typeof toolInput?.command === 'string' ? toolInput.command : '';\n\n if (!command) return {};\n\n recordScan();\n // Skip triage on PreToolUse Bash: any flagged command is blocked\n // regardless of triage verdict, so the LLM call would be wasted.\n const matches = await scanAndTriage(command, 'command', undefined);\n if (matches.length === 0) return {};\n\n const match = highestSeverityMatch(matches);\n recordMatch('PreToolUse', 'Bash', match, 'blocked');\n\n const attempt = repeatTracker.attempt('Bash', command);\n return {\n decision: 'block',\n reason: repeatBlockReason(\n attempt,\n 'Bash',\n `[YARA] ${match.rule}: ${\n match.metadata.description ?? 'security policy violation'\n }. Command blocked for security.`,\n ),\n };\n } catch (error) {\n logToFile('[YARA] PreToolUse hook error:', error);\n // Fail closed: block the command if scanning fails\n return {\n decision: 'block',\n reason: '[YARA] Scanner error — command blocked as a precaution.',\n };\n }\n },\n ],\n timeout: HOOK_TIMEOUT_MS,\n },\n {\n // ── publish_handoff content scanning ──\n hooks: [\n async (input: HookInput): Promise<HookOutput> => {\n try {\n const toolName = input.tool_name as string;\n // The wizard's publish_handoff MCP tool, fully qualified by the\n // SDK (e.g. mcp__wizard-tools__publish_handoff). Matched by\n // suffix so a server rename can't silently reopen the gap — the\n // exact FQN lives in WIZARD_TOOL_NAMES (wizard-tools/tools.ts).\n if (\n typeof toolName !== 'string' ||\n !toolName.endsWith('__publish_handoff')\n ) {\n return {};\n }\n\n const toolInput = input.tool_input as Record<string, unknown>;\n const content =\n typeof toolInput?.content === 'string' ? toolInput.content : '';\n\n if (!content) return {};\n\n recordScan();\n // Same 'output'-context scan + triage as the Write/Edit hook:\n // the report is agent-authored, quotes the user's code, and now\n // reaches a second persisted consumer (the host file + the cloud\n // worker's PR body) — so it must clear the bar of any other\n // agent-driven write BEFORE the tool executes.\n const matches = await scanAndTriage(content, 'output', llmProvider);\n if (matches.length === 0) return {};\n\n // Below critical: record it, don't cost the run its only report.\n const match = publishBlockingMatch(matches);\n if (!match) {\n const worst = highestSeverityMatch(matches);\n recordMatch('PreToolUse', 'publish_handoff', worst, 'warned');\n logToFile(\n `[YARA] publish_handoff allowed with a ${\n worst.metadata.severity ?? 'unknown'\n } match (${worst.rule}) — below the critical publish bar`,\n );\n return {};\n }\n\n recordMatch('PreToolUse', 'publish_handoff', match, 'blocked');\n\n const attempt = repeatTracker.attempt('publish_handoff', content);\n return {\n decision: 'block',\n reason: repeatBlockReason(\n attempt,\n 'publish_handoff',\n `[YARA] ${match.rule}: ${\n match.metadata.description ?? 'security policy violation'\n }. Handoff blocked for security — remove the flagged content from the report.`,\n ),\n };\n } catch (error) {\n logToFile('[YARA] PreToolUse publish_handoff hook error:', error);\n // Fail closed, but end the run: a silent block would just leave\n // the agent rewording a report that can never publish.\n const reason =\n '[YARA] Scanner error while scanning the handoff report — session terminated as a precaution.';\n onTerminate?.(reason);\n return { decision: 'block', reason };\n }\n },\n ],\n timeout: HOOK_TIMEOUT_MS,\n },\n ];\n}\n\n// ─── PostToolUse Hooks ───────────────────────────────────────────\n\n/**\n * Create PostToolUse hook matchers for YARA scanning.\n *\n * Three matchers:\n * 1. Write/Edit — scan written content ('output'-context rules: PII, secrets)\n * 2. Read/Grep — scan read content ('input'-context rules: prompt injection)\n * 3. Bash (skill install) — scan downloaded skill files for poisoned content\n *\n * `onTerminate` is invoked on the terminal paths (critical prompt injection in\n * read content, a poisoned skill, or a scanner error under fail-closed). It is\n * what ACTUALLY stops the run — returning `stopReason` from a PostToolUse hook\n * is not honored by the SDK, so the caller wires this to the run's\n * AbortController (see agent-interface).\n */\nexport function createPostToolUseYaraHooks(\n llmProvider: LLMProvider | undefined,\n // REQUIRED, not optional: the SDK ignores `stopReason` from PostToolUse\n // hooks, so terminal paths (critical prompt injection, poisoned skill,\n // fail-closed scanner error) depend entirely on this callback firing.\n // Making it required at compile time prevents a future caller from\n // silently regressing every terminal path to a no-op.\n onTerminate: (reason: string) => void,\n): HookCallbackMatcher[] {\n return [\n // ── Write/Edit content scanning ──\n {\n hooks: [\n async (input: HookInput): Promise<HookOutput> => {\n try {\n const toolName = input.tool_name as string;\n if (toolName !== 'Write' && toolName !== 'Edit') return {};\n\n const toolInput = input.tool_input as Record<string, unknown>;\n // For Write, scan the content being written.\n // For Edit, scan the new_string (replacement text).\n const content =\n toolName === 'Write'\n ? (toolInput?.content as string) ?? ''\n : (toolInput?.new_string as string) ?? '';\n\n if (!content) return {};\n\n recordScan();\n const flagged = await scanForContext(content, 'output');\n if (flagged.length === 0) return {};\n\n // Wizard-documentation paths: suppress posthog_pii matches that\n // come from the agent verbatim-copying the user's existing\n // capture calls into an inventory / report, or planning new\n // events with PII-shaped property keys. Every other category\n // still triggers the revert. Suppression runs BEFORE triage so\n // we never pay an LLM round trip for a match we're about to\n // discard anyway.\n // TODO(warlock#33): once warlock PR #33 lands with its\n // more-precise PII rules, the wizard-side suppression below\n // should become unnecessary — remove `WIZARD_DOC_BASENAMES`,\n // `WIZARD_DOC_PATTERNS`, and `isWizardDocumentationPath` and\n // verify the noisy-PII issue stays fixed.\n const filePath = toolInput?.file_path as string | undefined;\n const activeFlagged = isWizardDocumentationPath(filePath)\n ? flagged\n .map(({ chunk, matches }) => ({\n chunk,\n matches: matches.filter(\n (m) => m.metadata.category !== 'posthog_pii',\n ),\n }))\n .filter(({ matches }) => matches.length > 0)\n : flagged;\n if (activeFlagged.length === 0) {\n logToFile(\n `[YARA] posthog_pii match suppressed on wizard doc ${path.basename(\n filePath ?? '',\n )} (rule: ${flagged[0]?.matches[0]?.rule})`,\n );\n return {};\n }\n\n const matches = await triageFlagged(\n activeFlagged,\n 'output',\n llmProvider,\n );\n if (matches.length === 0) return {};\n\n const match = highestSeverityMatch(matches);\n recordMatch('PostToolUse', toolName, match, 'reverted');\n\n return {\n hookSpecificOutput: {\n hookEventName: 'PostToolUse',\n additionalContext:\n `[YARA VIOLATION] ${match.rule}: ${\n match.metadata.description ?? ''\n }. ` +\n `You MUST revert this change immediately. The content you just wrote violates security policy.`,\n },\n };\n } catch (error) {\n logToFile('[YARA] PostToolUse Write/Edit hook error:', error);\n // Fail closed: if scanning is broken on a Write/Edit, the next\n // Read/skill-install scan is also going to be broken — terminate\n // cleanly instead of looping the agent through \"please revert\"\n // until a Read finally aborts. Matches Read/Grep + skill-install\n // catch behavior; consistent fail-closed across all PostToolUse.\n const reason =\n '[YARA] Scanner error while scanning written content — session terminated as a precaution.';\n onTerminate(reason);\n return { stopReason: reason };\n }\n },\n ],\n timeout: HOOK_TIMEOUT_MS,\n },\n\n // ── Read/Grep prompt injection scanning ──\n {\n hooks: [\n async (input: HookInput): Promise<HookOutput> => {\n try {\n const toolName = input.tool_name as string;\n if (toolName !== 'Read' && toolName !== 'Grep') return {};\n\n const toolResponse = input.tool_response;\n // Guard before stringify: `JSON.stringify(undefined ?? '')`\n // returns the 2-char string '\"\"', which is truthy and slips\n // past the empty-content check below. Exit early when the SDK\n // gave us no response payload.\n if (toolResponse == null) return {};\n const content =\n typeof toolResponse === 'string'\n ? toolResponse\n : JSON.stringify(toolResponse);\n\n if (!content) return {};\n\n recordScan();\n const matches = await scanAndTriage(content, 'input', llmProvider);\n const verdict = scanVerdict(matches);\n if (!verdict) return {};\n\n const { match } = verdict;\n recordMatch('PostToolUse', toolName, match, verdict.action);\n\n if (verdict.terminal) {\n const reason =\n `[YARA CRITICAL] ${match.rule}: Prompt injection detected in file content. ` +\n `Agent context is potentially poisoned. Session terminated for safety.`;\n onTerminate(reason);\n return { stopReason: reason };\n }\n\n return {\n hookSpecificOutput: {\n hookEventName: 'PostToolUse',\n additionalContext: `[YARA WARNING] ${match.rule}: ${\n match.metadata.description ?? ''\n }`,\n },\n };\n } catch (error) {\n logToFile('[YARA] PostToolUse Read/Grep hook error:', error);\n // Fail closed: terminate session if scanning fails on read content\n const reason =\n '[YARA] Scanner error while scanning read content — session terminated as a precaution.';\n onTerminate(reason);\n return { stopReason: reason };\n }\n },\n ],\n timeout: HOOK_TIMEOUT_MS,\n },\n\n // ── Context-mill skill install scanning ──\n {\n hooks: [\n async (input: HookInput): Promise<HookOutput> => {\n try {\n const toolName = input.tool_name as string;\n if (toolName !== 'Bash') return {};\n\n const toolInput = input.tool_input as Record<string, unknown>;\n const command =\n typeof toolInput?.command === 'string' ? toolInput.command : '';\n\n // Only scan after skill install commands\n if (!isSkillInstallCommand(command)) return {};\n\n // Extract skill directory from command\n const dirMatch = command.match(\n /mkdir -p (.claude\\/skills\\/[^\\s&]+)/,\n );\n if (!dirMatch) return {};\n\n const skillDir = dirMatch[1];\n const cwd = (input.cwd as string) ?? process.cwd();\n recordScan();\n const matches = await scanSkillFiles(cwd, skillDir, llmProvider);\n\n const verdict = scanVerdict(matches);\n if (!verdict) return {};\n\n recordMatch(\n 'PostToolUse',\n 'Bash (skill install)',\n verdict.match,\n verdict.action,\n );\n // Same verdict as every other surface: critical terminates, the\n // rest warn. These rules fire on first-party skill prose often\n // enough that terminating costs more than it prevents.\n if (!verdict.terminal) return {};\n\n const reason =\n `[YARA CRITICAL] Poisoned skill detected in ${skillDir}: ${verdict.match.rule}. ` +\n `The downloaded skill contains potential prompt injection. Session terminated for safety.`;\n onTerminate(reason);\n return { stopReason: reason };\n } catch (error) {\n logToFile('[YARA] PostToolUse skill install hook error:', error);\n // Fail closed: terminate if skill scanning fails\n const reason =\n '[YARA] Scanner error while scanning skill files — session terminated as a precaution.';\n onTerminate(reason);\n return { stopReason: reason };\n }\n },\n ],\n timeout: SKILL_SCAN_HOOK_TIMEOUT_MS,\n },\n ];\n}\n\n// ─── Skill File Scanner ──────────────────────────────────────────\n\n/**\n * Scan a freshly installed skill directory (any root — .claude/skills or the\n * orchestrator's run cache) and return a terminate reason when it is poisoned,\n * else null. The choke point for TS-path installs (downloadSkill); agent Bash\n * installs are covered by the PostToolUse matcher above. Runs the same LLM\n * triage as the tool-use scans; fail-closed to treating every match as real when\n * no provider is configured, so a missing key never weakens the check.\n * `llmProvider` is explicit — pi and the orchestrator never set the env fallback.\n *\n * Terminates on the same verdict as every other surface (critical only). A\n * non-terminal match is recorded and the skill is kept: these rules fire on\n * first-party skill prose, and deleting the skill leaves the agent working blind\n * on the very content it needed.\n */\nexport async function scanInstalledSkill(\n absoluteSkillDir: string,\n llmProvider: LLMProvider | undefined,\n): Promise<string | null> {\n recordScan();\n const matches = await scanSkillFiles(absoluteSkillDir, '.', llmProvider);\n const verdict = scanVerdict(matches);\n if (!verdict) return null;\n recordMatch(\n 'skill-install',\n 'installSkillById',\n verdict.match,\n verdict.action,\n );\n if (!verdict.terminal) {\n logToFile(\n `[YARA] ${verdict.match.rule} (${\n verdict.match.metadata.severity ?? 'unknown'\n }) in ${absoluteSkillDir} — non-terminal, keeping the skill`,\n );\n return null;\n }\n return `Poisoned skill detected: ${verdict.match.rule} (${\n verdict.match.metadata.severity ?? 'unknown'\n }) in ${absoluteSkillDir}`;\n}\n\n/**\n * Read and scan all text files in a skill directory for prompt injection.\n * Scans each file sequentially (single-threaded WASM — parallelism buys\n * nothing), unions the 'input'-context matches, then triages once across the\n * combined content. Returns the real (post-triage) matches.\n */\nasync function scanSkillFiles(\n cwd: string,\n skillDir: string,\n llmProvider: LLMProvider | undefined,\n): Promise<ScanMatch[]> {\n const absoluteDir = path.resolve(cwd, skillDir);\n\n if (!fs.existsSync(absoluteDir)) {\n logToFile(`[YARA] Skill directory does not exist: ${absoluteDir}`);\n return [];\n }\n\n const files = await fg('**/*.{md,txt,yaml,yml,json,js,ts,py,rb,sh}', {\n cwd: absoluteDir,\n absolute: true,\n });\n\n if (files.length === 0) {\n logToFile(`[YARA] No text files found in skill directory: ${absoluteDir}`);\n return [];\n }\n\n logToFile(\n `[YARA] Scanning ${files.length} files in skill directory: ${skillDir}`,\n );\n\n // Pass 1 (sequential): read and scan ONE file at a time through the\n // chunk-aware path. Oversized files are scanned up to the cap and logged —\n // never silently skipped. Flagged chunks keep a reference to the exact\n // content their matches came from.\n const flagged: FlaggedChunk[] = [];\n for (const filePath of files) {\n let content: string | null;\n try {\n if (fs.statSync(filePath).size > SKILL_FILE_SCAN_BYTES) {\n logToFile(\n `[YARA] Skill file over ${SKILL_FILE_SCAN_BYTES} bytes; scanning its head only: ${filePath}`,\n );\n content = readFileHead(filePath, SKILL_FILE_SCAN_BYTES);\n } else {\n content = fs.readFileSync(filePath, 'utf-8');\n }\n } catch (err) {\n logToFile(`[YARA] Could not read skill file ${filePath}:`, err);\n continue;\n }\n if (content) {\n flagged.push(...(await scanForContext(content, 'input')));\n }\n }\n\n // Pass 2 (parallel, inside triageFlagged): triage each flagged chunk\n // against its own content concurrently. Triage is just an axios call —\n // running them in parallel cuts total wall-clock from N × triage-timeout\n // to ~1×. Important because the hook's HOOK_TIMEOUT_MS is 30s; serial\n // triage on 2+ matched files could blow the hook timeout under a slow\n // Haiku.\n return triageFlagged(flagged, 'input', llmProvider);\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@posthog/wizard",
|
|
3
|
-
"version": "2.84.
|
|
3
|
+
"version": "2.84.1",
|
|
4
4
|
"homepage": "https://github.com/PostHog/wizard",
|
|
5
5
|
"repository": "https://github.com/PostHog/wizard",
|
|
6
6
|
"description": "The PostHog wizard helps you to configure your project",
|
|
@@ -33,8 +33,13 @@
|
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@anthropic-ai/claude-agent-sdk": "0.3.169",
|
|
36
|
+
"@earendil-works/chord": "~1.1.0",
|
|
37
|
+
"@earendil-works/pi-agent-core": "~1.1.0",
|
|
36
38
|
"@earendil-works/pi-ai": "~1.1.0",
|
|
39
|
+
"@earendil-works/pi-codemode": "~1.1.0",
|
|
37
40
|
"@earendil-works/pi-coding-agent": "~1.1.0",
|
|
41
|
+
"@earendil-works/pi-mcp": "~1.1.0",
|
|
42
|
+
"@earendil-works/pi-telemetry": "~1.1.0",
|
|
38
43
|
"@earendil-works/pi-tui": "~1.1.0",
|
|
39
44
|
"@inkjs/ui": "^2.0.0",
|
|
40
45
|
"@langchain/core": "^0.3.40",
|
package/dist/api-H2UaPgjv.js
DELETED