Since #585 we are able to configure the HSTS policy provided by helmet.
Let's take the next step to modern web security and add a Content-Security-Policy.
This can be done by helmet, too. Details about how to do this can be found here:
https://helmetjs.github.io/docs/csp/
Keep in mind we should allow people themselves to enable and extend them. We want to ship secure defaults, so they should be enabled by default.
Also, keep in mind that we have the usecdn option which will include external sources. These should be added when usecdn is enabled, while it shouldn't be allowed to use them by default when usecdn is disabled.
Last but not least there is some validation work to do. Make sure that the features page is successfully rendered by default. Including all external embeddings like YouTube, Disqus, Google Analytics and more.
Pull Requests are very welcome ❤️
This is maybe interesting for someone who wants to work on web security and learn about the current technologies.
Some background information:
Since I also did #585, I'd like to do this one too. I figure that this will probably turn out to take some more time, since it also includes testing all features, plus figuring out all the external services.
Will we allow all images? I figure if we are only going to allow images from the configured provider, users will not be able to specify custom URLs, which doesn't seem like nice UX. (Of course a whitelist could be configurable if deemed a valid use case)
Also, is the feature list at /feature complete? (meaning if all issues there and the login/register form are fixed, there won't be any issues with variations?)
Update, looks like there is a significant number of inline styles in the views:
➜ views git:(master) ✗ grep -i --color -r "style=" . | wc -l
81
I'll be cleaning these up first in a separate PR, I guess. Using unsafe-inline for these relatively few doesn't seem like a viable 'solution' to me.
In unrelated news, helmet didn't promise too much, sending the headers is really straightforward.
Awesome! ❤️ I love to hear/read this!
The /features page should cover almost everything. We have to check the published notes and the slide-mode, but these are listed as features.
For now, I think we can go for these rules:
We should allow disabling these UX-friendly rules and restrict them to the real minimum, but that shouldn't be a problem.
I'm not completely sure what the ideal rules for fonts are, but that's something we can figure in this thread or with issues that open up afterward.
If we ware not sure about how to handle something we should block it until someone complains about it. This way we should get tight defaults.
Yes, removing the inline styles from the view sounds like a good idea.
I'll make sure we get these changes in as soon as possible.
Status update: The rules are working, I got the slide view to work completely (for some reason I started there). MathJax was using inline script blocks for configuration that were later sent though eval(). I moved these to a separate file and changed the syntax a little as per their recommendations.
From a short look, we probably won't be able to disable inline CSS even in the long run - some JS libs seem to be using it. I started externalising some of the inline styles, but some of them are dynamic.
The issue I'm facing currently is that some libraries are loaded using the (apparent) Webpack equivalent of a global <script> tag, the script-loader plugin. That one only supports eval() though, and that is denied because I'd rather not include unsafe-eval in script-src. That project's CSP policy is simple: Don't use this plugin with CSP. (No alternatives linked)
Here is the problematic section in the Webpack config:
Simply removing the script! prefix removes the warnings, but the libraries aren't available either. (lots of browser errors about undefined things)
Any advice on how to continue from there? I haven't looked into what these libraries are for yet, but I hope there's another way to include them.
Update: Temporarily allowing unsafe-eval, I've been able to test the rest - it seems to be working quite well, except for the speaker notes in slide mode. These are broken because they're one huge inline script. No idea how to fix them since they seem to be defined entirely in the library. I also had to allow everything in object-src (applets etc.) for Chrome's PDF viewer.
Another thing I noticed is that some of the integrations are still using HTTP URLs. That may need to be fixed.
Update: This is my current status: https://me.l1t.li:1337/features
This is already amazing!
I currently think about the best way to solve the script-loader problem. We can maybe have a chat on gitter about it in detail.
I have to verify a lot of things because I understand why they do it, as they do it, but finding a better implementation for it without breaking things is not that easy.
Sure! I've joined the room on gitter, so if you have any idea, text me there. We seem to be using a deprecated version of Webpack (1.x), so maybe there's a better solution in a more recent version. I doubt this though because it doesn't seem there's an obvious way around eval for global script blocks without module support on the library side.
The options I see so far are:
unsafe-eval (unsafe)<script>I see a CSP violation in Safari on an instance we host as well as on the Heroku instance. The error is:
Refused to execute a script because its hash, its nonce, or 'unsafe-inline' does not appear in the script-src directive of the Content Security Policy.
Safari locates the error at <the-hash-of-the-pad>:0, so line 0 of the HTML, which probably isn't very helpful. If anyone knows more about debugging this in Safari let me know and I will try to see if I can figure it out.
You could try adding a reportUri in the directives map of the CSP configuration, which clients then send details about CSP violations to. A helpful service to process these is https://report-uri.com/. (I've seen it recommended multiple times, but haven't used it myself) Idk though how detailed these are. If that doesn't help, maybe Chrome provides more details? Any inline scripts should be blocked by default iirc so you could also try searching for these.
I thought yesterday it would be pretty useful to have an option to directly inject the report URI instead of modifying the directives (as this is not possible when you use env vars only). I may prepare a PR for this later
Adding a report-uri to the csp directives in the config.json doesn't work, as HackMD mangles all the directives to arrays, but helmet-csp awaits a string for report-uri.
I'll add some code to handle this later today.
Was about to do it anyways ^^ So lets do it!
It turns that the CSP violation I mentioned before was caused by uBlock Origin, so nothing related to HackMD itself.
Added an option for the report URI. maybe @xxyy will have a look at it as review.
I am having some difficulty getting CSP to work for the reveal.js speaker-notes. I think the inline CSS is the issue there. I am running hackmd (docker) v 1.1.0-ce.
Below is my apache reverse proxy config (replaced domain with server.net).
Do you have any advice regarding setting the CSP in the reverse proxy?
Ideally, I'd prefer to serve all javascript from my server rather than third parties and am also keen on avoiding tracking scripts/cookies by google etc. (I think one of the loaded javascripts was calling google analytics.).
<VirtualHost *:443>
ServerName present.server.net
ServerAdmin [email protected]
#HACKMD in DOCKER
ProxyPreserveHost On
ProxyRequests Off
ProxyPass / http://hackmd:3000/
ProxyPassReverse / http://hackmd:3000/
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
SSLProxyEngine On
SSLEngine on
SSLCertificateFile /mnt/certs/ssl/server.net/server.net.crt
SSLCertificateKeyFile /mnt/certs/ssl/server.net/private/server.net_dec.key
SSLCertificateChainFile /mnt/certs/ssl/server.net/intermediate.ca
# Fix HTTPS requests
RequestHeader set X-Forwarded-Proto "https" env=HTTPS
<FilesMatch "\.(cgi|shtml|phtml|php)$">
SSLOptions +StdEnvVars
</FilesMatch>
<Directory /usr/lib/cgi-bin>
SSLOptions +StdEnvVars
</Directory>
Header set Content-Security-Policy "default-src 'self'; script-src 'self'"
# SSL Protocol Adjustments:
BrowserMatch "MSIE [2-6]" \
nokeepalive ssl-unclean-shutdown \
downgrade-1.0 force-response-1.0
# MSIE 7 and newer should be able to use keepalive
BrowserMatch "MSIE [17-9]" ssl-unclean-shutdown
</VirtualHost>
```
The defaults should include the hash of that very speaker notes script to
allow it, please verify with your browser's developer tools whether that is
actually being sent by the server in the CSP header. If it is, and still
not working, maybe the hash changed.
On Mon, 14 May 2018, 02:32 zeigerpuppy, notifications@github.com wrote:
I am having some difficulty getting CSP to work for the reveal.js
speaker-notes. I think the inline CSS is the issue there. I am running
hackmd (docker) v 1.1.0-ce.Below is my apache reverse proxy config (replaced domain with server.net).
Do you have any advice regarding setting the CSP in the reverse proxy?
Ideally, I'd prefer to serve all javascript from my server rather than
third parties and am also keen on avoiding tracking scripts/cookies by
google etc. (I think one of the loaded javascripts was calling google
analytics.).
ServerName present.server.net ServerAdmin [email protected] #HACKMD in DOCKER ProxyPreserveHost On ProxyRequests Off ProxyPass / http://hackmd:3000/ ProxyPassReverse / http://hackmd:3000/ ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined SSLProxyEngine On SSLEngine on SSLCertificateFile /mnt/certs/ssl/server.net/server.net.crt SSLCertificateKeyFile /mnt/certs/ssl/server.net/private/server.net_dec.key SSLCertificateChainFile /mnt/certs/ssl/server.net/intermediate.ca # Fix HTTPS requests RequestHeader set X-Forwarded-Proto "https" env=HTTPS <FilesMatch "\.(cgi|shtml|phtml|php)$"> SSLOptions +StdEnvVars </FilesMatch> <Directory /usr/lib/cgi-bin> SSLOptions +StdEnvVars </Directory> Header set Content-Security-Policy "default-src 'self'; script-src 'self'" # SSL Protocol Adjustments: BrowserMatch "MSIE [2-6]" \ nokeepalive ssl-unclean-shutdown \ downgrade-1.0 force-response-1.0 # MSIE 7 and newer should be able to use keepalive BrowserMatch "MSIE [17-9]" ssl-unclean-shutdown—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/hackmdio/hackmd/issues/594#issuecomment-388667848,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAhY_saPVzBVutPcZJwSVzFOzNZbO_woks5tyNC0gaJpZM4P8ib3
.
Please use the built-in CSPs and not serve own ones by your reverse proxy. We provide a very detailed set of configs to fine tune CSPs for your needs.
We try to keep it minimal while not breaking functionality. Your CSPs are definitely too simple. With a CSP like this HackMD doesn't work right now.
On 14 May 2018 02:32:50 GMT+02:00, zeigerpuppy notifications@github.com wrote:
I am having some difficulty getting CSP to work for the reveal.js
speaker-notes. I think the inline CSS is the issue there. I am
running hackmd (docker) v 1.1.0-ce.Below is my apache reverse proxy config (replaced domain with
server.net).Do you have any advice regarding setting the CSP in the reverse proxy?
Ideally, I'd prefer to serve all javascript from my server rather than
third parties and am also keen on avoiding tracking scripts/cookies by
google etc. (I think one of the loaded javascripts was calling google
analytics.).<VirtualHost *:443> ServerName present.server.net ServerAdmin [email protected] #HACKMD in DOCKER ProxyPreserveHost On ProxyRequests Off ProxyPass / http://hackmd:3000/ ProxyPassReverse / http://hackmd:3000/ ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined SSLProxyEngine On SSLEngine on SSLCertificateFile /mnt/certs/ssl/server.net/server.net.crt SSLCertificateKeyFile /mnt/certs/ssl/server.net/private/server.net_dec.key SSLCertificateChainFile /mnt/certs/ssl/server.net/intermediate.ca # Fix HTTPS requests RequestHeader set X-Forwarded-Proto "https" env=HTTPS <FilesMatch "\.(cgi|shtml|phtml|php)$"> SSLOptions +StdEnvVars </FilesMatch> <Directory /usr/lib/cgi-bin> SSLOptions +StdEnvVars </Directory> Header set Content-Security-Policy "default-src 'self'; script-src 'self'" # SSL Protocol Adjustments: BrowserMatch "MSIE [2-6]" \ nokeepalive ssl-unclean-shutdown \ downgrade-1.0 force-response-1.0 # MSIE 7 and newer should be able to use keepalive BrowserMatch "MSIE [17-9]" ssl-unclean-shutdown </VirtualHost>```
--
Signed
Sheogorath
Sent from my Android device with K-9 Mail. Please excuse my brevity.
Thanks for the tip. I have removed the CSP from the reverse proxy but am still encountering an error with the served CSP:
The offending file/line is : notes.html:321
Here is the error in Firefox
Content Security Policy: The page’s settings blocked the loading of a resource at self (“script-src https://present.server.net https://vimeo.com https://gist.github.com https://www.slideshare.net https://query.yahooapis.com 'unsafe-eval' https://*.disqus.com https://*.disquscdn.com https://www.google-analytics.com 'nonce-6ad1562a-1ecd-42e0-81b0-640ae972d180' 'sha256-EtvSSxRwce5cLeFBZbvZvDrTiRoyoXbWWwvEVciM5Ag='”). Source:
(function() {
var notes,
....
I guess it's possible the hash has changed, how is this regenerated (I have tried logging out, reloading and browsing to the speaker notes but the same error persists.)
I may try refactoring things a bit to drop dependencies on external scripts/fonts, it would be very much preferable for any javascript to be served locally. I don't mind running a local mathjax server if this is required too. Would this be a simple matter of replacing a few javascript calls or would it require significant modification of the base code?
Also, I generally think it's a better idea to define CSP on the external facing web server (in this case apache2.4 as a reverse proxy). This improved auditing and mitigation of security issues. I'm happy to document any changes that work from this regard and appreciate the pointers so far.
Honestly, the hash is updated manually in the source code, no magic involved. You can verify/change it by pasting the whole contents of the problematic script block (i.e. without the <script> and </script>) and generating the SHA-256 of that text.
Idk if you're referring to that, but there is a useCDN config setting that serves (at least some of the) resources locally if you set it to false. The built-in CSP provider adapts to that automatically.