From f2e9fe49c80aeb2acf56ca2f22ad2099f322caf4 Mon Sep 17 00:00:00 2001 From: Freddy Diaz <45578633+FreddyJD@users.noreply.github.com> Date: Sat, 12 Sep 2026 16:31:10 -0400 Subject: [PATCH 1/5] feat(settings): add Windows kernel tools integration Add an explicit, pinned install flow for the external kernel driver and bridge, including UAC-scoped service management, test-signing controls, separate agent-access confirmation, typed IPC APIs, localized settings UI, and smoke coverage. Co-authored-by: Roxy <299891354+roxy-commits@users.noreply.github.com> --- package.json | 3 +- src/main/index.ts | 2 + src/main/ipc/kernel.ts | 28 + src/main/services/kernel.ts | 490 ++++++++++++++++++ src/preload/index.ts | 8 + .../src/components/KernelToolsSection.tsx | 270 ++++++++++ src/renderer/src/locales/ar.json | 31 ++ src/renderer/src/locales/de.json | 31 ++ src/renderer/src/locales/default.json | 31 ++ src/renderer/src/locales/es.json | 31 ++ src/renderer/src/locales/fr.json | 31 ++ src/renderer/src/locales/hi.json | 31 ++ src/renderer/src/locales/ja.json | 31 ++ src/renderer/src/locales/pt.json | 31 ++ src/renderer/src/locales/ru.json | 31 ++ src/renderer/src/locales/zh.json | 31 ++ src/renderer/src/routes/Settings.tsx | 3 + src/shared/api.ts | 9 + src/shared/ipc.ts | 9 +- src/shared/kernel.ts | 16 + test/kernel.ts | 47 ++ 21 files changed, 1193 insertions(+), 2 deletions(-) create mode 100644 src/main/ipc/kernel.ts create mode 100644 src/main/services/kernel.ts create mode 100644 src/renderer/src/components/KernelToolsSection.tsx create mode 100644 src/shared/kernel.ts create mode 100644 test/kernel.ts diff --git a/package.json b/package.json index d52fc1f..d0b4d65 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "icons:providers": "node script/copy-provider-icons.mjs", "smoke:shared": "esbuild test/shared.ts --bundle --platform=node --format=cjs --packages=external --outfile=test/.out/shared.cjs && node test/.out/shared.cjs", "smoke:app": "esbuild test/smoke.ts --bundle --platform=node --format=cjs --packages=external --outfile=test/.out/smoke.cjs && electron test/.out/smoke.cjs", - "smoke": "npm run smoke:shared && npm run smoke:i18n && npm run smoke:store && npm run smoke:multirepo && npm run smoke:live && npm run smoke:cookies && npm run smoke:app", + "smoke": "npm run smoke:shared && npm run smoke:i18n && npm run smoke:store && npm run smoke:multirepo && npm run smoke:live && npm run smoke:cookies && npm run smoke:kernel && npm run smoke:app", "smoke:multirepo": "esbuild test/multirepo.ts --bundle --platform=node --format=cjs --packages=external --outfile=test/.out/multirepo.cjs && node test/.out/multirepo.cjs", "smoke:live": "esbuild test/live-multirepo.ts --bundle --platform=node --format=cjs --packages=external --outfile=test/.out/live.cjs && electron test/.out/live.cjs", "smoke:cliproxy": "esbuild test/cliproxy.ts --bundle --platform=node --format=cjs --packages=external --outfile=test/.out/cliproxy.cjs && electron test/.out/cliproxy.cjs", @@ -47,6 +47,7 @@ "smoke:store": "node test/store-guard.mjs", "smoke:cookies": "esbuild test/cookies.ts --bundle --platform=node --format=cjs --packages=external --outfile=test/.out/cookies.cjs && electron test/.out/cookies.cjs", "smoke:i18n": "esbuild test/i18n.ts --bundle --platform=node --format=cjs --outfile=test/.out/i18n.cjs && node test/.out/i18n.cjs", + "smoke:kernel": "esbuild test/kernel.ts --bundle --platform=node --format=cjs --outfile=test/.out/kernel.cjs && node test/.out/kernel.cjs", "i18n:translate": "node script/i18n-translate.mjs", "canvas": "vite --config test/canvas/vite.config.mjs", "smoke:canvas": "electron test/canvas/smoke.cjs", diff --git a/src/main/index.ts b/src/main/index.ts index 06d0ee2..6a102cb 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -4,6 +4,7 @@ import { electronApp, optimizer, is } from '@electron-toolkit/utils' import icon from '../../resources/icon.png?asset' import macDockIcon from '../../resources/icon-mac.png?asset' import { registerIpc } from './ipc' +import { registerKernelIpc } from './ipc/kernel' import { getDb } from './db/database' import { startLoopScheduler } from './services/loops' import { listModels } from './services/models' @@ -121,6 +122,7 @@ app.whenReady().then(() => { // Open the database (runs migrations) and wire up IPC before the first window. getDb() registerIpc() + registerKernelIpc() // Anonymous usage tracking (opt-out in Settings). Deliberately after the DB // and IPC are up so nothing here can delay the first window, and it owns its // own storage - a failure in it can't touch either. diff --git a/src/main/ipc/kernel.ts b/src/main/ipc/kernel.ts new file mode 100644 index 0000000..1bab9d2 --- /dev/null +++ b/src/main/ipc/kernel.ts @@ -0,0 +1,28 @@ +import { ipcMain } from 'electron' +import { CHANNELS } from '../../shared/ipc' +import { + getKernelStatus, + installKernelTools, + setKernelAgentAccess, + startKernelDriver, + toggleTestSigning, + uninstallKernelTools +} from '../services/kernel' + +export function registerKernelIpc(): void { + ipcMain.handle(CHANNELS.kernelStatus, () => getKernelStatus()) + ipcMain.handle(CHANNELS.kernelInstall, () => installKernelTools()) + ipcMain.handle(CHANNELS.kernelStart, () => startKernelDriver()) + ipcMain.handle(CHANNELS.kernelSetAgentAccess, (_event, enable: boolean) => + setKernelAgentAccess(enable === true) + ) + ipcMain.handle(CHANNELS.kernelUninstall, (_event, disableSigning: boolean) => { + if (typeof disableSigning !== 'boolean') + throw new TypeError('disableSigning must be a boolean.') + return uninstallKernelTools(disableSigning) + }) + ipcMain.handle(CHANNELS.kernelToggleTestSigning, (_event, enable: boolean) => { + if (typeof enable !== 'boolean') throw new TypeError('enable must be a boolean.') + return toggleTestSigning(enable) + }) +} diff --git a/src/main/services/kernel.ts b/src/main/services/kernel.ts new file mode 100644 index 0000000..dabf8c5 --- /dev/null +++ b/src/main/services/kernel.ts @@ -0,0 +1,490 @@ +import { createHash, randomUUID } from 'node:crypto' +import { execFile } from 'node:child_process' +import { existsSync, mkdirSync, readFileSync } from 'node:fs' +import { rm } from 'node:fs/promises' +import path from 'node:path' +import { promisify } from 'node:util' +import type { KernelInstallResult, KernelStatus } from '../../shared/kernel' +import { deleteMcpServer, listMcpServers, upsertMcpServer } from '../db/repo' +import { disposeConnection } from './mcp' + +const execFileAsync = promisify(execFile) + +const REPOSITORY_URL = 'https://github.com/roxy-gg/kernel-tools.git' +// Review and update this pin deliberately when the external driver changes. +const REPOSITORY_REVISION = '2f51a1d5981a553d7642db9c81d41a002f3c44e4' +const SERVICE_NAME = 'AIBridge' +const MCP_SERVER_ID = 'roxy-kernel-tools' +const MCP_SERVER_IDS = [MCP_SERVER_ID, 'kernel'] as const + +const INSTALL_DIR = path.join( + process.env.LOCALAPPDATA ?? path.join(process.env.USERPROFILE ?? '', 'AppData', 'Local'), + 'roxy', + 'kernel-tools' +) +const REPOSITORY_DIR = path.join(INSTALL_DIR, 'repo') +const OUTPUT_DIR = path.join(REPOSITORY_DIR, 'out') +const BUILT_DRIVER_PATH = path.join(OUTPUT_DIR, 'aibridge.sys') +const BUILT_BRIDGE_PATH = path.join(OUTPUT_DIR, 'roxy-kernel-bridge.exe') +const PRIVILEGED_INSTALL_DIR = path.join( + process.env.ProgramFiles ?? 'C:\\Program Files', + 'Roxy', + 'KernelTools' +) +const DRIVER_PATH = path.join(PRIVILEGED_INSTALL_DIR, 'aibridge.sys') +const BRIDGE_PATH = path.join(PRIVILEGED_INSTALL_DIR, 'roxy-kernel-bridge.exe') + +function isWindows(): boolean { + return process.platform === 'win32' +} + +function errorMessage(error: unknown): string { + if (!(error instanceof Error)) return String(error) + const output = error as Error & { stdout?: string; stderr?: string } + const details = [output.stderr, output.stdout] + .map((value) => value?.trim()) + .filter(Boolean) + .join('\n') + return details ? `${error.message}\n${details}` : error.message +} + +function powershellLiteral(value: string): string { + return `'${value.replace(/'/g, "''")}'` +} + +function sha256(file: string): string { + return createHash('sha256').update(readFileSync(file)).digest('hex') +} + +function systemExecutable(name: string): string { + const windowsDirectory = process.env.SystemRoot ?? 'C:\\Windows' + return path.join(windowsDirectory, 'System32', name) +} + +async function run( + file: string, + args: string[], + timeout = 30_000 +): Promise<{ stdout: string; stderr: string }> { + return execFileAsync(file, args, { timeout, windowsHide: true }) +} + +/** Run one executable through the standard Windows UAC consent prompt. */ +async function runElevated(file: string, args: string[], timeout = 120_000): Promise { + const argumentList = args.map(powershellLiteral).join(', ') + const resultPath = path.join(INSTALL_DIR, `elevated-${randomUUID()}.txt`) + const command = [ + `$resultPath = ${powershellLiteral(resultPath)}`, + 'try {', + ` $process = Start-Process -FilePath ${powershellLiteral(file)} -ArgumentList @(${argumentList}) -Verb RunAs -Wait -PassThru`, + " $result = if ($null -eq $process) { '1`nThe elevated process did not start.' } elseif ($process.ExitCode -eq 0) { '0' } else { \"$($process.ExitCode)`nThe elevated command failed.\" }", + '} catch { $result = "1`n$($_.Exception.Message)" }', + 'Set-Content -LiteralPath $resultPath -Value $result -Encoding UTF8' + ].join('; ') + + mkdirSync(INSTALL_DIR, { recursive: true }) + try { + await run( + systemExecutable('WindowsPowerShell\\v1.0\\powershell.exe'), + ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', command], + timeout + ) + const [exitCode, ...details] = readFileSync(resultPath, 'utf8') + .replace(/^\uFEFF/, '') + .trim() + .split(/\r?\n/) + if (exitCode !== '0') { + throw new Error(details.join('\n') || `Elevated command exited with code ${exitCode}.`) + } + } finally { + await rm(resultPath, { force: true }) + } +} +export async function checkTestSigning(): Promise { + if (!isWindows()) return false + try { + const { stdout } = await run( + systemExecutable('reg.exe'), + ['query', 'HKLM\\SYSTEM\\CurrentControlSet\\Control', '/v', 'SystemStartOptions'], + 5_000 + ) + return /\bTESTSIGNING\b/i.test(stdout) + } catch { + return false + } +} + +async function setTestSigning(enable: boolean): Promise { + await runElevated(systemExecutable('bcdedit.exe'), ['/set', 'testsigning', enable ? 'on' : 'off']) +} + +function installedDriverScript( + expectedDriverHash: string, + expectedBridgeHash: string, + startDriver: boolean +): string { + return [ + "$ErrorActionPreference = 'Stop'", + `$installDirectory = ${powershellLiteral(PRIVILEGED_INSTALL_DIR)}`, + `$driverSource = ${powershellLiteral(BUILT_DRIVER_PATH)}`, + `$driverTarget = ${powershellLiteral(DRIVER_PATH)}`, + `$bridgeSource = ${powershellLiteral(BUILT_BRIDGE_PATH)}`, + `$bridgeTarget = ${powershellLiteral(BRIDGE_PATH)}`, + `$expectedDriverHash = ${powershellLiteral(expectedDriverHash)}`, + `$expectedBridgeHash = ${powershellLiteral(expectedBridgeHash)}`, + "if ((Get-FileHash -Algorithm SHA256 $driverSource).Hash.ToLowerInvariant() -ne $expectedDriverHash) { throw 'Driver hash mismatch.' }", + "if ((Get-FileHash -Algorithm SHA256 $bridgeSource).Hash.ToLowerInvariant() -ne $expectedBridgeHash) { throw 'Bridge hash mismatch.' }", + `$service = Get-Service -Name ${powershellLiteral(SERVICE_NAME)} -ErrorAction SilentlyContinue`, + 'if ($service) {', + ` & sc.exe stop ${SERVICE_NAME} | Out-Null`, + ' Start-Sleep -Seconds 1', + ` & sc.exe delete ${SERVICE_NAME} | Out-Null`, + ' if ($LASTEXITCODE -ne 0 -and $LASTEXITCODE -ne 1060) { exit $LASTEXITCODE }', + ' Start-Sleep -Seconds 1', + '}', + 'New-Item -ItemType Directory -Force -Path $installDirectory | Out-Null', + 'Copy-Item -Force $driverSource $driverTarget', + 'Copy-Item -Force $bridgeSource $bridgeTarget', + "if ((Get-FileHash -Algorithm SHA256 $driverTarget).Hash.ToLowerInvariant() -ne $expectedDriverHash) { throw 'Installed driver hash mismatch.' }", + "if ((Get-FileHash -Algorithm SHA256 $bridgeTarget).Hash.ToLowerInvariant() -ne $expectedBridgeHash) { throw 'Installed bridge hash mismatch.' }", + `& sc.exe create ${SERVICE_NAME} 'type=' 'kernel' 'start=' 'demand' 'binPath=' $driverTarget | Out-Null`, + 'if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }', + startDriver ? `& sc.exe start ${SERVICE_NAME} | Out-Null` : 'exit 0', + startDriver ? 'exit $LASTEXITCODE' : '' + ].join('; ') +} +function uninstallDriverScript(): string { + return [ + "$ErrorActionPreference = 'Stop'", + `$service = Get-Service -Name ${powershellLiteral(SERVICE_NAME)} -ErrorAction SilentlyContinue`, + 'if ($service) {', + ` & sc.exe stop ${SERVICE_NAME} | Out-Null`, + ' Start-Sleep -Seconds 1', + ` & sc.exe delete ${SERVICE_NAME} | Out-Null`, + ' if ($LASTEXITCODE -ne 0 -and $LASTEXITCODE -ne 1060) { exit $LASTEXITCODE }', + '}', + `Remove-Item -Force ${powershellLiteral(DRIVER_PATH)} -ErrorAction SilentlyContinue`, + `Remove-Item -Force ${powershellLiteral(BRIDGE_PATH)} -ErrorAction SilentlyContinue`, + `if (Test-Path ${powershellLiteral(DRIVER_PATH)}) { throw 'The installed driver file could not be removed.' }`, + `if (Test-Path ${powershellLiteral(BRIDGE_PATH)}) { throw 'The installed bridge file could not be removed.' }`, + `if (Test-Path ${powershellLiteral(PRIVILEGED_INSTALL_DIR)}) { Remove-Item -Force ${powershellLiteral(PRIVILEGED_INSTALL_DIR)} -ErrorAction SilentlyContinue }` + ].join('; ') +} + +async function getDriverServiceState(): Promise { + try { + const { stdout } = await run( + systemExecutable('WindowsPowerShell\\v1.0\\powershell.exe'), + [ + '-NoProfile', + '-NonInteractive', + '-Command', + `$service = Get-Service -Name ${powershellLiteral(SERVICE_NAME)} -ErrorAction SilentlyContinue; if ($service) { [int]$service.Status } else { 0 }` + ], + 5_000 + ) + if (stdout.trim() === '4') return 'running' + if (stdout.trim() === '1') return 'stopped' + if (stdout.trim() === '0') return 'not-installed' + return 'unknown' + } catch { + return 'unknown' + } +} + +async function isProcessElevated(): Promise { + try { + const { stdout } = await run( + systemExecutable('WindowsPowerShell\\v1.0\\powershell.exe'), + [ + '-NoProfile', + '-NonInteractive', + '-Command', + '([Security.Principal.WindowsPrincipal]::new([Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)' + ], + 5_000 + ) + return stdout.trim().toLowerCase() === 'true' + } catch { + return false + } +} +function hasKernelMcpServer(): boolean { + return listMcpServers().some( + (server) => + MCP_SERVER_IDS.includes(server.id as (typeof MCP_SERVER_IDS)[number]) && + server.enabled && + server.config.type === 'local' && + path.resolve(server.config.command[0] ?? '') === path.resolve(BRIDGE_PATH) + ) +} + +async function removeKernelMcpServers(): Promise { + for (const id of MCP_SERVER_IDS) { + const server = listMcpServers().find((record) => record.id === id) + if ( + server?.config.type === 'local' && + path.resolve(server.config.command[0] ?? '') === path.resolve(BRIDGE_PATH) + ) { + await disposeConnection(id) + deleteMcpServer(id) + } + } +} + +export async function setKernelAgentAccess(enable: boolean): Promise { + if (!isWindows()) return { ok: false, error: 'Kernel tools require Windows.', steps: [] } + const steps: string[] = [] + + try { + await removeKernelMcpServers() + if (!enable) { + steps.push('Agent access disabled.') + return { ok: true, steps } + } + + const status = await getKernelStatus() + if (!status.installed || !status.bridgePath || status.driverState !== 'running') { + throw new Error('Install and start Kernel Tools before enabling agent access.') + } + + if (!(await isProcessElevated())) { + throw new Error( + 'Restart Roxy as administrator before enabling agent access. Roxy must remain elevated while using these tools.' + ) + } + + const existing = listMcpServers().find((record) => record.id === MCP_SERVER_ID) + if (existing) throw new Error(`An MCP server named ${MCP_SERVER_ID} already exists.`) + + upsertMcpServer({ + id: MCP_SERVER_ID, + config: { type: 'local', command: [status.bridgePath], cwd: path.dirname(status.bridgePath) }, + enabled: true + }) + steps.push('Agent access enabled through the roxy-kernel-tools MCP server.') + return { ok: true, steps } + } catch (error) { + return { ok: false, error: errorMessage(error), steps } + } +} + +export async function getKernelStatus(): Promise { + if (!isWindows()) { + return { + isWindows: false, + installed: false, + hasArtifacts: false, + driverPath: null, + testSigning: false, + bridgePath: null, + mcpRegistered: false, + driverState: 'not-installed' + } + } + + const [testSigning, driverState] = await Promise.all([ + checkTestSigning(), + getDriverServiceState() + ]) + const driverPath = existsSync(DRIVER_PATH) ? DRIVER_PATH : null + const bridgePath = existsSync(BRIDGE_PATH) ? BRIDGE_PATH : null + + let mcpRegistered = false + try { + mcpRegistered = hasKernelMcpServer() + } catch { + // The database may still be opening during app startup. + } + + return { + isWindows: true, + installed: driverPath !== null && bridgePath !== null && driverState !== 'not-installed', + hasArtifacts: driverPath !== null || bridgePath !== null || driverState !== 'not-installed', + driverPath, + testSigning, + bridgePath, + mcpRegistered, + driverState + } +} + +async function checkoutPinnedRepository(steps: string[]): Promise { + steps.push('Downloading the pinned kernel-tools source...') + await rm(REPOSITORY_DIR, { recursive: true, force: true }) + mkdirSync(INSTALL_DIR, { recursive: true }) + await run('git.exe', ['clone', '--no-checkout', REPOSITORY_URL, REPOSITORY_DIR], 120_000) + await run('git.exe', ['-C', REPOSITORY_DIR, 'checkout', '--detach', REPOSITORY_REVISION], 30_000) +} + +async function buildArtifacts(steps: string[]): Promise { + const powershell = systemExecutable('WindowsPowerShell\\v1.0\\powershell.exe') + + steps.push('Building the Windows kernel driver...') + await run( + powershell, + [ + '-NoProfile', + '-NonInteractive', + '-ExecutionPolicy', + 'Bypass', + '-File', + path.join(REPOSITORY_DIR, 'driver', 'build.ps1') + ], + 10 * 60_000 + ) + + steps.push('Building the MCP bridge...') + await run( + powershell, + [ + '-NoProfile', + '-NonInteractive', + '-ExecutionPolicy', + 'Bypass', + '-File', + path.join(REPOSITORY_DIR, 'bridge', 'build.ps1') + ], + 10 * 60_000 + ) + + if (!existsSync(BUILT_DRIVER_PATH) || !existsSync(BUILT_BRIDGE_PATH)) { + throw new Error('The external build completed without producing both required binaries.') + } +} + +export async function installKernelTools(): Promise { + if (!isWindows()) return { ok: false, error: 'Kernel tools require Windows.', steps: [] } + const steps: string[] = [] + let enabledTestSigning = false + + try { + await removeKernelMcpServers() + await checkoutPinnedRepository(steps) + await buildArtifacts(steps) + + const testSigningWasEnabled = await checkTestSigning() + if (!testSigningWasEnabled) { + steps.push('Requesting administrator approval to enable Windows test signing...') + await setTestSigning(true) + enabledTestSigning = true + steps.push('Test signing enabled. Restart Windows before starting the driver.') + } else { + steps.push('Windows test signing is already enabled.') + } + + steps.push('Requesting administrator approval to install the driver service...') + await runElevated(systemExecutable('WindowsPowerShell\\v1.0\\powershell.exe'), [ + '-NoProfile', + '-NonInteractive', + '-Command', + installedDriverScript( + sha256(BUILT_DRIVER_PATH), + sha256(BUILT_BRIDGE_PATH), + testSigningWasEnabled + ) + ]) + const driverState = await getDriverServiceState() + if (driverState === 'running') { + steps.push('Driver started. Agent access is not enabled automatically.') + return { ok: true, steps } + } + if (!testSigningWasEnabled && driverState === 'stopped') { + steps.push('Driver service installed. Restart Windows, then return here and start it.') + return { ok: true, steps } + } + throw new Error('The driver service was not installed successfully.') + } catch (error) { + if (enabledTestSigning) { + try { + await setTestSigning(false) + steps.push('Test signing was disabled again because installation failed.') + } catch { + steps.push( + 'Installation failed after enabling test signing; disable it manually if needed.' + ) + } + } + return { ok: false, error: errorMessage(error), steps } + } +} + +export async function startKernelDriver(): Promise { + if (!isWindows()) return { ok: false, error: 'Kernel tools require Windows.', steps: [] } + const steps: string[] = [] + + if (!existsSync(DRIVER_PATH) || !existsSync(BRIDGE_PATH)) { + return { ok: false, error: 'Install kernel tools before starting the driver.', steps } + } + + try { + steps.push('Requesting administrator approval to start the driver...') + await runElevated(systemExecutable('sc.exe'), ['start', SERVICE_NAME]) + if ((await getDriverServiceState()) !== 'running') { + throw new Error('The driver did not enter the running state.') + } + steps.push('Driver started. Agent access remains disabled until you enable it.') + return { ok: true, steps } + } catch (error) { + return { ok: false, error: errorMessage(error), steps } + } +} + +export async function toggleTestSigning(enable: boolean): Promise { + if (!isWindows()) return { ok: false, error: 'Kernel tools require Windows.', steps: [] } + const steps: string[] = [] + + try { + steps.push( + `Requesting administrator approval to ${enable ? 'enable' : 'disable'} test signing...` + ) + await setTestSigning(enable) + steps.push(`Test signing ${enable ? 'enabled' : 'disabled'}. Restart Windows to apply.`) + return { ok: true, steps } + } catch (error) { + return { ok: false, error: errorMessage(error), steps } + } +} + +export async function uninstallKernelTools(disableSigning: boolean): Promise { + if (!isWindows()) return { ok: false, error: 'Kernel tools require Windows.', steps: [] } + const steps: string[] = [] + + try { + await removeKernelMcpServers() + steps.push('Agent access disabled.') + + if ( + existsSync(DRIVER_PATH) || + existsSync(BRIDGE_PATH) || + (await getDriverServiceState()) !== 'not-installed' + ) { + steps.push('Requesting administrator approval to remove the driver service...') + await runElevated(systemExecutable('WindowsPowerShell\\v1.0\\powershell.exe'), [ + '-NoProfile', + '-NonInteractive', + '-Command', + uninstallDriverScript() + ]) + if ( + (await getDriverServiceState()) !== 'not-installed' || + existsSync(DRIVER_PATH) || + existsSync(BRIDGE_PATH) + ) { + throw new Error('The driver service could not be fully removed.') + } + steps.push('Driver service and bridge removed.') + } + + if (disableSigning) { + await setTestSigning(false) + steps.push('Test signing disabled. Restart Windows to apply.') + } + + await rm(INSTALL_DIR, { recursive: true, force: true }) + steps.push('Downloaded kernel-tools files removed.') + return { ok: true, steps } + } catch (error) { + return { ok: false, error: errorMessage(error), steps } + } +} diff --git a/src/preload/index.ts b/src/preload/index.ts index 9637d3f..cf36710 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -310,6 +310,14 @@ const roxy: RoxyApi = { ipcRenderer.on(CHANNELS.remoteDelta, handler) return () => ipcRenderer.removeListener(CHANNELS.remoteDelta, handler) } + }, + kernel: { + status: () => ipcRenderer.invoke(CHANNELS.kernelStatus), + install: () => ipcRenderer.invoke(CHANNELS.kernelInstall), + start: () => ipcRenderer.invoke(CHANNELS.kernelStart), + setAgentAccess: (enable) => ipcRenderer.invoke(CHANNELS.kernelSetAgentAccess, enable), + uninstall: (disableSigning) => ipcRenderer.invoke(CHANNELS.kernelUninstall, disableSigning), + toggleTestSigning: (enable) => ipcRenderer.invoke(CHANNELS.kernelToggleTestSigning, enable) } } diff --git a/src/renderer/src/components/KernelToolsSection.tsx b/src/renderer/src/components/KernelToolsSection.tsx new file mode 100644 index 0000000..e2f0f7f --- /dev/null +++ b/src/renderer/src/components/KernelToolsSection.tsx @@ -0,0 +1,270 @@ +import { useEffect, useState } from 'react' +import { CircleCheck, ShieldAlert } from 'lucide-react' +import { useTranslation } from 'react-i18next' +import type { KernelInstallResult, KernelStatus } from '@shared/kernel' +import { api } from '../lib/api' +import { Button } from './ui' + +type ConfirmAction = 'install' | 'uninstall' | 'disableTestSigning' | 'enableAgentAccess' +type KernelAction = ConfirmAction | 'refresh' | 'start' | 'disableAgentAccess' + +export function KernelToolsSection(): JSX.Element { + const { t } = useTranslation() + const [status, setStatus] = useState(null) + const [action, setAction] = useState(null) + const [confirming, setConfirming] = useState(null) + const [result, setResult] = useState(null) + + const refresh = async (): Promise => { + try { + setStatus(await api.kernel.status()) + } catch (error) { + setResult({ + ok: false, + error: error instanceof Error ? error.message : String(error), + steps: [] + }) + } + } + + useEffect(() => { + void refresh() + }, []) + + const run = async ( + nextAction: KernelAction, + operation: () => Promise + ): Promise => { + setAction(nextAction) + setConfirming(null) + setResult(null) + try { + setResult(await operation()) + await refresh() + } catch (error) { + setResult({ + ok: false, + error: error instanceof Error ? error.message : String(error), + steps: [] + }) + } finally { + setAction(null) + } + } + + const performConfirmedAction = (): void => { + if (confirming === 'install') void run('install', () => api.kernel.install()) + if (confirming === 'uninstall') void run('uninstall', () => api.kernel.uninstall(false)) + if (confirming === 'disableTestSigning') { + void run('disableTestSigning', () => api.kernel.toggleTestSigning(false)) + } + if (confirming === 'enableAgentAccess') { + void run('enableAgentAccess', () => api.kernel.setAgentAccess(true)) + } + } + + const busy = action !== null + const isWindows = status?.isWindows ?? true + const installed = status?.installed ?? false + const hasArtifacts = status?.hasArtifacts ?? false + const driverRunning = status?.driverState === 'running' + const agentAccess = status?.mcpRegistered ?? false + + return ( +
+

+ {t('settings.kernel.heading')} +

+
+
+
+
+
+

+ {t('settings.kernel.description')} +

+ + {status && isWindows && ( +
+ {installed && } + {status.testSigning && ( + + )} + {installed && status.driverState === 'running' && ( + + )} + {installed && status.driverState === 'stopped' && ( + + )} + {installed && status.driverState === 'unknown' && ( + + )} + {agentAccess && } +
+ )} + + {result && ( +
+ {result.error &&

{t('settings.kernel.failed', { error: result.error })}

} + {result.steps.length > 0 && ( +
    + {result.steps.map((step, index) => ( +
  • {step}
  • + ))} +
+ )} +
+ )} +
+ +
+ {!isWindows && ( + + {t('settings.kernel.windowsOnly')} + + )} + + {isWindows && !installed && !confirming && ( + + )} + + {isWindows && hasArtifacts && !confirming && ( +
+ {installed && !driverRunning && ( + + )} + {installed && !agentAccess && ( + + )} + {agentAccess && ( + + )} + +
+ )} + + {isWindows && status?.testSigning && !hasArtifacts && !confirming && ( + + )} + + {isWindows && confirming && ( +
+

+ {confirming === 'install' && t('settings.kernel.confirmInstallDescription')} + {confirming === 'uninstall' && t('settings.kernel.confirmUninstallDescription')} + {confirming === 'disableTestSigning' && + t('settings.kernel.confirmDisableTestSigningDescription')} + {confirming === 'enableAgentAccess' && + t('settings.kernel.confirmEnableAgentAccessDescription')} +

+
+ + +
+
+ )} + + +
+
+
+
+ ) +} + +function StatusLine({ + label, + tone = 'success' +}: { + label: string + tone?: 'success' | 'warning' +}): JSX.Element { + const Icon = tone === 'success' ? CircleCheck : ShieldAlert + return ( +

+

+ ) +} diff --git a/src/renderer/src/locales/ar.json b/src/renderer/src/locales/ar.json index 8412fdd..a2ce17c 100644 --- a/src/renderer/src/locales/ar.json +++ b/src/renderer/src/locales/ar.json @@ -387,6 +387,37 @@ "resetTitle": "إعادة تعيين كل شيء", "wiping": "جارٍ المسح…" }, + "kernel": { + "confirmDisableTestSigningDescription": "Turn off Windows test signing? This removes support for test-signed drivers after the next reboot.", + "confirmEnableAgentAccessDescription": "Allow Roxy sessions to call the installed kernel bridge tools? These tools run with elevated system access. Only enable this on a dedicated research machine or VM.", + "confirmInstallDescription": "This downloads and builds the external kernel-tools project, enables Windows test signing, and installs its driver. Administrator approval and a reboot are required; agent access is not enabled automatically.", + "confirmUninstallDescription": "This stops and removes the kernel driver and any related agent registration. Test signing remains enabled until you disable it separately.", + "description": "Install the separately maintained Windows driver and bridge for privileged security research. Nothing runs until you explicitly install it, and agent access is not enabled automatically.", + "disableAgentAccess": "Disable agent access", + "disableTestSigning": "Disable test signing", + "disablingAgentAccess": "Disabling...", + "disablingTestSigning": "Disabling...", + "enableAgentAccess": "Enable agent access", + "enablingAgentAccess": "Enabling...", + "failed": "The operation failed: {{error}}", + "heading": "Kernel tools", + "install": "Install kernel tools", + "installing": "Installing...", + "refresh": "Refresh", + "start": "Start driver", + "starting": "Starting...", + "statusDriverRunning": "Driver service running", + "statusDriverStopped": "Driver service stopped; a reboot may be required", + "statusDriverUnknown": "Driver service state could not be determined", + "statusInstalled": "Driver and bridge installed", + "statusMCPRegistered": "Agent access registered", + "statusNotInstalled": "Not installed", + "statusTestSigning": "Windows test signing enabled", + "title": "Windows kernel bridge", + "windowsOnly": "Kernel Tools are only available on Windows.", + "uninstall": "Uninstall", + "uninstalling": "Uninstalling..." + }, "language": { "description": "اللغة التي كُتبت بها واجهة Roxy. لا تُغير هذه اللغة طريقة تحدث الوكيل معك — اسأله بأي لغة تريدها في الدردشة.", "heading": "اللغة", diff --git a/src/renderer/src/locales/de.json b/src/renderer/src/locales/de.json index 01c5d46..8af7303 100644 --- a/src/renderer/src/locales/de.json +++ b/src/renderer/src/locales/de.json @@ -387,6 +387,37 @@ "resetTitle": "Alles zurücksetzen", "wiping": "Lösche…" }, + "kernel": { + "confirmDisableTestSigningDescription": "Turn off Windows test signing? This removes support for test-signed drivers after the next reboot.", + "confirmEnableAgentAccessDescription": "Allow Roxy sessions to call the installed kernel bridge tools? These tools run with elevated system access. Only enable this on a dedicated research machine or VM.", + "confirmInstallDescription": "This downloads and builds the external kernel-tools project, enables Windows test signing, and installs its driver. Administrator approval and a reboot are required; agent access is not enabled automatically.", + "confirmUninstallDescription": "This stops and removes the kernel driver and any related agent registration. Test signing remains enabled until you disable it separately.", + "description": "Install the separately maintained Windows driver and bridge for privileged security research. Nothing runs until you explicitly install it, and agent access is not enabled automatically.", + "disableAgentAccess": "Disable agent access", + "disableTestSigning": "Disable test signing", + "disablingAgentAccess": "Disabling...", + "disablingTestSigning": "Disabling...", + "enableAgentAccess": "Enable agent access", + "enablingAgentAccess": "Enabling...", + "failed": "The operation failed: {{error}}", + "heading": "Kernel tools", + "install": "Install kernel tools", + "installing": "Installing...", + "refresh": "Refresh", + "start": "Start driver", + "starting": "Starting...", + "statusDriverRunning": "Driver service running", + "statusDriverStopped": "Driver service stopped; a reboot may be required", + "statusDriverUnknown": "Driver service state could not be determined", + "statusInstalled": "Driver and bridge installed", + "statusMCPRegistered": "Agent access registered", + "statusNotInstalled": "Not installed", + "statusTestSigning": "Windows test signing enabled", + "title": "Windows kernel bridge", + "windowsOnly": "Kernel Tools are only available on Windows.", + "uninstall": "Uninstall", + "uninstalling": "Uninstalling..." + }, "language": { "description": "Die Sprache, in der Roxys eigene Oberfläche geschrieben ist. Sie ändert nicht, wie der Agent mit Ihnen spricht – fragen Sie ihn im Chat in jeder beliebigen Sprache.", "heading": "Sprache", diff --git a/src/renderer/src/locales/default.json b/src/renderer/src/locales/default.json index 9d5a189..0461d38 100644 --- a/src/renderer/src/locales/default.json +++ b/src/renderer/src/locales/default.json @@ -420,6 +420,37 @@ "idle": "Updates install automatically from GitHub." } }, + "kernel": { + "heading": "Kernel tools", + "title": "Windows kernel bridge", + "windowsOnly": "Kernel Tools are only available on Windows.", + "description": "Install the separately maintained Windows driver and bridge for privileged security research. Nothing runs until you explicitly install it, and agent access is not enabled automatically.", + "statusInstalled": "Driver and bridge installed", + "statusTestSigning": "Windows test signing enabled", + "statusDriverRunning": "Driver service running", + "statusDriverStopped": "Driver service stopped; a reboot may be required", + "statusDriverUnknown": "Driver service state could not be determined", + "statusMCPRegistered": "Agent access registered", + "enableAgentAccess": "Enable agent access", + "enablingAgentAccess": "Enabling...", + "disableAgentAccess": "Disable agent access", + "disablingAgentAccess": "Disabling...", + "statusNotInstalled": "Not installed", + "install": "Install kernel tools", + "installing": "Installing...", + "uninstall": "Uninstall", + "uninstalling": "Uninstalling...", + "start": "Start driver", + "starting": "Starting...", + "refresh": "Refresh", + "disableTestSigning": "Disable test signing", + "disablingTestSigning": "Disabling...", + "confirmInstallDescription": "This downloads and builds the external kernel-tools project, enables Windows test signing, and installs its driver. Administrator approval and a reboot are required; agent access is not enabled automatically.", + "confirmUninstallDescription": "This stops and removes the kernel driver and any related agent registration. Test signing remains enabled until you disable it separately.", + "confirmDisableTestSigningDescription": "Turn off Windows test signing? This removes support for test-signed drivers after the next reboot.", + "confirmEnableAgentAccessDescription": "Allow Roxy sessions to call the installed kernel bridge tools? These tools run with elevated system access. Only enable this on a dedicated research machine or VM.", + "failed": "The operation failed: {{error}}" + }, "danger": { "heading": "Danger zone", "resetTitle": "Reset everything", diff --git a/src/renderer/src/locales/es.json b/src/renderer/src/locales/es.json index 08cb689..becfbc5 100644 --- a/src/renderer/src/locales/es.json +++ b/src/renderer/src/locales/es.json @@ -387,6 +387,37 @@ "resetTitle": "Restablecer todo", "wiping": "Borrando…" }, + "kernel": { + "confirmDisableTestSigningDescription": "Turn off Windows test signing? This removes support for test-signed drivers after the next reboot.", + "confirmEnableAgentAccessDescription": "Allow Roxy sessions to call the installed kernel bridge tools? These tools run with elevated system access. Only enable this on a dedicated research machine or VM.", + "confirmInstallDescription": "This downloads and builds the external kernel-tools project, enables Windows test signing, and installs its driver. Administrator approval and a reboot are required; agent access is not enabled automatically.", + "confirmUninstallDescription": "This stops and removes the kernel driver and any related agent registration. Test signing remains enabled until you disable it separately.", + "description": "Install the separately maintained Windows driver and bridge for privileged security research. Nothing runs until you explicitly install it, and agent access is not enabled automatically.", + "disableAgentAccess": "Disable agent access", + "disableTestSigning": "Disable test signing", + "disablingAgentAccess": "Disabling...", + "disablingTestSigning": "Disabling...", + "enableAgentAccess": "Enable agent access", + "enablingAgentAccess": "Enabling...", + "failed": "The operation failed: {{error}}", + "heading": "Kernel tools", + "install": "Install kernel tools", + "installing": "Installing...", + "refresh": "Refresh", + "start": "Start driver", + "starting": "Starting...", + "statusDriverRunning": "Driver service running", + "statusDriverStopped": "Driver service stopped; a reboot may be required", + "statusDriverUnknown": "Driver service state could not be determined", + "statusInstalled": "Driver and bridge installed", + "statusMCPRegistered": "Agent access registered", + "statusNotInstalled": "Not installed", + "statusTestSigning": "Windows test signing enabled", + "title": "Windows kernel bridge", + "windowsOnly": "Kernel Tools are only available on Windows.", + "uninstall": "Uninstall", + "uninstalling": "Uninstalling..." + }, "language": { "description": "El idioma en el que está escrita la interfaz de Roxy. No cambia cómo te habla el agente: escríbele en el idioma que prefieras, en el chat.", "heading": "Idioma", diff --git a/src/renderer/src/locales/fr.json b/src/renderer/src/locales/fr.json index 08e2401..85d8389 100644 --- a/src/renderer/src/locales/fr.json +++ b/src/renderer/src/locales/fr.json @@ -387,6 +387,37 @@ "resetTitle": "Tout réinitialiser", "wiping": "Effacement…" }, + "kernel": { + "confirmDisableTestSigningDescription": "Turn off Windows test signing? This removes support for test-signed drivers after the next reboot.", + "confirmEnableAgentAccessDescription": "Allow Roxy sessions to call the installed kernel bridge tools? These tools run with elevated system access. Only enable this on a dedicated research machine or VM.", + "confirmInstallDescription": "This downloads and builds the external kernel-tools project, enables Windows test signing, and installs its driver. Administrator approval and a reboot are required; agent access is not enabled automatically.", + "confirmUninstallDescription": "This stops and removes the kernel driver and any related agent registration. Test signing remains enabled until you disable it separately.", + "description": "Install the separately maintained Windows driver and bridge for privileged security research. Nothing runs until you explicitly install it, and agent access is not enabled automatically.", + "disableAgentAccess": "Disable agent access", + "disableTestSigning": "Disable test signing", + "disablingAgentAccess": "Disabling...", + "disablingTestSigning": "Disabling...", + "enableAgentAccess": "Enable agent access", + "enablingAgentAccess": "Enabling...", + "failed": "The operation failed: {{error}}", + "heading": "Kernel tools", + "install": "Install kernel tools", + "installing": "Installing...", + "refresh": "Refresh", + "start": "Start driver", + "starting": "Starting...", + "statusDriverRunning": "Driver service running", + "statusDriverStopped": "Driver service stopped; a reboot may be required", + "statusDriverUnknown": "Driver service state could not be determined", + "statusInstalled": "Driver and bridge installed", + "statusMCPRegistered": "Agent access registered", + "statusNotInstalled": "Not installed", + "statusTestSigning": "Windows test signing enabled", + "title": "Windows kernel bridge", + "windowsOnly": "Kernel Tools are only available on Windows.", + "uninstall": "Uninstall", + "uninstalling": "Uninstalling..." + }, "language": { "description": "La langue de l'interface de Roxy. Cela ne change pas la façon dont l'agent vous parle — demandez-lui ce que vous voulez dans la langue de votre choix, dans le chat.", "heading": "Langue", diff --git a/src/renderer/src/locales/hi.json b/src/renderer/src/locales/hi.json index 64e953e..0e76376 100644 --- a/src/renderer/src/locales/hi.json +++ b/src/renderer/src/locales/hi.json @@ -387,6 +387,37 @@ "resetTitle": "सब कुछ रीसेट करें", "wiping": "मिटाया जा रहा है…" }, + "kernel": { + "confirmDisableTestSigningDescription": "Turn off Windows test signing? This removes support for test-signed drivers after the next reboot.", + "confirmEnableAgentAccessDescription": "Allow Roxy sessions to call the installed kernel bridge tools? These tools run with elevated system access. Only enable this on a dedicated research machine or VM.", + "confirmInstallDescription": "This downloads and builds the external kernel-tools project, enables Windows test signing, and installs its driver. Administrator approval and a reboot are required; agent access is not enabled automatically.", + "confirmUninstallDescription": "This stops and removes the kernel driver and any related agent registration. Test signing remains enabled until you disable it separately.", + "description": "Install the separately maintained Windows driver and bridge for privileged security research. Nothing runs until you explicitly install it, and agent access is not enabled automatically.", + "disableAgentAccess": "Disable agent access", + "disableTestSigning": "Disable test signing", + "disablingAgentAccess": "Disabling...", + "disablingTestSigning": "Disabling...", + "enableAgentAccess": "Enable agent access", + "enablingAgentAccess": "Enabling...", + "failed": "The operation failed: {{error}}", + "heading": "Kernel tools", + "install": "Install kernel tools", + "installing": "Installing...", + "refresh": "Refresh", + "start": "Start driver", + "starting": "Starting...", + "statusDriverRunning": "Driver service running", + "statusDriverStopped": "Driver service stopped; a reboot may be required", + "statusDriverUnknown": "Driver service state could not be determined", + "statusInstalled": "Driver and bridge installed", + "statusMCPRegistered": "Agent access registered", + "statusNotInstalled": "Not installed", + "statusTestSigning": "Windows test signing enabled", + "title": "Windows kernel bridge", + "windowsOnly": "Kernel Tools are only available on Windows.", + "uninstall": "Uninstall", + "uninstalling": "Uninstalling..." + }, "language": { "description": "वह भाषा जिसमें Roxy का अपना इंटरफ़ेस लिखा गया है। यह इस बात को नहीं बदलता कि एजेंट आपसे कैसे बात करता है — चैट में आप जिस भी भाषा में चाहें, उससे पूछें।", "heading": "भाषा", diff --git a/src/renderer/src/locales/ja.json b/src/renderer/src/locales/ja.json index 287c671..efa7efc 100644 --- a/src/renderer/src/locales/ja.json +++ b/src/renderer/src/locales/ja.json @@ -387,6 +387,37 @@ "resetTitle": "すべてリセット", "wiping": "消去中…" }, + "kernel": { + "confirmDisableTestSigningDescription": "Turn off Windows test signing? This removes support for test-signed drivers after the next reboot.", + "confirmEnableAgentAccessDescription": "Allow Roxy sessions to call the installed kernel bridge tools? These tools run with elevated system access. Only enable this on a dedicated research machine or VM.", + "confirmInstallDescription": "This downloads and builds the external kernel-tools project, enables Windows test signing, and installs its driver. Administrator approval and a reboot are required; agent access is not enabled automatically.", + "confirmUninstallDescription": "This stops and removes the kernel driver and any related agent registration. Test signing remains enabled until you disable it separately.", + "description": "Install the separately maintained Windows driver and bridge for privileged security research. Nothing runs until you explicitly install it, and agent access is not enabled automatically.", + "disableAgentAccess": "Disable agent access", + "disableTestSigning": "Disable test signing", + "disablingAgentAccess": "Disabling...", + "disablingTestSigning": "Disabling...", + "enableAgentAccess": "Enable agent access", + "enablingAgentAccess": "Enabling...", + "failed": "The operation failed: {{error}}", + "heading": "Kernel tools", + "install": "Install kernel tools", + "installing": "Installing...", + "refresh": "Refresh", + "start": "Start driver", + "starting": "Starting...", + "statusDriverRunning": "Driver service running", + "statusDriverStopped": "Driver service stopped; a reboot may be required", + "statusDriverUnknown": "Driver service state could not be determined", + "statusInstalled": "Driver and bridge installed", + "statusMCPRegistered": "Agent access registered", + "statusNotInstalled": "Not installed", + "statusTestSigning": "Windows test signing enabled", + "title": "Windows kernel bridge", + "windowsOnly": "Kernel Tools are only available on Windows.", + "uninstall": "Uninstall", + "uninstalling": "Uninstalling..." + }, "language": { "description": "Roxyのインターフェースの表示言語です。エージェントとの会話方法には影響しません。チャットでは好きな言語で質問してください。", "heading": "言語", diff --git a/src/renderer/src/locales/pt.json b/src/renderer/src/locales/pt.json index 5171c79..0baa46a 100644 --- a/src/renderer/src/locales/pt.json +++ b/src/renderer/src/locales/pt.json @@ -387,6 +387,37 @@ "resetTitle": "Redefinir tudo", "wiping": "Apagando…" }, + "kernel": { + "confirmDisableTestSigningDescription": "Turn off Windows test signing? This removes support for test-signed drivers after the next reboot.", + "confirmEnableAgentAccessDescription": "Allow Roxy sessions to call the installed kernel bridge tools? These tools run with elevated system access. Only enable this on a dedicated research machine or VM.", + "confirmInstallDescription": "This downloads and builds the external kernel-tools project, enables Windows test signing, and installs its driver. Administrator approval and a reboot are required; agent access is not enabled automatically.", + "confirmUninstallDescription": "This stops and removes the kernel driver and any related agent registration. Test signing remains enabled until you disable it separately.", + "description": "Install the separately maintained Windows driver and bridge for privileged security research. Nothing runs until you explicitly install it, and agent access is not enabled automatically.", + "disableAgentAccess": "Disable agent access", + "disableTestSigning": "Disable test signing", + "disablingAgentAccess": "Disabling...", + "disablingTestSigning": "Disabling...", + "enableAgentAccess": "Enable agent access", + "enablingAgentAccess": "Enabling...", + "failed": "The operation failed: {{error}}", + "heading": "Kernel tools", + "install": "Install kernel tools", + "installing": "Installing...", + "refresh": "Refresh", + "start": "Start driver", + "starting": "Starting...", + "statusDriverRunning": "Driver service running", + "statusDriverStopped": "Driver service stopped; a reboot may be required", + "statusDriverUnknown": "Driver service state could not be determined", + "statusInstalled": "Driver and bridge installed", + "statusMCPRegistered": "Agent access registered", + "statusNotInstalled": "Not installed", + "statusTestSigning": "Windows test signing enabled", + "title": "Windows kernel bridge", + "windowsOnly": "Kernel Tools are only available on Windows.", + "uninstall": "Uninstall", + "uninstalling": "Uninstalling..." + }, "language": { "description": "O idioma da interface do Roxy. Isso não altera a forma como o agente fala com você — pergunte o que quiser, no idioma que preferir, no chat.", "heading": "Idioma", diff --git a/src/renderer/src/locales/ru.json b/src/renderer/src/locales/ru.json index b5d0e14..395a8ca 100644 --- a/src/renderer/src/locales/ru.json +++ b/src/renderer/src/locales/ru.json @@ -387,6 +387,37 @@ "resetTitle": "Сбросить всё", "wiping": "Удаление…" }, + "kernel": { + "confirmDisableTestSigningDescription": "Turn off Windows test signing? This removes support for test-signed drivers after the next reboot.", + "confirmEnableAgentAccessDescription": "Allow Roxy sessions to call the installed kernel bridge tools? These tools run with elevated system access. Only enable this on a dedicated research machine or VM.", + "confirmInstallDescription": "This downloads and builds the external kernel-tools project, enables Windows test signing, and installs its driver. Administrator approval and a reboot are required; agent access is not enabled automatically.", + "confirmUninstallDescription": "This stops and removes the kernel driver and any related agent registration. Test signing remains enabled until you disable it separately.", + "description": "Install the separately maintained Windows driver and bridge for privileged security research. Nothing runs until you explicitly install it, and agent access is not enabled automatically.", + "disableAgentAccess": "Disable agent access", + "disableTestSigning": "Disable test signing", + "disablingAgentAccess": "Disabling...", + "disablingTestSigning": "Disabling...", + "enableAgentAccess": "Enable agent access", + "enablingAgentAccess": "Enabling...", + "failed": "The operation failed: {{error}}", + "heading": "Kernel tools", + "install": "Install kernel tools", + "installing": "Installing...", + "refresh": "Refresh", + "start": "Start driver", + "starting": "Starting...", + "statusDriverRunning": "Driver service running", + "statusDriverStopped": "Driver service stopped; a reboot may be required", + "statusDriverUnknown": "Driver service state could not be determined", + "statusInstalled": "Driver and bridge installed", + "statusMCPRegistered": "Agent access registered", + "statusNotInstalled": "Not installed", + "statusTestSigning": "Windows test signing enabled", + "title": "Windows kernel bridge", + "windowsOnly": "Kernel Tools are only available on Windows.", + "uninstall": "Uninstall", + "uninstalling": "Uninstalling..." + }, "language": { "description": "Язык интерфейса Roxy. Это не влияет на то, как агент общается с вами — задавайте ему вопросы на любом языке в чате.", "heading": "Язык", diff --git a/src/renderer/src/locales/zh.json b/src/renderer/src/locales/zh.json index 0332627..4d39d24 100644 --- a/src/renderer/src/locales/zh.json +++ b/src/renderer/src/locales/zh.json @@ -387,6 +387,37 @@ "resetTitle": "重置所有内容", "wiping": "正在清除…" }, + "kernel": { + "confirmDisableTestSigningDescription": "Turn off Windows test signing? This removes support for test-signed drivers after the next reboot.", + "confirmEnableAgentAccessDescription": "Allow Roxy sessions to call the installed kernel bridge tools? These tools run with elevated system access. Only enable this on a dedicated research machine or VM.", + "confirmInstallDescription": "This downloads and builds the external kernel-tools project, enables Windows test signing, and installs its driver. Administrator approval and a reboot are required; agent access is not enabled automatically.", + "confirmUninstallDescription": "This stops and removes the kernel driver and any related agent registration. Test signing remains enabled until you disable it separately.", + "description": "Install the separately maintained Windows driver and bridge for privileged security research. Nothing runs until you explicitly install it, and agent access is not enabled automatically.", + "disableAgentAccess": "Disable agent access", + "disableTestSigning": "Disable test signing", + "disablingAgentAccess": "Disabling...", + "disablingTestSigning": "Disabling...", + "enableAgentAccess": "Enable agent access", + "enablingAgentAccess": "Enabling...", + "failed": "The operation failed: {{error}}", + "heading": "Kernel tools", + "install": "Install kernel tools", + "installing": "Installing...", + "refresh": "Refresh", + "start": "Start driver", + "starting": "Starting...", + "statusDriverRunning": "Driver service running", + "statusDriverStopped": "Driver service stopped; a reboot may be required", + "statusDriverUnknown": "Driver service state could not be determined", + "statusInstalled": "Driver and bridge installed", + "statusMCPRegistered": "Agent access registered", + "statusNotInstalled": "Not installed", + "statusTestSigning": "Windows test signing enabled", + "title": "Windows kernel bridge", + "windowsOnly": "Kernel Tools are only available on Windows.", + "uninstall": "Uninstall", + "uninstalling": "Uninstalling..." + }, "language": { "description": "Roxy 界面本身的显示语言。它不会改变代理与您交流的方式 — 您可以在聊天中用任何您喜欢的语言提问。", "heading": "语言", diff --git a/src/renderer/src/routes/Settings.tsx b/src/renderer/src/routes/Settings.tsx index b9709b1..7d20ad5 100644 --- a/src/renderer/src/routes/Settings.tsx +++ b/src/renderer/src/routes/Settings.tsx @@ -27,6 +27,7 @@ import { SubscriptionAccounts } from '../components/SubscriptionSetup' import { ModelVisibility } from '../components/ModelVisibility' import { useRoxyStore } from '../lib/store' import { MotionSettings } from '../components/MotionSettings' +import { KernelToolsSection } from '../components/KernelToolsSection' /** The section heading repeated down the page. */ const SECTION_HEADING = 'mb-3 text-xs font-semibold uppercase tracking-wide text-text-subtle' @@ -402,6 +403,8 @@ export default function Settings(): JSX.Element { + +

{t('settings.danger.heading')} diff --git a/src/shared/api.ts b/src/shared/api.ts index 142a31a..9b5af92 100644 --- a/src/shared/api.ts +++ b/src/shared/api.ts @@ -33,6 +33,7 @@ import type { RepoLayout } from './repos' import type { SessionConfigPatch } from './session-config' import type { ClipboardAction } from './context-menu' import type { ResolvedTheme, ThemeView } from './theme' +import type { KernelStatus, KernelInstallResult } from './kernel' /** A configured MCP server merged with its live connection status (for Settings). */ export interface McpServerView { @@ -1192,4 +1193,12 @@ export interface RoxyApi { */ onDelta(callback: (payload: RemoteDelta) => void): () => void } + kernel: { + status(): Promise + install(): Promise + start(): Promise + setAgentAccess(enable: boolean): Promise + uninstall(disableSigning: boolean): Promise + toggleTestSigning(enable: boolean): Promise + } } diff --git a/src/shared/ipc.ts b/src/shared/ipc.ts index a2af080..235b12e 100644 --- a/src/shared/ipc.ts +++ b/src/shared/ipc.ts @@ -272,7 +272,14 @@ export const CHANNELS = { /** main -> renderer: Remote Workspace sharing status changed */ remoteState: 'remote:state', /** main -> renderer: a streamed event from a phone-driven turn (live desktop mirror) */ - remoteDelta: 'remote:delta' + remoteDelta: 'remote:delta', + + kernelStatus: 'kernel:status', + kernelInstall: 'kernel:install', + kernelStart: 'kernel:start', + kernelSetAgentAccess: 'kernel:setAgentAccess', + kernelUninstall: 'kernel:uninstall', + kernelToggleTestSigning: 'kernel:toggleTestSigning' } as const export type ChannelName = (typeof CHANNELS)[keyof typeof CHANNELS] diff --git a/src/shared/kernel.ts b/src/shared/kernel.ts new file mode 100644 index 0000000..eb60dad --- /dev/null +++ b/src/shared/kernel.ts @@ -0,0 +1,16 @@ +export interface KernelStatus { + isWindows: boolean + installed: boolean + hasArtifacts: boolean + driverPath: string | null + testSigning: boolean + bridgePath: string | null + mcpRegistered: boolean + driverState: 'not-installed' | 'stopped' | 'running' | 'unknown' +} + +export interface KernelInstallResult { + ok: boolean + error?: string + steps: string[] +} diff --git a/test/kernel.ts b/test/kernel.ts new file mode 100644 index 0000000..e750d9a --- /dev/null +++ b/test/kernel.ts @@ -0,0 +1,47 @@ +import assert from 'node:assert/strict' +import { readFileSync } from 'node:fs' +import path from 'node:path' +import { CHANNELS } from '../src/shared/ipc' +import type { KernelStatus } from '../src/shared/kernel' + +const nonWindowsStatus: KernelStatus = { + isWindows: false, + installed: false, + hasArtifacts: false, + driverPath: null, + bridgePath: null, + testSigning: false, + mcpRegistered: false, + driverState: 'not-installed' +} + +assert.equal(CHANNELS.kernelStatus, 'kernel:status') +assert.equal(CHANNELS.kernelInstall, 'kernel:install') +assert.equal(CHANNELS.kernelStart, 'kernel:start') +assert.equal(CHANNELS.kernelSetAgentAccess, 'kernel:setAgentAccess') +assert.equal(CHANNELS.kernelUninstall, 'kernel:uninstall') +assert.equal(CHANNELS.kernelToggleTestSigning, 'kernel:toggleTestSigning') +assert.equal(nonWindowsStatus.isWindows, false) + +const defaultLocale = JSON.parse( + readFileSync(path.join(process.cwd(), 'src/renderer/src/locales/default.json'), 'utf8') +) as { settings?: { kernel?: Record } } +const kernelStrings = defaultLocale.settings?.kernel +assert.ok(kernelStrings, 'settings.kernel locale section exists') +for (const key of [ + 'heading', + 'title', + 'windowsOnly', + 'description', + 'install', + 'uninstall', + 'refresh', + 'disableTestSigning', + 'enableAgentAccess', + 'disableAgentAccess', + 'confirmEnableAgentAccessDescription' +]) { + assert.ok(kernelStrings[key], `settings.kernel.${key} resolves`) +} + +console.log('kernel shared contract OK') From d998ef287d08c4e46ae47bbd59cf4084a0961315 Mon Sep 17 00:00:00 2001 From: Freddy Diaz <45578633+FreddyJD@users.noreply.github.com> Date: Sat, 12 Sep 2026 21:10:58 -0400 Subject: [PATCH 2/5] fix(settings): harden kernel tools safeguards Fail closed when the built driver is unsigned, restrict privileged IPC to the main frame, serialize mutations, and narrow supported service/platform handling.\n\nCo-authored-by: Roxy <299891354+roxy-commits@users.noreply.github.com> --- src/main/index.ts | 32 ++++++++----- src/main/ipc/kernel.ts | 64 +++++++++++++++++++++---- src/main/services/kernel.ts | 94 +++++++++++++++++++++++++++++++------ test/kernel.ts | 9 ++++ 4 files changed, 163 insertions(+), 36 deletions(-) diff --git a/src/main/index.ts b/src/main/index.ts index 6a102cb..6aca141 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -31,9 +31,11 @@ import { import { resolveThemeById } from './services/themes' import * as repo from './db/repo' +let mainWindow: BrowserWindow | null = null + function createWindow(): BrowserWindow { const isMac = process.platform === 'darwin' - const mainWindow = new BrowserWindow({ + const window = new BrowserWindow({ width: 1100, height: 720, minWidth: 760, @@ -55,30 +57,39 @@ function createWindow(): BrowserWindow { } }) - mainWindow.on('ready-to-show', () => { + mainWindow = window + window.on('closed', () => { + if (mainWindow === window) mainWindow = null + }) + + window.on('ready-to-show', () => { // Repaint the native window controls from the active theme before the // window is first shown. The constructor can only reach built-in themes // synchronously; this covers a user theme, whose file has to be read. void resolveThemeById(repo.getSettings().activeThemeId, chromePlatform()) - .then((theme) => applyWindowChrome(mainWindow, theme)) + .then((theme) => applyWindowChrome(window, theme)) .catch(() => undefined) - mainWindow.show() + window.show() }) // Open external links in the user's browser instead of a new Electron window. - mainWindow.webContents.setWindowOpenHandler((details) => { + window.webContents.setWindowOpenHandler((details) => { shell.openExternal(details.url) return { action: 'deny' } }) + window.webContents.on('will-navigate', (event, url) => { + const currentUrl = window.webContents.getURL() + if (currentUrl && url !== currentUrl) event.preventDefault() + }) // Load the Vite dev server in development, or the built HTML in production. if (is.dev && process.env['ELECTRON_RENDERER_URL']) { - mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL']) + window.loadURL(process.env['ELECTRON_RENDERER_URL']) } else { - mainWindow.loadFile(join(__dirname, '../renderer/index.html')) + window.loadFile(join(__dirname, '../renderer/index.html')) } - return mainWindow + return window } /** @@ -122,7 +133,7 @@ app.whenReady().then(() => { // Open the database (runs migrations) and wire up IPC before the first window. getDb() registerIpc() - registerKernelIpc() + registerKernelIpc(() => mainWindow) // Anonymous usage tracking (opt-out in Settings). Deliberately after the DB // and IPC are up so nothing here can delay the first window, and it owns its // own storage - a failure in it can't touch either. @@ -135,8 +146,7 @@ app.whenReady().then(() => { // backfilled rows can be priced (else they'd all cost $0). Best-effort + async. void warmCatalogThenBackfill() - const mainWindow = createWindow() - initAutoUpdater(mainWindow) + initAutoUpdater(createWindow()) app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) createWindow() diff --git a/src/main/ipc/kernel.ts b/src/main/ipc/kernel.ts index 1bab9d2..9c7a994 100644 --- a/src/main/ipc/kernel.ts +++ b/src/main/ipc/kernel.ts @@ -1,4 +1,4 @@ -import { ipcMain } from 'electron' +import { dialog, ipcMain, type BrowserWindow, type IpcMainInvokeEvent } from 'electron' import { CHANNELS } from '../../shared/ipc' import { getKernelStatus, @@ -9,19 +9,63 @@ import { uninstallKernelTools } from '../services/kernel' -export function registerKernelIpc(): void { - ipcMain.handle(CHANNELS.kernelStatus, () => getKernelStatus()) - ipcMain.handle(CHANNELS.kernelInstall, () => installKernelTools()) - ipcMain.handle(CHANNELS.kernelStart, () => startKernelDriver()) - ipcMain.handle(CHANNELS.kernelSetAgentAccess, (_event, enable: boolean) => - setKernelAgentAccess(enable === true) - ) - ipcMain.handle(CHANNELS.kernelUninstall, (_event, disableSigning: boolean) => { +function requireMainWindow( + event: IpcMainInvokeEvent, + getMainWindow: () => BrowserWindow | null +): BrowserWindow { + const mainWindow = getMainWindow() + if ( + !mainWindow || + mainWindow.isDestroyed() || + event.sender !== mainWindow.webContents || + event.senderFrame !== mainWindow.webContents.mainFrame + ) { + throw new Error('Kernel Tools requests are only accepted from the main Roxy window.') + } + return mainWindow +} + +export function registerKernelIpc(getMainWindow: () => BrowserWindow | null): void { + ipcMain.handle(CHANNELS.kernelStatus, (event) => { + requireMainWindow(event, getMainWindow) + return getKernelStatus() + }) + ipcMain.handle(CHANNELS.kernelInstall, (event) => { + requireMainWindow(event, getMainWindow) + return installKernelTools() + }) + ipcMain.handle(CHANNELS.kernelStart, (event) => { + requireMainWindow(event, getMainWindow) + return startKernelDriver() + }) + ipcMain.handle(CHANNELS.kernelSetAgentAccess, async (event, enable: boolean) => { + const mainWindow = requireMainWindow(event, getMainWindow) + if (typeof enable !== 'boolean') throw new TypeError('enable must be a boolean.') + if (enable) { + const { response } = await dialog.showMessageBox(mainWindow, { + type: 'warning', + title: 'Enable Kernel Tools agent access?', + message: 'This gives agents elevated access to protected system resources.', + detail: 'Only continue in an isolated test environment with no sensitive data.', + buttons: ['Cancel', 'Enable agent access'], + defaultId: 0, + cancelId: 0, + noLink: true + }) + if (response !== 1) { + return { ok: false, error: 'Agent access was not enabled.', steps: [] } + } + } + return setKernelAgentAccess(enable) + }) + ipcMain.handle(CHANNELS.kernelUninstall, (event, disableSigning: boolean) => { + requireMainWindow(event, getMainWindow) if (typeof disableSigning !== 'boolean') throw new TypeError('disableSigning must be a boolean.') return uninstallKernelTools(disableSigning) }) - ipcMain.handle(CHANNELS.kernelToggleTestSigning, (_event, enable: boolean) => { + ipcMain.handle(CHANNELS.kernelToggleTestSigning, (event, enable: boolean) => { + requireMainWindow(event, getMainWindow) if (typeof enable !== 'boolean') throw new TypeError('enable must be a boolean.') return toggleTestSigning(enable) }) diff --git a/src/main/services/kernel.ts b/src/main/services/kernel.ts index dabf8c5..a9534ef 100644 --- a/src/main/services/kernel.ts +++ b/src/main/services/kernel.ts @@ -13,7 +13,7 @@ const execFileAsync = promisify(execFile) const REPOSITORY_URL = 'https://github.com/roxy-gg/kernel-tools.git' // Review and update this pin deliberately when the external driver changes. const REPOSITORY_REVISION = '2f51a1d5981a553d7642db9c81d41a002f3c44e4' -const SERVICE_NAME = 'AIBridge' +const SERVICE_NAME = 'RoxyKernelToolsAIBridge' const MCP_SERVER_ID = 'roxy-kernel-tools' const MCP_SERVER_IDS = [MCP_SERVER_ID, 'kernel'] as const @@ -33,11 +33,30 @@ const PRIVILEGED_INSTALL_DIR = path.join( ) const DRIVER_PATH = path.join(PRIVILEGED_INSTALL_DIR, 'aibridge.sys') const BRIDGE_PATH = path.join(PRIVILEGED_INSTALL_DIR, 'roxy-kernel-bridge.exe') +let mutationInProgress = false function isWindows(): boolean { return process.platform === 'win32' } +function isSupportedPlatform(): boolean { + return isWindows() && process.arch === 'x64' +} + +async function runMutation( + operation: () => Promise +): Promise { + if (mutationInProgress) { + return { ok: false, error: 'Another Kernel Tools operation is already in progress.', steps: [] } + } + mutationInProgress = true + try { + return await operation() + } finally { + mutationInProgress = false + } +} + function errorMessage(error: unknown): string { if (!(error instanceof Error)) return String(error) const output = error as Error & { stdout?: string; stderr?: string } @@ -66,11 +85,11 @@ async function run( args: string[], timeout = 30_000 ): Promise<{ stdout: string; stderr: string }> { - return execFileAsync(file, args, { timeout, windowsHide: true }) + return execFileAsync(file, args, { timeout, windowsHide: true, maxBuffer: 10 * 1024 * 1024 }) } /** Run one executable through the standard Windows UAC consent prompt. */ -async function runElevated(file: string, args: string[], timeout = 120_000): Promise { +async function runElevated(file: string, args: string[]): Promise { const argumentList = args.map(powershellLiteral).join(', ') const resultPath = path.join(INSTALL_DIR, `elevated-${randomUUID()}.txt`) const command = [ @@ -87,7 +106,7 @@ async function runElevated(file: string, args: string[], timeout = 120_000): Pro await run( systemExecutable('WindowsPowerShell\\v1.0\\powershell.exe'), ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', command], - timeout + 0 ) const [exitCode, ...details] = readFileSync(resultPath, 'utf8') .replace(/^\uFEFF/, '') @@ -143,8 +162,8 @@ function installedDriverScript( ' Start-Sleep -Seconds 1', '}', 'New-Item -ItemType Directory -Force -Path $installDirectory | Out-Null', - 'Copy-Item -Force $driverSource $driverTarget', - 'Copy-Item -Force $bridgeSource $bridgeTarget', + 'Copy-Item -Force -LiteralPath $driverSource -Destination $driverTarget', + 'Copy-Item -Force -LiteralPath $bridgeSource -Destination $bridgeTarget', "if ((Get-FileHash -Algorithm SHA256 $driverTarget).Hash.ToLowerInvariant() -ne $expectedDriverHash) { throw 'Installed driver hash mismatch.' }", "if ((Get-FileHash -Algorithm SHA256 $bridgeTarget).Hash.ToLowerInvariant() -ne $expectedBridgeHash) { throw 'Installed bridge hash mismatch.' }", `& sc.exe create ${SERVICE_NAME} 'type=' 'kernel' 'start=' 'demand' 'binPath=' $driverTarget | Out-Null`, @@ -232,7 +251,7 @@ async function removeKernelMcpServers(): Promise { } } -export async function setKernelAgentAccess(enable: boolean): Promise { +async function setKernelAgentAccessImpl(enable: boolean): Promise { if (!isWindows()) return { ok: false, error: 'Kernel tools require Windows.', steps: [] } const steps: string[] = [] @@ -297,10 +316,11 @@ export async function getKernelStatus(): Promise { // The database may still be opening during app startup. } + const serviceExists = driverState === 'running' || driverState === 'stopped' return { isWindows: true, - installed: driverPath !== null && bridgePath !== null && driverState !== 'not-installed', - hasArtifacts: driverPath !== null || bridgePath !== null || driverState !== 'not-installed', + installed: driverPath !== null && bridgePath !== null && serviceExists, + hasArtifacts: driverPath !== null || bridgePath !== null || serviceExists, driverPath, testSigning, bridgePath, @@ -317,6 +337,28 @@ async function checkoutPinnedRepository(steps: string[]): Promise { await run('git.exe', ['-C', REPOSITORY_DIR, 'checkout', '--detach', REPOSITORY_REVISION], 30_000) } +export async function setKernelAgentAccess(enable: boolean): Promise { + return runMutation(() => setKernelAgentAccessImpl(enable)) +} + +async function verifyBuiltDriverSignature(): Promise { + const { stdout } = await run( + systemExecutable('WindowsPowerShell\\v1.0\\powershell.exe'), + [ + '-NoProfile', + '-NonInteractive', + '-Command', + `(Get-AuthenticodeSignature -LiteralPath ${powershellLiteral(BUILT_DRIVER_PATH)}).Status` + ], + 10_000 + ) + if (stdout.trim() !== 'Valid') { + throw new Error( + 'The built kernel driver does not have a valid embedded signature. Windows test-signing mode still requires a signed driver, so installation was stopped before changing your system.' + ) + } +} + async function buildArtifacts(steps: string[]): Promise { const powershell = systemExecutable('WindowsPowerShell\\v1.0\\powershell.exe') @@ -353,8 +395,10 @@ async function buildArtifacts(steps: string[]): Promise { } } -export async function installKernelTools(): Promise { - if (!isWindows()) return { ok: false, error: 'Kernel tools require Windows.', steps: [] } +async function installKernelToolsImpl(): Promise { + if (!isSupportedPlatform()) { + return { ok: false, error: 'Kernel tools require 64-bit x64 Windows.', steps: [] } + } const steps: string[] = [] let enabledTestSigning = false @@ -362,6 +406,8 @@ export async function installKernelTools(): Promise { await removeKernelMcpServers() await checkoutPinnedRepository(steps) await buildArtifacts(steps) + await verifyBuiltDriverSignature() + steps.push('Verified the kernel driver signature.') const testSigningWasEnabled = await checkTestSigning() if (!testSigningWasEnabled) { @@ -409,8 +455,14 @@ export async function installKernelTools(): Promise { } } -export async function startKernelDriver(): Promise { - if (!isWindows()) return { ok: false, error: 'Kernel tools require Windows.', steps: [] } +export async function installKernelTools(): Promise { + return runMutation(installKernelToolsImpl) +} + +async function startKernelDriverImpl(): Promise { + if (!isSupportedPlatform()) { + return { ok: false, error: 'Kernel tools require 64-bit x64 Windows.', steps: [] } + } const steps: string[] = [] if (!existsSync(DRIVER_PATH) || !existsSync(BRIDGE_PATH)) { @@ -430,7 +482,11 @@ export async function startKernelDriver(): Promise { } } -export async function toggleTestSigning(enable: boolean): Promise { +export async function startKernelDriver(): Promise { + return runMutation(startKernelDriverImpl) +} + +async function toggleTestSigningImpl(enable: boolean): Promise { if (!isWindows()) return { ok: false, error: 'Kernel tools require Windows.', steps: [] } const steps: string[] = [] @@ -446,7 +502,11 @@ export async function toggleTestSigning(enable: boolean): Promise { +export async function toggleTestSigning(enable: boolean): Promise { + return runMutation(() => toggleTestSigningImpl(enable)) +} + +async function uninstallKernelToolsImpl(disableSigning: boolean): Promise { if (!isWindows()) return { ok: false, error: 'Kernel tools require Windows.', steps: [] } const steps: string[] = [] @@ -488,3 +548,7 @@ export async function uninstallKernelTools(disableSigning: boolean): Promise { + return runMutation(() => uninstallKernelToolsImpl(disableSigning)) +} diff --git a/test/kernel.ts b/test/kernel.ts index e750d9a..320a8fd 100644 --- a/test/kernel.ts +++ b/test/kernel.ts @@ -44,4 +44,13 @@ for (const key of [ assert.ok(kernelStrings[key], `settings.kernel.${key} resolves`) } +const kernelService = readFileSync(path.join(process.cwd(), 'src/main/services/kernel.ts'), 'utf8') +assert.match(kernelService, /Get-AuthenticodeSignature/) +assert.match(kernelService, /RoxyKernelToolsAIBridge/) +assert.match(kernelService, /Another Kernel Tools operation is already in progress/) + +const kernelIpc = readFileSync(path.join(process.cwd(), 'src/main/ipc/kernel.ts'), 'utf8') +assert.match(kernelIpc, /event\.senderFrame !== mainWindow\.webContents\.mainFrame/) +assert.match(kernelIpc, /typeof enable !== 'boolean'/) + console.log('kernel shared contract OK') From e79ea7c4a2ddda8b70a857e7cd3662841c8b9f08 Mon Sep 17 00:00:00 2001 From: Freddy Diaz <45578633+FreddyJD@users.noreply.github.com> Date: Mon, 14 Sep 2026 05:58:37 -0400 Subject: [PATCH 3/5] feat(kernel): install pinned release artifacts Download and verify the published Kernel Tools package instead of requiring Git, Rust, Visual Studio, and the WDK on user machines. Harden elevated extraction, service ownership, certificate tracking, and x64-only installation. Co-authored-by: Roxy <299891354+roxy-commits@users.noreply.github.com> --- src/main/services/kernel.ts | 245 +++++++++++++++----------- src/renderer/src/locales/default.json | 2 +- test/kernel.ts | 21 ++- 3 files changed, 164 insertions(+), 104 deletions(-) diff --git a/src/main/services/kernel.ts b/src/main/services/kernel.ts index a9534ef..ea1e1af 100644 --- a/src/main/services/kernel.ts +++ b/src/main/services/kernel.ts @@ -1,18 +1,23 @@ import { createHash, randomUUID } from 'node:crypto' import { execFile } from 'node:child_process' import { existsSync, mkdirSync, readFileSync } from 'node:fs' -import { rm } from 'node:fs/promises' +import { open, rm } from 'node:fs/promises' import path from 'node:path' import { promisify } from 'node:util' +import { app, net as electronNet } from 'electron' import type { KernelInstallResult, KernelStatus } from '../../shared/kernel' import { deleteMcpServer, listMcpServers, upsertMcpServer } from '../db/repo' import { disposeConnection } from './mcp' const execFileAsync = promisify(execFile) -const REPOSITORY_URL = 'https://github.com/roxy-gg/kernel-tools.git' -// Review and update this pin deliberately when the external driver changes. -const REPOSITORY_REVISION = '2f51a1d5981a553d7642db9c81d41a002f3c44e4' +const RELEASE_TAG = 'v1.0.1' +const RELEASE_ASSET = 'kernel-tools-windows-x64.zip' +const RELEASE_URL = `https://github.com/roxy-gg/kernel-tools/releases/download/${RELEASE_TAG}/${RELEASE_ASSET}` +const RELEASE_SHA256 = 'a14ab9b420923f3de017923dacae87ebf72ac80def8f9ce568df3ebdaeb7f282' +const RELEASE_COMMIT = '88dd68313db9c08e6ce419426d545e02066f5d91' +const SIGNER_THUMBPRINT = '8106e5e23fc13860575a61c16e391ad44e174d46' +const MAX_RELEASE_BYTES = 5 * 1024 * 1024 const SERVICE_NAME = 'RoxyKernelToolsAIBridge' const MCP_SERVER_ID = 'roxy-kernel-tools' const MCP_SERVER_IDS = [MCP_SERVER_ID, 'kernel'] as const @@ -22,10 +27,7 @@ const INSTALL_DIR = path.join( 'roxy', 'kernel-tools' ) -const REPOSITORY_DIR = path.join(INSTALL_DIR, 'repo') -const OUTPUT_DIR = path.join(REPOSITORY_DIR, 'out') -const BUILT_DRIVER_PATH = path.join(OUTPUT_DIR, 'aibridge.sys') -const BUILT_BRIDGE_PATH = path.join(OUTPUT_DIR, 'roxy-kernel-bridge.exe') +const ARCHIVE_PATH = path.join(INSTALL_DIR, RELEASE_ASSET) const PRIVILEGED_INSTALL_DIR = path.join( process.env.ProgramFiles ?? 'C:\\Program Files', 'Roxy', @@ -33,6 +35,7 @@ const PRIVILEGED_INSTALL_DIR = path.join( ) const DRIVER_PATH = path.join(PRIVILEGED_INSTALL_DIR, 'aibridge.sys') const BRIDGE_PATH = path.join(PRIVILEGED_INSTALL_DIR, 'roxy-kernel-bridge.exe') +const INSTALL_MANIFEST_PATH = path.join(PRIVILEGED_INSTALL_DIR, 'install-manifest.json') let mutationInProgress = false function isWindows(): boolean { @@ -137,46 +140,100 @@ async function setTestSigning(enable: boolean): Promise { await runElevated(systemExecutable('bcdedit.exe'), ['/set', 'testsigning', enable ? 'on' : 'off']) } -function installedDriverScript( - expectedDriverHash: string, - expectedBridgeHash: string, - startDriver: boolean -): string { +function installedDriverScript(startDriver: boolean): string { return [ "$ErrorActionPreference = 'Stop'", + `$archivePath = ${powershellLiteral(ARCHIVE_PATH)}`, + `$expectedArchiveHash = ${powershellLiteral(RELEASE_SHA256)}`, + `$expectedVersion = ${powershellLiteral(RELEASE_TAG)}`, + `$expectedCommit = ${powershellLiteral(RELEASE_COMMIT)}`, + `$expectedSignerThumbprint = ${powershellLiteral(SIGNER_THUMBPRINT)}`, `$installDirectory = ${powershellLiteral(PRIVILEGED_INSTALL_DIR)}`, - `$driverSource = ${powershellLiteral(BUILT_DRIVER_PATH)}`, `$driverTarget = ${powershellLiteral(DRIVER_PATH)}`, - `$bridgeSource = ${powershellLiteral(BUILT_BRIDGE_PATH)}`, `$bridgeTarget = ${powershellLiteral(BRIDGE_PATH)}`, - `$expectedDriverHash = ${powershellLiteral(expectedDriverHash)}`, - `$expectedBridgeHash = ${powershellLiteral(expectedBridgeHash)}`, - "if ((Get-FileHash -Algorithm SHA256 $driverSource).Hash.ToLowerInvariant() -ne $expectedDriverHash) { throw 'Driver hash mismatch.' }", - "if ((Get-FileHash -Algorithm SHA256 $bridgeSource).Hash.ToLowerInvariant() -ne $expectedBridgeHash) { throw 'Bridge hash mismatch.' }", - `$service = Get-Service -Name ${powershellLiteral(SERVICE_NAME)} -ErrorAction SilentlyContinue`, + `$installManifestPath = ${powershellLiteral(INSTALL_MANIFEST_PATH)}`, + `$stagingDirectory = Join-Path $installDirectory ('.staging-' + [guid]::NewGuid().ToString('N'))`, + 'try {', + ' New-Item -ItemType Directory -Force -Path $installDirectory | Out-Null', + ' $archive = [IO.File]::Open($archivePath, [IO.FileMode]::Open, [IO.FileAccess]::Read, [IO.FileShare]::None)', + ' try {', + ' $archiveHash = [Security.Cryptography.SHA256]::Create().ComputeHash($archive)', + " $archiveHashHex = (($archiveHash | ForEach-Object { $_.ToString('x2') }) -join '')", + " if ($archiveHashHex -ne $expectedArchiveHash) { throw 'Release archive hash mismatch.' }", + ' $archive.Position = 0', + ' New-Item -ItemType Directory -Force -Path $stagingDirectory | Out-Null', + " Add-Type -AssemblyName 'System.IO.Compression'", + " Add-Type -AssemblyName 'System.IO.Compression.FileSystem'", + ' $zip = New-Object IO.Compression.ZipArchive($archive, [IO.Compression.ZipArchiveMode]::Read, $true)', + ' try {', + " $allowedNames = @('aibridge-test.cer', 'aibridge.sys', 'manifest.json', 'roxy-kernel-bridge.exe')", + " if ($zip.Entries.Count -ne $allowedNames.Count -or @($zip.Entries | Where-Object { $_.FullName -notin $allowedNames }).Count -ne 0) { throw 'Unexpected release archive contents.' }", + ' foreach ($entry in $zip.Entries) {', + ' $destination = Join-Path $stagingDirectory $entry.FullName', + ' [IO.Compression.ZipFileExtensions]::ExtractToFile($entry, $destination, $true)', + ' }', + ' } finally { $zip.Dispose() }', + ' } finally { $archive.Dispose() }', + '$manifest = Get-Content -Raw -LiteralPath (Join-Path $stagingDirectory "manifest.json") | ConvertFrom-Json', + "if ($manifest.version -ne $expectedVersion -or $manifest.architecture -ne 'x64' -or $manifest.sourceCommit -ne $expectedCommit -or -not $manifest.testSigned -or $manifest.signerThumbprint.ToLowerInvariant() -ne $expectedSignerThumbprint) { throw 'Release manifest mismatch.' }", + "$expectedNames = @('aibridge-test.cer', 'aibridge.sys', 'roxy-kernel-bridge.exe')", + "if ($manifest.files.Count -ne $expectedNames.Count -or @($manifest.files | Where-Object { $_.name -notin $expectedNames }).Count -ne 0) { throw 'Unexpected release manifest file set.' }", + 'foreach ($file in $manifest.files) {', + ' $filePath = Join-Path $stagingDirectory $file.name', + " if ((Get-Item -LiteralPath $filePath).Length -ne $file.size -or (Get-FileHash -Algorithm SHA256 -LiteralPath $filePath).Hash.ToLowerInvariant() -ne $file.sha256.ToLowerInvariant()) { throw ('Integrity check failed for ' + $file.name + '.') }", + '}', + '$driverSource = Join-Path $stagingDirectory "aibridge.sys"', + '$bridgeSource = Join-Path $stagingDirectory "roxy-kernel-bridge.exe"', + '$certificateSource = Join-Path $stagingDirectory "aibridge-test.cer"', + '$certificate = New-Object Security.Cryptography.X509Certificates.X509Certificate2($certificateSource)', + "if ($certificate.Thumbprint.ToLowerInvariant() -ne $expectedSignerThumbprint) { throw 'Certificate thumbprint mismatch.' }", + '$signature = Get-AuthenticodeSignature -LiteralPath $driverSource', + "if (-not $signature.SignerCertificate -or $signature.SignerCertificate.Thumbprint.ToLowerInvariant() -ne $expectedSignerThumbprint) { throw 'Driver signer mismatch.' }", + `$serviceKey = ${powershellLiteral(`Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\${SERVICE_NAME}`)}`, + '$service = Get-ItemProperty -LiteralPath $serviceKey -ErrorAction SilentlyContinue', 'if ($service) {', + ` $registeredPath = [Environment]::ExpandEnvironmentVariables([string]$service.ImagePath).Trim('"')`, + " if ($registeredPath.StartsWith('\\??\\')) { $registeredPath = $registeredPath.Substring(4) }", + " if ($registeredPath.StartsWith('\\\\?\\')) { $registeredPath = $registeredPath.Substring(4) }", + " if ([IO.Path]::GetFullPath($registeredPath) -ne [IO.Path]::GetFullPath($driverTarget)) { throw 'Refusing to replace an AIBridge service not owned by Roxy.' }", ` & sc.exe stop ${SERVICE_NAME} | Out-Null`, - ' Start-Sleep -Seconds 1', - ` & sc.exe delete ${SERVICE_NAME} | Out-Null`, - ' if ($LASTEXITCODE -ne 0 -and $LASTEXITCODE -ne 1060) { exit $LASTEXITCODE }', - ' Start-Sleep -Seconds 1', + " for ($attempt = 0; $attempt -lt 30 -and (Get-Service -Name ${SERVICE_NAME} -ErrorAction SilentlyContinue).Status -ne 'Stopped'; $attempt++) { Start-Sleep -Milliseconds 500 }", + " if ((Get-Service -Name ${SERVICE_NAME} -ErrorAction SilentlyContinue).Status -ne 'Stopped') { throw 'The AIBridge service did not stop.' }", '}', - 'New-Item -ItemType Directory -Force -Path $installDirectory | Out-Null', 'Copy-Item -Force -LiteralPath $driverSource -Destination $driverTarget', 'Copy-Item -Force -LiteralPath $bridgeSource -Destination $bridgeTarget', - "if ((Get-FileHash -Algorithm SHA256 $driverTarget).Hash.ToLowerInvariant() -ne $expectedDriverHash) { throw 'Installed driver hash mismatch.' }", - "if ((Get-FileHash -Algorithm SHA256 $bridgeTarget).Hash.ToLowerInvariant() -ne $expectedBridgeHash) { throw 'Installed bridge hash mismatch.' }", - `& sc.exe create ${SERVICE_NAME} 'type=' 'kernel' 'start=' 'demand' 'binPath=' $driverTarget | Out-Null`, - 'if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }', + '$previousInstallManifest = if (Test-Path $installManifestPath) { Get-Content -Raw -LiteralPath $installManifestPath | ConvertFrom-Json } else { $null }', + '$rootCertificateManagedByRoxy = [bool]$previousInstallManifest.rootCertificateManagedByRoxy -or -not (Test-Path ("Cert:\\LocalMachine\\Root\\" + $expectedSignerThumbprint))', + '$publisherCertificateManagedByRoxy = [bool]$previousInstallManifest.publisherCertificateManagedByRoxy -or -not (Test-Path ("Cert:\\LocalMachine\\TrustedPublisher\\" + $expectedSignerThumbprint))', + '@{ version = $expectedVersion; signerThumbprint = $expectedSignerThumbprint; rootCertificateManagedByRoxy = $rootCertificateManagedByRoxy; publisherCertificateManagedByRoxy = $publisherCertificateManagedByRoxy } | ConvertTo-Json | Set-Content -Encoding UTF8 -LiteralPath $installManifestPath', + 'try {', + ' if ($rootCertificateManagedByRoxy) { Import-Certificate -FilePath $certificateSource -CertStoreLocation Cert:\\LocalMachine\\Root | Out-Null }', + ' if ($publisherCertificateManagedByRoxy) { Import-Certificate -FilePath $certificateSource -CertStoreLocation Cert:\\LocalMachine\\TrustedPublisher | Out-Null }', + '} catch {', + ' if ($rootCertificateManagedByRoxy) { Remove-Item -Force ("Cert:\\LocalMachine\\Root\\" + $expectedSignerThumbprint) -ErrorAction SilentlyContinue }', + ' if ($publisherCertificateManagedByRoxy) { Remove-Item -Force ("Cert:\\LocalMachine\\TrustedPublisher\\" + $expectedSignerThumbprint) -ErrorAction SilentlyContinue }', + ' throw', + '}', + 'if (-not $service) {', + ` & sc.exe create ${SERVICE_NAME} 'type=' 'kernel' 'start=' 'demand' 'binPath=' $driverTarget | Out-Null`, + ' if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }', + '}', startDriver ? `& sc.exe start ${SERVICE_NAME} | Out-Null` : 'exit 0', - startDriver ? 'exit $LASTEXITCODE' : '' + startDriver ? 'exit $LASTEXITCODE' : '', + '} finally { Remove-Item -Recurse -Force $stagingDirectory -ErrorAction SilentlyContinue }' ].join('; ') } function uninstallDriverScript(): string { return [ "$ErrorActionPreference = 'Stop'", - `$service = Get-Service -Name ${powershellLiteral(SERVICE_NAME)} -ErrorAction SilentlyContinue`, + `$installManifestPath = ${powershellLiteral(INSTALL_MANIFEST_PATH)}`, + `$serviceKey = ${powershellLiteral(`Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\${SERVICE_NAME}`)}`, + '$service = Get-ItemProperty -LiteralPath $serviceKey -ErrorAction SilentlyContinue', 'if ($service) {', + ` $registeredPath = [Environment]::ExpandEnvironmentVariables([string]$service.ImagePath).Trim('"')`, + " if ($registeredPath.StartsWith('\\??\\')) { $registeredPath = $registeredPath.Substring(4) }", + " if ($registeredPath.StartsWith('\\\\?\\')) { $registeredPath = $registeredPath.Substring(4) }", + ` if ([IO.Path]::GetFullPath($registeredPath) -ne [IO.Path]::GetFullPath(${powershellLiteral(DRIVER_PATH)})) { throw 'Refusing to remove an AIBridge service not owned by Roxy.' }`, ` & sc.exe stop ${SERVICE_NAME} | Out-Null`, ' Start-Sleep -Seconds 1', ` & sc.exe delete ${SERVICE_NAME} | Out-Null`, @@ -184,9 +241,12 @@ function uninstallDriverScript(): string { '}', `Remove-Item -Force ${powershellLiteral(DRIVER_PATH)} -ErrorAction SilentlyContinue`, `Remove-Item -Force ${powershellLiteral(BRIDGE_PATH)} -ErrorAction SilentlyContinue`, + '$installManifest = if (Test-Path $installManifestPath) { Get-Content -Raw -LiteralPath $installManifestPath | ConvertFrom-Json } else { $null }', + `if ($installManifest.signerThumbprint -eq ${powershellLiteral(SIGNER_THUMBPRINT)} -and $installManifest.rootCertificateManagedByRoxy) { Remove-Item -Force ${powershellLiteral(`Cert:\LocalMachine\Root\${SIGNER_THUMBPRINT}`)} -ErrorAction SilentlyContinue }`, + `if ($installManifest.signerThumbprint -eq ${powershellLiteral(SIGNER_THUMBPRINT)} -and $installManifest.publisherCertificateManagedByRoxy) { Remove-Item -Force ${powershellLiteral(`Cert:\LocalMachine\TrustedPublisher\${SIGNER_THUMBPRINT}`)} -ErrorAction SilentlyContinue }`, `if (Test-Path ${powershellLiteral(DRIVER_PATH)}) { throw 'The installed driver file could not be removed.' }`, `if (Test-Path ${powershellLiteral(BRIDGE_PATH)}) { throw 'The installed bridge file could not be removed.' }`, - `if (Test-Path ${powershellLiteral(PRIVILEGED_INSTALL_DIR)}) { Remove-Item -Force ${powershellLiteral(PRIVILEGED_INSTALL_DIR)} -ErrorAction SilentlyContinue }` + `if (Test-Path ${powershellLiteral(PRIVILEGED_INSTALL_DIR)}) { Remove-Item -Recurse -Force ${powershellLiteral(PRIVILEGED_INSTALL_DIR)} -ErrorAction SilentlyContinue }` ].join('; ') } @@ -198,7 +258,18 @@ async function getDriverServiceState(): Promise { '-NoProfile', '-NonInteractive', '-Command', - `$service = Get-Service -Name ${powershellLiteral(SERVICE_NAME)} -ErrorAction SilentlyContinue; if ($service) { [int]$service.Status } else { 0 }` + [ + `$serviceKey = ${powershellLiteral(`Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\${SERVICE_NAME}`)}`, + '$serviceConfig = Get-ItemProperty -LiteralPath $serviceKey -ErrorAction SilentlyContinue', + 'if (-not $serviceConfig) { 0; exit }', + `$expectedPath = ${powershellLiteral(DRIVER_PATH)}`, + `$registeredPath = [Environment]::ExpandEnvironmentVariables([string]$serviceConfig.ImagePath).Trim('"')`, + "if ($registeredPath.StartsWith('\\??\\')) { $registeredPath = $registeredPath.Substring(4) }", + "if ($registeredPath.StartsWith('\\\\?\\')) { $registeredPath = $registeredPath.Substring(4) }", + 'if ([IO.Path]::GetFullPath($registeredPath) -ne [IO.Path]::GetFullPath($expectedPath)) { 9; exit }', + `$service = Get-Service -Name ${powershellLiteral(SERVICE_NAME)} -ErrorAction SilentlyContinue`, + 'if ($service) { [int]$service.Status } else { 0 }' + ].join('; ') ], 5_000 ) @@ -329,70 +400,49 @@ export async function getKernelStatus(): Promise { } } -async function checkoutPinnedRepository(steps: string[]): Promise { - steps.push('Downloading the pinned kernel-tools source...') - await rm(REPOSITORY_DIR, { recursive: true, force: true }) - mkdirSync(INSTALL_DIR, { recursive: true }) - await run('git.exe', ['clone', '--no-checkout', REPOSITORY_URL, REPOSITORY_DIR], 120_000) - await run('git.exe', ['-C', REPOSITORY_DIR, 'checkout', '--detach', REPOSITORY_REVISION], 30_000) +async function fetchRelease(): Promise { + try { + const response = app.isReady() ? await electronNet.fetch(RELEASE_URL) : await fetch(RELEASE_URL) + if (response.ok) return response + } catch { + // Fall through to Node's network stack for environments where Chromium is blocked. + } + return fetch(RELEASE_URL) } export async function setKernelAgentAccess(enable: boolean): Promise { return runMutation(() => setKernelAgentAccessImpl(enable)) } -async function verifyBuiltDriverSignature(): Promise { - const { stdout } = await run( - systemExecutable('WindowsPowerShell\\v1.0\\powershell.exe'), - [ - '-NoProfile', - '-NonInteractive', - '-Command', - `(Get-AuthenticodeSignature -LiteralPath ${powershellLiteral(BUILT_DRIVER_PATH)}).Status` - ], - 10_000 - ) - if (stdout.trim() !== 'Valid') { - throw new Error( - 'The built kernel driver does not have a valid embedded signature. Windows test-signing mode still requires a signed driver, so installation was stopped before changing your system.' - ) - } -} - -async function buildArtifacts(steps: string[]): Promise { - const powershell = systemExecutable('WindowsPowerShell\\v1.0\\powershell.exe') - - steps.push('Building the Windows kernel driver...') - await run( - powershell, - [ - '-NoProfile', - '-NonInteractive', - '-ExecutionPolicy', - 'Bypass', - '-File', - path.join(REPOSITORY_DIR, 'driver', 'build.ps1') - ], - 10 * 60_000 - ) - - steps.push('Building the MCP bridge...') - await run( - powershell, - [ - '-NoProfile', - '-NonInteractive', - '-ExecutionPolicy', - 'Bypass', - '-File', - path.join(REPOSITORY_DIR, 'bridge', 'build.ps1') - ], - 10 * 60_000 - ) +async function downloadPinnedRelease(steps: string[]): Promise { + steps.push(`Downloading pinned Kernel Tools ${RELEASE_TAG}...`) + await rm(ARCHIVE_PATH, { force: true }) + mkdirSync(INSTALL_DIR, { recursive: true }) - if (!existsSync(BUILT_DRIVER_PATH) || !existsSync(BUILT_BRIDGE_PATH)) { - throw new Error('The external build completed without producing both required binaries.') + const response = await fetchRelease() + if (!response.ok) throw new Error(`Kernel Tools download failed (${response.status}).`) + const contentLength = Number(response.headers.get('content-length') || 0) + if (contentLength > MAX_RELEASE_BYTES) throw new Error('The Kernel Tools download is too large.') + if (!response.body) throw new Error('The Kernel Tools download returned no body.') + const archive = await open(ARCHIVE_PATH, 'wx') + let downloadedBytes = 0 + try { + for await (const chunk of response.body) { + downloadedBytes += chunk.byteLength + if (downloadedBytes > MAX_RELEASE_BYTES) { + throw new Error('The Kernel Tools download is too large.') + } + await archive.write(chunk) + } + } finally { + await archive.close() + } + if (downloadedBytes === 0) throw new Error('The Kernel Tools download has an invalid size.') + if (sha256(ARCHIVE_PATH) !== RELEASE_SHA256) { + throw new Error('The Kernel Tools download failed its pinned SHA-256 integrity check.') } + + steps.push(`Verified the pinned Kernel Tools ${RELEASE_TAG} archive.`) } async function installKernelToolsImpl(): Promise { @@ -404,10 +454,7 @@ async function installKernelToolsImpl(): Promise { try { await removeKernelMcpServers() - await checkoutPinnedRepository(steps) - await buildArtifacts(steps) - await verifyBuiltDriverSignature() - steps.push('Verified the kernel driver signature.') + await downloadPinnedRelease(steps) const testSigningWasEnabled = await checkTestSigning() if (!testSigningWasEnabled) { @@ -424,11 +471,7 @@ async function installKernelToolsImpl(): Promise { '-NoProfile', '-NonInteractive', '-Command', - installedDriverScript( - sha256(BUILT_DRIVER_PATH), - sha256(BUILT_BRIDGE_PATH), - testSigningWasEnabled - ) + installedDriverScript(testSigningWasEnabled) ]) const driverState = await getDriverServiceState() if (driverState === 'running') { @@ -469,6 +512,11 @@ async function startKernelDriverImpl(): Promise { return { ok: false, error: 'Install kernel tools before starting the driver.', steps } } + const state = await getDriverServiceState() + if (state === 'unknown' || state === 'not-installed') { + return { ok: false, error: 'The AIBridge service is missing or is not owned by Roxy.', steps } + } + try { steps.push('Requesting administrator approval to start the driver...') await runElevated(systemExecutable('sc.exe'), ['start', SERVICE_NAME]) @@ -517,6 +565,7 @@ async function uninstallKernelToolsImpl(disableSigning: boolean): Promise } } @@ -44,11 +60,6 @@ for (const key of [ assert.ok(kernelStrings[key], `settings.kernel.${key} resolves`) } -const kernelService = readFileSync(path.join(process.cwd(), 'src/main/services/kernel.ts'), 'utf8') -assert.match(kernelService, /Get-AuthenticodeSignature/) -assert.match(kernelService, /RoxyKernelToolsAIBridge/) -assert.match(kernelService, /Another Kernel Tools operation is already in progress/) - const kernelIpc = readFileSync(path.join(process.cwd(), 'src/main/ipc/kernel.ts'), 'utf8') assert.match(kernelIpc, /event\.senderFrame !== mainWindow\.webContents\.mainFrame/) assert.match(kernelIpc, /typeof enable !== 'boolean'/) From 4f23d2e3e98dd543d6ec3d0a02ed1516e17f19b6 Mon Sep 17 00:00:00 2001 From: Freddy Diaz <45578633+FreddyJD@users.noreply.github.com> Date: Mon, 14 Sep 2026 06:06:45 -0400 Subject: [PATCH 4/5] style(kernel): format release installer Co-authored-by: Roxy <299891354+roxy-commits@users.noreply.github.com> --- test/kernel.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/test/kernel.ts b/test/kernel.ts index 94d7cef..45b672f 100644 --- a/test/kernel.ts +++ b/test/kernel.ts @@ -23,10 +23,7 @@ assert.equal(CHANNELS.kernelUninstall, 'kernel:uninstall') assert.equal(CHANNELS.kernelToggleTestSigning, 'kernel:toggleTestSigning') assert.equal(nonWindowsStatus.isWindows, false) -const kernelService = readFileSync( - path.join(process.cwd(), 'src/main/services/kernel.ts'), - 'utf8' -) +const kernelService = readFileSync(path.join(process.cwd(), 'src/main/services/kernel.ts'), 'utf8') assert.match(kernelService, /RELEASE_TAG = 'v\d+\.\d+\.\d+'/) assert.match(kernelService, /RELEASE_SHA256 = '[0-9a-f]{64}'/) assert.match(kernelService, /RELEASE_COMMIT = '[0-9a-f]{40}'/) From 5d886566213dfb11eff0f9e7a4e6f5a2711f1f38 Mon Sep 17 00:00:00 2001 From: Freddy Diaz <45578633+FreddyJD@users.noreply.github.com> Date: Mon, 14 Sep 2026 07:02:23 -0400 Subject: [PATCH 5/5] fix(kernel): pin hardened signed release Update Kernel Tools to v1.0.2 and require the pinned bridge executable hash before privileged installation. Co-authored-by: Roxy <299891354+roxy-commits@users.noreply.github.com> --- src/main/services/kernel.ts | 12 ++++++++---- test/kernel.ts | 2 ++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/main/services/kernel.ts b/src/main/services/kernel.ts index ea1e1af..2fb61f2 100644 --- a/src/main/services/kernel.ts +++ b/src/main/services/kernel.ts @@ -11,12 +11,13 @@ import { disposeConnection } from './mcp' const execFileAsync = promisify(execFile) -const RELEASE_TAG = 'v1.0.1' +const RELEASE_TAG = 'v1.0.2' const RELEASE_ASSET = 'kernel-tools-windows-x64.zip' const RELEASE_URL = `https://github.com/roxy-gg/kernel-tools/releases/download/${RELEASE_TAG}/${RELEASE_ASSET}` -const RELEASE_SHA256 = 'a14ab9b420923f3de017923dacae87ebf72ac80def8f9ce568df3ebdaeb7f282' -const RELEASE_COMMIT = '88dd68313db9c08e6ce419426d545e02066f5d91' -const SIGNER_THUMBPRINT = '8106e5e23fc13860575a61c16e391ad44e174d46' +const RELEASE_SHA256 = 'c3e0b19f69c13d4bb4ffb55987f13b3f4e6232a92245645e98acccf5cee2c4f5' +const RELEASE_COMMIT = '62126a86e335caee1a04fdfc8db08986638112d7' +const SIGNER_THUMBPRINT = 'f1b5ab3fa912b6bc5ae30301dd36b64708f293cf' +const BRIDGE_SHA256 = '48e24b722c77394416801bc1c6a19dcff109561942fe03f6590056b2b730563a' const MAX_RELEASE_BYTES = 5 * 1024 * 1024 const SERVICE_NAME = 'RoxyKernelToolsAIBridge' const MCP_SERVER_ID = 'roxy-kernel-tools' @@ -148,6 +149,7 @@ function installedDriverScript(startDriver: boolean): string { `$expectedVersion = ${powershellLiteral(RELEASE_TAG)}`, `$expectedCommit = ${powershellLiteral(RELEASE_COMMIT)}`, `$expectedSignerThumbprint = ${powershellLiteral(SIGNER_THUMBPRINT)}`, + `$expectedBridgeHash = ${powershellLiteral(BRIDGE_SHA256)}`, `$installDirectory = ${powershellLiteral(PRIVILEGED_INSTALL_DIR)}`, `$driverTarget = ${powershellLiteral(DRIVER_PATH)}`, `$bridgeTarget = ${powershellLiteral(BRIDGE_PATH)}`, @@ -184,6 +186,8 @@ function installedDriverScript(startDriver: boolean): string { '}', '$driverSource = Join-Path $stagingDirectory "aibridge.sys"', '$bridgeSource = Join-Path $stagingDirectory "roxy-kernel-bridge.exe"', + '$bridgeRecord = @($manifest.files | Where-Object { $_.name -eq "roxy-kernel-bridge.exe" })', + "if ($bridgeRecord.Count -ne 1 -or $bridgeRecord[0].sha256.ToLowerInvariant() -ne $expectedBridgeHash -or (Get-FileHash -Algorithm SHA256 -LiteralPath $bridgeSource).Hash.ToLowerInvariant() -ne $expectedBridgeHash) { throw 'Bridge executable hash mismatch.' }", '$certificateSource = Join-Path $stagingDirectory "aibridge-test.cer"', '$certificate = New-Object Security.Cryptography.X509Certificates.X509Certificate2($certificateSource)', "if ($certificate.Thumbprint.ToLowerInvariant() -ne $expectedSignerThumbprint) { throw 'Certificate thumbprint mismatch.' }", diff --git a/test/kernel.ts b/test/kernel.ts index 45b672f..6437ed7 100644 --- a/test/kernel.ts +++ b/test/kernel.ts @@ -28,6 +28,8 @@ assert.match(kernelService, /RELEASE_TAG = 'v\d+\.\d+\.\d+'/) assert.match(kernelService, /RELEASE_SHA256 = '[0-9a-f]{64}'/) assert.match(kernelService, /RELEASE_COMMIT = '[0-9a-f]{40}'/) assert.match(kernelService, /SIGNER_THUMBPRINT = '[0-9a-f]{40}'/) +assert.match(kernelService, /BRIDGE_SHA256 = '[0-9a-f]{64}'/) +assert.match(kernelService, /Bridge executable hash mismatch/) assert.match(kernelService, /Get-AuthenticodeSignature/) assert.match(kernelService, /IO\.FileShare\]::None/) assert.match(kernelService, /isSupportedPlatform/)