failproofai 1.0.4-beta.2 → 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.
Files changed (139) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +3 -3
  3. package/.next/standalone/.next/prerender-manifest.json +3 -3
  4. package/.next/standalone/.next/required-server-files.json +1 -1
  5. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  6. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  7. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  10. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +6 -6
  11. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  12. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  13. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  14. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  15. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  16. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  17. package/.next/standalone/.next/server/app/_not-found.rsc +14 -14
  18. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +14 -14
  19. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +13 -13
  20. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  21. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  22. package/.next/standalone/.next/server/app/api/audit/run/route.js +1 -1
  23. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  24. package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
  25. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  26. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  28. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  30. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +5 -5
  31. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  32. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  33. package/.next/standalone/.next/server/app/index.html +1 -1
  34. package/.next/standalone/.next/server/app/index.rsc +14 -14
  35. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +13 -13
  36. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +14 -14
  37. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  38. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  39. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  40. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  41. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
  42. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  43. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  44. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  45. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  46. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  47. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  48. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  49. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  50. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  51. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  52. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  53. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  54. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +4 -4
  55. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  56. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  57. package/.next/standalone/.next/server/chunks/{[externals]__1lh7m5d._.js → [externals]__0v1hz15._.js} +1 -1
  58. package/.next/standalone/.next/server/chunks/{[externals]__1rqkg_y._.js → [externals]__1nb206a._.js} +1 -1
  59. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0--lkk6._.js +1 -1
  60. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
  61. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0q0qzx1._.js +1 -1
  62. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1le9jqc._.js +1 -1
  63. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1p-qi2t._.js +1 -1
  64. package/.next/standalone/.next/server/chunks/{_08w6xzm._.js → _0otft92._.js} +2 -2
  65. package/.next/standalone/.next/server/chunks/_0tovk6q._.js +1 -1
  66. package/.next/standalone/.next/server/chunks/_0trp3yc._.js +1 -1
  67. package/.next/standalone/.next/server/chunks/_1ek68ln._.js +3 -3
  68. package/.next/standalone/.next/server/chunks/_1vslkoe._.js +1 -1
  69. package/.next/standalone/.next/server/chunks/lib_factory-projects_ts_1eo_bk-._.js +1 -1
  70. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  71. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__013jr2b._.js +2 -2
  72. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01wy8d-._.js +2 -2
  73. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02npjtd._.js +2 -2
  74. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0da85px._.js +2 -2
  75. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ftmoxc._.js +2 -2
  76. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0oa1lav._.js +1 -1
  77. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0p-5p8u._.js +2 -2
  78. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s740oi._.js +2 -2
  79. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0yl9wb5._.js +3 -0
  80. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1p2otjt._.js +2 -2
  81. package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
  82. package/.next/standalone/.next/server/chunks/ssr/_0guxnh-._.js +23 -0
  83. package/.next/standalone/.next/server/chunks/ssr/_0oq1dh6._.js +1 -1
  84. package/.next/standalone/.next/server/chunks/ssr/_1_ozgif._.js +1 -1
  85. package/.next/standalone/.next/server/chunks/ssr/_1es2j7i._.js +13 -0
  86. package/.next/standalone/.next/server/chunks/ssr/_1u8-lu2._.js +1 -1
  87. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +2 -2
  88. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
  89. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  90. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +1 -1
  91. package/.next/standalone/.next/server/chunks/ssr/app_settings_settings-client_tsx_20lq-mq._.js +1 -1
  92. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  93. package/.next/standalone/.next/server/pages/404.html +1 -1
  94. package/.next/standalone/.next/server/pages/500.html +1 -1
  95. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  96. package/.next/standalone/.next/server/server-reference-manifest.json +22 -22
  97. package/.next/standalone/.next/static/chunks/{04r6ch8uf_n8m.js → 04o5zfl7p5lhx.js} +1 -1
  98. package/.next/standalone/.next/static/chunks/{0o-hh5_turzlz.css → 0jpd8dv2vk930.css} +1 -1
  99. package/.next/standalone/.next/static/chunks/{2ej3b8gk5ittu.js → 0po8n4tyotjf2.js} +1 -1
  100. package/.next/standalone/.next/static/chunks/0qx7gav1c2_vu.js +1 -0
  101. package/.next/standalone/.next/static/chunks/{2aquitk72k2op.js → 17tfki9x5rd_7.js} +1 -1
  102. package/.next/standalone/.next/static/chunks/{010bv1w6j171t.js → 1i-i0pxg9q_8y.js} +1 -1
  103. package/.next/standalone/.next/static/chunks/{32spub4wqjem-.js → 2bxr2_2h4kaqq.js} +1 -1
  104. package/.next/standalone/.next/static/chunks/{0wz8yftk18ts2.js → 323cyb3a2s06y.js} +1 -1
  105. package/.next/standalone/.next/static/chunks/{1pb1oztsbwcss.js → 364cqbpeg1y5i.js} +1 -1
  106. package/.next/standalone/.next/static/chunks/{3m4upvybtrexd.js → 38dqur43qthrl.js} +1 -1
  107. package/.next/standalone/.next/static/chunks/{2bi_1y0a_smt7.js → 42gwu2bsz4ske.js} +2 -2
  108. package/.next/standalone/app/actions/get-leaks.ts +10 -1
  109. package/.next/standalone/app/audit/_components/audit-dashboard.tsx +29 -2
  110. package/.next/standalone/app/audit/_components/leak-section.tsx +15 -2
  111. package/.next/standalone/app/audit/audit-styles.css +18 -0
  112. package/.next/standalone/lib/sqlite-reader.ts +78 -1
  113. package/.next/standalone/package.json +10 -10
  114. package/.next/standalone/server.js +1 -1
  115. package/bin/failproofai.mjs +1 -1
  116. package/dist/cli.mjs +397 -225
  117. package/dist/worker.mjs +81 -35
  118. package/lib/sqlite-reader.ts +78 -1
  119. package/package.json +10 -10
  120. package/pi-extension/index.ts +19 -4
  121. package/src/audit/cache.ts +6 -0
  122. package/src/audit/cli-adapters/claude.ts +13 -0
  123. package/src/audit/cli-login.ts +4 -1
  124. package/src/audit/cli.ts +84 -59
  125. package/src/audit/index.ts +149 -8
  126. package/src/audit/leak-notice.ts +99 -1
  127. package/src/audit/leak-scan.ts +20 -44
  128. package/src/audit/leak-store.ts +49 -1
  129. package/src/audit/redact-example.ts +60 -5
  130. package/src/audit/types.ts +6 -0
  131. package/src/hooks/handler.ts +30 -10
  132. package/src/hooks/integrations.ts +14 -0
  133. package/src/hooks/notice.ts +45 -9
  134. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0rgu2r3._.js +0 -3
  135. package/.next/standalone/.next/server/chunks/ssr/_0-oij9d._.js +0 -23
  136. package/.next/standalone/.next/static/chunks/0zebh1n9jkfbt.js +0 -1
  137. /package/.next/standalone/.next/static/{aKNv4Kmv98Xpwns0Xa-C- → P6icI2V2PvVuePJrqjHCr}/_buildManifest.js +0 -0
  138. /package/.next/standalone/.next/static/{aKNv4Kmv98Xpwns0Xa-C- → P6icI2V2PvVuePJrqjHCr}/_clientMiddlewareManifest.js +0 -0
  139. /package/.next/standalone/.next/static/{aKNv4Kmv98Xpwns0Xa-C- → P6icI2V2PvVuePJrqjHCr}/_ssgManifest.js +0 -0
package/dist/cli.mjs CHANGED
@@ -29,7 +29,7 @@ var __esm = (fn, res, err) => () => {
29
29
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
30
30
 
31
31
  // package.json
32
- var version2 = "1.0.4-beta.2", package_default;
32
+ var version2 = "1.0.4-beta.4", package_default;
33
33
  var init_package = __esm(() => {
34
34
  package_default = {
35
35
  name: "failproofai",
@@ -150,20 +150,20 @@ var init_package = __esm(() => {
150
150
  vite: "8.0.16",
151
151
  undici: "7.29.0",
152
152
  "brace-expansion": "5.0.9",
153
- sharp: "0.35.0",
153
+ sharp: "0.35.4",
154
154
  browserslist: "4.28.8"
155
155
  },
156
156
  optionalDependencies: {
157
- "@failproofai/failproofaid-linux-x64": "1.0.4-beta.2",
158
- "@failproofai/failproofaid-linux-arm64": "1.0.4-beta.2",
159
- "@failproofai/failproofaid-darwin-x64": "1.0.4-beta.2",
160
- "@failproofai/failproofaid-darwin-arm64": "1.0.4-beta.2"
157
+ "@failproofai/failproofaid-linux-x64": "1.0.4-beta.4",
158
+ "@failproofai/failproofaid-linux-arm64": "1.0.4-beta.4",
159
+ "@failproofai/failproofaid-darwin-x64": "1.0.4-beta.4",
160
+ "@failproofai/failproofaid-darwin-arm64": "1.0.4-beta.4"
161
161
  },
162
162
  failproofaidBinaries: {
163
- "linux-x64": "3039575242ff240c68a79c4ebd4e0e35e865da99c6d8a46d1a17b2d7962cb47b",
164
- "linux-arm64": "02fe993dca0faccb528c48472ca1d637274c562992ba7da1bc8d439103dbcc13",
165
- "darwin-x64": "2be5a422007a773253611f24f2ad9e2a301f075392ed7bc2dc0f6354d3186696",
166
- "darwin-arm64": "5882a4acd7317289346934290ac8277ca79edadb009e284c9fc0b23b74d321d7"
163
+ "linux-x64": "4487790ed9b881da2914b5985a23b5adca1f20e226d71c635d8170064e6b2340",
164
+ "linux-arm64": "c903c5e201198213ccd8295fdab741c7d08d22c8dd5c276e52aa766595bedcc2",
165
+ "darwin-x64": "f0933584abbf0f9ff95cd153f8c6c704e467af1d6ffc8f42f9c426bd44b1142a",
166
+ "darwin-arm64": "387a3acbf13f667a557c8ac632fe67dee350cda078e3266e71edfcfacb9ecae6"
167
167
  }
168
168
  };
169
169
  });
@@ -11810,6 +11810,19 @@ async function applyDecision(result, ctx, eventName) {
11810
11810
  if (out && out.decision && out.decision.behavior === "deny") {
11811
11811
  throw new Error((out.decision.message) || "Blocked by failproofai");
11812
11812
  }
11813
+ const notice = parsed.failproofaiNotice;
11814
+ if (typeof notice === "string" && notice && ctx && ctx.client && ctx.client.tui) {
11815
+ try {
11816
+ await ctx.client.tui.showToast({
11817
+ body: {
11818
+ title: "failproofai audit",
11819
+ message: notice,
11820
+ variant: "warning",
11821
+ duration: 12_000,
11822
+ },
11823
+ });
11824
+ } catch { /* a courtesy notice must never break the host hook */ }
11825
+ }
11813
11826
  // Forward additional context as a prompt to the session. For Stop /
11814
11827
  // SubagentStop the prompt is the only force-retry channel (session.idle
11815
11828
  // already fired), so AWAIT to ensure the SDK round-trip completes before
@@ -16510,6 +16523,20 @@ function shapeNotice(cli, notice, existingStdout = "") {
16510
16523
  return empty;
16511
16524
  return { stdout: text + `
16512
16525
  `, stderr: "" };
16526
+ case "shim": {
16527
+ if (existingStdout.trim().startsWith("{")) {
16528
+ try {
16529
+ const parsed = JSON.parse(existingStdout);
16530
+ parsed.failproofaiNotice = text;
16531
+ return { stdout: JSON.stringify(parsed), stderr: "" };
16532
+ } catch {
16533
+ return empty;
16534
+ }
16535
+ }
16536
+ if (existingStdout.trim())
16537
+ return empty;
16538
+ return { stdout: JSON.stringify({ failproofaiNotice: text }), stderr: "" };
16539
+ }
16513
16540
  default:
16514
16541
  return empty;
16515
16542
  }
@@ -16518,9 +16545,15 @@ function canDeliverNotice(cli) {
16518
16545
  return !!cli && (NOTICE_CHANNEL[cli] ?? "none") !== "none";
16519
16546
  }
16520
16547
  function leakNoticeText(newFindings) {
16521
- const n = newFindings === 1 ? "a credential" : `${newFindings} credentials`;
16548
+ const n = newFindings === 1 ? "a possible credential exposure" : `${newFindings} possible credential exposures`;
16522
16549
  return `failproofai found ${n} in this project's agent transcripts.
16523
- ` + "Run `failproofai audit` to see the details, " + "or `failproofai audit --schedule` to get these by email.";
16550
+ ` + "Run `failproofai audit` to review the matches and rotate anything real.";
16551
+ }
16552
+ function desktopLeakNotice() {
16553
+ return {
16554
+ title: "failproofai audit needs review",
16555
+ body: "Possible credential exposure was found in your agent transcripts. " + "Run failproofai audit to verify the matches and rotate anything real."
16556
+ };
16524
16557
  }
16525
16558
  var NOTICE_CHANNEL, MAX_NOTICE_CHARS = 900;
16526
16559
  var init_notice = __esm(() => {
@@ -16528,7 +16561,9 @@ var init_notice = __esm(() => {
16528
16561
  claude: "systemMessage",
16529
16562
  codex: "systemMessage",
16530
16563
  copilot: "stderr",
16531
- factory: "stdout"
16564
+ factory: "stdout",
16565
+ opencode: "shim",
16566
+ pi: "shim"
16532
16567
  };
16533
16568
  });
16534
16569
 
@@ -16732,7 +16767,11 @@ function readJson(path) {
16732
16767
  function readLeakIdentity(home) {
16733
16768
  const existing = readJson(auditLeakIdentityFile(home));
16734
16769
  if (existing && typeof existing.salt === "string" && existing.salt.length >= 32) {
16735
- return { salt: existing.salt, dismissed: existing.dismissed ?? [] };
16770
+ return {
16771
+ salt: existing.salt,
16772
+ dismissed: existing.dismissed ?? [],
16773
+ reportViewedAt: typeof existing.reportViewedAt === "number" && Number.isFinite(existing.reportViewedAt) ? existing.reportViewedAt : undefined
16774
+ };
16736
16775
  }
16737
16776
  const minted = { salt: randomBytes2(32).toString("hex"), dismissed: [] };
16738
16777
  writeLeakIdentity(minted, home);
@@ -16814,16 +16853,6 @@ var init_leak_store = __esm(() => {
16814
16853
  // src/audit/leak-notice.ts
16815
16854
  import { existsSync as existsSync13, mkdirSync as mkdirSync9, readdirSync as readdirSync5, statSync as statSync8, writeFileSync as writeFileSync7, rmSync as rmSync4 } from "node:fs";
16816
16855
  import { resolve as resolve11 } from "node:path";
16817
- function pendingLeakNotice(home, channel = "cli") {
16818
- try {
16819
- const dir = NOTICE_DIR(home, channel);
16820
- const claimed = new Set(existsSync13(dir) ? readdirSync5(dir) : []);
16821
- const ids = activeFindings(readLeakRecord(home), home).map((f) => f.id).filter((id) => !claimed.has(id));
16822
- return { ids, count: ids.length };
16823
- } catch {
16824
- return { ids: [], count: 0 };
16825
- }
16826
- }
16827
16856
  function markLeakNoticeDelivered(ids, home, channel = "cli") {
16828
16857
  const won = [];
16829
16858
  try {
@@ -16840,6 +16869,48 @@ function markLeakNoticeDelivered(ids, home, channel = "cli") {
16840
16869
  } catch {}
16841
16870
  return won;
16842
16871
  }
16872
+ function releaseLeakNoticeClaims(ids, home, channel = "cli") {
16873
+ try {
16874
+ const dir = NOTICE_DIR(home, channel);
16875
+ for (const id of ids) {
16876
+ if (!isFindingId(id))
16877
+ continue;
16878
+ rmSync4(resolve11(dir, id), { force: true });
16879
+ }
16880
+ } catch {}
16881
+ }
16882
+ function sessionNoticePending(sessionId, home) {
16883
+ try {
16884
+ if (!sessionId || !/^[\w.:-]{1,200}$/.test(sessionId))
16885
+ return { ids: [], count: 0 };
16886
+ const dir = resolve11(auditDir(home), "notified-sessions");
16887
+ if (existsSync13(resolve11(dir, sessionId)))
16888
+ return { ids: [], count: 0 };
16889
+ const findings = activeFindings(readLeakRecord(home), home);
16890
+ if (findings.length === 0)
16891
+ return { ids: [], count: 0 };
16892
+ const viewedAt = readLeakIdentity(home).reportViewedAt ?? 0;
16893
+ const unviewed = findings.filter((f) => {
16894
+ const seen = Date.parse(f.firstSeen);
16895
+ return !Number.isFinite(seen) || seen > viewedAt;
16896
+ });
16897
+ return { ids: unviewed.map((f) => f.id), count: unviewed.length };
16898
+ } catch {
16899
+ return { ids: [], count: 0 };
16900
+ }
16901
+ }
16902
+ function markSessionNoticed(sessionId, home) {
16903
+ try {
16904
+ if (!sessionId || !/^[\w.:-]{1,200}$/.test(sessionId))
16905
+ return false;
16906
+ const dir = resolve11(auditDir(home), "notified-sessions");
16907
+ mkdirSync9(dir, { recursive: true });
16908
+ writeFileSync7(resolve11(dir, sessionId), "", { flag: "wx", mode: 384 });
16909
+ return true;
16910
+ } catch {
16911
+ return false;
16912
+ }
16913
+ }
16843
16914
  var NOTICE_DIR = (home, channel = "cli") => resolve11(auditDir(home), channel === "cli" ? "notified" : `notified-${channel}`), MARKER_TTL_MS;
16844
16915
  var init_leak_notice = __esm(() => {
16845
16916
  init_fp_home();
@@ -17542,8 +17613,21 @@ var init_cursor_projects = __esm(() => {
17542
17613
  // lib/sqlite-reader.ts
17543
17614
  import { existsSync as existsSync14, readFileSync as readFileSync9 } from "node:fs";
17544
17615
  import initSqlJs from "sql.js/dist/sql-asm.js";
17616
+ function silenceSqliteExperimentalWarning() {
17617
+ if (warningPatched)
17618
+ return;
17619
+ warningPatched = true;
17620
+ const original = process.emitWarning.bind(process);
17621
+ process.emitWarning = (warning, ...rest) => {
17622
+ const text = typeof warning === "string" ? warning : String(warning?.message ?? "");
17623
+ if (text.includes("SQLite is an experimental feature"))
17624
+ return;
17625
+ return original(warning, ...rest);
17626
+ };
17627
+ }
17545
17628
  async function tryNodeSqlite(dbPath) {
17546
17629
  try {
17630
+ silenceSqliteExperimentalWarning();
17547
17631
  const mod = await import("node:sqlite");
17548
17632
  const db = new mod.DatabaseSync(dbPath, { readOnly: true });
17549
17633
  return {
@@ -17564,10 +17648,21 @@ function loadSqlJs() {
17564
17648
  sqlPromise = initSqlJs();
17565
17649
  return sqlPromise;
17566
17650
  }
17651
+ function clearWalFlag(bytes) {
17652
+ if (bytes.length < 20)
17653
+ return bytes;
17654
+ if (bytes.toString("latin1", 0, 16) !== SQLITE_MAGIC)
17655
+ return bytes;
17656
+ if (bytes[18] === 1 && bytes[19] === 1)
17657
+ return bytes;
17658
+ bytes[18] = 1;
17659
+ bytes[19] = 1;
17660
+ return bytes;
17661
+ }
17567
17662
  async function trySqlJs(dbPath) {
17568
17663
  try {
17569
17664
  const SQL = await loadSqlJs();
17570
- const db = new SQL.Database(readFileSync9(dbPath));
17665
+ const db = new SQL.Database(clearWalFlag(readFileSync9(dbPath)));
17571
17666
  return {
17572
17667
  query(sql, params) {
17573
17668
  const stmt = db.prepare(sql);
@@ -17595,7 +17690,7 @@ async function openSqliteReadonly(dbPath) {
17595
17690
  return null;
17596
17691
  return await tryNodeSqlite(dbPath) ?? await trySqlJs(dbPath);
17597
17692
  }
17598
- var sqlPromise = null;
17693
+ var warningPatched = false, sqlPromise = null, SQLITE_MAGIC = "SQLite format 3\x00";
17599
17694
  var init_sqlite_reader = () => {};
17600
17695
 
17601
17696
  // lib/opencode-db.ts
@@ -23012,22 +23107,22 @@ function canonicalizeEventType(raw, cli) {
23012
23107
  }
23013
23108
  return raw;
23014
23109
  }
23015
- function attachLeakNotice(eventType, cli, outcome) {
23110
+ function attachLeakNotice(eventType, cli, outcome, sessionId) {
23016
23111
  try {
23017
23112
  if (!canDeliverNotice(cli))
23018
23113
  return outcome;
23019
23114
  if (!cli)
23020
23115
  return outcome;
23021
23116
  const canonical = canonicalizeEventType(eventType, cli);
23022
- if (canonical !== "Stop" && canonical !== "SessionStart")
23117
+ if (canonical !== "Stop")
23023
23118
  return outcome;
23024
- const pending = pendingLeakNotice();
23119
+ const pending = sessionNoticePending(sessionId ?? "");
23025
23120
  if (pending.count === 0)
23026
23121
  return outcome;
23027
23122
  const shaped = shapeNotice(cli, leakNoticeText(pending.count), outcome.stdout);
23028
23123
  if (shaped.stdout === outcome.stdout && !shaped.stderr)
23029
23124
  return outcome;
23030
- markLeakNoticeDelivered(pending.ids);
23125
+ markSessionNoticed(sessionId ?? "");
23031
23126
  return {
23032
23127
  exitCode: outcome.exitCode,
23033
23128
  stdout: shaped.stdout,
@@ -23339,11 +23434,7 @@ async function evaluateHookEvent(eventType, cli = "claude", stdinPayload, opts)
23339
23434
  }
23340
23435
  } catch {}
23341
23436
  }
23342
- const withNotice = attachLeakNotice(eventType, cli, {
23343
- exitCode: result.exitCode,
23344
- stdout: result.stdout,
23345
- stderr: result.stderr
23346
- });
23437
+ const withNotice = attachLeakNotice(eventType, cli, { exitCode: result.exitCode, stdout: result.stdout, stderr: result.stderr }, sessionId);
23347
23438
  return withNotice;
23348
23439
  } finally {
23349
23440
  if (opts?.awaitTelemetryFlush ?? true) {
@@ -26241,6 +26332,7 @@ var init_shared = __esm(() => {
26241
26332
 
26242
26333
  // src/audit/cli-adapters/claude.ts
26243
26334
  import { readFile as readFile15, open as open2 } from "node:fs/promises";
26335
+ import { readFileSync as readFileSync26 } from "node:fs";
26244
26336
  async function listClaudeTranscriptMetadata(opts = {}) {
26245
26337
  const projectFilter = opts.projects ? new Set(opts.projects) : null;
26246
26338
  const sinceMs = opts.sinceMs ?? 0;
@@ -26257,13 +26349,22 @@ async function listClaudeTranscriptMetadata(opts = {}) {
26257
26349
  for (const t of transcripts) {
26258
26350
  if (t.mtimeMs < sinceMs)
26259
26351
  continue;
26352
+ let sessionDescription;
26353
+ if (t.isSubagent) {
26354
+ try {
26355
+ const meta = JSON.parse(readFileSync26(t.transcriptPath.replace(/\.jsonl$/, ".meta.json"), "utf8"));
26356
+ if (typeof meta.description === "string")
26357
+ sessionDescription = meta.description;
26358
+ } catch {}
26359
+ }
26260
26360
  out.push({
26261
26361
  cli: "claude",
26262
26362
  projectName: project.name,
26263
26363
  sessionId: t.sessionId,
26264
26364
  transcriptPath: t.transcriptPath,
26265
26365
  mtimeMs: t.mtimeMs,
26266
- sizeBytes: t.sizeBytes
26366
+ sizeBytes: t.sizeBytes,
26367
+ sessionDescription
26267
26368
  });
26268
26369
  }
26269
26370
  }
@@ -27493,6 +27594,106 @@ var init_features = __esm(() => {
27493
27594
  })();
27494
27595
  });
27495
27596
 
27597
+ // src/audit/leak-scan.ts
27598
+ function unquote(v) {
27599
+ return v.startsWith('"') && v.endsWith('"') || v.startsWith("'") && v.endsWith("'") ? v.slice(1, -1) : v;
27600
+ }
27601
+ function isDocsLiteral(value) {
27602
+ const { createHash } = __require("node:crypto");
27603
+ return DOCS_LITERALS.has(createHash("sha256").update(value).digest("hex"));
27604
+ }
27605
+ function isSelfOutput(text) {
27606
+ return SELF_OUTPUT_MARKERS.some((m) => text.includes(m));
27607
+ }
27608
+ function literalPrefixOf(source) {
27609
+ let out = "";
27610
+ for (let i = 0;i < source.length; i++) {
27611
+ const c = source[i];
27612
+ if (c === "\\") {
27613
+ const next = source[i + 1];
27614
+ if (next === undefined)
27615
+ break;
27616
+ if (/[dwsSDWbB]/.test(next))
27617
+ break;
27618
+ out += next;
27619
+ i++;
27620
+ continue;
27621
+ }
27622
+ if ("[](){}|?*+^$.".includes(c))
27623
+ break;
27624
+ out += c;
27625
+ }
27626
+ return out.length >= 2 ? out : null;
27627
+ }
27628
+ function applicablePatterns(text) {
27629
+ return GATED_PATTERNS.filter(([, , gate]) => gate === null || text.includes(gate));
27630
+ }
27631
+ function findSecrets(text) {
27632
+ if (!text)
27633
+ return [];
27634
+ if (isSelfOutput(text))
27635
+ return [];
27636
+ const byValue = new Map;
27637
+ for (const [pattern, label] of applicablePatterns(text)) {
27638
+ const re = new RegExp(pattern.source, pattern.flags.includes("g") ? pattern.flags : pattern.flags + "g");
27639
+ for (const m of text.matchAll(re)) {
27640
+ const value = m[0];
27641
+ if (isDocsLiteral(value))
27642
+ continue;
27643
+ if (!byValue.has(value))
27644
+ byValue.set(value, { value, name: null, rule: label, shaped: true });
27645
+ }
27646
+ }
27647
+ for (const m of text.matchAll(ASSIGNMENT_RE)) {
27648
+ const name = m[2];
27649
+ const value = unquote(m[4]);
27650
+ const existing = byValue.get(value);
27651
+ if (existing)
27652
+ existing.name ??= name;
27653
+ }
27654
+ return [...byValue.values()];
27655
+ }
27656
+ function flattenToolInput(input, depth = 0) {
27657
+ if (depth > 6 || input == null)
27658
+ return "";
27659
+ if (typeof input === "string")
27660
+ return input;
27661
+ if (typeof input === "number" || typeof input === "boolean")
27662
+ return String(input);
27663
+ if (Array.isArray(input))
27664
+ return input.map((v) => flattenToolInput(v, depth + 1)).join(`
27665
+ `);
27666
+ if (typeof input === "object") {
27667
+ return Object.entries(input).map(([k, v]) => `${k}=${flattenToolInput(v, depth + 1)}`).join(`
27668
+ `);
27669
+ }
27670
+ return "";
27671
+ }
27672
+ var LEAK_SCAN_VERSION = 5, ASSIGNMENT_RE, DOCS_LITERALS, SELF_OUTPUT_MARKERS, GATED_PATTERNS;
27673
+ var init_leak_scan = __esm(() => {
27674
+ init_builtin_policies();
27675
+ ASSIGNMENT_RE = /(["']?)\b([A-Za-z_][A-Za-z0-9_]{0,127})\1([ \t]*(?::(?!\/\/)|=)[ \t]*)("[^"]*"|'[^']*'|[^\s;|&"']+)/g;
27676
+ DOCS_LITERALS = new Set([
27677
+ "1a5d44a2dca19669d72edf4c4f1c27c4c1ca4b4408fbb17f6ce4ad452d78ddb3",
27678
+ "78314b11be2e581549ac1c4f616563fad3fdf0c3b71678f6e2299182080e0598",
27679
+ "cb20d34a7adda2f5d2043836051e242fd4c2b233e02c80c91fe9d5af9b100574",
27680
+ "3543c50b0cb9cea8e55eb1f529b79f043cb250d485fde7b66cabbb5183add7da",
27681
+ "1fae28c1b317300acfd90078c973567c8159015822b0c8b81508a5ec40517d20",
27682
+ "23219833092a3136d7ad178ccb62ebd6ac8cba54ff24c2cfcd37b84aba9e95cf",
27683
+ "872db7f4419592e397ef45230f910e06b48c36ed9970cf1f9e4fb896e69f129c",
27684
+ "78a08441f4314f0a2833cfe58c62e555a162264206982cda39f6804f5048f570",
27685
+ "2cafc0970149a84f3b9e62eaf169f36f59907a3b3e31f7b82e68c69cd27f7326",
27686
+ "b363ade84d56c43bebbc9339b4971e340e3f13f7f8d45580b53373d2763e9e33"
27687
+ ]);
27688
+ SELF_OUTPUT_MARKERS = [
27689
+ "[REDACTED:",
27690
+ "[redacted:",
27691
+ "failproofai audit",
27692
+ "assigned secret]"
27693
+ ];
27694
+ GATED_PATTERNS = SECRET_PATTERNS.map(([re, label]) => [re, label, literalPrefixOf(re.source)]);
27695
+ });
27696
+
27496
27697
  // src/audit/redact-example.ts
27497
27698
  import { homedir as homedir27 } from "node:os";
27498
27699
  function maskTruncatedSecret(input) {
@@ -27529,7 +27730,7 @@ function isSecretName(name) {
27529
27730
  return components.length > 1 && components.some((part) => COMPOUND_ONLY_COMPONENTS.includes(part));
27530
27731
  }
27531
27732
  function maskAssignedSecrets(input) {
27532
- let out = input.replace(ASSIGNMENT_RE, (match, quote, name, separator, value) => {
27733
+ let out = input.replace(ASSIGNMENT_RE2, (match, quote, name, separator, value) => {
27533
27734
  if (!isSecretName(name))
27534
27735
  return match;
27535
27736
  if (value.startsWith("[REDACTED"))
@@ -27588,7 +27789,7 @@ function redactExample(input, home = homedir27()) {
27588
27789
  const collapsed = shortened.replace(/\s+/g, " ").trim();
27589
27790
  return collapsed.length > REDACTED_EXAMPLE_MAX_CHARS ? `${collapsed.slice(0, REDACTED_EXAMPLE_MAX_CHARS - 1)}…` : collapsed;
27590
27791
  }
27591
- var REDACTED_EXAMPLE_MAX_CHARS = 160, KEPT_PARENT_SEGMENTS = 0, ABSOLUTE_PATH_RE, PUBLIC_PATH_ROOTS, SECRET_PREFIXES, SECRET_NAME_SUBSTRINGS, SECRET_NAME_COMPONENTS, COMPOUND_ONLY_COMPONENTS, ASSIGNMENT_RE, URL_CREDENTIALS_RE, BASIC_AUTH_FLAG_RE, CLIENT_BUNDLE_PREFIXES, PUBLISHED_MARKERS;
27792
+ var REDACTED_EXAMPLE_MAX_CHARS = 160, KEPT_PARENT_SEGMENTS = 0, ABSOLUTE_PATH_RE, PUBLIC_PATH_ROOTS, SECRET_PREFIXES, SECRET_NAME_SUBSTRINGS, SECRET_NAME_COMPONENTS, COMPOUND_ONLY_COMPONENTS, ASSIGNMENT_RE2, URL_CREDENTIALS_RE, BASIC_AUTH_FLAG_RE, CLIENT_BUNDLE_PREFIXES, PUBLISHED_MARKERS;
27592
27793
  var init_redact_example = __esm(() => {
27593
27794
  init_builtin_policies();
27594
27795
  ABSOLUTE_PATH_RE = /(?:\/[\w.\-@+]+){2,}\/?/g;
@@ -27616,9 +27817,9 @@ var init_redact_example = __esm(() => {
27616
27817
  "APIKEY",
27617
27818
  "PRIVATEKEY"
27618
27819
  ];
27619
- SECRET_NAME_COMPONENTS = ["KEY", "PASS", "AUTH", "PAT", "SIG", "SIGNATURE", "SESSION", "COOKIE"];
27620
- COMPOUND_ONLY_COMPONENTS = ["PWD"];
27621
- ASSIGNMENT_RE = /(["']?)\b([A-Za-z_][A-Za-z0-9_]{0,127})\1([ \t]*(?::(?!\/\/)|=)[ \t]*)("[^"]*"|'[^']*'|[^\s;|&"']+)/g;
27820
+ SECRET_NAME_COMPONENTS = ["PASS", "AUTH", "PAT", "SIG", "SIGNATURE", "COOKIE"];
27821
+ COMPOUND_ONLY_COMPONENTS = ["PWD", "KEY"];
27822
+ ASSIGNMENT_RE2 = /(["']?)\b([A-Za-z_][A-Za-z0-9_]{0,127})\1([ \t]*(?::(?!\/\/)|=)[ \t]*)("[^"]*"|'[^']*'|[^\s;|&"']+)/g;
27622
27823
  URL_CREDENTIALS_RE = /([a-z][a-z0-9+.\-]{0,31}:\/\/)[^\s/:@]{1,256}:[^\s/@]{1,256}@/gi;
27623
27824
  BASIC_AUTH_FLAG_RE = /((?:^|\s)(?:-u|--user)[\s=])\S+:\S+/g;
27624
27825
  CLIENT_BUNDLE_PREFIXES = [
@@ -27634,136 +27835,9 @@ var init_redact_example = __esm(() => {
27634
27835
  PUBLISHED_MARKERS = /PUBLISHABLE|(^|_)PUBLIC_KEY$|(^|_)ANON_KEY$/;
27635
27836
  });
27636
27837
 
27637
- // src/audit/leak-scan.ts
27638
- function isNotACredential(value) {
27639
- if (value.length < MIN_VALUE_LEN)
27640
- return true;
27641
- if (/^[$<{(`%]/.test(value))
27642
- return true;
27643
- if (value.includes("${") || value.includes("{{") || value.includes("%("))
27644
- return true;
27645
- if (/^[A-Za-z_$][\w$]*(\.[A-Za-z_$][\w$]*)+$/.test(value))
27646
- return true;
27647
- if (value.includes("[REDACTED") || /^[•*#.]+$/.test(value))
27648
- return true;
27649
- if (/(.)\1{5,}/.test(value))
27650
- return true;
27651
- if (/^(true|false|null|undefined|none)$/i.test(value))
27652
- return true;
27653
- if (/^-?\d+(\.\d+)?$/.test(value))
27654
- return true;
27655
- if (/^\/|^~\/|^\.\.?\//.test(value))
27656
- return true;
27657
- return false;
27658
- }
27659
- function unquote(v) {
27660
- return v.startsWith('"') && v.endsWith('"') || v.startsWith("'") && v.endsWith("'") ? v.slice(1, -1) : v;
27661
- }
27662
- function isDocsLiteral(value) {
27663
- const { createHash } = __require("node:crypto");
27664
- return DOCS_LITERALS.has(createHash("sha256").update(value).digest("hex"));
27665
- }
27666
- function isSelfOutput(text) {
27667
- return SELF_OUTPUT_MARKERS.some((m) => text.includes(m));
27668
- }
27669
- function literalPrefixOf(source) {
27670
- let out = "";
27671
- for (let i = 0;i < source.length; i++) {
27672
- const c = source[i];
27673
- if (c === "\\") {
27674
- const next = source[i + 1];
27675
- if (next === undefined)
27676
- break;
27677
- if (/[dwsSDWbB]/.test(next))
27678
- break;
27679
- out += next;
27680
- i++;
27681
- continue;
27682
- }
27683
- if ("[](){}|?*+^$.".includes(c))
27684
- break;
27685
- out += c;
27686
- }
27687
- return out.length >= 2 ? out : null;
27688
- }
27689
- function applicablePatterns(text) {
27690
- return GATED_PATTERNS.filter(([, , gate]) => gate === null || text.includes(gate));
27691
- }
27692
- function findSecrets(text) {
27693
- if (!text)
27694
- return [];
27695
- if (isSelfOutput(text))
27696
- return [];
27697
- const byValue = new Map;
27698
- for (const [pattern, label] of applicablePatterns(text)) {
27699
- const re = new RegExp(pattern.source, pattern.flags.includes("g") ? pattern.flags : pattern.flags + "g");
27700
- for (const m of text.matchAll(re)) {
27701
- const value = m[0];
27702
- if (isDocsLiteral(value))
27703
- continue;
27704
- if (!byValue.has(value))
27705
- byValue.set(value, { value, name: null, rule: label, shaped: true });
27706
- }
27707
- }
27708
- for (const m of text.matchAll(ASSIGNMENT_RE2)) {
27709
- const name = m[2];
27710
- const value = unquote(m[4]);
27711
- if (!isSecretName(name) || isNotACredential(value) || isDocsLiteral(value))
27712
- continue;
27713
- const existing = byValue.get(value);
27714
- if (existing) {
27715
- existing.name ??= name;
27716
- continue;
27717
- }
27718
- byValue.set(value, { value, name, rule: "assigned secret", shaped: false });
27719
- }
27720
- return [...byValue.values()];
27721
- }
27722
- function flattenToolInput(input, depth = 0) {
27723
- if (depth > 6 || input == null)
27724
- return "";
27725
- if (typeof input === "string")
27726
- return input;
27727
- if (typeof input === "number" || typeof input === "boolean")
27728
- return String(input);
27729
- if (Array.isArray(input))
27730
- return input.map((v) => flattenToolInput(v, depth + 1)).join(`
27731
- `);
27732
- if (typeof input === "object") {
27733
- return Object.entries(input).map(([k, v]) => `${k}=${flattenToolInput(v, depth + 1)}`).join(`
27734
- `);
27735
- }
27736
- return "";
27737
- }
27738
- var ASSIGNMENT_RE2, MIN_VALUE_LEN = 8, DOCS_LITERALS, SELF_OUTPUT_MARKERS, GATED_PATTERNS;
27739
- var init_leak_scan = __esm(() => {
27740
- init_builtin_policies();
27741
- init_redact_example();
27742
- ASSIGNMENT_RE2 = /(["']?)\b([A-Za-z_][A-Za-z0-9_]{0,127})\1([ \t]*(?::(?!\/\/)|=)[ \t]*)("[^"]*"|'[^']*'|[^\s;|&"']+)/g;
27743
- DOCS_LITERALS = new Set([
27744
- "1a5d44a2dca19669d72edf4c4f1c27c4c1ca4b4408fbb17f6ce4ad452d78ddb3",
27745
- "78314b11be2e581549ac1c4f616563fad3fdf0c3b71678f6e2299182080e0598",
27746
- "cb20d34a7adda2f5d2043836051e242fd4c2b233e02c80c91fe9d5af9b100574",
27747
- "3543c50b0cb9cea8e55eb1f529b79f043cb250d485fde7b66cabbb5183add7da",
27748
- "1fae28c1b317300acfd90078c973567c8159015822b0c8b81508a5ec40517d20",
27749
- "23219833092a3136d7ad178ccb62ebd6ac8cba54ff24c2cfcd37b84aba9e95cf",
27750
- "872db7f4419592e397ef45230f910e06b48c36ed9970cf1f9e4fb896e69f129c",
27751
- "78a08441f4314f0a2833cfe58c62e555a162264206982cda39f6804f5048f570",
27752
- "2cafc0970149a84f3b9e62eaf169f36f59907a3b3e31f7b82e68c69cd27f7326",
27753
- "b363ade84d56c43bebbc9339b4971e340e3f13f7f8d45580b53373d2763e9e33"
27754
- ]);
27755
- SELF_OUTPUT_MARKERS = [
27756
- "[REDACTED:",
27757
- "[redacted:",
27758
- "failproofai audit",
27759
- "assigned secret]"
27760
- ];
27761
- GATED_PATTERNS = SECRET_PATTERNS.map(([re, label]) => [re, label, literalPrefixOf(re.source)]);
27762
- });
27763
-
27764
27838
  // src/audit/cache.ts
27765
27839
  import { createHash as createHash8 } from "node:crypto";
27766
- import { existsSync as existsSync33, mkdirSync as mkdirSync22, readFileSync as readFileSync26, writeFileSync as writeFileSync19, chmodSync as chmodSync5, openSync, readSync, closeSync } from "node:fs";
27840
+ import { existsSync as existsSync33, mkdirSync as mkdirSync22, readFileSync as readFileSync27, writeFileSync as writeFileSync19, chmodSync as chmodSync5, openSync, readSync, closeSync } from "node:fs";
27767
27841
  import { join as join33 } from "node:path";
27768
27842
  function getEngineVersion() {
27769
27843
  if (cachedEngineVersion)
@@ -27827,7 +27901,7 @@ function readCachedTranscript(transcriptPath, mtimeMs, sizeBytes) {
27827
27901
  if (!existsSync33(cachePath))
27828
27902
  return null;
27829
27903
  try {
27830
- const raw = readFileSync26(cachePath, "utf-8");
27904
+ const raw = readFileSync27(cachePath, "utf-8");
27831
27905
  const entry = JSON.parse(raw);
27832
27906
  if (entry.schemaVersion !== CACHE_SCHEMA_VERSION)
27833
27907
  return null;
@@ -27835,6 +27909,8 @@ function readCachedTranscript(transcriptPath, mtimeMs, sizeBytes) {
27835
27909
  return null;
27836
27910
  if (entry.detectorVersion !== getDetectorVersion())
27837
27911
  return null;
27912
+ if (entry.leakScanVersion !== LEAK_SCAN_VERSION)
27913
+ return null;
27838
27914
  if (!Number.isFinite(entry.cachedAt) || Date.now() - entry.cachedAt > CACHE_TTL_MS)
27839
27915
  return null;
27840
27916
  if (!entry.result)
@@ -27874,6 +27950,7 @@ function writeCachedTranscriptResult(transcriptPath, mtimeMs, sizeBytes, result,
27874
27950
  sizeBytes,
27875
27951
  engineVersion: getEngineVersion(),
27876
27952
  detectorVersion: getDetectorVersion(),
27953
+ leakScanVersion: LEAK_SCAN_VERSION,
27877
27954
  result
27878
27955
  };
27879
27956
  if (resume && resume.bytesScanned > 0) {
@@ -27894,6 +27971,7 @@ var cachedEngineVersion = null, cachedDetectorVersion = null, CACHE_SCHEMA_VERSI
27894
27971
  var init_cache = __esm(() => {
27895
27972
  init_builtin_policies();
27896
27973
  init_detectors();
27974
+ init_leak_scan();
27897
27975
  init_fp_home();
27898
27976
  CACHE_TTL_MS = 30 * 24 * 60 * 60000;
27899
27977
  });
@@ -27962,6 +28040,30 @@ var init_replay = __esm(() => {
27962
28040
  });
27963
28041
 
27964
28042
  // src/audit/index.ts
28043
+ class ByteGate {
28044
+ budget;
28045
+ inFlight = 0;
28046
+ waiting = [];
28047
+ constructor(budget) {
28048
+ this.budget = budget;
28049
+ }
28050
+ async acquire(bytes) {
28051
+ const want = Math.max(0, bytes);
28052
+ while (this.inFlight > 0 && this.inFlight + want > this.budget) {
28053
+ await new Promise((resolve) => this.waiting.push(resolve));
28054
+ }
28055
+ this.inFlight += want;
28056
+ }
28057
+ release(bytes) {
28058
+ this.inFlight -= Math.max(0, bytes);
28059
+ if (this.inFlight < 0)
28060
+ this.inFlight = 0;
28061
+ const waiters = this.waiting;
28062
+ this.waiting = [];
28063
+ for (const wake of waiters)
28064
+ wake();
28065
+ }
28066
+ }
27965
28067
  function shortPolicyName(name) {
27966
28068
  const slash = name.indexOf("/");
27967
28069
  return slash >= 0 ? name.slice(slash + 1) : name;
@@ -28003,6 +28105,27 @@ function parseSinceOpt(since) {
28003
28105
  return t;
28004
28106
  throw new Error(`Invalid --since value: "${since}" (expected e.g. "7d", "30d", or "2026-04-01")`);
28005
28107
  }
28108
+ function isCredentialResearchSession(events, sessionDescription) {
28109
+ if (sessionDescription && CREDENTIAL_RESEARCH_PURPOSE.test(sessionDescription))
28110
+ return true;
28111
+ const matched = new Set;
28112
+ let evidenceEvents = 0;
28113
+ for (const event of events) {
28114
+ const intent = flattenToolInput(event.toolInput);
28115
+ let eventMatched = false;
28116
+ for (let i = 0;i < CREDENTIAL_RESEARCH_MARKERS.length; i++) {
28117
+ if (CREDENTIAL_RESEARCH_MARKERS[i].test(intent)) {
28118
+ matched.add(i);
28119
+ eventMatched = true;
28120
+ }
28121
+ }
28122
+ if (eventMatched)
28123
+ evidenceEvents++;
28124
+ if (matched.size >= 2 || evidenceEvents >= 3)
28125
+ return true;
28126
+ }
28127
+ return false;
28128
+ }
28006
28129
  async function scanOneTranscript(meta, resume) {
28007
28130
  const empty = {
28008
28131
  transcriptPath: meta.transcriptPath,
@@ -28043,6 +28166,9 @@ async function scanOneTranscript(meta, resume) {
28043
28166
  const result = empty;
28044
28167
  result.eventsScanned = events.length;
28045
28168
  result.cwd = result.cwd || events[0].cwd || "";
28169
+ const suppressLeakScan = isCredentialResearchSession(events, meta.sessionDescription);
28170
+ if (suppressLeakScan)
28171
+ result.leakScanSuppressed = "credential-research";
28046
28172
  for (const event of events) {
28047
28173
  let replayHits;
28048
28174
  try {
@@ -28054,7 +28180,8 @@ async function scanOneTranscript(meta, resume) {
28054
28180
  const example = formatPolicyExample(hit.policyName, event);
28055
28181
  recordHit(result, hit.policyName, event.timestamp, event.cwd, truncateExample(example));
28056
28182
  }
28057
- recordLeaks(result, event);
28183
+ if (!suppressLeakScan)
28184
+ recordLeaks(result, event);
28058
28185
  }
28059
28186
  return { result, bytesScanned, detectorState: sessionState, resumed };
28060
28187
  }
@@ -28094,10 +28221,12 @@ function leakSalt() {
28094
28221
  }
28095
28222
  return cachedSalt;
28096
28223
  }
28097
- function persistLeaks(perTranscript) {
28224
+ function persistLeaks(perTranscript, replaceExisting) {
28098
28225
  const fresh = [];
28099
28226
  try {
28100
- const record = readLeakRecord();
28227
+ const previous = readLeakRecord();
28228
+ const previousIds = new Set(previous.findings.map((f) => f.id));
28229
+ const record = replaceExisting ? emptyRecord(previous.salt, new Date().toISOString()) : previous;
28101
28230
  for (const t of perTranscript) {
28102
28231
  for (const leak of t.leaks ?? []) {
28103
28232
  const { isNew } = upsertFinding(record, {
@@ -28114,7 +28243,7 @@ function persistLeaks(perTranscript) {
28114
28243
  mechanism: describeMechanism(leak.toolName, leak.direction, leak.path)
28115
28244
  }
28116
28245
  });
28117
- if (isNew)
28246
+ if (isNew && !previousIds.has(leak.id))
28118
28247
  fresh.push(leak.id);
28119
28248
  }
28120
28249
  }
@@ -28144,6 +28273,12 @@ function mergeIncremental(cached, tail) {
28144
28273
  examplesByName: {},
28145
28274
  rangeByName: { ...cached.rangeByName }
28146
28275
  };
28276
+ if (cached.leakScanSuppressed || tail.leakScanSuppressed) {
28277
+ out.leakScanSuppressed = "credential-research";
28278
+ out.leaks = [];
28279
+ } else {
28280
+ out.leaks = [...cached.leaks ?? [], ...tail.leaks ?? []];
28281
+ }
28147
28282
  for (const [name, list] of Object.entries(cached.examplesByName)) {
28148
28283
  out.examplesByName[name] = [...list];
28149
28284
  }
@@ -28267,16 +28402,19 @@ async function runAuditInner(opts, startedAt) {
28267
28402
  }
28268
28403
  } catch {}
28269
28404
  const allTranscripts = [];
28405
+ let discoveryErrors = 0;
28270
28406
  for (const cli of clis) {
28271
28407
  const adapter = ADAPTERS[cli];
28272
28408
  let list;
28273
28409
  try {
28274
28410
  list = await adapter.listTranscripts({ projects: opts.projects, sinceMs });
28275
28411
  } catch {
28412
+ discoveryErrors++;
28276
28413
  continue;
28277
28414
  }
28278
28415
  allTranscripts.push(...list);
28279
28416
  }
28417
+ const gate = new ByteGate(TRANSCRIPT_BYTE_BUDGET);
28280
28418
  let skipped = 0;
28281
28419
  let errors = 0;
28282
28420
  const tasks = allTranscripts.map((meta) => async () => {
@@ -28289,6 +28427,7 @@ async function runAuditInner(opts, startedAt) {
28289
28427
  resume = found?.kind === "resume" ? { fromByte: found.fromByte, detectorState: found.detectorState } : undefined;
28290
28428
  cachedPrefix = found?.kind === "resume" ? found.result : null;
28291
28429
  }
28430
+ await gate.acquire(meta.sizeBytes);
28292
28431
  try {
28293
28432
  const scan = await scanOneTranscript(meta, resume);
28294
28433
  const fresh = cachedPrefix && scan.resumed ? mergeIncremental(cachedPrefix, scan.result) : scan.result;
@@ -28311,6 +28450,8 @@ async function runAuditInner(opts, startedAt) {
28311
28450
  examplesByName: {},
28312
28451
  rangeByName: {}
28313
28452
  };
28453
+ } finally {
28454
+ gate.release(meta.sizeBytes);
28314
28455
  }
28315
28456
  });
28316
28457
  const settled = await batchAll(tasks, TRANSCRIPT_CONCURRENCY);
@@ -28337,7 +28478,8 @@ async function runAuditInner(opts, startedAt) {
28337
28478
  projectsScannedSet.add(t.cwd);
28338
28479
  eventsScanned += t.eventsScanned ?? 0;
28339
28480
  }
28340
- const newFindingIds = persistLeaks(perTranscript);
28481
+ const coversAllHistory = opts.clis === undefined && opts.projects === undefined && opts.since === undefined && discoveryErrors === 0 && errors === 0 && skipped === 0;
28482
+ const newFindingIds = persistLeaks(perTranscript, coversAllHistory);
28341
28483
  const auditResult = {
28342
28484
  version: 2,
28343
28485
  newLeakIds: newFindingIds,
@@ -28364,7 +28506,7 @@ async function runAuditInner(opts, startedAt) {
28364
28506
  };
28365
28507
  return auditResult;
28366
28508
  }
28367
- var TRANSCRIPT_CONCURRENCY = 8, cachedSalt;
28509
+ var TRANSCRIPT_CONCURRENCY = 8, TRANSCRIPT_BYTE_BUDGET, CREDENTIAL_RESEARCH_MARKERS, CREDENTIAL_RESEARCH_PURPOSE, cachedSalt;
28368
28510
  var init_audit = __esm(() => {
28369
28511
  init_builtin_policies();
28370
28512
  init_hooks_config();
@@ -28380,6 +28522,15 @@ var init_audit = __esm(() => {
28380
28522
  init_cache();
28381
28523
  init_replay();
28382
28524
  init_types2();
28525
+ TRANSCRIPT_BYTE_BUDGET = 48 * 1024 * 1024;
28526
+ CREDENTIAL_RESEARCH_MARKERS = [
28527
+ /\b(?:secret|credential)[-_ ](?:scanner|detection|detector|pattern|regex|corpus)\b/i,
28528
+ /\b(?:trufflehog|trufflesecurity|gitleaks|detect-secrets|secret-detection-rules)\b/i,
28529
+ /\b(?:SECRET_PATTERNS|findSecrets|isDocsLiteral|leak-scan)\b/,
28530
+ /\b(?:fixture|placeholder|synthetic|sample) (?:api )?(?:key|token|secret|credential)s?\b/i,
28531
+ /\b(?:grep|rg|ripgrep|scan|hunt|search)\b[^\n]{0,240}\b(?:secret|credential|token)s?\b/i
28532
+ ];
28533
+ CREDENTIAL_RESEARCH_PURPOSE = /\b(?:hunt|scan|search|research|test)(?:ing)?\b[^\n]{0,80}\b(?:secret|credential|token)s?\b|\b(?:secret|credential|token)s?\b[^\n]{0,80}\b(?:scanner|detector|detection|research|fixtures?|corpus)\b/i;
28383
28534
  });
28384
28535
 
28385
28536
  // src/audit/audit-lock.ts
@@ -28388,7 +28539,7 @@ import {
28388
28539
  linkSync,
28389
28540
  mkdirSync as mkdirSync23,
28390
28541
  openSync as openSync2,
28391
- readFileSync as readFileSync27,
28542
+ readFileSync as readFileSync28,
28392
28543
  unlinkSync as unlinkSync4,
28393
28544
  writeFileSync as writeFileSync20,
28394
28545
  writeSync
@@ -28449,7 +28600,7 @@ function handleFor(path, info) {
28449
28600
  }
28450
28601
  function readLock2(path) {
28451
28602
  try {
28452
- const parsed = JSON.parse(readFileSync27(path, "utf8"));
28603
+ const parsed = JSON.parse(readFileSync28(path, "utf8"));
28453
28604
  if (typeof parsed?.pid !== "number" || typeof parsed?.startedAt !== "number")
28454
28605
  return null;
28455
28606
  return {
@@ -28509,7 +28660,7 @@ var init_audit_lock = __esm(() => {
28509
28660
  });
28510
28661
 
28511
28662
  // src/audit/dashboard-cache.ts
28512
- import { existsSync as existsSync34, readFileSync as readFileSync28 } from "node:fs";
28663
+ import { existsSync as existsSync34, readFileSync as readFileSync29 } from "node:fs";
28513
28664
  function getCachePath() {
28514
28665
  return auditDashboardFile();
28515
28666
  }
@@ -28532,7 +28683,7 @@ function readDashboardCacheMeta() {
28532
28683
  if (!existsSync34(cachePath))
28533
28684
  return null;
28534
28685
  try {
28535
- const raw = readFileSync28(cachePath, "utf-8");
28686
+ const raw = readFileSync29(cachePath, "utf-8");
28536
28687
  const entry = JSON.parse(raw);
28537
28688
  if (!entry || entry.schemaVersion !== DASHBOARD_CACHE_SCHEMA_VERSION || typeof entry.cachedAt !== "string" || Number.isNaN(new Date(entry.cachedAt).getTime()))
28538
28689
  return null;
@@ -28756,7 +28907,7 @@ var init_api_server_client = __esm(() => {
28756
28907
  });
28757
28908
 
28758
28909
  // lib/auth/auth-store.ts
28759
- import { existsSync as existsSync35, readFileSync as readFileSync29, readdirSync as readdirSync19, rmSync as rmSync14 } from "node:fs";
28910
+ import { existsSync as existsSync35, readFileSync as readFileSync30, readdirSync as readdirSync19, rmSync as rmSync14 } from "node:fs";
28760
28911
  import { join as join34, resolve as resolve25 } from "node:path";
28761
28912
  function getAuthFilePath() {
28762
28913
  const override = process.env.FAILPROOFAI_AUTH_DIR;
@@ -28767,7 +28918,7 @@ function readAuth() {
28767
28918
  if (!existsSync35(p))
28768
28919
  return null;
28769
28920
  try {
28770
- const raw = readFileSync29(p, "utf-8");
28921
+ const raw = readFileSync30(p, "utf-8");
28771
28922
  const parsed = JSON.parse(raw);
28772
28923
  if (typeof parsed.access_token !== "string" || typeof parsed.refresh_token !== "string" || typeof parsed.access_expires_at !== "number" || typeof parsed.user !== "object" || !parsed.user || typeof parsed.user.id !== "string" || typeof parsed.user.email !== "string") {
28773
28924
  return null;
@@ -28977,7 +29128,7 @@ var init_harm_report = __esm(() => {
28977
29128
  });
28978
29129
 
28979
29130
  // src/audit/machine-store.ts
28980
- import { existsSync as existsSync36, readFileSync as readFileSync30, rmSync as rmSync15 } from "node:fs";
29131
+ import { existsSync as existsSync36, readFileSync as readFileSync31, rmSync as rmSync15 } from "node:fs";
28981
29132
  import { hostname as hostname5 } from "node:os";
28982
29133
  import { randomUUID as randomUUID4 } from "node:crypto";
28983
29134
  function readMachineIdentity(home) {
@@ -28985,7 +29136,7 @@ function readMachineIdentity(home) {
28985
29136
  if (!existsSync36(path))
28986
29137
  return null;
28987
29138
  try {
28988
- const parsed = JSON.parse(readFileSync30(path, "utf8"));
29139
+ const parsed = JSON.parse(readFileSync31(path, "utf8"));
28989
29140
  if (typeof parsed.machine_id !== "string" || !parsed.machine_id)
28990
29141
  return null;
28991
29142
  return {
@@ -29556,6 +29707,8 @@ var init_cli = __esm(() => {
29556
29707
  init_desktop_notify();
29557
29708
  init_macos_notifier();
29558
29709
  init_leak_notice();
29710
+ init_notice();
29711
+ init_leak_store();
29559
29712
  init_fp_config();
29560
29713
  init_tui();
29561
29714
  init_package();
@@ -29575,7 +29728,7 @@ var init_cli = __esm(() => {
29575
29728
  });
29576
29729
 
29577
29730
  // src/hooks/configure-wizard.ts
29578
- import { existsSync as existsSync37, mkdirSync as mkdirSync24, writeFileSync as writeFileSync21, readFileSync as readFileSync31 } from "node:fs";
29731
+ import { existsSync as existsSync37, mkdirSync as mkdirSync24, writeFileSync as writeFileSync21, readFileSync as readFileSync32 } from "node:fs";
29579
29732
  import { homedir as homedir28 } from "node:os";
29580
29733
  import { dirname as dirname17, resolve as resolve26, sep as sep5 } from "node:path";
29581
29734
  async function emit(event, props) {
@@ -29601,7 +29754,7 @@ function setCustomPoliciesEnabled(scope, cwd, enabled) {
29601
29754
  let config = {};
29602
29755
  try {
29603
29756
  if (existsSync37(path))
29604
- config = JSON.parse(readFileSync31(path, "utf8"));
29757
+ config = JSON.parse(readFileSync32(path, "utf8"));
29605
29758
  } catch {
29606
29759
  return;
29607
29760
  }
@@ -30725,7 +30878,7 @@ var init_harness_cli = __esm(() => {
30725
30878
  });
30726
30879
 
30727
30880
  // src/hooks/fp-config.ts
30728
- import { chmodSync as chmodSync6, existsSync as existsSync40, mkdirSync as mkdirSync27, readFileSync as readFileSync32, statSync as statSync25, writeFileSync as writeFileSync24 } from "node:fs";
30881
+ import { chmodSync as chmodSync6, existsSync as existsSync40, mkdirSync as mkdirSync27, readFileSync as readFileSync33, statSync as statSync25, writeFileSync as writeFileSync24 } from "node:fs";
30729
30882
  function detectLayout2() {
30730
30883
  const raw = readVersionFile2();
30731
30884
  if (raw) {
@@ -30766,7 +30919,7 @@ function readLegacyTomlVersion2(text) {
30766
30919
  function readVersionFile2() {
30767
30920
  let text;
30768
30921
  try {
30769
- text = readFileSync32(versionFile(), "utf8");
30922
+ text = readFileSync33(versionFile(), "utf8");
30770
30923
  } catch {
30771
30924
  return null;
30772
30925
  }
@@ -30799,7 +30952,7 @@ import {
30799
30952
  copyFileSync as copyFileSync3,
30800
30953
  existsSync as existsSync41,
30801
30954
  mkdirSync as mkdirSync28,
30802
- readFileSync as readFileSync33,
30955
+ readFileSync as readFileSync34,
30803
30956
  renameSync as renameSync9,
30804
30957
  rmSync as rmSync16,
30805
30958
  writeFileSync as writeFileSync25
@@ -30869,7 +31022,7 @@ function planMigration2(from, registry = MIGRATIONS2) {
30869
31022
  }
30870
31023
  function readLedger2() {
30871
31024
  try {
30872
- const parsed = JSON.parse(readFileSync33(migrationLedgerFile(), "utf8"));
31025
+ const parsed = JSON.parse(readFileSync34(migrationLedgerFile(), "utf8"));
30873
31026
  return Array.isArray(parsed) ? parsed : [];
30874
31027
  } catch {
30875
31028
  return [];
@@ -31064,7 +31217,7 @@ import {
31064
31217
  existsSync as existsSync42,
31065
31218
  mkdirSync as mkdirSync29,
31066
31219
  mkdtempSync as mkdtempSync2,
31067
- readFileSync as readFileSync34,
31220
+ readFileSync as readFileSync35,
31068
31221
  writeFileSync as writeFileSync26,
31069
31222
  unlinkSync as unlinkSync5,
31070
31223
  rmSync as rmSync17
@@ -31711,7 +31864,7 @@ var init_uninstall_cli = __esm(() => {
31711
31864
  import { execFileSync as execFileSync5 } from "node:child_process";
31712
31865
  import { homedir as homedir31, tmpdir as tmpdir4 } from "node:os";
31713
31866
  import { createHash as createHash9 } from "node:crypto";
31714
- import { copyFileSync as copyFileSync4, existsSync as existsSync44, mkdirSync as mkdirSync30, mkdtempSync as mkdtempSync3, readFileSync as readFileSync35, readdirSync as readdirSync22, realpathSync as realpathSync3, rmSync as rmSync19, statSync as statSync26, writeFileSync as writeFileSync27 } from "node:fs";
31867
+ import { copyFileSync as copyFileSync4, existsSync as existsSync44, mkdirSync as mkdirSync30, mkdtempSync as mkdtempSync3, readFileSync as readFileSync36, readdirSync as readdirSync22, realpathSync as realpathSync3, rmSync as rmSync19, statSync as statSync26, writeFileSync as writeFileSync27 } from "node:fs";
31715
31868
  import { basename as basename8, dirname as dirname21, isAbsolute as isAbsolute4, join as join38, relative as relative5, resolve as resolve30, sep as sep6 } from "node:path";
31716
31869
  function parseList(rest, flag) {
31717
31870
  const idx = rest.findIndex((a) => a === flag || a.startsWith(`${flag}=`));
@@ -31828,7 +31981,7 @@ async function build(rest) {
31828
31981
  const entryPath = resolve30(entry);
31829
31982
  if (!existsSync44(entryPath))
31830
31983
  return fail2([`No such file: ${entryPath}`]);
31831
- const source = readFileSync35(entryPath, "utf8");
31984
+ const source = readFileSync36(entryPath, "utf8");
31832
31985
  const localImport = /(?:^|\n)\s*(?:import|export)[^;\n]*from\s+["'](\.[^"']*)["']/.exec(source);
31833
31986
  if (localImport) {
31834
31987
  return fail2([
@@ -31891,7 +32044,7 @@ async function build(rest) {
31891
32044
  copyFileSync4(entryPath, entryOut);
31892
32045
  const sha = (bytes) => createHash9("sha256").update(bytes).digest("hex");
31893
32046
  writeFileSync27(resolve30(outDir, PACK_CHECKSUMS_ASSET), `${sha(manifest)} ${PACK_MANIFEST_ASSET}
31894
- ${sha(readFileSync35(entryOut))} ${PACK_ENTRY_ASSET}
32047
+ ${sha(readFileSync36(entryOut))} ${PACK_ENTRY_ASSET}
31895
32048
  `, "utf8");
31896
32049
  const on = policies.filter((p) => p.defaultEnabled).length;
31897
32050
  return ok2([
@@ -32217,7 +32370,7 @@ function findEntry(dir) {
32217
32370
  try {
32218
32371
  if (statSync26(full).size > 512 * 1024)
32219
32372
  continue;
32220
- const text = readFileSync35(full, "utf8");
32373
+ const text = readFileSync36(full, "utf8");
32221
32374
  if (/from\s+["']failproofai["']/.test(text) && /customPolicies\s*\.\s*add\s*\(/.test(text)) {
32222
32375
  found.push(full);
32223
32376
  }
@@ -32236,7 +32389,7 @@ function withLocalImports(sources) {
32236
32389
  try {
32237
32390
  if (statSync26(file).size > 512 * 1024)
32238
32391
  continue;
32239
- text = readFileSync35(file, "utf8");
32392
+ text = readFileSync36(file, "utf8");
32240
32393
  } catch {
32241
32394
  continue;
32242
32395
  }
@@ -32759,7 +32912,7 @@ async function publish(rest) {
32759
32912
  }
32760
32913
  const tag = flag("tag") ?? version;
32761
32914
  let entryToBuild = entry;
32762
- const needsBundle = discovered.length > 1 || /(?:^|\n)\s*(?:import|export)[^;\n]*from\s+["']\.[^"']*["']/.test(readFileSync35(entry, "utf8"));
32915
+ const needsBundle = discovered.length > 1 || /(?:^|\n)\s*(?:import|export)[^;\n]*from\s+["']\.[^"']*["']/.test(readFileSync36(entry, "utf8"));
32763
32916
  if (needsBundle) {
32764
32917
  const sources = discovered.length > 1 ? discovered : [entry];
32765
32918
  const bundled = bundleEntry(sources, outDirEarly);
@@ -32838,7 +32991,7 @@ async function publish(rest) {
32838
32991
  if (prior) {
32839
32992
  await gh(`${GITHUB_API2}/repos/${owner}/${name}/releases/assets/${prior.id}`, auth, { method: "DELETE" });
32840
32993
  }
32841
- const bytes = readFileSync35(resolve30(outDir, asset));
32994
+ const bytes = readFileSync36(resolve30(outDir, asset));
32842
32995
  const upload = await gh(`${GITHUB_UPLOADS2}/repos/${owner}/${name}/releases/${releaseId}/assets?name=${encodeURIComponent(asset)}`, auth, {
32843
32996
  method: "POST",
32844
32997
  contentType: asset.endsWith(".json") ? "application/json" : "application/octet-stream",
@@ -34242,11 +34395,11 @@ var init_manager2 = __esm(() => {
34242
34395
  });
34243
34396
 
34244
34397
  // src/audit/audit-schedule.ts
34245
- import { readFileSync as readFileSync36 } from "node:fs";
34398
+ import { readFileSync as readFileSync37 } from "node:fs";
34246
34399
  function readAuditSchedule(home) {
34247
34400
  let raw;
34248
34401
  try {
34249
- raw = readFileSync36(auditScheduleFile(home), "utf8");
34402
+ raw = readFileSync37(auditScheduleFile(home), "utf8");
34250
34403
  } catch {
34251
34404
  return null;
34252
34405
  }
@@ -34310,7 +34463,9 @@ async function ensureSignedIn(preset) {
34310
34463
  }
34311
34464
  if (!canPrompt()) {
34312
34465
  throw new LoginError(`Signing in needs an interactive terminal, and this one is not.
34313
- ` + "Run `failproofai audit --schedule` from a shell you are sitting at,\n" + "or sign in from the dashboard — both write the same session file.");
34466
+ ` + "Run `failproofai audit --schedule` from a shell you are sitting at,\n" + `or sign in from the dashboard — both write the same session file.
34467
+ ` + `
34468
+ ` + "`--email` does not avoid this: it fills in the address so you are not\n" + "asked for it, but a one-time code is still sent there for you to paste.");
34314
34469
  }
34315
34470
  return { user: await runLogin(preset), prompted: true };
34316
34471
  }
@@ -34692,7 +34847,7 @@ function parseScriptArgs(argv) {
34692
34847
  var init_parse_script_args = () => {};
34693
34848
 
34694
34849
  // scripts/install-diagnosis.mjs
34695
- import { existsSync as existsSync46, readFileSync as readFileSync37, realpathSync as realpathSync4 } from "node:fs";
34850
+ import { existsSync as existsSync46, readFileSync as readFileSync38, realpathSync as realpathSync4 } from "node:fs";
34696
34851
  import { dirname as dirname22, resolve as resolve33 } from "node:path";
34697
34852
  import { homedir as homedir32, platform as platform5 } from "node:os";
34698
34853
  import { spawnSync } from "node:child_process";
@@ -34706,7 +34861,7 @@ function findPackageRoot(start) {
34706
34861
  const pkgPath = resolve33(dir, "package.json");
34707
34862
  if (existsSync46(pkgPath)) {
34708
34863
  try {
34709
- const pkg = JSON.parse(readFileSync37(pkgPath, "utf8"));
34864
+ const pkg = JSON.parse(readFileSync38(pkgPath, "utf8"));
34710
34865
  if (pkg.name === PKG_NAME)
34711
34866
  return dir;
34712
34867
  } catch {}
@@ -34724,7 +34879,7 @@ function readPackageVersion(packageRoot) {
34724
34879
  if (!packageRoot)
34725
34880
  return null;
34726
34881
  try {
34727
- const pkg = JSON.parse(readFileSync37(resolve33(packageRoot, "package.json"), "utf8"));
34882
+ const pkg = JSON.parse(readFileSync38(resolve33(packageRoot, "package.json"), "utf8"));
34728
34883
  return typeof pkg.version === "string" ? pkg.version : null;
34729
34884
  } catch {
34730
34885
  return null;
@@ -34972,14 +35127,14 @@ function helpText() {
34972
35127
  const lines = helpScreen({
34973
35128
  command: "audit",
34974
35129
  version: version2,
34975
- tagline: "review your agent CLIs for risky and wasteful patterns",
35130
+ tagline: "find credentials your agents leaked into their own transcripts",
34976
35131
  sections: [
34977
35132
  {
34978
35133
  label: "usage",
34979
35134
  entries: [
34980
- ["(bare)", `Scan your session history, then open http://localhost:${DASHBOARD_PORT}/audit`],
34981
- ["--schedule [days]", "Scan on a timer and email the findings. Default 7 days, range 1-90. Signs you in the first time."],
34982
- ["--email <address>", "With --schedule, skips the sign-in prompt."],
35135
+ ["(bare)", `Scan your session history for leaked keys, then open http://localhost:${DASHBOARD_PORT}/audit`],
35136
+ ["--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."],
35137
+ ["--email <address>", "With --schedule, fills in your address. A sign-in code is still emailed to you to paste."],
34983
35138
  ["--no-schedule", "Stop the timer. Leaves you signed in."],
34984
35139
  ["--notify", "Show a desktop notification when a scan finds a credential. On by default."],
34985
35140
  ["--no-notify", "Stop those notifications. Scans and the digest are unaffected."],
@@ -34988,7 +35143,11 @@ function helpText() {
34988
35143
  ]
34989
35144
  }
34990
35145
  ],
34991
- footer: ["Everything runs on this machine; only a scheduled digest ever leaves it."]
35146
+ footer: [
35147
+ "Everything runs on this machine. A scan tells you three ways: a desktop",
35148
+ "notification, two lines in your agent session, and the emailed digest —",
35149
+ "and only that digest ever leaves."
35150
+ ]
34992
35151
  }, helpOptsFor(process.stdout));
34993
35152
  return ["", ...lines, ""].join(`
34994
35153
  `) + `
@@ -35154,24 +35313,35 @@ async function announceLeaksOrThrow(result) {
35154
35313
  const claimed = markLeakNoticeDelivered(ids, undefined, "desktop");
35155
35314
  if (claimed.length === 0)
35156
35315
  return;
35157
- const n = claimed.length === 1 ? "a credential" : `${claimed.length} credentials`;
35158
- const title = "failproofai found a leaked credential";
35159
- const body = `${n} in your agent transcripts. Run failproofai audit to see them — ` + "or failproofai audit --schedule to get them by email.";
35160
- if (process.platform === "darwin") {
35161
- pruneMacNotifyQueue();
35162
- if (!queueMacNotification(claimed[0], title, body)) {
35163
- process.stderr.write(`failproofai: found ${n} but could not queue a notification
35316
+ const live = new Set(activeFindings(readLeakRecord()).map((f) => f.id));
35317
+ const shown = claimed.filter((id) => live.has(id)).length || claimed.length;
35318
+ const n = shown === 1 ? "a possible credential exposure" : `${shown} possible credential exposures`;
35319
+ const { title, body } = desktopLeakNotice();
35320
+ let delivered = false;
35321
+ try {
35322
+ if (process.platform === "darwin") {
35323
+ pruneMacNotifyQueue();
35324
+ if (!queueMacNotification(claimed[0], title, body)) {
35325
+ process.stderr.write(`failproofai: found ${n} but could not queue a notification
35164
35326
  `);
35165
- } else if (!macNotifierInstalled()) {
35166
- process.stderr.write(`failproofai: found ${n}; the notifier is not installed, so nothing will show it ` + `(run \`failproofai config\`)
35327
+ } else {
35328
+ delivered = true;
35329
+ }
35330
+ if (delivered && !macNotifierInstalled()) {
35331
+ process.stderr.write(`failproofai: found ${n}; the notifier is not installed, so nothing will show it ` + `(run \`failproofai config\`)
35167
35332
  `);
35333
+ }
35334
+ return;
35168
35335
  }
35169
- return;
35170
- }
35171
- const outcome = await notifyDesktop(title, body, 0);
35172
- if (!outcome.ok) {
35173
- process.stderr.write(`failproofai: found ${n} but could not raise a desktop notification (${outcome.reason})
35336
+ const outcome = await notifyDesktop(title, body, 0);
35337
+ delivered = outcome.ok;
35338
+ if (!outcome.ok) {
35339
+ process.stderr.write(`failproofai: found ${n} but could not raise a desktop notification (${outcome.reason})
35174
35340
  `);
35341
+ }
35342
+ } finally {
35343
+ if (!delivered)
35344
+ releaseLeakNoticeClaims(claimed, undefined, "desktop");
35175
35345
  }
35176
35346
  }
35177
35347
  async function runScheduledAudit() {
@@ -35365,6 +35535,8 @@ var init_cli2 = __esm(() => {
35365
35535
  init_desktop_notify();
35366
35536
  init_macos_notifier();
35367
35537
  init_leak_notice();
35538
+ init_notice();
35539
+ init_leak_store();
35368
35540
  init_fp_config();
35369
35541
  init_tui();
35370
35542
  init_package();
@@ -35825,7 +35997,7 @@ import { realpathSync as realpathSync7 } from "fs";
35825
35997
  import { dirname as dirname25, resolve as resolve36 } from "path";
35826
35998
  import { fileURLToPath as fileURLToPath4 } from "url";
35827
35999
  // package.json
35828
- var version = "1.0.4-beta.2";
36000
+ var version = "1.0.4-beta.4";
35829
36001
 
35830
36002
  // bin/failproofai.mjs
35831
36003
  if (!process.env.FAILPROOFAI_PACKAGE_ROOT) {
@@ -36070,7 +36242,7 @@ Run \`failproofai help\` for usage.`);
36070
36242
  label: "see what it caught",
36071
36243
  entries: [
36072
36244
  ["(no args)", "Open the policy dashboard on localhost:8020"],
36073
- ["audit", "Scan your agents' history, then open the audit view"],
36245
+ ["audit", "Find credentials your agents leaked, and get told about it"],
36074
36246
  ["config --status", "Cloud connection, daemon version, pause state"]
36075
36247
  ]
36076
36248
  },