failproofai 1.0.4-beta.1 → 1.0.4-beta.4
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]__0v1hz15._.js} +1 -1
- package/.next/standalone/.next/server/chunks/{[externals]__0_gzk8q._.js → [externals]__1nb206a._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0--lkk6._.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]__0q0qzx1._.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/_09dz7xv._.js +1 -1
- package/.next/standalone/.next/server/chunks/_0otft92._.js +3 -0
- 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/_1vslkoe._.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_factory-projects_ts_1eo_bk-._.js +1 -1
- 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]__0s740oi._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__16gamwq._.js → [root-of-the-server]__0yl9wb5._.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-yi74u._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_1byck5v._.js → _0guxnh-._.js} +2 -2
- 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/_1_ozgif._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1es2j7i._.js +61 -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/04o5zfl7p5lhx.js +1 -0
- package/.next/standalone/.next/static/chunks/{29-iqnp9v_axt.css → 0jpd8dv2vk930.css} +1 -1
- package/.next/standalone/.next/static/chunks/{1po3lirgk2riw.js → 0po8n4tyotjf2.js} +1 -1
- package/.next/standalone/.next/static/chunks/0qmsunv04x4na.css +2 -0
- package/.next/standalone/.next/static/chunks/0qx7gav1c2_vu.js +1 -0
- package/.next/standalone/.next/static/chunks/17tfki9x5rd_7.js +1 -0
- package/.next/standalone/.next/static/chunks/{1_bxotmf9qngs.js → 1i-i0pxg9q_8y.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1_2jsatfuthrw.js → 2bxr2_2h4kaqq.js} +1 -1
- package/.next/standalone/.next/static/chunks/{01hvwwrhvbzyh.js → 2zafgs90r_leq.js} +1 -1
- package/.next/standalone/.next/static/chunks/{00gv0v91mycm-.js → 323cyb3a2s06y.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0qn9khv0oo9ex.js → 364cqbpeg1y5i.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2bvwsyu6x__ia.js → 38dqur43qthrl.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3_--3ibia6ba1.js → 3l36d73kj18zw.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/42gwu2bsz4ske.js +6 -0
- 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 +109 -0
- package/.next/standalone/app/audit/_components/audit-dashboard.tsx +140 -34
- 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 +192 -0
- package/.next/standalone/app/audit/_components/share-templates.ts +110 -44
- package/.next/standalone/app/audit/audit-styles.css +132 -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/lib/sqlite-reader.ts +78 -1
- 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 +8 -25
- package/dist/cli.mjs +20091 -25864
- package/dist/worker.mjs +4881 -6361
- 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/lib/sqlite-reader.ts +78 -1
- package/package.json +15 -14
- package/pi-extension/index.ts +61 -8
- package/scripts/changelog-open.py +115 -0
- package/src/audit/cache.ts +6 -0
- package/src/audit/cli-adapters/claude.ts +13 -0
- package/src/audit/cli-adapters/index.ts +0 -24
- package/src/audit/cli-login.ts +4 -1
- package/src/audit/cli.ts +164 -5
- package/src/audit/desktop-notify.ts +420 -0
- package/src/audit/harm-report.ts +101 -0
- package/src/audit/index.ts +284 -15
- package/src/audit/leak-fingerprint.ts +200 -0
- package/src/audit/leak-notice.ts +259 -0
- package/src/audit/leak-record.ts +232 -0
- package/src/audit/leak-scan.ts +268 -0
- package/src/audit/leak-store.ts +265 -0
- package/src/audit/macos-notifier.ts +310 -0
- package/src/audit/redact-example.ts +322 -15
- 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 +47 -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 +84 -62
- package/src/hooks/harness-cli.ts +1 -3
- package/src/hooks/integrations.ts +63 -769
- package/src/hooks/normalize-cli-payload.ts +0 -126
- package/src/hooks/notice.ts +191 -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]__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/_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 → P6icI2V2PvVuePJrqjHCr}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{qJx3mbOQV-5ElrcRb4Sp7 → P6icI2V2PvVuePJrqjHCr}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{qJx3mbOQV-5ElrcRb4Sp7 → P6icI2V2PvVuePJrqjHCr}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Open a stub CHANGELOG section for the version a release just moved to.
|
|
3
|
+
|
|
4
|
+
changelog-open.py <changelog> <version> <published> [<date>]
|
|
5
|
+
|
|
6
|
+
The companion to `changelog-section.py`, which REFUSES a release whose version has
|
|
7
|
+
no section. Both PyPI publish workflows call this from `bump`, in the same commit
|
|
8
|
+
that moves `_version.py`, so the two files can never disagree about which version
|
|
9
|
+
`main` is open on.
|
|
10
|
+
|
|
11
|
+
WHY THIS EXISTS AS AUTOMATION RATHER THAN A RULE. `bump` used to move the version
|
|
12
|
+
alone, which left `main` on a version with no section — the exact state
|
|
13
|
+
`changelog-section.py` refuses at release time and that
|
|
14
|
+
`__tests__/ci/python-version-pipeline.test.ts` asserts against. Bump commits carry
|
|
15
|
+
a skip-ci marker, so nothing went red until the next unrelated PR ran CI and came
|
|
16
|
+
back failing for a reason that had nothing to do with it. That happened at the
|
|
17
|
+
first publish (repaired by hand in #755, which named the recurrence and left it),
|
|
18
|
+
and again at the second. It is not a rule a maintainer can be asked to remember:
|
|
19
|
+
the commit that creates the state is written by a workflow, not a person.
|
|
20
|
+
|
|
21
|
+
IDEMPOTENT ON PURPOSE. A re-run of `bump` against a `main` that already carries
|
|
22
|
+
the section must be a no-op, not a second heading — two `## <version>` headings
|
|
23
|
+
would put only the first one's body in the GitHub Release, silently. The version
|
|
24
|
+
is matched with the same trailing word boundary the extractor uses, so `0.0.1b1`
|
|
25
|
+
is not answered by `0.0.1b10`.
|
|
26
|
+
|
|
27
|
+
The stub is deliberately non-empty prose, not a bare heading: an empty section
|
|
28
|
+
fails `changelog-section.py` just as a missing one does, so a stub that could not
|
|
29
|
+
release would only move the failure.
|
|
30
|
+
|
|
31
|
+
Stdlib only, same reason as its two siblings: it runs in a job that installs
|
|
32
|
+
nothing precisely because it is handed a token.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
from __future__ import annotations
|
|
36
|
+
|
|
37
|
+
import re
|
|
38
|
+
import sys
|
|
39
|
+
from datetime import datetime, timezone
|
|
40
|
+
|
|
41
|
+
# The heading grammar CLAUDE.md fixes for the root changelog and both per-package
|
|
42
|
+
# files follow — an EM DASH, and a date. Matched with `\b` after the version so a
|
|
43
|
+
# request to open `0.0.1b1` is not satisfied by an existing `0.0.1b10`.
|
|
44
|
+
_HEADING = "## {version} — {date}"
|
|
45
|
+
|
|
46
|
+
#: What lands under the new heading. Non-empty by construction (see the module
|
|
47
|
+
#: docstring), and it says who wrote it so the next reader does not go looking for
|
|
48
|
+
#: the human who did not.
|
|
49
|
+
_STUB = """Open for the next release. `{published}` published on {date} and the `bump` job
|
|
50
|
+
moved the version here automatically; nothing has landed against `{version}` yet.
|
|
51
|
+
Add entries as changes merge — this section becomes the GitHub Release body when
|
|
52
|
+
it ships.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class ChangelogError(Exception):
|
|
57
|
+
"""A message a maintainer can act on, printed without a traceback."""
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def has_section(text: str, version: str) -> bool:
|
|
61
|
+
return re.search(rf"^##[ \t]+{re.escape(version)}\b", text, re.MULTILINE) is not None
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def open_section(text: str, version: str, published: str, date: str) -> str:
|
|
65
|
+
"""`text` with a stub section for `version` above the topmost existing one."""
|
|
66
|
+
if has_section(text, version):
|
|
67
|
+
return text
|
|
68
|
+
|
|
69
|
+
block = _HEADING.format(version=version, date=date) + "\n\n" + _STUB.format(
|
|
70
|
+
published=published, version=version, date=date
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
# Above the newest existing section, never at the end: the file is
|
|
74
|
+
# reverse-chronological, and its preamble (title, and the grammar note the
|
|
75
|
+
# extractor depends on) sits above the first heading and must stay there.
|
|
76
|
+
first = re.search(r"^##[ \t]", text, re.MULTILINE)
|
|
77
|
+
if first:
|
|
78
|
+
return text[: first.start()] + block + "\n" + text[first.start() :]
|
|
79
|
+
|
|
80
|
+
# No sections yet — the first release off a fresh file. Keep the preamble and
|
|
81
|
+
# append, with exactly one blank line between.
|
|
82
|
+
return text.rstrip("\n") + "\n\n" + block
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def main(argv: list[str]) -> int:
|
|
86
|
+
if len(argv) < 4:
|
|
87
|
+
print(__doc__ or "", file=sys.stderr)
|
|
88
|
+
return 2
|
|
89
|
+
path, version, published = argv[1], argv[2], argv[3]
|
|
90
|
+
date = argv[4] if len(argv) >= 5 else datetime.now(timezone.utc).strftime("%Y-%m-%d")
|
|
91
|
+
if not re.fullmatch(r"\d{4}-\d{2}-\d{2}", date):
|
|
92
|
+
raise ChangelogError(f"'{date}' is not a YYYY-MM-DD date")
|
|
93
|
+
|
|
94
|
+
try:
|
|
95
|
+
with open(path, encoding="utf-8") as handle:
|
|
96
|
+
text = handle.read()
|
|
97
|
+
except OSError as exc:
|
|
98
|
+
raise ChangelogError(f"cannot read {path}: {exc}") from exc
|
|
99
|
+
|
|
100
|
+
if has_section(text, version):
|
|
101
|
+
print(f"{path} already has a '## {version}' section — nothing to open.")
|
|
102
|
+
return 0
|
|
103
|
+
|
|
104
|
+
with open(path, "w", encoding="utf-8") as handle:
|
|
105
|
+
handle.write(open_section(text, version, published, date))
|
|
106
|
+
print(f"opened '## {version} — {date}' in {path}")
|
|
107
|
+
return 0
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
if __name__ == "__main__":
|
|
111
|
+
try:
|
|
112
|
+
raise SystemExit(main(sys.argv))
|
|
113
|
+
except ChangelogError as error:
|
|
114
|
+
print(f"error: {error}", file=sys.stderr)
|
|
115
|
+
raise SystemExit(1) from None
|
package/src/audit/cache.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync, chmodSync, openSync
|
|
|
14
14
|
import { join } from "node:path";
|
|
15
15
|
import { BUILTIN_POLICIES } from "../hooks/builtin-policies";
|
|
16
16
|
import { AUDIT_DETECTORS } from "./detectors";
|
|
17
|
+
import { LEAK_SCAN_VERSION } from "./leak-scan";
|
|
17
18
|
import type { TranscriptAuditResult, DetectorSessionState } from "./types";
|
|
18
19
|
import { auditCacheDir } from "../hooks/fp-home";
|
|
19
20
|
|
|
@@ -162,6 +163,9 @@ interface CacheEntry {
|
|
|
162
163
|
sizeBytes: number;
|
|
163
164
|
engineVersion: string;
|
|
164
165
|
detectorVersion: string;
|
|
166
|
+
/** Separate because leak detection is neither a builtin policy nor one of
|
|
167
|
+
* the behavioural AUDIT_DETECTORS hashed above. */
|
|
168
|
+
leakScanVersion: number;
|
|
165
169
|
result: TranscriptAuditResult;
|
|
166
170
|
/**
|
|
167
171
|
* How far into the file the recorded result actually accounts for, at a LINE
|
|
@@ -269,6 +273,7 @@ export function readCachedTranscript(
|
|
|
269
273
|
if (entry.schemaVersion !== CACHE_SCHEMA_VERSION) return null;
|
|
270
274
|
if (entry.engineVersion !== getEngineVersion()) return null;
|
|
271
275
|
if (entry.detectorVersion !== getDetectorVersion()) return null;
|
|
276
|
+
if (entry.leakScanVersion !== LEAK_SCAN_VERSION) return null;
|
|
272
277
|
// Number.isFinite (not typeof) so a malformed JSON `Infinity` /
|
|
273
278
|
// `NaN` is rejected — those would otherwise bypass the TTL check
|
|
274
279
|
// and pin a stale entry as valid forever.
|
|
@@ -332,6 +337,7 @@ export function writeCachedTranscriptResult(
|
|
|
332
337
|
sizeBytes,
|
|
333
338
|
engineVersion: getEngineVersion(),
|
|
334
339
|
detectorVersion: getDetectorVersion(),
|
|
340
|
+
leakScanVersion: LEAK_SCAN_VERSION,
|
|
335
341
|
result,
|
|
336
342
|
};
|
|
337
343
|
if (resume && resume.bytesScanned > 0) {
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* parses each via lib/log-entries.ts.
|
|
7
7
|
*/
|
|
8
8
|
import { readFile, open, type FileHandle } from "node:fs/promises";
|
|
9
|
+
import { readFileSync } from "node:fs";
|
|
9
10
|
import {
|
|
10
11
|
listClaudeProjects,
|
|
11
12
|
listClaudeTranscripts,
|
|
@@ -40,6 +41,17 @@ export async function listClaudeTranscriptMetadata(
|
|
|
40
41
|
}
|
|
41
42
|
for (const t of transcripts) {
|
|
42
43
|
if (t.mtimeMs < sinceMs) continue;
|
|
44
|
+
let sessionDescription: string | undefined;
|
|
45
|
+
if (t.isSubagent) {
|
|
46
|
+
try {
|
|
47
|
+
const meta = JSON.parse(
|
|
48
|
+
readFileSync(t.transcriptPath.replace(/\.jsonl$/, ".meta.json"), "utf8"),
|
|
49
|
+
) as { description?: unknown };
|
|
50
|
+
if (typeof meta.description === "string") sessionDescription = meta.description;
|
|
51
|
+
} catch {
|
|
52
|
+
// Sidecars are optional (journal transcripts do not have one).
|
|
53
|
+
}
|
|
54
|
+
}
|
|
43
55
|
out.push({
|
|
44
56
|
cli: "claude",
|
|
45
57
|
projectName: project.name,
|
|
@@ -47,6 +59,7 @@ export async function listClaudeTranscriptMetadata(
|
|
|
47
59
|
transcriptPath: t.transcriptPath,
|
|
48
60
|
mtimeMs: t.mtimeMs,
|
|
49
61
|
sizeBytes: t.sizeBytes,
|
|
62
|
+
sessionDescription,
|
|
50
63
|
});
|
|
51
64
|
}
|
|
52
65
|
}
|
|
@@ -23,10 +23,6 @@ import { listFactoryTranscriptMetadata, streamFactoryEvents } from "./factory";
|
|
|
23
23
|
import { listAntigravityTranscriptMetadata, streamAntigravityEvents } from "./antigravity";
|
|
24
24
|
import { listDevinTranscriptMetadata, streamDevinEvents } from "./devin";
|
|
25
25
|
import { listGooseTranscriptMetadata, streamGooseEvents } from "./goose";
|
|
26
|
-
import { listGrokTranscriptMetadata, streamGrokEvents } from "./grok";
|
|
27
|
-
import { listQwenTranscriptMetadata, streamQwenEvents } from "./qwen";
|
|
28
|
-
import { listOriTranscriptMetadata, streamOriEvents } from "./ori";
|
|
29
|
-
import { listClineTranscriptMetadata, streamClineEvents } from "./cline";
|
|
30
26
|
|
|
31
27
|
export type { ListOpts };
|
|
32
28
|
|
|
@@ -127,26 +123,6 @@ export const ADAPTERS: Record<IntegrationType, CliAdapter> = {
|
|
|
127
123
|
listTranscripts: listGooseTranscriptMetadata,
|
|
128
124
|
streamEvents: streamGooseEvents,
|
|
129
125
|
},
|
|
130
|
-
grok: {
|
|
131
|
-
cli: "grok",
|
|
132
|
-
listTranscripts: listGrokTranscriptMetadata,
|
|
133
|
-
streamEvents: streamGrokEvents,
|
|
134
|
-
},
|
|
135
|
-
qwen: {
|
|
136
|
-
cli: "qwen",
|
|
137
|
-
listTranscripts: listQwenTranscriptMetadata,
|
|
138
|
-
streamEvents: streamQwenEvents,
|
|
139
|
-
},
|
|
140
|
-
ori: {
|
|
141
|
-
cli: "ori",
|
|
142
|
-
listTranscripts: listOriTranscriptMetadata,
|
|
143
|
-
streamEvents: streamOriEvents,
|
|
144
|
-
},
|
|
145
|
-
cline: {
|
|
146
|
-
cli: "cline",
|
|
147
|
-
listTranscripts: listClineTranscriptMetadata,
|
|
148
|
-
streamEvents: streamClineEvents,
|
|
149
|
-
},
|
|
150
126
|
};
|
|
151
127
|
|
|
152
128
|
export function getAdapter(cli: IntegrationType): CliAdapter {
|
package/src/audit/cli-login.ts
CHANGED
|
@@ -173,7 +173,10 @@ export async function ensureSignedIn(preset?: string): Promise<EnsureSignedIn> {
|
|
|
173
173
|
throw new LoginError(
|
|
174
174
|
"Signing in needs an interactive terminal, and this one is not.\n" +
|
|
175
175
|
"Run `failproofai audit --schedule` from a shell you are sitting at,\n" +
|
|
176
|
-
"or sign in from the dashboard — both write the same session file
|
|
176
|
+
"or sign in from the dashboard — both write the same session file.\n" +
|
|
177
|
+
"\n" +
|
|
178
|
+
"`--email` does not avoid this: it fills in the address so you are not\n" +
|
|
179
|
+
"asked for it, but a one-time code is still sent there for you to paste.",
|
|
177
180
|
);
|
|
178
181
|
}
|
|
179
182
|
|
package/src/audit/cli.ts
CHANGED
|
@@ -35,6 +35,12 @@ import { getInstanceId } from "../../lib/telemetry-id";
|
|
|
35
35
|
import { sanitizeErrorMessage } from "../../lib/telemetry-sanitize";
|
|
36
36
|
import { openWhenReady } from "./open-browser";
|
|
37
37
|
import { describeOutcome, reportHarm } from "./report-harm";
|
|
38
|
+
import { notifyDesktop } from "./desktop-notify";
|
|
39
|
+
import { macNotifierInstalled, pruneMacNotifyQueue, queueMacNotification } from "./macos-notifier";
|
|
40
|
+
import { markLeakNoticeDelivered, releaseLeakNoticeClaims } from "./leak-notice";
|
|
41
|
+
import { desktopLeakNotice } from "../hooks/notice";
|
|
42
|
+
import { activeFindings, readLeakRecord } from "./leak-store";
|
|
43
|
+
import { readConfig } from "../hooks/fp-config";
|
|
38
44
|
import { brandAnsi, ANSI_RESET, ANSI_BOLD, ANSI_DIM, helpScreen, helpOptsFor } from "../hooks/tui";
|
|
39
45
|
import { version } from "../../package.json";
|
|
40
46
|
|
|
@@ -92,26 +98,36 @@ export function helpText(): string {
|
|
|
92
98
|
{
|
|
93
99
|
command: "audit",
|
|
94
100
|
version,
|
|
95
|
-
tagline: "
|
|
101
|
+
tagline: "find credentials your agents leaked into their own transcripts",
|
|
96
102
|
sections: [
|
|
97
103
|
{
|
|
98
104
|
label: "usage",
|
|
99
105
|
entries: [
|
|
100
|
-
["(bare)", `Scan your session history, then open http://localhost:${DASHBOARD_PORT}/audit`],
|
|
101
|
-
["--schedule [days]", "Scan on a timer and email the findings. Default 7 days, range 1-90. Signs you in the first time."],
|
|
106
|
+
["(bare)", `Scan your session history for leaked keys, then open http://localhost:${DASHBOARD_PORT}/audit`],
|
|
107
|
+
["--schedule [days]", "Scan on a timer, notify you on this machine, and email the findings. Default 7 days, range 1-90. Signs you in the first time."],
|
|
102
108
|
// Its own row now, rather than a clause inside --schedule's. It
|
|
103
109
|
// does not stand alone, which is why it used to be a clause — but
|
|
104
110
|
// a clause wraps, and `--email <address>` landing with the flag at
|
|
105
111
|
// the end of one line and its placeholder at the start of the next
|
|
106
112
|
// is not a flag anybody can read or copy.
|
|
107
|
-
["--email <address>", "With --schedule,
|
|
113
|
+
["--email <address>", "With --schedule, fills in your address. A sign-in code is still emailed to you to paste."],
|
|
108
114
|
["--no-schedule", "Stop the timer. Leaves you signed in."],
|
|
115
|
+
// Its own pair of flags rather than a clause on --schedule: whether
|
|
116
|
+
// this machine scans and whether it may interrupt you are separate
|
|
117
|
+
// decisions, and nobody silencing a banner should have to re-state
|
|
118
|
+
// their schedule to do it.
|
|
119
|
+
["--notify", "Show a desktop notification when a scan finds a credential. On by default."],
|
|
120
|
+
["--no-notify", "Stop those notifications. Scans and the digest are unaffected."],
|
|
109
121
|
["--status", "Whether scheduling is on, where reports go, the daemon's state, and when the next scan is due."],
|
|
110
122
|
["-h, --help", "Show this help."],
|
|
111
123
|
],
|
|
112
124
|
},
|
|
113
125
|
],
|
|
114
|
-
footer: [
|
|
126
|
+
footer: [
|
|
127
|
+
"Everything runs on this machine. A scan tells you three ways: a desktop",
|
|
128
|
+
"notification, two lines in your agent session, and the emailed digest —",
|
|
129
|
+
"and only that digest ever leaves.",
|
|
130
|
+
],
|
|
115
131
|
},
|
|
116
132
|
helpOptsFor(process.stdout),
|
|
117
133
|
);
|
|
@@ -199,6 +215,18 @@ function startProgress(): Progress {
|
|
|
199
215
|
const lines = Array.from({ length: n }, (_, i) => lineFor(i));
|
|
200
216
|
// Move the cursor back up over the previously-drawn block, then clear and
|
|
201
217
|
// rewrite each line in place.
|
|
218
|
+
//
|
|
219
|
+
// INVARIANT: nothing else may write to the terminal while this is running.
|
|
220
|
+
// The cursor-up is a fixed count, so any stray line pushes the cursor down
|
|
221
|
+
// and the next redraw repaints the block lower — stranding the top of the
|
|
222
|
+
// old frame above it, which reads as the audit having run twice. That is
|
|
223
|
+
// not hypothetical: Node's "SQLite is an experimental feature" warning did
|
|
224
|
+
// exactly this on every first run, two lines of it, and it is why
|
|
225
|
+
// `lib/sqlite-reader.ts` now filters that one warning at the source.
|
|
226
|
+
//
|
|
227
|
+
// There is no defensive fix from inside here — erasing to end of screen
|
|
228
|
+
// still leaves the stranded lines ABOVE the cursor. Keep the terminal
|
|
229
|
+
// quiet instead.
|
|
202
230
|
if (printed) process.stdout.write(`\x1b[${n}A`);
|
|
203
231
|
process.stdout.write(lines.map((l) => `\x1b[2K${l}`).join("\n") + "\n");
|
|
204
232
|
printed = true;
|
|
@@ -334,6 +362,122 @@ function heldByLine(held: AuditLockInfo | null): string {
|
|
|
334
362
|
return `another audit is already running (pid ${held.pid}, started by ${held.source} ${ageS}s ago)`;
|
|
335
363
|
}
|
|
336
364
|
|
|
365
|
+
/**
|
|
366
|
+
* Raise a desktop banner for credentials this scan found and nothing has
|
|
367
|
+
* announced yet.
|
|
368
|
+
*
|
|
369
|
+
* Scheduled runs only, and the reason is the same one that keeps `reportHarm`
|
|
370
|
+
* here: an interactive `failproofai audit` prints its findings to a person who
|
|
371
|
+
* is already looking at them, so a banner on top is noise. The scheduled run is
|
|
372
|
+
* the one with nobody watching, which is exactly when a notification is the
|
|
373
|
+
* only thing that reaches anyone.
|
|
374
|
+
*
|
|
375
|
+
* Every failure is swallowed on purpose. This is an announcement about a
|
|
376
|
+
* completed scan, not part of the scan: a machine with no desktop, a locked
|
|
377
|
+
* session, a bus that hung — none of those make the audit itself a failure, and
|
|
378
|
+
* turning a good scan into exit 1 would make the scheduler back off from
|
|
379
|
+
* running the thing that actually matters.
|
|
380
|
+
*/
|
|
381
|
+
async function announceLeaksOnDesktop(result: AuditResult): Promise<void> {
|
|
382
|
+
try {
|
|
383
|
+
await announceLeaksOrThrow(result);
|
|
384
|
+
} catch (err) {
|
|
385
|
+
// The catch is not decoration. `notifyDesktop` documents that it never
|
|
386
|
+
// throws, but it is doing socket I/O, and so are the config read and the
|
|
387
|
+
// marker write beside it — depending on three modules' promises to keep a
|
|
388
|
+
// completed scan from reporting failure is a dependency this does not need
|
|
389
|
+
// to have.
|
|
390
|
+
process.stderr.write(
|
|
391
|
+
`failproofai: could not announce a leak on the desktop: ` +
|
|
392
|
+
`${err instanceof Error ? err.message : String(err)}\n`,
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
async function announceLeaksOrThrow(result: AuditResult): Promise<void> {
|
|
398
|
+
const ids = result.newLeakIds ?? [];
|
|
399
|
+
if (ids.length === 0) return;
|
|
400
|
+
// Read at fire time, not at scan time: the switch answers "should this
|
|
401
|
+
// interrupt me", and it is the user's most recent answer that counts.
|
|
402
|
+
if (!readConfig().audit.notify) return;
|
|
403
|
+
|
|
404
|
+
// Claimed BEFORE the call goes out, and in the desktop channel only. Before,
|
|
405
|
+
// because a process that dies mid-notify would otherwise re-announce the same
|
|
406
|
+
// finding on every scheduled run forever; desktop-only, because a banner the
|
|
407
|
+
// user may never have seen must not also silence the in-session notice.
|
|
408
|
+
const claimed = markLeakNoticeDelivered(ids, undefined, "desktop");
|
|
409
|
+
if (claimed.length === 0) return;
|
|
410
|
+
|
|
411
|
+
// Report what SURVIVED into the record, not how many ids the scan minted.
|
|
412
|
+
// `writeLeakRecord` prunes to MAX_FINDINGS, so on a large history the two
|
|
413
|
+
// numbers diverge wildly — a real run announced "5703 credentials" while the
|
|
414
|
+
// dashboard it points at showed 500. A banner that disagrees with the page it
|
|
415
|
+
// sends you to is worse than no banner: it teaches you the number is noise.
|
|
416
|
+
const live = new Set(activeFindings(readLeakRecord()).map((f) => f.id));
|
|
417
|
+
const shown = claimed.filter((id) => live.has(id)).length || claimed.length;
|
|
418
|
+
|
|
419
|
+
const n = shown === 1 ? "a possible credential exposure" : `${shown} possible credential exposures`;
|
|
420
|
+
// Fixed text with no scanned content and no count. A finding's own text comes
|
|
421
|
+
// from a repository this machine cloned, and notification bodies render
|
|
422
|
+
// markup on several Linux desktops. The count is intentionally left to the
|
|
423
|
+
// report too: a banner must not turn detector candidates into a claim that
|
|
424
|
+
// the user leaked N real credentials.
|
|
425
|
+
//
|
|
426
|
+
// No action buttons, deliberately. `Notify` supports them, but a server
|
|
427
|
+
// delivers the click back as an `ActionInvoked` signal to the sender — and
|
|
428
|
+
// this process exits as soon as the scan finishes, so the button would be
|
|
429
|
+
// dead. A plain command the user can copy beats a button that does nothing.
|
|
430
|
+
const { title, body } = desktopLeakNotice();
|
|
431
|
+
let delivered = false;
|
|
432
|
+
|
|
433
|
+
try {
|
|
434
|
+
// The two platforms need opposite things, and the split is not cosmetic. On
|
|
435
|
+
// Linux this process can reach the session bus itself. On macOS it cannot
|
|
436
|
+
// reach Notification Center at all — it is a child of a LaunchDaemon, outside
|
|
437
|
+
// the GUI session — so it hands the message to an agent that lives inside
|
|
438
|
+
// one. See `macos-notifier.ts` for why that agent exists.
|
|
439
|
+
if (process.platform === "darwin") {
|
|
440
|
+
pruneMacNotifyQueue();
|
|
441
|
+
if (!queueMacNotification(claimed[0], title, body)) {
|
|
442
|
+
process.stderr.write(`failproofai: found ${n} but could not queue a notification\n`);
|
|
443
|
+
} else {
|
|
444
|
+
delivered = true; // the durable queue owns delivery from here
|
|
445
|
+
}
|
|
446
|
+
if (delivered && !macNotifierInstalled()) {
|
|
447
|
+
// Queued, but nothing currently collects it. Keep the claim because the
|
|
448
|
+
// payload is durable and will be consumed when the LaunchAgent returns.
|
|
449
|
+
// Worth a line, because the immediate symptom is otherwise silence.
|
|
450
|
+
process.stderr.write(
|
|
451
|
+
`failproofai: found ${n}; the notifier is not installed, so nothing will show it ` +
|
|
452
|
+
`(run \`failproofai config\`)\n`,
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const outcome = await notifyDesktop(
|
|
459
|
+
title,
|
|
460
|
+
body,
|
|
461
|
+
// A stable id would let a repeat scan replace the previous bubble instead
|
|
462
|
+
// of stacking. We do not have one yet — the server assigns it and we would
|
|
463
|
+
// have to persist it — so a fresh notification is correct here, and it is
|
|
464
|
+
// bounded: at most one per distinct credential, ever.
|
|
465
|
+
0,
|
|
466
|
+
);
|
|
467
|
+
delivered = outcome.ok;
|
|
468
|
+
if (!outcome.ok) {
|
|
469
|
+
// stderr, so it lands in the journal beside the run it belongs to. Not a
|
|
470
|
+
// failure of the audit — but "we found a key and could not tell you" is
|
|
471
|
+
// exactly the sentence someone debugging this needs to find.
|
|
472
|
+
process.stderr.write(
|
|
473
|
+
`failproofai: found ${n} but could not raise a desktop notification (${outcome.reason})\n`,
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
} finally {
|
|
477
|
+
if (!delivered) releaseLeakNoticeClaims(claimed, undefined, "desktop");
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
337
481
|
// ── Headless (scheduled) audit ───────────────────────────────────────────────
|
|
338
482
|
|
|
339
483
|
/**
|
|
@@ -404,6 +548,11 @@ export async function runScheduledAudit(): Promise<number> {
|
|
|
404
548
|
`${num(result.transcripts.scanned)} sessions, ${num(result.totals.hits)} hits\n`,
|
|
405
549
|
);
|
|
406
550
|
|
|
551
|
+
// Tell whoever owns this machine, on the machine, before anything is sent
|
|
552
|
+
// anywhere. Local first: this is the channel that works with no account, no
|
|
553
|
+
// email and no network, which describes most users.
|
|
554
|
+
await announceLeaksOnDesktop(result);
|
|
555
|
+
|
|
407
556
|
// Report harmful findings upstream, if the user switched emailed reports on.
|
|
408
557
|
//
|
|
409
558
|
// AFTER the dashboard cache is written and AFTER the success line, because
|
|
@@ -552,6 +701,16 @@ export async function runAuditCli(args: string[]): Promise<void> {
|
|
|
552
701
|
process.exit(0);
|
|
553
702
|
}
|
|
554
703
|
|
|
704
|
+
for (const [flag, enable] of [["--notify", true], ["--no-notify", false]] as const) {
|
|
705
|
+
if (args.includes(flag)) {
|
|
706
|
+
const extra = args.find((a) => a !== flag);
|
|
707
|
+
if (extra) die(`\`audit ${flag}\` takes no other arguments (got: ${extra}).`);
|
|
708
|
+
const { runNotifyToggle } = await import("./schedule-cli");
|
|
709
|
+
runNotifyToggle(enable);
|
|
710
|
+
process.exit(0);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
555
714
|
if (args.includes("--no-schedule")) {
|
|
556
715
|
const extra = args.find((a) => a !== "--no-schedule");
|
|
557
716
|
if (extra) die(`\`audit --no-schedule\` takes no other arguments (got: ${extra}).`);
|