A love letter to Web browsers
Tags: browsers, web, csp
Published: 2024-03-22 18:24:00

---
title: A love letter to Web browsers
published: true
description: My CSP story and how it made me write a love letter to the Web browsers.
tags: Browsers, Web, CSP
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/axa6nu3homyyot9u3yu4.jpeg
published_at: 2024-03-22 18:24 +0000
---

The prompt for this article started as an innocent request to migrate an application to different server. Did it bazillion times, this will be done in 5 minutes, right? In the end it really just boiled down to some Docker work, change of the **domain** for the API and the App itself.

After the changes were made, tests went green and application deployed, I did basic visual smoke test and concluded that everything went **fine**.

Later on I got a bug report that one less important part of the app was misbehaving and **print** functionality of the page was incorrectly aligned. Quite certain that change of URLs could not cause such specific issue, I went to investigate.

## Preamble
While answer to many of the woes below might be "just do it differently", I'd call such recommendation a delulu for practical life as any application more complex than a Hello World will have dependencies that do their own things in their own ways and patching those or re-writing them from scratch just to please the CSP gods is nothing we should bend to.

## A story
### The problem
After opening **DevTools** in **Firefox** I was greeted with **CSP** error:

```
Content-Security-Policy: The page’s settings blocked an inline 
style (style-src-attr) from being applied because it violates 
the following directive: “style-src 'self'”
```

Well, that's not really helpful, is it? I can click into the respective piece of source code that caused this, but in most of the cases it will be some `node_module` that my app is consuming and I will need to invest time to **understand** what is it doing and why. So I opened the same page in **Chrome** and got two surprises.

First I got a **new, additional error** that was not reported as an issue in previous browser:

```
Refused to load manifest from 'http://localhost:4201/manifest.webmanifest' 
because it violates the following Content Security Policy
directive: "default-src 'none'". Note that 'manifest-src'
was not explicitly set, so 'default-src' is used as a fallback.
```

But now the browser from Google is trying to be helpful by explaining to me the fallback order. Nice.

```
Refused to apply inline style because it violates the following
Content Security Policy directive: "style-src 'self'". Either
the 'unsafe-inline' keyword, a hash ('sha256-RDWWGcFzQIh1SH4oQIaKd+tX/bMXZOzUetRR1raWCXw='),
or a nonce ('nonce-...') is required to enable inline execution.
Note that hashes do not apply to event handlers, style attributes
and javascript: navigations unless the 'unsafe-hashes' keyword
is present.
```

Second message was also quite nicely trying to **explain what is happening** and tried to give me some hints **how to fix** the situation. The surprise is that this time the issue is supposedly in `style-src` and not `style-src-attr`.

Now for a good measure let's try the same in **Safari**:

```
Refused to load http://localhost:4201/manifest.webmanifest
because it appears in neither the manifest-src directive
nor the default-src directive of the Content Security Policy.
```

Again it tells us the sequence of fallbacks, which is nice, but it omits the information what CSP rules are **currently in use**.

Let's look at the error message for the original issue:

```
Refused to apply a stylesheet because its hash, its nonce,
or 'unsafe-inline' does not appear in the style-src directive
of the Content Security Policy.
```

So Safari agrees with Chrome that the issue is in `style-src`, that's something. Sadly it completely omits telling us the current CSP and the calculated values for **hash**, so from this perspective it's the least helpful of the three browsers.

### The hunt for the fix
Let's combine the information we've hunted & gathered:
- Chrome & Safari tells us **what** we should change: `style-src`.
- Chrome & Firefox tells us what is the **current value** of said rule (here I'm intentionally ignoring the fact that Firefox points at different rule).
- Chrome gives us quite solid hint of what should the **new value** be, including calculated values, Safari just points to values giving us gentle UTFG hint and Firefox just gives us middle finger. Or whatever would a flaming oversized cat do.

### The typeo
Now I'm not saying that I'm perfect, but if I do an **obvious mistake** the technology should be there to help me, right?

If you look at the error messages above you can easily notice that there is a lot of nested enclosing happening. Within single quotes, double quotes and sometimes even parentheses. Sadly this plays a vital role with [CSP source values](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/Sources#relevant_directives).

So while `host sources` **have to** be **without** surrounding quotes, the (let's call them) "reserved values" have to surrounded be **with** **single** quotes, but `scheme sources` like `data:` has to be **without** quotes and source code hashes are treated as "reserved values", meaning they **have to** be **with** quotes again. Example:

```
style-src 'self'               # reserved value with quotes
style-src http://*.example.com # host source has no quotes
style-src 'sha256-ABCDef='     # hashes are reserved values, so quotes
style-src data:Hello           # scheme sources again without quotes
```

Now since we know that enclosing quotes are somewhat important. And hella confusing, let's try to play with two possible scenarios.

#### Missing quotes
Let's imagine a scenario where we want to put in "reserved value", but forget the surrounding quotes. The browser can't identify it as a `scheme source`, because that requires a colon (e.g: `data:`). So it has to be a `host source`, which can then be confused by **host by name**. This is an unfortunate design, because while you do want to allow host value of `localhost`, it should (but can't by design) raise any red flags when you put host value of `unsafe-inline`.

Now what will our browsers do when we try following CSP rule:

```
style-src unsafe-inline
```

- Firefox: 🦗
- Chrome: 🦗
- Safari: 🦗

That's right. This little typo is silently ignored by all the major browsers and if you're reading this article just to find out that this is your case, then I can feel your pain.
#### Extra quotes
If you surround `CSP source value` with quotes, you are effectively saying "this belongs to a list of well known values". So the browser should be able to tell you off if you put nonsense there, right?

Assume following CSP rule:

```
style-src 'Primeagen'
```

What would you expect the reaction of the most common browsers to be? Let's find out!

- Firefox: 🦗
- Chrome: `The source list for the Content Security Policy directive 'style-src' contains an invalid source: ''Primeagen''. It will be ignored.`
- Safari: `The source list for Content Security Policy directive 'style-src' contains an invalid source: ''Primeagen''. It will be ignored.`

So while the first two have such cool & copy-pasted implementation that they both show duplicated single quote, Firefox will just keep **silently ignoring** your **obvious** typo.
#### Bonus points
For an extra homework points you can try to see what will happen if you use **double quotes** instead of **single quotes** in the [CSP source "reserved" values](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/Sources#relevant_directives).

### The missing bit
Now imagine the situation where I went through all those traps, figured out all my tiny & big mistakes have the final form of my CSP:

```
style-src 'self' 'sha256-RDWWGcFzQIh1SH4oQIaKd+tX/bMXZOzUetRR1raWCXw='
```

But the browser is still stubbornly saying that it won't load my resource with the errors effectively the same as in the first part of the article.

What am I missing? What did I miss?

If you look **really** closely you might guess it. The **only helpful** error message comes from Chrome:

```
... Either the 'unsafe-inline' keyword, a hash
('sha256-RDWWGcFzQIh1SH4oQIaKd+tX/bMXZOzUetRR1raWCXw='),
or a nonce ('nonce-...') is required to enable inline
execution. Note that hashes do not apply to event
handlers, style attributes and javascript: navigations
unless the 'unsafe-hashes' keyword is present.
```

Sadly for deciphering what went wrong here I have to acquire a knowledge that I should not care for in the first place: I have to **dig into source code** of a 3rd party library.

Now I'm not saying that for the **security** aspect I could skip this step. It's a foreign code being executed in my app, so code inspection is a fair play for security. What I'm saying is that for the sole **unblocking** of the resource I should not **how** it's internally working.

Now after the code inspection I found out that the 3rd party library code manipulates the DOM and changes some styles. But that is not allowed even from allow-listed hashes **unless** I **also** add `unsafe-hashes`.

So the final (albeit a bit unsafe) form of my CSP looks like:

```
style-src 'self' 'unsafe-hashes' 'sha256-RDWWGcFzQIh1SH4oQIaKd+tX/bMXZOzUetRR1raWCXw='
```

## Table
Everyone loves summary tables!

| Issue                                                                 | Firefox | Chrome | Safari |
| --------------------------------------------------------------------- | ------- | ------ | ------ |
| Reports missing CSP for manifest                                      | ❌       | ✅      | ✅      |
| Points at incorrect directive                                         | ❓       | ✅      | ✅      |
| Shows current value                                                   | ✅       | ✅      | ❌      |
| Shows warning for extra quotes                                        | ❌       | ✅      | ✅      |
| Shows (speculative) warning for missing quotes                        | ❌       | ❌      | ❌      |
| Shows hint for new value                                              | ❌       | ✅      | ✅      |
| Shows calculated value for hash                                       | ❌       | ✅      | ❌      |
| Spells out whole CSP source value example(s) that would just work(tm) | ❌       | ❌      | ❌      |
## Love letter
Now you might be thinking about: "what is love letter-y" about this article? Well, it's the hope & vision for the future.

> Dear browser makers, please give us a future where:
>
> - The **current** CSP directive that is being violated is **fully** printed out.
> - The **value** that caused the violation is printed out alongside the link to the piece of the source code that caused it.
> - Calculated values for **hashes** are printed out.
> - Obviously **invalid reserved values** are reported.
> - Reserved values **without quotes** are reported.
> - Suggestions for **most probable** fixes are **fully** printed out. When there are multiple options, they are sorted descending from the most secure to the least secure.
> 
> While, at first, these requests might seem like a whining of lazy frontend devs, I'd argue that it's really your side of the playing field that will need to up their game as you literally have the code & logic for most of those requests written. You parsed the CSPs, you tried to load the resource, and in one of the if statements you decided that it's not worthy. Resurface the information, the context, the decision making back to the user.
>
> Why should you care you ask? Well because with every confusion, frustration and set back, with every Senior developer stating: "I barely even understand X. All I know is that when something goes wrong with X, I know I will be Googleing", with all of these the internet is gaining potentially another website with `unsafe-*`.
> 
> Dear browser makers, please help us make the internet a better place.


## Bonus: style-src vs style-src-attr
Unresolved stands the different reporting of what's wrong between Firefox ([style-src-attr](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/style-src-attr)) and the two other browsers ([style-src](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/style-src)). When we look at the definitions:

> The HTTP [`Content-Security-Policy`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy) (CSP) **`style-src-attr`** directive specifies valid sources for inline styles applied to individual DOM elements.

> The HTTP [`Content-Security-Policy`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy) (CSP) **`style-src`** directive specifies valid sources for stylesheets.

We can guess that the first is subset of the second and therefore `style-src` can be used as "fallback" for `style-src-attr`. IDK. I don't care. I'm tired now.
## Browser versions
This article was written using:
- Firefox developer edition - 125.0b3
- Google Chrome - 123.0.6312.59
- Safari - 17.4 (19618.1.15.11.12)

---
Title image generated with ChatGPT4 using prompt: "Web developer in a moment of despair, kneeling with their shoulders slumped forward, holding a keyboard. This developer is characterized by exaggerated, comic-style tears streaming from their eyes, emphasizing the emotional weight of their situation. Behind them, three computer monitors are visible, each displaying an "access denied" message. These messages are shown on different web browsers: Firefox, Chrome, and Safari, symbolizing the developer's frustration and despair in their work environment. The scene blends elements of realism and comic artistry, creating an atmosphere that is both humorous and sympathetic, highlighting the struggle of the developer in a light-hearted manner." --ar 16:9"