Skip to content

secu: ancrer le bloc d'identité (spec 3.1) et l'ouvrir aux tiers - #30

Merged
desiorac merged 3 commits into
mainfrom
secu/identite-ancree-spec-3-1
Sep 14, 2026
Merged

desiorac merged 3 commits into
mainfrom
secu/identite-ancree-spec-3-1

Conversation

@desiorac

Copy link
Copy Markdown
Member

Le défaut, mesuré

Sur une preuve spec 3.0 émise par ce code :

champs engagés : buyer_fingerprint, request_hash, response_hash, seller, timestamp, transaction_id

agent_identity, agent_identity_verified, did_resolution_status et identity_consistent n'y sont pas. Ils sont servis dans la vue publique mais hors racine Merkle, donc hors hashes.chain, hors arkforge_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_verified de False à True et agent_identity de did:web:evil.example à did:web:trust.arkforge.tech laisse verify_proof_integrity à True et 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_proof engage les quatre champs dans chain_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 : True ou None, jamais False, normalisé une seule fois à la source. Sans ça, parties (qui normalisait déjà False → None) et chain_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_proof publie disclosed (nonce + valeur des quatre champs) et lit les champs plats depuis la donnée engagée : éditer parties ne change plus rien de ce qu'un lecteur voit.
  • verify_proof_integrity refuse une preuve 3.1 sans engagement d'identité.
  • identity_consistent remonte avant generate_proof dans proxy.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

  • 795 tests verts, dont tests/test_identity_anchoring.py (19 cas).
  • 10 mutations sur 10 tuées. Trois tests passaient pour la mauvaise raison et ont été réparés après mutation : une égalité vraie sur une preuve non falsifiée, une racine recalculée qui masquait la règle visée, et la non-persistance des nonces.
  • Deux défauts trouvés en exécutant, invisibles à la relecture :
    • verify_proof.py ne 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.
    • Le chemin de production sert une preuve rechargée du disque, pas l'objet en mémoire. Vérifié que les nonces survivent au disque, et le test le verrouille.
  • Les deux gates joués à la main contre https://trust.arkforge.tech avant d'en dépendre : security_smoke_test.py 55/55, verify_proof.py par le réseau sur deux vraies preuves de prod (2.0 et 3.0, VERIFIED toutes les deux).

Ordre de déploiement

⚠️ ark-forge/proof-spec#4 doit être mergé et tiré sur VPS1 AVANT ce déploiement. Le gate 4 fait tourner la suite trust-layer, et test_spec_conformance.py lit test-vectors.json depuis le clone proof-spec de VPS1, qui est encore en 3.0. deploy_trust_layer_prod.sh ne met jamais ce clone à jour.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CzXFq94XGK7vcDgoSRsPFp

desiorac and others added 2 commits September 13, 2026 20:06
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
@github-actions

Copy link
Copy Markdown

⚠️ Deprecation Warning: The deny-licenses option is deprecated for possible removal in the next major release. For more information, see issue 997.

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

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
@desiorac

Copy link
Copy Markdown
Member Author

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. verify_proof.py rendait une trace d'exception au lieu d'un verdict : sur un disclosed non-dict (ValueError depuis dict("garbage")) et sur un engagement d'identité non-string (AttributeError depuis strip_sha256, hors du try qui protégeait déjà _commit). Le test générique existant mutait seller, qui ne traverse jamais check_identity — le chemin n'était couvert par rien.

Corrigé en classe, pas en deux cas : strip_sha256 devient totale, disclosed est lu défensivement depuis ses deux sources, et tout témoin qui lève devient un FAIL. Ce script est à la fois la procédure qu'un tiers exécute et un gate bloquant du déploiement ; dans les deux rôles une trace est pire qu'un échec (le tiers n'obtient aucun verdict, le pipeline casse au lieu de refuser proprement).

Un défaut FAIBLE retenu. X-Agent-Identity n'était pas borné. Le problème n'est pas nouveau, mais 3.1 change sa nature : la valeur devient engagée et publiée dans disclosed, donc gravée, là où elle était jusqu'ici nettoyable côté serveur. Bornée à 256 caractères sans caractère de contrôle (400 sinon). Borne choisie après mesure de la production — 204 preuves, 2 identités distinctes, 27 caractères au plus, aucun caractère de contrôle — pas au jugé.

Un constat écarté. Le rapport signalait la suite rouge sur test_the_vector_file_covers_spec_3_1. C'était un artefact de mon propre git checkout main dans le clone proof-spec local pendant une tentative de merge. La contrainte d'ordre reste réelle (proof-spec#4 avant ce déploiement), le défaut de code non.

État : 814 tests verts, 14 mutations sur 14 tuées. Les trois cas du rapport rejoués depuis la ligne de commande rendent maintenant VERDICT: FAILED et sortent en 1, sans trace.

La CI de cette PR reste rouge sur test_the_vector_file_covers_spec_3_1 tant que proof-spec#4 n'est pas mergée : la CI ne clone pas proof-spec et lit les vecteurs de main, encore en 3.0.0. C'est le mécanisme qui fonctionne, pas un défaut à corriger ici.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CzXFq94XGK7vcDgoSRsPFp

@desiorac
desiorac merged commit 6a3de67 into main Sep 14, 2026
3 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant