From ff48a0a3c28589c03251224c4a569ebba71dead2 Mon Sep 17 00:00:00 2001 From: arsam Date: Fri, 18 Sep 2026 23:19:47 +0330 Subject: [PATCH] Import source: app/backend/src/plugins/security-headers Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality. --- app/backend/src/plugins/security-headers.ts | 36 +++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 app/backend/src/plugins/security-headers.ts diff --git a/app/backend/src/plugins/security-headers.ts b/app/backend/src/plugins/security-headers.ts new file mode 100644 index 0000000..3ac8cf0 --- /dev/null +++ b/app/backend/src/plugins/security-headers.ts @@ -0,0 +1,36 @@ +import fp from 'fastify-plugin'; +import type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify'; +import { randomBytes } from 'node:crypto'; + +declare module 'fastify' { + interface FastifyReply { + cspNonce: string; + } +} + +function generateNonce(): string { + return randomBytes(16).toString('base64'); +} + +export default fp(async function securityHeadersPlugin(app: FastifyInstance) { + app.decorateReply('cspNonce', ''); + + app.addHook('onRequest', async (req: FastifyRequest, reply: FastifyReply) => { + const nonce = generateNonce(); + reply.cspNonce = nonce; + reply.header('X-Content-Type-Options', 'nosniff'); + reply.header('X-Frame-Options', 'SAMEORIGIN'); + reply.header('Referrer-Policy', 'strict-origin-when-cross-origin'); + reply.header('Permissions-Policy', 'camera=(), microphone=(), geolocation=()'); + + if (req.url.startsWith('/embed') || req.url === '/embed.html') { + reply.header('Content-Security-Policy', `default-src 'self'; script-src 'self' 'nonce-${nonce}'; style-src 'self' 'unsafe-inline'; frame-ancestors *; report-uri /api/v1/csp-report`); + } else { + reply.header('Content-Security-Policy', `default-src 'self'; script-src 'self' 'nonce-${nonce}'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self'; connect-src 'self'; frame-ancestors 'self'; form-action 'self'; report-uri /api/v1/csp-report`); + } + + if (process.env.NODE_ENV === 'production') { + reply.header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains'); + } + }); +});