Skip to content

Commit 9e3b5bd

Browse files
committed
fix(runtime): enforce network policy on preserved preact imports
1 parent d4eaf6c commit 9e3b5bd

2 files changed

Lines changed: 76 additions & 8 deletions

File tree

packages/runtime/src/runtime-source-module-loader.ts

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,11 @@ export class RuntimeSourceModuleLoader {
175175
}
176176

177177
if (isBrowserRuntime() && this.shouldPreserveRemoteImport(normalizedUrl)) {
178+
if (!this.isRemoteImportAllowed(normalizedUrl)) {
179+
throw new Error(
180+
`Remote module URL is blocked by runtime network policy: ${normalizedUrl}`,
181+
);
182+
}
178183
return normalizedUrl;
179184
}
180185

@@ -355,21 +360,27 @@ export class RuntimeSourceModuleLoader {
355360
private filterDisallowedAttempts(attempts: string[]): string[] {
356361
const allowed: string[] = [];
357362
for (const attempt of attempts) {
358-
if (this.isRemoteUrlAllowedFn(attempt)) {
363+
if (this.isRemoteImportAllowed(attempt)) {
359364
allowed.push(attempt);
360-
continue;
361365
}
362-
363-
this.diagnostics.push({
364-
level: "warning",
365-
code: "RUNTIME_SOURCE_IMPORT_BLOCKED",
366-
message: `Blocked remote module URL by runtime network policy: ${attempt}`,
367-
});
368366
}
369367

370368
return allowed;
371369
}
372370

371+
private isRemoteImportAllowed(url: string): boolean {
372+
if (this.isRemoteUrlAllowedFn(url)) {
373+
return true;
374+
}
375+
376+
this.diagnostics.push({
377+
level: "warning",
378+
code: "RUNTIME_SOURCE_IMPORT_BLOCKED",
379+
message: `Blocked remote module URL by runtime network policy: ${url}`,
380+
});
381+
return false;
382+
}
383+
373384
private errorToMessage(error: unknown): string {
374385
if (error instanceof Error) {
375386
return error.message;

tests/runtime.test.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1869,6 +1869,63 @@ test("runtime source loader preserves preact remote imports in browser runtime",
18691869
}
18701870
});
18711871

1872+
test("runtime source loader blocks preserved preact imports by runtime network policy", async () => {
1873+
class BrowserWorkerMock {}
1874+
const restoreGlobals = installBrowserSandboxGlobals(BrowserWorkerMock);
1875+
const runtime = new DefaultRuntimeManager({
1876+
remoteFallbackCdnBases: [],
1877+
remoteFetchRetries: 0,
1878+
remoteFetchBackoffMs: 10,
1879+
remoteFetchTimeoutMs: 500,
1880+
allowArbitraryNetwork: false,
1881+
allowedNetworkHosts: ["cdn.jspm.io"],
1882+
});
1883+
1884+
const internals = runtime as unknown as {
1885+
createSourceModuleLoader: (
1886+
moduleManifest: RuntimeModuleManifest | undefined,
1887+
diagnostics: Array<{ code?: string; message?: string }>,
1888+
) => {
1889+
materializeRemoteModule(url: string): Promise<string>;
1890+
};
1891+
};
1892+
1893+
const preactUrl =
1894+
"https://ga.jspm.io/npm:preact@10.28.3/hooks/dist/hooks.module.js";
1895+
const diagnostics: Array<{ code?: string; message?: string }> = [];
1896+
const loader = internals.createSourceModuleLoader(undefined, diagnostics);
1897+
1898+
let fetchCount = 0;
1899+
const originalFetch = globalThis.fetch;
1900+
globalThis.fetch = (async (_input: RequestInfo | URL) => {
1901+
fetchCount += 1;
1902+
return new Response("export default 1;", {
1903+
status: 200,
1904+
headers: {
1905+
"content-type": "text/javascript; charset=utf-8",
1906+
},
1907+
});
1908+
}) as typeof fetch;
1909+
1910+
try {
1911+
await assert.rejects(
1912+
loader.materializeRemoteModule(preactUrl),
1913+
/Remote module URL is blocked by runtime network policy/,
1914+
);
1915+
assert.equal(fetchCount, 0);
1916+
assert.ok(
1917+
diagnostics.some(
1918+
(item) =>
1919+
item.code === "RUNTIME_SOURCE_IMPORT_BLOCKED" &&
1920+
item.message?.includes(preactUrl),
1921+
),
1922+
);
1923+
} finally {
1924+
globalThis.fetch = originalFetch;
1925+
restoreGlobals();
1926+
}
1927+
});
1928+
18721929
test("runtime source loader materializes preact remote imports outside browser runtime", async () => {
18731930
const runtime = new DefaultRuntimeManager({
18741931
remoteFallbackCdnBases: [],

0 commit comments

Comments
 (0)