nodejs / security-wg

Node.js Ecosystem Security Working Group
MIT License
490 stars 122 forks source link

Permission Model #791

Closed RafaelGSS closed 1 year ago

RafaelGSS commented 2 years ago

Permission Model initial issue

Hello everybody!

Following up on the Security Model initiative and the Mini summit (Next-10) in April seems and consensus that Node.js aims to have a security permission system, thus, avoiding third-party libraries to access machine resources without user consent.

This system was previously researched by James Snell and Anna Henningsen[1], which resulted in an excellent material as a starting point.

For context, the material is available through those links:

Constraints

This security model is not bulletproof, which means, there are constraints we agree on before implementing this system:

Points to be discussed

This is a big topic that could lead to several discussions topics. In order to avoid unnecessary discussions at this moment, let's use the following 3 topics as boundaries for this first iteration.

1) Should it be process or module scoped?

The effort to make it work with modules is worth it? Example:

{
  "name": "package-name",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "permissions": [
    "fs"
  ]
}

Then the user should provide permission for each module (as you need to do in your smartphone apps)

2) Should the user be able to change the permissions in runtime?

If yes, how does it behave in an Asynchronous Context? Example:

const { setTimeout } = require('timers/promises');

console.log(process.policy.check('net')); // true

process.nextTick(() => {
  process.policy.deny('net');
  setTimeout(1000).then(() => {
    console.log(process.policy.check('net')); // false
  });
});

process.nextTick(() => {
  setTimeout(1000).then(() => {
    console.log(process.policy.check('net')); // ???
  });
});

3) What would be the desired granularity level?

cc/ @jasnell @Qard @mcollina @mhdawson

RafaelGSS commented 1 year ago

After talking with @addaleax and @jasnell about propagating the rules to the worker thread, we agreed that besides adding unnecessary complexity, it's too risky to do it (at least in this first iteration). I believe it can be a thing for the next iteration, but we need to be careful with this implementation.

jasnell commented 1 year ago

Definitely agree. Let's handle worker threads as a separate pr.

mcollina commented 1 year ago

That's ok, thanks for the clarification!

tniessen commented 1 year ago

@RafaelGSS Before https://github.com/nodejs/node/pull/44004 lands, I want to say that I am having trouble following the arguments in this discussion. This is somewhat reiterating my concerns from last year's https://github.com/nodejs/security-wg/issues/791#issuecomment-1367980274.

The main reason for implementing this at all is, as has been stated multiple times, to constrain the resources that can be accessed by supply chain attacks:

The goal for the Permission System is pretty straightforward. Supply Chain Attack is one of the attacks that anybody is vulnerable to nowadays. Today, if you install a package using npm i x, the package can exploit it in several ways. This security feature consists to grant powers to the developer in order to select which resource an application is allowed to execute

Yet, integration with package managers has not been discussed at all as far as I can tell. There are high-level ideas but I have yet to see specific implementation plans.

we haven't thought too much about its interoperability with package managers. My intention is to land the permission model targeting runtime usage and allowing its expansion easily. I'm pretty sure once it gets landed we'll discuss the next steps to implement it by default on package managers.

I think we can (and should) discuss some of the obvious challenges before landing any permission model.

For example, npm scripts in a package.json file are rather arbitrary shell commands, and npm merely adjusts the PATH environment variable to add executable files within node_modules/.bin to the PATH so that they can be used by the shell command.

It is extremely common to have scripts such as "node src/post-install.js", i.e., invoking node is often not up to the package manager but rather to the script, thus, it is up to the shell command which arguments to pass to the process. Similarly, the executable scripts within node_modules/.bin, at least on Linux, are regular JavaScript files with an added shebang (or, more precisely, symbolic links to rather arbitrary files), and it is non-trivial to pass additional arguments.

As @brianleroux wrote in https://github.com/nodejs/security-wg/issues/791#issuecomment-1108916559, it may not be likely that this finds adoption in production environments, where granular access control through more mature and complete features is widely available. In development environments, however, it is very common to have an npm test script such as "ava" (or any other test runner). Not only would npm somehow have to pass arguments to the shebang line that the ava binary contains, but many test runners, including ava, rely on process isolation and thus happily spawn child processes for separate tests, which either has to be prevented or will allow circumventing all restrictions. It seems that securing npm test through the proposed permission model would require significant changes to Node.js, to package managers, and perhaps even to package.json files and the ecosystem in general.

Similarly, I made this assumption above:

So would this only be helpful for the subset of npm install executions that don't work with --ignore-scripts but that also don't use native add-ons or child processes? And in those cases, npm would have to fine-tune permissions to prevent, for example, uploading credentials from disk?

Following this, I am wondering what the main use cases for scripts are that we hope to cover with this. Compilation, for example, often resorts to child processes, which is incompatible with the proposed permission model. The npm documentation even contains examples for running make through npm scripts, which entirely bypasses any permission model within Node.js.


As @Qard stated above:

For sure we can start with some MVP, but I suspect that won't actually be all that useful short of serving as a proof-of-concept.

Beyond said proof-of-concept, we should have at least a rough roadmap for what we hope to accomplish. Otherwise, we are adding a lot of complexity and attack surface to Node.js without a good justification.

Qard commented 1 year ago

Perhaps we could introduce a new parallel to the "scripts" section of a package.json something like "tasks" which only accepts js files and that can use whatever permissions are provided when telling node to run the task? For example, a package.json with my-task mapped to ./tasks/my-task.js would be run with something like node task my-task and it would internally do the equivalent of node ./tasks/my-task.js automatically. With node task --allow-whatever my-task it would apply the allow-whatever permission. Package managers could be responsible for narrowing those permissions appropriately in their own installing/running of each task. Could probably even make it runnable as a specialized worker thread so package managers can run each task within their own process and narrow task permissions from their own permission set.

In any case, I feel like the level of complexity and ecosystem impact here probably warrants an actual in-depth RFC covering the steps from initial implementation to the ultimate goal of how permissions will be used in the future when it's "complete".

RafaelGSS commented 1 year ago

@tniessen thanks for raising this discussion. I will try to answer your questions inline and in the end, I'll share a bit about my point of view on this feature.

The main reason for implementing this at all is, as has been stated multiple times, to constrain the resources that can be accessed by supply chain attacks:

This information isn't totally accurate. But that's my fault. I've used supply chain attacks as an example because it's easy to understand the motivations and the intention of this model. However, this feature will not solve supply chain attacks entirely, at least not in this first iteration. As you described very well:

Yet, integration with package managers has not been discussed at all as far as I can tell. There are high-level ideas but I have yet to see specific implementation plans.

It is extremely common to have scripts such as "node src/post-install.js", i.e., invoking node is often not up to the package manager but rather to the script, thus, it is up to the shell command which arguments to pass to the process. Similarly, the executable scripts within node_modules/.bin, at least on Linux, are regular JavaScript files with an added shebang (or, more precisely, symbolic links to rather arbitrary files), and it is non-trivial to pass additional arguments.

There are some edge cases uncovered by this feature yet and I agree that the statement of solving supply chain attacks doesn't apply totally.

However, this PR is just the gateway for something bigger, and I totally agree with you that we should be discussing how to integrate it into the ecosystem, that's on me. I've been working on this and my plan right after landing this on the 'main' as experimental is to create a roadmap, for instance, we should support other resources (net, os) and npm hooks.

We can also argue that the utility of this feature is in the development workflow, as you said, usually, production environments are surrounded by tools that supersede it. However, I don't think having a permission model in Node.js is useless, I think this is a good addition to runtime access management. For instance, denying access to folders/files in runtime or as prevention to modules that perform any kind of file system access during runtime. Until package managers adopt it as mitigation, the utility of this implementation will live in runtime. But, as I said, this is just a gateway, I'm pretty sure once we land it and see the adoption we can explore several strategies around this. And well, if that proves useless or not worth it to maintain, I will be the first one to request its removal. As an experimental module, we can do it without issues.

For what it's worth, the permission model was previously researched by @addaleax and @jasnell, and we have iterated over it a few times since last year. If you folks want to share your point of view, it would be much appreciated.

naugtur commented 1 year ago

Let me help with addressing the need/usecase for this as I'm working on a parallel implementation (and also a second generation of it) and running it "in production".

Here's a MetaMask repository with a policy (that's what permissions specification is called there) that's generated and enforced by LavaMoat https://github.com/MetaMask/metamask-extension/tree/develop/lavamoat/build-system

LavaMoat consists of 2 main tools:

For those of you who may enjoy a video format: https://www.youtube.com/watch?v=-IE4KzGdsUI

While it is not currently possible to apply a detailed policy to whatever runs as a postinstall script, turning them off by default is quite effective.

If we control the runtime environment and want to defend against malicious code being added to the software in our dependencies (especially things we actually call) - the policy is hard to bypass when running in hardened javascript (SES lockdown)

So yes, there's reasons to have this.

RafaelGSS commented 1 year ago

The Permission Model was landed in the main branch. A roadmap issue was created #898 to discuss the future of this feature. Therefore, I'm closing it as completed. Thank you all for your valuable input.

luislobo commented 1 year ago

Sorry to post on an old thread, may be just direct me to the right place.

With this Permission Model, would it make sense to start also defining permissions in package.json modules?

I thought of that once one of those NPM security issues where an author started deleting files because of politics.

After searching found a blog post from @davidgilbertson https://david-gilbertson.medium.com/npm-package-permissions-an-idea-441a02902d9b discussing about it.

Anyway, again, may be just to be pointed to the right thread/topic/slack/discord to contribute?

Thank you

tniessen commented 1 year ago

@luislobo Permissions are only supported at the process scope, that is, permission cannot be granted for some modules only. However, you might be interested in the existing policies feature, which are scoped to files/modules.