close
Skip to content

Commit ab6ada8

Browse files
authored
fix: avoid system path on Windows for cache when user is system OR cache path includes system32 (#9776)
1 parent 2181fd0 commit ab6ada8

6 files changed

Lines changed: 69 additions & 14 deletions

File tree

‎.changeset/easy-items-hang.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"electron-builder": patch
3+
"app-builder-lib": patch
4+
---
5+
6+
fix: avoid system path on windows for cache when user is `system` OR path is `system32`

‎packages/app-builder-lib/src/binDownload.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export async function download(url: string, output: string, checksum?: string |
1414
const downloadedFile = await get.downloadArtifact({
1515
version: "9.9.9",
1616
artifactName: filenameWithExt,
17-
cacheRoot: path.resolve(getCacheDirectory(), "downloads"),
17+
cacheRoot: path.resolve(getCacheDirectory({ allowEnvVarOverride: true }), "downloads"),
1818
cacheMode: ElectronDownloadCacheMode.ReadWrite,
1919
...(checksum != null ? { checksums: { [filenameWithExt]: checksum } } : { unsafelyDisableChecksums: true }),
2020
mirrorOptions: { resolveAssetURL: async () => Promise.resolve(url) },

‎packages/app-builder-lib/src/util/electronGet.ts‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,8 @@ function hashUrlSafe(input: string, length = 6): string {
8686
return out.length >= length ? out.slice(0, length) : out.padStart(length, "0")
8787
}
8888

89-
export function getCacheDirectory(isAvoidSystemOnWindows = false, allowEnvVarOverride = true): string {
89+
export function getCacheDirectory(options: { isAvoidSystemOnWindows?: boolean; allowEnvVarOverride: boolean }): string {
90+
const { isAvoidSystemOnWindows = true, allowEnvVarOverride } = options
9091
const env = process.env.ELECTRON_BUILDER_CACHE?.trim()
9192
if (allowEnvVarOverride && env && path.parse(env).root) {
9293
return env
@@ -102,6 +103,7 @@ export function getCacheDirectory(isAvoidSystemOnWindows = false, allowEnvVarOve
102103
if (platform === "win32") {
103104
const localAppData = process.env.LOCALAPPDATA?.trim()
104105
const username = process.env.USERNAME?.trim()?.toLowerCase()
106+
// https://github.com/electron-userland/electron-builder/issues/1164
105107
const isSystemUser = isAvoidSystemOnWindows && (localAppData?.toLowerCase()?.includes("\\windows\\system32\\") || username === "system")
106108
if (!localAppData || isSystemUser) {
107109
return path.join(os.tmpdir(), `${appName}-cache`)
@@ -365,7 +367,7 @@ export async function downloadBuilderToolset(options: {
365367
const fullUrl = overrideUrl ? `${overrideUrl}/${filenameWithExt}` : `${baseUrl}${releaseName}/${filenameWithExt}`
366368
const suffix = hashUrlSafe(fullUrl, 5)
367369
const folderName = `${filenameWithExt.replace(/\.(tar\.gz|tgz|zip|7z)$/, "")}-${suffix}`
368-
const extractDir = path.join(getCacheDirectory(), releaseName, folderName)
370+
const extractDir = path.join(getCacheDirectory({ allowEnvVarOverride: true }), releaseName, folderName)
369371

370372
// Use resolveAssetURL so @electron/get's ELECTRON_MIRROR env var check cannot override
371373
// the builder-binaries URL we've already resolved (see getArtifactRemoteURL in @electron/get).
@@ -376,7 +378,7 @@ export async function downloadBuilderToolset(options: {
376378
const config: ElectronDownloadRequest & ElectronDownloadRequestOptions & { isGeneric: true } = {
377379
version: "9.9.9", // must be >1.3.2 to bypass @electron/get validation shortcut
378380
artifactName: filenameWithExt,
379-
cacheRoot: path.resolve(getCacheDirectory(), "downloads"),
381+
cacheRoot: path.resolve(getCacheDirectory({ allowEnvVarOverride: true }), "downloads"),
380382
cacheMode: resolveCacheMode(),
381383
...(checksums != null ? { checksums } : { unsafelyDisableChecksums: true }),
382384
mirrorOptions,
@@ -447,7 +449,7 @@ export async function downloadElectronArtifact(options: ArtifactDownloadOptions)
447449

448450
const suffix = hashUrlSafe(JSON.stringify(artifactConfig), 5)
449451
const folderName = `${artifactName}-v${version}-${platform}-${arch}-${suffix}`
450-
const extractDir = path.join(getCacheDirectory(), `${artifactName}-v${version}`, folderName)
452+
const extractDir = path.join(getCacheDirectory({ allowEnvVarOverride: true }), `${artifactName}-v${version}`, folderName)
451453

452454
return downloadAndExtract(artifactConfig, extractDir, artifactName)
453455
}

‎packages/electron-builder/src/cli/clear-cache.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { createInterface } from "readline/promises"
55
import * as path from "path"
66

77
export async function clearCache(): Promise<void> {
8-
const cacheDir = getCacheDirectory(false, false)
8+
const cacheDir = getCacheDirectory({ isAvoidSystemOnWindows: false, allowEnvVarOverride: false })
99

1010
if (cacheDir === path.parse(cacheDir).root) {
1111
log.error({ cacheDir }, "cache directory resolves to a filesystem root — aborting")

‎test/src/cliTest.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ describe("clearCache", () => {
7171

7272
test("calls getCacheDirectory with isAvoidSystemOnWindows=false, allowEnvVarOverride=false", async () => {
7373
await clearCache()
74-
expect(getCacheDirectory).toHaveBeenCalledWith(false, false)
74+
expect(getCacheDirectory).toHaveBeenCalledWith({ isAvoidSystemOnWindows: false, allowEnvVarOverride: false })
7575
})
7676

7777
test("deletes cache dir when it exists and user confirms", async () => {

‎test/src/electronGetTest.ts‎

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,17 @@ describe("getCacheDirectory", () => {
2626

2727
test("returns ELECTRON_BUILDER_CACHE when set", ({ expect }) => {
2828
vi.stubEnv("ELECTRON_BUILDER_CACHE", "/custom/cache")
29-
expect(getCacheDirectory()).toBe("/custom/cache")
29+
expect(getCacheDirectory({ allowEnvVarOverride: true })).toBe("/custom/cache")
3030
})
3131

3232
test("trims whitespace from ELECTRON_BUILDER_CACHE", ({ expect }) => {
3333
vi.stubEnv("ELECTRON_BUILDER_CACHE", " /padded/path ")
34-
expect(getCacheDirectory()).toBe("/padded/path")
34+
expect(getCacheDirectory({ allowEnvVarOverride: true })).toBe("/padded/path")
3535
})
3636

3737
test("returns platform-appropriate default when env var is absent", ({ expect }) => {
3838
vi.stubEnv("ELECTRON_BUILDER_CACHE", "")
39-
const result = getCacheDirectory()
39+
const result = getCacheDirectory({ allowEnvVarOverride: true })
4040
expect(typeof result).toBe("string")
4141
expect(result.length).toBeGreaterThan(0)
4242
if (process.platform === "darwin") {
@@ -55,19 +55,66 @@ describe("getCacheDirectory", () => {
5555
}
5656
vi.stubEnv("ELECTRON_BUILDER_CACHE", "")
5757
vi.stubEnv("XDG_CACHE_HOME", "/xdg/cache")
58-
expect(getCacheDirectory()).toBe("/xdg/cache/electron-builder")
58+
expect(getCacheDirectory({ allowEnvVarOverride: true })).toBe("/xdg/cache/electron-builder")
5959
})
6060

61-
test("isAvoidSystemOnWindows falls back to tmpdir for system users", ({ expect }) => {
61+
test("falls back to tmpdir when LOCALAPPDATA is absent on Windows", ({ expect }) => {
6262
if (process.platform !== "win32") {
6363
expect(true).toBe(true)
6464
return
6565
}
66-
vi.stubEnv("ELECTRON_BUILDER_CACHE", "")
6766
vi.stubEnv("LOCALAPPDATA", "")
68-
const result = getCacheDirectory(true)
67+
const result = getCacheDirectory({ isAvoidSystemOnWindows: true, allowEnvVarOverride: false })
68+
expect(result).toContain(os.tmpdir())
69+
})
70+
71+
test("allowEnvVarOverride:false ignores ELECTRON_BUILDER_CACHE even when set", ({ expect }) => {
72+
vi.stubEnv("ELECTRON_BUILDER_CACHE", "/custom/cache")
73+
const result = getCacheDirectory({ allowEnvVarOverride: false })
74+
expect(result).not.toBe("/custom/cache")
75+
expect(result).toContain("electron-builder")
76+
})
77+
78+
test("ignores ELECTRON_BUILDER_CACHE when value has no filesystem root (relative path)", ({ expect }) => {
79+
vi.stubEnv("ELECTRON_BUILDER_CACHE", "relative/path/no-root")
80+
const result = getCacheDirectory({ allowEnvVarOverride: true })
81+
expect(result).not.toBe("relative/path/no-root")
82+
expect(result).toContain("electron-builder")
83+
})
84+
85+
test("falls back to tmpdir when USERNAME is 'system' (isAvoidSystemOnWindows defaults to true)", ({ expect }) => {
86+
if (process.platform !== "win32") {
87+
expect(true).toBe(true)
88+
return
89+
}
90+
vi.stubEnv("LOCALAPPDATA", "C:\\Users\\system\\AppData\\Local")
91+
vi.stubEnv("USERNAME", "system")
92+
const result = getCacheDirectory({ allowEnvVarOverride: false })
6993
expect(result).toContain(os.tmpdir())
7094
})
95+
96+
test("falls back to tmpdir when LOCALAPPDATA path contains \\windows\\system32\\", ({ expect }) => {
97+
if (process.platform !== "win32") {
98+
expect(true).toBe(true)
99+
return
100+
}
101+
vi.stubEnv("LOCALAPPDATA", "C:\\Windows\\System32\\config\\systemprofile\\AppData\\Local")
102+
vi.stubEnv("USERNAME", "not-system")
103+
const result = getCacheDirectory({ allowEnvVarOverride: false })
104+
expect(result).toContain(os.tmpdir())
105+
})
106+
107+
test("isAvoidSystemOnWindows:false does not fall back to tmpdir for USERNAME=system", ({ expect }) => {
108+
if (process.platform !== "win32") {
109+
expect(true).toBe(true)
110+
return
111+
}
112+
vi.stubEnv("LOCALAPPDATA", "C:\\Users\\system\\AppData\\Local")
113+
vi.stubEnv("USERNAME", "system")
114+
const result = getCacheDirectory({ isAvoidSystemOnWindows: false, allowEnvVarOverride: false })
115+
expect(result).not.toContain(os.tmpdir())
116+
expect(result).toContain("electron-builder")
117+
})
71118
})
72119

73120
describe("getBinariesMirrorUrl", () => {

0 commit comments

Comments
 (0)