Module Pattern Provides No Privacy...at least not in JavaScript(TM)

The module pattern has been discussed many times and has shown how ECMAScript has the ability to encapsulate data as "private" variables by using closures.

Today, in a comment on my blog, a reader, haysmark, points out that Mozilla's JavaScript(TM), the implementation in Firefox, has a second argument extension to eval that allows external code to spy on otherwise private variables.

Try the examples below in Firefox.

// Getting "private" variables
var obj = (function() {
  var a = 21;
  return {
    // public function must reference 'a'
    fn: function() {a;}
  };
})();

var foo;
eval('foo=a', obj.fn);
console.log(foo); // 21


// Setting "private" variables
var obj = (function() {
  var a = 21;
  return {
    getA: function(){return a;},
    alertA: function(){alert(a);}
  };
})();

console.log(obj.getA()); //21
eval('a=3', obj.getA);
console.log(obj.getA()); // 3
obj.alertA(); // 3

This use of eval delivers a blow to the usefulness of statements like "JavaScript provides the means to construct durable objects that can perfectly guard their state by using a variation of the Module Pattern." Perhaps an ECMAScript implementation with no extensions can provide such security but one of the most important implementations, JavaScript(TM) in Firefox, apparently does not.

This use of the eval, however, doesn't make the module pattern useless. Its primary benefits are modularizing code so similarly named variables are not colliding and protects you or other developers from accidentally violating a programming interface. The module pattern also makes it possible to do OOP-like things without using keywords new, this and prototype which generally makes code more robust.

So the module pattern is still good. It just doesn't provide any security in a major browser.

Thanks to haysmark for the comment today. A big part of the reason I blog is to put ideas out there to see if they are shot down. I'm interested to see if this idea is shot down.

Update June 27, 2008 This post has been discussed at some other places: Caja Discussion Group, Ajaxian, Simon Willison's Blog

Update July 2, 2008 Apparently this post mattered and the second argument to eval will be gone in Firefox 3.1. More discussion on Douglas Crockford's Blog, Ajaxian, and John Resig's blog.

I didn't necessarily think JavaScript(TM) should be changed at all. Patching this security hole doesn't make JavaScript "secure". Since there are so many ECMAScript implementations, the ECMAScript specification allows for implementation extensions to the language, and there is no governing body certifying all publicly available implementations as "secure", the language will never be secure. I just thought this example was kind of a neat oddity and it seemed many others found this surprising as well. If security is a concern then ensure all the code you allow in your page from third parties is safe. Projects which define a safe subset of JavaScript and/or automate checking of third party code like Caja and ADSafe are more likely the way forward. I have also been thinking legal business agreements would be a good idea before allowing third party script on pages.

Comments

Have something to write? Comment on this article.

Matt Snider June 27, 2008

Peter, does it actually change the value of (a) or does it change the value of (a) returned by the getter function?

Peter Michaux June 27, 2008

Matt,

I wondered the very same thing. This is why I added the obj.alertA() part of the second test. It looks like it is really changing a.

Chris Anderson June 27, 2008

I'm working on a project that depends on sandboxing code using Spidermonkey's ability to provide multiple Contexts in a single JS user-space. Eg I'm executing end-user functions in a sandboxed enviroment, and passing them a limited-use HTTP function. I'll have to test that they can't use the eval capability to remove the limits (coded in Javascript outside their context) by replacing them with a permissive function.

Thanks for the heads up on a possible exploit.

The Doctor What June 27, 2008

Hmm.... Couldn't you just wrap the not-really-private-context in another context? to make it truely private?

Ben Swieringa June 27, 2008

Try substituting this in the example provided by haysmark. I've been working with object variations similar to the following:

var Person = function (first, age) {  
  var _p=function(){}; //private object scope
  var p=function(){};  //public object scope
  _p.first = first;
  p.getFirstName = function() {
    // captialize the name
    return  _p.first.charAt(0).toUpperCase() + _p.first.substr(1);  
  };
};
_p.year = age;
p.getAge = function() {
    return _p.year;  
};
p.sayInfo = function() {        
    alert(p.getFirstName() + ' ' + p.getAge());
};
return p;
Edwin Martin June 27, 2008

Saying "It just doesn't provide any security" is a bit too strong. If you have to use eval with two arguments, you know you're doing something you shouldn't do. It's like saying "A lock on a door doesn't provide any security because you can use a crowbar to open the door".

Mark S. Miller June 27, 2008

This is not a new problem, as Brendan explains at

http://groups.google.com/group/google-caja-discuss/msg/ead8d8597a22c013

Dustin Diaz June 27, 2008

Peter. This is a really good find. I tried out a few examples myself, and basically the only way to get yourself out of this loophole is to hide your entire implementation code into another (anonymous) closure. Nevertheless, good find :) Thanks for the tip.

matt June 28, 2008

I haven't used eval() in the real world since YUI released its JSON tool, but I wasn't aware of the context parameter, so this is good info. Thanks.

Costin June 28, 2008

Why should not every browser vendor implement their own version of javascript? I mean, Mozilla has it, Microsoft has it... when everyone else will come up with their own versions too?

I mean, hell, why not? Instead of following the standard let's have dozens of javascript versions so when you write a simple js code you should add a note that: "this script only works great in FF. In IE and Opera fails miserably"...

Why does it have to be so complicated? Why don't they KISS??

W June 29, 2008

"It just doesn't provide any security" is a bit too strong.

Especially since at the bottom of this (in most contexts, anyway), we're talking about interpreted source code.

I suspect the best enforcement mechanism isn't strictly technical, but lies in the process of refining the object interface until it removes most of the temptation to mess about with the internals.

Mus June 30, 2008

By the same token, you can use reflection to modify private members in Java as well. Basically, you cannot expect to run arbitrary code in the same VM/interpreter and expect to have any distinction of privileges. Private members serve more to guide the programmer about what is part of the public interface for an object, and what is not. This does not make this programming pattern any less useful.

Isaac Z. Schlueter July 3, 2008

All this really proves is that eval is evil. No news there.

The takeaway is, if you're using eval, stop. If you're running untrusted JS, run it through Caja or AdSafe, and using an iframe on a different domain name is probably a good idea, too.

By the way, I don't think "JavaScript" is trademarked by anyone.

Peter Michaux July 3, 2008

I disagree with all the "eval is evil" rhetoric. eval makes it possible to write some very efficient code. For example, eval makes for efficient parsing of JSON, compiling a CSS selector string (or any domain-specific language) into a JavaScript function. eval opens up other possibilities for meta-programming in JavaScript that would otherwise be impossible. eval makes writing a JavaScript REPL possible.

The idea that "eval is evil" doesn't seem to appear so much in communities which use eval where it helps and not where it hurts. The Lisp community seems to have a much better perspective on the use of eval.

Brad Fults July 13, 2008

I've always found the idea of data "security" in client-side JavaScript a bit silly. After all, it's code and data that sits on the client machine.

I've never run into any instance where private variables in JS were actually necessary so this realization doesn't bother me much.

Have something to write? Comment on this article.