fix(core): Remove inline JS from FE for more secure CSP (#17195)

This commit is contained in:
Iván Ovejero
2025-07-15 11:00:00 +02:00
committed by GitHub
parent 61e2e34caa
commit fc3129e378
3 changed files with 20 additions and 23 deletions

View File

@@ -13,6 +13,8 @@ import replaceStream from 'replacestream';
import { pipeline } from 'stream/promises'; import { pipeline } from 'stream/promises';
import { z } from 'zod'; import { z } from 'zod';
import { BaseCommand } from './base-command';
import { ActiveExecutions } from '@/active-executions'; import { ActiveExecutions } from '@/active-executions';
import { ActiveWorkflowManager } from '@/active-workflow-manager'; import { ActiveWorkflowManager } from '@/active-workflow-manager';
import config from '@/config'; import config from '@/config';
@@ -32,8 +34,6 @@ import { UrlService } from '@/services/url.service';
import { WaitTracker } from '@/wait-tracker'; import { WaitTracker } from '@/wait-tracker';
import { WorkflowRunner } from '@/workflow-runner'; import { WorkflowRunner } from '@/workflow-runner';
import { BaseCommand } from './base-command';
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const open = require('open'); const open = require('open');
@@ -164,9 +164,8 @@ export class Start extends BaseCommand<z.infer<typeof flagsSchema>> {
} }
}; };
await compileFile('index.html');
const files = await glob('**/*.{css,js}', { cwd: EDITOR_UI_DIST_DIR }); const files = await glob('**/*.{css,js}', { cwd: EDITOR_UI_DIST_DIR });
await Promise.all(files.map(compileFile)); await Promise.all([compileFile('index.html'), ...files.map(compileFile)]);
} }
async init() { async init() {

View File

@@ -1,3 +1,4 @@
import { CLI_DIR, EDITOR_UI_DIST_DIR, inE2ETests, N8N_VERSION } from '@/constants';
import { inDevelopment, inProduction } from '@n8n/backend-common'; import { inDevelopment, inProduction } from '@n8n/backend-common';
import { SecurityConfig } from '@n8n/config'; import { SecurityConfig } from '@n8n/config';
import { Time } from '@n8n/constants'; import { Time } from '@n8n/constants';
@@ -14,7 +15,6 @@ import { resolve } from 'path';
import { AbstractServer } from '@/abstract-server'; import { AbstractServer } from '@/abstract-server';
import config from '@/config'; import config from '@/config';
import { CLI_DIR, EDITOR_UI_DIST_DIR, inE2ETests, N8N_VERSION } from '@/constants';
import { ControllerRegistry } from '@/controller.registry'; import { ControllerRegistry } from '@/controller.registry';
import { CredentialsOverwrites } from '@/credentials-overwrites'; import { CredentialsOverwrites } from '@/credentials-overwrites';
import { MessageEventBus } from '@/eventbus/message-event-bus/message-event-bus'; import { MessageEventBus } from '@/eventbus/message-event-bus/message-event-bus';
@@ -278,19 +278,21 @@ export class Server extends AbstractServer {
ResponseHelper.send(async () => frontendService.getModuleSettings()), ResponseHelper.send(async () => frontendService.getModuleSettings()),
); );
// Return Sentry config as a static file this.app.get(`/${this.restEndpoint}/config.js`, (_req, res) => {
this.app.get(`/${this.restEndpoint}/sentry.js`, (_, res) => { const frontendSentryConfig = JSON.stringify({
res.type('js'); dsn: this.globalConfig.sentry.frontendDsn,
res.write('window.sentry='); environment: process.env.ENVIRONMENT || 'development',
res.write( serverName: process.env.DEPLOYMENT_NAME,
JSON.stringify({ release: `n8n@${N8N_VERSION}`,
dsn: this.globalConfig.sentry.frontendDsn, });
environment: process.env.ENVIRONMENT || 'development', const frontendConfig = [
serverName: process.env.DEPLOYMENT_NAME, `window.BASE_PATH = '${this.globalConfig.path}';`,
release: `n8n@${N8N_VERSION}`, `window.REST_ENDPOINT = '${this.globalConfig.endpoints.rest}';`,
}), `window.sentry = ${frontendSentryConfig};`,
); ].join('\n');
res.end();
res.type('application/javascript');
res.send(frontendConfig);
}); });
} }

View File

@@ -6,11 +6,7 @@
<meta name="viewport" content="width=device-width,initial-scale=1.0" /> <meta name="viewport" content="width=device-width,initial-scale=1.0" />
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
<style>@media (prefers-color-scheme: dark) { body { background-color: rgb(45, 46, 46) } }</style> <style>@media (prefers-color-scheme: dark) { body { background-color: rgb(45, 46, 46) } }</style>
<script type="text/javascript"> <script src="/{{REST_ENDPOINT}}/config.js"></script>
window.BASE_PATH = '/{{BASE_PATH}}/';
window.REST_ENDPOINT = '{{REST_ENDPOINT}}';
</script>
<script src="/{{REST_ENDPOINT}}/sentry.js"></script>
<script>!function(t,e){var o,n,p,r;e.__SV||(window.posthog=e,e._i=[],e.init=function(i,s,a){function g(t,e){var o=e.split(".");2==o.length&&(t=t[o[0]],e=o[1]),t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}}(p=t.createElement("script")).type="text/javascript",p.async=!0,p.src=s.api_host+"/static/array.js",(r=t.getElementsByTagName("script")[0]).parentNode.insertBefore(p,r);var u=e;for(void 0!==a?u=e[a]=[]:a="posthog",u.people=u.people||[],u.toString=function(t){var e="posthog";return"posthog"!==a&&(e+="."+a),t||(e+=" (stub)"),e},u.people.toString=function(){return u.toString(1)+".people (stub)"},o="capture identify alias people.set people.set_once set_config register register_once unregister opt_out_capturing has_opted_out_capturing opt_in_capturing reset isFeatureEnabled getFeatureFlag onFeatureFlags reloadFeatureFlags".split(" "),n=0;n<o.length;n++)g(u,o[n]);e._i.push([i,s,a])},e.__SV=1)}(document,window.posthog||[])</script> <script>!function(t,e){var o,n,p,r;e.__SV||(window.posthog=e,e._i=[],e.init=function(i,s,a){function g(t,e){var o=e.split(".");2==o.length&&(t=t[o[0]],e=o[1]),t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}}(p=t.createElement("script")).type="text/javascript",p.async=!0,p.src=s.api_host+"/static/array.js",(r=t.getElementsByTagName("script")[0]).parentNode.insertBefore(p,r);var u=e;for(void 0!==a?u=e[a]=[]:a="posthog",u.people=u.people||[],u.toString=function(t){var e="posthog";return"posthog"!==a&&(e+="."+a),t||(e+=" (stub)"),e},u.people.toString=function(){return u.toString(1)+".people (stub)"},o="capture identify alias people.set people.set_once set_config register register_once unregister opt_out_capturing has_opted_out_capturing opt_in_capturing reset isFeatureEnabled getFeatureFlag onFeatureFlags reloadFeatureFlags".split(" "),n=0;n<o.length;n++)g(u,o[n]);e._i.push([i,s,a])},e.__SV=1)}(document,window.posthog||[])</script>
<title>n8n.io - Workflow Automation</title> <title>n8n.io - Workflow Automation</title>