Skip to content

Commit 6dadebb

Browse files
authored
chore: add security headers (#1718)
1 parent 11f96c4 commit 6dadebb

File tree

2 files changed

+133
-1
lines changed

2 files changed

+133
-1
lines changed

scripts/security-headers.js

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
'use strict'
2+
3+
const { writeFile } = require('fs').promises
4+
const { once } = require('events')
5+
const helmet = require('helmet')()
6+
const http = require('http')
7+
8+
const listen = async (server, ...args) => {
9+
server.listen(...args)
10+
await once(server, 'listening')
11+
const { address, port, family } = server.address()
12+
return `http://${family === 'IPv6' ? `[${address}]` : address}:${port}/`
13+
}
14+
15+
const createServer = ({ withHelmet = false } = {}) =>
16+
http.createServer((req, res) => {
17+
const fn = withHelmet ? helmet : (req, res, next) => next()
18+
fn(req, res, err => {
19+
if (err) {
20+
res.statusCode = 500
21+
res.end(
22+
'Helmet failed for some unexpected reason. Was it configured correctly?'
23+
)
24+
return
25+
}
26+
res.end('Hello world!')
27+
})
28+
})
29+
30+
const getSecurityHeaders = async () => {
31+
let server = createServer({ withHelmet: false })
32+
33+
let serverUrl = await listen(server)
34+
35+
let res = await fetch(serverUrl)
36+
const headers = Object.fromEntries(res.headers)
37+
38+
server.close()
39+
40+
server = createServer({ withHelmet: true })
41+
serverUrl = await listen(server)
42+
43+
res = await fetch(serverUrl)
44+
const headersWithHelmet = Object.fromEntries(res.headers)
45+
46+
server.close()
47+
48+
const diff = Object.fromEntries(
49+
Object.entries(headersWithHelmet).filter(
50+
([key, value]) => headers[key] !== value
51+
)
52+
)
53+
54+
return Object.entries(diff).map(([key, value]) => ({ key, value }))
55+
}
56+
57+
;(async () => {
58+
const SOURCE_PATTERN = '/(.*)'
59+
const headers = await getSecurityHeaders()
60+
61+
const vercelJSON = require('../vercel.json')
62+
63+
if (!vercelJSON.headers) {
64+
vercelJSON.headers = [
65+
{
66+
source: SOURCE_PATTERN,
67+
headers
68+
}
69+
]
70+
} else {
71+
const rule = vercelJSON.headers.find(rule => rule.source === SOURCE_PATTERN)
72+
if (!rule) vercelJSON.headers.push({ source: SOURCE_PATTERN, headers })
73+
else rule.headers = headers
74+
}
75+
76+
await writeFile('vercel.json', JSON.stringify(vercelJSON, null, 2))
77+
})()

vercel.json

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,59 @@
11
{
2+
"headers": [
3+
{
4+
"source": "/(.*)",
5+
"headers": [
6+
{
7+
"key": "content-security-policy",
8+
"value": "default-src 'self';base-uri 'self';font-src 'self' https: data:;form-action 'self';frame-ancestors 'self';img-src 'self' data:;object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline';upgrade-insecure-requests"
9+
},
10+
{
11+
"key": "cross-origin-opener-policy",
12+
"value": "same-origin"
13+
},
14+
{
15+
"key": "cross-origin-resource-policy",
16+
"value": "same-origin"
17+
},
18+
{
19+
"key": "origin-agent-cluster",
20+
"value": "?1"
21+
},
22+
{
23+
"key": "referrer-policy",
24+
"value": "no-referrer"
25+
},
26+
{
27+
"key": "strict-transport-security",
28+
"value": "max-age=15552000; includeSubDomains"
29+
},
30+
{
31+
"key": "x-content-type-options",
32+
"value": "nosniff"
33+
},
34+
{
35+
"key": "x-dns-prefetch-control",
36+
"value": "off"
37+
},
38+
{
39+
"key": "x-download-options",
40+
"value": "noopen"
41+
},
42+
{
43+
"key": "x-frame-options",
44+
"value": "SAMEORIGIN"
45+
},
46+
{
47+
"key": "x-permitted-cross-domain-policies",
48+
"value": "none"
49+
},
50+
{
51+
"key": "x-xss-protection",
52+
"value": "0"
53+
}
54+
]
55+
}
56+
],
257
"redirects": [
358
{
459
"source": "/adblock",
@@ -197,4 +252,4 @@
197252
"destination": "/docs/api/parameters/waitUntil"
198253
}
199254
]
200-
}
255+
}

0 commit comments

Comments
 (0)