mirror of
https://github.com/overleaf/overleaf.git
synced 2024-12-29 09:23:03 +00:00
[web] set a default, strict CSP on ALL endpoints (#6271)
* Remove use of CSP_PERCENTAGE * Move header calculation earlier * Set a default policy and add comments * Apply the CSP header to all responses * Enable CSP in dev environment * [web] set a default, strict CSP on ALL endpoints * [misc] enable CSP in dev-env * Only build the default policy once * Update docker-compose.yml * [web] webpack: set default CSP header on webpack assets This aligns the webpack dev-server with production in nocdn=true mode. Co-authored-by: Alf Eaton <alf.eaton@overleaf.com> GitOrigin-RevId: 088a6082ad21c5b3f229887ba0ab3eca8d0528cd
This commit is contained in:
parent
ecfe3df5ed
commit
224edddad4
4 changed files with 60 additions and 26 deletions
|
@ -6,46 +6,37 @@ module.exports = function ({
|
|||
reportPercentage,
|
||||
reportOnly = false,
|
||||
exclude = [],
|
||||
percentage,
|
||||
}) {
|
||||
const header = reportOnly
|
||||
? 'Content-Security-Policy-Report-Only'
|
||||
: 'Content-Security-Policy'
|
||||
|
||||
const defaultPolicy = buildDefaultPolicy(reportUri)
|
||||
|
||||
return function (req, res, next) {
|
||||
// set the default policy
|
||||
res.set(header, defaultPolicy)
|
||||
|
||||
const originalRender = res.render
|
||||
|
||||
res.render = (...args) => {
|
||||
const view = relativeViewPath(args[0])
|
||||
|
||||
// enable the CSP header for a percentage of requests
|
||||
const belowCutoff = Math.random() * 100 <= percentage
|
||||
|
||||
if (belowCutoff && !exclude.includes(view)) {
|
||||
if (exclude.includes(view)) {
|
||||
// remove the default policy
|
||||
res.removeHeader(header)
|
||||
} else {
|
||||
// set the view policy
|
||||
res.locals.cspEnabled = true
|
||||
|
||||
const scriptNonce = crypto.randomBytes(16).toString('base64')
|
||||
|
||||
res.locals.scriptNonce = scriptNonce
|
||||
|
||||
const directives = [
|
||||
`script-src 'nonce-${scriptNonce}' 'unsafe-inline' 'strict-dynamic' https: 'report-sample'`,
|
||||
`object-src 'none'`,
|
||||
`base-uri 'none'`,
|
||||
]
|
||||
|
||||
// enable the report URI for a percentage of CSP-enabled requests
|
||||
const belowReportCutoff = Math.random() * 100 <= reportPercentage
|
||||
|
||||
if (reportUri && belowReportCutoff) {
|
||||
directives.push(`report-uri ${reportUri}`)
|
||||
// NOTE: implement report-to once it's more widely supported
|
||||
}
|
||||
|
||||
const policy = directives.join('; ')
|
||||
const policy = buildViewPolicy(scriptNonce, reportPercentage, reportUri)
|
||||
|
||||
// Note: https://csp-evaluator.withgoogle.com/ is useful for checking the policy
|
||||
|
||||
const header = reportOnly
|
||||
? 'Content-Security-Policy-Report-Only'
|
||||
: 'Content-Security-Policy'
|
||||
|
||||
res.set(header, policy)
|
||||
}
|
||||
|
||||
|
@ -56,6 +47,43 @@ module.exports = function ({
|
|||
}
|
||||
}
|
||||
|
||||
const buildDefaultPolicy = reportUri => {
|
||||
const directives = [
|
||||
`base-uri 'none'`, // forbid setting a "base" element
|
||||
`default-src 'none'`, // forbid loading anything from a "src" attribute
|
||||
`form-action 'none'`, // forbid setting a form action
|
||||
`frame-ancestors 'none'`, // forbid loading embedded content
|
||||
`img-src 'self'`, // allow loading images from the same domain (e.g. the favicon).
|
||||
]
|
||||
|
||||
if (reportUri) {
|
||||
directives.push(`report-uri ${reportUri}`)
|
||||
// NOTE: implement report-to once it's more widely supported
|
||||
}
|
||||
|
||||
return directives.join('; ')
|
||||
}
|
||||
|
||||
const buildViewPolicy = (scriptNonce, reportPercentage, reportUri) => {
|
||||
const directives = [
|
||||
`script-src 'nonce-${scriptNonce}' 'unsafe-inline' 'strict-dynamic' https: 'report-sample'`, // only allow scripts from certain sources
|
||||
`object-src 'none'`, // forbid loading an "object" element
|
||||
`base-uri 'none'`, // forbid setting a "base" element
|
||||
]
|
||||
|
||||
if (reportUri) {
|
||||
// enable the report URI for a percentage of CSP-enabled requests
|
||||
const belowReportCutoff = Math.random() * 100 <= reportPercentage
|
||||
|
||||
if (belowReportCutoff) {
|
||||
directives.push(`report-uri ${reportUri}`)
|
||||
// NOTE: implement report-to once it's more widely supported
|
||||
}
|
||||
}
|
||||
|
||||
return directives.join('; ')
|
||||
}
|
||||
|
||||
const webRoot = path.resolve(__dirname, '..', '..', '..')
|
||||
|
||||
// build the view path relative to the web root
|
||||
|
@ -64,3 +92,5 @@ function relativeViewPath(view) {
|
|||
? path.relative(webRoot, view)
|
||||
: path.join('app', 'views', view)
|
||||
}
|
||||
|
||||
module.exports.buildDefaultPolicy = buildDefaultPolicy
|
||||
|
|
|
@ -271,7 +271,7 @@ webRouter.use(
|
|||
// add CSP header to HTML-rendering routes, if enabled
|
||||
if (Settings.csp && Settings.csp.enabled) {
|
||||
logger.info('adding CSP header to rendered routes', Settings.csp)
|
||||
webRouter.use(csp(Settings.csp))
|
||||
app.use(csp(Settings.csp))
|
||||
}
|
||||
|
||||
logger.info('creating HTTP server'.yellow)
|
||||
|
|
|
@ -772,7 +772,6 @@ module.exports = {
|
|||
moduleImportSequence: ['launchpad', 'server-ce-scripts', 'user-activate'],
|
||||
|
||||
csp: {
|
||||
percentage: parseFloat(process.env.CSP_PERCENTAGE) || 0,
|
||||
enabled: process.env.CSP_ENABLED === 'true',
|
||||
reportOnly: process.env.CSP_REPORT_ONLY === 'true',
|
||||
reportPercentage: parseFloat(process.env.CSP_REPORT_PERCENTAGE) || 0,
|
||||
|
|
|
@ -3,6 +3,7 @@ const merge = require('webpack-merge')
|
|||
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
||||
|
||||
const base = require('./webpack.config')
|
||||
const { buildDefaultPolicy } = require('./app/src/infrastructure/CSP')
|
||||
|
||||
module.exports = merge(base, {
|
||||
mode: 'development',
|
||||
|
@ -30,6 +31,10 @@ module.exports = merge(base, {
|
|||
port: 3808,
|
||||
public: 'www.dev-overleaf.com:443',
|
||||
|
||||
headers: {
|
||||
'Content-Security-Policy': buildDefaultPolicy(),
|
||||
},
|
||||
|
||||
// Customise output to the (node) console
|
||||
stats: {
|
||||
colors: true, // Enable some coloured highlighting
|
||||
|
|
Loading…
Reference in a new issue