Upstream issue: nodejs/node#63739
When the object passed to vm.createContext() is a Proxy, properties of the
contextified global are readable, listed by getOwnPropertyNames, and have a
full own-property descriptor — yet in, Object.prototype.hasOwnProperty,
Object.hasOwn, and Reflect.has all return false for them.
- Regressed in: v26.3.0
- Last good: v26.2.0 (v22.x also fine)
- Cause (bisected): nodejs/node#63549 —
PropertyDefinerCallbacknow returnsIntercepted::kYes(correct, fixes the #52634 double-define), so properties live only on the sandbox Proxy. ButPropertyQueryCallbackresolveshas-type queries viaHasRealNamedProperty, which cannot see through aJSProxy. Pre-26.3.0 the double-define masked this: the query found the duplicate on the real context global. - A plain-object sandbox (
vm.createContext({})) is not affected.
node repro.js # exit 0 = consistent, exit 1 = regressionObserved output:
| check | v22.22.3 | v26.2.0 | v26.3.0 |
|---|---|---|---|
foo (read) |
42 | 42 | 42 |
getOwnPropertyDescriptor |
descriptor | descriptor | descriptor |
getOwnPropertyNames includes |
true | true | true |
hasOwnProperty |
true | true | false |
Object.hasOwn |
true | true | false |
"foo" in globalThis |
true | true | false |
Reflect.has |
true | true | false |
jest-environment-node (all Jest 29.x/30.x) passes a Proxy (GlobalProxy) as
the vm sandbox, so inside any Jest test on v26.3.0 every copied node global is
affected — e.g. 'setTimeout' in globalThis is false. Worst symptom:
@sinonjs/fake-timers trusts hasOwnProperty at install time, so
jest.useRealTimers() deletes setTimeout instead of restoring it.
cd jest-impact
npm install
npx jest # passes on <= 26.2.0, both tests fail on 26.3.0repro.js— minimal pure-Node repro, doubles as a bisect test (exit code:git bisect run node repro.js)jest-impact/— demonstrates the downstream Jest fake-timers breakage