From 25e53f4b0ca37cad5e17ef3fb94fc1acd8ebfa22 Mon Sep 17 00:00:00 2001
From: Neil <4138956+nwparker@users.noreply.github.com>
Date: Mon, 19 Oct 2026 04:21:16 -0700
Subject: [PATCH] fix(security): put every trust decision on a host-owned token
surface (#30710)
* fix(security): put every trust dialog on a host-owned token surface
Plugin themes write CSS tokens on the document root, and dialogs portal to
body, so any trust surface without plugin-security-chrome inherits them; a
theme could hide what is being approved. The orca.yaml hook trust dialog,
notebook run confirmation, plugin install, remove and rollback dialogs were
missing it. DialogContent gains surface="security", which applies the
host-owned tokens, and a scrollable prop for the plugin dialogs' existing
viewport-height scrolling; every trust dialog uses the surface and a test
lists them. The style guide says when to use each.
* fix(security): keep inline trust decisions on the host-owned surface
The agent command approval card (Allow/Deny) and the browser certificate
override sit outside a dialog, so plugin theme tokens still reached them.
A SecuritySurface primitive puts its child on the same host-owned token set
as DialogContent surface="security"; both use it, and the security surface
test now lists inline trust surfaces as well as dialogs.
---
docs/STYLEGUIDE.md | 2 +
.../navigate/browser-load-failure-overlay.tsx | 202 ++++++++-------
.../src/components/editor/IpynbViewer.tsx | 2 +-
.../native-chat/NativeChatApprovalCard.tsx | 244 +++++++++---------
.../settings/PluginConsentDialog.tsx | 4 +-
.../settings/PluginInstallDialog.tsx | 2 +-
.../PluginMarketplacePreviewDialog.tsx | 2 +-
.../PluginMarketplaceSourceDialog.tsx | 4 +-
.../settings/PluginRemoveDialog.tsx | 1 +
.../settings/PluginRollbackDialog.tsx | 1 +
.../sidebar/OrcaYamlTrustDialog.tsx | 2 +-
src/renderer/src/components/ui/dialog.tsx | 9 +-
.../components/ui/security-surface.test.tsx | 61 +++++
.../src/components/ui/security-surface.tsx | 15 ++
14 files changed, 327 insertions(+), 224 deletions(-)
create mode 100644 src/renderer/src/components/ui/security-surface.test.tsx
create mode 100644 src/renderer/src/components/ui/security-surface.tsx
diff --git a/docs/STYLEGUIDE.md b/docs/STYLEGUIDE.md
index e236389c7d8..9b9a6f5a62f 100644
--- a/docs/STYLEGUIDE.md
+++ b/docs/STYLEGUIDE.md
@@ -156,6 +156,8 @@ Sizes: `default` (36px), `sm` (32px), `xs` (24px), `lg` (40px), plus `icon`, `ic
Browse `src/renderer/src/components/ui/` for the full list. Most wrap a Radix UI primitive — exceptions are `command` (wraps `cmdk`), `sonner` (wraps `sonner`), and the visual-only wrappers (`badge`, `button-group`, `card`, `input`) which apply tokens and Tailwind utilities directly. Never reimplement headless behavior; extend the existing wrapper.
+Use `DialogContent surface="security"` for every dialog that asks the user to trust, approve, run or remove code, including plugin provenance and approval, so plugin themes cannot change the trust surface's tokens. Use its `scrollable` prop when the whole dialog scrolls. Wrap a trust decision outside a dialog, such as an agent approval card or a certificate override, in `SecuritySurface`.
+
### Picking the right primitive
When a control has multiple plausible primitives, use this fork:
diff --git a/src/renderer/src/components/browser-pane/navigate/browser-load-failure-overlay.tsx b/src/renderer/src/components/browser-pane/navigate/browser-load-failure-overlay.tsx
index eb79eed7d6a..d8163f21a61 100644
--- a/src/renderer/src/components/browser-pane/navigate/browser-load-failure-overlay.tsx
+++ b/src/renderer/src/components/browser-pane/navigate/browser-load-failure-overlay.tsx
@@ -1,3 +1,4 @@
+import { SecuritySurface } from '@/components/ui/security-surface'
import { useEffect, useRef, useState } from 'react'
import { Copy, ExternalLink, Globe, Loader2, RefreshCw, ShieldAlert } from 'lucide-react'
@@ -261,107 +262,114 @@ export function BrowserLoadFailureOverlay({
) : null
return (
-
- {translate(
- 'browser.loadFailure.sshRoutedHint',
- "This page browses through the workspace's SSH host — the host may be disconnected or unable to reach this site."
- )}
- {onRecheckSshRoute ? (
+ {certificateError && meta.isLocalhostLike ? (
+
+ {translate(
+ 'browser.loadFailure.trustedCertificateGuidance',
+ 'For local development, use a trusted local certificate when possible.'
+ )}
+
+ ) : null}
+ {recoveryHint ? (
+
{recoveryHint}
+ ) : null}
+ {sshRoutedHint ? (
+
+ {translate(
+ 'browser.loadFailure.sshRoutedHint',
+ "This page browses through the workspace's SSH host — the host may be disconnected or unable to reach this site."
+ )}
+ {onRecheckSshRoute ? (
+ <>
+ {' '}
+
+ >
+ ) : null}
+