failproofai 1.0.4-beta.1 → 1.0.4-beta.2
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/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +5 -5
- package/.next/standalone/.next/prerender-manifest.json +5 -5
- package/.next/standalone/.next/required-server-files.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +6 -6
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/invite/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/audit/run/route.js +4 -4
- package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/run/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/status/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-request/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-verify/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/logout/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/status/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js +1 -1
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/audit/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +37 -4
- package/.next/standalone/.next/server/app/audit/page.js +2 -2
- package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/policies/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
- package/.next/standalone/.next/server/app/policies/page.js +1 -1
- package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page.js +3 -3
- package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/projects/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/projects/page.js +1 -1
- package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +4 -4
- package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/chunks/{[externals]__1zndo1i._.js → [externals]__1lh7m5d._.js} +1 -1
- package/.next/standalone/.next/server/chunks/{[externals]__0_gzk8q._.js → [externals]__1rqkg_y._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__08dchfu._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0cuho4x._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1adacul._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1le9jqc._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1p-qi2t._.js +5 -0
- package/.next/standalone/.next/server/chunks/_08w6xzm._.js +3 -0
- package/.next/standalone/.next/server/chunks/_09dz7xv._.js +1 -1
- package/.next/standalone/.next/server/chunks/_0tovk6q._.js +1 -1
- package/.next/standalone/.next/server/chunks/_0trp3yc._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1ek68ln._.js +14 -21
- package/.next/standalone/.next/server/chunks/lib_telemetry_ts_0dctyyw._.js +1 -1
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_01r25oi._.js +2 -2
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +2 -2
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/src_hooks_0iu54mz._.js +1 -1
- package/.next/standalone/.next/server/chunks/src_hooks_fp-home_ts_09kv0bn._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__00-s7h8._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__013jr2b._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01wy8d-._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02npjtd._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0da85px._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ftmoxc._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0wvusu0._.js → [root-of-the-server]__0n_lxhg._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0oa1lav._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0p-5p8u._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0rgu2r3._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s740oi._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__190won8._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ctpynv._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1p2otjt._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1qapotl._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1sc84bb._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1zl_r0t._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0-oij9d._.js +23 -0
- package/.next/standalone/.next/server/chunks/ssr/_0-yi74u._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0l2hi_d._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0oq1dh6._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0wso5d3._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1es2j7i._.js +48 -168
- package/.next/standalone/.next/server/chunks/ssr/{_08f0jd2._.js → _1if9ixi._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1u8-lu2._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -69
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_rerun-button_tsx_0blihgw._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_settings_settings-client_tsx_20lq-mq._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_13n822a._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589g._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-home_ts_0je3xkv._.js +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +5 -5
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +54 -21
- package/.next/standalone/.next/static/chunks/{1_bxotmf9qngs.js → 010bv1w6j171t.js} +1 -1
- package/.next/standalone/.next/static/chunks/04r6ch8uf_n8m.js +1 -0
- package/.next/standalone/.next/static/chunks/{29-iqnp9v_axt.css → 0o-hh5_turzlz.css} +1 -1
- package/.next/standalone/.next/static/chunks/0qmsunv04x4na.css +2 -0
- package/.next/standalone/.next/static/chunks/{00gv0v91mycm-.js → 0wz8yftk18ts2.js} +1 -1
- package/.next/standalone/.next/static/chunks/0zebh1n9jkfbt.js +1 -0
- package/.next/standalone/.next/static/chunks/{0qn9khv0oo9ex.js → 1pb1oztsbwcss.js} +1 -1
- package/.next/standalone/.next/static/chunks/2aquitk72k2op.js +1 -0
- package/.next/standalone/.next/static/chunks/2bi_1y0a_smt7.js +6 -0
- package/.next/standalone/.next/static/chunks/{1po3lirgk2riw.js → 2ej3b8gk5ittu.js} +1 -1
- package/.next/standalone/.next/static/chunks/{01hvwwrhvbzyh.js → 2zafgs90r_leq.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1_2jsatfuthrw.js → 32spub4wqjem-.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3_--3ibia6ba1.js → 3l36d73kj18zw.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2bvwsyu6x__ia.js → 3m4upvybtrexd.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2k9f4tyv04809.css → 3uyhmm01g64k4.css} +1 -0
- package/.next/standalone/.next/static/chunks/{2bnf_4pgyk1rq.js → 40lpt2o2b283e.js} +1 -1
- package/.next/standalone/.next/static/chunks/{turbopack-18qf4jodtft6b.js → turbopack-3s3c-u_u_l0u9.js} +1 -1
- package/.next/standalone/.opencode/plugins/failproofai.mjs +51 -27
- package/.next/standalone/app/actions/get-leaks.ts +100 -0
- package/.next/standalone/app/audit/_components/audit-dashboard.tsx +112 -33
- package/.next/standalone/app/audit/_components/audit-poster.tsx +29 -10
- package/.next/standalone/app/audit/_components/come-back-better-section.tsx +6 -1
- package/.next/standalone/app/audit/_components/empty-state.tsx +4 -1
- package/.next/standalone/app/audit/_components/how-to-improve-section.tsx +19 -5
- package/.next/standalone/app/audit/_components/leak-section.tsx +179 -0
- package/.next/standalone/app/audit/_components/share-templates.ts +110 -44
- package/.next/standalone/app/audit/audit-styles.css +114 -1
- package/.next/standalone/app/project/[name]/page.tsx +4 -24
- package/.next/standalone/fp-cloud-cli/CHANGELOG.md +26 -3
- package/.next/standalone/fp-cloud-cli/fp_cli/_click_compat.py +31 -8
- package/.next/standalone/fp-cloud-cli/fp_cli/permissions.py +1 -0
- package/.next/standalone/fp-cloud-cli/pyproject.toml +9 -2
- package/.next/standalone/fp-cloud-cli/tests/test_click_compat.py +54 -0
- package/.next/standalone/fp-cloud-cli/uv.lock +9 -12
- package/.next/standalone/lib/auth/api-server-client.ts +28 -0
- package/.next/standalone/lib/claude-sessions.ts +101 -27
- package/.next/standalone/lib/cli-registry.ts +1 -33
- package/.next/standalone/lib/download-session.ts +0 -37
- package/.next/standalone/lib/projects.ts +3 -27
- package/.next/standalone/node_modules/@next/env/package.json +1 -1
- package/.next/standalone/node_modules/next/dist/build/swc/index.js +1 -1
- package/.next/standalone/node_modules/next/dist/compiled/next-server/pages-turbo.runtime.prod.js +1 -1
- package/.next/standalone/node_modules/next/dist/experimental/testmode/fetch.js +10 -2
- package/.next/standalone/node_modules/next/dist/lib/patch-incorrect-lockfile.js +3 -3
- package/.next/standalone/node_modules/next/dist/lib/typescript/runTypeScriptCli.js +2 -2
- package/.next/standalone/node_modules/next/dist/lib/verify-typescript-setup.js +3 -1
- package/.next/standalone/node_modules/next/dist/server/config.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-turbopack.js +2 -2
- package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-webpack.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/image-optimizer.js +2 -2
- package/.next/standalone/node_modules/next/dist/server/lib/app-info-log.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/lib/start-server.js +1 -1
- package/.next/standalone/node_modules/next/dist/telemetry/anonymous-meta.js +1 -1
- package/.next/standalone/node_modules/next/dist/telemetry/events/swc-load-failure.js +1 -1
- package/.next/standalone/node_modules/next/dist/telemetry/events/version.js +2 -2
- package/.next/standalone/node_modules/next/package.json +11 -11
- package/.next/standalone/package.json +15 -14
- package/.next/standalone/sdk/python/CHANGELOG.md +155 -0
- package/.next/standalone/sdk/python/README.md +8 -0
- package/.next/standalone/sdk/python/examples/evaluator_worker.py +121 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_version.py +1 -1
- package/.next/standalone/sdk/python/failproofai_sdk/evaluator/__init__.py +101 -0
- package/.next/standalone/sdk/python/failproofai_sdk/evaluator/__main__.py +49 -0
- package/.next/standalone/sdk/python/failproofai_sdk/evaluator/_sandbox_runner.py +63 -0
- package/.next/standalone/sdk/python/failproofai_sdk/evaluator/authoring.py +404 -0
- package/.next/standalone/sdk/python/failproofai_sdk/evaluator/client.py +299 -0
- package/.next/standalone/sdk/python/failproofai_sdk/evaluator/protocol.py +754 -0
- package/.next/standalone/sdk/python/failproofai_sdk/evaluator/runtime.py +936 -0
- package/.next/standalone/sdk/python/failproofai_sdk/evaluator/source.py +684 -0
- package/.next/standalone/sdk/python/tests/fixtures/evaluator_v2/README.md +28 -0
- package/.next/standalone/sdk/python/tests/fixtures/evaluator_v2/contract.json +252 -0
- package/.next/standalone/sdk/python/tests/test_evaluator_authoring.py +124 -0
- package/.next/standalone/sdk/python/tests/test_evaluator_client.py +253 -0
- package/.next/standalone/sdk/python/tests/test_evaluator_example.py +35 -0
- package/.next/standalone/sdk/python/tests/test_evaluator_http_e2e.py +636 -0
- package/.next/standalone/sdk/python/tests/test_evaluator_main.py +47 -0
- package/.next/standalone/sdk/python/tests/test_evaluator_protocol.py +246 -0
- package/.next/standalone/sdk/python/tests/test_evaluator_review_fixes.py +164 -0
- package/.next/standalone/sdk/python/tests/test_evaluator_runtime.py +1103 -0
- package/.next/standalone/sdk/python/tests/test_evaluator_source.py +430 -0
- package/.next/standalone/sdk/python/tests/test_zero_dependencies.py +17 -0
- package/.next/standalone/sdk/python/uv.lock +18 -18
- package/.next/standalone/server.js +1 -1
- package/README.md +19 -95
- package/bin/failproofai.mjs +7 -24
- package/dist/cli.mjs +19910 -25855
- package/dist/worker.mjs +4818 -6344
- package/lib/auth/api-server-client.ts +28 -0
- package/lib/claude-sessions.ts +101 -27
- package/lib/cli-registry.ts +1 -33
- package/lib/download-session.ts +0 -37
- package/lib/projects.ts +3 -27
- package/package.json +15 -14
- package/pi-extension/index.ts +42 -4
- package/scripts/changelog-open.py +115 -0
- package/src/audit/cli-adapters/index.ts +0 -24
- package/src/audit/cli.ts +134 -0
- package/src/audit/desktop-notify.ts +420 -0
- package/src/audit/harm-report.ts +101 -0
- package/src/audit/index.ts +141 -13
- package/src/audit/leak-fingerprint.ts +200 -0
- package/src/audit/leak-notice.ts +161 -0
- package/src/audit/leak-record.ts +232 -0
- package/src/audit/leak-scan.ts +292 -0
- package/src/audit/leak-store.ts +217 -0
- package/src/audit/macos-notifier.ts +310 -0
- package/src/audit/redact-example.ts +266 -14
- package/src/audit/report-harm.ts +12 -1
- package/src/audit/report.ts +6 -3
- package/src/audit/schedule-cli.ts +37 -0
- package/src/audit/scoring.ts +49 -0
- package/src/audit/types.ts +41 -0
- package/src/hooks/builtin-policies.ts +71 -8
- package/src/hooks/configure-wizard.ts +20 -0
- package/src/hooks/enforcement-capability.ts +0 -97
- package/src/hooks/fp-config.ts +71 -11
- package/src/hooks/fp-home.ts +27 -0
- package/src/hooks/handler.ts +64 -62
- package/src/hooks/harness-cli.ts +1 -3
- package/src/hooks/integrations.ts +49 -769
- package/src/hooks/normalize-cli-payload.ts +0 -126
- package/src/hooks/notice.ts +155 -0
- package/src/hooks/policy-evaluator.ts +6 -297
- package/src/hooks/resolve-transcript-path.ts +0 -10
- package/src/hooks/tool-name-canonicalize.ts +0 -64
- package/src/hooks/types.ts +2 -533
- package/src/hooks/uninstall-cli.ts +15 -0
- package/.next/standalone/.grok/hooks/failproofai.json +0 -172
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__07hor7h._.js +0 -5
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0elsuli._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0gnj_qa._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ljuzm_._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0w23bpg._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0x5c7sh._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0yik9_w._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1jg6s9m._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1s6qci9._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1xfm05z._.js +0 -3
- package/.next/standalone/.next/server/chunks/_0tdy_6l._.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_grok-projects_ts_1i2epl_._.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_qwen-projects_ts_0wu_a8e._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0_4gcz8._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0d--wmy._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qqrlf_._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s11fx1._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__16gamwq._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1bbk-si._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1c3ybxr._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1izi7zn._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__21bj7uw._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_00z5uuq._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_1byck5v._.js +0 -23
- package/.next/standalone/.next/server/chunks/ssr/_1ylae7o._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/lib_cline-projects_ts_13_hbdd._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/lib_grok-projects_ts_0rc-d0o._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/lib_ori-projects_ts_04tofqo._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/lib_qwen-projects_ts_0w3i9o7._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/node_modules_html-to-image_es_index_1ao30b1.js +0 -3
- package/.next/standalone/.next/static/chunks/0hzmxwb2pvk5p.js +0 -6
- package/.next/standalone/.next/static/chunks/0j884dwnwtyrt.js +0 -1
- package/.next/standalone/.next/static/chunks/0mr9dg3bnloa1.js +0 -1
- package/.next/standalone/.next/static/chunks/0u64yey446k4f.css +0 -1
- package/.next/standalone/.next/static/chunks/3gf1jg-5m-u1t.css +0 -2
- package/.next/standalone/.next/static/chunks/3qfoeqhy5l10x.js +0 -1
- package/.next/standalone/.next/static/chunks/3ws7eljkq25wg.js +0 -69
- package/.next/standalone/.next/static/chunks/3zkg2s2vzxc3d.js +0 -1
- package/.next/standalone/.qwen/settings.json +0 -251
- package/.next/standalone/assets/logos/cline.png +0 -0
- package/.next/standalone/assets/logos/grok-dark.svg +0 -1
- package/.next/standalone/assets/logos/grok-light.svg +0 -1
- package/.next/standalone/assets/logos/ori-dark.svg +0 -1
- package/.next/standalone/assets/logos/ori-light.svg +0 -1
- package/.next/standalone/assets/logos/qwen.svg +0 -1
- package/.next/standalone/lib/cline-projects.ts +0 -155
- package/.next/standalone/lib/cline-sessions.ts +0 -268
- package/.next/standalone/lib/grok-projects.ts +0 -123
- package/.next/standalone/lib/grok-sessions.ts +0 -355
- package/.next/standalone/lib/ori-projects.ts +0 -141
- package/.next/standalone/lib/ori-sessions.ts +0 -221
- package/.next/standalone/lib/qwen-projects.ts +0 -106
- package/.next/standalone/lib/qwen-sessions.ts +0 -313
- package/lib/cline-projects.ts +0 -155
- package/lib/cline-sessions.ts +0 -268
- package/lib/grok-projects.ts +0 -123
- package/lib/grok-sessions.ts +0 -355
- package/lib/ori-projects.ts +0 -141
- package/lib/ori-sessions.ts +0 -221
- package/lib/qwen-projects.ts +0 -106
- package/lib/qwen-sessions.ts +0 -313
- package/src/audit/cli-adapters/cline.ts +0 -50
- package/src/audit/cli-adapters/grok.ts +0 -70
- package/src/audit/cli-adapters/ori.ts +0 -55
- package/src/audit/cli-adapters/qwen.ts +0 -71
- package/src/hooks/batch-expand.ts +0 -380
- package/src/hooks/batch-fanout.ts +0 -142
- package/src/hooks/risk-patterns.ts +0 -26
- /package/.next/standalone/.next/static/{qJx3mbOQV-5ElrcRb4Sp7 → aKNv4Kmv98Xpwns0Xa-C-}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{qJx3mbOQV-5ElrcRb4Sp7 → aKNv4Kmv98Xpwns0Xa-C-}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{qJx3mbOQV-5ElrcRb4Sp7 → aKNv4Kmv98Xpwns0Xa-C-}/_ssgManifest.js +0 -0
|
@@ -16,12 +16,6 @@ import { POLICY_CATALOG } from "./policy-catalog";
|
|
|
16
16
|
import { allow, deny, instruct } from "./policy-helpers";
|
|
17
17
|
import { normalizePolicyName, registerPolicy } from "./policy-registry";
|
|
18
18
|
import { hookLogWarn } from "./hook-logger";
|
|
19
|
-
import {
|
|
20
|
-
ENV_FILE_PATH_RE,
|
|
21
|
-
SECRET_FILE_RE,
|
|
22
|
-
SECRET_FILE_ID_RSA_RE,
|
|
23
|
-
SECRET_FILE_CREDENTIALS_RE,
|
|
24
|
-
} from "./risk-patterns";
|
|
25
19
|
|
|
26
20
|
/**
|
|
27
21
|
* Whether `resolved` lives under an agent CLI's home directory
|
|
@@ -132,15 +126,80 @@ const JWT_RE = /eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/;
|
|
|
132
126
|
|
|
133
127
|
// sanitizeApiKeys
|
|
134
128
|
const API_KEY_PATTERNS: Array<[RegExp, string]> = [
|
|
129
|
+
// ── Anthropic ── prefix doc-verified: platform.claude.com says a key
|
|
130
|
+
// "starts with `sk-ant-`" and its own example is `sk-ant-api03-...`.
|
|
131
|
+
[/sk-ant-api03-[A-Za-z0-9\-_]{20,}/, "Anthropic API key"],
|
|
132
|
+
// Minted by the Claude CLI's OAuth flow and billed against a Pro/Max
|
|
133
|
+
// subscription rather than the API account — a different thing to revoke.
|
|
134
|
+
[/sk-ant-oat01-[A-Za-z0-9\-_]{20,}/, "Anthropic OAuth token"],
|
|
135
135
|
[/sk-ant-[A-Za-z0-9\-_]{20,}/, "Anthropic API key"],
|
|
136
|
+
|
|
137
|
+
// ── OpenAI ── the body charset EXCLUDES `-` and `_` on purpose: with them,
|
|
138
|
+
// this rule's run would bridge the hyphen in `sk-proj-` and report every
|
|
139
|
+
// project key a second time under the wrong label.
|
|
136
140
|
[/sk-proj-[A-Za-z0-9\-_]{20,}/, "OpenAI project API key"],
|
|
137
|
-
[/sk-[A-Za-z0-9]{20,}/, "OpenAI
|
|
138
|
-
|
|
141
|
+
[/sk-svcacct-[A-Za-z0-9\-_]{20,}/, "OpenAI service-account key"],
|
|
142
|
+
// Highest blast radius in the family: it authenticates the organisation
|
|
143
|
+
// admin surface, and lives at a DIFFERENT console from the other keys.
|
|
144
|
+
[/sk-admin-[A-Za-z0-9\-_]{20,}/, "OpenAI admin key"],
|
|
145
|
+
|
|
146
|
+
// ── OpenAI-compatible gateways ──
|
|
147
|
+
[/sk-or-v1-[A-Za-z0-9\-_]{20,}/, "OpenRouter API key"],
|
|
148
|
+
// A BARE `sk-` CANNOT BE ATTRIBUTED, and calling it OpenAI is what this rule
|
|
149
|
+
// used to do. LiteLLM's own docs say its virtual keys "must start with sk-",
|
|
150
|
+
// and DeepSeek plus every other OpenAI-compatible proxy mints the same shape
|
|
151
|
+
// — so on a machine running a gateway (this one does) the old label sent the
|
|
152
|
+
// user to the wrong console to revoke a key that was never OpenAI's. The
|
|
153
|
+
// honest label names the family, not a vendor.
|
|
154
|
+
[/sk-[A-Za-z0-9]{20,}/, "OpenAI-compatible key (issuer unknown)"],
|
|
155
|
+
|
|
156
|
+
// ── GitHub ── all five documented types. github.blog's token-format post
|
|
157
|
+
// names ghp/gho/ghu/ghs/ghr; only two of them shipped before.
|
|
139
158
|
[/github_pat_[A-Za-z0-9_]{82}/, "GitHub fine-grained token"],
|
|
159
|
+
[/ghp_[A-Za-z0-9]{36}/, "GitHub personal access token"],
|
|
160
|
+
[/gho_[A-Za-z0-9]{36}/, "GitHub OAuth token"],
|
|
161
|
+
[/ghu_[A-Za-z0-9]{36}/, "GitHub user-to-server token"],
|
|
162
|
+
[/ghs_[A-Za-z0-9]{36}/, "GitHub server-to-server token"],
|
|
163
|
+
[/ghr_[A-Za-z0-9]{36}/, "GitHub refresh token"],
|
|
164
|
+
[/glpat-[A-Za-z0-9\-_]{20,}/, "GitLab personal access token"],
|
|
165
|
+
|
|
166
|
+
// ── AWS ── the ID is the PUBLIC half; the paired 40-char secret is the
|
|
167
|
+
// credential. Reported anyway because an exposed ID names the account and is
|
|
168
|
+
// what a user searches their console for. `ASIA` (temporary STS) was missing.
|
|
140
169
|
[/AKIA[A-Z0-9]{16}/, "AWS access key ID"],
|
|
170
|
+
[/ASIA[A-Z0-9]{16}/, "AWS temporary access key ID"],
|
|
171
|
+
|
|
172
|
+
// ── Payments ── Stripe documents `pk_*` as "Safe to expose: Yes", so only
|
|
173
|
+
// the secret and restricted halves appear here.
|
|
141
174
|
[/sk_live_[A-Za-z0-9]{24,}/, "Stripe live secret key"],
|
|
142
175
|
[/sk_test_[A-Za-z0-9]{24,}/, "Stripe test secret key"],
|
|
176
|
+
[/rk_live_[A-Za-z0-9]{24,}/, "Stripe restricted key"],
|
|
177
|
+
|
|
178
|
+
// ── Google ── `AIza` also covers Firebase Web and Maps browser keys, which
|
|
179
|
+
// are public by design, so the label stays non-committal about severity.
|
|
143
180
|
[/AIza[0-9A-Za-z\-_]{35}/, "Google API key"],
|
|
181
|
+
|
|
182
|
+
// ── Messaging ── the Telegram bot token was matched by ZERO of the shipped
|
|
183
|
+
// patterns and was found only because a phone-number regex tripped over it;
|
|
184
|
+
// two live-looking ones sit in this machine's corpus right now. Slack's
|
|
185
|
+
// prefixes were in the Rust collector but not here — one of nine such
|
|
186
|
+
// divergences between the two engines.
|
|
187
|
+
[/\d{8,10}:[A-Za-z0-9_-]{35}/, "Telegram bot token"],
|
|
188
|
+
[/xox[baprs]-[A-Za-z0-9-]{10,}/, "Slack token"],
|
|
189
|
+
[/xapp-[0-9]-[A-Za-z0-9-]{10,}/, "Slack app-level token"],
|
|
190
|
+
|
|
191
|
+
// ── Model hosts ──
|
|
192
|
+
[/hf_[A-Za-z0-9]{30,}/, "Hugging Face token"],
|
|
193
|
+
[/gsk_[A-Za-z0-9]{40,}/, "Groq API key"],
|
|
194
|
+
[/r8_[A-Za-z0-9]{30,}/, "Replicate token"],
|
|
195
|
+
[/xai-[A-Za-z0-9]{40,}/, "xAI API key"],
|
|
196
|
+
|
|
197
|
+
// ── Registries ──
|
|
198
|
+
[/npm_[A-Za-z0-9]{36}/, "npm access token"],
|
|
199
|
+
[/dop_v1_[a-f0-9]{64}/, "DigitalOcean token"],
|
|
200
|
+
[/shpat_[a-fA-F0-9]{32}/, "Shopify access token"],
|
|
201
|
+
[/sb_secret_[A-Za-z0-9\-_]{20,}/, "Supabase secret key"],
|
|
202
|
+
[/sbp_[a-f0-9]{40}/, "Supabase personal token"],
|
|
144
203
|
];
|
|
145
204
|
|
|
146
205
|
// sanitizeConnectionStrings
|
|
@@ -198,6 +257,7 @@ const DOTNET_GETENV_RE = /\[Environment\]::GetEnvironment/i;
|
|
|
198
257
|
const CMD_ECHO_ENV_RE = /echo\s+%[A-Za-z_]/i;
|
|
199
258
|
|
|
200
259
|
// blockEnvFiles
|
|
260
|
+
const ENV_FILE_PATH_RE = /(?:^|[\\/])\.env(?:\.|$)/;
|
|
201
261
|
const ENV_CMD_RE = /\.env(?:\b|\s|$|\.)/;
|
|
202
262
|
|
|
203
263
|
// blockSudo
|
|
@@ -449,6 +509,9 @@ const SHORT_FLAG_BUNDLE_RE = /^-[a-zA-Z]*f[a-zA-Z]*$/;
|
|
|
449
509
|
const SAFE_FORCE_PREFIXES = ["--force-with-lease", "--force-if-includes"] as const;
|
|
450
510
|
|
|
451
511
|
// blockSecretsWrite
|
|
512
|
+
const SECRET_FILE_RE = /\.(?:pem|key)$/;
|
|
513
|
+
const SECRET_FILE_ID_RSA_RE = /id_rsa/;
|
|
514
|
+
const SECRET_FILE_CREDENTIALS_RE = /credentials/;
|
|
452
515
|
|
|
453
516
|
// blockWorkOnMain
|
|
454
517
|
const GIT_COMMIT_MERGE_RE = /git\s+(commit|merge|rebase|cherry-pick)\b/;
|
|
@@ -74,6 +74,7 @@ import {
|
|
|
74
74
|
probeDaemonEndToEnd,
|
|
75
75
|
uninstallDaemonService,
|
|
76
76
|
} from "./daemon-service";
|
|
77
|
+
import { installMacNotifier } from "../audit/macos-notifier";
|
|
77
78
|
import { hookLogWarn } from "./hook-logger";
|
|
78
79
|
import {
|
|
79
80
|
readCloudCredentials,
|
|
@@ -1442,6 +1443,25 @@ export async function runConfigureWizard(
|
|
|
1442
1443
|
// longer referenced by anything. Keeps the previous version for an
|
|
1443
1444
|
// offline rollback.
|
|
1444
1445
|
pruneOldDaemonBinaries();
|
|
1446
|
+
|
|
1447
|
+
// The delivery path for what the scheduled audit finds. macOS only, and a
|
|
1448
|
+
// no-op everywhere else: a Linux box's audit talks to the session bus
|
|
1449
|
+
// itself, while on a Mac the daemon runs outside the GUI session and cannot
|
|
1450
|
+
// reach Notification Center at all — see `audit/macos-notifier.ts`.
|
|
1451
|
+
//
|
|
1452
|
+
// Installed here, with the daemon, because it exists only to deliver what
|
|
1453
|
+
// the daemon's scheduled scan produces, and removed alongside it by
|
|
1454
|
+
// `uninstall`. Silent, and not a step in the wizard: it needs no password,
|
|
1455
|
+
// asks nothing, and narrating it would be an apology for the delivery half
|
|
1456
|
+
// of a feature the user just switched on. What they are actually asked —
|
|
1457
|
+
// does this machine scan, and may it interrupt — are real settings, and
|
|
1458
|
+
// both are theirs to change afterwards.
|
|
1459
|
+
//
|
|
1460
|
+
// Its failure is not the wizard's failure. A Mac where `osacompile` is
|
|
1461
|
+
// missing or launchd refuses the job must still finish installing its
|
|
1462
|
+
// daemon, its hooks and its policies, which are the parts that enforce
|
|
1463
|
+
// anything; the notice in-CLI still reaches them either way.
|
|
1464
|
+
installMacNotifier();
|
|
1445
1465
|
}
|
|
1446
1466
|
|
|
1447
1467
|
// Telemetry runs concurrently with the install (never rejects, 5s-bounded) so
|
|
@@ -253,103 +253,6 @@ export const ENFORCEMENT_CAPABILITY: Record<
|
|
|
253
253
|
// the policy-evaluator comment both claim "Goose has NO Stop event"; that is
|
|
254
254
|
// false at v1.43.0 (agent.rs:1956, :2840 emit_stop_hook_blocking).
|
|
255
255
|
},
|
|
256
|
-
|
|
257
|
-
// grok 1.0.3 (1a29d5bc12). Every "block" row below was proven by a live
|
|
258
|
-
// probe, not read from grok's hooks doc — which is also where two of the
|
|
259
|
-
// three surprises came from (see types.ts): the doc's own tool name for the
|
|
260
|
-
// shell tool disagrees with the wire, and nothing documents that project
|
|
261
|
-
// hooks need a git repo. grok's capability advertisement corroborates the
|
|
262
|
-
// blocking set: its ACP initialize response carries
|
|
263
|
-
// x.ai/hooks: {blockingEvents:["pre_tool_use","stop","subagent_stop"],
|
|
264
|
-
// decisions:["deny","block"], stopSignals:[…]}
|
|
265
|
-
// — exactly the three rows marked "block" here.
|
|
266
|
-
grok: {
|
|
267
|
-
PreToolUse: "block", // VERIFIED live: {decision:"deny",reason} blocked `echo` and beat --yolo (permissionMode bypassPermissions); Claude's hookSpecificOutput shape did NOT block (A/B on the same hook)
|
|
268
|
-
Stop: "block", // VERIFIED live: {decision:"block",reason} forced another turn — the agent ran the required command, then stopped. ONLY on reason==="end_turn"; the session-shutdown fire is parsed and discarded upstream. Cap: 8 continuations/turn
|
|
269
|
-
SubagentStop: "block", // advertised in x.ai/hooks blockingEvents; not exercised by a probe
|
|
270
|
-
UserPromptSubmit: "observe", // fired live; not in blockingEvents
|
|
271
|
-
PostToolUse: "observe", // fired live; post-hoc, and not in blockingEvents
|
|
272
|
-
PostToolUseFailure: "observe",
|
|
273
|
-
SessionStart: "observe", // fired live
|
|
274
|
-
SessionEnd: "observe", // fired live
|
|
275
|
-
// The six below are the widened set. All OBSERVE by construction, not by
|
|
276
|
-
// caution: blockingEvents lists exactly three events, so grok cannot honour
|
|
277
|
-
// a deny on any of these no matter what we send.
|
|
278
|
-
StopFailure: "observe", // VERIFIED firing (a 429 turn): {error:"rate_limit", errorDetails, lastAssistantMessage}
|
|
279
|
-
Notification: "observe", // VERIFIED firing: notificationType "agent_error" — a type grok's own docs do not list
|
|
280
|
-
PermissionDenied: "observe", // config key accepted (count=14); firing not exercised — needs a real permission denial
|
|
281
|
-
SubagentStart: "observe", // config key accepted; firing not exercised — needs a subagent
|
|
282
|
-
PreCompact: "observe", // config key accepted; firing not exercised — needs compaction
|
|
283
|
-
PostCompact: "observe", // config key accepted; firing not exercised — needs compaction
|
|
284
|
-
},
|
|
285
|
-
|
|
286
|
-
// qwen-code 0.21.12. A near-pure Claude clone on the wire, so most rows
|
|
287
|
-
// inherit Claude's semantics — but only the two proven ones are "block".
|
|
288
|
-
qwen: {
|
|
289
|
-
PreToolUse: "block", // VERIFIED live: hookSpecificOutput.permissionDecision:"deny" blocked run_shell_command and beat -y (yolo); reason reached the model verbatim. ("ask" degrades to deny in headless and background subagents)
|
|
290
|
-
Stop: "block", // VERIFIED live: top-level {decision:"block",reason} forced another turn. NOTE stop_hook_active is true on the FIRST fire, so it is not a usable loop guard here
|
|
291
|
-
UserPromptSubmit: "observe", // docs accept a decision, unverified — and it fires per MODEL INVOCATION (4× in one observed turn), so treat as observation until probed
|
|
292
|
-
PostToolUse: "observe", // docs accept `decision`, unverified; post-hoc regardless
|
|
293
|
-
PostToolUseFailure: "observe",
|
|
294
|
-
PermissionRequest: "observe",
|
|
295
|
-
PermissionDenied: "observe",
|
|
296
|
-
SubagentStart: "observe",
|
|
297
|
-
SubagentStop: "observe", // sibling of Stop upstream, but not probed — left honest rather than assumed
|
|
298
|
-
PreCompact: "observe",
|
|
299
|
-
SessionStart: "observe", // fired live
|
|
300
|
-
SessionEnd: "observe",
|
|
301
|
-
// Widened set. Every one has a real executeHooks() dispatch site in the
|
|
302
|
-
// shipped bundle; the three marked VERIFIED were also observed firing.
|
|
303
|
-
TaskCreated: "block", // qwen's TodoCreated. VERIFIED firing ×4 with phase:"validation", where upstream docs state a {decision:"block"} PREVENTS the write and returns the reason to the model
|
|
304
|
-
TaskCompleted: "block", // qwen's TodoCompleted. VERIFIED firing ×4, same validation-phase contract
|
|
305
|
-
InstructionsLoaded: "observe", // VERIFIED firing: {file_path, memory_type, load_reason}
|
|
306
|
-
Notification: "observe", // VERIFIED firing: notification_type "auth_success"
|
|
307
|
-
StopFailure: "observe", // upstream documents it fire-and-forget — output and exit code ignored
|
|
308
|
-
PostCompact: "observe",
|
|
309
|
-
UserPromptExpansion: "observe",
|
|
310
|
-
},
|
|
311
|
-
|
|
312
|
-
// ── ori ───────────────────────────────────────────────────────────────────
|
|
313
|
-
// ori 0.12.0+68f9a36, probed LIVE against the built-in `@ori-runloop/
|
|
314
|
-
// agent-loop` harness driving nvidia/nemotron-3.5-lightning:free. Enforcement
|
|
315
|
-
// is the `approval-asker` / `unattended-approvals` extension points rather
|
|
316
|
-
// than a hook-event stream, so PreToolUse is the only row there can be: ori
|
|
317
|
-
// exposes no prompt-submit, post-tool, session or stop gate to subscribe to.
|
|
318
|
-
ori: {
|
|
319
|
-
PreToolUse: "block", // LIVE: {outcome:"deny"} from our approval-asker provision turned 3 tool.started into 3 tool.failed with ZERO tool.succeeded, and ori held the deny across the model's retries. MODE-GATED — read the note below before quoting this row
|
|
320
|
-
// DELIBERATELY NO OTHER ROWS. Two things this table cannot express, both
|
|
321
|
-
// verified live and both load-bearing:
|
|
322
|
-
// 1. ori's approval mode defaults to `self-drive`, and in that mode the
|
|
323
|
-
// DYNAMIC points are never called — so no deny is ever PRODUCED, and
|
|
324
|
-
// the row above never gets a chance to apply. Isolated three ways: no
|
|
325
|
-
// callback under self-drive; still none after claiming approval-policy
|
|
326
|
-
// with defaultAction:"ask"; but defaultAction:"reject" DID block every
|
|
327
|
-
// call, which proves the static point is wired and that self-drive
|
|
328
|
-
// skips specifically the dynamic asker. Coverage therefore requires
|
|
329
|
-
// `--approvals manual` (or /approvals in the TUI).
|
|
330
|
-
// 2. There is no Stop event at all, so the 5 require-*-before-stop
|
|
331
|
-
// builtins are INAPPLICABLE on ori, exactly as on Hermes and Goose.
|
|
332
|
-
},
|
|
333
|
-
|
|
334
|
-
// ── cline ─────────────────────────────────────────────────────────────────
|
|
335
|
-
// cline v3.0.60. Its hooks are event-NAMED scripts under a hooks directory,
|
|
336
|
-
// and THE EXIT CODE IS IGNORED on every event — so every row here is a claim
|
|
337
|
-
// about a stdout SHAPE, never about exit 2. cline's verdict schema has no
|
|
338
|
-
// `decision` / `block` / `permissionDecision` field at all, which is why every
|
|
339
|
-
// generic branch in policy-evaluator.ts is inert for it.
|
|
340
|
-
cline: {
|
|
341
|
-
PreToolUse: "block", // LIVE: {"cancel":true,"errorMessage":…} on stdout stopped a real run_commands call and the side effect never happened. CAVEAT, and it is a product decision rather than a footnote: `cancel` becomes {stop:true} -> applyStopControl THROWS ControlledStopError, ABORTING THE WHOLE RUN ("[abort] aborted by another client"), not just this tool call. A STRONGER action than a per-tool deny, not a weaker one
|
|
342
|
-
Stop: "observe", // TaskComplete maps here, and `cancel` is NOT a force-retry channel: the task has already completed, so a cancel kills a finished run rather than re-entering the loop. The 5 require-*-before-stop builtins are INAPPLICABLE on cline, exactly as on Hermes, Goose and ori
|
|
343
|
-
// DELIBERATELY NO OTHER ROWS. PostToolUse, UserPromptSubmit, TaskStart,
|
|
344
|
-
// TaskResume, TaskCancel, TaskError and SessionShutdown ARE installed — for
|
|
345
|
-
// the audit trail and for observe-mode custom policies — but nothing has
|
|
346
|
-
// been probed showing a verdict on any of them changes what the agent does,
|
|
347
|
-
// and ABSENT MEANS UNKNOWN in this file.
|
|
348
|
-
//
|
|
349
|
-
// FAIL-OPEN across the whole surface, and unlike ori there is no
|
|
350
|
-
// failureBehavior:"deny" to inherit: a timeout (120s default), a parse
|
|
351
|
-
// failure or a spawn error makes cline SKIP the hook and run the tool.
|
|
352
|
-
},
|
|
353
256
|
};
|
|
354
257
|
|
|
355
258
|
/**
|
package/src/hooks/fp-config.ts
CHANGED
|
@@ -325,12 +325,53 @@ export interface FpConfig {
|
|
|
325
325
|
* refresh token expiring must not silently switch off a background feature
|
|
326
326
|
* somebody configured months ago.
|
|
327
327
|
*
|
|
328
|
-
*
|
|
329
|
-
*
|
|
330
|
-
*
|
|
331
|
-
*
|
|
328
|
+
* ON for a configured machine that has never stated an opinion, OFF for
|
|
329
|
+
* every machine we could not read an opinion from — which is not the same
|
|
330
|
+
* default twice, and the difference is the whole design.
|
|
331
|
+
*
|
|
332
|
+
* The scan reads the CONTENTS of every session transcript on disk, so the
|
|
333
|
+
* conservative instinct is to keep it off until asked. But the thing it
|
|
334
|
+
* looks for now is a leaked credential, and a credential nobody scans for
|
|
335
|
+
* is a credential nobody revokes: a switch that is off by default is a
|
|
336
|
+
* feature that, for most users, never runs. Setup is where the machine's
|
|
337
|
+
* owner is told what it does, so completing setup is the opinion.
|
|
338
|
+
*
|
|
339
|
+
* Hence the split. Once `config.json` exists AND carries an `audit` table,
|
|
340
|
+
* this is on unless it says exactly `false`. Absent file, unparseable JSON,
|
|
341
|
+
* or no `audit` table at all still read as OFF, because those are the three
|
|
342
|
+
* ways of not knowing, and "we could not tell" must never turn a transcript
|
|
343
|
+
* scan on. `crates/failproofaid/src/audit_lane.rs` makes the identical
|
|
344
|
+
* distinction over the identical bytes — the daemon and this reader must
|
|
345
|
+
* agree, or the settings page describes a machine that is doing something
|
|
346
|
+
* else.
|
|
347
|
+
*
|
|
348
|
+
* Turning it on sends NOTHING on its own: the digest is gated on
|
|
349
|
+
* `reportsConsentedAt` below, not on this switch, so a machine that flips
|
|
350
|
+
* on at upgrade scans locally and mails nobody.
|
|
332
351
|
*/
|
|
333
352
|
auto: boolean;
|
|
353
|
+
/**
|
|
354
|
+
* Raise a desktop notification when a scan finds a credential.
|
|
355
|
+
*
|
|
356
|
+
* Separate from `auto` because they answer different questions: `auto` is
|
|
357
|
+
* whether the machine scans, this is whether it interrupts. Somebody who
|
|
358
|
+
* wants the scan and not the banner is a coherent person, and without this
|
|
359
|
+
* their only way to stop the banner is to stop the scan.
|
|
360
|
+
*
|
|
361
|
+
* ON by default, and the direction is the point: this fires at most once
|
|
362
|
+
* per distinct credential ever (`leak-notice.ts` claims each finding with
|
|
363
|
+
* an O_EXCL marker), so the volume it can reach is bounded by how many
|
|
364
|
+
* secrets are actually leaking. A silent default would make the common
|
|
365
|
+
* case — a user who never opens the dashboard and never gave an email —
|
|
366
|
+
* a machine that finds the key and tells nobody.
|
|
367
|
+
*
|
|
368
|
+
* It does NOT silence the in-CLI notice. That one appears inside a session
|
|
369
|
+
* the user is already driving, costs two lines, and is the only channel
|
|
370
|
+
* left when there is no desktop to notify — a headless box, a container, an
|
|
371
|
+
* SSH session. Turning off every channel at once is not an option this
|
|
372
|
+
* offers, because the resulting state is indistinguishable from broken.
|
|
373
|
+
*/
|
|
374
|
+
notify: boolean;
|
|
334
375
|
/**
|
|
335
376
|
* When this machine's owner agreed that a scheduled scan may send what it
|
|
336
377
|
* finds off the box, as epoch ms. Absent means they never did.
|
|
@@ -402,7 +443,9 @@ export const DEFAULT_CONFIG: FpConfig = {
|
|
|
402
443
|
environment: "local",
|
|
403
444
|
},
|
|
404
445
|
telemetry: { enabled: true },
|
|
405
|
-
|
|
446
|
+
// `auto: false` is the no-file case specifically — see the field's doc for
|
|
447
|
+
// why a machine that HAS a config reads the other way.
|
|
448
|
+
audit: { auto: false, notify: true, intervalDays: DEFAULT_AUDIT_INTERVAL_DAYS },
|
|
406
449
|
};
|
|
407
450
|
|
|
408
451
|
/**
|
|
@@ -495,7 +538,11 @@ export function projectConfig(parsed: Record<string, unknown>): FpConfig {
|
|
|
495
538
|
const daemon = (parsed.daemon ?? {}) as Record<string, unknown>;
|
|
496
539
|
const collector = (parsed.collector ?? {}) as Record<string, unknown>;
|
|
497
540
|
const telemetry = (parsed.telemetry ?? {}) as Record<string, unknown>;
|
|
498
|
-
|
|
541
|
+
// Presence, not just contents: `audit.auto` reads differently when the
|
|
542
|
+
// table is absent than when it is present-but-silent. See the field's doc.
|
|
543
|
+
const auditPresent =
|
|
544
|
+
!!parsed.audit && typeof parsed.audit === "object" && !Array.isArray(parsed.audit);
|
|
545
|
+
const audit = (auditPresent ? parsed.audit : {}) as Record<string, unknown>;
|
|
499
546
|
return {
|
|
500
547
|
// Anything unrecognised reads as `oss`. The failure direction matters: a
|
|
501
548
|
// corrupt config must not be able to turn cloud reporting ON.
|
|
@@ -525,12 +572,20 @@ export function projectConfig(parsed: Record<string, unknown>): FpConfig {
|
|
|
525
572
|
// reads as on — the shipped default, and what a config with no
|
|
526
573
|
// [telemetry] block at all means.
|
|
527
574
|
telemetry: { enabled: telemetry.enabled !== false },
|
|
528
|
-
//
|
|
529
|
-
//
|
|
530
|
-
//
|
|
531
|
-
//
|
|
575
|
+
// A configured machine with no stated opinion scans; a machine we could
|
|
576
|
+
// not read an opinion FROM does not. `auditPresent` is what separates
|
|
577
|
+
// them, and it is the reason this is not simply `!== false`: with no
|
|
578
|
+
// `audit` table there is no opinion to have been stated, only an absence,
|
|
579
|
+
// and an absence must not start reading every transcript on disk. Same
|
|
580
|
+
// three-way split as `audit_lane.rs`, over the same file.
|
|
532
581
|
audit: {
|
|
533
|
-
auto: audit.auto
|
|
582
|
+
auto: auditPresent && audit.auto !== false,
|
|
583
|
+
// Default ON, off only on an explicit `false` — a notification the user
|
|
584
|
+
// never sees is the same as no detection at all. Ungated on
|
|
585
|
+
// `auditPresent`, unlike `auto`: this is read at the moment a
|
|
586
|
+
// notification is about to fire, which means a scan already ran and the
|
|
587
|
+
// question is only whether to speak.
|
|
588
|
+
notify: audit.notify !== false,
|
|
534
589
|
intervalDays: readIntervalDays(audit.interval_days),
|
|
535
590
|
// A finite number or nothing. A garbage value reads as absent, which is
|
|
536
591
|
// the direction that sends nothing.
|
|
@@ -584,6 +639,7 @@ const OWNED_CONFIG_KEYS: readonly (readonly string[])[] = [
|
|
|
584
639
|
["collector", "sources", "*", "extra_paths"],
|
|
585
640
|
["telemetry", "enabled"],
|
|
586
641
|
["audit", "auto"],
|
|
642
|
+
["audit", "notify"],
|
|
587
643
|
["audit", "interval_days"],
|
|
588
644
|
["audit", "reports_consented_at"],
|
|
589
645
|
];
|
|
@@ -688,6 +744,10 @@ export function writeConfig(config: FpConfig, raw?: Record<string, unknown>): vo
|
|
|
688
744
|
// total rather than conditional.
|
|
689
745
|
audit: {
|
|
690
746
|
auto: config.audit.auto,
|
|
747
|
+
// Emitted unconditionally, like `auto`. A switch that only appears in the
|
|
748
|
+
// file once you have used it is a switch nobody discovers, and this one
|
|
749
|
+
// exists to be found by somebody the banner annoyed.
|
|
750
|
+
notify: config.audit.notify,
|
|
691
751
|
interval_days: config.audit.intervalDays,
|
|
692
752
|
// Written only once there IS consent, so an untouched machine's config
|
|
693
753
|
// does not grow a key implying it was asked. It is in
|
package/src/hooks/fp-home.ts
CHANGED
|
@@ -299,6 +299,31 @@ export const auditSessionFile = (home?: string) => resolve(auditDir(home), "sess
|
|
|
299
299
|
*/
|
|
300
300
|
export const auditMachineFile = (home?: string) => resolve(auditDir(home), "machine.json");
|
|
301
301
|
|
|
302
|
+
/**
|
|
303
|
+
* The leak record: one entry per DISTINCT credential seen in a transcript.
|
|
304
|
+
*
|
|
305
|
+
* `derived` — every finding in it is reproducible by rescanning, so a reset
|
|
306
|
+
* costs a rescan and nothing else. What is NOT reproducible lives next door in
|
|
307
|
+
* `auditLeakIdentityFile`, because the two have opposite reset semantics and
|
|
308
|
+
* this directory's own header says to classify the children, never the parent.
|
|
309
|
+
*/
|
|
310
|
+
export const auditLeaksFile = (home?: string) => resolve(auditDir(home), "leaks.json");
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* The two things about a leak that a rescan cannot recreate: the HMAC salt that
|
|
314
|
+
* gives each finding its stable id, and the ids the user has dismissed.
|
|
315
|
+
*
|
|
316
|
+
* `identity`, and both halves for the same reason. Regenerate the salt and every
|
|
317
|
+
* id changes, so every previously-seen credential reads as brand new and the
|
|
318
|
+
* machine re-alerts for all of them at once. Drop the dismissals and the
|
|
319
|
+
* findings a human already judged not-a-secret come back — which is worse than
|
|
320
|
+
* a missed alert, because it teaches them the tool does not listen.
|
|
321
|
+
*
|
|
322
|
+
* Small by construction: a 32-byte salt and a list of 16-char ids.
|
|
323
|
+
*/
|
|
324
|
+
export const auditLeakIdentityFile = (home?: string) =>
|
|
325
|
+
resolve(auditDir(home), "leak-identity.json");
|
|
326
|
+
|
|
302
327
|
|
|
303
328
|
// ── fp-cloud-cli ───────────────────────────────────────────────────────────────────
|
|
304
329
|
|
|
@@ -628,6 +653,7 @@ export const HOME_CLASSES: readonly { path: (home?: string) => string; class: Da
|
|
|
628
653
|
// history the user was already told about. Kept OUT of `auditSessionFile`
|
|
629
654
|
// precisely so both survive a sign-out.
|
|
630
655
|
{ path: auditMachineFile, class: "identity" },
|
|
656
|
+
{ path: auditLeakIdentityFile, class: "identity" },
|
|
631
657
|
|
|
632
658
|
// ── May be dropped: rebuilt on demand ──
|
|
633
659
|
// NOTE: `auditDir` itself is deliberately absent. Layout 4 made it MIXED — it
|
|
@@ -637,6 +663,7 @@ export const HOME_CLASSES: readonly { path: (home?: string) => string; class: Da
|
|
|
637
663
|
// the delete list.
|
|
638
664
|
{ path: auditDashboardFile, class: "derived" },
|
|
639
665
|
{ path: auditCacheDir, class: "derived" },
|
|
666
|
+
{ path: auditLeaksFile, class: "derived" },
|
|
640
667
|
{ path: auditScheduleFile, class: "derived" },
|
|
641
668
|
{ path: collectorHealthFile, class: "derived" },
|
|
642
669
|
{ path: codexSessionPathsFile, class: "derived" },
|
package/src/hooks/handler.ts
CHANGED
|
@@ -15,7 +15,6 @@ import type {
|
|
|
15
15
|
HermesHookEventType,
|
|
16
16
|
OpenClawHookEventType,
|
|
17
17
|
AntigravityHookEventType,
|
|
18
|
-
QwenHookEventType,
|
|
19
18
|
} from "./types";
|
|
20
19
|
import {
|
|
21
20
|
CODEX_EVENT_MAP,
|
|
@@ -24,12 +23,9 @@ import {
|
|
|
24
23
|
HERMES_EVENT_MAP,
|
|
25
24
|
OPENCLAW_EVENT_MAP,
|
|
26
25
|
ANTIGRAVITY_EVENT_MAP,
|
|
27
|
-
QWEN_EVENT_MAP,
|
|
28
26
|
} from "./types";
|
|
29
27
|
import { canonicalizeToolName, canonicalizeToolInput } from "./tool-name-canonicalize";
|
|
30
|
-
import {
|
|
31
|
-
import { evaluateExpandedBatch } from "./batch-fanout";
|
|
32
|
-
import { normalizeCliPayload, resolveEffectiveCli } from "./normalize-cli-payload";
|
|
28
|
+
import { normalizeCliPayload } from "./normalize-cli-payload";
|
|
33
29
|
import type { PolicyFunction, PolicyResult, HooksConfig } from "./policy-types";
|
|
34
30
|
import { readMergedHooksConfig } from "./hooks-config";
|
|
35
31
|
import { registerBuiltinPolicies } from "./builtin-policies";
|
|
@@ -39,6 +35,8 @@ import { loadAllCustomHooks } from "./custom-hooks-loader";
|
|
|
39
35
|
import type { CustomHook } from "./policy-types";
|
|
40
36
|
import { persistHookActivity } from "./hook-activity-store";
|
|
41
37
|
import { deliveryHealth, deliveryHealthLine } from "./delivery-health";
|
|
38
|
+
import { shapeNotice, canDeliverNotice, leakNoticeText } from "./notice";
|
|
39
|
+
import { pendingLeakNotice, markLeakNoticeDelivered } from "../audit/leak-notice";
|
|
42
40
|
import { trackHookEvent, flushHookTelemetry } from "./hook-telemetry";
|
|
43
41
|
import { resolveCwd } from "./resolve-cwd";
|
|
44
42
|
import { resolvePermissionMode } from "./resolve-permission-mode";
|
|
@@ -97,13 +95,6 @@ export function canonicalizeEventType(raw: string, cli: IntegrationType): HookEv
|
|
|
97
95
|
const mapped = ANTIGRAVITY_EVENT_MAP[raw as AntigravityHookEventType];
|
|
98
96
|
if (mapped) return mapped;
|
|
99
97
|
}
|
|
100
|
-
if (cli === "qwen") {
|
|
101
|
-
// Seventeen of qwen's nineteen events are already canonical; this maps the
|
|
102
|
-
// two that are not — TodoCreated/TodoCompleted, which are qwen's spelling
|
|
103
|
-
// of TaskCreated/TaskCompleted. Verified live against qwen-code 0.21.12.
|
|
104
|
-
const mapped = QWEN_EVENT_MAP[raw as QwenHookEventType];
|
|
105
|
-
if (mapped) return mapped;
|
|
106
|
-
}
|
|
107
98
|
// claude / copilot / unknown — already PascalCase, pass through.
|
|
108
99
|
// HOOK_EVENT_TYPES type-checks downstream.
|
|
109
100
|
return raw as HookEventType;
|
|
@@ -115,6 +106,48 @@ export interface HookEventOutcome {
|
|
|
115
106
|
stderr: string;
|
|
116
107
|
}
|
|
117
108
|
|
|
109
|
+
/**
|
|
110
|
+
* Merge a pending leak notice into an outcome, once.
|
|
111
|
+
*
|
|
112
|
+
* Anchored on Stop wherever the host renders there, and SessionStart only where
|
|
113
|
+
* it does not: Stop fires once per turn, AFTER the answer, so it never
|
|
114
|
+
* interrupts what the user was reading — and at session open the background
|
|
115
|
+
* scan may not have produced the finding yet.
|
|
116
|
+
*
|
|
117
|
+
* Every failure is swallowed. A courtesy message must never be the reason a
|
|
118
|
+
* hook misbehaves, and the finding is on disk either way.
|
|
119
|
+
*/
|
|
120
|
+
function attachLeakNotice(
|
|
121
|
+
eventType: string,
|
|
122
|
+
cli: IntegrationType | undefined,
|
|
123
|
+
outcome: HookEventOutcome,
|
|
124
|
+
): HookEventOutcome {
|
|
125
|
+
try {
|
|
126
|
+
if (!canDeliverNotice(cli)) return outcome;
|
|
127
|
+
// canDeliverNotice() above already proved cli is set; narrow for the call.
|
|
128
|
+
if (!cli) return outcome;
|
|
129
|
+
const canonical = canonicalizeEventType(eventType, cli);
|
|
130
|
+
if (canonical !== "Stop" && canonical !== "SessionStart") return outcome;
|
|
131
|
+
|
|
132
|
+
const pending = pendingLeakNotice();
|
|
133
|
+
if (pending.count === 0) return outcome;
|
|
134
|
+
|
|
135
|
+
const shaped = shapeNotice(cli, leakNoticeText(pending.count), outcome.stdout);
|
|
136
|
+
if (shaped.stdout === outcome.stdout && !shaped.stderr) return outcome;
|
|
137
|
+
|
|
138
|
+
// Claimed only once the notice is actually on the stream, so a crash
|
|
139
|
+
// between the two re-notifies rather than silently swallowing the alert.
|
|
140
|
+
markLeakNoticeDelivered(pending.ids);
|
|
141
|
+
return {
|
|
142
|
+
exitCode: outcome.exitCode,
|
|
143
|
+
stdout: shaped.stdout,
|
|
144
|
+
stderr: outcome.stderr + shaped.stderr,
|
|
145
|
+
};
|
|
146
|
+
} catch {
|
|
147
|
+
return outcome;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
118
151
|
export interface EvaluateHookEventOptions {
|
|
119
152
|
/**
|
|
120
153
|
* Await pending telemetry POSTs before returning. Defaults to `true`,
|
|
@@ -192,7 +225,7 @@ async function runObserved(
|
|
|
192
225
|
*/
|
|
193
226
|
export async function evaluateHookEvent(
|
|
194
227
|
eventType: string,
|
|
195
|
-
|
|
228
|
+
cli: IntegrationType = "claude",
|
|
196
229
|
stdinPayload: string,
|
|
197
230
|
opts?: EvaluateHookEventOptions,
|
|
198
231
|
): Promise<HookEventOutcome> {
|
|
@@ -213,28 +246,12 @@ export async function evaluateHookEvent(
|
|
|
213
246
|
hookLogWarn(`payload parse failed for ${eventType} (${stdinPayload.length} bytes)`);
|
|
214
247
|
void trackHookEvent(getInstanceId(), "hook_payload_parse_error", {
|
|
215
248
|
event_type: eventType,
|
|
216
|
-
cli
|
|
249
|
+
cli,
|
|
217
250
|
payload_size: stdinPayload.length,
|
|
218
251
|
});
|
|
219
252
|
}
|
|
220
253
|
}
|
|
221
254
|
|
|
222
|
-
// grok executes other CLIs' hook configs — including the
|
|
223
|
-
// `<cwd>/.claude/settings.json` our own claude install writes — passing
|
|
224
|
-
// `--cli claude` while piping ITS camelCase payload. Resolve the CLI whose
|
|
225
|
-
// contract actually governs this event BEFORE anything reads `cli`, so both
|
|
226
|
-
// halves land on grok's path: the tool maps (without which every builtin
|
|
227
|
-
// reads undefined) and the response shape (grok ignores Claude's
|
|
228
|
-
// hookSpecificOutput deny — verified by A/B against a live session).
|
|
229
|
-
// No-op for every other CLI. See normalize-cli-payload.ts:isGrokEnvelope.
|
|
230
|
-
const cli = resolveEffectiveCli(declaredCli, parsed);
|
|
231
|
-
if (cli !== declaredCli) {
|
|
232
|
-
hookLogWarn(
|
|
233
|
-
`payload for ${eventType} is a ${cli} envelope but --cli says ${declaredCli}; ` +
|
|
234
|
-
`evaluating with the ${cli} contract`,
|
|
235
|
-
);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
255
|
normalizeCliPayload(cli, parsed);
|
|
239
256
|
|
|
240
257
|
// Canonicalize event name (Codex sends snake_case; internals expect PascalCase)
|
|
@@ -256,11 +273,6 @@ export async function evaluateHookEvent(
|
|
|
256
273
|
// passes are idempotent because the camelCase keys won't match a
|
|
257
274
|
// snake_case input.
|
|
258
275
|
const rawInput = parsed.tool_input;
|
|
259
|
-
// Read the RAW batch BEFORE canonicalization collapses it. The expander
|
|
260
|
-
// emits canonical scalars directly, so there is no ambiguity about whether
|
|
261
|
-
// canonicalization ran once, twice, or on the wrong shape. Returns null for
|
|
262
|
-
// every CLI except cline, so the other 15 take the unchanged path below.
|
|
263
|
-
const batchExpansion = expandBatchToolInput(cli, canonicalToolName, rawInput);
|
|
264
276
|
const canonicalInput = canonicalizeToolInput(canonicalToolName, rawInput, cli);
|
|
265
277
|
if (canonicalInput !== rawInput) {
|
|
266
278
|
parsed.tool_input = canonicalInput;
|
|
@@ -677,32 +689,8 @@ export async function evaluateHookEvent(
|
|
|
677
689
|
);
|
|
678
690
|
}
|
|
679
691
|
|
|
680
|
-
//
|
|
681
|
-
|
|
682
|
-
// one per element. A null set (every non-batch call) changes nothing.
|
|
683
|
-
const telemetryDedupe = new Set<string>();
|
|
684
|
-
|
|
685
|
-
// Evaluate policies (use canonical PascalCase event type).
|
|
686
|
-
//
|
|
687
|
-
// Fan out only on PreToolUse: it is the only cline payload shape captured
|
|
688
|
-
// live, and the only event where a per-element verdict changes anything.
|
|
689
|
-
// forceDecision is excluded — its synthetic policy denies unconditionally,
|
|
690
|
-
// so N elements would produce N identical denies and a meaningless locator.
|
|
691
|
-
const result =
|
|
692
|
-
batchExpansion && canonicalEventType === "PreToolUse" && !opts?.forceDecision
|
|
693
|
-
? await evaluateExpandedBatch(
|
|
694
|
-
canonicalEventType,
|
|
695
|
-
parsed,
|
|
696
|
-
session,
|
|
697
|
-
config,
|
|
698
|
-
batchExpansion,
|
|
699
|
-
telemetryDedupe,
|
|
700
|
-
)
|
|
701
|
-
// Deliberately NOT passed the dedupe set: it exists only to collapse
|
|
702
|
-
// per-element telemetry inside a fan-out, and a single-shot call has
|
|
703
|
-
// exactly one element. Passing it would change this call's shape for
|
|
704
|
-
// all 16 CLIs to no effect.
|
|
705
|
-
: await evaluatePolicies(canonicalEventType, parsed, session, config);
|
|
692
|
+
// Evaluate policies (use canonical PascalCase event type)
|
|
693
|
+
const result = await evaluatePolicies(canonicalEventType, parsed, session, config);
|
|
706
694
|
const durationMs = Math.round(performance.now() - startTime);
|
|
707
695
|
hookLogInfo(`result=${result.decision} policy=${result.policyName ?? "none"} duration=${durationMs}ms`);
|
|
708
696
|
|
|
@@ -813,7 +801,21 @@ export async function evaluateHookEvent(
|
|
|
813
801
|
}
|
|
814
802
|
}
|
|
815
803
|
|
|
816
|
-
|
|
804
|
+
// Attach a pending leak notice, if this event is one that can carry it.
|
|
805
|
+
//
|
|
806
|
+
// Emitted HERE rather than in `handleHookEvent`, which is what fixes the
|
|
807
|
+
// bug this replaced: `bin/failproofai.mjs`'s `isDaemonConfigured()` branch
|
|
808
|
+
// writes `result.stdout`/`result.stderr` and EXITS INSIDE THAT BRANCH — it
|
|
809
|
+
// never falls through to `handleHookEvent`. Setup requires the daemon, so
|
|
810
|
+
// the previous notice was dead code on every configured machine, and had
|
|
811
|
+
// been since it shipped. Both paths return this object, so producing the
|
|
812
|
+
// notice here reaches both with no change to either caller.
|
|
813
|
+
const withNotice = attachLeakNotice(eventType, cli, {
|
|
814
|
+
exitCode: result.exitCode,
|
|
815
|
+
stdout: result.stdout,
|
|
816
|
+
stderr: result.stderr,
|
|
817
|
+
});
|
|
818
|
+
return withNotice;
|
|
817
819
|
} finally {
|
|
818
820
|
if (opts?.awaitTelemetryFlush ?? true) {
|
|
819
821
|
// Await any un-awaited (`void trackHookEvent(...)`) events fired during
|
package/src/hooks/harness-cli.ts
CHANGED
|
@@ -60,7 +60,7 @@ import {
|
|
|
60
60
|
* the two lists are identical by reading the Rust source, because nothing else
|
|
61
61
|
* connects them.
|
|
62
62
|
*
|
|
63
|
-
*
|
|
63
|
+
* Twelve keys, thirteen sources: `claude` covers both the main and the subagent
|
|
64
64
|
* transcript formats, which share a root — an extra path holding Claude
|
|
65
65
|
* transcripts holds their subagents too.
|
|
66
66
|
*/
|
|
@@ -77,8 +77,6 @@ export const HARNESS_KEYS = [
|
|
|
77
77
|
"opencode",
|
|
78
78
|
"devin",
|
|
79
79
|
"hermes",
|
|
80
|
-
"grok",
|
|
81
|
-
"qwen",
|
|
82
80
|
] as const;
|
|
83
81
|
|
|
84
82
|
export type HarnessKey = (typeof HARNESS_KEYS)[number];
|