Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

> For that to work you need not only a carefully designed set of types but also they must be able to protect their internals. JavaScript historically hasn't had this, I don't know about modern versions, but the ability to restrict monkey-patching, reflection over private fields etc is a must.

At the bottom of the post I sketched out how we could make this work in practice in javascript. We can use a Symbol[1], and then have that be a key into a Map owned by the builtin capabilities library. That would make the token itself safe from being messed with.

But so long as the capabilities library uses whatever the object is as a key in a JS Map (with the value being the token's scope), we could just as easily use anonymous objects or something else.

[1] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...



I think the issue is more the code that uses the capability itself. Like, if I can just read the capability straight out of the object that owns it, or monkey-patch the definition of some other object it calls into so I can use its capabilities indirectly, then you still lose. That's what I meant by playing defense all the time. If you give a bit of sandboxed code a generic utility object, it can all go wrong.


The idea here is that there's 2 things: The token (a Symbol() or something) and the scope of capabilities which that token gives you. The capabilities themselves are stored in a Map that you don't control. Javascript function scopes give us everything we need to hide that map and make sure nobody can modify it. The only methods which are exposed are things like getScopeForToken() which reads from the map (and does a deep clone) then returns that scope object.

In privileged methods like fs.writeFile(), you don't pass the scope. You pass the token. And that method would explicitly go and check if that token has the scope that it needs to write to the passed path.

But I do hear you about playing defense. I mentioned it in the post - there's probably a bunch of subtle ways you could use javascript to mess with things. Covering all of these cases would need some serious rigor.


I don't know how relevant it still is, but did you ever look at the old Google Caja project?

https://en.wikipedia.org/wiki/Caja_project

It was trying to implement capabilities in JavaScript, but failed because JS was too dynamic at the time. It might be that newer language versions have made it possible but it'd be worth researching why they gave up on it.

Caja was designed by Google research scientist Mark S. Miller in 2008[3][4] as a JavaScript implementation for "virtual iframes" based on the principles of object-capabilities. It would take JavaScript (technically, ECMAScript 5 strict mode code), HTML, and CSS input and rewrite it into a safe subset of HTML and CSS, plus a single JavaScript function with no free variables. That means the only way such a function could modify an object, was if it was given a reference to the object by the host page. Instead of giving direct references to DOM objects, the host page typically gives references to wrappers that sanitize HTML, proxy URLs, and prevent redirecting the page; this allowed Caja to prevent certain phishing and cross-site scripting attacks, and prevent downloading malware. Also, since all rewritten programs ran in the same frame, the host page could allow one program to export an object reference to another program; then inter-frame communication was simply method invocation.


I spent some time with one of the Caja developers back in 2010 or so, before it was made public.

From memory, the problem they were trying to solve was a bit different. From what I remember, they wanted to be able to run potentially hostile user supplied javascript code inside the JS VM purely using source code level validation. So for example, Caja needed to make sure the sandbox container didn't access the global object (since then it could escape its sandbox). And because simple code like this: (function () { return this })() evaluates to the global object, they banned the keyword this in sandboxed code.

I'm hoping there's a way we can give untrusted code more or less full access to the JS environment, but just limit its access to the rest of the operating system. Javascript was first developed for web browsers, and to this day most javascript still has little to no need to access the rest of the operating system directly.

But Javascript's obsessively granular modularity works in our favor here. If you look at a library like express, the core library makes vanishingly few calls to the nodejs environment. `app.listen()` is the only method I know about which wouldn't "just work" in this new world I'm proposing. And thats just a convenience wrapper around `require('http').createServer(app)` anyway. All the hard work happens in libraries like express.static - but thats trivially easy to swap out for another package that supports capabilities correctly, if we need to do that.

A bad library could always be buggy - we can't stop that. I mostly want to stop opportunistic developers from taking advantage of the machines their modules are run on, so we can detect (and stop them) from doing nasty things. But as a few people have mentioned, this approach might be stuck "always playing defense". The nice thing about caja is that it was "complete". There were no weird edge cases left over in the language that the sandbox authors didn't consider. Thats what I'm worried the most about here.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: