• [Proxy] No way to hook property creation?

    From JJ@21:1/5 to All on Sun Aug 7 12:56:38 2022
    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?

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Michael Haufe (TNO)@21:1/5 to All on Sat Aug 20 17:17:46 2022
    On Sunday, August 7, 2022 at 12:56:47 AM UTC-5, JJ wrote:
    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?

    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>

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From JJ@21:1/5 to All on Sun Aug 21 14:30:09 2022
    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.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Michael Haufe (TNO)@21:1/5 to All on Sun Aug 21 15:37:42 2022
    On Sunday, August 21, 2022 at 2:30:18 AM UTC-5, JJ wrote:
    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.

    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>

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Thomas 'PointedEars' Lahn@21:1/5 to All on Mon Aug 22 01:08:06 2022
    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.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From JJ@21:1/5 to Thomas 'PointedEars' Lahn on Tue Aug 23 03:31:02 2022
    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).

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Thomas 'PointedEars' Lahn@21:1/5 to to be a property- on Wed Aug 24 01:45:54 2022
    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.

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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From TheScreamingFedora@21:1/5 to All on Thu Aug 25 08:39:14 2022
    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.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Thomas 'PointedEars' Lahn@21:1/5 to Thomas 'PointedEars' Lahn on Sat Aug 27 22:40:15 2022
    Thomas 'PointedEars' Lahn wrote:

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

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

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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)