Paraquire, or Stop Trusting Libraries

    TL DR


    Using npm, the NodeJS package manager, is fraught with security issues. By regular means it is impossible to control the access rights granted to libraries. Together with the abundance of micromodules, this can lead to unpredictable consequences, some of what has already happened is described here , and in the best traditions of the npm ecosystem, I refer to it.



    Under the cat, a proof-of-concept library is described that implements a mechanism for loading npm modules with the ability to set permissions, just like on Android you can issue specific permissions to an application.

    Instead

    var lib = require('untrusted-lib');
    

    invited to write somewhere

    var paraquire = require('paraquire')(module);
    

    and then

    var lib = paraquire('untrusted-lib');
    

    or

    var lib = paraquire('untrusted-lib', {builtin:{https:true}});
    

    The source code is available on the github under LGPLv3.

    In addition, I, not being a fairly experienced NodeJS developer, ask the community for advice and discussion.

    A brief history analysis


    Currently there are several attack vectors for npm and its packages. The most attractive packages for attack are those on which many other packages depend.

    1. Package recall. Devastatingly demonstrated by Azer Kochulu, who recalled the leftpad package. Currently not feasible, as package recall is very limited.
    2. Malicious preinstall and postinstall scripts. Currently, the main method of attack (see link above). Theoretically, it can be easily suppressed by the npm install flag, which would lead to a warning before running any shell script, so that the programmer himself can decide whether to run the proposed one. It is one thing when a preinstall script tries to run PhantomJS (a headless browser), and quite another when a small library requires it to add spaces to the left of the line. Why such a flag has not yet been introduced is unclear.
      UPD: Thanks SDSWanderer for the comment on the opportunity to do

      npm install --ignore-scripts
      

      However, an interactive option (preferably with the contents of the script shown) would also not be amiss.
    3. And finally, you can hide the malicious load directly into the executable code of the package, as described in a comic form here . The npm shrinkwrap command, which fixes the package version hard, will not help with “slow-motion mines”, which, for example, are waiting for a certain date (recall “Chernobyl”). In addition, with the current abundance of dependencies, an audit of the source code of even one version of the desired package along with everything that this package uses is, of course, a feasible task, but obviously not everyday.

    It is to combat the last vector that the paraquire library was created. Untrusted library code is isolated in separate execution contexts . Actively used internal tools of NodeJS itself for module management. In other words, an alternative module management system has been created, the possibility of which is clearly indicated by NodeJS developers themselves.

    Using


    Currently, the API is very poor.

    So, for starters, load the paraquire library itself somewhere in relatively trusted code, i.e. our application code:

    var paraquire = require('paraquire')(module);
    

    Pay attention to "(module)". Since paraquire manages modules, it needs to know which module it is necessary to connect dependencies to, so connecting paraquire looks somewhat unusual. You can connect paraquire to any number of modules from your project.

    Now connect the dependencies. For example, let untrusted-lib be a library, which, in theory, should not require anything (for example, leftpad or imurmurhash ). Then we can not give her access to anything at all by connecting it like this:

    var lib = paraquire('untrusted-lib');
    

    That's it, an attempt to make require ('fs') in untrusted-lib code will throw an error.

    The paraquire function also has a second, optional, object parameter. For example, this is how you can give the untrusted-lib library access to the built-in http and https modules, as well as to the console console and process.argv:

    var lib = paraquire('untrusted-lib', {
        builtin: {
            http: true,
            https: true,
        },
        sandbox: {
            console: console,
            process: {argv:process.argv}
        },
    });
    

    By the way, giving access to the whole process is strongly discouraged, in particular because of process.env and process.bindings ('fs').

    I ask for advice


    As mentioned above, I am not an experienced enough developer, and therefore this brief publication is intended to initiate a discussion more quickly. Is this topic relevant? How similar problems are solved in other languages, in particular, in Ruby and Python? Maybe someone can suggest a way to get around paraquire and get elevated privileges?

    In which direction should the API be developed? Maybe you should consult with one of the foreign masters? What questions should be covered in future articles and is it worth writing them at all?

    Only registered users can participate in the survey. Please come in.

    Is there a need for access control for libraries in NodeJS?

    • 68.8% Need 86
    • 31.2% Do not need 39

    Is it worth writing one more (or several) articles on Habr, telling in detail about how paraquire works?

    • 70.7% Worth 80
    • 29.2% Not worth 33

    Also popular now: