<?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: Webfixerr</title>
    <description>The latest articles on DEV Community by Webfixerr (@wfx1607).</description>
    <link>https://dev.to/wfx1607</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%2F4142691%2F47c61c44-ce3e-479b-9fe2-5572c68ef225.png</url>
      <title>DEV Community: Webfixerr</title>
      <link>https://dev.to/wfx1607</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/wfx1607"/>
    <language>en</language>
    <item>
      <title>I built a tiny library that makes your audit logs tamper-evident</title>
      <dc:creator>Webfixerr</dc:creator>
      <pubDate>Fri, 25 Sep 2026 10:12:01 +0000</pubDate>
      <link>https://dev.to/wfx1607/i-built-a-tiny-library-that-makes-your-audit-logs-tamper-evident-1eeo</link>
      <guid>https://dev.to/wfx1607/i-built-a-tiny-library-that-makes-your-audit-logs-tamper-evident-1eeo</guid>
      <description>&lt;p&gt;Most apps keep an audit log — who did what, and when. An admin deleted a user, a payment went through, a permission changed. And most of those logs sit in a database table or a file that anyone with access can silently edit or delete after the fact. A rogue insider, an attacker covering their tracks, even an honest bug — nothing stops a past log line from being quietly rewritten, and nothing lets you &lt;em&gt;prove&lt;/em&gt; it wasn't.&lt;/p&gt;

&lt;p&gt;For a lot of systems that's fine. For anything where the log is evidence — compliance (SOC 2, ISO 27001, HIPAA), security investigations, billing disputes — it's a real problem. When someone later claims "that record was changed," you have no way to show it wasn't.&lt;/p&gt;

&lt;p&gt;I kept running into this, and every time the answer online was a blog post explaining how to hand-roll a hash chain. So I turned it into a small library instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  chainlog
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;chainlog&lt;/code&gt; makes an existing audit log &lt;strong&gt;tamper-evident&lt;/strong&gt;. Each entry stores a SHA-256 hash computed over its own contents plus the hash of the entry before it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;hash(entry) = SHA256( index + timestamp + data + prevHash )
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because each entry commits to the previous one, the entries form a chain. Alter any past entry, reorder them, or delete one, and every hash after it stops matching. A single &lt;code&gt;verify()&lt;/code&gt; call walks the chain and tells you exactly where it broke.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ChainLog&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;FileStore&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;chainlog&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ChainLog&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;store&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FileStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./audit.log&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;deleted_user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user_42&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;alice&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;exported_report&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// { valid: true, count: 2 }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And after someone edits a past entry in storage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// { valid: false, count: 2, brokenAt: 0, reason: "entry 0: contents were altered (hash mismatch)" }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  A library, not a database
&lt;/h2&gt;

&lt;p&gt;There are excellent heavyweight tools for verifiable data — Google's Trillian, or immudb if you want the database itself to be immutable. But they're infrastructure: you run a service or migrate your data into a new store. That's a lot of lift if all you want is for your &lt;em&gt;existing&lt;/em&gt; app's audit log to be trustworthy.&lt;/p&gt;

&lt;p&gt;chainlog is deliberately the small option. You don't run anything or migrate anything — you wrap the logging you already have. It ships with in-memory, JSONL-file, and SQLite stores, and a tiny &lt;code&gt;Store&lt;/code&gt; interface so you can back it with whatever you use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest about what it is
&lt;/h2&gt;

&lt;p&gt;chainlog is tamper-&lt;em&gt;evident&lt;/em&gt;, not tamper-&lt;em&gt;proof&lt;/em&gt;. It lets you &lt;em&gt;detect&lt;/em&gt; alteration; it doesn't physically prevent writes. An attacker who can rewrite the entire log and recompute every hash could still forge a consistent chain — so for strong guarantees you anchor the head hash somewhere outside the log (email it, commit it, or timestamp it), and &lt;code&gt;verify(expectedHead)&lt;/code&gt; catches a full rewrite. It's a single-writer chain in v0.1, and it's not trying to be a distributed transparency log. It's the 80% that most apps actually need, in a few lines.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it's going
&lt;/h2&gt;

&lt;p&gt;The core is TypeScript, with memory/file/SQLite stores today. Next: Postgres, MySQL, and MongoDB adapters, then Python and PHP ports, and an external anchoring helper. The &lt;code&gt;Store&lt;/code&gt; interface is small, so adapters are a good first contribution.&lt;/p&gt;

&lt;p&gt;It's open source (MIT), because nobody should trust a security library they can't read.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/webfixerr/chain-log" rel="noopener noreferrer"&gt;https://github.com/webfixerr/chain-log&lt;/a&gt;  ·  I'd genuinely like feedback — especially from anyone fighting audit-log integrity for compliance. What would you need it to do?&lt;/p&gt;

</description>
      <category>backend</category>
      <category>opensource</category>
      <category>security</category>
    </item>
  </channel>
</rss>
