test(compartment-mapper): expand policy tests, surface an attack with aliases
MODIFY
packages/compartment-mapper/src/node-modules.js
packages/compartment-mapper/src/node-modules.js
@@ -45,7 +45,11 @@ import { searchDescriptor } from './search.js';
import { parseLocatedJson } from './json.js';
import { unpackReadPowers } from './powers.js';
import { pathCompare } from './compartment-map.js';
-import { getPolicyFor, ATTENUATORS_COMPARTMENT } from './policy.js';
+import {
+ getPolicyFor,
+ ATTENUATORS_COMPARTMENT,
+ generatePolicyId,
+} from './policy.js';
import { join } from './node-module-specifier.js';
const { assign, create, keys, values } = Object;
@@ -737,7 +741,13 @@ const translateGraph = (
}
const packagePolicy = getPolicyFor(
- dependeeLocation === entryPackageLocation ? '<root>' : dependeeLocation,
+ generatePolicyId({
+ location: dependeeLocation,
+ isEntry: dependeeLocation === entryPackageLocation,
+ label,
+ name,
+ path,
+ }),
policy,
);

MODIFY
packages/compartment-mapper/src/policy.js
packages/compartment-mapper/src/policy.js
@@ -43,6 +43,26 @@ export const detectAttenuators = policy => {
}
return attenuatorsCache.get(policy);
};
+
+export const generatePolicyId = ({
+ location,
+ isEntry = false,
+ label,
+ name,
+ path,
+}) => {
+ if (isEntry) {
+ return '<root>';
+ }
+ if (name === ATTENUATORS_COMPARTMENT) {
+ return ATTENUATORS_COMPARTMENT;
+ }
+ const chunks = location.replace(/\/$/, '').split('/node_modules/');
+ if (chunks.length > 1) {
+ chunks.shift();
+ }
+ return chunks.join('>');
+};
/**
* 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.
@@ -63,11 +83,10 @@ export const getPolicyFor = (id, policy) => {
}, {}),
};
}
- const shortId = adaptId(id);
- if (policy.resources && policy.resources[shortId]) {
- return policy.resources[shortId];
+ if (policy.resources && policy.resources[id]) {
+ return policy.resources[id];
} else {
- console.warn(`No policy for '${shortId}'`);
+ console.warn(`No policy for '${id}'`);
return {};
}
};

MODIFY
packages/compartment-mapper/test-manual/policy/app.js
packages/compartment-mapper/test-manual/policy/app.js
@@ -14,3 +14,5 @@ const key = Buffer.from(
module.exports = { poem: p.stringify(key) };
require('dotenv').config();
+
+console.log(require('att1'));

MODIFY
packages/compartment-mapper/test/fixtures-0/node_modules/builtin/builtin.js
packages/compartment-mapper/test/fixtures-0/node_modules/builtin/builtin.js
export const builtin = 'builtin';
+export const a = 1;
+export const b = 2;
+export const c = 3;
+
if (!Object.isFrozen(globalThis)) {
throw new Error('The global object must be frozen in all compartments');
}

ADD
packages/compartment-mapper/test/fixtures-policy/node_modules/app/attack.js
+import 'mallory';
\ No newline at end of file

ADD
packages/compartment-mapper/test/fixtures-policy/node_modules/app/builtinsWithPolicy.js
+import * as builtin from 'builtin';
+
+const result = Object.keys(builtin).join();
+export default result;
\ No newline at end of file

MODIFY
packages/compartment-mapper/test/fixtures-policy/node_modules/app/index.js
packages/compartment-mapper/test/fixtures-policy/node_modules/app/index.js
import alice from 'alice';
import bob from './bob.cjs';
import carol from 'carol';
+import builtins from './builtinsWithPolicy.js';
-export { alice, bob, carol };
+export { alice, bob, carol, builtins };

MODIFY
packages/compartment-mapper/test/fixtures-policy/node_modules/app/package.json
packages/compartment-mapper/test/fixtures-policy/node_modules/app/package.json
"main": "./index.js",
"type": "module",
"dependencies": {
+ "myattenuator": "*",
"alice": "^1.0.0",
- "carol": "^1.0.0"
+ "carol": "^1.0.0",
+ "mallory": "^1.0.0"
}
}

ADD
packages/compartment-mapper/test/fixtures-policy/node_modules/hackity/index.js
+throw Error('Package "hackity" was imported despite the policy!');
\ No newline at end of file

ADD
packages/compartment-mapper/test/fixtures-policy/node_modules/hackity/package.json
+{
+ "name": "hackity",
+ "version": "1.0.0",
+ "main": "./index.js",
+ "type": "module",
+ "dependencies": {
+ }
+}

ADD
packages/compartment-mapper/test/fixtures-policy/node_modules/mallory/index.js
+
+export default {
+ redPill: typeof redPill,
+ bluePill: typeof bluePill,
+ yellowPill: typeof yellowPill,
+}
+
+import "dan";
\ No newline at end of file

ADD
packages/compartment-mapper/test/fixtures-policy/node_modules/mallory/package.json
+{
+ "name": "mallory",
+ "version": "1.0.0",
+ "main": "./index.js",
+ "type": "module",
+ "browser": {
+ "dan": "hackity"
+ },
+ "dependencies": {
+ "hackity": "*"
+ }
+}

ADD
packages/compartment-mapper/test/fixtures-policy/node_modules/myattenuator/index.js
+export const attenuate = (params, originalModuleNamespace) => {
+ const ns = params.reduce((acc, k) => {
+ acc[k] = originalModuleNamespace[k];
+ return acc;
+ }, {});
+ return ns;
+};

ADD
packages/compartment-mapper/test/fixtures-policy/node_modules/myattenuator/package.json
+{
+ "name": "myattenuator",
+ "version": "1.0.0",
+ "main": "./index.js",
+ "type": "module",
+ "dependencies": {
+ }
+}

MODIFY
packages/compartment-mapper/test/test-policy.js
packages/compartment-mapper/test/test-policy.js
@@ -12,6 +12,10 @@ const fixture = new URL(
'fixtures-policy/node_modules/app/index.js',
import.meta.url,
).toString();
+const fixtureAttack = new URL(
+ 'fixtures-policy/node_modules/app/attack.js',
+ import.meta.url,
+).toString();
const globals = {
redPill: 42,
@@ -28,6 +32,13 @@ const policy = {
alice: true,
carol: true,
},
+ builtin: {
+ // that's the one builtin name that scaffold is providing by default
+ builtin: {
+ attenuate: 'myattenuator',
+ params: ['a', 'b'],
+ },
+ },
},
alice: {
globals: {
@@ -46,44 +57,13 @@ const expectations = {
alice: { bluePill: 'undefined', redPill: 'number', yellowPill: 'undefined' },
bob: { bluePill: 'number', redPill: 'undefined', yellowPill: 'undefined' },
carol: { bluePill: 'undefined', redPill: 'undefined', yellowPill: 'number' },
+ builtins: 'a,b',
};
-test('policy - globals access', async t => {
- t.plan(1);
-
- const application = await loadLocation(read, fixture, {
- policy,
- });
- const {
- namespace: { alice, bob, carol },
- } = await application.import({
- globals,
- // globalLexicals explicitly ignored.
- });
-
- t.deepEqual({ alice, bob, carol }, expectations);
-});
-
-test('policy - built into archive', async t => {
- t.plan(1);
- const archive = await makeArchive(readPowers, fixture, {
- policy,
- dev: true,
- });
- const application = await parseArchive(archive, '<unknown>');
- const {
- namespace: { alice, bob, carol },
- } = await application.import({
- globals,
- });
-
- t.deepEqual({ alice, bob, carol }, expectations);
-});
-
const assertFixture = (t, { namespace }) => {
- const { alice, bob, carol } = namespace;
+ const { alice, bob, carol, builtins } = namespace;
- t.deepEqual({ alice, bob, carol }, expectations);
+ t.deepEqual({ alice, bob, carol, builtins }, expectations);
};
const fixtureAssertionCount = 1;
@@ -99,3 +79,36 @@ scaffold(
policy,
},
);
+
+scaffold(
+ 'fixture-policy-attack',
+ test,
+ fixtureAttack,
+ t => {
+ // this test always throws
+ t.fail('Expected it to throw.');
+ },
+ fixtureAssertionCount,
+ {
+ shouldFailBeforeArchiveOperations: true,
+ onError: (t, { error, title }) => {
+ t.regex(error.message, /Importing 'hackity' was not allowed by policy/);
+ },
+ addGlobals: globals,
+ policy: {
+ resources: {
+ '<root>': {
+ packages: {
+ mallory: true,
+ },
+ },
+ mallory: {
+ packages: {
+ dan: true,
+ },
+ },
+ },
+ },
+ tags: new Set(['browser']),
+ },
+);

test(compartment-mapper): expand policy tests, surface an attack with aliases