failproofai 1.0.4-beta.8 → 1.0.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.
Files changed (245) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +5 -5
  3. package/.next/standalone/.next/prerender-manifest.json +4 -4
  4. package/.next/standalone/.next/required-server-files.json +1 -1
  5. package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +2 -2
  6. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  7. package/.next/standalone/.next/server/app/_global-error/page.js +2 -2
  8. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  9. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  11. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  12. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +6 -6
  13. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  14. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  15. package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +2 -2
  16. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  17. package/.next/standalone/.next/server/app/_not-found/page.js +2 -2
  18. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  19. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  20. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  21. package/.next/standalone/.next/server/app/_not-found.rsc +14 -14
  22. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +14 -14
  23. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +13 -13
  24. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  25. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  26. package/.next/standalone/.next/server/app/api/audit/run/route.js +4 -4
  27. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  28. package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  30. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  32. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  33. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js +1 -1
  34. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/audit/page/build-manifest.json +2 -2
  36. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +4 -37
  37. package/.next/standalone/.next/server/app/audit/page.js +2 -2
  38. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  39. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  40. package/.next/standalone/.next/server/app/index.html +1 -1
  41. package/.next/standalone/.next/server/app/index.rsc +14 -14
  42. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +13 -13
  43. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +14 -14
  44. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  45. package/.next/standalone/.next/server/app/page/build-manifest.json +2 -2
  46. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  47. package/.next/standalone/.next/server/app/page.js +2 -2
  48. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  49. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  50. package/.next/standalone/.next/server/app/policies/page/build-manifest.json +2 -2
  51. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
  52. package/.next/standalone/.next/server/app/policies/page.js +4 -4
  53. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  54. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  55. package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +2 -2
  56. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  57. package/.next/standalone/.next/server/app/project/[name]/page.js +4 -4
  58. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  59. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  60. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +2 -2
  61. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  62. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  63. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +5 -5
  64. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  65. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  66. package/.next/standalone/.next/server/app/projects/page/build-manifest.json +2 -2
  67. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  68. package/.next/standalone/.next/server/app/projects/page.js +3 -3
  69. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  70. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  71. package/.next/standalone/.next/server/app/settings/page/build-manifest.json +2 -2
  72. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +4 -15
  73. package/.next/standalone/.next/server/app/settings/page.js +4 -4
  74. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  75. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  76. package/.next/standalone/.next/server/chunks/{[externals]__0hmcwgg._.js → [externals]__0-9ti7z._.js} +1 -1
  77. package/.next/standalone/.next/server/chunks/{[externals]__14b7cc6._.js → [externals]__0u3wrvq._.js} +1 -1
  78. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-219ec._.js +3 -0
  79. package/.next/standalone/.next/server/chunks/[root-of-the-server]__04_h-a5._.js +3 -0
  80. package/.next/standalone/.next/server/chunks/[root-of-the-server]__08dchfu._.js +1 -1
  81. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
  82. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0rv7m0k._.js +1 -1
  83. package/.next/standalone/.next/server/chunks/[root-of-the-server]__17g9wh7._.js +1 -1
  84. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1le9jqc._.js +3 -0
  85. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1legmza._.js +1 -1
  86. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1p-qi2t._.js +5 -0
  87. package/.next/standalone/.next/server/chunks/_0tovk6q._.js +1 -1
  88. package/.next/standalone/.next/server/chunks/_0trp3yc._.js +1 -1
  89. package/.next/standalone/.next/server/chunks/{_1_eakl4._.js → _1-akg6w._.js} +2 -2
  90. package/.next/standalone/.next/server/chunks/_1ek68ln._.js +10 -10
  91. package/.next/standalone/.next/server/chunks/_1vslkoe._.js +1 -1
  92. package/.next/standalone/.next/server/chunks/lib_telemetry_ts_0dctyyw._.js +1 -1
  93. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  94. package/.next/standalone/.next/server/chunks/src_hooks_fp-home_ts_09kv0bn._.js +1 -1
  95. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__187jks6._.js → [root-of-the-server]__00-s7h8._.js} +1 -1
  96. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__013jr2b._.js +4 -0
  97. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01wy8d-._.js +4 -0
  98. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02npjtd._.js +4 -0
  99. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1fa1jd9._.js → [root-of-the-server]__09flfsx._.js} +2 -2
  100. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0da85px._.js +4 -0
  101. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ftmoxc._.js +4 -0
  102. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0kokc8z._.js → [root-of-the-server]__0n_lxhg._.js} +1 -1
  103. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0oa1lav._.js +1 -1
  104. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0p-5p8u._.js +4 -0
  105. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s740oi._.js +6 -0
  106. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1kpd1za._.js → [root-of-the-server]__190won8._.js} +1 -1
  107. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ctpynv._.js +3 -0
  108. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1nq7ivq._.js +3 -0
  109. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1p2otjt._.js +4 -0
  110. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__00y9ww2._.js → [root-of-the-server]__1sc84bb._.js} +2 -21
  111. package/.next/standalone/.next/server/chunks/ssr/_02yoeej._.js +3 -0
  112. package/.next/standalone/.next/server/chunks/ssr/{_00bqkhj._.js → _04rbnaq._.js} +2 -2
  113. package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
  114. package/.next/standalone/.next/server/chunks/ssr/{_0s-77v5._.js → _0h_douw._.js} +1 -1
  115. package/.next/standalone/.next/server/chunks/ssr/_1_ozgif._.js +1 -1
  116. package/.next/standalone/.next/server/chunks/ssr/_1ddpc5g._.js +1 -1
  117. package/.next/standalone/.next/server/chunks/ssr/_1es2j7i._.js +14 -51
  118. package/.next/standalone/.next/server/chunks/ssr/{_14c6q09._.js → _1if9ixi._.js} +2 -21
  119. package/.next/standalone/.next/server/chunks/ssr/{_07t3jol._.js → _1u8-lu2._.js} +2 -2
  120. package/.next/standalone/.next/server/chunks/ssr/_1ylae7o._.js +3 -0
  121. package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +3 -0
  122. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +2 -2
  123. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +69 -1
  124. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  125. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +1 -1
  126. package/.next/standalone/.next/server/chunks/ssr/app_settings_settings-client_tsx_20lq-mq._.js +1 -1
  127. package/.next/standalone/.next/server/chunks/ssr/lib_openclaw-projects_ts_0qxq3bp._.js +1 -1
  128. package/.next/standalone/.next/server/chunks/ssr/node_modules_13n822a._.js +1 -1
  129. package/.next/standalone/.next/server/chunks/ssr/node_modules_html-to-image_es_index_1ao30b1.js +3 -0
  130. package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +1 -1
  131. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589g._.js +1 -1
  132. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-home_ts_0je3xkv._.js +1 -1
  133. package/.next/standalone/.next/server/middleware-build-manifest.js +5 -5
  134. package/.next/standalone/.next/server/pages/404.html +1 -1
  135. package/.next/standalone/.next/server/pages/500.html +1 -1
  136. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  137. package/.next/standalone/.next/server/server-reference-manifest.json +21 -65
  138. package/.next/standalone/.next/static/chunks/{1302duobnz07l.js → 0-tyrpwuc3lla.js} +1 -1
  139. package/.next/standalone/.next/static/chunks/0ca7octhod0g2.js +69 -0
  140. package/.next/standalone/.next/static/chunks/0u64yey446k4f.css +1 -0
  141. package/.next/standalone/.next/static/chunks/{2n85g-crgcx5j.js → 1j6o9min_6qa9.js} +1 -1
  142. package/.next/standalone/.next/static/chunks/27cl8h-c2w7cd.js +1 -0
  143. package/.next/standalone/.next/static/chunks/{0jpd8dv2vk930.css → 29-iqnp9v_axt.css} +1 -1
  144. package/.next/standalone/.next/static/chunks/{3uyhmm01g64k4.css → 2k9f4tyv04809.css} +0 -1
  145. package/.next/standalone/.next/static/chunks/{1chm9piluggc-.js → 2lwipmv5etc8m.js} +1 -1
  146. package/.next/standalone/.next/static/chunks/2pd16tn5qmx13.js +1 -0
  147. package/.next/standalone/.next/static/chunks/{2ukucf6g-kbs5.js → 2u8vh73tpw28_.js} +1 -1
  148. package/.next/standalone/.next/static/chunks/{1_x7pqemw5n_w.js → 31u94yukw9fz-.js} +1 -1
  149. package/.next/standalone/.next/static/chunks/{2mizt96igig1y.js → 3p9_8x5ezce-9.js} +1 -1
  150. package/.next/standalone/.next/static/chunks/3qfoeqhy5l10x.js +1 -0
  151. package/.next/standalone/.next/static/chunks/{0vm4wdx3w-2_w.js → 3tjji451hrgc0.js} +1 -1
  152. package/.next/standalone/.next/static/chunks/3zkg2s2vzxc3d.js +1 -0
  153. package/.next/standalone/.next/static/chunks/{turbopack-3s3c-u_u_l0u9.js → turbopack-3p73obe0xmrxm.js} +1 -1
  154. package/.next/standalone/.opencode/plugins/failproofai.mjs +27 -51
  155. package/.next/standalone/app/actions/get-scheduled-audit.ts +0 -5
  156. package/.next/standalone/app/actions/update-scheduled-audit.ts +57 -18
  157. package/.next/standalone/app/api/auth/status/route.ts +2 -3
  158. package/.next/standalone/app/audit/_components/audit-dashboard.tsx +34 -140
  159. package/.next/standalone/app/audit/_components/audit-poster.tsx +10 -29
  160. package/.next/standalone/app/audit/_components/come-back-better-section.tsx +1 -6
  161. package/.next/standalone/app/audit/_components/empty-state.tsx +1 -4
  162. package/.next/standalone/app/audit/_components/how-to-improve-section.tsx +5 -19
  163. package/.next/standalone/app/audit/_components/share-templates.ts +44 -110
  164. package/.next/standalone/app/audit/audit-styles.css +1 -132
  165. package/.next/standalone/app/settings/page.tsx +1 -1
  166. package/.next/standalone/app/settings/settings-client.tsx +45 -50
  167. package/.next/standalone/lib/auth/api-server-client.ts +5 -33
  168. package/.next/standalone/lib/claude-sessions.ts +27 -101
  169. package/.next/standalone/lib/codex-sessions.ts +4 -20
  170. package/.next/standalone/lib/copilot-sessions.ts +1 -32
  171. package/.next/standalone/lib/cursor-sessions.ts +5 -62
  172. package/.next/standalone/lib/download-session.ts +13 -58
  173. package/.next/standalone/lib/openclaw-projects.ts +18 -54
  174. package/.next/standalone/lib/openclaw-sessions.ts +17 -75
  175. package/.next/standalone/lib/sqlite-reader.ts +1 -78
  176. package/.next/standalone/package.json +9 -9
  177. package/.next/standalone/server.js +1 -1
  178. package/bin/failproofai.mjs +1 -1
  179. package/dist/cli.mjs +664 -2121
  180. package/dist/worker.mjs +105 -422
  181. package/lib/auth/api-server-client.ts +5 -33
  182. package/lib/claude-sessions.ts +27 -101
  183. package/lib/codex-sessions.ts +4 -20
  184. package/lib/copilot-sessions.ts +1 -32
  185. package/lib/cursor-sessions.ts +5 -62
  186. package/lib/download-session.ts +13 -58
  187. package/lib/openclaw-projects.ts +18 -54
  188. package/lib/openclaw-sessions.ts +17 -75
  189. package/lib/sqlite-reader.ts +1 -78
  190. package/package.json +9 -9
  191. package/pi-extension/index.ts +5 -50
  192. package/src/audit/cache.ts +0 -6
  193. package/src/audit/cli-adapters/claude.ts +0 -13
  194. package/src/audit/cli-login.ts +1 -4
  195. package/src/audit/cli.ts +7 -157
  196. package/src/audit/harm-report.ts +4 -111
  197. package/src/audit/index.ts +15 -286
  198. package/src/audit/machine-store.ts +9 -8
  199. package/src/audit/redact-example.ts +15 -324
  200. package/src/audit/report-harm.ts +37 -33
  201. package/src/audit/report.ts +3 -6
  202. package/src/audit/schedule-cli.ts +33 -54
  203. package/src/audit/scoring.ts +0 -49
  204. package/src/audit/types.ts +0 -52
  205. package/src/hooks/builtin-policies.ts +2 -67
  206. package/src/hooks/configure-wizard.ts +0 -55
  207. package/src/hooks/fp-config.ts +48 -83
  208. package/src/hooks/fp-home.ts +0 -27
  209. package/src/hooks/integrations.ts +14 -51
  210. package/src/hooks/uninstall-cli.ts +2 -17
  211. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0faqz9x._.js +0 -22
  212. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0zyc_nm._.js +0 -3
  213. package/.next/standalone/.next/server/chunks/[root-of-the-server]__112b4go._.js +0 -24
  214. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1l71g3y._.js +0 -22
  215. package/.next/standalone/.next/server/chunks/lib_openclaw-projects_ts_0gc-p24._.js +0 -3
  216. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0-4ja6q._.js +0 -4
  217. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__00cr3xu._.js +0 -4
  218. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04px77e._.js +0 -4
  219. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0egr5s1._.js +0 -6
  220. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0i0l1di._.js +0 -4
  221. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0n2txhk._.js +0 -3
  222. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0r3c1_0._.js +0 -4
  223. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__17xnxlx._.js +0 -4
  224. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ckpmkk._.js +0 -4
  225. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1kaotix._.js +0 -22
  226. package/.next/standalone/.next/server/chunks/ssr/_04ng_6x._.js +0 -3
  227. package/.next/standalone/.next/server/chunks/ssr/_0l2hi_d._.js +0 -3
  228. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_rerun-button_tsx_0blihgw._.js +0 -3
  229. package/.next/standalone/.next/static/chunks/0igq884ziuv-8.js +0 -1
  230. package/.next/standalone/.next/static/chunks/349jkb79v5g6z.js +0 -1
  231. package/.next/standalone/.next/static/chunks/3a4gth64hwtwf.js +0 -1
  232. package/.next/standalone/app/actions/get-leaks.ts +0 -104
  233. package/.next/standalone/app/audit/_components/leak-section.tsx +0 -192
  234. package/.next/standalone/lib/openclaw-db.ts +0 -271
  235. package/lib/openclaw-db.ts +0 -271
  236. package/src/audit/desktop-notify.ts +0 -420
  237. package/src/audit/leak-fingerprint.ts +0 -198
  238. package/src/audit/leak-record.ts +0 -248
  239. package/src/audit/leak-scan.ts +0 -268
  240. package/src/audit/leak-store.ts +0 -220
  241. package/src/audit/macos-notifier.ts +0 -298
  242. package/src/hooks/notice.ts +0 -24
  243. /package/.next/standalone/.next/static/{DNhaAH8iPGH0tIuxee5_2 → dg8VQ7Ejlo0Qsh_x92oIt}/_buildManifest.js +0 -0
  244. /package/.next/standalone/.next/static/{DNhaAH8iPGH0tIuxee5_2 → dg8VQ7Ejlo0Qsh_x92oIt}/_clientMiddlewareManifest.js +0 -0
  245. /package/.next/standalone/.next/static/{DNhaAH8iPGH0tIuxee5_2 → dg8VQ7Ejlo0Qsh_x92oIt}/_ssgManifest.js +0 -0
@@ -1,298 +0,0 @@
1
- /**
2
- * Getting a notification onto a Mac's screen from a process that has no screen.
3
- *
4
- * On Linux the scheduled audit talks to the session bus directly and is done
5
- * (`desktop-notify.ts`). macOS has no equivalent shortcut, and the reason is
6
- * structural rather than a missing permission: launchd has two domains, and
7
- * Notification Center lives in only one of them. `failproofaid` is installed as
8
- * a **LaunchDaemon** — that is what makes it start at boot and survive logout,
9
- * the property the whole daemon design turns on — so it and every child it
10
- * spawns run in the *system* domain, outside the logged-in user's GUI session.
11
- * A `display notification` from there posts into nothing. Apple's TN2083 draws
12
- * exactly this line, and Time Machine ships the same split: a privileged daemon
13
- * doing the work, an agent in the user's session doing the talking.
14
- *
15
- * So this module installs the second half — a **LaunchAgent**, which launchd
16
- * starts inside the Aqua session, where Notification Center is reachable.
17
- *
18
- * ## Three choices worth stating, because the obvious alternative is wrong
19
- *
20
- * **`~/Library/LaunchAgents`, not `/Library/LaunchAgents`.** The per-user
21
- * directory is owned by the user and writable without elevation, so installing
22
- * this asks for nothing — no password, no second sudo prompt in a wizard that
23
- * already spent its one. The system-wide agent directory would need admin and
24
- * would install for every account on the box, which is not what a per-user
25
- * notification is.
26
- *
27
- * **A user-session `osascript`, not an applet binary launched directly.** The
28
- * compiled applet path looked attractive because it could carry a FailproofAI
29
- * bundle identifier, but launching `Contents/MacOS/applet` directly from
30
- * launchd is not a dependable Notification Center client on current macOS.
31
- * `osascript` from a user LaunchAgent is the same mechanism ordinary scheduled
32
- * jobs use successfully. Delivery matters more than owning the label shown by
33
- * Notification Center, so the runner uses that proven path.
34
- *
35
- * **A watched drop directory, not a resident process.** The agent is not a
36
- * daemon: `WatchPaths` makes launchd start it only when a file appears, and it
37
- * exits as soon as the directory is empty. Nothing is resident, nothing polls,
38
- * and a machine that never leaks a credential never runs it at all.
39
- *
40
- * ## What this does NOT claim
41
- *
42
- * That the user saw it. macOS can be in Do Not Disturb, the display can be
43
- * asleep, and the user can deny the bundle in System Settings — none of which
44
- * is visible from here. The banner is a prompt to look; the record on disk is
45
- * the thing that lasts.
46
- */
47
- import { execFileSync } from "node:child_process";
48
- import { chmodSync, existsSync, mkdirSync, readdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
49
- import { homedir } from "node:os";
50
- import { resolve } from "node:path";
51
-
52
- import { runDir } from "../hooks/fp-home";
53
- import { isFindingId } from "./leak-fingerprint";
54
-
55
- /** Stable label used to load, inspect, and remove the per-user LaunchAgent. */
56
- export const MAC_NOTIFIER_LABEL = "ai.failproof.notifier";
57
-
58
- /** Where a queued notification waits for the agent to pick it up. */
59
- export const macNotifyDir = (home?: string) => resolve(runDir(home), "notify");
60
-
61
- /** Legacy compiled applet path, retained so upgrades and uninstall remove it. */
62
- export function macNotifierAppPath(home?: string): string {
63
- return resolve(runDir(home), "..", "bin", "FailproofAI Notifier.app");
64
- }
65
-
66
- /** User-session runner invoked by launchd when a notification is queued. */
67
- export function macNotifierRunnerPath(home?: string): string {
68
- return resolve(runDir(home), "..", "bin", "failproofai-notifier.zsh");
69
- }
70
-
71
- export function macNotifierPlistPath(): string {
72
- return resolve(homedir(), "Library", "LaunchAgents", `${MAC_NOTIFIER_LABEL}.plist`);
73
- }
74
-
75
- function escapeXml(s: string): string {
76
- return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
77
- }
78
-
79
- /**
80
- * The user-session runner: drain the directory, say each notification, exit.
81
- *
82
- * Payload format is two lines — title, then body. Values are passed to
83
- * AppleScript as argv, never interpolated into source, so notification text
84
- * cannot become AppleScript code.
85
- *
86
- * The file is REMOVED BEFORE the notification is posted, and that order is
87
- * deliberate: `WatchPaths` fires on every change to the directory, so a file
88
- * that cannot be displayed and is not removed would wake this agent forever.
89
- * At-most-once is the right failure here because the caller has already claimed
90
- * the finding on disk — a dropped banner costs one silent finding, a wake loop
91
- * costs the machine.
92
- */
93
- export function notifierScript(home?: string): string {
94
- const dir = macNotifyDir(home);
95
- const quotedDir = `'${dir.replace(/'/g, `'\\''`)}'`;
96
- return `#!/bin/zsh
97
- set -u
98
- setopt NULL_GLOB
99
-
100
- drop_dir=${quotedDir}
101
-
102
- for entry_path in "$drop_dir"/*; do
103
- [ -f "$entry_path" ] || continue
104
- note_title=$(/usr/bin/sed -n '1p' "$entry_path")
105
- note_body=$(/usr/bin/sed -n '2,$p' "$entry_path" | /usr/bin/tr '\\n' ' ')
106
- /bin/rm -f "$entry_path"
107
- [ -n "$note_title" ] || continue
108
- /usr/bin/osascript - "$note_title" "$note_body" <<'APPLESCRIPT'
109
- on run argv
110
- display notification (item 2 of argv) with title (item 1 of argv)
111
- end run
112
- APPLESCRIPT
113
- done
114
- `;
115
- }
116
-
117
- /**
118
- * Exported for the same reason `launchdPlistContents` is: it is the only way to
119
- * assert this file's shape from a Linux CI runner, which is every CI runner we
120
- * have.
121
- */
122
- export function notifierPlistContents(runnerPath: string, watchDir: string): string {
123
- return `<?xml version="1.0" encoding="UTF-8"?>
124
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
125
- <plist version="1.0">
126
- <dict>
127
- <key>Label</key>
128
- <string>${escapeXml(MAC_NOTIFIER_LABEL)}</string>
129
- <key>ProgramArguments</key>
130
- <array>
131
- <string>${escapeXml(runnerPath)}</string>
132
- </array>
133
- <!-- Started only when something is queued, and it exits when the queue is
134
- empty. RunAtLoad would fire it once at login with nothing to say. -->
135
- <key>WatchPaths</key>
136
- <array>
137
- <string>${escapeXml(watchDir)}</string>
138
- </array>
139
- <key>RunAtLoad</key>
140
- <false/>
141
- <!-- Not a service: it does one pass and exits, and launchd must not treat
142
- that as a crash to restart. -->
143
- <key>KeepAlive</key>
144
- <false/>
145
- <key>ProcessType</key>
146
- <string>Interactive</string>
147
- </dict>
148
- </plist>
149
- `;
150
- }
151
-
152
- export interface MacNotifierResult {
153
- installed: boolean;
154
- reason?: string;
155
- }
156
-
157
- /** Bounded, and never inherits a TTY — this runs from inside a wizard. */
158
- function run(cmd: string, args: string[]): void {
159
- execFileSync(cmd, args, { stdio: "ignore", timeout: 20_000 });
160
- }
161
-
162
- /**
163
- * Install the notifier, silently.
164
- *
165
- * Silent on purpose. An earlier draft printed "installing a small helper so we
166
- * can notify you about leaks (macOS only)" during setup, which reads as an
167
- * apology for something the user already asked for by running setup — every
168
- * other app that can raise a notification installs its delivery path without
169
- * narrating it. What the user is asked is the thing that matters (does this
170
- * machine scan, and may it interrupt), and both of those are real settings.
171
- *
172
- * Returns rather than throws. Notifications are one channel of several, and a
173
- * Mac that cannot load the user LaunchAgent must still finish setting up its
174
- * daemon, hooks and policies — the parts that actually enforce anything.
175
- */
176
- export function installMacNotifier(home?: string): MacNotifierResult {
177
- if (process.platform !== "darwin") return { installed: false, reason: "not macOS" };
178
- try {
179
- const runner = macNotifierRunnerPath(home);
180
- const watch = macNotifyDir(home);
181
- mkdirSync(watch, { recursive: true, mode: 0o700 });
182
- mkdirSync(resolve(runner, ".."), { recursive: true });
183
- writeFileSync(runner, notifierScript(home), { mode: 0o700 });
184
- // writeFileSync preserves an existing file's mode. Repair it explicitly on
185
- // upgrades from a partially-created or hand-edited runner.
186
- chmodSync(runner, 0o700);
187
- // Remove the applet used by older builds. The LaunchAgent below no longer
188
- // references it, and leaving it behind makes diagnosis ambiguous.
189
- rmSync(macNotifierAppPath(home), { recursive: true, force: true });
190
-
191
- mkdirSync(resolve(macNotifierPlistPath(), ".."), { recursive: true });
192
- writeFileSync(macNotifierPlistPath(), notifierPlistContents(runner, watch), { mode: 0o644 });
193
-
194
- const target = `gui/${process.getuid?.() ?? 0}`;
195
- // bootout first: bootstrap fails outright on an already-loaded label, and
196
- // a reinstall must land the NEW plist rather than leave the old one live.
197
- try {
198
- run("launchctl", ["bootout", `${target}/${MAC_NOTIFIER_LABEL}`]);
199
- } catch {
200
- // Not loaded. That is the normal first-install case.
201
- }
202
- run("launchctl", ["bootstrap", target, macNotifierPlistPath()]);
203
- return { installed: true };
204
- } catch (err) {
205
- return { installed: false, reason: err instanceof Error ? err.message : String(err) };
206
- }
207
- }
208
-
209
- /**
210
- * Remove the agent and its runner, including the applet used by older builds.
211
- *
212
- * Called from `failproofai uninstall --purge`. Unconditional and quiet: an
213
- * agent left behind after an uninstall is a plist launchd keeps trying to start
214
- * from a path that no longer exists, which shows up in the user's log forever
215
- * and is the exact class of leftover an uninstall exists to prevent.
216
- */
217
- export function uninstallMacNotifier(home?: string): void {
218
- if (process.platform !== "darwin") return;
219
- try {
220
- run("launchctl", ["bootout", `gui/${process.getuid?.() ?? 0}/${MAC_NOTIFIER_LABEL}`]);
221
- } catch {
222
- // Already gone, or never loaded.
223
- }
224
- rmSync(macNotifierPlistPath(), { force: true });
225
- rmSync(macNotifierAppPath(home), { recursive: true, force: true });
226
- rmSync(macNotifierRunnerPath(home), { force: true });
227
- rmSync(macNotifyDir(home), { recursive: true, force: true });
228
- }
229
-
230
- /**
231
- * Queue one notification for the agent to deliver.
232
- *
233
- * Written to a sibling directory and RENAMED in, so the watcher never observes
234
- * a half-written file: `WatchPaths` fires on the first byte, and a partial read
235
- * would be a truncated banner that the agent then deletes.
236
- *
237
- * Returns whether the file landed — not whether anything was shown, which this
238
- * side cannot know and does not claim.
239
- */
240
- export function queueMacNotification(
241
- id: string,
242
- title: string,
243
- body: string,
244
- home?: string,
245
- ): boolean {
246
- // The id becomes a filename in a watched directory, so validate the path
247
- // component before resolving it — see `isFindingId`.
248
- if (!isFindingId(id)) return false;
249
- const dir = macNotifyDir(home);
250
- const tmp = resolve(dir, "..", `notify-${id}.tmp`);
251
- try {
252
- mkdirSync(dir, { recursive: true, mode: 0o700 });
253
- // One line each. Newlines inside either field would be read as extra body
254
- // lines by the applet, so they are flattened here rather than there.
255
- const payload = `${title.replace(/\s+/g, " ")}\n${body.replace(/\s+/g, " ")}\n`;
256
- writeFileSync(tmp, payload, { mode: 0o600 });
257
- renameSync(tmp, resolve(dir, id));
258
- return true;
259
- } catch {
260
- // Clean up the staging file. Without this, every failed queue attempt left
261
- // a `notify-*.tmp` beside the watched directory forever — invisible to the
262
- // agent, which only reads inside it, and never collected by anything.
263
- try {
264
- rmSync(tmp, { force: true });
265
- } catch { /* nothing left to do */ }
266
- return false;
267
- }
268
- }
269
-
270
- /** True when an agent is installed and could pick a queued file up. */
271
- export function macNotifierInstalled(home?: string): boolean {
272
- return existsSync(macNotifierPlistPath()) && existsSync(macNotifierRunnerPath(home));
273
- }
274
-
275
- /**
276
- * Drop anything the agent never collected.
277
- *
278
- * A machine that queued a banner while logged out, or with the agent removed,
279
- * would otherwise show a pile of stale findings at the next login — "we found
280
- * this key" for keys already rotated weeks ago.
281
- */
282
- export function pruneMacNotifyQueue(home?: string, maxAgeMs = 7 * 86_400_000): void {
283
- try {
284
- const dir = macNotifyDir(home);
285
- if (!existsSync(dir)) return;
286
- const { statSync } = require("node:fs") as typeof import("node:fs");
287
- for (const name of readdirSync(dir)) {
288
- const path = resolve(dir, name);
289
- try {
290
- if (Date.now() - statSync(path).mtimeMs > maxAgeMs) rmSync(path, { force: true });
291
- } catch {
292
- // Raced with the agent collecting it. Nothing to do.
293
- }
294
- }
295
- } catch {
296
- // Housekeeping only.
297
- }
298
- }
@@ -1,24 +0,0 @@
1
- /** Copy for the OS notification raised by an unattended scheduled scan.
2
- *
3
- * Deliberately omits the count. A burst of generated fixtures can hit the
4
- * record cap, and a desktop banner saying "500 leaked credentials" states a
5
- * conclusion the detector cannot support. The dashboard carries the evidence;
6
- * the banner's job is only to ask for review. */
7
- export type DesktopLeakEmailState = "sent" | "missing" | "failed" | "held";
8
-
9
- export function desktopLeakNotice(email: DesktopLeakEmailState = "held"): { title: string; body: string } {
10
- const followUp =
11
- email === "sent"
12
- ? "A masked alert was also sent to your email."
13
- : email === "missing"
14
- ? "Add email alerts with: failproofai audit --schedule --email you@example.com"
15
- : email === "failed"
16
- ? "Email delivery failed this time; the local result is still available."
17
- : "Email was not sent for this run.";
18
- return {
19
- title: "failproofai audit needs review",
20
- body:
21
- "Possible credential exposure was found in your agent transcripts. " +
22
- `Run failproofai audit to verify the matches and rotate anything real. ${followUp}`,
23
- };
24
- }