Multiline CSP headers are joined incorrectly

Hello,

I am seeing an unexpected (for me at least) behaviour when setting my CSP headers in my _headers file.
Since my CSP list is quite long I’d like to break it down to multiple line like so (this is a stripped down version, just an example):

/*
  X-XSS-Protection: 1; mode=block
  Content-Security-Policy: default-src 'self'; 
  Content-Security-Policy: script-src 'self' 'unsafe-inline' https://www.googletagmanager.com;

This validates on redirects-playground like so:

[[headers]]
for = "/*"
[headers.values]
Content-Security-Policy = [
"default-src 'self';",
"script-src 'self' 'unsafe-inline' https://www.googletagmanager.com;"
]
X-XSS-Protection = "1; mode=block"

And I would expect to see the values of the CSP fold to a single value like so:

Content-Security-Policy:  default-src 'self'; script-src 'self' 'unsafe-inline' https://www.googletagmanager.com;

Which is a valid CSP directive CSP Header Inspector and Validator

If I try to deploy that though, here’s the result I see in my headers:

default-src 'self';, script-src 'self' 'unsafe-inline' https://www.googletagmanager.com;

(x-nf-request-id: 5f73cde1-aac6-4bd8-b0a9-ee2edf06b76b-487459)

Notice the comma after the first semicolon. That makes the policy invalid:

Even removing my semicolons will still make it invalid, because the result would be:

default-src 'self', script-src 'self' 'unsafe-inline' https://www.googletagmanager.com

And the separator should be ;.

This is not a blocker for me, I solved this by declaring everything on one line.
Still it would be nice to have a way to spread the directive on multiple lines to make both reading and diffs easier to scan.

Let me know if I’m doing anything wrong or if I can provide more details.
Many thanks!

2 Likes

Hmm, why not remove the comma in your spec? I’m not sure if that feature is intended to work without the commas, since the example in our docs is one where the commas are syntactically required and desired (from Custom headers | Netlify Docs). You could also just try porting to the below quoted format as it is the one our tests use:

[[headers]]
  for = "/*"
  [headers.values]
	cache-control = '''
	max-age=0,
	no-cache,
	no-store,
	must-revalidate'''

why not remove the comma in your spec?

I am not adding commas (they are invalid in csp directives), it’s Netlify that’s adding them.
I think that by default Netlify should just join without any separator, so a user would be able to use - or not use - the correct separator that goes along with that header.

Just taking your example as a base, one would have to write a _headers file such as:

Cache-control: max-age=0,
Cache-control: no-cache,
Cache-control: no-store,
# so on..

Note that the commas are added explicitly by the user. That would be the ideal scenario in my view.

I realise this would be a breaking change, so it’s alright, but it’s still worth logging this as an issue someone else might encounter.

For the record I ended up using a .toml file because it played better with multi lines + just happened to be a better option in my particular case.

Let me know if I need to expand on any of the above, other than that thanks for the reply.

netlify doesn’t change your netlify.toml, which is where the comments were:

Content-Security-Policy = [
"default-src 'self';",
"script-src 'self' 'unsafe-inline' https://www.googletagmanager.com;"
]

That is where I was suggesting removing them :slight_smile:

Also, I suggested converting to the “```” style quoting instead of your style with “[” and “]” to contain the multi-line syntax - how did that go?

I see what you mean. To clarify, my original issue was not related to the .toml file, but to the _header file.
I didn’t have a toml file at that time.

if I want to use just a headers file and write it like so:

/*
  Content-Security-Policy: default-src 'self'; 
  Content-Security-Policy: script-src 'self' 'unsafe-inline' https://www.googletagmanager.com;

Netlify will add the commas to the HTTP headers that are delivered to live site.

This is a screenshot with a _headers file with the configuration laid out as above:

I then resolved by deleting my headers file and using a toml, where I could successfully use multilines and - as you noted - there’s no comma insertion.

If you paste the code above here https://play.netlify.com/headers you will see that the generated output is with added commas.
Again, I resolved by using a .toml but I think the original issue still stands.

1 Like

Yes, that playground is not so great - sorry it led you astray! When you do it instead as the docs describe, I think that things work as intended. (The docs here do indicate that the commas are added because that is how the RFC suggests merging multiple values: RFC 7230: Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing). I understand that in your case it may have been a single value that you split up, but our implementation is intended to fit the “multiple values” intention.

I’ll work with the documentation team to see if we can make things clearer in the docs!

I don’t think this is a documentation issue. If you add the following lines in a _headers file:

Content-Security-Policy: default-src 'none';
Content-Security-Policy: font-src 'self';

It’s joined as:
Content-Security-Policy: default-src 'none';,font-src 'self';

which is invalid due to the semi-colon followed by the comma which Netlify added.

If the spec insists on adding commas, I think the recommended approach is to use a toml when a site has directives that use semi-colons such as CSP and FP.

1 Like

2023 here and the same issue. I’m not sure why it doesn’t get more attention but would be nice to have someone else assigned to this issue, not the original support assignee.

I 100% agree with everything OP said but unfortunately I don’t have an option to fallback to netlify.toml because it won’t allow us to have different headers for different contexts.

So what am I left with? Doing a huge oneliner instead of a multiline? How to maintain it then. Ridiculous.

FWIW, the ''' style did work for me to support multiline headers from netlify.toml. Like this:

[[headers]]
  for = '/*'
  [headers.values]
    Content-Security-Policy = '''
      upgrade-insecure-requests;
      block-all-mixed-content;
      frame-ancestors 'none';
    '''

Turned into this header:

Content-Security-Policy: upgrade-insecure-requests; block-all-mixed-content; frame-ancestors 'none';

Nobody was talking about netlify.toml The whole topic of OP was about the syntax of _headers file which is completely different and doesn’t work. Please read the whole topic I beg you guys.

netlify.toml can be used instead of _headers, @leks , for exactly the same purpose: to specify custom headers. I suggest you follow @weotch 's advice here since he got the problem that people are trying to solve - good CSP headers - working using that path :slight_smile:

Thanks for the advice but again you missed the point. netlify.toml doesn’t allow you to have different headers per context. You can’t specify a certain header for dev context and a different one for prod. The only existing workaround is to use _headers file which allows that. But unfortunately it doesn’t allow multiline syntax same way as it’s allowed in netlify.toml

Let’s close the topic. I can see that you guys don’t understand neither me nor OP. I just went ahead and used _headers file with huge one-liners all over the place. No multilines is not the end of the World in the end of the day

Cheers

@leks, you can use Netlify Build Plugins: Create Build Plugins | Netlify Docs and add any custom headers to netlifyConfig. In javaScript, you’d get much more flexibility than _headers or netlify.toml and you can also use the CONTEXT environment variable: Build environment variables | Netlify Docs to generate correct headers for each context.