4 ms·
In general, as the sibling points out, runtimes can't tell which dependency a particular API call is coming from if it's all bundled into one blob. Unfortunatel
by cormacrelf 5y ago
In general, as the sibling points out, runtimes can't tell which dependency a particular API call is coming from if it's all bundled into one blob. Unfortunately most APIs, for example in WASI for ease of writing POSIX code, don't have an argument for a capability assertion to be made. So you can only realistically associate one set of capabilities to a single VM unit.
So what you actually need to do is split your program into more VM units, not unlike code splitting in bundler-integrated JavaScript HTML5 routers. You can build that atop a WASM runtime, if you pass the ability to call other WASM modules into other WASM modules. And then you could pass literal capability tokens around. You just need to split your code up.
Then you can bridge them back again with trusted code, and you can add additional constraints on API calls between modules (e.g. callbacks don't work across units, or if they do, then they remember the capabilities they were instantiated with, something like that).
If there were an easier way to have your dependency management system do this code splitting for you, then it would be feasible.
- spankalee 5y agoThis isn't really true for Node. Node knows what package coffee is running under and relies on that for several core pieces of functionality. Packages really shouldn't bundle their dependencies anyway.
- deleted 5y ago[deleted]
- chriswarbo 5y agoThe setup/constraints/etc. you're describing sound more like a permission-based, ACL-like approach. Capability security is generally based on the following: (1) A "capability" to perform some action is (by definition) something which is necessary and sufficient to perform that action. If that action is calling a particular function (like 'eval'), then we can treat the function's name/reference as a capability. This fits the definition since (a) we can only call functions which we have a name/reference for, and (b) anything with the name/reference to a function can call that function. For example: function somethingWhichCanUseEval(eval) { return eval("foo"); } function somethingWhichCannotUseEval() { throw "Not given a reference to 'eval', so we don't have anything we can call"; } (Note the contrast to your statement that "runtimes can't tell which dependency a particular API call is coming from". Such functionality would actually violate (b) above, making it much harder to implement capability security!) (2) Capabilities are kept secret to begin with, and we pass them around to whatever needs them. In particular, this requires that (i) we can't ask for a list of capabilities (e.g. a list of references to every function), and (ii) we can't guess a capability (e.g. names should be cryptographically-random strings). For example: // All guessable references to the eval function need to be shadowed, e.g. function eval() { throw "Error: Something attempted to call 'eval' without using the package"; } window.eval = eval; // etc. // The package manager can give each package a cryptographically-random "import-name" // When a package is imported, it's given the import-names of those packages it directly // depends on function myPackage(deps) { // The 'deps' argument is a map from package names to (randomly generated) import-names // We can import a package if we know its import-name, hence import-names are capabilities // for importing packages // We can import the 'eval' package by getting its import-name from the 'deps' map const evalPkg = require(deps['eval']); // The 'eval' package provides a reference to the 'eval' function const eval = evalPkg.eval; // At this point, we have the capability to call 'eval' (or pass it around to other functions, etc.) } (3) If we find ourselves "checking permissions" then it's already too late (those without permission shouldn't have even been capable of asking in the first place). Likewise if we try to "validate the caller's identity", since (i) that's a case of ambient authority (the "caller" may be a Confused Deputy), and (ii) it would prevent us delegating capabilities to others to act on our behalf, which in turn requires granting sweeping access to a broad range of systems 'just in case' anyone might want to use them. (4) We can wrap capabilities in a more restricted interface, which acts as a capability for some more restricted action. For example, the following code provides the capability to evaluate code of the form 'foo = bar;': function namerPkg(deps) { const eval = require(deps['eval']).eval; // Regex for valid Javascript names const jsNameRegex = /[_a-zA-Z][_a-zA-Z0-9]*/; return { // A function which evals '<oldName> = <newName>;' 'namer': function namer(oldName, newName) { // Parse/sanitise the input const oldJSNames = oldName.match(jsNameRegex); const newJSNames = newName.match(jsNameRegex); if (oldJSNames == null || newJSNames == null || oldJSNames.length == 0 || newJSNames.length == 0) { throw "Error: namer needs to be given Javascript-compatible names"; } const oldJSName = oldJSNames[0]; const newJSName = newJSNames[0]; // Now it's safe to call eval eval(newJSName + " = " + oldJSName + ";"); }; }; }