feat(endo): Support exec shell environment
MODIFY
packages/endo/bin/endo.js
packages/endo/bin/endo.js
#!/usr/bin/env node
-import fs from "fs";
-import { main } from "../src/cli.js";
-
+(async () => {
+ const fs = await import("fs");
+ const { main } = await import("../src/cli.js");
main(process, { fs: fs.promises });
+})();

ADD
packages/endo/mitm/.gitignore
+node

MODIFY
packages/endo/package.json
packages/endo/package.json
"depcheck": "depcheck",
"lint": "eslint '**/*.js'",
"lint-fix": "eslint --fix '**/*.js'",
+ "postinstall": "node src/postinstall.js",
"prepublish": "yarn clean && yarn build",
"test": "yarn build && tap --no-esm --no-coverage --reporter spec 'test/**/*.test.js'"
},

MODIFY
packages/endo/src/cli.js
packages/endo/src/cli.js
/* eslint no-shadow: [0] */
import "./lockdown.js";
+import subprocess from "child_process";
import { writeArchive } from "./main.js";
import { search } from "./search.js";
import { compartmentMapForNodeModules } from "./compartmap.js";
+const mitmPath = new URL("../mitm", import.meta.url).pathname;
+
function usage(message) {
console.error(message);
return 1;
@@ -36,7 +39,7 @@ async function parameter(args, handle, usage) {
return handle(arg, rest);
}
-async function run(args, { cwd, read, write, stdout }) {
+async function run(args, { cwd, read, write, stdout, env }) {
async function compartmap(args) {
async function handleEntry(applicationPath, args) {
if (args.length) {
@@ -72,12 +75,20 @@ async function run(args, { cwd, read, write, stdout }) {
return parameter(args, handleArchive, noArchiveUsage);
}
- return subcommand(args, { compartmap, archive });
+ async function exec([arg, ...args]) {
+ const child = subprocess.spawn(arg, args, {
+ env: { ...env, PATH: `${mitmPath}:${env.PATH}` },
+ stdio: "inherit"
+ });
+ return new Promise(resolve => child.on("exit", resolve));
+ }
+
+ return subcommand(args, { compartmap, archive, exec });
}
export async function main(process, modules) {
const { fs } = modules;
- const { cwd, stdout } = process;
+ const { cwd, stdout, env } = process;
// Filesystem errors often don't have stacks:
@@ -102,7 +113,8 @@ export async function main(process, modules) {
read,
write,
cwd,
- stdout
+ stdout,
+ env
});
} catch (error) {
process.exitCode = usage(error.stack || error.message);

ADD
packages/endo/src/lockdown.cjs
+/* global lockdown */
+
+// This is a CommonJS module, to be used like `node -r endo/src/lockdown.cjs`.
+// The `endo exec` command stages a man-in-the-middle `node` shell script that
+// in turn injects this SES lockdown parameter in all Node.js commands in the
+// resulting shell environment.
+// The taming behavior may be overridden with environment variables
+// like `ERROR_TAMING=unsafe endo exec node ...`
+
+require("ses");
+lockdown({
+ dateTaming: process.env.ENDO_DATE_TAMING || 'safe',
+ errorTaming: process.env.ENDO_ERROR_TAMING || 'safe',
+ mathTaming: process.env.ENDO_MATH_TAMING || 'safe',
+ regExpTaming: process.env.ENDO_REGEXP_TAMING || 'safe',
+ localeTaming: process.env.ENDO_LOCAlE_TAMING || 'safe',
+});

MODIFY
packages/endo/src/lockdown.js
packages/endo/src/lockdown.js
/* global lockdown */
import "ses";
+// This is used by Endo to lockdown its own start compartment.
+
lockdown({
errorTaming: "unsafe"
});

ADD
packages/endo/src/postinstall.js
+// This postinstall hook creates mitm/node.
+// This in turn interposes SES lockdown for all descendent processes, provided
+// that the mitm directory is appears before Node.js's bin directory on the
+// environment PATH.
+
+import fs from "fs";
+
+const node = process.argv[0];
+const lockdown = new URL("../src/lockdown.cjs", import.meta.url).pathname;
+const mitm = new URL("../mitm/node", import.meta.url).pathname;
+
+const script = `#!/bin/bash
+set -ueo pipefail
+${node} -r ${lockdown} "$@"`;
+
+fs.writeFileSync(mitm, script, "utf-8");
+fs.chmodSync(mitm, 0o755);

feat(endo): Support exec shell environment (#411)