Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is using `with` statement with Proxies a bad practice?

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).

like image 976
FZs Avatar asked Jan 05 '20 16:01

FZs


1 Answers

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.

like image 169
ceving Avatar answered Nov 11 '22 16:11

ceving