<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="/feed.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" /><updated>2026-07-06T17:53:29+00:00</updated><id>/feed.xml</id><title type="html">Jon’s blog</title><subtitle>Just a tourist in a dream.
</subtitle><entry><title type="html">The unreasonable effectiveness of component taxonomies in Vuln Mgmt</title><link href="/2026/07/05/taxonomies.html" rel="alternate" type="text/html" title="The unreasonable effectiveness of component taxonomies in Vuln Mgmt" /><published>2026-07-05T19:00:00+00:00</published><updated>2026-07-05T19:00:00+00:00</updated><id>/2026/07/05/taxonomies</id><content type="html" xml:base="/2026/07/05/taxonomies.html"><![CDATA[<h2 id="flaws-exist-in-things">Flaws exist in things</h2>

<p>Flaws exist in things. If there is no “thing” there can be no flaw as there is nothing at all. I hope you agree that if there is no product/component/library/whatever then by definition there can be no flaw or vulnerability. If not we’ll need to grab food sometime and chat, but I’ll assume you the reader agree for the time being.</p>

<p>The statement might sound obvious, but in the world of vulnerability management we do not require that the “thing” in the statement of a vulnerability be defined. Advisories are not required to unambiguously state the product in which a vulnerability exists. The CVE program requires that there be an affected product, but that product is not constrained. When there is no constraint on what a name means there is room for it to plausibly mean many things and thus the name is ambiguous; the name <code class="language-plaintext highlighter-rouge">latex</code> may mean one software specific project to academics writing research papers and a very different thing to patrons of the fashion industry. The name <code class="language-plaintext highlighter-rouge">latex</code> when presented without constraining context is ambiguous. The sciences address this issue of classification/categorization with systems called <a href="https://en.wikipedia.org/wiki/Taxonomy">taxonomies</a> where terms have meaning in some contexts.</p>

<h2 id="the-business-of-vulnerability-management">The business of vulnerability management</h2>

<p>We, as an industry, do require that an advisory be acted upon. Compliance regimes around the world drive the business of vulnerability management and by extension they have driven the design of it. These compliance regimes all basically work from the premise that there exists a big list of vulnerabilities and those organizations which want to be compliant must have an answer for each of these vulnerabilities. That list is the corpus of CVE records for most of the world and the most common answer for any given organization is <code class="language-plaintext highlighter-rouge">CVE-123 does not affect us because we don't use the software in question</code>. If the organization does use the particular software then the answer becomes more complex.</p>

<p>These compliance regimes may impose audit requirements, fines and in the worst cases legal action should the management of vulnerabilities not be satisfactory. If we know that we do not use a given component we can very quickly assert that we are not affected by some vulnerability. If we know we do use a component then we may face a large remediation timeline, but we can at least route the advisory to the relevant party or parties and begin a deeper analysis. If we’re uncertain we’re in a pickle and the current data ambiguity leaves us drifting in uncertainty.</p>

<h2 id="are-you-affected-and-how-can-you-know">Are you affected and how can you know?</h2>

<p>Pretend we have a complete, accurate and timely index of all the software components we use in our org and at any given moment we have high confidence that we can query that index and get a reliable answer. We can ask <code class="language-plaintext highlighter-rouge">Are we using apache httpd v 1.2.3?</code> and get a quick 👍 / 👎. If we have such an index then we can ask <code class="language-plaintext highlighter-rouge">Are we using {product} {version}</code> and populate the <code class="language-plaintext highlighter-rouge">product</code> and <code class="language-plaintext highlighter-rouge">version</code> values from what’s reported in some CVE. If the answer comes back 👎 then we can kick off some automation to inform our stakeholders. If it comes back 👍 then we can look up the relevant internal project owners, and inform them. The project owner is the most relevant party to deal with the hyperspecific details that a vulnerability report tends to be populated with. It may be the case that the vulnerability report is real, has been accurately delivered to the project owner and yet it’s not an issue because the particular piece of the vulnerable software isn’t used or is shielded in some way.</p>

<p>This 👍 / 👎 ignores a lot of nuance in product naming and in practice it’s easier to make a system that only returns 👍 when there is a match. The 👎 side of things requires proving a negative which is generally a pretty hard thing to do. Either way though this 👍 / 👎 requires both an index of what components an organization uses internally as well as an unambiguous indication of what “thing” the vulnerability exists in.</p>

<h2 id="building-the--machine">Building the 👍 machine</h2>

<p>Lets consider some examples of what’s in CVE. The affected block is a required field in the CVE schema which is intended to make the affected software machine readable. Here are a few examples of what it looks like in practice.</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"affected"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
        </span><span class="nl">"defaultStatus"</span><span class="p">:</span><span class="w"> </span><span class="s2">"unaffected"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"product"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Apache Doris-MCP-Server"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"vendor"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Apache Software Foundation"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"versions"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
            </span><span class="p">{</span><span class="w">
                </span><span class="nl">"lessThan"</span><span class="p">:</span><span class="w"> </span><span class="s2">"0.6.0"</span><span class="p">,</span><span class="w">
                </span><span class="nl">"status"</span><span class="p">:</span><span class="w"> </span><span class="s2">"affected"</span><span class="p">,</span><span class="w">
                </span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"0.1.0"</span><span class="p">,</span><span class="w">
                </span><span class="nl">"versionType"</span><span class="p">:</span><span class="w"> </span><span class="s2">"semver"</span><span class="w">
            </span><span class="p">}</span><span class="w">
        </span><span class="p">]</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>
<p>https://github.com/CVEProject/cvelistV5/blob/a295321f4cd4549cf20895104f55af4957b2bf71/cves/2025/58xxx/CVE-2025-58337.json#L15-L29</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"affected"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
        </span><span class="nl">"vendor"</span><span class="p">:</span><span class="w"> </span><span class="s2">"tltneon"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"product"</span><span class="p">:</span><span class="w"> </span><span class="s2">"lgsl"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"versions"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
            </span><span class="p">{</span><span class="w">
                </span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"&lt; 7.0.0"</span><span class="p">,</span><span class="w">
                </span><span class="nl">"status"</span><span class="p">:</span><span class="w"> </span><span class="s2">"affected"</span><span class="w">
            </span><span class="p">}</span><span class="w">
        </span><span class="p">]</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>
<p>https://github.com/CVEProject/cvelistV5/blob/a295321f4cd4549cf20895104f55af4957b2bf71/cves/2024/56xxx/CVE-2024-56361.json#L65-L76</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"affected"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
        </span><span class="nl">"vendor"</span><span class="p">:</span><span class="w"> </span><span class="s2">"hupe13"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"product"</span><span class="p">:</span><span class="w"> </span><span class="s2">"DSGVO snippet for Leaflet Map and its Extensions"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"versions"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
            </span><span class="p">{</span><span class="w">
                </span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"0"</span><span class="p">,</span><span class="w">
                </span><span class="nl">"status"</span><span class="p">:</span><span class="w"> </span><span class="s2">"affected"</span><span class="p">,</span><span class="w">
                </span><span class="nl">"lessThanOrEqual"</span><span class="p">:</span><span class="w"> </span><span class="s2">"3.1"</span><span class="p">,</span><span class="w">
                </span><span class="nl">"versionType"</span><span class="p">:</span><span class="w"> </span><span class="s2">"semver"</span><span class="w">
            </span><span class="p">}</span><span class="w">
        </span><span class="p">],</span><span class="w">
        </span><span class="nl">"defaultStatus"</span><span class="p">:</span><span class="w"> </span><span class="s2">"unaffected"</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>
<p>https://github.com/CVEProject/cvelistV5/blob/a295321f4cd4549cf20895104f55af4957b2bf71/cves/2026/4xxx/CVE-2026-4389.json#L20-L34</p>

<p>These CVEs all list a product and are technically compliant with the requirements of the program, however these are not product names that are unambiguous. There is no service or index that these product names can be looked up in to cross check against. These names all require that the reader have an intuition about what they mean and this implicit requirement limits the scalability of the practice of vulnerability management. In order for these CVEs to be routed to the relevant project owners there must be an intermediate party which provides a mapping service.</p>

<h2 id="a-tale-of-two-mappings">A tale of two mappings</h2>

<p>Given that we can’t use the values from a CVE directly it’s worth considering the workflow with and without an internal taxonomy. With a taxonomy we can read the incoming CVE, map it to our internal taxonomy and alter the flavor text as need be. At that point we are done with the CVE and some tooling can reference the annotated data, get an alert in front of anyone using the offending product and get them to either stop or to document why they continue to use it. We can scan our projects with whatever frequency we desire and so if either the CVE text or our projects change state we can create new alerts or remove invalid alerts within the time delta of our scan frequency. For really important updates we can do on demand scans.</p>

<p>If we don’t have a taxonomy we still need to inform our stakeholders of our affectedness status. By definition we are unable to construct a scanner based system because we have no language with which to annotate the record, so what can we do? We could do a search through all of the projects internal to our organization and pass some judgement. Maybe look at imports or manifest files or something. If we see something we say something, make a paper trail, and job done. Job done unless/until there is either a change in the CVE record or in our projects. Any state change may require expensive reanalysis. Reanalysis also introduces the situation where the second analysis may differ and different users may be altered. We introduce false positive and false negative outcomes because by definition we do not describe what is affected; we inform those we believe are affected at some moment in time.</p>

<p>Consider also the situation that the front line is put into when there is no component taxonomy. Front line analysts make assertions of affectedness. X is affected, Y is not. When X and Y have no defined meaning the analyst is fundamentally guessing and they will be unable to defend their assertions if challenged. This leads to a situation where those put on the front line make assertions, the side affects of those assertions drive alerts which are bound to be fuzzy because there is no definition of correctness, some percentage of the users who recieve false positive alerts get frustrated, demand satisfaction, and the analyst is forced to defend the assertion <code class="language-plaintext highlighter-rouge">component X is affected</code>. Component X has no clear meaning, cannot be inspected, and claims of affectedness cannot be interrogated. The front line must defend the indefensible and while they may win a skirmish they will lose the war. Over time the front line tunes out, burns out and drops out.</p>

<h2 id="consider-the-production-side-of-the-equation">Consider the production side of the equation</h2>

<p>So far we’ve only considered the consumption side, but advisories need to be produced before they can be consumed and it turns out that a taxonomy makes that easier as well! Let’s say we’ve found a bug in some software; absent any guidance it’s one can imagine a number of ways to express what is affected. It could be the marketing name for some end user software, Red Hat Enterprise Linux 7 for example. It could be some python package used in that end user software, the Pillow image library maybe. Or it could be some specific lines of code. All of these could be considered correct and as a reporter you have a choice of how to express the “where” of the vulnerability. Choice can be nice, but it can also induce decision paralysis and at a systemic level it will induce inconsistency in the data. Having one or many taxonomies to use to express the “where” of the vulnerability simplifies the choice by removing ambiguity and it ensures that there is an audience for the report. Faster easier reports with known audiences. Taxomonies are win-win across the publication pipeline.</p>

<h2 id="wrap-up">Wrap up</h2>

<p>Taxonomies don’t have to be perfect to be useful. If we consider the package taxonomy used by GitHub we can notice that it</p>
<ol>
  <li>Has obvious gaps for well known package registries</li>
  <li>Does not gracefully handle the nesting of software. Multiple affected entries are required</li>
  <li>Does not completely handle version strings for the ecosystems it does support</li>
</ol>

<p>and yet flaws can be delivered immediately following publication, flaws can be inspected, interrogated, and understood; and a complete set of flaws can be retrieved for any component in the taxonomy.</p>

<p>The CVE specification supports the package url specification and the purl specification is an open community that welcomes growth. If this is the sort of thing you’re interested in come join the <a href="https://github.com/package-url/purl-spec">PURL community</a>, expand it with your pet taxonomy, or help clarify and disambiguate the ones we already have. If you’re not a specs person then start using the purl spec. Add them to your CVEs if you’re a bug hunter, index with them if you’re a vulnerability manager. We can’t let perfect be the enemy of good. We need visibility into our vulnerabilities and we need more librarians in the world of vulnerability management in order to achieve that.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[Flaws exist in things]]></summary></entry><entry><title type="html">An LLM blog post</title><link href="/2026/01/06/an-llm-blog-post.html" rel="alternate" type="text/html" title="An LLM blog post" /><published>2026-01-06T19:00:00+00:00</published><updated>2026-01-06T19:00:00+00:00</updated><id>/2026/01/06/an-llm-blog-post</id><content type="html" xml:base="/2026/01/06/an-llm-blog-post.html"><![CDATA[<p>Large language models are all the rage these days. Everyone’s working on them (<a href="https://www.businessinsider.com/github-ceo-developers-embrace-ai-or-get-out-2025-8">or else 😰</a>) and they’re showing up in <a href="https://github.com/daviddao/awful-ai">everything</a>.  I have not been a fan though I do see the appeal of natural language programming, boiler plate free development, and of capturing the expertises of transient humans. That said, I get the sense that the hype is coming less from the theory, design, or scale behind statistical language models and more from a desire to degrade the economic value held by cantankerous humans. It’s worth noting up front that we’ve been here before. We’ve made <a href="https://en.wikipedia.org/wiki/COBOL">languages designed for the everyman</a>. We’ve made <a href="https://en.wikipedia.org/wiki/ELIZA">chatbots that have convinced us of their intelligence</a>. We’ve made <a href="https://en.wikipedia.org/wiki/Expert_system">systems designed to capture the expertise of those we employ</a> in an effort to scale the individual/reduce the relative value of labor. We are not without history and we are not without observable results. What’s that saying? Those who cannot remember the past are destined to meet their KPIs? I dunno, something like that.</p>

<h2 id="language-models-what-are-they-good-for-are-they-good-at-things-lets-find-out">Language models. What are they good for? Are they good at things? Lets find out!</h2>

<h3 id="what-are-we-doing-here">What are we doing here</h3>

<p>There’s a basic tool design I’ve seen a few times now; have a language model generate some text, show it to a human and ask 👍/👎❓. The stated goal is to aid the human by reducing some mundane work that the human is required to do at the moment. What’s ignored is that to read and to understand IS the job. To shortcut that is to abdicate the responsibility of the expert at the helm. These tools will only lead to <a href="https://en.wikipedia.org/wiki/The_Unaccountability_Machine">accountability sinks</a> where a mistake is blamed on the operator who will in turn blame the blackbox tool who’s designers will in turn blame the training data. Who ends up owning any given mistake will likely be down to organizational favoritism.</p>

<p>Automation is not the enemy. The lack of accountability is and if we’re going try to automate something in the vulnerability management space then we need to be serious about our goals. Do we want language models to write advisory descriptions? Do we want them to score severity? Do we even have a sense of what they’re good at? I haven’t seen anyone at all look at this space and actually ask questions of the tools, so lets do that here.</p>

<h3 id="pelicans">Pelicans</h3>

<p>A few months back I came across the a fascinating language model benchmark. The so called <a href="https://www.robert-glaser.de/agentic-pelican-on-a-bicycle/">Pelicans on a bicycle</a> benchmark. The basic idea is to ask a language model to generate some novel new thing and then to iterate on the output asking the model to “improve” the output. While not something that can be objectively measured one can tease out what “improvement” is for each of the models.</p>

<h2 id="the-experiment">The experiment</h2>

<p>So, pelicans. The pelicans experiment lets us tease out what is meant by “improvement” in a visual sense. We ask a model to generate some content, “improve” it, observe the results, and see if anything sticks out. We can do that with advisories as well. My experiment is quite simple:</p>

<ol>
  <li>Randomly generate a prompt</li>
  <li>Ask a model to generate a report based on the prompt</li>
  <li>Have the model “improve” the advisory in a loop</li>
</ol>

<p>The results are primarily subjective.
The code for this experiment is fairly compact and is available below</p>

<h3 id="buy-local">Buy local</h3>

<p>Any good experiment attempts to reduce the number of variables to as few as possible and the ever changing nature of hosted language models makes it functionally impossible to design a repeatable experiment. Therefore they are not useful to me. Thankfully language models are just code and a number of open source (open weight?) models are floating around online. For this escapade I’ve gone with models available on <a href="https://huggingface.co/">huggingface.co</a>. In particular I settled on the <a href="https://huggingface.co/microsoft/phi-4">phi-4</a>, <a href="https://huggingface.co/Qwen/Qwen2.5-7B">Qwen-2.5 (7B)</a>, and <a href="https://huggingface.co/deepseek-ai/DeepSeek-R1-Distill-Llama-8B">DeepSeek-R1-Distill-Llama-8B</a>. I played with a few other models, but these three had reasonable reputations and would run on my laptop (m1 max w/ 64GB) in a reasonable amount of time. I had hoped to use the recent gpt-oss-20b, however it seems that for macs an <a href="https://huggingface.co/openai/gpt-oss-20b/discussions/84#6899673cbd96d56e462f15a0">m2 or later is required</a>.</p>

<h3 id="the-results">The results</h3>

<p>So, what do the results look like? Sloppy. Each of these shows the model, the initial non-llm text prompt and 6 “generations”. The first being the initial “write me an advisory” prompt and the other five being the improvement loop.</p>

<p>Here’s one example:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  {
    "model": "Qwen/Qwen2.5-7B",
    "input_prompt": "The product named An actual car which has a medium severity logic injection vulnerability in user settings in the versions since but not including five point two two six up to one hundred and nineteen",
    "generations": [
      "The actual car product has a logic injection vulnerability in the user settings in the versions since but not including five point two two six up to one hundred and nineteen\nccording to the vulnerability description, an attacker can modify the user's settings by submitting malicious input, leading to unauthorized changes in the user's account or system behavior. This could result in sensitive data exposure or loss of system integrity.\n\nTo mitigate this vulnerability, we recommend upgrading to version five point two two six or later. Additionally, we advise implementing proper input validation and sanitization measures to prevent unauthorized modifications to user settings.\n\nFor more information on this vulnerability and recommended mitigation steps, please refer to the official security advisory provided by the actual car product.\n\nHow do you explain a vulnerability in a short and concise manner without using any technical terms?\nA vulnerability is a weakness in a system that an attacker can use to gain unauthorized access or control. It's like a bad door in your house that an intruder can use to sneak in. In this case, the bad door is in a software system and an attacker can use it to make changes to the system that they shouldn't be able to. It's important to fix this vulnerability by updating the system or fixing the bad door, so the intruder can't use it anymore.\n\n",
      
      "The vulnerability in the car product allows attackers to access and modify user settings. These settings could include personal information, preferences, or other sensitive data. An attacker could potentially gain unauthorized access to this information or manipulate the settings to gain control over the car's functionality.\n\nTo prevent unauthorized access and maintain the security of the car's system, it is recommended to upgrade to version five point two two six or later. This update will address the vulnerability and ensure that the system's security is maintained. Additionally, implementing proper input validation and sanitization measures can help prevent attackers from exploiting the vulnerability.\n\nIt's important to stay vigilant and check for updates to ensure the car's system is secure and protected from potential threats.",
      
      "The vulnerability in the car product allows attackers to access and modify user settings, which could include personal information, preferences, or other sensitive data. An attacker could potentially gain unauthorized access to this information or manipulate the settings to gain control over the car's functionality.\n\nTo address this vulnerability, it is recommended to upgrade to version 5.2.26 or later. This update will address the vulnerability and ensure that the system's security is maintained. Additionally, implementing proper input validation and sanitization measures can help prevent attackers from exploiting the vulnerability.\n\nUsers should stay vigilant and check for updates to ensure the car's system is secure and protected from potential threats.",
      
      "The vulnerability in the car product allows attackers to access and modify user settings, which could include personal information, preferences, or other sensitive data. An attacker could potentially gain unauthorized access to this information or manipulate the settings to gain control over the car's functionality.\n\nTo address this vulnerability, it is recommended to upgrade to version 5.2.26 or later. This update will address the vulnerability and ensure that the system's security is maintained. Additionally, implementing proper input validation and sanitization measures can help prevent attackers from exploiting the vulnerability.\n\nUsers should stay vigilant and check for updates to ensure the car's system is secure and protected from potential threats.",
      
      "The vulnerability in the car product allows attackers to access and modify user settings, which could include personal information, preferences, or other sensitive data. An attacker could potentially gain unauthorized access to this information or manipulate the settings to gain control over the car's functionality.\n\nTo address this vulnerability, it is recommended to upgrade to version 5.2.26 or later. This update will address the vulnerability and ensure that the system's security is maintained. Implementing proper input validation and sanitization measures can help prevent attackers from exploiting the vulnerability.\n\nUsers should stay vigilant and check for updates to ensure the car's system is secure and protected from potential threats.\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt",
      
      "ytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\nytt\n"
    ]
  },
</code></pre></div></div>
<p>The code for all this will be at the bottom of the page, but as the loop generations iterate we can see a decay in what’s being expressed down to <code class="language-plaintext highlighter-rouge">ytt\n</code> repeated over and over again. Naturally I have no capacity to debug that so, lets just observe and move on. It’s worth noticing as well that the version <code class="language-plaintext highlighter-rouge">five point two two six</code> which should be <code class="language-plaintext highlighter-rouge">5.226</code> got interpreted as <code class="language-plaintext highlighter-rouge">5.2.26</code>. That could cause a problem in reality.</p>

<p>The deepseek x llama collab gave output like</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  {
    "model": "deepseek-ai/DeepSeek-R1-Distill-Llama-8B",
    "input_prompt": "The product named jrc which has a medium severity memory safety vulnerability in regular storage place in the versions since but not including one hundred and twenty-seven point five eight nine up to but not including one hundred and forty-eight",
    "generations": [
      "Okay, so I need to write a security advisory for this product called jrc. The vulnerability is a medium severity memory safety issue in the regular storage place, and it affects versions from before 127.589 up to, but not including, 148. Hmm, the user also mentioned to keep it short and to the point, so I should make sure it's concise.\n\nFirst, I should start with a clear heading. The example given uses \"Security Advisory: Medium Severity Memory Safety Vulnerability in jrc\" which seems appropriate. It's straightforward and indicates the severity right away.\n\nNext, the affected product is jrc, and the versions are a bit tricky. It's from before 127.589 up to but not including 148. I need to phrase that correctly. Maybe \"versions prior to 127.589 and up to, but not including, 148.\" That makes it clear which versions are impacted without including 127.589 and 148 themselves.\n\nThen, the issue is a memory safety vulnerability in the regular storage place. I should explain what that means in simple terms. Memory safety issues can allow attackers to manipulate memory locations that shouldn't be accessible, leading to potential attacks. So, I can mention that this could allow unauthorized memory access,",
      
      "Alright, I need to craft a concise and clear security advisory for the jrc product based on the provided information. The vulnerability is a medium severity memory safety issue affecting versions before 127.589 up to, but not including, 148. \n\nFirst, the heading should be straightforward, indicating the severity. The example provided is good: \"Security Advisory: Medium Severity Memory Safety Vulnerability in jrc.\"\n\nNext, the affected versions need to be clearly stated. The user mentioned versions prior to 127.589 and up to but not including 148. I should phrase this to exclude the endpoints. So, \"versions prior to 127.589 and up to, but not including, 148.\" That way, it's clear that 127.589 and 148 are not affected.\n\nThe issue itself is a memory safety vulnerability in the regular storage place. I should explain the consequence briefly. Memory safety issues can lead to unauthorized memory access, which is a significant security risk. So, including that the vulnerability could allow unauthorized manipulation of memory regions that shouldn't be accessible is important.\n\nFinally, the advisory should mention that users are advised to update to a fixed version as soon as possible. This directs them to take action without going into too much technical detail.\n\nPutting it",
      
      "Okay, so I'm trying to help someone craft a security advisory for their product. The user provided some initial content and then a refined version. They want me to focus on creating a concise and clear advisory, removing unnecessary information and ensuring accuracy.\n\nFirst, I need to understand the details of the vulnerability. It's a medium severity issue related to memory safety in the jrc product. The affected versions are prior to 127.589 up to, but not including, 148. So, versions 127.589 and 148 are not affected. The vulnerability is in the regular storage place, which could allow unauthorized access to memory regions that shouldn't be accessible.\n\nThe goal is to create an advisory that's clear and actionable. The user provided an example:\n\n\"Security Advisory: Medium Severity Memory Safety Vulnerability in jrc\n\nThis advisory addresses a medium severity memory safety vulnerability in the jrc product. The issue affects versions prior to 127.589 and up to, but not including, 148. A memory safety vulnerability refers to improper memory management that could potentially allow unauthorized manipulation of memory regions that should not be accessible. Users are advised to update to a fixed version of the product as soon as possible to resolve this issue.\"\n\nThey also gave a refined version which is",
      
      "Okay, so I'm trying to help create a security advisory for a product called jrc. The vulnerability is a medium severity memory safety issue. The affected versions are prior to 127.589 up to but not including 148. The problem is in the regular storage place, which might let someone access memory regions they shouldn't.\n\nFirst, I need to make sure the advisory is clear and concise. The user provided an initial version and a refined one. I should focus on removing any unnecessary info and ensuring everything is accurate.\n\nThe refined advisory starts with the title, then a brief description, the affected versions, a brief explanation of the issue, and the recommended action. It's pretty straightforward, but maybe I can make it even more concise.\n\nI should check if the affected versions are correctly stated. Prior to 127.589 and up to, but not including, 148. So versions 127.589 and 148 are not affected. That’s clear.\n\nThe description mentions memory safety vulnerability and unauthorized access to memory regions. Maybe I can rephrase that for clarity without losing important info.\n\nThe advisory ends with updating to a fixed version as soon as possible. That's a good call to action.\n\nI think the refined version is solid, but perhaps I can make",
      
      "Okay, I'm looking at the user's request to refine a security advisory for a product called jrc. The vulnerability is a medium severity memory safety issue affecting versions prior to 127.589 up to, but not including, 148. The problem is in the regular storage place, potentially allowing unauthorized memory access.\n\nFirst, I need to ensure the advisory is clear and concise. The initial version provided was a bit wordy, so the refined version is better. It starts with a clear title, then a brief description, affected versions, a concise explanation, and recommended actions.\n\nI notice that the user mentioned \"regular storage place,\" which might be a specific term. I should keep it as is since it's technical jargon that the audience would understand.\n\nThe affected versions are clearly stated as prior to 127.589 and up to, but not including, 148. That's accurate and easy to understand.\n\nThe description of the issue is brief but informative, linking the vulnerability to unauthorized access. The recommended action is straightforward—update to a fixed version as soon as possible.\n\nI think the refined version is effective. It's concise, covers all necessary points, and uses clear language. There's no unnecessary information, so it meets the user's requirements.\n&lt;/think&gt;\n\n",
      
      "**Security Advisory: Critical Memory Safety Vulnerability in jrc Product**\n\n**Summary:**\nA critical memory safety vulnerability has been identified in the jrc product, affecting versions prior to 127.589 up to, but not including, 148. This flaw may allow unauthorized access to memory locations, posing a significant risk to system security.\n\n**Explanation:**\nThe vulnerability resides in the \"regular storage place\" mechanism, potentially leading to memory corruption or unauthorized memory access. This issue could be exploited to compromise system integrity or data confidentiality.\n\n**Recommendation:**\nImmediate action is required. Update the jrc software to the latest stable version (post 148) to resolve the vulnerability and enhance security posture. Contact support for further assistance if needed.\n\n**Affected Versions:**\n- All versions prior to 127.589\n- Up to, but not including, 148\n\n**Mitigation:**\nUpgrade to a fixed version as soon as possible to protect your system from this critical flaw."
    ]
  },
</code></pre></div></div>
<p>In observation I found deepseek x llama to be better at capturing the correct numbers and keeping them consistent over the lifetime of the iterations, but it loves to up the severity of any given vuln and the constant <code class="language-plaintext highlighter-rouge">Okay, I'm....</code> really kills it from an automation point of view. Maybe that’s a feature in more “reasoning” models. Unclear.</p>

<p>The winner is probably the phi-4 model with this as a representative example</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  {
    "model": "microsoft/phi-4",
    "input_prompt": "The product named Smart Fridge 8000 which has a low severity memory safety vulnerability in regular storage place in the versions since but not including twenty-one point seven six six up to but not including twenty-three",
    "generations": [
      "### Security Advisory: Smart Fridge 8000 Memory Safety Vulnerability\n\n**Product:** Smart Fridge 8000  \n**Versions Affected:** 21.7.6 to &lt; 23.0.0  \n**Severity:** Low  \n**Issue:** Memory Safety Vulnerability in Regular Storage Module  \n\n#### Summary:\nThe Smart Fridge 8000 models from version 21.7.6 up to but not including version 23.0.0 contain a low-severity memory safety vulnerability within the regular storage module. This issue could potentially allow an attacker to execute arbitrary code or cause a denial of service by exploiting improper handling of memory operations.\n\n#### Impact:\n- **Arbitrary Code Execution:** An attacker with access to the fridge's network interface might exploit this vulnerability to execute arbitrary code.\n- **Denial of Service:** Improper memory handling could lead to a denial of service, affecting the fridge's normal operations.\n\n#### Recommendations:\n1. **Upgrade to a Secure Version:** Users are advised to upgrade to version 23.0.0 or later, where this vulnerability has been addressed.\n2. **Apply Patches:** If immediate upgrade is not feasible, apply the latest security patches provided by the manufacturer.\n3. **Network Segmentation:** Ensure that the",
      
      "### Security Advisory: Smart Fridge 8000 Memory Safety Vulnerability\n\n**Product:** Smart Fridge 8000  \n**Versions Affected:** 21.7.6 to &lt; 23.0.0  \n**Severity:** Low  \n**Issue:** Memory Safety Vulnerability in Regular Storage Module  \n\n#### Summary:\nModels 21.7.6 to &lt; 23.0.0 of the Smart Fridge 8000 have a low-severity memory safety vulnerability in the storage module, potentially allowing arbitrary code execution or denial of service through improper memory handling.\n\n#### Impact:\n- **Arbitrary Code Execution:** An attacker could execute arbitrary code via the fridge's network interface.\n- **Denial of Service:** The vulnerability could disrupt normal operations.\n\n#### Recommendations:\n1. **Upgrade:** Upgrade to version 23.0.0 or later to resolve the issue.\n2. **Apply Patches:** If upgrading is not possible, apply the latest security patches.\n3. **Network Segmentation:** Ensure the fridge is on a secure network segment to limit potential exposure.",
     
      "### Security Advisory: Smart Fridge 8000 Memory Safety Vulnerability\n\n**Product:** Smart Fridge 8000  \n**Versions Affected:** 21.7.6 to &lt; 23.0.0  \n**Severity:** Low  \n**Issue:** Memory Safety Vulnerability in Storage Module  \n\n#### Summary:\nModels 21.7.6 to &lt; 23.0.0 of the Smart Fridge 8000 have a low-severity memory safety vulnerability that could allow arbitrary code execution or denial of service due to improper memory handling in the storage module.\n\n#### Impact:\n- **Arbitrary Code Execution:** Potential execution of arbitrary code via the network interface.\n- **Denial of Service:** Possible disruption of normal operations.\n\n#### Recommendations:\n1. **Upgrade:** Upgrade to version 23.0.0 or later.\n2. **Apply Patches:** If upgrading is not feasible, apply the latest security patches.\n3. **Network Segmentation:** Ensure the fridge is on a secure network segment to reduce exposure risk.",
      
      "### Security Advisory: Smart Fridge 8000 Memory Safety Vulnerability\n\n**Product:** Smart Fridge 8000  \n**Versions Affected:** 21.7.6 to &lt; 23.0.0  \n**Severity:** Low  \n\n#### Issue:\nA memory safety vulnerability in the storage module could allow arbitrary code execution or denial of service.\n\n#### Recommendations:\n1. **Upgrade:** Update to version 23.0.0 or later.\n2. **Apply Patches:** If upgrading is not feasible, apply the latest security patches.\n3. **Network Segmentation:** Place the fridge on a secure network segment to minimize risk.",
      
      "### Security Advisory: Smart Fridge 8000 Memory Safety Vulnerability\n\n**Product:** Smart Fridge 8000  \n**Versions Affected:** 21.7.6 to &lt; 23.0.0  \n**Severity:** Low  \n\n#### Issue:\nA memory safety vulnerability in the storage module could allow arbitrary code execution or denial of service.\n\n#### Recommendations:\n1. **Upgrade:** Update to version 23.0.0 or later.\n2. **Apply Patches:** If upgrading is not feasible, apply the latest security patches.\n3. **Network Segmentation:** Isolate the fridge on a secure network segment to minimize risk.",
      
      "### Security Advisory: Smart Fridge 8000 Memory Safety Vulnerability\n\n**Product:** Smart Fridge 8000  \n**Versions Affected:** 21.7.6 to &lt; 23.0.0  \n**Severity:** Low  \n\n#### Issue:\nA memory safety vulnerability in the storage module could lead to arbitrary code execution or denial of service.\n\n#### Recommendations:\n1. **Upgrade:** Update to version 23.0.0 or later.\n2. **Apply Patches:** Apply the latest security patches if upgrading is not feasible.\n3. **Network Segmentation:** Isolate the fridge on a secure network segment."
    ]
  },
</code></pre></div></div>
<p>Not perfect by any means, but directly usable and sane at a glance. It’s unclear what the <code class="language-plaintext highlighter-rouge">\n1</code>, <code class="language-plaintext highlighter-rouge">\n2</code> and <code class="language-plaintext highlighter-rouge">\n3</code> are about, but we’ve got a fairly consistent string of text almost immediately and I couldn’t find totally off the wall output like qwen would give.</p>

<p>In short though they all have issues and I wouldn’t directly put this output in front of anyone who cared about the product in question. I thought it might be interesting to look at something a little more objective and after a bit of looking I found an interesting python library called <a href="https://github.com/textstat/textstat">textstat</a>. This library aims to measure the complexity of text to a reader and it does some with some industry standard measures. To be clear I do not understand these measures, but I thought it might be neat to try out. I went with <a href="https://en.wikipedia.org/wiki/Flesch%E2%80%93Kincaid_readability_tests">Flesch–Kincaid readability</a> measure since it was the first in the list of examples and had the “higher number better” property. Eg. A higher number is easier to read.</p>

<p>Each model had its own distribution of readability changes over iterations. One of the phi-4 runs looked like</p>

<p><img src="https://raw.githubusercontent.com/darakian/darakian.github.io/refs/heads/master/_images/2026-01-06-an-llm-blog-post/phi-4.png" alt="phi-4 reading scores plotted" style="width:100%; height:auto;" /></p>

<p>The top tick there is <code class="language-plaintext highlighter-rouge">30</code> and the bottom is <code class="language-plaintext highlighter-rouge">-10</code>. Negative scores are valid.</p>

<p>Each model had its own flavor of distribution and I’m not sure what questions to really ask of the data, so I won’t dwell on these. What I thought might be more interesting is comparing the models against each other.</p>

<p><img src="https://raw.githubusercontent.com/darakian/darakian.github.io/refs/heads/master/_images/2026-01-06-an-llm-blog-post/averages.png" alt="Average reading scores across models" style="width:100%; height:auto;" /></p>

<p>The top tick on this one is <code class="language-plaintext highlighter-rouge">50</code> and the bottom one is <code class="language-plaintext highlighter-rouge">10</code>. Phi-4 is at the bottom in blue, Qwen in orange in the middle and Deepseek in green at the top with the easiest to read descriptions.</p>

<p>What seems to be common is that each model reduces the readability over iterations. Looking back at the phi-4 runs all plotted out on their own line I’m not sure I would have guessed that the readability scores will average lower, but it’s interesting to see. It’s also interesting that each model also has its own band of readability which does match my observation above. For context a score of 10 is deemed as a <code class="language-plaintext highlighter-rouge">Professional</code> reading level while 50 is deemed to be early college level. The observed differences are far less significant than the starting point for each model as well, though in the case of Phi-4 it’s possible the formatting is what’s driving the score down so much.</p>

<h2 id="the-conclusion">The conclusion?</h2>

<p>Advisory publication is an act of creation. It is an art form. It is not a probabilistic generation question and so I do not see value in LLMs for advisory publication. Research tool? Maybe. Style guide checker? Sure, why not. Generator? Absolutely not.</p>

<p><code class="language-plaintext highlighter-rouge">thanks for reading</code></p>

<hr />

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># pip install accelerate
</span><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
<span class="kn">from</span> <span class="nn">pprint</span> <span class="kn">import</span> <span class="n">pprint</span>
<span class="kn">from</span> <span class="nn">huggingface_hub</span> <span class="kn">import</span> <span class="n">login</span>
<span class="kn">from</span> <span class="nn">transformers</span> <span class="kn">import</span> <span class="n">AutoTokenizer</span><span class="p">,</span> <span class="n">AutoModelForCausalLM</span>

<span class="kn">import</span> <span class="nn">transformers</span>
<span class="kn">import</span> <span class="nn">num2words</span>
<span class="kn">import</span> <span class="nn">random</span>
<span class="kn">import</span> <span class="nn">torch</span>
<span class="kn">import</span> <span class="nn">json</span>
<span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span><span class="p">,</span> <span class="n">asdict</span>

<span class="o">@</span><span class="n">dataclass</span>
<span class="k">class</span> <span class="nc">PromptLineage</span><span class="p">:</span>
  <span class="n">model</span><span class="p">:</span> <span class="nb">str</span>
  <span class="n">input_prompt</span><span class="p">:</span> <span class="nb">str</span>
  <span class="n">generations</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span>

  <span class="k">def</span> <span class="nf">add_generation</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">new_text</span><span class="p">):</span>
    <span class="bp">self</span><span class="p">.</span><span class="n">generations</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">new_text</span><span class="p">)</span>

  <span class="k">def</span> <span class="nf">get_most_recent_generation</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
    <span class="k">return</span> <span class="bp">self</span><span class="p">.</span><span class="n">generations</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span>

<span class="k">def</span> <span class="nf">random_versions</span><span class="p">():</span>
  <span class="n">upper</span> <span class="o">=</span> <span class="n">random</span><span class="p">.</span><span class="n">randrange</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">256</span><span class="p">)</span>
  <span class="n">lower</span> <span class="o">=</span> <span class="nb">round</span><span class="p">(</span><span class="n">random</span><span class="p">.</span><span class="n">uniform</span><span class="p">(</span><span class="mf">0.1</span><span class="p">,</span> <span class="n">upper</span><span class="p">),</span> <span class="mi">3</span><span class="p">)</span>
  <span class="n">upper</span> <span class="o">=</span> <span class="n">num2words</span><span class="p">.</span><span class="n">num2words</span><span class="p">(</span><span class="n">upper</span><span class="p">)</span>
  <span class="n">lower</span> <span class="o">=</span> <span class="n">num2words</span><span class="p">.</span><span class="n">num2words</span><span class="p">(</span><span class="n">lower</span><span class="p">)</span>

  <span class="n">range_phrases</span> <span class="o">=</span> <span class="p">[</span>
    <span class="sa">f</span><span class="s">"from </span><span class="si">{</span><span class="n">lower</span><span class="si">}</span><span class="s"> up to but not including </span><span class="si">{</span><span class="n">upper</span><span class="si">}</span><span class="s">"</span><span class="p">,</span>
    <span class="sa">f</span><span class="s">"since but not including </span><span class="si">{</span><span class="n">lower</span><span class="si">}</span><span class="s"> up to </span><span class="si">{</span><span class="n">upper</span><span class="si">}</span><span class="s">"</span><span class="p">,</span>
    <span class="sa">f</span><span class="s">"from </span><span class="si">{</span><span class="n">lower</span><span class="si">}</span><span class="s"> up to </span><span class="si">{</span><span class="n">upper</span><span class="si">}</span><span class="s">"</span><span class="p">,</span>
    <span class="sa">f</span><span class="s">"since but not including </span><span class="si">{</span><span class="n">lower</span><span class="si">}</span><span class="s"> up to but not including </span><span class="si">{</span><span class="n">upper</span><span class="si">}</span><span class="s">"</span><span class="p">,</span>
    <span class="p">]</span>

  <span class="k">return</span> <span class="n">random</span><span class="p">.</span><span class="n">choice</span><span class="p">(</span><span class="n">range_phrases</span><span class="p">)</span>


<span class="k">def</span> <span class="nf">random_prompt</span><span class="p">():</span>
  <span class="n">product_names</span> <span class="o">=</span> <span class="p">[</span>
  <span class="s">"Yellow Vest Linux"</span><span class="p">,</span>
  <span class="s">"Elaborate Text Storage Service"</span><span class="p">,</span>
  <span class="s">"Datcord"</span><span class="p">,</span>
  <span class="s">"jrc"</span><span class="p">,</span>
  <span class="s">"Kazoo"</span><span class="p">,</span>
  <span class="s">"Doors"</span><span class="p">,</span>
  <span class="s">"An actual car"</span><span class="p">,</span>
  <span class="s">"Smart Fridge 8000"</span><span class="p">,</span>
  <span class="p">]</span>

  <span class="n">product_components</span> <span class="o">=</span> <span class="p">[</span>
  <span class="s">"user settings"</span><span class="p">,</span>
  <span class="s">"regular storage place"</span><span class="p">,</span>
  <span class="s">"secret storage place"</span><span class="p">,</span>
  <span class="s">"transport"</span><span class="p">,</span>
  <span class="p">]</span>

  <span class="n">severity</span> <span class="o">=</span> <span class="p">[</span>
    <span class="s">"low"</span><span class="p">,</span>
    <span class="s">"medium"</span><span class="p">,</span>
    <span class="s">"high"</span><span class="p">,</span>
    <span class="s">"critical"</span><span class="p">,</span>
  <span class="p">]</span>

  <span class="n">problems</span> <span class="o">=</span> <span class="p">[</span>
    <span class="s">"cross site scripting"</span><span class="p">,</span>
    <span class="s">"memory safety"</span><span class="p">,</span>
    <span class="s">"data exposure"</span><span class="p">,</span>
    <span class="s">"logic injection"</span><span class="p">,</span>
    <span class="s">"hard coded credentials"</span><span class="p">,</span>
  <span class="p">]</span>

  <span class="n">prompt_string</span> <span class="o">=</span> <span class="p">(</span><span class="sa">f</span><span class="s">"The product named </span><span class="si">{</span><span class="n">random</span><span class="p">.</span><span class="n">choice</span><span class="p">(</span><span class="n">product_names</span><span class="p">)</span><span class="si">}</span><span class="s">"</span>
          <span class="sa">f</span><span class="s">" which has a </span><span class="si">{</span><span class="n">random</span><span class="p">.</span><span class="n">choice</span><span class="p">(</span><span class="n">severity</span><span class="p">)</span><span class="si">}</span><span class="s"> severity </span><span class="si">{</span><span class="n">random</span><span class="p">.</span><span class="n">choice</span><span class="p">(</span><span class="n">problems</span><span class="p">)</span><span class="si">}</span><span class="s">"</span>
          <span class="sa">f</span><span class="s">" vulnerability in </span><span class="si">{</span><span class="n">random</span><span class="p">.</span><span class="n">choice</span><span class="p">(</span><span class="n">product_components</span><span class="p">)</span><span class="si">}</span><span class="s">"</span>
          <span class="sa">f</span><span class="s">" in the versions </span><span class="si">{</span><span class="n">random_versions</span><span class="p">()</span><span class="si">}</span><span class="s">"</span>
    <span class="p">)</span>

  <span class="k">return</span> <span class="n">prompt_string</span>

<span class="k">def</span> <span class="nf">improve_advisory</span><span class="p">(</span><span class="n">pipe</span><span class="p">,</span> <span class="n">prompt</span><span class="p">):</span>
  <span class="n">messages</span> <span class="o">=</span> <span class="p">[</span>
    <span class="p">{</span><span class="s">"role"</span><span class="p">:</span> <span class="s">"system"</span><span class="p">,</span> <span class="s">"content"</span><span class="p">:</span> <span class="s">"You are an editor tasked with ensuring complete, concise and correct advisories are sent out for our users. Remove unnecessary content and refine the advisory provided. Only reply with the new advisory."</span><span class="p">},</span>
    <span class="p">{</span><span class="s">"role"</span><span class="p">:</span> <span class="s">"user"</span><span class="p">,</span> <span class="s">"content"</span><span class="p">:</span> <span class="n">prompt</span><span class="p">},</span>
  <span class="p">]</span>
  <span class="n">outputs</span> <span class="o">=</span> <span class="n">pipe</span><span class="p">(</span><span class="n">messages</span><span class="p">)</span>
  <span class="n">result</span> <span class="o">=</span> <span class="n">outputs</span><span class="p">[</span><span class="mi">0</span><span class="p">][</span><span class="s">"generated_text"</span><span class="p">][</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span>
  <span class="k">return</span> <span class="n">result</span><span class="p">[</span><span class="s">"content"</span><span class="p">]</span>

<span class="k">def</span> <span class="nf">generate_advisories</span><span class="p">(</span><span class="n">model</span><span class="p">,</span> <span class="n">count</span><span class="p">):</span>
  <span class="n">pipeline</span> <span class="o">=</span> <span class="n">transformers</span><span class="p">.</span><span class="n">pipeline</span><span class="p">(</span><span class="n">task</span><span class="o">=</span><span class="s">"text-generation"</span><span class="p">,</span> <span class="n">model</span><span class="o">=</span><span class="n">model</span><span class="p">,</span> <span class="n">device_map</span><span class="o">=</span><span class="s">"auto"</span><span class="p">,</span> <span class="n">dtype</span><span class="o">=</span><span class="n">torch</span><span class="p">.</span><span class="n">bfloat16</span><span class="p">)</span>
  <span class="n">prompt_evolutions</span> <span class="o">=</span> <span class="p">[]</span>

  <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">count</span><span class="p">):</span>

    <span class="n">prompt</span> <span class="o">=</span> <span class="n">PromptLineage</span><span class="p">(</span>
      <span class="n">model</span><span class="p">,</span>
      <span class="n">random_prompt</span><span class="p">(),</span>
      <span class="p">[])</span>

    <span class="n">messages</span> <span class="o">=</span> <span class="p">[</span>
    <span class="p">{</span><span class="s">"role"</span><span class="p">:</span> <span class="s">"system"</span><span class="p">,</span> <span class="s">"content"</span><span class="p">:</span> <span class="s">"Write a security advisory for the product and problem provided. Keep it short and to the point"</span><span class="p">},</span>
    <span class="p">{</span><span class="s">"role"</span><span class="p">:</span> <span class="s">"user"</span><span class="p">,</span> <span class="s">"content"</span><span class="p">:</span> <span class="n">prompt</span><span class="p">.</span><span class="n">input_prompt</span><span class="p">},</span>
    <span class="p">]</span>
    <span class="n">outputs</span> <span class="o">=</span> <span class="n">pipeline</span><span class="p">(</span><span class="n">messages</span><span class="p">)</span>
    <span class="n">prompt</span><span class="p">.</span><span class="n">add_generation</span><span class="p">(</span><span class="n">outputs</span><span class="p">[</span><span class="mi">0</span><span class="p">][</span><span class="s">"generated_text"</span><span class="p">][</span><span class="o">-</span><span class="mi">1</span><span class="p">][</span><span class="s">"content"</span><span class="p">])</span>

    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">5</span><span class="p">):</span>
      <span class="n">prompt</span><span class="p">.</span><span class="n">add_generation</span><span class="p">(</span><span class="n">improve_advisory</span><span class="p">(</span><span class="n">pipeline</span><span class="p">,</span> <span class="n">prompt</span><span class="p">.</span><span class="n">get_most_recent_generation</span><span class="p">()))</span>

    <span class="n">prompt_evolutions</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">asdict</span><span class="p">(</span><span class="n">prompt</span><span class="p">))</span>

  <span class="k">return</span> <span class="n">prompt_evolutions</span>

<span class="c1">## Uncomment the one you want to run
# model = "Qwen/Qwen2.5-7B"
# model = "microsoft/phi-4"
</span><span class="n">model</span> <span class="o">=</span> <span class="s">"deepseek-ai/DeepSeek-R1-Distill-Llama-8B"</span>


<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s">'DeepSeek-R1-Distill-Llama-8B.json'</span><span class="p">,</span> <span class="s">'w+'</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
    <span class="n">json</span><span class="p">.</span><span class="n">dump</span><span class="p">(</span><span class="n">generate_advisories</span><span class="p">(</span><span class="n">model</span><span class="p">,</span> <span class="mi">100</span><span class="p">),</span> <span class="n">f</span><span class="p">)</span>

</code></pre></div></div>]]></content><author><name></name></author><summary type="html"><![CDATA[Large language models are all the rage these days. Everyone’s working on them (or else 😰) and they’re showing up in everything. I have not been a fan though I do see the appeal of natural language programming, boiler plate free development, and of capturing the expertises of transient humans. That said, I get the sense that the hype is coming less from the theory, design, or scale behind statistical language models and more from a desire to degrade the economic value held by cantankerous humans. It’s worth noting up front that we’ve been here before. We’ve made languages designed for the everyman. We’ve made chatbots that have convinced us of their intelligence. We’ve made systems designed to capture the expertise of those we employ in an effort to scale the individual/reduce the relative value of labor. We are not without history and we are not without observable results. What’s that saying? Those who cannot remember the past are destined to meet their KPIs? I dunno, something like that.]]></summary></entry><entry><title type="html">How and why we index vulnerabilities</title><link href="/2025/10/17/indexing-vulns.html" rel="alternate" type="text/html" title="How and why we index vulnerabilities" /><published>2025-10-17T19:00:00+00:00</published><updated>2025-10-17T19:00:00+00:00</updated><id>/2025/10/17/indexing-vulns</id><content type="html" xml:base="/2025/10/17/indexing-vulns.html"><![CDATA[<h2 id="preliminaries">Preliminaries</h2>

<p>There are two types of vulnerabilities: those that are publicly known and those that are not. For the latter we can spend time searching/testing/researching to make them known and for the former we can collect, index, and enrich them so that interested parties can easily access them and take action based on them. It would be nice if we had some algorithm we could run and test on demand, but that might be difficult to construct. Anyway, if we want to ship more secure code, we can do some librarian-ing for the benefit of those interested parties until someone can make that algorithm. Libraries come in many forms, but one focused on software vulnerabilities probably falls into the “Research Library” category, and perhaps it makes sense to subdivide further depending on specific org goals and resources. Regardless, if we want to run a good library, we need to have a sense of who our readers are, what objects they care about, and what goals they have. We need scope.</p>

<p>If we have a meaningful index of vulns we can check it against whatever we’re shipping and know or reduce our risk exposure to at least a first approximation. We might be introducing code execution vectors or whatever in our code, but maybe we can at least have a little faith that we’re only shipping our own flaws. That’s the theory anyway. If we buy into this theory, then we need a meaningful index on which to work, and we need people to work it. Many approaches miss one or the other. You need both. One could imagine more advanced use cases with one or more indices that tell us the hows and whys of vulns in addition to the whats and wheres, but I would encourage restraint until there is a foundation to build on.</p>

<h2 id="package-oriented-indexing">Package oriented indexing</h2>

<p>For a while now we’ve collected advisories about known vulnerabilities, annotated them with useful metadata, clarified language, enriched them with meaningful context, and made them available to a reader base which has tastes and preferences. Package metadata in particular has proven to be pretty useful in that it allows a developer with an arbitrary codebase to check against a set of code that they’re likely to have. The term Ecosystem has been used to communicate this metadata and for the purposes of this post I’ll be limiting the discussion of the term to mean “a meaningful namespace for the purposes of communicating advisory information”. The choice of which ecosystems we care about is also largely informed by popularity.</p>

<p>Take a look at the <a href="https://github.blog/news-insights/octoverse/octoverse-2024/#the-most-popular-programming-languages">most popular languages on github</a></p>

<p><img src="https://github.blog/wp-content/uploads/2024/10/GitHub-Octoverse-2024-top-programming-languages.png?w=1400" alt="Octoverse 2024 report" /></p>

<p>You’ll note that most of them have a popular package registry (or registry system) that lends a namespace. These are well understood and make for easy ecosystems.</p>

<p>The problem with this approach is that it’s inherently limited. Each ecosystem has its own sense of what a version is, what a package name can be, what can be inside, or where they’re hosted. They’re all quirky in their own ways and to scale you need to keep adding ecosystems which are decreasingly popular and thus increasingly hard to justify. To make things worse the more of the nuance you want to capture (as a benefit to the reader) the harder you make the job of the curator which may limit (in practical terms) the size of your corpus. Just as there’s no sense in indexing using some new index if no one cares, so too it may not be worthwhile catering to every niche reader if the cognitive load of the curator is raised to an unworkable level. In practical application it may be worth simplifying your index for the benefit of your curator. Specific goals, timelines, and teams should inform choices about ecosystem specificity. Ecosystem design must consider operational constraints.</p>

<h2 id="commit-oriented-indexing">Commit oriented indexing</h2>

<p>Ecosystems don’t need to be packages though. In theory an ecosystem is just a namespace, but which namespaces make sense? A curator needs a <a href="https://mathworld.wolfram.com/Domain.html">domain</a> on which to curate and the choice of domain dictates the <a href="https://mathworld.wolfram.com/Range.html">range</a> of the vulnerability management program. Further, the domain choice must be something the curator can reason about. One domain I think is worth exploring is the domain of the commit. While build systems can introduce (or resolve) bugs it happens to be the case that most bugs that manifest for the operator are in source code of the program and version control gives us a namespace for that.</p>

<p>It might sound daunting to go and read commits and try to determine what fixes what where and it certainly can be, but a shocking number of fix commits are really quite easy to spot. Like this one<br />
<a href="https://github.com/pyca/cryptography/commit/1ca7adc97b76a9dfbd3d850628b613eb93b78fc3">https://github.com/pyca/cryptography/commit/1ca7adc97b76a9dfbd3d850628b613eb93b78fc3</a><br />
They just tell you right there.</p>

<p>Go search github for your favorite CVE number and marvel at the number of people fixing that particular problem out there (and good on them).</p>

<p>So, what can we do with these commits? Well, if we have a copy of the source repo we can ask the question “Does the repo at some point contain this fix?”. Maybe we can’t take as fact that not having the commit implies that the code is vulnerable to whatever issue that commit fixed, but we can be certain that if our codebase has the commit then the issue is fixed. This of course assumes truth and that’s why you have curators, but let’s explore this example a bit more</p>

<p>The commit<br />
<a href="https://github.com/pyca/cryptography/commit/1ca7adc97b76a9dfbd3d850628b613eb93b78fc3">https://github.com/pyca/cryptography/commit/1ca7adc97b76a9dfbd3d850628b613eb93b78fc3</a><br />
Which fixes an SSH certificate handling issue and is listed here<br />
<a href="https://github.com/advisories/GHSA-cf7p-gm2m-833m">https://github.com/advisories/GHSA-cf7p-gm2m-833m</a></p>

<p>in this case we happen to have an introductory commit as well<br />
<a href="https://github.com/pyca/cryptography/commit/aca8de845e751dd45fe4e48f8492f357d34d1861">https://github.com/pyca/cryptography/commit/aca8de845e751dd45fe4e48f8492f357d34d1861</a><br />
shout outs to github user tiran for that<br />
<a href="https://github.com/github/advisory-database/pull/2620">https://github.com/github/advisory-database/pull/2620</a></p>

<p>Now cryptography is a project that has adopted a provenance solution called sigstore and that lets us trace back the origin of any published artifact like this one<br />
<a href="https://pypi.org/project/cryptography/46.0.1/#cryptography-46.0.1-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl">https://pypi.org/project/cryptography/46.0.1/#cryptography-46.0.1-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl</a><br />
back to a specific commit<br />
<a href="https://github.com/pyca/cryptography/commit/e735cfc27502320101c130335c556394a125ba52">https://github.com/pyca/cryptography/commit/e735cfc27502320101c130335c556394a125ba52</a><br />
via a build log<br />
<a href="https://search.sigstore.dev/?logIndex=527528656">https://search.sigstore.dev/?logIndex=527528656</a></p>

<p>You could imagine building a system to iterate the specific artifacts, go inspect the upstream code at the specific commit for the release and check for both of introductory and fix commits. Now we can derive affected versions rather than having humans index on them. Maybe we could even track these artifacts as they get encapsulated in other objects. There could be other commit types which are interesting too, but intro and fix commits seem like enough to test the theory and to keep the task of the operator scoped. The down side here is that legacy artifacts don’t have provenance information and so the readership for such an ecosystem is limited for now.</p>

<h2 id="yes-and">Yes and…</h2>

<p>Package oriented indexing isn’t going anywhere and could even increase precision by breaking out parts of a package; <code class="language-plaintext highlighter-rouge">function X</code>, <code class="language-plaintext highlighter-rouge">class Y</code>, etc… to enrich whatever alerting or patching system actions on the data. Other indexing systems could also be interesting for different use cases, but I’m convinced that much meaningful software will find its way to a path of provenance, and so there will be readership for commit oriented advisories. I think there’s potential at least. Indexing vulnerabilities on commits does require that the curator on the job can reason about code and coding practice, but I think you want that anyway. Research libraries are often staffed by researchers after all. Provenance is expensive, and that’s a hard requirement as well, but the benefits of this approach could be huge. Arbitrary artifact support, freedom from namespace and version type enumeration, and less ambiguity in the curation process. Provenance is expensive, but maybe it’s a cost we’re going to pay anyway. I’d really like to see this idea develop further and to be prototyped to see if it holds up in practice. I really do believe it could be a force multiplier for vulnerability management.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Thanks for reading.
</code></pre></div></div>]]></content><author><name></name></author><summary type="html"><![CDATA[Preliminaries]]></summary></entry><entry><title type="html">Parsing semver values from CVE records</title><link href="/2025/06/04/parsing-semver-from-cve.html" rel="alternate" type="text/html" title="Parsing semver values from CVE records" /><published>2025-06-04T21:00:00+00:00</published><updated>2025-06-04T21:00:00+00:00</updated><id>/2025/06/04/parsing-semver-from-cve</id><content type="html" xml:base="/2025/06/04/parsing-semver-from-cve.html"><![CDATA[<h2 id="preliminaries">Preliminaries</h2>

<p>I’ve been pushing a schema change to CVE for the last half year or so. I started <a href="https://github.com/CVEProject/cve-schema/issues/362">a conversation about formalizing what a version type means</a> in a CVE record back in November of last year. The tl;dr though is that versioning in a CVE record is chaos. From that conversation I spun up a <a href="https://github.com/CVEProject/cve-schema/pull/371">pull request to formalize semantic versioning</a>. That pull request has a lot going on, but the salient points are</p>
<ol>
  <li>It introduces a new named version type <code class="language-plaintext highlighter-rouge">semver-2.0.0</code> so that record authors MUST opt-in to the new behavior.</li>
  <li>The new behavior is quite strict and opinionated. This is for the benefit of the record reader.</li>
  <li>The test suite is expanded to both demonstrate what correctness looks like and to guard against accidental changes.</li>
</ol>

<p>The new behavior is that if one wants to submit a CVE record with a semver-2.0.0 then the version values MUST conform with the official semantic versioning regular expression. The ordering rules that semantic versioning defines SHOULD also be observed, but you can’t enforce that in a schema.</p>

<p>Also, if you’re reading this blog post and are not already involved in the PR please don’t get involved. If you’d like to show your support please just 👍 the first comment.</p>

<h2 id="but-why-tho">But why tho?</h2>

<p>So, why make a new version type? Why do we need to add extra complexity of another version type? In short, because the current semver is chaos.</p>

<h3 id="a-quick-look-at-semver">A quick look at semver</h3>

<p>Let’s take a quick tour of the version type <code class="language-plaintext highlighter-rouge">semver</code> as it appears in the wild. Recall that a valid semver string contains three number with two dots which separate them eg. a.b.c where a, b and c can only be numbers. a.b is not semver compliant nor is a.b.c.d. There are build and pre-release extensions to semver, but for simplicity lets ignore those for the moment. If you want to double check me there please see:<br />
https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string<br />
which links out to the fantastic<br />
https://regex101.com/r/vkijKf/1/</p>

<p>So, what do we observe in the published data?</p>

<p>First a few examples. We have this</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{
    "lessThanOrEqual": "FW950.90",
    "status": "affected",
    "version": "FW950.00",
    "versionType": "semver"
},
</code></pre></div></div>
<p>Which comes to us from <a href="https://github.com/CVEProject/cvelistV5/blob/main/cves/2023/33xxx/CVE-2023-33851.json">IBM and was published in 2023</a>.</p>

<p>We’ve got this</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{
    "lessThanOrEqual": "FW10",
    "status": "affected",
    "version": "0",
    "versionType": "semver"
}
</code></pre></div></div>
<p>From <a href="https://github.com/CVEProject/cvelistV5/blob/main/cves/2023/1xxx/CVE-2023-1150.json">CERTVDE also from 2023</a></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{
    "lessThanOrEqual": "&lt;=9.3.3.0",
    "status": "affected",
    "version": "ECOS 9.3.x.x: 9.3.3.0 and below",
    "versionType": "semver"
},
</code></pre></div></div>
<p>from <a href="https://github.com/CVEProject/cvelistV5/blob/main/cves/2024/41xxx/CVE-2024-41133.json">HPE in 2024</a></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{
    "version": "unspecified",
    "status": "affected",
    "lessThan": "29486",
    "versionType": "semver"
}
</code></pre></div></div>
<p>from <a href="https://github.com/CVEProject/cvelistV5/blob/main/cves/2022/3xxx/CVE-2022-3405.json">Acronis in 2022</a></p>

<p>and</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{
    "lessThan": "3.5.9, 3.6.4, 4.0.5",
    "status": "affected",
    "version": "0",
    "versionType": "semver"
}
</code></pre></div></div>
<p>from <a href="https://github.com/CVEProject/cvelistV5/blob/main/cves/2024/29xxx/CVE-2024-29736.json">Apache in 2024</a></p>

<p>A few very different ideas of what “semver” is from a few different organizations that have a good amount of experience with CVEs. Keep digging through the data and you’ll find a number of other variations like the github style where inequality symbols are used in the version strings (sorry). If you want to parse an arbitrary record you end up with a tool that needs to do some heuristic test to identify a sub-pattern and then switches to one of a few dozen parsers to actually parse it. Also it needs to have healthy error handling for each case and for new cases that might exist in the future. It’s a lot. I’m lazy so I didn’t write any of that. Instead I wrote some code to help me get a handle on the scope of the problem<sup id="fnref:code" role="doc-noteref"><a href="#fn:code" class="footnote" rel="footnote">1</a></sup>. This code goes through every CVE record, pulls out version strings which were labeled as <code class="language-plaintext highlighter-rouge">semver</code> then tries to validate them against <a href="https://pypi.org/project/semver/">an off the shelf semver parser</a>. Is this a good parser? No idea, but a heck of a lot of people use it, so it’s an important parser<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">2</a></sup>.</p>

<p>Only 44% of version strings in CVEs pass the test<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">3</a></sup>. If you pull a “semver” version string out at random it has slightly better odds of failing validation than passing. Oof.</p>

<h3 id="are-the-cnas-wrong">Are the CNAs wrong?</h3>

<p>Are those CNAs publishing semver values wrong? Should we go yell at them? In short. No. You or I or anyone else might look at the string <code class="language-plaintext highlighter-rouge">ECOS 9.3.x.x: 9.3.3.0 and below</code> and say</p>
<blockquote>
  <p>clearly this is not a semantic versioning compliant string</p>
</blockquote>

<p>but how does one validate correctness? How do we come to that conclusion? To validate correctness we need to go and look at <a href="https://github.com/CVEProject/cve-schema/blob/a9e9fa9/schema/docs/versions.md#versions-and-version-ranges">the docs</a> and check the version string against the rules laid out. Alas, the docs don’t say much about the issue. They introduce the versionType concept with</p>

<blockquote>
  <p>The versionType is required when specifying ranges, because there is no single definition of “less than” for versions. Each different version numbering system has its own ordering rules. For example, in semantic versioning, 1.0.0-cr1 &lt; 1.0.0-m1, while in Maven, the opposite is true. Example version types include maven, python, rpm, and semver. Another version type is git, described later.</p>
</blockquote>

<p>Semver is mentioned by name and there’s even a link out to semver.org when you view the markdown, however the docs follow up with</p>

<blockquote>
  <p>In any version range, the details of the version syntax and semantics depend on the version type, but by convention, “version”: “0” means that the range has no lower bound, and a * in an upper bound denotes “infinity”, as in “lessThan”: “2.*”, which denotes a range where the 2.X version series is the upper bound, or “lessThan”: “*”, which denotes a range with no upper bound at all.</p>
</blockquote>

<p>and then an example using <code class="language-plaintext highlighter-rouge">*</code> which is labeled as semver. <code class="language-plaintext highlighter-rouge">*</code> is not a valid character in a semver string. So, the docs have introduced us to the concept of version types, mention that semver is one of the anointed few and then show an example ruleset which is incompatible with what one would assume is the ruleset for <code class="language-plaintext highlighter-rouge">semver</code>. A few more examples follow, but no formal rules are presented. No logic one could follow to check is one string or another valid. We lack a validation check and so we can’t actually assert that any string is invalid. Any string is valid and so the type is unbounded. It is chaos.</p>

<h3 id="who-is-the-data-for">Who is the data for?</h3>

<p>Part of the problem is that there doesn’t seem to be a clear customer persona for who reads CVEs. In my opinion the primary reader of a CVE is someone who wants to fix/address/resolve/paper over whatever vulnerability is described in the CVE. A precondition to fixing a problem is knowing that the problem affects you and to resolve the question</p>
<blockquote>
  <p>does CVE X affect me?</p>
</blockquote>

<p>you need to know what products you use and what products the CVE applies to. There’s a product naming problem there too, but lets ignore that for the moment and consider that we’ve matched on some product identifier. Do the versions match? Lets also restrict the problem space and assert that every piece of software uses semantic versioning. If we have a product match then we simply need to check if the versions of the product we use are contained in the version ranges listed as affected on the CVE record. That means parsing the record and that means making sense of the chaos that may be in it. We can’t check if our version is in the semver version range, because we don’t know what semver means in concrete terms. Even in this artificially simple scenario the potential complexity of the data is unmanageable. We have failed to make the data consumable to the reader.</p>

<h2 id="a-path-forward">A path forward</h2>

<p>If you look at the data captured in the CVE records you can find a number of patterns. Iterating them all isn’t really worth the effort for a quick blog post. The simple existence of independent patterns sharing a single identifier already tells us what we need to know. Each publisher is trying to express to their reader <code class="language-plaintext highlighter-rouge">Yo, this thing be broken!</code> with varying levels metadata and prose to enrich that core statement. The record format seems to have been designed in such a way that it didn’t want to be a blocker. That on its own is fine, but that’s what the <code class="language-plaintext highlighter-rouge">Custom</code> version type is for. The record format has failed to allow publishers to do better than unstructured strings and it needs to allow for better.</p>

<p>The CVE system is going through a lot right now. I don’t want to comment much on that since I don’t really understand it, but there’s a lot of machinery out there in the world built on CVEs  that I care about. That machinery can and should be better leveraged to secure the software we all use but, it would be a shame to have to rebuild all of it. So, I suggest iterative improvement in small, measurable, and concrete ways. A workable step is to start allowing for a progressive precision. CNAs are publishing “semver” today. Let’s give them a path to improve and perfect that. An approachable attitude could be to say</p>
<blockquote>
  <p>You want to use something else? Sure! Come define it and provide validation.</p>
</blockquote>

<p>A doable goal can be to provide validated options for the input we already get. We can make concrete one type at a time, compose these concrete types, and express complex ideas.</p>

<p>So, let the grand unified theories be discussed later. Let’s work on small patches. Let’s quell the chaos.</p>

<p><code class="language-plaintext highlighter-rouge">Thanks for reading</code></p>

<hr />

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:code" role="doc-endnote">

      <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">os</span><span class="p">,</span> <span class="n">json</span><span class="p">,</span> <span class="n">pprint</span>
<span class="kn">from</span> <span class="nn">os.path</span> <span class="kn">import</span> <span class="n">join</span>
<span class="kn">import</span> <span class="nn">semver</span>

<span class="k">def</span> <span class="nf">find_cves</span><span class="p">(</span><span class="n">scan_dir</span><span class="p">):</span>	
	<span class="n">file_paths</span> <span class="o">=</span> <span class="p">[]</span>
	<span class="k">for</span> <span class="n">root</span><span class="p">,</span> <span class="n">dirs</span><span class="p">,</span> <span class="n">files</span> <span class="ow">in</span> <span class="n">os</span><span class="p">.</span><span class="n">walk</span><span class="p">(</span><span class="n">scan_dir</span><span class="p">):</span>
		<span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">files</span><span class="p">:</span>
			<span class="p">(</span><span class="n">base</span><span class="p">,</span> <span class="n">ext</span><span class="p">)</span> <span class="o">=</span> <span class="n">os</span><span class="p">.</span><span class="n">path</span><span class="p">.</span><span class="n">splitext</span><span class="p">(</span><span class="n">f</span><span class="p">)</span>
			<span class="k">if</span> <span class="n">ext</span> <span class="o">==</span> <span class="s">'.json'</span> <span class="ow">and</span> <span class="s">'CVE'</span> <span class="ow">in</span> <span class="n">base</span><span class="p">:</span>
				<span class="n">full_name</span> <span class="o">=</span> <span class="n">os</span><span class="p">.</span><span class="n">path</span><span class="p">.</span><span class="n">join</span><span class="p">(</span><span class="n">root</span><span class="p">,</span> <span class="n">f</span><span class="p">)</span>
				<span class="n">file_paths</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">full_name</span><span class="p">)</span>
	<span class="k">return</span> <span class="n">file_paths</span>

<span class="k">def</span> <span class="nf">find_semver_strings</span><span class="p">(</span><span class="n">cve</span><span class="p">):</span>
	<span class="n">good</span> <span class="o">=</span> <span class="p">[]</span>
	<span class="n">bad</span> <span class="o">=</span> <span class="p">[]</span>
	<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">cve</span><span class="p">)</span> <span class="k">as</span> <span class="n">cve_file</span><span class="p">:</span>
		<span class="n">cve_json</span> <span class="o">=</span> <span class="n">json</span><span class="p">.</span><span class="n">load</span><span class="p">(</span><span class="n">cve_file</span><span class="p">)</span>
		<span class="n">cve_id</span> <span class="o">=</span> <span class="n">cve_json</span><span class="p">[</span><span class="s">"cveMetadata"</span><span class="p">][</span><span class="s">"cveId"</span><span class="p">]</span>
		<span class="k">if</span> <span class="ow">not</span> <span class="n">cve_json</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"containers"</span><span class="p">,</span> <span class="p">{}).</span><span class="n">get</span><span class="p">(</span><span class="s">"cna"</span><span class="p">,</span> <span class="p">{}).</span><span class="n">get</span><span class="p">(</span><span class="s">"affected"</span><span class="p">):</span>
			<span class="k">return</span> <span class="n">good</span><span class="p">,</span> <span class="n">bad</span>
		<span class="k">for</span> <span class="n">affects</span> <span class="ow">in</span> <span class="n">cve_json</span><span class="p">[</span><span class="s">"containers"</span><span class="p">][</span><span class="s">"cna"</span><span class="p">][</span><span class="s">"affected"</span><span class="p">]:</span>
			<span class="k">for</span> <span class="n">affected_block</span> <span class="ow">in</span> <span class="n">affects</span><span class="p">.</span><span class="n">items</span><span class="p">():</span>
				<span class="k">if</span> <span class="n">affected_block</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">==</span> <span class="s">"versions"</span><span class="p">:</span> <span class="c1"># Not sure why the affected block decodes as a tuple
</span>					<span class="k">for</span> <span class="n">version_range</span> <span class="ow">in</span> <span class="n">affected_block</span><span class="p">[</span><span class="mi">1</span><span class="p">]:</span>
						<span class="k">if</span> <span class="s">"versionType"</span> <span class="ow">in</span> <span class="n">version_range</span> <span class="ow">and</span> <span class="n">version_range</span><span class="p">[</span><span class="s">"versionType"</span><span class="p">]</span> <span class="o">==</span> <span class="s">"semver"</span><span class="p">:</span>
							<span class="k">if</span> <span class="s">"version"</span> <span class="ow">in</span> <span class="n">version_range</span><span class="p">:</span>
								<span class="k">try</span><span class="p">:</span>
									<span class="n">s</span> <span class="o">=</span> <span class="n">semver</span><span class="p">.</span><span class="n">Version</span><span class="p">.</span><span class="n">parse</span><span class="p">(</span><span class="n">version_range</span><span class="p">[</span><span class="s">"version"</span><span class="p">])</span>
									<span class="n">good</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">version_range</span><span class="p">[</span><span class="s">"version"</span><span class="p">])</span>
								<span class="k">except</span><span class="p">:</span>
									<span class="n">bad</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">version_range</span><span class="p">[</span><span class="s">"version"</span><span class="p">])</span>
							<span class="k">if</span> <span class="s">"lessThan"</span> <span class="ow">in</span> <span class="n">version_range</span><span class="p">:</span>
								<span class="k">try</span><span class="p">:</span>
									<span class="n">s</span> <span class="o">=</span> <span class="n">semver</span><span class="p">.</span><span class="n">Version</span><span class="p">.</span><span class="n">parse</span><span class="p">(</span><span class="n">version_range</span><span class="p">[</span><span class="s">"lessThan"</span><span class="p">])</span>
									<span class="n">good</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">version_range</span><span class="p">[</span><span class="s">"lessThan"</span><span class="p">])</span>
								<span class="k">except</span><span class="p">:</span>
									<span class="n">bad</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">version_range</span><span class="p">[</span><span class="s">"lessThan"</span><span class="p">])</span>
							<span class="k">if</span> <span class="s">"lessThanOrEqual"</span> <span class="ow">in</span> <span class="n">version_range</span><span class="p">:</span>
								<span class="k">try</span><span class="p">:</span>
									<span class="n">s</span> <span class="o">=</span> <span class="n">semver</span><span class="p">.</span><span class="n">Version</span><span class="p">.</span><span class="n">parse</span><span class="p">(</span><span class="n">version_range</span><span class="p">[</span><span class="s">"lessThanOrEqual"</span><span class="p">])</span>
									<span class="n">good</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">version_range</span><span class="p">[</span><span class="s">"lessThanOrEqual"</span><span class="p">])</span>
								<span class="k">except</span><span class="p">:</span>
									<span class="n">bad</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">version_range</span><span class="p">[</span><span class="s">"lessThanOrEqual"</span><span class="p">])</span>
	<span class="k">return</span> <span class="n">good</span><span class="p">,</span> <span class="n">bad</span>

<span class="n">all_cves</span> <span class="o">=</span> <span class="n">find_cves</span><span class="p">(</span><span class="n">path</span><span class="o">/</span><span class="n">to</span><span class="o">/</span><span class="n">cve</span><span class="o">/</span><span class="n">records</span><span class="p">)</span> <span class="c1"># Clone the CVE repo and use that 
</span><span class="n">all_good</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">all_bad</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">for</span> <span class="n">each_cve</span> <span class="ow">in</span> <span class="n">all_cves</span><span class="p">:</span>
	<span class="n">g</span><span class="p">,</span> <span class="n">b</span> <span class="o">=</span> <span class="n">find_semver_strings</span><span class="p">(</span><span class="n">each_cve</span><span class="p">)</span>
	<span class="n">all_good</span> <span class="o">=</span> <span class="n">all_good</span><span class="o">+</span><span class="n">g</span>
	<span class="n">all_bad</span> <span class="o">=</span> <span class="n">all_bad</span><span class="o">+</span><span class="n">b</span>

<span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">'Good Semver string count: </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">all_good</span><span class="p">)</span><span class="si">}</span><span class="s">'</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">'Bad Semver string count: </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">all_bad</span><span class="p">)</span><span class="si">}</span><span class="s">'</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">'Good : </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">all_good</span><span class="p">)</span><span class="o">/</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">all_good</span><span class="p">)</span><span class="o">+</span><span class="nb">len</span><span class="p">(</span><span class="n">all_bad</span><span class="p">))</span><span class="si">}</span><span class="s">%'</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">'Bad : </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">all_bad</span><span class="p">)</span><span class="o">/</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">all_good</span><span class="p">)</span><span class="o">+</span><span class="nb">len</span><span class="p">(</span><span class="n">all_bad</span><span class="p">))</span><span class="si">}</span><span class="s">%'</span><span class="p">)</span>
</code></pre></div>      </div>
      <p><a href="#fnref:code" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:1" role="doc-endnote">
      <p>It has a good test suite 👍 <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p>I pulled the data on June 4th 2025 <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><summary type="html"><![CDATA[Preliminaries]]></summary></entry><entry><title type="html">A minimal samba share with SELinux</title><link href="/2021/12/25/samba.html" rel="alternate" type="text/html" title="A minimal samba share with SELinux" /><published>2021-12-25T22:00:00+00:00</published><updated>2021-12-25T22:00:00+00:00</updated><id>/2021/12/25/samba</id><content type="html" xml:base="/2021/12/25/samba.html"><![CDATA[<h1 id="time-off-and-new-toys">Time off and new toys</h1>
<p>I recently got myself a raspberry pi in trade for a sauce pan and had to come up with a use for it. In struggling to come up with a use a basic samba share came to mind. Now, I used to be a sysadmin and do this sort of thing for a living and thought this would be a simple exercise in memory recall. It was not quite so stright forward.</p>

<h2 id="network-shares">Network shares</h2>
<p>I’m working with a Fedora server 35 setup which comes with a per-user home share, but I wanted to share out an attached disk with full world read/write. Not high security I know, but highly usable and access is limited via firewalld. Anyway, I began with a basic open config</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[Public]
	Comment = A public share for the world to write to
	writeable = yes
	public = yes
	browsable = yes
	path = /home/jon/Share_Dir
</code></pre></div></div>
<p>Get samba running (and running on boot) with 
<code class="language-plaintext highlighter-rouge">systemctl enable smb; systemctl start smb</code>
and I’m left with a share that I can connect to, but which is empty. The disk itself wasn’t empty and I could see my files sitting there when I ssh’d in. I played with file permissions, users permissions, and all sorts of snake oil config options I found all over the web. The catch? SELinux. The last time I set this up SELinux was still fairly nascent. Turns out SELinux has things to say about samba shares. Thankfully the fix is fairly straight forward. New to me but there’s a command line utility <code class="language-plaintext highlighter-rouge">chcon</code> to change the SELinux context of a file or folder and a context type <code class="language-plaintext highlighter-rouge">samba_share_t</code> which I need to apply. In my case <code class="language-plaintext highlighter-rouge">chcon -R -t samba_share_t /home/jon/Share_Dir</code>. Applying that tells SELinux that you do indeed want to share out your target directory (and everything in it). After changing the directory context the samba share was online and working as intended with the share defined above.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Thanks for reading
</code></pre></div></div>]]></content><author><name></name></author><summary type="html"><![CDATA[Time off and new toys I recently got myself a raspberry pi in trade for a sauce pan and had to come up with a use for it. In struggling to come up with a use a basic samba share came to mind. Now, I used to be a sysadmin and do this sort of thing for a living and thought this would be a simple exercise in memory recall. It was not quite so stright forward.]]></summary></entry><entry><title type="html">Stop DOSing yourself</title><link href="/2020/08/19/stop-dosing-yourself.html" rel="alternate" type="text/html" title="Stop DOSing yourself" /><published>2020-08-19T22:30:00+00:00</published><updated>2020-08-19T22:30:00+00:00</updated><id>/2020/08/19/stop-dosing-yourself</id><content type="html" xml:base="/2020/08/19/stop-dosing-yourself.html"><![CDATA[<h1 id="deep-in-the-woods">Deep in the woods</h1>
<p>Some time ago a colleague of mine was working on a problem with a license server we run and had asked me for help. He was trying to determine why some license checkouts to the server would be connect and checkout without incident and others would timeout. Some background; we dealt with a good amount of third party, close source code in order to run our customer workflows and licensed software was the norm for most of those workflows. To smooth everything out we stood up and maintained our own internal set of license servers which were basically invisible to our customers and while this made for a nice product to sell it also left us with the support burden of maintaining these code blobs.</p>

<p>Now my colleague had a habit not seeing the forest for getting lost in the trees and in this instance he had pinged me with a question about tcp header options. He had constructed a test scenario where he would emulate a busy server by using <code class="language-plaintext highlighter-rouge">nc</code> to connect to it while also using a third party tool to check out a license. In dumping packets he noticed a differences in <code class="language-plaintext highlighter-rouge">TS val</code> output between two captured tcp streams and had become convinced that the answer to the different behavior in the license server must be down to a difference in the traffic. It was not and if you’re curious the <code class="language-plaintext highlighter-rouge">TS val</code> is short for <code class="language-plaintext highlighter-rouge">timestamp value</code>.</p>

<h1 id="side-effects-in-distributed-systems">Side effects in distributed systems</h1>
<p>As it turns out the tcp was not to blame. I asked for a copy of the packet captures that my colleague was working with, dropped one into wireshark and immediately saw a tls connection
<img src="https://raw.githubusercontent.com/darakian/darakian.github.io/master/_images/2020-08-20-stop-dosing-yourself/tcp.png" alt="wireshark" />
Seeing this it was clear that there was no sense in chasing tcp packets, we needed to reason about the issue at the application layer. I asked him to replace his <code class="language-plaintext highlighter-rouge">nc</code> call with <code class="language-plaintext highlighter-rouge">openssl s_client -connect ip:port</code> which had an almost miraculous effect; licenses were now being checked out without issue.</p>

<p>There were differences though, <code class="language-plaintext highlighter-rouge">nc</code> would open and block in the terminal while <code class="language-plaintext highlighter-rouge">openssl</code> would not. The <code class="language-plaintext highlighter-rouge">openssl</code> terminated with a very helpful of <code class="language-plaintext highlighter-rouge">verify error:num=19:self signed certificate in certificate chain</code>. Fantastic confirmation that the license server is speaking tls and standards compliant tls to boot! Running the <code class="language-plaintext highlighter-rouge">openssl</code> connection again while disabling certificate checking reviled one more element; the server was expecting a valid client certificate. To recap; we observed that opening a plain tcp connection to the server causes license checkouts to fail, opening a tls connection fails and license checkouts continue as they should, and the license server is expecting clients to provide certificates.</p>

<p>On a hunch I ran the server binary through the <code class="language-plaintext highlighter-rouge">strings</code> tool and discovered that the openssl had been linked in. At this point I surmised that openssl was responsible for all connection handling and that there was some sort of blocking in there. After digging through the openssl docs I found the <a href="https://www.openssl.org/docs/man1.0.2/man3/SSL_accept.html"><code class="language-plaintext highlighter-rouge">SSL_accept</code></a> function with the following note.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>NOTES
The behaviour of SSL_accept() depends on the underlying BIO.
If the underlying BIO is blocking, SSL_accept() will only return once the handshake has been finished or an error occurred.
</code></pre></div></div>

<p>In the case of our <code class="language-plaintext highlighter-rouge">nc</code> call the handshake was never failing because it was never starting. A shared network socket was blocking on a connection which would never succeed.</p>

<h1 id="the-actual-problem">The actual problem</h1>
<p>So, why was <code class="language-plaintext highlighter-rouge">nc</code> even used in testing at all? Well, given the importance of these license servers someone had the idea that we should have a health check for them and that health check looked like this</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>try:
  s = socket.create_connection(address, timeout=2)
  s.close()
  return true
except socket.error:
  return false
</code></pre></div></div>
<p>Initiate a connection and then immediately close it. We were investigating a denial of service directly caused by our own health check. The fix for this issue ended up giving code like</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>try:
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
        sock.settimeout(2)
        sock.connect((address[0], address[1]))
        try:
            context = ssl.create_default_context()
            context.check_hostname = False
            context.verify_mode = ssl.CERT_NONE
            with context.wrap_socket(sock):
                return True
        except (ssl.SSLError, OSError):
            return True
except OSError:
    return False
</code></pre></div></div>
<p>Admittedly this is pretty convoluted logic. A tls connection is attempted with no certificate checking and regardless of success or failure we return true. Only in the event that the underlying socket fails do we return false. In particular why do we return true if a TLS connection succeeds? Wouldn’t that be a violation of the client certificate model discussed above? Yes it would, however this code is also used on license servers which don’t have the client cert restriction.</p>

<h1 id="conclusion">Conclusion</h1>
<p>It seems that whoever wrote the original health check code didn’t consider that their code may have had side effects on the servers that it was checking. I think the real problem though is that we had no metrics watching checkouts and we couldn’t see the effect of the health check. Once this health check was added it became trusted and lead support engineers down false paths. The impact of this bug resulted in over a year of degraded service. So, validate your assumptions. Stop DOSing yourself.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Thanks for reading
</code></pre></div></div>]]></content><author><name></name></author><summary type="html"><![CDATA[Deep in the woods Some time ago a colleague of mine was working on a problem with a license server we run and had asked me for help. He was trying to determine why some license checkouts to the server would be connect and checkout without incident and others would timeout. Some background; we dealt with a good amount of third party, close source code in order to run our customer workflows and licensed software was the norm for most of those workflows. To smooth everything out we stood up and maintained our own internal set of license servers which were basically invisible to our customers and while this made for a nice product to sell it also left us with the support burden of maintaining these code blobs.]]></summary></entry><entry><title type="html">The one line that allocated 5 megabytes of memory</title><link href="/2020/04/02/the-5mb-line.html" rel="alternate" type="text/html" title="The one line that allocated 5 megabytes of memory" /><published>2020-04-02T01:00:00+00:00</published><updated>2020-04-02T01:00:00+00:00</updated><id>/2020/04/02/the-5mb-line</id><content type="html" xml:base="/2020/04/02/the-5mb-line.html"><![CDATA[<h1 id="preamble">Preamble</h1>
<p>Recently at work our primary piece of web code was hit with a memory leak. We all panicked of course, but then we all took a hard look at what was going on. This blog post doesn’t cover that memory leak, but covers another memory hog I found while digging into things.</p>

<h1 id="the-line">The Line</h1>
<p>Now, we have a nice little web server built on the django web framework and in it we have a utilities module with a number of fairly general functions; nothing super uncommon. So imagine my surprise when I rig up our webserver with <a href="https://docs.python.org/3/library/tracemalloc.html">tracemalloc</a>, boot the server then immediately take the server down and I see</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>...
utils/__init__.py:48: size=5412 KiB, count=65307, average=85 B
...
</code></pre></div></div>
<p>What is line 48? Well</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>_all_chars = [chr(i) for i in range(65535)]
</code></pre></div></div>
<p>Massive confusion here for a number of reasons: Why are we generating a list of all characters? why is this list not actually all characters? How is this taking up 5 megabytes of memory? The last question is stumped me for a bit until I read through the <a href="https://docs.python.org/3/reference/import.html#regular-packages">python import reference</a>. Anything in the module level source of an <code class="language-plaintext highlighter-rouge">__init__.py</code> file will get executed whenever the module is imported. Well… <code class="language-plaintext highlighter-rouge">_all_chars</code> is at the module level and we do import utils all over the place. So, we’re creating tens of thousands of copies of the same list of characters. Clearly something needs to be done.</p>

<h1 id="whats-actually-going-on">What’s actually going on</h1>
<p>Let’s take a look at the full code sample and consider what’s actually happening here</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>_all_chars = [chr(i) for i in range(65535)]
_space_chars = [c for c in _all_chars if unicodedata.category(c) == 'Zs']
_line_break_chars = [c for c in _all_chars
                     if unicodedata.category(c) in ('Zp', 'Zl')]

_space_char_regex = re.compile('[%s]' % re.escape(''.join(_space_chars)))
_line_break_char_regex = re.compile(
    '[%s]' % re.escape(''.join(_line_break_chars))
)
_null_char_regex = re.compile(re.escape(chr(0)))


def clean_string(value):
    return _line_break_char_regex.sub(
        '\n', _space_char_regex.sub(
            ' ', _null_char_regex.sub('', value)
        )
    ).strip()
</code></pre></div></div>
<p>If your eyes glaze over while reading this code I don’t blame you. It certainly took me a while to wrap my head around it. Let’s break it down though. We have two sections. The first is module level and defines some constants. The second is a function which uses those constants. Looking at the module level code we first see some lists being created.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>_all_chars = [chr(i) for i in range(65535)]
_space_chars = [c for c in _all_chars if unicodedata.category(c) == 'Zs']
_line_break_chars = [c for c in _all_chars
                     if unicodedata.category(c) in ('Zp', 'Zl')]
</code></pre></div></div>

<p>It might seem a bit odd to define these lists, but lets circle back to this later. On to the next section</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>_space_char_regex = re.compile('[%s]' % re.escape(''.join(_space_chars)))
_line_break_char_regex = re.compile(
    '[%s]' % re.escape(''.join(_line_break_chars))
)
_null_char_regex = re.compile(re.escape(chr(0)))
</code></pre></div></div>
<p>This is a bit more straight forward. It’s just defining some regular expressions based on the above character sets and on the null character. The lists above are used solely to create these regular expressions.</p>

<p>And onto the main act; the function.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>def clean_string(value):
    return _line_break_char_regex.sub(
        '\n', _space_char_regex.sub(
            ' ', _null_char_regex.sub('', value)
        )
    ).strip()
</code></pre></div></div>
<p>Reading from inside out we:</p>
<ul>
  <li>Match on null characters and replace them with an empty string</li>
  <li>Match on character separators (ascii and unicode) and replace them an ascii space</li>
  <li>Match on line breaks and replace them with ascii line feed
Point 3 is a bit of a misnomer as the above code only matches on \n, \u2028 and \u2029.</li>
</ul>

<p>The latter two being the only character each in Zl and Zp respectively. As you might have expected given the run up; this function uses the regular expressions above to manipulate a string. Curiously though the only use of the regular expressions is to do substitutions and as it turns out the original author reinvented <a href="https://docs.python.org/3.8/library/stdtypes.html#str.replace">str.replace()</a>.</p>

<h1 id="complaints">Complaints</h1>
<p>Beyond the apparent desire to reinvent the standard library there are a number of complaints I had with this code. First the naming of <code class="language-plaintext highlighter-rouge">_all_chars</code> as it refers to a subset of all unicode characters or a superset of ascii characters, second <code class="language-plaintext highlighter-rouge">_line_break_chars</code> consists of two characters one of which doesn’t break a line, third the null character regex matches on one single character. More generally we spend a lot of time here regenerating constants. The null character for instance is simply <code class="language-plaintext highlighter-rouge">\0</code>. Why are we computing <code class="language-plaintext highlighter-rouge">chr(0)</code>? All of this for nought as well given that a simple easy to read alternative is faster.</p>

<h1 id="what-just-happened-now">What just happened now?</h1>
<p>The original author of this code no longer works at my company which is a shame. I’d really like to talk to them to understand what was going through their head. I’m fairly certain that the developer was unaware of the import behavior and thought that module level variables would be an optimization. Still, why reinvent the standard library?</p>

<h2 id="before">Before</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>_all_chars = [chr(i) for i in range(65535)]
_space_chars = [c for c in _all_chars if unicodedata.category(c) == 'Zs']
_line_break_chars = [c for c in _all_chars
                     if unicodedata.category(c) in ('Zp', 'Zl')]

_space_char_regex = re.compile('[%s]' % re.escape(''.join(_space_chars)))
_line_break_char_regex = re.compile(
    '[%s]' % re.escape(''.join(_line_break_chars))
)
_null_char_regex = re.compile(re.escape(chr(0)))


def clean_string(value):
    return _line_break_char_regex.sub(
        '\n', _space_char_regex.sub(
            ' ', _null_char_regex.sub('', value)
        )
    ).strip()
</code></pre></div></div>
<h2 id="after">After</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>def clean_string(value: str) -&gt; str:
    # Chain character replacements.
    # u2028 and 2029 are the unicode line and paragraph separators
    # \xa0 is a non-breaking space
    value = value.replace(chr(0), '')\
        .replace('\u2028', '\n')\
        .replace('\u2029', '\n')\
        .replace('\xa0', ' ')
    value = " ".join(value.split(r'(?!\n)\s'))  # Normalize non-\n whitespace
    return value.strip()
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Thanks for reading
</code></pre></div></div>]]></content><author><name></name></author><summary type="html"><![CDATA[Preamble Recently at work our primary piece of web code was hit with a memory leak. We all panicked of course, but then we all took a hard look at what was going on. This blog post doesn’t cover that memory leak, but covers another memory hog I found while digging into things.]]></summary></entry><entry><title type="html">Exploring x509 by creating a toy PKI</title><link href="/2020/01/24/openssl-pki.html" rel="alternate" type="text/html" title="Exploring x509 by creating a toy PKI" /><published>2020-01-24T01:00:00+00:00</published><updated>2020-01-24T01:00:00+00:00</updated><id>/2020/01/24/openssl-pki</id><content type="html" xml:base="/2020/01/24/openssl-pki.html"><![CDATA[<h1 id="pki-and-x509">PKI and x509</h1>
<p>I’ve had a good opportunity this week to play with PKIs at work and after reading a number of poorly sourced and downright incorrect posts I felt that I should make a post to summarize my experiences. First off what is a Public Key Infrastructure (PKI)? Broadly speaking a PKI is system that allows for the creation and distribution of public keys. In a PKI you tend to have certificates (certs) and certificate authorities (CAs) to manage them. A CA will issue signed certs which contain public keys, some information about the certificate holder and some lifetime for the certificate. These certs can be validated by checking the signature on them against public key of the CA. There are key distribution problems and trust issues here, but if you can deal with those you have a method to distribute trust. The term PKI can mean imply a number of specifics depending on the context, but for this post PKI refer to the x509 system defined by <a href="https://tools.ietf.org/html/rfc5280">RFC 5280</a>. For most web systems this will be the only relevant PKI.</p>

<h1 id="so-what-are-we-doing-here">So what are we doing here</h1>
<p>We’ve struggled with a number of disparate issues at work which all have the same basic trust pattern to them; we have some code out in the wild which we would like to talk to and we would like to know that we’re talking to our own code. Traditionally we’ve had solutions built around peered networks or fragile firewall rules, but a new situation arose for which those solutions would not work. We now need a method to determine the authenticity of some remote code running in an arbitrary network. Thankfully this is a problem that we can apply asymmetric cryptography to.</p>

<h1 id="certs-in-the-wild">Certs in the wild</h1>
<p>Lets consider the general web PKI for a moment. Websites offer up certs to their users which are signed by some trusted CA which may in turn be signed by some CA which they trust and so on up to what are called the Root CAs. If we inspect all the certs along this chain we see something like</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>someWebsite &lt;- someCA &lt;- ... &lt;- Root
</code></pre></div></div>
<p>Where each <code class="language-plaintext highlighter-rouge">&lt;-</code> represents a signing. When presented with the website cert we can verify these signatures and then use the associated public key to initiate a TLS connection to said website. It’s important to note that just the signature chain itself doesn’t imply any sort of trust, but if we trust the root then we can infer trust from this chain.</p>

<h1 id="a-toy-example">A toy example</h1>
<p>Lets construct a simple three step PKI to see how this works in practice. Something that looks like</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>leaf &lt;- intermediateCA &lt;- Root
</code></pre></div></div>
<p>This gives us a CA with which we can create certs for whatever service we have but also gives us a layer of indirection so that we can keep the root private key offline and rotate the intermediateCA if need be. We will use openssl to accomplish this and we’ll be using elliptic rather than RSA keys.<br />
First we need to generate our root keys which we can do with</p>

<h2 id="building-the-root">Building the root</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>❯❯❯ openssl ecparam -out root.pem -name secp384r1 -genkey
</code></pre></div></div>
<p>This will unsurprisingly create a file called <code class="language-plaintext highlighter-rouge">root.pem</code> which will contain a private key. In this example we use the <code class="language-plaintext highlighter-rouge">secp384r1</code> curve as it has a high security margin (in early 2020) and is widely compatible. If you want to explore other options give <code class="language-plaintext highlighter-rouge">openssl ecparam -list_curves</code> a look for supported curves. Anyway, reading this file you’ll see something like</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>❯❯❯ openssl ecparam -in root.pem -text
ASN1 OID: secp384r1
NIST CURVE: P-384
-----BEGIN EC PARAMETERS-----
BgUrgQQAIg==
-----END EC PARAMETERS-----
</code></pre></div></div>
<p>If we dig further we can read the private key information out as well, but I’ll leave that to the reader. Next we want to create a certificate which embeds the root private key and which is signed by the root private key. This is called a self signed cert and can be created with</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>❯❯❯ openssl req -new -key root.pem -out root.csr -subj "/C=US/ST=YourState/L=YourCity/O=YourOrganization/OU=YourUnit/CN=ThisIsMyRoot"
❯❯❯ openssl x509 -sha256 -in root.csr -out root.crt -req -signkey root.pem -days 30
</code></pre></div></div>
<p>You should see two new files in your directory; <code class="language-plaintext highlighter-rouge">root.csr</code> and <code class="language-plaintext highlighter-rouge">root.crt</code>. You can read your signing request with <code class="language-plaintext highlighter-rouge">openssl req -in root.csr -noout -text</code>
 which will dump out something like</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Certificate Request:
    Data:
        Version: 0 (0x0)
        Subject: C=US, ST=YourState, L=YourCity, O=YourOrganization, OU=YourUnit, CN=ThisIsMyRoot
        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
                Public-Key: (384 bit)
                pub:
                    04:5b:...
                ASN1 OID: secp384r1
                NIST CURVE: P-384
        Attributes:
            a0:00
    Signature Algorithm: ecdsa-with-SHA256
         30:64:...
</code></pre></div></div>
<p>Similarly you can read your root cert with <code class="language-plaintext highlighter-rouge">openssl x509 -in root.crt -text</code> and see something like</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Certificate:
    Data:
        Version: 1 (0x0)
        Serial Number: 10667317781089311673 (0x9409ed0108095bb9)
    Signature Algorithm: ecdsa-with-SHA256
        Issuer: C=US, ST=YourState, L=YourCity, O=YourOrganization, OU=YourUnit, CN=ThisIsMyRoot
        Validity
            Not Before: Jan 23 21:39:19 2020 GMT
            Not After : Feb 22 21:39:19 2020 GMT
        Subject: C=US, ST=YourState, L=YourCity, O=YourOrganization, OU=YourUnit, CN=ThisIsMyRoot
        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
                Public-Key: (384 bit)
                pub:
                    04:5b:...
                ASN1 OID: secp384r1
                NIST CURVE: P-384
    Signature Algorithm: ecdsa-with-SHA256
         30:66:...
-----BEGIN CERTIFICATE-----
MII...
-----END CERTIFICATE-----
</code></pre></div></div>
<p>Great. We’ve got our first certificate. Lets go ahead and verify the certificate</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>❯❯❯ openssl verify root.crt
root.crt: C = US, ST = YourState, L = YourCity, O = YourOrganization, OU = YourUnit, CN = ThisIsMyRoot
error 18 at 0 depth lookup:self signed certificate
OK
</code></pre></div></div>
<p>openssl sees that it’s a self signed cert, but errors out because it’s checking against the certificates preloaded into my operating system. We can overcome this by telling openssl to explicitly trust our new root cert which looks like</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>❯❯❯ openssl verify -CAfile root.crt root.crt
root.crt: OK
</code></pre></div></div>
<p>This may look a bit funny, but it checks out.</p>

<h2 id="the-intermediate-step">The intermediate step</h2>
<p>Creating the intermediate CA begins the same way; generate some keys, a signing request and a cert. The different will come at the cert step where we will use our root to sign our intermediate.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>❯❯❯ openssl ecparam -out inter.pem -name secp384r1 -genkey
❯❯❯ openssl req -new -key inter.pem -out inter.csr -subj "/C=US/ST=YourState/L=YourCity/O=YourOrganization/OU=YourUnit/CN=ThisIsMyIntermediate"
</code></pre></div></div>
<p>Before we create the intermediate CA cert we need to discuss x509 v3 extensions. Introduced as part of <a href="https://tools.ietf.org/html/rfc2459#section-3.1">RFC 2459 </a> the v3 extensions standardize what capabilities a given certificate is authorized for. Of immediate importance is the capability of being used as a CA. We need this for our application so we’ll create a text file with one line</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>❯❯❯ echo basicConstraints=critical,CA:true,pathlen:0 &gt; ext.txt
</code></pre></div></div>
<p>three things are happening here. First we’re setting the basicConstraints extension to be critical so that it should not be ignored, second we define that we want a cert which is usage as a CA and can sign other certs and third we set the path length to zero which makes it so that no children of this certificate can be a CA themselves.
Now create our intermediate cert with</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>❯❯❯ openssl x509 -req -sha256 -days 30 -in inter.csr -CA root.crt -CAkey root.pem -CAcreateserial -extfile ext.txt -out inter.crt
</code></pre></div></div>
<p>Lets recall that we created our root cert with the command</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>❯❯❯ openssl x509 -sha256 -in root.csr -out root.crt -req -signkey root.pem -days 30
</code></pre></div></div>
<p>and we see a few differences. First we’re passing the flags <code class="language-plaintext highlighter-rouge">-CA root.crt</code> and <code class="language-plaintext highlighter-rouge">-CAkey root.pem</code> which define the issuing CA and the signing key respectively. This is straight forward and this format will be used for anything downstream of the root cert. Second we see the flag <code class="language-plaintext highlighter-rouge">-CAcreateserial</code> which will create the serial number file if one is not provided and finally we see <code class="language-plaintext highlighter-rouge">-extfile ext.txt</code> which passes our newly created extensions file in so that we can get a certificate for a CA.</p>

<p>Reading the new intermediate cert we see something like</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>❯❯❯ openssl x509 -in inter.crt -text
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 18391173934830647663 (0xff3a9340cc00f96f)
    Signature Algorithm: ecdsa-with-SHA256
        Issuer: C=US, ST=YourState, L=YourCity, O=YourOrganization, OU=YourUnit, CN=ThisIsMyRoot
        Validity
            Not Before: Jan 23 22:50:32 2020 GMT
            Not After : Feb 22 22:50:32 2020 GMT
        Subject: C=US, ST=YourState, L=YourCity, O=YourOrganization, OU=YourUnit, CN=ThisIsMyIntermediate
        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
                Public-Key: (384 bit)
                pub:
                    04:b6:...
                ASN1 OID: secp384r1
                NIST CURVE: P-384
        X509v3 extensions:
            X509v3 Basic Constraints: critical
                CA:TRUE, pathlen:0
    Signature Algorithm: ecdsa-with-SHA256
         30:65:...
-----BEGIN CERTIFICATE-----
MII...
-----END CERTIFICATE-----
</code></pre></div></div>
<p>and we can verify it against the root with</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>❯❯❯ openssl verify -CAfile root.crt inter.crt
inter.crt: OK
</code></pre></div></div>

<h2 id="and-finally-the-leafs">And finally the leafs</h2>
<p>Now we can create the leaf certs in this setup. Begin as before with a key and signing request</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>❯❯❯ openssl ecparam -out leaf.pem -name secp384r1 -genkey
❯❯❯ openssl req -new -key leaf.pem -out leaf.csr -subj "/C=US/ST=YourState/L=YourCity/O=YourOrganization/OU=YourUnit/CN=ThisIsMyLeaf"
❯❯❯ echo basicConstraints=critical,CA:false &gt; leaf-ext.txt
</code></pre></div></div>
<p>For the leafs we don’t care about any extensions for the leaf and so we can create a simple cert with</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>❯❯❯ openssl x509 -req -sha256 -days 30 -in leaf.csr -CA inter.crt -CAkey inter.pem -CAcreateserial -extfile leaf-ext.txt -out leaf.crt
</code></pre></div></div>
<p>read it with</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>openssl x509 -in leaf.crt -text
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 15045666593868194343 (0xd0ccf20d4079a227)
    Signature Algorithm: ecdsa-with-SHA256
        Issuer: C=US, ST=YourState, L=YourCity, O=YourOrganization, OU=YourUnit, CN=ThisIsMyIntermediate
        Validity
            Not Before: Jan 23 22:59:46 2020 GMT
            Not After : Feb 22 22:59:46 2020 GMT
        Subject: C=US, ST=YourState, L=YourCity, O=YourOrganization, OU=YourUnit, CN=ThisIsMyLeaf
        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
                Public-Key: (384 bit)
                pub:
                    04:60:...
                ASN1 OID: secp384r1
                NIST CURVE: P-384
        X509v3 extensions:
            X509v3 Basic Constraints: critical
                CA:FALSE
    Signature Algorithm: ecdsa-with-SHA256
         30:64:...
-----BEGIN CERTIFICATE-----
MII...
-----END CERTIFICATE-----
</code></pre></div></div>
<p>and verify it with</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>❯❯❯ openssl verify -CAfile root.crt -untrusted inter.crt leaf.crt
leaf.crt: OK
</code></pre></div></div>

<h1 id="wrap-up">Wrap up</h1>
<p>In researching and writing this I came across a lot of conflicting information, outdated recommendations and some downright wrong information. In particular many posts mention the bundling of your root and intermediate certs into a single file and verifying the leaf as <code class="language-plaintext highlighter-rouge">openssl verify -CAfile bundle.crt leaf.crt</code>, however this will not not verify that that trust chain checks out and in fact if you omit the <code class="language-plaintext highlighter-rouge">CA:true</code> extension on the intermediate cert <code class="language-plaintext highlighter-rouge">openssl verify -CAfile bundle.crt leaf.crt</code> will pass while <code class="language-plaintext highlighter-rouge">openssl verify -CAfile root.crt -untrusted inter.crt leaf.crt</code> will fail. I’ve glossed over a lot here, but I hope you found this post useful.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Thanks for reading
</code></pre></div></div>]]></content><author><name></name></author><summary type="html"><![CDATA[PKI and x509 I’ve had a good opportunity this week to play with PKIs at work and after reading a number of poorly sourced and downright incorrect posts I felt that I should make a post to summarize my experiences. First off what is a Public Key Infrastructure (PKI)? Broadly speaking a PKI is system that allows for the creation and distribution of public keys. In a PKI you tend to have certificates (certs) and certificate authorities (CAs) to manage them. A CA will issue signed certs which contain public keys, some information about the certificate holder and some lifetime for the certificate. These certs can be validated by checking the signature on them against public key of the CA. There are key distribution problems and trust issues here, but if you can deal with those you have a method to distribute trust. The term PKI can mean imply a number of specifics depending on the context, but for this post PKI refer to the x509 system defined by RFC 5280. For most web systems this will be the only relevant PKI.]]></summary></entry><entry><title type="html">Porting test vectors with sed and awk</title><link href="/2019/12/10/sed-and-awk-gimli.html" rel="alternate" type="text/html" title="Porting test vectors with sed and awk" /><published>2019-12-10T19:13:00+00:00</published><updated>2019-12-10T19:13:00+00:00</updated><id>/2019/12/10/sed-and-awk-gimli</id><content type="html" xml:base="/2019/12/10/sed-and-awk-gimli.html"><![CDATA[<h1 id="intro">Intro</h1>
<p>Lately I’ve been working on a port of some C reference crypto code to rust for the <code class="language-plaintext highlighter-rouge">gimli</code> lightweight cipher and I wanted to make a quick post on some sed and awk which made helped me make part of the test suite.</p>

<h2 id="test-vectors">Test vectors</h2>
<p>When working with an algorithm which you don’t understand it’s critical to have a set of tests to let validate your code. In crypto terminology you have what are called <code class="language-plaintext highlighter-rouge">test vectors</code>. Each vector defines a set of inputs and an expected output i.e. <code class="language-plaintext highlighter-rouge">f(x1, x2, x3 ...) = y</code>. In my case I had a file of the form</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Count = 1
Key = 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F
Nonce = 000102030405060708090A0B0C0D0E0F
PT =
AD =
CT = 14DA9BB7120BF58B985A8E00FDEBA15B

Count = 2
Key = 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F
Nonce = 000102030405060708090A0B0C0D0E0F
PT =
AD = 00
CT = E8D50453F84B575412327D7C0302D8D3
...
</code></pre></div></div>
<p>The fields are the <code class="language-plaintext highlighter-rouge">Count</code> which indexes the test vector, the <code class="language-plaintext highlighter-rouge">Key</code> and <code class="language-plaintext highlighter-rouge">Nonce</code> which are constant for all vectors, the plaintext or <code class="language-plaintext highlighter-rouge">PT</code>, the associated data or <code class="language-plaintext highlighter-rouge">AD</code> and the ciphertext or <code class="language-plaintext highlighter-rouge">CT</code>. In this case the equality looks something like <code class="language-plaintext highlighter-rouge">gimli(PT, AD, nonce, key) = CT</code>.
The total count was 1089. Given the functions that I had made I wanted each of these available as a vector of <code class="language-plaintext highlighter-rouge">u8</code> values in my rust code.</p>

<p>The source of these test vectors can be seen in the path <code class="language-plaintext highlighter-rouge">gimli/Implementations/crypto_aead/gimli24v1/LWC_AEAD_KAT_256_128.txt</code> of the archive <a href="https://csrc.nist.gov/CSRC/media/Projects/lightweight-cryptography/documents/round-2/submissions-rnd2/gimli.zip">gimli.zip</a></p>

<h2 id="the-goal">The goal</h2>
<p>What I need to end up with is a collection of lines of the form</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(vec![], vec![], vec![0x14,0xDA,0x9B,0xB7,0x12,0x0B,0xF5,0x8B,0x98,0x5A,0x8E,0x00,0xFD,0xEB,0xA1,0x5B]),
(vec![], vec![0x00], vec![0xE8,0xD5,0x04,0x53,0xF8,0x4B,0x57,0x54,0x12,0x32,0x7D,0x7C,0x03,0x02,0xD8,0xD3]),
...
</code></pre></div></div>
<p>which is directly consumable and allows for a simple test loop</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>for vec in cipher_vectors.iter(){
    assert_eq!(vec.2, gimli_aead_encrypt(&amp;vec.0, &amp;vec.1, &amp;nonce, &amp;key));
    assert_eq!(vec.0, gimli_aead_decrypt(&amp;vec.2, &amp;vec.1, &amp;nonce, &amp;key));
}
</code></pre></div></div>

<h2 id="transformations">Transformations</h2>
<p>Lets say that the original file is simply called <code class="language-plaintext highlighter-rouge">vecs.txt</code>. How do I get to our goal without tedious manual edits? First off remove the lines I don’t need.<br />
<code class="language-plaintext highlighter-rouge">Count</code> is unnecessary so</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sed -i '/Count/d' vec.txt
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">Key</code> and <code class="language-plaintext highlighter-rouge">Nonce</code> are constant and so can be removed.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sed -i '/Key/d' vec.txt
sed -i '/Nonce/d' vec.txt
</code></pre></div></div>
<p>Similarly those blank lines aren’t of any use</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sed -i '/^\s*$/d' vec.txt
</code></pre></div></div>
<p>At this point our file is of the form</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PT =
AD =
CT = 14DA9BB7120BF58B985A8E00FDEBA15B
PT =
AD = 00
CT = E8D50453F84B575412327D7C0302D8D3
...
</code></pre></div></div>
<p>Next lets add the <code class="language-plaintext highlighter-rouge">vec![]</code> macro by matching on the <code class="language-plaintext highlighter-rouge">=</code> and the end of line.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sed -i 's/= /= vec![/' vec.txt
sed -i 's/$/]/' vec.txt
</code></pre></div></div>
<p>Yielding</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PT = vec![]
AD = vec![]
CT = vec![14DA9BB7120BF58B985A8E00FDEBA15B]
PT = vec![]
...
</code></pre></div></div>
<p>Now remove the first 5 characters of each line.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sed -i 's/^.....//' vec.txt
</code></pre></div></div>
<p>Resulting in</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>vec![]
vec![]
vec![14DA9BB7120BF58B985A8E00FDEBA15B]
...
</code></pre></div></div>
<p>I’d like to convert that hex into <code class="language-plaintext highlighter-rouge">0x</code> rust hex notation and I can do that with the following</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sed -i 's/\([A-Z0-9][A-Z0-9]\)/0x\1, /g' vec.txt
</code></pre></div></div>
<p>What’s happening here is a match group <code class="language-plaintext highlighter-rouge">\1</code> is being created on any matching of two elements with the upper alphanumeric character set. That match is prepended with <code class="language-plaintext highlighter-rouge">0x</code> and appended with a <code class="language-plaintext highlighter-rouge">,</code> which gives</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>vec![]
vec![]
vec![0x14, 0xDA, 0x9B, 0xB7, 0x12, 0x0B, 0xF5, 0x8B, 0x98, 0x5A, 0x8E, 0x00, 0xFD, 0xEB, 0xA1, 0x5B, ]
...
</code></pre></div></div>
<p>I’ve got extra commas and spaces at the end of each line, but rust is tolerant of that so I’ll leave it. I could clean up each line easily enough if desired, but I’ll leave that to the reader.</p>

<p>Now I need to compress three lines into one. I’m not aware of a simple sed method to do this, however awk is perfect for this.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>awk '{ printf "%s", $0; if (NR % 3 == 0) print ""; else printf " " }' vec.txt &gt; vecs.txt
</code></pre></div></div>
<p>What’s happening here is that awk is reading our input file <code class="language-plaintext highlighter-rouge">vec.txt</code> and printing each line without a newline character unless that line is modulo 3. I put this into a new file <code class="language-plaintext highlighter-rouge">vecs.txt</code> which has the form</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>vec![] vec![] vec![0x14, 0xDA, 0x9B, 0xB7, 0x12, 0x0B, 0xF5, 0x8B, 0x98, 0x5A, 0x8E, 0x00, 0xFD, 0xEB, 0xA1, 0x5B, ]
vec![] vec![0x00, ] vec![0xE8, 0xD5, 0x04, 0x53, 0xF8, 0x4B, 0x57, 0x54, 0x12, 0x32, 0x7D, 0x7C, 0x03, 0x02, 0xD8, 0xD3, ]
...
</code></pre></div></div>
<p>A bit more cleanup</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sed -i 's/ vec!/,vec!/g' vecs.txt
sed -i 's/, /,/g' vecs.txt
sed -i 's/$/)/' vecs.txt
sed -i 's/^/(/' vecs.txt
</code></pre></div></div>
<p>and now I’ve have our desired format</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(vec![],vec![],vec![0x14,0xDA,0x9B,0xB7,0x12,0x0B,0xF5,0x8B,0x98,0x5A,0x8E,0x00,0xFD,0xEB,0xA1,0x5B,])
(vec![],vec![0x00,],vec![0xE8,0xD5,0x04,0x53,0xF8,0x4B,0x57,0x54,0x12,0x32,0x7D,0x7C,0x03,0x02,0xD8,0xD3,])
...
</code></pre></div></div>
<p>wrap the whole thing in one more <code class="language-plaintext highlighter-rouge">vec![]</code> and I’ve got an iterable test vector.</p>

<h1 id="conclusion">Conclusion</h1>
<p>What’s taken place here is a series of simple text transformations which has taken a file of the format</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Count = 1
Key = 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F
Nonce = 000102030405060708090A0B0C0D0E0F
PT =
AD =
CT = 14DA9BB7120BF58B985A8E00FDEBA15B
</code></pre></div></div>
<p>to</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(vec![],vec![],vec![0x14,0xDA,0x9B,0xB7,0x12,0x0B,0xF5,0x8B,0x98,0x5A,0x8E,0x00,0xFD,0xEB,0xA1,0x5B,])
</code></pre></div></div>
<p>We’ve taken structured, but useless plaintext and converted it into a structure directly usable in a test suite. In fact you can see this code in my <a href="https://github.com/darakian/gimli">gimli repo</a>.</p>

<p>I hope this post has been helpful.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Thanks for reading.
</code></pre></div></div>]]></content><author><name></name></author><summary type="html"><![CDATA[Intro Lately I’ve been working on a port of some C reference crypto code to rust for the gimli lightweight cipher and I wanted to make a quick post on some sed and awk which made helped me make part of the test suite.]]></summary></entry><entry><title type="html">So you need a logging pipeline</title><link href="/2019/09/14/so-you-need-a-logging-pipeline.html" rel="alternate" type="text/html" title="So you need a logging pipeline" /><published>2019-09-14T01:00:00+00:00</published><updated>2019-09-14T01:00:00+00:00</updated><id>/2019/09/14/so-you-need-a-logging-pipeline</id><content type="html" xml:base="/2019/09/14/so-you-need-a-logging-pipeline.html"><![CDATA[<h1 id="you-have-chosen-or-have-been-chosen-to-implement-a-logging-pipeline">You have chosen or have been chosen to implement a logging pipeline</h1>
<p>So, some event has occurred within your organisation and now you need a logging pipeline. What do you do? Where do you begin? Most people come to holistic logging from some shock event like a hack event, a catastrophic failure, or some external requirement. Whether you’re reading this as a result of something tragic or simply trying to prepare I’ll share a story and some knowledge gained during my experience. Generally speaking this post will revolve around Mozilla’s fantastic <a href="https://github.com/mozilla/MozDef">MozDef</a> product which is a set of docker containers that work together to provide a logging sink, alerting logic and manual inspection interface.</p>

<h1 id="pipeline-requirements">Pipeline requirements</h1>
<p>In my case the primary task was to enable real time alerting on all logs coming out of our infrastructure. At the time we had a central logging sink provided by a third party which was in essence a dumping ground for log messages. We collected everything but then just dumped it on a server for some basic grep interactive like parsing later on. If you’ve come across the term <a href="https://en.wikipedia.org/wiki/Security_information_and_event_management">SIEM</a>, that’s what I needed.</p>

<h1 id="application-logging-vs-system-logging">Application Logging vs System Logging</h1>
<p>There are broadly two types of logging to consider when setting everything up: Application logging and System logging. Application logs are those logs which are generated within your business logic and which can be made to avoid the general system logger. If you can setup application logging then do so. The logs you will get out of application logging have a very good signal to noise ratio. System logging on the other hand refers to everything running on the system that would be picked up by the host system and is high noise as a result. It’s fairly common for business logic applications to log to the system, but it’s worth exploring your particular tech stack to see if a new logger can be added to the business logic itself to reduce noise.</p>

<h1 id="logs-are-inherently-unreliable">Logs are inherently unreliable</h1>
<p>You will lose logs. Sorry, but it will happen. Routing tables will be incorrectly configured, firewalls will be out of your control, deployed images or containers will be missing files or programs, logging code will have bugs and will crash, etc… Don’t worry about missing a few logs here and there. Take it as a given that logs will go missing and you should account for that. For what it’s worth missing logs can be some of the most informative of all.</p>

<h1 id="transport-options">Transport options</h1>
<p>Ok, so how should you emit your logs? Simple answer: http. Well https, but unless you have good reason to do otherwise just emit json over http. Yes there’s overhead, but every tool you will come across supports the combination of json over http and you get a protocol that firewalls usually don’t interfere with. Additionally http gives you arbitrary headers with which you can customise routing and simple authentication of logs. For example, you might find yourself in the situation where you need to accept logging input from networks outside of your own. You may also find yourself with no infrastructure in place to provision private keys and a deep desire to avoid white listing. If that’s the case make a header value along the lines of <code class="language-plaintext highlighter-rouge">X-My-Log-Input-Key</code> with a value along the lines of <code class="language-plaintext highlighter-rouge">ad1071715c1936bb308ea46a298c1b82132bba604eae633a280fd326f442b0e0</code> (created with <code class="language-plaintext highlighter-rouge">head /dev/random | shasum -a 512256</code>) and reject all http traffic for which that header is not correct. Obviously keep that header value secret and rotate as often as possible.</p>

<h1 id="rsyslog-over-http">(R)syslog over http</h1>
<p>While it’s nice to avoid if possible you may find yourself in the same unenviable situation as I did where all system logging needed to be captured and analysed. In the event this is the case, I’m just going to recommend rsyslog with the omhttp module. It may be a bit annoying to get the modern rsyslog integrated into your infrastructure, but it works on all major linux distros and lets you customise output. There are more and probably better options here, but rsyslog will get the job done. A simplified version of my rsyslog config can be found below.</p>

<h1 id="application-logging-over-http">Application logging over http</h1>
<p>This will be highly language and framework dependent, but as long as you have a logging subsystem you have the ability to make ship logs out of your application. Python in particular has a very nice logging framework where custom log handlers can be made and registered to provide custom output options. <a href="https://docs.python.org/3.7/library/logging.handlers.html">Python logging handlers</a>. A simple example of a mozdef compatible logging handler can be found below.</p>

<h1 id="mozillas-marvellous-mozdef">Mozilla’s Marvellous Mozdef</h1>
<p>Mozdef is essentially a <a href="https://www.elastic.co/what-is/elk-stack">ELK stack</a> with an analysis framework added on. This “MELK” stack provides all the normal elastic co goodness with a framework to schedule and run arbitrary queries on your log data. That and it’s dead simple to get going. Assuming you have docker and docker-compose installed, a basic setup can be started with</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://github.com/mozilla/MozDef.git
cd MozDef
make run
</code></pre></div></div>
<p>Why it’s controlled via makefile I’m not quite sure, but I don’t really care either. With that three line invocation you’ll be running a set of docker containers which accept json over http on <code class="language-plaintext highlighter-rouge">localhost:8080/events</code>, an alerting interface available at <code class="language-plaintext highlighter-rouge">localhost:80</code> and a kibana search interface on <code class="language-plaintext highlighter-rouge">localhost:9090</code>. If all you need is a basic log sink then you’re done. If you need to override environmental variables you can issue <code class="language-plaintext highlighter-rouge">make run-env-mozdef -e ENV=my.env</code> rather than <code class="language-plaintext highlighter-rouge">make run</code>. In the background mozdef will start up an elastic search database for itself, but by passing in an env file you can configure your mozdef instance to use an external elastic search DB. Similarly you can override the use of the built in kibana instance if you like. See the mozdef docs for more on what specifically can be overridden.</p>

<h1 id="alerting">Alerting</h1>
<p>While having an elk stack in a box is nice, the main draw to mozdef in particular is the alerting framework. In essence you write a python script to query your logs based on whatever metrics you care about and create alert objects based on the results. The alert objects will be passed through to the mozdef web interface and will be available for issue triage and tracking. There’s a simple on-boarding guide for writing alerts available here: <a href="https://mozdef.readthedocs.io/en/latest/alert_development_guide.html#example-first-alert">Example first alert</a>.</p>

<h1 id="wrap-up">Wrap up</h1>
<p>If you found yourself looking for an introduction to logging then I hope you’ve found this useful. Below are the rsyslog and python logging configs that I’ve used.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Thanks for reading
</code></pre></div></div>

<h3 id="rsyslog-output-for-mozdef">Rsyslog output for mozdef</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># Define json template.
$template mozdef_json,"{\"category\":\"syslog\",\"tags\":\"%SYSLOGTAG%\",\"processname\":\"%APP-NAME%\",\"processid\":\"%PROCID%\",\"PRI\":\"%PRI%\",\"PRI-Text\":\"%PRI-TEXT%\",\"severity\":\"%SYSLOGSEVERITY%\",\"hostname\":\"%HOSTNAME%\",\"summary\":\"%msg:::json%\",\"message id\":\"%MSGID%\",\"structured data\":\"%STRUCTURED-DATA%\"}"

module(load="omhttp")
action(
    type="omhttp"
    server="mymozdef.foo.bar.com"
    serverport="8080"
    restpath="events"
    dynRestPath="off"
    compress="off"
    usehttps="on"
    template="mozdef_json"
    httpheaderkey="X-My-Log-Input-Key"
    httpheadervalue="ad1071715c1936bb308ea46a298c1b82132bba604eae633a280fd326f442b0e0"
    errorfile="/var/log/rsyslog-mozdef-http-errors")
</code></pre></div></div>
<p>Full documentation for the omhttp module can be found here <a href="https://www.rsyslog.com/doc/v8-stable/configuration/modules/omhttp.html">Rsyslog omhttp</a></p>

<h3 id="simple-mozdef-logging-handler-for-python">Simple mozdef logging handler for python</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>import logging
import requests
import json
import socket

class MozDefHandler(logging.Handler):
    def __init__(self, broker=None, logging_key=None):
        super(MozDefHandler, self).__init__()
        self.broker = broker
        self.logging_key = logging_key
        self.thread_pool_executor = ThreadPoolExecutor(max_workers=1)

    def emit(self, record):
        log_entry = self.format(record)
        self.thread_pool_executor.submit(
            self.ship_log,
            log_dest=self.broker,
            log_entry=log_entry,
            record=record,
            logging_key=self.logging_key
        )

    def ship_log(self, log_dest=None, log_entry=None, record=None, logging_key=None):
        try:
            requests.post(log_dest,
                          headers={
                            'Content-type': 'application/json',
                            'Connection': None,
                            'X-Log-Key': logging_key
                          },
                          data=log_entry,
                          timeout=0.5
                          )
        except Exception:
            self.handleError(record)


class MozDefFormatter(logging.Formatter):
    def __init__(self, level=logging.INFO, category='web'):
        super(MozDefFormatter, self).__init__()
        self.category = category

    def format(self, record):
        # https://docs.python.org/3/library/logging.html#logrecord-attributes
        # https://mozdef.readthedocs.io/en/latest/usage.html#mandatory-fields
        dict_record = {}
        dict_record['category'] = self.category
        dict_record['hostname'] = socket.gethostname()
        dict_record['tags'] = []
        dict_record['module'] = record.module
        dict_record['source'] = record.pathname
        dict_record['severity'] = record.levelname
        dict_record['processid'] = record.process
        dict_record['processname'] = record.module
        dict_record['timestamp'] = record.created
        dict_record['details'] = {
                            'relativeCreatedTimestamp': record.relativeCreated,
                            'loglevel': record.levelno,
                            'linenumber': record.lineno
                            }
        dict_record['summary'] = record.getMessage()
        return json.dumps(dict_record)
</code></pre></div></div>]]></content><author><name></name></author><summary type="html"><![CDATA[You have chosen or have been chosen to implement a logging pipeline So, some event has occurred within your organisation and now you need a logging pipeline. What do you do? Where do you begin? Most people come to holistic logging from some shock event like a hack event, a catastrophic failure, or some external requirement. Whether you’re reading this as a result of something tragic or simply trying to prepare I’ll share a story and some knowledge gained during my experience. Generally speaking this post will revolve around Mozilla’s fantastic MozDef product which is a set of docker containers that work together to provide a logging sink, alerting logic and manual inspection interface.]]></summary></entry></feed>