9

First of all, I want to clarify, I know that with is deprecated, and using it is generally a bad practice.

However, my question is about a special case: using a special Proxy object as the parameter of with.


Background

I'm working on a project, where I have to limit access of a piece of code to the global scope.

One approach might be to use a loop with eval, that creates constant variables with the value of undefined for each property of the global object, but that seems even worse than using with, and cannot limit access to variables created with let and const.

The idea

The idea is to use a Proxy as the argument of with, whose...

  • has trap always returns true, therefore it doesn't allow any lookups or assignments to go beyond the with statement
  • get trap operates normally, except that it throws ReferenceErrors when trying to access a non-existing variable (i.e. property)
  • set trap operates normally (or maybe contains some custom logic)
  • target object has no [[Prototype]] (i.e. it was created with Object.create(null))
  • target object has an @@unscopables property, with the value of an empty object, to allow scoping of every property

So, something like this code:

const scope = Object.create(null)
Object.assign(scope, {
  undefined,
  console,
  String,
  Number,
  Boolean,
  Array,
  Object,
  /* etc. */
  [Symbol.unscopables]: Object.create(null)
})

const scopeProxy = new Proxy(scope, {
  get: (obj, prop) => {
    if (prop in obj)
      return obj[prop]
    else
      throw new ReferenceError(`${prop} is not defined`)
  },
  set: Reflect.set,
  has: () => true
})

with(scopeProxy) {
  //Sandboxed code
  
  foo = Number('42')
  console.log(foo) //42
  
  try{
    console.log(scopeProxy) //Inaccessible
  }catch(e){
    console.error(e) //ReferenceError: scopeProxy is not defined
  }
}

Avoiding contras

There are several contras listed on the MDN's page about the with statement, but this usage of it gets rid of each.

1. Performance

  • The problem:

    Looking up identifiers that aren't a member of with statement's parameter object is less performant.

  • Avoidance:

    No lookups can go beyond the parameter object.

2. Ambiguity

  • The problem:

    It is hard to decide, which identifier gets looked up of those with the same name.

  • Avoidance:

    All lookups and assignments retrieve or modify the property of the parameter object.

3. Forward compatibility

  • The problem:

    The properties of the parameter object or its prototype might change in the future.

  • Avoidance:

    The parameter object is initially empty and has no prototype, therefore no properties can change.

Question

The above code works perfectly, and the contras listed on MDN don't seem to apply to this case.

So, my question is:

Is it still a bad practice to use the with statement, and if so, what are the downsides of using it in this specific case?


Note: I know that this approach in itself is not secure and can be bypassed. However, this question is limited only to whether it's considered bad for some reason to use the abovementioned Proxy-with combination. In this question, I'm not concerned about security (that's a related, but different question).

5
  • It sounds like what you actually want is to run code inside a sandbox. Yes, a sandbox might use a proxy in its implementation, but you probably shouldn't write this yourself. Commented Mar 11, 2020 at 22:20
  • @Bergi I know, that a sandbox mechanism should be more complicated that this one, but my question is not really about whether this sandbox is safe and good, but instead if using with this way is bad or not. Commented Mar 12, 2020 at 5:43
  • Well, it still prevents you from using strict mode, and accessing properties through with and a proxy is still quite slow. I would try looking for a different solution to the underlying problem, but other than that you're just using with as a tool that seems to do what you need. Commented Mar 12, 2020 at 7:56
  • 1
    Btw, ({}.constructor.constructor('return alert')()('Unsafe global!')); is not affected by the proxy at all. Commented Dec 15, 2020 at 20:19
  • @Bergi That's right, and I know about it, but it's off-topic for this question. I'll edit it to clarify. Commented Dec 15, 2020 at 20:46

1 Answer 1

0

Sounds like the good old lexical vs dynamic scope topic. In general lexical scope is more safe but in some situations dynamic scope makes sense, because it simplifies some solutions very much. I would say your example is one of the cases, where it may be useful.

Sign up to request clarification or add additional context in comments.

1 Comment

Lexical versus dynamic scope has nothing to do with this.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.