Ghost
Ghost copied to clipboard
Feature: headers.yaml & default security headers
In Ghost we have the ability to set a content type header for any custom route in routes.yaml, but otherwise, headers are not customisable.
There are many different headers that a user might want to set, the most common use case being to improve security. We should also provide a better default set of headers, as part of this.
Ref material
- Netlify _headers file docs: https://www.netlify.com/docs/headers-and-basic-auth/
- Ghost docs _headers file example: https://github.com/TryGhost/docs/commit/8af94d2c92d455738b940150cba56bf741ed9568
- Statamic content type: https://docs.statamic.com/routing#content-types
- Ghost content type: https://docs.ghost.org/concepts/routing/#content_type
- Ghost CLI default headers: https://github.com/TryGhost/Ghost-CLI/blob/975049cd850f0cdf33555c73a2b675fe7b6e742e/extensions/nginx/templates/ssl-params.conf#L11
Spec
We will provide the ability to upload a headers.yaml
file. The file must contain valid YAML.
Headers will apply to the web application of Ghost only (same as redirects).
We will allow for setting headers per route with route matching exactly like Netlify _headers
Format
The top level YAML keys represent rules, with wildcard matches. Nested key value pairs should represent header names and values. Case should not matter.
/*:
Header-Name: Value
/something/*:
header-name: value
Real world example (taken from Ghost docs):
/*:
Referrer-Policy: no-referrer-when-downgrade
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-Xss-Protection: 1; mode=block
Feature-Policy: accelerometer 'none'; camera 'none'; geolocation 'none'; gyroscope 'none'; magnetometer 'none'; microphone 'none'; payment 'none'; usb 'none'
Blacklisted / Ignored Headers
There may be some headers that Ghost sets which should be ignored if they are overridden via headers.yaml, to prevent any conflicts or issues on Ghost(Pro). The first that comes to mind is Cache-Control & Proxy headers.
- Cache-control
- X-Forwarded-*
- X-Ghost-*
- Location?
- ???
X-Powered-By
Express also automatically sets X-Powered-By to express. It's always been tricky for us to remove because the setting doesn't cascade, and we have 4 different express apps. As part of this task we should remove X-Powered-By: express from everywhere.
Default Headers
Ghost currently does not set any security headers. This task includes deciding on and implementing a set of defaults for the frontend, admin and API. Defaults for the frontend should then be included as the default headers.yaml file, so that they can be overridden and extended.
At bare minimum we should have defaults something like those Ghost-CLI already sets:
/*:
Strict-Transport-Security 'max-age=63072000; includeSubDomains; preload';
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options nosniff;
Note: Because of the View Site feature in Ghost admin, the X-Frame-Options
header should be set to allow-from admin.url
if an admin url is set.
To determine a full set of defaults, we should review the headers set on docs: https://github.com/TryGhost/docs/commit/8af94d2c92d455738b940150cba56bf741ed9568 and the advisory info from securityheaders.com
If a user uploads a file without these defaults, they won't be set for the Ghost frontend.
It may be the case that the API & admin need slightly different or less default security headers, but we should still implement the best possible set we can without impacting users.
Tasks
- [ ] Finalise header blacklist
- [ ] Decide on default headers for each area of Ghost
- [ ] Implement headers on admin & api
- [ ] Fully remove x-powered-by
- [ ] Implement frontend defaults & ability to override via headers.yaml file
- [ ] Add UI for uploading headers.yaml
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
Not stale - note to self: need to update the bot to ignore the feature label
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
Not stale - still relevant.
Hi there @ErisDS 👋🏽 I was thinking of giving this feature a try. I just wanted to double check if there has been any advancements or any decisions have been made concerning this issue? If not, I m gonna go ahead and figure out what all has to be done for this to advance. Cheerz.
Hey @piggydoughnut :wave: There have not been any advances as far as I know of. Although I see the "pinned" label has been removed last year, so the stale bot should have closed this issue long time ago :grimacing: Will ping internally if the feature is still relevant!
@naz Oh I see, thank you for that :) I thought I d ask first as it seems suspiciously old :) hah
Is there still work being done on this feature? I would be interested to take it on.
Is there still work being done on this feature? I would be interested to take it on.
My student group (me, dmitrymarokhonov and two others) took on this issue as part of a university course project, but we could not implement it successfully and I believe our pull request was rejected. So I can at least tell you that we aren't working on it anymore.
@naz Is this issue still relevant? If yes, can I take up this issue?
Hi @ErisDS! I see this ticket seems to be still open. I'm interested in picking up this issue but I wanted to confirm that this feature request is still live before I begin working. Have there been any changes to the current spec?