JerryScript: Proxy getOwnPropertyDescriptor invariant check misses descriptor defaults
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 toundefined; - generic/data descriptors missing
[[Writable]]to default tofalse; - accessor descriptors missing
[[Get]]or[[Set]]to default toundefined; - all descriptors missing
[[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:
b7069350c2e52e7dc721dfb75f067147bd79b39bTested 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 --cleanDebug build also shows the same incorrect behavior:
python3 tools/build.py --clean --debugBuild 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.jsOutput
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.
Source: jerryscript-project/jerryscript