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+<distro> launches as out of scope.
This commit is contained in:
Neil
2026-10-27 16:53:48 -07:00
committed by GitHub
parent 965286939d
commit b930606ee9
3 changed files with 16 additions and 13 deletions
@@ -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 {
@@ -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`)
)
})
@@ -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+<distro> (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'