# X-Content-Type-Options (/fr/docs/web-security/security-headers/x-content-type-options)



Les navigateurs inspectaient autrefois les premiers octets d'une réponse pour
deviner son vrai type dès que le `Content-Type` déclaré manquait ou semblait
faux, un comportement appelé [MIME sniffing](https://mimesniff.spec.whatwg.org/).
`X-Content-Type-Options: nosniff` met fin à cette devinette : le navigateur fait
confiance au type que votre serveur déclare et traite la réponse comme ce type
uniquement.

Deviner, c'était justement la surface d'attaque : un fichier uploadé comme image
inoffensive mais qui contient en réalité du HTML et du JavaScript pouvait être
interprété comme une page et exécuté dans l'origine de votre site, soit un XSS
stocké. Ce header ferme ce chemin.

Le header se résume à ça. Il ne fait son travail que si le `Content-Type` de
chaque réponse est correct :

```http
X-Content-Type-Options: nosniff
```

## Valeurs et ce que fait chacune [#valeurs-et-ce-que-fait-chacune]

| Valeur    | Statut | Description                                                                                                                                        |
| --------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| `nosniff` | ✅ Bon  | La seule valeur. Le navigateur fait confiance au `Content-Type` déclaré et bloque les scripts et feuilles de style dont le type ne correspond pas. |

`nosniff`, défini dans le
[standard Fetch](https://fetch.spec.whatwg.org/#x-content-type-options-header),
a deux effets.

D'abord, un blocage strict pour les scripts et les feuilles de style. Tout
chargement de type script (un `<script>`, un worker, un shared worker, un
service worker, un worklet audio ou paint) doit être servi avec un type MIME
JavaScript, et une feuille de style avec exactement `text/css`.
Si le type ne correspond pas, le navigateur bloque la réponse au niveau réseau
et elle ne s'exécute jamais ; Chromium journalise une erreur console indiquant
que le script a été refusé parce que « strict MIME type checking is enabled ».
Sans le header, Chromium exécute encore les scripts servis en `text/plain`,
`text/html` ou `application/octet-stream` (les types image, audio, vidéo et
CSV sont bloqués comme scripts dans les deux cas).

Ensuite, le navigateur cesse d'inspecter les octets partout ailleurs. Quand une
réponse porte un `Content-Type`, le type déclaré est définitif. Quand elle n'en
porte aucun, le navigateur peut encore classer le corps comme texte ou binaire,
mais il ne le requalifiera jamais en HTML, en XML ou en PDF.

Chrome embarque aussi [Opaque Response Blocking (ORB)](https://github.com/annevk/orb),
qui bloque pour tous les sites les lectures no-cors cross-origin des réponses
qui ressemblent à du HTML, du JSON ou du XML, quels que soient les headers
envoyés. `nosniff` rend ce blocage déterministe : un type de la blocklist, un
`text/plain` ou un type absent est bloqué d'office au lieu de dépendre d'un
sniffing de confirmation. ORB ne fait rien pour les chargements same-origin,
donc le header reste le seul à imposer un typage MIME correct pour vos
propres scripts et feuilles de style.

## Le volet Content-Type [#le-volet-content-type]

Le header fait respecter l'étiquette, donc l'étiquette doit être juste sur
chaque ressource : les scripts servis avec un type MIME JavaScript
(`text/javascript`), les feuilles de style en `text/css`, le HTML en
`text/html`. Sur les réponses HTML, déclarez aussi l'encodage des caractères :

```http
Content-Type: text/html; charset=UTF-8
```

La [cheat sheet OWASP sur les headers HTTP](https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html)
le dit sans détour : « the charset attribute is necessary to prevent XSS in
HTML pages » (l'attribut charset est nécessaire pour empêcher le XSS dans les
pages HTML). La version classique de cette attaque, faire passer du script via
UTF-7, est morte parce que les navigateurs ont abandonné cet encodage, mais
omettre le charset reste exploitable.
[Une recherche publiée par Sonar en 2024](https://www.sonarsource.com/blog/encoding-differentials-why-charset-matters/)
a montré que lorsqu'une page omet son charset, Chrome et Firefox auto-détectent
ISO-2022-JP, et qu'un attaquant qui contrôle une partie de la page peut utiliser
les séquences d'échappement de cet encodage pour sortir de l'échappement en
plein document. Soit un XSS qui marchait encore en 2024. Déclarez le charset sur
chaque réponse HTML.

## Contre quoi cela protège [#contre-quoi-cela-protège]

* **Des uploads mal étiquetés exécutés comme HTML ou script.** Un `avatar.png`
  qui est en réalité un fichier HTML, servi avec un type absent ou inconnu,
  pouvait être interprété comme une page et s'exécuter dans votre origine, soit
  un XSS stocké. Avec `nosniff`, le navigateur s'en tient au type déclaré.
* **Une infrastructure mal configurée ou qui retire le type.** Un serveur qui
  omet `Content-Type`, ou un proxy qui le retire, laisse le navigateur
  deviner. Le header supprime la devinette, donc une erreur de configuration
  reste une ressource cassée au lieu de devenir du contenu exécutable.
* **Des scripts de worker mal étiquetés.** Le blocage strict couvre les
  workers, shared workers, service workers et worklets, donc une ressource
  chargée comme worker doit elle aussi porter un type MIME JavaScript.
* **Les fuites de données cross-origin.** Il renforce le blocage des lectures
  cross-origin du navigateur (ORB) contre les attaques de classe Spectre en
  rendant le blocage déterministe plutôt que fondé sur le sniffing.

Le header fait respecter l'étiquette, rien de plus. Une réponse correctement
étiquetée `text/html` s'affiche toujours, du JavaScript correctement étiqueté
s'exécute toujours, donc il ne remplace ni la
[politique de sécurité du contenu (CSP)](/fr/docs/web-security/policies/content-security-policy/introduction/what-is-csp)
ni la gestion des uploads (validation, une origine séparée pour le contenu
utilisateur).

## Risques sans le header [#risques-sans-le-header]

Sans le header, chaque réponse a besoin d'un `Content-Type` parfait, parce que
toute réponse qui n'en a pas peut être réinterprétée. Une seule
route de contenu utilisateur qui sert des fichiers avec un type absent ou
inconnu, un seul proxy qui retire le header, et un moteur de sniffing peut
faire passer pour du HTML un fichier d'apparence inoffensive, dans votre
origine. Les
moteurs modernes ont réduit le sniffing (ils ne sniffent vers HTML que lorsque
le type est absent ou inconnu), donc l'exposition pratique se concentre sur
les surfaces mal configurées et les moteurs anciens, ce qui est exactement le
rôle de la défense en profondeur.

## Risques et pièges à l'usage [#risques-et-pièges-à-lusage]

* **Les scripts mal typés cessent de se charger.** Un script légitime servi en
  `text/plain` ou `application/octet-stream` (un serveur de fichiers brut, un
  objet [Amazon S3](https://docs.aws.amazon.com/AmazonS3/latest/userguide/UsingMetadata.html)
  uploadé sans type) est bloqué dès que `nosniff` est déployé. Corrigez les
  mappings MIME avant d'ajouter le header, pas après.
* **Les feuilles de style doivent être exactement `text/css`.** Tout le reste
  est bloqué, de la même façon que les scripts exigent un type MIME
  JavaScript.
* **Header uniquement.** Il n'existe pas d'équivalent `<meta>` ; il doit
  être envoyé comme header de réponse.
* **Aucun rapport de violation.** Les chargements bloqués n'apparaissent que
  comme erreurs console. Contrairement à CSP, le header n'a aucune intégration
  avec la Reporting API, donc rien ne vous signale qu'une ressource est cassée
  en production ; testez avant et après le déploiement.

## Comment le mettre en place [#comment-le-mettre-en-place]

1. Auditez vos mappings `Content-Type` : chaque script servi avec un type MIME
   JavaScript, chaque feuille de style en `text/css`, chaque réponse HTML en
   `text/html; charset=UTF-8`. Prêtez attention aux serveurs de fichiers et au
   stockage objet, où le type est celui défini à l'upload.
2. Ajoutez `X-Content-Type-Options: nosniff` à chaque réponse, réponses d'API
   comprises. Le définir une fois au niveau du serveur ou du CDN est plus
   simple que route par route, et il n'y a aucune valeur à régler.
3. Vérifiez le header déployé, et le reste de vos headers de réponse, avec le
   [scanner de headers de sécurité](/tools/security-headers), et
   surveillez la console du navigateur après le déploiement, à la recherche de
   scripts ou de feuilles de style refusés.

## Recommandation [#recommandation]

Envoyez `X-Content-Type-Options: nosniff` sur chaque réponse :

```http
X-Content-Type-Options: nosniff
```

La [cheat sheet OWASP sur les headers HTTP](https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html)
recommande de l'envoyer sur toutes les réponses que les navigateurs peuvent
consommer, et la
[cheat sheet OWASP sur la sécurité REST](https://cheatsheetseries.owasp.org/cheatsheets/REST_Security_Cheat_Sheet.html)
étend cela aux réponses d'API. Associez-le à un `Content-Type` correct sur
chaque ressource et à un charset sur le HTML (`text/html; charset=UTF-8`) : le
header fait respecter l'étiquette, donc l'étiquette doit être juste.

## Prise en charge par les navigateurs [#prise-en-charge-par-les-navigateurs]

Pris en charge par tous les moteurs modernes. Microsoft a introduit le header
dans [Internet Explorer en 2008](https://learn.microsoft.com/en-us/archive/blogs/ie/ie8-security-part-vi-beta-2-update) ;
leur démo était un fichier HTML servi en `text/plain` qui s'affichait comme du
HTML dans les anciennes versions d'Internet Explorer mais comme du texte brut
dans la version qui a apporté `nosniff`. Chrome, Firefox et Safari l'appliquent
tous intégralement dans leurs versions actuelles (certaines premières versions
de Chrome n'appliquaient pas la vérification des feuilles de style). Firefox
applique aussi `nosniff` aux chargements de page de premier niveau (voir
[l'annonce de Mozilla](https://blog.mozilla.org/security/2020/04/07/firefox-75-will-respect-nosniff-for-page-loads/)) :
une navigation dont le type déclaré ne correspond pas affiche une page
d'erreur, et un type absent s'affiche en texte brut ou se télécharge.

## FAQ [#faq]

### Faut-il nosniff sur les réponses d'API ? [#faut-il-nosniff-sur-les-réponses-dapi-]

Oui. La cheat sheet OWASP sur la sécurité REST recommande
`X-Content-Type-Options: nosniff` sur chaque réponse, réponses d'API comprises.
Il ne coûte rien à envoyer sur tout le site, il empêche qu'une réponse JSON soit
prise pour du HTML, et il n'y a aucune valeur à régler, donc définissez-le une
fois au niveau du serveur ou du CDN plutôt que route par route.

### nosniff casse-t-il quelque chose ? [#nosniff-casse-t-il-quelque-chose-]

Seulement les ressources envoyées avec le mauvais `Content-Type`. Un script servi
en `text/plain`, ou une feuille de style qui n'est pas exactement `text/css`, est
bloqué dès que le header est déployé. Corrigez d'abord vos mappings MIME, puis
ajoutez `nosniff`, de sorte que le contenu correctement étiqueté continue de se
charger et que seules les ressources jusque-là mal étiquetées apparaissent en
erreur.

### nosniff arrête-t-il le XSS ? [#nosniff-arrête-t-il-le-xss-]

Non. Il ferme un chemin précis : un upload mal étiqueté sniffé et exécuté comme
HTML ou script dans votre origine. Du HTML correctement étiqueté s'affiche
toujours et du JavaScript correctement étiqueté s'exécute toujours, donc vous
avez encore besoin d'une politique de sécurité du contenu (CSP) et d'une bonne gestion des
uploads. Traitez `nosniff` comme de la défense en profondeur, pas comme un
correctif XSS.

## Voir aussi [#voir-aussi]

* [Vue d'ensemble des headers de sécurité](/fr/docs/web-security/security-headers)
* [Qu'est-ce que la Content Security Policy](/fr/docs/web-security/policies/content-security-policy/introduction/what-is-csp),
  qui restreint ce que peut faire un contenu correctement étiqueté ; `nosniff`
  ne fait que faire respecter les étiquettes
* [Strict-Transport-Security](/fr/docs/web-security/security-headers/strict-transport-security),
  le pendant côté transport dans cette section
* [Scanner de headers de sécurité](/tools/security-headers) pour
  vérifier vos headers déployés

## Sources [#sources]

* [Standard Fetch, header X-Content-Type-Options](https://fetch.spec.whatwg.org/#x-content-type-options-header)
* [Standard MIME Sniffing](https://mimesniff.spec.whatwg.org/)
* [MDN, X-Content-Type-Options](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Content-Type-Options)
* [OWASP, cheat sheet des headers HTTP](https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html)
* [Sonar, encoding differentials, pourquoi le charset compte](https://www.sonarsource.com/blog/encoding-differentials-why-charset-matters/)
* [Microsoft, IE8 security part VI, l'annonce de nosniff](https://learn.microsoft.com/en-us/archive/blogs/ie/ie8-security-part-vi-beta-2-update)
