Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

[Proxy] No way to hook property creation?

45 views
Skip to first unread message

JJ

unread,
Aug 7, 2022, 1:56:47 AM8/7/22
to
I'm trying to use Proxy and the `with` statement to redirect access to the
global object.

The problem is that, Proxy doesn't seem to have any handler for property
creation which is done without object accessor or `Object.defineProperty()`.
e.g. `prop = 123` instead of `obj.prop = 123`

My current code is like this.

const fakeGlobal = {console: console};
fakeGlobal.globalThis = fakeGlobal;
const proxy = new Proxy(fakeGlobal, {
has(tgt, key) {
//return key in tgt //initial code
return true
},
get(tgt, key) {
return tgt[key]
},
set(tgt, key, val) {
tgt[key] = val;
return true
}
});
with (proxy) {
prop = 123;
}

With that code, the proxy's `has()` handler is called, then the `set`
handler is called. The object creation happens between of those two
handlers, and is done internally by the JS engine without calling
`Object.defineProperty()`.

Because the `prop` property doesn't yet exist in `fakeGlobal` object, the
property is created in the outer scope object, which is the actual global
object.

For sandboxing purpose, the `has()` handler has to return `true` whether the
given property name actually exist in the `fakeGlobal` object or not. i.e.

has(tgt, key) {
return true
},

However, it will breaks the `in` operator functionality if it's used to
simply check for a property presence in the `fakeGlobal` object. It also
prevents an exception to occur if the sandboxed code refers to a non
existing property which is without any object accessor. e.g.

const prop = nonExistingProp;

Without sandboxing, that code would throw a ReferenceError exception as
expected. But if it's run in the sandbox, no exception will occur - which is
not how it should be.

So, is there a way to work around this problem?

If the combination of Proxy and the `with` statement is simply not
sufficient for sandboxing, what's a better method other than to implement an
interpreter?

Michael Haufe (TNO)

unread,
Aug 20, 2022, 8:17:51 PM8/20/22
to
I'm having trouble understanding your goal. Can you share more than just the tactics?

Are you avoiding modules?

Are you trying to create your own version of the ECMAScript ShadowRealm proposal?

<https://github.com/tc39/proposal-shadowrealm/blob/main/explainer.md>

JJ

unread,
Aug 21, 2022, 3:30:18 AM8/21/22
to
On Sat, 20 Aug 2022 17:17:46 -0700 (PDT), Michael Haufe (TNO) wrote:
>
> I'm having trouble understanding your goal. Can you share more than just
> the tactics?

I'm trying to create a JS sandbox without the need of a separate browser
Window object.

> Are you avoiding modules?

No. The code can be anything.

> Are you trying to create your own version of the ECMAScript ShadowRealm
> proposal?
>
> <https://github.com/tc39/proposal-shadowrealm/blob/main/explainer.md>

I'm not aware of that. What I'm trying to achieve is not based on that. That
proposal does much more than what I want.

Michael Haufe (TNO)

unread,
Aug 21, 2022, 6:37:47 PM8/21/22
to
The best you can do today I think practically without resorting to a ShadowRealm-like polyfill is to utilize a detached iframe.

Which means you're limited to the browser for that approach.

I think node has an analogous approach:

<https://nodejs.org/api/vm.html>

Thomas 'PointedEars' Lahn

unread,
Aug 21, 2022, 7:08:16 PM8/21/22
to
JJ wrote:
^^
Please post here using your real name.

> I'm trying to use Proxy and the `with` statement to redirect access to the
> global object.
>
> The problem is that, Proxy doesn't seem to have any handler for property
> creation which is done without object accessor or
> `Object.defineProperty()`. e.g. `prop = 123` instead of `obj.prop = 123`

The (by-design) inconsistent and unpredictable behavior of the *legacy*
“with” statement that *never* a property on the target object is being
created with implicit access, but either

a) the property of an object higher up in the scope chain is modified –
or not if it is read-only or has a dismissive object value getter
(host object) or property setter,

or

b) a property of the global object is created (or not)

is one of the reasons why it is *deprecated* since ECMAScript Edition 5,
and *code containing it cannot even be executed* in Strict Mode.

This has nothing to do with the capabilities of the Proxy object because its
instance does not even "see" that write access.

<https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/with>

You can determine whether a property is being created in the “set” handler
of the Proxy instance:

var target = {};

var proxy = new Proxy(target, {
set (target, propertyName, value) {
var propertyExisted = (propertyName in target);

target[propertyName] = value;

if (!propertyExisted) {
console.log(`Created property "${propertyName}" on target with value
"${value}".`);
}
}
});

/*
* displays
* "Created property "foo" on target with value ""bar""
*/
proxy.foo = "bar";


/* does not display anything */
proxy.foo = "baz";

Tested in Chromium 90 on GNU/Linux,

navigator.userAgent == "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36
(KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36".

The usual caveats for the “in” statement and ES 6+ features apply.

See also:

<https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy>

--
PointedEars
<https://github.com/PointedEars> | <http://PointedEars.de/wsvn/>
Twitter: @PointedEars2
Please do not cc me. /Bitte keine Kopien per E-Mail.

JJ

unread,
Aug 22, 2022, 4:31:11 PM8/22/22
to
On Mon, 22 Aug 2022 01:08:06 +0200, Thomas 'PointedEars' Lahn wrote:
> is one of the reasons why it is *deprecated* since ECMAScript Edition 5,

Yet, ECMAScript later added `Symbol.unscopables` to support `with`.

> and *code containing it cannot even be executed* in Strict Mode.

I'm using `with`, so I don't use Strict mode in the first place.

> This has nothing to do with the capabilities of the Proxy object because its
> instance does not even "see" that write access.

That is correct. It's not supposed to and can not be the job for Proxy. And
that's the problem, because Proxy doesn't have a way to intercept property
creation.

> You can determine whether a property is being created in the “set” handler
> of the Proxy instance:

That's not possible, because `set()` will only be called if the JS engine
already know that the property exists as reported by `has()`. As I said, the
actual property creation (not the property value assignment) happens
internally within the internal JS engine, outside of `set()`.

So, the problem is that, there's no way to know whether `has()` is called
due to a value assignment to a non existing property, or due to `in`
operator. Because the return value of `has()`, determines which object the
property will be created, when assigning a value to a non existing property.

Also for the same reason, `has()` can not simply throw a `ReferenceError`
when the queried property doesn't yet exist, because `has()` may have been
called due to `in` operator (which shouldn't throw any exception), instead
of due to a value assignment to a non existing unscoped property (which
should throw an exception).

Thomas 'PointedEars' Lahn

unread,
Aug 23, 2022, 7:46:03 PM8/23/22
to
JJ wrote:

> On Mon, 22 Aug 2022 01:08:06 +0200, Thomas 'PointedEars' Lahn wrote:
>> is one of the reasons why it is *deprecated* since ECMAScript Edition 5,
>
> Yet, ECMAScript later added `Symbol.unscopables` to support `with`.

Only to make ES 5− code that still uses “with” for *read* access easily
compatible with ES 6+:

<https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/unscopables>

But:

,-<https://262.ecma-international.org/13.0/#sec-with-statement>
|
| | LEGACY
| |
| | 14.11 The with Statement
| |
| | NOTE 1: Use of the <Legacy> “with” statement is discouraged in new
^^^^^^^^^^^^^^^^^^^^^
| | ECMAScript code. Consider alternatives that are permitted in both
^^^^^^^^^^^^^^^
| | strict mode code and non-strict code, such as destructuring assignment.
| |
| | […]

,-<https://262.ecma-international.org/13.0/#sec-conformance>
|
| A conforming implementation of ECMAScript must implement *Legacy*
| subclauses, unless they are also marked as Normative Optional. All of the
| language features and behaviours specified within Legacy subclauses have
| one or more undesirable characteristics. However, their continued usage in
| existing applications prevents their removal from this specification.
| These features are not considered part of the core ECMAScript language.
| Programmers should not use or assume the existence of these features and
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| behaviours when writing new ECMAScript code.
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| | LEGACY
| |
| | 2.2 Example Legacy Clause Heading
| |
| | Example clause contents.

>> and *code containing it cannot even be executed* in Strict Mode.
>
> I'm using `with`, so I don't use Strict mode in the first place.

PEBKAC.

>> This has nothing to do with the capabilities of the Proxy object because
>> its instance does not even "see" that write access.
>
> That is correct. It's not supposed to and can not be the job for Proxy.
> And that's the problem, because Proxy doesn't have a way to intercept
> property creation.

1. That is false, as I have demonstrated previously.

2. Again: The Proxy instance is NOT the target object in what only *seems*
to be a property-write access facilitated with the “with” statement.

>> You can determine whether a property is being created in the “set”
>> handler of the Proxy instance:
>
> That's not possible, because `set()` will only be called if the JS engine
> already know that the property exists as reported by `has()`.

I have demonstrated that to be false previously, too.

TheScreamingFedora

unread,
Aug 25, 2022, 11:39:20 AM8/25/22
to
This is my first post on usenet so I am not familiar with the etiquette, but I believe this may be what you are trying to accomplish?

https://blog.risingstack.com/writing-a-javascript-framework-sandboxed-code-evaluation/

This works for me in hiding global variables, but it still does not hide the Javascript global object (I don't believe that's possible though), so any top-scope declared functions are still visible. I could fix this though by throwing all the top-level functions in my main "environment" into a Global object that gets hidden, and then including them via "with". I am a js noob too though so I am not sure if this is what you are asking for.

Thomas 'PointedEars' Lahn

unread,
Aug 27, 2022, 4:40:24 PM8/27/22
to
Thomas 'PointedEars' Lahn wrote:

> The usual caveats for the “in” statement and ES 6+ features apply.

Correction: “in” is an _operator_, not a statement.
0 new messages