<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>Automation | FirewallSync</title><link>https://firewallsync.com/tags/automation/</link><description>Practical cybersecurity writing for engineering teams that ship fast.</description><language>en-us</language><image><url>https://firewallsync.com/icon-512.png</url><title>FirewallSync</title><link>https://firewallsync.com/</link></image><lastBuildDate>Sun, 08 Nov 2026 04:55:00 +0000</lastBuildDate><atom:link href="https://firewallsync.com/tags/automation/index.xml" rel="self" type="application/rss+xml"/><item><title>cvelistV5 delta.json and deltaLog.json: Sync CVE Changes</title><link>https://firewallsync.com/posts/cvelistv5-delta-json-deltalog-incremental-cve-sync/</link><guid>https://firewallsync.com/posts/cvelistv5-delta-json-deltalog-incremental-cve-sync/</guid><pubDate>Sun, 08 Nov 2026 04:55:00 +0000</pubDate><dc:creator>John Marshall</dc:creator><category>Security Operations</category><description>The CVE Program's cvelistV5 repository publishes two small index files, cves/delta.json and cves/deltaLog.json, that tell you which CVE records changed and when. We read 30 days of deltaLog (2,870 entries, 122,761 changes) to show what each field means, what 'new' and 'updated' really cover, how often the feed moves, and a tested Python script that keeps a local mirror current from a checkpoint.</description><enclosure url="https://firewallsync.com/posts/cvelistv5-delta-json-deltalog-incremental-cve-sync/cover_hu13465230699450244882.jpg" type="image/jpeg" length="0"/><media:content url="https://firewallsync.com/posts/cvelistv5-delta-json-deltalog-incremental-cve-sync/cover_hu13465230699450244882.jpg" medium="image" type="image/jpeg" width="1200" height="630"/><content:encoded><![CDATA[<p><img src="https://firewallsync.com/posts/cvelistv5-delta-json-deltalog-incremental-cve-sync/cover_hu13465230699450244882.jpg" alt="Rows of colourful printed cards sorted into clear acrylic racks on wall-mounted wooden shelves" width="1200" height="630"></p><p>If you keep your own copy of the CVE List, you do not need to re-download all 421,074 record files (the count on <code>main</code> on November 8, 2026) to stay current. The CVE Program&rsquo;s <a href="https://github.com/CVEProject/cvelistV5">cvelistV5</a> repository publishes two index files, <code>cves/delta.json</code> and <code>cves/deltaLog.json</code>, that list which records were added or modified and when. Store the last <code>fetchTime</code> you processed, read every batch after it, fetch the newest version of each changed record, and fall back to a full baseline when your checkpoint ages out of the log&rsquo;s 30-day window.</p>
<p>This article explains the fields, what the two change queues really mean, and how the feed behaves in practice, based on the copy of <code>deltaLog.json</code> we downloaded on November 8, 2026 (2,870 batches, October 9 to November 8). It ends with a sync script we ran against that feed. Where a record&rsquo;s fields fit into a patch decision, see our <a href="/posts/vulnerability-prioritization-workflow-cve-record-to-patch-ticket/">vulnerability prioritization workflow from CVE record to ticket</a>.</p>
<h2 id="the-two-files-and-how-they-relate">The two files and how they relate</h2>
<table>
  <thead>
      <tr>
          <th>File</th>
          <th>What it holds</th>
          <th>Size on Nov 8, 2026</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>cves/delta.json</code></td>
          <td>The single most recent change batch</td>
          <td>684 bytes (2 changes)</td>
      </tr>
      <tr>
          <td><code>cves/deltaLog.json</code></td>
          <td>An array of change batches, newest first, for a rolling 30 days</td>
          <td>36.8 MB (2,870 batches)</td>
      </tr>
  </tbody>
</table>
<p>Both files sit on the <code>main</code> branch and are readable over <code>raw.githubusercontent.com</code> without authentication. On the morning we checked, <code>delta.json</code> was byte-for-byte the same object as the first element of <code>deltaLog.json</code>.</p>
<p>The 30-day window comes from two places. The repository README says the log &ldquo;normally retains a rolling 30 days worth of CVE record modification history&rdquo;, and notes that it was temporarily cut to 15 days during a date-normalization exercise in February 2026. The repository&rsquo;s <code>update.yml</code> workflow sets <code>CVES_DEFAULT_DELTA_LOG_HISTORY_IN_DAYS: 30</code>. The window is a setting, so code that assumes exactly 30 days will eventually be wrong. Check the timestamp of the oldest entry instead.</p>
<h2 id="fields-in-a-delta-batch">Fields in a delta batch</h2>
<p>Each batch (the whole of <code>delta.json</code>, or one element of <code>deltaLog.json</code>) looks like this. The example is the real <code>delta.json</code> from 03:15 UTC on November 8, 2026, trimmed to one item:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;fetchTime&#34;</span>: <span style="color:#e6db74">&#34;2026-11-08T03:15:41.690Z&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;numberOfChanges&#34;</span>: <span style="color:#ae81ff">2</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;new&#34;</span>: [],
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;updated&#34;</span>: [
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;cveId&#34;</span>: <span style="color:#e6db74">&#34;CVE-2024-28834&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;cveOrgLink&#34;</span>: <span style="color:#e6db74">&#34;https://www.cve.org/CVERecord?id=CVE-2024-28834&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;githubLink&#34;</span>: <span style="color:#e6db74">&#34;https://raw.githubusercontent.com/CVEProject/cvelistV5/main/cves/2024/28xxx/CVE-2024-28834.json&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;dateUpdated&#34;</span>: <span style="color:#e6db74">&#34;2026-11-08T03:06:52.175Z&#34;</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  ],
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;error&#34;</span>: []
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><table>
  <thead>
      <tr>
          <th>Field</th>
          <th>Meaning</th>
          <th>What to do with it</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>fetchTime</code></td>
          <td>When the update job pulled changes from CVE Services (UTC)</td>
          <td>Your checkpoint. Store the newest one you processed.</td>
      </tr>
      <tr>
          <td><code>numberOfChanges</code></td>
          <td>Count of items in <code>new</code> + <code>updated</code> + <code>error</code></td>
          <td>A sanity check; it matched the list lengths in all 2,870 batches we read.</td>
      </tr>
      <tr>
          <td><code>new</code></td>
          <td>Records whose JSON file was added to the repository in this batch</td>
          <td>Fetch and insert. Do not assume they are newly disclosed vulnerabilities (see below).</td>
      </tr>
      <tr>
          <td><code>updated</code></td>
          <td>Records whose existing file was modified</td>
          <td>Fetch and replace your copy.</td>
      </tr>
      <tr>
          <td><code>error</code></td>
          <td>Items the tooling could not classify</td>
          <td>Log and alert. Empty in every batch we read.</td>
      </tr>
      <tr>
          <td><code>cveId</code></td>
          <td>The CVE ID</td>
          <td>Your deduplication key.</td>
      </tr>
      <tr>
          <td><code>githubLink</code></td>
          <td>Raw URL of the record JSON on <code>main</code></td>
          <td>Fetch this to get the current record.</td>
      </tr>
      <tr>
          <td><code>cveOrgLink</code></td>
          <td>The record&rsquo;s page on cve.org</td>
          <td>For humans and tickets.</td>
      </tr>
      <tr>
          <td><code>dateUpdated</code></td>
          <td>The record&rsquo;s <code>cveMetadata.dateUpdated</code> when the batch was built</td>
          <td>Use it to keep only the newest change per CVE.</td>
      </tr>
  </tbody>
</table>
<p>Every one of the 122,761 change items we read had exactly those four keys. None of them tells you <em>what</em> changed in the record. If you need a diff, keep the previous version and compare.</p>
<h2 id="what-new-and-updated-actually-mean">What &ldquo;new&rdquo; and &ldquo;updated&rdquo; actually mean</h2>
<p>The labels come from git, not from the CVE lifecycle. The repository&rsquo;s bundled tooling (<code>.github/workflows/dist/index.js</code>) builds each delta from git status: files that were not in the repository go to the new queue, modified files go to updated. Two consequences matter for triage.</p>
<p><strong>A &ldquo;new&rdquo; record can be REJECTED.</strong> We fetched 300 randomly sampled IDs from the new queue in the most recent 600 batches. 272 are PUBLISHED records today and 28 are REJECTED. A reserved ID that is rejected before publication still lands as a new file. If you feed &ldquo;new&rdquo; straight into an alerting channel, filter on <code>cveMetadata.state</code> first. Our <a href="/posts/cve-record-states-published-rejected-and-disputed-what-each-means-for-vulnerability-triage/">guide to CVE record states</a> covers what each state means for a ticket.</p>
<p><strong>Most updates are enrichment.</strong> We fetched 300 sampled IDs from the updated queue (299 parsed) and compared each record&rsquo;s <code>cveMetadata.dateUpdated</code> with the timestamps on its containers. In 244 records the closest match (within two minutes) was the CISA-ADP container, in 51 it was the CNA container and in 4 it was the CVE Program container. In other words, the typical update is CISA adding or revising SSVC, KEV or CVSS data rather than the vendor changing the description or affected versions. That is useful: an update can be the moment a CVE gains an SSVC <code>Exploitation: active</code> value or a KEV entry. See <a href="/posts/cve-adp-containers-cisa-adp-cve-program-supplier-sadp/">how ADP containers work</a> and <a href="/posts/cisa-vulnrichment-how-to-read-ssvc-values-in-a-cve-record-and-turn-them-into-a-patch-decision/">how to read the SSVC values CISA adds</a>.</p>
<p>Both samples reflect the records as they stood when we fetched them on November 8, 2026, which can be later than the batch that listed them. Treat the proportions as indicative of one month, not a constant.</p>
<h2 id="how-the-feed-behaves">How the feed behaves</h2>
<p>We counted the whole log with Python (the counts below are recomputed from the file, not estimated).</p>
<table>
  <thead>
      <tr>
          <th>Measure (Oct 9 to Nov 8, 2026)</th>
          <th>Value</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Batches</td>
          <td>2,870</td>
      </tr>
      <tr>
          <td>Items in <code>new</code></td>
          <td>17,699</td>
      </tr>
      <tr>
          <td>Items in <code>updated</code></td>
          <td>105,062</td>
      </tr>
      <tr>
          <td>Items in <code>error</code></td>
          <td>0</td>
      </tr>
      <tr>
          <td>Distinct CVE IDs touched</td>
          <td>31,848</td>
      </tr>
      <tr>
          <td>IDs that appear in more than one batch</td>
          <td>21,774</td>
      </tr>
      <tr>
          <td>Most appearances for one ID</td>
          <td>118 (two IDs, each updated roughly every 6 hours)</td>
      </tr>
      <tr>
          <td>Median gap between batches</td>
          <td>10.2 minutes</td>
      </tr>
      <tr>
          <td>90th percentile gap</td>
          <td>28.9 minutes</td>
      </tr>
      <tr>
          <td>Longest gap</td>
          <td>228.8 minutes</td>
      </tr>
      <tr>
          <td>Largest single batch</td>
          <td>1,417 changes (Oct 13, 21:42 UTC)</td>
      </tr>
  </tbody>
</table>
<p>Three points follow for anyone building on the feed:</p>
<ol>
<li><strong>The same CVE appears many times, and each appearance is a real change.</strong> No <code>(cveId, dateUpdated)</code> pair repeated across the 30 days. If you process batches one by one you will fetch the same record repeatedly; collapse to the newest <code>dateUpdated</code> per ID first.</li>
<li><strong>Schedule is not cadence.</strong> <code>update.yml</code> is set to run every five minutes, but the gaps above show batches arriving anywhere from 2 minutes to nearly 4 hours apart. Alert on &ldquo;no new batch for several hours&rdquo;, not &ldquo;no batch in 5 minutes&rdquo;.</li>
<li><strong><code>dateUpdated</code> trails <code>fetchTime</code> slightly.</strong> Across all items the median lag from a record&rsquo;s <code>dateUpdated</code> to the batch&rsquo;s <code>fetchTime</code> was 4.8 minutes and the maximum was about 102 minutes. That is fine for vulnerability management, but it is not a real-time feed.</li>
</ol>
<h2 id="the-other-ways-to-sync">The other ways to sync</h2>
<p>The README offers two download routes, and the delta files sit alongside them.</p>
<ul>
<li><strong>git.</strong> Clone once, then <code>git pull</code>. Git works out what changed for you. A shallow, blob-less clone keeps the first download small, but a full working tree still means checking out more than 400,000 JSON files.</li>
<li><strong>Release zips.</strong> The README says a baseline named <code>YYYY-MM-DD_all_CVEs_at_midnight.zip</code> is &ldquo;issued at the end of each day at midnight&rdquo; and that hourly files named like <code>2024-04-04_delta_CVEs_at_0100Z.zip</code> &ldquo;only [contain] the deltas since the baseline zip file&rdquo;. We downloaded <code>2026-11-08_delta_CVEs_at_0300Z.zip</code> from the release tagged <code>cve_2026-11-08_0300Z</code>: it held 194 records under <code>deltaCves/</code>, and all 194 IDs appear in <code>deltaLog.json</code> batches between midnight and 03:30 UTC. Note the hourly zip is cumulative since midnight, not just the last hour. In the release assets we fetched, the baseline file actually carried a doubled extension (<code>2026-11-08_all_CVEs_at_midnight.zip.zip</code>).</li>
<li><strong>delta files over HTTPS.</strong> Best when you only want the records that changed and do not want a git clone at all, for example in a serverless function or a scanner plugin.</li>
</ul>
<h2 id="a-tested-sync-script">A tested sync script</h2>
<p>The script below keeps a local folder of records current from a checkpoint. It uses only the Python standard library.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#75715e">#!/usr/bin/env python3</span>
</span></span><span style="display:flex;"><span><span style="color:#e6db74">&#34;&#34;&#34;Incremental CVE record sync from cvelistV5&#39;s cves/deltaLog.json (Python 3.9+, stdlib only).&#34;&#34;&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> json<span style="color:#f92672">,</span> pathlib<span style="color:#f92672">,</span> sys<span style="color:#f92672">,</span> urllib.request
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> datetime <span style="color:#f92672">import</span> datetime
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>RAW <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;https://raw.githubusercontent.com/CVEProject/cvelistV5/main/cves/deltaLog.json&#34;</span>
</span></span><span style="display:flex;"><span>STATE <span style="color:#f92672">=</span> pathlib<span style="color:#f92672">.</span>Path(<span style="color:#e6db74">&#34;state.json&#34;</span>)      <span style="color:#75715e"># remembers the last fetchTime we processed</span>
</span></span><span style="display:flex;"><span>MIRROR <span style="color:#f92672">=</span> pathlib<span style="color:#f92672">.</span>Path(<span style="color:#e6db74">&#34;records&#34;</span>)        <span style="color:#75715e"># one JSON file per CVE ID</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">ts</span>(s):
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> datetime<span style="color:#f92672">.</span>fromisoformat(s<span style="color:#f92672">.</span>replace(<span style="color:#e6db74">&#34;Z&#34;</span>, <span style="color:#e6db74">&#34;+00:00&#34;</span>))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">get_json</span>(url):
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">with</span> urllib<span style="color:#f92672">.</span>request<span style="color:#f92672">.</span>urlopen(url, timeout<span style="color:#f92672">=</span><span style="color:#ae81ff">60</span>) <span style="color:#66d9ef">as</span> r:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> json<span style="color:#f92672">.</span>load(r)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">main</span>():
</span></span><span style="display:flex;"><span>    state <span style="color:#f92672">=</span> json<span style="color:#f92672">.</span>loads(STATE<span style="color:#f92672">.</span>read_text()) <span style="color:#66d9ef">if</span> STATE<span style="color:#f92672">.</span>exists() <span style="color:#66d9ef">else</span> {}
</span></span><span style="display:flex;"><span>    log <span style="color:#f92672">=</span> get_json(RAW)                              <span style="color:#75715e"># newest entry first</span>
</span></span><span style="display:flex;"><span>    oldest <span style="color:#f92672">=</span> ts(log[<span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>][<span style="color:#e6db74">&#34;fetchTime&#34;</span>])
</span></span><span style="display:flex;"><span>    since <span style="color:#f92672">=</span> ts(state[<span style="color:#e6db74">&#34;lastFetchTime&#34;</span>]) <span style="color:#66d9ef">if</span> <span style="color:#e6db74">&#34;lastFetchTime&#34;</span> <span style="color:#f92672">in</span> state <span style="color:#66d9ef">else</span> <span style="color:#66d9ef">None</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> since <span style="color:#f92672">is</span> <span style="color:#66d9ef">None</span> <span style="color:#f92672">or</span> since <span style="color:#f92672">&lt;</span> oldest:
</span></span><span style="display:flex;"><span>        sys<span style="color:#f92672">.</span>exit(<span style="color:#e6db74">&#34;No checkpoint, or checkpoint older than the log window: &#34;</span>
</span></span><span style="display:flex;"><span>                 <span style="color:#e6db74">&#34;load a midnight baseline zip first, then set lastFetchTime.&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Collect every change after the checkpoint; keep the newest dateUpdated per CVE.</span>
</span></span><span style="display:flex;"><span>    todo <span style="color:#f92672">=</span> {}
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> entry <span style="color:#f92672">in</span> log:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> ts(entry[<span style="color:#e6db74">&#34;fetchTime&#34;</span>]) <span style="color:#f92672">&lt;=</span> since:
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">break</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">for</span> queue <span style="color:#f92672">in</span> (<span style="color:#e6db74">&#34;new&#34;</span>, <span style="color:#e6db74">&#34;updated&#34;</span>):
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">for</span> item <span style="color:#f92672">in</span> entry[queue]:
</span></span><span style="display:flex;"><span>                cur <span style="color:#f92672">=</span> todo<span style="color:#f92672">.</span>get(item[<span style="color:#e6db74">&#34;cveId&#34;</span>])
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">if</span> cur <span style="color:#f92672">is</span> <span style="color:#66d9ef">None</span> <span style="color:#f92672">or</span> ts(item[<span style="color:#e6db74">&#34;dateUpdated&#34;</span>]) <span style="color:#f92672">&gt;</span> ts(cur[<span style="color:#e6db74">&#34;dateUpdated&#34;</span>]):
</span></span><span style="display:flex;"><span>                    todo[item[<span style="color:#e6db74">&#34;cveId&#34;</span>]] <span style="color:#f92672">=</span> item
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> entry[<span style="color:#e6db74">&#34;error&#34;</span>]:
</span></span><span style="display:flex;"><span>            print(<span style="color:#e6db74">&#34;delta reported errors:&#34;</span>, entry[<span style="color:#e6db74">&#34;error&#34;</span>], file<span style="color:#f92672">=</span>sys<span style="color:#f92672">.</span>stderr)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    MIRROR<span style="color:#f92672">.</span>mkdir(exist_ok<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>)
</span></span><span style="display:flex;"><span>    counts <span style="color:#f92672">=</span> {}
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> cve_id, item <span style="color:#f92672">in</span> todo<span style="color:#f92672">.</span>items():
</span></span><span style="display:flex;"><span>        record <span style="color:#f92672">=</span> get_json(item[<span style="color:#e6db74">&#34;githubLink&#34;</span>])
</span></span><span style="display:flex;"><span>        state_name <span style="color:#f92672">=</span> record[<span style="color:#e6db74">&#34;cveMetadata&#34;</span>][<span style="color:#e6db74">&#34;state&#34;</span>]          <span style="color:#75715e"># PUBLISHED or REJECTED</span>
</span></span><span style="display:flex;"><span>        counts[state_name] <span style="color:#f92672">=</span> counts<span style="color:#f92672">.</span>get(state_name, <span style="color:#ae81ff">0</span>) <span style="color:#f92672">+</span> <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>        (MIRROR <span style="color:#f92672">/</span> <span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">{</span>cve_id<span style="color:#e6db74">}</span><span style="color:#e6db74">.json&#34;</span>)<span style="color:#f92672">.</span>write_text(json<span style="color:#f92672">.</span>dumps(record))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    STATE<span style="color:#f92672">.</span>write_text(json<span style="color:#f92672">.</span>dumps({<span style="color:#e6db74">&#34;lastFetchTime&#34;</span>: log[<span style="color:#ae81ff">0</span>][<span style="color:#e6db74">&#34;fetchTime&#34;</span>]}))
</span></span><span style="display:flex;"><span>    print(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">{</span>len(todo)<span style="color:#e6db74">}</span><span style="color:#e6db74"> records refreshed since </span><span style="color:#e6db74">{</span>since<span style="color:#f92672">.</span>isoformat()<span style="color:#e6db74">}</span><span style="color:#e6db74">: </span><span style="color:#e6db74">{</span>counts<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> __name__ <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;__main__&#34;</span>:
</span></span><span style="display:flex;"><span>    main()
</span></span></code></pre></div><p><strong>How we tested it.</strong> On November 8, 2026 we ran it with Python 3 on Linux, with a checkpoint set to the sixth-newest batch (<code>2026-11-08T01:47:15.999Z</code>). It downloaded the log, refreshed 29 records (all PUBLISHED) in about five seconds, and moved the checkpoint to <code>2026-11-08T03:15:41.690Z</code>. With the state file deleted it exited with the re-baseline message, as intended.</p>
<p><strong>Limits of the script.</strong> It is a starting point, not a production sync:</p>
<ul>
<li>It downloads the whole 36.8 MB log on every run. For runs every few minutes, poll <code>delta.json</code> and only pull the log when you have missed batches.</li>
<li>It fetches records one at a time and does not retry. Add parallelism, retries and backoff before you point it at a large backlog.</li>
<li><code>githubLink</code> points at <code>main</code>, so you get the record as it is when you fetch it, which may be newer than the batch. That is usually what you want, but it means <code>dateUpdated</code> in your copy can be later than the one in the delta.</li>
<li>It does not handle the rare case of a record file being removed from the repository; the delta format has no &ldquo;deleted&rdquo; queue.</li>
</ul>
<h2 id="where-this-fits-in-a-vulnerability-workflow">Where this fits in a vulnerability workflow</h2>
<p>A local, current mirror is what makes the rest of the prioritization stack cheap: join it to the <a href="/posts/cisa-kev-json-feed-what-each-field-means-and-how-to-match-it-against-a-scanner-export/">CISA KEV JSON feed</a> and the <a href="/posts/epss-data-feed-columns-percentile-vs-probability-and-how-to-join-it-to-a-scanner-export/">EPSS daily file</a>, and re-run the decision only for the CVE IDs that the delta says changed. Because most updates are CISA-ADP enrichment, a changed record is worth re-scoring even when the vendor text did not move.</p>
<h2 id="sources-and-verification">Sources and verification</h2>
<table>
  <thead>
      <tr>
          <th>Important claim</th>
          <th>Source</th>
          <th>Status</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>deltaLog retains a rolling 30 days; baseline and hourly zip naming; hourly zip holds deltas since the baseline</td>
          <td><a href="https://github.com/CVEProject/cvelistV5/blob/main/README.md">cvelistV5 README</a></td>
          <td>Verified</td>
      </tr>
      <tr>
          <td>Update job scheduled every 5 minutes; 30-day history setting</td>
          <td><a href="https://github.com/CVEProject/cvelistV5/blob/main/.github/workflows/update.yml">cvelistV5 update.yml</a></td>
          <td>Verified</td>
      </tr>
      <tr>
          <td>new = added file, updated = modified file</td>
          <td><a href="https://github.com/CVEProject/cvelistV5/tree/main/.github/workflows/dist">cvelistV5 workflow tooling, dist/index.js</a></td>
          <td>Verified (bundled source read on Nov 8, 2026)</td>
      </tr>
      <tr>
          <td>Field names, counts, gaps, largest batch</td>
          <td><a href="https://github.com/CVEProject/cvelistV5/blob/main/cves/deltaLog.json">cves/deltaLog.json</a>, downloaded Nov 8, 2026</td>
          <td>Verified (computed)</td>
      </tr>
      <tr>
          <td>28 of 300 sampled new IDs REJECTED; 244 of 299 sampled updates match CISA-ADP</td>
          <td>Sampled record files from cvelistV5 <code>main</code>, fetched Nov 8, 2026</td>
          <td>Qualified (random sample, current record state)</td>
      </tr>
      <tr>
          <td>Hourly zip contents (194 records, all in deltaLog)</td>
          <td><a href="https://github.com/CVEProject/cvelistV5/releases">cvelistV5 releases</a>, tag cve_2026-11-08_0300Z</td>
          <td>Verified</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item></channel></rss>