fix(desktop): grant Windows sandbox access during installation (#46696)

This commit is contained in:
Luke Parker
2026-09-02 12:08:34 +10:00
committed by GitHub
parent 4d0eb97ddf
commit 0b771030ed
6 changed files with 95 additions and 0 deletions
@@ -18,6 +18,20 @@ const channels = [
] as const
for (const channel of channels) {
test(`includes the Windows sandbox permission hook for ${channel.channel}`, async () => {
const previous = process.env.OPENCODE_CHANNEL
process.env.OPENCODE_CHANNEL = channel.channel
try {
const config = (await import(`./electron-builder.config.ts?channel=${channel.channel}`)).default as Configuration
const include = path.join(import.meta.dirname, "resources/windows/installer.nsh")
expect(config.nsis?.include).toBe(include)
expect(await Bun.file(include).exists()).toBe(true)
} finally {
if (previous === undefined) delete process.env.OPENCODE_CHANNEL
else process.env.OPENCODE_CHANNEL = previous
}
})
test(`uses one Linux desktop identity for ${channel.channel}`, async () => {
const previous = process.env.OPENCODE_CHANNEL
process.env.OPENCODE_CHANNEL = channel.channel
@@ -124,6 +124,7 @@ const getBase = (appId: string): Configuration => ({
verifyUpdateCodeSignature: false,
},
nsis: {
include: path.join(packageDir, "resources", "windows", "installer.nsh"),
oneClick: true,
perMachine: false,
installerIcon: `resources/icons/icon.ico`,
+1
View File
@@ -11,6 +11,7 @@
},
"scripts": {
"typecheck": "tsgo -b",
"test": "bun test --timeout 30000",
"dev": "bun ./scripts/dev.ts",
"prebuild": "bun ./scripts/prebuild.ts",
"build": "electron-vite build",
@@ -0,0 +1,6 @@
!macro customInstall
; Chromium's sandbox needs read/execute access to the installed runtime files.
; https://github.com/electron/electron/issues/49143#issuecomment-3618354787
nsExec::ExecToLog '"$SYSDIR\icacls.exe" "$INSTDIR" /grant "*S-1-15-2-2:(OI)(CI)(RX)"'
Pop $0
!macroend
@@ -0,0 +1,69 @@
import { expect, test } from "bun:test"
import { mkdtemp, rm } from "node:fs/promises"
import os from "node:os"
import path from "node:path"
test.skipIf(process.platform !== "win32")(
"installer grants sandbox access without changing unrelated permissions",
async () => {
const { getMakeNsisPath } = await import("app-builder-lib/out/toolsets/windows")
const config = (await import("../electron-builder.config")).default
const compiler = await getMakeNsisPath(config.toolsets?.nsis, config.nsis?.customNsisBinary)
const dir = await mkdtemp(path.join(os.tmpdir(), "opencode-installer-"))
const run = async (cmd: string[]) => {
const result = Bun.spawn(cmd, {
env: { ...process.env, ...compiler.env, OPENCODE_INSTALLER_TEST_ROOT: dir },
stdout: "pipe",
stderr: "pipe",
})
const [stdout, stderr, code] = await Promise.all([
new Response(result.stdout).text(),
new Response(result.stderr).text(),
result.exited,
])
expect(code, `${stdout}\n${stderr}`).toBe(0)
return stdout.trim()
}
const acl = (file: string) =>
run([
"pwsh",
"-NoProfile",
"-NonInteractive",
"-Command",
`(Get-Acl -LiteralPath (Join-Path $env:OPENCODE_INSTALLER_TEST_ROOT '${file}')).Sddl`,
])
try {
await Bun.write(path.join(dir, "app/runtime/icudtl.dat"), "runtime fixture")
await Bun.write(path.join(dir, "unrelated.txt"), "outside the installation")
await run(["icacls.exe", path.join(dir, "app"), "/grant", "*S-1-15-2-999-999-999:(OI)(CI)(F)"])
const parent = await acl(".")
const unrelated = await acl("unrelated.txt")
await Bun.write(
path.join(dir, "installer.nsi"),
`Unicode true
RequestExecutionLevel user
SilentInstall silent
OutFile "${path.join(dir, "installer.exe")}"
!include "${config.nsis?.include}"
Section
StrCpy $INSTDIR "$EXEDIR\\app"
!insertmacro customInstall
SectionEnd
`,
)
await run([compiler.path, path.join(dir, "installer.nsi")])
await run([path.join(dir, "installer.exe")])
const installed = await acl("app/runtime/icudtl.dat")
expect(installed).toContain("(A;ID;0x1200a9;;;S-1-15-2-2)")
expect(installed).toContain("S-1-15-2-999-999-999")
await run([path.join(dir, "installer.exe")])
expect(await acl("app/runtime/icudtl.dat")).toBe(installed)
expect(await acl(".")).toBe(parent)
expect(await acl("unrelated.txt")).toBe(unrelated)
} finally {
await rm(dir, { recursive: true, force: true })
}
},
120_000,
)
+4
View File
@@ -49,6 +49,10 @@
"@opencode-ai/function#test": {
"outputs": []
},
"@opencode-ai/desktop#test": {
"outputs": [],
"cache": false
},
"@opencode-ai/httpapi-codegen#test": {
"outputs": []
},