Sandbox: Add SRI validation for CDN assets loaded in the sandbox (#99023)
Add SRI validation for frontend sandbox
This commit is contained in:
@@ -52,7 +52,12 @@ export async function getPluginCode(meta: SandboxPluginMeta): Promise<string> {
|
|||||||
// Load plugin from CDN, no need for "resolveWithCache" as CDN URLs already include the version
|
// Load plugin from CDN, no need for "resolveWithCache" as CDN URLs already include the version
|
||||||
const url = meta.module;
|
const url = meta.module;
|
||||||
const response = await fetch(url);
|
const response = await fetch(url);
|
||||||
|
|
||||||
let pluginCode = await response.text();
|
let pluginCode = await response.text();
|
||||||
|
if (!verifySRI(pluginCode, meta.moduleHash)) {
|
||||||
|
throw new Error('Invalid SRI for plugin module file');
|
||||||
|
}
|
||||||
|
|
||||||
pluginCode = transformPluginSourceForCDN({
|
pluginCode = transformPluginSourceForCDN({
|
||||||
url,
|
url,
|
||||||
source: pluginCode,
|
source: pluginCode,
|
||||||
@@ -66,7 +71,12 @@ export async function getPluginCode(meta: SandboxPluginMeta): Promise<string> {
|
|||||||
// to ensure correct cached version is served for local plugins
|
// to ensure correct cached version is served for local plugins
|
||||||
const pluginCodeUrl = resolveWithCache(modulePath);
|
const pluginCodeUrl = resolveWithCache(modulePath);
|
||||||
const response = await fetch(pluginCodeUrl);
|
const response = await fetch(pluginCodeUrl);
|
||||||
|
|
||||||
let pluginCode = await response.text();
|
let pluginCode = await response.text();
|
||||||
|
if (!verifySRI(pluginCode, meta.moduleHash)) {
|
||||||
|
throw new Error('Invalid SRI for plugin module file');
|
||||||
|
}
|
||||||
|
|
||||||
pluginCode = transformPluginSourceForCDN({
|
pluginCode = transformPluginSourceForCDN({
|
||||||
url: pluginCodeUrl,
|
url: pluginCodeUrl,
|
||||||
source: pluginCode,
|
source: pluginCode,
|
||||||
@@ -78,6 +88,27 @@ export async function getPluginCode(meta: SandboxPluginMeta): Promise<string> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function verifySRI(pluginCode: string, moduleHash?: string): Promise<boolean> {
|
||||||
|
if (!config.featureToggles.pluginsSriChecks) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!moduleHash || moduleHash.length === 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [algorithm, _] = moduleHash.split('-');
|
||||||
|
const cleanAlgorithm = algorithm.replace('sha', 'SHA-');
|
||||||
|
|
||||||
|
const encoder = new TextEncoder();
|
||||||
|
const data = encoder.encode(pluginCode);
|
||||||
|
|
||||||
|
const digest = await crypto.subtle.digest(cleanAlgorithm, data);
|
||||||
|
const actualHash = btoa(String.fromCharCode(...new Uint8Array(digest)));
|
||||||
|
|
||||||
|
return `${algorithm}-${actualHash}` === moduleHash;
|
||||||
|
}
|
||||||
|
|
||||||
function patchPluginAPIs(pluginCode: string): string {
|
function patchPluginAPIs(pluginCode: string): string {
|
||||||
return pluginCode.replace(/window\.location/gi, 'window.locationSandbox');
|
return pluginCode.replace(/window\.location/gi, 'window.locationSandbox');
|
||||||
}
|
}
|
||||||
@@ -113,6 +144,7 @@ export function getPluginLoadData(pluginId: string): SandboxPluginMeta {
|
|||||||
id: pluginId,
|
id: pluginId,
|
||||||
type: PluginType.app,
|
type: PluginType.app,
|
||||||
module: app.path,
|
module: app.path,
|
||||||
|
moduleHash: app.moduleHash,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,4 +11,4 @@ export type SandboxedPluginObject = {
|
|||||||
|
|
||||||
export type SandboxEnvironment = ReturnType<typeof createVirtualEnvironment>;
|
export type SandboxEnvironment = ReturnType<typeof createVirtualEnvironment>;
|
||||||
|
|
||||||
export type SandboxPluginMeta = Pick<PluginMeta, 'id' | 'type' | 'module'>;
|
export type SandboxPluginMeta = Pick<PluginMeta, 'id' | 'type' | 'module' | 'moduleHash'>;
|
||||||
|
|||||||
Reference in New Issue
Block a user