# Uma Content Security Policy, lida como o navegador a lê

> Um cabeçalho CSP é uma frase numa pequena linguagem formal, e o navegador o lê com um algoritmo que a especificação soletra: dividir nos pontos e vírgulas, passar os nomes a minúsculas, ignorar uma diretiva que se repete, ler cada token de um valor contra uma gramática. A maioria das políticas quebradas quebra exatamente nesses passos, e em silêncio: uma palavra-chave sem aspas vira nome de host, um ponto e vírgula faltando funde duas diretivas numa só, um default-src que parece protetor não governa nada depois que script-src é definido, 'unsafe-inline' ao lado de um nonce é ignorado sem aviso. Aqui está a leitura do próprio CSP Level 3, passo a passo, com a cadeia de recurso, as regras de casamento para curingas, esquemas e endereços IP, o que 'strict-dynamic' desliga, e a política de três diretivas que a própria especificação chama de Strict CSP.

Source: https://ronutz.com/pt-BR/learn/content-security-policy-read-by-the-specification  
Updated: 2026-10-05  
Related tools: https://ronutz.com/pt-BR/tools/csp-evaluator, https://ronutz.com/pt-BR/tools/secure-headers, https://ronutz.com/pt-BR/tools/html-structure-explainer

---

[Content Security Policy, diretiva por diretiva](https://ronutz.com/pt-BR/learn/content-security-policy) explica para que uma CSP serve e o que cada diretiva compra. Este artigo trata de algo mais estreito e, na prática, da causa da maioria das políticas que fazem menos do que os seus autores pensam: como o navegador *lê* o cabeçalho. O Content Security Policy Level 3 (W3C Working Draft, 16 de setembro de 2026, lido em 2026-10-05) não deixa isso à intuição. Dá um algoritmo para transformar o texto do cabeçalho num objeto de política, uma gramática para cada token, uma cadeia de recurso para cada destino e regras de casamento para cada tipo de expressão de fonte. Cada passo tem um modo de falha silencioso: o navegador não rejeita uma política que não conseguiu entender, ele aplica a política que conseguiu entender. O [avaliador de CSP](https://ronutz.com/pt-BR/tools/csp-evaluator) faz a leitura abaixo e mostra o resultado; esta é a leitura.

## Passo um: pontos e vírgulas, minúsculas, e a duplicata que desaparece

O algoritmo "Parse a serialized CSP" (2.2.1) começa por "strictly splitting serialized on the U+003B SEMICOLON character". Cada pedaço perde os espaços das pontas; pedaços vazios são pulados; o nome da diretiva é "the result of collecting a sequence of code points from token which are not ASCII whitespace", depois passado a minúsculas, de modo que "script-SRC 'none' and ScRiPt-sRc 'none' are equivalent". O resto do pedaço, dividido nos espaços, é o valor.

Então o passo que morde: "If policy's directive set contains a directive whose name is directive name, continue." Um segundo `script-src` mais adiante na mesma política não é mesclado nem sobrepõe; é descartado, e a especificação só pede que "the user agent SHOULD notify developers that a duplicate directive was ignored. A console warning might be appropriate". Uma política montada por duas camadas de configuração, cada uma acrescentando o seu `script-src`, fica com a que veio primeiro.

A regra do ponto e vírgula tem uma falha gêmea. Escreva `script-src 'self' object-src 'none'` com o ponto e vírgula esquecido e há uma diretiva, `script-src`, cujo valor tem quatro tokens. O terceiro, `object-src`, é lido contra a gramática de expressões de fonte e casa com `host-source`: é um servidor chamado object-src. A diretiva `object-src` que deveria fechar o sumidouro de plugins nunca existe.

## Passo dois: cada token contra a gramática

Uma lista de fontes é "a whitespace-delimited series of source expressions", cada uma de cinco formas (2.3.1): uma `scheme-source` como `https:`, uma `host-source` como `*.example.com` ou `https://example.com:12/path/to/file.js`, uma `keyword-source` como `'self'` ou `'unsafe-inline'`, uma `nonce-source` como `'nonce-ch4hvvbHDpv7xCSvXCs3BrNggHdTzxUA'`, e uma `hash-source` como `'sha256-abcd...'`. As palavras-chave vêm entre aspas na própria gramática: `keyword-source = "'self'" / "'unsafe-inline'" / "'unsafe-eval'" / "'strict-dynamic'" / "'unsafe-hashes'" / ...`. Escrito sem as aspas, `self` não é uma palavra-chave que falhou; é uma host-source que deu certo, um host chamado self, e `unsafe-inline` é um host chamado unsafe-inline. A política é analisada, o console fica quieto, e o script inline é bloqueado enquanto o autor acredita que foi permitido, ou `self` não casa enquanto o autor acredita que casou.

Dois fatos a mais da gramática explicam surpresas comuns. `'none'` é uma produção própria: `serialized-source-list = ( source-expression *( required-ascii-whitespace source-expression ) ) / "'none'"`; ao lado de outras expressões não casa com nada e não muda nada. E "Though IP address do match the grammar above, only 127.0.0.1 will actually match a URL when used in a source expression", porque o casamento de host começa com "If host is not a domain, return Does Not Match" (6.7.2.10). Uma política que permite `10.0.0.1` não permite nada.

## Passo três: a cadeia de recurso, e a herança que não existe

`default-src` "serves as a fallback for the other fetch directives" (6.1.3), e a especificação dá a ordem exata por destino em "Get fetch directive fallback list" (6.8.3): elementos script olham para `script-src-elem`, depois `script-src`, depois `default-src`; manipuladores de evento inline para `script-src-attr`, depois `script-src`, depois `default-src`; workers para `worker-src`, `child-src`, `script-src`, `default-src`; frames para `frame-src`, `child-src`, `default-src`; e `connect-src`, `img-src`, `font-src`, `media-src`, `manifest-src` e `object-src` recorrem só a `default-src`.

A frase mais mal lida vem a seguir: "There is no inheritance. If a script-src directive is explicitly specified, for example, then the value of default-src has no influence on script requests." Uma política que diz `default-src 'none'; script-src https://cdn.example.com` não significa "nada, exceto aquela rede de distribuição de conteúdo (CDN) para scripts"; significa scripts da CDN e de nenhum outro lugar, e para script o `'none'` nem é consultado. Três diretivas nunca recorrem: `base-uri`, `form-action` e `frame-ancestors`. A especificação é explícita sobre a última: "a policy that declares default-src 'none' will still allow the resource to be embedded by anyone" (6.4.2). A proteção contra clickjacking precisa ser escrita por extenso.

## Passo quatro: o que casa com o quê

O curinga é mais estreito do que parece. Para `*`, o algoritmo de casamento (6.7.2.8) devolve um casamento só se "url's scheme is an HTTP(S) scheme" ou "url's scheme is the same as origin's scheme"; URLs `data:` e `blob:` não casam com `*` a menos que escritas por extenso, "e.g. default-src * data: custom-scheme-1: custom-scheme-2:". Esquemas sobem, mas nunca descem: "script-src http: is treated as equivalent to script-src http: https:" (6.7.2.9), e um host escrito sem esquema casa com o esquema da página e a sua elevação segura. `'self'` casa com a origem da página e, dentro de limites, com as suas formas https e wss na porta padrão ou igual; nunca casa com `blob:` (6.7.2.8).

Então a regra que decide a maioria das auditorias. "A source list allows all inline behavior of a given type if it contains the keyword-source expression 'unsafe-inline', and does not override that expression" (6.7.3.2), e a anulação é qualquer nonce ou hash na mesma lista, ou, para script, `'strict-dynamic'`: "If expression matches the nonce-source or hash-source grammar, return Does Not Allow". Então `script-src 'unsafe-inline' 'nonce-abc'` não permite script inline em nenhum navegador que entenda nonces; a palavra-chave é um recurso para os navegadores que não entendem. A especificação mostra a escada pretendida: a política `'unsafe-inline' https: 'nonce-abcdefg' 'strict-dynamic'` "will act like 'unsafe-inline' https: in browsers that support CSP1, https: 'nonce-DhcnhD3khTMePgXwdayK9BsMqXjhguVV' in browsers that support CSP2, and 'nonce-DhcnhD3khTMePgXwdayK9BsMqXjhguVV' 'strict-dynamic' in browsers that support CSP3" (8.2).

## O que 'strict-dynamic' desliga

"If present in a script-src or default-src directive, it has two main effects: host-source and scheme-source expressions, as well as the 'unsafe-inline' and 'self' keyword-sources will be ignored when loading script. hash-source and nonce-source expressions will be honored. Script requests which are triggered by non-'parser-inserted' script elements are allowed" (8.2). O primeiro efeito é por que uma lista de hosts e `'strict-dynamic'` na mesma lista não é uma contradição, mas uma escada; é também por que `'strict-dynamic'` sem nonce nem hash não deixa nada que possa rodar. O segundo efeito é o ponto: um script em que a página confiou por nonce pode carregar as suas próprias dependências com `document.createElement('script')`, e elas rodam porque o script confiável as criou, não porque o host delas foi listado. A especificação é franca sobre a razão de isso existir: "Host- and path-based policies are tough to get right, especially on sprawling origins like CDNs", e as listas que resultam "end up being brittle, awkward, and difficult to implement and maintain".

## A política que a especificação chama de Strict CSP

A seção 8.5 nomeia o resultado de tudo isso: "enforcing the following set of CSP directives has been identified as an effective and deployable mitigation against XSS. script-src: Only use nonce source-expression and/or hash source-expression with the 'strict-dynamic' keyword-source. ... base-uri: Specify a value of either 'self' or 'none'." Os seus dois exemplos são o todo: `script-src 'strict-dynamic' 'nonce-{RANDOM}'; base-uri 'self';` e `script-src 'strict-dynamic' 'sha256-{HASHED_INLINE_SCRIPT}'; base-uri 'self';`. O nonce tem a sua própria regra: "the server MUST generate a unique value each time it transmits a policy. The generated value SHOULD be at least 128 bits long (before encoding), and SHOULD be generated via a cryptographically secure random number generator" (7.1), porque "Nonces override the other restrictions present in the directive in which they're delivered." Acrescente `object-src 'none'` para fechar o sumidouro de plugins que a especificação descreve em 6.1.9, `frame-ancestors` no cabeçalho (é uma das três diretivas que um elemento `meta` não pode carregar, 3.3) e `report-to` para que as violações sejam ouvidas, e a política fica tão forte quanto este mecanismo permite. Tudo o mais num cabeçalho é lido, pelo navegador, exatamente como descrito acima, e o avaliador o lê do mesmo modo.

## Fontes

- [Content Security Policy Level 3, W3C Working Draft 16 September 2026](https://www.w3.org/TR/CSP3/) (lido em 2026-10-05): 2.2.1 Parse a serialized CSP; 2.3.1 Source Lists; 3.3 The meta element; 6.1.3 default-src; 6.4.2 frame-ancestors; 6.7.2.8 a 6.7.2.10 URL matching; 6.7.3.2 Does a source list allow all inline behavior; 6.8.3 Get fetch directive fallback list; 7.1 Nonce Reuse; 8.2 Usage of 'strict-dynamic'; 8.5 Strict CSP
- [Mixed Content, W3C Candidate Recommendation Draft 23 February 2023: 6.1 Strict Mixed Content Checking](https://www.w3.org/TR/mixed-content/#strict-checking) (lido em 2026-10-05)
- [Google csp-evaluator](https://github.com/google/csp-evaluator) (lido em 2026-10-05), o modelo do conjunto de verificações do avaliador
