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
@@ -24,18 +24,26 @@
24
24
  * them apart so the report can tell a user which of the two happened.
25
25
  */
26
26
  import { SECRET_PATTERNS } from "../hooks/builtin-policies";
27
- import { isSecretName } from "./redact-example";
27
+
28
+ /**
29
+ * Cache key for the credential detector.
30
+ *
31
+ * The transcript cache used to key only on builtin policies and behavioural
32
+ * audit detectors. Changing THIS scanner therefore kept serving the old
33
+ * `result.leaks` forever, which made a precision fix appear to do nothing on a
34
+ * warm machine. Bump this whenever matching or suppression semantics change.
35
+ */
36
+ export const LEAK_SCAN_VERSION = 5;
28
37
 
29
38
  /** One credential value found in one event. */
30
39
  export interface SecretMatch {
31
40
  /** The raw value. Held in memory only — the caller fingerprints it and must
32
41
  * never persist or transmit this. */
33
42
  value: string;
34
- /** The identifier it was assigned to, when it came from an assignment. For
35
- * the first-party class (230 of 237 secret-named assignments match no vendor
36
- * pattern) this is the only actionable label there is. */
43
+ /** The identifier it was assigned to, when a recognized shaped value also
44
+ * appeared in an assignment. Used only as an actionable label. */
37
45
  name: string | null;
38
- /** Which rule found it — a vendor label, or "assigned secret". */
46
+ /** Which recognizable credential shape found it. */
39
47
  rule: string;
40
48
  /** Whether the value carried a recognised vendor shape. Drives the record's
41
49
  * confidence, and therefore whether an alert may name a console. */
@@ -69,36 +77,6 @@ export interface SecretMatch {
69
77
  const ASSIGNMENT_RE =
70
78
  /(["']?)\b([A-Za-z_][A-Za-z0-9_]{0,127})\1([ \t]*(?::(?!\/\/)|=)[ \t]*)("[^"]*"|'[^']*'|[^\s;|&"']+)/g;
71
79
 
72
- /** Shortest value worth reporting. Below this it is a flag, a boolean or a
73
- * placeholder far more often than a credential. */
74
- const MIN_VALUE_LEN = 8;
75
-
76
- /**
77
- * Values that are structurally incapable of being a leaked credential.
78
- *
79
- * Deliberately structural, never entropy: the corpus's one confirmed-live
80
- * password measures 3.19 bits per character while 1.3 million UUIDs in the same
81
- * corpus sit at 3.72, so any entropy threshold that catches the password also
82
- * catches every UUID. These tests are decisive instead — a `$VAR` reference
83
- * contains no secret by construction, and `process.env.X` is the CORRECT secure
84
- * form, which the redactor currently flags 7,870 times.
85
- */
86
- function isNotACredential(value: string): boolean {
87
- if (value.length < MIN_VALUE_LEN) return true;
88
- // Shell/template indirection: the value is a reference, not a secret.
89
- if (/^[$<{(`%]/.test(value)) return true;
90
- if (value.includes("${") || value.includes("{{") || value.includes("%(")) return true;
91
- // A code expression — `process.env.DISCORD_TOKEN` is how you SHOULD do it.
92
- if (/^[A-Za-z_$][\w$]*(\.[A-Za-z_$][\w$]*)+$/.test(value)) return true;
93
- // Already masked, by us or by anyone else.
94
- if (value.includes("[REDACTED") || /^[•*#.]+$/.test(value)) return true;
95
- if (/(.)\1{5,}/.test(value)) return true; // xxxxxxxx, 00000000
96
- if (/^(true|false|null|undefined|none)$/i.test(value)) return true;
97
- if (/^-?\d+(\.\d+)?$/.test(value)) return true; // pure number
98
- if (/^\/|^~\/|^\.\.?\//.test(value)) return true; // a path
99
- return false;
100
- }
101
-
102
80
  /** Strip one layer of surrounding quotes. */
103
81
  function unquote(v: string): string {
104
82
  return (v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))
@@ -249,19 +227,17 @@ export function findSecrets(text: string): SecretMatch[] {
249
227
  }
250
228
  }
251
229
 
230
+ // Names annotate a value the shape layer already proved; they no longer
231
+ // create findings. Even after increasingly elaborate value heuristics, the
232
+ // name-only lane still produced hundreds of code literals and test fixtures
233
+ // on the measured machine. The product now deliberately prefers missing an
234
+ // unrecognisable first-party password over telling somebody they leaked 500
235
+ // credentials they do not have.
252
236
  for (const m of text.matchAll(ASSIGNMENT_RE)) {
253
237
  const name = m[2];
254
238
  const value = unquote(m[4]);
255
- if (!isSecretName(name) || isNotACredential(value) || isDocsLiteral(value)) continue;
256
239
  const existing = byValue.get(value);
257
- if (existing) {
258
- // A vendor-shaped value that also has a name: keep the vendor rule (it
259
- // can name a console) and gain the name (it says which of the user's
260
- // variables to change).
261
- existing.name ??= name;
262
- continue;
263
- }
264
- byValue.set(value, { value, name, rule: "assigned secret", shaped: false });
240
+ if (existing) existing.name ??= name;
265
241
  }
266
242
 
267
243
  return [...byValue.values()];
@@ -46,6 +46,24 @@ interface LeakIdentity {
46
46
  salt: string;
47
47
  /** Finding ids the user has said are not secrets. */
48
48
  dismissed: string[];
49
+ /**
50
+ * When the user last actually LOOKED at the leak report, as epoch ms.
51
+ *
52
+ * This is what stops the in-CLI notice, and it replaces a per-finding "we
53
+ * emitted it once" marker that silently lost the alert twice in one day: once
54
+ * because it was claimed on an event whose channel the host ignores, and once
55
+ * because hooks were disabled in the project being tested. Both marked every
56
+ * finding delivered and showed nothing, with no retry, ever.
57
+ *
58
+ * Keyed on the user's own action rather than on ours, the failure direction
59
+ * inverts: a dropped notice simply reappears next session, and the only thing
60
+ * that silences it is the user opening the report — which is the outcome the
61
+ * notice exists to produce.
62
+ *
63
+ * `identity`, alongside `dismissed`, for the same reason: losing it re-alerts
64
+ * about credentials somebody has already seen and judged.
65
+ */
66
+ reportViewedAt?: number;
49
67
  }
50
68
 
51
69
  /** Mode for both files. They carry no credential, but they do carry a machine
@@ -74,7 +92,14 @@ function readJson<T>(path: string): T | null {
74
92
  export function readLeakIdentity(home?: string): LeakIdentity {
75
93
  const existing = readJson<Partial<LeakIdentity>>(auditLeakIdentityFile(home));
76
94
  if (existing && typeof existing.salt === "string" && existing.salt.length >= 32) {
77
- return { salt: existing.salt, dismissed: existing.dismissed ?? [] };
95
+ return {
96
+ salt: existing.salt,
97
+ dismissed: existing.dismissed ?? [],
98
+ reportViewedAt:
99
+ typeof existing.reportViewedAt === "number" && Number.isFinite(existing.reportViewedAt)
100
+ ? existing.reportViewedAt
101
+ : undefined,
102
+ };
78
103
  }
79
104
  const minted: LeakIdentity = { salt: randomBytes(32).toString("hex"), dismissed: [] };
80
105
  writeLeakIdentity(minted, home);
@@ -194,6 +219,29 @@ export function writeLeakRecord(record: LeakRecord, home?: string, nowMs = Date.
194
219
  }
195
220
 
196
221
  /** Mark a finding as not-a-secret. Survives a reset; see the module header. */
222
+ /**
223
+ * Record that the user has seen the leak report.
224
+ *
225
+ * Called from the dashboard when it reads the findings, and from the
226
+ * interactive `failproofai audit`, which prints them. Both are the user
227
+ * looking — which is the only signal worth silencing the notice on.
228
+ *
229
+ * Never throws: failing to record a view costs one extra notice, which is the
230
+ * side this whole design errs on.
231
+ */
232
+ export function markLeakReportViewed(home?: string, nowMs = Date.now()): void {
233
+ try {
234
+ const identity = readLeakIdentity(home);
235
+ writeJsonAtomically(
236
+ auditLeakIdentityFile(home),
237
+ { ...identity, reportViewedAt: nowMs },
238
+ { mode: FILE_MODE },
239
+ );
240
+ } catch {
241
+ // See the doc comment: an unrecorded view re-notifies once.
242
+ }
243
+ }
244
+
197
245
  export function dismissFinding(id: string, home?: string): boolean {
198
246
  const identity = readLeakIdentity(home);
199
247
  if (identity.dismissed.includes(id)) return true;
@@ -147,14 +147,40 @@ const SECRET_NAME_SUBSTRINGS = [
147
147
  "APIKEY",
148
148
  "PRIVATEKEY",
149
149
  ];
150
- const SECRET_NAME_COMPONENTS = ["KEY", "PASS", "AUTH", "PAT", "SIG", "SIGNATURE", "SESSION", "COOKIE"];
150
+ const SECRET_NAME_COMPONENTS = ["PASS", "AUTH", "PAT", "SIG", "SIGNATURE", "COOKIE"];
151
151
 
152
152
  /**
153
- * `PWD` names a credential only in a COMPOUND identifier. `MYSQL_PWD` is
154
- * MySQL's documented password variable; a bare `PWD` is the shell's working
155
- * directory, which is on every second line of a captured session.
153
+ * Names that mean "credential" only inside a COMPOUND identifier.
154
+ *
155
+ * Every entry here was measured as a dominant false positive on a real 2,664-
156
+ * transcript corpus, and the reason they are demotions rather than deletions is
157
+ * that the SHAPE layer already catches real vendor keys regardless of what they
158
+ * are called. The name layer exists for FIRST-PARTY secrets with no recognisable
159
+ * format — and those essentially always carry a qualified name
160
+ * (`COMPOSIO_API_KEY`, `DB_PASSWORD`), never a bare one. So requiring a compound
161
+ * costs almost nothing real and removes most of the noise.
162
+ *
163
+ * PWD `MYSQL_PWD` is MySQL's documented password variable; a bare `PWD`
164
+ * is the shell's working directory, on every second line of a
165
+ * captured session.
166
+ * KEY a bare `key=` is a React list prop, a map entry, a sort field.
167
+ * 80 findings on the measured corpus, none of them credentials.
168
+ *
169
+ * `SIG` was demoted here too and put back: a bare `sig=` in a URL query IS a
170
+ * request signature, and an existing test says so. It also never appeared in
171
+ * the measured noise, unlike the two above — the rule is that a demotion needs
172
+ * evidence, not a plausible story.
173
+ *
174
+ * `SESSION` is REMOVED from both lists rather than demoted, and the difference
175
+ * matters: demoting it would have changed nothing, because
176
+ * `DBUS_SESSION_BUS_ADDRESS` is already a compound. It was the single largest
177
+ * source of noise measured — 254 of 500 findings, every one of them
178
+ * `DBUS_SESSION_BUS_ADDRESS`, `XDG_SESSION_TYPE`, `SESSION_MANAGER`,
179
+ * `session_id` or `sessionUpdate`. A session IDENTIFIER is not a rotatable
180
+ * credential; a session SECRET still matches through `SECRET`, `TOKEN`, or
181
+ * `KEY` in `sessionKey`.
156
182
  */
157
- const COMPOUND_ONLY_COMPONENTS = ["PWD"];
183
+ const COMPOUND_ONLY_COMPONENTS = ["PWD", "KEY"];
158
184
 
159
185
  /**
160
186
  * Split an identifier into its uppercased components, on `_`, `-`, `.` AND
@@ -300,6 +326,35 @@ export function isPublishedByDesign(name: string): boolean {
300
326
  return PUBLISHED_MARKERS.test(upper);
301
327
  }
302
328
 
329
+ /**
330
+ * How confidently the NAME alone says "credential".
331
+ *
332
+ * `strong` — the word means secret and nothing else: `DB_PASSWORD`,
333
+ * `STRIPE_SECRET`, `PRIVATE_KEY`. A short value here is still worth flagging;
334
+ * `hunter2` assigned to `PASSWORD` is a leaked password.
335
+ *
336
+ * `weak` — the word is ALSO ordinary programming vocabulary: `key`, `token`,
337
+ * `auth`, `sig`. Measured on a real 2,664-transcript machine these produced
338
+ * `keyType`, `tokenLimitCancelled`, `max_output_tokens`, `resultKey`,
339
+ * `configDirKey` — 394 findings from this layer, of which 389 had no vendor
340
+ * prefix and 227 were under 24 characters. Nothing that short is an API key.
341
+ * A weak name has to be backed by a value that actually looks like a secret.
342
+ */
343
+ export type SecretNameStrength = "strong" | "weak" | "none";
344
+
345
+ const STRONG_NAME_WORDS = ["SECRET", "PASSWORD", "PASSWD", "PASSPHRASE", "CREDENTIAL", "APIKEY", "PRIVATEKEY"];
346
+
347
+ export function secretNameStrength(name: string): SecretNameStrength {
348
+ if (!isSecretName(name)) return "none";
349
+ const upper = name.toUpperCase();
350
+ if (STRONG_NAME_WORDS.some((w) => upper.includes(w))) return "strong";
351
+ // `API_KEY` / `apiKey` split into components rather than matching APIKEY as a
352
+ // substring, and they are as unambiguous as the substring form.
353
+ const parts = identifierComponents(name);
354
+ if (parts.includes("API") && parts.includes("KEY")) return "strong";
355
+ return "weak";
356
+ }
357
+
303
358
  export function isSecretName(name: string): boolean {
304
359
  // Runs first and beats every other signal: a value the framework compiles
305
360
  // into the browser bundle is public no matter what the rest of the name says.
@@ -45,6 +45,9 @@ export interface TranscriptMetadata {
45
45
  mtimeMs: number;
46
46
  /** Byte size of the transcript file. Used for cache invalidation. */
47
47
  sizeBytes: number;
48
+ /** Optional host-authored session purpose. Used only to distinguish
49
+ * deliberate credential-scanner research from evidence of a leak. */
50
+ sessionDescription?: string;
48
51
  }
49
52
 
50
53
  /** Per-session detector state. Detectors mutate this freely. */
@@ -172,6 +175,9 @@ export interface TranscriptAuditResult {
172
175
  * absent reads as "this transcript predates leak scanning", not "clean".
173
176
  */
174
177
  leaks?: TranscriptLeak[];
178
+ /** The session was explicitly generating/researching credential fixtures,
179
+ * so its matches are examples rather than evidence of user leakage. */
180
+ leakScanSuppressed?: "credential-research";
175
181
  /** First/last timestamp per name. */
176
182
  rangeByName: Record<string, { first: string; last: string }>;
177
183
  }
@@ -36,7 +36,7 @@ import type { CustomHook } from "./policy-types";
36
36
  import { persistHookActivity } from "./hook-activity-store";
37
37
  import { deliveryHealth, deliveryHealthLine } from "./delivery-health";
38
38
  import { shapeNotice, canDeliverNotice, leakNoticeText } from "./notice";
39
- import { pendingLeakNotice, markLeakNoticeDelivered } from "../audit/leak-notice";
39
+ import { sessionNoticePending, markSessionNoticed } from "../audit/leak-notice";
40
40
  import { trackHookEvent, flushHookTelemetry } from "./hook-telemetry";
41
41
  import { resolveCwd } from "./resolve-cwd";
42
42
  import { resolvePermissionMode } from "./resolve-permission-mode";
@@ -121,15 +121,34 @@ function attachLeakNotice(
121
121
  eventType: string,
122
122
  cli: IntegrationType | undefined,
123
123
  outcome: HookEventOutcome,
124
+ sessionId: string | undefined,
124
125
  ): HookEventOutcome {
125
126
  try {
126
127
  if (!canDeliverNotice(cli)) return outcome;
127
128
  // canDeliverNotice() above already proved cli is set; narrow for the call.
128
129
  if (!cli) return outcome;
129
130
  const canonical = canonicalizeEventType(eventType, cli);
130
- if (canonical !== "Stop" && canonical !== "SessionStart") return outcome;
131
-
132
- const pending = pendingLeakNotice();
131
+ // STOP ONLY, and the narrowing is the fix for a measured failure rather
132
+ // than caution. `notice.ts` derived each host's channel from a live probe
133
+ // on **Stop**; SessionStart was allowed here on the assumption that a
134
+ // channel proven on one event works on another. It does not. On a real
135
+ // machine 499 findings were CLAIMED and the user saw nothing: SessionStart
136
+ // fires first in a session, consumed every claim, and the host dropped the
137
+ // field — Claude Code documents `hookSpecificOutput.additionalContext` for
138
+ // SessionStart, not `systemMessage`.
139
+ //
140
+ // That is the worst outcome this design has: a finding recorded as
141
+ // delivered that reached nobody, and never retried. Stop fires at the end
142
+ // of every assistant turn, so nothing is lost by waiting for it — the user
143
+ // is reading output at that moment anyway. Add SessionStart back only with
144
+ // a probe showing the notice actually rendered there.
145
+ if (canonical !== "Stop") return outcome;
146
+
147
+ // Keyed on the SESSION and on whether the user has opened the report, not
148
+ // on a per-finding "we emitted this once" marker — see
149
+ // `sessionNoticePending` for the two ways that marker lost the alert
150
+ // permanently. A dropped notice now costs one session's silence instead.
151
+ const pending = sessionNoticePending(sessionId ?? "");
133
152
  if (pending.count === 0) return outcome;
134
153
 
135
154
  const shaped = shapeNotice(cli, leakNoticeText(pending.count), outcome.stdout);
@@ -137,7 +156,7 @@ function attachLeakNotice(
137
156
 
138
157
  // Claimed only once the notice is actually on the stream, so a crash
139
158
  // between the two re-notifies rather than silently swallowing the alert.
140
- markLeakNoticeDelivered(pending.ids);
159
+ markSessionNoticed(sessionId ?? "");
141
160
  return {
142
161
  exitCode: outcome.exitCode,
143
162
  stdout: shaped.stdout,
@@ -810,11 +829,12 @@ export async function evaluateHookEvent(
810
829
  // the previous notice was dead code on every configured machine, and had
811
830
  // been since it shipped. Both paths return this object, so producing the
812
831
  // notice here reaches both with no change to either caller.
813
- const withNotice = attachLeakNotice(eventType, cli, {
814
- exitCode: result.exitCode,
815
- stdout: result.stdout,
816
- stderr: result.stderr,
817
- });
832
+ const withNotice = attachLeakNotice(
833
+ eventType,
834
+ cli,
835
+ { exitCode: result.exitCode, stdout: result.stdout, stderr: result.stderr },
836
+ sessionId,
837
+ );
818
838
  return withNotice;
819
839
  } finally {
820
840
  if (opts?.awaitTelemetryFlush ?? true) {
@@ -784,6 +784,7 @@ export const cursor: Integration = {
784
784
  // • exit 2 OR `permissionDecision: "deny"` → `throw new Error(reason)`
785
785
  // (which OpenCode surfaces as a tool-call failure to the agent)
786
786
  // • `additionalContext` → `client.session.prompt(...)` (fire-and-forget)
787
+ // • `failproofaiNotice` → `client.tui.showToast(...)` (user-visible)
787
788
  // • everything else → no-op (allow)
788
789
  //
789
790
  // Settings paths:
@@ -956,6 +957,19 @@ async function applyDecision(result, ctx, eventName) {
956
957
  if (out && out.decision && out.decision.behavior === "deny") {
957
958
  throw new Error((out.decision.message) || "Blocked by failproofai");
958
959
  }
960
+ const notice = parsed.failproofaiNotice;
961
+ if (typeof notice === "string" && notice && ctx && ctx.client && ctx.client.tui) {
962
+ try {
963
+ await ctx.client.tui.showToast({
964
+ body: {
965
+ title: "failproofai audit",
966
+ message: notice,
967
+ variant: "warning",
968
+ duration: 12_000,
969
+ },
970
+ });
971
+ } catch { /* a courtesy notice must never break the host hook */ }
972
+ }
959
973
  // Forward additional context as a prompt to the session. For Stop /
960
974
  // SubagentStop the prompt is the only force-retry channel (session.idle
961
975
  // already fired), so AWAIT to ensure the SDK round-trip completes before
@@ -28,16 +28,17 @@
28
28
  * copilot exit 0 + stderr renders as a `!` session warning
29
29
  * factory plain stdout renders under a `Hooks Stop` block
30
30
  *
31
- * opencode, pi and openclaw have real channels too, but they live inside the
32
- * plugin shims we ship rather than in a hook's stdout, so they are wired there.
33
- * cursor, devin, goose, antigravity and hermes have no channel that reaches a
34
- * human without an unsupported escape hatch they get nothing here, and their
35
- * users get the finding from `failproofai audit` and the emailed digest.
31
+ * opencode and pi have real channels too, but they live inside the plugin shims
32
+ * we ship rather than in a hook's stdout, so a private JSON field is consumed
33
+ * there and handed to `showToast` / `ui.notify`. OpenClaw, cursor, devin, goose,
34
+ * antigravity and hermes have no verified channel that reaches a human without
35
+ * an unsupported escape hatch they get nothing here, and their users get the
36
+ * finding from `failproofai audit` and the scheduled desktop notification.
36
37
  */
37
38
  import type { IntegrationType } from "./types";
38
39
 
39
40
  /** How a notice is delivered on one CLI. */
40
- type NoticeChannel = "systemMessage" | "stderr" | "stdout" | "none";
41
+ type NoticeChannel = "systemMessage" | "stderr" | "stdout" | "shim" | "none";
41
42
 
42
43
  /**
43
44
  * Per-CLI channel, from live probes rather than documentation.
@@ -52,6 +53,8 @@ const NOTICE_CHANNEL: Partial<Record<IntegrationType, NoticeChannel>> = {
52
53
  codex: "systemMessage",
53
54
  copilot: "stderr",
54
55
  factory: "stdout",
56
+ opencode: "shim",
57
+ pi: "shim",
55
58
  };
56
59
 
57
60
  /**
@@ -123,6 +126,23 @@ export function shapeNotice(
123
126
  // verdict JSON is already on the stream.
124
127
  if (existingStdout.trim()) return empty;
125
128
  return { stdout: text + "\n", stderr: "" };
129
+ case "shim": {
130
+ // OpenCode and Pi are in-process plugin integrations. Their generated
131
+ // shims consume this private field and call the host's real user-facing
132
+ // notification API; putting the text in additionalContext would send it
133
+ // to the model instead.
134
+ if (existingStdout.trim().startsWith("{")) {
135
+ try {
136
+ const parsed = JSON.parse(existingStdout) as Record<string, unknown>;
137
+ parsed.failproofaiNotice = text;
138
+ return { stdout: JSON.stringify(parsed), stderr: "" };
139
+ } catch {
140
+ return empty;
141
+ }
142
+ }
143
+ if (existingStdout.trim()) return empty;
144
+ return { stdout: JSON.stringify({ failproofaiNotice: text }), stderr: "" };
145
+ }
126
146
  default:
127
147
  return empty;
128
148
  }
@@ -146,10 +166,26 @@ export function canDeliverNotice(cli: IntegrationType | undefined): boolean {
146
166
  * directly in the prompt. So nothing scanned ever reaches this template.
147
167
  */
148
168
  export function leakNoticeText(newFindings: number): string {
149
- const n = newFindings === 1 ? "a credential" : `${newFindings} credentials`;
169
+ const n = newFindings === 1
170
+ ? "a possible credential exposure"
171
+ : `${newFindings} possible credential exposures`;
150
172
  return (
151
173
  `failproofai found ${n} in this project's agent transcripts.\n` +
152
- "Run `failproofai audit` to see the details, " +
153
- "or `failproofai audit --schedule` to get these by email."
174
+ "Run `failproofai audit` to review the matches and rotate anything real."
154
175
  );
155
176
  }
177
+
178
+ /** Copy for the OS notification raised by an unattended scheduled scan.
179
+ *
180
+ * Deliberately omits the count. A burst of generated fixtures can hit the
181
+ * record cap, and a desktop banner saying "500 leaked credentials" states a
182
+ * conclusion the detector cannot support. The dashboard carries the evidence;
183
+ * the banner's job is only to ask for review. */
184
+ export function desktopLeakNotice(): { title: string; body: string } {
185
+ return {
186
+ title: "failproofai audit needs review",
187
+ body:
188
+ "Possible credential exposure was found in your agent transcripts. " +
189
+ "Run failproofai audit to verify the matches and rotate anything real.",
190
+ };
191
+ }
@@ -1,3 +0,0 @@
1
- module.exports=[18622,(a,b,c)=>{b.exports=a.x("next/dist/compiled/next-server/app-page-turbo.runtime.prod.js",()=>require("next/dist/compiled/next-server/app-page-turbo.runtime.prod.js"))},20635,(a,b,c)=>{b.exports=a.x("next/dist/server/app-render/action-async-storage.external.js",()=>require("next/dist/server/app-render/action-async-storage.external.js"))},24725,(a,b,c)=>{b.exports=a.x("next/dist/server/app-render/after-task-async-storage.external.js",()=>require("next/dist/server/app-render/after-task-async-storage.external.js"))},43285,(a,b,c)=>{b.exports=a.x("next/dist/server/app-render/dynamic-access-async-storage.external.js",()=>require("next/dist/server/app-render/dynamic-access-async-storage.external.js"))},56704,(a,b,c)=>{b.exports=a.x("next/dist/server/app-render/work-async-storage.external.js",()=>require("next/dist/server/app-render/work-async-storage.external.js"))},32319,(a,b,c)=>{b.exports=a.x("next/dist/server/app-render/work-unit-async-storage.external.js",()=>require("next/dist/server/app-render/work-unit-async-storage.external.js"))},59043,(a,b,c)=>{b.exports=a.x("next/dist/server/runtime-reacts.external.js",()=>require("next/dist/server/runtime-reacts.external.js"))},1355,a=>{"use strict";var b=a.i(5050);let c=(0,b.createServerReference)("000ae5b9f039195740b0697383b66ef36475abe12d",b.callServer,void 0,b.findSourceMapURL,"getTelemetryConfig");a.s(["getTelemetryConfig",0,c])},3171,a=>{"use strict";var b=a.i(87924),c=a.i(72131),d=a.i(1355),e=a.i(66036);a.s(["default",0,function({error:a,reset:f}){return(0,c.useEffect)(()=>{(0,d.getTelemetryConfig)().then(b=>{(0,e.setClientTelemetryConfig)(b),(0,e.captureClientEvent)("client_error",{error_message:a.message,error_name:a.name,error_digest:a.digest,boundary:"global"})}).catch(()=>{})},[a]),(0,b.jsx)("html",{children:(0,b.jsx)("body",{children:(0,b.jsx)("main",{style:{minHeight:"100vh",display:"flex",alignItems:"center",justifyContent:"center",background:"#031035",color:"#f8fafc",fontFamily:"system-ui, sans-serif"},children:(0,b.jsxs)("div",{style:{textAlign:"center",padding:"2rem",border:"1px solid rgba(239,68,68,0.4)",borderRadius:"0.5rem",maxWidth:"500px"},children:[(0,b.jsx)("h2",{style:{color:"#ef4444",marginBottom:"0.5rem",fontSize:"1.25rem"},children:"Something went wrong"}),(0,b.jsx)("p",{style:{color:"#94a3b8",marginBottom:"1.5rem"},children:a.message||"An unexpected error occurred."}),(0,b.jsx)("button",{onClick:f,style:{padding:"0.5rem 1.25rem",background:"#3b82f6",color:"white",border:"none",borderRadius:"0.375rem",cursor:"pointer",fontSize:"0.875rem"},children:"Try again"})]})})})})}])},66036,a=>{"use strict";a.s(["captureClientEvent",0,function(a,b){},"setClientTelemetryConfig",0,function(a){}])},46058,(a,b,c)=>{"use strict";function d(a){if("function"!=typeof WeakMap)return null;var b=new WeakMap,c=new WeakMap;return(d=function(a){return a?c:b})(a)}c._=function(a,b){if(!b&&a&&a.__esModule)return a;if(null===a||"object"!=typeof a&&"function"!=typeof a)return{default:a};var c=d(b);if(c&&c.has(a))return c.get(a);var e={__proto__:null},f=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var g in a)if("default"!==g&&Object.prototype.hasOwnProperty.call(a,g)){var h=f?Object.getOwnPropertyDescriptor(a,g):null;h&&(h.get||h.set)?Object.defineProperty(e,g,h):e[g]=a[g]}return e.default=a,c&&c.set(a,e),e}},5050,(a,b,c)=>{"use strict";Object.defineProperty(c,"__esModule",{value:!0});var d={callServer:function(){return f.callServer},createServerReference:function(){return h.createServerReference},findSourceMapURL:function(){return g.findSourceMapURL}};for(var e in d)Object.defineProperty(c,e,{enumerable:!0,get:d[e]});let f=a.r(20611),g=a.r(1722),h=a.r(38783)},20611,(a,b,c)=>{"use strict";Object.defineProperty(c,"__esModule",{value:!0}),Object.defineProperty(c,"callServer",{enumerable:!0,get:function(){return g}});let d=a.r(72131),e=a.r(88347),f=a.r(90841);async function g(a,b){return new Promise((c,g)=>{(0,d.startTransition)(()=>{(0,f.dispatchAppRouterAction)({type:e.ACTION_SERVER_ACTION,actionId:a,actionArgs:b,resolve:c,reject:g})})})}("function"==typeof c.default||"object"==typeof c.default&&null!==c.default)&&void 0===c.default.__esModule&&(Object.defineProperty(c.default,"__esModule",{value:!0}),Object.assign(c.default,c),b.exports=c.default)},1722,(a,b,c)=>{"use strict";let d;Object.defineProperty(c,"__esModule",{value:!0}),Object.defineProperty(c,"findSourceMapURL",{enumerable:!0,get:function(){return d}});("function"==typeof c.default||"object"==typeof c.default&&null!==c.default)&&void 0===c.default.__esModule&&(Object.defineProperty(c.default,"__esModule",{value:!0}),Object.assign(c.default,c),b.exports=c.default)},88347,(a,b,c)=>{"use strict";Object.defineProperty(c,"__esModule",{value:!0});var d,e,f={ACTION_HMR_REFRESH:function(){return l},ACTION_NAVIGATE:function(){return i},ACTION_REFRESH:function(){return h},ACTION_RESTORE:function(){return j},ACTION_SERVER_ACTION:function(){return m},ACTION_SERVER_PATCH:function(){return k},PrefetchKind:function(){return n},ScrollBehavior:function(){return o}};for(var g in f)Object.defineProperty(c,g,{enumerable:!0,get:f[g]});let h="refresh",i="navigate",j="restore",k="server-patch",l="hmr-refresh",m="server-action";var n=((d={}).AUTO="auto",d.FULL="full",d),o=((e={})[e.Default=0]="Default",e[e.NoScroll=1]="NoScroll",e);("function"==typeof c.default||"object"==typeof c.default&&null!==c.default)&&void 0===c.default.__esModule&&(Object.defineProperty(c.default,"__esModule",{value:!0}),Object.assign(c.default,c),b.exports=c.default)},90841,(a,b,c)=>{"use strict";Object.defineProperty(c,"__esModule",{value:!0});var d={dispatchAppRouterAction:function(){return i},dispatchGestureState:function(){return j},refreshOnInstantNavigationUnlock:function(){return h},useActionQueue:function(){return k}};for(var e in d)Object.defineProperty(c,e,{enumerable:!0,get:d[e]});let f=a.r(46058)._(a.r(72131)),g=a.r(67009);a.r(88347);function h(){}function i(a){!0;throw Object.defineProperty(Error("Internal Next.js error: Router action dispatched before initialization."),"__NEXT_ERROR_CODE",{value:"E668",enumerable:!1,configurable:!0})}function j(a){!0;throw Object.defineProperty(Error("Internal Next.js error: Router action dispatched before initialization."),"__NEXT_ERROR_CODE",{value:"E668",enumerable:!1,configurable:!0})}function k(a){let[b,c]=f.default.useState(a.state),[d,e]=(0,f.useOptimistic)(b),h=(0,f.useMemo)(()=>d,[d]);return(0,g.isThenable)(h)?(0,f.use)(h):h}("function"==typeof c.default||"object"==typeof c.default&&null!==c.default)&&void 0===c.default.__esModule&&(Object.defineProperty(c.default,"__esModule",{value:!0}),Object.assign(c.default,c),b.exports=c.default)},42602,(a,b,c)=>{"use strict";b.exports=a.r(18622)},9270,(a,b,c)=>{"use strict";b.exports=a.r(42602).vendored.contexts.AppRouterContext},36313,(a,b,c)=>{"use strict";b.exports=a.r(42602).vendored.contexts.HooksClientContext},18341,(a,b,c)=>{"use strict";b.exports=a.r(42602).vendored.contexts.ServerInsertedHtml},35112,(a,b,c)=>{"use strict";b.exports=a.r(42602).vendored["react-ssr"].ReactDOM},87924,(a,b,c)=>{"use strict";b.exports=a.r(42602).vendored["react-ssr"].ReactJsxRuntime},38783,(a,b,c)=>{"use strict";b.exports=a.r(42602).vendored["react-ssr"].ReactServerDOMTurbopackClient},72131,(a,b,c)=>{"use strict";b.exports=a.r(42602).vendored["react-ssr"].React},67009,(a,b,c)=>{"use strict";function d(a){return null!==a&&"object"==typeof a&&"then"in a&&"function"==typeof a.then}Object.defineProperty(c,"__esModule",{value:!0}),Object.defineProperty(c,"isThenable",{enumerable:!0,get:function(){return d}})}];
2
-
3
- //# sourceMappingURL=%5Broot-of-the-server%5D__0rgu2r3._.js.map
@@ -1,23 +0,0 @@
1
- module.exports=[1355,a=>{"use strict";var b=a.i(5050);let c=(0,b.createServerReference)("000ae5b9f039195740b0697383b66ef36475abe12d",b.callServer,void 0,b.findSourceMapURL,"getTelemetryConfig");a.s(["getTelemetryConfig",0,c])},18952,a=>{"use strict";var b=a.i(87924),c=a.i(72131);let d=[],e=0,f=new Set;function g(){for(let a of f)a()}function h(a){return f.add(a),()=>f.delete(a)}function i(){return d}function j({message:a}){let[d,e]=(0,c.useState)(!1);return(0,c.useEffect)(()=>{requestAnimationFrame(()=>e(!0));let a=setTimeout(()=>e(!1),2e3);return()=>clearTimeout(a)},[]),(0,b.jsx)("div",{className:`bg-card border border-border text-foreground text-sm px-4 py-2 rounded-lg shadow-lg transition-opacity duration-300 ${d?"opacity-100":"opacity-0"}`,children:a})}a.s(["Toaster",0,function(){let a=(0,c.useSyncExternalStore)(h,i,i);return(0,b.jsx)("div",{className:"fixed bottom-4 right-4 z-50 flex flex-col gap-2",children:a.map(a=>(0,b.jsx)(j,{message:a.message},a.id))})},"toast",0,function(a){let b=e++;d=[...d,{id:b,message:a}],g(),setTimeout(()=>{d=d.filter(a=>a.id!==b),g()},2500)}])},10765,a=>{"use strict";var b=a.i(87924),c=a.i(72131),d=a.i(50944),e=a.i(1355),f=a.i(66036);let g=(0,c.createContext)(void 0);a.s(["PostHogProvider",0,function({children:a}){let h=(0,d.usePathname)(),i=(0,c.useRef)(null),j=(0,c.useRef)(!1);(0,c.useEffect)(()=>{let a=!1;return(0,e.getTelemetryConfig)().then(b=>{a||((0,f.setClientTelemetryConfig)(b),j.current=!0,(0,f.captureClientEvent)("$pageview"),i.current=window.location.pathname)}),()=>{a=!0}},[]),(0,c.useEffect)(()=>{j.current&&i.current!==h&&(i.current=h,(0,f.captureClientEvent)("$pageview"))},[h]);let k=c.default.useCallback((a,b)=>{(0,f.captureClientEvent)(a,b)},[]);return(0,b.jsx)(g.Provider,{value:{capture:k},children:a})},"usePostHog",0,function(){let a=(0,c.useContext)(g);if(void 0===a)throw Error("usePostHog must be used within a PostHogProvider");return a}])},66036,a=>{"use strict";a.s(["captureClientEvent",0,function(a,b){},"setClientTelemetryConfig",0,function(a){}])},46058,(a,b,c)=>{"use strict";function d(a){if("function"!=typeof WeakMap)return null;var b=new WeakMap,c=new WeakMap;return(d=function(a){return a?c:b})(a)}c._=function(a,b){if(!b&&a&&a.__esModule)return a;if(null===a||"object"!=typeof a&&"function"!=typeof a)return{default:a};var c=d(b);if(c&&c.has(a))return c.get(a);var e={__proto__:null},f=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var g in a)if("default"!==g&&Object.prototype.hasOwnProperty.call(a,g)){var h=f?Object.getOwnPropertyDescriptor(a,g):null;h&&(h.get||h.set)?Object.defineProperty(e,g,h):e[g]=a[g]}return e.default=a,c&&c.set(a,e),e}},5050,(a,b,c)=>{"use strict";Object.defineProperty(c,"__esModule",{value:!0});var d={callServer:function(){return f.callServer},createServerReference:function(){return h.createServerReference},findSourceMapURL:function(){return g.findSourceMapURL}};for(var e in d)Object.defineProperty(c,e,{enumerable:!0,get:d[e]});let f=a.r(20611),g=a.r(1722),h=a.r(38783)},20611,(a,b,c)=>{"use strict";Object.defineProperty(c,"__esModule",{value:!0}),Object.defineProperty(c,"callServer",{enumerable:!0,get:function(){return g}});let d=a.r(72131),e=a.r(88347),f=a.r(90841);async function g(a,b){return new Promise((c,g)=>{(0,d.startTransition)(()=>{(0,f.dispatchAppRouterAction)({type:e.ACTION_SERVER_ACTION,actionId:a,actionArgs:b,resolve:c,reject:g})})})}("function"==typeof c.default||"object"==typeof c.default&&null!==c.default)&&void 0===c.default.__esModule&&(Object.defineProperty(c.default,"__esModule",{value:!0}),Object.assign(c.default,c),b.exports=c.default)},1722,(a,b,c)=>{"use strict";let d;Object.defineProperty(c,"__esModule",{value:!0}),Object.defineProperty(c,"findSourceMapURL",{enumerable:!0,get:function(){return d}});("function"==typeof c.default||"object"==typeof c.default&&null!==c.default)&&void 0===c.default.__esModule&&(Object.defineProperty(c.default,"__esModule",{value:!0}),Object.assign(c.default,c),b.exports=c.default)},88347,(a,b,c)=>{"use strict";Object.defineProperty(c,"__esModule",{value:!0});var d,e,f={ACTION_HMR_REFRESH:function(){return l},ACTION_NAVIGATE:function(){return i},ACTION_REFRESH:function(){return h},ACTION_RESTORE:function(){return j},ACTION_SERVER_ACTION:function(){return m},ACTION_SERVER_PATCH:function(){return k},PrefetchKind:function(){return n},ScrollBehavior:function(){return o}};for(var g in f)Object.defineProperty(c,g,{enumerable:!0,get:f[g]});let h="refresh",i="navigate",j="restore",k="server-patch",l="hmr-refresh",m="server-action";var n=((d={}).AUTO="auto",d.FULL="full",d),o=((e={})[e.Default=0]="Default",e[e.NoScroll=1]="NoScroll",e);("function"==typeof c.default||"object"==typeof c.default&&null!==c.default)&&void 0===c.default.__esModule&&(Object.defineProperty(c.default,"__esModule",{value:!0}),Object.assign(c.default,c),b.exports=c.default)},90841,(a,b,c)=>{"use strict";Object.defineProperty(c,"__esModule",{value:!0});var d={dispatchAppRouterAction:function(){return i},dispatchGestureState:function(){return j},refreshOnInstantNavigationUnlock:function(){return h},useActionQueue:function(){return k}};for(var e in d)Object.defineProperty(c,e,{enumerable:!0,get:d[e]});let f=a.r(46058)._(a.r(72131)),g=a.r(67009);a.r(88347);function h(){}function i(a){!0;throw Object.defineProperty(Error("Internal Next.js error: Router action dispatched before initialization."),"__NEXT_ERROR_CODE",{value:"E668",enumerable:!1,configurable:!0})}function j(a){!0;throw Object.defineProperty(Error("Internal Next.js error: Router action dispatched before initialization."),"__NEXT_ERROR_CODE",{value:"E668",enumerable:!1,configurable:!0})}function k(a){let[b,c]=f.default.useState(a.state),[d,e]=(0,f.useOptimistic)(b),h=(0,f.useMemo)(()=>d,[d]);return(0,g.isThenable)(h)?(0,f.use)(h):h}("function"==typeof c.default||"object"==typeof c.default&&null!==c.default)&&void 0===c.default.__esModule&&(Object.defineProperty(c.default,"__esModule",{value:!0}),Object.assign(c.default,c),b.exports=c.default)},14827,(a,b,c)=>{"use strict";Object.defineProperty(c,"__esModule",{value:!0});var d={createLinkPrefetchPartialError:function(){return g},createUnrenderedSegmentError:function(){return f}};for(var e in d)Object.defineProperty(c,e,{enumerable:!0,get:d[e]});function f(a,b){let c=`Route "${a}": Could not validate that a segment in your UI has instant navigation.`;if(b.length>0){let a=1===b.length?"Dropped segment":"Dropped segments";c+=`
2
-
3
- This segment was dropped from rendering. Issues that would prevent instant navigation will go undetected.
4
-
5
- ${a}:
6
- ${b.map(a=>` ${a}`).join("\n")}
7
-
8
- Ways to fix this:
9
- - [render] Render the dropped segment
10
- - [ignore] Set \`export const instant = false\` to opt the dropped segment out of instant-navigation validation
11
-
12
- Learn more: https://nextjs.org/docs/messages/instant-unrendered-segment`}return Object.defineProperty(Error(c),"__NEXT_ERROR_CODE",{value:"E1286",enumerable:!1,configurable:!0})}function g(a){return Object.defineProperty(Error(`Next.js encountered dynamic data during prefetching for "${a}".
13
-
14
- This will lead to slower, more expensive prefetches.
15
-
16
- Ways to fix this:
17
- - [upgrade] Opt into Partial Prefetching by exporting \`const prefetch = 'partial'\` from the page or layout, or by setting \`partialPrefetching: true\` in next.config to opt the whole app in
18
- - [disable] Remove \`prefetch={true}\` from the <Link> to use the default prefetch
19
- - [ignore] Set \`export const instant = false\` to opt the route out of instant-navigation validation
20
-
21
- Learn more: https://nextjs.org/docs/messages/instant-link-prefetch-partial`),"__NEXT_ERROR_CODE",{value:"E1435",enumerable:!1,configurable:!0})}},88644,(a,b,c)=>{"use strict";Object.defineProperty(c,"__esModule",{value:!0}),Object.defineProperty(c,"InvariantError",{enumerable:!0,get:function(){return d}});class d extends Error{constructor(a,b){super(`Invariant: ${a.endsWith(".")?a:a+"."} This is a bug in Next.js.`,b),Object.defineProperty(this,"__NEXT_ERROR_CODE",{value:"E1179",enumerable:!1,configurable:!0}),this.name="InvariantError"}}},67009,(a,b,c)=>{"use strict";function d(a){return null!==a&&"object"==typeof a&&"then"in a&&"function"==typeof a.then}Object.defineProperty(c,"__esModule",{value:!0}),Object.defineProperty(c,"isThenable",{enumerable:!0,get:function(){return d}})},41997,(a,b,c)=>{"use strict";Object.defineProperty(c,"__esModule",{value:!0});var d={BailoutToCSRError:function(){return g},isBailoutToCSRError:function(){return h}};for(var e in d)Object.defineProperty(c,e,{enumerable:!0,get:d[e]});let f="BAILOUT_TO_CLIENT_SIDE_RENDERING";class g extends Error{constructor(a){super(`Bail out to client-side rendering: ${a}`),this.reason=a,this.digest=f}}function h(a){return"object"==typeof a&&null!==a&&"digest"in a&&a.digest===f}},54427,(a,b,c)=>{"use strict";function d(){let a,b,c=new Promise((c,d)=>{a=c,b=d});return{resolve:a,reject:b,promise:c}}Object.defineProperty(c,"__esModule",{value:!0}),Object.defineProperty(c,"createPromiseWithResolvers",{enumerable:!0,get:function(){return d}})},39118,(a,b,c)=>{"use strict";Object.defineProperty(c,"__esModule",{value:!0});var d={DEFAULT_SEGMENT_KEY:function(){return l},NOT_FOUND_SEGMENT_KEY:function(){return m},PAGE_SEGMENT_KEY:function(){return k},addSearchParamsIfPageSegment:function(){return i},computeSelectedLayoutSegment:function(){return j},getSegmentValue:function(){return f},getSelectedLayoutSegmentPath:function(){return function a(b,c,d=!0,e=[]){let g;if(d)g=b[1][c];else{let a=b[1];g=a.children??Object.values(a)[0]}if(!g)return e;let h=f(g[0]);return!h||h.startsWith(k)?e:(e.push(h),a(g,c,!1,e))}},isGroupSegment:function(){return g},isParallelRouteSegment:function(){return h}};for(var e in d)Object.defineProperty(c,e,{enumerable:!0,get:d[e]});function f(a){return Array.isArray(a)?a[1]:a}function g(a){return"("===a[0]&&a.endsWith(")")}function h(a){return a.startsWith("@")&&"@children"!==a}function i(a,b){if(a.includes(k)){let a=JSON.stringify(b);return"{}"!==a?k+"?"+a:k}return a}function j(a,b){if(!a||0===a.length)return null;let c="children"===b?a[0]:a[a.length-1];return c===l?null:c}let k="__PAGE__",l="__DEFAULT__",m="/_not-found"}];
22
-
23
- //# sourceMappingURL=_0-oij9d._.js.map
@@ -1 +0,0 @@
1
- (globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,24358,e=>{"use strict";var s=e.i(43476),a=e.i(71645),t=e.i(95187);let n=(0,t.createServerReference)("00840e34fdc0ab0093541d34b83cc649ce4f5d86f3",t.callServer,void 0,t.findSourceMapURL,"getAuditResultAction");var r=e.i(12344);function i({cachedAt:e,fixed:a=!1}){return(0,s.jsxs)("footer",{className:a?"report-footer report-footer--fixed":"report-footer",children:[(0,s.jsx)("img",{src:"/logo.svg",alt:"failproof_ai",style:{height:18,display:"inline-block",verticalAlign:"middle"}}),(0,s.jsx)("span",{style:{margin:"0 12px",color:"var(--line-2)"},children:"·"}),"audit v1.0",(0,s.jsx)("span",{style:{margin:"0 12px",color:"var(--line-2)"},children:"·"}),"generated ",function(e){if(!e)return"—";let s=new Date(e);if(Number.isNaN(s.getTime()))return"—";let a=s.getUTCDate().toString().padStart(2,"0"),t=["jan","feb","mar","apr","may","jun","jul","aug","sep","oct","nov","dec"][s.getUTCMonth()],n=s.getUTCFullYear(),r=s.getUTCHours().toString().padStart(2,"0"),i=s.getUTCMinutes().toString().padStart(2,"0");return`${a} ${t} ${n}, ${r}:${i} utc`}(e),(0,s.jsx)("span",{style:{margin:"0 12px",color:"var(--line-2)"},children:"·"}),(0,s.jsx)("span",{style:{color:"var(--ink-2)"},children:"auto-healing for your agents."})]})}let l=(0,t.createServerReference)("40d9250b20c84e4d86f5898290e4a7a464c019b309",t.callServer,void 0,t.findSourceMapURL,"dismissLeakAction"),c=(0,t.createServerReference)("00290d6c5d144e8bf121d1b8e95c08990b6bafca27",t.callServer,void 0,t.findSourceMapURL,"getLeaksAction"),d=(0,t.createServerReference)("40f2667668aae1ab9d2a5b00c81eebc40f91a3fc8b",t.callServer,void 0,t.findSourceMapURL,"setLeakNotifyAction");function o(){let[e,t]=(0,a.useState)(null),[n,r]=(0,a.useState)(!0),[i,o]=(0,a.useState)(null),u=(0,a.useCallback)(async()=>{let e=await c();t(e.rows),r(e.notify)},[]);(0,a.useEffect)(()=>{u()},[u]);let h=(0,a.useCallback)(async e=>{o(e);try{await l(e)&&t(s=>s?.filter(s=>s.id!==e)??null)}finally{o(null)}},[]),p=(0,a.useCallback)(async()=>{let e=!n;r(e),await d(e)||r(!e)},[n]);return null===e?null:(0,s.jsxs)("section",{className:"audit-sec","data-screen-label":"02 Leaks",children:[(0,s.jsxs)("div",{className:"audit-sec-head",children:[(0,s.jsxs)("span",{className:"audit-sec-eyebrow",children:[(0,s.jsx)("span",{className:"ix",children:"02"}),"// credentials"]}),(0,s.jsx)("button",{type:"button",className:"cbb-toggle leak-notify","data-on":n,onClick:p,"aria-label":n?"turn desktop notifications off":"turn desktop notifications on",title:"desktop notification when a new credential is found",children:(0,s.jsx)("span",{className:"cbb-toggle-knob"})})]}),0===e.length?(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)("h2",{className:"audit-sec-title",children:"no credentials found"}),(0,s.jsxs)("div",{className:"audit-sec-sub",children:["// nothing in this machine's transcripts matched a known key format",(0,s.jsx)("br",{}),"// or a secret-named assignment."]})]}):(0,s.jsxs)(s.Fragment,{children:[(0,s.jsxs)("h2",{className:"audit-sec-title",children:[e.length," credential",1===e.length?"":"s"," in your transcripts"]}),(0,s.jsx)("div",{className:"audit-sec-sub",children:"// one row per distinct key — an exposure count is not a key count."}),(0,s.jsx)("ul",{className:"leak-list",children:e.map(e=>(0,s.jsxs)("li",{className:"leak-row","data-attributed":e.attributed,children:[(0,s.jsxs)("div",{className:"leak-row-top",children:[(0,s.jsx)("code",{className:"leak-key",children:e.display}),(0,s.jsx)("span",{className:"leak-label",children:e.label}),e.name?(0,s.jsx)("span",{className:"leak-name",children:e.name}):null,(0,s.jsx)("button",{type:"button",className:"leak-dismiss",onClick:()=>void h(e.id),disabled:i===e.id,children:"not a secret"})]}),(0,s.jsxs)("div",{className:"leak-how",children:[(0,s.jsx)("span",{className:"leak-verb",children:e.mechanism})," in ",(0,s.jsx)("span",{className:"leak-where",children:e.project})," via ",(0,s.jsx)("span",{className:"leak-who",children:e.cli})," · ",(0,s.jsx)("span",{className:"leak-when",children:function(e){let s=Date.parse(e);if(!Number.isFinite(s))return"unknown";let a=Math.floor((Date.now()-s)/864e5);return a<=0?"today":1===a?"yesterday":a<30?`${a}d ago`:new Date(s).toLocaleDateString(void 0,{month:"short",day:"numeric"})}(e.lastSeen)})]}),(0,s.jsxs)("div",{className:"leak-foot",children:[(0,s.jsxs)("span",{className:"leak-meta",children:[e.occurrences," exposure",1===e.occurrences?"":"s",e.sessions>1?` \xb7 ${e.sessions} sessions`:""," · ",e.length," chars"]}),(0,s.jsx)("span",{className:"leak-dir",children:"input"===e.direction?"sent by the agent — blockable at PreToolUse":"returned to the agent — already in the transcript"}),(0,s.jsx)("span",{className:"leak-fix",children:e.attributed?`rotate it in the ${e.label.replace(/ (API )?(key|token).*$/i,"")} console`:e.name?`find what reads ${e.name}, then rotate it there`:"trace it to its owner, then rotate it"})]})]},e.id))})]})]})}var u=e.i(86616);function h({mode:e,running:a,onStarted:t,onCompleted:n}){let{capture:i}=(0,r.usePostHog)(),l=async()=>{i("audit_first_run_clicked",{source:"empty_state",mode:e}),t();try{await (0,u.triggerRun)({cli:[],since:"all"})}catch(e){console.error("audit first run failed:",e)}finally{await n()}};return"no-cache"===e?(0,s.jsxs)("section",{className:"section empty-section","data-screen-label":"00 Empty",children:[(0,s.jsxs)("div",{className:"section-mast",children:[(0,s.jsxs)("div",{className:"section-label",children:[(0,s.jsx)("span",{className:"glyph",children:"━━"})," audit"," ",(0,s.jsx)("span",{className:"sep",children:"·"})," first run"]}),(0,s.jsxs)("div",{className:"section-meta",children:[(0,s.jsx)("span",{style:{color:"var(--dim)"},children:"○"})," no cache yet"]})]}),(0,s.jsx)("h2",{className:"section-h",children:"scan and see."}),(0,s.jsxs)("div",{className:"panel empty-panel",children:[(0,s.jsxs)("div",{className:"empty-glyph","aria-hidden":"true",children:[(0,s.jsx)("div",{className:"empty-glyph-grid",children:Array.from({length:36}).map((e,a)=>{let t=["1","5","8","10","13","17","20","23","25","30","32"].includes(String(a));return(0,s.jsx)("span",{className:"px"+(t?" on":"")},a)})}),(0,s.jsx)("div",{className:"empty-glyph-label",children:"▮▮ no audit data yet"})]}),(0,s.jsx)("h3",{className:"empty-headline",children:"run your first audit."}),(0,s.jsxs)("p",{className:"empty-sub",children:["we'll walk every transcript across your installed CLIs — Claude Code, Codex, Copilot, Cursor, OpenCode, Pi — and count every wasteful","or risky action. you'll get your agent's archetype and a punch-list."]}),(0,s.jsxs)("div",{className:"empty-actions",children:[(0,s.jsx)("button",{type:"button",className:"btn btn-primary btn-press empty-cta",onClick:l,disabled:a,children:a?"[ scanning… ]":"[ run audit ]"}),(0,s.jsx)("span",{className:"empty-meta",children:"scans all sessions · all installed CLIs · may take a while"})]})]})]}):(0,s.jsxs)("section",{className:"section empty-section","data-screen-label":"00 Empty",children:[(0,s.jsxs)("div",{className:"section-mast",children:[(0,s.jsxs)("div",{className:"section-label",children:[(0,s.jsx)("span",{className:"glyph",children:"━━"})," audit"," ",(0,s.jsx)("span",{className:"sep",children:"·"})," zero transcripts"]}),(0,s.jsxs)("div",{className:"section-meta",children:[(0,s.jsx)("span",{style:{color:"var(--amber)"},children:"●"})," hooks not installed"]})]}),(0,s.jsx)("h2",{className:"section-h",children:"nothing to read."}),(0,s.jsxs)("div",{className:"panel empty-panel",children:[(0,s.jsxs)("div",{className:"empty-glyph","aria-hidden":"true",children:[(0,s.jsx)("div",{className:"empty-glyph-grid",children:Array.from({length:36}).map((e,a)=>{let t=["2","4","9","11","16","18","23","25","27","30","33"].includes(String(a));return(0,s.jsx)("span",{className:"px"+(t?" on":"")},a)})}),(0,s.jsx)("div",{className:"empty-glyph-label",children:"▮▮ no sessions found"})]}),(0,s.jsx)("h3",{className:"empty-headline",children:"install hooks first."}),(0,s.jsx)("p",{className:"empty-sub",children:"failproofai couldn't find any transcripts to scan on this machine. install the hooks for at least one CLI and come back."}),(0,s.jsxs)("div",{className:"empty-actions",children:[(0,s.jsx)("a",{href:"https://docs.befailproof.ai/getting-started",target:"_blank",rel:"noopener noreferrer",className:"btn btn-primary btn-press empty-cta",onClick:()=>{i("audit_install_guide_clicked",{source:"empty_state",mode:e})},children:"[ install guide → ]"}),(0,s.jsx)("span",{className:"empty-meta",children:"takes about 30 seconds · one command per CLI"})]})]})]})}let p=[{label:"discovering transcripts",detail:"walking ~/.claude, ~/.codex, ~/.cursor, …"},{label:"parsing session logs",detail:"reading JSONL + sqlite session stores"},{label:"running policy checks",detail:"replaying every policy against each tool call"},{label:"aggregating results",detail:"counting hits, ranking by frequency"}];function m(){let[e,t]=(0,a.useState)(0),[n,r]=(0,a.useState)(0);(0,a.useEffect)(()=>{let e=setInterval(()=>{t(e=>Math.min(e+1,p.length-1))},4e3),s=setInterval(()=>r(e=>(e+1)%4),350);return()=>{clearInterval(e),clearInterval(s)}},[]);let i=".".repeat(n+1),l=e===p.length-1,c=Math.round(100*Math.min(.9,(e+1)/p.length*.9));return(0,s.jsxs)("section",{className:"section running-section","data-screen-label":"00 Running",children:[(0,s.jsxs)("div",{className:"section-mast",children:[(0,s.jsxs)("div",{className:"section-label",children:[(0,s.jsx)("span",{className:"glyph",children:"━━"})," audit"," ",(0,s.jsx)("span",{className:"sep",children:"·"})," in progress"]}),(0,s.jsxs)("div",{className:"section-meta",children:[(0,s.jsx)("span",{style:{color:"var(--accent-pink)"},children:"●"})," scanning"]})]}),(0,s.jsxs)("h2",{className:"section-h",children:["scanning sessions",i]}),(0,s.jsxs)("div",{className:"panel running-panel",children:[(0,s.jsxs)("div",{className:"running-header",children:[(0,s.jsx)("span",{className:"running-prompt",children:"$"}),(0,s.jsx)("span",{className:"running-cmd",children:"failproofai audit"}),(0,s.jsx)("span",{className:"running-cursor","aria-hidden":"true",children:"▮"})]}),(0,s.jsx)("ul",{className:"running-stages",children:p.map((a,t)=>{let r=t<e,i=t===e;return(0,s.jsxs)("li",{className:"running-stage"+(r?" done":i?" active":" queued"),children:[(0,s.jsx)("span",{className:"running-marker","aria-hidden":"true",children:r?"✓":i?"▮▮":"○"}),(0,s.jsxs)("div",{className:"running-stage-body",children:[(0,s.jsx)("div",{className:"running-stage-label",children:a.label}),i&&(0,s.jsx)("div",{className:"running-stage-detail",children:l?"finishing up…":a.detail})]}),i&&(0,s.jsx)("span",{className:"running-stage-spin","aria-hidden":"true",children:["⠋","⠙","⠹","⠸"][n]})]},t)})}),(0,s.jsxs)("div",{className:"running-bar-label",children:[(0,s.jsx)("span",{children:"progress"}),(0,s.jsxs)("span",{style:{color:"var(--dim)"},children:[c,"%"]})]}),(0,s.jsx)("div",{className:"running-bar-track",children:(0,s.jsx)("div",{className:"running-bar-fill",style:{width:`${c}%`}})}),(0,s.jsx)("p",{className:"running-foot",children:"this can take a while depending on how much session history you have."})]})]})}let g={timeout:"audit took too long — try again",network:"network hiccup — check your connection",post_failed:"audit failed — try again"};function x({status:e,onDismiss:t}){let[n,r]=(0,a.useState)(()=>Date.now());if((0,a.useEffect)(()=>{if("running"!==e.kind)return;let s=setInterval(()=>r(Date.now()),1e3);return()=>clearInterval(s)},[e.kind]),"idle"===e.kind)return null;if("running"===e.kind){let a=Math.max(0,n-e.startedAt);return(0,s.jsxs)("div",{className:"audit-progress-strip",role:"status","aria-live":"polite",children:[(0,s.jsxs)("div",{className:"audit-progress-strip__inner",children:[(0,s.jsxs)("span",{className:"audit-progress-strip__label",children:[(0,s.jsx)("span",{className:"audit-progress-strip__spinner","aria-hidden":"true"}),"re-auditing your sessions"]}),(0,s.jsx)("span",{className:"audit-progress-strip__elapsed",children:function(e){if(e<0)return"00:00";let s=Math.floor(e/1e3),a=Math.floor(s/60).toString().padStart(2,"0"),t=(s%60).toString().padStart(2,"0");return`${a}:${t}`}(a)})]}),(0,s.jsx)("div",{className:"audit-progress-strip__pulse","aria-hidden":"true"})]})}return(0,s.jsx)("div",{className:"audit-progress-strip audit-progress-strip--failed",role:"alert",children:(0,s.jsxs)("div",{className:"audit-progress-strip__inner",children:[(0,s.jsxs)("span",{className:"audit-progress-strip__label",children:[(0,s.jsx)("span",{className:"audit-progress-strip__x","aria-hidden":"true",children:"×"}),g[e.reason]]}),(0,s.jsx)("button",{type:"button",className:"audit-progress-strip__dismiss",onClick:t,"aria-label":"dismiss",children:"[ dismiss ]"})]})})}function f({result:e,cachedAt:t,projectFromUrl:n,isRunning:l,rerunStatus:c,onRerun:d,onDismissRerun:u}){let{capture:h}=(0,r.usePostHog)(),{detectorsTriggered:p,missing:m}=(0,a.useMemo)(()=>{let s=0,a=0;for(let t of e.results)t.hits>0&&s++,"builtin"===t.source&&!t.enabledInConfig&&t.hits>0&&a++;return{detectorsTriggered:s,missing:a}},[e]),g=(0,a.useRef)(!1);return(0,a.useEffect)(()=>{g.current||(g.current=!0,h("audit_dashboard_viewed",{missing:m,transcripts_scanned:e.transcripts.scanned,results_count:e.results.length,detectors_triggered:p}))},[h,m,e.transcripts.scanned,e.results.length,p]),(0,s.jsxs)("div",{className:"app",children:[(0,s.jsx)(x,{status:c,onDismiss:u}),(0,s.jsxs)("div",{className:"app-shell",children:[(0,s.jsxs)("div",{className:"report",children:[(0,s.jsx)(j,{transcripts:e.transcripts.scanned,events:e.eventsScanned??0,projects:e.projectsScanned.length}),(0,s.jsx)(o,{})]}),(0,s.jsx)(i,{cachedAt:t})]})]})}function j({transcripts:e,events:a,projects:t}){return(0,s.jsxs)("section",{className:"audit-sec","data-screen-label":"01 Scan",children:[(0,s.jsx)("div",{className:"audit-sec-head",children:(0,s.jsxs)("span",{className:"audit-sec-eyebrow",children:[(0,s.jsx)("span",{className:"ix",children:"01"}),"// scan"]})}),(0,s.jsx)("h2",{className:"audit-sec-title",children:"scan complete"}),(0,s.jsxs)("div",{className:"audit-sec-sub",children:[a.toLocaleString()," tool call",1===a?"":"s"," across ",e.toLocaleString()," ","transcript",1===e?"":"s",t>0?` \xb7 ${t.toLocaleString()} project${1===t?"":"s"}`:""]}),(0,s.jsxs)("div",{className:"audit-sec-sub",children:["// the report is being rebuilt around leak detection. the scan, the",(0,s.jsx)("br",{}),"// 12-CLI transcript reader and the emailed digest are unaffected."]})]})}function y({running:e,mode:a="no-cache",rerunStatus:t,onDismissRerun:n,onStarted:r,onCompleted:l}){return(0,s.jsxs)("div",{className:"app",children:[(0,s.jsx)(x,{status:t,onDismiss:n}),(0,s.jsxs)("div",{className:"app-shell",children:[(0,s.jsx)("div",{className:"report",children:e?(0,s.jsx)(m,{}):(0,s.jsx)(h,{mode:a,running:e,onStarted:r,onCompleted:l})}),(0,s.jsx)(i,{cachedAt:null,fixed:!0})]})]})}e.s(["AuditDashboard",0,function({initial:e,projectFromUrl:t,totalCatalogSize:i}){let[l,c]=(0,a.useState)(e),[d,o]=(0,a.useState)(!1),[h,p]=(0,a.useState)({kind:"idle"}),{capture:m}=(0,r.usePostHog)(),g=(0,a.useRef)(null),x=(0,a.useRef)(!1),j="cached"===l.status?l.result.transcripts.scanned:0,b="cached"===l.status?l.result.results.length:0,N=(0,a.useCallback)(async()=>{c(await n())},[]),v=(0,a.useCallback)(async e=>{if(d)return;m("audit_rerun_clicked",{source:e,since:"all"}),o(!0);let s=Date.now();p({kind:"running",startedAt:s});try{await (0,u.triggerRun)({cli:[],since:"all",noCache:!0}),await N(),m("audit_rerun_succeeded",{source:e,since:"all",duration_ms:Date.now()-s}),p({kind:"idle"})}catch(t){let a=t instanceof u.RerunError?t.kind:"network";m("audit_rerun_failed",{kind:a,source:e,since:"all",cli_filter:"all",duration_ms:Date.now()-s}),p({kind:"failed",reason:a,failedAt:Date.now()})}finally{o(!1)}},[m,N,d]),k=(0,a.useCallback)(()=>{p({kind:"idle"})},[]);if((0,a.useEffect)(()=>(document.body.classList.add("audit-body"),()=>document.body.classList.remove("audit-body")),[]),(0,a.useEffect)(()=>{let e=d?"running":"empty"===l.status?"empty":0===j?"zero_sessions":"report";g.current!==e&&(g.current=e,m("audit_page_viewed",{state:e,has_cache:"cached"===l.status}))},[l.status,m,d,j]),(0,a.useEffect)(()=>{x.current=!1},[l.status,d,j]),(0,a.useEffect)(()=>{if(d||"cached"!==l.status||0===j)return;let e=0,s=()=>{if(e=0,x.current)return;let s=document.documentElement.scrollHeight-window.innerHeight;s<=0||window.scrollY>=s-24&&(x.current=!0,m("audit_page_scrolled_to_end",{results_count:b,transcripts_scanned:j}))},a=()=>{0===e&&(e=requestAnimationFrame(s))};return s(),window.addEventListener("scroll",a,{passive:!0}),()=>{window.removeEventListener("scroll",a),0!==e&&cancelAnimationFrame(e)}},[l.status,m,b,d,j]),"empty"===l.status&&!d)return(0,s.jsx)(y,{running:!1,rerunStatus:h,onDismissRerun:k,onStarted:()=>o(!0),onCompleted:async()=>{o(!1),await N()}});if("empty"===l.status&&d)return(0,s.jsx)(y,{running:!0,rerunStatus:h,onDismissRerun:k,onStarted:()=>{},onCompleted:async()=>{o(!1),await N()}});let w="cached"===l.status?l.result:null;if(!w)return null;let S="cached"===l.status?l.cachedAt:null;return 0===w.transcripts.scanned?(0,s.jsx)(y,{running:d,mode:"zero-sessions",rerunStatus:h,onDismissRerun:k,onStarted:()=>o(!0),onCompleted:async()=>{o(!1),await N()}}):(0,s.jsx)(f,{result:w,cachedAt:S,projectFromUrl:t,isRunning:d,rerunStatus:h,onRerun:e=>v(e),onDismissRerun:k})}],24358)},86616,29054,e=>{"use strict";function s(e,a={},t=15e3){let n=AbortSignal.timeout(t),r=a.signal?AbortSignal.any([a.signal,n]):n;return fetch(e,{...a,signal:r})}function a(e){if(null===e||"object"!=typeof e)return!1;let s=e.name;return"AbortError"===s||"TimeoutError"===s}e.s(["fetchWithTimeout",0,s,"isAbortError",0,a],29054);class t extends Error{kind;constructor(e,s){super(s),this.kind=e,this.name="RerunError"}}async function n(e){try{let a=await s("/api/audit/run",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({cli:e.cli.length>0?e.cli:void 0,since:"all"===e.since?void 0:e.since,noCache:!!e.noCache||void 0})});if(!a.ok&&409!==a.status){let e=await a.text().catch(()=>"");throw console.error("audit run failed:",a.status,e),new t("post_failed",`audit run failed (${a.status})`)}}catch(e){if(e instanceof t)throw e;throw console.error("audit run request failed:",e),new t(a(e)?"timeout":"network","audit run request failed")}let n=0;for(;;){await new Promise(e=>setTimeout(e,1e3));let e=null;try{let a=await s("/api/audit/status",{cache:"no-store"});a.ok&&(e=await a.json())}catch{}if(null===e){if(++n>=10)throw new t("network","audit status poll lost the server");continue}if(n=0,!e.running){if(e.error)throw new t("post_failed",e.error);return}}}e.s(["RerunError",0,t,"triggerRun",0,n],86616)}]);