Données sensibles des rapports de crash : stoppez les fuites de contexte des agents
Les données sensibles d’un rapport de crash peuvent exposer des prompts d’agent, des corps de requête et des jetons. Découvrez comment limiter la collecte, tester les exportations et conserver les diagnostics en toute sécurité.

Le signalement des crashs peut devenir discrètement le principal moyen d’exporter des données dans une application équipée d’agents. Les équipes protègent leurs identifiants de production, puis laissent un SDK d’exception collecter les breadcrumbs, le contexte HTTP, les informations sur le processus et les entrées d’outils copiées avant d’envoyer le paquet vers un compte fournisseur. Le crash était local. Les éléments de preuve ne le sont plus.
La solution n’est pas d’abandonner les rapports de crash. Ils sont nécessaires lorsqu’une intégration d’agent échoue d’une manière que les journaux ordinaires ne permettent pas d’expliquer. Il faut décider quelles informations de débogage peuvent sortir de la machine, cesser de collecter par défaut les éléments bruts des requêtes et vérifier que le collecteur respecte ces limites. J’ai vu des analyses d’incident dérailler parce que la trace de pile était inoffensive, alors qu’un champ de contexte « utile » contenait toute la requête en échec.
Les rapports de crash sont des dossiers de preuves, pas de simples traces de pile
Un rapport de crash est souvent un paquet assemblé à plusieurs niveaux. Le système d’exploitation peut créer un rapport de diagnostic natif. Votre application peut y joindre des métadonnées d’exception. Un SDK de crash peut ajouter des informations sur l’appareil, des breadcrumbs, l’historique de session, des journaux, des champs de traçage et des étiquettes définies par l’utilisateur. Un framework d’agent peut déjà avoir sérialisé son prompt, l’entrée d’un outil, sa réponse ou l’état d’une nouvelle tentative dans l’un de ces champs.
Cette distinction compte, car les développeurs auditent souvent la trace de pile visible et considèrent le résultat comme propre. Les données sensibles se trouvent généralement à côté.
Un rapport peut exposer des données par le biais :
- de messages d’exception qui intègrent une URL, une commande ou un corps de réponse ;
- de breadcrumbs issus des journaux de requêtes ou des événements d’appel d’outil ;
- de champs personnalisés qui reçoivent un objet de requête complet ;
- d’arguments de ligne de commande, de chemins de fichiers temporaires et de valeurs issues de l’environnement ;
- de fichiers joints, de captures d’écran, de replays de session ou de retours envoyés au support.
Une adresse mémoire dans un rapport natif ne révèle généralement pas à elle seule une clé API. En revanche, une chaîne copiée dans un message de panique peut tout à fait le faire. Ne mélangez pas ces deux situations. Les diagnostics natifs ont besoin de contrôles d’accès ; la télémétrie applicative exige une réduction volontaire des données.
La documentation d’Apple décrit les rapports de diagnostic comme des enregistrements servant à diagnostiquer les problèmes d’une application, avec des informations sur les processus et les threads. C’est utile et généralement plus limité qu’un événement de surveillance applicative. Dès qu’un SDK enrichit ce rapport avec votre propre contexte d’exécution, la frontière des données change. Traitez l’événement enrichi comme une donnée applicative destinée à un autre système.
Les échecs d’agent créent un contexte de diagnostic particulièrement riche
Un gestionnaire de requêtes ordinaire peut échouer avec une route et un code d’état. Une action d’agent peut transporter une instruction utilisateur, des chemins de dépôt, la sortie d’une commande, des arguments destinés à une API distante, une destination SSH et les résultats précédents des outils ayant mené à l’échec. Ce contexte aide à reproduire le bug. Il rend aussi toute collecte imprudente coûteuse.
Le scénario à risque est prévisible. Un développeur enveloppe chaque invocation d’outil dans un logger pratique. Le logger émet l’objet d’arguments complet. Le SDK d’observabilité transforme les enregistrements en breadcrumbs. Un délai d’attente réseau déclenche une exception. Le rapport contient alors l’entrée de l’outil, avec éventuellement des en-têtes ou un jeton copié, et le fournisseur la reçoit.
Le même problème apparaît avec ce code :
try {
await runTool(toolName, input);
} catch (error) {
throw new Error(`Tool failed: ${toolName} input=${JSON.stringify(input)} error=${error.message}`);
}
Ce message semble utile pendant un test local. En production, il crée un champ sans limite que chaque collecteur d’erreurs, système de journaux et intégration d’alertes peut copier. Remplacez-le par des identifiants stables et un résumé limité à une liste autorisée :
try {
await runTool(toolName, input);
} catch (error) {
throw new Error(`Tool failed: name=${toolName} request_id=${requestId} input_shape=${inputShape}`);
}
inputShape peut désigner une liste de noms de champs autorisés et de tailles, jamais leurs valeurs. Si vous ne pouvez pas expliquer pourquoi un champ doit figurer dans un événement d’erreur, retirez-le. La reproduction doit commencer par un identifiant de corrélation qui renvoie vers un enregistrement local protégé, et non par une requête complète collée dans un tableau de bord hébergé.
La capture brute des requêtes est un mauvais comportement par défaut
La capture brute des requêtes reste populaire, car elle accélère la première session de débogage. Elle ne doit pas être le comportement par défaut des services qui gèrent des actions d’agent. Les en-têtes d’autorisation, cookies, URL signées, paramètres de requête, corps et en-têtes personnalisés contiennent régulièrement des éléments qui n’ont rien à faire dans un système de crash.
Un événement sûr contient suffisamment d’informations pour regrouper, trier et orienter l’incident :
{
"request_id": "rq_8c2f1a",
"channel": "http",
"method": "POST",
"route": "/v1/issues/{issue_id}/comments",
"status_class": "5xx",
"duration_ms": 8120,
"attempt": 2,
"error_kind": "upstream_timeout"
}
La route utilise un modèle, pas le chemin littéral. L’événement indique que la requête a été relancée, pas ce qu’elle a envoyé. L’identifiant opaque permet à une personne autorisée de consulter l’enregistrement local si nécessaire.
Ne hachez pas les secrets en prétendant les avoir masqués. Un hachage déterministe peut toujours identifier un même jeton bearer et être vulnérable lorsque l’espace de recherche de la valeur d’origine est réduit. Remplacez les valeurs sensibles par un marqueur constant ou supprimez-les. Si vous devez savoir si un identifiant était présent, enregistrez auth_present: true, pas son type, sa longueur, son préfixe ou son empreinte.
Examinez aussi la gestion des URL. De nombreuses bibliothèques enregistrent automatiquement les URL complètes. Une route telle que /callback?code=... ou une URL de téléchargement signée peut fuiter par un champ que le développeur n’a jamais ajouté manuellement. Supprimez les chaînes de requête avant que l’événement n’entre dans le SDK, au lieu d’espérer qu’un processeur ultérieur reconnaîtra toutes les variantes.
Le masquage doit s’appliquer avant le stockage et avant l’export
Un filtre de nettoyage est un filet de sécurité, pas une autorisation de tout collecter. Les hooks des SDK diffèrent : certains traitent l’événement final, certains ne traitent que certains champs et d’autres ne couvrent pas les pièces jointes de crash natif ou les breadcrumbs produits par une intégration distincte. Une règle qui masque Authorization peut manquer authorization, x-api-token, un paramètre d’URL ou une chaîne JSON intégrée à une exception.
Construisez la frontière en plusieurs couches. Commencez par désactiver les captures automatiques dont vous n’avez pas besoin. Construisez ensuite les événements à partir de champs autorisés. Appliquez ensuite un filtre défensif à toutes les chaînes restantes. Enfin, testez les données sérialisées reçues par le collecteur.
Ce pseudocode montre l’ordre qui évite la plupart des problèmes :
request arrives
-> derive route template and request ID
-> retain protected local diagnostic record if policy permits
-> create minimal crash context from allowlisted fields
-> scrub all residual strings
-> send minimized event
Ne transmettez pas l’objet de requête d’origine à un callback « before send ». Dès qu’une bibliothèque a inspecté cet objet, des plugins ont peut-être déjà transformé son contenu en breadcrumbs ou en spans. Fournissez au code de télémétrie un petit objet qui ne peut pas contenir de champs sensibles dès le départ.
Sentry documente le nettoyage des données et les processeurs d’événements, tandis que Firebase Crashlytics documente les clés personnalisées et la collecte de journaux. Considérez ces paramètres comme des contrôles de collecte, et non comme une garantie générale de confidentialité. Dans les deux cas, le contexte personnalisé et les journaux sont les endroits où les équipes applicatives contournent le plus souvent leurs propres protections par défaut.
Les valeurs par défaut des SDK changent avec les intégrations
Une mise à niveau du SDK de crash peut ajouter le traçage, la capture de la console, les breadcrumbs, le replay de session, l’instrumentation des performances ou une intégration de framework qui voit davantage l’état des requêtes. Un examen de sécurité réalisé lorsque l’application ne produisait que des traces de pile ne couvre pas ce nouveau chemin de données.
Tenez un petit inventaire de collecte pour chaque environnement d’exécution. Notez le collecteur de crash natif, le SDK d’exception, le pont de journalisation, le paquet de traçage, le module de retour utilisateur et le mécanisme de paquet de support. Pour chacun, répondez à quatre questions simples : qu’est-ce qui déclenche la collecte, quels champs sont collectés automatiquement, où les données sont-elles stockées avant l’envoi et qui peut les lire après l’envoi ?
N’oubliez pas les canaux secondaires. Les développeurs désactivent souvent les corps de requête dans le produit de crash, puis transmettent la même exception à un agrégateur de journaux. Les règles d’alerte peuvent copier le texte de l’exception dans des notifications de chat. Un bouton de support peut joindre une archive de journaux locale. Il vous faut une seule carte des données pour le parcours d’incident, pas une vision optimiste et distincte pour chaque fournisseur.
Sur macOS, examinez à la fois les rapports au niveau de l’application et les diagnostics du système d’exploitation. Les rapports de crash Apple peuvent rester locaux ou être partagés selon les choix de signalement du système, tandis que les SDK tiers suivent leur propre configuration et leur propre chemin réseau. Une application de bureau doit expliquer clairement cette distinction aux personnes qui l’exploitent. « Les rapports de crash sont désactivés » ne veut rien dire si un client distinct de surveillance des erreurs téléverse encore des événements enrichis.
Gardez les identifiants hors du processus de l’agent
Le masquage réduit l’exposition après que le code a manipulé un secret. La meilleure approche consiste à empêcher le processus de l’agent de détenir ce secret. Ainsi, un crash de l’agent, une copie de l’environnement ou un vidage de débogage accidentel contiennent moins de données sensibles susceptibles d’être capturées.
Cela ne rend pas un rapport de crash inoffensif. L’agent peut toujours détenir du code source privé, des prompts ou des arguments d’outils. En revanche, un jeton bearer ne peut pas fuiter d’un processus qui ne l’a jamais reçu.
Sallyport applique cette séparation aux API HTTP et aux actions SSH : l’application conserve les identifiants dans son coffre chiffré, exécute l’action et renvoie le résultat à l’agent sans lui transmettre les identifiants. Cela supprime une cause récurrente de fuite dans la télémétrie de crash, mais vous devez toujours réduire les données de requête et le texte des résultats traités par votre agent.
Évitez également de placer des secrets dans les arguments de commande. Les arguments de processus apparaissent dans les outils de diagnostic plus souvent que les développeurs ne l’imaginent, et l’historique du shell ou l’inspection des processus peuvent les exposer indépendamment des rapports de crash. Faites passer les secrets par un intermédiaire protégé ou un mécanisme local soigneusement géré, pas par un argument visible comme --token=....
Le stockage local doit lui aussi avoir une règle de conservation
Conserver des rapports complets sur la machine peut être approprié pour les problèmes difficiles, surtout pendant un développement contrôlé. Ce n’est pas automatiquement sûr. Un tampon local peut survivre à l’incident, être copié dans une archive de support ou rester dans un profil utilisateur partagé où un autre processus peut le lire.
Séparez deux enregistrements. Envoyez un événement distant réduit pour le regroupement et les alertes. Conservez tout enregistrement de reproduction plus complet localement, dans un emplacement protégé, pendant une courte durée et avec une procédure de suppression explicite. L’événement distant a besoin de l’identifiant opaque de l’enregistrement local, pas de son contenu.
Un enregistrement local utile contient l’identifiant exact de la version, une empreinte de configuration nettoyée, les informations de durée et une référence à la tentative d’action. Il ne doit pas prendre la forme d’un fichier texte libre qui concatène les variables d’environnement, les corps de requête et la sortie du terminal. Un stockage local structuré permet d’appliquer les mêmes règles de liste autorisée et de suppression qu’à vos exportations.
Pour les passerelles d’actions d’agent, les journaux d’audit demandent une attention particulière. Ils aident à établir ce que l’agent a tenté de faire, mais ne donnent pas le droit de recopier des identifiants bruts ou des prompts complets dans chaque sous-système de diagnostic. La piste d’audit de Sallyport sépare les enregistrements d’action de l’agent lui-même, et sa commande sp audit verify vérifie hors ligne la chaîne de hachage chiffrée sans clé du coffre. La vérification indique si la piste a été modifiée ; la réduction des données détermine toujours quel contexte de diagnostic doit être transmis ailleurs.
Prouvez le comportement du collecteur avec des secrets factices
L’examen de la configuration repère les erreurs évidentes. Un test avec des données factices repère celles qui comptent. Exécutez-le avant le lancement, après les mises à niveau des SDK de télémétrie et chaque fois qu’un framework d’agent reçoit un nouvel outil ou une nouvelle intégration de journalisation.
Utilisez des chaînes marqueurs uniques, impossibles à confondre avec de véritables identifiants. Placez des marqueurs différents aux endroits que les équipes oublient régulièrement : un en-tête d’autorisation, un paramètre de requête, un champ de corps JSON, une instruction d’agent, une variable d’environnement, un argument de commande et un résultat d’outil. Déclenchez une exception contrôlée, puis recherchez chaque marqueur dans toutes les destinations.
Vérifiez la vue de l’événement chez le fournisseur, l’export brut de l’événement s’il est disponible, le tampon de crash local, les journaux applicatifs, les événements de traçage, les charges utiles d’alerte, les pièces jointes de support et toute file entre l’application et le collecteur. Recherchez les marqueurs exacts ainsi que les transformations courantes comme l’encodage d’URL ou l’échappement JSON. Examiner uniquement le tableau de bord web est le meilleur moyen de manquer une fuite dans un breadcrumb ou un journal joint.
Écrivez le résultat attendu. Par exemple, un rapport peut contenir route=/v1/files/{file_id} et request_id=rq_test_01, mais il ne doit pas contenir MARKER_HEADER_7, MARKER_PROMPT_7 ni la chaîne de requête littérale. Traitez un test en échec comme un défaut de sécurité : désactivez le chemin de capture fautif, ajoutez un test de non-régression et testez à nouveau l’événement sérialisé.
Une petite revue suffit à éviter la plupart des exportations accidentelles
Examinez la télémétrie de crash chaque fois qu’une personne modifie la gestion des erreurs, l’observabilité, un outil d’agent ou les diagnostics de support. Le réviseur doit se demander si le nouveau code peut sérialiser un objet ayant la forme d’une requête, et pas seulement s’il a ajouté un nouveau champ secret.
Utilisez cette courte liste lors de la revue :
- Les messages d’erreur contiennent des identifiants et des catégories, pas des entrées ou sorties sérialisées.
- La télémétrie reçoit un contexte limité à une liste autorisée, jamais une requête brute ni un objet d’état de l’agent.
- Les URL perdent leurs chaînes de requête et les en-têtes n’entrent jamais dans les breadcrumbs ni les champs personnalisés.
- Les diagnostics locaux complets disposent d’un stockage protégé, de règles de suppression et d’une raison clairement établie d’exister.
- Un test avec marqueurs factices couvre chaque destination d’export configurée.
La partie inconfortable, c’est que les bonnes habitudes de débogage provoquent souvent la fuite. Les développeurs ajoutent du contexte parce que le dernier incident en manquait. Conservez le contexte qui permet de classer l’échec, gardez les preuves plus complètes sous contrôle local lorsque cela se justifie et cessez d’exporter des données brutes simplement parce qu’un SDK de crash propose un champ pour les recevoir.
FAQ
Les rapports de crash peuvent-ils contenir des clés API ou des prompts d’agent ?
Oui. Un rapport de crash peut contenir des arguments de processus, des chemins issus de l’environnement, des modules chargés, des piles de threads, des breadcrumbs, des journaux et l’état de l’application capturé juste avant la panne. La présence d’un secret dépend de ce que votre programme a placé en mémoire, dans les journaux, les URL, les messages d’exception ou les métadonnées.
Est-il sûr d’envoyer des rapports de crash de production à un service tiers ?
La collecte de crashs est suffisamment sûre seulement après avoir traité le système comme une destination de données externe et testé le contenu réel de l’événement. Les garanties du fournisseur n’effacent pas un jeton intégré à une URL, un corps de requête enregistré comme breadcrumb ou un prompt copié dans un message d’exception.
Dois-je inclure les en-têtes HTTP dans les rapports de crash ?
La plupart des équipes devraient exclure les en-têtes de requête de la télémétrie de crash. Si un rapport d’erreur a besoin du contexte de la requête, envoyez la méthode autorisée, le modèle de route, le code d’état et un identifiant de corrélation, plutôt que la carte brute des en-têtes.
Puis-je compter sur le masquage du système de rapports de crash pour supprimer les secrets ?
Une fonction de masquage aide, mais elle ne peut pas nettoyer des données capturées par le SDK avant son exécution, ni des données exposées par une autre intégration. Désactivez d’abord les captures risquées, puis utilisez le masquage comme filet de sécurité et testez-le en continu.
Quelles données les systèmes de crash collectent-ils par défaut ?
Les systèmes de crash mobiles et de bureau collectent souvent par défaut des informations sur l’appareil, la version de l’application, les frames de la pile et des métadonnées de diagnostic. Les journaux, clés personnalisées, captures d’écran, replays de session, breadcrumbs et retours utilisateur demandent généralement un examen séparé, car chacun peut contenir beaucoup plus de contexte.
Quelles informations de requête sont utiles sans révéler la requête elle-même ?
Capturez le modèle de route, la méthode HTTP, la classe de réponse, la durée, le nombre de tentatives et un identifiant de requête opaque. Ne capturez pas les chaînes de requête brutes, les corps, les en-têtes d’autorisation, les cookies ni les arguments complets des outils de l’agent, sauf si un processus local précisément contrôlé l’exige.
Comment vérifier que la télémétrie de crash ne révèle pas de secrets ?
Utilisez un crash synthétique ou une exception contrôlée dans un projet de test, puis examinez l’événement dans la console du fournisseur, le JSON exporté, le tampon local et tous les systèmes de journaux connectés. Recherchez les marqueurs placés dans les en-têtes, les paramètres de requête, le texte du prompt et les variables d’environnement.
Les appels d’outils d’un agent peuvent-ils apparaître dans la surveillance des erreurs ?
Un système de crash peut capturer indirectement des arguments si la bibliothèque cliente enregistre les entrées d’outil comme breadcrumb, entrée de journal, attribut de span, champ de contexte personnalisé ou texte d’exception. Traitez les prompts d’agent et les données utiles des outils comme des données applicatives sensibles, même lorsqu’il ne s’agit pas d’identifiants.
Les variables d’environnement peuvent-elles fuiter dans les rapports de crash ?
Désactivez la capture automatique de l’environnement lorsque le SDK le permet et ne placez jamais de secrets dans les noms de variables d’environnement ou les arguments de ligne de commande. Surtout, sortez les identifiants du processus de l’agent afin qu’un paquet de diagnostic accidentel ait moins de données à exposer.
Les rapports de crash doivent-ils rester sur la machine locale ?
Conservez les artefacts de diagnostic bruts sur une machine locale dont l’accès est contrôlé, ou n’envoyez au collecteur hébergé qu’un événement réduit. Si vous devez téléverser des artefacts plus complets pour le débogage, utilisez un processus d’incident distinct avec des limites de conservation et des approbateurs désignés, plutôt que de faire de la capture complète le comportement par défaut.