failproofai 1.0.4-beta.8 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +5 -5
- package/.next/standalone/.next/prerender-manifest.json +4 -4
- package/.next/standalone/.next/required-server-files.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page.js +2 -2
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +6 -6
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js +2 -2
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +14 -14
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/run/route.js +4 -4
- package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js +1 -1
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +4 -37
- package/.next/standalone/.next/server/app/audit/page.js +2 -2
- package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +14 -14
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/page.js +2 -2
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/policies/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
- package/.next/standalone/.next/server/app/policies/page.js +4 -4
- package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page.js +4 -4
- package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +5 -5
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/projects/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/projects/page.js +3 -3
- package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +4 -15
- package/.next/standalone/.next/server/app/settings/page.js +4 -4
- package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/chunks/{[externals]__0hmcwgg._.js → [externals]__0-9ti7z._.js} +1 -1
- package/.next/standalone/.next/server/chunks/{[externals]__14b7cc6._.js → [externals]__0u3wrvq._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-219ec._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__04_h-a5._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__08dchfu._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0rv7m0k._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__17g9wh7._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1le9jqc._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1legmza._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1p-qi2t._.js +5 -0
- package/.next/standalone/.next/server/chunks/_0tovk6q._.js +1 -1
- package/.next/standalone/.next/server/chunks/_0trp3yc._.js +1 -1
- package/.next/standalone/.next/server/chunks/{_1_eakl4._.js → _1-akg6w._.js} +2 -2
- package/.next/standalone/.next/server/chunks/_1ek68ln._.js +10 -10
- package/.next/standalone/.next/server/chunks/_1vslkoe._.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_telemetry_ts_0dctyyw._.js +1 -1
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/src_hooks_fp-home_ts_09kv0bn._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__187jks6._.js → [root-of-the-server]__00-s7h8._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__013jr2b._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01wy8d-._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02npjtd._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1fa1jd9._.js → [root-of-the-server]__09flfsx._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0da85px._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ftmoxc._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0kokc8z._.js → [root-of-the-server]__0n_lxhg._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0oa1lav._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0p-5p8u._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s740oi._.js +6 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1kpd1za._.js → [root-of-the-server]__190won8._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ctpynv._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1nq7ivq._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1p2otjt._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__00y9ww2._.js → [root-of-the-server]__1sc84bb._.js} +2 -21
- package/.next/standalone/.next/server/chunks/ssr/_02yoeej._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{_00bqkhj._.js → _04rbnaq._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_0s-77v5._.js → _0h_douw._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1_ozgif._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1ddpc5g._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1es2j7i._.js +14 -51
- package/.next/standalone/.next/server/chunks/ssr/{_14c6q09._.js → _1if9ixi._.js} +2 -21
- package/.next/standalone/.next/server/chunks/ssr/{_07t3jol._.js → _1u8-lu2._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_1ylae7o._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +69 -1
- package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_settings_settings-client_tsx_20lq-mq._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/lib_openclaw-projects_ts_0qxq3bp._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_13n822a._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_html-to-image_es_index_1ao30b1.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589g._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-home_ts_0je3xkv._.js +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +5 -5
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +21 -65
- package/.next/standalone/.next/static/chunks/{1302duobnz07l.js → 0-tyrpwuc3lla.js} +1 -1
- package/.next/standalone/.next/static/chunks/0ca7octhod0g2.js +69 -0
- package/.next/standalone/.next/static/chunks/0u64yey446k4f.css +1 -0
- package/.next/standalone/.next/static/chunks/{2n85g-crgcx5j.js → 1j6o9min_6qa9.js} +1 -1
- package/.next/standalone/.next/static/chunks/27cl8h-c2w7cd.js +1 -0
- package/.next/standalone/.next/static/chunks/{0jpd8dv2vk930.css → 29-iqnp9v_axt.css} +1 -1
- package/.next/standalone/.next/static/chunks/{3uyhmm01g64k4.css → 2k9f4tyv04809.css} +0 -1
- package/.next/standalone/.next/static/chunks/{1chm9piluggc-.js → 2lwipmv5etc8m.js} +1 -1
- package/.next/standalone/.next/static/chunks/2pd16tn5qmx13.js +1 -0
- package/.next/standalone/.next/static/chunks/{2ukucf6g-kbs5.js → 2u8vh73tpw28_.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1_x7pqemw5n_w.js → 31u94yukw9fz-.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2mizt96igig1y.js → 3p9_8x5ezce-9.js} +1 -1
- package/.next/standalone/.next/static/chunks/3qfoeqhy5l10x.js +1 -0
- package/.next/standalone/.next/static/chunks/{0vm4wdx3w-2_w.js → 3tjji451hrgc0.js} +1 -1
- package/.next/standalone/.next/static/chunks/3zkg2s2vzxc3d.js +1 -0
- package/.next/standalone/.next/static/chunks/{turbopack-3s3c-u_u_l0u9.js → turbopack-3p73obe0xmrxm.js} +1 -1
- package/.next/standalone/.opencode/plugins/failproofai.mjs +27 -51
- package/.next/standalone/app/actions/get-scheduled-audit.ts +0 -5
- package/.next/standalone/app/actions/update-scheduled-audit.ts +57 -18
- package/.next/standalone/app/api/auth/status/route.ts +2 -3
- package/.next/standalone/app/audit/_components/audit-dashboard.tsx +34 -140
- package/.next/standalone/app/audit/_components/audit-poster.tsx +10 -29
- package/.next/standalone/app/audit/_components/come-back-better-section.tsx +1 -6
- package/.next/standalone/app/audit/_components/empty-state.tsx +1 -4
- package/.next/standalone/app/audit/_components/how-to-improve-section.tsx +5 -19
- package/.next/standalone/app/audit/_components/share-templates.ts +44 -110
- package/.next/standalone/app/audit/audit-styles.css +1 -132
- package/.next/standalone/app/settings/page.tsx +1 -1
- package/.next/standalone/app/settings/settings-client.tsx +45 -50
- package/.next/standalone/lib/auth/api-server-client.ts +5 -33
- package/.next/standalone/lib/claude-sessions.ts +27 -101
- package/.next/standalone/lib/codex-sessions.ts +4 -20
- package/.next/standalone/lib/copilot-sessions.ts +1 -32
- package/.next/standalone/lib/cursor-sessions.ts +5 -62
- package/.next/standalone/lib/download-session.ts +13 -58
- package/.next/standalone/lib/openclaw-projects.ts +18 -54
- package/.next/standalone/lib/openclaw-sessions.ts +17 -75
- package/.next/standalone/lib/sqlite-reader.ts +1 -78
- package/.next/standalone/package.json +9 -9
- package/.next/standalone/server.js +1 -1
- package/bin/failproofai.mjs +1 -1
- package/dist/cli.mjs +664 -2121
- package/dist/worker.mjs +105 -422
- package/lib/auth/api-server-client.ts +5 -33
- package/lib/claude-sessions.ts +27 -101
- package/lib/codex-sessions.ts +4 -20
- package/lib/copilot-sessions.ts +1 -32
- package/lib/cursor-sessions.ts +5 -62
- package/lib/download-session.ts +13 -58
- package/lib/openclaw-projects.ts +18 -54
- package/lib/openclaw-sessions.ts +17 -75
- package/lib/sqlite-reader.ts +1 -78
- package/package.json +9 -9
- package/pi-extension/index.ts +5 -50
- package/src/audit/cache.ts +0 -6
- package/src/audit/cli-adapters/claude.ts +0 -13
- package/src/audit/cli-login.ts +1 -4
- package/src/audit/cli.ts +7 -157
- package/src/audit/harm-report.ts +4 -111
- package/src/audit/index.ts +15 -286
- package/src/audit/machine-store.ts +9 -8
- package/src/audit/redact-example.ts +15 -324
- package/src/audit/report-harm.ts +37 -33
- package/src/audit/report.ts +3 -6
- package/src/audit/schedule-cli.ts +33 -54
- package/src/audit/scoring.ts +0 -49
- package/src/audit/types.ts +0 -52
- package/src/hooks/builtin-policies.ts +2 -67
- package/src/hooks/configure-wizard.ts +0 -55
- package/src/hooks/fp-config.ts +48 -83
- package/src/hooks/fp-home.ts +0 -27
- package/src/hooks/integrations.ts +14 -51
- package/src/hooks/uninstall-cli.ts +2 -17
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0faqz9x._.js +0 -22
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0zyc_nm._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__112b4go._.js +0 -24
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1l71g3y._.js +0 -22
- package/.next/standalone/.next/server/chunks/lib_openclaw-projects_ts_0gc-p24._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0-4ja6q._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__00cr3xu._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04px77e._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0egr5s1._.js +0 -6
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0i0l1di._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0n2txhk._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0r3c1_0._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__17xnxlx._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ckpmkk._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1kaotix._.js +0 -22
- package/.next/standalone/.next/server/chunks/ssr/_04ng_6x._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0l2hi_d._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_rerun-button_tsx_0blihgw._.js +0 -3
- package/.next/standalone/.next/static/chunks/0igq884ziuv-8.js +0 -1
- package/.next/standalone/.next/static/chunks/349jkb79v5g6z.js +0 -1
- package/.next/standalone/.next/static/chunks/3a4gth64hwtwf.js +0 -1
- package/.next/standalone/app/actions/get-leaks.ts +0 -104
- package/.next/standalone/app/audit/_components/leak-section.tsx +0 -192
- package/.next/standalone/lib/openclaw-db.ts +0 -271
- package/lib/openclaw-db.ts +0 -271
- package/src/audit/desktop-notify.ts +0 -420
- package/src/audit/leak-fingerprint.ts +0 -198
- package/src/audit/leak-record.ts +0 -248
- package/src/audit/leak-scan.ts +0 -268
- package/src/audit/leak-store.ts +0 -220
- package/src/audit/macos-notifier.ts +0 -298
- package/src/hooks/notice.ts +0 -24
- /package/.next/standalone/.next/static/{DNhaAH8iPGH0tIuxee5_2 → dg8VQ7Ejlo0Qsh_x92oIt}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{DNhaAH8iPGH0tIuxee5_2 → dg8VQ7Ejlo0Qsh_x92oIt}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{DNhaAH8iPGH0tIuxee5_2 → dg8VQ7Ejlo0Qsh_x92oIt}/_ssgManifest.js +0 -0
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* How a leaked credential is NAMED once it leaves the machine.
|
|
3
|
-
*
|
|
4
|
-
* Every other redactor in this codebase exists to destroy a value. This one
|
|
5
|
-
* exists to describe one, because the leak report has the opposite job: a
|
|
6
|
-
* finding the reader cannot tie to a row in a vendor console is a finding they
|
|
7
|
-
* cannot act on. `[REDACTED: GitHub token]` tells you nothing you can revoke.
|
|
8
|
-
*
|
|
9
|
-
* The rule is: **enough to identify, never enough to use.**
|
|
10
|
-
*
|
|
11
|
-
* ## Why the mask character matters, which is not obvious
|
|
12
|
-
*
|
|
13
|
-
* The three most natural masking characters — `X`, `x` and `0` — RE-MATCH every
|
|
14
|
-
* vendor prefix pattern we ship. `AKIA` + sixteen `X`s satisfies
|
|
15
|
-
* `AKIA[A-Z0-9]{16}`, so a masked AWS key is reported by failproofai as a live
|
|
16
|
-
* AWS key on the next scan. The same holds for a masked connection string,
|
|
17
|
-
* whose pattern keys on `://…@` and does not care what sits between. So the
|
|
18
|
-
* mask is `•` (U+2022), which is in no credential charset anywhere, and the
|
|
19
|
-
* unit tests assert that every masked form fails to re-match.
|
|
20
|
-
*
|
|
21
|
-
* That is not a hypothetical: this product measured its own audit output
|
|
22
|
-
* feeding back into its own corpus, one leaked credential becoming seven
|
|
23
|
-
* reported findings across four sessions.
|
|
24
|
-
*
|
|
25
|
-
* ## Why the last four, and why not always
|
|
26
|
-
*
|
|
27
|
-
* Last-4 is what vendor consoles display next to a key, so it is the token that
|
|
28
|
-
* lets a human find the right row and press revoke. But it is only safe when
|
|
29
|
-
* what remains is genuinely unguessable. For a 40-character GitHub token,
|
|
30
|
-
* showing `ghp_` and four trailing characters leaves ~32 unknown base62
|
|
31
|
-
* characters — no help to anyone. For a 13-character human-chosen password,
|
|
32
|
-
* last-4 is nearly a third of the secret, and the corpus's one confirmed-live
|
|
33
|
-
* password was exactly 13 characters at 3.19 bits per character.
|
|
34
|
-
*
|
|
35
|
-
* So the tail is shown only when the value clears BOTH bars: a recognised
|
|
36
|
-
* public vendor prefix (which proves it is a minted token, not a typed
|
|
37
|
-
* password) AND enough residual length. Everything else is described by class
|
|
38
|
-
* and length alone.
|
|
39
|
-
*/
|
|
40
|
-
|
|
41
|
-
/** The mask glyph. Never `X`, `x` or `0` — see the module header. */
|
|
42
|
-
const MASK_CHAR = "•";
|
|
43
|
-
|
|
44
|
-
/** Longest run of mask glyphs rendered, so a 200-char token stays readable. */
|
|
45
|
-
const MAX_MASK_RUN = 8;
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Minimum total length before any trailing characters are shown.
|
|
49
|
-
*
|
|
50
|
-
* 24 is chosen so that even the shortest minted token we recognise keeps well
|
|
51
|
-
* over 100 bits of residual entropy after the prefix and tail are removed.
|
|
52
|
-
* Human-chosen passwords essentially never reach it, which is the point.
|
|
53
|
-
*/
|
|
54
|
-
const MIN_LEN_FOR_TAIL = 24;
|
|
55
|
-
|
|
56
|
-
/** How many trailing characters a vendor console typically shows. */
|
|
57
|
-
const TAIL_CHARS = 4;
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Public, minted prefixes — the part of a credential that carries no secret.
|
|
61
|
-
*
|
|
62
|
-
* Each of these is published by its issuer and identical across every key they
|
|
63
|
-
* mint, so showing it discloses nothing while telling the reader which console
|
|
64
|
-
* to open. Ordered longest-first, so `sk-ant-api03-` wins over `sk-ant-` and
|
|
65
|
-
* `sk-`; the first match is the one rendered.
|
|
66
|
-
*
|
|
67
|
-
* Deliberately NOT the full detection vocabulary. This list only has to be
|
|
68
|
-
* right about what is safe to PRINT — a prefix missing from it costs a less
|
|
69
|
-
* specific label, never a disclosure.
|
|
70
|
-
*/
|
|
71
|
-
const PUBLIC_PREFIXES: ReadonlyArray<readonly [string, string]> = [
|
|
72
|
-
["sk-ant-api03-", "Anthropic API key"],
|
|
73
|
-
["sk-ant-oat01-", "Anthropic OAuth token"],
|
|
74
|
-
["sk-ant-", "Anthropic key"],
|
|
75
|
-
["sk-proj-", "OpenAI project key"],
|
|
76
|
-
["sk-svcacct-", "OpenAI service-account key"],
|
|
77
|
-
["sk-admin-", "OpenAI admin key"],
|
|
78
|
-
["sk-or-v1-", "OpenRouter key"],
|
|
79
|
-
["github_pat_", "GitHub fine-grained token"],
|
|
80
|
-
["ghp_", "GitHub personal access token"],
|
|
81
|
-
["gho_", "GitHub OAuth token"],
|
|
82
|
-
["ghu_", "GitHub user-to-server token"],
|
|
83
|
-
["ghs_", "GitHub server-to-server token"],
|
|
84
|
-
["ghr_", "GitHub refresh token"],
|
|
85
|
-
["glpat-", "GitLab personal access token"],
|
|
86
|
-
["xoxb-", "Slack bot token"],
|
|
87
|
-
["xoxp-", "Slack user token"],
|
|
88
|
-
["xapp-", "Slack app-level token"],
|
|
89
|
-
["sk_live_", "Stripe live secret key"],
|
|
90
|
-
["sk_test_", "Stripe test secret key"],
|
|
91
|
-
["rk_live_", "Stripe restricted key"],
|
|
92
|
-
["AKIA", "AWS access key ID"],
|
|
93
|
-
["ASIA", "AWS temporary access key ID"],
|
|
94
|
-
["AIza", "Google API key"],
|
|
95
|
-
["hf_", "Hugging Face token"],
|
|
96
|
-
["gsk_", "Groq API key"],
|
|
97
|
-
["r8_", "Replicate token"],
|
|
98
|
-
["xai-", "xAI key"],
|
|
99
|
-
["npm_", "npm access token"],
|
|
100
|
-
["dop_v1_", "DigitalOcean token"],
|
|
101
|
-
["shpat_", "Shopify access token"],
|
|
102
|
-
["sbp_", "Supabase token"],
|
|
103
|
-
];
|
|
104
|
-
|
|
105
|
-
export interface LeakFingerprint {
|
|
106
|
-
/** What the reader sees: `ghp_••••••••4f2a` or `[13-char password]`. */
|
|
107
|
-
display: string;
|
|
108
|
-
/** Human label for the class, e.g. "GitHub personal access token". */
|
|
109
|
-
label: string;
|
|
110
|
-
/** Total character length of the original value. */
|
|
111
|
-
length: number;
|
|
112
|
-
/** True when a minted public prefix was recognised — i.e. we can name a
|
|
113
|
-
* console to revoke at. False means "we know it leaked, not who issued it". */
|
|
114
|
-
attributed: boolean;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/** The recognised public prefix for a value, longest match first. */
|
|
118
|
-
function matchPublicPrefix(value: string): readonly [string, string] | null {
|
|
119
|
-
for (const entry of PUBLIC_PREFIXES) {
|
|
120
|
-
if (value.startsWith(entry[0])) return entry;
|
|
121
|
-
}
|
|
122
|
-
return null;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Render a credential as something a human can recognise and nobody can use.
|
|
127
|
-
*
|
|
128
|
-
* `fallbackLabel` names the class when no public prefix is recognised — pass
|
|
129
|
-
* the detecting rule's own label (e.g. "assigned secret", "database password")
|
|
130
|
-
* so an unattributed finding still says what KIND of thing leaked.
|
|
131
|
-
*/
|
|
132
|
-
export function fingerprintSecret(value: string, fallbackLabel = "secret"): LeakFingerprint {
|
|
133
|
-
const length = value.length;
|
|
134
|
-
const matched = matchPublicPrefix(value);
|
|
135
|
-
|
|
136
|
-
if (!matched) {
|
|
137
|
-
// No minted prefix: this is a password, a first-party key, or a format we
|
|
138
|
-
// do not recognise. Nothing but the class and the length is safe to print,
|
|
139
|
-
// and the length is what lets the owner recognise their own value.
|
|
140
|
-
return {
|
|
141
|
-
display: `[${length}-char ${fallbackLabel}]`,
|
|
142
|
-
label: fallbackLabel,
|
|
143
|
-
length,
|
|
144
|
-
attributed: false,
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const [prefix, label] = matched;
|
|
149
|
-
const bodyLength = length - prefix.length;
|
|
150
|
-
const run = MASK_CHAR.repeat(Math.max(1, Math.min(MAX_MASK_RUN, bodyLength)));
|
|
151
|
-
|
|
152
|
-
// The tail is the actionable half — but only when what stays hidden is
|
|
153
|
-
// genuinely unguessable. See the module header.
|
|
154
|
-
if (length >= MIN_LEN_FOR_TAIL && bodyLength > TAIL_CHARS * 2) {
|
|
155
|
-
return {
|
|
156
|
-
display: `${prefix}${run}${value.slice(-TAIL_CHARS)}`,
|
|
157
|
-
label,
|
|
158
|
-
length,
|
|
159
|
-
attributed: true,
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
return { display: `${prefix}${run}`, label, length, attributed: true };
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* A stable identity for one distinct credential, for deduping across scans.
|
|
168
|
-
*
|
|
169
|
-
* Salted with a per-machine secret so the digest cannot be used as a lookup
|
|
170
|
-
* oracle: without the salt, an attacker holding a guessable candidate (and the
|
|
171
|
-
* corpus is full of dictionary passwords) could confirm it by comparing
|
|
172
|
-
* hashes. With it, the id is meaningful only on the machine that produced it.
|
|
173
|
-
*
|
|
174
|
-
* The salt therefore forecloses cross-machine dedupe until an org-scoped key
|
|
175
|
-
* exists — a deliberate trade recorded here so nobody reads a per-machine salt
|
|
176
|
-
* as an oversight.
|
|
177
|
-
*/
|
|
178
|
-
/**
|
|
179
|
-
* Every id `fingerprintId` can produce, and nothing else.
|
|
180
|
-
*
|
|
181
|
-
* Ids become FILENAMES for pending macOS banners, so an id is a path component,
|
|
182
|
-
* and a path component built from unvalidated input is a directory traversal.
|
|
183
|
-
*
|
|
184
|
-
* Ids are HMAC hex today, so nothing in a normal run can carry a `/`. That is
|
|
185
|
-
* not the same as safe: the record is JSON read off disk, and `leaks.json` is a
|
|
186
|
-
* file a user (or anything running as them) can edit. The guard is one regex
|
|
187
|
-
* and it makes the property structural instead of incidental.
|
|
188
|
-
*/
|
|
189
|
-
export function isFindingId(id: string): boolean {
|
|
190
|
-
return /^[0-9a-f]{16}$/.test(id);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
export function fingerprintId(value: string, machineSalt: string): string {
|
|
194
|
-
// Lazily required so this module stays importable in the browser bundle,
|
|
195
|
-
// where the dashboard renders findings but never computes ids.
|
|
196
|
-
const { createHmac } = require("node:crypto") as typeof import("node:crypto");
|
|
197
|
-
return createHmac("sha256", machineSalt).update(value).digest("hex").slice(0, 16);
|
|
198
|
-
}
|
package/src/audit/leak-record.ts
DELETED
|
@@ -1,248 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The per-credential record — what the audit knows that `AuditCount` cannot say.
|
|
3
|
-
*
|
|
4
|
-
* `AuditCount` aggregates BY RULE: it can report that `sanitize-api-keys` fired
|
|
5
|
-
* 47 times and show three 80-character examples. It cannot enumerate 47 distinct
|
|
6
|
-
* keys, say which of them is the same key seen twice, or answer "which CLI, in
|
|
7
|
-
* which project, at what time, and how did it get there". Every surface the
|
|
8
|
-
* narrowed product needs — the emailed digest, the dashboard's leak view, and
|
|
9
|
-
* the once-per-finding notification guard — asks exactly those questions, so
|
|
10
|
-
* none of them can be built on the rule-level record.
|
|
11
|
-
*
|
|
12
|
-
* ## Grouped by VALUE, not by occurrence — this is the whole bloat story
|
|
13
|
-
*
|
|
14
|
-
* The obvious shape is one row per sighting. Measured against the real corpus
|
|
15
|
-
* that is 10,301 rows, against roughly 266 distinct values, against about 25
|
|
16
|
-
* genuinely real credentials. Two orders of magnitude of duplication, because
|
|
17
|
-
* one pasted key fans out across every session that touched it — the single
|
|
18
|
-
* confirmed-live password in the corpus appeared in 11 files and 22 occurrences,
|
|
19
|
-
* partly because Claude's own checkpoint records replay the prompt that carried
|
|
20
|
-
* it.
|
|
21
|
-
*
|
|
22
|
-
* So the unit is the DISTINCT VALUE, with a bounded list of sightings attached
|
|
23
|
-
* as evidence. A finding seen a thousand times costs the same on disk as one
|
|
24
|
-
* seen twice: `MAX_SIGHTINGS` entries, plus a count. At the corpus's own numbers
|
|
25
|
-
* that is roughly 130 KB for the whole file, which is why this is one file and
|
|
26
|
-
* not a directory — a per-finding file would spend more on inodes and directory
|
|
27
|
-
* entries than on the findings.
|
|
28
|
-
*
|
|
29
|
-
* ## What is deliberately NOT stored
|
|
30
|
-
*
|
|
31
|
-
* The credential itself, ever. The record holds a salted id (for dedupe across
|
|
32
|
-
* scans) and a fingerprint (for a human to recognise the key in a console) and
|
|
33
|
-
* nothing that could reconstruct the value. `~/.failproofai/` is a directory the
|
|
34
|
-
* user did not ask for and cannot audit, so a leak scanner that caches the leaks
|
|
35
|
-
* has made the problem worse rather than better.
|
|
36
|
-
*/
|
|
37
|
-
import type { IntegrationType } from "../hooks/types";
|
|
38
|
-
import type { LeakFingerprint } from "./leak-fingerprint";
|
|
39
|
-
|
|
40
|
-
/** Sightings kept per distinct value. Enough to answer 5W1H for the first and
|
|
41
|
-
* most recent exposure without storing the long tail nobody reads. */
|
|
42
|
-
export const MAX_SIGHTINGS = 5;
|
|
43
|
-
|
|
44
|
-
/** Findings not seen in this many days are dropped on the next write. A key
|
|
45
|
-
* whose transcripts have aged out of the scan window is not evidence of
|
|
46
|
-
* anything current, and keeping it forever grows the file without bound. */
|
|
47
|
-
export const FINDING_TTL_DAYS = 90;
|
|
48
|
-
|
|
49
|
-
/** Cap on distinct findings retained, newest-first. A machine with more than
|
|
50
|
-
* this is not going to triage them, and the census measured a name-only rule
|
|
51
|
-
* emitting thousands — the cap is what stops a bad matcher filling the disk. */
|
|
52
|
-
export const MAX_FINDINGS = 500;
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* How a credential came to be where it was found — the "how" of 5W1H.
|
|
56
|
-
*
|
|
57
|
-
* Derived from the tool event rather than authored, so it is always specific:
|
|
58
|
-
* "read from ~/…/.env" beats "detected in a file operation". The path is
|
|
59
|
-
* home-shortened by the caller before it reaches here.
|
|
60
|
-
*/
|
|
61
|
-
export interface LeakMechanism {
|
|
62
|
-
/** Short past-tense phrase, e.g. "read from ~/…/.env". */
|
|
63
|
-
summary: string;
|
|
64
|
-
/** Canonical tool name (Bash, Read, Write, Edit…). */
|
|
65
|
-
toolName: string;
|
|
66
|
-
/** Where the value appeared: the tool's INPUT (the agent sent it) or its
|
|
67
|
-
* RESULT (the agent received it). Different exposures with different
|
|
68
|
-
* remedies — an input is deniable at PreToolUse, a result is not. */
|
|
69
|
-
direction: "input" | "result";
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/** One exposure of one credential: the where/when/who of 5W1H. */
|
|
73
|
-
export interface LeakSighting {
|
|
74
|
-
/** WHO — the harness whose transcript carried it. */
|
|
75
|
-
cli: IntegrationType;
|
|
76
|
-
/** WHERE — session and project. `cwd` is already home-shortened. */
|
|
77
|
-
sessionId: string;
|
|
78
|
-
cwd: string;
|
|
79
|
-
/** WHEN — ISO-8601, from the transcript line. */
|
|
80
|
-
at: string;
|
|
81
|
-
/** HOW. */
|
|
82
|
-
mechanism: LeakMechanism;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/** One distinct credential, however many times it was seen. */
|
|
86
|
-
export interface LeakFinding {
|
|
87
|
-
/** Salted, machine-local, stable across scans. Never derived from the
|
|
88
|
-
* fingerprint, which is not unique. */
|
|
89
|
-
id: string;
|
|
90
|
-
/** WHAT — the masked rendering, its class, and its length. */
|
|
91
|
-
fingerprint: LeakFingerprint;
|
|
92
|
-
/** The identifier it was assigned to, when it came from an assignment
|
|
93
|
-
* (`COMPOSIO_API_KEY`). For the 230-of-237 first-party class this is the
|
|
94
|
-
* only actionable field there is, since no vendor console exists. */
|
|
95
|
-
name: string | null;
|
|
96
|
-
/** Which rule found it, for explaining and for suppression. */
|
|
97
|
-
rule: string;
|
|
98
|
-
/** How much we trust the attribution — mirrors the pattern census's tiers. */
|
|
99
|
-
confidence: "doc-verified" | "source-verified" | "partner-corroborated" | "observed-only" | "name-only";
|
|
100
|
-
firstSeen: string;
|
|
101
|
-
lastSeen: string;
|
|
102
|
-
/** Total exposures, which is NOT `sightings.length` — see MAX_SIGHTINGS. */
|
|
103
|
-
occurrences: number;
|
|
104
|
-
/** Bounded evidence, oldest first. */
|
|
105
|
-
sightings: LeakSighting[];
|
|
106
|
-
/** Set when the user dismisses it. Dismissed findings are retained (so they
|
|
107
|
-
* do not re-alert) but never notified about again. */
|
|
108
|
-
dismissedAt?: string;
|
|
109
|
-
/** Set when a notice for this finding has been delivered, so the next scan
|
|
110
|
-
* stays quiet. The delivery guard itself is a separate O_EXCL marker; this
|
|
111
|
-
* is the human-readable record of it. */
|
|
112
|
-
notifiedAt?: string;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
export interface LeakRecord {
|
|
116
|
-
schemaVersion: number;
|
|
117
|
-
/** Per-machine HMAC salt for `fingerprintId`. Generated once, never sent. */
|
|
118
|
-
salt: string;
|
|
119
|
-
updatedAt: string;
|
|
120
|
-
findings: LeakFinding[];
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
export const LEAK_RECORD_SCHEMA_VERSION = 1;
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Fold one new sighting into the record.
|
|
127
|
-
*
|
|
128
|
-
* Returns whether this is a **newly seen** credential, which is what the
|
|
129
|
-
* notification guard keys on: a finding whose id is already present must not
|
|
130
|
-
* alert again, however many new sightings it accumulates. Getting this backwards
|
|
131
|
-
* is the failure the census measured — keying on the rule-level examples, which
|
|
132
|
-
* `mergeIncremental` freezes at the first three, silently misses the arrival of
|
|
133
|
-
* a brand-new key while re-alerting for old ones.
|
|
134
|
-
*/
|
|
135
|
-
export function upsertFinding(
|
|
136
|
-
record: LeakRecord,
|
|
137
|
-
incoming: Omit<LeakFinding, "firstSeen" | "lastSeen" | "occurrences" | "sightings"> & {
|
|
138
|
-
sighting: LeakSighting;
|
|
139
|
-
},
|
|
140
|
-
): { record: LeakRecord; isNew: boolean } {
|
|
141
|
-
const { sighting, ...rest } = incoming;
|
|
142
|
-
const existing = record.findings.find((f) => f.id === rest.id);
|
|
143
|
-
|
|
144
|
-
if (!existing) {
|
|
145
|
-
record.findings.push({
|
|
146
|
-
...rest,
|
|
147
|
-
firstSeen: sighting.at,
|
|
148
|
-
lastSeen: sighting.at,
|
|
149
|
-
occurrences: 1,
|
|
150
|
-
sightings: [sighting],
|
|
151
|
-
});
|
|
152
|
-
return { record, isNew: true };
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Keep the earliest sighting AND the most recent evidence. The report/email
|
|
156
|
-
// promises the CLI and timestamp of the latest exposure; keeping only the
|
|
157
|
-
// first five made those fields stale forever after a busy credential crossed
|
|
158
|
-
// the cap. Middle sightings are the expendable ones.
|
|
159
|
-
const seen = existing.sightings.some(
|
|
160
|
-
(s) =>
|
|
161
|
-
s.sessionId === sighting.sessionId &&
|
|
162
|
-
s.at === sighting.at &&
|
|
163
|
-
s.mechanism.direction === sighting.mechanism.direction &&
|
|
164
|
-
s.mechanism.summary === sighting.mechanism.summary,
|
|
165
|
-
);
|
|
166
|
-
// Cached transcript results are folded through this function again. An
|
|
167
|
-
// identical sighting is therefore not a new exposure and must not inflate
|
|
168
|
-
// the count each time the same cache entry is reused.
|
|
169
|
-
if (seen) return { record, isNew: false };
|
|
170
|
-
|
|
171
|
-
existing.occurrences += 1;
|
|
172
|
-
if (sighting.at > existing.lastSeen) existing.lastSeen = sighting.at;
|
|
173
|
-
if (sighting.at < existing.firstSeen) existing.firstSeen = sighting.at;
|
|
174
|
-
|
|
175
|
-
existing.sightings.push(sighting);
|
|
176
|
-
existing.sightings.sort((a, b) => a.at.localeCompare(b.at));
|
|
177
|
-
if (existing.sightings.length > MAX_SIGHTINGS) {
|
|
178
|
-
existing.sightings.splice(1, existing.sightings.length - MAX_SIGHTINGS);
|
|
179
|
-
}
|
|
180
|
-
return { record, isNew: false };
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Drop what is no longer worth keeping, so the file cannot grow without bound.
|
|
185
|
-
*
|
|
186
|
-
* Order matters: age out first, then cap. Capping first would let a burst of
|
|
187
|
-
* stale findings evict fresh ones.
|
|
188
|
-
*/
|
|
189
|
-
export function pruneRecord(record: LeakRecord, nowMs: number): LeakRecord {
|
|
190
|
-
const cutoff = new Date(nowMs - FINDING_TTL_DAYS * 86_400_000).toISOString();
|
|
191
|
-
// A missing or malformed timestamp cannot be aged out safely. Keep it until
|
|
192
|
-
// the bounded-cap pass below rather than silently deleting a finding that the
|
|
193
|
-
// reporting path deliberately surfaces as having an unknown date.
|
|
194
|
-
const live = record.findings.filter(
|
|
195
|
-
(f) => !f.lastSeen || !Number.isFinite(Date.parse(f.lastSeen)) || f.lastSeen >= cutoff,
|
|
196
|
-
);
|
|
197
|
-
live.sort((a, b) => (a.lastSeen < b.lastSeen ? 1 : a.lastSeen > b.lastSeen ? -1 : 0));
|
|
198
|
-
record.findings = live.slice(0, MAX_FINDINGS);
|
|
199
|
-
return record;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/** An empty record, for a machine that has never scanned. */
|
|
203
|
-
export function emptyRecord(salt: string, nowIso: string): LeakRecord {
|
|
204
|
-
return {
|
|
205
|
-
schemaVersion: LEAK_RECORD_SCHEMA_VERSION,
|
|
206
|
-
salt,
|
|
207
|
-
updatedAt: nowIso,
|
|
208
|
-
findings: [],
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Describe how a credential came to be in a tool event.
|
|
214
|
-
*
|
|
215
|
-
* Specific by construction: the path or command is what makes a finding
|
|
216
|
-
* recognisable to the person who caused it, and a generic phrase ("detected in
|
|
217
|
-
* tool input") is the kind of alert people learn to ignore.
|
|
218
|
-
*/
|
|
219
|
-
export function describeMechanism(
|
|
220
|
-
toolName: string,
|
|
221
|
-
direction: "input" | "result",
|
|
222
|
-
shortPath: string | null,
|
|
223
|
-
): LeakMechanism {
|
|
224
|
-
const where = shortPath ? ` ${shortPath}` : "";
|
|
225
|
-
let summary: string;
|
|
226
|
-
if (direction === "result") {
|
|
227
|
-
summary = shortPath ? `appeared in the output of reading${where}` : "appeared in tool output";
|
|
228
|
-
} else {
|
|
229
|
-
switch (toolName) {
|
|
230
|
-
case "Read":
|
|
231
|
-
summary = `read from${where || " a file"}`;
|
|
232
|
-
break;
|
|
233
|
-
case "Write":
|
|
234
|
-
summary = `written to${where || " a file"}`;
|
|
235
|
-
break;
|
|
236
|
-
case "Edit":
|
|
237
|
-
case "NotebookEdit":
|
|
238
|
-
summary = `edited into${where || " a file"}`;
|
|
239
|
-
break;
|
|
240
|
-
case "Bash":
|
|
241
|
-
summary = "passed in a shell command";
|
|
242
|
-
break;
|
|
243
|
-
default:
|
|
244
|
-
summary = `sent to the ${toolName} tool`;
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
return { summary, toolName, direction };
|
|
248
|
-
}
|