Skip to content

Commit

Permalink
Encampsulate security header related config prior to altering it for …
Browse files Browse the repository at this point in the history
…In Context editor

Refs: #2116
  • Loading branch information
erkannt committed Dec 5, 2024
1 parent dcccc6d commit a25fa97
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 63 deletions.
41 changes: 2 additions & 39 deletions src/WebApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
HttpServerResponse,
Path,
} from '@effect/platform'
import cspBuilder from 'content-security-policy-builder'
import cookieSignature from 'cookie-signature'
import { Cause, Config, Effect, flow, Layer, Option, pipe, Schema } from 'effect'
import { StatusCodes } from 'http-status-codes'
Expand All @@ -22,6 +21,7 @@ import { DefaultLocale, SupportedLocales } from './locales/index.js'
import { PublicUrl } from './public-url.js'
import { FlashMessageSchema } from './response.js'
import { Router } from './Router.js'
import { securityHeaders } from './securityHeaders.js'
import * as TemplatePage from './TemplatePage.js'
import { Uuid } from './types/index.js'
import { LoggedInUser, UserSchema } from './user.js'
Expand Down Expand Up @@ -104,44 +104,7 @@ const addSecurityHeaders = HttpMiddleware.make(app =>
const publicUrl = yield* PublicUrl
const response = yield* app

return HttpServerResponse.setHeaders(response, {
'Content-Security-Policy': cspBuilder({
directives: {
'script-src': ["'self'", 'cdn.usefathom.com'],
'img-src': [
"'self'",
'data:',
'avatars.slack-edge.com',
'cdn.usefathom.com',
'content.prereview.org',
'res.cloudinary.com',
'secure.gravatar.com',
'*.wp.com',
],
'upgrade-insecure-requests': publicUrl.protocol === 'https:',
'default-src': "'self'",
'base-uri': "'self'",
'font-src': ["'self'", 'https:', 'data:'],
'form-action': "'self'",
'frame-ancestors': "'self'",
'object-src': "'none'",
'script-src-attr': "'none'",
'style-src': ["'self'", 'https:', "'unsafe-inline'"],
},
}),
'Cross-Origin-Embedder-Policy': 'credentialless',
'Cross-Origin-Opener-Policy': 'same-origin',
'Cross-Origin-Resource-Policy': 'same-origin',
'Origin-Agent-Cluster': '?1',
'Referrer-Policy': 'no-referrer',
'Strict-Transport-Security': publicUrl.protocol === 'https:' ? 'max-age=15552000; includeSubDomains' : undefined,
'X-Content-Type-Options': 'nosniff',
'X-DNS-Prefetch-Control': 'off',
'X-Download-Options': 'noopen',
'X-Frame-Options': 'SAMEORIGIN',
'X-Permitted-Cross-Domain-Policies': 'none',
'X-XSS-Protection': '0',
})
return HttpServerResponse.setHeaders(response, securityHeaders(publicUrl.protocol))
}),
)

Expand Down
26 changes: 2 additions & 24 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { type LegacyEnv, legacyRoutes } from './legacy-routes/index.js'
import type { SupportedLocale } from './locales/index.js'
import { type NodemailerEnv, sendEmailWithNodemailer } from './nodemailer.js'
import { handleResponse } from './response.js'
import { helmetOptions } from './securityHeaders.js'
import { maybeGetUser, type User } from './user.js'

export type ConfigEnv = Omit<
Expand Down Expand Up @@ -128,30 +129,7 @@ export const app = (config: ConfigEnv) => {

next()
})
.use(
helmet({
contentSecurityPolicy: {
directives: {
'script-src': ["'self'", 'cdn.usefathom.com'],
'img-src': [
"'self'",
'data:',
'avatars.slack-edge.com',
'cdn.usefathom.com',
'content.prereview.org',
'res.cloudinary.com',
'secure.gravatar.com',
'*.wp.com',
],
upgradeInsecureRequests: config.publicUrl.protocol === 'https:' ? [] : null,
},
},
crossOriginEmbedderPolicy: {
policy: 'credentialless',
},
strictTransportSecurity: config.publicUrl.protocol === 'https:',
}),
)
.use(helmet(helmetOptions(config.publicUrl.protocol)))
.use(asyncHandler(proxy))
.use(express.urlencoded({ extended: true }))
.use((req, res, next) => {
Expand Down
65 changes: 65 additions & 0 deletions src/securityHeaders.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import cspBuilder from 'content-security-policy-builder'
import type { HelmetOptions } from 'helmet'

export const securityHeaders = (protocol: URL['protocol']) => ({
'Content-Security-Policy': cspBuilder({
directives: {
'script-src': ["'self'", 'cdn.usefathom.com'],
'img-src': [
"'self'",
'data:',
'avatars.slack-edge.com',
'cdn.usefathom.com',
'content.prereview.org',
'res.cloudinary.com',
'secure.gravatar.com',
'*.wp.com',
],
'upgrade-insecure-requests': protocol === 'https:',
'default-src': "'self'",
'base-uri': "'self'",
'font-src': ["'self'", 'https:', 'data:'],
'form-action': "'self'",
'frame-ancestors': "'self'",
'object-src': "'none'",
'script-src-attr': "'none'",
'style-src': ["'self'", 'https:', "'unsafe-inline'"],
},
}),
'Cross-Origin-Embedder-Policy': 'credentialless',
'Cross-Origin-Opener-Policy': 'same-origin',
'Cross-Origin-Resource-Policy': 'same-origin',
'Origin-Agent-Cluster': '?1',
'Referrer-Policy': 'no-referrer',
'Strict-Transport-Security': protocol === 'https:' ? 'max-age=15552000; includeSubDomains' : undefined,
'X-Content-Type-Options': 'nosniff',
'X-DNS-Prefetch-Control': 'off',
'X-Download-Options': 'noopen',
'X-Frame-Options': 'SAMEORIGIN',
'X-Permitted-Cross-Domain-Policies': 'none',
'X-XSS-Protection': '0',
})

export const helmetOptions = (protocol: URL['protocol']) =>
({
contentSecurityPolicy: {
directives: {
'script-src': ["'self'", 'cdn.usefathom.com'],
'img-src': [
"'self'",
'data:',
'avatars.slack-edge.com',
'cdn.usefathom.com',
'content.prereview.org',
'res.cloudinary.com',
'secure.gravatar.com',
'*.wp.com',
],
upgradeInsecureRequests: protocol === 'https:' ? [] : null,
},
},
crossOriginEmbedderPolicy: {
policy: 'credentialless',
},
strictTransportSecurity: protocol === 'https:',
}) satisfies Readonly<HelmetOptions>

0 comments on commit a25fa97

Please sign in to comment.