Summary
ecma_op_to_complete_property_descriptor does not apply the default values required by ECMA-262 CompletePropertyDescriptor. It ensures that a descriptor is a data or accessor descriptor, but it does not mark missing writable, enumerable, and configurable attributes as defined with value false.
As a result, Proxy getOwnPropertyDescriptor invariant checks can approve a trap result that should be rejected. The returned descriptor shown to JavaScript is completed later, but the internal invariant checks have already run on incomplete flags.
Security impact
- Suggested severity: Low / specification conformance bug
- Suggested CWE: none assigned; this is primarily an ECMAScript Proxy invariant-validation error
- Attack surface: pure JavaScript
- Observed impact: ECMA-262 Proxy invariant violation; no crash observed
Root cause
At jerry-core/ecma/operations/ecma-objects-general.c:734-748, ecma_op_to_complete_property_descriptor does not complete missing boolean attributes:
void
ecma_op_to_complete_property_descriptor (ecma_property_descriptor_t *desc_p)
{
if (!(desc_p->flags & (JERRY_PROP_IS_GET_DEFINED | JERRY_PROP_IS_SET_DEFINED)))
{
desc_p->flags |= JERRY_PROP_IS_VALUE_DEFINED;
}
else
{
desc_p->flags |= (JERRY_PROP_IS_GET_DEFINED | JERRY_PROP_IS_SET_DEFINED);
}
}
ECMA-262 requires:
- generic/data descriptors missing
[[Value]] to default to undefined;
- generic/data descriptors missing
[[Writable]] to default to false;
- accessor descriptors missing
[[Get]] or [[Set]] to default to undefined;
- all descriptors missing
[[Enumerable]] or [[Configurable]] to default to false.
Since the boolean fields are not marked as defined before Proxy invariant checks, the engine can miss an invalid descriptor transition.
JerryScript revision
Observed at JerryScript commit:
b7069350c2e52e7dc721dfb75f067147bd79b39b
Tested on: 2026-06-20.
Build platform
Ubuntu 24.04.4 LTS (Linux 6.6.87.2-microsoft-standard-WSL2 x86_64)
Build steps
python3 tools/build.py --clean
Debug build also shows the same incorrect behavior:
python3 tools/build.py --clean --debug
Build log
Not a build problem. Build completed successfully.
Test case
poc_proxy_invariant_descriptor.js:
var target = {};
Object.defineProperty(target, 'p', {
value: 1,
writable: true,
enumerable: true,
configurable: false
});
var handler = {
getOwnPropertyDescriptor: function(t, prop) {
if (prop === 'p') {
return { value: 1 };
}
}
};
var proxy = new Proxy(target, handler);
try {
var desc = Object.getOwnPropertyDescriptor(proxy, 'p');
print("desc =", JSON.stringify(desc));
} catch (e) {
print("error:", e.message);
}
Execution platform
Same as the build platform.
Execution steps
build/bin/jerry poc_proxy_invariant_descriptor.js
Output
Current JerryScript output:
desc = {"value":1,"writable":false,"enumerable":false,"configurable":false}
Backtrace
No crash occurs.
Expected behavior
This should throw a TypeError.
The trap returns { value: 1 }. After CompletePropertyDescriptor, this is equivalent to:
{ value: 1, writable: false, enumerable: false, configurable: false }
The target property is non-configurable but writable:
{ value: 1, writable: true, enumerable: true, configurable: false }
For Proxy [[GetOwnProperty]], reporting a non-configurable, non-writable data descriptor for a target property that is non-configurable and writable violates the Proxy invariants and should be rejected.
Suggested fix
Complete all missing descriptor fields before invariant checks. The fix must distinguish data/generic descriptors from accessor descriptors: accessor descriptors must not receive JERRY_PROP_IS_WRITABLE_DEFINED.
Under JerryScript's descriptor representation, this assumes that descriptor value/getter/setter fields and boolean value bits are already initialized to their undefined / false defaults, and the *_DEFINED flags only mark whether the field exists.
void
ecma_op_to_complete_property_descriptor (ecma_property_descriptor_t *desc_p)
{
if (!(desc_p->flags & (JERRY_PROP_IS_GET_DEFINED | JERRY_PROP_IS_SET_DEFINED)))
{
/* Generic and data descriptors become complete data descriptors. */
desc_p->flags |= (JERRY_PROP_IS_VALUE_DEFINED
| JERRY_PROP_IS_WRITABLE_DEFINED);
}
else
{
/* Accessor descriptors get undefined getter/setter defaults. */
desc_p->flags |= (JERRY_PROP_IS_GET_DEFINED | JERRY_PROP_IS_SET_DEFINED);
}
desc_p->flags |= (JERRY_PROP_IS_ENUMERABLE_DEFINED
| JERRY_PROP_IS_CONFIGURABLE_DEFINED);
}
After this, Proxy invariant validation should see the same completed descriptor that is later exposed to JavaScript.
Also consider checking the return value of ecma_op_to_property_descriptor before calling ecma_op_to_complete_property_descriptor, so completion is not applied to an error result.
Regression test suggestion
Add a Proxy regression test where getOwnPropertyDescriptor returns { value: 1 } for a non-configurable, writable target property. Object.getOwnPropertyDescriptor(proxy, "p") should throw a TypeError.
Summary
ecma_op_to_complete_property_descriptordoes not apply the default values required by ECMA-262CompletePropertyDescriptor. It ensures that a descriptor is a data or accessor descriptor, but it does not mark missingwritable,enumerable, andconfigurableattributes as defined with valuefalse.As a result, Proxy
getOwnPropertyDescriptorinvariant checks can approve a trap result that should be rejected. The returned descriptor shown to JavaScript is completed later, but the internal invariant checks have already run on incomplete flags.Security impact
Root cause
At
jerry-core/ecma/operations/ecma-objects-general.c:734-748,ecma_op_to_complete_property_descriptordoes not complete missing boolean attributes:ECMA-262 requires:
[[Value]]to default toundefined;[[Writable]]to default tofalse;[[Get]]or[[Set]]to default toundefined;[[Enumerable]]or[[Configurable]]to default tofalse.Since the boolean fields are not marked as defined before Proxy invariant checks, the engine can miss an invalid descriptor transition.
JerryScript revision
Observed at JerryScript commit:
Tested on: 2026-06-20.
Build platform
Build steps
Debug build also shows the same incorrect behavior:
Build log
Not a build problem. Build completed successfully.
Test case
poc_proxy_invariant_descriptor.js:Execution platform
Same as the build platform.
Execution steps
Output
Current JerryScript output:
Backtrace
No crash occurs.
Expected behavior
This should throw a
TypeError.The trap returns
{ value: 1 }. AfterCompletePropertyDescriptor, this is equivalent to:The target property is non-configurable but writable:
For Proxy
[[GetOwnProperty]], reporting a non-configurable, non-writable data descriptor for a target property that is non-configurable and writable violates the Proxy invariants and should be rejected.Suggested fix
Complete all missing descriptor fields before invariant checks. The fix must distinguish data/generic descriptors from accessor descriptors: accessor descriptors must not receive
JERRY_PROP_IS_WRITABLE_DEFINED.Under JerryScript's descriptor representation, this assumes that descriptor value/getter/setter fields and boolean value bits are already initialized to their
undefined/falsedefaults, and the*_DEFINEDflags only mark whether the field exists.After this, Proxy invariant validation should see the same completed descriptor that is later exposed to JavaScript.
Also consider checking the return value of
ecma_op_to_property_descriptorbefore callingecma_op_to_complete_property_descriptor, so completion is not applied to an error result.Regression test suggestion
Add a Proxy regression test where
getOwnPropertyDescriptorreturns{ value: 1 }for a non-configurable, writable target property.Object.getOwnPropertyDescriptor(proxy, "p")should throw aTypeError.