Skip to content

Commit

Permalink
feat(endomoat): policy-gen wip
Browse files Browse the repository at this point in the history
  • Loading branch information
boneskull committed Jan 30, 2024
1 parent 8442adb commit a353b11
Show file tree
Hide file tree
Showing 105 changed files with 868 additions and 122 deletions.
3 changes: 2 additions & 1 deletion .git-blame-ignore-revs
@@ -1 +1,2 @@
b026356079a4bfbd0f39edd578373c07d100453c
b026356079a4bfbd0f39edd578373c07d100453c
e12aedd5019347be29e52939b2aade9b2dc99baf
28 changes: 21 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/core/package.json
Expand Up @@ -35,6 +35,7 @@
"test:ses": "diff -q ../../node_modules/ses/dist/lockdown.umd.js ./lib/lockdown.umd.js"
},
"dependencies": {
"@babel/types": "7.23.6",
"json-stable-stringify": "1.1.1",
"lavamoat-tofu": "^7.1.0",
"merge-deep": "3.0.3",
Expand Down
43 changes: 24 additions & 19 deletions packages/core/src/generatePolicy.js
Expand Up @@ -251,6 +251,7 @@ function createModuleInspector(opts) {
* @param {boolean} includeDebugInfo
*/
function inspectForGlobals(ast, moduleRecord, packageName, includeDebugInfo) {
// TODO: this is too node-specific
const commonJsRefs = ['require', 'module', 'exports', 'arguments']
const globalObjPrototypeRefs = Object.getOwnPropertyNames(Object.prototype)
const foundGlobals = inspectGlobals(ast, {
Expand Down Expand Up @@ -316,6 +317,7 @@ function createModuleInspector(opts) {

/**
* @type {GeneratePolicyFn}
* @todo Allow aggredateDeps replacement fn to be passed in
*/
function generatePolicy({ policyOverride, includeDebugInfo = false }) {
/** @type {import('./schema').Resources} */
Expand All @@ -341,6 +343,7 @@ function createModuleInspector(opts) {
return
}
// get dependencies, ignoring builtins
// TODO: pass in packageName
const packageDeps = aggregateDeps({
packageModules,
moduleIdToModuleRecord,
Expand Down Expand Up @@ -423,33 +426,35 @@ function createModuleInspector(opts) {
* import('./moduleRecord').LavamoatModuleRecord
* >
* }} opts
* @returns
* @returns {string[]}
*/
function aggregateDeps({ packageModules, moduleIdToModuleRecord }) {
/** @type {Set<string>} */
const deps = new Set()
// get all dep package from the "packageModules" collection of modules
Object.values(packageModules).forEach((moduleRecord) => {
Object.entries(moduleRecord.importMap).forEach(
([requestedName, specifier]) => {
// skip entries where resolution was skipped
if (!specifier) {
return
}
// get packageName from module record, or guess
const moduleRecord = moduleIdToModuleRecord.get(specifier)
if (moduleRecord) {
// builtin modules are ignored here, handled elsewhere
if (moduleRecord.type === 'builtin') {
return
}
deps.add(moduleRecord.packageName)
Object.entries(moduleRecord.importMap).forEach(([specifier, moduleId]) => {
// verify what these values are!!

// skip entries where resolution was skipped
if (!moduleId) {
return
}
// get packageName from module record, or guess
const moduleRecord = moduleIdToModuleRecord.get(moduleId)
if (moduleRecord) {
// builtin modules are ignored here, handled elsewhere
if (moduleRecord.type === 'builtin') {
return
}
// moduleRecord missing, guess package name
const packageName = guessPackageName(requestedName)
deps.add(packageName)
deps.add(moduleRecord.packageName)
return
}
)
// moduleRecord missing, guess package name
// why/when does this happen?
const packageName = guessPackageName(specifier)
deps.add(packageName)
})
// ensure the package is not listed as its own dependency
deps.delete(moduleRecord.packageName)
})
Expand Down
106 changes: 95 additions & 11 deletions packages/core/src/moduleRecord.js
@@ -1,8 +1,75 @@
// @ts-check

/**
* A module record
*
* @template {any[]} [InitArgs=unknown[]] Default is `unknown[]`
*/
class LavamoatModuleRecord {
/**
* @param {LavamoatModuleRecordOptions} opts
* Module specifier
*
* @type {string}
*/
specifier

/**
* A filepath if {@link type} is `js`; otherwise the same value as
* {@link specifier}
*
* @type {string}
*/
file

/**
* The type of module
*
* @type {ModuleRecordType}
*/
type

/**
* The containing package name if {@link type} is `js`; otherwise the same
* value as {@link specifier}
*
* @type {string}
*/
packageName

/**
* Content of module (source code)
*
* Only applicable if {@link type} is `js`.
*
* @type {string | undefined}
*/
content

/**
* Map of specifiers to resolved filepaths or specifiers
*
* @type {Record<string, string>}
*/
importMap

/**
* Parsed AST, if any
*
* @type {import('@babel/types').File | undefined}
*/
ast

/**
* Module initializer function
*
* @type {ModuleInitializer<InitArgs> | undefined}
*/
moduleInitializer

/**
* Assigns properties!
*
* @param {LavamoatModuleRecordOptions<InitArgs>} opts
*/
constructor({
specifier,
Expand All @@ -28,16 +95,33 @@ class LavamoatModuleRecord {
module.exports = { LavamoatModuleRecord }

/**
* Options for {@link LavamoatModuleRecord} constructor.
*
* @template {any[]} [InitArgs=unknown[]] Default is `unknown[]`
* @typedef LavamoatModuleRecordOptions
* @property {string} specifier
* @property {string} file
* @property {'builtin' | 'native' | 'js'} type
* @property {string} packageName
* @property {string} [content]
* @property {Record<string, string>} [importMap]
* @property {import('@babel/types').File} [ast]
* @property {(...args: any[]) => any} [moduleInitializer]
* @todo `moduleInitializer` probably needs narrowing
* @property {string} specifier - Module specifier
* @property {string} file - Path to module file (or specifier)
* @property {ModuleRecordType} type - Module type
* @property {string} packageName - Package containing module (or specifier)
* @property {string} [content] - Content of module (source)
* @property {Record<string, string>} [importMap] - Import map
* @property {import('@babel/types').File} [ast] - Parsed AST
* @property {ModuleInitializer<InitArgs>} [moduleInitializer] - Module
* initializer function
*/

/**
* Possible value of {@link LavamoatModuleRecord.type}.
*
* _Note:_ `js` means "source code", **not** "JavaScript source code"
*
* @typedef {'builtin' | 'native' | 'js'} ModuleRecordType
*/

/**
* Module initializer function
*
* @todo `@babel/types` should be a prod dep
* @template {any[]} [InitArgs=unknown[]] Default is `unknown[]`
* @typedef {(...args: InitArgs) => void} ModuleInitializer
* @todo Define what this means exactly
*/
3 changes: 2 additions & 1 deletion packages/endomoat/.eslintrc.cjs
Expand Up @@ -9,8 +9,9 @@ module.exports = {
node: true,
},
rules: {
// agoric-specific rule
// agoric-specific rules
'@endo/no-nullish-coalescing': 'off',
'@endo/no-optional-chaining': 'off',
},
extends: ['plugin:@endo/recommended'],
}
2 changes: 1 addition & 1 deletion packages/endomoat/package.json
Expand Up @@ -39,7 +39,7 @@
"@types/node": "20.10.6",
"lavamoat-core": "^15.0.0",
"ses": "1.1.0",
"type-fest": "4.10.0",
"type-fest": "4.10.1",
"yargs": "17.7.2"
},
"devDependencies": {
Expand Down
7 changes: 6 additions & 1 deletion packages/endomoat/src/constants.js
Expand Up @@ -3,7 +3,7 @@
*
* _Type a string more than once? Make it a constant!_
*/

import { builtinModules } from 'node:module'
import path from 'node:path'

export const DEFAULT_POLICY_PATH = path.join(
Expand All @@ -27,3 +27,8 @@ export const LAVAMOAT_PKG_POLICY_ROOT = '$root$'
export const RSRC_POLICY_PKGS = 'packages'
export const RSRC_POLICY_BUILTINS = 'builtins'
export const RSRC_POLICY_GLOBALS = 'globals'

export const NODEJS_BUILTIN_MODULES = Object.freeze([
...builtinModules,
...builtinModules.map((id) => `node:${id}`),
])
26 changes: 26 additions & 0 deletions packages/endomoat/src/create-module-transform.js
@@ -0,0 +1,26 @@
import { evadeCensor } from '@endo/evasive-transform'
import { applySourceTransforms } from 'lavamoat-core'
import { textDecoder, textEncoder } from './index.js'

/**
* Create module transform which performs source transforms to evade SES
* restrictions
*
* @param {import('@endo/compartment-mapper').Language} parser
* @returns {import('@endo/compartment-mapper').ModuleTransform}
*/
export function createModuleTransform(parser) {
return async (sourceBytes, specifier, location, _packageLocation, opts) => {
let source = textDecoder.decode(sourceBytes)
// FIXME: this function calls stuff we could get in `ses/tools.js`
// except `evadeDirectEvalExpressions`. unclear if we should be using this from `lavamoat-core`
source = applySourceTransforms(source)
const { code, map } = await evadeCensor(source, {
sourceMap: opts?.sourceMap,
sourceUrl: new URL(specifier, location).href,
sourceType: 'module',
})
const objectBytes = textEncoder.encode(code)
return { bytes: objectBytes, parser, map }
}
}
18 changes: 18 additions & 0 deletions packages/endomoat/src/import-hook.js
@@ -0,0 +1,18 @@
const { freeze, keys, assign } = Object

/**
* @type {import('@endo/compartment-mapper').ExitModuleImportHook}
*/
export const importHook = async (specifier) => {
const ns = await import(specifier)
return freeze(
/** @type {import('ses').ThirdPartyStaticModuleInterface} */ ({
imports: [],
exports: keys(ns),
execute: (moduleExports) => {
moduleExports.default = ns
assign(moduleExports, ns)
},
})
)
}

0 comments on commit a353b11

Please sign in to comment.