Aller au contenu
mcpd
aucun daemon
Source

Sécurité

Une page pour répondre à une revue de sécurité. Ce qui est conservé, ce qui est envoyé ailleurs, qui peut le lire, et les contrôles que mcpd n’a pas.

Tout ce qui suit vient du code source du daemon et de spec/mcpd.spec.json, le fichier dont l’API est générée. Là où un contrôle n’existe pas, cette page le dit, plutôt que de vous laisser découvrir le manque dans un questionnaire.

Ce que mcpd stocke

mcpd stocke le texte des conversations de votre agent. C’est sa raison d’être, et une revue doit partir de là. Un span de type model_generation porte input et output : le prompt exact envoyé au modèle et le texte exact qui est revenu. Pour la plupart des utilisateurs, ce sont les données les plus sensibles qu’ils détiennent.

Sur les six signaux, les champs libres stockés sont les suivants :

SignalCe qu’il contient
Spans input, output, attributes, metadata, et le message d’erreur
Logsle message, et les fields structurés
Métriqueslabels
Scoresreason, et metadata
Feedbackle comment en texte libre, et metadata
Notes d’aspectla phrase, dans text

À côté du payload, chaque signal porte des champs de corrélation qui relient une exécution au reste de votre système : projet, environnement, nom et version du service, session, thread, utilisateur, ressource, exécution, requête, expérience et release. user_id est haché à l’entrée, sauf si vous désactivez le masquage. Les autres sont stockés tels qu’ils ont été envoyés : traitez chacun d’eux comme un champ par lequel une personne peut être identifiée si vous y mettez une identité.

Le nombre de tokens et un montant de coût sont calculés à l’ingestion et stockés avec l’exécution. Rien de ce qui concerne l’appel au modèle n’est récupéré auprès d’un fournisseur.

Où c’est écrit

Un seul backend, choisi au démarrage. Par défaut, c’est un fichier SQLite embarqué à ./mcpd.db, défini par --data. L’index plein texte vit dans le même fichier : le stockage est donc un fichier à sauvegarder et un fichier à protéger. --postgres-url ou --clickhouse-url place plutôt les données dans une base que vous exploitez vous-même.

Les exécutions durables, quand elles sont actives, vivent dans un second fichier SQLite à côté du premier, quel que soit le stockage des traces. Ce fichier garde l’entrée, l’état, la sortie et la corrélation de chaque exécution, et l’entrée, la sortie et l’erreur de chaque étape, exactement tels qu’ils ont été envoyés : sans masquage, avec user_id non haché, parce qu’une étape ultérieure les relit et qu’une valeur réécrite changerait ce que fait l’exécution. Les spans qu’une exécution écrit dans le stockage des traces sont masqués comme tous les autres. Protégez le fichier des exécutions comme vous protégez le stockage des traces, ou davantage. La conservation et l’effacement par sujet l’atteignent tous deux, pour les exécutions terminées ; voir Exécutions.

mcpd ne chiffre pas ce qu’il écrit. Le chiffrement au repos est l’affaire du volume avec le stockage embarqué, et celle de la base avec les deux autres.

Ce qui quitte la machine

Avec une configuration par défaut, rien. Le daemon émet des requêtes sortantes à exactement quatre endroits, et chacun d’eux reste inactif tant que vous ne l’avez pas configuré. Il n’y a pas de vérification de mise à jour, pas de rapport d’usage et aucune télémétrie.

DestinationCe qui est envoyéInactif sauf si
Backend de stockage Tout ce que mcpd stocke, par votre propre connexion. Vous définissez --postgres-url ou --clickhouse-url. Le stockage par défaut est un fichier local et n’a besoin d’aucun réseau.
Transfert Chaque span stocké, réémis en OTLP JSON vers {endpoint}/v1/traces. Cela inclut le payload : input et output partent dans les attributs input.value et output.value, chacun plafonné à 32 KiB. Les valeurs déjà masquées le restent, et un user_id haché est transféré sous forme de hachage. Vous définissez --forward-endpoint, ou un bloc forward dans le fichier de configuration.
Webhooks de moniteur et d’objectif Un corps JSON fixe : l’identifiant et le nom du moniteur ou de l’objectif, le projet, la condition, ce qu’elle surveille, le seuil, la mesure, le nombre d’échantillons, un détail d’une ligne et un horodatage. Aucun texte de prompt ou de complétion ne s’y trouve. Un moniteur ou un objectif du fichier de configuration nomme un webhook.
Modèle juge Le critère, plus la question de l’exécution et sa réponse, chacune tronquée à 4 000 caractères, envoyés en POST vers {endpoint}/chat/completions. C’est le seul chemin qui envoie du texte de prompt et de complétion stocké à un fournisseur de modèles. Un bloc judge du fichier de configuration nomme un endpoint, un modèle et au moins un critère, et un appelant demande ce scorer par son nom.

L’interface que le binaire sert à / est compilée dans le binaire. Ses polices, ses styles et ses scripts viennent de la même origine, et elle n’émet aucune requête vers un tiers.

Clés et portées

L’authentification se fait par clés d’API. Il n’y a pas de compte, pas de session et pas de connexion. Une clé est présentée sous la forme Authorization: Bearer … ou dans l’en-tête X-Mcpd-Key, et les deux valeurs sont hachées en SHA-256 avant d’être comparées : ni la longueur de la clé configurée ni la position du premier caractère erroné ne sont observables par le temps de réponse.

Il y a six portées, de la plus étroite à la plus large :

PortéeCe qu’elle permetPourquoi elle est une portée à part
ingest Écrire des signaux. Ne rien lire. Cette clé vit dans chaque conteneur qui émet un span, y compris ceux qui exécutent du code tiers. Elle ne doit pas ouvrir en plus tous les prompts stockés.
read Lire les signaux stockés. C’est en lecture que se trouvent les prompts.
write Modifier les données stockées : scores, retours, datasets, expériences et acquittements. Plusieurs de ces handlers lisent un payload au passage. Capturer une exécution de production dans un dataset lit l’input et l’output de cette exécution, ce qui est plus qu’émettre un span.
execute Démarrer, annuler et signaler des exécutions, et leur publier des événements. Démarrer une exécution dépense de l’argent et agit sur vos systèmes. Une clé qui peut enregistrer un score ne doit pas pouvoir aussi lancer du travail.
worker Réclamer des étapes et rendre compte de ce qui leur est arrivé. C’est la clé du processus qui exécute le corps des étapes. Une réclamation renvoie l’input de l’étape et l’output de chaque étape précédente, donc elle lit des payloads, et elle ne doit pas pouvoir aussi démarrer des exécutions ni modifier des données stockées.
admin Détruire des données, et lire le journal d’audit. Supprimer n’est pas lire.

La portée exigée par un endpoint est déclarée endpoint par endpoint dans la spécification et publiée dans la référence API HTTP générée : il y a donc une seule réponse à « de quoi cet endpoint a-t-il besoin ? » plutôt qu’une réponse par document.

Le repli, et ce qu’il implique pour une clé que vous avez déjà

Une liste write non définie se replie sur la liste ingest, et une liste admin non définie se replie sur la liste read. Des listes execute et worker non définies se replient sur la liste write, donc sur la liste ingest quand celle-ci n’est pas définie non plus. C’est ce qui empêche une mise à jour de révoquer l’accès sur tous les déploiements configurés avant l’existence des portées plus étroites. Une clé admin est aussi acceptée pour execute et worker, pour qu’un opérateur puisse piloter une exécution à la main. Ensemble, ces règles veulent dire que sur un daemon qui ne détient qu’une clé d’ingestion et une clé de lecture :

  • la clé d’ingestion attache aussi des scores et des retours, démarre des exécutions et réclame des étapes, et
  • la clé de lecture supprime aussi des traces et, par le repli admin, démarre des exécutions et réclame des étapes.

Une clé de lecture est donc une clé de suppression tant que vous n’avez pas défini --admin-key, et les deux clés atteignent la surface des exécutions tant que vous n’avez pas défini --execute-key, --worker-key et --admin-key. C’est sur cette base qu’il faut distribuer les clés. Rien ne se restreint tout seul. Chaque portée a son option, --ingest-key, --read-key, --write-key, --admin-key, --execute-key et --worker-key, et chacune prend une liste ; toutes peuvent aussi être définies par projet dans le fichier de configuration.

Sans clé, toutes les portées sont ouvertes

Ce défaut est délibéré : un premier essai sur un portable ne doit pas exiger d’identifiant. C’est aussi ce qu’il y a de plus dangereux sur une instance exposée, donc quand le daemon écoute sur une adresse autre que la boucle locale, il journalise au démarrage un avertissement par surface ouverte à un appelant qui ne devrait pas l’atteindre. Chaque avertissement nomme la surface et les options qui la ferment. Lancez mcpd config avant d’exposer un port : la commande affiche la configuration à laquelle une invocation se résout, puis se termine. Elle porte une ligne par portée, et elle nomme la liste sur laquelle une portée s’est repliée : admin auth key required (read keys) vous dit que les clés de lecture peuvent encore supprimer. Docker et Kubernetes montre la sortie.

fermé avant d’être joignable

mcpd --ingest-key "$INGEST" --read-key "$READ"

Un projet est une frontière de cloisonnement

Une clé configurée au niveau supérieur donne accès à tous les projets. Une clé inscrite à la place sous un projet nommé dans le bloc projects du fichier de configuration donne accès à ce projet et à aucun autre, et chaque projet tient sa propre liste par portée. Une même clé peut apparaître sous plusieurs projets : c’est ainsi qu’une équipe plateforme détient une clé couvrant ce qu’elle exploite sans détenir le serveur entier.

Sur le chemin d’écriture, la vérification se fait par signal, pas par requête :

  • Un lot peut nommer plusieurs projets. Les signaux qui nomment un projet que la clé n’atteint pas sont écartés et signalés comme rejetés dans la réponse, avec le motif : une flotte pointée sur le mauvais projet se découvre donc dans la réponse qu’elle lit déjà. Le reste du lot est stocké.

Sur le chemin de lecture, chaque endpoint qui nomme un projet le compare aux droits de la clé : traces, spans, logs, métriques, scores, retours, analytics, datasets, expériences et journal d’audit. Les éléments d’un dataset sont adressés par l’identifiant du dataset, et le daemon cherche quel projet possède ce dataset puis y compare la clé : une clé qui ne peut pas ouvrir le projet ne peut pas lire les cas qui en ont été capturés. Un identifiant qui n’existe pas est refusé de la même façon qu’un identifiant que la clé n’a pas le droit d’atteindre, donc l’endpoint ne peut pas servir à tester quels identifiants existent. Trois endpoints ne prennent aucun projet et sont joignables par toute clé qui détient la portée de lecture, quels que soient les projets auxquels elle est limitée : la liste des projets, le catalogue des scorers et l’ensemble des capacités du stockage (GET /v1/api/capabilities). La liste des projets est filtrée par l’autorisation de la clé, donc une clé limitée à un projet ne voit que les projets qu’elle atteint. Sur une instance partagée, les noms de projet sont souvent des noms d’équipes ou de clients. L’état des moniteurs et l’état des objectifs y figuraient, jusqu’à ce qu’ils soient filtrés par l’autorisation de projet de la clé, ce qu’ils auraient toujours dû être : un moniteur nomme le projet qu’il surveille et porte le webhook vers lequel il poste. L’API ne nomme que l’hôte de ce webhook, car l’URL d’un webhook est souvent sa propre clé d’accès.

Un refus dit lequel des deux problèmes se pose

Une clé absente ou inconnue donne un 401, et une clé reconnue que cet endpoint ou ce projet n’admet pas donne un 403. La distinction est opérationnelle : un 401 vous envoie chercher une faute de frappe dans un identifiant, un 403 vous dit que l’identifiant est bon et que les droits ne le sont pas.

quatre refus
$ curl -sw "%{http_code} " "$MCPD/v1/api/traces?project=acme"401  this endpoint needs an API key; send it as `Authorization: Bearer <key>`$ curl … -H "authorization: Bearer $TYPO"401  that key is not one this daemon knows$ curl … -H "authorization: Bearer $INGEST"403  that key is not valid for reading$ curl … -H "authorization: Bearer $BILLING_READ"403  that key does not reach the project "acme"

Ce dernier message est le même que le projet existe ou non. Confirmer quels noms de projets sont réels est une petite fuite, et sur un daemon partagé ces noms sont ceux de vos clients.

Le journal d’audit

Restituer un payload stocké hors de mcpd est enregistré, et chaque suppression l’est aussi : la question « qui a lu cette exécution ? » a donc une réponse. Une ligne porte le moment, la clé, l’action, le projet, la trace ou le dataset nommé, et le fait que la requête ait été servie ou refusée. GET /v1/api/audit la relit, de la plus récente à la plus ancienne, et exige la portée admin.

Dix-huit actions sont enregistrées : get_trace, list_spans, list_logs, list_feedback, list_dataset_items, capture_into_dataset, read_trace_aspect_notes, get_theme, get_theme_flow, list_theme_history, list_theme_examples, delete_trace, erase_subject, et cinq sur les exécutions durables : list_runs, get_run, list_run_events, get_step_cause et cancel_run. La frontière est le payload, pas la requête. Lister des traces renvoie une projection de noms, de durées, de nombres de tokens et de coûts, sans aucun prompt ni aucune complétion : ce n’est donc pas enregistré. Ouvrir l’une de ces traces l’est, parce que c’est là qu’est le texte. Lister des exécutions durables est enregistré, parce que chaque exécution de la liste porte son entrée, sa sortie et son état tels qu’ils ont été envoyés. Une lecture refusée d’une exécution est enregistrée sous le projet qui possède l’exécution.

Les appels de worker ne sont pas enregistrés : réclamer une étape, déclarer qu’elle a démarré, le heartbeat, le complete et le fail. Un bail porte bien l’entrée de l’étape et les sorties des étapes qui la précèdent, mais la boucle du worker réclame de nombreuses fois par minute, et une ligne par réclamation noierait les lectures faites par une personne. Traitez la clé worker comme une clé qui lit chaque payload des exécutions qu’elle sert. Démarrer une exécution n’est pas enregistré non plus ; un nouvel envoi d’une clé d’idempotence encore retenue reçoit en réponse l’exécution que cette clé a démarrée, donc traitez la clé execute comme une clé qui peut lire les exécutions qu’elle démarre.

Cinq propriétés qu’un auditeur doit connaître :

  • La clé n’est jamais écrite. Le principal est une empreinte SHA-256 tronquée de la clé, qui distingue une clé d’une autre sans stocker de vérificateur. Une requête sans clé sur un daemon ouvert est enregistrée comme anonymous.
  • La personne vient de votre ingress, ou de nulle part. mcpd n’a pas de comptes utilisateurs : seule, une ligne attribue une action à une clé et non à un humain. Le champ subject porte une identité quand --trusted-identity-header et --trusted-proxy-cidr sont tous deux définis, et il est vide sinon, ce qui est le cas par défaut. mcpd n’établit jamais cette identité lui-même : il enregistre ce qu’une adresse que vous avez nommée a affirmé.
  • Un refus est enregistré aussi. La ligne est écrite avant que le 403 ne soit renvoyé, parce qu’une tentative refusée sur le projet d’une autre équipe est exactement la ligne pour laquelle un journal d’audit existe. Lire le journal n’est pas enregistré : il grossirait sans qu’aucun payload ne soit lu, et la ligne ne dirait rien que le détenteur de la clé d’administration ne sache déjà.
  • Il enregistre quelle clé, et en option qui. La clé est stockée sous forme d’empreinte tronquée, jamais la clé elle-même, donc le journal n’est pas un endroit où s’accumulent des identifiants. Cela répond à « une clé d’administration a supprimé ceci » et non à « quelle personne l’a fait », parce que mcpd n’a pas de fournisseur d’identité. Si vous voulez la seconde réponse, donnez-lui l’en-tête que votre ingress pose déjà et les adresses autorisées à le poser : --trusted-identity-header x-forwarded-user avec --trusted-proxy-cidr 10.42.0.0/16. Les deux moitiés sont obligatoires. Un en-tête est une chaîne que peut envoyer quiconque atteint le port, donc mcpd ne le lit que depuis une adresse que vous avez nommée ; une requête venue d’ailleurs est servie normalement et sa ligne ne porte aucun sujet, ce qui est la réponse honnête plutôt qu’une supposition. Sans aucune des deux options, aucun sujet n’est jamais enregistré, et c’est le comportement par défaut.
  • La conservation ne l’élague pas. La passe de conservation retire les spans, les logs, les métriques, les traces, les scores, les retours, les notes d’aspect et les exécutions durables terminées. Elle laisse le journal d’audit : un journal qui expirerait en même temps que les données qu’il décrit ne pourrait pas dire qui a lu les données supprimées.
  • Il ne fait jamais échouer la requête qu’il décrit. Une ligne qui ne peut pas être écrite est journalisée et comptée dans mcpd_audit_write_failures_total sur /metrics, et la lecture aboutit quand même. Un journal d’audit capable de faire tomber le chemin de lecture est un risque pire que celui qu’il évite : surveillez ce compteur plutôt que de compter sur l’échec des requêtes.

Ce qu’aucune clé ne couvre

  • L’interface. Le balisage servi à / l’est à quiconque atteint le port. Les données derrière ne le sont pas : chaque requête qu’elle émet passe par l’API de consultation et rencontre les mêmes clés. Il n’y a pas d’écran de connexion dans le daemon.
  • Les sondes et /metrics. Sans authentification, délibérément, parce qu’un kubelet et un collecteur ne peuvent pas porter de clé. Ils rapportent l’état, la version, la durée de fonctionnement, le nom du backend de stockage et des compteurs, jamais des payloads.
  • L’origine. L’API de consultation et les sondes répondent avec Access-Control-Allow-Origin: *, donc une page de navigateur, quelle que soit son origine, peut les appeler. mcpd n’utilise ni cookie ni identifiant ambiant, si bien qu’une telle page doit quand même détenir une clé : une politique d’origine permissive ne lui en fournit pas.

Ce que le masquage fait, et ce qu’il ne peut pas faire

Le masquage est actif par défaut et s’exécute avant l’écriture, en troisième étape du pipeline d’ingestion. Une valeur qu’une règle reconnaît n’atteint jamais la base de données, ce qui est une garantie différente de celle d’une valeur supprimée après coup, et c’est cette différence qu’un auditeur interroge. Ce qu’il ne peut pas faire, c’est garder les octets hors du réseau : ils l’ont déjà traversé quand la règle s’exécute. Pour cela, les SDK portent MCPD_CAPTURE_PAYLOADS=false, qui retire input et output avant qu’un span ne soit mis en file, et laisse tout le reste dessus.

Ingest pipeline
  1. Decode

    OTLP or native, gzip or plain

  2. Sample

    Decided once, at the root

  3. Redact

    Before anything is written

  4. Price

    From the configured price book

  5. Store

    One transaction per batch

Redaction is stage three, not a cleanup job. A value that a rule matches never reaches storage at all.

Une règle est un ensemble de sous-chaînes en minuscules et un mode. Une clé correspond quand son nom, mis en minuscules, contient l’une d’elles. Les valeurs reconnues sont remplacées, tronquées, hachées par une empreinte à clé, ou supprimées. Quand une clé reconnue porte un objet, tout ce qui se trouve dessous est masqué aussi, quel que soit le nom des clés filles, et un secret envoyé comme nombre JSON est traité comme un secret envoyé comme chaîne. La table complète des règles est sur Masquage et conservation.

Voici maintenant la limite, celle qu’une revue demandera et qu’aucune configuration ne supprime. Les règles reconnaissent des noms de champs, pas la forme d’une valeur. Une clé d’API qu’un utilisateur a collée au milieu d’une phrase, et qui arrive sous un champ nommé content ou question, n’est reconnue par rien dans le pipeline. Le nom du champ est innocent, donc la valeur est stockée en entier.

Le masquage arrête donc une chose précise :

  • Il arrête la capture structurée des secrets et des identifiants, là où un secret arrive dans un champ nommé d’après ce qu’il est.
  • Il n’analyse pas le texte libre, et ce n’est pas un classificateur de contenu.
  • Il ne détecte pas un secret à sa forme ni à son entropie.
  • Il ne touche ni au nom d’un span ni au message d’un enregistrement de log. Ce sont des identifiants qui servent au regroupement plutôt que des payloads, et les réécrire casserait ce regroupement. Ne construisez pas un nom de span à partir de l’adresse e-mail d’un client.
  • Il ne touche pas au stockage des exécutions. Les payloads d’une exécution durable sont ses données de travail et sont gardés tels qu’ils ont été envoyés ; seuls les spans qu’une exécution écrit dans le stockage des traces sont masqués.

Un contrôle travaille bien sur la forme, et c’est une détection après coup plutôt qu’une prévention : le scorer intégré no_leaked_secret pénalise une exécution dont la réponse porte un jeton qui ressemble à un secret, par préfixe connu ou par une longue suite alphanumérique à casse mixte. Il note une exécution déjà stockée. Il ne supprime rien, il ne lit pas le prompt, et il ne s’exécute que si vous le demandez. Voyez-le comme un moyen d’apprendre que cela s’est produit, pas comme une deuxième ligne de masquage.

Deux bornes s’appliquent qu’une règle ait reconnu quelque chose ou non. Toute chaîne de plus de 128 KiB est tronquée sur une frontière de caractère, avec un marqueur, et le parcours s’arrête à une profondeur de 32. Les deux existent pour qu’un seul document emballé ne décide pas de la taille de votre base ni ne sature un CPU.

--no-redact désactive tout le pipeline, ce qui n’a de sens que si quelque chose en amont masque déjà. Là où le masquage hache un identifiant, définissez --redact-key : la clé intégrée est une constante dans du code publié, et les identifiants viennent de petits espaces, donc une empreinte sans clé se retrouve en devinant les candidats.

Rétention et effacement

--retention-days supprime les signaux plus vieux que ce nombre de jours. Omettez l’option et rien n’est jamais supprimé, ce qui est le bon défaut : un outil qui jetterait sans bruit les traces du mois dernier se ferait découvrir au pire moment.

La passe s’exécute sur un minuteur horaire dans le daemon, jamais dans la requête d’un utilisateur. Elle couvre chaque projet nommé dans le bloc projects, chacun avec son propre retention_days lorsqu’il en définit un et avec la valeur globale sinon, plus le projet par défaut. Un projet sans fenêtre nulle part n’est pas élagué du tout.

Pour une demande concernant une personne, DELETE /v1/api/subjects/{user_id}?project=… retire toutes les exécutions enregistrées au nom de cette personne dans le projet, avec les spans, les logs, les métriques, les scores et les retours qui y sont attachés, et répond par un compte par signal. L’identifiant est haché avec la même clé avant d’être comparé, donc la demande fonctionne sur des identifiants hachés sans que vous ayez à en hacher un vous-même, et la réponse indique combien d’identifiants stockés étaient sous une forme que la demande n’a pas pu reconnaître. Pour retirer une exécution plutôt qu’une personne, DELETE /v1/api/traces/{trace_id} la retire avec tout ce qui y est attaché, en une seule transaction. Les deux exigent la portée admin et les deux sont enregistrées dans le journal d’audit. Masquage et conservation détaille la demande d’effacement et ce qu’elle peut ou ne peut pas promettre.

Ce que mcpd ne fait pas

Rien de ce qui suit n’existe dans cette version. Ces manques sont listés parce qu’une revue qui les découvre plus tard est un plus mauvais résultat qu’une revue qui les lit ici.

Absent de mcpdQue faire à la place
La terminaison TLS. mcpd parle du HTTP en clair. Les prompts et les complétions traversent ce lien en entier. Terminez le TLS sur votre ingress ou dans votre mesh, et gardez le port du daemon hors de tout réseau non fiable.
Le SSO. Aucune intégration avec un fournisseur d’identité, pas d’OIDC, pas de SAML, pas d’écran de connexion. Placez l’interface derrière le proxy d’identité que vous exploitez déjà. L’identité d’une personne est celle que ce proxy établit.
SCIM. Aucun provisionnement d’utilisateurs ni de groupes, parce que mcpd n’a pas la notion de compte utilisateur. Provisionnez au niveau du proxy. Dans mcpd, l’unité est une clé, ajoutée à une liste ou retirée d’une liste.
Des rôles au-delà des six portées. L’autorisation est une portée et une liste de projets. Il n’y a pas de rôle par endpoint et pas de groupe. Séparez par clé et par projet. Si une distinction compte, faites-en un projet.
Établir qui est une personne. Le journal d’audit peut enregistrer un sujet, mais mcpd ne détermine jamais qui appelle. Il enregistre ce que votre ingress a affirmé, et seulement depuis une adresse que vous avez nommée. Définissez --trusted-identity-header et --trusted-proxy-cidr pour que l’en-tête que votre proxy pose déjà atteigne la ligne d’audit. Sans eux, une ligne attribue une action à une clé, et émettre une clé par équipe resserre l’empreinte.
Le cycle de vie des clés. Les clés sont des listes lues au démarrage. Il n’y a pas d’expiration, pas de calendrier de rotation et pas de quota par clé. Le plafond qui existe est par projet : --quota-per-minute borne le nombre de signaux qu’un projet peut stocker en une minute, quelle que soit la clé qui les a envoyés. Configurez plusieurs clés, une par équipe ou par flotte, pour que révoquer l’une soit une modification et un redémarrage plutôt qu’un redéploiement de tout ce qui émet un span. Faites d’une flotte son propre projet si elle a besoin de son propre plafond.
Le chiffrement au repos. mcpd écrit des fichiers en clair ou des lignes en clair. Chiffrez le volume, ou utilisez une base qui le fait.

Signaler quelque chose

Les vulnérabilités présumées vont à [email protected] avec « Security » en objet, pas dans une issue publique. Le fichier SECURITY.md du dépôt indique le périmètre et ce que contient un bon rapport. Une instance sans authentification que quelqu’un a choisi d’exposer est un comportement par défaut documenté, pas une vulnérabilité. Tout ce qui passe outre une clé qui était configurée, outre une règle de masquage configurée, ou par-dessus une frontière de projet en est une.

La suite