Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

endo run #1168

Draft
wants to merge 6 commits into
base: dev-run-cli
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/experiment-run-cli/bin/endo
8 changes: 8 additions & 0 deletions packages/experiment-run-cli/bin/endo.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/usr/bin/env node
(async () => {
const { main } = await import('../src/endo.js');
await main(process.argv.slice(2));
})().catch(error => {
console.error(error);
process.exitCode = 1;
});
1 change: 1 addition & 0 deletions packages/experiment-run-cli/bin/endo.cmd
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@node %~dp0\endo.cjs %*
17 changes: 13 additions & 4 deletions packages/experiment-run-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@
"lint-fix": "eslint --fix .",
"lint:js": "eslint .",
"lint:types": "tsc -p jsconfig.json",
"test": "exit 0",
"test": "ls -d ./test/fixtures/* | xargs -n1 ./bin/endo run --lockdown --evasion",
"prepublishOnly": "exit 112"
},
"dependencies": {
"@endo/compartment-mapper": "^0.7.5",
"@endo/compartment-mapper": "../compartment-mapper",
"@endo/lockdown": "^0.1.13",
"@endo/where": "^0.2.7",
"commander": "^5.0.0",
"commander": "^9.2.0",
"ses": "^0.15.15"
},
"devDependencies": {
Expand All @@ -47,12 +47,21 @@
},
"eslintConfig": {
"extends": [
"node",
"@endo"
]
},
"prettier": {
"trailingComma": "all",
"singleQuote": true
"singleQuote": true,
"overrides": [
{
"files": "*.cjs",
"options": {
"parser": "babel"
}
}
]
},
"ava": {
"files": [
Expand Down
47 changes: 47 additions & 0 deletions packages/experiment-run-cli/src/core-modules.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
export const importReal = async (
moduleNames,
containerName = 'external-modules',
) => {
const realModules = {};

const coreModulesCompartment = new Compartment(
{},
{},
{
name: containerName,
resolveHook: moduleSpecifier => {
return moduleSpecifier;
},
importHook: async moduleSpecifier => {
const ns =
realModules[moduleSpecifier].default || realModules[moduleSpecifier];

const staticModuleRecord = Object.freeze({
imports: [],
exports: Object.keys(ns),
execute: moduleExports => {
Object.assign(moduleExports, ns);
moduleExports.default = ns;
},
});
return staticModuleRecord;
},
},
);

await Promise.all(
moduleNames
.map(async name => {
realModules[name] = await import(name);
})
);

return Object.fromEntries(
await Promise.all(
Object.keys(realModules).map(async name => [
name,
(await coreModulesCompartment.import(name)).namespace,
]),
),
);
};
69 changes: 69 additions & 0 deletions packages/experiment-run-cli/src/endo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/* global process */

// Establish a perimeter:
import 'ses';
// import '@endo/eventual-send/shim.js';
// import '@endo/lockdown/commit.js';

import fs from 'fs';
import url from 'url';
import crypto from 'crypto';

import { Command } from 'commander';

import {
makeReadPowers,
makeWritePowers,
} from '@endo/compartment-mapper/node-powers.js';
import { run } from './run.js';

const readPowers = makeReadPowers({ fs, url, crypto });

const packageDescriptorPath = url.fileURLToPath(
new URL('../package.json', import.meta.url),
);

export const main = async rawArgs => {
const program = new Command();

// program.storeOptionsAsProperties(false);

const packageDescriptorBytes = await fs.promises.readFile(
packageDescriptorPath,
);
const packageDescriptor = JSON.parse(packageDescriptorBytes);
program.name(packageDescriptor.name).version(packageDescriptor.version);



program
.command('run <file>')
.option('--lockdown', 'enable lockdown', false)
.option('--evasion', 'enable evasion transform', false)
.option('--stingy', 'not be generous with endowments', false)
.option('--lockdown-verbose', 'print extra information from lockdown; enables --lockdown too', false)
.action(async (applicationEntrypoint, { lockdown, evasion, stingy, lockdownVerbose }, cmd) => {
await run({
path: applicationEntrypoint,
readPowers,
shouldLockdown: lockdown,
verboseLockdown: lockdownVerbose,
shouldUseEvasionTransform: evasion,
shouldEndowAll: !stingy
})
});


// Throw an error instead of exiting directly.
program.exitOverride();

try {
await program.parse(rawArgs, { from: 'user' });
} catch (e) {
if (e && e.name === 'CommanderError') {
return e.exitCode;
}
throw e;
}
return 0;
};
46 changes: 46 additions & 0 deletions packages/experiment-run-cli/src/node-core-enough.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { importReal } from './core-modules.js';

const coreModuleNames = [
'node:assert',
'assert',
'node:fs',
'fs',
'node:path',
'path',
'node:os',
'os',
'node:util',
'util',
'node:buffer',
'buffer',
'node:crypto',
'crypto',
'node:events',
'events',
'node:stream',
'stream',
'readable-stream',
'inherits',
'node:http',
'http',
'node:https',
'https',
'node:zlib',
'zlib',
'node:url',
'url',
'node:net',
'net',
'node:module',
'module',
'node:tty',
'tty',
'node:constants',
'constants',
'node:querystring',
'querystring',
'node:string_decoder',
'string_decoder',
];

export const nodeCoreModules = await importReal(coreModuleNames);
51 changes: 51 additions & 0 deletions packages/experiment-run-cli/src/run.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import url from 'url';
import 'ses';
import { loadLocation } from '@endo/compartment-mapper';
import { nodeCoreModules } from './node-core-enough.js';
import { moduleTransforms } from './sesEvasionTransform.js';

export async function run({
path, readPowers,
shouldLockdown = false, shouldUseEvasionTransform = false, shouldEndowAll = true, verboseLockdown = false,
}) {
if (verboseLockdown) {
lockdown({"errorTaming":"unsafe","stackFiltering":"verbose","overrideTaming":"severe","overrideDebug":["constructor", "toString"]});
}
if (shouldLockdown) {
lockdown();
}

let transforms;
if (shouldUseEvasionTransform) {
transforms = moduleTransforms;
}

let globals;
if(shouldEndowAll){
globals = {
process, global, console, globalThis, /*btoa, atob,*/ Buffer,
...globalThis,
};
} else {
globals = { console }
}


const entrypoint = url.pathToFileURL(path);

const application = await loadLocation(readPowers, entrypoint,{
moduleTransforms: transforms,
dev: true,
});

try {

await application.import({
globals,
modules: shouldEndowAll?nodeCoreModules:{},
});
} catch (error) {
console.error(error);
process.exit(1)
}
}
65 changes: 65 additions & 0 deletions packages/experiment-run-cli/src/sesEvasionTransform.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import * as babelParser from '@babel/parser';
import babelGenerate from '@agoric/babel-generator';
import babelTraverse from '@babel/traverse';
import * as t from '@babel/types';

// Don't look at me, I just copied it
const parseBabel = babelParser.default
? babelParser.default.parse
: babelParser.parse || babelParser;

function rewriteComment(node) {
node.type = 'CommentBlock';
// No need for comments at runtime
node.value = '';
}
function transformAst(ast) {
(babelTraverse.default || babelTraverse)(ast, {
enter(p) {
const { comments, leadingComments, innerComments, trailingComments } =
p.node;
// Let modules use the tamed eval
if (p.node.name === 'eval' && !p.parentPath.isSequenceExpression()) {
p.replaceWith(t.sequenceExpression([t.numericLiteral(0), t.identifier('eval')]))
}
// Defuse import statement triggers in string literals without affecting the resulting string
if (p.node.type === 'StringLiteral') {
p.node.value = p.node.value.replace(/import\(/g, 'import\\(');
}
// Rewrite all comments.
(comments || []).forEach((node) => rewriteComment(node));
(leadingComments || []).forEach((node) => rewriteComment(node));
if (p.node.type.startsWith('Comment')) {
rewriteComment(p.node);
}
(innerComments || []).forEach((node) => rewriteComment(node));
(trailingComments || []).forEach((node) => rewriteComment(node));
},
});
}

export function transformSource(code, { sourceType } = {}) {
const ast = parseBabel(code, {
sourceType,
allowReturnOutsideFunction: true,
});

transformAst(ast);

return (babelGenerate.default || babelGenerate)(ast, {
retainLines: true,
});
}


const importsTransform = (sourceType, parser) => sourceBytes => {
const source = new TextDecoder().decode(sourceBytes);
const object = transformSource(source, { sourceType });
const objectBytes = new TextEncoder().encode(object.code);

return { bytes: objectBytes, parser };
};
export const moduleTransforms = {
mjs: importsTransform('module', 'mjs'),
cjs: importsTransform('script', 'cjs'),
};
3 changes: 3 additions & 0 deletions packages/experiment-run-cli/test/fixtures/simple.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
var a = Math.E;

console.log('E', a)
Loading