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

feat(import-bundle): Test bundle format #2719 #2735

Merged
merged 1 commit into from
Mar 11, 2025

Conversation

kriskowal
Copy link
Member

@kriskowal kriskowal commented Mar 6, 2025

Closes: #2719

Description

This change introduces a new bundle format to simplify tests for subsystems that take a bundle that they will ultimately use importBundle to extract a mock module exports namespace.
The new bundle format takes care to render impossible any accidental or malicious use of a test h
To that end, the format captures the mock exports namespace on a symbol-named property of then bundle, so it will be elided or rejected when serialized.
And, to compensate for the cryptic protocol, this change provides a tiny utility function for making test bundles from mock exports.

Security Considerations

Existing bundle importers expect only to be able to confine execution of local, serialized bundles.
This change takes some care to maintain the expectation by limiting exposure to live objects in a way that’s only reachable by test code.
If an adversary were able to present a test bundle, it’s not clear that this would constitute any escalation in privilege, given that an attacker would need to arrange for a live object by some other escalator.

Scaling Considerations

This change introduces a very small amount of new code to the Agoric kernel and, out of an abundance of caution, is not reached in the common case of importing the endoZipBase64 bundle format.

Documentation Considerations

The change includes relevant documentation in NEWS.md and README.md for the import-bundle package. This is a new platform API and may be relevant in the context of tutorials for testing applications on Agoric and other Endo platforms.

Testing Considerations

Includes unit tests exercising the new behavior and invariants.

Compatibility Considerations

New features, no breaking changes.

Upgrade Considerations

None.

@kriskowal kriskowal requested a review from turadg March 6, 2025 23:47
@kriskowal kriskowal force-pushed the kriskowal-import-bundle-test-format-2719 branch from 058918b to c18ed26 Compare March 7, 2025 00:03
Copy link
Contributor

@mhofman mhofman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can be a little more conformant without a much higher cost

# Next release

- Adds support for `test` format bundles, which simply return a promise for an
object that resembles a module exports namespace with the objects specified
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh interesting in French it's "ressemble"

Comment on lines 90 to 131
return Object.assign(
Object.create(null, {
[Symbol.toStringTag]: {
value: 'Module',
writable: false,
enumerable: false,
configurable: false,
},
}),
exports,
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some conformance feedback:

  • Object.assign would copy any string or symbol own properties, but really we should only allow string properties as exports.
    • It does only copy enumerable properties, and sets values (doesn't copy accessors), which is nice.
  • The exports string properties are supposed to be sorted
  • The symbol property is supposed to enumerate after the string export properties
  • The namespace object should be sealed
  • While the exotic behavior is to have properties writable: true and deny any external changes to the properties, I think we should be fine with non-writable regular data properties
    • This would effectively make the namespace object frozen

There is a question of whether we should trigger getters on the test bundle exports at all, or we should deny this. In a perfect world we'd detect proxies and deny that as well.

* as JSON or passable.
* @param {Record<PropertyKey, unknown>} exports
*/
export const bundleTestExports = exports => ({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably enforce string only data properties here too to catch mistakes early.

@dckc
Copy link
Contributor

dckc commented Mar 7, 2025

looks like fun!

To partially validate that it should help with these sorts of things...

I did yarn add --dev @agoric/fast-usdc and then ran this test:

import test from '@endo/ses-ava/prepare-endo.js';
import { importBundle } from '../src/index.js';
import * as f from '@agoric/fast-usdc/src/fast-usdc.contract.js';

test('get live contract code using importBundle', async t => {
  const bundle = {
    moduleFormat: 'test',
    [Symbol.for('exports')]: f,
  };
  const ns = await importBundle(bundle);
  t.log(ns);
  t.is(typeof ns.start, 'function');
});

works as expected:

~/projects/endo/packages/import-bundle$ yarn test test/contract-fun.test.js 

  ✔ get live contract code using importBundle
    ℹ @Module {
        contract: AsyncFunction {},
        meta: {
          customTermsShape: {
            usdcDenom: Object @match:string { … },
          },
          privateArgsShape: {
            agoricNames: Object @match:kind { … },
            assetInfo: Object @match:arrayOf { … },
            chainInfo: Object @match:recordOf { … },
            feeConfig: Object { … },
            localchain: Object @match:kind { … },
            marshaller: Object @match:kind { … },
            orchestrationService: Object @match:kind { … },
            poolMetricsNode: Object @match:kind { … },
            storageNode: Object @match:kind { … },
            timerService: Object @match:kind { … },
          },
        },
        start: AsyncFunction {},
      }


  1 test passed

turadg added a commit to Agoric/agoric-sdk that referenced this pull request Mar 10, 2025
@kriskowal kriskowal force-pushed the kriskowal-import-bundle-test-format-2719 branch 2 times, most recently from 6b9805d to bf80971 Compare March 10, 2025 22:18
@kriskowal kriskowal requested a review from mhofman March 10, 2025 22:37
@kriskowal
Copy link
Member Author

I’ve added an early error detection guard for symbol-named properties to the bundleTestExports utility method and also a defensive guard for symbol-named exports to importBundle, and sorted the properties of the simulated module exports namespace. Back to my reviewers in the studio.

turadg added a commit to Agoric/agoric-sdk that referenced this pull request Mar 10, 2025
@kriskowal kriskowal force-pushed the kriskowal-import-bundle-test-format-2719 branch from bf80971 to 05577a0 Compare March 11, 2025 00:03
@@ -124,6 +174,21 @@ export async function importBundle(bundle, options = {}, powers = {}) {
}
}

/**
* A utility function for producing test bundles, which are not seerializable
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* A utility function for producing test bundles, which are not seerializable
* A utility function for producing test bundles, which are not serializable

// We emulate a module exports namespace object, which has certain invariants:
// Property names are only strings, so we will ignore symbol-named properties.
// All properties are enumerable, so we will ignore non-enumerable properties.
// All properties are non-writable values.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: String keys on Module namespace objects are actually all writable but we purposely deviate here.

Comment on lines 183 to 190
const symbols = Object.getOwnPropertySymbols(exports);
symbols.length > 0 &&
Fail`exports must not have symbol-named properties, got: ${symbols.map(String).join(', ')}`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's be flexible and allow a Symbol.toStringTag own prop so that a module namespace object can be used directly.

Comment on lines +186 to +194
return {
moduleFormat: 'test',
[Symbol.for('exports')]: exports,
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we freeze the object (not harden it since we don't care about the stability of exports at this point)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would be a departure from the norm for bundleSource. I think it’s reasonable to defer that choice to the caller. But, if we at some point reörganize the bundler infrastructure and create a new entry-point, I’d be open to changing the contract. Other reasons to consider reorganizing around an @endo/bundler module maybe someday would be to consolidate @endo/import-bundle, @endo/bundle-source, and @endo/check-bundle into a single package so they can more easily cross-test, do a better job of isolating powers and decoupling them from the underlying platform (like node-powers.js in the Compartment Mapper), remove deprecated powers like the ones taken by Rollup, one README for all the bundle formats and how to read and write them, &c. The list is growing but not pressing.

@kriskowal kriskowal requested a review from mhofman March 11, 2025 02:14
Copy link
Contributor

@mhofman mhofman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@kriskowal kriskowal force-pushed the kriskowal-import-bundle-test-format-2719 branch from 5a383a2 to 17ec018 Compare March 11, 2025 02:18
@kriskowal kriskowal enabled auto-merge March 11, 2025 02:21
@kriskowal kriskowal merged commit a7954e9 into master Mar 11, 2025
16 checks passed
@kriskowal kriskowal deleted the kriskowal-import-bundle-test-format-2719 branch March 11, 2025 02:30
@turadg turadg mentioned this pull request Mar 11, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

ability in importBundle to take a live object
4 participants