lib,src: drop --experimental-network-imports

PR-URL: https://github.com/nodejs/node/pull/53822
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com>
Reviewed-By: Marco Ippolito <marcoippolito54@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
This commit is contained in:
Rafael Gonzaga 2024-07-28 13:33:05 -03:00 committed by GitHub
parent e4263dad96
commit 5ac969fdca
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 10 additions and 622 deletions

View File

@ -965,18 +965,6 @@ changes:
Specify the `module` containing exported [module customization hooks][].
`module` may be any string accepted as an [`import` specifier][].
### `--experimental-network-imports`
<!-- YAML
added:
- v17.6.0
- v16.15.0
-->
> Stability: 1 - Experimental
Enable experimental support for the `https:` protocol in `import` specifiers.
### `--experimental-network-inspection`
<!-- YAML
@ -2912,7 +2900,6 @@ one is included in the list below.
* `--experimental-json-modules`
* `--experimental-loader`
* `--experimental-modules`
* `--experimental-network-imports`
* `--experimental-permission`
* `--experimental-print-required-tla`
* `--experimental-require-module`

View File

@ -3583,23 +3583,6 @@ removed: v10.0.0
Used by the `Node-API` when `Constructor.prototype` is not an object.
<a id="ERR_NETWORK_IMPORT_BAD_RESPONSE"></a>
### `ERR_NETWORK_IMPORT_BAD_RESPONSE`
> Stability: 1 - Experimental
Response was received but was invalid when importing a module over the network.
<a id="ERR_NETWORK_IMPORT_DISALLOWED"></a>
### `ERR_NETWORK_IMPORT_DISALLOWED`
> Stability: 1 - Experimental
A network module attempted to load another module that it is not allowed to
load. Likely this restriction is for security reasons.
<a id="ERR_NO_LONGER_SUPPORTED"></a>
### `ERR_NO_LONGER_SUPPORTED`

View File

@ -697,71 +697,6 @@ spawn(execPath, [
});
```
## HTTPS and HTTP imports
> Stability: 1 - Experimental
Importing network based modules using `https:` and `http:` is supported under
the `--experimental-network-imports` flag. This allows web browser-like imports
to work in Node.js with a few differences due to application stability and
security concerns that are different when running in a privileged environment
instead of a browser sandbox.
### Imports are limited to HTTP/1
Automatic protocol negotiation for HTTP/2 and HTTP/3 is not yet supported.
### HTTP is limited to loopback addresses
`http:` is vulnerable to man-in-the-middle attacks and is not allowed to be
used for addresses outside of the IPv4 address `127.0.0.0/8` (`127.0.0.1` to
`127.255.255.255`) and the IPv6 address `::1`. Support for `http:` is intended
to be used for local development.
### Authentication is never sent to the destination server.
`Authorization`, `Cookie`, and `Proxy-Authorization` headers are not sent to the
server. Avoid including user info in parts of imported URLs. A security model
for safely using these on the server is being worked on.
### CORS is never checked on the destination server
CORS is designed to allow a server to limit the consumers of an API to a
specific set of hosts. This is not supported as it does not make sense for a
server-based implementation.
### Cannot load non-network dependencies
These modules cannot access other modules that are not over `http:` or `https:`.
To still access local modules while avoiding the security concern, pass in
references to the local dependencies:
```mjs
// file.mjs
import worker_threads from 'node:worker_threads';
import { configure, resize } from 'https://example.com/imagelib.mjs';
configure({ worker_threads });
```
```mjs
// https://example.com/imagelib.mjs
let worker_threads;
export function configure(opts) {
worker_threads = opts.worker_threads;
}
export function resize(img, size) {
// Perform resizing in worker_thread to avoid main thread blocking
}
```
### Network-based loading is not enabled by default
For now, the `--experimental-network-imports` flag is required to enable loading
resources over `http:` or `https:`. In the future, a different mechanism will be
used to enforce this. Opt-in is required to prevent transitive dependencies
inadvertently using potentially mutable state that could affect reliability
of Node.js applications.
<i id="esm_experimental_loaders"></i>
## Loaders
@ -804,8 +739,7 @@ does not determine whether the resolved URL protocol can be loaded,
or whether the file extensions are permitted, instead these validations
are applied by Node.js during the load phase
(for example, if it was asked to load a URL that has a protocol that is
not `file:`, `data:`, `node:`, or if `--experimental-network-imports`
is enabled, `https:`).
not `file:`, `data:` or `node:`.
The algorithm also tries to determine the format of the file based
on the extension (see `ESM_FILE_FORMAT` algorithm below). If it does

View File

@ -717,7 +717,7 @@ behaviors.
#### Import from HTTPS
In current Node.js, specifiers starting with `https://` are experimental (see
[HTTPS and HTTP imports][]).
\[HTTPS and HTTP imports]\[]).
The hook below registers hooks to enable rudimentary support for such
specifiers. While this may seem like a significant improvement to Node.js core
@ -1054,7 +1054,6 @@ returned object contains the following keys:
[Conditional exports]: packages.md#conditional-exports
[Customization hooks]: #customization-hooks
[ES Modules]: esm.md
[HTTPS and HTTP imports]: esm.md#https-and-http-imports
[Source map v3 format]: https://sourcemaps.info/spec.html#h.mofvlxcwqzej
[`"exports"`]: packages.md#exports
[`--enable-source-maps`]: cli.md#--enable-source-maps

View File

@ -173,9 +173,6 @@ Specify the
.Ar module
to use as a custom module loader.
.
.It Fl -experimental-network-imports
Enable experimental support for loading modules using `import` over `https:`.
.
.It Fl -experimental-permission
Enable the experimental permission model.
.

View File

@ -1591,10 +1591,6 @@ E('ERR_NAPI_INVALID_TYPEDARRAY_ALIGNMENT',
'start offset of %s should be a multiple of %s', RangeError);
E('ERR_NAPI_INVALID_TYPEDARRAY_LENGTH',
'Invalid typed array length', RangeError);
E('ERR_NETWORK_IMPORT_BAD_RESPONSE',
"import '%s' received a bad response: %s", Error);
E('ERR_NETWORK_IMPORT_DISALLOWED',
"import of '%s' by %s is not supported: %s", Error);
E('ERR_NOT_BUILDING_SNAPSHOT',
'Operation cannot be invoked when not building startup snapshot', Error);
E('ERR_NOT_IN_SINGLE_EXECUTABLE_APPLICATION',

View File

@ -2,8 +2,6 @@
const {
ObjectPrototypeHasOwnProperty,
PromisePrototypeThen,
PromiseResolve,
RegExpPrototypeExec,
SafeSet,
StringPrototypeCharCodeAt,
@ -18,8 +16,6 @@ const {
} = require('internal/modules/esm/formats');
const detectModule = getOptionValue('--experimental-detect-module');
const experimentalNetworkImports =
getOptionValue('--experimental-network-imports');
const { containsModuleSyntax } = internalBinding('contextify');
const { getPackageScopeConfig, getPackageType } = require('internal/modules/package_json_reader');
const { fileURLToPath } = require('internal/url');
@ -29,8 +25,6 @@ const protocolHandlers = {
'__proto__': null,
'data:': getDataProtocolModuleFormat,
'file:': getFileProtocolModuleFormat,
'http:': getHttpProtocolModuleFormat,
'https:': getHttpProtocolModuleFormat,
'node:'() { return 'builtin'; },
};
@ -219,23 +213,6 @@ function getFileProtocolModuleFormat(url, context = { __proto__: null }, ignoreE
throw new ERR_UNKNOWN_FILE_EXTENSION(ext, filepath);
}
/**
* @param {URL} url
* @param {{parentURL: string}} context
* @returns {Promise<string> | undefined} only works when enabled
*/
function getHttpProtocolModuleFormat(url, context) {
if (experimentalNetworkImports) {
const { fetchModule } = require('internal/modules/esm/fetch_module');
return PromisePrototypeThen(
PromiseResolve(fetchModule(url, context)),
(entry) => {
return mimeToFormat(entry.headers['content-type']);
},
);
}
}
/**
* @param {URL} url
* @param {{parentURL: string}} context

View File

@ -1,7 +1,6 @@
'use strict';
const {
ArrayPrototypePush,
RegExpPrototypeExec,
decodeURIComponent,
} = primordials;
@ -12,8 +11,6 @@ const { validateAttributes, emitImportAssertionWarning } = require('internal/mod
const { getOptionValue } = require('internal/options');
const { readFileSync } = require('fs');
const experimentalNetworkImports =
getOptionValue('--experimental-network-imports');
const defaultType =
getOptionValue('--experimental-default-type');
@ -39,7 +36,7 @@ const DATA_URL_PATTERN = /^[^/]+\/[^,;]+(?:[^,]*?)(;base64)?,([\s\S]*)$/;
*/
async function getSource(url, context) {
const { protocol, href } = url;
let responseURL = href;
const responseURL = href;
let source;
if (protocol === 'file:') {
const { readFile: readFileAsync } = require('internal/fs/promises').exports;
@ -51,19 +48,8 @@ async function getSource(url, context) {
}
const { 1: base64, 2: body } = match;
source = BufferFrom(decodeURIComponent(body), base64 ? 'base64' : 'utf8');
} else if (experimentalNetworkImports && (
protocol === 'https:' ||
protocol === 'http:'
)) {
const { fetchModule } = require('internal/modules/esm/fetch_module');
const res = await fetchModule(url, context);
source = await res.body;
responseURL = res.resolvedHREF;
} else {
const supportedSchemes = ['file', 'data'];
if (experimentalNetworkImports) {
ArrayPrototypePush(supportedSchemes, 'http', 'https');
}
throw new ERR_UNSUPPORTED_ESM_URL_SCHEME(url, supportedSchemes);
}
return { __proto__: null, responseURL, source };
@ -121,7 +107,7 @@ async function defaultLoad(url, context = kEmptyObject) {
const urlInstance = new URL(url);
throwIfUnsupportedURLScheme(urlInstance, experimentalNetworkImports);
throwIfUnsupportedURLScheme(urlInstance);
if (urlInstance.protocol === 'node:') {
source = null;
@ -224,9 +210,8 @@ function defaultLoadSync(url, context = kEmptyObject) {
* throws an error if the protocol is not one of the protocols
* that can be loaded in the default loader
* @param {URL} parsed
* @param {boolean} experimentalNetworkImports
*/
function throwIfUnsupportedURLScheme(parsed, experimentalNetworkImports) {
function throwIfUnsupportedURLScheme(parsed) {
// Avoid accessing the `protocol` property due to the lazy getters.
const protocol = parsed?.protocol;
if (
@ -235,17 +220,11 @@ function throwIfUnsupportedURLScheme(parsed, experimentalNetworkImports) {
protocol !== 'data:' &&
protocol !== 'node:' &&
(
!experimentalNetworkImports ||
(
protocol !== 'https:' &&
protocol !== 'http:'
)
protocol !== 'https:' &&
protocol !== 'http:'
)
) {
const schemes = ['file', 'data', 'node'];
if (experimentalNetworkImports) {
ArrayPrototypePush(schemes, 'https', 'http');
}
throw new ERR_UNSUPPORTED_ESM_URL_SCHEME(parsed, schemes);
}
}

View File

@ -30,7 +30,7 @@ const {
} = require('internal/errors').codes;
const { getOptionValue } = require('internal/options');
const { isURL, pathToFileURL, URLParse } = require('internal/url');
const { emitExperimentalWarning, kEmptyObject } = require('internal/util');
const { kEmptyObject } = require('internal/util');
const {
compileSourceTextModule,
getDefaultConditions,
@ -145,9 +145,6 @@ class ModuleLoader {
#customizations;
constructor(customizations) {
if (getOptionValue('--experimental-network-imports')) {
emitExperimentalWarning('Network Imports');
}
this.setCustomizations(customizations);
}

View File

@ -30,8 +30,6 @@ const { getOptionValue } = require('internal/options');
const { sep, posix: { relative: relativePosixPath }, resolve } = require('path');
const preserveSymlinks = getOptionValue('--preserve-symlinks');
const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main');
const experimentalNetworkImports =
getOptionValue('--experimental-network-imports');
const inputTypeFlag = getOptionValue('--input-type');
const { URL, pathToFileURL, fileURLToPath, isURL, URLParse } = require('internal/url');
const { getCWDURL, setOwnProperty } = require('internal/util');
@ -48,7 +46,6 @@ const {
ERR_PACKAGE_PATH_NOT_EXPORTED,
ERR_UNSUPPORTED_DIR_IMPORT,
ERR_UNSUPPORTED_RESOLVE_REQUEST,
ERR_NETWORK_IMPORT_DISALLOWED,
} = require('internal/errors').codes;
const { Module: CJSModule } = require('internal/modules/cjs/loader');
@ -886,10 +883,6 @@ function moduleResolve(specifier, base, conditions, preserveSymlinks) {
StringPrototypeSlice(base, 0, StringPrototypeIndexOf(base, ':') + 1) :
base.protocol;
const isData = protocol === 'data:';
const isRemote =
isData ||
protocol === 'http:' ||
protocol === 'https:';
// Order swapped from spec for minor perf gain.
// Ok since relative URLs cannot parse as URLs.
let resolved;
@ -907,7 +900,7 @@ function moduleResolve(specifier, base, conditions, preserveSymlinks) {
try {
resolved = new URL(specifier);
} catch (cause) {
if (isRemote && !BuiltinModule.canBeRequiredWithoutScheme(specifier)) {
if (isData && !BuiltinModule.canBeRequiredWithoutScheme(specifier)) {
const error = new ERR_UNSUPPORTED_RESOLVE_REQUEST(specifier, base);
setOwnProperty(error, 'cause', cause);
throw error;
@ -976,57 +969,6 @@ function resolveAsCommonJS(specifier, parentURL) {
}
}
/**
* Throw an error if an import is not allowed.
* TODO(@JakobJingleheimer): de-dupe `specifier` & `parsed`
* @param {string} specifier - The import specifier.
* @param {URL} parsed - The parsed URL of the import specifier.
* @param {URL} parsedParentURL - The parsed URL of the parent module.
* @throws {ERR_NETWORK_IMPORT_DISALLOWED} - If the import is disallowed.
*/
function checkIfDisallowedImport(specifier, parsed, parsedParentURL) {
if (parsedParentURL) {
// Avoid accessing the `protocol` property due to the lazy getters.
const parentProtocol = parsedParentURL.protocol;
if (
parentProtocol === 'http:' ||
parentProtocol === 'https:'
) {
if (shouldBeTreatedAsRelativeOrAbsolutePath(specifier)) {
// Avoid accessing the `protocol` property due to the lazy getters.
const parsedProtocol = parsed?.protocol;
// data: and blob: disallowed due to allowing file: access via
// indirection
if (parsedProtocol &&
parsedProtocol !== 'https:' &&
parsedProtocol !== 'http:'
) {
throw new ERR_NETWORK_IMPORT_DISALLOWED(
specifier,
parsedParentURL,
'remote imports cannot import from a local location.',
);
}
return { url: parsed.href };
}
if (BuiltinModule.canBeRequiredWithoutScheme(specifier)) {
throw new ERR_NETWORK_IMPORT_DISALLOWED(
specifier,
parsedParentURL,
'remote imports cannot import from a local location.',
);
}
throw new ERR_NETWORK_IMPORT_DISALLOWED(
specifier,
parsedParentURL,
'only relative and absolute specifiers are supported.',
);
}
}
}
/**
* Validate user-input in `context` supplied by a custom loader.
* @param {string | URL | undefined} parentURL - The parent URL.
@ -1068,36 +1010,10 @@ function defaultResolve(specifier, context = {}) {
// Avoid accessing the `protocol` property due to the lazy getters.
protocol = parsed.protocol;
if (protocol === 'data:' &&
parsedParentURL.protocol !== 'file:' &&
experimentalNetworkImports) {
throw new ERR_NETWORK_IMPORT_DISALLOWED(
specifier,
parsedParentURL,
'import data: from a non file: is not allowed',
);
}
if (protocol === 'data:' ||
(experimentalNetworkImports &&
(
protocol === 'https:' ||
protocol === 'http:'
)
)
) {
if (protocol === 'data:') {
return { __proto__: null, url: parsed.href };
}
}
// There are multiple deep branches that can either throw or return; instead
// of duplicating that deeply nested logic for the possible returns, DRY and
// check for a return. This seems the least gnarly.
const maybeReturn = checkIfDisallowedImport(
specifier,
parsed,
parsedParentURL,
);
if (maybeReturn) { return maybeReturn; }
// This must come after checkIfDisallowedImport
protocol ??= parsed?.protocol;

View File

@ -436,10 +436,6 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
kAllowedInEnvvar);
AddAlias("--loader", "--experimental-loader");
AddOption("--experimental-modules", "", NoOp{}, kAllowedInEnvvar);
AddOption("--experimental-network-imports",
"experimental https: support for the ES Module loader",
&EnvironmentOptions::experimental_https_modules,
kAllowedInEnvvar);
AddOption("--experimental-wasm-modules",
"experimental ES Module support for webassembly modules",
&EnvironmentOptions::experimental_wasm_modules,

View File

@ -123,7 +123,6 @@ class EnvironmentOptions : public Options {
std::string localstorage_file;
bool experimental_global_navigator = true;
bool experimental_global_web_crypto = true;
bool experimental_https_modules = false;
bool experimental_wasm_modules = false;
bool experimental_import_meta_resolve = false;
std::string input_type; // Value of --input-type

View File

@ -30,7 +30,6 @@ describe('ESM: warn for obsolete hooks provided', { concurrency: !process.env.TE
'--experimental-loader',
fileURL('es-module-loaders', 'hooks-custom.mjs'),
],
[/Network Imports/, '--experimental-network-imports'],
]
) {
it(`should print for ${experiment.toString().replaceAll('/', '')}`, async () => {

View File

@ -1,48 +0,0 @@
import { mustCall, spawnPromisified } from '../common/index.mjs';
import { ok, match, notStrictEqual } from 'node:assert';
import { spawn as spawnAsync } from 'node:child_process';
import { execPath } from 'node:process';
import { describe, it } from 'node:test';
describe('ESM: http import via CLI', { concurrency: !process.env.TEST_PARALLEL }, () => {
const disallowedSpecifier = 'http://example.com';
it('should throw disallowed error for insecure protocol', async () => {
const { code, stderr } = await spawnPromisified(execPath, [
'--experimental-network-imports',
'--input-type=module',
'--eval',
`import ${JSON.stringify(disallowedSpecifier)}`,
]);
notStrictEqual(code, 0);
// [ERR_NETWORK_IMPORT_DISALLOWED]: import of 'http://example.com/' by
// …/[eval1] is not supported: http can only be used to load local
// resources (use https instead).
match(stderr, /ERR_NETWORK_IMPORT_DISALLOWED/);
ok(stderr.includes(disallowedSpecifier));
});
it('should throw disallowed error for insecure protocol in REPL', () => {
const child = spawnAsync(execPath, [
'--experimental-network-imports',
'--input-type=module',
]);
child.stdin.end(`import ${JSON.stringify(disallowedSpecifier)}`);
let stderr = '';
child.stderr.setEncoding('utf8');
child.stderr.on('data', (data) => stderr += data);
child.on('close', mustCall((code, _signal) => {
notStrictEqual(code, 0);
// [ERR_NETWORK_IMPORT_DISALLOWED]: import of 'http://example.com/' by
// …/[stdin] is not supported: http can only be used to load local
// resources (use https instead).
match(stderr, /\[ERR_NETWORK_IMPORT_DISALLOWED\]/);
ok(stderr.includes(disallowedSpecifier));
}));
});
});

View File

@ -1,311 +0,0 @@
// Flags: --experimental-network-imports --dns-result-order=ipv4first
import * as common from '../common/index.mjs';
import * as fixtures from '../common/fixtures.mjs';
import tmpdir from '../common/tmpdir.js';
import assert from 'assert';
import http from 'http';
import os from 'os';
import util from 'util';
import { describe, it } from 'node:test';
if (!common.hasCrypto) {
common.skip('missing crypto');
}
tmpdir.refresh();
const https = (await import('https')).default;
const createHTTPServer = http.createServer;
// Needed to deal w/ test certs
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
const options = {
key: fixtures.readKey('agent1-key.pem'),
cert: fixtures.readKey('agent1-cert.pem')
};
const createHTTPSServer = https.createServer.bind(null, options);
const testListeningOptions = [
{
hostname: 'localhost',
listenOptions: {
host: '127.0.0.1'
}
},
];
const internalInterfaces = Object.values(os.networkInterfaces()).flat().filter(
(iface) => iface?.internal && iface.address && !iface.scopeid
);
for (const iface of internalInterfaces) {
testListeningOptions.push({
hostname: iface?.family === 'IPv6' ? `[${iface?.address}]` : iface?.address,
listenOptions: {
host: iface?.address,
ipv6Only: iface?.family === 'IPv6'
}
});
}
for (const { protocol, createServer } of [
{ protocol: 'http:', createServer: createHTTPServer },
{ protocol: 'https:', createServer: createHTTPSServer },
]) {
const body = `
export default (a) => () => a;
export let url = import.meta.url;
`;
const base = 'http://127.0.0.1';
for (const { hostname, listenOptions } of testListeningOptions) {
const host = new URL(base);
host.protocol = protocol;
host.hostname = hostname;
// /not-found is a 404
// ?redirect causes a redirect, no body. JSON.parse({status:number,location:string})
// ?mime sets the content-type, string
// ?body sets the body, string
const server = createServer(function(_req, res) {
const url = new URL(_req.url, host);
const redirect = url.searchParams.get('redirect');
if (url.pathname === 'json') {
common.mustCall(() => assert.strictEqual(_req.header.content, 'application/json,*/*;charset=utf-8;q=0.5'));
}
if (url.pathname === '/not-found') {
res.writeHead(404);
res.end();
return;
}
if (redirect) {
const { status, location } = JSON.parse(redirect);
res.writeHead(status, {
location
});
res.end();
return;
}
res.writeHead(200, {
'content-type': url.searchParams.get('mime') || 'text/javascript'
});
res.end(url.searchParams.get('body') || body);
});
const listen = util.promisify(server.listen.bind(server));
await listen({
...listenOptions,
port: 0
});
const url = new URL(host);
url.port = server?.address()?.port;
const ns = await import(url.href);
assert.strict.deepStrictEqual(Object.keys(ns), ['default', 'url']);
const obj = {};
assert.strict.equal(ns.default(obj)(), obj);
assert.strict.equal(ns.url, url.href);
// Redirects have same import.meta.url but different cache
// entry on Web
const redirect = new URL(url.href);
redirect.searchParams.set('redirect', JSON.stringify({
status: 302,
location: url.href
}));
const redirectedNS = await import(redirect.href);
assert.strict.deepStrictEqual(
Object.keys(redirectedNS),
['default', 'url']
);
assert.strict.notEqual(redirectedNS.default, ns.default);
assert.strict.equal(redirectedNS.url, url.href);
// Redirects have the same import.meta.url but different cache
// entry on Web
const relativeAfterRedirect = new URL(url.href + 'foo/index.js');
const redirected = new URL(url.href + 'bar/index.js');
redirected.searchParams.set('body', 'export let relativeDepURL = (await import("./baz.js")).url');
relativeAfterRedirect.searchParams.set('redirect', JSON.stringify({
status: 302,
location: redirected.href
}));
const relativeAfterRedirectedNS = await import(relativeAfterRedirect.href);
assert.strict.equal(
relativeAfterRedirectedNS.relativeDepURL,
url.href + 'bar/baz.js'
);
const unsupportedMIME = new URL(url.href);
unsupportedMIME.searchParams.set('mime', 'application/node');
unsupportedMIME.searchParams.set('body', '');
await assert.rejects(
import(unsupportedMIME.href),
{ code: 'ERR_UNKNOWN_MODULE_FORMAT' }
);
const notFound = new URL(url.href);
notFound.pathname = '/not-found';
await assert.rejects(
import(notFound.href),
{ code: 'ERR_MODULE_NOT_FOUND' },
);
const jsonUrl = new URL(url.href + 'json');
jsonUrl.searchParams.set('mime', 'application/json');
jsonUrl.searchParams.set('body', '{"x": 1}');
const json = await import(jsonUrl.href, { with: { type: 'json' } });
assert.deepStrictEqual(Object.keys(json), ['default']);
assert.strictEqual(json.default.x, 1);
await describe('guarantee data url will not bypass import restriction', () => {
it('should not be bypassed by cross protocol redirect', async () => {
const crossProtocolRedirect = new URL(url.href);
crossProtocolRedirect.searchParams.set('redirect', JSON.stringify({
status: 302,
location: 'data:text/javascript,'
}));
await assert.rejects(
import(crossProtocolRedirect.href),
{ code: 'ERR_NETWORK_IMPORT_DISALLOWED' }
);
});
it('should not be bypassed by data URL', async () => {
const deps = new URL(url.href);
deps.searchParams.set('body', `
export {data} from 'data:text/javascript,export let data = 1';
import * as http from ${JSON.stringify(url.href)};
export {http};
`);
await assert.rejects(
import(deps.href),
{ code: 'ERR_NETWORK_IMPORT_DISALLOWED' }
);
});
it('should not be bypassed by encodedURI import', async () => {
const deepDataImport = new URL(url.href);
deepDataImport.searchParams.set('body', `
import 'data:text/javascript,import${encodeURIComponent(JSON.stringify('data:text/javascript,import "os"'))}';
`);
await assert.rejects(
import(deepDataImport.href),
{ code: 'ERR_NETWORK_IMPORT_DISALLOWED' }
);
});
it('should not be bypassed by relative deps import', async () => {
const relativeDeps = new URL(url.href);
relativeDeps.searchParams.set('body', `
import * as http from "./";
export {http};
`);
const relativeDepsNS = await import(relativeDeps.href);
assert.strict.deepStrictEqual(Object.keys(relativeDepsNS), ['http']);
assert.strict.equal(relativeDepsNS.http, ns);
});
it('should not be bypassed by file dependency import', async () => {
const fileDep = new URL(url.href);
const { href } = fixtures.fileURL('/es-modules/message.mjs');
fileDep.searchParams.set('body', `
import ${JSON.stringify(href)};
export default 1;`);
await assert.rejects(
import(fileDep.href),
{ code: 'ERR_NETWORK_IMPORT_DISALLOWED' }
);
});
it('should not be bypassed by builtin dependency import', async () => {
const builtinDep = new URL(url.href);
builtinDep.searchParams.set('body', `
import 'node:fs';
export default 1;
`);
await assert.rejects(
import(builtinDep.href),
{ code: 'ERR_NETWORK_IMPORT_DISALLOWED' }
);
});
it('should not be bypassed by unprefixed builtin dependency import', async () => {
const unprefixedBuiltinDep = new URL(url.href);
unprefixedBuiltinDep.searchParams.set('body', `
import 'fs';
export default 1;
`);
await assert.rejects(
import(unprefixedBuiltinDep.href),
{ code: 'ERR_NETWORK_IMPORT_DISALLOWED' }
);
});
it('should not be bypassed by indirect network import', async () => {
const indirect = new URL(url.href);
indirect.searchParams.set('body', `
import childProcess from 'data:text/javascript,export { default } from "node:child_process"'
export {childProcess};
`);
await assert.rejects(
import(indirect.href),
{ code: 'ERR_NETWORK_IMPORT_DISALLOWED' }
);
});
it('data: URL can always import other data:', async () => {
const data = new URL('data:text/javascript,');
data.searchParams.set('body',
'import \'data:text/javascript,import \'data:\''
);
// doesn't throw
const empty = await import(data.href);
assert.ok(empty);
});
it('data: URL cannot import file: or builtin', async () => {
const data1 = new URL(url.href);
data1.searchParams.set('body',
'import \'file:///some/file.js\''
);
await assert.rejects(
import(data1.href),
{ code: 'ERR_NETWORK_IMPORT_DISALLOWED' }
);
const data2 = new URL(url.href);
data2.searchParams.set('body',
'import \'node:fs\''
);
await assert.rejects(
import(data2.href),
{ code: 'ERR_NETWORK_IMPORT_DISALLOWED' }
);
});
it('data: URL cannot import HTTP URLs', async () => {
const module = fixtures.fileURL('/es-modules/import-data-url.mjs');
try {
await import(module);
} catch (err) {
// We only want the module to load, we don't care if the module throws an
// error as long as the loader does not.
assert.notStrictEqual(err?.code, 'ERR_MODULE_NOT_FOUND');
}
const data1 = new URL(url.href);
const dataURL = 'data:text/javascript;export * from "node:os"';
data1.searchParams.set('body', `export * from ${JSON.stringify(dataURL)};`);
await assert.rejects(
import(data1),
{ code: 'ERR_NETWORK_IMPORT_DISALLOWED' }
);
});
});
server.close();
}
}

View File

@ -1,11 +0,0 @@
// Flags: --experimental-require-module
'use strict';
require('../common');
const assert = require('assert');
assert.throws(() => {
require('../fixtures/es-modules/network-import.mjs');
}, {
code: 'ERR_NETWORK_IMPORT_DISALLOWED'
});

View File

@ -1 +0,0 @@
import 'http://example.com/foo.js';