-
Notifications
You must be signed in to change notification settings - Fork 80
feat(compartment-mapper)!: new hooks & type overhaul #2988
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
Conversation
6c51178 to
d2e827c
Compare
d2e827c to
ee56c40
Compare
|
ee56c40 to
5e78f40
Compare
|
c37edbe to
1d864b5
Compare
8b5ac3e to
38e98bf
Compare
b21326c to
983ad2e
Compare
7916a39 to
d73f3d9
Compare
| message, | ||
| log, | ||
| }) => { | ||
| log(`WARN: Invalid resource ${q(canonicalName)} in policy: ${message}`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This kind of things, at least in my experience with webpack, worked better if aggregated and displayed as a single warning. In a debug mode of sorts they could be used to generate proposed policy fixes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This sounds like more work 😄
I could do this, but I think even reporting this at all is going out on a limb (in terms of the overall user-friendliness of @endo/compartment-mapper). I would not be necessarily opposed to expanding this and implementing other friendly improvements, but I don't think they need to be in this PR.
| } | ||
|
|
||
| // #region packageDescriptor hook | ||
| if (packageDescriptorHook) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure this is useful, but it could be useful if it allowed altering the descriptor (eg. in case it contain something that was not supported or needed a fix.
| Compartment: CompartmentOption = DefaultCompartment, | ||
| log = noop, | ||
| preload = [], | ||
| packageConnectionsHook, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
packageDependenciesHook and packageConnectionsHook are similar
I looked at both of these hooks and I think packageConnectionsHook could be replaced with packageDependenciesHook and the main difference is that one of them allows returning a mutated set.
If we could offer the same in digest, we'd have a single hook for both.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The data from packageDependenciesHook and packageConnectionsHook come from two different types of compartment map, and the latter happens post-digest. I would not want to do this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not enjoying there being two flows that are somewhat similar but ultimately distinct. But the solution to that is ultimately not forcing the unification fo the hook, but finding way to split regular flow into steps so that capture was no longer necessary a all.
Can leave this be if you're certain unifying the 2 hooks wouldn't be feasible.
Note that I'm not fond of explaining the details and differences between the two different types of compartment maps to the end user 🙈
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I mean, ideally the end user wouldn't need to touch a compartment map.
284b755 to
d764dcc
Compare
2255797 to
4afedea
Compare
…duleConfiguration This narrows `CompartmentDescriptor` into three discrete flavors; `PackageCompartmentDescriptor` (as returned by `mapNodeModules()`, `DigestedCompartmentDescriptor` (as returned by `digestCompartmentMap()`, and `FileCompartmentDescriptor` (as used in `parseArchive()`. `ModuleDescriptor` has been renamed to `ModuleConfiguration` to avoid conflicts with `ses`' `ModuleDescriptor`. `ModuleConfiguration` has been narrowed into several types: - `CompartmentModuleConfiguration` - references another `ModuleConfiguration` - `FileModuleConfiguration` - refers to a module on disk - `ErrorModuleConfiguration` - a module configuration having a `deferredError` prop - `ExitModuleConfiguration` - refers to an exit module
exclude .d.ts files (which may exist after running `prepack`)
…jects This adds some helpers to snapshot `CompartmentMapDescriptor`s, and `CaptureResult` objects
…ties `ProjectFixture` objects can now have `entrypoint` properties. Extend project fixture read powers to actually read phony files, not just package descriptors. Abuses node's custom `inspect()` facilities to make a real sweet dump of a `CompartmentMapDescriptor`.
…env var This changes `test/scaffold.js` to recognize a `SCAFFOLD_LOGGING` env var. If it is set, then calls to `log()` throughout various exported functions in the package will actually log (via AVA) instead of doing nothing.
868a287 to
70b3440
Compare
BREAKING CHANGES: `CompartmentMapDescriptor` no longer has a `path` property. `CompartmentMapDescriptor`'s `label` property is now a _canonical name_ (a string of one or more npm package names separated by `>`). The `CompartmentMapDescriptor` returned by `captureFromMap()` now uses canonical names as the keys in its `compartments` property.
- Breaking types: `CompartmentMapDescriptor`, `CompartmentDescriptor`,
`ModuleConfiguration` (renamed from `ModuleDescriptor`) and `ModuleSource`
have all been narrowed into discrete subtypes.
- `captureFromMap()`, `loadLocation()` and `importLocation()` now accept a
`moduleSourceHook` option. This hook is called when processing each module
source, receiving the module source data (location, language, bytes, or error
information) and the canonical name of the containing package.
- `mapNodeModules()`, `loadLocation()`, `importLocation()`, `makeScript()`,
`makeFunctor()`, and `writeScript()` now accept the following hook options:
- `unknownCanonicalNameHook`: Called for each canonical name mentioned in
policy but not found in the compartment map. Useful for detecting policy
misconfigurations.
- `packageDependenciesHook`: Called for each package with its set of
dependencies. Can return partial updates to modify the dependencies,
enabling dependency filtering or injection based on policy.
- `packageDataHook`: Called once with data about all packages found while
crawling `node_modules`, just prior to creation of a compartment map.
* * *
- When packages are disallowed by policy, the disallowing bit happens earlier than it used to. This has downstream effects on which errors are thrown when access is requested to a disallowed compartment. Regardless, the ultimate behavior doesn't change—disallowed compartments are still disallowed. The new behavior is reflected in the changes to `src/policy.test.js`.
- Improved compartment map validation to work with discrete `CompartmentMapDescriptor` subtypes
- Type guards used
- `mapNodeModules()` now has a concept of a `FinalNode` having a `label` prop
- `makePackagePolicy()` replaces `getPolicyForPackage()` in `src/policy.js`
- Many tests added for new and old features and fixed (such as nonsensical policies in `test/dynamic-require.test.js`)
70b3440 to
e3a1f27
Compare
|
@boneskull Why remove the hooks.md with a graph documenting how hooks are reachable from public API? I found it very helpful in understanding how it all goes together. |
naugtur
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM.
Would prefer to keep hooks.md
|
@naugtur For some reason I thought that doc was outdated and reflected the old system. If it's not, I'll restore it UPDATE: I restored it. |
e3a1f27 to
ccf0ae1
Compare
Refs: #2929
This PR supersedes #2929 which should now be considered abandoned.
This PR targets #2915, if you didn't notice.
Description
This PR is "complete" and ready for review. It makes a significant overhaul to types and adds new options to several public APIs.
Description of changes to Types
CompartmentMapDescriptorhave been vastly expanded to reflect the different "flavors" ofCompartmentMapDescriptor,CompartmentDescriptor,ModuleDescriptor(nowModuleDescriptorConfigurationto differentiate it fromses'ModuleDescriptor), and the formatting of the compartment names (keys).CompartmentDescriptor.labelis now a canonical name.CompartmentDescriptor.pathis removedCompartmentDescriptor.compartmentsis removedNew Default Behavior in
mapNodeModules()Currently,
mapNodeModules()avoids adding aModuleDescriptor(ModuleDescriptorConfiguration) to a nascentCompartmentDescriptor'smodulesprop if policy disallows it. This PR changes the behavior to consider policy & excise the dependency from theNodeitself before the (dependency)Graphis translated into aCompartmentDescriptor. This has implications around what sort of errors are thrown when Compartment A attempts to load a Compartment B to which it has no access (see note about policy tests below).Other Notes
CompartmentDescriptor.compartmentshas been removed, that means implicitly allowing dynamic requires of parent/ancestor Compartments from a given Compartment via absolute path is no longer supported. That's fine, since:@lavamoat/nodewas the only user of this feature and no longer needs it$root$. It is valid withinPackagePolicy; i.e. some other package can be allowed to access the$root$compartment.test/policy.test.tsneeded to change because of howmapNodeModules'packageDependencieshook works. Instead of rejecting module descriptors (ModuleDescriptorConfigurations) based on policy, we reject entire dependencies before they can be "digested" into module descriptors. This results inScopeDescriptors not being populated, in addition toModuleDescriptorConfigurations, and causes downstream effects. Different exceptions are thrown at different times, but the intent of these tests doesn't deviate from "ensure this naughty behavior is not allowed."Security Considerations
None that I'm aware of.
Scaling Considerations
Any consumer providing a resource-intensive blocking hook to
@endo/compartment-mappergets the performance they deserve.Documentation Considerations
This is a breaking change to the contents of the archives as well as a breaking change to the types. It is not a breaking change to the archive format, but it will contain different values (instead of
packageName-v<version>the compartment descriptors will be keyed on the canonical name).Testing Considerations
Compatibility Considerations
The breaking changes mentioned above, but it also solves like ten different problems
@lavamoat/nodewas having trying to get things to work. It significantly improves ecosystem compatibility and will improve the performance of@lavamoat/node.Upgrade Considerations
*BREAKING*:in the commit message with migration instructions for any breaking change.NEWS.mdfor user-facing changes.This commit introduces a comprehensive universal hook system for
@endo/compartment-mapper,enabling extensible customization of module mapping, bundling, and policy enforcement.
BREAKING CHANGES:
CompartmentDescriptorobjects no longer have apathproperty and thelabelproperty is now a canonical name. Types have changed dramatically. Enhanced validation ofCompartmentMapDescriptorobjects.New Options
mapNodeModules()packageDataHook: Called once beforetranslateGraphwith data about all packages found while crawlingnode_modules. Receives a read-onlyMap<CanonicalName, PackageData>where each entry contains:name: Package namepackageDescriptor: The package.json contentslocation: File URL to the packagecanonicalName: The canonical name used in policypackageDependenciesHook: Called for each package's dependencies during graph translation, allowing dynamic modification of the dependency graph. Can add or remove dependencies from packages based on policy or other criteria.unknownCanonicalNameHook: Called when policy references canonical names that don't exist in the dependency graph, with optional suggestions for typos/similar names.makeImportHookMaker()moduleSourceHook(): Called when module source objects are created (either from local files containingbytes, exit modules, or "error-type" sources)captureFromMap()packageConnectionsHook: Called during digest; surfaces "connections" between compartment descriptorsType System Overhaul (
src/types/)Major Breaking Type Changes in
compartment-map-schema.ts:CompartmentDescriptor Interface Restructure:
pathproperty - compartment descriptors no longer track dependency pathslabelproperty type changed fromstringtoCanonicalName<U>- labels are now type-safe canonical namesCompartmentDescriptorgeneric with<T extends ModuleDescriptorConfiguration, U extends string>for better type safetylocation: stringas a required property for all compartment descriptorssourceDirnameas found in the sourcesCompartmentMapDescriptor Genericization (Is That A Word? No):
CompartmentMapDescriptoris now generic:CompartmentMapDescriptor<T, Name, EntryName>compartmentsproperty changed fromRecord<string, CompartmentDescriptor>toCompartmentDescriptors<T, Name>entryproperty type changed fromEntryDescriptortoEntryDescriptor<EntryName>PackageCompartmentMapDescriptor,FileCompartmentMapDescriptor, andDigestedCompartmentMapDescriptorEnhanced Module Descriptor System:
ModuleDescriptoris nowModuleDescriptorConfigurationto differentiate between it & theModuleDescriptorfromsesModuleDescriptorConfigurationCreatorenum tracking module creation source ('link' | 'transform' | 'import-hook' | 'digest' | 'node-modules')BaseModuleDescriptorConfigurationwith__createdByproperty for debuggingErrorModuleDescriptorConfigurationfor deferred error handlingModuleDescriptorConfigurationKindToTypeutility typeNew Type Infrastructure:
canonical-name.ts- Canonical Name Type System:CanonicalName<S>- validates npm package name chains separated by '>' (e.g., "foo>bar", "@scope/pkg>dep")ScopedPackageNameandUnscopedPackageNamefor npm package name validationSplitOnGt<S>andAllValidPackageNames<Parts>for compile-time canonical name parsingIsCanonicalName<S>predicate type for conditional type logicEnhanced Type Safety Features:
Compartment Descriptor Validation:
DigestedCompartmentDescriptor- restricted descriptor for archived compartment mapsneverfor digested descriptors:path,retained,scopes,parsers,types,__createdBy,sourceDirnameCompartmentDescriptorWithPolicy<T>- enforces policy presence where requiredModule Configuration Type Safety:
ModuleDescriptorConfigurationKindunion for module type discrimination__createdBytrackingPolicy Integration:
PackageCompartmentDescriptorwith canonical name constraintsLiteralUnionusage for special canonical names (ATTENUATORS_COMPARTMENT,ENTRY_COMPARTMENT)Type System Utilities:
typescript.tsEnhancements:LiteralUnion<L, B>for better literal type preservationSimplifyfrom tests here (because it's useful dammit)Enhanced Policy Validation
Policy Validation: