Aller au contenu

Contrats / Politique de sécurité

Le domaine security-policy regroupe trois contrats deny-by-default : CryptoPolicyInterface contrôle le choix des algorithmes et des clés, HtmlSecurityPolicyInterface restreint la surface des fonctionnalités HTML, et ExternalResourcePolicyInterface régit le chargement des ressources distantes. Chacun est un contrat : un déploiement peut donc fournir une politique plus stricte sans forker le code.

Fenêtre de terminal
composer require nextpdf/core:^3

CryptoPolicyInterface est le point de contrôle cryptographique. Le cœur le consulte avant toute étape de signature, de chiffrement ou de hachage. La vérification couvre le hachage, l’OID de signature, le chiffrement et la robustesse de la clé. Le contrat indique aussi le hachage minimal et un nom de politique pour le journal d’audit. Il applique un jeu de règles tel que FIPS 140-3 ou eIDAS. Le code de signature et de chiffrement reste inchangé. Quand aucune politique n’est définie, tous les algorithmes sont autorisés. Un site soumis à une réglementation doit définir une politique explicite.

HtmlSecurityPolicyInterface agit au niveau de l’analyse HTML. Il s’exécute avant que le contenu n’atteigne le moindre moteur de rendu. Il indique si une balise, un attribut, une propriété CSS ou un schéma d’URL est autorisé. Il plafonne aussi la taille de l’entrée et la profondeur d’imbrication. Il se combine avec les politiques de transport propres à chaque moteur de rendu (Chrome, Cloudflare, Gotenberg), qui fixent des limites de taille et des en-têtes CSP. La politique HTML réduit la surface d’attaque de la couche d’analyse. Une balise supprimée n’atteint jamais la mise en page. Un élément injecté ne peut donc pas modifier la sortie. Quand aucune politique n’est définie, la valeur par défaut autorise l’ensemble des fonctionnalités.

ExternalResourcePolicyInterface indique si le pipeline HTML peut récupérer une police, une feuille de style ou une image externe. Il fixe aussi les limites applicables à chaque récupération. Sa posture par défaut est deny-all. Chaque option reste désactivée tant que tu ne l’actives pas. Le contrat applique le principe du moindre privilège. Du HTML non fiable peut pointer vers une URL contrôlée par un attaquant. Il contrôle la récupération @font-face par schéma, taille et nombre de glyphes. Il contrôle @import par schéma, profondeur et taille totale. Il contrôle background-image au moyen d’une liste de schémas et d’une liste blanche de domaines à correspondance exacte. Il plafonne la taille des data-URI. Il contrôle aussi les références externes des SVG. Le contrat précise qu’en production, elles doivent toujours être refusées. Elles ouvrent la voie à la falsification de requête et à l’injection de scripts. La récupération d’URL ouverte est un vecteur de falsification de requête côté serveur. Le contrôle d’accès est contourné en modifiant l’URL, conformément à l’OWASP Top 10 2025. Les composants doivent provenir uniquement de sources officielles, via des liens sécurisés.

TypeNatureMembres clésStabilitéDepuis
CryptoPolicyInterfaceinterfaceisHashAlgorithmAllowed(), isSignatureAlgorithmAllowed(), isEncryptionAlgorithmAllowed(), isKeyStrengthAllowed(), getPreferredHashAlgorithm(), getName()stable1.9.0
HtmlSecurityPolicyInterfaceinterfaceisTagAllowed(), isAttributeAllowed(), isCssPropertyAllowed(), isUrlSchemeAllowed(), getMaxInputSize(), getMaxNestingDepth(), getName()stable3.1.0
ExternalResourcePolicyInterfaceinterfaceisFontFaceAllowed(), getAllowedFontSchemes(), getMaxFontFileSize(), getMaxFontGlyphs(), isImportAllowed(), getMaxImportDepth(), isBackgroundImageAllowed(), getAllowedImageDomains(), getMaxDataUrlSize(), isSvgExternalReferenceAllowed()stable4.0.0

ExternalResourcePolicyInterface renvoie des bornes typées : des tailles positive-int, une profondeur d’import int<1, 100>, ainsi que des listes de schémas et de domaines list<non-empty-string>. L’implémentation par défaut refuse toutes les capacités.

examples/contracts/security-policy-quickstart.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use NextPDF\Contracts\HtmlSecurityPolicyInterface;
/**
* Decide whether a tag survives the policy.
*
* @param HtmlSecurityPolicyInterface $policy A core or custom policy.
*/
function tagSurvives(HtmlSecurityPolicyInterface $policy, string $tag): bool
{
return $policy->isTagAllowed($tag);
}

La fonction dépend du contrat. Une politique restrictive et la politique par défaut satisfont toutes deux ce contrat.

examples/contracts/security-policy-production.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use NextPDF\Contracts\CryptoPolicyInterface;
use NextPDF\Contracts\ExternalResourcePolicyInterface;
use NextPDF\Contracts\HtmlSecurityPolicyInterface;
use Psr\Log\LoggerInterface;
final readonly class UntrustedHtmlGate
{
public function __construct(
private HtmlSecurityPolicyInterface $htmlPolicy,
private ExternalResourcePolicyInterface $resourcePolicy,
private CryptoPolicyInterface $cryptoPolicy,
private LoggerInterface $logger,
) {}
/**
* Reject input that exceeds the configured limits before rendering.
*
* @param string $html Untrusted HTML markup.
*/
public function assertAcceptable(string $html): void
{
$maxInput = $this->htmlPolicy->getMaxInputSize();
if ($maxInput > 0 && \strlen($html) > $maxInput) {
$this->logger->warning('HTML rejected: input over limit', [
'policy' => $this->htmlPolicy->getName(),
'limit' => $maxInput,
]);
throw new \LengthException('HTML input exceeds policy limit.');
}
if ($this->resourcePolicy->isSvgExternalReferenceAllowed()) {
$this->logger->error('Unsafe policy: SVG external references enabled.');
throw new \LogicException('SVG external references must be denied in production.');
}
}
}

Le point de contrôle applique le plafond d’entrée et refuse une politique de ressources non sûre avant l’exécution du pipeline. Il journalise le nom de la politique pour l’audit et lève une exception spécifique.

  • CryptoPolicyInterface autorise tous les algorithmes quand aucune politique n’est définie. Cette ouverture par défaut est un confort pour le développement, pas une posture de production. Définis une politique explicite dans tout déploiement soumis à une réglementation.
  • HtmlSecurityPolicyInterface::getMaxInputSize() renvoie 0 pour une taille illimitée. Traite 0 comme « aucune limite de politique », pas comme « tout refuser », et applique aussi un plafond au niveau du transport.
  • ExternalResourcePolicyInterface est deny-all par défaut. Activer @font-face ou background-image sans définir de liste de schémas ouvre une surface de falsification de requête ; définis la liste blanche lorsque tu actives une capacité.
  • Une liste blanche de domaines vide sur getAllowedImageDomains() signifie que tous les domaines sont autorisés dès que les images de fond sont activées. Une liste vide n’est pas un refus ; fournis des domaines explicites.
  • isSvgExternalReferenceAllowed() doit renvoyer false en production. Le contrat le documente ; une politique qui renvoie true constitue un défaut, pas un choix de configuration.

Une vérification de politique est un appel de prédicat : O(1), sans coût proportionnel à l’entrée. La politique est consultée par balise, attribut, propriété CSS et URL pendant l’analyse. Un document pathologique multiplie le nombre d’appels. Chaque appel reste à temps constant. Le performance_budget de 1500 ms de temps réel et 64 Mo en pic mémoire est dominé par l’analyse et le rendu, pas par l’évaluation de la politique. Les plafonds de taille d’entrée et de profondeur d’imbrication existent pour borner le coût propre de l’analyseur. Une politique stricte améliore les performances dans le pire cas en rejetant un document surdimensionné ou trop profondément imbriqué avant la mise en page.

Ces contrats forment le périmètre défensif du moteur ; le modèle de menace est donc explicite. La rétrogradation d’algorithme est atténuée par CryptoPolicyInterface, qui bloque les hachages faibles et les clés courtes avant toute opération. Le cross-site scripting dirigé vers PDF et l’injection de contenu sont atténués par HtmlSecurityPolicyInterface, qui supprime les balises, attributs et CSS non autorisés au niveau de l’analyse, avant l’exécution du moteur de rendu. La falsification de requête côté serveur, les bombes de décompression et les bombes de taille cumulée sont atténuées par ExternalResourcePolicyInterface, qui adopte deny-all par défaut et borne chaque récupération par schéma, taille, profondeur et domaine. L’épuisement des ressources est atténué par les plafonds de taille d’entrée, de profondeur d’imbrication, de glyphes de police et de profondeur d’import. Parce que chaque politique est un contrat, un déploiement peut durcir le périmètre sans forker le moteur, et le nom de la politique est exposé pour la journalisation d’audit. Traite tout le HTML, toutes les URL et tous les octets de polices et d’images comme hostiles. Cette page est marquée export_control_class: legal-review-required parce que les contrats régissent la politique cryptographique ; la prose paraphrase toutes les sources normatives et n’en cite aucune.

AffirmationNormeClausePreuve
Une gestion d’URL non contrainte permet de contourner le contrôle d’accès en modifiant l’URL, ce que la politique des ressources externes atténue par ses valeurs par défaut deny-all et par une liste blanche de domaines à correspondance exacte.OWASP Top 10 2025A01
Les composants externes doivent être obtenus uniquement depuis des sources officielles, via des liens sécurisés, ce que la politique impose à l’aide de listes blanches de schémas.OWASP Top 10 2025Chaîne d’approvisionnement logicielle

Les deux points sont paraphrasés à partir des recommandations OWASP. Le contenu OWASP est référencé par clause ; le moteur n’en reproduit pas le texte.

Le cœur définit et fige les trois contrats de politique. Il fournit des valeurs par défaut permissives pour le développement, ainsi que des valeurs par défaut strictes pour la politique de ressources deny-all. L’édition Enterprise fournit un profil FIPS 140-3 au travers de CryptoPolicyInterface : un déploiement soumis à une réglementation gagne ainsi une posture algorithmique validée sans modifier le code de signature ni de chiffrement. La surface contractuelle est identique d’une édition à l’autre. La différence tient à l’implémentation de politique qu’un déploiement injecte.

  • Contracts : 41 interfaces publiques (SPI) — la vue d’ensemble du SPI et les niveaux de stabilité.
  • Contracts / SigningCryptoPolicyInterface appliqué à la signature.
  • Contracts / Document — les points d’entrée writeHtml() et image() contrôlés par ces politiques.
  • Security — la surface de chiffrement que la politique cryptographique restreint.
  • HTML — le pipeline d’analyse que les politiques HTML et de ressources protègent.
  • Audit — la journalisation d’audit du nom de politique.