Skip to content

Commit 4b958b0

Browse files
nichtsamkentcdodds
andauthored
move away from express helmet (#927)
Co-authored-by: Kent C. Dodds <[email protected]> Co-authored-by: Kent C. Dodds <[email protected]>
1 parent 87b1b0e commit 4b958b0

File tree

4 files changed

+48
-45
lines changed

4 files changed

+48
-45
lines changed

app/entry.server.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { PassThrough } from 'node:stream'
22
import { styleText } from 'node:util'
3+
import { contentSecurity } from '@nichtsam/helmet/content'
34
import { createReadableStreamFromReadable } from '@react-router/node'
45
import * as Sentry from '@sentry/node'
56
import { isbot } from 'isbot'
@@ -20,6 +21,8 @@ export const streamTimeout = 5000
2021
init()
2122
global.ENV = getEnv()
2223

24+
const MODE = process.env.NODE_ENV ?? 'development'
25+
2326
type DocRequestArgs = Parameters<HandleDocumentRequestFunction>
2427

2528
export default async function handleRequest(...args: DocRequestArgs) {
@@ -64,6 +67,33 @@ export default async function handleRequest(...args: DocRequestArgs) {
6467
const body = new PassThrough()
6568
responseHeaders.set('Content-Type', 'text/html')
6669
responseHeaders.append('Server-Timing', timings.toString())
70+
71+
contentSecurity(responseHeaders, {
72+
crossOriginEmbedderPolicy: false,
73+
contentSecurityPolicy: {
74+
// NOTE: Remove reportOnly when you're ready to enforce this CSP
75+
reportOnly: true,
76+
directives: {
77+
fetch: {
78+
'connect-src': [
79+
MODE === 'development' ? 'ws:' : undefined,
80+
process.env.SENTRY_DSN ? '*.sentry.io' : undefined,
81+
"'self'",
82+
],
83+
'font-src': ["'self'"],
84+
'frame-src': ["'self'"],
85+
'img-src': ["'self'", 'data:'],
86+
'script-src': [
87+
"'strict-dynamic'",
88+
"'self'",
89+
`'nonce-${nonce}'`,
90+
],
91+
'script-src-attr': [`'nonce-${nonce}'`],
92+
},
93+
},
94+
},
95+
})
96+
6797
resolve(
6898
new Response(createReadableStreamFromReadable(body), {
6999
headers: responseHeaders,

package-lock.json

Lines changed: 10 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
"@epic-web/totp": "^3.0.0",
5454
"@mjackson/form-data-parser": "^0.7.0",
5555
"@nasa-gcn/remix-seo": "^2.0.1",
56+
"@nichtsam/helmet": "0.3.0",
5657
"@oslojs/crypto": "^1.0.1",
5758
"@oslojs/encoding": "^1.1.0",
5859
"@paralleldrive/cuid2": "^2.2.2",
@@ -89,7 +90,6 @@
8990
"express-rate-limit": "^7.5.0",
9091
"get-port": "^7.1.0",
9192
"glob": "^11.0.1",
92-
"helmet": "^8.0.0",
9393
"input-otp": "^1.4.2",
9494
"intl-parse-accept-language": "^1.0.0",
9595
"isbot": "^5.1.22",

server/index.ts

Lines changed: 7 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import crypto from 'node:crypto'
22
import { styleText } from 'node:util'
3+
import { helmet } from '@nichtsam/helmet/node-http'
34
import { createRequestHandler } from '@react-router/express'
45
import * as Sentry from '@sentry/node'
56
import { ip as ipAddress } from 'address'
@@ -8,7 +9,6 @@ import compression from 'compression'
89
import express from 'express'
910
import rateLimit from 'express-rate-limit'
1011
import getPort, { portNumbers } from 'get-port'
11-
import helmet from 'helmet'
1212
import morgan from 'morgan'
1313
import { type ServerBuild } from 'react-router'
1414

@@ -68,6 +68,12 @@ app.use(compression())
6868
// http://expressjs.com/en/advanced/best-practice-security.html#at-a-minimum-disable-x-powered-by-header
6969
app.disable('x-powered-by')
7070

71+
app.use((_, res, next) => {
72+
// The referrerPolicy breaks our redirectTo logic
73+
helmet(res, { general: { referrerPolicy: false } })
74+
next()
75+
})
76+
7177
if (viteDevServer) {
7278
app.use(viteDevServer.middlewares)
7379
} else {
@@ -110,39 +116,6 @@ app.use((_, res, next) => {
110116
next()
111117
})
112118

113-
app.use(
114-
helmet({
115-
xPoweredBy: false,
116-
referrerPolicy: { policy: 'same-origin' },
117-
crossOriginEmbedderPolicy: false,
118-
contentSecurityPolicy: {
119-
// NOTE: Remove reportOnly when you're ready to enforce this CSP
120-
reportOnly: true,
121-
directives: {
122-
'connect-src': [
123-
MODE === 'development' ? 'ws:' : null,
124-
process.env.SENTRY_DSN ? '*.sentry.io' : null,
125-
"'self'",
126-
].filter(Boolean),
127-
'font-src': ["'self'"],
128-
'frame-src': ["'self'"],
129-
'img-src': ["'self'", 'data:'],
130-
'script-src': [
131-
"'strict-dynamic'",
132-
"'self'",
133-
// @ts-expect-error
134-
(_, res) => `'nonce-${res.locals.cspNonce}'`,
135-
],
136-
'script-src-attr': [
137-
// @ts-expect-error
138-
(_, res) => `'nonce-${res.locals.cspNonce}'`,
139-
],
140-
'upgrade-insecure-requests': null,
141-
},
142-
},
143-
}),
144-
)
145-
146119
// When running tests or running in development, we want to effectively disable
147120
// rate limiting because playwright tests are very fast and we don't want to
148121
// have to wait for the rate limit to reset between tests.

0 commit comments

Comments
 (0)