Sandbox: Add SRI validation for CDN assets loaded in the sandbox (#99023)

Add SRI validation for frontend sandbox
This commit is contained in:
Esteban Beltran 2025-01-17 11:47:36 +01:00 committed by GitHub
parent f9cc08a42d
commit f65e9fa9ab
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 33 additions and 1 deletions

View File

@ -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
const url = meta.module;
const response = await fetch(url);
let pluginCode = await response.text();
if (!verifySRI(pluginCode, meta.moduleHash)) {
throw new Error('Invalid SRI for plugin module file');
}
pluginCode = transformPluginSourceForCDN({
url,
source: pluginCode,
@ -66,7 +71,12 @@ export async function getPluginCode(meta: SandboxPluginMeta): Promise<string> {
// to ensure correct cached version is served for local plugins
const pluginCodeUrl = resolveWithCache(modulePath);
const response = await fetch(pluginCodeUrl);
let pluginCode = await response.text();
if (!verifySRI(pluginCode, meta.moduleHash)) {
throw new Error('Invalid SRI for plugin module file');
}
pluginCode = transformPluginSourceForCDN({
url: pluginCodeUrl,
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 {
return pluginCode.replace(/window\.location/gi, 'window.locationSandbox');
}
@ -113,6 +144,7 @@ export function getPluginLoadData(pluginId: string): SandboxPluginMeta {
id: pluginId,
type: PluginType.app,
module: app.path,
moduleHash: app.moduleHash,
};
}
}

View File

@ -11,4 +11,4 @@ export type SandboxedPluginObject = {
export type SandboxEnvironment = ReturnType<typeof createVirtualEnvironment>;
export type SandboxPluginMeta = Pick<PluginMeta, 'id' | 'type' | 'module'>;
export type SandboxPluginMeta = Pick<PluginMeta, 'id' | 'type' | 'module' | 'moduleHash'>;