fix(python): name the interpreter python3 rather than the worker's host path - #423
mutewinter wants to merge 1 commit into
Conversation
…ost path Emscripten's Node glue reads the program name off process.argv[1], which in a worker thread is worker.js's absolute path on the host. CPython received it as argv[0] and as the _ environment variable, so sys.executable and os.environ['_'] disclosed the host install path to the guest, and its length shaped the initial heap layout: at some lengths (200 characters, a pnpm store path with a patch hash) Py_FinalizeEx aborted with gilstate_tss_clear: failed to clear current tstate after the program had run, and every invocation exited 1 with the right output. Passing thisProgram pins what CPython sees.
|
@mutewinter is attempting to deploy a commit to the Vercel Labs Team on Vercel. A member of the Team first needs to authorize it. |
🤖 auto-maintain reviewAutomated, advisory triage for
Review panel: 🟢 low highest severity just-bash maintainer code review: 🟢 low
General code review: 🟢 low
Adversarial security: 🟢 low
Adversarial security (second opinion): 🟢 low
Standard Bash and host portability: 🟢 low
Posted by auto-maintain. This automated code review is advisory; a human maintainer makes the call. |
vercel-labs/just-bash#423 pins the program name, #424 corrects the errnos, #425 names the program in tracebacks; the register, the finding, the decision, and the plan each carry the numbers where they said the fixes were unoffered.
|
#444 (opened 2026-09-20) reaches the same diagnosis independently: |
Problem
The guest sees the host's absolute install path, which is the one class of string the rest of the worker goes to some length to keep out (
sanitizeHostErrorMessage, the protocol-abuse tests).The second consequence is the one that sent me here. In a pnpm workspace with a patched
just-bash, the worker lives atnode_modules/.pnpm/just-bash@3.4.1_patch_hash=<64 hex>/node_modules/just-bash/dist/bundle/chunks/worker.js, 200 characters, and everypython3invocation exited 1 with correct stdout:An agent reads that as its script failing. The
Security violationline is a consequence: Emscripten'sabort()builds aWebAssembly.RuntimeError, and the worker defense blocks theWebAssemblyglobal after CPython loads, so the abort's own error is what gets reported.Cause
Emscripten's Node glue sets
thisProgram = process.argv[1], and in aworker_threadsworker that is the worker script's own path. It reaches CPython twice:callMainprepends it asargv[0], andgetEnvStrings()sets_to it. Nothing inworker.tsoverrides it.The length of that string shapes the initial heap, and at some lengths
Py_FinalizeExaborts ingilstate_tss_clear, meaningpthread_setspecificreturnedEINVAL, which under Emscripten's pthread stub means the key was no longer marked in use. Copying the identicalworker.jsto paths of different lengths, same script, same machine:Holding the path fixed and setting
thisProgramdirectly reproduces it: 7, 16, 50, 100, 150 characters pass; 200 aborts. The script does not matter (passaborts too) and what the script allocates does not matter; only the early heap does. The underlying fault is in the CPython/Emscripten build, not in this repo, but this repo is where the install path becomes the program name.The existing tests cannot see it because a checkout's
src/commands/python3/worker.jspath is short (82 characters here), as is a plainnpm install's.Fix
thisProgram: "python3"in thecreatePythonModuleconfig. The guest seespython3for_, andsys.executablebecomes'', which is what CPython reports for a program name it cannot locate onPATH.Scope
Unchanged:
sys.argv, which the setup code already sets fromscriptPathand the script args.Not addressed, deliberately: the finalization abort itself, which is a CPython-on-Emscripten bug this pin only stops one input from triggering. I did not find any other input that lands in the window; the environment strings are otherwise fixed by the glue.
Tests
python3.env.test.ts:os.environ['_']ispython3andsys.executableis''. Fails before the change with the worker's host path in both. It proves the path no longer reaches the guest; it cannot prove the abort is gone, since that depends on the checkout's path length, which is what the table above is for.Suite: 239 passed, 2 skipped across the 16 python3 and python-scripting files.
Authored with Claude Opus 5