secu: ancrer le bloc d'identité (spec 3.1) et l'ouvrir aux tiers - #30
Conversation
Mesure de depart, par execution : sur une preuve 3.0, passer agent_identity_verified de False a True et agent_identity de did:web:evil.example a did:web:trust.arkforge.tech laisse verify_proof_integrity a True et le chain hash inchange au bit pres. Les trois champs etaient servis publiquement et engages nulle part. - generate_proof engage le triplet dans chain_data, toujours, une identite absente engagee a null : l engager seulement quand il est present laisserait un emetteur omettre les champs et le scorer sans engagement a refuser - normalize_identity_verified : True ou None, jamais False, normalise une seule fois a la source pour que parties et chain_data ne puissent pas diverger (meme famille que le bug Redis du binding : ecrire dans un store, lire l autre) - get_public_proof publie disclosed (nonce + valeur des trois champs) et lit les champs plats depuis la donnee engagee : editer parties ne change plus rien de ce qu un lecteur voit - verify_proof_integrity refuse une preuve 3.1 sans engagement d identite - verify_proof.py : temoin "agent identity" distinct, et une ligne explicite sur une preuve anterieure a 3.1 qui declare une identite. Trouve en jouant le tiers, pas en relisant : le script ne disait rien du tout sur une preuve 2.0 et le silence sur une affirmation non adossee se lit comme un accord 791 tests verts, 7 mutations sur 7 tuees. Deux tests passaient pour la mauvaise raison (egalite triviale, racine recalculee) et ont ete corriges apres mutation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CzXFq94XGK7vcDgoSRsPFp
Trouve en relecture : identity_consistent est un jugement SUR l identite, calcule par le proxy et servi publiquement, et il etait reste hors chain_data. Ancrer ses trois voisins en le laissant dehors reconstruisait le meme trou un champ plus a gauche, sur le champ que le rendu HTML affiche. Le calcul ne depend de rien que produise l appel amont : il remonte avant generate_proof, qui l engage et le porte dans l enregistrement, donc la vue publique et l engagement partent du meme endroit. Mesure par execution du chemin reel : preuve construite par le chemin demo, ecrite puis RECHARGEE du disque, procedure tierce jouee dessus -> 4 champs ouverts contre leurs engagements ancres. C est ce chemin que sert la prod, pas l objet en memoire sur lequel portaient les autres verifications. 795 tests verts, 3 mutations de plus tuees (dont la non-persistance des nonces, qui rendrait une preuve 3.1 sans disclosed au tiers). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CzXFq94XGK7vcDgoSRsPFp
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Scanned FilesNone |
Relecture §4.2 (Sonnet, lecture seule, perimetre = les commits de la PR) : REJETE, deux defauts eleves reels, revérifiés par execution avant traitement. - verify_proof.py plantait sur un `disclosed` non-dict (ValueError) et sur un engagement d identite non-string (AttributeError depuis strip_sha256, hors du try qui protege deja _commit). Le test generique existant mutait `seller`, qui ne traverse jamais check_identity : le chemin n etait couvert par rien. - Corrige en classe et pas en cas : strip_sha256 devient totale, `disclosed` est lu defensivement des deux sources, et tout temoin qui leve devient un FAIL. Ce script est la procedure qu un tiers execute ET un gate bloquant du deploiement ; dans les deux roles une trace est pire qu un echec. - X-Agent-Identity borne a 256 car. sans caractere de controle. Le defaut n est pas nouveau, mais 3.1 change sa nature : la valeur devient gravee et publiee. Borne choisie apres mesure de la prod (2 identites, 27 car. max), pas au jugé. Un troisieme constat du rapport (suite rouge) etait un artefact de mon propre `git checkout main` dans ~/proof-spec pendant la tentative de merge : la contrainte d ordre reste reelle, le defaut de code non. 814 tests verts, 4 mutations de plus tuees (14 au total). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CzXFq94XGK7vcDgoSRsPFp
Relecture §4.2 : REJETÉ → corrigéRelecteur Sonnet, lecture seule, périmètre = les commits de la PR. Chaque constat revérifié par exécution avant traitement. Deux défauts ÉLEVÉS réels, confirmés puis corrigés. Corrigé en classe, pas en deux cas : Un défaut FAIBLE retenu. Un constat écarté. Le rapport signalait la suite rouge sur État : 814 tests verts, 14 mutations sur 14 tuées. Les trois cas du rapport rejoués depuis la ligne de commande rendent maintenant La CI de cette PR reste rouge sur 🤖 Generated with Claude Code |
Le défaut, mesuré
Sur une preuve spec 3.0 émise par ce code :
agent_identity,agent_identity_verified,did_resolution_statusetidentity_consistentn'y sont pas. Ils sont servis dans la vue publique mais hors racine Merkle, donc horshashes.chain, horsarkforge_signature(qui ne signe que le chain hash,crypto.py:66), donc hors jeton TSA et hors entrée Rekor.Vérifié par exécution, pas déduit : passer
agent_identity_verifieddeFalseàTrueetagent_identitydedid:web:evil.exampleàdid:web:trust.arkforge.techlaisseverify_proof_integrityàTrueet le chain hash inchangé au bit près, et la vue publique sert la version falsifiée.Une note interne annonçait ce problème comme « les champs d'identité sont caviardés de la vue publique ». C'était périmé depuis
c80e811— et c'est plus dangereux ainsi : un champ absent se remarque, un champ présent mais adossé à rien se lit comme une preuve.Ce que fait ce lot
generate_proofengage les quatre champs danschain_data, toujours, une identité absente engagée ànull. Les engager seulement quand ils sont présents laisserait un émetteur les omettre et un vérificateur sans engagement à refuser.normalize_identity_verified:TrueouNone, jamaisFalse, normalisé une seule fois à la source. Sans ça,parties(qui normalisait déjàFalse→None) etchain_data(valeur brute) divergent et toute preuve à identité non vérifiée cesse de vérifier. Même famille que le bug Redis du binding : écrire dans un store, lire dans l'autre.get_public_proofpubliedisclosed(nonce + valeur des quatre champs) et lit les champs plats depuis la donnée engagée : éditerpartiesne change plus rien de ce qu'un lecteur voit.verify_proof_integrityrefuse une preuve 3.1 sans engagement d'identité.identity_consistentremonte avantgenerate_proofdansproxy.py: c'est un jugement sur l'identité, l'ancrer à côté de ses trois voisins évite de reconstruire le même trou un champ plus à gauche. Il ne dépend de rien que produise l'appel amont.verify_proof.py: témoin « agent identity » distinct, et une ligne explicite sur une preuve antérieure à 3.1 qui déclare une identité.Ce que ça établit
L'affirmation d'identité devient non répudiable : l'émetteur s'y est engagé avant l'ancrage et ne peut plus se dédire. Ça ne rend pas le binding vérifiable par un tiers, aucun artefact public ne prouve le challenge-response Ed25519. Écrit tel quel dans la spec.
Mesure
tests/test_identity_anchoring.py(19 cas).verify_proof.pyne disait rien du tout sur une preuve 2.0 déclarant une identité. Le silence sur une affirmation non adossée se lit comme un accord.https://trust.arkforge.techavant d'en dépendre :security_smoke_test.py55/55,verify_proof.pypar le réseau sur deux vraies preuves de prod (2.0 et 3.0, VERIFIED toutes les deux).Ordre de déploiement
test_spec_conformance.pylittest-vectors.jsondepuis le clone proof-spec de VPS1, qui est encore en 3.0.deploy_trust_layer_prod.shne met jamais ce clone à jour.🤖 Generated with Claude Code
https://claude.ai/code/session_01CzXFq94XGK7vcDgoSRsPFp