Context Security Policy Error is Blocking meteor_runtime_config?

CSP requires the hash of the meteor_runtime_config to be included in csp policies.

I prepare the hash like this, as described in the Meteor docs:

// Prepare runtime config for generating the sha256 hash
// It is important, that the hash meets exactly the hash of the
// script in the client bundle.
// Otherwise the app would not be able to start, since the runtimeConfigScript
// is rejected __meteor_runtime_config__ is not available, causing
// a cascade of follow-up errors.
const runtimeConfig = Object.assign(__meteor_runtime_config__, Autoupdate, {
    // the following lines may depend on, whether you called Accounts.config
    // and whether your Meteor app is a "newer" version
    accountsConfigCalled: true,
    isModern: true
})

// add client versions to __meteor_runtime_config__
Object.keys(WebApp.clientPrograms).forEach(arch => {
    __meteor_runtime_config__.versions[arch] = {
        version: Autoupdate.autoupdateVersion || WebApp.clientPrograms[arch].version(),
        versionRefreshable: Autoupdate.autoupdateVersion || WebApp.clientPrograms[arch].versionRefreshable(),
        versionNonRefreshable: Autoupdate.autoupdateVersion || WebApp.clientPrograms[arch].versionNonRefreshable(),
        // comment the following line if you use Meteor < 2.0
        versionReplaceable: Autoupdate.autoupdateVersion || WebApp.clientPrograms[arch].versionReplaceable()
    }
})


const runtimeConfigScript = `__meteor_runtime_config__ = JSON.parse(decodeURIComponent("${encodeURIComponent(JSON.stringify(runtimeConfig))}"))`
const runtimeConfigHash = crypto.createHash('sha256').update(runtimeConfigScript).digest('base64')

const helmetOptions = {
    crossOriginEmbedderPolicy: false,
    contentSecurityPolicy: {
        blockAllMixedContent: true,
        directives: {
            reportUri: '/report-violation',
            defaultSrc: [self],
            scriptSrc: [
                // Remove / comment out unsafeEval if you do not use dynamic imports
                // to tighten security. However, if you use dynamic imports this line
                // must be kept in order to make them work.
                unsafeEval,
                // unsafeInline,
                // strictDynamic,
                `'sha256-${runtimeConfigHash}'`,
            ].concat(allowedOrigins_script_src),
            [.....]

Up until yesterday I was successfully deploying to production.

For some reason as of today, I have the wrong hash:

Uncaught ReferenceError: meteor_runtime_config is not defined
at 49243b6c229d7a94e9dd57ae7b1b37cacdcba46a.js?meteor_js_resource=true:3:116
at 49243b6c229d7a94e9dd57ae7b1b37cacdcba46a.js?meteor_js_resource=true:3:407
at r (49243b6c229d7a94e9dd57ae7b1b37cacdcba46a.js?meteor_js_resource=true:1:891)
at Object.t [as queue] (49243b6c229d7a94e9dd57ae7b1b37cacdcba46a.js?meteor_js_resource=true:1:814)
at 49243b6c229d7a94e9dd57ae7b1b37cacdcba46a.js?meteor_js_resource=true:3:25

My site is deployed on Galaxy. I’m using Meteor 3.0.1, and have also tried with Meteor 3.1.1.

Am I generating the hash incorrectly?

Update

It appears that the hash being generated on the server is not the one the browser is calculating for meteor_runtime_config.

The one generated on the server is generated via this code:

const runtimeConfig = Object.assign(__meteor_runtime_config__, Autoupdate, {
// the following lines may depend on, whether you called Accounts.config
// and whether your Meteor app is a "newer" version
accountsConfigCalled: true,
isModern: true
})

// add client versions to __meteor_runtime_config__
Object.keys(WebApp.clientPrograms).forEach(arch => {
__meteor_runtime_config__.versions[arch] = {
version: Autoupdate.autoupdateVersion || WebApp.clientPrograms[arch].version(),
versionRefreshable: Autoupdate.autoupdateVersion || WebApp.clientPrograms[arch].versionRefreshable(),
versionNonRefreshable: Autoupdate.autoupdateVersion || WebApp.clientPrograms[arch].versionNonRefreshable(),
// comment the following line if you use Meteor < 2.0
versionReplaceable: Autoupdate.autoupdateVersion || WebApp.clientPrograms[arch].versionReplaceable()
}
})

const runtimeConfigScript = `__meteor_runtime_config__ = JSON.parse(decodeURIComponent("${encodeURIComponent(JSON.stringify(runtimeConfig))}"))`
const runtimeConfigHash = crypto.createHash('sha256').update(runtimeConfigScript).digest('base64')
console.log('runtimeConfigHash: ', runtimeConfigHash)

The one the browser is calculating is found by running this in the browser console:

const scriptElement = Array.from(document.getElementsByTagName('script'))
 .find(script = script.textContent.includes('__meteor_runtime_config__'));
 if (scriptElement) {
	 const hash = await crypto.subtle.digest('SHA-256',
	 new TextEncoder().encode(scriptElement.textContent));
	 console.log('Actual browser script hash:',
	 btoa(String.fromCharCode(...new Uint8Array(hash))));
 }

At the moment, with the latest code iterations, the one calculated on the server is:

sha256-a7xwxkAVidOMc0ohV81ggMT2YML6izKGW4DzCgHKokM=

…and the one the browser is expecting is:

YCYDFNqA/AUaP+9ZdWeEjAiNtIcZUTOMAPRJQredXho=

The browser throws a content security policy error and meteor_runtime_config is blocked, so my app doesn’t run.

Does that even make sense? I’m baffled at this point.

@vikr00001 - A naive question. Why can’t you use BrowserPolicy? That seems to be straightforward and simpler. Browser Policy | Docs

1 Like

I will look into Browser Policy.

Thanks for this link, @paulishca . I see that the link you provided is about BrowserPolicy. And the link seems to apply equally to CSP implemented via Helmet. It’s odd because I was deploying successfully until a couple days ago.