-
Notifications
You must be signed in to change notification settings - Fork 80
feat(compartment-mapper): support mapping of undiscoverable packages #2856
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
`${compartmentDescriptors[additionalPackageLocation].name}${additionalPackageModuleSpecifier.substring(1)}`, | ||
); | ||
} else { | ||
throw new Error( |
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 only way for this to happen would be to provide a broken CompartmentMapDescriptor
in the first place. IDK if it's worth worrying about.
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.
Review in progress, but I think there might be a less (opinion) invasive way to serve the same need.
That being said, I want to encourage others to review because this exists and my hypothetical other way doesn't (at least until I try)
@naugtur Regarding setting the AFAICT it is needed, since we do not know what will happen with the compartment descriptor map after it exits If it is provided to If it is provided to Whether or not to set this bit could potentially be a flag to The latest change will actually set this flag recursively. It should be possible to narrow the set by determining which compartment descriptors are only referenced by the additional modules and other like descriptors. I haven't done that yet, but I think it makes sense to do since the additional module may reference the entry point (so we've just flagged the whole compartment map). An alternative design (which may be a better idea, but idk) would involve fiddling with the digest function to force it to keep certain compartments. |
Also: I noticed that we need explicit control over the For example, if our entrypoint is |
0ba0952
to
31d52de
Compare
32e87c6
to
577eaf8
Compare
working on sorting out the merge conflict |
a4fb5c6
to
f860560
Compare
577eaf8
to
6bf2f4f
Compare
f860560
to
80fdac9
Compare
6bf2f4f
to
0728447
Compare
80fdac9
to
371504b
Compare
49809df
to
fbf783c
Compare
…rror messaging - Adds a reusable function, `policyEnforcementFailureMessage()`, to generate helpful error messages when policy enforcement fails - Narrows `PolicyOption` type - Loosens `FullAttenuationDefinition` type, wherein `params` is actually an optional field per the extant test suites - `policy-format.js`: - Moves `generateCanonicalName()` here - Updates `PackageNamingKit.name` for use in this context - Adds more type guards and assertions for validation - Adds `or()`, `and()` and `not()` which can be used to compose type guards in a type-safe way - Moved `ATTENUATORS_COMPARTMENT` here to avoid cycle - Added some helpers to `typescript.ts` - Update snapshots to reflect slight changes to error messaging # Conflicts: # packages/compartment-mapper/src/node-modules.js
…resort package policy lookup This adds `enforcePackagePolicyByPath()` which accepts two (2) `CompartmentDescriptor`s (_A_ and _B)_ and determines if _B_ is allowed access to _A_ via package policy. `findRedirect()`, called by `importNowHook()`, now uses this if it cannot enforce policy any other way. This solves the issue described in #2876, which is that a package needs to dynamically require a module from an otherwise-unrelated-except-by-policy `CompartmentDescriptor`. Slightly changed the existing error hint to differentiate this case. Fixes #2876
This adds options to `mapNodeModules()`, `captureFromMap()`, `loadFromMap()` and `importLocation()` to support loading additional packages that wouldn't otherwise be discovered by `mapNodeModules()`. It takes several ideas from #2864 which were not already implemented in #2876. The first option is `additionalModuleLocations`, which is an `Array<string | AdditionalModuleLocationObject>` where items can either be fully-qualified `file://` URLs or an object with shape: ```ts export interface AdditionalModuleLocationObject { location: string; dev?: boolean; conditions?: Set<string>; } ``` Using the above object, consumers have granular control over certain options when graphing additional modules. If `dev` or `conditions` are provided ("not `undefined`") as options to `mapNodeModules()` directly, the additional modules will _inherit_ those settings (there is coverage for this). The second option, `additionalPackageDetails`, is a superset of `AdditionalModuleLocationObject` which also contains a package descriptor and package location. This array can then be handed to `captureFromMap()` or `loadFromMap()` after it has been populated. Because the `CompartmentMapDescriptor` can only have a single `CompartmentDescriptor` with an empty `path` (the entry), the `path` of each additional package is its name. Related `CompartmentDescriptors` In `mapNodeModules()`, `additionalModuleLocations` causes `graphPackages()` to be called against each location while reusing the `Graph`. The internal API has been changed somewhat to allow this. Because we do not know whether or we will be executing using the resulting `CompartmentMapDescriptor` (e.g. we can provide it to `captureFromMap()` which will not invoke dynamic requires), `mapNodeModules()` sets the `CompartmentDescriptor.retained` flag. When it does, it also sets the `retained` flag for each `ModuleDescriptor` found within `CompartmentDescriptor.modules`. It does this as the _last_ step before returning the `CompartmentMapDescriptor` and only does so on `CompartmentDescriptor`s not already found prior to graphing additional modules. This is a recursive operation which bails out of if either a) the `CompartmentDescriptor` was graphed by way of the entry module, or b) we've seen the `CompartmentDescriptor` before (to protect against cycles). The supporting implementation in other modules is minimal excepting a change to `loadFromMap` where all additional packages are eagerly loaded via `compartment.load(additionalPackageLocation)`. Tests are concentrated in a new test suite (`additional-modules.test.js`) which covers the aforementioned APIs using the same comprehensive fixture. Also: - Fixed signature of `chooseModuleDescriptor` - Fixed deprecation notice for `compartmentMapForNodeModules`; it was previously on the options object, but it should only be on the export. - Passed `log` option thru `importLocations` - Added explicit type for `LoadFromMapOptions` (it was previously using `ImportLocationOptions`, but it is now slightly different) - `makeModuleMapHook()` now accepts a `Record<string, CompartmentDescriptor>` parameter (ostensibly from `CompartmentMapDescriptor.compartments`) for the purpose of error reporting, which can now reference canonical names / paths
fbf783c
to
0c3e081
Compare
0728447
to
18b0de6
Compare
ff67972
to
9f7d098
Compare
I'm going to close this in lieu of a forthcoming PR. |
Description
This adds options to
mapNodeModules()
,captureFromMap()
,loadFromMap()
andimportLocation()
to support loading additional packages that wouldn't otherwise be discovered bymapNodeModules()
. It takes several ideas from #2864 which were not already implemented in #2876.The first option is
additionalModuleLocations
, which is anArray<string | AdditionalModuleLocationObject>
where items can either be fully-qualifiedfile://
URLs or an object with shape:Using the above object, consumers have granular control over certain options when graphing additional modules. If
dev
orconditions
are provided ("notundefined
") as options tomapNodeModules()
directly, the additional modules will inherit those settings (there is coverage for this).The second option,
additionalPackageDetails
, is a superset ofAdditionalModuleLocationObject
which also contains a package descriptor and package location. This array can then be handed tocaptureFromMap()
orloadFromMap()
after it has been populated.In
mapNodeModules()
,additionalModuleLocations
causesgraphPackages()
to be called against each location while reusing theGraph
. The internal API has been changed somewhat to allow this. Because we do not know whether or we will be executing using the resultingCompartmentMapDescriptor
(e.g. we can provide it tocaptureFromMap()
which will not invoke dynamic requires),mapNodeModules()
sets theCompartmentDescriptor.retained
flag. When it does, it also sets theretained
flag for eachModuleDescriptor
found withinCompartmentDescriptor.modules
. It does this as the last step before returning theCompartmentMapDescriptor
and only does so onCompartmentDescriptor
s not already found prior to graphing additional modules. This is a recursive operation which bails out of if either a) theCompartmentDescriptor
was graphed by way of the entry module, or b) we've seen theCompartmentDescriptor
before (to protect against cycles).The supporting implementation in other modules is minimal excepting a change to
loadFromMap
where all additional packages are eagerly loaded viacompartment.load(additionalPackageLocation)
.Tests are concentrated in a new test suite (
additional-modules.test.js
) which covers the aforementioned APIs using the same comprehensive fixture.Also:
chooseModuleDescriptor
compartmentMapForNodeModules
; it was previously on the options object, but it should only be on the export.Questions for Reviewers
Naming is hard and I was not sure what to name the new options. The
AdditionalPackageDetails
type looks like the existingPackageDetails
type (with a new field) thus influencing the naming—but they are otherwise unrelated and because of this I did not extendPackageDetails
.I am monkeying withI am not doing this anymore. Do not worry about it.imports
in aRecordModuleDescriptor
object in order to direct SES' module loading. Is there a better way? The things I stuff inimports
are also extremely dubious.Because additional packages are crawled similarly to the entry package and we do not tell Endo what package should be expected to import the additional package, the
CompartmentDescriptor.path
for this package would naturally be[]
w/o any other intervention.Since that seems obviously wrong to me—because only the entry
CompartmentDescriptor
can have an emptypath
—it is now always set to[CompartmentDescriptor.name]
.IMO this is less wrong; the additional package must be reachable (at runtime) via the entry package (otherwise why would we specify it as an additional package?), but the additional package is not necessarily directly reachable (i.e. not a direct dependency) from the entry package.
This has an impact on shortest path calculation, but I feel like a stable value for
CompartmentDescriptor.path
is more important than a logically-consistent one.Given the previous point, consider: the only way we would know which package directly (dynamically) imports this "additional" package is if the user explicitly told us which. One way to do this is via package policy, but we do not currently consider policy to be an indicator of a dependency relationship. Further, policy is not required to leverage additional packages.
If we cannot use policy (though it would seem handy!), we would need to do something like change the shape of
AdditionalModuleLocationObject
(see above) to allow a new field which would presumably be the package location of whatever directly imports the module provided inAdditionalModuleLocationObject.location
. If we had that information, we could use it to build logically consistent paths.@naugtur's implementation was using a special prefix in the canonical name for these "additional packages". I don't know what purpose this serves (other than he mentioned something about LavaMoat having some historical support for it).
Security Considerations
n/a
These changes do not meddle with systems loading untrusted code, afaik.
Scaling Considerations
Use of the new option is not free.
Documentation Considerations
n/a
Testing Considerations
n/a
Compatibility Considerations
No.
Upgrade Considerations
This warrants an entry in
NEWS.md
since it is part of a public API.