diff --git a/scripts/security-headers.js b/scripts/security-headers.js new file mode 100644 index 000000000..7e7081feb --- /dev/null +++ b/scripts/security-headers.js @@ -0,0 +1,77 @@ +'use strict' + +const { writeFile } = require('fs').promises +const { once } = require('events') +const helmet = require('helmet')() +const http = require('http') + +const listen = async (server, ...args) => { + server.listen(...args) + await once(server, 'listening') + const { address, port, family } = server.address() + return `http://${family === 'IPv6' ? `[${address}]` : address}:${port}/` +} + +const createServer = ({ withHelmet = false } = {}) => + http.createServer((req, res) => { + const fn = withHelmet ? helmet : (req, res, next) => next() + fn(req, res, err => { + if (err) { + res.statusCode = 500 + res.end( + 'Helmet failed for some unexpected reason. Was it configured correctly?' + ) + return + } + res.end('Hello world!') + }) + }) + +const getSecurityHeaders = async () => { + let server = createServer({ withHelmet: false }) + + let serverUrl = await listen(server) + + let res = await fetch(serverUrl) + const headers = Object.fromEntries(res.headers) + + server.close() + + server = createServer({ withHelmet: true }) + serverUrl = await listen(server) + + res = await fetch(serverUrl) + const headersWithHelmet = Object.fromEntries(res.headers) + + server.close() + + const diff = Object.fromEntries( + Object.entries(headersWithHelmet).filter( + ([key, value]) => headers[key] !== value + ) + ) + + return Object.entries(diff).map(([key, value]) => ({ key, value })) +} + +;(async () => { + const SOURCE_PATTERN = '/(.*)' + const headers = await getSecurityHeaders() + + const vercelJSON = require('../vercel.json') + + if (!vercelJSON.headers) { + vercelJSON.headers = [ + { + source: SOURCE_PATTERN, + headers + } + ] + } else { + const rule = vercelJSON.headers.find(rule => rule.source === SOURCE_PATTERN) + if (!rule) vercelJSON.headers.push({ source: SOURCE_PATTERN, headers }) + else rule.headers = headers + } + + await writeFile('vercel.json', JSON.stringify(vercelJSON, null, 2)) +})() diff --git a/vercel.json b/vercel.json index 7ccb369c6..a499febe5 100644 --- a/vercel.json +++ b/vercel.json @@ -1,4 +1,59 @@ { + "headers": [ + { + "source": "/(.*)", + "headers": [ + { + "key": "content-security-policy", + "value": "frame-ancestors 'self'; upgrade-insecure-requests" + }, + { + "key": "cross-origin-opener-policy", + "value": "same-origin" + }, + { + "key": "cross-origin-resource-policy", + "value": "same-origin" + }, + { + "key": "origin-agent-cluster", + "value": "?1" + }, + { + "key": "referrer-policy", + "value": "no-referrer" + }, + { + "key": "strict-transport-security", + "value": "max-age=15552000; includeSubDomains" + }, + { + "key": "x-content-type-options", + "value": "nosniff" + }, + { + "key": "x-dns-prefetch-control", + "value": "off" + }, + { + "key": "x-download-options", + "value": "noopen" + }, + { + "key": "x-frame-options", + "value": "SAMEORIGIN" + }, + { + "key": "x-permitted-cross-domain-policies", + "value": "none" + }, + { + "key": "x-xss-protection", + "value": "0" + } + ] + } + ], "redirects": [ { "source": "/adblock", @@ -197,4 +252,4 @@ "destination": "/docs/api/parameters/waitUntil" } ] -} \ No newline at end of file +}