Skip to content

Threat Model

Statusnone420 edited this page Jun 12, 2026 · 3 revisions

Threat Model

What Diff Drift defends against, what it trusts, and what is explicitly out of scope. Written for security reviewers deciding whether to allow the tool; references point at the code so claims can be checked.

What Diff Drift Is

A local Windows desktop app (Tauri 2: Rust backend + WebView2 frontend) and a read-only CLI. It opens a git repository you choose, parses supported changed source files and package.json, and renders structural drift with heuristic security flags. Security heuristics are strongest for JS/TS and package drift, with language-neutral secret detection across the newer structural language families. Nothing leaves the machine — see Privacy and Data Flow.

Assets

  • Your repository contents — read, never modified, never transmitted.
  • Per-repo triage state (repo-state.json in the app config dir) — dismissed flags, baseline choice, trust point, review hashes. Integrity matters: stale or forged triage could hide a flag.
  • Exported reports — written only to paths you choose in the save dialog.

Trust Boundaries

Untrusted: repository content

The main attack surface. A cloned repo can contain arbitrary hostile bytes, and Diff Drift parses them:

  • Source files → tree-sitter (parse.rs). Parse failures return an empty node list instead of panicking; invalid UTF-8 degrades via utf8_text(...).unwrap_or(""). Files larger than the parse cap are skipped and labeled "Skipped — file too large to analyze" rather than parsed. The skip decision uses sizes first — baseline size from git object headers (odb.read_header), worktree size from filesystem metadata — so deciding that a file is over-cap costs neither memory nor parse time. A skipped file's content identity (used to detect changes and revoke a stale review) is then a git blob oid: within the cap the worktree is read directly (bounded by the 2 MB cap); over the cap it is stream-hashed by libgit2 in fixed chunks, so even a multi-GB file is never held in memory at once. The whole file is never parsed and never fully allocated.
  • package.json and lockfiles → serde_json / line parsing (deps_diff.rs). Unparseable JSON yields no dependency drift rather than an error path.
  • Git metadata → git2 (libgit2) (git.rs). Read-only object access; Diff Drift never shells out to a git binary and never writes to .git/.
  • Flag text rendered in the UI comes from rule templates plus node names extracted by tree-sitter. React escapes rendered text by default, and the CSP (below) blocks remote script regardless.

Repository content is never executed: no npm install, no script hooks, no loading of code from the reviewed repo.

Sandboxed: the renderer

The WebView2 frontend is confined by Tauri:

  • CSP (tauri.conf.json): default-src 'self'; connect-src ipc: http://ipc.localhost — the renderer can only talk to the local backend. No external network destination is permitted even if the frontend were compromised.
  • Capabilities (capabilities/default.json): window controls, the OS file dialog, and the opener plugin. No filesystem, shell, or HTTP capabilities are granted to the renderer.

Trusted: the local user and OS

Diff Drift runs as you, with your permissions. It trusts the OS, the filesystem, and whoever can already write to your user profile.

Explicit Non-Goals

  • Triage-state tampering is out of scope. repo-state.json is plain, unsigned JSON in your app-config directory. Anyone with write access to your profile can already modify the Diff Drift binary itself, so signing the state file would add complexity without a real boundary. What the design does defend against is staleness: every dismissal is pinned to a content hash of the flagged node, so editing the flagged code resurfaces the flag even if the state file says "dismissed" (store.rs).
  • Diff Drift is not a security boundary for the code it reviews. Flags are heuristic review prompts. A clean run is not proof the change is safe, and the threat model does not claim detection of a deliberately evasive attacker — see Rule Reference for per-rule caveats.
  • Multi-user/team integrity. Triage state is per-machine, per-user. There is no shared state to protect.

Denial of Service

  • Oversized files: skipped at a fixed byte cap decided from object headers and file metadata before content is loaded, surfaced in the file list (see User Guide).
  • Watcher thrash: file events are debounced (400 ms) in watcher.rs.
  • Pathological nesting: tree-sitter is error-tolerant and bounded; analysis surfaces top-level statements plus one level of function-body children, so output size stays proportional to input.

A hostile repo can still make analysis slow (many changed files near the size cap). That costs you time, not integrity: analysis is read-only.

No-Network Posture

Verifiable, not just claimed:

  • No application code performs network I/O, and no HTTP client is compiled into the Windows binary: cargo tree -i reqwest on the host target prints nothing. (Cargo.lock does list reqwest/hyper — the Tauri framework pulls an HTTP stack for other platforms, visible via cargo tree --target all -i reqwest; no enabled feature or plugin uses it on any target.)
  • The CSP forbids any non-IPC connection from the renderer, and no networking capability is granted.
  • No updater plugin is configured; updates are manual downloads from Releases.
  • CI runs cargo audit and npm audit on every push (ci.yml) to catch supply-chain advisories in this dependency tree.

Residual Risks

  • tree-sitter / git2 / serde parse untrusted input in native code. They are mature, widely deployed libraries, and CI audits them, but a memory-safety bug in a C dependency (libgit2, tree-sitter runtime) is the most plausible severe vulnerability in this design. Report suspected cases per SECURITY.md.
  • Unsigned binaries (current state): verify downloads against the published SHA256SUMS.txt until code signing lands — see Release and Platform Support.

Clone this wiki locally