Keep desktop sidecar control local after H5 auth

The H5 LAN release made the desktop sidecar bind broadly and require auth for non-localhost hosts. The Tauri WebView still talks to the loopback control URL without an H5 bearer token, so startup requests were rejected before the app could load.

Constraint: Desktop WebView control requests must stay tokenless for the local sidecar.
Rejected: Reuse H5 token bootstrap for Tauri | would add remote-access state to the native app startup path.
Confidence: high
Scope-risk: narrow
Directive: Tauri WebView bypass is limited to loopback control hosts; do not extend it to LAN or public hosts without re-reviewing H5 auth boundaries.
Tested: bun test src/server/__tests__/h5-access-auth.test.ts
Tested: bun run check:server
This commit is contained in:
程序员阿江(Relakkes) 2026-05-11 13:44:35 +08:00
parent d1211e4dca
commit 4501ef2adb
2 changed files with 35 additions and 0 deletions

View File

@ -176,12 +176,31 @@ describe('remote H5 auth and CORS integration', () => {
})
})
test('allows the Tauri desktop WebView origin to control the local sidecar without H5 token', async () => {
const response = await fetch(`${baseUrl}/api/status`, {
headers: {
Origin: 'http://tauri.localhost',
},
})
expect(response.status).toBe(200)
expect(response.headers.get('Access-Control-Allow-Origin')).toBe('http://tauri.localhost')
await expect(response.json()).resolves.toMatchObject({
status: 'ok',
})
})
test('only lets localhost WebUI origin bypass H5 auth for loopback or private server hosts', () => {
expect(canBypassRemoteAuthForLocalBrowser('http://127.0.0.1:5179', '127.0.0.1')).toBe(true)
expect(canBypassRemoteAuthForLocalBrowser('http://localhost:5179', '192.168.0.102')).toBe(true)
expect(canBypassRemoteAuthForLocalBrowser('http://tauri.localhost', '127.0.0.1')).toBe(true)
expect(canBypassRemoteAuthForLocalBrowser('tauri://localhost', '127.0.0.1')).toBe(true)
expect(canBypassRemoteAuthForLocalBrowser('asset://localhost', '127.0.0.1')).toBe(true)
expect(canBypassRemoteAuthForLocalBrowser('http://localhost:5179', '10.0.0.5')).toBe(true)
expect(canBypassRemoteAuthForLocalBrowser('http://localhost:5179', '172.20.1.8')).toBe(true)
expect(canBypassRemoteAuthForLocalBrowser('http://localhost:5179', 'public.example.com')).toBe(false)
expect(canBypassRemoteAuthForLocalBrowser('http://tauri.localhost', 'public.example.com')).toBe(false)
expect(canBypassRemoteAuthForLocalBrowser('http://tauri.localhost', '192.168.0.102')).toBe(false)
expect(canBypassRemoteAuthForLocalBrowser('http://192.168.0.50:5179', '192.168.0.102')).toBe(false)
})

View File

@ -64,6 +64,18 @@ function isLocalBrowserOrigin(origin: string | null): boolean {
}
}
function isTauriWebViewOrigin(origin: string | null): boolean {
if (!origin) return false
try {
const url = new URL(origin)
return url.hostname === 'tauri.localhost' ||
((url.protocol === 'tauri:' || url.protocol === 'asset:') && url.hostname === 'localhost')
} catch {
return false
}
}
function isPrivateNetworkHost(host: string): boolean {
const normalized = host.trim().replace(/^\[/, '').replace(/\]$/, '').toLowerCase()
@ -93,6 +105,10 @@ function isPrivateNetworkHost(host: string): boolean {
}
export function canBypassRemoteAuthForLocalBrowser(origin: string | null, requestHost: string): boolean {
if (isTauriWebViewOrigin(origin)) {
return isLocalServerHost(requestHost)
}
return isLocalBrowserOrigin(origin) &&
(isLocalServerHost(requestHost) || isPrivateNetworkHost(requestHost))
}