Skip to content

Commit b5f6763

Browse files
fix(rsc-mf): harden static path and bridge load dedupe
Prevent static middleware path traversal-by-reset by resolving requests under the bundles root with explicit containment checks, and make bridge loading dedupe atomic by caching a pending promise before triggering remote load side effects. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 27af5a7 commit b5f6763

2 files changed

Lines changed: 20 additions & 7 deletions

File tree

packages/modernjs-v3/src/cli/mfRuntimePlugins/rsc-bridge-runtime-plugin.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -289,9 +289,16 @@ const rscBridgeRuntimePlugin = (): ModuleFederationRuntimePlugin => {
289289
'[modern-js-v3:rsc-bridge] Module Federation runtime instance is unavailable while loading the RSC bridge',
290290
);
291291
}
292-
const bridgePromise = Promise.resolve(
293-
runtimeInstance.loadRemote(`${alias}/${RSC_BRIDGE_EXPOSE}`),
294-
)
292+
let resolveBridge!: (bridge: BridgeModule) => void;
293+
let rejectBridge!: (error: unknown) => void;
294+
const bridgePromise = new Promise<BridgeModule>((resolve, reject) => {
295+
resolveBridge = resolve;
296+
rejectBridge = reject;
297+
});
298+
bridgePromises[alias] = bridgePromise;
299+
300+
void Promise.resolve()
301+
.then(() => runtimeInstance.loadRemote(`${alias}/${RSC_BRIDGE_EXPOSE}`))
295302
.then((bridge: BridgeModule) => {
296303
if (
297304
!bridge ||
@@ -302,14 +309,14 @@ const rscBridgeRuntimePlugin = (): ModuleFederationRuntimePlugin => {
302309
`[modern-js-v3:rsc-bridge] Remote "${alias}" is missing the internal RSC bridge expose`,
303310
);
304311
}
305-
return bridge;
312+
resolveBridge(bridge);
306313
})
307314
.catch((error: unknown) => {
308315
// Allow retry when bridge loading fails transiently.
309316
delete bridgePromises[alias];
310-
throw error;
317+
rejectBridge(error);
311318
});
312-
bridgePromises[alias] = bridgePromise;
319+
313320
return bridgePromise;
314321
};
315322

packages/modernjs-v3/src/server/staticMiddleware.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ const createStaticMiddleware = (options: {
2626
pwd: string;
2727
}): MiddlewareHandler => {
2828
const { assetPrefix, pwd } = options;
29+
const bundlesRootDir = path.resolve(pwd, `.${bundlesAssetPrefix}`);
2930

3031
return async (c, next) => {
3132
const pathname = c.req.path;
@@ -47,7 +48,12 @@ const createStaticMiddleware = (options: {
4748
}
4849

4950
const pathnameWithoutPrefix = pathname.replace(prefixWithBundle, '');
50-
const filepath = path.join(pwd, bundlesAssetPrefix, pathnameWithoutPrefix);
51+
const relativeBundlePath = pathnameWithoutPrefix.replace(/^\/+/, '');
52+
const filepath = path.resolve(bundlesRootDir, relativeBundlePath);
53+
const allowedPrefix = `${bundlesRootDir}${path.sep}`;
54+
if (filepath !== bundlesRootDir && !filepath.startsWith(allowedPrefix)) {
55+
return next();
56+
}
5157
if (!(await fs.pathExists(filepath))) {
5258
return next();
5359
}

0 commit comments

Comments
 (0)