Skip to content

feat(app): opt-in debug logging mode and safer, richer diagnostic bundles - #314

Merged
pmaxhogan merged 5 commits into
mainfrom
wave-1-debug-diag
Aug 18, 2026
Merged

feat(app): opt-in debug logging mode and safer, richer diagnostic bundles#314
pmaxhogan merged 5 commits into
mainfrom
wave-1-debug-diag

Conversation

@pmaxhogan

@pmaxhogan pmaxhogan commented Aug 17, 2026

Copy link
Copy Markdown
Owner

Summary

Closes #309, closes #204. Part of the v2.12.0 wave (PR5).

#204 (diagnostic bundle PII/secret leaks) - redact_settings() used to clone GlobalSettings verbatim and patch only proxy_url. It now builds a field-for-field RedactedGlobalSettings struct, so a future secret-bearing field added to GlobalSettings fails to compile here until someone decides how to redact it, instead of leaking silently. Fixes the three concrete leaks the issue named:

  • pre_backup_hook / post_backup_hook (command lines - a classic home for embedded secrets) are now redacted wholesale (<hook-redacted: N chars>), not shipped raw.
  • custom_root_ca_path is now hashed through the same <path:hash> scheme the rest of the bundle already uses.
  • proxy_url in PAC mode (a local file path, not a URL) is now hashed instead of only having userinfo-stripping applied (which never matched a bare path).
  • The issue's "also worth fixing" item (ProxyError's Display embedding raw userinfo) was already fixed by fix(net): redact userinfo from PAC source in logs #208 - verified via git blame, not touched again.

#309 (debug logging mode) - a new Settings > Privacy & Data toggle ("Debug logging") with an always-visible amber warning panel (shown before the toggle is ever switched on, per the approved mockup), backed by:

  • A real runtime-reloadable tracing filter (logging.rs, tracing_subscriber::reload) - flipping the toggle now actually changes the live process's verbosity, no restart needed. This also closes a long-documented gap where global.log_level only ever exported RUST_LOG for the next launch; it now reloads the live filter too (deferred while debug mode is active, so it doesn't undo the debug-mode filter).
  • A persisted epoch-ms expiry + a boot-time reconcile and periodic watchdog (debug_mode.rs) that auto-turns the toggle off 24h after enabling - honoured across a restart, not just while the app keeps running. The watchdog is registered on AppState and joined by fix(app): never freeze on tray quit during a backup; quitting tray state; honest recovery status #312's no-orphan quit drain (ShutdownHandles/drain_shutdown_handles), the same pattern feat: live bottleneck indicator on the Activity dashboard #311's bottleneck sampler uses.
  • A rolling log cap that widens from 25 MB to 250 MB while debug mode is on.
  • The diagnostic bundle gains DEBUG_MODE.txt and an unredacted debug/engine_state.txt while debug mode is on - the one deliberate exception to the Diagnostic bundle still ships PII and possible secrets in settings_redacted.json #204 redaction rules, gated on the user's explicit opt-in (every other bundle file stays redacted regardless).
  • Every bundle now also ships manifest.txt (entry name + size), a small bundle-usefulness improvement.
  • Activity's "Export diagnostic bundle" button shows an amber "Debug data included" chip while debug mode is on.

Also in this PR

  • Rebased onto main after fix: clear the attention banner after a passing run and make source removal backend-aware #310-fix(s3): honest per-source versioning across backends and multipart-upload leak sweep #316 merged. Re-homed the debug-mode watchdog from a detached memlog.rs-style task into fix(app): never freeze on tray quit during a backup; quitting tray state; honest recovery status #312's ShutdownHandles/drain_shutdown_handles no-orphan quit drain (new DebugModeRuntime on AppState, set_debug_mode_task/shutdown_debug_mode_task mirroring set_bottleneck_task/shutdown_bottleneck_task).
  • CodeQL rust/path-injection fix (not a dismissal): two test helpers (settings.rs's pre-existing seeded_repo() and this PR's new debug_mode.rs one) hand-rolled a temp dir via std::env::temp_dir().join(format!(...)) before feeding it to SqliteStateRepo::open - exactly the pattern this repo's CodeQL rule flags (see the tempfile dependency comment in src-tauri/Cargo.toml, and PR 151 precedent). Switched both to tempfile::tempdir().keep(), an opaque external call CodeQL's dataflow can't see into, so the taint chain never forms.
  • Also carries the h2 advisory fix (RUSTSEC-2026-0258, low severity, unbounded empty DATA frames) - cargo update -p h2 (0.4.15 -> 0.4.16), lockfile-only, no Cargo.toml changes. This advisory is unrelated to this PR's own diff (git diff against the pre-PR base shows zero Cargo.lock changes before this commit) and would fail cargo deny repo-wide on main too; landing it here unblocks this PR's cargo deny check and delivers the fix to main in the same step.

Test plan

  • cargo test -p driven-app --lib - 494 passed (18 Diagnostic bundle still ships PII and possible secrets in settings_redacted.json #204 redaction tests with leak-shaped fixtures, incl. one asserting the full serialized bundle JSON end-to-end; 5 debug-mode watchdog/expiry tests; 6 settings-persistence round-trip tests; 1 AppState debug-mode task/shutdown round-trip test)
  • cargo clippy --workspace --all-targets -- -D warnings - clean
  • cargo fmt --all -- --check - clean
  • cargo build --workspace --tests - clean
  • cargo deny check - clean (advisories ok, bans ok, licenses ok, sources ok)
  • pnpm lint / pnpm format:check / pnpm test:unit (861 passed, 64 files) / pnpm build (vue-tsc + vite) - all clean, run in the CI job's exact order
  • Linux visual baselines regenerated via just visual-update (Docker) - privacy.png (light+dark) plus 9 shell.spec.ts baselines (light+dark) that had drifted independently of this PR; all 106 visual specs pass
  • README updated (Features list + comparison-table footnote ³⁴)

🤖 Generated with Claude Code

https://claude.ai/code/session_019xKUm9vH4ifb5LHR5szy1v

Comment thread src-tauri/src/debug_mode.rs Fixed
@github-actions

github-actions Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Coverage

Area main this PR delta
Rust (lib crates) 84.45% 84.37% -0.08 (OK)
UI (vue/ts) 93.40% 93.42% +0.02 (OK)

Gate: passed - no coverage regression (epsilon 0.1 pp).

pmaxhogan added a commit that referenced this pull request Aug 17, 2026
PR #314's "ui build + lint + unit" CI job failed in 38s at the
prettier --check step (after pnpm install + lint, before test:unit
and build): activity-debug-chip.test.ts and settings-pages.test.ts
were hand-typed and not run through Prettier before the previous
commit. No logic change, whitespace/wrapping only - re-verified
lint, format:check, test:unit (793 passed), and build all pass
locally in the workflow's exact step order.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019xKUm9vH4ifb5LHR5szy1v
pmaxhogan and others added 3 commits August 18, 2026 12:09
…dles

Closes #309, closes #204.

instead of cloning GlobalSettings and patching one field, so a future
secret-bearing field fails to compile here rather than leaking silently.
Fixes three concrete leaks: pre/post_backup_hook command lines (a classic
home for embedded secrets) are now redacted wholesale, custom_root_ca_path
is hashed like every other path in the bundle, and proxy_url in PAC mode
(a local file path, not a URL) is now hashed instead of passing the
userinfo-strip-only path through untouched. The ProxyError Display leak
the issue also flagged was already fixed by #208 - verified, not touched.

always-visible amber warning, backed by a real runtime-reloadable tracing
filter (logging.rs) that raises Driven's own crates to trace level while
on. The toggle persists an epoch-ms expiry and auto-turns-off 24h after
being enabled, enforced by a boot-time reconcile plus a periodic watchdog
(debug_mode.rs) so the window is honoured even across a restart. The
rolling log cap widens from 25 MB to 250 MB while debug mode is on. The
diagnostic bundle gains a DEBUG_MODE.txt notice and an unredacted
debug/engine_state.txt when debug mode is on - the one deliberate
exception to the #204 redaction rules, gated on the user's explicit
opt-in. Every bundle now also ships manifest.txt (entry name + size).
Activity's export button shows an amber "Debug data included" chip while
debug mode is on.

Also closes the long-documented gap where global.log_level only exported
RUST_LOG for the next launch - it now reloads the live filter too.

Testing: 18 Rust redaction tests (leak-shaped fixtures for hooks, CA path,
PAC-mode path, PAC-mode URL, and one full end-to-end fixture asserting the
serialized bundle JSON), 5 debug_mode watchdog/expiry tests, 5
settings-persistence round-trip tests. cargo test -p driven-app --lib:
458 passed. cargo clippy --workspace --all-targets -- -D warnings: clean.
cargo fmt --all -- --check: clean. pnpm vitest run: 793 passed across 60
files (new: activity-debug-chip.test.ts, plus PrivacyPage toggle tests in
settings-pages.test.ts). vue-tsc --noEmit: clean. Linux visual baselines
regenerated via `just visual-update` (privacy.png light+dark) and pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019xKUm9vH4ifb5LHR5szy1v
PR #314's "ui build + lint + unit" CI job failed in 38s at the
prettier --check step (after pnpm install + lint, before test:unit
and build): activity-debug-chip.test.ts and settings-pages.test.ts
were hand-typed and not run through Prettier before the previous
commit. No logic change, whitespace/wrapping only - re-verified
lint, format:check, test:unit (793 passed), and build all pass
locally in the workflow's exact step order.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019xKUm9vH4ifb5LHR5szy1v
…quit drain

Rebases wave-1-debug-diag onto origin/main now that #310-#316 merged
(#312's quit-path restructuring in particular). Structural follow-up:

- The debug-logging-mode watchdog (debug_mode.rs) previously used the
  detached memlog.rs pattern (no shutdown tracking). #312 replaced the
  old shutdown_orchestrators() with a proper ShutdownHandles/
  drain_shutdown_handles structure that every other periodic background
  task (updater, telemetry, iostat, and now #311's bottleneck sampler)
  registers into for a no-orphan quit drain. Re-homed the watchdog into
  that same structure: a new DebugModeRuntime (task + shutdown watch,
  no shared hub - the watchdog only reads/writes settings directly)
  on AppState, set_debug_mode_task/shutdown_debug_mode_task mirroring
  set_bottleneck_task/shutdown_bottleneck_task exactly, a debug_mode
  field on ShutdownHandles, and spawn_watchdog now runs the same
  select!-on-shutdown-or-tick loop bottleneck_hub/iostat_hub use
  instead of a bare loop.
- Added app_state::tests::debug_mode_runtime_task_and_shutdown_round_trip,
  mirroring bottleneck's round-trip test.
- privacy.png (light+dark) and 9 shell.spec.ts baselines (light+dark)
  regenerated via `just visual-update` (Docker) - the shell baselines
  drifted independently of this PR's own diff (same delta across every
  scenario in both themes), consistent with normal headless-Chromium
  rendering drift between visual-update runs; all 106 visual specs pass
  against the regenerated set.

No other conflicts: README.md, dtos.rs, settings.rs's redaction code,
en-US.json, Activity.vue, and fixtures.ts all auto-merged cleanly with
#311's bottleneck-tile additions coexisting alongside this PR's debug
logging toggle and diagnostic-bundle changes.

Verified after rebase: cargo test -p driven-app --lib (494 passed),
cargo clippy --workspace --all-targets -- -D warnings (clean),
cargo fmt --all --check (clean), pnpm lint (0 errors), pnpm format:check
(clean), pnpm test:unit (861 passed, 64 files), pnpm build / vue-tsc
(clean).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019xKUm9vH4ifb5LHR5szy1v
@pmaxhogan
pmaxhogan enabled auto-merge (squash) August 18, 2026 17:16
pmaxhogan and others added 2 commits August 18, 2026 12:28
…ction

Both `seeded_repo()` test helpers (settings.rs, pre-existing; debug_mode.rs,
new in this PR) hand-rolled a temp directory via
`std::env::temp_dir().join(format!("...{nonce}-{:p}...", ...))` before
passing it to `SqliteStateRepo::open`. That is exactly the pattern
`rust/path-injection` flags in this repo (driven-ci-flakes memory, PR 151
precedent; also documented at src-tauri/Cargo.toml's `tempfile` dependency
comment) - CodeQL's dataflow can see straight through the inline `format!`
call, and the pointer-formatted nonce reads as attacker-observable data.

Switched both to `tempfile::tempdir().keep()` (an opaque external call
CodeQL's analysis does not see into, so the taint chain never forms),
keeping the `(SqliteStateRepo, PathBuf)` return shape unchanged so every
existing `cleanup(dir)` call site round-trips with no other edits. This is
the repo's established FIX (not a dismissal) for this exact pattern.

Also explains the refingerprinted `crates/driven-core/src/state/sqlite.rs`
alert this PR's CI run surfaced as "new": that file's `SqliteStateRepo::open`
sink is unchanged, but adding new callers (via the two seeded_repo() sites)
to the analyzed call graph re-triggers CodeQL's whole-program dataflow scan
and re-mints the alert number. Fixing the source pattern at both call sites
resolves it without a dismissal.

Verified: cargo test -p driven-app --lib (494 passed), cargo clippy
--workspace --all-targets -- -D warnings (clean), cargo fmt --all --check
(clean).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019xKUm9vH4ifb5LHR5szy1v
@pmaxhogan
pmaxhogan merged commit 33c281c into main Aug 18, 2026
19 checks passed
@pmaxhogan
pmaxhogan deleted the wave-1-debug-diag branch August 18, 2026 17:55
@github-project-automation github-project-automation Bot moved this from Todo to Done in Driven Aug 18, 2026
pmaxhogan added a commit that referenced this pull request Aug 18, 2026
🤖 I have created a release *beep* *boop*
---


##
[2.12.0](v2.11.1...v2.12.0)
(2026-08-18)


### Features

* **app:** opt-in debug logging mode and safer, richer diagnostic
bundles ([#314](#314))
([33c281c](33c281c))
* **core:** allow nested backup sources when the parent excludes the
child ([#294](#294))
([0b62df9](0b62df9))
* **core:** live exclusion pickup and a visible pending-work queue
([#313](#313))
([e6427c7](e6427c7))
* live bottleneck indicator on the Activity dashboard
([#311](#311))
([2d9d763](2d9d763))
* **ui:** folder picker sort/filter/create/rename and exclusions size
rollups ([#315](#315))
([7e87341](7e87341))


### Bug Fixes

* **app:** never freeze on tray quit during a backup; quitting tray
state; honest recovery status
([#312](#312))
([f951cde](f951cde))
* clear the attention banner after a passing run and make source removal
backend-aware ([#310](#310))
([6d8e1ab](6d8e1ab))
* **s3:** honest per-source versioning across backends and
multipart-upload leak sweep
([#316](#316))
([d462592](d462592))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

2 participants