Aller au contenu

Architecture

Obsign, ce sont quatre rôles séparés autour d’un format d’enregistrement gelé. Chacune de ces séparations ferme une attaque précise.

agent ──MCP──▶ obsign-proxy ──MCP──▶ tool server
│ writes, fsync-before-forward
WAL ◀──read-only── obsign-ledger ──▶ checkpoint store
│ │
└── evidence packs ┴─▶ obsign verify (offline)
git repo ──▶ obsign-control ──signed releases──▶ policy + identity bundles ──▶ gateway

Elle se place sur le chemin MCP : stdio pour un processus agent unique, Streamable HTTP pour une flotte. Chaque acte est arbitré : identité vérifiée à chaque appel, politique Cedar évaluée, enregistrement écrit et persisté par fsync avant que l’appel ne soit transmis. Si le processus meurt entre les deux, la trace montre un acte qui n’a pas eu lieu, ce qui est gênant mais défendable. L’inverse serait un acte sans trace, ce qui ruine le produit.

L’espace de méthodes est en refus par défaut dans les deux sens. La découverte (tools/list, resources/list, prompts/list) est filtrée pour qu’un outil invisible ne soit jamais tenté. Les canaux de lecture (resources/read, prompts/get), les complétions et les sampling/elicitation initiés par le serveur passent tous par la même barrière. Tout ce qui est inconnu (extension propriétaire, révision future du protocole) est refusé et enregistré.

La passerelle ne détient aucune clé de signature. Sa seule sortie est le WAL.

Un journal local durable, en ajout seul, rejoué et dont l’origine est vérifiée au démarrage. Un fichier <chain>.jsonl par chaîne d’audit ; avec le transport HTTP, une chaîne par session. Les enregistrements conservent args_hash ; le contenu des arguments n’y figure pas.

Tant que le scellement a lieu dans la passerelle, la clé de signature et le journal cohabitent sur un même hôte : qui la compromet peut réécrire le journal et le re-sceller. Le ledger s’exécute ailleurs, sur une autre machine ou dans une tâche cron sous une autre identité ; il lit le WAL sans jamais y écrire, et scelle avec une clé que la passerelle ne détient jamais. En production, cette clé vit dans un HSM derrière PKCS#11 et n’entre pas davantage dans le processus du ledger.

Avant de sceller quoi que ce soit de nouveau, le ledger recalcule le hachage de l’enregistrement à la frontière scellée : un WAL réécrit, même recalculé pour être cohérent en interne, est refusé avec DivergedLog, et une divergence ne se répare jamais d’elle-même, par conception. Voir Scellement & le ledger.

Tout ce à quoi la passerelle fait confiance arrive sous forme de fichier signé, et c’est d’ici que ces fichiers viennent : politiques, catalogue d’outils, bundle d’identité (JWKS compris) sont compilés depuis un checkout git en releases signées immuables nommées par le sha du commit. Un changement de règle passe par une pull request datée et revue. Voir Le plan de contrôle.

La CLI que l’auditeur exécute hors ligne, compilable depuis les sources, ~31 crates. Voir Vérifier une preuve.

obsign-audit-core est le seul endroit où un hachage est calculé. La passerelle, le ledger, le plan de contrôle et le vérificateur en dépendent tous. Deux implémentations divergeraient, et le jour où l’export dit « valide » pendant que le vérificateur dit « falsifié », le produit ne vaut plus rien.

Les autres décisions de fond, en bref :

  • Encodage canonique écrit à la main pour les hachages. Chaque champ est préfixé par sa longueur et la concaténation est injective. JSON reste le format de transport et n’entre jamais dans le calcul.
  • Des empreintes, pas des contenus. Les prompts et les arguments d’outils contiennent des données personnelles. Le journal conserve l’empreinte ; le contenu retenu est chiffré avec une clé détenue par le client (SealedRef).
  • Merkle avec promotion. La duplication (CVE-2012-2459) permet de construire deux lots différents ayant la même racine.
  • Séparation des domaines. Enregistrements, feuilles, nœuds internes et points de contrôle sont hachés avec des préfixes distincts, de sorte qu’aucun ne puisse être présenté comme un autre.