failproofai 1.0.4-beta.7 → 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 (221) 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.nft.json +1 -1
  34. package/.next/standalone/.next/server/app/audit/page/build-manifest.json +2 -2
  35. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +4 -37
  36. package/.next/standalone/.next/server/app/audit/page.js +2 -2
  37. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  38. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  39. package/.next/standalone/.next/server/app/index.html +1 -1
  40. package/.next/standalone/.next/server/app/index.rsc +14 -14
  41. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +13 -13
  42. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +14 -14
  43. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  44. package/.next/standalone/.next/server/app/page/build-manifest.json +2 -2
  45. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  46. package/.next/standalone/.next/server/app/page.js +2 -2
  47. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  48. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  49. package/.next/standalone/.next/server/app/policies/page/build-manifest.json +2 -2
  50. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
  51. package/.next/standalone/.next/server/app/policies/page.js +4 -4
  52. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  53. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  54. package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +2 -2
  55. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  56. package/.next/standalone/.next/server/app/project/[name]/page.js +3 -3
  57. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  58. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  59. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +2 -2
  60. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  61. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  62. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +4 -4
  63. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  64. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  65. package/.next/standalone/.next/server/app/projects/page/build-manifest.json +2 -2
  66. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  67. package/.next/standalone/.next/server/app/projects/page.js +3 -3
  68. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  69. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  70. package/.next/standalone/.next/server/app/settings/page/build-manifest.json +2 -2
  71. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +4 -15
  72. package/.next/standalone/.next/server/app/settings/page.js +4 -4
  73. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  74. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  75. package/.next/standalone/.next/server/chunks/{[externals]__0v1hz15._.js → [externals]__0-9ti7z._.js} +1 -1
  76. package/.next/standalone/.next/server/chunks/{[externals]__1nb206a._.js → [externals]__0u3wrvq._.js} +1 -1
  77. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
  78. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0rv7m0k._.js +1 -1
  79. package/.next/standalone/.next/server/chunks/[root-of-the-server]__17g9wh7._.js +1 -1
  80. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1legmza._.js +1 -1
  81. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1p-qi2t._.js +1 -1
  82. package/.next/standalone/.next/server/chunks/_0tovk6q._.js +1 -1
  83. package/.next/standalone/.next/server/chunks/_0trp3yc._.js +1 -1
  84. package/.next/standalone/.next/server/chunks/{_0otft92._.js → _1-akg6w._.js} +2 -2
  85. package/.next/standalone/.next/server/chunks/_1ek68ln._.js +10 -10
  86. package/.next/standalone/.next/server/chunks/_1vslkoe._.js +1 -1
  87. package/.next/standalone/.next/server/chunks/lib_telemetry_ts_0dctyyw._.js +1 -1
  88. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  89. package/.next/standalone/.next/server/chunks/src_hooks_fp-home_ts_09kv0bn._.js +1 -1
  90. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1fvfw59._.js → [root-of-the-server]__00-s7h8._.js} +1 -1
  91. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__013jr2b._.js +4 -0
  92. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01wy8d-._.js +4 -0
  93. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02npjtd._.js +4 -0
  94. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__12hbpha._.js → [root-of-the-server]__09flfsx._.js} +2 -2
  95. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0da85px._.js +4 -0
  96. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ftmoxc._.js +4 -0
  97. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0kokc8z._.js → [root-of-the-server]__0n_lxhg._.js} +1 -1
  98. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0oa1lav._.js +1 -1
  99. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0p-5p8u._.js +4 -0
  100. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s740oi._.js +6 -0
  101. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0dxf928._.js → [root-of-the-server]__190won8._.js} +1 -1
  102. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0n2txhk._.js → [root-of-the-server]__1ctpynv._.js} +1 -1
  103. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1p2otjt._.js +4 -0
  104. package/.next/standalone/.next/server/chunks/ssr/_02yoeej._.js +3 -0
  105. package/.next/standalone/.next/server/chunks/ssr/{_0a0zlpr._.js → _04rbnaq._.js} +2 -2
  106. package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
  107. package/.next/standalone/.next/server/chunks/ssr/{_0s-77v5._.js → _0h_douw._.js} +1 -1
  108. package/.next/standalone/.next/server/chunks/ssr/_1_ozgif._.js +1 -1
  109. package/.next/standalone/.next/server/chunks/ssr/_1ddpc5g._.js +1 -1
  110. package/.next/standalone/.next/server/chunks/ssr/_1es2j7i._.js +14 -51
  111. package/.next/standalone/.next/server/chunks/ssr/{_07t3jol._.js → _1u8-lu2._.js} +2 -2
  112. package/.next/standalone/.next/server/chunks/ssr/_1ylae7o._.js +3 -0
  113. package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +3 -0
  114. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +2 -2
  115. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +69 -1
  116. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  117. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +1 -1
  118. package/.next/standalone/.next/server/chunks/ssr/app_settings_settings-client_tsx_20lq-mq._.js +1 -1
  119. package/.next/standalone/.next/server/chunks/ssr/node_modules_13n822a._.js +1 -1
  120. package/.next/standalone/.next/server/chunks/ssr/node_modules_html-to-image_es_index_1ao30b1.js +3 -0
  121. package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +1 -1
  122. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589g._.js +1 -1
  123. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-home_ts_0je3xkv._.js +1 -1
  124. package/.next/standalone/.next/server/middleware-build-manifest.js +5 -5
  125. package/.next/standalone/.next/server/pages/404.html +1 -1
  126. package/.next/standalone/.next/server/pages/500.html +1 -1
  127. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  128. package/.next/standalone/.next/server/server-reference-manifest.json +21 -65
  129. package/.next/standalone/.next/static/chunks/{0f1pznnc7zlub.js → 0-tyrpwuc3lla.js} +1 -1
  130. package/.next/standalone/.next/static/chunks/0ca7octhod0g2.js +69 -0
  131. package/.next/standalone/.next/static/chunks/0u64yey446k4f.css +1 -0
  132. package/.next/standalone/.next/static/chunks/{1csubx9gwhsf6.js → 1j6o9min_6qa9.js} +1 -1
  133. package/.next/standalone/.next/static/chunks/27cl8h-c2w7cd.js +1 -0
  134. package/.next/standalone/.next/static/chunks/{0jpd8dv2vk930.css → 29-iqnp9v_axt.css} +1 -1
  135. package/.next/standalone/.next/static/chunks/{3uyhmm01g64k4.css → 2k9f4tyv04809.css} +0 -1
  136. package/.next/standalone/.next/static/chunks/{0t6ea7d3bkmdv.js → 2lwipmv5etc8m.js} +1 -1
  137. package/.next/standalone/.next/static/chunks/2pd16tn5qmx13.js +1 -0
  138. package/.next/standalone/.next/static/chunks/{0ntdfr2552x-h.js → 2u8vh73tpw28_.js} +1 -1
  139. package/.next/standalone/.next/static/chunks/{15uon66hpwbgg.js → 31u94yukw9fz-.js} +1 -1
  140. package/.next/standalone/.next/static/chunks/{2pwfrn6_mw-1u.js → 3p9_8x5ezce-9.js} +1 -1
  141. package/.next/standalone/.next/static/chunks/3qfoeqhy5l10x.js +1 -0
  142. package/.next/standalone/.next/static/chunks/{083fk73jtv9a0.js → 3tjji451hrgc0.js} +1 -1
  143. package/.next/standalone/.next/static/chunks/3zkg2s2vzxc3d.js +1 -0
  144. package/.next/standalone/.next/static/chunks/{turbopack-3s3c-u_u_l0u9.js → turbopack-3p73obe0xmrxm.js} +1 -1
  145. package/.next/standalone/.opencode/plugins/failproofai.mjs +27 -51
  146. package/.next/standalone/app/actions/get-scheduled-audit.ts +0 -5
  147. package/.next/standalone/app/actions/update-scheduled-audit.ts +57 -18
  148. package/.next/standalone/app/api/auth/status/route.ts +2 -3
  149. package/.next/standalone/app/audit/_components/audit-dashboard.tsx +34 -140
  150. package/.next/standalone/app/audit/_components/audit-poster.tsx +10 -29
  151. package/.next/standalone/app/audit/_components/come-back-better-section.tsx +1 -6
  152. package/.next/standalone/app/audit/_components/empty-state.tsx +1 -4
  153. package/.next/standalone/app/audit/_components/how-to-improve-section.tsx +5 -19
  154. package/.next/standalone/app/audit/_components/share-templates.ts +44 -110
  155. package/.next/standalone/app/audit/audit-styles.css +1 -132
  156. package/.next/standalone/app/settings/page.tsx +1 -1
  157. package/.next/standalone/app/settings/settings-client.tsx +45 -50
  158. package/.next/standalone/lib/auth/api-server-client.ts +5 -33
  159. package/.next/standalone/lib/claude-sessions.ts +27 -101
  160. package/.next/standalone/lib/codex-sessions.ts +4 -20
  161. package/.next/standalone/lib/copilot-sessions.ts +1 -32
  162. package/.next/standalone/lib/cursor-sessions.ts +5 -62
  163. package/.next/standalone/lib/sqlite-reader.ts +1 -78
  164. package/.next/standalone/package.json +9 -9
  165. package/.next/standalone/server.js +1 -1
  166. package/bin/failproofai.mjs +1 -1
  167. package/dist/cli.mjs +500 -1762
  168. package/dist/worker.mjs +16 -138
  169. package/lib/auth/api-server-client.ts +5 -33
  170. package/lib/claude-sessions.ts +27 -101
  171. package/lib/codex-sessions.ts +4 -20
  172. package/lib/copilot-sessions.ts +1 -32
  173. package/lib/cursor-sessions.ts +5 -62
  174. package/lib/sqlite-reader.ts +1 -78
  175. package/package.json +9 -9
  176. package/pi-extension/index.ts +5 -50
  177. package/src/audit/cache.ts +0 -6
  178. package/src/audit/cli-adapters/claude.ts +0 -13
  179. package/src/audit/cli-login.ts +1 -4
  180. package/src/audit/cli.ts +7 -157
  181. package/src/audit/harm-report.ts +4 -111
  182. package/src/audit/index.ts +15 -286
  183. package/src/audit/machine-store.ts +9 -8
  184. package/src/audit/redact-example.ts +15 -324
  185. package/src/audit/report-harm.ts +37 -33
  186. package/src/audit/report.ts +3 -6
  187. package/src/audit/schedule-cli.ts +33 -54
  188. package/src/audit/scoring.ts +0 -49
  189. package/src/audit/types.ts +0 -52
  190. package/src/hooks/builtin-policies.ts +2 -67
  191. package/src/hooks/configure-wizard.ts +0 -55
  192. package/src/hooks/fp-config.ts +48 -83
  193. package/src/hooks/fp-home.ts +0 -27
  194. package/src/hooks/integrations.ts +14 -51
  195. package/src/hooks/uninstall-cli.ts +2 -17
  196. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0-4ja6q._.js +0 -4
  197. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__00cr3xu._.js +0 -4
  198. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04px77e._.js +0 -4
  199. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0egr5s1._.js +0 -6
  200. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0i0l1di._.js +0 -4
  201. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0r3c1_0._.js +0 -4
  202. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__17xnxlx._.js +0 -4
  203. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ckpmkk._.js +0 -4
  204. package/.next/standalone/.next/server/chunks/ssr/_04ng_6x._.js +0 -3
  205. package/.next/standalone/.next/server/chunks/ssr/_0l2hi_d._.js +0 -3
  206. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_rerun-button_tsx_0blihgw._.js +0 -3
  207. package/.next/standalone/.next/static/chunks/0divvxng208xo.js +0 -1
  208. package/.next/standalone/.next/static/chunks/27du9n7ssja7l.js +0 -1
  209. package/.next/standalone/.next/static/chunks/2h_pcb1y8_jde.js +0 -1
  210. package/.next/standalone/app/actions/get-leaks.ts +0 -104
  211. package/.next/standalone/app/audit/_components/leak-section.tsx +0 -192
  212. package/src/audit/desktop-notify.ts +0 -420
  213. package/src/audit/leak-fingerprint.ts +0 -198
  214. package/src/audit/leak-record.ts +0 -248
  215. package/src/audit/leak-scan.ts +0 -268
  216. package/src/audit/leak-store.ts +0 -220
  217. package/src/audit/macos-notifier.ts +0 -298
  218. package/src/hooks/notice.ts +0 -24
  219. /package/.next/standalone/.next/static/{YiPsVpjrc-pEQoN7XvhZ0 → dg8VQ7Ejlo0Qsh_x92oIt}/_buildManifest.js +0 -0
  220. /package/.next/standalone/.next/static/{YiPsVpjrc-pEQoN7XvhZ0 → dg8VQ7Ejlo0Qsh_x92oIt}/_clientMiddlewareManifest.js +0 -0
  221. /package/.next/standalone/.next/static/{YiPsVpjrc-pEQoN7XvhZ0 → dg8VQ7Ejlo0Qsh_x92oIt}/_ssgManifest.js +0 -0
@@ -1,420 +0,0 @@
1
- /**
2
- * A desktop notification on Linux, from a process with no desktop.
3
- *
4
- * The scheduled audit runs as a child of a SYSTEM-scope service. Its
5
- * environment was measured on a real machine and carries no
6
- * `DBUS_SESSION_BUS_ADDRESS`, no `DISPLAY`, no `WAYLAND_DISPLAY` and no
7
- * `XDG_RUNTIME_DIR` — the daemon starts at boot, before anyone logs in, which
8
- * is the whole reason it survives logout.
9
- *
10
- * That turns out not to be a permission problem. `/run/user/<uid>/bus` exists
11
- * whenever the user is logged in, the daemon already runs as that uid, and
12
- * D-Bus's EXTERNAL auth is `SO_PEERCRED` — the kernel vouches for the uid, so
13
- * there is no cookie, no session membership and no environment to inherit. The
14
- * address just has to be CONSTRUCTED rather than read. Proven on a real bus
15
- * with a fully scrubbed environment: `AUTH … OK` → accepted.
16
- *
17
- * ## Awaiting the reply is the entire correctness story
18
- *
19
- * Measured: a call sent with NO_REPLY_EXPECTED against a bus with no
20
- * notification server returns exit 0, empty stdout, empty stderr — the
21
- * notification silently evaporates. The same call awaiting its reply returns a
22
- * named error. Ship the fire-and-forget variant and you get exactly the failure
23
- * this feature exists to avoid: believing the user was told.
24
- *
25
- * With the reply awaited every failure is loud and fast (10-16ms measured):
26
- * logged out → ENOENT on connect, stale socket → ECONNREFUSED, no desktop →
27
- * `org.freedesktop.DBus.Error.ServiceUnknown`.
28
- *
29
- * One failure is NOT detectable from here and must not be papered over: on a
30
- * locked screen the shell still owns the name and still returns an id, while
31
- * the user sees nothing. `Notify` succeeding means the server accepted it,
32
- * never that a human saw it — which is why the queue, not the toast, is the
33
- * record of a finding.
34
- *
35
- * No dependency: the wire protocol is written by hand rather than pulling a
36
- * D-Bus client into a package that installs into other people's machines.
37
- */
38
- import { connect, type Socket } from "node:net";
39
-
40
- /** Milliseconds allowed for the whole exchange. Generous — a live bus answers
41
- * in ~10ms, and this runs off the hook path entirely. */
42
- const TIMEOUT_MS = 2_000;
43
-
44
- export type NotifyOutcome =
45
- | { ok: true; id: number }
46
- | { ok: false; reason: "no-session" | "no-server" | "refused" | "timeout" | "error"; detail: string };
47
-
48
- /**
49
- * A D-Bus marshaller that knows where it is.
50
- *
51
- * Every alignment rule in the wire format is stated relative to the START OF
52
- * THE MESSAGE, which is why this tracks an offset instead of padding buffers in
53
- * isolation. The first version of this file did the latter — each helper padded
54
- * its own result to the boundary it needed — and it produced a header-fields
55
- * array two bytes too long, because the padding after the LAST element belongs
56
- * to the message rather than to the array. Real `dbus-daemon` dropped the
57
- * connection; the hand-written test server did not, because it mirrored the
58
- * same assumption the encoder made. The byte diff against a real client's
59
- * `Hello` is what settled it (0x70 vs 0x6e), and it is why the tests below now
60
- * run against a real bus.
61
- */
62
- class Marshal {
63
- private chunks: Buffer[] = [];
64
- private len = 0;
65
-
66
- /** Pad to the next `n`-byte boundary, counted from the start of the message. */
67
- align(n: number): void {
68
- const pad = (n - (this.len % n)) % n;
69
- if (pad) this.raw(Buffer.alloc(pad));
70
- }
71
- raw(b: Buffer): void {
72
- this.chunks.push(b);
73
- this.len += b.length;
74
- }
75
- byte(v: number): void {
76
- this.raw(Buffer.from([v]));
77
- }
78
- u32(v: number): void {
79
- this.align(4);
80
- const b = Buffer.alloc(4);
81
- b.writeUInt32LE(v, 0);
82
- this.raw(b);
83
- }
84
- i32(v: number): void {
85
- this.align(4);
86
- const b = Buffer.alloc(4);
87
- b.writeInt32LE(v, 0);
88
- this.raw(b);
89
- }
90
- /** STRING / OBJECT_PATH: 4-aligned length, bytes, NUL. */
91
- str(v: string): void {
92
- const b = Buffer.from(v, "utf8");
93
- this.u32(b.length);
94
- this.raw(b);
95
- this.byte(0);
96
- }
97
- /** SIGNATURE: a single length BYTE, bytes, NUL. No alignment. */
98
- sig(v: string): void {
99
- const b = Buffer.from(v, "utf8");
100
- this.byte(b.length);
101
- this.raw(b);
102
- this.byte(0);
103
- }
104
- get length(): number {
105
- return this.len;
106
- }
107
- buffer(): Buffer {
108
- return Buffer.concat(this.chunks);
109
- }
110
- }
111
-
112
- /** One header field: a STRUCT of (BYTE code, VARIANT value), 8-aligned. */
113
- type HeaderField = readonly [code: number, signature: string, value: string];
114
-
115
- /**
116
- * Encode the header-fields array.
117
- *
118
- * Returns the declared length SEPARATELY from the bytes, because they are not
119
- * the same number: the length stops at the end of the last element's content,
120
- * while the message then pads to 8 before the body. Conflating them is the bug
121
- * described on `Marshal`.
122
- */
123
- function headerFields(fields: readonly HeaderField[]): { data: Buffer; declaredLength: number } {
124
- // The array's data starts at message offset 16, which is already 8-aligned,
125
- // so offsets inside this marshaller line up with the real message.
126
- const m = new Marshal();
127
- for (const [code, signature, value] of fields) {
128
- m.align(8); // each STRUCT element starts on an 8-byte boundary
129
- m.byte(code);
130
- m.sig(signature);
131
- // Every header field we send is a string-like: OBJECT_PATH, STRING or
132
- // SIGNATURE. Nothing here needs a numeric variant.
133
- if (signature === "g") m.sig(value);
134
- else m.str(value);
135
- }
136
- // No trailing padding was added, so the buffer length IS the declared length.
137
- return { data: m.buffer(), declaredLength: m.length };
138
- }
139
-
140
- /** A METHOD_CALL: fixed header, header fields, 8-byte pad, then the body. */
141
- function methodCall(serial: number, fields: readonly HeaderField[], body: Buffer): Buffer {
142
- const { data, declaredLength } = headerFields(fields);
143
- const head = new Marshal();
144
- head.raw(Buffer.from([0x6c, 1, 0, 1])); // little-endian, METHOD_CALL, no flags, proto 1
145
- head.u32(body.length);
146
- head.u32(serial);
147
- head.u32(declaredLength);
148
- head.raw(data);
149
- head.align(8); // the body always starts on an 8-byte boundary
150
- return Buffer.concat([head.buffer(), body]);
151
- }
152
-
153
- /**
154
- * The arguments of `org.freedesktop.Notifications.Notify`.
155
- *
156
- * Signature `susssasa{sv}i`: app_name, replaces_id, app_icon, summary, body,
157
- * actions, hints, expire_timeout.
158
- */
159
- function notifyBody(app: string, replacesId: number, summary: string, body: string): Buffer {
160
- const m = new Marshal();
161
- m.str(app);
162
- m.u32(replacesId);
163
- m.str(""); // app_icon — none; the app name is what identifies us
164
- m.str(summary);
165
- m.str(body);
166
- m.u32(0); // actions: `as`, empty. Element alignment 4, already met.
167
- m.u32(0); // hints: `a{sv}`, empty...
168
- m.align(8); // ...and an array pads to its ELEMENT alignment even when
169
- // empty. DICT_ENTRY is 8, so this padding is required and
170
- // counts toward the body length.
171
- m.i32(-1); // expire_timeout: -1 lets the server decide
172
- return m.buffer();
173
- }
174
-
175
- /** `org.freedesktop.DBus.Hello` — mandatory before any other method call. */
176
- const HELLO_FIELDS: readonly HeaderField[] = [
177
- [1, "o", "/org/freedesktop/DBus"],
178
- [6, "s", "org.freedesktop.DBus"],
179
- [2, "s", "org.freedesktop.DBus"],
180
- [3, "s", "Hello"],
181
- ];
182
-
183
- const NOTIFY_FIELDS: readonly HeaderField[] = [
184
- [1, "o", "/org/freedesktop/Notifications"],
185
- [6, "s", "org.freedesktop.Notifications"],
186
- [2, "s", "org.freedesktop.Notifications"],
187
- [3, "s", "Notify"],
188
- [8, "g", "susssasa{sv}i"],
189
- ];
190
-
191
- /** A parsed inbound message: only what the state machine dispatches on. */
192
- interface Incoming {
193
- type: number;
194
- replySerial: number | null;
195
- errorName: string | null;
196
- bodyStart: number;
197
- bodyLength: number;
198
- total: number;
199
- }
200
-
201
- /**
202
- * Read one whole message out of `buf`, or null when it has not all arrived.
203
- *
204
- * A framed reader rather than "assume one message per chunk", because a real
205
- * bus does not oblige: it answers `Hello` with a METHOD_RETURN and then emits a
206
- * `NameAcquired` SIGNAL, and the two can land in one chunk or two. The earlier
207
- * version consumed whichever chunk came first and treated the NEXT one as its
208
- * reply — which, for a SIGNAL, is neither an error nor a return, so it reported
209
- * the notification as delivered with a garbage id.
210
- */
211
- function readMessage(buf: Buffer): Incoming | null {
212
- if (buf.length < 16) return null;
213
- const bodyLength = buf.readUInt32LE(4);
214
- const fieldsLength = buf.readUInt32LE(12);
215
- const bodyStart = 16 + fieldsLength + ((8 - (fieldsLength % 8)) % 8);
216
- const total = bodyStart + bodyLength;
217
- if (buf.length < total) return null;
218
-
219
- let replySerial: number | null = null;
220
- let errorName: string | null = null;
221
- // Walk the header fields far enough to find REPLY_SERIAL (5) and ERROR_NAME
222
- // (4). Anything unparseable stops the walk rather than throwing: a header we
223
- // cannot read is a message we ignore, not a crash on the audit's exit path.
224
- let i = 16;
225
- const end = 16 + fieldsLength;
226
- while (i < end) {
227
- i += (8 - ((i - 16) % 8)) % 8; // struct alignment, relative to the array start
228
- if (i + 4 > end) break;
229
- const code = buf[i];
230
- const sigLen = buf[i + 1];
231
- const sig = buf.toString("latin1", i + 2, i + 2 + sigLen);
232
- i += 2 + sigLen + 1;
233
- if (sig === "u") {
234
- i += (4 - ((i - 16) % 4)) % 4;
235
- if (i + 4 > end) break;
236
- if (code === 5) replySerial = buf.readUInt32LE(i);
237
- i += 4;
238
- } else if (sig === "s" || sig === "o") {
239
- i += (4 - ((i - 16) % 4)) % 4;
240
- if (i + 4 > end) break;
241
- const len = buf.readUInt32LE(i);
242
- if (i + 4 + len > end) break;
243
- if (code === 4) errorName = buf.toString("utf8", i + 4, i + 4 + len);
244
- i += 4 + len + 1;
245
- } else if (sig === "g") {
246
- const len = buf[i];
247
- i += 1 + len + 1;
248
- } else {
249
- break; // a type we do not send and cannot size; stop rather than guess
250
- }
251
- }
252
- return { type: buf[1], replySerial, errorName, bodyStart, bodyLength, total };
253
- }
254
-
255
- /**
256
- * Where this process's session bus lives.
257
- *
258
- * Two callers with opposite environments, one rule. A human running
259
- * `failproofai audit` in their terminal HAS `DBUS_SESSION_BUS_ADDRESS`, and on
260
- * an unusual setup it is the only correct answer — so it wins when it is there
261
- * and names a socket we can open. The scheduled audit, a child of a system
262
- * service started before login, has nothing: for it the address is CONSTRUCTED
263
- * from the uid, which works because D-Bus EXTERNAL auth is `SO_PEERCRED` and
264
- * the kernel supplies the identity the environment could not.
265
- *
266
- * Only the `unix:path=` form is honored. The spec allows `unix:abstract=`,
267
- * `tcp:` and semicolon-separated alternatives; a bus we cannot dial is better
268
- * reported as "no session" than dialed wrong, and the fallback below already
269
- * covers every desktop that ships systemd.
270
- */
271
- export function resolveBusPath(env: Readonly<Record<string, string | undefined>>): string | null {
272
- const declared = env.DBUS_SESSION_BUS_ADDRESS;
273
- if (declared) {
274
- for (const part of declared.split(";")) {
275
- const m = /^unix:(?:.*,)?path=([^,]+)/.exec(part.trim());
276
- if (m) return m[1];
277
- }
278
- }
279
- const uid = typeof process.getuid === "function" ? process.getuid() : -1;
280
- return uid >= 0 ? `/run/user/${uid}/bus` : null;
281
- }
282
-
283
- /**
284
- * Post a notification, awaiting the reply.
285
- *
286
- * `replacesId` reuses an earlier notification's id so repeated scans update one
287
- * bubble instead of stacking a fresh alert every time — the difference between
288
- * a reminder and a nag.
289
- */
290
- export function notifyDesktop(
291
- summary: string,
292
- body: string,
293
- replacesId = 0,
294
- opts: { socketPath?: string } = {},
295
- ): Promise<NotifyOutcome> {
296
- return new Promise((resolvePromise) => {
297
- if (process.platform !== "linux" && !opts.socketPath) {
298
- resolvePromise({ ok: false, reason: "error", detail: "linux only" });
299
- return;
300
- }
301
- const uid = typeof process.getuid === "function" ? process.getuid() : -1;
302
- const path = opts.socketPath ?? resolveBusPath(process.env as Record<string, string | undefined>);
303
- if (!path || uid < 0) {
304
- resolvePromise({ ok: false, reason: "no-session", detail: "no bus address" });
305
- return;
306
- }
307
-
308
- let settled = false;
309
- const done = (o: NotifyOutcome) => {
310
- if (settled) return;
311
- settled = true;
312
- clearTimeout(timer);
313
- try { sock.destroy(); } catch { /* already gone */ }
314
- resolvePromise(o);
315
- };
316
- const timer = setTimeout(() => done({ ok: false, reason: "timeout", detail: `${TIMEOUT_MS}ms` }), TIMEOUT_MS);
317
-
318
- let sock: Socket;
319
- try {
320
- sock = connect(path);
321
- } catch (err) {
322
- done({ ok: false, reason: "no-session", detail: String(err) });
323
- return;
324
- }
325
-
326
- let stage: "auth" | "hello" | "notify" = "auth";
327
- let buf = Buffer.alloc(0);
328
-
329
- sock.on("error", (err: NodeJS.ErrnoException) => {
330
- // ENOENT: nobody is logged in, so /run/user/<uid> does not exist.
331
- // ECONNREFUSED: a stale socket left behind by a dead bus.
332
- done({
333
- ok: false,
334
- reason: err.code === "ENOENT" || err.code === "ECONNREFUSED" ? "no-session" : "error",
335
- detail: err.code ?? String(err),
336
- });
337
- });
338
-
339
- // A bus that hangs up mid-exchange is the signature of a message it could
340
- // not parse. Without this the socket simply goes quiet and the whole thing
341
- // reports a 2-second timeout — which points at a slow desktop rather than
342
- // at us, and is exactly how the header-length bug survived its first tests.
343
- sock.on("close", () => {
344
- done({ ok: false, reason: "refused", detail: `bus closed the connection during ${stage}` });
345
- });
346
-
347
- sock.on("connect", () => {
348
- // SASL EXTERNAL: the kernel already told the bus our uid over SO_PEERCRED,
349
- // so the "credential" is just that uid in hex and no secret is exchanged.
350
- const hexUid = Buffer.from(String(uid), "utf8").toString("hex");
351
- sock.write(Buffer.concat([Buffer.from([0]), Buffer.from(`AUTH EXTERNAL ${hexUid}\r\n`, "utf8")]));
352
- });
353
-
354
- sock.on("data", (chunk: Buffer) => {
355
- buf = Buffer.concat([buf, chunk]);
356
-
357
- if (stage === "auth") {
358
- const text = buf.toString("latin1");
359
- const nl = text.indexOf("\r\n");
360
- if (nl === -1) return;
361
- if (!text.startsWith("OK")) {
362
- done({ ok: false, reason: "refused", detail: text.slice(0, nl).trim().slice(0, 80) });
363
- return;
364
- }
365
- // Anything after the OK line is already binary protocol — keep it.
366
- buf = buf.subarray(nl + 2);
367
- stage = "hello";
368
- sock.write(Buffer.from("BEGIN\r\n", "utf8"));
369
- sock.write(methodCall(1, HELLO_FIELDS, Buffer.alloc(0)));
370
- // Fall through: the reply may already be in `buf`.
371
- }
372
-
373
- // Consume whole messages only. The bus answers Hello and then emits a
374
- // NameAcquired SIGNAL; both can arrive in one chunk, and neither is
375
- // guaranteed to arrive whole.
376
- for (;;) {
377
- const msg = readMessage(buf);
378
- if (!msg) return;
379
- const frame = buf.subarray(0, msg.total);
380
- buf = buf.subarray(msg.total);
381
-
382
- // 1 = METHOD_CALL, 2 = METHOD_RETURN, 3 = ERROR, 4 = SIGNAL.
383
- if (msg.type === 4 || msg.type === 1) continue; // NameAcquired and friends
384
-
385
- if (stage === "hello") {
386
- if (msg.type === 3) {
387
- done({ ok: false, reason: "refused", detail: msg.errorName ?? "Hello failed" });
388
- return;
389
- }
390
- if (msg.replySerial !== 1) continue;
391
- stage = "notify";
392
- sock.write(methodCall(2, NOTIFY_FIELDS, notifyBody("failproofai", replacesId, summary, body)));
393
- continue;
394
- }
395
-
396
- // Only OUR call's reply settles this. Matching on serial rather than on
397
- // "the next message" is what keeps an unrelated signal from being read
398
- // as a successful delivery.
399
- if (msg.replySerial !== 2) continue;
400
-
401
- if (msg.type === 3) {
402
- const name = msg.errorName ?? "";
403
- // The common one: logged in, but nothing is drawing notifications.
404
- const noServer = name.includes("ServiceUnknown") || name.includes("NameHasNoOwner");
405
- done({ ok: false, reason: noServer ? "no-server" : "error", detail: name || "error" });
406
- return;
407
- }
408
-
409
- // METHOD_RETURN. The body is a single UINT32 id — read it back so a
410
- // future call could replace this bubble rather than stack a new one.
411
- const id =
412
- msg.bodyLength >= 4 && frame.length >= msg.bodyStart + 4
413
- ? frame.readUInt32LE(msg.bodyStart)
414
- : 0;
415
- done({ ok: true, id });
416
- return;
417
- }
418
- });
419
- });
420
- }
@@ -1,198 +0,0 @@
1
- /**
2
- * How a leaked credential is NAMED once it leaves the machine.
3
- *
4
- * Every other redactor in this codebase exists to destroy a value. This one
5
- * exists to describe one, because the leak report has the opposite job: a
6
- * finding the reader cannot tie to a row in a vendor console is a finding they
7
- * cannot act on. `[REDACTED: GitHub token]` tells you nothing you can revoke.
8
- *
9
- * The rule is: **enough to identify, never enough to use.**
10
- *
11
- * ## Why the mask character matters, which is not obvious
12
- *
13
- * The three most natural masking characters — `X`, `x` and `0` — RE-MATCH every
14
- * vendor prefix pattern we ship. `AKIA` + sixteen `X`s satisfies
15
- * `AKIA[A-Z0-9]{16}`, so a masked AWS key is reported by failproofai as a live
16
- * AWS key on the next scan. The same holds for a masked connection string,
17
- * whose pattern keys on `://…@` and does not care what sits between. So the
18
- * mask is `•` (U+2022), which is in no credential charset anywhere, and the
19
- * unit tests assert that every masked form fails to re-match.
20
- *
21
- * That is not a hypothetical: this product measured its own audit output
22
- * feeding back into its own corpus, one leaked credential becoming seven
23
- * reported findings across four sessions.
24
- *
25
- * ## Why the last four, and why not always
26
- *
27
- * Last-4 is what vendor consoles display next to a key, so it is the token that
28
- * lets a human find the right row and press revoke. But it is only safe when
29
- * what remains is genuinely unguessable. For a 40-character GitHub token,
30
- * showing `ghp_` and four trailing characters leaves ~32 unknown base62
31
- * characters — no help to anyone. For a 13-character human-chosen password,
32
- * last-4 is nearly a third of the secret, and the corpus's one confirmed-live
33
- * password was exactly 13 characters at 3.19 bits per character.
34
- *
35
- * So the tail is shown only when the value clears BOTH bars: a recognised
36
- * public vendor prefix (which proves it is a minted token, not a typed
37
- * password) AND enough residual length. Everything else is described by class
38
- * and length alone.
39
- */
40
-
41
- /** The mask glyph. Never `X`, `x` or `0` — see the module header. */
42
- const MASK_CHAR = "•";
43
-
44
- /** Longest run of mask glyphs rendered, so a 200-char token stays readable. */
45
- const MAX_MASK_RUN = 8;
46
-
47
- /**
48
- * Minimum total length before any trailing characters are shown.
49
- *
50
- * 24 is chosen so that even the shortest minted token we recognise keeps well
51
- * over 100 bits of residual entropy after the prefix and tail are removed.
52
- * Human-chosen passwords essentially never reach it, which is the point.
53
- */
54
- const MIN_LEN_FOR_TAIL = 24;
55
-
56
- /** How many trailing characters a vendor console typically shows. */
57
- const TAIL_CHARS = 4;
58
-
59
- /**
60
- * Public, minted prefixes — the part of a credential that carries no secret.
61
- *
62
- * Each of these is published by its issuer and identical across every key they
63
- * mint, so showing it discloses nothing while telling the reader which console
64
- * to open. Ordered longest-first, so `sk-ant-api03-` wins over `sk-ant-` and
65
- * `sk-`; the first match is the one rendered.
66
- *
67
- * Deliberately NOT the full detection vocabulary. This list only has to be
68
- * right about what is safe to PRINT — a prefix missing from it costs a less
69
- * specific label, never a disclosure.
70
- */
71
- const PUBLIC_PREFIXES: ReadonlyArray<readonly [string, string]> = [
72
- ["sk-ant-api03-", "Anthropic API key"],
73
- ["sk-ant-oat01-", "Anthropic OAuth token"],
74
- ["sk-ant-", "Anthropic key"],
75
- ["sk-proj-", "OpenAI project key"],
76
- ["sk-svcacct-", "OpenAI service-account key"],
77
- ["sk-admin-", "OpenAI admin key"],
78
- ["sk-or-v1-", "OpenRouter key"],
79
- ["github_pat_", "GitHub fine-grained token"],
80
- ["ghp_", "GitHub personal access token"],
81
- ["gho_", "GitHub OAuth token"],
82
- ["ghu_", "GitHub user-to-server token"],
83
- ["ghs_", "GitHub server-to-server token"],
84
- ["ghr_", "GitHub refresh token"],
85
- ["glpat-", "GitLab personal access token"],
86
- ["xoxb-", "Slack bot token"],
87
- ["xoxp-", "Slack user token"],
88
- ["xapp-", "Slack app-level token"],
89
- ["sk_live_", "Stripe live secret key"],
90
- ["sk_test_", "Stripe test secret key"],
91
- ["rk_live_", "Stripe restricted key"],
92
- ["AKIA", "AWS access key ID"],
93
- ["ASIA", "AWS temporary access key ID"],
94
- ["AIza", "Google API key"],
95
- ["hf_", "Hugging Face token"],
96
- ["gsk_", "Groq API key"],
97
- ["r8_", "Replicate token"],
98
- ["xai-", "xAI key"],
99
- ["npm_", "npm access token"],
100
- ["dop_v1_", "DigitalOcean token"],
101
- ["shpat_", "Shopify access token"],
102
- ["sbp_", "Supabase token"],
103
- ];
104
-
105
- export interface LeakFingerprint {
106
- /** What the reader sees: `ghp_••••••••4f2a` or `[13-char password]`. */
107
- display: string;
108
- /** Human label for the class, e.g. "GitHub personal access token". */
109
- label: string;
110
- /** Total character length of the original value. */
111
- length: number;
112
- /** True when a minted public prefix was recognised — i.e. we can name a
113
- * console to revoke at. False means "we know it leaked, not who issued it". */
114
- attributed: boolean;
115
- }
116
-
117
- /** The recognised public prefix for a value, longest match first. */
118
- function matchPublicPrefix(value: string): readonly [string, string] | null {
119
- for (const entry of PUBLIC_PREFIXES) {
120
- if (value.startsWith(entry[0])) return entry;
121
- }
122
- return null;
123
- }
124
-
125
- /**
126
- * Render a credential as something a human can recognise and nobody can use.
127
- *
128
- * `fallbackLabel` names the class when no public prefix is recognised — pass
129
- * the detecting rule's own label (e.g. "assigned secret", "database password")
130
- * so an unattributed finding still says what KIND of thing leaked.
131
- */
132
- export function fingerprintSecret(value: string, fallbackLabel = "secret"): LeakFingerprint {
133
- const length = value.length;
134
- const matched = matchPublicPrefix(value);
135
-
136
- if (!matched) {
137
- // No minted prefix: this is a password, a first-party key, or a format we
138
- // do not recognise. Nothing but the class and the length is safe to print,
139
- // and the length is what lets the owner recognise their own value.
140
- return {
141
- display: `[${length}-char ${fallbackLabel}]`,
142
- label: fallbackLabel,
143
- length,
144
- attributed: false,
145
- };
146
- }
147
-
148
- const [prefix, label] = matched;
149
- const bodyLength = length - prefix.length;
150
- const run = MASK_CHAR.repeat(Math.max(1, Math.min(MAX_MASK_RUN, bodyLength)));
151
-
152
- // The tail is the actionable half — but only when what stays hidden is
153
- // genuinely unguessable. See the module header.
154
- if (length >= MIN_LEN_FOR_TAIL && bodyLength > TAIL_CHARS * 2) {
155
- return {
156
- display: `${prefix}${run}${value.slice(-TAIL_CHARS)}`,
157
- label,
158
- length,
159
- attributed: true,
160
- };
161
- }
162
-
163
- return { display: `${prefix}${run}`, label, length, attributed: true };
164
- }
165
-
166
- /**
167
- * A stable identity for one distinct credential, for deduping across scans.
168
- *
169
- * Salted with a per-machine secret so the digest cannot be used as a lookup
170
- * oracle: without the salt, an attacker holding a guessable candidate (and the
171
- * corpus is full of dictionary passwords) could confirm it by comparing
172
- * hashes. With it, the id is meaningful only on the machine that produced it.
173
- *
174
- * The salt therefore forecloses cross-machine dedupe until an org-scoped key
175
- * exists — a deliberate trade recorded here so nobody reads a per-machine salt
176
- * as an oversight.
177
- */
178
- /**
179
- * Every id `fingerprintId` can produce, and nothing else.
180
- *
181
- * Ids become FILENAMES for pending macOS banners, so an id is a path component,
182
- * and a path component built from unvalidated input is a directory traversal.
183
- *
184
- * Ids are HMAC hex today, so nothing in a normal run can carry a `/`. That is
185
- * not the same as safe: the record is JSON read off disk, and `leaks.json` is a
186
- * file a user (or anything running as them) can edit. The guard is one regex
187
- * and it makes the property structural instead of incidental.
188
- */
189
- export function isFindingId(id: string): boolean {
190
- return /^[0-9a-f]{16}$/.test(id);
191
- }
192
-
193
- export function fingerprintId(value: string, machineSalt: string): string {
194
- // Lazily required so this module stays importable in the browser bundle,
195
- // where the dashboard renders findings but never computes ids.
196
- const { createHmac } = require("node:crypto") as typeof import("node:crypto");
197
- return createHmac("sha256", machineSalt).update(value).digest("hex").slice(0, 16);
198
- }