Skip to content

feat(desktop): SSRF hardening + shared @sim/security/ssrf (re-home of #5763)#5784

Merged
waleedlatif1 merged 6 commits into
staging-v4from
feat/desktop-ssrf-rehome
Jul 21, 2026
Merged

feat(desktop): SSRF hardening + shared @sim/security/ssrf (re-home of #5763)#5784
waleedlatif1 merged 6 commits into
staging-v4from
feat/desktop-ssrf-rehome

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

What

Re-homes the SSRF-hardening work from #5763 onto the current dev. #5763 was merged then dev was force-pushed to a new history that dropped it, so these changes are gone from dev and are re-applied here onto dev's evolved code.

dev currently has no SSRF protection in the browser-agent and still carries all the duplicated private-IP/loopback logic, so this is not redundant.

Shared SSRF primitive (@sim/security/ssrf)

isPrivateIp / isPrivateIpHost / isIpLiteral / isLoopbackIp / isLoopbackHostname / unwrapIpv6Brackets — one audited, ipaddr.js-based module that dedupes the divergent copies across apps/sim (input-validation ×2, mcp/domain-check, sap_concur/zoominfo/sap, s3, onepassword) and the desktop shell. All callers migrated; the edge-case matrix lives in the package test.

Browser-agent SSRF guard

  • The agent partition's onBeforeRequest is the authoritative choke point: DNS-resolving check (fail-closed) for document navigations — covering page-initiated redirects/clicks the tool layer never sees — and a cheap synchronous literal-IP backstop for subresources.
  • browser_navigate/browser_open_tab validate up front for a clean model error.
  • Blocks loopback, RFC1918, link-local / cloud-metadata (169.254.169.254), obfuscated/IPv4-mapped forms; hostnames are DNS-resolved (rebinding-aware).

Other hardening

  • crashReporter (local-only minidumps, no upload) + CSP fallback (only when an app-origin doc ships none).
  • config: shared isLoopbackHostname (drops the dead ::1); cdp: per-tab callbacks; updater: manual check no longer swallows errors; driver: shared sleep/getErrorMessage, banners removed; README App-Sandbox note corrected.

Re-integration notes

  • Verified equivalences by direct execution; dev already did some cleanups independently (launcher isVisible, decideStartRoute, local-filesystem.clear()) so those are omitted as moot.
  • Not re-applied: the handoff self-DoS gate — dev rewrote handoff.ts with a new OAuth-connect route table, and re-deriving that minor hardening onto the new structure is out of scope here.
  • api-validation: annotated dev's validated-envelope double-cast; bumped the route-count baseline 964→965 for dev's already-merged route (non-Zod and double-cast ratchets stay at baseline).

Testing

@sim/security/ssrf (106) · desktop (228) · affected apps/sim suites (462) — all green. Type-check (desktop + full apps/sim), Biome, and check:api-validation:strict all pass.

Re-applies the browser-agent SSRF guard and hardening onto dev's evolved
desktop files (dev rewrote session/driver/handoff/index and split out
errors.ts/keyboard.ts):

- session.ts: agent-partition onBeforeRequest is the SSRF choke point —
  DNS-resolving check (fail-closed) for document navigations, synchronous
  literal-IP backstop for subresources.
- driver.ts: browser_navigate/browser_open_tab validate via checkAgentUrl for a
  clean model error; also adopt shared sleep/getErrorMessage and drop the local
  reimplementations + banner separators.
- index.ts: local-only crashReporter (native minidumps, no upload) + CSP
  fallback wired into the app session.
- window.ts: record the crash-dump dir on renderer_gone.
- config.ts: drop the local LOCAL_HOSTNAMES set for the shared isLoopbackHostname
  (also removes the dead bare '::1').
- cdp.ts: per-WebContents callbacks so a background tab's events reach its own
  driver.
- updater.ts: the manual check now surfaces network/manifest failures instead of
  silently swallowing them.
- README: correct the App Sandbox / security-scoped-bookmark note.
- electron-mock: webRequest.onBeforeRequest + crashReporter stubs.
- api-validation: annotate dev's validated-envelope double-cast; bump the
  route-count baseline 964→965 for dev's already-merged route (ratchets stay
  tight; non-Zod and double-cast at baseline).

Skipped as moot (dev already did them independently): launcher isVisible removal,
decideStartRoute param drop, local-filesystem clear() removal.
@waleedlatif1
waleedlatif1 requested a review from a team as a code owner July 20, 2026 23:40
@vercel

vercel Bot commented Jul 20, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Jul 21, 2026 12:06am

Request Review

@cursor

cursor Bot commented Jul 20, 2026

Copy link
Copy Markdown

PR Summary

High Risk
Touches authentication-adjacent navigation (MCP popup gating), outbound URL safety across many integrations, and new network interception on the agent browser—any misclassification could block legitimate URLs or leave SSRF gaps.

Overview
Introduces @sim/security/ssrf (isPrivateIp, isPrivateIpHost, loopback helpers, IPv6 bracket handling) and replaces scattered private-IP logic in apps/sim (input validation, MCP, SAP/ZoomInfo/Concur, 1Password, S3) and desktop config/navigation with that single module.

Desktop agent browser gets new SSRF controls: DNS-resolving checkAgentUrl (fail-closed, 5s timeout) on tool navigations and on the agent partition’s onBeforeRequest for main/sub frames, plus a synchronous isBlockedRequestUrl backstop for subresources and literal-IP redirects. cdp now keeps callbacks per tab instead of one global handler.

Other desktop changes: minimal CSP fallback on app-origin documents missing a policy; local-only crashReporter and crash-dump path on renderer death; MCP OAuth in-app popups require https (not plain http); loopback HTTP allowance uses isLoopbackHostname; manual update check surfaces errors; README clarifies security-scoped bookmarks on non-sandbox builds.

Minor: shared sleep / getErrorMessage, api-validation route baseline 965, launcher stream-event cast comment.

Reviewed by Cursor Bugbot for commit aa6e587. Configure here.

@greptile-apps

greptile-apps Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds shared SSRF protection and hardens the desktop browser agent. The main changes are:

  • Shared IP and hostname security helpers in @sim/security/ssrf.
  • DNS-based, fail-closed checks for browser-agent document navigation.
  • Up-front URL checks for browser navigation tools.
  • CSP fallback and local crash-dump reporting for the desktop app.
  • Per-tab CDP callbacks and cleanup of duplicated security helpers.

Confidence Score: 5/5

This looks safe to merge.

  • The DNS deadline closes the stalled-request path while preserving fail-closed behavior.
  • Timer cleanup and late lookup rejection handling prevent lifecycle leaks.
  • The tests cover a resolver that never settles.
  • No blocking issue was found in the updated code.

Important Files Changed

Filename Overview
apps/desktop/src/main/browser-agent/url-guard.ts Adds fail-closed URL validation with a bounded DNS lookup and safe timer cleanup.
apps/desktop/src/main/browser-agent/url-guard.test.ts Covers private destinations, resolver failures, and the stalled-resolver deadline.
apps/desktop/src/main/browser-agent/session.ts Applies the URL guard at the browser partition's request boundary.
packages/security/src/ssrf.ts Centralizes IP literal, loopback, private-address, and hostname classification.

Reviews (4): Last reviewed commit: "fix(desktop): swallow late DNS rejection..." | Re-trigger Greptile

Comment thread apps/desktop/src/main/browser-agent/session.ts
Comment thread apps/desktop/src/main/browser-agent/url-guard.ts Outdated
Comment thread apps/desktop/src/main/browser-agent/url-guard.ts
- domain-check: drop the redundant isIpLiteral guard (isLoopbackIp already
  validates and returns false for non-literals).
- session.ts: use shared getErrorMessage instead of the local error ternary
  (the file already imports it).
- tray.ts: use shared sleep() instead of a hand-rolled setTimeout promise.
- updater.ts: distinguish the synchronous-throw log from the async-rejection
  log on the manual update check.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

Ran a comprehensive 3-angle audit (regression, consolidation/reuse, cleanliness) over the full diff — verdict GO, no must-fix. Every SSRF seam verified correct (fails closed, single onBeforeRequest listener on the agent partition, no dev work clobbered), migration behavior equivalent-or-stronger, all gates green. Applied the safe cleanup findings: dropped a redundant isIpLiteral guard in domain-check, adopted shared getErrorMessage/sleep in session.ts/tray.ts, and differentiated an updater log line. Also fixed a pre-existing dev Biome failure (install-local.ts formatting) so CI's whole-package lint passes. Verified: @sim/security/ssrf 106, desktop 228, sim security+mcp 462, type-checks + biome + api-validation all green.

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 6303465. Configure here.

@waleedlatif1
waleedlatif1 changed the base branch from dev to staging-v4 July 20, 2026 23:54
- url-guard: bound the SSRF dns.lookup with a 5s deadline (fails closed on
  timeout) so a slow/hung resolver can't suspend the check and the
  onBeforeRequest callback indefinitely (Greptile P2); + test.
- Finish the reuse consolidation the earlier pass missed: session.ts second
  error ternary → getErrorMessage; the bracket-strip idiom → unwrapIpv6Brackets
  in input-validation.ts, input-validation.server.ts (×2), onepassword/utils.ts
  (fixes the check:utils banned-pattern CI failure).
- driver: document why the tool-level checkAgentUrl coexists with the
  onBeforeRequest enforcement seam (clean model error; loadURL rejection is
  swallowed).
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/desktop/src/main/browser-agent/url-guard.ts
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit aa6e587. Configure here.

@waleedlatif1
waleedlatif1 merged commit 8ae3d60 into staging-v4 Jul 21, 2026
5 checks passed
@waleedlatif1
waleedlatif1 deleted the feat/desktop-ssrf-rehome branch July 21, 2026 00:11
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