diff --git a/src/app/[[...panel]]/page.tsx b/src/app/[[...panel]]/page.tsx
index 308a52d..16e7f23 100644
--- a/src/app/[[...panel]]/page.tsx
+++ b/src/app/[[...panel]]/page.tsx
@@ -38,6 +38,7 @@ import { ExecApprovalPanel } from '@/components/panels/exec-approval-panel'
import { ChatPagePanel } from '@/components/panels/chat-page-panel'
import { ChatPanel } from '@/components/chat/chat-panel'
import { getPluginPanel } from '@/lib/plugins'
+import { shouldRedirectDashboardToHttps } from '@/lib/browser-security'
import { ErrorBoundary } from '@/components/ErrorBoundary'
import { LocalModeBanner } from '@/components/layout/local-mode-banner'
import { UpdateBanner } from '@/components/layout/update-banner'
@@ -65,10 +66,6 @@ function renderPluginPanel(panelId: string) {
return pluginPanel ? createElement(pluginPanel) :
}
-function isLocalHost(hostname: string): boolean {
- return hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1'
-}
-
export default function Home() {
const router = useRouter()
const { connect } = useWebSocket()
@@ -148,9 +145,11 @@ export default function Home() {
useEffect(() => {
setIsClient(true)
- // OpenClaw control-ui device identity requires a secure browser context.
- // Redirect remote HTTP sessions to HTTPS automatically to avoid handshake failures.
- if (window.location.protocol === 'http:' && !isLocalHost(window.location.hostname)) {
+ if (shouldRedirectDashboardToHttps({
+ protocol: window.location.protocol,
+ hostname: window.location.hostname,
+ forceHttps: process.env.NEXT_PUBLIC_FORCE_HTTPS === '1',
+ })) {
const secureUrl = new URL(window.location.href)
secureUrl.protocol = 'https:'
window.location.replace(secureUrl.toString())
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index b585218..ed9ee18 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,5 +1,6 @@
import type { Metadata, Viewport } from 'next'
import { Inter, JetBrains_Mono } from 'next/font/google'
+import { headers } from 'next/headers'
import { ThemeProvider } from 'next-themes'
import { THEME_IDS } from '@/lib/themes'
import { ThemeBackground } from '@/components/ui/theme-background'
@@ -78,17 +79,20 @@ export const metadata: Metadata = {
},
}
-export default function RootLayout({
+export default async function RootLayout({
children,
}: {
children: React.ReactNode
}) {
+ const nonce = (await headers()).get('x-nonce') || undefined
+
return (
{/* Blocking script to set 'dark' class before first paint, preventing FOUC.
Content is a static string literal — no user input, no XSS vector. */}