diff --git a/packages/daemon/src/daemon-node-powers.js b/packages/daemon/src/daemon-node-powers.js index ee1b4acdf4..3240d1ed8a 100644 --- a/packages/daemon/src/daemon-node-powers.js +++ b/packages/daemon/src/daemon-node-powers.js @@ -507,11 +507,12 @@ export const makeDaemonicPersistencePowers = ( const provideRootNonce = async () => { const noncePath = filePowers.joinPath(locator.statePath, 'nonce'); let nonce = await filePowers.maybeReadFileText(noncePath); + const isNewlyCreated = nonce === undefined; if (nonce === undefined) { nonce = await cryptoPowers.randomHex512(); await filePowers.writeFileText(noncePath, `${nonce}\n`); } - return nonce.trim(); + return { rootNonce: nonce.trim(), isNewlyCreated }; }; const makeContentSha512Store = () => { diff --git a/packages/daemon/src/daemon.js b/packages/daemon/src/daemon.js index ae81dffa2c..b929dac396 100644 --- a/packages/daemon/src/daemon.js +++ b/packages/daemon/src/daemon.js @@ -14,13 +14,6 @@ import { assertPetName } from './pet-name.js'; import { makeContextMaker } from './context.js'; import { parseFormulaIdentifier } from './formula-identifier.js'; -/** @type {import('./types.js').EndoGuest} */ -const leastAuthority = Far('EndoGuest', { - async request() { - throw new Error('declined'); - }, -}); - const delay = async (ms, cancelled) => { // Do not attempt to set up a timer if already cancelled. await Promise.race([cancelled, undefined]); @@ -61,7 +54,7 @@ const makeInspector = (type, number, record) => * @param {number} args.gracePeriodMs * @param {Promise} args.gracePeriodElapsed */ -const makeEndoBootstrap = async ( +const makeDaemonCore = async ( powers, webletPortP, { cancelled, cancel, gracePeriodMs, gracePeriodElapsed }, @@ -72,36 +65,35 @@ const makeEndoBootstrap = async ( persistence: persistencePowers, control: controlPowers, } = powers; - const { randomHex512, makeSha512 } = cryptoPowers; - const derive = (...path) => { - const digester = makeSha512(); - digester.updateText(path.join(':')); - return digester.digestHex(); - }; - + const { randomHex512 } = cryptoPowers; const contentStore = persistencePowers.makeContentSha512Store(); - const rootNonce = await persistencePowers.provideRootNonce(); - - const endoFormulaIdentifier = `endo:${rootNonce}`; - const defaultHostFormulaNumber = derive(rootNonce, 'host'); - const defaultHostFormulaIdentifier = `host:${defaultHostFormulaNumber}`; - const webPageJsFormulaIdentifier = `web-page-js:${derive( - rootNonce, - 'web-page-js', - )}`; - const leastAuthorityFormulaNumber = derive(rootNonce, 'least-authority'); - const leastAuthorityFormulaIdentifier = `least-authority:${leastAuthorityFormulaNumber}`; - - /** @type {Map>} */ + + /** + * The two functions "provideValueForNumberedFormula" and "provideValueForFormulaIdentifier" + * share a responsibility for maintaining the memoization tables + * "controllerForFormulaIdentifier" and "formulaIdentifierForRef". + * "provideValueForNumberedFormula" is used for incarnating and persisting + * new formulas, whereas "provideValueForFormulaIdentifier" is used for + * reincarnating stored formulas. + */ + + /** + * Reverse look-up, for answering "what is my name for this near or far + * reference", and not for "what is my name for this promise". + * @type {Map} + */ const controllerForFormulaIdentifier = new Map(); - // Reverse look-up, for answering "what is my name for this near or far - // reference", and not for "what is my name for this promise". - /** @type {WeakMap} */ + /** + * Reverse look-up, for answering "what is my name for this near or far + * reference", and not for "what is my name for this promise". + * @type {WeakMap} + */ const formulaIdentifierForRef = new WeakMap(); const getFormulaIdentifierForRef = ref => formulaIdentifierForRef.get(ref); /** * @param {string} sha512 + * @returns {import('./types.js').FarEndoReadable} */ const makeReadableBlob = sha512 => { const { text, json, streamBase64 } = contentStore.fetch(sha512); @@ -118,7 +110,9 @@ const makeEndoBootstrap = async ( */ const storeReaderRef = async readerRef => { const sha512Hex = await contentStore.store(makeRefReader(readerRef)); - return `readable-blob:${sha512Hex}`; + // eslint-disable-next-line no-use-before-define + const { formulaIdentifier } = await incarnateReadableBlob(sha512Hex); + return formulaIdentifier; }; /** @@ -356,8 +350,13 @@ const makeEndoBootstrap = async ( formula.values, context, ); + } else if (formula.type === 'readable-blob') { + const external = makeReadableBlob(formula.content); + return { external, internal: undefined }; } else if (formula.type === 'lookup') { return makeControllerForLookup(formula.hub, formula.path, context); + } else if (formula.type === 'worker') { + return makeIdentifiedWorkerController(formulaNumber, context); } else if (formula.type === 'make-unconfined') { return makeControllerForUnconfinedPlugin( formula.worker, @@ -372,18 +371,26 @@ const makeEndoBootstrap = async ( formula.bundle, context, ); + } else if (formula.type === 'host') { + // Behold, recursion: + // eslint-disable-next-line no-use-before-define + return makeIdentifiedHost( + formulaIdentifier, + formula.endo, + formula.petStore, + formula.inspector, + formula.worker, + formula.leastAuthority, + context, + ); } else if (formula.type === 'guest') { - const storeFormulaNumber = derive(formulaNumber, 'pet-store'); - const storeFormulaIdentifier = `pet-store:${storeFormulaNumber}`; - const workerFormulaNumber = derive(formulaNumber, 'worker'); - const workerFormulaIdentifier = `worker:${workerFormulaNumber}`; // Behold, recursion: // eslint-disable-next-line no-use-before-define return makeIdentifiedGuestController( formulaIdentifier, formula.host, - storeFormulaIdentifier, - workerFormulaIdentifier, + formula.petStore, + formula.worker, context, ); } else if (formula.type === 'web-bundle') { @@ -404,6 +411,82 @@ const makeEndoBootstrap = async ( }))(), internal: undefined, }; + } else if (formula.type === 'handle') { + context.thisDiesIfThatDies(formula.target); + return { + external: {}, + internal: { + targetFormulaIdentifier: formula.target, + }, + }; + } else if (formula.type === 'endo') { + /** @type {import('./types.js').FarEndoBootstrap} */ + const endoBootstrap = Far('Endo private facet', { + // TODO for user named + ping: async () => 'pong', + terminate: async () => { + cancel(new Error('Termination requested')); + }, + host: () => { + // Behold, recursion: + return /** @type {Promise} */ ( + // eslint-disable-next-line no-use-before-define + provideValueForFormulaIdentifier(formula.host) + ); + }, + leastAuthority: () => { + // Behold, recursion: + return /** @type {Promise} */ ( + // eslint-disable-next-line no-use-before-define + provideValueForFormulaIdentifier(formula.leastAuthority) + ); + }, + webPageJs: () => { + if (formula.webPageJs === undefined) { + throw new Error('No web-page-js formula provided.'); + } + // Behold, recursion: + // eslint-disable-next-line no-use-before-define + return provideValueForFormulaIdentifier(formula.webPageJs); + }, + importAndEndowInWebPage: async (webPageP, webPageNumber) => { + const { bundle: bundleBlob, powers: endowedPowers } = + /** @type {import('./types.js').EndoWebBundle} */ ( + // Behold, recursion: + // eslint-disable-next-line no-use-before-define + await provideValueForFormulaIdentifier( + `web-bundle:${webPageNumber}`, + ).catch(() => { + throw new Error('Not found'); + }) + ); + const bundle = await E(bundleBlob).json(); + await E(webPageP).makeBundle(bundle, endowedPowers); + }, + }); + return { + external: endoBootstrap, + internal: undefined, + }; + } else if (formula.type === 'least-authority') { + /** @type {import('./types.js').EndoGuest} */ + const leastAuthority = Far('EndoGuest', { + async request() { + throw new Error('declined'); + }, + }); + return { external: leastAuthority, internal: undefined }; + } else if (formula.type === 'pet-store') { + const external = petStorePowers.makeIdentifiedPetStore( + formulaNumber, + assertPetName, + ); + return { external, internal: undefined }; + } else if (formula.type === 'pet-inspector') { + // Behold, unavoidable forward-reference: + // eslint-disable-next-line no-use-before-define + const external = makePetStoreInspector(formula.petStore); + return { external, internal: undefined }; } else { throw new TypeError(`Invalid formula: ${q(formula)}`); } @@ -420,84 +503,23 @@ const makeEndoBootstrap = async ( context, ) => { const formulaIdentifier = `${formulaType}:${formulaNumber}`; - if (formulaType === 'readable-blob') { - // Behold, forward-reference: - // eslint-disable-next-line no-use-before-define - const external = makeReadableBlob(formulaNumber); - return { external, internal: undefined }; - } else if (formulaType === 'worker') { - return makeIdentifiedWorkerController(formulaNumber, context); - } else if (formulaType === 'pet-inspector') { - const storeFormulaNumber = derive(formulaNumber, 'pet-store'); - const storeFormulaIdentifier = `pet-store:${storeFormulaNumber}`; - // Behold, unavoidable forward-reference: - // eslint-disable-next-line no-use-before-define - const external = makePetStoreInspector(storeFormulaIdentifier); - return { external, internal: undefined }; - } else if (formulaType === 'pet-store') { - const external = petStorePowers.makeIdentifiedPetStore( - formulaNumber, - assertPetName, - ); - return { external, internal: undefined }; - } else if (formulaType === 'host') { - const workerFormulaNumber = derive(formulaNumber, 'worker'); - const workerFormulaIdentifier = `worker:${workerFormulaNumber}`; - const inspectorFormulaNumber = derive(formulaNumber, 'pet-inspector'); - const inspectorFormulaIdentifier = `pet-inspector:${inspectorFormulaNumber}`; - // Note the pet store formula number derivation path: - // root -> host -> inspector -> pet store - const storeFormulaNumber = derive(inspectorFormulaNumber, 'pet-store'); - const storeFormulaIdentifier = `pet-store:${storeFormulaNumber}`; - - // Behold, recursion: - // eslint-disable-next-line no-use-before-define - return makeIdentifiedHost( - formulaIdentifier, - endoFormulaIdentifier, - storeFormulaIdentifier, - inspectorFormulaIdentifier, - workerFormulaIdentifier, - leastAuthorityFormulaIdentifier, - context, - ); - } else if (formulaType === 'endo') { - if (formulaNumber !== rootNonce) { - throw new Error('Invalid endo formula number.'); - } - - // TODO reframe "cancelled" as termination of the "endo" object and - // ensure that all values ultimately depend on "endo". - // Behold, self-referentiality: - // eslint-disable-next-line no-use-before-define - return { external: endoBootstrap, internal: undefined }; - } else if (formulaType === 'least-authority') { - return { external: leastAuthority, internal: undefined }; - } else if (formulaType === 'web-page-js') { - if (persistencePowers.getWebPageBundlerFormula === undefined) { - throw Error('No web-page-js formula provided.'); - } - // Note that this worker is hardcoded to be the "MAIN" worker of the - // default host. - const workerFormulaNumber = derive(defaultHostFormulaNumber, 'worker'); - const workerFormulaIdentifier = `worker:${workerFormulaNumber}`; - - return makeControllerForFormula( - 'web-page-js', - derive(formulaNumber, 'web-page-js'), - persistencePowers.getWebPageBundlerFormula( - workerFormulaIdentifier, - defaultHostFormulaIdentifier, - ), - context, - ); - } else if ( + if ( [ + 'endo', + 'worker', 'eval', + 'readable-blob', 'make-unconfined', 'make-bundle', + 'host', 'guest', + 'least-authority', 'web-bundle', + 'web-page-js', + 'handle', + 'pet-inspector', + 'pet-store', + 'lookup', ].includes(formulaType) ) { const formula = await persistencePowers.readFormula( @@ -518,10 +540,6 @@ const makeEndoBootstrap = async ( } }; - // The two functions provideValueForFormula and provideValueForFormulaIdentifier - // share a responsibility for maintaining the memoization tables - // controllerForFormulaIdentifier and formulaIdentifierForRef, since the - // former bypasses the latter in order to avoid a round trip with disk. /** @type {import('./types.js').ProvideValueForNumberedFormula} */ const provideValueForNumberedFormula = async ( formulaType, @@ -569,15 +587,6 @@ const makeEndoBootstrap = async ( }); }; - /** - * @param {import('./types.js').Formula} formula - * @param {string} formulaType - */ - const provideValueForFormula = async (formula, formulaType) => { - const formulaNumber = await randomHex512(); - return provideValueForNumberedFormula(formulaType, formulaNumber, formula); - }; - /** @type {import('./types.js').ProvideControllerForFormulaIdentifier} */ const provideControllerForFormulaIdentifier = formulaIdentifier => { const { type: formulaType, number: formulaNumber } = @@ -624,33 +633,384 @@ const makeEndoBootstrap = async ( return value; }; + /** @type {import('./types.js').ProvideControllerForFormulaIdentifierAndResolveHandle} */ + const provideControllerForFormulaIdentifierAndResolveHandle = + async formulaIdentifier => { + let currentFormulaIdentifier = formulaIdentifier; + // eslint-disable-next-line no-constant-condition + while (true) { + const controller = provideControllerForFormulaIdentifier( + currentFormulaIdentifier, + ); + // eslint-disable-next-line no-await-in-loop + const internalFacet = await controller.internal; + if (internalFacet === undefined || internalFacet === null) { + return controller; + } + // @ts-expect-error We can't know the type of the internal facet. + if (internalFacet.targetFormulaIdentifier === undefined) { + return controller; + } + const handle = /** @type {import('./types.js').InternalHandle} */ ( + internalFacet + ); + currentFormulaIdentifier = handle.targetFormulaIdentifier; + } + }; + + /** + * @returns {Promise<{ formulaIdentifier: string, value: import('./types').EndoGuest }>} + */ + const incarnateLeastAuthority = async () => { + const formulaNumber = await randomHex512(); + /** @type {import('./types.js').LeastAuthorityFormula} */ + const formula = { + type: 'least-authority', + }; + return /** @type {Promise<{ formulaIdentifier: string, value: import('./types').EndoGuest }>} */ ( + provideValueForNumberedFormula(formula.type, formulaNumber, formula) + ); + }; + + /** + * @param {string} targetFormulaIdentifier + * @returns {Promise<{ formulaIdentifier: string, value: import('./types').ExternalHandle }>} + */ + const incarnateHandle = async targetFormulaIdentifier => { + const formulaNumber = await randomHex512(); + /** @type {import('./types.js').HandleFormula} */ + const formula = { + type: 'handle', + target: targetFormulaIdentifier, + }; + return /** @type {Promise<{ formulaIdentifier: string, value: import('./types').ExternalHandle }>} */ ( + provideValueForNumberedFormula(formula.type, formulaNumber, formula) + ); + }; + + /** + * @returns {Promise<{ formulaIdentifier: string, value: import('./types').PetStore }>} + */ + const incarnatePetStore = async () => { + const formulaNumber = await randomHex512(); + /** @type {import('./types.js').PetStoreFormula} */ + const formula = { + type: 'pet-store', + }; + return /** @type {Promise<{ formulaIdentifier: string, value: import('./types').PetStore }>} */ ( + provideValueForNumberedFormula(formula.type, formulaNumber, formula) + ); + }; + + /** + * @returns {Promise<{ formulaIdentifier: string, value: import('./types').EndoWorker }>} + */ + const incarnateWorker = async () => { + const formulaNumber = await randomHex512(); + /** @type {import('./types.js').WorkerFormula} */ + const formula = { + type: 'worker', + }; + return /** @type {Promise<{ formulaIdentifier: string, value: import('./types').EndoWorker }>} */ ( + provideValueForNumberedFormula(formula.type, formulaNumber, formula) + ); + }; + + /** + * @param {string} endoFormulaIdentifier + * @param {string} leastAuthorityFormulaIdentifier + * @param {string} [specifiedWorkerFormulaIdentifier] + * @returns {Promise<{ formulaIdentifier: string, value: import('./types').EndoHost }>} + */ + const incarnateHost = async ( + endoFormulaIdentifier, + leastAuthorityFormulaIdentifier, + specifiedWorkerFormulaIdentifier, + ) => { + const formulaNumber = await randomHex512(); + let workerFormulaIdentifier = specifiedWorkerFormulaIdentifier; + if (workerFormulaIdentifier === undefined) { + ({ formulaIdentifier: workerFormulaIdentifier } = + await incarnateWorker()); + } + const { formulaIdentifier: storeFormulaIdentifier } = + await incarnatePetStore(); + const { formulaIdentifier: inspectorFormulaIdentifier } = + // eslint-disable-next-line no-use-before-define + await incarnatePetInspector(storeFormulaIdentifier); + /** @type {import('./types.js').HostFormula} */ + const formula = { + type: 'host', + petStore: storeFormulaIdentifier, + inspector: inspectorFormulaIdentifier, + worker: workerFormulaIdentifier, + endo: endoFormulaIdentifier, + leastAuthority: leastAuthorityFormulaIdentifier, + }; + return /** @type {Promise<{ formulaIdentifier: string, value: import('./types').EndoHost }>} */ ( + provideValueForNumberedFormula('host', formulaNumber, formula) + ); + }; + + /** + * @param {string} hostHandleFormulaIdentifier + * @returns {Promise<{ formulaIdentifier: string, value: import('./types').EndoGuest }>} + */ + const incarnateGuest = async hostHandleFormulaIdentifier => { + const formulaNumber = await randomHex512(); + const { formulaIdentifier: storeFormulaIdentifier } = + await incarnatePetStore(); + const { formulaIdentifier: workerFormulaIdentifier } = + await incarnateWorker(); + /** @type {import('./types.js').GuestFormula} */ + const formula = { + type: 'guest', + host: hostHandleFormulaIdentifier, + petStore: storeFormulaIdentifier, + worker: workerFormulaIdentifier, + }; + return /** @type {Promise<{ formulaIdentifier: string, value: import('./types').EndoGuest }>} */ ( + provideValueForNumberedFormula(formula.type, formulaNumber, formula) + ); + }; + + /** + * @param {string} workerFormulaIdentifier + * @param {string} source + * @param {string[]} codeNames + * @param {string[]} endowmentFormulaIdentifiers + * @returns {Promise<{ formulaIdentifier: string, value: unknown }>} + */ + const incarnateEval = async ( + workerFormulaIdentifier, + source, + codeNames, + endowmentFormulaIdentifiers, + ) => { + const formulaNumber = await randomHex512(); + /** @type {import('./types.js').EvalFormula} */ + const formula = { + type: 'eval', + worker: workerFormulaIdentifier, + source, + names: codeNames, + values: endowmentFormulaIdentifiers, + }; + return /** @type {Promise<{ formulaIdentifier: string, value: unknown }>} */ ( + provideValueForNumberedFormula(formula.type, formulaNumber, formula) + ); + }; + + /** + * @param {string} hubFormulaIdentifier + * A "naming hub" is an objected with a variadic lookup method. It includes + * objects such as guests and hosts. + * @param {string[]} petNamePath + * @returns {Promise<{ formulaIdentifier: string, value: unknown }>} + */ + const incarnateLookup = async (hubFormulaIdentifier, petNamePath) => { + const formulaNumber = await randomHex512(); + /** @type {import('./types.js').LookupFormula} */ + const formula = { + type: 'lookup', + hub: hubFormulaIdentifier, + path: petNamePath, + }; + return /** @type {Promise<{ formulaIdentifier: string, value: unknown }>} */ ( + provideValueForNumberedFormula(formula.type, formulaNumber, formula) + ); + }; + + /** + * @param {string} workerFormulaIdentifier + * @param {string} powersFormulaIdentifiers + * @param {string} specifier + * @returns {Promise<{ formulaIdentifier: string, value: unknown }>} + */ + const incarnateUnconfined = async ( + workerFormulaIdentifier, + powersFormulaIdentifiers, + specifier, + ) => { + const formulaNumber = await randomHex512(); + /** @type {import('./types.js').MakeUnconfinedFormula} */ + const formula = { + type: 'make-unconfined', + worker: workerFormulaIdentifier, + powers: powersFormulaIdentifiers, + specifier, + }; + return /** @type {Promise<{ formulaIdentifier: string, value: unknown }>} */ ( + provideValueForNumberedFormula(formula.type, formulaNumber, formula) + ); + }; + + /** + * @param {string} contentSha512 + * @returns {Promise<{ formulaIdentifier: string, value: import('./types.js').FarEndoReadable }>} + */ + const incarnateReadableBlob = async contentSha512 => { + const formulaNumber = await randomHex512(); + /** @type {import('./types.js').ReadableBlobFormula} */ + const formula = { + type: 'readable-blob', + content: contentSha512, + }; + return /** @type {Promise<{ formulaIdentifier: string, value: import('./types.js').FarEndoReadable }>} */ ( + provideValueForNumberedFormula(formula.type, formulaNumber, formula) + ); + }; + + /** + * @param {string} powersFormulaIdentifier + * @param {string} workerFormulaIdentifier + * @returns {Promise<{ formulaIdentifier: string, value: unknown }>} + */ + const incarnateBundler = async ( + powersFormulaIdentifier, + workerFormulaIdentifier, + ) => { + if (persistencePowers.getWebPageBundlerFormula === undefined) { + throw Error('No web-page-js bundler formula provided.'); + } + const formulaNumber = await randomHex512(); + const formula = persistencePowers.getWebPageBundlerFormula( + workerFormulaIdentifier, + powersFormulaIdentifier, + ); + return provideValueForNumberedFormula(formula.type, formulaNumber, formula); + }; + + /** + * @param {string} powersFormulaIdentifier + * @param {string} workerFormulaIdentifier + * @param {string} bundleFormulaIdentifier + * @returns {Promise<{ formulaIdentifier: string, value: unknown }>} + */ + const incarnateBundle = async ( + powersFormulaIdentifier, + workerFormulaIdentifier, + bundleFormulaIdentifier, + ) => { + const formulaNumber = await randomHex512(); + /** @type {import('./types.js').MakeBundleFormula} */ + const formula = { + type: 'make-bundle', + worker: workerFormulaIdentifier, + powers: powersFormulaIdentifier, + bundle: bundleFormulaIdentifier, + }; + return provideValueForNumberedFormula(formula.type, formulaNumber, formula); + }; + + /** + * @param {string} powersFormulaIdentifier + * @param {string} bundleFormulaIdentifier + * @returns {Promise<{ formulaIdentifier: string, value: unknown }>} + */ + const incarnateWebBundle = async ( + powersFormulaIdentifier, + bundleFormulaIdentifier, + ) => { + // TODO use regular-length (512-bit) formula numbers for web bundles + const formulaNumber = (await randomHex512()).slice(32, 64); + /** @type {import('./types.js').WebBundleFormula} */ + const formula = { + type: 'web-bundle', + powers: powersFormulaIdentifier, + bundle: bundleFormulaIdentifier, + }; + return provideValueForNumberedFormula(formula.type, formulaNumber, formula); + }; + + /** + * @param {string} petStoreFormulaIdentifier + * @returns {Promise<{ formulaIdentifier: string, value: unknown }>} + */ + const incarnatePetInspector = async petStoreFormulaIdentifier => { + const formulaNumber = await randomHex512(); + /** @type {import('./types.js').PetInspectorFormula} */ + const formula = { + type: 'pet-inspector', + petStore: petStoreFormulaIdentifier, + }; + return provideValueForNumberedFormula(formula.type, formulaNumber, formula); + }; + + /** + * @param {string} [specifiedFormulaNumber] + * @returns {Promise<{ formulaIdentifier: string, value: import('./types').FarEndoBootstrap }>} + */ + const incarnateEndoBootstrap = async specifiedFormulaNumber => { + const formulaNumber = specifiedFormulaNumber || (await randomHex512()); + const endoFormulaIdentifier = `endo:${formulaNumber}`; + + const { formulaIdentifier: defaultHostWorkerFormulaIdentifier } = + await incarnateWorker(); + const { formulaIdentifier: leastAuthorityFormulaIdentifier } = + await incarnateLeastAuthority(); + + // Ensure the default host is incarnated and persisted. + const { formulaIdentifier: defaultHostFormulaIdentifier } = + await incarnateHost( + endoFormulaIdentifier, + leastAuthorityFormulaIdentifier, + defaultHostWorkerFormulaIdentifier, + ); + // If supported, ensure the web page bundler is incarnated and persisted. + let webPageJsFormulaIdentifier; + if (persistencePowers.getWebPageBundlerFormula !== undefined) { + ({ formulaIdentifier: webPageJsFormulaIdentifier } = + await incarnateBundler( + defaultHostFormulaIdentifier, + defaultHostWorkerFormulaIdentifier, + )); + } + + /** @type {import('./types.js').EndoFormula} */ + const formula = { + type: 'endo', + host: defaultHostFormulaIdentifier, + leastAuthority: leastAuthorityFormulaIdentifier, + webPageJs: webPageJsFormulaIdentifier, + }; + return /** @type {Promise<{ formulaIdentifier: string, value: import('./types').FarEndoBootstrap }>} */ ( + provideValueForNumberedFormula(formula.type, formulaNumber, formula) + ); + }; + const makeContext = makeContextMaker({ controllerForFormulaIdentifier, provideControllerForFormulaIdentifier, }); const makeMailbox = makeMailboxMaker({ + incarnateLookup, getFormulaIdentifierForRef, provideValueForFormulaIdentifier, provideControllerForFormulaIdentifier, - makeSha512, - provideValueForNumberedFormula, + provideControllerForFormulaIdentifierAndResolveHandle, }); const makeIdentifiedGuestController = makeGuestMaker({ provideValueForFormulaIdentifier, - provideControllerForFormulaIdentifier, + provideControllerForFormulaIdentifierAndResolveHandle, makeMailbox, }); const makeIdentifiedHost = makeHostMaker({ provideValueForFormulaIdentifier, - provideValueForFormula, - provideValueForNumberedFormula, provideControllerForFormulaIdentifier, + incarnateWorker, + incarnateHost, + incarnateGuest, + incarnateEval, + incarnateUnconfined, + incarnateBundle, + incarnateWebBundle, + incarnateHandle, storeReaderRef, randomHex512, - makeSha512, makeMailbox, }); @@ -760,7 +1120,6 @@ const makeEndoBootstrap = async ( }), ); } - // @ts-expect-error this should never occur return makeInspector(formula.type, formulaNumber, harden({})); }; @@ -775,37 +1134,65 @@ const makeEndoBootstrap = async ( return info; }; - const endoBootstrap = Far('Endo private facet', { - // TODO for user named - - ping: async () => 'pong', - - terminate: async () => { - cancel(new Error('Termination requested')); - }, - - host: () => provideValueForFormulaIdentifier(defaultHostFormulaIdentifier), - - leastAuthority: () => leastAuthority, + const daemonCore = { + provideValueForFormulaIdentifier, + incarnateEndoBootstrap, + incarnateLeastAuthority, + incarnateHandle, + incarnatePetStore, + incarnateWorker, + incarnateHost, + incarnateGuest, + incarnateEval, + incarnateLookup, + incarnateUnconfined, + incarnateReadableBlob, + incarnateBundler, + incarnateBundle, + incarnateWebBundle, + incarnatePetInspector, + }; + return daemonCore; +}; - webPageJs: () => - provideValueForFormulaIdentifier(webPageJsFormulaIdentifier), +/** + * @param {import('./types.js').DaemonicPowers} powers + * @param {Promise} webletPortP + * @param {object} args + * @param {Promise} args.cancelled + * @param {(error: Error) => void} args.cancel + * @param {number} args.gracePeriodMs + * @param {Promise} args.gracePeriodElapsed + * @returns {Promise} + */ +const provideEndoBootstrap = async ( + powers, + webletPortP, + { cancelled, cancel, gracePeriodMs, gracePeriodElapsed }, +) => { + const { persistence: persistencePowers } = powers; - importAndEndowInWebPage: async (webPageP, webPageNumber) => { - const { bundle: bundleBlob, powers: endowedPowers } = - /** @type {import('./types.js').EndoWebBundle} */ ( - await provideValueForFormulaIdentifier( - `web-bundle:${webPageNumber}`, - ).catch(() => { - throw new Error('Not found'); - }) - ); - const bundle = await E(bundleBlob).json(); - await E(webPageP).makeBundle(bundle, endowedPowers); - }, + const daemonCore = await makeDaemonCore(powers, webletPortP, { + cancelled, + cancel, + gracePeriodMs, + gracePeriodElapsed, }); - return endoBootstrap; + const { rootNonce: endoFormulaNumber, isNewlyCreated } = + await persistencePowers.provideRootNonce(); + const isInitialized = !isNewlyCreated; + if (isInitialized) { + const endoFormulaIdentifier = `endo:${endoFormulaNumber}`; + return /** @type {Promise} */ ( + daemonCore.provideValueForFormulaIdentifier(endoFormulaIdentifier) + ); + } else { + const { value: endoBootstrap } = await daemonCore.incarnateEndoBootstrap( + endoFormulaNumber, + ); + return endoBootstrap; + } }; /** @@ -837,12 +1224,16 @@ export const makeDaemon = async (powers, daemonLabel, cancel, cancelled) => { makePromiseKit() ); - const endoBootstrap = makeEndoBootstrap(powers, assignedWebletPortP, { - cancelled, - cancel, - gracePeriodMs, - gracePeriodElapsed, - }); + const endoBootstrap = await provideEndoBootstrap( + powers, + assignedWebletPortP, + { + cancelled, + cancel, + gracePeriodMs, + gracePeriodElapsed, + }, + ); return { endoBootstrap, cancelGracePeriod, assignWebletPort }; }; diff --git a/packages/daemon/src/guest.js b/packages/daemon/src/guest.js index 9a3b203146..7772652344 100644 --- a/packages/daemon/src/guest.js +++ b/packages/daemon/src/guest.js @@ -4,39 +4,35 @@ import { Far } from '@endo/far'; export const makeGuestMaker = ({ provideValueForFormulaIdentifier, - provideControllerForFormulaIdentifier, + provideControllerForFormulaIdentifierAndResolveHandle, makeMailbox, }) => { /** * @param {string} guestFormulaIdentifier - * @param {string} hostFormulaIdentifier + * @param {string} hostHandleFormulaIdentifier * @param {string} petStoreFormulaIdentifier * @param {string} mainWorkerFormulaIdentifier * @param {import('./types.js').Context} context */ const makeIdentifiedGuestController = async ( guestFormulaIdentifier, - hostFormulaIdentifier, + hostHandleFormulaIdentifier, petStoreFormulaIdentifier, mainWorkerFormulaIdentifier, context, ) => { - context.thisDiesIfThatDies(hostFormulaIdentifier); + context.thisDiesIfThatDies(hostHandleFormulaIdentifier); context.thisDiesIfThatDies(petStoreFormulaIdentifier); context.thisDiesIfThatDies(mainWorkerFormulaIdentifier); const petStore = /** @type {import('./types.js').PetStore} */ ( await provideValueForFormulaIdentifier(petStoreFormulaIdentifier) ); - const hostController = /** @type {import('./types.js').Controller<>} */ ( - await provideControllerForFormulaIdentifier(hostFormulaIdentifier) - ); - const hostPrivateFacet = await hostController.internal; - if (hostPrivateFacet === undefined) { - throw new Error( - `panic: a host request function must exist for every host`, + const hostController = + await provideControllerForFormulaIdentifierAndResolveHandle( + hostHandleFormulaIdentifier, ); - } + const hostPrivateFacet = await hostController.internal; const { respond: deliverToHost } = hostPrivateFacet; if (deliverToHost === undefined) { throw new Error( @@ -67,7 +63,7 @@ export const makeGuestMaker = ({ selfFormulaIdentifier: guestFormulaIdentifier, specialNames: { SELF: guestFormulaIdentifier, - HOST: hostFormulaIdentifier, + HOST: hostHandleFormulaIdentifier, }, context, }); diff --git a/packages/daemon/src/host.js b/packages/daemon/src/host.js index fdf86f225b..c0cf74bc42 100644 --- a/packages/daemon/src/host.js +++ b/packages/daemon/src/host.js @@ -7,11 +7,16 @@ const { quote: q } = assert; export const makeHostMaker = ({ provideValueForFormulaIdentifier, - provideValueForFormula, - provideValueForNumberedFormula, provideControllerForFormulaIdentifier, + incarnateWorker, + incarnateHost, + incarnateGuest, + incarnateEval, + incarnateUnconfined, + incarnateBundle, + incarnateWebBundle, + incarnateHandle, storeReaderRef, - makeSha512, randomHex512, makeMailbox, }) => { @@ -75,6 +80,13 @@ export const makeHostMaker = ({ context, }); + /** + * @returns {Promise<{ formulaIdentifier: string, value: import('./types').ExternalHandle }>} + */ + const makeNewHandleForSelf = () => { + return incarnateHandle(hostFormulaIdentifier); + }; + /** * @param {import('./types.js').Controller} newController * @param {Record} introducedNames @@ -109,15 +121,12 @@ export const makeHostMaker = ({ } if (formulaIdentifier === undefined) { - /** @type {import('./types.js').GuestFormula} */ - const formula = { - type: /* @type {'guest'} */ 'guest', - host: hostFormulaIdentifier, - }; + const { formulaIdentifier: hostHandleFormulaIdentifier } = + await makeNewHandleForSelf(); const { value, formulaIdentifier: guestFormulaIdentifier } = // Behold, recursion: // eslint-disable-next-line no-use-before-define - await provideValueForFormula(formula, 'guest'); + await incarnateGuest(hostHandleFormulaIdentifier); if (petName !== undefined) { assertPetName(petName); await petStore.write(petName, guestFormulaIdentifier); @@ -178,8 +187,8 @@ export const makeHostMaker = ({ } let workerFormulaIdentifier = identifyLocal(workerName); if (workerFormulaIdentifier === undefined) { - const workerId512 = await randomHex512(); - workerFormulaIdentifier = `worker:${workerId512}`; + ({ formulaIdentifier: workerFormulaIdentifier } = + await incarnateWorker()); assertPetName(workerName); await petStore.write(workerName, workerFormulaIdentifier); } else if (!workerFormulaIdentifier.startsWith('worker:')) { @@ -199,14 +208,15 @@ export const makeHostMaker = ({ if (workerName === 'MAIN') { return mainWorkerFormulaIdentifier; } else if (workerName === 'NEW') { - const workerId512 = await randomHex512(); - return `worker:${workerId512}`; + const { formulaIdentifier: workerFormulaIdentifier } = + await incarnateWorker(); + return workerFormulaIdentifier; } assertPetName(workerName); let workerFormulaIdentifier = identifyLocal(workerName); if (workerFormulaIdentifier === undefined) { - const workerId512 = await randomHex512(); - workerFormulaIdentifier = `worker:${workerId512}`; + ({ formulaIdentifier: workerFormulaIdentifier } = + await incarnateWorker()); assertPetName(workerName); await petStore.write(workerName, workerFormulaIdentifier); } @@ -257,7 +267,7 @@ export const makeHostMaker = ({ throw new Error('Evaluator requires one pet name for each code name'); } - const formulaIdentifiers = await Promise.all( + const endowmentFormulaIdentifiers = await Promise.all( petNamePaths.map(async (petNameOrPath, index) => { if (typeof codeNames[index] !== 'string') { throw new Error(`Invalid endowment name: ${q(codeNames[index])}`); @@ -278,20 +288,13 @@ export const makeHostMaker = ({ }), ); - const evalFormula = { - /** @type {'eval'} */ - type: 'eval', - worker: workerFormulaIdentifier, - source, - names: codeNames, - values: formulaIdentifiers, - }; - // Behold, recursion: // eslint-disable-next-line no-use-before-define - const { formulaIdentifier, value } = await provideValueForFormula( - evalFormula, - 'eval', + const { formulaIdentifier, value } = await incarnateEval( + workerFormulaIdentifier, + source, + codeNames, + endowmentFormulaIdentifiers, ); if (resultName !== undefined) { await petStore.write(resultName, formulaIdentifier); @@ -314,19 +317,12 @@ export const makeHostMaker = ({ powersName, ); - const formula = { - /** @type {'make-unconfined'} */ - type: 'make-unconfined', - worker: workerFormulaIdentifier, - powers: powersFormulaIdentifier, - specifier, - }; - // Behold, recursion: // eslint-disable-next-line no-use-before-define - const { formulaIdentifier, value } = await provideValueForFormula( - formula, - 'make-unconfined', + const { formulaIdentifier, value } = await incarnateUnconfined( + workerFormulaIdentifier, + powersFormulaIdentifier, + specifier, ); if (resultName !== undefined) { await petStore.write(resultName, formulaIdentifier); @@ -359,19 +355,12 @@ export const makeHostMaker = ({ powersName, ); - const formula = { - /** @type {'make-bundle'} */ - type: 'make-bundle', - worker: workerFormulaIdentifier, - powers: powersFormulaIdentifier, - bundle: bundleFormulaIdentifier, - }; - // Behold, recursion: // eslint-disable-next-line no-use-before-define - const { value, formulaIdentifier } = await provideValueForFormula( - formula, - 'make-bundle', + const { value, formulaIdentifier } = await incarnateBundle( + powersFormulaIdentifier, + workerFormulaIdentifier, + bundleFormulaIdentifier, ); if (resultName !== undefined) { @@ -383,19 +372,16 @@ export const makeHostMaker = ({ /** * @param {string} [petName] + * @returns {Promise} */ const makeWorker = async petName => { - const workerId512 = await randomHex512(); - const formulaIdentifier = `worker:${workerId512}`; + // Behold, recursion: + const { formulaIdentifier, value } = await incarnateWorker(); if (petName !== undefined) { assertPetName(petName); await petStore.write(petName, formulaIdentifier); } - return /** @type {Promise} */ ( - // Behold, recursion: - // eslint-disable-next-line no-use-before-define - provideValueForFormulaIdentifier(formulaIdentifier) - ); + return /** @type {import('./types.js').EndoWorker} */ (value); }; /** @@ -410,12 +396,16 @@ export const makeHostMaker = ({ formulaIdentifier = identifyLocal(petName); } if (formulaIdentifier === undefined) { - const id512 = await randomHex512(); - formulaIdentifier = `host:${id512}`; + const { formulaIdentifier: newFormulaIdentifier, value } = + await incarnateHost( + endoFormulaIdentifier, + leastAuthorityFormulaIdentifier, + ); if (petName !== undefined) { assertPetName(petName); - await petStore.write(petName, formulaIdentifier); + await petStore.write(petName, newFormulaIdentifier); } + return { formulaIdentifier: newFormulaIdentifier, value }; } else if (!formulaIdentifier.startsWith('host:')) { throw new Error( `Existing pet name does not designate a host powers capability: ${q( @@ -459,24 +449,10 @@ export const makeHostMaker = ({ powersName, ); - const digester = makeSha512(); - digester.updateText( - `${bundleFormulaIdentifier},${powersFormulaIdentifier}`, - ); - const formulaNumber = digester.digestHex().slice(32, 64); - - const formula = { - type: 'web-bundle', - bundle: bundleFormulaIdentifier, - powers: powersFormulaIdentifier, - }; - // Behold, recursion: - // eslint-disable-next-line no-use-before-define - const { value, formulaIdentifier } = await provideValueForNumberedFormula( - 'web-bundle', - formulaNumber, - formula, + const { value, formulaIdentifier } = await incarnateWebBundle( + powersFormulaIdentifier, + bundleFormulaIdentifier, ); if (webPageName !== undefined) { diff --git a/packages/daemon/src/mail.js b/packages/daemon/src/mail.js index fe060114fa..e8a0974427 100644 --- a/packages/daemon/src/mail.js +++ b/packages/daemon/src/mail.js @@ -10,18 +10,18 @@ const { quote: q } = assert; /** * @param {object} args + * @param {(hubFormulaIdentifier: string, petNamePath: string[]) => Promise<{ formulaIdentifier: string, value: unknown }>} args.incarnateLookup * @param {import('./types.js').ProvideValueForFormulaIdentifier} args.provideValueForFormulaIdentifier * @param {import('./types.js').ProvideControllerForFormulaIdentifier} args.provideControllerForFormulaIdentifier * @param {import('./types.js').GetFormulaIdentifierForRef} args.getFormulaIdentifierForRef - * @param {import('./types.js').MakeSha512} args.makeSha512 - * @param {import('./types.js').ProvideValueForNumberedFormula} args.provideValueForNumberedFormula + * @param {import('./types.js').ProvideControllerForFormulaIdentifierAndResolveHandle} args.provideControllerForFormulaIdentifierAndResolveHandle */ export const makeMailboxMaker = ({ + incarnateLookup, + getFormulaIdentifierForRef, provideValueForFormulaIdentifier, provideControllerForFormulaIdentifier, - getFormulaIdentifierForRef, - makeSha512, - provideValueForNumberedFormula, + provideControllerForFormulaIdentifierAndResolveHandle, }) => { /** * @param {object} args @@ -124,28 +124,8 @@ export const makeMailboxMaker = ({ /** @type {import('./types.js').Mail['provideLookupFormula']} */ const provideLookupFormula = async petNamePath => { - // The lookup formula identifier consists of the hash of the associated - // naming hub's formula identifier and the pet name path. - // A "naming hub" is an objected with a variadic lookup method. It includes - // objects such as guests and hosts. - const digester = makeSha512(); - digester.updateText(`${selfFormulaIdentifier},${petNamePath.join(',')}`); - const lookupFormulaNumber = digester.digestHex(); - // TODO:lookup Check if the lookup formula already exists in the store - /** @type {import('./types.js').LookupFormula} */ - const lookupFormula = { - /** @type {'lookup'} */ - type: 'lookup', - hub: selfFormulaIdentifier, - path: petNamePath, - }; - - return provideValueForNumberedFormula( - 'lookup', - lookupFormulaNumber, - lookupFormula, - ); + return incarnateLookup(selfFormulaIdentifier, petNamePath); }; /** @@ -375,9 +355,10 @@ export const makeMailboxMaker = ({ if (recipientFormulaIdentifier === undefined) { throw new Error(`Unknown pet name for party: ${recipientName}`); } - const recipientController = await provideControllerForFormulaIdentifier( - recipientFormulaIdentifier, - ); + const recipientController = + await provideControllerForFormulaIdentifierAndResolveHandle( + recipientFormulaIdentifier, + ); const recipientInternal = await recipientController.internal; if (recipientInternal === undefined || recipientInternal === null) { throw new Error(`Recipient cannot receive messages: ${recipientName}`); @@ -416,7 +397,6 @@ export const makeMailboxMaker = ({ strings, edgeNames, formulaIdentifiers, - recipientFormulaIdentifier, ); // add to own mailbox receive( @@ -424,6 +404,7 @@ export const makeMailboxMaker = ({ strings, edgeNames, formulaIdentifiers, + // Sender expects the handle formula identifier. recipientFormulaIdentifier, ); }; @@ -486,20 +467,18 @@ export const makeMailboxMaker = ({ throw new Error(`Unknown pet name for party: ${recipientName}`); } const recipientController = - /** @type {import('./types.js').Controller<>} */ ( - await provideControllerForFormulaIdentifier( - recipientFormulaIdentifier, - ) + await provideControllerForFormulaIdentifierAndResolveHandle( + recipientFormulaIdentifier, ); - const recipientInternal = await recipientController.internal; - if (recipientInternal === undefined) { + if (recipientInternal === undefined || recipientInternal === null) { throw new Error( `panic: a receive request function must exist for every party`, ); } - const { respond: deliverToRecipient } = await recipientInternal; + // @ts-expect-error We sufficiently check if recipientInternal or deliverToRecipient is undefined + const { respond: deliverToRecipient } = recipientInternal; if (deliverToRecipient === undefined) { throw new Error( `panic: a receive request function must exist for every party`, @@ -528,6 +507,7 @@ export const makeMailboxMaker = ({ responseName, selfFormulaIdentifier, petStore, + // Sender expects the handle formula identifier. recipientFormulaIdentifier, ); const newResponseP = Promise.race([recipientResponseP, selfResponseP]); diff --git a/packages/daemon/src/pet-store.js b/packages/daemon/src/pet-store.js index 4e052adb14..6dc4c8d8db 100644 --- a/packages/daemon/src/pet-store.js +++ b/packages/daemon/src/pet-store.js @@ -9,7 +9,7 @@ const { quote: q } = assert; const validIdPattern = /^[0-9a-f]{128}$/; const validFormulaPattern = - /^(?:(?:readable-blob|worker|pet-store|pet-inspector|eval|lookup|make-unconfined|make-bundle|host|guest):[0-9a-f]{128}|web-bundle:[0-9a-f]{32})$/; + /^(?:(?:readable-blob|worker|pet-store|pet-inspector|eval|lookup|make-unconfined|make-bundle|host|guest|handle):[0-9a-f]{128}|web-bundle:[0-9a-f]{32})$/; /** * @param {import('./types.js').FilePowers} filePowers diff --git a/packages/daemon/src/types.d.ts b/packages/daemon/src/types.d.ts index 6a81e90da7..c9e3b1b2e0 100644 --- a/packages/daemon/src/types.d.ts +++ b/packages/daemon/src/types.d.ts @@ -57,9 +57,35 @@ type FormulaIdentifierRecord = { number: string; }; +type EndoFormula = { + type: 'endo'; + host: string; + leastAuthority: string; + webPageJs?: string; +}; + +type WorkerFormula = { + type: 'worker'; +}; + +type HostFormula = { + type: 'host'; + worker: string; + inspector: string; + petStore: string; + endo: string; + leastAuthority: string; +}; + type GuestFormula = { type: 'guest'; host: string; + petStore: string; + worker: string; +}; + +type LeastAuthorityFormula = { + type: 'least-authority'; }; type EvalFormula = { @@ -71,6 +97,11 @@ type EvalFormula = { // TODO formula slots }; +type ReadableBlobFormula = { + type: 'readable-blob'; + content: string; +}; + type LookupFormula = { type: 'lookup'; @@ -108,13 +139,35 @@ type WebBundleFormula = { powers: string; }; +type HandleFormula = { + type: 'handle'; + target: string; +}; + +type PetStoreFormula = { + type: 'pet-store'; +}; + +type PetInspectorFormula = { + type: 'pet-inspector'; + petStore: string; +}; + export type Formula = + | EndoFormula + | WorkerFormula + | HostFormula | GuestFormula + | LeastAuthorityFormula | EvalFormula + | ReadableBlobFormula | LookupFormula | MakeUnconfinedFormula | MakeBundleFormula - | WebBundleFormula; + | WebBundleFormula + | HandleFormula + | PetInspectorFormula + | PetStoreFormula; export type Label = { number: number; @@ -188,6 +241,28 @@ export type ProvideValueForFormulaIdentifier = ( export type ProvideControllerForFormulaIdentifier = ( formulaIdentifier: string, ) => Controller; +export type ProvideControllerForFormulaIdentifierAndResolveHandle = ( + formulaIdentifier: string, +) => Promise; + +/** + * A handle is used to create a pointer to a formula without exposing it directly. + * This is the external facet of the handle and is safe to expose. This is used to + * provide an EndoGuest with a reference to its creator EndoHost. By using a handle + * that points to the host instead of giving a direct reference to the host, the + * guest does not get access to the functions of the host. This is the external + * facet of a handle. It directly exposes nothing. The handle's target is only + * exposed on the internal facet. + */ +export interface ExternalHandle {} +/** + * This is the internal facet of a handle. It exposes the formula id that the + * handle points to. This should not be exposed outside of the endo daemon. + */ +export interface InternalHandle { + targetFormulaIdentifier: string; +} + export type GetFormulaIdentifierForRef = (ref: unknown) => string | undefined; export type MakeSha512 = () => Sha512; @@ -306,6 +381,7 @@ export interface EndoReadable { text(): Promise; json(): Promise; } +export type FarEndoReadable = FarRef; export interface EndoWorker { terminate(): void; @@ -385,6 +461,15 @@ export type EndoWebBundle = { powers: ERef; }; +export type FarEndoBootstrap = FarRef<{ + ping: () => Promise; + terminate: () => Promise; + host: () => Promise; + leastAuthority: () => Promise; + webPageJs: () => Promise; + importAndEndowInWebPage: () => Promise; +}>; + export type CryptoPowers = { makeSha512: () => Sha512; randomHex512: () => Promise; @@ -441,13 +526,13 @@ export type NetworkPowers = SocketPowers & { cancelled: Promise; }) => Promise; makePrivatePathService: ( - endoBootstrap: FarRef, + endoBootstrap: FarEndoBootstrap, sockPath: string, cancelled: Promise, exitWithError: (error: Error) => void, ) => { started: Promise; stopped: Promise }; makePrivateHttpService: ( - endoBootstrap: FarRef, + endoBootstrap: FarEndoBootstrap, port: number, assignWebletPort: (portP: Promise) => void, cancelled: Promise, @@ -457,7 +542,10 @@ export type NetworkPowers = SocketPowers & { export type DaemonicPersistencePowers = { initializePersistence: () => Promise; - provideRootNonce: () => Promise; + provideRootNonce: () => Promise<{ + rootNonce: string; + isNewlyCreated: boolean; + }>; makeContentSha512Store: () => { store: (readable: AsyncIterable) => Promise; fetch: (sha512: string) => EndoReadable; @@ -471,7 +559,7 @@ export type DaemonicPersistencePowers = { getWebPageBundlerFormula?: ( workerFormulaIdentifier: string, powersFormulaIdentifier: string, - ) => Formula; + ) => MakeUnconfinedFormula; }; export interface DaemonWorkerFacet {} diff --git a/packages/daemon/test/test-endo.js b/packages/daemon/test/test-endo.js index c039e8999c..16a63f0108 100644 --- a/packages/daemon/test/test-endo.js +++ b/packages/daemon/test/test-endo.js @@ -1111,3 +1111,28 @@ test('list special names', async t => { t.assert(specialNames.every(name => name.toUpperCase() === name)); t.deepEqual([...specialNames, ...names], allNames); }); + +test('guest cannot access host methods', async t => { + const { promise: cancelled, reject: cancel } = makePromiseKit(); + t.teardown(() => cancel(Error('teardown'))); + + const locator = makeLocator('tmp', 'guest-cannot-host'); + + await start(locator); + t.teardown(() => stop(locator)); + + const { getBootstrap } = await makeEndoClient( + 'client', + locator.sockPath, + cancelled, + ); + const bootstrap = getBootstrap(); + const host = E(bootstrap).host(); + const guest = E(host).provideGuest('guest'); + const guestsHost = E(guest).lookup('HOST'); + await t.throwsAsync(() => E(guestsHost).lookup('SELF'), { + message: /target has no method "lookup"/u, + }); + const revealedTarget = await E.get(guestsHost).targetFormulaIdentifier; + t.is(revealedTarget, undefined); +});