feat(compartment-mapper): attenuate builtins with attenuators from packages
MODIFY
packages/compartment-mapper/src/archive.js
packages/compartment-mapper/src/archive.js
@@ -31,6 +31,7 @@ import {
stringCompare,
pathCompare,
} from './compartment-map.js';
+import { detectAttenuators } from './policy.js';
const textEncoder = new TextEncoder();
@@ -52,7 +53,7 @@ const parserForLanguage = {
*/
const resolveLocation = (rel, abs) => new URL(rel, abs).toString();
-const { keys, entries, fromEntries } = Object;
+const { keys, values, entries, fromEntries } = Object;
/**
* We attempt to produce compartment maps that are consistent regardless of
@@ -302,7 +303,7 @@ const digestLocation = async (powers, moduleLocation, options) => {
searchSuffixes,
);
// Induce importHook to record all the necessary modules to import the given module specifier.
- const { compartment } = link(compartmentMap, {
+ const { compartment, attenuatorsCompartment } = link(compartmentMap, {
resolve,
modules: exitModules,
makeImportHook,
@@ -312,6 +313,14 @@ const digestLocation = async (powers, moduleLocation, options) => {
policy,
});
await compartment.load(entryModuleSpecifier);
+ if (policy) {
+ // retain all attenuators. this is the only reason we still need attenuators field in policy. Can be generated from the rest of policy
+ await Promise.all(
+ detectAttenuators(policy).map(attenuatorSpecifier =>
+ attenuatorsCompartment.load(attenuatorSpecifier),
+ ),
+ );
+ }
const compartmentRenames = renameCompartments(compartments);
const archiveCompartments = translateCompartmentMap(

MODIFY
packages/compartment-mapper/src/import.js
packages/compartment-mapper/src/import.js
@@ -74,7 +74,6 @@ export const loadLocation = async (readPowers, moduleLocation, options) => {
globals,
modules,
transforms,
- attenuations,
__shimTransforms__,
Compartment,
} = options;
@@ -92,12 +91,13 @@ export const loadLocation = async (readPowers, moduleLocation, options) => {
parserForLanguage,
globals,
modules,
- attenuations,
+ policy,
transforms,
moduleTransforms,
__shimTransforms__,
Compartment,
});
+
return compartment.import(moduleSpecifier);
};

MODIFY
packages/compartment-mapper/src/link.js
packages/compartment-mapper/src/link.js
@@ -18,6 +18,8 @@ import {
getAllowedGlobals,
gatekeepModuleAccess,
attenuateModuleHook,
+ ATTENUATORS_COMPARTMENT,
+ getPolicyFor,
} from './policy.js';
const { entries, fromEntries, freeze } = Object;
@@ -192,7 +194,7 @@ const trimModuleSpecifierPrefix = (moduleSpecifier, prefix) => {
* @param {Record<string, ModuleDescriptor>} moduleDescriptors
* @param {Record<string, ModuleDescriptor>} scopeDescriptors
* @param {Record<string, string>} exitModules
- * @param {Record<string, Object>} attenuations
+ * @param {Record<string, Object>} attenuators
* @param {boolean} archiveOnly
* @returns {ModuleMapHook | undefined}
*/
@@ -203,7 +205,7 @@ const makeModuleMapHook = (
moduleDescriptors,
scopeDescriptors,
exitModules,
- attenuations,
+ attenuators,
archiveOnly,
) => {
/**
@@ -245,14 +247,17 @@ const makeModuleMapHook = (
exit,
module,
compartmentDescriptor.policy,
- attenuations,
+ attenuators,
);
}
}
if (foreignModuleSpecifier !== undefined) {
+ if (!moduleSpecifier.startsWith('./')) {
+ // archive goes through foreignModuleSpecifier for local modules too
gatekeepModuleAccess(moduleSpecifier, compartmentDescriptor.policy, {
exit: false,
});
+ }
const foreignCompartment = compartments[foreignCompartmentName];
if (foreignCompartment === undefined) {
@@ -281,7 +286,7 @@ const makeModuleMapHook = (
moduleSpecifier,
exitModules[moduleSpecifier],
compartmentDescriptor.policy,
- attenuations,
+ attenuators,
);
}
}
@@ -364,7 +369,7 @@ export const link = (
moduleTransforms = {},
__shimTransforms__ = [],
modules: exitModules = {},
- attenuations,
+ policy,
archiveOnly = false,
Compartment = defaultCompartment,
},
@@ -373,6 +378,9 @@ export const link = (
/** @type {Record<string, Compartment>} */
const compartments = Object.create(null);
+
+ const attenuators = v => compartments[ATTENUATORS_COMPARTMENT].import(v);
+
/** @type {Record<string, ResolveHook>} */
const resolvers = Object.create(null);
for (const [compartmentName, compartmentDescriptor] of entries(
@@ -422,7 +430,7 @@ export const link = (
modules,
scopes,
exitModules,
- attenuations,
+ attenuators,
archiveOnly,
);
const resolveHook = resolve;
@@ -458,11 +466,13 @@ export const link = (
)} is missing from the compartment map`,
);
}
+ const attenuatorsCompartment = compartments[ATTENUATORS_COMPARTMENT];
return {
compartment,
compartments,
resolvers,
+ attenuatorsCompartment,
};
};

MODIFY
packages/compartment-mapper/src/node-modules.js
packages/compartment-mapper/src/node-modules.js
@@ -45,7 +45,7 @@ import { searchDescriptor } from './search.js';
import { parseLocatedJson } from './json.js';
import { unpackReadPowers } from './powers.js';
import { pathCompare } from './compartment-map.js';
-import { getPolicyFor } from './policy.js';
+import { getPolicyFor, ATTENUATORS_COMPARTMENT } from './policy.js';
import { join } from './node-module-specifier.js';
const { assign, create, keys, values } = Object;
@@ -737,7 +737,7 @@ const translateGraph = (
}
const packagePolicy = getPolicyFor(
- dependeeLocation === entryPackageLocation ? name : dependeeLocation,
+ dependeeLocation === entryPackageLocation ? '<root>' : dependeeLocation,
policy,
);
@@ -791,14 +791,23 @@ export const compartmentMapForNodeModules = async (
canonical,
packageLocation,
tags,
- packageDescriptor,
+ packageDescriptor, // attenuators are likely to already be here if they're dependencies of the project.
dev,
commonDependencies,
);
- trace(graph, packageLocation, []);
+ if (policy) {
+ // Instead of worrying if '<ATTENUATORS_COMPARTMENT>' is unique enough, we're overwriting whatever tried to use it anyway
+ // the attenuators compartment is where attenuators get loaded
+ graph[ATTENUATORS_COMPARTMENT] = {
+ ...graph[packageLocation],
+ externalAliases: {},
+ label: ATTENUATORS_COMPARTMENT,
+ name: ATTENUATORS_COMPARTMENT,
+ };
+ }
- // console.log(graph)
+ trace(graph, packageLocation, []);
const compartmentMap = translateGraph(
packageLocation,

MODIFY
packages/compartment-mapper/src/policy.js
packages/compartment-mapper/src/policy.js
@@ -26,7 +26,23 @@ const adaptId = id => {
}
return chunks.join('>');
};
+export const ATTENUATORS_COMPARTMENT = '<ATTENUATORS>';
+const attenuatorsCache = new WeakMap();
+export const detectAttenuators = policy => {
+ if (!attenuatorsCache.has(policy)) {
+ const attenuators = [];
+ // a free recursive visitor implementation for simple objects
+ stringify(policy, (key, value) => {
+ if (key === 'attenuate' && typeof value === 'string') {
+ attenuators.push(value);
+ }
+ return value;
+ });
+ attenuatorsCache.set(policy, attenuators);
+ }
+ return attenuatorsCache.get(policy);
+};
/**
* Returns the policy applicable to the id - either by taking from user
* supplied policy or returning localPolicy if user didn't specify one at runtime.
@@ -39,6 +55,14 @@ export const getPolicyFor = (id, policy) => {
if (!policy) {
return undefined;
}
+ if (id === ATTENUATORS_COMPARTMENT) {
+ return {
+ packages: detectAttenuators(policy).reduce((packages, specifier) => {
+ packages[specifier] = true;
+ return packages;
+ }, {}),
+ };
+ }
const shortId = adaptId(id);
if (policy.resources && policy.resources[shortId]) {
return policy.resources[shortId];
@@ -47,6 +71,7 @@ export const getPolicyFor = (id, policy) => {
return {};
}
};
+
const getGlobalsList = myPolicy => {
if (!myPolicy.globals) {
return [];
@@ -82,7 +107,7 @@ export const getAllowedGlobals = (globals, localPolicy) => {
export const gatekeepModuleAccess = (specifier, policy, info) => {
const policyChoice = info.exit ? 'builtin' : 'packages';
if (policy && (!policy[policyChoice] || !policy[policyChoice][specifier])) {
- console.trace(specifier);
+ // console.trace(specifier);
throw Error(
`Importing '${specifier}' was not allowed by policy ${policyChoice}:${JSON.stringify(
policy[policyChoice],
@@ -92,7 +117,8 @@ export const gatekeepModuleAccess = (specifier, policy, info) => {
};
function attenuateModule({ attenuators, name, params, originalModule }) {
- if (!attenuators[name]) {
+ if (!attenuators) {
+ // TODO: figure out what to pass here or where to move this check
throw Error(
`Attenuation '${name}' in policy doesn't have a corresponding implementation.`,
);
@@ -104,7 +130,10 @@ function attenuateModule({ attenuators, name, params, originalModule }) {
name,
resolveHook: moduleSpecifier => moduleSpecifier,
importHook: async () => {
- const ns = await attenuators[name](params, originalModule);
+ const {
+ namespace: { attenuate },
+ } = await attenuators(name);
+ const ns = await attenuate(params, originalModule);
const staticModuleRecord = freeze({
imports: [],
exports: keys(ns),
@@ -125,16 +154,15 @@ function attenuateModule({ attenuators, name, params, originalModule }) {
* @param {string} specifier - exit module name
* @param {Object} originalModule - reference to the exit module
* @param {Object} policy - local compartment policy
- * @param {Object} attenuationsImpl - a key-value where attenuations can be found
+ * @param {Object} attenuators - a key-value where attenuations can be found
*/
export const attenuateModuleHook = (
specifier,
originalModule,
policy,
- attenuationsImpl,
+ attenuators,
) => {
if (policy && (!policy.builtin || !policy.builtin[specifier])) {
- console.trace(specifier);
throw Error(
`Attenuation failed '${specifier}' was not in policy builtin:${stringify(
policy.builtin,
@@ -146,7 +174,7 @@ export const attenuateModuleHook = (
}
return attenuateModule({
- attenuators: attenuationsImpl,
+ attenuators,
name: policy.builtin[specifier].attenuate,
params: policy.builtin[specifier].params,
originalModule,

MODIFY
packages/compartment-mapper/test-manual/policy/app.js
packages/compartment-mapper/test-manual/policy/app.js
/* global require Buffer module */
-
const Poet = require('entropoetry');
const fs = require('fs');

ADD
packages/compartment-mapper/test-manual/policy/att1/index.js
+console.log('@enuator imported');
+export const attenuate = (params, originalModuleNamespace) => {
+ console.log('@enuator called', params);
+ // Object.assign(exportsProxy, originalModuleNamespace);
+ const ns = params.reduce((acc, k) => {
+ acc[k] = originalModuleNamespace[k];
+ return acc;
+ }, {});
+ return ns;
+};

ADD
packages/compartment-mapper/test-manual/policy/att1/package.json
+{
+ "name": "att1",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "type": "module",
+ "license": "ISC"
+}

MODIFY
packages/compartment-mapper/test-manual/policy/index.mjs
packages/compartment-mapper/test-manual/policy/index.mjs
/* eslint-disable import/first */
import 'ses';
-lockdown();
+lockdown({
+ errorTaming: 'unsafe',
+ stackFiltering: 'verbose',
+});
import fs from 'fs';
import os from 'os';
@@ -9,7 +12,7 @@ import assert from 'assert';
import zlib from 'zlib';
import path from 'path';
-import { importLocation } from '../../index.js';
+import { importLocation, makeArchive, parseArchive } from '../../index.js';
import { addToCompartment } from './helper.mjs';
@@ -20,13 +23,20 @@ const entrypointPath = new URL('./app.js', import.meta.url).href;
const ApiSubsetOfBuffer = harden({ from: Buffer.from });
-const { namespace } = await importLocation(readPower, entrypointPath, {
+const options = {
policy: {
resources: {
- 'endo-sample': {
+ att1: {
+ // this is nice
+ globals: {
+ console: true,
+ },
+ },
+ '<root>': {
globals: {
// 'Buffer.from': true, // "write"
Buffer: true,
+ console: true,
},
packages: {
entropoetry: true,
@@ -34,17 +44,18 @@ const { namespace } = await importLocation(readPower, entrypointPath, {
},
builtin: {
fs: {
- attenuate: 'fs-read-attenuation',
+ attenuate: 'att1',
params: ['existsSync'],
},
},
},
dotenv: {
+ // transform:{
+ // ...
+ // }
builtin: {
- // "fs.readFileSync": true,
- // "fs": "fs-read-attenuation",
fs: {
- attenuate: 'fs-read-attenuation',
+ attenuate: 'att1',
params: ['readFileSync'],
},
os: true,
@@ -91,17 +102,34 @@ const { namespace } = await importLocation(readPower, entrypointPath, {
fs: await addToCompartment('fs', fs),
os: await addToCompartment('os', os),
},
- attenuations: {
- 'fs-read-attenuation': (params, originalModuleNamespace) => {
- console.log('>>>fs-read-attenuation', params);
- // Object.assign(exportsProxy, originalModuleNamespace);
- const ns = params.reduce((acc, k) => {
- acc[k] = originalModuleNamespace[k];
- return acc;
- }, {});
- return ns;
- },
- },
-});
+};
+
+console.log('\n\n________________________________________________ Location\n');
+{
+ const { namespace } = await importLocation(
+ readPower,
+ entrypointPath,
+ options,
+ );
+ console.log(1, namespace.poem);
+}
-console.log(namespace.poem);
+console.log('\n\n________________________________________________ Archive\n');
+{
+ const archive = await makeArchive(readPower, entrypointPath, {
+ modules: options.modules,
+ policy: options.policy,
+ });
+ console.log('>----------makeArchive');
+ const application = await parseArchive(archive, '<unknown>', {
+ modules: options.modules,
+ // policy: options.policy,
+ });
+ console.log('>----------parseArchive');
+ const { namespace } = await application.import({
+ globals: options.globals,
+ modules: options.modules,
+ });
+ console.log('>----------import');
+ console.log(2, namespace.poem);
+}

MODIFY
packages/compartment-mapper/test-manual/policy/package-lock.json
packages/compartment-mapper/test-manual/policy/package-lock.json
"version": "1.0.0",
"license": "ISC",
"dependencies": {
- "dotenv": "^16.0.1",
+ "att1": "file:att1",
+ "dotenv": "16.0.1",
"entropoetry": "2.0.0-alpha4"
}
},
+ "att1": {
+ "version": "1.0.0",
+ "license": "ISC"
+ },
+ "node_modules/att1": {
+ "resolved": "att1",
+ "link": true
+ },
"node_modules/bn.js": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
}
},
"dependencies": {
+ "att1": {
+ "version": "file:att1"
+ },
"bn.js": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",

MODIFY
packages/compartment-mapper/test-manual/policy/package.json
packages/compartment-mapper/test-manual/policy/package.json
"author": "",
"license": "ISC",
"dependencies": {
+ "att1": "file:att1",
"dotenv": "16.0.1",
"entropoetry": "2.0.0-alpha4"
}

MODIFY
packages/compartment-mapper/test/test-policy.js
packages/compartment-mapper/test/test-policy.js
@@ -20,7 +20,7 @@ const globals = {
};
const policy = {
resources: {
- app: {
+ '<root>': {
globals: {
bluePill: true,
},

feat(compartment-mapper): attenuate builtins with attenuators from packages