diff --git a/public/app/features/plugins/sandbox/code_loader.ts b/public/app/features/plugins/sandbox/code_loader.ts index 04bd418d33a..72d8a524d7f 100644 --- a/public/app/features/plugins/sandbox/code_loader.ts +++ b/public/app/features/plugins/sandbox/code_loader.ts @@ -52,7 +52,12 @@ export async function getPluginCode(meta: SandboxPluginMeta): Promise { // 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 { // 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 { } } +async function verifySRI(pluginCode: string, moduleHash?: string): Promise { + 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, }; } } diff --git a/public/app/features/plugins/sandbox/types.ts b/public/app/features/plugins/sandbox/types.ts index 3b9e104f801..8db6fd1b05e 100644 --- a/public/app/features/plugins/sandbox/types.ts +++ b/public/app/features/plugins/sandbox/types.ts @@ -11,4 +11,4 @@ export type SandboxedPluginObject = { export type SandboxEnvironment = ReturnType; -export type SandboxPluginMeta = Pick; +export type SandboxPluginMeta = Pick;