Il n’existait nulle part dans le produit d’événement disant “un nouveau rapport est arrivé”. Les notifications arrivaient dans une boîte de réception et nulle part ailleurs, si bien qu’une équipe qui voulait ses rapports dans n8n, dans Zapier ou dans un script à elle n’avait rien vers quoi les pointer.

Il y a maintenant une destination : une URL à vous, les événements que vous voulez, et une clé de signature. Quatre événements, nommés une fois et écrits à l’identique partout - report.created, report.status_changed, report.severity_changed et report.first_viewed. Ils constituent une interface dès l’instant où ils sortent, parce que quelqu’un en met un dans un flux et qu’un renommage le casse en silence, si bien que la charge utile porte sa propre version dès la première version publiée.

Ce qu’il vaut la peine de savoir avant de pointer quelque chose vers elle :

  • Chaque POST est signé. HMAC-SHA256 sur l’horodatage et le corps brut, l’horodatage se trouvant à l’intérieur de la matière signée, de sorte qu’une requête capturée ne peut pas vous être rejouée plus tard.
  • La destination appartient à l’équipe, pas à une personne. L’adresse doit continuer de fonctionner quand celui qui l’a ajoutée s’en va.
  • Les échecs sont réessayés, puis abandonnés. La temporisation court environ huit heures et demie avant de renoncer.
  • Chaque tentative est consignée. “Le webhook s’est-il déclenché ?” se répond depuis /app/webhooks plutôt que depuis les journaux de production de quelqu’un.

Une URL fournie par un client est par construction une surface de falsification de requêtes, aussi l’adresse est-elle résolue et vérifiée à nouveau juste avant chaque connexion et non seulement au moment de son enregistrement, aucune redirection n’est jamais suivie, et le corps de la réponse n’est lu que ce qu’il faut pour fermer le flux.

La page qu’ouvre un inconnu

La page partagée d’un rapport est autant la démonstration du produit que l’outil de tri de son propriétaire, et les deux attendent des choses différentes du même écran.

L’ordre des onglets avait été décidé pour le propriétaire : ce qu’a écrit celui qui signale vient en premier, parce que c’est ce qu’un rapport contient de plus précieux et de plus court. C’est juste pour quelqu’un qui traite sa propre boîte de réception et faux pour celui à qui le lien a été envoyé, qui arrivait devant sept mots dont il ne peut rien faire, tandis que les erreurs de console, les requêtes en échec et l’enregistrement se trouvaient à un clic de là, jamais ouverts. Un lecteur qui n’est pas propriétaire du rapport arrive désormais sur les preuves, et le bandeau au-dessus des onglets annonce ce que contient la capture au lieu de le laisser découvrir.

Le lecteur lui-même avait deux défauts, tous deux visibles sur une seule copie d’écran. Sa surface sombre s’étendait sur toute la largeur de la page, si bien que tout enregistrement plus étroit que cette bande - sur un écran large, la plupart d’entre eux - se retrouvait échoué au milieu d’un champ noir. Et il s’ouvrait en pause sur l’image zéro, c’est-à-dire sur ce qui se trouvait à l’écran de celui qui signalait pendant qu’il tendait encore la main vers le bouton d’enregistrement. La surface sombre est maintenant une marge intérieure de l’élément média lui-même, de sorte qu’elle épouse l’enregistrement quel que soit le format, et le lecteur s’ouvre sur une image choisie pour valoir la peine d’être regardée.

Celui qui signale sait ce que devient son enregistrement

Le journal des interactions refuse de conserver ce que quelqu’un a tapé dans un champ de mot de passe. Le journal réseau masque par défaut les valeurs d’identifiants. La vidéo montre l’écran sur lequel il les a tapées, et rien n’en tient compte.

Même question, deux réponses différentes - et la personne la mieux placée pour trancher est celle qui signale, parce qu’elle est la seule à savoir si la fiche client ouverte derrière le bogue compte. On lui dit donc désormais ce que devient l’enregistrement avant qu’elle le lance, plutôt qu’après.

C’est le même réflexe qui est à l’origine de trois changements plus modestes de l’autre côté. Le fait que quelqu’un lise son propre rapport n’a jamais compté dans son nombre de vues, mais rien ne le disait ; la page du rapport, la liste des rapports et la vue d’ensemble expliquent maintenant chacune pourquoi le nombre les exclut.

Session Replay

Extension Chrome gratuite. Un clic sur la page qui se comporte mal capture la copie d'écran, la console et le journal réseau, et vous rend un lien à coller dans le ticket.

Installer l'extension

La capture coûte moins cher, et son masquage est devenu cohérent

Une capture tourne au rapport de pixels de l’écran sauf indication contraire, si bien qu’un écran 6K enregistrait 6016 pixels de large. Ces pixels étaient payés trois fois : à l’envoi par celui qui signale, où ils mangent le budget en octets de la formule et donc la durée qu’il a le droit d’enregistrer ; au stockage, parce que l’original est conservé quoi qu’on encode à côté ; et à chaque transcodage, où le décodage est la moitié coûteuse. Mesuré en production, un extrait de trente-deux secondes coûtait neuf minutes et cinquante-quatre secondes de processeur. La capture est désormais plafonnée à 1920 de large.

Le correctif du masquage est le plus important des deux. Un domaine ayant droit à une capture non masquée ramenait le masque des en-têtes et de la chaîne de requête aux seuls noms de cartes, si bien qu’un même enregistrement pouvait masquer un champ de formulaire nommé api_key tout en gardant en clair un en-tête X-Api-Key, un jeton CSRF et un ?password= issus de la même requête. Personne n’avait choisi cela : c’étaient deux chemins à travers un même fichier qui répondaient différemment à la même question. Les identifiants dans les en-têtes et les URL sont masqués sur un domaine ayant ce droit lui aussi, et un champ de passeport que la liste des identifiants avalait est maintenant correctement repéré.

Trois choses que nous avions annoncées

Il vaut la peine de les citer ensemble, parce que chacune a été écrite ici avant d’être faite.

L’extension n’expose plus ses icônes et ses sons à toutes les origines. web_accessible_resources accordait les deux répertoires à toutes les URL et aucune page n’a jamais chargé quoi que ce soit de l’un ou de l’autre - une extension lit ses propres fichiers empaquetés sans aucune autorisation. C’était le pendant de la permission inutilisée dont nous avons parlé dimanche, et les deux ont disparu ; l’extension 1.6.2 est en boutique.

La bibliothèque d’intégration est servie depuis nos propres pages en 0.4.0, si bien que identify() acceptant n’importe quelle clé qu’une balise meta peut porter n’est plus vrai seulement pour ceux qui installent depuis npm. Trois endroits qui disaient encore aux propriétaires de sites qu’elle accepte cinq clés ont été corrigés.

Et à minuit et demi cette nuit, le serveur MCP a commencé à annoncer lors de la poignée de main que le texte qu’il relaie est d’origine tierce : que le commentaire, le titre, l’URL et le nom d’un rapport ont été écrits par celui qui a signalé le bogue, le plus souvent un membre du public, et qu’ils doivent être traités comme des données à lire et à citer, jamais comme des instructions à suivre. Nous l’avons consigné hier, sur la foi des travaux de GhostSplice, et avons écrit pourquoi.

Choses plus petites

  • Le titulaire de l’abonnement est celui qui décide de l’annuler et de le reprendre.
  • Un domaine revendiqué s’appelle un domaine partout où l’interface le nomme, plutôt qu’un site une fois sur deux.
  • Les images des articles peuvent porter une légende, un texte alternatif et une taille, et tiennent dans la largeur que l’article leur donne.
  • Les pages des conditions et de la sécurité sont traduites dans les six autres langues, et chaque page statique déclare maintenant la langue dans laquelle elle est réellement écrite.
  • L’API délivre une session par jeton plutôt qu’une par renouvellement, et la page des jetons se lit comme une ligne par jeton.