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
package/src/audit/index.ts
CHANGED
|
@@ -16,11 +16,6 @@ import { INTEGRATION_TYPES, type IntegrationType } from "../hooks/types";
|
|
|
16
16
|
import { ADAPTERS } from "./cli-adapters";
|
|
17
17
|
import { AUDIT_DETECTORS } from "./detectors";
|
|
18
18
|
import { severityForBuiltin } from "./features";
|
|
19
|
-
import { findSecrets, flattenToolInput } from "./leak-scan";
|
|
20
|
-
import { fingerprintSecret, fingerprintId } from "./leak-fingerprint";
|
|
21
|
-
import { readLeakIdentity, readLeakRecord, writeLeakRecord } from "./leak-store";
|
|
22
|
-
import { upsertFinding, describeMechanism, emptyRecord } from "./leak-record";
|
|
23
|
-
import { shortenPaths } from "./redact-example";
|
|
24
19
|
import { readCachedTranscript, writeCachedTranscriptResult } from "./cache";
|
|
25
20
|
import { initReplay, replayEvent, restoreReplay } from "./replay";
|
|
26
21
|
import {
|
|
@@ -37,64 +32,6 @@ import {
|
|
|
37
32
|
|
|
38
33
|
const TRANSCRIPT_CONCURRENCY = 8;
|
|
39
34
|
|
|
40
|
-
/**
|
|
41
|
-
* Raw transcript bytes allowed in flight at once.
|
|
42
|
-
*
|
|
43
|
-
* The concurrency above counts FILES, which is the wrong unit. `streamEventsFrom`
|
|
44
|
-
* returns an array — despite the name it materialises every event of a
|
|
45
|
-
* transcript into JS objects — and a 57 MB JSONL becomes several hundred MB of
|
|
46
|
-
* them. On a real 1.15 GB corpus, 7 files hold 224 MB and the largest is
|
|
47
|
-
* 57.5 MB, so eight workers could put a quarter of the corpus in memory
|
|
48
|
-
* simultaneously. That machine printed `Aborted(OOM)` 22 times in one run.
|
|
49
|
-
*
|
|
50
|
-
* This is NOT what fixed those lines, and the distinction is worth keeping
|
|
51
|
-
* honest: they were measured at ~22 per run, appear within the first two
|
|
52
|
-
* seconds, are unchanged by this budget at 48 MB or at 16 MB, and do not fail
|
|
53
|
-
* the run — its counts come back correct. Their source is still unidentified.
|
|
54
|
-
* This bounds a real and separate failure mode (eight of the seven largest
|
|
55
|
-
* files in flight at once); it should not be read as having fixed the other.
|
|
56
|
-
*
|
|
57
|
-
* 48 MB of raw input is the budget because the expansion factor from JSONL to
|
|
58
|
-
* parsed objects is roughly 5-10x, which keeps the peak inside a default heap
|
|
59
|
-
* with room for the eight small-file workers this is meant not to slow down.
|
|
60
|
-
* Chosen to bound memory, not to be exactly right: the failure it prevents is a
|
|
61
|
-
* crash, and the cost of being conservative is that one very large file scans
|
|
62
|
-
* alone for a moment.
|
|
63
|
-
*/
|
|
64
|
-
const TRANSCRIPT_BYTE_BUDGET = 48 * 1024 * 1024;
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Admission control by weight, so a worker waits for MEMORY as well as a slot.
|
|
68
|
-
*
|
|
69
|
-
* A file bigger than the whole budget is admitted alone rather than refused —
|
|
70
|
-
* otherwise the largest transcript on the machine could never be scanned, which
|
|
71
|
-
* is precisely the one most likely to hold something.
|
|
72
|
-
*/
|
|
73
|
-
export class ByteGate {
|
|
74
|
-
private inFlight = 0;
|
|
75
|
-
private waiting: Array<() => void> = [];
|
|
76
|
-
|
|
77
|
-
constructor(private readonly budget: number) {}
|
|
78
|
-
|
|
79
|
-
async acquire(bytes: number): Promise<void> {
|
|
80
|
-
const want = Math.max(0, bytes);
|
|
81
|
-
while (this.inFlight > 0 && this.inFlight + want > this.budget) {
|
|
82
|
-
await new Promise<void>((resolve) => this.waiting.push(resolve));
|
|
83
|
-
}
|
|
84
|
-
this.inFlight += want;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
release(bytes: number): void {
|
|
88
|
-
this.inFlight -= Math.max(0, bytes);
|
|
89
|
-
if (this.inFlight < 0) this.inFlight = 0;
|
|
90
|
-
// Wake everyone and let each re-test its own weight: a small task behind a
|
|
91
|
-
// large one should not be held by a queue position it does not need.
|
|
92
|
-
const waiters = this.waiting;
|
|
93
|
-
this.waiting = [];
|
|
94
|
-
for (const wake of waiters) wake();
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
35
|
/** Canonicalize a policy name to its short, qualified form for display
|
|
99
36
|
* (`failproofai/foo` → `foo`). */
|
|
100
37
|
function shortPolicyName(name: string): string {
|
|
@@ -176,50 +113,6 @@ interface ScanOutcome {
|
|
|
176
113
|
resumed: boolean;
|
|
177
114
|
}
|
|
178
115
|
|
|
179
|
-
const CREDENTIAL_RESEARCH_MARKERS: ReadonlyArray<RegExp> = [
|
|
180
|
-
/\b(?:secret|credential)[-_ ](?:scanner|detection|detector|pattern|regex|corpus)\b/i,
|
|
181
|
-
/\b(?:trufflehog|trufflesecurity|gitleaks|detect-secrets|secret-detection-rules)\b/i,
|
|
182
|
-
/\b(?:SECRET_PATTERNS|findSecrets|isDocsLiteral|leak-scan)\b/,
|
|
183
|
-
/\b(?:fixture|placeholder|synthetic|sample) (?:api )?(?:key|token|secret|credential)s?\b/i,
|
|
184
|
-
/\b(?:grep|rg|ripgrep|scan|hunt|search)\b[^\n]{0,240}\b(?:secret|credential|token)s?\b/i,
|
|
185
|
-
];
|
|
186
|
-
|
|
187
|
-
const CREDENTIAL_RESEARCH_PURPOSE =
|
|
188
|
-
/\b(?:hunt|scan|search|research|test)(?:ing)?\b[^\n]{0,80}\b(?:secret|credential|token)s?\b|\b(?:secret|credential|token)s?\b[^\n]{0,80}\b(?:scanner|detector|detection|research|fixtures?|corpus)\b/i;
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Deliberate secret-detector research is full of realistic fake credentials.
|
|
192
|
-
* Reporting those as leaks is the feedback loop that made one measured session
|
|
193
|
-
* contribute 455 of the 500 rows in its own audit.
|
|
194
|
-
*
|
|
195
|
-
* Require independent signals across tool INPUTS. Results are excluded from
|
|
196
|
-
* classification: a normal command can print hostile text, while commands and
|
|
197
|
-
* URLs state what the agent intentionally set out to inspect. Two signals keep
|
|
198
|
-
* an ordinary discussion that happens to say "secret scanner" from silencing a
|
|
199
|
-
* session; dedicated detector/test work reliably carries several.
|
|
200
|
-
*/
|
|
201
|
-
export function isCredentialResearchSession(
|
|
202
|
-
events: NormalizedToolEvent[],
|
|
203
|
-
sessionDescription?: string,
|
|
204
|
-
): boolean {
|
|
205
|
-
if (sessionDescription && CREDENTIAL_RESEARCH_PURPOSE.test(sessionDescription)) return true;
|
|
206
|
-
const matched = new Set<number>();
|
|
207
|
-
let evidenceEvents = 0;
|
|
208
|
-
for (const event of events) {
|
|
209
|
-
const intent = flattenToolInput(event.toolInput);
|
|
210
|
-
let eventMatched = false;
|
|
211
|
-
for (let i = 0; i < CREDENTIAL_RESEARCH_MARKERS.length; i++) {
|
|
212
|
-
if (CREDENTIAL_RESEARCH_MARKERS[i].test(intent)) {
|
|
213
|
-
matched.add(i);
|
|
214
|
-
eventMatched = true;
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
if (eventMatched) evidenceEvents++;
|
|
218
|
-
if (matched.size >= 2 || evidenceEvents >= 3) return true;
|
|
219
|
-
}
|
|
220
|
-
return false;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
116
|
async function scanOneTranscript(
|
|
224
117
|
meta: TranscriptMetadata,
|
|
225
118
|
resume?: { fromByte: number; detectorState: DetectorSessionState },
|
|
@@ -288,27 +181,21 @@ async function scanOneTranscript(
|
|
|
288
181
|
// Capture the session's cwd from the first event that carried one — every
|
|
289
182
|
// event in a single transcript shares the same cwd by construction.
|
|
290
183
|
result.cwd = result.cwd || events[0].cwd || "";
|
|
291
|
-
const suppressLeakScan = isCredentialResearchSession(events, meta.sessionDescription);
|
|
292
|
-
if (suppressLeakScan) result.leakScanSuppressed = "credential-research";
|
|
293
184
|
|
|
294
185
|
for (const event of events) {
|
|
295
|
-
//
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
//
|
|
308
|
-
// truncateExample(hit.example),
|
|
309
|
-
// );
|
|
310
|
-
// }
|
|
311
|
-
// Replay through every builtin policy.
|
|
186
|
+
// Run audit detectors first (stateful, must see every event).
|
|
187
|
+
for (const detector of AUDIT_DETECTORS) {
|
|
188
|
+
const hit = detector.detect(event, sessionState);
|
|
189
|
+
if (!hit) continue;
|
|
190
|
+
recordHit(
|
|
191
|
+
result,
|
|
192
|
+
detector.name,
|
|
193
|
+
event.timestamp,
|
|
194
|
+
event.cwd,
|
|
195
|
+
truncateExample(hit.example),
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
// Then replay through every builtin policy.
|
|
312
199
|
let replayHits;
|
|
313
200
|
try {
|
|
314
201
|
replayHits = await replayEvent(event);
|
|
@@ -325,129 +212,11 @@ async function scanOneTranscript(
|
|
|
325
212
|
truncateExample(example),
|
|
326
213
|
);
|
|
327
214
|
}
|
|
328
|
-
|
|
329
|
-
// Credential VALUES, which the replay above cannot report: it returns a
|
|
330
|
-
// decision per policy, never the text that matched. Scanned separately so
|
|
331
|
-
// the leak report can name WHICH key, and so detection can be tuned for
|
|
332
|
-
// precision without dragging the redactor's recall down with it.
|
|
333
|
-
if (!suppressLeakScan) recordLeaks(result, event);
|
|
334
215
|
}
|
|
335
216
|
|
|
336
217
|
return { result, bytesScanned, detectorState: sessionState, resumed };
|
|
337
218
|
}
|
|
338
219
|
|
|
339
|
-
/**
|
|
340
|
-
* Fingerprint every credential in one event and attach the sightings.
|
|
341
|
-
*
|
|
342
|
-
* The raw value exists only inside this function. It is read out of the event,
|
|
343
|
-
* hashed into a salted id, rendered into a mask, and dropped — nothing that
|
|
344
|
-
* leaves here can reconstruct it.
|
|
345
|
-
*
|
|
346
|
-
* Input and result are scanned separately and tagged, because they are
|
|
347
|
-
* different exposures: an input is what the agent SENT (deniable at PreToolUse
|
|
348
|
-
* on 12/12 CLIs, and 39.5% of measured appearances) while a result is what it
|
|
349
|
-
* RECEIVED (blockable on 2/12, and only after the fact).
|
|
350
|
-
*/
|
|
351
|
-
function recordLeaks(result: TranscriptAuditResult, event: NormalizedToolEvent): void {
|
|
352
|
-
const salt = leakSalt();
|
|
353
|
-
if (!salt) return;
|
|
354
|
-
|
|
355
|
-
const filePath = (event.toolInput as { file_path?: unknown }).file_path;
|
|
356
|
-
const shortPath = typeof filePath === "string" ? shortenPaths(filePath) : null;
|
|
357
|
-
|
|
358
|
-
const scan = (text: string, direction: "input" | "result"): void => {
|
|
359
|
-
for (const match of findSecrets(text)) {
|
|
360
|
-
(result.leaks ??= []).push({
|
|
361
|
-
id: fingerprintId(match.value, salt),
|
|
362
|
-
fingerprint: fingerprintSecret(match.value, match.rule),
|
|
363
|
-
name: match.name,
|
|
364
|
-
rule: match.rule,
|
|
365
|
-
shaped: match.shaped,
|
|
366
|
-
timestamp: event.timestamp,
|
|
367
|
-
cwd: event.cwd,
|
|
368
|
-
toolName: event.toolName,
|
|
369
|
-
direction,
|
|
370
|
-
path: shortPath,
|
|
371
|
-
});
|
|
372
|
-
}
|
|
373
|
-
};
|
|
374
|
-
|
|
375
|
-
scan(flattenToolInput(event.toolInput), "input");
|
|
376
|
-
if (event.toolResultText !== undefined) scan(event.toolResultText, "result");
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
/**
|
|
380
|
-
* The machine's HMAC salt, read once per process.
|
|
381
|
-
*
|
|
382
|
-
* Cached because `scanOneTranscript` runs this per EVENT — 340,918 of them on
|
|
383
|
-
* the measured corpus — and re-reading a file that many times would dominate
|
|
384
|
-
* the scan. `null` when the store is unreadable, which disables leak recording
|
|
385
|
-
* rather than failing the audit: a scan that cannot fingerprint is still a scan
|
|
386
|
-
* that can count.
|
|
387
|
-
*/
|
|
388
|
-
let cachedSalt: string | null | undefined;
|
|
389
|
-
function leakSalt(): string | null {
|
|
390
|
-
if (cachedSalt === undefined) {
|
|
391
|
-
try {
|
|
392
|
-
cachedSalt = readLeakIdentity().salt;
|
|
393
|
-
} catch {
|
|
394
|
-
cachedSalt = null;
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
return cachedSalt;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
/**
|
|
401
|
-
* Merge this scan's sightings into `~/.failproofai/audit/leaks.json`.
|
|
402
|
-
*
|
|
403
|
-
* Returns the ids seen for the FIRST time, which is the set worth telling a
|
|
404
|
-
* human about. Never throws: a scan that finds leaks it cannot write down has
|
|
405
|
-
* still found them, and the caller reports on the in-memory result either way.
|
|
406
|
-
*/
|
|
407
|
-
export function persistLeaks(perTranscript: TranscriptAuditResult[], replaceExisting: boolean): string[] {
|
|
408
|
-
const fresh: string[] = [];
|
|
409
|
-
try {
|
|
410
|
-
const previous = readLeakRecord();
|
|
411
|
-
const previousIds = new Set(previous.findings.map((f) => f.id));
|
|
412
|
-
// A successful all-history scan is authoritative. Rebuilding is what lets
|
|
413
|
-
// detector precision fixes remove findings they now reject; merging made
|
|
414
|
-
// every false positive live for 90 days even after the code was fixed.
|
|
415
|
-
// Scoped or incomplete scans still merge because absence outside their
|
|
416
|
-
// coverage says nothing.
|
|
417
|
-
const record = replaceExisting
|
|
418
|
-
? emptyRecord(previous.salt, new Date().toISOString())
|
|
419
|
-
: previous;
|
|
420
|
-
for (const t of perTranscript) {
|
|
421
|
-
for (const leak of t.leaks ?? []) {
|
|
422
|
-
const { isNew } = upsertFinding(record, {
|
|
423
|
-
id: leak.id,
|
|
424
|
-
fingerprint: leak.fingerprint,
|
|
425
|
-
name: leak.name,
|
|
426
|
-
rule: leak.rule,
|
|
427
|
-
// A vendor shape is what licenses an alert to name a console; a
|
|
428
|
-
// name-only match knows something leaked, not who issued it.
|
|
429
|
-
confidence: leak.shaped ? "doc-verified" : "name-only",
|
|
430
|
-
sighting: {
|
|
431
|
-
cli: t.cli,
|
|
432
|
-
sessionId: t.sessionId,
|
|
433
|
-
cwd: shortenPaths(leak.cwd),
|
|
434
|
-
at: leak.timestamp,
|
|
435
|
-
mechanism: describeMechanism(leak.toolName, leak.direction, leak.path),
|
|
436
|
-
},
|
|
437
|
-
});
|
|
438
|
-
// During an authoritative rebuild every insertion is new to the empty
|
|
439
|
-
// record, but only ids absent from the previous record are new to the
|
|
440
|
-
// HUMAN and eligible for a notification.
|
|
441
|
-
if (isNew && !previousIds.has(leak.id)) fresh.push(leak.id);
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
writeLeakRecord(record);
|
|
445
|
-
} catch {
|
|
446
|
-
// Bookkeeping failure must not fail the audit — see leak-store.ts.
|
|
447
|
-
}
|
|
448
|
-
return fresh;
|
|
449
|
-
}
|
|
450
|
-
|
|
451
220
|
function formatPolicyExample(_policyName: string, event: NormalizedToolEvent): string {
|
|
452
221
|
if (event.toolName === "Bash") {
|
|
453
222
|
const command = (event.toolInput as { command?: unknown }).command;
|
|
@@ -467,7 +236,7 @@ function formatPolicyExample(_policyName: string, event: NormalizedToolEvent): s
|
|
|
467
236
|
* came from the FIRST event in the file and the tail's came from the first
|
|
468
237
|
* event after the offset, so the older one wins.
|
|
469
238
|
*/
|
|
470
|
-
|
|
239
|
+
function mergeIncremental(
|
|
471
240
|
cached: TranscriptAuditResult,
|
|
472
241
|
tail: TranscriptAuditResult,
|
|
473
242
|
): TranscriptAuditResult {
|
|
@@ -481,12 +250,6 @@ export function mergeIncremental(
|
|
|
481
250
|
examplesByName: {},
|
|
482
251
|
rangeByName: { ...cached.rangeByName },
|
|
483
252
|
};
|
|
484
|
-
if (cached.leakScanSuppressed || tail.leakScanSuppressed) {
|
|
485
|
-
out.leakScanSuppressed = "credential-research";
|
|
486
|
-
out.leaks = [];
|
|
487
|
-
} else {
|
|
488
|
-
out.leaks = [...(cached.leaks ?? []), ...(tail.leaks ?? [])];
|
|
489
|
-
}
|
|
490
253
|
for (const [name, list] of Object.entries(cached.examplesByName)) {
|
|
491
254
|
out.examplesByName[name] = [...list];
|
|
492
255
|
}
|
|
@@ -669,22 +432,18 @@ async function runAuditInner(opts: RunAuditOptions, startedAt: number): Promise<
|
|
|
669
432
|
|
|
670
433
|
// 1. Discover transcripts across all selected CLIs.
|
|
671
434
|
const allTranscripts: TranscriptMetadata[] = [];
|
|
672
|
-
let discoveryErrors = 0;
|
|
673
435
|
for (const cli of clis) {
|
|
674
436
|
const adapter = ADAPTERS[cli];
|
|
675
437
|
let list: TranscriptMetadata[];
|
|
676
438
|
try {
|
|
677
439
|
list = await adapter.listTranscripts({ projects: opts.projects, sinceMs });
|
|
678
440
|
} catch {
|
|
679
|
-
discoveryErrors++;
|
|
680
441
|
continue; // adapter failures shouldn't kill the whole audit
|
|
681
442
|
}
|
|
682
443
|
allTranscripts.push(...list);
|
|
683
444
|
}
|
|
684
445
|
|
|
685
|
-
// 2. Scan each transcript (cache-aware), 8
|
|
686
|
-
// budget — see TRANSCRIPT_BYTE_BUDGET for why a file count is the wrong unit.
|
|
687
|
-
const gate = new ByteGate(TRANSCRIPT_BYTE_BUDGET);
|
|
446
|
+
// 2. Scan each transcript (cache-aware), 8 in parallel.
|
|
688
447
|
let skipped = 0;
|
|
689
448
|
let errors = 0;
|
|
690
449
|
const tasks = allTranscripts.map((meta) => async (): Promise<TranscriptAuditResult> => {
|
|
@@ -698,9 +457,6 @@ async function runAuditInner(opts: RunAuditOptions, startedAt: number): Promise<
|
|
|
698
457
|
: undefined;
|
|
699
458
|
cachedPrefix = found?.kind === "resume" ? found.result : null;
|
|
700
459
|
}
|
|
701
|
-
// Memory admission. Taken AFTER the cache check above, so a cache hit — the
|
|
702
|
-
// common case on a warm machine — never waits for a slot it does not use.
|
|
703
|
-
await gate.acquire(meta.sizeBytes);
|
|
704
460
|
try {
|
|
705
461
|
const scan = await scanOneTranscript(meta, resume);
|
|
706
462
|
// A resumed scan produced hits for the TAIL only; the cached result holds
|
|
@@ -744,11 +500,6 @@ async function runAuditInner(opts: RunAuditOptions, startedAt: number): Promise<
|
|
|
744
500
|
examplesByName: {},
|
|
745
501
|
rangeByName: {},
|
|
746
502
|
};
|
|
747
|
-
} finally {
|
|
748
|
-
// Released on every path, including the error one — a task that threw
|
|
749
|
-
// still freed its memory, and holding its weight would shrink the budget
|
|
750
|
-
// permanently over a long scan until nothing could be admitted at all.
|
|
751
|
-
gate.release(meta.sizeBytes);
|
|
752
503
|
}
|
|
753
504
|
});
|
|
754
505
|
|
|
@@ -776,30 +527,8 @@ async function runAuditInner(opts: RunAuditOptions, startedAt: number): Promise<
|
|
|
776
527
|
eventsScanned += t.eventsScanned ?? 0;
|
|
777
528
|
}
|
|
778
529
|
|
|
779
|
-
// Fold every sighting into the persistent record. This is the ONLY place the
|
|
780
|
-
// leak record is written: `scanOneTranscript` runs 8-way concurrent and a
|
|
781
|
-
// per-transcript write would be eight processes racing one file — the exact
|
|
782
|
-
// shape that lost updates when it was measured (2 concurrent writers, 5/5
|
|
783
|
-
// trials, the loser's entry gone permanently).
|
|
784
|
-
//
|
|
785
|
-
// `newFindingIds` is what a notice keys on: an id already in the record must
|
|
786
|
-
// not alert again however many fresh sightings it accumulates, and a
|
|
787
|
-
// credential seen for the first time must alert even though its rule has
|
|
788
|
-
// fired a thousand times before.
|
|
789
|
-
const coversAllHistory =
|
|
790
|
-
opts.clis === undefined &&
|
|
791
|
-
opts.projects === undefined &&
|
|
792
|
-
opts.since === undefined &&
|
|
793
|
-
discoveryErrors === 0 &&
|
|
794
|
-
errors === 0 &&
|
|
795
|
-
skipped === 0;
|
|
796
|
-
const newFindingIds = persistLeaks(perTranscript, coversAllHistory);
|
|
797
|
-
const leakIds = [...new Set(perTranscript.flatMap((t) => (t.leaks ?? []).map((leak) => leak.id)))];
|
|
798
|
-
|
|
799
530
|
const auditResult: AuditResult = {
|
|
800
531
|
version: 2,
|
|
801
|
-
leakIds,
|
|
802
|
-
newLeakIds: newFindingIds,
|
|
803
532
|
scannedAt: new Date(startedAt).toISOString(),
|
|
804
533
|
scope: {
|
|
805
534
|
cli: clis,
|
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
* - `machine_id` is what the api-server keys reports on. Regenerate it and the
|
|
8
8
|
* server sees a brand-new machine, which burns a slot off the account's cap
|
|
9
9
|
* on every logout and splits one box's history into two.
|
|
10
|
-
* - `last_reported_at` is how far the last
|
|
11
|
-
*
|
|
10
|
+
* - `last_reported_at` is how far the last digest reached. Reset it and the
|
|
11
|
+
* next report re-covers months of history, and the user gets a digest of
|
|
12
|
+
* everything that ever happened as though it just did.
|
|
12
13
|
*
|
|
13
14
|
* That is why this is a separate file from `session.json` rather than two more
|
|
14
15
|
* keys in it: signing out deletes the session, and neither of these may go with
|
|
@@ -21,7 +22,7 @@
|
|
|
21
22
|
* `state/telemetry-id` is already a stable per-machine random id and would have
|
|
22
23
|
* been free to reuse. It is deliberately not reused: that id is the anonymous
|
|
23
24
|
* PostHog person, and sending it alongside a verified email address would link
|
|
24
|
-
* the two the moment somebody
|
|
25
|
+
* the two the moment somebody turns emailed reports on. Opting into a digest
|
|
25
26
|
* should not de-anonymise telemetry, so this feature gets its own id and the
|
|
26
27
|
* two never meet.
|
|
27
28
|
*/
|
|
@@ -65,8 +66,8 @@ export function readMachineIdentity(home?: string): MachineIdentity | null {
|
|
|
65
66
|
/**
|
|
66
67
|
* Read the identity, creating it on first call.
|
|
67
68
|
*
|
|
68
|
-
* Only ever called from the
|
|
69
|
-
*
|
|
69
|
+
* Only ever called from the reporting path, so a machine that never opts into
|
|
70
|
+
* emailed reports never gets an id at all — there is nothing to mint one for.
|
|
70
71
|
*/
|
|
71
72
|
export function ensureMachineIdentity(home?: string): MachineIdentity {
|
|
72
73
|
const existing = readMachineIdentity(home);
|
|
@@ -80,13 +81,13 @@ export function ensureMachineIdentity(home?: string): MachineIdentity {
|
|
|
80
81
|
}
|
|
81
82
|
|
|
82
83
|
/**
|
|
83
|
-
* Record how far the last
|
|
84
|
+
* Record how far the last DELIVERED digest reached.
|
|
84
85
|
*
|
|
85
86
|
* The value is the server's `next_window_from`, not the window this run
|
|
86
87
|
* scanned. The server is authoritative because it knows which reports actually
|
|
87
88
|
* produced an email — a report held by the cooldown, or one whose send failed,
|
|
88
89
|
* must not advance the watermark or its findings are silently dropped from every
|
|
89
|
-
* future
|
|
90
|
+
* future digest.
|
|
90
91
|
*/
|
|
91
92
|
export function recordReportWatermark(nextWindowFrom: string, home?: string): void {
|
|
92
93
|
const current = ensureMachineIdentity(home);
|
|
@@ -104,7 +105,7 @@ export function deleteMachineIdentity(home?: string): void {
|
|
|
104
105
|
/**
|
|
105
106
|
* A display name for this machine — its hostname.
|
|
106
107
|
*
|
|
107
|
-
* Shown in the
|
|
108
|
+
* Shown in the digest so somebody with three boxes can tell which one is
|
|
108
109
|
* misbehaving, which is the whole reason it is sent. Falls back to `undefined`
|
|
109
110
|
* rather than a placeholder: the server keeps whatever label it already has when
|
|
110
111
|
* one is omitted, so guessing here would overwrite a good name with a bad one.
|