fix(code-runtime): separate model environment from native startup paths

This commit is contained in:
Tianyi Cui committed 2026-09-12 15:03:01 +08:00
1 parent a6f7eeadc6
commit 98d3af0683
12 files changed
+46 -14

No files matched your search

@@ -20,6 +20,9 @@ function processState(): ProgramProcess { return { env: { FIXTURE_SECRET: 'test'
it('clears process environment, dispatches a binding reply and flushes the terminal frame', async () => {
const { child, host } = endpoints()
const state = processState()
const nativeEnvironment = state.env
nativeEnvironment.SystemRoot = 'C:\\Windows'
nativeEnvironment.PATH = '/native/bin'
const messages: Record<string, unknown>[] = []
const peer = new JsonChannel(host, 4096, (raw) => {
const message = raw as Record<string, unknown>
@@ -30,6 +33,9 @@ it('clears process environment, dispatches a binding reply and flushes the termi
onTestFinished(() => { peer.close() })
await runNodeMain(child, 4096, state)
expect(state.env).toEqual({})
expect(state.env).not.toBe(nativeEnvironment)
expect(Object.getPrototypeOf(state.env)).toBeNull()
expect(nativeEnvironment).toEqual({ SystemRoot: 'C:\\Windows', PATH: '/native/bin' })
expect(state.exitCode).toBeUndefined()
expect(decodeCodeJsonWire(messages.find(message => message.type === 'done')?.value)).toBe(42)
})
@@ -11,7 +11,7 @@ import { decodeCodeJsonWire, encodeCodeJsonWire } from '../src/json-wire.ts'
it('boots an unbuilt source closure outside the workspace and exchanges tool replies', async () => {
const directory = await mkdtemp(join(tmpdir(), 'dsh-node-source-'))
onTestFinished(async () => { await rm(directory, { recursive: true, force: true }) })
for (const file of ['process.ts', 'bootstrap.ts', 'channel.ts', 'json-wire.ts', 'output-json.ts', 'protocol.ts']) {
for (const file of ['process.ts', 'bootstrap.ts', 'channel.ts', 'json-wire.ts', 'output-json.ts', 'protocol.ts', 'environment.ts']) {
await copyFile(new URL(`../src/${file}`, import.meta.url), join(directory, file))
}
const source = `import {Socket} from 'node:net';import {runNodeMain} from ${JSON.stringify(pathToFileURL(join(directory, 'process.ts')).href)};await runNodeMain(new Socket({fd:7,readable:true,writable:true}),100000,process);`
@@ -33,6 +33,23 @@ describe('Node program process', () => {
expect(result.sandbox).toEqual({ mode: 'danger-full-access', denied: false })
})
it('keeps native startup paths available for nested Node creation with an empty model environment', async () => {
const { run } = await setup()
const result = await run({
program: 'const {spawnSync}=await import("node:child_process"); const child=spawnSync(process.execPath,["-e","process.stdout.write(JSON.stringify(Object.keys(process.env)))"],{encoding:"utf8"}); return {env:Object.keys(process.env),status:child.status,error:child.error?.message ?? null,childKeys:JSON.parse(child.stdout || "[]")};',
bindings: [],
})
expect(result.error).toBeUndefined()
const value = result.value as { env: string[]; status: number | null; error: string | null; childKeys: string[] }
expect(value.env).toEqual([])
expect(value.status).toBe(0)
expect(value.error).toBeNull()
const nativeKeys = ['PATH', 'PATHEXT', 'SYSTEMROOT', 'WINDIR']
// CoreFoundation initializes this entry independently when a macOS child starts.
if (process.platform === 'darwin') nativeKeys.push('__CF_USER_TEXT_ENCODING')
expect(value.childKeys.filter(key => !nativeKeys.includes(key.toUpperCase()))).toEqual([])
})
it('returns binding values and preserves typed binding rejection', async () => {
const { run } = await setup()
const result = await run({
@@ -180,7 +197,7 @@ describe('Node program process', () => {
const confine = ctx.sandbox.confine.bind(ctx.sandbox)
const policy = runtime.resolve({ program: '', bindings: [] }).sandboxPolicy
if (policy === undefined || policy.mode === 'danger-full-access') throw new Error('expected confined policy')
const wrapped = confine([process.execPath, '--version'], policy)
const wrapped = confine([process.execPath, '--version'], { ...policy, mode: policy.mode })
const original = wrapped.argv[0]
if (original === undefined) throw new Error('expected sandbox launcher')
const executable = await ctx.subprocess.resolveExecutable(original)