CSP.js 1.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162
  1. const crypto = require('crypto')
  2. const path = require('path')
  3. module.exports = function ({
  4. reportUri,
  5. reportOnly = false,
  6. exclude = [],
  7. percentage
  8. }) {
  9. return function (req, res, next) {
  10. const originalRender = res.render
  11. res.render = (...args) => {
  12. const view = relativeViewPath(args[0])
  13. // enable the CSP header for a percentage of requests
  14. const belowCutoff = Math.random() * 100 <= percentage
  15. if (belowCutoff && !exclude.includes(view)) {
  16. res.locals.cspEnabled = true
  17. const scriptNonce = crypto.randomBytes(16).toString('base64')
  18. res.locals.scriptNonce = scriptNonce
  19. const directives = [
  20. `script-src 'nonce-${scriptNonce}' 'unsafe-inline' 'strict-dynamic' https: 'report-sample'`,
  21. `object-src 'none'`,
  22. `base-uri 'none'`
  23. ]
  24. if (reportUri) {
  25. directives.push(`report-uri ${reportUri}`)
  26. // NOTE: implement report-to once it's more widely supported
  27. }
  28. const policy = directives.join('; ')
  29. // Note: https://csp-evaluator.withgoogle.com/ is useful for checking the policy
  30. const header = reportOnly
  31. ? 'Content-Security-Policy-Report-Only'
  32. : 'Content-Security-Policy'
  33. res.set(header, policy)
  34. }
  35. originalRender.apply(res, args)
  36. }
  37. next()
  38. }
  39. }
  40. const webRoot = path.resolve(__dirname, '..', '..', '..')
  41. // build the view path relative to the web root
  42. function relativeViewPath(view) {
  43. return path.isAbsolute(view)
  44. ? path.relative(webRoot, view)
  45. : path.join('app', 'views', view)
  46. }