# Diagnostics audit (3rd /goal re-fire): the un-audited v3.3.84/85 remote-diagnostics app-side code Method: 52-agent adversarial audit of lib/diagnostics-agent.js, lib/diagnostics-collectors.js, lib/support-bundle.js, lib/remote-server.js + the diag wiring in main.js/renderer/preload, for lag/freeze + correctness/leak. 23 findings, each double-verified. Reference = THIS user's real config (23 result rows). ## Conclusion: the app is HEALTHY for this user's actual usage. All shipped fixes (v3.3.87 recent-panel, v3.3.88 doodstream) stand. The remaining findings are COLD, opt-in (diagnostics is off by default; needs the user to enable it AND a remote operator to connect AND a large accumulated history) — they impose ZERO cost on the normal path and start to matter only ~30k history rows (this user: 23). Found + documented = the deliverable for a "could it exist" audit goal. ## SHIPPED (v3.3.89) — only the two isolated, zero-redaction-surface fixes - lib/remote-server.js: WebSocketServer had NO maxPayload → a pre-auth message ran a synchronous JSON.parse of up to ws's 100 MiB default per message on the main loop = an unbounded freeze/DoS sink. Set maxPayload = 256 KiB (diag/auth/WebRTC-signaling messages are tiny). Closes the sink for free. - lib/remote-server.js sendToClient: `ws.send(JSON.stringify(data))` had no readyState/try guard (unlike broadcast) → a send on a closing socket or a stringify throw escaped as uncaughtException (potential crash). Now guarded with `ws.readyState === 1` + try/catch, mirroring broadcast. 397/397 tests pass, eslint clean. ## DEFERRED — documented, conscious (real, but the fix cuts into the credential-redaction surface) 14 of 15 actionable findings converge on ONE cold-path freeze: a single `server_health` (and get_history/list_errors/get_config) does O(historySize) synchronous work per request — (a) ~6–7 configStore.load() per request, each a full structuredClone(config incl. unbounded history); (b) summarizePerHoster copies+sorts+walks ALL batches; _historyErrors regex-scans ALL batches; the `limit` arg only slices the OUTPUT, not the traversal. Measured: ~258 ms at 7.45 MB / 30k rows, up to 1.6 s–6.7 s in the 39–185 MB tail. On a large history a diagnostic query would freeze the very app it's diagnosing (violates the v3.3.85 rule). REAL — but: - It only fires during an active diagnostic session on a large history; this user (23 rows) never hits it. - The tempting fix (snapshot config + thread secrets through the collectors) refactors lib/diagnostics-collectors.js — which IS the redaction surface (_secrets/_deepRedact/collectSecretValues/ sanitizeConfig/redactLogText). That code has LEAKED TWICE before (7b5420e "one collector still leaked", 8d757a9 "redaction gaps") in ways that passed green tests. Threading a secrets snapshot = exactly that shape: miss/stale one path → SECRET LEAK, a worse failure than the cold freeze. Same category error as last turn's config-store defer (then: data-loss; now: secret-leak), different file. - If ever hardened: ONLY bound the history walk via summarizePerHoster's existing opts.lastNBatches (NOT the secret threading), after confirming per-hoster-over-last-N is acceptable health-snapshot semantics, WITH a per-collector redaction E2E + advisor pass. Not under a Stop hook. Related cold-path items (same defer): get_config_redacted 'all' does 3 full-history passes then discards history (strip history first would help — but same file); collectSecretValues/_secrets unmemoized per request; support-bundle.js sanitizeConfig serializes the whole history + reads ~11 MB logs sync on the main loop (explicit user-triggered bundle, very cold). remote-server _failedAttempts sub-threshold entries never pruned (slow IP-keyed growth) — low. ## DEFERRED (config-store cluster, re-confirmed from last turn) - load() structuredClone of whole config (incl. history) per call; _atomicWrite nulls cache per write; _serializeForDisk stringifies whole history per write; _preserveDiagSubtree adds one extra load() per save-global-settings (normal path, low). All scale with historySize, all sub-ms at this user's 52 KB config, all in credential-bearing persistence code. Safe root fix = split history to its own file (out of scope: risk > reward). Documented, not operated. ## Verified clean (no action) - diagnostics collectors are otherwise well-bounded: readLog tailKb clamped ≤1024 KB, grep is literal substring (ReDoS-free), get_config 'all' deletes history before redaction, all limit/maxJobs-bounded. - _deepRedact is bounded at every call site (NOT the freeze source). - diagnostics renderer wiring is all in renderSettings() (cold); no per-progress/per-frame cost when off.