From b930606ee9f2037035813bb556ed3e7371aa9de2 Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Tue, 27 Oct 2026 16:53:48 -0700 Subject: [PATCH] chore(wsl): require the allowlist check in any file that hand-builds a wsl.exe exec (#34615) resolveCommand only checks what it resolves, so a file that also builds its own wsl.exe exec no longer passes the import check on a resolveCommand call alone. Note editor --remote wsl+ launches as out of scope. --- config/scripts/check-process-host-imports.mjs | 13 +++++-------- .../scripts/check-process-host-imports.test.mjs | 15 ++++++++++----- .../process-host/src/wsl-distro-allowlist.ts | 1 + 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/config/scripts/check-process-host-imports.mjs b/config/scripts/check-process-host-imports.mjs index 47010e37106..89cdb485c11 100644 --- a/config/scripts/check-process-host-imports.mjs +++ b/config/scripts/check-process-host-imports.mjs @@ -11,11 +11,10 @@ const PACKAGE_NAME = '@orca/process-host' const PACKAGE_DIRECTORY = 'src/packages/process-host' const PACKAGE_SOURCE = `${PACKAGE_DIRECTORY}/src/` const BASELINE_PATH = 'config/process-host-direct-import-baseline.txt' -// A direct wsl.exe exec bypasses process-host's internal distro allowlist unless it is checked: -// by calling the check itself, or by taking its program from resolveCommand, which checks centrally. +// A file that builds its own wsl.exe exec must check the internal distro allowlist itself; only +// programs taken from resolveCommand are checked centrally, and those never match this pattern. const WSL_EXE_PROGRAM = /['"`]wsl\.exe['"`]|resolveWslExecutablePath\(|buildWslExecArgs\(/ -const WSL_ALLOWLIST_CHECK = - /assertWslDistroAllowed\(|\bresolve(Git|DefaultWsl)?Command\(|resolveDefaultWslCli\(/ +const WSL_ALLOWLIST_CHECK = 'assertWslDistroAllowed(' const SOURCE_PATTERNS = [ '*.{ts,tsx,mts,cts,js,mjs,cjs}', ...['src', 'config', 'tests', 'mobile/src', 'mobile/app', 'mobile/scripts'].map( @@ -110,10 +109,8 @@ export function assessProcessHostImports( } for (const file of direct) { const contents = sources.get(file) ?? '' - if (WSL_EXE_PROGRAM.test(contents) && !WSL_ALLOWLIST_CHECK.test(contents)) { - violations.push( - `${file}: direct wsl.exe exec must call assertWslDistroAllowed or resolve through resolveCommand` - ) + if (WSL_EXE_PROGRAM.test(contents) && !contents.includes(WSL_ALLOWLIST_CHECK)) { + violations.push(`${file}: direct wsl.exe exec must call assertWslDistroAllowed`) } } return { diff --git a/config/scripts/check-process-host-imports.test.mjs b/config/scripts/check-process-host-imports.test.mjs index 0b9818b6e68..094a914a6e9 100644 --- a/config/scripts/check-process-host-imports.test.mjs +++ b/config/scripts/check-process-host-imports.test.mjs @@ -191,7 +191,7 @@ describe('process-host import boundary', () => { }) }) - it('requires the distro allowlist check, or resolveCommand, before any direct wsl.exe exec outside process-host', () => { + it('requires the distro allowlist check before any hand-built wsl.exe exec outside process-host', () => { const exec = "import { execFile } from 'node:child_process'\n" expect( assess( @@ -202,6 +202,8 @@ describe('process-host import boundary', () => { 'src/main/unrelated.ts': `${exec}execFile('git', ['status'])`, 'src/main/built.ts': `${exec}execFile(program, buildWslExecArgs(distro, ['sh']))`, 'src/main/resolved-git.ts': `${exec}const r = resolveGitCommand(args, o)\nspawn(r.binary, r.args)`, + // resolveCommand checks only what it resolves, not a wsl.exe exec built beside it. + 'src/main/mixed.ts': `${exec}const r = resolveCommand('git', a, cwd)\nexecFile('wsl.exe', ['-d', distro])`, 'src/main/comment-only.ts': "import type { ChildProcess } from 'node:child_process'\n// wsl.exe pads output", 'src/main/caller.test.ts': `${exec}execFile('wsl.exe', [])`, @@ -214,14 +216,17 @@ describe('process-host import boundary', () => { 'src/main/unrelated.ts', 'src/main/built.ts', 'src/main/resolved-git.ts', + 'src/main/mixed.ts', 'src/main/comment-only.ts' ] ).violations ).toEqual( - ['src/main/built.ts', 'src/main/resolved.ts', 'src/main/unguarded.ts'].map( - (file) => - `${file}: direct wsl.exe exec must call assertWslDistroAllowed or resolve through resolveCommand` - ) + [ + 'src/main/built.ts', + 'src/main/mixed.ts', + 'src/main/resolved.ts', + 'src/main/unguarded.ts' + ].map((file) => `${file}: direct wsl.exe exec must call assertWslDistroAllowed`) ) }) diff --git a/src/packages/process-host/src/wsl-distro-allowlist.ts b/src/packages/process-host/src/wsl-distro-allowlist.ts index 457ee9026ee..08a3cca20d0 100644 --- a/src/packages/process-host/src/wsl-distro-allowlist.ts +++ b/src/packages/process-host/src/wsl-distro-allowlist.ts @@ -1,4 +1,5 @@ // Internal test-box switch: keep every wsl.exe exec out of distros not on the list. Unset changes nothing. +// Out of scope: \\wsl.localhost file access, and editors given --remote wsl+ (they boot it). export const WSL_DISTRO_ALLOWLIST_ENV = 'ORCA_INTERNAL_WSL_DISTRO_ALLOWLIST' export const WSL_DISTRO_NOT_ALLOWLISTED_CODE = 'ORCA_WSL_DISTRO_NOT_ALLOWLISTED'