This repository has been archived by the owner. It is now read-only.

vm: context can't be a proxy #7526

Closed
doughsay opened this Issue Apr 29, 2014 · 12 comments

Comments

Projects
None yet
6 participants
@doughsay

I know I'm probably completely going off the deep end of "nobody ever does this" here, but here goes:

When creating a VM context, it seems you can't use a proxy:

var vm = require('vm')
  , proxy = Proxy.create({}, {foo: 'bar'})
  , context = vm.createContext(proxy)
  , script = new vm.Script('this.foo')

console.log(script.runInContext(context))

This (when run with node --harmony) throws TypeError: sandbox must be an object

I'd like to be able to control what properties can be accessed and set on the context object when running scripts in it. Is this possible in any other way?

@doughsay

This comment has been minimized.

Show comment
Hide comment
@doughsay

doughsay Apr 29, 2014

It appears this is because v8's IsObject returns false for proxies...

It appears this is because v8's IsObject returns false for proxies...

@OrangeDog

This comment has been minimized.

Show comment
Hide comment
@OrangeDog

OrangeDog Apr 29, 2014

Are the Object constructor methods not sufficient?
seal, freeze, preventExtensions and defineProperty can achieve a lot.

Are the Object constructor methods not sufficient?
seal, freeze, preventExtensions and defineProperty can achieve a lot.

@doughsay

This comment has been minimized.

Show comment
Hide comment
@doughsay

doughsay Apr 29, 2014

Thanks for the response.

They can get me almost there, but what I'm really trying to achieve is the ability for my trusted code to be able to manipulate the sandbox, but not the untrusted code. Freezing it freezes it for me too. Adding non-configurable properties prevents me from future modifications to them.

Thanks for the response.

They can get me almost there, but what I'm really trying to achieve is the ability for my trusted code to be able to manipulate the sandbox, but not the untrusted code. Freezing it freezes it for me too. Adding non-configurable properties prevents me from future modifications to them.

@OrangeDog

This comment has been minimized.

Show comment
Hide comment
@OrangeDog

OrangeDog Apr 29, 2014

Should probably move this to the mailing list, but maybe a setter function that checks the stack trace of the caller would just about work?

Should probably move this to the mailing list, but maybe a setter function that checks the stack trace of the caller would just about work?

@doughsay

This comment has been minimized.

Show comment
Hide comment
@doughsay

doughsay Apr 29, 2014

That's an interesting idea, but seems pretty hacky to me.

That's an interesting idea, but seems pretty hacky to me.

@bnoordhuis

This comment has been minimized.

Show comment
Hide comment
@bnoordhuis

bnoordhuis Apr 30, 2014

Member

proxy = Proxy.create({}, {foo: 'bar'})

I'm curious what you would expect a proxy object like that to do? I don't think it's too surprising that Script#runInContext() rejects it because it's essentially an "un-object" - about the only thing you can do with it is call typeof on it.

Member

bnoordhuis commented Apr 30, 2014

proxy = Proxy.create({}, {foo: 'bar'})

I'm curious what you would expect a proxy object like that to do? I don't think it's too surprising that Script#runInContext() rejects it because it's essentially an "un-object" - about the only thing you can do with it is call typeof on it.

@doughsay

This comment has been minimized.

Show comment
Hide comment
@doughsay

doughsay Apr 30, 2014

It was just an example, a fully implemented proxy gets the same error. And incidentally, it's the vm.createContext(proxy) call that actually throws the error.

It was just an example, a fully implemented proxy gets the same error. And incidentally, it's the vm.createContext(proxy) call that actually throws the error.

@doughsay

This comment has been minimized.

Show comment
Hide comment
@doughsay

doughsay Apr 30, 2014

A more fully featured example, showing that a working proxy object can't be used as a vm sandbox object:

// taken straight from http://wiki.ecmascript.org/doku.php?id=harmony:proxies
function handlerMaker(obj) {
  return {
   getOwnPropertyDescriptor: function(name) {
     var desc = Object.getOwnPropertyDescriptor(obj, name);
     // a trapping proxy's properties must always be configurable
     if (desc !== undefined) { desc.configurable = true; }
     return desc;
   },
   getPropertyDescriptor:  function(name) {
     var desc = Object.getPropertyDescriptor(obj, name); // not in ES5
     // a trapping proxy's properties must always be configurable
     if (desc !== undefined) { desc.configurable = true; }
     return desc;
   },
   getOwnPropertyNames: function() {
     return Object.getOwnPropertyNames(obj);
   },
   getPropertyNames: function() {
     return Object.getPropertyNames(obj);                // not in ES5
   },
   defineProperty: function(name, desc) {
     Object.defineProperty(obj, name, desc);
   },
   delete:       function(name) { return delete obj[name]; },
   fix:          function() {
     if (Object.isFrozen(obj)) {
       var result = {};
       Object.getOwnPropertyNames(obj).forEach(function(name) {
         result[name] = Object.getOwnPropertyDescriptor(obj, name);
       });
       return result;
     }
     // As long as obj is not frozen, the proxy won't allow itself to be fixed
     return undefined; // will cause a TypeError to be thrown
   },

   has:          function(name) { return name in obj; },
   hasOwn:       function(name) { return ({}).hasOwnProperty.call(obj, name); },
   get:          function(receiver, name) { return obj[name]; },
   set:          function(receiver, name, val) { obj[name] = val; return true; }, // bad behavior when set fails in non-strict mode
   enumerate:    function() {
     var result = [];
     for (var name in obj) { result.push(name); };
     return result;
   },
   keys: function() { return Object.keys(obj); }

  };
}

var obj = {foo: 'bar'};
var proxy = Proxy.create(handlerMaker(obj));

console.log(typeof proxy);
console.log(proxy.constructor.name);
console.log('foo' in proxy);
console.log(Object.hasOwnProperty('foo'));
console.log(proxy.foo);
delete proxy.foo;
proxy.bar = 'baz';
console.log(proxy.foo);
console.log(proxy.bar);

var vm = require('vm');

vm.createContext(proxy); // This throws `TypeError: sandbox must be an object`

Output:

node --harmony vm-test.js
object
Object
true
false
bar
undefined
baz
vm.js:50
  } else if (binding.isContext(sandbox)) {
                     ^
TypeError: sandbox must be an object
    at Object.exports.createContext (vm.js:50:22)
    at Object.<anonymous> (/Users/chris/Sites/room.js.new/server/vm-test.js:67:4)
    at Module._compile (module.js:449:26)
    at Object.Module._extensions..js (module.js:467:10)
    at Module.load (module.js:349:32)
    at Function.Module._load (module.js:305:12)
    at Function.Module.runMain (module.js:490:10)
    at startup (node.js:124:16)
    at node.js:803:3

A more fully featured example, showing that a working proxy object can't be used as a vm sandbox object:

// taken straight from http://wiki.ecmascript.org/doku.php?id=harmony:proxies
function handlerMaker(obj) {
  return {
   getOwnPropertyDescriptor: function(name) {
     var desc = Object.getOwnPropertyDescriptor(obj, name);
     // a trapping proxy's properties must always be configurable
     if (desc !== undefined) { desc.configurable = true; }
     return desc;
   },
   getPropertyDescriptor:  function(name) {
     var desc = Object.getPropertyDescriptor(obj, name); // not in ES5
     // a trapping proxy's properties must always be configurable
     if (desc !== undefined) { desc.configurable = true; }
     return desc;
   },
   getOwnPropertyNames: function() {
     return Object.getOwnPropertyNames(obj);
   },
   getPropertyNames: function() {
     return Object.getPropertyNames(obj);                // not in ES5
   },
   defineProperty: function(name, desc) {
     Object.defineProperty(obj, name, desc);
   },
   delete:       function(name) { return delete obj[name]; },
   fix:          function() {
     if (Object.isFrozen(obj)) {
       var result = {};
       Object.getOwnPropertyNames(obj).forEach(function(name) {
         result[name] = Object.getOwnPropertyDescriptor(obj, name);
       });
       return result;
     }
     // As long as obj is not frozen, the proxy won't allow itself to be fixed
     return undefined; // will cause a TypeError to be thrown
   },

   has:          function(name) { return name in obj; },
   hasOwn:       function(name) { return ({}).hasOwnProperty.call(obj, name); },
   get:          function(receiver, name) { return obj[name]; },
   set:          function(receiver, name, val) { obj[name] = val; return true; }, // bad behavior when set fails in non-strict mode
   enumerate:    function() {
     var result = [];
     for (var name in obj) { result.push(name); };
     return result;
   },
   keys: function() { return Object.keys(obj); }

  };
}

var obj = {foo: 'bar'};
var proxy = Proxy.create(handlerMaker(obj));

console.log(typeof proxy);
console.log(proxy.constructor.name);
console.log('foo' in proxy);
console.log(Object.hasOwnProperty('foo'));
console.log(proxy.foo);
delete proxy.foo;
proxy.bar = 'baz';
console.log(proxy.foo);
console.log(proxy.bar);

var vm = require('vm');

vm.createContext(proxy); // This throws `TypeError: sandbox must be an object`

Output:

node --harmony vm-test.js
object
Object
true
false
bar
undefined
baz
vm.js:50
  } else if (binding.isContext(sandbox)) {
                     ^
TypeError: sandbox must be an object
    at Object.exports.createContext (vm.js:50:22)
    at Object.<anonymous> (/Users/chris/Sites/room.js.new/server/vm-test.js:67:4)
    at Module._compile (module.js:449:26)
    at Object.Module._extensions..js (module.js:467:10)
    at Module.load (module.js:349:32)
    at Function.Module._load (module.js:305:12)
    at Function.Module.runMain (module.js:490:10)
    at startup (node.js:124:16)
    at node.js:803:3
@bnoordhuis

This comment has been minimized.

Show comment
Hide comment
@bnoordhuis

bnoordhuis May 1, 2014

Member

Okay, that looks reasonable and I can confirm the behavior. For some reason, V8 considers them non-objects (it's hard-coded in objects.h and objects-inl.h) but I'm unclear on why that is.

Giving the proxy an object prototype so that proxy instanceof Object === true makes no difference, v8::Value::IsObject() operates solely on the type tag.

V8's tip shows the same behavior so maybe it's something that should be raised on their bug tracker or the v8-users mailing list.

Member

bnoordhuis commented May 1, 2014

Okay, that looks reasonable and I can confirm the behavior. For some reason, V8 considers them non-objects (it's hard-coded in objects.h and objects-inl.h) but I'm unclear on why that is.

Giving the proxy an object prototype so that proxy instanceof Object === true makes no difference, v8::Value::IsObject() operates solely on the type tag.

V8's tip shows the same behavior so maybe it's something that should be raised on their bug tracker or the v8-users mailing list.

@domenic

This comment has been minimized.

Show comment
Hide comment
@domenic

domenic May 4, 2014

Member

V8's proxies are nonstandard and headed for the scrap-heap...

Member

domenic commented May 4, 2014

V8's proxies are nonstandard and headed for the scrap-heap...

@jasnell

This comment has been minimized.

Show comment
Hide comment
@jasnell

jasnell Jun 3, 2015

Member

Closing given the current state of Proxy.

Member

jasnell commented Jun 3, 2015

Closing given the current state of Proxy.

@jasnell jasnell closed this Jun 3, 2015

@pmros

This comment has been minimized.

Show comment
Hide comment
@pmros

pmros Oct 16, 2015

I hope proxies will be real object soon at v8.

pmros commented Oct 16, 2015

I hope proxies will be real object soon at v8.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.