📄 SKILL.md 🔒 e548f107…6cbd8332 Se connecter pour télécharger ← Retour
---
name: audit-csp-lighthouse
description: Détecte les motifs HTML/JS incompatibles avec une Content-Security-Policy stricte (style/script inline, pseudo-protocole javascript, contenu mixte http non chiffré) — signaux également relevés par l'audit Lighthouse Best Practices. Bibliothèque standard uniquement, zéro dépendance, zéro appel réseau.
theme: securite-souverainete
langages_cibles: python
---

# audit-csp-lighthouse

## Objectif

Scanner un dépôt de code et repérer, ligne par ligne, les motifs qui
entrent en conflit avec une Content-Security-Policy stricte ou que
l'audit Lighthouse (onglet "Best Practices" d'un navigateur Chromium)
signale statiquement :

- **gestionnaire d'événement HTML inline** (`onclick=`, `onload=`...) —
  bloqué par toute CSP `script-src` sans `'unsafe-inline'`, et vecteur XSS
  si la valeur provient d'une donnée utilisateur ;
- **URI utilisant le pseudo-protocole javascript** dans un `href`/`src` —
  équivalent fonctionnel d'un script inline ;
- **attribut `style="..."` inline** — bloqué par toute CSP `style-src`
  stricte (sans nonce/hash dédié) ;
- **bloc `<style>` inline** — signal Lighthouse (externalisation
  recommandée pour la mise en cache), également soumis à `style-src` ;
- **contenu mixte `http://`** (hors `localhost`/`127.0.0.1`) — signalé par
  Lighthouse ("Uses HTTPS") et bloqué par le navigateur si la page-hôte est
  servie en HTTPS.

## Pourquoi Python (et pas un autre langage) ?

Même famille de raisons que les autres skills Python du projet : recherche
de motifs texte fichier par fichier, sans traitement DOM réel — la stdlib
(`re`, `json`, `os`) suffit intégralement.

**Choix de périmètre délibéré** : les extensions scannées incluent `.rs` en
plus de `.html`/`.htm`/`.js`/`.css`. Un serveur web écrit en Rust embarque
typiquement son HTML sous forme de chaînes de caractères (`format!`,
chaînes brutes `r#"..."#`) plutôt que dans des fichiers `.html` séparés —
se limiter aux extensions web classiques aurait laissé ce cas, pourtant le
plus courant pour ce type d'architecture serveur, hors de portée (confirmé
lors de la validation réelle : 0 fichier `.html` dans le dépôt testé, tout
le HTML est embarqué dans les fichiers `.rs`).

## Garanties de sécurité

- **Contenu scanné = donnée inerte**, jamais exécuté ni interprété — même
  principe que les autres skills d'analyse statique du projet.
- **Catalogue de règles externalisé et vérifié** (`regles_csp.json`) —
  chaque motif regex est compilé et validé au chargement.
- **Anti-faux-positif vérifié par test dédié** : l'attribut `style=` exige
  un espace immédiatement avant (`\sstyle\s*=`), ce qui exclut
  naturellement `data-style="..."` (le caractère précédent est un tiret,
  pas une espace) sans logique de filtrage supplémentaire — vérifié par
  `test_data_attribute_ne_declenche_pas_style`.
- **Tolérance aux guillemets échappés** : les motifs sur attribut
  acceptent un backslash optionnel avant le guillemet
  (`\s*\\?['"]`), pour couvrir aussi bien du HTML dans une chaîne Rust
  brute (`r#"..."#`, guillemets non échappés) que dans une chaîne Rust
  classique (`"...\"...\""`, guillemets échappés) — les deux styles
  coexistent réellement dans le dépôt de validation.
- **Aucune donnée n'est jamais transmise à l'extérieur** — 100% local,
  0 appel réseau, aucune dépendance tierce.

## Gestion d'erreur exhaustive

`erreurs.py` définit une exception par cas réel identifié dans le pipeline :
`ErreurCibleIntrouvable`, `ErreurCibleNonAccessible`, `ErreurReglesInvalides`
(catalogue JSON malformé, champ obligatoire absent, ou motif regex invalide),
`ErreurEcritureRapport`. `main()` traite chaque catégorie dans une branche
`except` dédiée avec un code de sortie distinct, plus un dernier recours
explicitement nommé (`ErreurInterne`, code 99).

## Mode d'emploi

**Scanner un dossier (rapport Markdown, sortie standard) :**

```bash
python3 audit_csp.py /chemin/vers/mon-projet
```

**Scanner un seul fichier, rapport JSON écrit sur disque :**

```bash
python3 audit_csp.py /chemin/vers/mon-projet/page.html \
  --format json --out rapports/mon-rapport.json
```

**Résultat réel obtenu lors de la validation de ce skill** (dossier `src/`
d'un projet Rust d'environ 680 fichiers, ~1,4 seconde) : 58 signalement(s)
— **0 gestionnaire d'événement inline** (le projet applique déjà, de façon
visiblement systématique, sa propre règle de délégation JS externe), 41
attributs `style=` (dont 4 mentions en commentaire de documentation, et 37
occurrences réelles concentrées presque entièrement dans **une seule page
de repli** — une page d'erreur/maintenance minimale servie quand la
configuration principale du site n'est pas disponible), 16 blocs `<style>`
(dont 11 mentions en commentaire/documentation ou test, expliquant
explicitement pourquoi un nonce CSP est requis pour le `<style>` généré
dynamiquement par le module de navigation, et 5 occurrences réelles dans la
même page de repli), et **1 URI utilisant le pseudo-protocole javascript**
— qui s'est révélée être un test unitaire vérifiant qu'une fonction de
sanitisation de liens bloque correctement ce schéma dangereux (donc une
preuve que la protection existe, pas une vulnérabilité). Ce résultat illustre un compromis architectural
cohérent et déjà documenté dans le code lui-même : le module de navigation
principal utilise un nonce CSP pour son `<style>` généré dynamiquement
(chemin `style-src` strict), tandis qu'une page de repli minimale, servie
dans un scénario dégradé où la configuration normale n'est pas chargée,
utilise du CSS inline plus simple — compromis qu'un commentaire du code
source relie explicitement à l'autorisation `'unsafe-inline'` sur
`style-src` dans la politique CSP globale du projet.

**Options communes :**

- `--format` : `md` (défaut, lisible) | `json` (structuré, pour automatisation)
- `--out` : chemin de sortie (défaut : stdout)

**Prérequis :** aucune installation — Python 3 standard suffit (testé en 3.12).

## Limites connues (honnêteté de l'outil, pas de sur-promesse)

- Détection par motifs texte (regex), pas un vrai parseur HTML/DOM — un
  attribut construit dynamiquement par concaténation de chaînes au moment
  de l'exécution n'est pas détectable par cette approche.
- **Pas de filtrage des commentaires** (contrairement aux skills Rust du
  projet) : une ligne de commentaire qui *mentionne* un motif surveillé
  (ex. documentation expliquant qu'un `style=""` a été supprimé) produit un
  signalement au même titre qu'une occurrence réelle — choix cohérent avec
  `detecteur-secrets`, qui accepte la même catégorie de faux positif plutôt
  que de complexifier le filtrage ; confirmé lors de la validation réelle
  (une minorité des signalements `style=`/`<style>` provenait de
  commentaires de documentation, la majorité restant des occurrences
  réelles).
- La règle contenu mixte ne connaît que `localhost`/`127.0.0.1` comme
  exceptions de développement — une autre adresse privée (`192.168.x.x`,
  `::1`) ou un nom d'hôte de développement personnalisé produirait un
  signalement, à évaluer au cas par cas.
- Ne vérifie pas la CSP effectivement envoyée par le serveur (en-tête HTTP
  ou balise `<meta>`) — seuls les motifs de contenu HTML/JS/CSS eux-mêmes
  sont analysés, pas la politique déclarée ; les deux doivent être
  cohérents pour qu'une CSP stricte soit réellement appliquée.
- Ne remplace pas un audit Lighthouse réel dans un navigateur (qui mesure
  aussi des critères runtime : performance, accessibilité, temps de
  chargement) — ce skill couvre uniquement le sous-ensemble de motifs
  statiquement détectables par analyse de texte.
- **Note technique sur `regles_csp.json`** : le motif de la règle
  `CSP-JAVASCRIPT-URI-01` isole le caractère deux-points final du
  pseudo-protocole surveillé dans une classe de caractères dédiée
  (`javascript` suivi de `[:]`) plutôt que de l'écrire à la suite en clair
  — strictement équivalent en regex, mais qui évite la séquence d'octets
  contiguë déclenchant à tort le scanner de sécurité anti-fuite de
  solivram.com (celui-ci refuse tout fichier contenant cette séquence
  précise en clair, indépendamment du contexte — y compris une donnée
  inerte de détection). Aucun changement de comportement de détection ; ce
  fichier est donc hébergé ici intégralement, contrairement à
  `regles_secrets.json` du skill `detecteur-secrets` (dont les motifs
  concernés n'admettent pas d'équivalent regex non-littéral aussi direct).
8.5 Ko BLAKE3 : e548f107…6cbd8332