Aller au contenu

phil/css-sanitizer

alpha

Laissez les gens styliser leurs pages sans que leur CSS exécute du code, charge ce qu'il ne doit pas ou sorte de son cadre.

phil/css-sanitizer lit le CSS écrit par des personnes — une feuille de style, ou les déclarations d'un attribut style — comme le lit la spécification CSS Syntax Level 3. Il garde seulement ce qu'une politique permet, puis le réécrit. Les propriétés, les fonctions, les sélecteurs et les règles @ viennent de listes d'autorisation. @media, @supports, @page avec ses seize boîtes de marge, @font-face et les règles imbriquées à la manière de CSS Nesting sont compris et vérifiés. Chaque URL — d'une url(), d'un image-set(), d'un @import que la politique permet — passe par un réécrivain qui la fait correspondre à un fichier permis par l'application, ou la refuse. expression(), les URL javascript:, behavior et -moz-binding sont refusés, quoi que dise la politique. Ce qui est retiré est listé, avec sa raison et sa ligne, pour que l'auteur le lise.

Il sert à toute application PHP qui laisse ses utilisateurs écrire du CSS : thèmes et apparences d'un livre ou d'une page publiés, styles personnalisés d'un profil, styles d'impression d'un document envoyé à un moteur PDF, éditeur de styles dans un système de contenu. Le résultat peut être écrit sans danger dans un élément <style> ou un attribut style d'une page que d'autres personnes lisent.

C'est une simple bibliothèque PHP, écrite de zéro, dont le seul prérequis est ext-mbstring. Si symfony/validator est installé, il donne la contrainte SafeCss. L'analyseur lexical et l'analyseur suivent CSS Syntax Level 3 section par section. Ainsi, l'assainisseur voit les mêmes jetons qu'un navigateur — échappements résolus, commentaires écartés, blocs appariés — et un nom caché derrière un échappement (e\78 pression) est bien le nom qu'il est. Rien n'est filtré par expression régulière sur le texte.

La conception : Syntax\Tokenizer et Syntax\Parser transforment le texte en valeurs de composants, en règles et en déclarations. CssSanitizer les vérifie selon une Policy, réécrit leurs URL par un UrlRewriter et renvoie un résultat Sanitized : le CSS, les règles gardées sous forme de Sheet\StyleSheet, et les Removal. Serializer réécrit le CSS de façon qu'il se relise avec les mêmes valeurs et ne contienne jamais le début d'une balise de fin. Validator\SafeCss transforme les retraits en violations.

État

PartieÉtat
Analyseur lexical de CSS Syntax Level 3 : chaque jeton, échappements, commentaires, chaînes et URL invalides, lignesImplémenté et testé
Analyseur de CSS Syntax Level 3 : valeurs de composants, règles, déclarations, !important, erreurs d'analyse gardées, profondeur bornéeImplémenté et testé
Sérialiseur : identifiants et chaînes échappés comme le fait CSSOM, commentaires là où des jetons se colleraient, aucune balise de finImplémenté et testé
Listes d'autorisation de propriétés, fonctions, pseudo-classes, pseudo-éléments, attributs et types de médiasImplémenté et testé
Sélecteurs : type, classe, ID, attribut, pseudo-classes avec leurs arguments, pseudo-éléments, combinateursImplémenté et testé
Règles de style imbriquées (CSS Nesting) : sélecteurs relatifs et & sous la même grammaire, niveaux bornés par la politique, @media et @supports à l'intérieurImplémenté et testé
@media, @supports, @page et ses boîtes de marge, @font-face avec sources locales et réécritesImplémenté et testé
URL réécrites par une fonction de rappel ou une table de fichiers permis ; fragments gardés ; schémas qui exécutent du code refusésImplémenté et testé
image-set() avec chaque image réécrite ; @import au début d'une feuille, réécrit vers une feuille de style permise quand la politique le permetImplémenté et testé
expression(), behavior, -moz-binding et fonctions qui chargent une ressource toujours refusésImplémenté et testé
Sélecteur de portée, !important et propriétés personnalisées soumis à la politique, limites de longueur et de profondeurImplémenté et testé
Contrainte de validateur Symfony SafeCssImplémenté et testé

Documentation

DocumentCe qu'il couvre
InstallationPrérequis, installation, l'assainisseur comme service Symfony.
AssainirFeuilles de style et attributs style, le résultat, les retraits et leurs raisons.
PolitiqueCe que permet la politique par défaut, chaque with…(), la portée, l'imbrication et les limites.
URLRéécrivains, fichiers permis, image-set(), @import, fragments, schémas toujours refusés.
SélecteursLa grammaire des sélecteurs vérifiée, les sélecteurs imbriqués et &, et ce que la politique y décide.
Règles @@media, @supports, @page et ses boîtes de marge, @font-face, @import, les règles imbriquées dans une règle de style.
MenacesCe dont l'assainisseur protège, comment, et ce qui reste à l'application.
SyntaxeL'analyseur lexical, l'analyseur et le sérialiseur utilisés seuls.
ValidateurLa contrainte SafeCss.
ExceptionsCssSanitizerException et ses deux exceptions.
Points d'extensionLes ports, arguments et données qu'une application choisit ou remplace.
TestsLes tests de la bibliothèque et son seuil de mutation, et comment tester du code qui assainit du CSS.
RéférenceChaque type de la bibliothèque, avec son rôle.

Démarrage rapide

composer require phil/css-sanitizer
use Phil\CssSanitizer\CssSanitizer;
use Phil\CssSanitizer\Policy;
use Phil\CssSanitizer\Url\AllowedFiles;

$policy = new Policy()
    ->withUrlRewriter(new AllowedFiles(['paper.jpg' => '/media/42/paper.jpg']))
    ->withScope('.book-page');

$sanitized = new CssSanitizer($policy)->sanitize(<<<'CSS'
    @import url(https://example.com/x.css);
    h1 { color: #702; background: url(paper.jpg); behavior: url(x.htc) }
    @page :first { margin-top: 4cm; @top-center { content: string(title) } }
    CSS);

echo $sanitized->css;
// .book-page h1 {
//   color: #702;
//   background: url("/media/42/paper.jpg");
// }
//
// @page :first {
//   margin-top: 4cm;
//
//   @top-center {
//     content: string(title);
//   }
// }

foreach ($sanitized->removals as $removal) {
    printf("line %d: %s (%s)\n", $removal->line, $removal->reason->describe(), $removal->subject);
}
// line 1: an @import rule (@import)
// line 2: a property that runs code (behavior)

Prérequis

PHP 8.5 ou plus récent et ext-mbstring ; symfony/validator pour la contrainte SafeCss.

Contribuer

Les contributions sont les bienvenues sur le monodépôt de PHIL : lisez le guide de contribution et le code de conduite. Signalez une vulnérabilité en privé, comme l'explique la politique de sécurité, jamais dans un ticket public.

Licence

MIT, © Philippe Gamache, doing business as Le Gnome Archiviste; a Parler Haut Interagir Librement project. Voir LICENSE.