<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Hassan Balbakie</title>
    <description>The latest articles on DEV Community by Hassan Balbakie (@balbaks).</description>
    <link>https://dev.to/balbaks</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4043631%2Fe3e25d44-9868-4684-b32a-df9732a620d4.png</url>
      <title>DEV Community: Hassan Balbakie</title>
      <link>https://dev.to/balbaks</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/balbaks"/>
    <language>en</language>
    <item>
      <title>I built a provenance protocol, audited my own auth, found 2 CRITICALs</title>
      <dc:creator>Hassan Balbakie</dc:creator>
      <pubDate>Thu, 23 Jul 2026 10:45:50 +0000</pubDate>
      <link>https://dev.to/balbaks/i-built-a-provenance-protocol-audited-my-own-auth-found-2-criticals-1de5</link>
      <guid>https://dev.to/balbaks/i-built-a-provenance-protocol-audited-my-own-auth-found-2-criticals-1de5</guid>
      <description>&lt;h1&gt;
  
  
  I Built a Provenance Protocol, Audited My Own Auth, and Found 2 CRITICALs
&lt;/h1&gt;

&lt;p&gt;I spent weeks building VERITAS — a protocol for verifiable provenance, identity, and attestation. 25 authenticated endpoints. Ed25519 signatures. Six layers: identity, content provenance, agent trust, economic escrow, governance.&lt;/p&gt;

&lt;p&gt;Then I audited my own auth. It was broken.&lt;/p&gt;

&lt;h2&gt;
  
  
  CRITICAL #1: Every signed request was replayable forever
&lt;/h2&gt;

&lt;p&gt;I had Ed25519 signatures on all mutating endpoints. That proves the sender controls the private key. What it doesn't prove is that the request is &lt;em&gt;fresh&lt;/em&gt;. A captured &lt;code&gt;(did, message, signature)&lt;/code&gt; tuple was valid indefinitely. Someone with access to a single logged request could replay it a thousand times.&lt;/p&gt;

&lt;p&gt;The fix: a 60-second timestamp window. Every request now includes an ISO-8601 timestamp. The server rejects anything outside the window. A signature from two minutes ago is dead. Replay protection isn't a feature you add later — it's part of the signature verification itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  CRITICAL #2: The server trusted caller-supplied messages
&lt;/h2&gt;

&lt;p&gt;This one was worse. Every endpoint accepted a &lt;code&gt;message&lt;/code&gt; field from the client, then verified the signature against it. The problem: the &lt;code&gt;message&lt;/code&gt; the client signed and the parameters the server actually acted on were two different things. A client could sign &lt;code&gt;"amount=10"&lt;/code&gt; while submitting &lt;code&gt;amount=1000&lt;/code&gt; in the body. The signature would verify — because it verified the &lt;em&gt;message&lt;/em&gt;, not the &lt;em&gt;operation&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The fix: canonical payload binding. The server now reconstructs the message from the actual request parameters it's about to act on. Keys are sorted lexicographically. Values are serialized with a deterministic float format. The signature is verified against the reconstructed message. If the body says &lt;code&gt;amount=1000&lt;/code&gt;, the reconstructed message says &lt;code&gt;amount=1000&lt;/code&gt;, and the signature must match that — not whatever the caller claims it should be. The &lt;code&gt;message&lt;/code&gt; field was deleted from every request model entirely. That field was the vulnerability.&lt;/p&gt;

&lt;p&gt;The implementation is in &lt;code&gt;identity/did.py::canonical_message()&lt;/code&gt;. The critical line is the float serialization: &lt;code&gt;f"{float(v):.8f}".rstrip("0").rstrip(".")&lt;/code&gt; — if your client uses &lt;code&gt;str(v)&lt;/code&gt; or &lt;code&gt;repr(v)&lt;/code&gt; instead, you get mystifying 403s. That one line is documented in the README because it's the thing that will bite anyone implementing a client.&lt;/p&gt;

&lt;h2&gt;
  
  
  The meta-test that prevents regression
&lt;/h2&gt;

&lt;p&gt;Fixing bugs is easy. Proving they stay fixed is harder. I wrote a route-enumerating meta-test that programmatically discovers every authenticated endpoint, sends a request with a bad signature to each one, and asserts 403. If I ever add a new endpoint and forget to wire auth, the test fails. The two CRITICALs above can't silently return because the test suite won't let them.&lt;/p&gt;

&lt;p&gt;18 tests total. 4 unit tests on the trust scoring engine. 14 integration tests covering auth rejection, replay protection, dispute resolution, and the full governance→arbiter→escrow→agent trust chain.&lt;/p&gt;

&lt;h2&gt;
  
  
  The demo that proves it works end-to-end
&lt;/h2&gt;

&lt;p&gt;A six-second asciinema recording showing all six layers firing together:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An AI agent exceeds its 100-credit delegation cap&lt;/li&gt;
&lt;li&gt;The cap is enforced at the agent endpoint (403)&lt;/li&gt;
&lt;li&gt;The violation routes through escrow&lt;/li&gt;
&lt;li&gt;A dispute is filed&lt;/li&gt;
&lt;li&gt;Carol is elected arbiter through a real governance cycle&lt;/li&gt;
&lt;li&gt;Carol resolves the dispute&lt;/li&gt;
&lt;li&gt;The agent's trust score drops 50→40 because its side lost&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every step is a real HTTP call. The demo is a shell script in the repo. Anyone can clone and run it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd do differently
&lt;/h2&gt;

&lt;p&gt;I shipped with no test suite. Don't. The first time Claude audited my code, it found 11 issues in one pass — missing auth on 20+ endpoints, private keys stored in the database, proof validation that counted instead of verifying. Every single one was fixed, and every fix got a test. But I should have written the tests first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Repo
&lt;/h2&gt;

&lt;p&gt;github.com/balbaks/veritas — please break it.&lt;/p&gt;

&lt;p&gt;The threat model and known challenges are in docs/SPEC.md. The demo runs with &lt;code&gt;bash scripts/demo.sh&lt;/code&gt;. If you find a vulnerability, open an issue. Public credit for every verified finding.&lt;/p&gt;

</description>
      <category>python</category>
      <category>security</category>
      <category>opensource</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
