diff --git a/.changeset/tall-dodos-heal.md b/.changeset/tall-dodos-heal.md new file mode 100644 index 00000000000..05fb392c7b6 --- /dev/null +++ b/.changeset/tall-dodos-heal.md @@ -0,0 +1,7 @@ +--- +"@module-federation/enhanced": patch +"@module-federation/runtime": patch +"@module-federation/runtime-core": patch +--- + +feat: add an optional remote origin allowlist for remote entry loading and allow configuring it from enhanced build options without changing the default behavior. diff --git a/apps/website-new/docs/en/configure/_meta.json b/apps/website-new/docs/en/configure/_meta.json index 0fb0e49af33..a37b320e9e8 100644 --- a/apps/website-new/docs/en/configure/_meta.json +++ b/apps/website-new/docs/en/configure/_meta.json @@ -49,6 +49,11 @@ "name": "runtimeplugins", "label": "runtimePlugins" }, + { + "type": "file", + "name": "security", + "label": "security" + }, { "type": "file", "name": "getpublicpath", diff --git a/apps/website-new/docs/en/configure/index.mdx b/apps/website-new/docs/en/configure/index.mdx index 10c1ea85440..24363a348b6 100644 --- a/apps/website-new/docs/en/configure/index.mdx +++ b/apps/website-new/docs/en/configure/index.mdx @@ -20,6 +20,11 @@ type ModuleFederationOptions = { getPublicPath?: string; // Runtime plugins runtimePlugins?: Array]>; + // Runtime security options + security?: { + allowedRemoteOrigins?: string[]; + [key: string]: unknown; + }; // The runtime implementation to use implementation?: string; // manifest configuration diff --git a/apps/website-new/docs/en/configure/security.mdx b/apps/website-new/docs/en/configure/security.mdx new file mode 100644 index 00000000000..5b9b7abacde --- /dev/null +++ b/apps/website-new/docs/en/configure/security.mdx @@ -0,0 +1,38 @@ +# security + +- Type: `{ allowedRemoteOrigins?: string[]; [key: string]: unknown }` +- Required: No +- Default value: `undefined` +- Usage scenario: used to pass runtime security options + +At the moment, the built-in runtime behavior uses `allowedRemoteOrigins` to restrict which remote entry origins can be loaded. If you add extra fields under `security`, they are preserved and can be used by your own runtime extensions or plugins. + +## allowedRemoteOrigins + +- Type: `string[]` +- Required: No +- Default value: `undefined` + +When `allowedRemoteOrigins` is not set, remote loading behavior stays unchanged. + +Once `allowedRemoteOrigins` is configured, only matching network remote entry URLs can be loaded. This check applies to network remotes such as `http://`, `https://`, or protocol-relative URLs like `//cdn.example.com/remoteEntry.js`. + +Supported values: + +- `'*'`: allow all origins +- Hostname such as `localhost` or `cdn.example.com` +- Host with port such as `localhost:3001` +- Exact origin such as `https://cdn.example.com` +- Regex literal such as `/^https:\\/\\/.*\\.example\\.com$/` + +```ts title="rspack.config.ts" +new ModuleFederationPlugin({ + name: 'host', + remotes: { + remote: 'remote@https://cdn.example.com/remoteEntry.js', + }, + security: { + allowedRemoteOrigins: ['cdn.example.com', 'localhost:3001'], + }, +}); +``` diff --git a/apps/website-new/docs/zh/configure/_meta.json b/apps/website-new/docs/zh/configure/_meta.json index 0fb0e49af33..a37b320e9e8 100644 --- a/apps/website-new/docs/zh/configure/_meta.json +++ b/apps/website-new/docs/zh/configure/_meta.json @@ -49,6 +49,11 @@ "name": "runtimeplugins", "label": "runtimePlugins" }, + { + "type": "file", + "name": "security", + "label": "security" + }, { "type": "file", "name": "getpublicpath", diff --git a/apps/website-new/docs/zh/configure/index.mdx b/apps/website-new/docs/zh/configure/index.mdx index 85f1e313516..cb851a84c79 100644 --- a/apps/website-new/docs/zh/configure/index.mdx +++ b/apps/website-new/docs/zh/configure/index.mdx @@ -22,6 +22,11 @@ type ModuleFederationOptions { getPublicPath?: string; // 运行时插件(支持元组 [path, params]) runtimePlugins?: (string | [string, Record])[]; + // 运行时安全配置 + security?: { + allowedRemoteOrigins?: string[]; + [key: string]: unknown; + }; // runtime pkg 依赖 implementation?: string; // manifest 配置 diff --git a/apps/website-new/docs/zh/configure/security.mdx b/apps/website-new/docs/zh/configure/security.mdx new file mode 100644 index 00000000000..6234d6a1823 --- /dev/null +++ b/apps/website-new/docs/zh/configure/security.mdx @@ -0,0 +1,38 @@ +# security + +- 类型:`{ allowedRemoteOrigins?: string[]; [key: string]: unknown }` +- 是否必填:否 +- 默认值:`undefined` +- 使用场景:用于传递运行时安全配置 + +当前内置的运行时行为主要使用 `allowedRemoteOrigins` 来限制允许加载哪些远程入口来源。如果你在 `security` 下放了额外字段,它们会被原样保留,可供你自己的运行时扩展或插件使用。 + +## allowedRemoteOrigins + +- 类型:`string[]` +- 是否必填:否 +- 默认值:`undefined` + +如果不配置 `allowedRemoteOrigins`,远程加载行为保持不变。 + +配置了 `allowedRemoteOrigins` 之后,只允许匹配到的网络远程入口地址继续加载。这个检查适用于 `http://`、`https://`,以及 `//cdn.example.com/remoteEntry.js` 这种协议相对地址。 + +支持的写法: + +- `'*'`:允许所有来源 +- 主机名,例如 `localhost`、`cdn.example.com` +- 主机名加端口,例如 `localhost:3001` +- 精确来源,例如 `https://cdn.example.com` +- 正则字面量,例如 `/^https:\\/\\/.*\\.example\\.com$/` + +```ts title="rspack.config.ts" +new ModuleFederationPlugin({ + name: 'host', + remotes: { + remote: 'remote@https://cdn.example.com/remoteEntry.js', + }, + security: { + allowedRemoteOrigins: ['cdn.example.com', 'localhost:3001'], + }, +}); +``` diff --git a/packages/enhanced/README.md b/packages/enhanced/README.md index 07500356a7f..3cf7b437d2f 100644 --- a/packages/enhanced/README.md +++ b/packages/enhanced/README.md @@ -164,6 +164,44 @@ Used to add additional plug-ins required at runtime. The value is the path of th Once set, the runtime plugin is automatically injected and used at build time. +### security + +- Type: `{ allowedRemoteOrigins?: string[] }` +- Required: False +- Default: `undefined` + +Used to restrict which remote entry origins can be loaded by the runtime. + +If this option is not set, the existing loading behavior remains unchanged. +Once `allowedRemoteOrigins` is configured, only matching network remote entry URLs +can be loaded. + +`allowedRemoteOrigins` supports: + +- `'*'` to allow all origins +- hostnames such as `localhost` or `cdn.example.com` +- host + port values such as `cdn.example.com:8080` +- exact origins such as `https://cdn.example.com` +- regex literals such as `/^https:\\/\\/.*\\.example\\.com$/` + +Example: + +```js +module.exports = { + plugins: [ + new ModuleFederationPlugin({ + name: 'host', + remotes: { + remote: 'remote@https://cdn.example.com/remoteEntry.js', + }, + security: { + allowedRemoteOrigins: ['cdn.example.com'], + }, + }), + ], +}; +``` + ### implementation - Type: `string` diff --git a/packages/enhanced/src/lib/container/runtime/utils.ts b/packages/enhanced/src/lib/container/runtime/utils.ts index 672ba85a752..0c67878140b 100644 --- a/packages/enhanced/src/lib/container/runtime/utils.ts +++ b/packages/enhanced/src/lib/container/runtime/utils.ts @@ -13,12 +13,15 @@ import type { moduleFederationPlugin } from '@module-federation/sdk'; import type { init } from '@module-federation/runtime-tools'; type Remotes = Parameters[0]['remotes']; +type SecurityOptions = Parameters[0]['security']; export interface NormalizedRuntimeInitOptionsWithOutShared { name: string; remotes: Array< Remotes[0] & { externalType: moduleFederationPlugin.ExternalsType } >; + shareStrategy: 'version-first' | 'loaded-first'; + security?: SecurityOptions; } const extractUrlAndGlobal = require( @@ -98,10 +101,15 @@ export function normalizeRuntimeInitOptionsWithOutShared( }); }); + const security = options.security + ? (JSON.parse(JSON.stringify(options.security)) as SecurityOptions) + : undefined; + const initOptionsWithoutShared = { name: options.name!, remotes: remoteOptions, shareStrategy: options.shareStrategy || 'version-first', + security, }; return initOptionsWithoutShared; diff --git a/packages/enhanced/src/schemas/container/ModuleFederationPlugin.check.ts b/packages/enhanced/src/schemas/container/ModuleFederationPlugin.check.ts index 4881c76c975..4f888c83a7c 100644 --- a/packages/enhanced/src/schemas/container/ModuleFederationPlugin.check.ts +++ b/packages/enhanced/src/schemas/container/ModuleFederationPlugin.check.ts @@ -361,6 +361,16 @@ const t = { enum: ['version-first', 'loaded-first'], type: 'string', }, + security: { + type: 'object', + properties: { + allowedRemoteOrigins: { + type: 'array', + items: { type: 'string', minLength: 1 }, + }, + }, + additionalProperties: !0, + }, shared: { $ref: '#/definitions/Shared' }, dts: { anyOf: [ @@ -2984,98 +2994,139 @@ function D( b = r === c; } else b = !0; if (b) { - if (void 0 !== o.shared) { - const e = c; - (A(o.shared, { - instancePath: i + '/shared', - parentData: o, - parentDataProperty: 'shared', - rootData: f, - }) || - ((u = - null === u - ? A.errors - : u.concat(A.errors)), - (c = u.length)), - (b = e === c)); + if (void 0 !== o.security) { + let e = o.security; + const t = c; + if (c === t) { + if ( + !e || + 'object' != typeof e || + Array.isArray(e) + ) + return ( + (D.errors = [ + { params: { type: 'object' } }, + ]), + !1 + ); + if (void 0 !== e.allowedRemoteOrigins) { + let t = e.allowedRemoteOrigins; + if (c == c) { + if (!Array.isArray(t)) + return ( + (D.errors = [ + { params: { type: 'array' } }, + ]), + !1 + ); + { + const e = t.length; + for (let r = 0; r < e; r++) { + let e = t[r]; + const n = c; + if (c === n) { + if ('string' != typeof e) + return ( + (D.errors = [ + { + params: { + type: 'string', + }, + }, + ]), + !1 + ); + if (e.length < 1) + return ( + (D.errors = [ + { params: {} }, + ]), + !1 + ); + } + if (n !== c) break; + } + } + } + } + } + b = t === c; } else b = !0; if (b) { - if (void 0 !== o.dts) { - let e = o.dts; - const r = c, - n = c; - let s = !1; - const i = c; - if ('boolean' != typeof e) { - const e = { - params: { type: 'boolean' }, - }; - (null === u ? (u = [e]) : u.push(e), - c++); - } - var j = i === c; - if (((s = s || j), !s)) { - const r = c; - if (c === r) - if ( - e && - 'object' == typeof e && - !Array.isArray(e) - ) { - if (void 0 !== e.generateTypes) { - let t = e.generateTypes; - const r = c, - n = c; - let s = !1; - const o = c; - if ('boolean' != typeof t) { - const e = { - params: { type: 'boolean' }, - }; - (null === u - ? (u = [e]) - : u.push(e), - c++); - } - var x = o === c; - if (((s = s || x), !s)) { - const e = c; - if (c === e) - if ( - t && - 'object' == typeof t && - !Array.isArray(t) - ) { + if (void 0 !== o.shared) { + const e = c; + (A(o.shared, { + instancePath: i + '/shared', + parentData: o, + parentDataProperty: 'shared', + rootData: f, + }) || + ((u = + null === u + ? A.errors + : u.concat(A.errors)), + (c = u.length)), + (b = e === c)); + } else b = !0; + if (b) { + if (void 0 !== o.dts) { + let e = o.dts; + const r = c, + n = c; + let s = !1; + const i = c; + if ('boolean' != typeof e) { + const e = { + params: { type: 'boolean' }, + }; + (null === u ? (u = [e]) : u.push(e), + c++); + } + var j = i === c; + if (((s = s || j), !s)) { + const r = c; + if (c === r) + if ( + e && + 'object' == typeof e && + !Array.isArray(e) + ) { + if ( + void 0 !== e.generateTypes + ) { + let t = e.generateTypes; + const r = c, + n = c; + let s = !1; + const o = c; + if ('boolean' != typeof t) { + const e = { + params: { + type: 'boolean', + }, + }; + (null === u + ? (u = [e]) + : u.push(e), + c++); + } + var x = o === c; + if (((s = s || x), !s)) { + const e = c; + if (c === e) if ( - void 0 !== - t.tsConfigPath + t && + 'object' == typeof t && + !Array.isArray(t) ) { - const e = c; - if ( - 'string' != - typeof t.tsConfigPath - ) { - const e = { - params: { - type: 'string', - }, - }; - (null === u - ? (u = [e]) - : u.push(e), - c++); - } - var O = e === c; - } else O = !0; - if (O) { if ( void 0 !== - t.typesFolder + t.tsConfigPath ) { const e = c; if ( 'string' != - typeof t.typesFolder + typeof t.tsConfigPath ) { const e = { params: { @@ -3087,17 +3138,17 @@ function D( : u.push(e), c++); } - O = e === c; + var O = e === c; } else O = !0; if (O) { if ( void 0 !== - t.compiledTypesFolder + t.typesFolder ) { const e = c; if ( 'string' != - typeof t.compiledTypesFolder + typeof t.typesFolder ) { const e = { params: { @@ -3114,16 +3165,16 @@ function D( if (O) { if ( void 0 !== - t.deleteTypesFolder + t.compiledTypesFolder ) { const e = c; if ( - 'boolean' != - typeof t.deleteTypesFolder + 'string' != + typeof t.compiledTypesFolder ) { const e = { params: { - type: 'boolean', + type: 'string', }, }; (null === u @@ -3136,113 +3187,113 @@ function D( if (O) { if ( void 0 !== - t.additionalFilesToCompile + t.deleteTypesFolder ) { - let e = - t.additionalFilesToCompile; - const r = c; - if (c === r) - if ( - Array.isArray( - e, - ) - ) { - const t = - e.length; - for ( - let r = 0; - r < t; - r++ + const e = c; + if ( + 'boolean' != + typeof t.deleteTypesFolder + ) { + const e = { + params: { + type: 'boolean', + }, + }; + (null === u + ? (u = [e]) + : u.push(e), + c++); + } + O = e === c; + } else O = !0; + if (O) { + if ( + void 0 !== + t.additionalFilesToCompile + ) { + let e = + t.additionalFilesToCompile; + const r = c; + if (c === r) + if ( + Array.isArray( + e, + ) ) { const t = - c; - if ( - 'string' != - typeof e[ - r - ] + e.length; + for ( + let r = 0; + r < t; + r++ ) { - const e = - { - params: - { - type: 'string', - }, - }; - (null === - u - ? (u = - [ + const t = + c; + if ( + 'string' != + typeof e[ + r + ] + ) { + const e = + { + params: + { + type: 'string', + }, + }; + (null === + u + ? (u = + [ + e, + ]) + : u.push( e, - ]) - : u.push( - e, - ), - c++); + ), + c++); + } + if ( + t !== + c + ) + break; } - if ( - t !== c - ) - break; + } else { + const e = + { + params: + { + type: 'array', + }, + }; + (null === + u + ? (u = [ + e, + ]) + : u.push( + e, + ), + c++); } - } else { - const e = { - params: { - type: 'array', - }, - }; - (null === u - ? (u = [ - e, - ]) - : u.push( - e, - ), - c++); - } - O = r === c; - } else O = !0; - if (O) { - if ( - void 0 !== - t.compileInChildProcess - ) { - const e = c; - if ( - 'boolean' != - typeof t.compileInChildProcess - ) { - const e = { - params: { - type: 'boolean', - }, - }; - (null === u - ? (u = [ - e, - ]) - : u.push( - e, - ), - c++); - } - O = e === c; + O = r === c; } else O = !0; if (O) { if ( void 0 !== - t.compilerInstance + t.compileInChildProcess ) { const e = c; if ( - 'string' != - typeof t.compilerInstance + 'boolean' != + typeof t.compileInChildProcess ) { const e = { params: { - type: 'string', + type: 'boolean', }, }; (null === @@ -3260,19 +3311,19 @@ function D( if (O) { if ( void 0 !== - t.generateAPITypes + t.compilerInstance ) { const e = c; if ( - 'boolean' != - typeof t.generateAPITypes + 'string' != + typeof t.compilerInstance ) { const e = { params: { - type: 'boolean', + type: 'string', }, }; (null === @@ -3293,20 +3344,13 @@ function D( if (O) { if ( void 0 !== - t.extractThirdParty + t.generateAPITypes ) { - let e = - t.extractThirdParty; - const r = - c, - n = c; - let s = - !1; - const o = + const e = c; if ( 'boolean' != - typeof e + typeof t.generateAPITypes ) { const e = { @@ -3326,132 +3370,210 @@ function D( ), c++); } - var k = - o === + O = + e === c; + } else + O = !0; + if (O) { if ( - ((s = - s || - k), - !s) + void 0 !== + t.extractThirdParty ) { - const t = + let e = + t.extractThirdParty; + const r = + c, + n = + c; + let s = + !1; + const o = + c; + if ( + 'boolean' != + typeof e + ) { + const e = + { + params: + { + type: 'boolean', + }, + }; + (null === + u + ? (u = + [ + e, + ]) + : u.push( + e, + ), + c++); + } + var k = + o === c; if ( - c === - t - ) + ((s = + s || + k), + !s) + ) { + const t = + c; if ( - e && - 'object' == - typeof e && - !Array.isArray( - e, - ) - ) { - const t = - c; - for (const t in e) - if ( - 'exclude' !== - t - ) { - const e = - { - params: - { - additionalProperty: - t, - }, - }; - (null === - u - ? (u = - [ - e, - ]) - : u.push( - e, - ), - c++); - break; - } + c === + t + ) if ( - t === - c && - void 0 !== - e.exclude - ) { - let t = - e.exclude; - if ( - c == - c + e && + 'object' == + typeof e && + !Array.isArray( + e, ) + ) { + const t = + c; + for (const t in e) if ( - Array.isArray( - t, - ) + 'exclude' !== + t ) { const e = - t.length; - for ( - let r = 0; - r < - e; - r++ + { + params: + { + additionalProperty: + t, + }, + }; + (null === + u + ? (u = + [ + e, + ]) + : u.push( + e, + ), + c++); + break; + } + if ( + t === + c && + void 0 !== + e.exclude + ) { + let t = + e.exclude; + if ( + c == + c + ) + if ( + Array.isArray( + t, + ) ) { - let e = - t[ - r - ]; - const n = - c, - s = - c; - let o = - !1; - const i = - c; - if ( - 'string' != - typeof e + const e = + t.length; + for ( + let r = 0; + r < + e; + r++ ) { - const e = - { - params: - { - type: 'string', - }, - }; - (null === - u - ? (u = - [ + let e = + t[ + r + ]; + const n = + c, + s = + c; + let o = + !1; + const i = + c; + if ( + 'string' != + typeof e + ) { + const e = + { + params: + { + type: 'string', + }, + }; + (null === + u + ? (u = + [ + e, + ]) + : u.push( e, - ]) - : u.push( - e, - ), - c++); - } - var S = - i === - c; - if ( - ((o = - o || - S), - !o) - ) { - const t = + ), + c++); + } + var S = + i === c; if ( - !( - e instanceof - RegExp - ) + ((o = + o || + S), + !o) ) { + const t = + c; + if ( + !( + e instanceof + RegExp + ) + ) { + const e = + { + params: + {}, + }; + (null === + u + ? (u = + [ + e, + ]) + : u.push( + e, + ), + c++); + } + ((S = + t === + c), + (o = + o || + S)); + } + if ( + o + ) + ((c = + s), + null !== + u && + (s + ? (u.length = + s) + : (u = + null))); + else { const e = { params: @@ -3468,143 +3590,73 @@ function D( ), c++); } - ((S = - t === - c), - (o = - o || - S)); + if ( + n !== + c + ) + break; } - if ( - o - ) - ((c = - s), - null !== - u && - (s - ? (u.length = - s) - : (u = - null))); - else { - const e = - { - params: - {}, - }; - (null === - u - ? (u = - [ - e, - ]) - : u.push( + } else { + const e = + { + params: + { + type: 'array', + }, + }; + (null === + u + ? (u = + [ e, - ), - c++); - } - if ( - n !== - c - ) - break; + ]) + : u.push( + e, + ), + c++); } - } else { - const e = + } + } else { + const e = + { + params: { - params: - { - type: 'array', - }, - }; - (null === - u - ? (u = - [ - e, - ]) - : u.push( - e, - ), - c++); - } - } - } else { - const e = - { - params: - { - type: 'object', - }, - }; - (null === - u - ? (u = - [ + type: 'object', + }, + }; + (null === + u + ? (u = + [ + e, + ]) + : u.push( e, - ]) - : u.push( - e, - ), - c++); - } - ((k = - t === - c), - (s = - s || - k)); - } - if (s) - ((c = - n), - null !== - u && - (n - ? (u.length = - n) - : (u = - null))); - else { - const e = - { - params: - {}, - }; - (null === - u - ? (u = - [ - e, - ]) - : u.push( - e, - ), - c++); - } - O = - r === - c; - } else - O = !0; - if (O) { - if ( - void 0 !== - t.extractRemoteTypes - ) { - const e = - c; - if ( - 'boolean' != - typeof t.extractRemoteTypes - ) { + ), + c++); + } + ((k = + t === + c), + (s = + s || + k)); + } + if (s) + ((c = + n), + null !== + u && + (n + ? (u.length = + n) + : (u = + null))); + else { const e = { params: - { - type: 'boolean', - }, + {}, }; (null === u @@ -3618,7 +3670,7 @@ function D( c++); } O = - e === + r === c; } else O = @@ -3626,13 +3678,13 @@ function D( if (O) { if ( void 0 !== - t.abortOnError + t.extractRemoteTypes ) { const e = c; if ( 'boolean' != - typeof t.abortOnError + typeof t.extractRemoteTypes ) { const e = { @@ -3658,23 +3710,25 @@ function D( } else O = !0; - if (O) + if ( + O + ) { if ( void 0 !== - t.afterGenerate + t.abortOnError ) { const e = c; if ( - !( - t.afterGenerate instanceof - Function - ) + 'boolean' != + typeof t.abortOnError ) { const e = { params: - {}, + { + type: 'boolean', + }, }; (null === u @@ -3693,105 +3747,124 @@ function D( } else O = !0; - } - } - } - } - } - } - } - } - } - } - } else { - const e = { - params: { - type: 'object', - }, - }; - (null === u - ? (u = [e]) - : u.push(e), - c++); - } - ((x = e === c), (s = s || x)); - } - if (s) - ((c = n), - null !== u && - (n - ? (u.length = n) - : (u = null))); - else { - const e = { params: {} }; - (null === u - ? (u = [e]) - : u.push(e), - c++); - } - var T = r === c; - } else T = !0; - if (T) { - if (void 0 !== e.consumeTypes) { - let r = e.consumeTypes; - const n = c, - s = c; - let o = !1; - const i = c; - if ('boolean' != typeof r) { - const e = { - params: { - type: 'boolean', - }, - }; + if ( + O + ) + if ( + void 0 !== + t.afterGenerate + ) { + const e = + c; + if ( + !( + t.afterGenerate instanceof + Function + ) + ) { + const e = + { + params: + {}, + }; + (null === + u + ? (u = + [ + e, + ]) + : u.push( + e, + ), + c++); + } + O = + e === + c; + } else + O = + !0; + } + } + } + } + } + } + } + } + } + } + } else { + const e = { + params: { + type: 'object', + }, + }; + (null === u + ? (u = [e]) + : u.push(e), + c++); + } + ((x = e === c), + (s = s || x)); + } + if (s) + ((c = n), + null !== u && + (n + ? (u.length = n) + : (u = null))); + else { + const e = { params: {} }; (null === u ? (u = [e]) : u.push(e), c++); } - var L = i === c; - if (((o = o || L), !o)) { - const e = c; - if (c === e) - if ( - r && - 'object' == typeof r && - !Array.isArray(r) - ) { + var T = r === c; + } else T = !0; + if (T) { + if ( + void 0 !== e.consumeTypes + ) { + let r = e.consumeTypes; + const n = c, + s = c; + let o = !1; + const i = c; + if ('boolean' != typeof r) { + const e = { + params: { + type: 'boolean', + }, + }; + (null === u + ? (u = [e]) + : u.push(e), + c++); + } + var L = i === c; + if (((o = o || L), !o)) { + const e = c; + if (c === e) if ( - void 0 !== - r.typesFolder + r && + 'object' == + typeof r && + !Array.isArray(r) ) { - const e = c; - if ( - 'string' != - typeof r.typesFolder - ) { - const e = { - params: { - type: 'string', - }, - }; - (null === u - ? (u = [e]) - : u.push(e), - c++); - } - var E = e === c; - } else E = !0; - if (E) { if ( void 0 !== - r.abortOnError + r.typesFolder ) { const e = c; if ( - 'boolean' != - typeof r.abortOnError + 'string' != + typeof r.typesFolder ) { const e = { params: { - type: 'boolean', + type: 'string', }, }; (null === u @@ -3799,21 +3872,21 @@ function D( : u.push(e), c++); } - E = e === c; + var E = e === c; } else E = !0; if (E) { if ( void 0 !== - r.remoteTypesFolder + r.abortOnError ) { const e = c; if ( - 'string' != - typeof r.remoteTypesFolder + 'boolean' != + typeof r.abortOnError ) { const e = { params: { - type: 'string', + type: 'boolean', }, }; (null === u @@ -3826,16 +3899,16 @@ function D( if (E) { if ( void 0 !== - r.deleteTypesFolder + r.remoteTypesFolder ) { const e = c; if ( - 'boolean' != - typeof r.deleteTypesFolder + 'string' != + typeof r.remoteTypesFolder ) { const e = { params: { - type: 'boolean', + type: 'string', }, }; (null === u @@ -3848,16 +3921,16 @@ function D( if (E) { if ( void 0 !== - r.maxRetries + r.deleteTypesFolder ) { const e = c; if ( - 'number' != - typeof r.maxRetries + 'boolean' != + typeof r.deleteTypesFolder ) { const e = { params: { - type: 'number', + type: 'boolean', }, }; (null === u @@ -3874,18 +3947,18 @@ function D( if (E) { if ( void 0 !== - r.consumeAPITypes + r.maxRetries ) { const e = c; if ( - 'boolean' != - typeof r.consumeAPITypes + 'number' != + typeof r.maxRetries ) { const e = { params: { - type: 'boolean', + type: 'number', }, }; (null === @@ -3903,272 +3976,271 @@ function D( if (E) { if ( void 0 !== - r.runtimePkgs + r.consumeAPITypes ) { - let e = - r.runtimePkgs; - const t = + const e = c; if ( - c === t - ) - if ( - Array.isArray( - e, - ) - ) { - const t = - e.length; - for ( - let r = 0; - r < - t; - r++ - ) { - const t = - c; - if ( - 'string' != - typeof e[ - r - ] - ) { - const e = - { - params: - { - type: 'string', - }, - }; - (null === - u - ? (u = - [ - e, - ]) - : u.push( - e, - ), - c++); - } - if ( - t !== - c - ) - break; - } - } else { - const e = - { - params: - { - type: 'array', - }, - }; - (null === - u - ? (u = - [ - e, - ]) - : u.push( + 'boolean' != + typeof r.consumeAPITypes + ) { + const e = + { + params: + { + type: 'boolean', + }, + }; + (null === + u + ? (u = + [ e, - ), - c++); - } + ]) + : u.push( + e, + ), + c++); + } E = - t === c; + e === c; } else E = !0; if (E) { if ( void 0 !== - r.remoteTypeUrls + r.runtimePkgs ) { let e = - r.remoteTypeUrls; + r.runtimePkgs; const t = - c, - n = c; - let s = - !1; - const o = c; if ( - !( - e instanceof - Function - ) - ) { - const e = - { - params: - {}, - }; - (null === - u - ? (u = - [ + c === + t + ) + if ( + Array.isArray( + e, + ) + ) { + const t = + e.length; + for ( + let r = 0; + r < + t; + r++ + ) { + const t = + c; + if ( + 'string' != + typeof e[ + r + ] + ) { + const e = + { + params: + { + type: 'string', + }, + }; + (null === + u + ? (u = + [ + e, + ]) + : u.push( + e, + ), + c++); + } + if ( + t !== + c + ) + break; + } + } else { + const e = + { + params: + { + type: 'array', + }, + }; + (null === + u + ? (u = + [ + e, + ]) + : u.push( e, - ]) - : u.push( - e, - ), - c++); - } - var R = - o === + ), + c++); + } + E = + t === c; + } else + E = !0; + if (E) { if ( - ((s = - s || - R), - !s) + void 0 !== + r.remoteTypeUrls ) { + let e = + r.remoteTypeUrls; const t = + c, + n = + c; + let s = + !1; + const o = c; if ( - c === - t - ) - if ( - e && - 'object' == - typeof e && - !Array.isArray( - e, - ) + !( + e instanceof + Function ) - for (const t in e) { - let r = - e[ - t - ]; - const n = - c; - if ( - c === - n + ) { + const e = + { + params: + {}, + }; + (null === + u + ? (u = + [ + e, + ]) + : u.push( + e, + ), + c++); + } + var R = + o === + c; + if ( + ((s = + s || + R), + !s) + ) { + const t = + c; + if ( + c === + t + ) + if ( + e && + 'object' == + typeof e && + !Array.isArray( + e, ) + ) + for (const t in e) { + let r = + e[ + t + ]; + const n = + c; if ( - r && - 'object' == - typeof r && - !Array.isArray( - r, - ) - ) { - let e; + c === + n + ) if ( - (void 0 === - r.api && - (e = - 'api')) || - (void 0 === - r.zip && - (e = - 'zip')) + r && + 'object' == + typeof r && + !Array.isArray( + r, + ) ) { - const t = - { - params: - { - missingProperty: - e, - }, - }; - (null === - u - ? (u = - [ - t, - ]) - : u.push( - t, - ), - c++); - } else { - const e = - c; - for (const e in r) - if ( - 'alias' !== - e && - 'api' !== - e && - 'zip' !== - e - ) { - const t = - { - params: - { - additionalProperty: - e, - }, - }; - (null === - u - ? (u = - [ - t, - ]) - : u.push( - t, - ), - c++); - break; - } + let e; if ( - e === - c + (void 0 === + r.api && + (e = + 'api')) || + (void 0 === + r.zip && + (e = + 'zip')) ) { - if ( - void 0 !== - r.alias - ) { - const e = - c; + const t = + { + params: + { + missingProperty: + e, + }, + }; + (null === + u + ? (u = + [ + t, + ]) + : u.push( + t, + ), + c++); + } else { + const e = + c; + for (const e in r) if ( - 'string' != - typeof r.alias + 'alias' !== + e && + 'api' !== + e && + 'zip' !== + e ) { - const e = + const t = { params: { - type: 'string', + additionalProperty: + e, }, }; (null === u ? (u = [ - e, + t, ]) : u.push( - e, + t, ), c++); + break; } - var C = - e === - c; - } else - C = - !0; if ( - C + e === + c ) { if ( void 0 !== - r.api + r.alias ) { const e = c; if ( 'string' != - typeof r.api + typeof r.alias ) { const e = { @@ -4188,7 +4260,7 @@ function D( ), c++); } - C = + var C = e === c; } else @@ -4196,16 +4268,16 @@ function D( !0; if ( C - ) + ) { if ( void 0 !== - r.zip + r.api ) { const e = c; if ( 'string' != - typeof r.zip + typeof r.api ) { const e = { @@ -4231,109 +4303,112 @@ function D( } else C = !0; + if ( + C + ) + if ( + void 0 !== + r.zip + ) { + const e = + c; + if ( + 'string' != + typeof r.zip + ) { + const e = + { + params: + { + type: 'string', + }, + }; + (null === + u + ? (u = + [ + e, + ]) + : u.push( + e, + ), + c++); + } + C = + e === + c; + } else + C = + !0; + } } } + } else { + const e = + { + params: + { + type: 'object', + }, + }; + (null === + u + ? (u = + [ + e, + ]) + : u.push( + e, + ), + c++); } - } else { - const e = + if ( + n !== + c + ) + break; + } + else { + const e = + { + params: { - params: - { - type: 'object', - }, - }; - (null === - u - ? (u = - [ - e, - ]) - : u.push( - e, - ), - c++); - } - if ( - n !== - c - ) - break; - } - else { - const e = - { - params: - { - type: 'object', - }, - }; - (null === - u - ? (u = - [ + type: 'object', + }, + }; + (null === + u + ? (u = + [ + e, + ]) + : u.push( e, - ]) - : u.push( - e, - ), - c++); - } - ((R = - t === - c), - (s = - s || - R)); - } - if (s) - ((c = - n), - null !== - u && - (n - ? (u.length = - n) - : (u = - null))); - else { - const e = - { - params: - {}, - }; - (null === - u - ? (u = - [ - e, - ]) - : u.push( - e, - ), - c++); - } - E = - t === - c; - } else - E = !0; - if (E) { - if ( - void 0 !== - r.timeout - ) { - const e = - c; - if ( - 'number' != - typeof r.timeout - ) { + ), + c++); + } + ((R = + t === + c), + (s = + s || + R)); + } + if (s) + ((c = + n), + null !== + u && + (n + ? (u.length = + n) + : (u = + null))); + else { const e = { params: - { - type: 'number', - }, + {}, }; (null === u @@ -4347,7 +4422,7 @@ function D( c++); } E = - e === + t === c; } else E = @@ -4355,33 +4430,19 @@ function D( if (E) { if ( void 0 !== - r.family + r.timeout ) { - let e = - r.family; - const n = + const e = c; if ( - 4 !== - e && - 6 !== - e + 'number' != + typeof r.timeout ) { const e = { params: { - allowedValues: - t - .properties - .dts - .anyOf[1] - .properties - .consumeTypes - .anyOf[1] - .properties - .family - .enum, + type: 'number', }, }; (null === @@ -4396,27 +4457,43 @@ function D( c++); } E = - n === + e === c; } else E = !0; - if (E) + if ( + E + ) { if ( void 0 !== - r.typesOnBuild + r.family ) { - const e = + let e = + r.family; + const n = c; if ( - 'boolean' != - typeof r.typesOnBuild + 4 !== + e && + 6 !== + e ) { const e = { params: { - type: 'boolean', + allowedValues: + t + .properties + .dts + .anyOf[1] + .properties + .consumeTypes + .anyOf[1] + .properties + .family + .enum, }, }; (null === @@ -4431,11 +4508,49 @@ function D( c++); } E = - e === + n === c; } else E = !0; + if ( + E + ) + if ( + void 0 !== + r.typesOnBuild + ) { + const e = + c; + if ( + 'boolean' != + typeof r.typesOnBuild + ) { + const e = + { + params: + { + type: 'boolean', + }, + }; + (null === + u + ? (u = + [ + e, + ]) + : u.push( + e, + ), + c++); + } + E = + e === + c; + } else + E = + !0; + } } } } @@ -4444,71 +4559,47 @@ function D( } } } + } else { + const e = { + params: { + type: 'object', + }, + }; + (null === u + ? (u = [e]) + : u.push(e), + c++); } - } else { - const e = { - params: { - type: 'object', - }, - }; - (null === u - ? (u = [e]) - : u.push(e), - c++); - } - ((L = e === c), - (o = o || L)); - } - if (o) - ((c = s), - null !== u && - (s - ? (u.length = s) - : (u = null))); - else { - const e = { params: {} }; - (null === u - ? (u = [e]) - : u.push(e), - c++); - } - T = n === c; - } else T = !0; - if (T) { - if ( - void 0 !== e.tsConfigPath - ) { - const t = c; - if ( - 'string' != - typeof e.tsConfigPath - ) { - const e = { - params: { - type: 'string', - }, - }; + ((L = e === c), + (o = o || L)); + } + if (o) + ((c = s), + null !== u && + (s + ? (u.length = s) + : (u = null))); + else { + const e = { params: {} }; (null === u ? (u = [e]) : u.push(e), c++); } - T = t === c; + T = n === c; } else T = !0; if (T) { if ( - void 0 !== e.extraOptions + void 0 !== e.tsConfigPath ) { - let t = e.extraOptions; - const r = c; + const t = c; if ( - !t || - 'object' != typeof t || - Array.isArray(t) + 'string' != + typeof e.tsConfigPath ) { const e = { params: { - type: 'object', + type: 'string', }, }; (null === u @@ -4516,21 +4607,24 @@ function D( : u.push(e), c++); } - T = r === c; + T = t === c; } else T = !0; if (T) { if ( void 0 !== - e.implementation + e.extraOptions ) { - const t = c; + let t = e.extraOptions; + const r = c; if ( - 'string' != - typeof e.implementation + !t || + 'object' != + typeof t || + Array.isArray(t) ) { const e = { params: { - type: 'string', + type: 'object', }, }; (null === u @@ -4538,14 +4632,17 @@ function D( : u.push(e), c++); } - T = t === c; + T = r === c; } else T = !0; if (T) { - if (void 0 !== e.cwd) { + if ( + void 0 !== + e.implementation + ) { const t = c; if ( 'string' != - typeof e.cwd + typeof e.implementation ) { const e = { params: { @@ -4559,19 +4656,18 @@ function D( } T = t === c; } else T = !0; - if (T) + if (T) { if ( - void 0 !== - e.displayErrorInTerminal + void 0 !== e.cwd ) { const t = c; if ( - 'boolean' != - typeof e.displayErrorInTerminal + 'string' != + typeof e.cwd ) { const e = { params: { - type: 'boolean', + type: 'string', }, }; (null === u @@ -4581,80 +4677,85 @@ function D( } T = t === c; } else T = !0; + if (T) + if ( + void 0 !== + e.displayErrorInTerminal + ) { + const t = c; + if ( + 'boolean' != + typeof e.displayErrorInTerminal + ) { + const e = { + params: { + type: 'boolean', + }, + }; + (null === u + ? (u = [e]) + : u.push(e), + c++); + } + T = t === c; + } else T = !0; + } } } } } + } else { + const e = { + params: { type: 'object' }, + }; + (null === u + ? (u = [e]) + : u.push(e), + c++); } - } else { - const e = { - params: { type: 'object' }, - }; - (null === u + ((j = r === c), (s = s || j)); + } + if (!s) { + const e = { params: {} }; + return ( + null === u ? (u = [e]) : u.push(e), - c++); - } - ((j = r === c), (s = s || j)); - } - if (!s) { - const e = { params: {} }; - return ( - null === u ? (u = [e]) : u.push(e), - c++, - (D.errors = u), - !1 - ); - } - ((c = n), - null !== u && - (n ? (u.length = n) : (u = null)), - (b = r === c)); - } else b = !0; - if (b) { - if (void 0 !== o.experiments) { - let e = o.experiments; - const r = c; - if (c === r) { - if ( - !e || - 'object' != typeof e || - Array.isArray(e) - ) - return ( - (D.errors = [ - { - params: { type: 'object' }, - }, - ]), - !1 - ); - if (void 0 !== e.asyncStartup) { - const t = c; + c++, + (D.errors = u), + !1 + ); + } + ((c = n), + null !== u && + (n ? (u.length = n) : (u = null)), + (b = r === c)); + } else b = !0; + if (b) { + if (void 0 !== o.experiments) { + let e = o.experiments; + const r = c; + if (c === r) { if ( - 'boolean' != - typeof e.asyncStartup + !e || + 'object' != typeof e || + Array.isArray(e) ) return ( (D.errors = [ { params: { - type: 'boolean', + type: 'object', }, }, ]), !1 ); - var $ = t === c; - } else $ = !0; - if ($) { - if ( - void 0 !== e.externalRuntime - ) { + if (void 0 !== e.asyncStartup) { const t = c; if ( 'boolean' != - typeof e.externalRuntime + typeof e.asyncStartup ) return ( (D.errors = [ @@ -4666,17 +4767,16 @@ function D( ]), !1 ); - $ = t === c; + var $ = t === c; } else $ = !0; if ($) { if ( - void 0 !== - e.provideExternalRuntime + void 0 !== e.externalRuntime ) { const t = c; if ( 'boolean' != - typeof e.provideExternalRuntime + typeof e.externalRuntime ) return ( (D.errors = [ @@ -4690,179 +4790,184 @@ function D( ); $ = t === c; } else $ = !0; - if ($) + if ($) { if ( - void 0 !== e.optimization + void 0 !== + e.provideExternalRuntime ) { - let r = e.optimization; - const n = c; - if (c === n) { - if ( - !r || - 'object' != typeof r || - Array.isArray(r) - ) - return ( - (D.errors = [ - { - params: { - type: 'object', - }, + const t = c; + if ( + 'boolean' != + typeof e.provideExternalRuntime + ) + return ( + (D.errors = [ + { + params: { + type: 'boolean', }, - ]), - !1 - ); - { - const e = c; - for (const e in r) - if ( - 'disableSnapshot' !== - e && - 'target' !== e - ) - return ( - (D.errors = [ - { - params: { - additionalProperty: - e, - }, + }, + ]), + !1 + ); + $ = t === c; + } else $ = !0; + if ($) + if ( + void 0 !== e.optimization + ) { + let r = e.optimization; + const n = c; + if (c === n) { + if ( + !r || + 'object' != + typeof r || + Array.isArray(r) + ) + return ( + (D.errors = [ + { + params: { + type: 'object', }, - ]), - !1 - ); - if (e === c) { - if ( - void 0 !== - r.disableSnapshot - ) { - const e = c; + }, + ]), + !1 + ); + { + const e = c; + for (const e in r) if ( - 'boolean' != - typeof r.disableSnapshot + 'disableSnapshot' !== + e && + 'target' !== e ) return ( (D.errors = [ { params: { - type: 'boolean', + additionalProperty: + e, }, }, ]), !1 ); - var I = e === c; - } else I = !0; - if (I) + if (e === c) { if ( void 0 !== - r.target + r.disableSnapshot ) { - let e = r.target; - const n = c; + const e = c; if ( - 'web' !== e && - 'node' !== e + 'boolean' != + typeof r.disableSnapshot ) return ( (D.errors = [ { params: { - allowedValues: - t - .properties - .experiments - .properties - .optimization - .properties - .target - .enum, + type: 'boolean', }, }, ]), !1 ); - I = n === c; + var I = e === c; } else I = !0; + if (I) + if ( + void 0 !== + r.target + ) { + let e = + r.target; + const n = c; + if ( + 'web' !== e && + 'node' !== e + ) + return ( + (D.errors = + [ + { + params: + { + allowedValues: + t + .properties + .experiments + .properties + .optimization + .properties + .target + .enum, + }, + }, + ]), + !1 + ); + I = n === c; + } else I = !0; + } } } - } - $ = n === c; - } else $ = !0; + $ = n === c; + } else $ = !0; + } } } - } - b = r === c; - } else b = !0; - if (b) { - if (void 0 !== o.bridge) { - let e = o.bridge; - const t = c; - if (c === t) { - if ( - !e || - 'object' != typeof e || - Array.isArray(e) - ) - return ( - (D.errors = [ - { - params: { - type: 'object', - }, - }, - ]), - !1 - ); - { - const t = c; - for (const t in e) - if ( - 'enableBridgeRouter' !== - t && - 'disableAlias' !== t - ) - return ( - (D.errors = [ - { - params: { - additionalProperty: - t, - }, + b = r === c; + } else b = !0; + if (b) { + if (void 0 !== o.bridge) { + let e = o.bridge; + const t = c; + if (c === t) { + if ( + !e || + 'object' != typeof e || + Array.isArray(e) + ) + return ( + (D.errors = [ + { + params: { + type: 'object', }, - ]), - !1 - ); - if (t === c) { - if ( - void 0 !== - e.enableBridgeRouter - ) { - const t = c; + }, + ]), + !1 + ); + { + const t = c; + for (const t in e) if ( - 'boolean' != - typeof e.enableBridgeRouter + 'enableBridgeRouter' !== + t && + 'disableAlias' !== t ) return ( (D.errors = [ { params: { - type: 'boolean', + additionalProperty: + t, }, }, ]), !1 ); - var q = t === c; - } else q = !0; - if (q) + if (t === c) { if ( - void 0 !== e.disableAlias + void 0 !== + e.enableBridgeRouter ) { const t = c; if ( 'boolean' != - typeof e.disableAlias + typeof e.enableBridgeRouter ) return ( (D.errors = [ @@ -4874,183 +4979,59 @@ function D( ]), !1 ); - q = t === c; + var q = t === c; } else q = !0; + if (q) + if ( + void 0 !== + e.disableAlias + ) { + const t = c; + if ( + 'boolean' != + typeof e.disableAlias + ) + return ( + (D.errors = [ + { + params: { + type: 'boolean', + }, + }, + ]), + !1 + ); + q = t === c; + } else q = !0; + } } } - } - b = t === c; - } else b = !0; - if (b) { - if ( - void 0 !== o.virtualRuntimeEntry - ) { - const e = c; - if ( - 'boolean' != - typeof o.virtualRuntimeEntry - ) - return ( - (D.errors = [ - { - params: { - type: 'boolean', - }, - }, - ]), - !1 - ); - b = e === c; + b = t === c; } else b = !0; if (b) { - if (void 0 !== o.dev) { - let e = o.dev; - const t = c, - r = c; - let n = !1; - const s = c; - if ('boolean' != typeof e) { - const e = { - params: { type: 'boolean' }, - }; - (null === u - ? (u = [e]) - : u.push(e), - c++); - } - var V = s === c; - if (((n = n || V), !n)) { - const t = c; - if (c === t) - if ( - e && - 'object' == typeof e && - !Array.isArray(e) - ) { - const t = c; - for (const t in e) - if ( - 'disableLiveReload' !== - t && - 'disableHotTypesReload' !== - t && - 'disableDynamicRemoteTypeHints' !== - t - ) { - const e = { - params: { - additionalProperty: - t, - }, - }; - (null === u - ? (u = [e]) - : u.push(e), - c++); - break; - } - if (t === c) { - if ( - void 0 !== - e.disableLiveReload - ) { - const t = c; - if ( - 'boolean' != - typeof e.disableLiveReload - ) { - const e = { - params: { - type: 'boolean', - }, - }; - (null === u - ? (u = [e]) - : u.push(e), - c++); - } - var w = t === c; - } else w = !0; - if (w) { - if ( - void 0 !== - e.disableHotTypesReload - ) { - const t = c; - if ( - 'boolean' != - typeof e.disableHotTypesReload - ) { - const e = { - params: { - type: 'boolean', - }, - }; - (null === u - ? (u = [e]) - : u.push(e), - c++); - } - w = t === c; - } else w = !0; - if (w) - if ( - void 0 !== - e.disableDynamicRemoteTypeHints - ) { - const t = c; - if ( - 'boolean' != - typeof e.disableDynamicRemoteTypeHints - ) { - const e = { - params: { - type: 'boolean', - }, - }; - (null === u - ? (u = [e]) - : u.push(e), - c++); - } - w = t === c; - } else w = !0; - } - } - } else { - const e = { + if ( + void 0 !== o.virtualRuntimeEntry + ) { + const e = c; + if ( + 'boolean' != + typeof o.virtualRuntimeEntry + ) + return ( + (D.errors = [ + { params: { - type: 'object', + type: 'boolean', }, - }; - (null === u - ? (u = [e]) - : u.push(e), - c++); - } - ((V = t === c), (n = n || V)); - } - if (!n) { - const e = { params: {} }; - return ( - null === u - ? (u = [e]) - : u.push(e), - c++, - (D.errors = u), + }, + ]), !1 ); - } - ((c = r), - null !== u && - (r - ? (u.length = r) - : (u = null)), - (b = t === c)); + b = e === c; } else b = !0; if (b) { - if (void 0 !== o.manifest) { - let e = o.manifest; + if (void 0 !== o.dev) { + let e = o.dev; const t = c, r = c; let n = !1; @@ -5066,8 +5047,8 @@ function D( : u.push(e), c++); } - var F = s === c; - if (((n = n || F), !n)) { + var w = s === c; + if (((n = n || w), !n)) { const t = c; if (c === t) if ( @@ -5078,11 +5059,11 @@ function D( const t = c; for (const t in e) if ( - 'filePath' !== t && - 'disableAssetsAnalyze' !== + 'disableLiveReload' !== + t && + 'disableHotTypesReload' !== t && - 'fileName' !== t && - 'additionalData' !== + 'disableDynamicRemoteTypeHints' !== t ) { const e = { @@ -5100,16 +5081,16 @@ function D( if (t === c) { if ( void 0 !== - e.filePath + e.disableLiveReload ) { const t = c; if ( - 'string' != - typeof e.filePath + 'boolean' != + typeof e.disableLiveReload ) { const e = { params: { - type: 'string', + type: 'boolean', }, }; (null === u @@ -5117,17 +5098,17 @@ function D( : u.push(e), c++); } - var N = t === c; - } else N = !0; - if (N) { + var V = t === c; + } else V = !0; + if (V) { if ( void 0 !== - e.disableAssetsAnalyze + e.disableHotTypesReload ) { const t = c; if ( 'boolean' != - typeof e.disableAssetsAnalyze + typeof e.disableHotTypesReload ) { const e = { params: { @@ -5139,21 +5120,21 @@ function D( : u.push(e), c++); } - N = t === c; - } else N = !0; - if (N) { + V = t === c; + } else V = !0; + if (V) if ( void 0 !== - e.fileName + e.disableDynamicRemoteTypeHints ) { const t = c; if ( - 'string' != - typeof e.fileName + 'boolean' != + typeof e.disableDynamicRemoteTypeHints ) { const e = { params: { - type: 'string', + type: 'boolean', }, }; (null === u @@ -5161,36 +5142,8 @@ function D( : u.push(e), c++); } - N = t === c; - } else N = !0; - if (N) - if ( - void 0 !== - e.additionalData - ) { - const t = c; - if ( - !( - e.additionalData instanceof - Function - ) - ) { - const e = { - params: - {}, - }; - (null === u - ? (u = [ - e, - ]) - : u.push( - e, - ), - c++); - } - N = t === c; - } else N = !0; - } + V = t === c; + } else V = !0; } } } else { @@ -5204,8 +5157,8 @@ function D( : u.push(e), c++); } - ((F = t === c), - (n = n || F)); + ((w = t === c), + (n = n || w)); } if (!n) { const e = { params: {} }; @@ -5226,131 +5179,143 @@ function D( (b = t === c)); } else b = !0; if (b) { - if ( - void 0 !== o.runtimePlugins - ) { - let e = o.runtimePlugins; - const t = c; - if (c === t) { - if (!Array.isArray(e)) - return ( - (D.errors = [ - { - params: { - type: 'array', - }, - }, - ]), - !1 - ); - { - const t = e.length; - for ( - let r = 0; - r < t; - r++ + if (void 0 !== o.manifest) { + let e = o.manifest; + const t = c, + r = c; + let n = !1; + const s = c; + if ('boolean' != typeof e) { + const e = { + params: { + type: 'boolean', + }, + }; + (null === u + ? (u = [e]) + : u.push(e), + c++); + } + var F = s === c; + if (((n = n || F), !n)) { + const t = c; + if (c === t) + if ( + e && + 'object' == + typeof e && + !Array.isArray(e) ) { - let t = e[r]; - const n = c, - s = c; - let o = !1; - const i = c; - if ( - 'string' != typeof t - ) { - const e = { - params: { - type: 'string', - }, - }; - (null === u - ? (u = [e]) - : u.push(e), - c++); - } - var z = i === c; - if ( - ((o = o || z), !o) - ) { - const e = c; - if (c === e) + const t = c; + for (const t in e) + if ( + 'filePath' !== + t && + 'disableAssetsAnalyze' !== + t && + 'fileName' !== + t && + 'additionalData' !== + t + ) { + const e = { + params: { + additionalProperty: + t, + }, + }; + (null === u + ? (u = [e]) + : u.push(e), + c++); + break; + } + if (t === c) { + if ( + void 0 !== + e.filePath + ) { + const t = c; if ( - Array.isArray(t) - ) + 'string' != + typeof e.filePath + ) { + const e = { + params: { + type: 'string', + }, + }; + (null === u + ? (u = [e]) + : u.push(e), + c++); + } + var N = t === c; + } else N = !0; + if (N) { + if ( + void 0 !== + e.disableAssetsAnalyze + ) { + const t = c; if ( - t.length > 2 + 'boolean' != + typeof e.disableAssetsAnalyze ) { const e = { params: { - limit: 2, + type: 'boolean', }, }; (null === u ? (u = [e]) : u.push(e), c++); - } else if ( - t.length < 2 + } + N = t === c; + } else N = !0; + if (N) { + if ( + void 0 !== + e.fileName ) { - const e = { - params: { - limit: 2, - }, - }; - (null === u - ? (u = [e]) - : u.push(e), - c++); - } else { - const e = - t.length; - if (e > 0) { - const e = c; - if ( - 'string' != - typeof t[0] - ) { - const e = - { - params: - { - type: 'string', - }, - }; - (null === - u - ? (u = [ - e, - ]) - : u.push( - e, - ), - c++); - } - var U = - e === c; + const t = c; + if ( + 'string' != + typeof e.fileName + ) { + const e = { + params: { + type: 'string', + }, + }; + (null === u + ? (u = [ + e, + ]) + : u.push( + e, + ), + c++); } + N = t === c; + } else N = !0; + if (N) if ( - U && - e > 1 + void 0 !== + e.additionalData ) { - let e = - t[1]; - const r = c; + const t = c; if ( - !e || - 'object' != - typeof e || - Array.isArray( - e, + !( + e.additionalData instanceof + Function ) ) { const e = { params: - { - type: 'object', - }, + {}, }; (null === u @@ -5362,86 +5327,257 @@ function D( ), c++); } - U = r === c; - } - } - else { - const e = { - params: { - type: 'array', - }, - }; - (null === u - ? (u = [e]) - : u.push(e), - c++); + N = t === c; + } else N = !0; } - ((z = e === c), - (o = o || z)); - } - if (!o) { - const e = { - params: {}, - }; - return ( - null === u - ? (u = [e]) - : u.push(e), - c++, - (D.errors = u), - !1 - ); + } } - if ( - ((c = s), - null !== u && - (s - ? (u.length = s) - : (u = null)), - n !== c) - ) - break; + } else { + const e = { + params: { + type: 'object', + }, + }; + (null === u + ? (u = [e]) + : u.push(e), + c++); } - } + ((F = t === c), + (n = n || F)); + } + if (!n) { + const e = { params: {} }; + return ( + null === u + ? (u = [e]) + : u.push(e), + c++, + (D.errors = u), + !1 + ); } - b = t === c; + ((c = r), + null !== u && + (r + ? (u.length = r) + : (u = null)), + (b = t === c)); } else b = !0; if (b) { if ( - void 0 !== o.getPublicPath + void 0 !== + o.runtimePlugins ) { - const e = c; - if ( - 'string' != - typeof o.getPublicPath - ) - return ( - (D.errors = [ - { - params: { - type: 'string', + let e = o.runtimePlugins; + const t = c; + if (c === t) { + if (!Array.isArray(e)) + return ( + (D.errors = [ + { + params: { + type: 'array', + }, }, - }, - ]), - !1 - ); - b = e === c; + ]), + !1 + ); + { + const t = e.length; + for ( + let r = 0; + r < t; + r++ + ) { + let t = e[r]; + const n = c, + s = c; + let o = !1; + const i = c; + if ( + 'string' != + typeof t + ) { + const e = { + params: { + type: 'string', + }, + }; + (null === u + ? (u = [e]) + : u.push(e), + c++); + } + var z = i === c; + if ( + ((o = o || z), !o) + ) { + const e = c; + if (c === e) + if ( + Array.isArray( + t, + ) + ) + if ( + t.length > 2 + ) { + const e = { + params: { + limit: 2, + }, + }; + (null === u + ? (u = [ + e, + ]) + : u.push( + e, + ), + c++); + } else if ( + t.length < 2 + ) { + const e = { + params: { + limit: 2, + }, + }; + (null === u + ? (u = [ + e, + ]) + : u.push( + e, + ), + c++); + } else { + const e = + t.length; + if (e > 0) { + const e = + c; + if ( + 'string' != + typeof t[0] + ) { + const e = + { + params: + { + type: 'string', + }, + }; + (null === + u + ? (u = + [ + e, + ]) + : u.push( + e, + ), + c++); + } + var U = + e === c; + } + if ( + U && + e > 1 + ) { + let e = + t[1]; + const r = + c; + if ( + !e || + 'object' != + typeof e || + Array.isArray( + e, + ) + ) { + const e = + { + params: + { + type: 'object', + }, + }; + (null === + u + ? (u = + [ + e, + ]) + : u.push( + e, + ), + c++); + } + U = + r === c; + } + } + else { + const e = { + params: { + type: 'array', + }, + }; + (null === u + ? (u = [e]) + : u.push(e), + c++); + } + ((z = e === c), + (o = o || z)); + } + if (!o) { + const e = { + params: {}, + }; + return ( + null === u + ? (u = [e]) + : u.push(e), + c++, + (D.errors = u), + !1 + ); + } + if ( + ((c = s), + null !== u && + (s + ? (u.length = + s) + : (u = null)), + n !== c) + ) + break; + } + } + } + b = t === c; } else b = !0; if (b) { if ( void 0 !== - o.dataPrefetch + o.getPublicPath ) { const e = c; if ( - 'boolean' != - typeof o.dataPrefetch + 'string' != + typeof o.getPublicPath ) return ( (D.errors = [ { params: { - type: 'boolean', + type: 'string', }, }, ]), @@ -5449,21 +5585,21 @@ function D( ); b = e === c; } else b = !0; - if (b) + if (b) { if ( void 0 !== - o.implementation + o.dataPrefetch ) { const e = c; if ( - 'string' != - typeof o.implementation + 'boolean' != + typeof o.dataPrefetch ) return ( (D.errors = [ { params: { - type: 'string', + type: 'boolean', }, }, ]), @@ -5471,6 +5607,29 @@ function D( ); b = e === c; } else b = !0; + if (b) + if ( + void 0 !== + o.implementation + ) { + const e = c; + if ( + 'string' != + typeof o.implementation + ) + return ( + (D.errors = [ + { + params: { + type: 'string', + }, + }, + ]), + !1 + ); + b = e === c; + } else b = !0; + } } } } diff --git a/packages/enhanced/src/schemas/container/ModuleFederationPlugin.json b/packages/enhanced/src/schemas/container/ModuleFederationPlugin.json index 066e205da0e..80282ed815c 100644 --- a/packages/enhanced/src/schemas/container/ModuleFederationPlugin.json +++ b/packages/enhanced/src/schemas/container/ModuleFederationPlugin.json @@ -708,6 +708,21 @@ "enum": ["version-first", "loaded-first"], "type": "string" }, + "security": { + "description": "Runtime security options for remote loading.", + "type": "object", + "properties": { + "allowedRemoteOrigins": { + "description": "Allowed remote entry origins. Leave unset to keep the default behavior.", + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + } + }, + "additionalProperties": true + }, "shared": { "$ref": "#/definitions/Shared" }, diff --git a/packages/enhanced/src/schemas/container/ModuleFederationPlugin.ts b/packages/enhanced/src/schemas/container/ModuleFederationPlugin.ts index 0d33acb00cb..8f5a8b26e1a 100644 --- a/packages/enhanced/src/schemas/container/ModuleFederationPlugin.ts +++ b/packages/enhanced/src/schemas/container/ModuleFederationPlugin.ts @@ -785,6 +785,22 @@ export default { enum: ['version-first', 'loaded-first'], type: 'string', }, + security: { + description: 'Runtime security options for remote loading.', + type: 'object', + properties: { + allowedRemoteOrigins: { + description: + 'Allowed remote entry origins. Leave unset to keep the default behavior.', + type: 'array', + items: { + type: 'string', + minLength: 1, + }, + }, + }, + additionalProperties: true, + }, shared: { $ref: '#/definitions/Shared', }, diff --git a/packages/enhanced/test/unit/container/runtime-utils.test.ts b/packages/enhanced/test/unit/container/runtime-utils.test.ts new file mode 100644 index 00000000000..05532fd9f1a --- /dev/null +++ b/packages/enhanced/test/unit/container/runtime-utils.test.ts @@ -0,0 +1,38 @@ +/* + * @rstest-environment node + */ + +import { normalizeRuntimeInitOptionsWithOutShared } from '../../../src/lib/container/runtime/utils'; + +describe('normalizeRuntimeInitOptionsWithOutShared', () => { + it('includes the full security options when configured', () => { + const allowedRemoteOrigins = ['localhost', 'https://cdn.example.com']; + const security = { + allowedRemoteOrigins, + customPolicy: { + mode: 'strict', + }, + }; + const options = normalizeRuntimeInitOptionsWithOutShared({ + name: 'test-container', + remotes: {}, + security, + } as any); + + expect(options.security).toEqual(security); + expect(options.security).not.toBe(security); + expect(options.security?.allowedRemoteOrigins).not.toBe( + allowedRemoteOrigins, + ); + expect(options.security?.customPolicy).not.toBe(security.customPolicy); + }); + + it('omits security when it is not configured', () => { + const options = normalizeRuntimeInitOptionsWithOutShared({ + name: 'test-container', + remotes: {}, + } as any); + + expect(options.security).toBeUndefined(); + }); +}); diff --git a/packages/runtime-core/src/core.ts b/packages/runtime-core/src/core.ts index c7148aa4182..4e3f9e75359 100644 --- a/packages/runtime-core/src/core.ts +++ b/packages/runtime-core/src/core.ts @@ -50,6 +50,36 @@ const USE_SNAPSHOT = ? !FEDERATION_OPTIMIZE_NO_SNAPSHOT_PLUGIN : true; // Default to true (use snapshot) when not explicitly defined +const lockSecurityOptions = (options: Options): void => { + const security = options.security; + if (!security || typeof security !== 'object') { + return; + } + + const allowedRemoteOrigins = (security as any).allowedRemoteOrigins; + if ( + Array.isArray(allowedRemoteOrigins) && + !Object.isFrozen(allowedRemoteOrigins) + ) { + Object.freeze(allowedRemoteOrigins); + } + + if (!Object.isFrozen(security)) { + Object.freeze(security); + } + + try { + Object.defineProperty(options, 'security', { + value: security, + enumerable: true, + configurable: false, + writable: false, + }); + } catch { + // ignore errors for readonly / non-configurable options objects + } +}; + export class ModuleFederation { options: Options; hooks = new PluginSystem({ @@ -216,6 +246,7 @@ export class ModuleFederation { ...(userOptions.plugins || []), ]); this.options = this.formatOptions(defaultOptions, userOptions); + lockSecurityOptions(this.options); } initOptions(userOptions: UserOptions): Options { @@ -226,6 +257,7 @@ export class ModuleFederation { const options = this.formatOptions(this.options, userOptions); this.options = options; + lockSecurityOptions(this.options); return options; } diff --git a/packages/runtime-core/src/type/config.ts b/packages/runtime-core/src/type/config.ts index 98c6c357bbf..6feaf2dd364 100644 --- a/packages/runtime-core/src/type/config.ts +++ b/packages/runtime-core/src/type/config.ts @@ -122,6 +122,19 @@ export type ShareInfos = { [pkgName: string]: Shared[]; }; +export interface SecurityOptions { + /** + * A whitelist for remote entry origins. + * + * - When unset or empty, remote entry loading behavior is unchanged. + * - When set to `['*']`, it allows loading from any origin. + * - Items can be a hostname (e.g. `example.com`), a host with port + * (e.g. `localhost:3001`), an origin (e.g. `https://example.com`), + * or a regex literal (e.g. `/^https:\\/\\/example\\.com$/`). + */ + allowedRemoteOrigins?: string[]; +} + export interface Options { id?: string; name: string; @@ -131,6 +144,7 @@ export interface Options { plugins: Array; inBrowser: boolean; shareStrategy?: ShareStrategy; + security?: SecurityOptions; } export type UserOptions = Omit< @@ -140,6 +154,7 @@ export type UserOptions = Omit< shared?: { [pkgName: string]: ShareArgs | ShareArgs[]; }; + security?: SecurityOptions; }; export type LoadModuleOptions = { diff --git a/packages/runtime-core/src/utils/load.ts b/packages/runtime-core/src/utils/load.ts index 22be1e71bd3..fa6397525e6 100644 --- a/packages/runtime-core/src/utils/load.ts +++ b/packages/runtime-core/src/utils/load.ts @@ -8,7 +8,7 @@ import { DEFAULT_REMOTE_TYPE, DEFAULT_SCOPE } from '../constant'; import { ModuleFederation } from '../core'; import { globalLoading, getRemoteEntryExports } from '../global'; import { Remote, RemoteEntryExports, RemoteInfo } from '../type'; -import { assert, error } from './logger'; +import { error } from './logger'; import { RUNTIME_001, RUNTIME_008, @@ -19,16 +19,142 @@ import { declare const ENV_TARGET: 'web' | 'node'; const importCallback = '.then(callbacks[0]).catch(callbacks[1])'; +function getDefaultRemoteProtocol(): 'http:' | 'https:' { + if ( + typeof globalThis !== 'undefined' && + typeof globalThis.location?.protocol === 'string' && + (globalThis.location.protocol === 'http:' || + globalThis.location.protocol === 'https:') + ) { + return globalThis.location.protocol; + } + + return 'https:'; +} + +function parseNetworkRemoteUrl(entryUrl: string): URL | undefined { + const isNetworkRemote = + entryUrl.startsWith('//') || + entryUrl.startsWith('http://') || + entryUrl.startsWith('https://'); + + if (!isNetworkRemote) { + return; + } + + const normalizedEntryUrl = entryUrl.startsWith('//') + ? `${getDefaultRemoteProtocol()}${entryUrl}` + : entryUrl; + + const parsed = new URL(normalizedEntryUrl); + + if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') { + return; + } + + return parsed; +} + +function assertRemoteOriginAllowed( + entryUrl: string, + allowedRemoteOrigins?: string[], +): void { + if (!allowedRemoteOrigins || allowedRemoteOrigins.length === 0) { + return; + } + + let parsed: URL | undefined; + try { + parsed = parseNetworkRemoteUrl(entryUrl); + } catch { + throw new Error( + `Remote origin "${entryUrl}" is not allowed by security.allowedRemoteOrigins.`, + ); + } + + if (!parsed) { + // Only enforce origin checks for network-loaded http(s) URLs. + return; + } + + if (allowedRemoteOrigins.includes('*')) { + return; + } + + const origin = parsed.origin; + const host = parsed.host; + const hostname = parsed.hostname; + + const isAllowed = allowedRemoteOrigins.some((rawPattern) => { + const pattern = rawPattern.trim(); + + if (!pattern) { + return false; + } + + if (pattern === '*') { + return true; + } + + // Regex literal pattern, e.g. `/^https:\\/\\/example\\.com$/` or `/foo/i` + if ( + pattern.length > 2 && + pattern.startsWith('/') && + pattern.lastIndexOf('/') > 0 + ) { + const lastSlashIndex = pattern.lastIndexOf('/'); + const source = pattern.slice(1, lastSlashIndex); + const flags = pattern.slice(lastSlashIndex + 1); + + try { + const regex = new RegExp(source, flags as any); + return regex.test(origin) || regex.test(entryUrl); + } catch { + // Ignore invalid regex patterns + return false; + } + } + + if (pattern.includes('://')) { + try { + return new URL(pattern).origin === origin; + } catch { + return false; + } + } + + if (pattern.includes(':')) { + return host === pattern; + } + + // Hostname match: exact or subdomain of the pattern + if (hostname === pattern || hostname.endsWith(`.${pattern}`)) { + return true; + } + + return false; + }); + + if (!isAllowed) { + throw new Error( + `Remote origin "${origin}" is not allowed by security.allowedRemoteOrigins.`, + ); + } +} + async function loadEsmEntry({ entry, remoteEntryExports, + allowedRemoteOrigins, }: { entry: string; remoteEntryExports: RemoteEntryExports | undefined; + allowedRemoteOrigins?: string[]; }): Promise { return new Promise((resolve, reject) => { try { if (!remoteEntryExports) { + assertRemoteOriginAllowed(entry, allowedRemoteOrigins); if (typeof FEDERATION_ALLOW_NEW_FUNCTION !== 'undefined') { new Function('callbacks', `import("${entry}")${importCallback}`)([ resolve, @@ -52,13 +178,16 @@ async function loadEsmEntry({ async function loadSystemJsEntry({ entry, remoteEntryExports, + allowedRemoteOrigins, }: { entry: string; remoteEntryExports: RemoteEntryExports | undefined; + allowedRemoteOrigins?: string[]; }): Promise { return new Promise((resolve, reject) => { try { if (!remoteEntryExports) { + assertRemoteOriginAllowed(entry, allowedRemoteOrigins); //@ts-ignore if (typeof __system_context__ === 'undefined') { //@ts-ignore @@ -107,6 +236,7 @@ async function loadEntryScript({ remoteInfo, loaderHook, getEntryUrl, + allowedRemoteOrigins, }: { name: string; globalName: string; @@ -114,6 +244,7 @@ async function loadEntryScript({ remoteInfo: RemoteInfo; loaderHook: ModuleFederation['loaderHook']; getEntryUrl?: (url: string) => string; + allowedRemoteOrigins?: string[]; }): Promise { const { entryExports: remoteEntryExports } = getRemoteEntryExports( name, @@ -126,6 +257,7 @@ async function loadEntryScript({ // if getEntryUrl is passed, use the getEntryUrl to get the entry url const url = getEntryUrl ? getEntryUrl(entry) : entry; + assertRemoteOriginAllowed(url, allowedRemoteOrigins); return loadScript(url, { attrs: {}, createScriptHook: (url, attrs) => { @@ -178,19 +310,29 @@ async function loadEntryDom({ remoteEntryExports, loaderHook, getEntryUrl, + allowedRemoteOrigins, }: { remoteInfo: RemoteInfo; remoteEntryExports?: RemoteEntryExports; loaderHook: ModuleFederation['loaderHook']; getEntryUrl?: (url: string) => string; + allowedRemoteOrigins?: string[]; }) { const { entry, entryGlobalName: globalName, name, type } = remoteInfo; switch (type) { case 'esm': case 'module': - return loadEsmEntry({ entry, remoteEntryExports }); + return loadEsmEntry({ + entry, + remoteEntryExports, + allowedRemoteOrigins, + }); case 'system': - return loadSystemJsEntry({ entry, remoteEntryExports }); + return loadSystemJsEntry({ + entry, + remoteEntryExports, + allowedRemoteOrigins, + }); default: return loadEntryScript({ entry, @@ -199,6 +341,7 @@ async function loadEntryDom({ remoteInfo, loaderHook, getEntryUrl, + allowedRemoteOrigins, }); } } @@ -206,9 +349,11 @@ async function loadEntryDom({ async function loadEntryNode({ remoteInfo, loaderHook, + allowedRemoteOrigins, }: { remoteInfo: RemoteInfo; loaderHook: ModuleFederation['loaderHook']; + allowedRemoteOrigins?: string[]; }) { const { entry, entryGlobalName: globalName, name, type } = remoteInfo; const { entryExports: remoteEntryExports } = getRemoteEntryExports( @@ -220,6 +365,8 @@ async function loadEntryNode({ return remoteEntryExports; } + assertRemoteOriginAllowed(entry, allowedRemoteOrigins); + return loadScriptNode(entry, { attrs: { name, globalName, type }, loaderHook: { @@ -270,6 +417,12 @@ export async function getRemoteEntry(params: { getEntryUrl, _inErrorHandling = false, } = params; + const allowedRemoteOrigins = origin.options.security?.allowedRemoteOrigins; + + if (allowedRemoteOrigins && allowedRemoteOrigins.length) { + assertRemoteOriginAllowed(remoteInfo.entry, allowedRemoteOrigins); + } + const uniqueKey = getRemoteEntryUniqueKey(remoteInfo); if (remoteEntryExports) { return remoteEntryExports; @@ -301,8 +454,13 @@ export async function getRemoteEntry(params: { remoteEntryExports, loaderHook, getEntryUrl, + allowedRemoteOrigins, }) - : loadEntryNode({ remoteInfo, loaderHook }); + : loadEntryNode({ + remoteInfo, + loaderHook, + allowedRemoteOrigins, + }); }) .catch(async (err) => { const uniqueKey = getRemoteEntryUniqueKey(remoteInfo); diff --git a/packages/runtime/__tests__/load-remote.spec.ts b/packages/runtime/__tests__/load-remote.spec.ts index 6801cd1dc64..60b91f08723 100644 --- a/packages/runtime/__tests__/load-remote.spec.ts +++ b/packages/runtime/__tests__/load-remote.spec.ts @@ -1,7 +1,6 @@ import 'whatwg-fetch'; import { assert, describe, it } from 'vitest'; import { ModuleFederation, init } from '../src/index'; -import { mockRemoteSnapshot } from './mock/utils'; import { matchRemoteWithNameAndExpose } from '@module-federation/runtime-core'; import { addGlobalSnapshot, @@ -604,3 +603,159 @@ describe('loadRemote', () => { reset(); }); }); + +describe('security.allowedRemoteOrigins', () => { + it('does not affect remote loading when allowedRemoteOrigins is not configured', async () => { + const federationInstance = new ModuleFederation({ + name: '@federation-test/security-default', + remotes: [ + { + name: '__FEDERATION_@federation-test/app2:custom__', + alias: 'app2', + entry: + 'http://localhost:1111/resources/app2/federation-remote-entry.js', + }, + ], + }); + + const module = + await federationInstance.loadRemote<() => string>('app2/say'); + assert(module, 'module should be a function'); + expect(module()).toBe('hello app2'); + }); + + it('allows remote when origin hostname is in allowedRemoteOrigins', async () => { + const federationInstance = new ModuleFederation({ + name: '@federation-test/security-host', + remotes: [ + { + name: '__FEDERATION_@federation-test/app2:custom__', + alias: 'app2', + entry: + 'http://localhost:1111/resources/app2/federation-remote-entry.js', + }, + ], + security: { + allowedRemoteOrigins: ['localhost'], + }, + }); + + const module = + await federationInstance.loadRemote<() => string>('app2/say'); + assert(module, 'module should be a function'); + expect(module()).toBe('hello app2'); + }); + + it('allows remote when origin matches regex in allowedRemoteOrigins', async () => { + const federationInstance = new ModuleFederation({ + name: '@federation-test/security-regex', + remotes: [ + { + name: '__FEDERATION_@federation-test/app2:custom__', + alias: 'app2', + entry: + 'http://localhost:1111/resources/app2/federation-remote-entry.js', + }, + ], + security: { + allowedRemoteOrigins: ['/localhost/'], + }, + }); + + const module = + await federationInstance.loadRemote<() => string>('app2/say'); + assert(module, 'module should be a function'); + expect(module()).toBe('hello app2'); + }); + + it('allows any remote when allowedRemoteOrigins includes *', async () => { + const federationInstance = new ModuleFederation({ + name: '@federation-test/security-wildcard', + remotes: [ + { + name: '__FEDERATION_@federation-test/app2:custom__', + alias: 'app2', + entry: + 'http://untrusted.example.test:1111/resources/app2/federation-remote-entry.js', + }, + ], + security: { + allowedRemoteOrigins: ['*'], + }, + }); + + const module = + await federationInstance.loadRemote<() => string>('app2/say'); + assert(module, 'module should be a function'); + expect(module()).toBe('hello app2'); + }); + + it('blocks remote when origin hostname is not in allowedRemoteOrigins', async () => { + const federationInstance = new ModuleFederation({ + name: '@federation-test/security-block-host', + remotes: [ + { + name: '__FEDERATION_@federation-test/app2:custom__', + alias: 'app2', + entry: + 'http://untrusted.example.test:1111/resources/app2/federation-remote-entry.js', + }, + ], + security: { + allowedRemoteOrigins: ['localhost'], + }, + }); + + await expect( + federationInstance.loadRemote<() => string>('app2/say'), + ).rejects.toThrow( + 'Remote origin "http://untrusted.example.test:1111" is not allowed by security.allowedRemoteOrigins.', + ); + }); + + it('does not treat origin entries as string prefixes', async () => { + const federationInstance = new ModuleFederation({ + name: '@federation-test/security-origin-prefix', + remotes: [ + { + name: '__FEDERATION_@federation-test/app2:custom__', + alias: 'app2', + entry: + 'http://localhost.attacker.test:1111/resources/app2/federation-remote-entry.js', + }, + ], + security: { + allowedRemoteOrigins: ['http://localhost'], + }, + }); + + await expect( + federationInstance.loadRemote<() => string>('app2/say'), + ).rejects.toThrow( + 'Remote origin "http://localhost.attacker.test:1111" is not allowed by security.allowedRemoteOrigins.', + ); + }); + + it('checks protocol-relative network URLs when allowedRemoteOrigins is configured', async () => { + const federationInstance = new ModuleFederation({ + name: '@federation-test/security-protocol-relative', + remotes: [ + { + name: '__FEDERATION_@federation-test/app2:custom__', + alias: 'app2', + entry: + '//protocol-relative.example.test:1111/federation-remote-entry.js', + }, + ], + security: { + allowedRemoteOrigins: ['localhost'], + }, + }); + + await expect( + federationInstance.loadRemote<() => string>('app2/say'), + ).rejects.toThrow( + /Remote origin "https?:\/\/protocol-relative\.example\.test:1111" is not allowed by security.allowedRemoteOrigins\./, + ); + }); +}); diff --git a/packages/sdk/src/types/plugins/ModuleFederationPlugin.ts b/packages/sdk/src/types/plugins/ModuleFederationPlugin.ts index a9340b21711..0b82ab81bd3 100644 --- a/packages/sdk/src/types/plugins/ModuleFederationPlugin.ts +++ b/packages/sdk/src/types/plugins/ModuleFederationPlugin.ts @@ -462,6 +462,16 @@ export interface ModuleFederationPluginOptions { * load shared strategy(defaults to 'version-first'). */ shareStrategy?: SharedStrategy; + /** + * Runtime security options for remote loading. + */ + security?: { + [key: string]: unknown; + /** + * Allowed remote entry origins. Leave unset to keep the default behavior. + */ + allowedRemoteOrigins?: string[]; + }; /** * Modules that should be shared in the share scope. When provided, property names are used to match requested modules in this compilation. */