Sécurité du site
Une note sur 100 et la liste de ce qu'il faut corriger, assemblée depuis les contrôles qui tournent à chaque déploiement — en-têtes, fichiers exposés, secrets, cookies, redirections, scripts tiers, TLS.
Mis à jour le 25 septembre 2026
Sur cette page
- Ce que PostShip fait
- D'où viennent les constats
- La note
- Le correctif
- Dans le temps
- Régler
- Ce qui déclenche une alerte
- Limites et plans
- Dépannage
- « Pas encore mesurée »
- La note baisse sans déploiement
- Un constat « À corriger » que je ne peux pas corriger
- En-têtes de sécurité
- Ce que PostShip vérifie
- Régler
- Ce qui déclenche une alerte
- Limites et plans
- Dépannage
- Cookies et consentement
- Ce que PostShip vérifie
- Régler
- Ce qui déclenche une alerte
- Limites et plans
- Dépannage
- Scripts tiers
- Ce que PostShip vérifie
- Le verdict
- Régler
- Ce qui déclenche une alerte
- Limites et plans
- Dépannage
- Fichiers exposés
- Ce que PostShip vérifie
- Régler
- Ce qui déclenche une alerte
- Limites et plans
- Dépannage
- Fuites
- Ce que PostShip vérifie
- Régler
- Ce qui déclenche une alerte
- Limites et plans
- Dépannage
Disponible avec les plans Pro et Pro Plus.
6 contrôles regardent déjà la sécurité du site à chaque déploiement, chacun sur sa ligne dans Contrôles du site. La page Sécurité (Projet → Qualité → Sécurité) les lit ensemble : une note, 3 listes du plus grave au plus cosmétique, un correctif par constat, les en-têtes tels que le serveur les sert, et la note dans le temps. Rien de neuf n'est sondé : c'est la lecture qui change.
Ce que PostShip fait
D'où viennent les constats
| Pièce | Contrôle | Ce qu'on y lit |
|---|---|---|
| En-têtes et contenu mixte | En-têtes de sécurité | HSTS, clickjacking, nosniff, CSP, Referrer-Policy, Permissions-Policy, serveur bavard, ressources en http |
| Fichiers exposés | Fichiers exposés | .env, .git, sauvegardes, phpinfo, .htpasswd, server-status, robots.txt trop bavard |
| Secrets dans la page | Fuites | une clé secrète servie dans le HTML, une adresse localhost |
| Cookies | Cookies et consentement | Secure, HttpOnly sur la session, SameSite |
| Redirections | Contrôle des redirections | un site en http, http qui ne renvoie pas vers https |
| Scripts tiers | Scripts tiers | un script sans attribut integrity, un script modifié chez le fournisseur |
| TLS | Domaines et certificats | TLS 1.0/1.1 encore acceptés, certificat expiré |
La note
3 gravités. Bloquant (25 points) : une porte ouverte, à fermer aujourd'hui — un .env lisible, un .git téléchargeable, un secret dans la page, un site en http, un certificat expiré. À corriger (10 points) : une protection connue qui manque et qu'une attaque documentée exploite — HSTS, protection contre le clickjacking, nosniff, cookies sans Secure ou session sans HttpOnly, http non redirigé, sauvegarde téléchargeable, TLS anciens. Durcissement (3 points) : ce qu'un site sérieux a, mais dont l'absence est la norme sur la moitié du web — CSP, Referrer-Policy, Permissions-Policy, SameSite, version du serveur, .DS_Store.
La note part de 100 et retire les points de chaque constat, plancher zéro. La lettre se lit d'un coup d'œil : A à partir de 90, B de 75, C de 60, D de 40, F dessous — et F dès qu'un constat est bloquant, quelle que soit la note : une porte ouverte ne se compense pas par de bons en-têtes.
Le correctif
Chaque constat vient avec ce qu'il faut faire, en une phrase, avec l'en-tête ou l'attribut exact : Strict-Transport-Security: max-age=31536000; includeSubDomains, frame-ancestors 'none', SameSite=Lax… Quand le contrôle sait où (le chemin du fichier exposé, le nom des cookies, les ressources en http), la page le dit.
Dans le temps
La note est recomposée à chaque passage du contrôle des en-têtes, avec le dernier passage connu de chaque autre contrôle, sur 60 jours : une barre par passage. Une refonte qui perd HSTS se voit à la barre suivante.
Régler
Rien à régler. Les contrôles sont posés au premier déploiement vérifié ; « Relancer les contrôles » les fait tourner tout de suite, tous les 6, sans attendre un déploiement (une fois par minute au plus). Un contrôle refusé dans Contrôles du site ne compte plus dans la note.
Ce qui déclenche une alerte
Aucune alerte nouvelle : ce sont celles des contrôles eux-mêmes, au déploiement, sous le type « Qualité du déploiement » de vos canaux. La page sert à lire, prioriser et corriger.
Limites et plans
| Découverte | Pro | Pro Plus | |
|---|---|---|---|
| La note et le nombre de constats | Oui | Oui | Oui |
| Le détail, les correctifs, les en-têtes, la courbe, « Relancer » | — | Oui | Oui |
Voir les plans.
Dépannage
« Pas encore mesurée »
Les contrôles tournent au déploiement : sans hébergeur raccordé ni déploiement détecté, ils n'ont pas encore tourné. « Relancer les contrôles » les lance à la main.
La note baisse sans déploiement
Un cookie posé par un script tiers, un fournisseur de script qui change son fichier, un certificat qui expire : la note lit le monde tel qu'il est au dernier passage, pas seulement ce que vous avez déployé.
Un constat « À corriger » que je ne peux pas corriger
Un en-tête se pose chez l'hébergeur (Vercel, Netlify, Cloudflare ont une configuration d'en-têtes) ou dans le serveur ; si ni l'un ni l'autre n'est à votre main, le constat reste, et c'est une information honnête sur le site.
En-têtes de sécurité
Les en-têtes que PostShip attend sur votre page d'accueil, le contenu mixte, et ce qui fait échouer le contrôle.
Ce que ce contrôle attrape n'a pas de symptôme : le site répond 200, s'affiche, et reste encadrable dans une iframe de phishing, ou charge un script en http:// qu'un réseau d'hôtel peut remplacer. Rien ne casse jusqu'au jour où quelqu'un s'en sert. PostShip lit les en-têtes que votre serveur envoie sur la page d'accueil, et le HTML qu'elle contient, à chaque déploiement.
Ce que PostShip vérifie
Une seule requête, celle de la page d'accueil du projet. Les en-têtes jugés sont ceux de la réponse finale, après redirections : c'est elle que le navigateur affiche, et une redirection 301 sans HSTS avant une page qui l'a n'est pas le défaut qu'on cherche. Pas de navigateur : on lit ce que le serveur envoie, pas ce que le JavaScript ajoute ensuite.
2 niveaux, tenus séparés d'un bout à l'autre. Les manques sont des protections dont l'absence ouvre une attaque connue et documentée : ils font échouer le contrôle. Les avertissements sont des durcissements qu'un site sérieux devrait avoir, mais dont l'absence est la norme sur la moitié du web ; en faire un échec vous réveillerait toutes les nuits pour rien.
Les manques (le contrôle échoue)
| Code | Ce qui manque |
|---|---|
hsts_absent | Pas d'en-tête Strict-Transport-Security : un visiteur peut être renvoyé en http à son insu. Vérifié seulement en https — les navigateurs ignorent l'en-tête reçu en clair. |
x_content_type_absent | Pas de X-Content-Type-Options: nosniff — le navigateur peut exécuter un fichier comme s'il était d'un autre type. Seule la valeur nosniff compte ; none ou une faute de frappe vaut absence. |
frame_protection_absente | Ni X-Frame-Options, ni directive frame-ancestors dans la CSP : le clickjacking est possible. L'un ou l'autre suffit. |
contenu_mixte | La page en https charge des ressources en http : le navigateur les bloque ou avertit. |
Le contenu mixte ne compte que les éléments que le navigateur charge lui-même : script, iframe, img, et les link en rel="stylesheet". Un lien a href="http://…" est une navigation choisie par le visiteur, pas une ressource de la page. La liste est dédoublonnée et plafonnée à 10 : au-delà, le problème est systémique.
Les avertissements (le contrôle passe)
| Code | Ce qui est signalé |
|---|---|
csp_absente | Pas de Content-Security-Policy. |
referrer_policy_absente | Pas de Referrer-Policy. |
permissions_policy_absente | Pas de Permissions-Policy. |
serveur_bavard | Le serveur annonce sa version dans Server ou X-Powered-By (nginx/1.18.0). Server: nginx seul ne dit rien d'utile à un attaquant ; avec le numéro, c'est la liste des CVE à essayer. |
Le score
100 points, moins 20 par manque et 5 par avertissement. Tout manquer donne exactement 0. Le score et la valeur brute de chaque en-tête sont dans le détail du contrôle, pour que vous voyiez ce que votre serveur envoie réellement plutôt que notre lecture.
Régler
Rien à configurer : la cible est créée par PostShip pour chaque projet, sur l'URL de base, et ne consomme pas votre quota d'URLs. Projet → Surveillance → Sécurité pour lire le dernier verdict, le score, et les en-têtes reçus. Le contrôle peut être désactivé depuis la même carte si vous ne voulez pas le voir.
Comme les autres contrôles du site, il tourne au déploiement (webhook, T+2 / T+8, « Vérifier maintenant »), jamais au tick ordinaire : les en-têtes d'un site ne changent qu'au déploiement.
Ce qui déclenche une alerte
Un fail, c'est-à-dire au moins un manque, après le nombre de confirmations de vos règles. L'alerte porte la phrase du code (par exemple « Rien n'empêche d'afficher le site dans une iframe (ni X-Frame-Options, ni frame-ancestors) : le clickjacking est possible. »). Les avertissements ne sonnent jamais : ils se lisent dans l'app.
Pas de réponse (délai, DNS, TLS illisible) : le contrôle est en error, pas en fail. Une coupure réseau n'est pas un défaut de sécurité, et le contrôle HTTP la signale déjà.
Un échec pèse aussi sur le Ship Score, dans la catégorie « hygiène du site » (10 points, plafonnés pour l'ensemble des contrôles).
Limites et plans
| Découverte | Pro | Pro Plus | |
|---|---|---|---|
| Verdict et score à l'écran | Oui | Oui | Oui |
| Alerte sur un manque | Non | Oui | Oui |
Découverte voit le résultat dans Projet → URLs, où chacun a sa ligne ; ce sont les notifications des contrôles de déploiement que le plan ne porte pas. Voir les plans.
Dépannage
Pourquoi le contrôle passe-t-il alors que ma CSP est absente ?
Parce que l'absence de CSP est un avertissement, pas un manque : une CSP mal écrite casse un site, et exiger qu'elle existe pousserait à en poser une vide. Le panneau Scripts tiers propose une directive script-src construite sur ce que PostShip a réellement vu.
Pourquoi « hsts_absent » n'apparaît-il pas sur mon site en http ?
HSTS n'a de sens qu'en https. Reprocher son absence à une page http accuserait le mauvais problème : c'est le http lui-même qu'il faut signaler, et le contrôle des redirections le fait (base_en_http).
Pourquoi le contenu mixte cite-t-il une image que j'ai déjà remplacée ?
Le contrôle tourne au déploiement. Si l'image a été remplacée sans déploiement (un CMS, par exemple), relancez la cible avec « Vérifier maintenant » ou attendez le prochain ship.
Cookies et consentement
Les cookies que votre serveur pose sur un premier visiteur, avant tout bandeau, et ce que PostShip en dit.
Une requête sur la page d'accueil, sans aucun cookie envoyé : c'est exactement ce que reçoit quelqu'un qui arrive pour la première fois, avant tout bandeau, avant tout clic. Ce qui est déposé là l'est sans consentement — et c'est précisément ce que la CNIL sanctionne quand un _ga part avec la page. Ce contrôle lit les en-têtes Set-Cookie de cette première réponse, à chaque déploiement.
Ce que PostShip vérifie
Une seule requête GET, sans en-tête Cookie. Les cookies jugés sont ceux de la réponse finale, après redirections : c'est elle que le navigateur garde. Pas de navigateur : PostShip lit ce que le serveur envoie, pas ce que le JavaScript pose ensuite via document.cookie. Un _ga déposé côté client lui échappe — c'est la limite assumée de l'outil, et la raison pour laquelle le script de mesure chargé est au moins signalé.
2 niveaux, tenus séparés. Les manques sont des faits lus dans Set-Cookie : ils font échouer le contrôle. Les avertissements sont des indices qui demandent un regard humain.
Les manques (le contrôle échoue)
| Code | Ce qui est lu |
|---|---|
traceurs_avant_consentement | Des cookies de mesure ou de publicité sont posés avant tout consentement. |
cookies_non_secure | Des cookies sont posés sans l'attribut Secure. Vérifié seulement sur une page https : un navigateur refuse même de poser Secure en clair. |
session_sans_httponly | Un cookie de session est lisible par JavaScript (pas de HttpOnly) : un vol de session à la première XSS. |
Les traceurs sont reconnus par leur nom, par préfixe et sans tenir compte de la casse : _ga, _gid, _gat, _gcl_, _fbp, _fbc, _hjid, _hjSession, _clck, _clsk, _uetsid, _uetvid, _pin_unauth, _tt_, _ttp, ajs_anonymous_id. Google Analytics 4 pose _ga_XXXXXXX à côté de _ga : le préfixe l'attrape sans connaître l'identifiant de propriété. IDE et NID (DoubleClick, Google) ne comptent que sur leur nom exact — 3 lettres en préfixe attraperaient identifiant et tout ce qu'un développeur nomme librement.
Un cookie « ressemble à une session » quand son nom contient sess, session, sid, token, auth ou jwt. Les noms contenant csrf ou xsrf sont exclus à dessein : le motif « double soumission » exige que le script lise le jeton, et un jeton CSRF sans HttpOnly est la norme, pas une faute. Un traceur n'est jamais compté 2 fois : _hjSession est reproché comme traceur, pas comme session exposée.
Les avertissements (le contrôle passe)
| Code | Ce qui est signalé |
|---|---|
samesite_absent | Des cookies n'ont pas d'attribut SameSite. Chrome applique Lax par défaut, pas tous les navigateurs. |
scripts_traceurs_charges | Un script de mesure tiers est chargé sur la page : googletagmanager.com, google-analytics.com, connect.facebook.net, static.hotjar.com, clarity.ms, cdn.segment.com (et leurs sous-domaines). |
Chaque cookie lu est dans le détail du contrôle (30 au plus) : nom, Secure, HttpOnly, SameSite, durée en jours (depuis Max-Age, qui l'emporte sur Expires, comme le fait un navigateur), domaine. Zéro cookie est un pass parfaitement normal : un site statique n'en pose aucun, c'est même la situation la plus saine.
Régler
Rien à configurer : la cible est créée par PostShip pour chaque projet, sur l'URL de base, hors quota d'URLs. Projet → Surveillance → Sécurité, ligne « Cookies », pour lire le verdict et la liste des cookies. Le contrôle se désactive depuis la même carte.
Il tourne au déploiement, comme les autres contrôles du site : les cookies qu'un serveur pose ne changent qu'au déploiement.
Ce qui déclenche une alerte
Un fail (au moins un manque), après le nombre de confirmations de vos règles. L'alerte porte la phrase du code, par exemple « Des cookies de mesure ou de publicité sont posés avant tout consentement. » Les avertissements se lisent dans l'app et ne sonnent pas.
Pas de réponse : error, pas fail. Une coupure réseau n'est pas un défaut de conformité.
Un échec compte dans le Ship Score, catégorie « hygiène du site ».
Limites et plans
| Découverte | Pro | Pro Plus | |
|---|---|---|---|
| Verdict et liste des cookies | Oui | Oui | Oui |
| Alerte sur un manque | Non | Oui | Oui |
Voir les plans.
Dépannage
Pourquoi le contrôle passe-t-il alors que mon bandeau pose _ga au chargement ?
Parce que le cookie est posé par le JavaScript, pas par votre serveur. PostShip ne lit que Set-Cookie. Si le script de mesure est chargé, l'avertissement scripts_traceurs_charges vous le dit : c'est le signal pour vérifier vous-même.
Pourquoi mon cookie de session est-il signalé alors qu'il est HttpOnly ?
Vérifiez qu'il est posé par la réponse finale et non par une redirection intermédiaire : PostShip ne voit que la dernière réponse. Vérifiez aussi la casse et l'orthographe de l'attribut — HttpOnly est lu sans tenir compte de la casse, mais Http-Only n'existe pas.
Pourquoi « cookies_non_secure » n'apparaît-il pas sur mon site en http ?
Secure n'a de sens que sur une page chiffrée. Exiger l'attribut sur une page http serait reprocher un défaut impossible à corriger. Passez en https d'abord ; le contrôle des redirections vous y aide.
Scripts tiers
Disponible avec les plans Pro et Pro Plus.
Comment PostShip relève l'empreinte de chaque script chargé depuis un autre domaine, et vous prévient quand il change chez son hébergeur.
Votre page d'accueil charge des scripts que vous n'hébergez pas : un lecteur vidéo, un outil de mesure, une bibliothèque sur un CDN. Si l'un d'eux change chez son hébergeur, votre site sert du code que personne chez vous n'a relu — sans déploiement, sans erreur, avec un 200 partout. C'est le scénario de Polyfill.io en 2024, et les autres contrôles ne le voient pas : ils regardent ce que votre site fait, pas ce que ses fournisseurs lui font.
Ce que PostShip vérifie
PostShip lit le HTML de la page d'accueil du projet (jusqu'à 2 Mo, parce que les balises script d'une page marchande sont en bas) et en tire les balises script src chargées depuis une autre origine que la vôtre. Vos propres bundles ne sont pas relevés : ils changent à chaque déploiement, c'est leur rôle.
Pour chacun, PostShip télécharge le fichier complet et calcule 2 empreintes :
sha256en hexadécimal, celle qu'on retrouve avecsha256sumou dans l'onglet réseau du navigateur ;sha384sous la forme exacte que l'attributintegrityattend,sha384-<base64>, pour qu'elle se colle sans transformation.
| Limite | Valeur | Pourquoi |
|---|---|---|
| Scripts relevés | 25, les premiers dans l'ordre du document | Au-delà, ce sont le plus souvent des tags marketing empilés |
| Taille d'un script | 2 Mo | Une empreinte calculée sur un début de fichier serait fausse, et une empreinte fausse est pire qu'aucune |
Un script qui répond autre chose que 200 ou qui dépasse 2 Mo est marqué « non lu » avec sa raison (« Le script répond 404. », « Plus de 2 Mo : empreinte non relevée. »). On ne juge pas ce qu'on n'a pas lu.
Jamais le contenu d'un script dans les résultats, seulement ses empreintes et sa taille.
Quand ça tourne
À chaque déploiement, comme les autres contrôles du site — et, à la différence des autres, une fois par jour sans déploiement, puisque c'est précisément un changement sans déploiement que ce contrôle cherche. Pas toutes les 5 minutes : ce sont des téléchargements de scripts entiers.
Le verdict
Au premier passage, tout est nouveau : PostShip mémorise et ne s'alarme pas. Ensuite :
- Modifié : connu, et l'empreinte n'est plus la même. C'est l'alerte.
- Nouveau : jamais vu sur ce projet. Mémorisé, sans alerte par défaut — c'est le plus souvent votre choix.
- Disparu : connu, absent du HTML de ce déploiement. Une information, pas une panne ; s'il revient, on saura s'il a changé entre-temps.
Le contrôle lui-même reste pass : un script qui change chez son hébergeur ne casse pas votre site, mais mérite un regard. Il passe par le canal des modifications, comme le ce qui a changé.
Régler
Projet → Surveillance → Sécurité, ligne « Scripts tiers ». Le panneau liste chaque script avec son nom court, sa taille, le début de son sha256, et combien de fois il a été modifié.
- La balise avec integrity, prête à coller, pour chaque script lu. PostShip prévient après coup ; le navigateur, lui, empêche avant : avec l'attribut
integrity, un script dont l'empreinte ne correspond plus n'est pas exécuté. - C'était attendu : après une modification, ce bouton acquitte la dernière. La date et l'historique restent, seul l'écran change de ton.
- Me prévenir aussi quand un script tiers apparaît : une case à cocher, pour une agence qui livre un site fini et veut savoir qu'un script est apparu sans elle. Jamais au premier relevé.
- L'en-tête CSP suggéré : une directive
script-srcconstruite sur les origines réellement vues, plus'self'. Elle complète l'attribut —integrityprotège un fichier à la fois contre un CDN compromis,script-srccontre un script injecté depuis ailleurs. Si la page a du script en ligne, le panneau ajoute'unsafe-inline'et le dit.
<!-- La balise proposée par PostShip : integrity et crossorigin ensemble -->
<script src="https://cdn.tiers.com/lib.js" integrity="sha384-…" crossorigin="anonymous"></script>Ce qui déclenche une alerte
1 script tiers a changé chez son hébergeur : cdn.tiers.com/lib.js— ou, au pluriel,3 scripts tiers ont changé chez leur hébergeur : …avec 3 noms au plus, puis « et N autres ».- Avec l'option cochée :
1 script tiers nouveau sur la page : cdn.x/w.js(2 noms au plus). Seulement si au moins un script de ce site est déjà connu.
Ce qui se tait : un script disparu, un script non lu, le premier relevé. L'alerte suit vos canaux et le réglage « Le contenu a changé après un déploiement » des notifications.
Limites et plans
| Découverte | Pro | Pro Plus | |
|---|---|---|---|
| Relevé et empreintes à l'écran | Oui | Oui | Oui |
| Alerte quand un script change | Non | Oui | Oui |
Le contrôle tourne pour tout projet, hors quota d'URLs. Les alertes des contrôles du site font partie du plan Pro (voir les plans).
Dépannage
Pourquoi un script est-il marqué « non lu » ?
Il répond autre chose que 200, il dépasse 2 Mo, ou le délai du contrôle (12 s) est écoulé avant son tour. Le contrôle HTTP, lui, signale un script qui ne répond plus.
Pourquoi mon script a-t-il changé alors que rien n'a bougé chez moi ?
C'est le cas normal d'un CDN qui publie une nouvelle version sous la même URL (latest, une version majeure sans numéro de patch). Si c'est attendu, cliquez « C'était attendu » et épinglez la version dans l'URL, sinon la prochaine publication alertera encore.
Pourquoi la balise avec integrity casse-t-elle ma page ?
Soit crossorigin="anonymous" manque, soit le CDN sert un contenu différent selon le navigateur ou la région. Dans ce second cas, l'attribut ne peut pas fonctionner : gardez l'alerte PostShip, retirez l'attribut.
Fichiers exposés
Les chemins que PostShip sonde à la racine de votre site (.env, .git, sauvegardes, phpinfo) et comment il évite le faux positif de la SPA.
Un .env avec la clé Stripe, un .git/ complet qu'on peut cloner, une sauvegarde backup.sql de la base clients, un phpinfo.php oublié d'un débogage : rien de tout ça n'est une faille du code. C'est un fichier au mauvais endroit, servi par un serveur qui sert tout ce qu'on lui demande. Ça arrive au déploiement, à la migration, et personne ne le voit : le site marche exactement pareil. Les scanners automatiques, eux, passent sur ces chemins toutes les heures. Ce contrôle y passe avant eux, à chaque déploiement.
Ce que PostShip vérifie
Depuis l'origine du site, pas depuis l'URL complète : un .env traîne à la racine du serveur, pas sous /fr/accueil. PostShip demande d'abord /robots.txt (le témoin que l'origine répond, et une lecture utile de toute façon), puis les chemins ci-dessous, du plus grave au moins grave. Si le budget de requêtes coupe la liste, c'est la fin qu'on perd, pas le .env. 6 requêtes de front : assez pour tenir dans le délai, pas assez pour ressembler à un scan hostile pour votre pare-feu.
| Chemin sondé | Code levé | Ce qui confirme |
|---|---|---|
/.env | env_expose | Une ligne CLE=valeur en majuscules |
/.git/config, /.git/HEAD | git_expose | [core] ou [remote, ou ref: refs/ |
/wp-config.php.bak, .php~, .php.old | config_expose | DB_PASSWORD ou DB_NAME |
/phpinfo.php, /info.php | phpinfo_expose | PHP Version ou phpinfo() |
/backup.zip, /site.zip | sauvegarde_exposee | La signature d'un zip (PK) |
/backup.tar.gz | sauvegarde_exposee | La signature d'un gzip |
/backup.sql, /dump.sql, /db.sql | sauvegarde_exposee | CREATE TABLE ou INSERT INTO |
/.htpasswd | htpasswd_expose | Une ligne utilisateur:$hash |
/.DS_Store | ds_store_expose | L'en-tête binaire du fichier |
/server-status | server_status_expose | Apache Server Status |
Pourquoi le statut ne suffit pas
Le piège de ce contrôle, c'est le site qui répond 200 à tout : une application monopage dont le serveur renvoie index.html pour n'importe quel chemin inconnu. Sur un tel site, /.env répond 200 — et ne contient rien. Le statut ne prouve donc rien ; c'est le contenu qui fait foi. Chaque chemin a une signature (quelques octets ou une ligne caractéristique) sans laquelle un 200 ne compte pas. PostShip ne lit que les 16 premiers Ko d'un corps : toutes les signatures tiennent dans les premières lignes, et lire plus reviendrait à télécharger la sauvegarde qu'on cherche justement à ne pas voir circuler.
Un 403 est une protection (tant mieux), un 404 une absence, une redirection une réponse d'ailleurs : dans les 3 cas, le corps n'est pas lu. Le chemin est rapporté, jamais le contenu.
robots.txt qui en dit trop
Un robots.txt n'est jamais un manque, mais il peut trop en dire. Interdire /backup aux robots, c'est dire à tout le monde qu'il y a un /backup : le fichier est lu par les scanners bien avant les robots. Une directive Disallow: qui commence par /admin, /backup ou /.git lève l'avertissement robots_revele_des_chemins. Pas /wp-admin : WordPress l'écrit lui-même par défaut, et le chemin n'est un secret pour personne.
Le score
100 points, moins 30 par manque et 5 par avertissement, plancher à 0. Un .env en ligne est un incident ; un robots.txt bavard est un conseil.
Régler
Rien à configurer : la cible est créée par PostShip pour chaque projet, hors quota d'URLs. Projet → Surveillance → Sécurité, ligne « Fichiers exposés », pour lire le verdict, les chemins trouvés et le score. Le contrôle se désactive depuis la même carte.
Réservé à un domaine vérifié. Sonder /.env ou /.git sur un site, c'est ce que fait un attaquant en repérage : PostShip ne le fait que pour le propriétaire prouvé. Tant que le domaine du projet n'est pas vérifié (Réglages → Domaine, un enregistrement TXT), ce contrôle ne tourne pas et la page Sécurité le dit, avec le lien pour le débloquer.
Il tourne au déploiement (webhook, T+2 / T+8, « Vérifier maintenant »), comme les autres contrôles du site.
Ce qui déclenche une alerte
Un fail — au moins un fichier confirmé — après les confirmations de vos règles. L'alerte porte la phrase du code : « Le fichier .env est lisible depuis le web : vos secrets sont publics, révoquez-les. », « Le dossier .git est lisible depuis le web : tout votre code source est téléchargeable. »
Ce qui ne sonne pas : l'avertissement sur le robots.txt ; une origine qui ne répond pas du tout (le contrôle est en error, et le contrôle HTTP dira la même chose) ; un budget épuisé avant la première requête (« reporté au prochain passage »). Ce qui n'a pas été testé n'est jamais déclaré absent : ce serait une conclusion sans requête.
Limites et plans
| Découverte | Pro | Pro Plus | |
|---|---|---|---|
| Verdict et score à l'écran | Oui | Oui | Oui |
| Alerte sur un fichier exposé | Non | Oui | Oui |
Voir les plans.
Dépannage
Pourquoi mon site en SPA passe-t-il alors que /.env répond 200 ?
Parce que ce 200 renvoie votre index.html, pas un fichier dotenv. C'est exactement le cas que la signature existe pour écarter. Le contrôle échouerait si la réponse contenait une ligne CLE=valeur.
Pourquoi un fichier que je sais présent n'est-il pas signalé ?
3 raisons possibles : il redirige (PostShip ne lit pas le corps d'une redirection), sa signature n'est pas reconnue (un .env sans aucune ligne en majuscules, un dump sans CREATE TABLE), ou le budget du passage s'est épuisé avant son tour — le détail dit alors combien de chemins ont été testés.
Pourquoi ne pas sonder security.txt ?
Son absence est la norme (quelques pour cent des sites en ont un), et un avertissement présent sur presque chaque projet finit par masquer les vrais.
Fuites
Ce que votre page d'accueil laisse voir qu'elle ne devrait pas — lorem ipsum, localhost, adresse de préproduction, clé secrète — et comment PostShip le lit.
Une mise en ligne se fait souvent depuis une branche de travail : le gabarit garde un « Lorem ipsum » dans une section oubliée, une variable d'environnement pointe encore sur localhost:3000, une clé secrète Stripe copiée « pour tester » finit dans le bundle. Rien de tout cela ne fait tomber le site — les autres contrôles passent — et c'est un client, ou un inconnu qui lit le code source, qui le voit en premier. Ce contrôle lit le HTML de la page d'accueil à chaque déploiement et nomme ce qui a fui.
Ce que PostShip vérifie
Une lecture statique de la page d'accueil (jusqu'à 512 Ko) : ni ce que le JavaScript ajoute, ni les bundles externes. 2 régimes, comme pour le contenu attendu : les mots de maquette et de chantier ne comptent que dans le texte qu'un visiteur lit — un TODO dans un commentaire HTML n'est visible de personne ; les URL et les secrets se cherchent dans tout le HTML servi, parce que c'est là qu'ils fuient.
Les manques (le contrôle échoue)
| Code | Ce qui est lu |
|---|---|
texte_de_maquette | « lorem ipsum » dans le texte visible. |
url_locale | Une ressource ou un lien vers localhost, 127.0.0.1, 0.0.0.0, [::1], un hôte en .local ou .test, ou un port de développement (3000, 5173, 8080) — y compris un ws://localhost dans un script en ligne, la fuite la plus courante d'un bundle de développement. |
url_de_preprod | Un hôte contenant staging, preprod, preview ou sandbox ; une étiquette entière dev. ou test. (dev.exemple.fr oui, mondev.fr non, exemple.dev non plus) ; ou une adresse .vercel.app, .netlify.app, .pages.dev alors que votre site n'y vit pas. |
secret_dans_le_html | Une clé qui n'a rien à faire côté client : sk_live_, sk_test_, rk_live_ (Stripe), AKIA… (AWS), ghp_… (GitHub), xoxb- / xoxp- (Slack), un bloc PRIVATE KEY, ou un JWT dont le rôle est service_role (Supabase). |
Les clés publiques sont exclues à dessein : pk_live_, la clé anon Supabase, une clé Google Maps sont faites pour être servies au navigateur, et les signaler ferait ignorer le contrôle. Les liens de votre site vers lui-même ne comptent jamais.
Les avertissements (le contrôle passe)
| Code | Ce qui est signalé |
|---|---|
mot_de_chantier | TODO, FIXME, XXX, « à faire », « placeholder », « coming soon » — en mot entier, dans le texte visible. |
image_de_remplissage | Une image depuis placeholder.com, placehold.co, picsum.photos, dummyimage.com, ou un fichier placeholder.png/jpg/svg. |
email_exemple | Une adresse en @example.com, @exemple.fr ou @test.com, dans le texte ou un mailto:. |
console_debug | Un script en ligne exécutable contient console.log( ou debugger;. Un application/ld+json ou un bloc de données ne compte pas. |
Le rapport
Chaque trouvaille est rendue avec un extrait de 80 caractères au plus, 3 par code, sans doublon. Un secret n'apparaît jamais entier : l'extrait ne montre que le préfixe (sk_live_…), qui suffit à nommer le service, et un JWT devient eyJ….
Le score : 100 points, moins 25 par manque et 5 par avertissement, plancher à 0. Si le HTML a été tronqué à 512 Ko, le détail le dit : un pass sur une page tronquée n'en est qu'à moitié un.
Régler
Rien à configurer : la cible est créée par PostShip pour chaque projet, hors quota d'URLs. Projet → Surveillance → Sécurité, ligne « Fuites », pour lire le verdict, les extraits et le score. Le contrôle se désactive depuis la même carte.
Il tourne au déploiement, comme les autres contrôles du site : c'est le moment où une branche de travail arrive en ligne.
Ce qui déclenche une alerte
Un fail (au moins un manque), après les confirmations de vos règles. L'alerte porte la phrase du code : « Une clé secrète est servie dans le HTML de la page : révoquez-la maintenant. », « La page charge une ressource depuis localhost ou un port de développement : elle est cassée pour tout le monde sauf vous. »
Ce qui ne sonne pas : les avertissements ; une page qui répond 4xx ou 5xx (« La page répond 503 : rien à analyser. » — juger un gabarit d'erreur qui dit « Coming soon » accuserait la mauvaise page) ; une réponse qui n'est pas du HTML.
Limites et plans
| Découverte | Pro | Pro Plus | |
|---|---|---|---|
| Verdict et extraits à l'écran | Oui | Oui | Oui |
| Alerte sur un manque | Non | Oui | Oui |
Voir les plans.
Dépannage
Pourquoi « url_de_preprod » alors que mon site est hébergé sur Vercel ?
Si votre site vit lui-même sur *.vercel.app, ses liens vers un autre *.vercel.app ne sont pas signalés. S'il a son propre domaine et pointe vers mon-site-git-feature.vercel.app, c'est bien une adresse de prévisualisation qui a fui.
Pourquoi « mot_de_chantier » sur une page qui n'a pas de TODO ?
« placeholder » et « coming soon » comptent aussi, en mot entier, dans le texte visible — y compris le texte d'un bouton ou d'une section. Le mot dans un attribut (placeholder="Votre email") ne compte pas.
Pourquoi la clé anon de Supabase n'est-elle pas signalée ?
Parce qu'elle est publique par conception. Seul un JWT dont la charge utile porte "role":"service_role" est un secret. Les deux ont la même forme : c'est le rôle qui les distingue.