summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorChristoph (Sheogorath) Kern2018-01-22 20:43:46 +0100
committerGitHub2018-01-22 20:43:46 +0100
commit7de6e3211f797ed9e9e5ca3d52181f77abf030d0 (patch)
tree8dc88e060df51106eedda199fe7ea571d0ffd5b4 /lib
parentfbfe3272f58232e6a7e46a82232b4dab065e2390 (diff)
parent3a752fde5117e800d65e26cbe7b15d65eb5b491e (diff)
Merge pull request #598 from xxyy/feature/csp
Implement basic CSP support
Diffstat (limited to '')
-rw-r--r--lib/config/default.js7
-rw-r--r--lib/config/environment.js3
-rw-r--r--lib/csp.js80
-rw-r--r--lib/response.js3
4 files changed, 92 insertions, 1 deletions
diff --git a/lib/config/default.js b/lib/config/default.js
index 000c154a..ec7c7451 100644
--- a/lib/config/default.js
+++ b/lib/config/default.js
@@ -13,6 +13,13 @@ module.exports = {
includeSubdomains: true,
preload: true
},
+ csp: {
+ enable: true,
+ directives: {
+ },
+ addDefaults: true,
+ upgradeInsecureRequests: 'auto'
+ },
protocolusessl: false,
usecdn: true,
allowanonymous: true,
diff --git a/lib/config/environment.js b/lib/config/environment.js
index eedd4913..24a85d65 100644
--- a/lib/config/environment.js
+++ b/lib/config/environment.js
@@ -14,6 +14,9 @@ module.exports = {
includeSubdomains: toBooleanConfig(process.env.HMD_HSTS_INCLUDE_SUBDOMAINS),
preload: toBooleanConfig(process.env.HMD_HSTS_PRELOAD)
},
+ csp: {
+ enable: toBooleanConfig(process.env.HMD_CSP_ENABLE)
+ },
protocolusessl: toBooleanConfig(process.env.HMD_PROTOCOL_USESSL),
alloworigin: toArrayConfig(process.env.HMD_ALLOW_ORIGIN),
usecdn: toBooleanConfig(process.env.HMD_USECDN),
diff --git a/lib/csp.js b/lib/csp.js
new file mode 100644
index 00000000..509bc530
--- /dev/null
+++ b/lib/csp.js
@@ -0,0 +1,80 @@
+var config = require('./config')
+var uuid = require('uuid')
+
+var CspStrategy = {}
+
+var defaultDirectives = {
+ defaultSrc: ['\'self\''],
+ scriptSrc: ['\'self\'', 'vimeo.com', 'https://gist.github.com', 'www.slideshare.net', 'https://query.yahooapis.com', 'https://*.disqus.com', '\'unsafe-eval\''],
+ // ^ TODO: Remove unsafe-eval - webpack script-loader issues https://github.com/hackmdio/hackmd/issues/594
+ imgSrc: ['*'],
+ styleSrc: ['\'self\'', '\'unsafe-inline\'', 'https://assets-cdn.github.com'], // unsafe-inline is required for some libs, plus used in views
+ fontSrc: ['\'self\'', 'https://public.slidesharecdn.com'],
+ objectSrc: ['*'], // Chrome PDF viewer treats PDFs as objects :/
+ childSrc: ['*'],
+ connectSrc: ['*']
+}
+
+var cdnDirectives = {
+ scriptSrc: ['https://cdnjs.cloudflare.com', 'https://cdn.mathjax.org'],
+ styleSrc: ['https://cdnjs.cloudflare.com', 'https://fonts.googleapis.com'],
+ fontSrc: ['https://cdnjs.cloudflare.com', 'https://fonts.gstatic.com']
+}
+
+CspStrategy.computeDirectives = function () {
+ var directives = {}
+ mergeDirectives(directives, config.csp.directives)
+ mergeDirectivesIf(config.csp.addDefaults, directives, defaultDirectives)
+ mergeDirectivesIf(config.usecdn, directives, cdnDirectives)
+ if (!areAllInlineScriptsAllowed(directives)) {
+ addInlineScriptExceptions(directives)
+ }
+ addUpgradeUnsafeRequestsOptionTo(directives)
+ return directives
+}
+
+function mergeDirectives (existingDirectives, newDirectives) {
+ for (var propertyName in newDirectives) {
+ var newDirective = newDirectives[propertyName]
+ if (newDirective) {
+ var existingDirective = existingDirectives[propertyName] || []
+ existingDirectives[propertyName] = existingDirective.concat(newDirective)
+ }
+ }
+}
+
+function mergeDirectivesIf (condition, existingDirectives, newDirectives) {
+ if (condition) {
+ mergeDirectives(existingDirectives, newDirectives)
+ }
+}
+
+function areAllInlineScriptsAllowed (directives) {
+ return directives.scriptSrc.indexOf('\'unsafe-inline\'') !== -1
+}
+
+function addInlineScriptExceptions (directives) {
+ directives.scriptSrc.push(getCspNonce)
+ // TODO: This is the SHA-256 hash of the inline script in build/reveal.js/plugins/notes/notes.html
+ // Any more clean solution appreciated.
+ directives.scriptSrc.push('\'sha256-EtvSSxRwce5cLeFBZbvZvDrTiRoyoXbWWwvEVciM5Ag=\'')
+}
+
+function getCspNonce (req, res) {
+ return "'nonce-" + res.locals.nonce + "'"
+}
+
+function addUpgradeUnsafeRequestsOptionTo (directives) {
+ if (config.csp.upgradeInsecureRequests === 'auto' && config.usessl) {
+ directives.upgradeInsecureRequests = true
+ } else if (config.csp.upgradeInsecureRequests === true) {
+ directives.upgradeInsecureRequests = true
+ }
+}
+
+CspStrategy.addNonceToLocals = function (req, res, next) {
+ res.locals.nonce = uuid.v4()
+ next()
+}
+
+module.exports = CspStrategy
diff --git a/lib/response.js b/lib/response.js
index 1c04f9f6..445aa0d7 100644
--- a/lib/response.js
+++ b/lib/response.js
@@ -598,7 +598,8 @@ function showPublishSlide (req, res, next) {
lastchangeuserprofile: note.lastchangeuser ? models.User.getProfile(note.lastchangeuser) : null,
robots: meta.robots || false, // default allow robots
GA: meta.GA,
- disqus: meta.disqus
+ disqus: meta.disqus,
+ cspNonce: res.locals.nonce
}
return renderPublishSlide(data, res)
}).catch(function (err) {