fix(security): isolate previews from native APIs - #2390
Conversation
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. 🧰 Additional context used📚 Code guidelines (2)No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (14)
Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review. 📝 WalkthroughWalkthroughThe change validates workspace preview URLs, gates preload APIs by renderer document, and authorizes native IPC senders before route dispatch or clipboard access. It also enables web security for the floating button and chat windows. ChangesRenderer Isolation
Priority: ➖ Normal Estimated code review effort: 3 (Moderate) | ~25 minutes Change: Bug fix Sequence Diagram(s)sequenceDiagram
participant Renderer
participant DeepchatRoutes
participant RendererIpcAuthorizer
participant RouteDispatcher
Renderer->>DeepchatRoutes: Invoke route
DeepchatRoutes->>RendererIpcAuthorizer: Authorize event and route
RendererIpcAuthorizer-->>DeepchatRoutes: Allow or reject
DeepchatRoutes->>RouteDispatcher: Dispatch authorized route
Suggested reviewers: Merge Risk: ⚪ Minimal · up to The preview and native-API isolation changes are mergeable after normal checks; no actionable issue remains established. Security Architecture ReviewSecurity architecture risk: 🔵 Low · up to The changes tighten preview isolation and native API access. No introduced privilege bypass was identified in the reviewed paths, but incomplete downstream coverage and unverified recovery behavior leave limited residual uncertainty. Retained concerns Security review detailsSecurity Blast Radius
Security Findings and Attack Paths
Trust Boundaries and Controls
Resilience and Maintainability Implications
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Full details: Docstring CoverageExplanation Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 10 functions across 11 files. (3 skipped: 3 unsupported.)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
zerob13
left a comment
There was a problem hiding this comment.
Review: fix(security): isolate previews from native APIs
Verdict: Approve. This is a well-constructed security hardening with genuine defense in depth — the preload gates exposure at the renderer side, the main process independently re-validates the live sender on every route dispatch and clipboard call, and the preview surface is restricted at the URL level. The one breaking change (custom previews that used native APIs or relied on disabled web security) is honestly declared in the PR body's compatibility section. Verified on this head: 26/26 rendererIpcSecurity tests, 14/14 WorkspacePreviewPane tests, typecheck clean in PR-touched files (the single residual error is the known pre-existing acp-extension-core drift in an untouched file).
The security model, verified layer by layer
- Preload fails closed (
src/preload/index.ts):contextBridge.exposeInMainWorldnow runs only when context isolation is on, the frame is the main frame, AND the document is exactly themainorsettingsentry document. The old non-isolated fallback (window.api = api) is deleted outright — without context isolation you now get no API rather than an unprotected one. - Main-side re-validation on every call (
src/main/app/rendererIpcSecurity.ts): sender not destroyed,senderFrame === sender.mainFrame(child iframes can't invoke),senderFrame.url === sender.getURL()(a mid-flight navigation race can't carry a stale document's privileges), and the document must exactly match one of the three entry URLs — exacthrefmatching, not prefix, so arbitrary HTML files in the renderer directory (i.e., preview artifacts on the same origin) never qualify. Splash gets exactly one route (config.getLanguage); plugin settings windows get the four plugin routes, which the route layer already guards with per-plugin ownership checks. - Document matching is precise (
src/shared/rendererDocument.ts): entries resolved from the renderer directory URL, dev-server URLs validated (http:/https:only, credentials rejected), hash/search stripped for normalization. Same matcher is shared between preload and main process — one definition of "app document", no drift. - Preview frames are origin-locked (
WorkspacePreviewPane.vue): the iframe URL must beworkspace-preview:with a hostname and no credentials or port; anything else renders no frame at all, with an inline comment warning against ever substitutingsrcdoc/blob URLs (which would re-couple the preview to the app origin). - Floating windows restore Chromium web security: both
FloatingButtonWindowandFloatingChatWindowflipwebSecurity: false → true, so previews there no longer run with same-origin/CORS checks disabled. - Splash language lookup survives: the splash screen has its own minimal preload (
splash-preload.ts→window.deepchatSplash), so it never needed the broad bridge — the main-side whitelist covers its singleconfig.getLanguageinvocation.
Test coverage
- 26 unit tests on the authorizer cover the full matrix: top-level vs child frame, live vs navigated URL, each document kind, splash's single allowed route, plugin windows' route set, and the rejection paths.
- 14 renderer tests cover the preview URL validator (protocol, hostname, credentials, port, malformed input → no frame).
- The new e2e spec (3 scenarios) does real end-to-end verification — a workspace HTML file with ES modules and relative fetches keeps working, while the app bridge stays inaccessible from the preview origin. Not runnable in this review environment (needs a build); the code reads correct.
Minor (non-blocking)
- The compatibility note about custom previews breaking is accurate but terse — if any users had built previews that call
window.deepchat, a one-line migration hint in the release notes ("previews are sandboxed to their own origin; use the documented preview APIs") would preempt support questions. documentUrlstrips hash and search before matching — correct for normalization; just noting thatindex.html?anythingintentionally still counts as the app document (it is the app document).
Detailed references
src/main/app/rendererIpcSecurity.ts— the live-sender authorizer.src/shared/rendererDocument.ts— the shared exact-entry matcher.src/preload/index.ts:88-104— the fail-closed exposure gate.src/renderer/src/components/sidepanel/viewer/WorkspacePreviewPane.vue:129-145— theworkspace-preview:URL validation.docs/issues/html-preview-isolation/spec.md— the issue record.
Summary
Harden preview isolation and native IPC access following the retirement of legacy HTML artifacts. This PR addresses only the HTML preview security issue.
workspace-preview:URLs and keep Chromium web security enabled in floating windows.Compatibility
HTML previews retain scripts, ES modules, and relative resource loading on their own origin. No layout changes, dependencies, settings, or data migrations are introduced.
Custom previews that access the parent app's native APIs, or rely on disabled same-origin/CORS checks in floating windows, will no longer work. MCP approval semantics and unrelated security issues are unchanged.
Preview behavior
Validation
Summary by CodeRabbit