diff --git a/dylan-testing/common/sudo.js b/dylan-testing/common/sudo.js
new file mode 100644
index 0000000000000000000000000000000000000000..96f01c6b6b9097e1ed9cc35c24acd03449b97f9c
--- /dev/null
+++ b/dylan-testing/common/sudo.js
@@ -0,0 +1,88 @@
+"use strict";
+
+var cproc = require("child_process");
+
+// TODO: JSDoc
+module.exports = function sudo(command, args, options, callback){
+    const REGEX_USER = /^[a-zA-Z0-9_]+$/;
+    const REGEX_GROUP = REGEX_USER;
+    const REGEX_PASS = /^[^\x00-\x1f\x7f]+$/;
+    
+    var argv = [];
+
+    try {
+        // Process and validate arguments, building an array of args for
+        // the underlying exec call
+        if(!command || typeof command != "string")
+            throw new Error("command must be a non-empty string");    
+        
+        // Validate any additional options
+        if(options && typeof options == "object"){
+            if(options.username){
+                if(
+                    typeof options.username != "string"
+                    || !options.username.match(REGEX_USER)
+                )
+                    throw new Error(
+                        "username must be a string with only letters, "
+                        + "numbers, and underscores"
+                    );
+                else argv.push("-u", options.username);
+            }
+            // TODO: Add test cases for this
+            if(options.password){
+                if(
+                    typeof options.password != "string"
+                    || !options.password.match(REGEX_PASS)
+                )
+                    throw new Error(
+                        "password must only contain printable characters"
+                    );
+            }
+            if(options.group){
+                if(
+                    typeof options.group != "string"
+                    || !options.group.match(REGEX_GROUP)
+                )
+                    throw new Error(
+                        "group must be a string with only letters, "
+                        + "numbers, and underscores"
+                    );
+                else argv.push("-g", options.group);
+            }
+            if(options.timeout); // here for readability
+        }
+
+        // Add -k to avoid credential caching, -S to accept password from
+        // stdin, and -- to prevent args from injecting arguments that affect
+        // the behaviour of sudo. Finally, add command to the list of arguments
+        // for sudo
+        argv.push("-kS", "--");
+        argv.push(command);
+
+        // If args was specified, append these one by one to argv for sudo,
+        // casting each to a string, and checking for null character abuse 
+        if(args && args.length > 0){
+            for(var i = 0; i < args.length; i++){
+                var arg = ""+args[i];
+                if(arg.indexOf("\0") >= 0)
+                    throw new Error("args must not contain null characters");
+                argv.push(arg);
+            }
+        }
+        console.dir(argv);
+
+        // Call sudo, and write password (if any) to its stdin, then return
+        // the resulting ChildProcess
+        var execOpts = {
+            timeout: (options.timeout>0 ? options.timeout : 0),
+            killSignal: (options.timeout>0 ? "SIGKILL" : "SIGTERM")
+        };
+        var childProc = cproc.execFile("sudo", argv, execOpts, callback);
+        childProc.stdin.end((options.password ? options.password : "") + "\n");
+        return childProc;
+    }
+    catch(e){
+        if(typeof callback == "function") callback(e);
+    }
+}
diff --git a/dylan-testing/core/login-service.js b/dylan-testing/core/login-service.js
index 7b1787d64993d58847cfc2141f7e004cb77654dd..4994d62e23c6561e7927b6fb231683132f975ff1 100644
--- a/dylan-testing/core/login-service.js
+++ b/dylan-testing/core/login-service.js
@@ -7,9 +7,14 @@ var qstring = require("querystring");
 var express = require("express");
 
 var hsocks = require("common/http-sockets");
+var sudo = require("common/sudo");
 var config = require("common/config");
 
-function testCredentials(username, password, callback){    
+
+// TODO: replace with sudo implementation
+// This is a stub that will likely just be replaced with
+// a call to the sudo function
+function testCredentials(username, password, callback){ 
     // Validate input
     if(
         typeof username != "string" || !username