feat(server): support csp report-uri (#7307)

This commit is contained in:
Yugo Ogura 2020-05-05 03:24:17 +09:00 committed by GitHub
parent c2c7081dc2
commit 37271f8ac4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 47 additions and 8 deletions

View File

@ -70,9 +70,10 @@ export default ({ options, nuxt, renderRoute, resources }) => async function nux
if (options.render.csp && cspScriptSrcHashes) {
const { allowedSources, policies } = options.render.csp
const cspHeader = options.render.csp.reportOnly ? 'Content-Security-Policy-Report-Only' : 'Content-Security-Policy'
const isReportOnly = !!options.render.csp.reportOnly
const cspHeader = isReportOnly ? 'Content-Security-Policy-Report-Only' : 'Content-Security-Policy'
res.setHeader(cspHeader, getCspString({ cspScriptSrcHashes, allowedSources, policies, isDev: options.dev }))
res.setHeader(cspHeader, getCspString({ cspScriptSrcHashes, allowedSources, policies, isDev: options.dev, isReportOnly }))
}
// Send response
@ -123,20 +124,18 @@ const defaultPushAssets = (preloadFiles, shouldPush, publicPath, options) => {
return links
}
const getCspString = ({ cspScriptSrcHashes, allowedSources, policies, isDev }) => {
const getCspString = ({ cspScriptSrcHashes, allowedSources, policies, isDev, isReportOnly }) => {
const joinedHashes = cspScriptSrcHashes.join(' ')
const baseCspStr = `script-src 'self'${isDev ? ' \'unsafe-eval\'' : ''} ${joinedHashes}`
const policyObjectAvailable = typeof policies === 'object' && policies !== null && !Array.isArray(policies)
if (Array.isArray(allowedSources) && allowedSources.length) {
return `${baseCspStr} ${allowedSources.join(' ')}`
return isReportOnly && policyObjectAvailable && !!policies['report-uri'] ? `${baseCspStr} ${allowedSources.join(' ')}; report-uri ${policies['report-uri']};` : `${baseCspStr} ${allowedSources.join(' ')}`
}
const policyObjectAvailable = typeof policies === 'object' && policies !== null && !Array.isArray(policies)
if (policyObjectAvailable) {
const transformedPolicyObject = transformPolicyObject(policies, cspScriptSrcHashes)
return Object.entries(transformedPolicyObject).map(([k, v]) => `${k} ${v.join(' ')}`).join('; ')
return Object.entries(transformedPolicyObject).map(([k, v]) => `${k} ${Array.isArray(v) ? v.join(' ') : v}`).join('; ')
}
return baseCspStr

View File

@ -128,6 +128,26 @@ describe('basic ssr csp', () => {
}
)
test(
'Contain report-uri in Content-Security-Policy-Report-Only header, when explicitly asked for CSRP, allowedSources, csp.report-url',
async () => {
const cspOption = {
allowedSources: ['https://example.com', 'https://example.io'],
reportOnly: true,
policies: {
'report-uri': '/csp_report_uri'
}
}
nuxt = await startCspDevServer(cspOption)
const { headers } = await rp(url('/stateless'))
expect(headers[reportOnlyHeader]).toMatch(/^script-src 'self' 'sha256-.*'/)
expect(headers[reportOnlyHeader]).toContain('https://example.com')
expect(headers[reportOnlyHeader]).toContain('https://example.io')
expect(headers[reportOnlyHeader]).toContain('report-uri /csp_report_uri')
}
)
test(
'Contain only unique hashes in header when csp.policies is set',
async () => {
@ -302,6 +322,26 @@ describe('basic ssr csp', () => {
}
)
test(
'Contain report-uri in Content-Security-Policy-Report-Only header, when explicitly asked for CSRP, allowedSources, csp.report-url',
async () => {
const cspOption = {
allowedSources: ['https://example.com', 'https://example.io'],
reportOnly: true,
policies: {
'report-uri': '/csp_report_uri'
}
}
nuxt = await startCspDevServer(cspOption)
const { headers } = await rp(url('/stateless'))
expect(headers[reportOnlyHeader]).toMatch(/^script-src 'self' 'sha256-.*'/)
expect(headers[reportOnlyHeader]).toContain('https://example.com')
expect(headers[reportOnlyHeader]).toContain('https://example.io')
expect(headers[reportOnlyHeader]).toContain('report-uri /csp_report_uri')
}
)
test(
'Contain Content-Security-Policy-Report-Only header, when csp.policies.script-src is not set',
async () => {