Skip to content

Security headers ​

A few lines in the server's response telling the browser what the page may not do: guess a file's type, open inside a frame on someone else's domain, hand strangers the full address of the page.

They are sent for you. Configured in System → System Settings → Security.

What is sent out of the box ​

These three are on by default because they break nothing:

HeaderSettingDefaultWhat it does
X-Content-Type-Options—nosniffThe browser does not guess a file's type from its content. An uploaded "image" with a script inside stays an image
X-Frame-Optionspageblocks_security_frame_optionsSAMEORIGINThe site cannot be framed on another domain — clickjacking protection
Referrer-Policypageblocks_security_referrer_policystrict-origin-when-cross-originAnother site sees that the visit came from your domain, but not which page

An empty setting means "do not send this header". nosniff has no setting of its own — there is no site on which guessing a file's type is wanted.

pageblocks_security_headers is the master switch, and it defaults to true: nothing has to be turned on for the three above to go out.

Why these are on rather than left to you

A header you have to go and switch on never gets switched on, on most sites. So the safe set works out of the box, and only what can break a site asks for a decision.

What you switch on yourself ​

These three are off for a reason: each of them can take a working site down.

HeaderSettingDefault
Strict-Transport-Securitypageblocks_security_hsts0 — not sent
Content-Security-Policypageblocks_security_cspempty — not sent
Permissions-Policypageblocks_security_permissions_policyempty — not sent

HSTS ​

Tells the browser the site is https-only, and for how many seconds to remember that.

The browser remembers for a long time, and you cannot take it back

Switch HSTS on for a year and you get a year during which your visitors' browsers refuse to open the site over http. A certificate expires, https breaks on a subdomain, you move hosts — and the visitor sees an error instead of a page, with no way around it.

Turn it on once https works everywhere and you trust it. Start with a day (86400) and grow.

Over http the header is not sent at all: there it is meaningless and dangerous.

Content-Security-Policy ​

Lists where the page may load scripts, styles, images and fonts from.

Breaks inline scripts and third-party widgets

The first time you switch it on it will kill analytics, chats, maps and everything pasted in as a <script> tag in the markup. Tune the policy on a copy of the site with the browser console open: it names every blocked source.

Permissions-Policy ​

Denies browser features the site does not need:

geolocation=(), microphone=(), camera=()

Look at what the site actually uses first

Denying geolocation breaks a map that shows visitors where they are. Hence the empty default: the list depends on the site, not on general principle.

Someone else's header is not overwritten ​

If the web server or the site has already sent a header, the component leaves it alone. A rule in nginx or .htaccess can stay as it is — there is no conflict, and it wins.

Where the headers reach ​

Every response the site makes: both the page MODX renders from its own resource and the virtual page a route of the component answers.

Two places send them, and both are needed. A MODX page is served by the OnWebPageInit handler, which is MODX's own page initialisation. A response assembled by the component sends them itself: Response emits them on every output, including whatever answers before the controller — a cache hit, a middleware refusal, a CORS preflight.

No need to set the headers in nginx

Only the event used to send them, and OnHandleRequest sends the response and exits before that event fires: a virtual page went out without the headers, and the suggested way around it was a web-server rule. That workaround is no longer needed, and one already in place can be removed. It does no harm either way — the component never touches a header that has already been sent, see above.

How this differs from MODX content types ​

MODX has its own field for headers — System → Content Types. It works, and nothing here replaces it.

The difference is that a content type is chosen per resource, while these settings are one decision for the whole site — and they reach a resource whose content type carries no headers at all, which is every resource by default.

How to check ​

bash
curl -sI https://your-site.com | grep -i 'x-frame\|x-content\|referrer\|strict-transport'

Or the Network tab in developer tools — the response headers for the document itself, not for images and scripts.

Check both an ordinary page and a virtual one: they are different output paths. And to tell your own headers from the server's, look at any static file — an image or a .css. The web server serves those without PHP, so anything that arrives on them was not put there by the component.

© PageBlocks 2019-present