Web developers and admins looking to tighten the security of their websites should consider defining a Content Security Policy for their site. For sites hosted using Apache, a simple way to achieve this is by sending the Content-Security-Policy header using mod_headers. Unfortunately, making this simple solution robust is more difficult than it first appears. This post describes a method for setting or modifying the Content-Security-Policy header in a way that won’t clobber previous values set by earlier configuration options or returned by an application server.

The Problem

A first-attempt at setting the Content-Security-Policy header using mod_header may look something like this:

Header always set Content-Security-Policy "referrer origin"

For simple use cases, this is straight-forward and sufficient to get the job done. But what happens if the site includes an application which sets its own Content-Security-Policy header (either via .htaccess or from a dynamic page or application server)? This configuration will clobber it. That’s not ideal.

An easy solution would be to use setifempty and require that any overriding configuration include the entire policy. This can make configuration changes during deployment difficult and it pushes all policy decisions up into the application, which may be undesirable. It’s possible to do better.

A better solution would be to use append or merge. Unfortunately, a quick look at the documentation reveals that this is not quite right. The major issue is that it treats the header as a comma-separated list, while Content-Security-Policy is semicolon-separated, and there is no way to change that behavior.

A Solution

After pondering this problem for a bit, I realized it would not be difficult to implement the merge behavior using a combination of setifempty and edit. Here’s how:

Header always setifempty Content-Security-Policy ""
Header always edit Content-Security-Policy "^(?!(?:.*;)?\s*referrer\s)" "referrer origin;"

It first ensures that the header exists using setifempty (otherwise edit will not apply), then prepends the referrer policy only if the header does not already contain one (by matching with a negative-lookahead). Note that it relies on the fact that extra semicolons are permitted in both CSP1 and CSP2, since that will occur when the header is empty. Alternatively, it’s easy to add another edit command to remove a tailing semicolon if it is not desirable for some reason.

But wait, there’s more! Using edit provides more power than just prepending. With a quick adjustment, the regular expression can be used to unconditionally replace policy components. Here’s how:

Header always setifempty Content-Security-Policy ""
Header always edit Content-Security-Policy "(^(?!(?:.*;)?\s*referrer\s)|(?:.*;)?\s*referrer\s+[^;]+;?)" "referrer origin;"

Now the regular expression matches against either the beginning of the string, if it does not already contain the referrer policy, or against the existing referrer policy if it does. This way the configured policy directive is always used, regardless of any other policies present. This method can also be applied multiple times for multiple policy directives, using either the first or second variant to either preserve or overwrite the policy directives respectively:

Header always setifempty Content-Security-Policy ""
# Override the referrer policy directive, if present
Header always edit Content-Security-Policy "(^(?!(?:.*;)?\s*referrer\s)|(?:.*;)?\s*referrer\s+[^;]+;?)" "referrer origin;"
# Preserve the script-src policy directive, if present
Header always edit Content-Security-Policy "^(?!(?:.*;)?\s*script-src\s)" "script-src 'self';"

This technique allows defining directives and overriding them wherever it is most convenient, in either the application or in the Apache configuration files, while minimizing the risk of unintentionally overwriting the policy, either in whole or in part. I hope you find it useful!