Skip to content

Conversation

ExE-Boss
Copy link
Contributor

@ExE-Boss ExE-Boss commented Feb 6, 2021

Fixes: #36098 (applies only to commit 1d8c8b7)
Refs: #37178 (applies only to commit b0fbc91)

@ExE-Boss ExE-Boss force-pushed the lib/module/require-node-url branch from 8615225 to 4f61960 Compare February 6, 2021 11:21
@aduh95
Copy link
Contributor

aduh95 commented Feb 6, 2021

You read my mind, I was precisely working on this. I noticed it produces an arguably better error when trying to load internal modules:

$ node -p "require('internal/test/binding')"
Error: Cannot find module 'internal/test/binding'
$ node -p "require('node:internal/test/binding')"
Error: Should not compile internal/test/binding for public use

Can you add tests in test/parallel/test-require-node-prefix.js to make sure requiring internal modules still throws please?

@ExE-Boss ExE-Boss force-pushed the lib/module/require-node-url branch from e740b61 to 320ef5e Compare February 6, 2021 13:57
@benjamingr
Copy link
Member

cc @nodejs/modules

);
}

// `node:`-prefixed `require(...)` calls bypass the require cache:
Copy link
Member

Choose a reason for hiding this comment

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

this differs from other builtins, I love it, but want to be sure it is intentional.

Copy link
Member

Choose a reason for hiding this comment

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

this actually seems like an issue - it means someone who replaces a builtin intentionally can’t replace one of these (altho presumably mutating a builtin would be visible in both). That could cause an issue where someone wants to use a package to instrument or lock down fs (and thus, policies are not an ergonomic option, unless I’m misunderstanding how policies work), but accidentally leaves a gaping security hole when an attacker requires node:fs.

Copy link
Member

Choose a reason for hiding this comment

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

policies should intercept before any of the resolution helpers even get called

, if any module redirection is in place (including complete shutdown) then it would never call out to module.require and get here without some kind of opt-in to the behavior (like dependencies:true).

Copy link
Member

Choose a reason for hiding this comment

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

At the very least this needs documentation and probably a strong indication people using policies + instrumenting fs now need to update the policy.

Copy link
Member

Choose a reason for hiding this comment

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

My point was that policies aren’t currently required to ensure this for CJS - a package (not the app itself) can do it. Forcing policies to be the mechanism means packages are no longer capable of abstracting this for users.

Copy link
Member

Choose a reason for hiding this comment

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

Totally understood. Given that policies are the superior mechanism for app developers to lock things down, what's the benefit of adding special, inconsistent, cache-bypassing behavior for require with a node: prefix?

Copy link
Member

Choose a reason for hiding this comment

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

@ljharb i have no strong opinion on if we should diverge, but cache first behavior of CJS isn't shared by ESM and isn't robust against things like core destructuring the original impls. My like is just that it isn't a confusing situation like with fs/.

Copy link
Contributor Author

@ExE-Boss ExE-Boss Feb 8, 2021

Choose a reason for hiding this comment

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

but cache first behavior of CJS isn't shared by ESM and isn't robust against things like core destructuring the original impls.

This is demonstrated by:

const fs = require('fs');

const fakeModule = {};
require.cache.fs = { exports: fakeModule };

assert.strictEqual((await import('fs')).default, fs);

Copy link
Member

Choose a reason for hiding this comment

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

indeed, but that doesn’t mean it’s a good idea to make CJS inconsistent with itself.

Copy link
Member

Choose a reason for hiding this comment

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

I don't think CJS is able to be claimed as consistent with itself since in general the natives don't normally populate the cache. I think it was just ad-hoc written together and the cache behavior is evolutionary not intentional or well understood (even by me).

@jasnell jasnell added the module Issues and PRs related to the module subsystem. label Feb 6, 2021
@ExE-Boss ExE-Boss force-pushed the lib/module/require-node-url branch from 3634cd4 to d196d45 Compare February 6, 2021 19:37
@@ -837,7 +850,8 @@ Module._load = function(request, parent, isMain) {
};

Module._resolveFilename = function(request, parent, isMain, options) {
if (NativeModule.canBeRequiredByUsers(request)) {
if (StringPrototypeStartsWith(request, 'node:') ||
NativeModule.canBeRequiredByUsers(request)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This bifurcation will have implications for mocking, but I can appreciate the benefit too.

I'd still prefer a more convergent path here eg, to return node:fs for both node:fs and fs inputs, and then to still populate a cache['fs'] entry in the node:fs case as the same object for backwards compatibility. Such a change would hopefully be mostly compatible but would probably need to be a separate major nontheless. Worth thinking about at least.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Existing mocking tools (such as Jest) provide their own require(…) implementation, since they need to be able to bypass the mocked module using jest.requireActual(…).

@ExE-Boss ExE-Boss force-pushed the lib/module/require-node-url branch from 10b08bf to 8564814 Compare February 8, 2021 13:11
@ExE-Boss ExE-Boss requested a review from aduh95 February 8, 2021 13:12
Comment on lines +770 to +784
if (StringPrototypeStartsWith(filename, 'node:')) {
// Slice 'node:' prefix
const id = StringPrototypeSlice(filename, 5);

const module = loadNativeModule(id, request);
if (!module?.canBeRequiredByUsers) {
throw new ERR_UNKNOWN_BUILTIN_MODULE(filename);
}

return module.exports;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the motivation for short-circuiting the cache here, something that has never been done previously for native modules?

I don't see why this scheme should be any different to any other in this regard.

Copy link
Member

Choose a reason for hiding this comment

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

On this topic, should we throw or defer to cache on unknown builtins?

Copy link
Contributor

@aduh95 aduh95 left a comment

Choose a reason for hiding this comment

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

LGTM

Copy link
Member

@benjamingr benjamingr left a comment

Choose a reason for hiding this comment

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

Just making sure #37246 (comment) is addressed before landing :) (namely the caveat) - I'm appreciative of the work put in, thanks!

Also would like more @nodejs/modules and @nodejs/tsc eyes on this.

(The actual changes LGTM)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
module Issues and PRs related to the module subsystem. notable-change PRs with changes that should be highlighted in changelogs. review wanted PRs that need reviews. semver-minor PRs that contain new features and should be released in the next minor version.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add support for node:‑prefixed imports of built‑in modules to require(…)