CSP with regards to dynamic __meteor_runtime_config__ - question

I’m trying to set up a CSP as per the guidelines here: Security | Meteor Guide

The (main) issue I am having is in this part of the code:

// 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,
});

/// ...


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

The runtimeConfigHash generated for the CSP does not match what’s being built at runtime. After parsing out and analysing the differences, it seems the generated code has some values that runtimeConfigHash does not have.

Specifically as per below - extra lines in code labelled with <---- THIS LINE does not exist in runtimeConfigHash :

{
	"meteorRelease": "METEOR@2.15",
	"gitCommitHash": "b74823974210b87beb4d832ee5cf4a404c8c39f0",
	"meteorEnv": {
		"NODE_ENV": "development",
		"TEST_METADATA": "{}"
	},
	"PUBLIC_SETTINGS": {
		"foo": "bar"
	},
	"ROOT_URL": "http://localhost:3000/",
	"ROOT_URL_PATH_PREFIX": "",
	"reactFastRefreshEnabled": true,
	"autoupdate": {
		"versions": {
			"web.browser": {
				"version": "9f4f2565d3907c66bce0ba12b13e4b6ad606a786",
				"versionRefreshable": "b94e9d10c1d9bdb6b3ecd2e68671e5ca8697db1b",
				"versionNonRefreshable": "ac6592debd66ac85a7003e5af726cfb47e315e44",
				"versionReplaceable": "85c79f1a9fd0f202b134a91b37e15fa4c5a69cf2",
				"versionHmr": 1718084148520. <---- THIS LINE  does not exist in runtimeConfigHash, changes each build
				},
			"versionHmr": 1718084148520. <---- THIS LINE  does not exist in runtimeConfigHash, same each build
		},
		"autoupdateVersion": null,
		"autoupdateVersionRefreshable": null,
		"autoupdateVersionCordova": null,
		"appId": "1t230wbbhj2a9nz92h"
	},
	"appId": "1t230wbbhj2a9nz92h",
	"versions": {
		"web.browser": {
			"version": "9f4f2565d3907c66bce0ba12b13e4b6ad606a786",
			"versionRefreshable": "b94e9d10c1d9bdb6b3ecd2e68671e5ca8697db1b",
			"versionNonRefreshable": "ac6592debd66ac85a7003e5af726cfb47e315e44",
			"versionReplaceable": "85c79f1a9fd0f202b134a91b37e15fa4c5a69cf2",
			"versionHmr": 1718084148520 <---- THIS LINE  does not exist in runtimeConfigHash, changes each build
			},
		"versionHmr": 1718084148520 <---- THIS LINE  does not exist in runtimeConfigHash, same each build
	},
	"autoupdateVersion": null,
	"autoupdateVersionRefreshable": null,
	"autoupdateVersionCordova": null,
	"accountsConfigCalled": true,
	"isModern": true,
	"kadira": { <---- THIS LINE  does not exist in runtimeConfigHash
		"appId": "IIvPOSCkmSoLB2iZ", <---- THIS LINE  does not exist in runtimeConfigHash
		"endpoint": "https://engine.montiapm.com", <---- THIS LINE  does not exist in runtimeConfigHash
		"clientEngineSyncDelay": 10000, <---- THIS LINE  does not exist in runtimeConfigHash
		"recordIPAddress": "full", <---- THIS LINE  does not exist in runtimeConfigHash
		"disableClientErrorTracking": false, <---- THIS LINE  does not exist in runtimeConfigHash
		"enableErrorTracking": true <---- THIS LINE  does not exist in runtimeConfigHash
	}
}

Questions:

  1. I imagine I can hard-code the Kadira/Monti-APM values as they don’t seem dynamic but what is the versionHmr value and how can I accurately generate that for the CSP?
  2. Do the docs regarding this need updating or have I made a mistake in the implementation?

Thanks in advance!

I only see versionHmr in the development. It comes from here: meteor/packages/autoupdate/autoupdate_server.js at bc7febd23431dc12416fe5e904d91e1ecc2620fd · meteor/meteor · GitHub

I can confirm I also have this kadira object in Production only and I don’t know if this is because I only do a Monti.connect(…) in Production.

1 Like

So I guess I just deactivate CSP in development and hard code the Kadira stuff…

Is that what you do?

I’ll give it a try in production in report only mode.

Update to this.

  1. I made a mistake in my original code sample (updated now), there were 4 instance of versionHmr that were showing up, not 2 like I had originally written up. 2 of these are dynamic and change with each build, 2 are static.
  2. I managed to get the CSP working in development by utilising the source of versionHmr (from the link by @paulishca by modifying the code with these changes:

  1. Add the dynamic versionHmr values like so (see last line):
// 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(),
		versionReplaceable: Autoupdate.autoupdateVersion || WebApp.clientPrograms[arch].versionReplaceable(),
		versionHmr: WebApp.clientPrograms[arch].hmrVersion,
	};
});

  1. Setting the static versionHmr values like so:
set(runtimeConfig, `versions.versionHmr`, 1718084148520);
set(runtimeConfig, `autoupdate.versions.versionHmr`, 1718084148520);
  1. Adding the Kadira/Monti static values:
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,
	},
	{
		kadira: {
			appId: MONTI_APP_ID,
			endpoint: `https://engine.montiapm.com`,
			clientEngineSyncDelay: 10000,
			recordIPAddress: `full`,
			disableClientErrorTracking: false,
			enableErrorTracking: true,
		},
	}
);

This does seem like a very hacky job though and there are unanswered questions:

  1. Why are two of the versionHmr values static while 2 are dynamic?
  2. Will this break in production since the versionHmr values don’t seem to be present in prod?

I’ll report more here as I find out. If anyone has any insights then they would be much appreciated.