Skip to content

feat: least-privilege VSS elevation helper broker + secured IPC#96

Open
pmaxhogan wants to merge 1 commit into
mainfrom
feat/vss-helper-service
Open

feat: least-privilege VSS elevation helper broker + secured IPC#96
pmaxhogan wants to merge 1 commit into
mainfrom
feat/vss-helper-service

Conversation

@pmaxhogan

Copy link
Copy Markdown
Owner

What

Rebuild VSS elevation as a narrow privileged broker that elevates ONLY the shadow-copy operation (DESIGN s5.3.1), replacing the whole-app elevation removed pre-V1. The main backup app stays un-elevated; a small driven-vss-helper.exe creates the VSS snapshot and streams the locked file's bytes back over a secured Windows named pipe.

Closes #25.

Trust boundary + IPC (DESIGN s5.3.1)

  • Trust boundary: the un-elevated app is UNTRUSTED by the helper; the helper is the sole holder of Administrator rights and re-validates every request from scratch.
  • IPC surface: a random-suffixed Windows named pipe (\.\pipe\driven-vss-<uuid>), length-prefixed 1-byte-kind framing (control JSON + raw data chunks, so streamed bytes carry no base64 overhead), tiny vocabulary (Hello/OpenLocked/Read/CloseFile/EndCycle/Shutdown).
  • Client auth: pipe DACL restricted to the creating user + BUILTIN\Administrators; per-connection caller PID -> image-path check (must be a sibling in the helper's install dir). The client also verifies the server's image before sending any path.
  • Input validation: volume normalise + ..-traversal reject + component-wise within-configured-roots (lexical AND after canonicalising the directory chain) -> a compromised app can only ever read files the user already configured Driven to back up.
  • Streaming solves the read problem: the un-elevated client never opens the \?\GLOBALROOT shadow device; the helper streams the bytes. BrokeredVssProvider lands them in an app-owned temp copy and returns that path, fitting the existing executor read_path seam unchanged.

Lifecycle

On-demand elevated broker (not a persistent SYSTEM service): the app launches the helper elevated via ShellExecute runas (one UAC prompt) with the pipe name + configured roots on its command line; the helper serves for the session and exits on Shutdown. A Windows service install remains a documented alternative under the same IPC + auth + validation contract.

Tested

  • Unit (cross-OS, run on ubuntu/macOS/windows CI): protocol framing + caps, boundary validation (traversal/out-of-root/volume-mismatch/string-prefix-trap), SDDL + image-identity decisions, launch argv/pipe-name, provider degrade + temp-file cleanup.
  • Elevated integration test (tests/helper_integration.rs, gate-skips when not elevated, NOT #[ignore]-faked): spins the real server + client, snapshots a genuinely exclusively-locked file via real VSS, asserts the streamed bytes match, and asserts the boundary rejects out-of-root + traversal requests. Also proves the BrokeredVssProvider temp-copy path. Verified locally elevated via sudo - both cases PASS.
  • Settings: windows.vss_helper setting round-trip + get_vss_helper_status logic (backend tests); the degraded-backup banner show/hide (vitest).
  • Gates green locally: cargo fmt --all --check, cargo clippy --workspace --all-targets (0 warnings), cargo-deny check bans, vue-tsc, the full UI vitest suite, eslint.

Deferred (documented in CODEX_NOTES)

The production launch/provider-swap lifecycle - assembly::build_vss constructing the BrokeredVssProvider when the setting is enabled, coordinating one session pipe name across accounts, and the on-demand elevated launch on first locked-file need - is a follow-up. Its only untestable piece is the literal UAC prompt (argv is unit-tested); the provider/launch/server/client are all tested. Until it lands, build_vss still returns the in-process RealVssProvider (default app behaviour unchanged) and the banner is informational.

🤖 Generated with Claude Code

Rebuild VSS elevation as a narrow privileged broker that elevates ONLY
the shadow-copy operation (DESIGN s5.3.1), replacing the removed
whole-app elevation. New driven-vss-helper crate:

- protocol: length-prefixed, capped, 1-byte-kind framing (control JSON +
  raw data chunks) so streamed file bytes carry no base64 overhead.
- validate: boundary input validation (volume normalise + configured
  allowed-roots + `..`-traversal + component-wise within-root).
- auth: pipe DACL SDDL (user + admins only) + process-image identity
  (caller PID -> image, sibling-install-dir check) both directions.
- server (Windows): accept -> authenticate client -> validate -> create
  or reuse a per-volume VssSnapshot -> stream the locked file's bytes.
  The un-elevated client never opens the \?\GLOBALROOT device.
- client + BrokeredVssProvider: streams bytes into an app-owned temp
  copy and returns that path, fitting the existing executor read_path
  seam; degrades to Unavailable off Windows / when the helper is absent.
- launch + driven-vss-helper.exe: ShellExecute runas + argv/pipe-name.

Elevated integration test proves a real exclusively-locked file streams
through a real VSS snapshot and that the boundary rejects out-of-root +
traversal requests (gate-skips when not elevated; verified via sudo).

Adds the windows.vss_helper setting, a get_vss_helper_status command,
and a Settings banner shown when locked-file backup is degraded.

The production launch/provider-swap lifecycle (build_vss wiring + on-
demand elevated launch) is deferred and documented in CODEX_NOTES.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014fLmkjpFkiuky72eLBijL4
@github-project-automation github-project-automation Bot moved this to Todo in Driven Jul 3, 2026
@pmaxhogan pmaxhogan added this to the v2.0.0 milestone Jul 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

Post-V1: least-privilege VSS elevation helper service

1 participant