javascriptnode.jsmoduleread-eval-print-loopnode-repl

How do I limit the node repl's access to internal node modules?


In a previous question I figured out how to eliminate unwanted global variables from the repl context. However, I figured out that the repl automatically has access to ALL internal node modules without the use of require. I have no idea how to disable this. I even tried overriding the module variables in the repl itself and it doesn't work.

> fs = "test";
> fs

It still displays fs original value. This is very unfortunate because I'm trying to expose a public repl but it gives them access to the entire server.

Any ideas?


Solution

  • As you said, the REPL has access to core modules.

    (however, after checking, I am able to override them with node 0.10.20, so there should be a solution)

    > fs
    > { Stats: [Function], …
    > fs = 'hello';
    > fs
    'hello'
    

    A better way would be to just override repl._builtinLibs before creating a repl instance.

    var repl = require('repl');
    repl._builtinLibs = [];
    repl.start('> ');
    

    Also, it's fairly trivial to white-list repl commands if you don't want to expose commands like .save or .load.

    var allowedReplCmds = ['.break', '.clear', '.help'];
    
    var newRepl = repl.start('> ');
    for (var key in newRepl.commands)
        if (!allowedReplCmds.contains(key))
            delete replInstance.commands[key];
    

    Note: Arrays don't normally have a contains method so I added one.

    Array.prototype.contains = function(v) {
        for(var i = 0; i < this.length; i++) {
            if(this[i] === v) return true;
        }
        return false;
    };
    

    If you want to remove variables from the repl instance's global scope see this question.


    Please note that it is very unsafe to expose a REPL to the public.

    You can easily crash the whole server

    > setTimeout(function () { throw 'bye bye!'; }, 0);
    

    Errors that happen in async callbacks are not caught by the REPL and bring down the node.js instance.

    You can block the server

    > while(true) {};
    

    Your best bet would be to code your own REPL in a separate process with child_process, readline and vm. Here's a starting point:

    The master:

    // master.js
    var fork = require('child_process').fork;
    
    // functions exposed to the repl
    var replApi = {
      hello: function () {
        return 'world!';
      },
    
      unknown: function () {
        return 'unknown';
      }
    };
    
    function forkRepl() {
      var repl = fork('./child_repl');
    
      repl.on('message', function (command) {
        var fun = replApi[command] || replApi.unknown;
         repl.send(fun());
      });
    
      // restart the repl if it dies
      repl.on('exit', forkRepl);
    }
    
    forkRepl();
    

    and the separate process for the repl:

    // child_repl.js
    var readline = require('readline'),
        vm = require('vm');
    
    var context = vm.createContext({
      hello: function () {
        process.send('hello');
      }
    });
    
    var rl = readline.createInterface({
      input: process.stdin,
      output: process.stdout
    });
    
    rl.on('line', function (line) {
      vm.runInContext(line, context, 'repl.vm');
    });
    
    process.on('message', function (message) {
      console.log('master:', message);
    });
    
    rl.prompt();