From 7b44fd5d0a49341156fe3e6bca5a843d1953f875 Mon Sep 17 00:00:00 2001 From: Moritz Marby Date: Wed, 5 Nov 2025 09:50:29 +0100 Subject: [PATCH 1/6] feat(security): enhance content security policy and add HSTS configuration --- server.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/server.ts b/server.ts index 9c025a82..6c196ded 100644 --- a/server.ts +++ b/server.ts @@ -94,6 +94,9 @@ if (DYNATRACE_SCRIPT_URL) { fastify.register(helmet, { contentSecurityPolicy: { directives: { + defaultSrc: ["'self'"], + styleSrc: ["'self'", "'unsafe-inline'"], + imgSrc: ["'self'", 'data:', 'https:'], 'connect-src': ["'self'", 'sdk.openui5.org', sentryHost, dynatraceOrigin], 'script-src': isLocalDev ? ["'self'", "'unsafe-inline'", "'unsafe-eval'", sentryHost, dynatraceOrigin] @@ -102,6 +105,12 @@ fastify.register(helmet, { 'frame-ancestors': [...fastify.config.FRAME_ANCESTORS.split(',')], }, }, + // Needed for https enforcement + hsts: { + maxAge: 31536000, + includeSubDomains: true, + preload: true, + }, }); fastify.register(proxy, { From e408b0ca0cdefa5813d3faf8a512642598a4f64d Mon Sep 17 00:00:00 2001 From: Moritz Marby Date: Wed, 5 Nov 2025 09:53:29 +0100 Subject: [PATCH 2/6] fix(build): only enable sourcemaps if not building for production --- vite.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vite.config.js b/vite.config.js index 283c2549..6d8e139d 100644 --- a/vite.config.js +++ b/vite.config.js @@ -35,7 +35,7 @@ export default defineConfig({ }, build: { - sourcemap: true, + sourcemap: process.env.NODE_ENV !== 'production', target: 'esnext', // Support top-level await }, }); From 379a1ec4da01e6345ea1ac94ee7be4dad2abcfad Mon Sep 17 00:00:00 2001 From: Moritz Marby Date: Fri, 7 Nov 2025 10:19:09 +0100 Subject: [PATCH 3/6] fix: removed env output for not leaking secrets --- server.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/server.ts b/server.ts index 6c196ded..ab5dac2b 100644 --- a/server.ts +++ b/server.ts @@ -12,8 +12,6 @@ import { injectDynatraceTag } from './server/config/dynatrace.js'; dotenv.config(); -console.log(process.env); - const { DYNATRACE_SCRIPT_URL } = process.env; if (DYNATRACE_SCRIPT_URL) { injectDynatraceTag(DYNATRACE_SCRIPT_URL); From 7e91e6ad31bda46c83a70567a64ad2f59681504c Mon Sep 17 00:00:00 2001 From: Moritz Marby Date: Fri, 7 Nov 2025 13:51:39 +0100 Subject: [PATCH 4/6] feat(cors): add CORS support with configurable allowed origins --- .env.template | 4 ++++ package-lock.json | 21 +++++++++++++++++++++ package.json | 1 + server.ts | 24 ++++++++++++++++++++++++ server/config/env.ts | 1 + 5 files changed, 51 insertions(+) diff --git a/.env.template b/.env.template index 8969789f..6fdad6e9 100644 --- a/.env.template +++ b/.env.template @@ -32,3 +32,7 @@ FEEDBACK_URL_LINK= # frame-ancestors attribute of CSP. Separate multiple values with a space FRAME_ANCESTORS= + +# Allowed CORS origins (comma-separated). Example: https://app.example.com,https://admin.example.com +# Leave empty to use POST_LOGIN_REDIRECT as the default allowed origin +ALLOWED_CORS_ORIGINS= diff --git a/package-lock.json b/package-lock.json index fff9284e..17125a58 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@apollo/client": "3.14.0", "@fastify/autoload": "6.3.1", "@fastify/cookie": "11.0.2", + "@fastify/cors": "^11.1.0", "@fastify/env": "5.0.3", "@fastify/helmet": "13.0.2", "@fastify/http-proxy": "11.3.0", @@ -1434,6 +1435,26 @@ "fastify-plugin": "^5.0.0" } }, + "node_modules/@fastify/cors": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/@fastify/cors/-/cors-11.1.0.tgz", + "integrity": "sha512-sUw8ed8wP2SouWZTIbA7V2OQtMNpLj2W6qJOYhNdcmINTu6gsxVYXjQiM9mdi8UUDlcoDDJ/W2syPo1WB2QjYA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "fastify-plugin": "^5.0.0", + "toad-cache": "^3.7.0" + } + }, "node_modules/@fastify/deepmerge": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@fastify/deepmerge/-/deepmerge-3.1.0.tgz", diff --git a/package.json b/package.json index 8d796074..91e02811 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "@apollo/client": "3.14.0", "@fastify/autoload": "6.3.1", "@fastify/cookie": "11.0.2", + "@fastify/cors": "^11.1.0", "@fastify/env": "5.0.3", "@fastify/helmet": "13.0.2", "@fastify/http-proxy": "11.3.0", diff --git a/server.ts b/server.ts index ab5dac2b..1c3180b4 100644 --- a/server.ts +++ b/server.ts @@ -1,5 +1,6 @@ import Fastify from 'fastify'; import FastifyVite from '@fastify/vite'; +import cors from '@fastify/cors'; import helmet from '@fastify/helmet'; import { fileURLToPath } from 'node:url'; import path from 'node:path'; @@ -65,6 +66,29 @@ const fastify = Fastify({ logger: true, }); +fastify.register(cors, { + origin: isLocalDev + ? true // Allow all origins in local development + : (origin, callback) => { + // In production, validate against allowed origins + // @ts-ignore + const allowedOrigins = fastify.config.ALLOWED_CORS_ORIGINS + ? // @ts-ignore + fastify.config.ALLOWED_CORS_ORIGINS.split(',').map((o) => o.trim()) + : // @ts-ignore + [fastify.config.POST_LOGIN_REDIRECT]; // Fallback to POST_LOGIN_REDIRECT + + console.log('Allowed Origin:', allowedOrigins, !origin || allowedOrigins.includes(origin)); + if (!origin || allowedOrigins.includes(origin)) { + callback(null, true); + } else { + callback(new Error(`Origin ${origin} not allowed by CORS policy`), false); + } + }, + methods: ['GET', 'HEAD', 'POST', 'PATCH', 'DELETE'], + credentials: true, // Required for cookie-based sessions +}); + Sentry.setupFastifyErrorHandler(fastify); await fastify.register(envPlugin); diff --git a/server/config/env.ts b/server/config/env.ts index 1bcfe43a..b495579d 100644 --- a/server/config/env.ts +++ b/server/config/env.ts @@ -29,6 +29,7 @@ const schema = { FEEDBACK_SLACK_URL: { type: 'string' }, FEEDBACK_URL_LINK: { type: 'string' }, FRAME_ANCESTORS: { type: 'string' }, + ALLOWED_CORS_ORIGINS: { type: 'string' }, BFF_SENTRY_DSN: { type: 'string' }, FRONTEND_SENTRY_DSN: { type: 'string' }, FRONTEND_SENTRY_ENVIRONMENT: { type: 'string' }, From 5096ebc5721ce603e26978888a60f720db756698 Mon Sep 17 00:00:00 2001 From: Moritz Marby Date: Fri, 7 Nov 2025 14:01:07 +0100 Subject: [PATCH 5/6] fix(deps): pin @fastify/cors version to 11.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 91e02811..4b76703c 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "@apollo/client": "3.14.0", "@fastify/autoload": "6.3.1", "@fastify/cookie": "11.0.2", - "@fastify/cors": "^11.1.0", + "@fastify/cors": "11.1.0", "@fastify/env": "5.0.3", "@fastify/helmet": "13.0.2", "@fastify/http-proxy": "11.3.0", From caf33d72df92bdab05e33ad3e0fa194fdd41a660 Mon Sep 17 00:00:00 2001 From: Moritz Marby Date: Fri, 7 Nov 2025 14:22:44 +0100 Subject: [PATCH 6/6] fix(cors): remove console log for allowed origins in CORS configuration --- server.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/server.ts b/server.ts index 1c3180b4..30e9e6b5 100644 --- a/server.ts +++ b/server.ts @@ -78,7 +78,6 @@ fastify.register(cors, { : // @ts-ignore [fastify.config.POST_LOGIN_REDIRECT]; // Fallback to POST_LOGIN_REDIRECT - console.log('Allowed Origin:', allowedOrigins, !origin || allowedOrigins.includes(origin)); if (!origin || allowedOrigins.includes(origin)) { callback(null, true); } else {