<?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: Jonna Fassbender</title>
    <description>The latest articles on DEV Community by Jonna Fassbender (@fassbender_fiesole2026).</description>
    <link>https://dev.to/fassbender_fiesole2026</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3833990%2Ff8c5369b-78c0-4bf4-8e24-dd904023294a.png</url>
      <title>DEV Community: Jonna Fassbender</title>
      <link>https://dev.to/fassbender_fiesole2026</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/fassbender_fiesole2026"/>
    <language>en</language>
    <item>
      <title>Your package was compromised. How do you prove which version you actually shipped?</title>
      <dc:creator>Jonna Fassbender</dc:creator>
      <pubDate>Mon, 30 Mar 2026 17:27:18 +0000</pubDate>
      <link>https://dev.to/fassbender_fiesole2026/your-package-was-compromised-how-do-you-prove-which-version-you-actually-shipped-5e9m</link>
      <guid>https://dev.to/fassbender_fiesole2026/your-package-was-compromised-how-do-you-prove-which-version-you-actually-shipped-5e9m</guid>
      <description>&lt;p&gt;Last week, LiteLLM got owned.&lt;/p&gt;

&lt;p&gt;Not the company. Not the code. The publishing pipeline. An attacker compromised a vulnerability scanner in their CI/CD, used it to grab PyPI credentials, and pushed a malicious version that stole API keys from every engineer who installed it.&lt;/p&gt;

&lt;p&gt;Three days later, the same thing happened to Telnyx.&lt;/p&gt;

&lt;p&gt;If you work with AI, you probably had LiteLLM installed. The question isn't whether you were affected. It's whether you can prove what version you were running, and when.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real problem isn't the hack
&lt;/h2&gt;

&lt;p&gt;Supply chain attacks aren't new. SolarWinds. Codecov. ua-parser-js. The pattern is always the same: something between the developer and the user gets compromised.&lt;/p&gt;

&lt;p&gt;What's new is the response problem.&lt;/p&gt;

&lt;p&gt;When LiteLLM was hit, every team using it had to answer: "Were we running the compromised version?" Most couldn't answer with certainty because their evidence came from the same systems that were potentially compromised.&lt;/p&gt;

&lt;p&gt;Your CI logs live in your CI. Your registry timestamps come from the registry. Your internal records are... internal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The system under dispute is also the source of the evidence. That's not proof.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What anchoring does
&lt;/h2&gt;

&lt;p&gt;Anchoring creates a cryptographic fingerprint of your artifact (a package, a model, a build) and timestamps it in Bitcoin's blockchain. Not a copy. Not a backup. A mathematical proof that this exact artifact existed at this exact moment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;artifact.whl (your package)
    ↓ SHA-256
a]7c3f9e...  (fingerprint)
    ↓ anchor
origin_id: ec8eead9-...  (your proof)
    ↓ Bitcoin
anchored to Bitcoin block 889,412
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The proof is independently verifiable. It doesn't depend on Umarise, your CI provider, or any single system staying honest.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this looks like in practice
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;In your build pipeline (one line):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;umarise anchor dist/package-1.2.0.whl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;In GitHub Actions:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Anchor build artifact&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AnchoringTrust/anchor-action@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dist/package-1.2.0.whl&lt;/span&gt;
  &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;UMARISE_API_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;% raw %&lt;/span&gt;&lt;span class="pi"&gt;}&lt;/span&gt;&lt;span class="s"&gt;${{ secrets.UMARISE_API_KEY }}{% endraw %}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;In Python:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;umarise&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;UmariseCore&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dist/package-1.2.0.whl&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;sha256&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sha256:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;UmariseCore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;um_...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hash_value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;origin_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. One call. The proof is permanent.&lt;/p&gt;

&lt;h2&gt;
  
  
  The timeline that matters
&lt;/h2&gt;

&lt;p&gt;Here's what the LiteLLM attack looked like, and what changes with anchoring:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Day 0: Team publishes litellm 1.52.6 (legitimate)
       → Without anchoring: version exists on PyPI
       → With anchoring: SHA-256 fingerprint anchored to Bitcoin

Day 3: Attacker compromises CI vulnerability scanner
       → Attacker extracts PyPI credentials
       → Pushes malicious litellm 1.52.7

Day 4: Malicious version steals API keys from .env files
       → 1000+ engineers affected

Day 6: Attack discovered, version yanked

Day 7: Incident response begins
       → Without anchoring: "Our logs say we had 1.52.6"
       → With anchoring: "Here's the Bitcoin-anchored proof
         of exactly what we were running"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The difference: one is a claim. The other is a proof that anyone can verify, independently, forever.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters for your team
&lt;/h2&gt;

&lt;p&gt;You don't need to have been hit by LiteLLM to care about this. Ask yourself:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can you prove which version of a dependency you deployed last Tuesday?&lt;/li&gt;
&lt;li&gt;If your CI provider gets compromised, does your evidence survive?&lt;/li&gt;
&lt;li&gt;If a regulator asks for proof of what was in production, can you provide something that doesn't come from your own systems?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most teams answer no to all three.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern is bigger than packages
&lt;/h2&gt;

&lt;p&gt;The LiteLLM attack targeted a Python package. But the same vulnerability exists everywhere artifacts move through a pipeline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ML models&lt;/strong&gt;: Can you prove which weights were in production when a decision was made?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Training data&lt;/strong&gt;: Can you prove the dataset wasn't modified after the audit?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docker images&lt;/strong&gt;: Can you prove the image that ran in prod matches what was built?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Firmware&lt;/strong&gt;: Can you prove the binary that shipped is the binary that was signed?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every artifact that moves through a pipeline without independent proof is vulnerable to the same class of attack.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;umarise
umarise anchor &amp;lt;your-artifact&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The LiteLLM attack wasn't exotic. It exploited a dependency in a CI pipeline. This will happen again.&lt;/p&gt;

&lt;p&gt;The real question is whether you'll be able to prove what you shipped, what you installed, and when.&lt;/p&gt;

&lt;p&gt;Right now, for most teams, the answer is no.&lt;/p&gt;

&lt;p&gt;Adding temporal proof to your publish pipeline takes one line.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before anchoring:&lt;/strong&gt; "Our logs say so."&lt;br&gt;
&lt;strong&gt;After anchoring:&lt;/strong&gt; "Verify it yourself."&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://umarise.com/sdk-download" rel="noopener noreferrer"&gt;Umarise CLI &amp;amp; SDK&lt;/a&gt; — anchor from terminal or CI&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/marketplace/actions/anchor-action" rel="noopener noreferrer"&gt;GitHub Action&lt;/a&gt; — one-step anchoring in any workflow&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://anchoring-spec.org" rel="noopener noreferrer"&gt;Anchoring Specification&lt;/a&gt; — open standard, 18 sections&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://umarise.com/case/supply-chain" rel="noopener noreferrer"&gt;Case study: Supply chain integrity&lt;/a&gt; — the full LiteLLM timeline&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/Jonna1976/anchoring-examples" rel="noopener noreferrer"&gt;Integration examples&lt;/a&gt; — MLflow, W&amp;amp;B, HuggingFace, S3&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>security</category>
      <category>supplychain</category>
      <category>python</category>
      <category>devops</category>
    </item>
    <item>
      <title>Git history is editable. Here's how to make it externally verifiable.</title>
      <dc:creator>Jonna Fassbender</dc:creator>
      <pubDate>Tue, 24 Mar 2026 10:19:37 +0000</pubDate>
      <link>https://dev.to/fassbender_fiesole2026/we-anchor-every-ai-generated-commit-to-bitcoin-here-is-why-38ee</link>
      <guid>https://dev.to/fassbender_fiesole2026/we-anchor-every-ai-generated-commit-to-bitcoin-here-is-why-38ee</guid>
      <description>&lt;p&gt;Your git log says the fix shipped before the incident.&lt;br&gt;
Your CI says the pipeline passed at 14:32.&lt;br&gt;
Your release tag says v2.1.3.&lt;/p&gt;

&lt;p&gt;Now prove that to someone who does not trust your systems.&lt;/p&gt;

&lt;p&gt;That’s the real problem.&lt;/p&gt;

&lt;p&gt;Most engineering evidence is self-issued: git history, CI timestamps, artifact metadata, cloud logs. Useful for operations, weak for disputes. In an audit, incident review, or IP conflict, the investigated system is often also the system providing the timeline.&lt;/p&gt;

&lt;p&gt;That is circular.&lt;/p&gt;

&lt;p&gt;The practical fix (takes minutes)&lt;br&gt;
Add one independent layer: hash critical outputs and anchor the hash to a public timeline.&lt;/p&gt;

&lt;p&gt;Three commands to see the model:&lt;/p&gt;

&lt;p&gt;Create your artifact (example): tar -czf build.tar.gz .&lt;br&gt;
Anchor it: npx @umarise/cli anchor build.tar.gz&lt;br&gt;
Verify it later: npx @umarise/cli verify build.tar.gz.proof&lt;br&gt;
Expected verification result looks like this:&lt;/p&gt;

&lt;p&gt;✓ Hash Match | Bitcoin Block #941168 | VALID&lt;/p&gt;

&lt;p&gt;No source code upload is required for verification. The proof is tied to the hash, and the hash is tied to a public blockchain timestamp.&lt;/p&gt;

&lt;p&gt;Why this matters more now&lt;br&gt;
AI and automation increase throughput dramatically. One person can now ship what used to require a team. Great for speed, harder for forensics.&lt;/p&gt;

&lt;p&gt;When output volume jumps, questions arrive faster too:&lt;/p&gt;

&lt;p&gt;Did this exact build exist before the deadline?&lt;br&gt;
Was this fix deployed before or after the outage?&lt;br&gt;
Did this model checkpoint exist before the external claim?&lt;br&gt;
If your answer depends only on internal logs, you have a trust gap.&lt;/p&gt;

&lt;p&gt;Minimal GitHub setup&lt;br&gt;
If you use GitHub, you can automate anchoring on every push via a small workflow using AnchoringTrust/anchor-action@v1 with UMARISE_API_KEY in secrets.&lt;br&gt;
Result: each push produces a proof artifact that can be independently checked later.&lt;/p&gt;

&lt;p&gt;(If you prefer CLI-first, you can run the same flow in any CI provider.)&lt;/p&gt;

&lt;p&gt;What you get from a proof&lt;br&gt;
A proof gives one precise statement:&lt;/p&gt;

&lt;p&gt;These exact bytes existed no later than time T, verifiable by third parties.&lt;/p&gt;

&lt;p&gt;Not “our system says so.”&lt;br&gt;
“Check it yourself.”&lt;/p&gt;

&lt;p&gt;That shift matters in:&lt;/p&gt;

&lt;p&gt;Compliance and regulated delivery&lt;br&gt;
Security incidents and postmortems&lt;br&gt;
IP provenance and publication priority&lt;br&gt;
AI/ML lineage (weights, datasets, evaluation bundles)&lt;br&gt;
“Can’t we just use OpenTimestamps directly?”&lt;br&gt;
Yes, and that’s a valid path.&lt;br&gt;
The tradeoff is operational: proof lifecycle handling, upgrades, retries, and workflow integration across repos.&lt;/p&gt;

&lt;p&gt;Same cryptographic foundation. Different operational burden.&lt;/p&gt;

&lt;p&gt;Quick self-check for your team&lt;br&gt;
Ask these three questions:&lt;/p&gt;

&lt;p&gt;Can we prove when exact bytes existed, outside our own infrastructure?&lt;br&gt;
Can an external party verify that proof without our account or backend?&lt;br&gt;
If our vendor disappeared, would our old proofs still verify?&lt;br&gt;
If one answer is “no,” you have an evidence gap.&lt;/p&gt;

&lt;p&gt;Try it on one artifact this week&lt;br&gt;
Pick one output that matters (release tarball, model file, legal PDF, data snapshot) and anchor it.&lt;br&gt;
Don’t start with policy. Start with one verifiable proof.&lt;/p&gt;

&lt;p&gt;SDK quickstart: &lt;a href="https://umarise.com/developers" rel="noopener noreferrer"&gt;https://umarise.com/developers&lt;/a&gt;&lt;br&gt;
Independent verifier: &lt;a href="https://verify-anchoring.org" rel="noopener noreferrer"&gt;https://verify-anchoring.org&lt;/a&gt;&lt;br&gt;
Methodology: &lt;a href="https://github.com/Jonna1976/AnchorStack" rel="noopener noreferrer"&gt;https://github.com/Jonna1976/AnchorStack&lt;/a&gt;&lt;br&gt;
Live case study: &lt;a href="https://umarise.com/case/ai-code-generation" rel="noopener noreferrer"&gt;https://umarise.com/case/ai-code-generation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If this category is new to your team, that’s normal.&lt;br&gt;
The goal isn’t complexity. The goal is to stop relying on evidence that only you can issue.     &lt;/p&gt;

</description>
      <category>ai</category>
      <category>github</category>
      <category>bitcoin</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
