Aller au contenu
getnextpdf.com

Enterprise édition

Content Disarm and Reconstruction — Référence approfondie

Cette page est la référence approfondie du module NextPDF\Enterprise\Security\Cdr. Le module désarme un PDF non fiable et reconstruit un fichier propre à partir de ses objets sûrs. Le pipeline est le suivant : analyse, contrôle d’admission, détection des menaces, filtrage, nettoyage des références, reconstruction. La sortie est une projection de sécurité de l’entrée, jamais une copie probante. Pour des conseils sur le flux de travail, lis d’abord la page de capacité CDR.

Cette capacité est livrée dans NextPDF Enterprise (nextpdf/enterprise) et s’active avec une enveloppe de licence de niveau Enterprise. Un déploiement dépourvu de cette autorisation ne charge pas les classes de la capacité. Compare les éditions et obtiens une licence.

SymboleParamètresComportement par défautRetourneLève ou échoue avecNotes
CdrEngine::__constructaucunConstruit le détecteur et le reconstructeur internesCdrEngineRien de déclaréAucun collaborateur injectable
CdrEngine::sanitizestring $pdfData, ?CdrPolicy $policy = nullExécute le pipeline complet sous CdrPolicy::standard()CdrResultNe lève pas d’exception sur une entrée hostile ; les échecs d’analyse et d’admission retournent un résultat rejetéLe résultat signale le rejet distinctement de l’assainissement
CdrPolicy::__constructsept paramètres nommés optionnels, voir l’encartJeu de suppression vide ; allowUriActions false ; flattenIncrementalUpdates true ; limites de 100000 objets, 256 MiB décodés, 10000 pages, inflation 1000.0CdrPolicyRien de déclaréfinal readonly ; une liste removeThreatTypes vide ne détecte rien
CdrPolicy::standardaucunJeu de menaces historique ; actions URI supprimées ; limites par défautselfRien de déclaréExclut les sept cas Strip* à perte
CdrPolicy::paranoidaucunJeu de menaces historique avec des limites plus strictes : 50000 objets, 128 MiB, 5000 pages, inflation 100.0selfRien de déclaréExclut les sept cas Strip* à perte
CdrPolicy::permissiveaucunSupprime uniquement JavaScript, LaunchAction, NamedJavaScript, SubmitForm, ImportData ; préserve les actions URIselfRien de déclaréDestiné aux sources fiables
CdrPolicy::allThreatTypesaucunRetourne tous les cas ThreatType, y compris les cas Strip* à pertelist<ThreatType>Rien de déclaréL’activation explicite du décapage maximal
CdrPolicy::legacyThreatTypesaucunRetourne tous les cas sauf les sept cas Strip*list<ThreatType>Rien de déclaréJeu de suppression par défaut pour standard() et paranoid()
CdrPolicy::shouldRemoveThreatType $typeTest d’appartenance à removeThreatTypesboolRien de déclaréRetourne false pour UriAction quand allowUriActions vaut true
ThreatDetector::detectPdfReader $reader, CdrPolicy $policyAnalyse chaque objet et le catalogue du trailer à la recherche des types de menaces de la politiquelist<DetectedThreat>Ne lève pas d’exception ; un objet non analysable devient une menace UnparseableObjectL’analyse du catalogue couvre l’arbre /Names/JavaScript
CdrRebuilder::rebuildPdfReader $reader, list<int> $safeObjNums, list<int> $removedObjNums, CdrPolicy $policySérialise les objets sûrs dans un fichier %PDF-2.0 à révision uniquestringRien de déclaré ; les objets qui échouent à la relecture ou à la validation /Length sont ignorés$policy est réservé pour de futurs ajustements de sérialisation
DetectedThreat::__constructThreatType $type, int $objectNumber, string $description, string $location = ''Objet-valeur de constat immuableDetectedThreatRien de déclaréLes quatre propriétés sont public readonly
ThreatTypeénumération adossée à des chaînesVingt cas : treize historiques plus sept cas Strip* optionnelsn/an/aVoir l’inventaire des cas ci-dessous
final class CdrEngine
{
public function __construct()
public function sanitize(string $pdfData, ?CdrPolicy $policy = null): CdrResult
}
final readonly class CdrPolicy
{
public function __construct(
public array $removeThreatTypes = [],
public bool $allowUriActions = false,
public bool $flattenIncrementalUpdates = true,
public int $maxObjects = 100_000,
public int $maxDecodedStreamBytes = 268_435_456,
public int $maxPageCount = 10_000,
public float $maxInflationRatio = 1000.0,
)
public static function standard(): self
public static function paranoid(): self
public static function permissive(): self
public static function allThreatTypes(): array
public static function legacyThreatTypes(): array
public function shouldRemove(ThreatType $type): bool
}
final class ThreatDetector
{
public function detect(PdfReader $reader, CdrPolicy $policy): array
}
final class CdrRebuilder
{
public function rebuild(PdfReader $reader, array $safeObjNums, array $removedObjNums, CdrPolicy $policy): string
}
final readonly class DetectedThreat
{
public function __construct(
public ThreatType $type,
public int $objectNumber,
public string $description,
public string $location = '',
)
}
enum ThreatType: string

Treize cas historiques forment le jeu de suppression par défaut. Les cas Strip* sont à perte par conception et n’entrent jamais dans une politique par défaut.

CasValeur d’adossementSurface de détection
ThreatType::JavaScriptjavascriptClé /JS sur n’importe quel objet, ou une action /S /JavaScript
ThreatType::AdditionalActionsadditional-actionsDictionnaire /AA sur n’importe quel objet
ThreatType::OpenActionopen-actionClé /OpenAction sur n’importe quel objet
ThreatType::LaunchActionlaunch-actionAction /S /Launch
ThreatType::RemoteGoToremote-gotoAction /S /GoToR ou /S /GoToE
ThreatType::SubmitFormsubmit-formAction /S /SubmitForm
ThreatType::ImportDataimport-dataAction /S /ImportData
ThreatType::EmbeddedFilesembedded-filesArbre de noms /EmbeddedFiles ou dictionnaire /EF
ThreatType::RichMediarich-media/Subtype /RichMedia
ThreatType::NamedJavaScriptnamed-javascriptArbre de noms /Names/JavaScript du catalogue
ThreatType::UriActionuri-actionAction /S /URI ; supprimée quand allowUriActions vaut true
ThreatType::XfaxfaClé /XFA
ThreatType::UnparseableObjectunparseable-objectTout objet ou catalogue qui échoue à l’analyse
ThreatType::StripJavaScriptstrip-javascriptSur-ensemble optionnel : clé /JS, /S /JavaScript, ou /Subtype /JavaScript
ThreatType::StripEmbeddedFilesstrip-embedded-filesOptionnel : /Type /EmbeddedFile, /Type /Filespec, /EmbeddedFiles, ou /EF
ThreatType::StripFormFieldsstrip-form-fieldsOptionnel : /Subtype /Widget, clé /FT, ou clé /AcroForm
ThreatType::StripAnnotationsRichstrip-annotations-richSous-types optionnels : Movie, Sound, FileAttachment, 3D, RichMedia, Screen
ThreatType::StripOcgNonDefaultstrip-ocg-non-defaultOptionnel : /Type /OCG avec une clé /Usage ou /Visibility
ThreatType::StripDigitalSignaturesAtRebuildstrip-digital-signatures-at-rebuildOptionnel : /Type /Sig, /FT /Sig, /DSS, /VRI, ou /ByteRange
ThreatType::Strip3dAndRichMediastrip-3d-and-rich-mediaSous-types optionnels : 3D, U3D, PRC, RMF, RichMedia, Sound, Movie

CdrEngine::sanitize exécute six phases ordonnées et ne lève jamais d’exception pour une entrée hostile.

  1. Analyse. Un échec d’analyse retourne un résultat avec admitted à false et un motif de rejet d’erreur d’analyse. La sortie assainie est vide dans ce cas.
  2. Contrôle d’admission. Le nombre d’objets, le total des octets de flux décodés, le ratio d’inflation par flux et le nombre de pages sont vérifiés par rapport aux limites de la politique. Un document hors limites est rejeté, pas assaini. Le rejet et l’assainissement sont signalés distinctement.
  3. Détection. ThreatDetector::detect analyse chaque objet et le catalogue du trailer à la recherche des types de menaces de la politique. Les objets non analysables sont enregistrés comme des constats ThreatType::UnparseableObject plutôt qu’ignorés.
  4. Filtrage. Les objets porteurs de constats sont mis en file d’attente pour suppression. Le catalogue du document n’est jamais supprimé en tant qu’objet entier. Les constats au niveau du catalogue (OpenAction, AdditionalActions, NamedJavaScript) sont remédiés par décapage de clés à la place.
  5. Nettoyage des références. Chaque référence indirecte vers un objet supprimé est remplacée par null pendant la sérialisation.
  6. Reconstruction. CdrRebuilder::rebuild émet un fichier %PDF-2.0 à révision unique avec des objets renumérotés, une table de références croisées classique et un trailer neuf. Les octets de flux sûrs sont copiés à l’identique octet pour octet. Le catalogue reconstruit abandonne /OpenAction, /AA et /Names ; /AA est abandonné de chaque objet.

Le CdrResult retourné expose les octets reconstruits, la liste des menaces supprimées, les deux tailles en octets, l’indicateur d’admission et le motif de rejet. Si la source avait un /Root résoluble et que la sortie reconstruite l’a perdu, le moteur rejette la sortie au lieu de retourner un fichier structurellement cassé. C’est une garantie fail-closed : admitted à true implique que la sortie porte encore une référence au catalogue du document.

Les mises à jour incrémentales ne survivent jamais : la reconstruction sérialise exactement une révision sous chaque politique, si bien que les révisions tardives de type shadow sont aplaties par construction. Les signatures numériques d’origine ne peuvent pas rester valides après une reconstruction, car les plages d’octets ne correspondent plus à la sortie.

Ligne rouge d’architecture. Le CDR est une couche de projection de sécurité, pas une couche de préservation. La sortie ne doit pas être utilisée pour la préservation de preuves légales, la comparaison de hachage avec l’original, ou les copies d’archivage.

  • Une politique null se résout en CdrPolicy::standard(). Une politique construite avec le removeThreatTypes vide par défaut ne détecte et ne supprime rien.
  • allowUriActions à true supprime le retrait de UriAction même quand le cas est présent dans removeThreatTypes.
  • flattenIncrementalUpdates est déclaratif dans cette version : la reconstruction émet une seule révision sous chaque politique, y compris permissive(), qui met l’indicateur à false.
  • Le contrôle du ratio d’inflation traite une longueur de flux brut de zéro comme un, de sorte qu’un flux qui se gonfle à partir de rien reste borné. Quand aucune forme décodée n’est conservée, la longueur du flux brut compte dans le budget agrégé.
  • Le contrôle d’admission du nombre de pages est au mieux : un échec de lecture du catalogue ou de l’arbre des pages ne rejette pas le document à lui seul. Les budgets de nombre d’objets et de décompression sont toujours appliqués.
  • Un objet dont la longueur de flux brut diffère de son entrée entière /Length est ignoré au moment de la reconstruction (défense polyglotte). Une référence vers un tel objet ignoré conserve son numéro d’objet source et peut ne pas se résoudre dans la sortie. sanitize() refuse les résultats détectablement cassés (un /Root manquant), mais un appelant qui pilote directement le bas niveau CdrRebuilder::rebuild() doit revalider lui-même la structure de sortie et l’intégrité des références.
  • Quand le trailer source porte /ID, le trailer reconstruit porte un /ID aléatoire fraîchement généré, pas l’original. Les autres entrées du trailer, y compris /Info, ne sont pas reportées ; le trailer reconstruit contient /Size, /Root quand il est résoluble, et le /ID régénéré.
  • Les octets de noms et de clés décodés sont réémis avec des échappements hexadécimaux pour les délimiteurs, les espaces et les octets non imprimables, de sorte que des noms hostiles ne peuvent pas injecter de syntaxe de dictionnaire dans la sortie.
  • Les valeurs de chaîne sous des clés de dictionnaire hors de l’ensemble connu de clés à valeur de nom sont émises de manière conservatrice comme des chaînes littérales.
  • CdrPolicy::legacyThreatTypes() traite tout futur cas d’énumération comme supprimé par défaut sauf s’il est enregistré comme cas Strip*, de sorte que de nouveaux cas à perte ne peuvent pas entrer silencieusement dans les politiques par défaut.
  • Le CDR n’est pas un module cryptographique. Son seul usage d’aléa est le /ID régénéré du trailer. La validation de signature est hors du périmètre ici ; voir la référence approfondie sur les signatures.
AffirmationStandardClause
L’invocation d’une action ECMAScript fait exécuter le script intégré par un processeur PDF.ISO 32000-2§12.6.4.17
Les scripts au niveau du document dans l’arbre de noms JavaScript s’exécutent tous à l’ouverture du document.ISO 32000-2§12.6.4.17
Le dictionnaire de noms du catalogue peut contenir un arbre de noms JavaScript d’actions de script au niveau du document.ISO 32000-2§7.7.4 (Table 32)
Une action de lancement lance une application, ou ouvre ou imprime un document.ISO 32000-2§12.6.4.6
Les dictionnaires d’actions additionnelles /AA étendent les événements déclencheurs sur les annotations, les pages, les champs et le catalogue.ISO 32000-2§12.6.3
L’ingestion de fichiers non fiables doit borner la présence, le volume et le contenu des fichiers entrants.OWASP ASVS 5.0§5.2
Les systèmes devraient empêcher l’exécution inappropriée des fichiers téléversés et détecter les contenus dangereux.OWASP ASVS 5.0§5.3

Toutes les clauses sont paraphrasées ; NextPDF ne reproduit pas de texte normatif. NextPDF ne formule aucune revendication de certification. Le CDR retire les surfaces de contenu actif énumérées par ThreatType selon la politique configurée ; c’est une capacité, pas un assainisseur certifié. Le CDR n’est pas un antivirus et ne détecte pas de signatures de logiciels malveillants ; il complète, et ne satisfait pas, des contrôles tels que l’analyse antivirus OWASP ASVS 5.4.3. Savoir si un fichier désarmé est acceptable pour un pipeline d’ingestion donné reste la décision de risque de l’opérateur.

  • La source du module porte @since 1.9.0 ; cette référence documente la surface telle que livrée dans nextpdf/enterprise 3.1.0.
  • Tout s’exécute dans le processus sur ton hôte. Aucun accès réseau n’a lieu pendant l’assainissement.
  • CdrPolicy et DetectedThreat sont final readonly ; construis une nouvelle instance de politique pour changer les limites.
  • CdrEngine construit son détecteur et son reconstructeur en interne. ThreatDetector et CdrRebuilder restent directement utilisables pour des pipelines par étapes qui fournissent leur propre PdfReader.
  • Le paramètre $policy de CdrRebuilder::rebuild est actuellement réservé ; la source le documente comme conservé pour la compatibilité des sites d’appel et de futurs ajustements de sérialisation par politique.
  • La sortie est reproductible structurellement, pas au bit près : le /ID régénéré diffère à chaque exécution quand la source en portait un.
  • Le type de résultat CdrResult (valeur de retour de sanitize()) est couvert comportementalement ci-dessus ; ses champs sont public readonly, avec hadThreats() et threatCount() comme commodités.

Cette page documente uniquement le comportement observable de l’extérieur et la surface d’API publique prise en charge. Les chemins de namespace internes, les classes utilitaires, les tables de mécanismes, les noms de fichiers de runbook et les préfixes de tickets sont hors du périmètre.