Skip to content

fix(backend): review-pass hardening (SSRF, delete guard, git timeout, RAII, import, dedupe)#88

Merged
juacker merged 7 commits into
mainfrom
fix/web-fetch-ssrf-hardening
Jul 2, 2026
Merged

fix(backend): review-pass hardening (SSRF, delete guard, git timeout, RAII, import, dedupe)#88
juacker merged 7 commits into
mainfrom
fix/web-fetch-ssrf-hardening

Conversation

@juacker

@juacker juacker commented Jul 2, 2026

Copy link
Copy Markdown
Owner

What

A backend hardening + cleanup pass from the senior-engineer code review of src-tauri/. One commit per finding, all on this PR:

  1. fix(tools): harden web_fetch against private targets — parse with reqwest::Url, disable auto-redirects and follow manually, revalidate every hop's host against the private/loopback/link-local/reserved/CGNAT blocklist (IPv4 + IPv6), pin the validated address into reqwest (resolve_to_addrs) to defeat DNS rebinding, and stream the body into a bounded buffer before HTML→markdown so a hostile public server can't OOM the process.
  2. fix(workspace): block deletion while runs are activeworkspace_has_active_run (queued/running/waiting_for_tool across all sessions) now gates workspace_delete before index removal + remove_dir_all, mirroring the existing session-delete guard.
  3. fix(skills): bound git source sync commands — every skill-source git invocation runs non-interactive (GIT_TERMINAL_PROMPT=0, GIT_ASKPASS=true, forwarded through flatpak-spawn --env) under a 120s timeout with drained stdout/stderr, converting network/credential hangs into fast failures.
  4. refactor(runtime): unregister active runs on guard dropregister_run returns a RunRegistration RAII guard (generation-tagged so a duplicate registration can't evict a newer token); drop unregisters on both normal exit and panic. Removed the manual unregister_run from all three spawn sites and the now-dead public fn.
  5. fix(import): avoid overwriting file collisions — replaced exists()+copy() with an exclusive-create (O_EXCL) copy loop that cleans up partial files, closing a TOCTOU.
  6. refactor(compaction): share context-limit recovery helper — extracted compact_for_context_limit_recovery, the one shared op between the HTTP and CLI recovery paths (behavior identical, CLI keeps its session-reset hook locally).
  7. fix(tools): close web_fetch scheme-downgrade + NAT64 SSRF gaps — from the review of this stack: refuse https→http redirect downgrades, and treat NAT64 (64:ff9b::/96 well-known, 64:ff9b:1::/48 local-use) by extracting the embedded IPv4 and applying the IPv4 blocklist.

Review

Independent static review of the full main..HEAD range (Code Reviewer Minimax): production_quality — no blockers, no majors. Commit 7 above folds in the two in-scope security minors it surfaced. Remaining minors are documented follow-ups, deliberately out of scope: an end-to-end redirect-loop test (needs a mock-server dev-dep), a reviewer-acknowledged-benign queued-run TOCTOU in workspace_delete (the comment already calls the guard "coarse"; the RAII guard closes the in-flight case), and two cosmetic cleanups (redundant input.rewind(), #[cfg(test)] unique_destination).

Verification

cargo fmt --check · cargo clippy --lib --no-deps -- -D warnings · cargo test --lib (726) · cargo test --test cancel_run — all green on Linux. New unit/integration tests: workspace-delete active-run guard, git env/timeout arg composition (native + flatpak), RAII register→cancel→drop + double-register + stale-drop, import collision + O_EXCL, NAT64 private/public classification.

juacker added 7 commits July 2, 2026 13:20
Addresses the two in-scope security minors from the PR #88 static review:

- Refuse https->http redirects. fetch_public_url now records whether the
  original request was https and rejects any redirect that downgrades the
  transport, so a redirect can't strip TLS.
- Handle NAT64 addresses in the IPv6 private check. The well-known prefix
  64:ff9b::/96 embeds a target IPv4 in its low 32 bits, so 64:ff9b::7f00:1
  would reach 127.0.0.1. Extract the embedded IPv4 and apply the IPv4
  blocklist (only the embedded address decides, so public NAT64 targets like
  64:ff9b::808:808 still resolve). The local-use prefix 64:ff9b:1::/48
  (RFC 8215) is site-internal by definition and is blocked wholesale.

Tests: NAT64 embedded-loopback + local-use added to the private-literal set,
NAT64 embedded-public added to the allowed set. cargo fmt/clippy/test green
(726 lib tests).
@juacker juacker changed the title fix(tools): harden web_fetch against private targets fix(backend): review-pass hardening (SSRF, delete guard, git timeout, RAII, import, dedupe) Jul 2, 2026
@juacker juacker marked this pull request as ready for review July 2, 2026 21:59
@juacker juacker merged commit 9122bc6 into main Jul 2, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant