Shellsharks Blogroll - BlogFlock 2026-05-06T08:02:53.312Z BlogFlock Adepts of 0xCC, destructured, fLaMEd, Trail of Bits Blog, Aaron Parecki, James' Coffee Blog, Westenberg, gynvael.coldwind//vx.log (pl), joelchrono, Evan Boehs, Kev Quirk, cool-as-heck, Posts feed, Sophie Koonin, <span>Songs</span> on the Security of Networks, cmdr-nova@internet:~$, Johnny.Decimal, Werd I/O, Robb Knight, Molly White, Hey, it's Jason!, Terence Eden’s Blog Hot Cross Buns - The Weblog of fLaMEd https://flamedfury.com/posts/hot-cross-buns/ 2026-05-06T14:58:18.000Z <p>What’s going on, Internet? I’ve been eating hot cross buns since January. Can you believe it? Growing up I remember that seasonal treats would come out a week or two before the associated holiday. As you might have noticed yourself, these days the bakeries, and supermarkets are pushing them out months before the event. For hot cross buns though, I’m not complaining.</p> <p>I’m not a big fan of raisins, or orange spice, and whatever else they use to make hot cross buns. But when they’re combined and baked and the end result is a fresh hot cross bun, I’m right there for all of it.</p> <p>While Easter is over now and my stash of hot cross buns has dried up, I’m both saddened and relieved. Since January I’ve been having at least one hot cross bun every morning with my hot chocolate (I gave up caffeine, and coffee with it, years ago). My favourite method of heating these delicious buns these days is in the air fryer. Cut in half and 170°C on the bake function for 4 minutes does the trick. Then a layer of butter. I’m not talking about a measly spread of butter. Nope. I’m talking about a slice of butter as thick as a slice of cheese. Then the goal is to eat it as quickly, but not too quickly before the butter melts and drips everywhere.</p> <p>Damn they’re so good.</p> <p><strong>Daily Bread</strong> were the standout, but at the price I only sprung for them once. They use an Italian sourdough starter, multiple awards every year, you can taste why. Daily Bread also bakes a collab bun for <strong>Farro Fresh</strong>, sold right alongside the originals — slightly bigger, half the price, probably not the same starter but still super good. <strong>Bakers Delight</strong> ran a little drier than those two, though the size meant I could load them up with even more butter.</p> <p>During our road trip down to Martinborough I got to try a few. <strong>The Stables</strong> in Greytown had a hot cross doughnutm the dough was good but the sugar coating wasn’t really my thing. I would have preferred a standard bun than the doughnut hybrid. <strong>The French Baker</strong>, also in Greytown, delivered the real deal. But the surprise was <strong>Jean’s</strong>, a small bakery in Upper Hutt that my wife loves to visit. Crazy delicious like all of their baked goods. Next year I’m getting a box shipped up to Auckland the moment they’re available. I was a bit gutted when I demolished the last one the other day.</p> <p>I’m relieved I get a break until next year. If they were around all year I’d be in real trouble. But who am I kidding? I’m already counting down to next year’s batch. 🤙</p> <p>Hey, thanks for reading this post in your feed reader! Want to chat? <a href="mailto:hello@flamedfury.com?subject=RE: Hot Cross Buns">Reply by email</a> or add me on <a href="xmpp:flamed@omg.lol">XMPP</a>, or send a <a href="https://flamedfury.com/posts/hot-cross-buns/#webmention">webmention</a>. Check out the <a href="https://flamedfury.com/posts/">posts archive</a> on the website.</p> Emotional regulation is a dying art. - Westenberg 69fa8961ccfa0c0001fd807f 2026-05-06T00:21:50.000Z <img src="https://www.joanwestenberg.com/content/images/2026/05/photo-1617209597807-5528a9767db7.jpeg" alt="Emotional regulation is a dying art."><p>There was a time when adults could feel something without screaming at you about it. We could disagree, hard, in a meeting and walk out with our faces still attached. Bad news arrived at the dinner table and the meal finished anyway. Call it discipline: the capacity to feel a thing in full and still choose what to do next.</p><p>That capacity is going the way of the Buffalo. You can watch it disappear in real time on any platform that rewards reaction; the faster the feedback loop, the worse the regulation. People are unleashing their feelings, unbounded and uninhibited, before they&#x2019;ve finished having them, which means they aren&#x2019;t really having them at all. They&#x2019;re skipping the inner step where a person sits with a sensation and decides whether or not it deserves to leave the body.</p><p>The new orthodoxy says suppressing emotion is harmful. This might be true, but outside of a therapist&#x2019;s office, it&#x2019;s trivial. Suppression and regulation are different animals. Suppression is shoving the feeling into a closet and pretending it isn&#x2019;t there until it crawls out twenty years later as an autoimmune disease; regulation is letting yourself feel the feeling, in full, while keeping your hands on the wheel of the car. We&#x2019;ve collapsed the distinction.</p><p>Look at how grown people now speak about minor friction. Someone disagrees with them at work and they describe it as &#x201C;harm.&#x201D; Someone fails to text back within an acceptable window and they say their boundary has been violated. The vocabulary of clinical psychology has been borrowed wholesale and applied to ordinary life, where it has begun to function as a permission slip. If every discomfort is trauma, then every reaction is justified, and the work of metabolizing your experience becomes either optional or the domain of the privileged.</p><p>This is downstream of a cultural shift that confuses authenticity with reactivity. The assumption is that whatever you feel first, raw and unmediated, is the real you, and anything else is a performance. In my experience, the opposite is closer to the truth. The real self is the part of you who survives the first reaction; the part that can be angry and still be kind, scared and still steady. The reactive self is a child throwing food.</p><p>Calling the food-throwing &#x201C;brave&#x201D; is a mistake.</p><p>Actual children, watching this, are absorbing the model with terrifying efficiency, and they&#x2019;re growing up in homes where the adults rage-text their bosses and stage public meltdowns in airport terminals while filming themselves. The lesson they&#x2019;ll enact is that feelings are emergencies, and emergencies require an audience. Walk into any third-grade classroom now and you&#x2019;ll find children who can name fourteen emotions and regulate none of them. They already have the vocabulary of a therapist but they maintain the impulse control of a puppy.</p><p>Some of this is technological. Phones reward a specific kind of nervous system, twitching first and thinking later. The dopamine architecture that hooks you on slot machines hooks you on outrage, and the platforms have figured out that a regulated person is a bad customer. The regulated close the app, but the dysregulated person scroll until four in the morning, bleeding cortisol and efficiently monetized.</p><p>But laming the phone lets too many people off the hook. Phones inherited the tantrum and scaled it; the deeper rot is philosophical. Several decades of therapeutic culture, well-meaning and badly executed, have taught generations that the goal of inner life is to express and never to contain. Containment has been rebranded as toxic, and composure rebranded as being cold.</p><p>For all my critiques of the philosophy&#x2019;s Reddit-bound adherents, the Stoics weren&#x2019;t automatons; Marcus Aurelius wept for his son, and Epictetus had been a slave whose owner crippled him for sport. They knew exactly how much the world hurt, and they wrote about it unapologetically. Their &#x201C;innovation&#x201D; was the claim that our hurt <em>is not the last word</em>. Between stimulus and response there exists a space, and in that space a person with agency can choose, and be responsible for that choice. A human being, however battered, retains a small and sovereign workshop where they make and remake and rebuild and mend themselves. That workshop is the only piece of territory that can&#x2019;t be confiscated by circumstance - lose access to it and you lose yourself.</p><p>A good many people now are locked out of their own workshops. They feel a thing and the thing feels them right back, and there&#x2019;s no daylight between the two.</p><p>The art of emotional regulation is dying because the conditions that taught it have been removed. We&#x2019;ve lost slow time and private time; we&#x2019;ve lost the time when no one asked what you thought before you&#x2019;d finished thinking it. A whole generation of children has watched the adults around them treat every passing affect as a press release, and they&#x2019;re learning to do the same. But you can&#x2019;t regulate what you&#x2019;ve already broadcast - and you can&#x2019;t reclaim a workshop you&#x2019;ve turned into a stage.</p> Building the future - Posts feed https://www.coryd.dev/posts/2026/building-the-future 2026-05-05T19:45:00.000Z <p>Watching the tech hype train while working in the industry has always felt a bit surreal. There's this nascent feeling that what I'm working on, while it pays my bills, either won't amount to much or is potentially a net negative. I'll admit that I'm predisposed to cynicism and will freely cast off jokes at the expense of what I observe. It's easy to do.</p> RSS Feeds Send Me More Traffic Than Google - Terence Eden’s Blog https://shkspr.mobi/blog/?p=70238 2026-05-05T11:34:12.000Z <p>Yeah yeah, I know, data-point of 1.</p> <p>I recently read <a href="https://susam.net/from-rss-to-atom.html">Susam&#39;s blog post</a> where they said that &#34;most of the traffic to my personal website still comes from web feeds&#34; - I wondered if that was true for my site.</p> <p>I&#39;ve been writing this blog for a while. I&#39;ve never much bothered with &#34;aggressive&#34; <abbr title="Search Engine Optimisation">SEO</abbr> - I have a fairly <a href="https://shkspr.mobi/blog/2026/01/maximally-semantic-structure-for-a-blog-post/">semantic layout</a>, all my <a href="https://shkspr.mobi/blog/2023/01/restaurant-review-metadata/">reviews have metadata</a>, and stuff like that - but I&#39;m not cramming in keywords, using AMP, or whatever other chickens Google requires to be sacrificed for a higher ranking. Nevertheless, I do OK.</p> <p>Last year, I added a bit of <a href="https://shkspr.mobi/blog/2025/09/reasonably-accurate-privacy-conscious-cookieless-visitor-tracking-for-wordpress/">local-only, lightweight statistics-gathering</a> to my blog. I can see which sites people click on to reach mine. Google is right up the top, DuckDuckGo is surprisingly high, Bing is lucky to crack the top 20 on any day. Similarly, I can see <a href="https://shkspr.mobi/blog/2025/11/now-witness-the-power-of-this-fully-operational-fediverse/">how much traffic I get from the Fediverse</a> and BlueSky (Twitter has all but vanished).</p> <p>A few weeks ago I added RSS and Newsletter tracking. These data are <em>very</em> lossy. If someone is subscribed to my RSS feed <em>and</em> opens a post <em>and</em> their client downloads a lazy-loaded image at the end of the post, I get a hit. For email it&#39;s broadly the same. If an email is opened and the tracker image is loaded, I get a hit (although <a href="https://gmail.googleblog.com/2013/12/images-now-showing.html">Gmail does obfuscate that somewhat</a>).</p> <p>I&#39;m not looking for super-accurate numbers (although I do block as many AI crawlers and bots as possible). I&#39;m not creepily following people around the web nor am I trying to sell them anything. I just want a rough idea of where people find me.</p> <p>Here are my blog&#39;s views for the last 28 days.</p> <img src="https://shkspr.mobi/blog/wp-content/uploads/2026/05/page-views.webp" alt="Atom 13774. Google 10833. RSS 10419. DuckDuckGo 2302. Email 2123." width="553" height="395" class="aligncenter size-full wp-image-71208 no-border-radius"/> <p>Some months I get a surge of hits from link aggregators like HN or Reddit. Sometimes I&#39;m linked to from a popular site or <a href="https://shkspr.mobi/blog/citations">cited in academic work</a>. But most of the time I bumble along getting hits from here, there, and everywhere. Nevertheless, it&#39;s lovely to see so many people choosing to subscribe<sup id="fnref:rss"><a href="https://shkspr.mobi/blog/2026/05/rss-feeds-send-me-more-traffic-than-google/#fn:rss" class="footnote-ref" title="For historic reasons, I have separate Atom and RSS feeds. Perhaps I should consider merging them? But it doesn&#39;t take much effort to publish in two subtly different formats." role="doc-noteref">0</a></sup> (for free!) and astonishing that they provide more traffic than a major search engine.</p> <p>Obviously, these are two <em>very</em> different types of traffic. People who are searching for a specific thing and stumble upon my blog are different from those who decide to like and subscribe.</p> <p>But, yeah, about 25% of my traffic comes from people who have chosen to subscribe.</p> <p>I&#39;m just delighted that so many people read my random thoughts.</p> <div id="footnotes" role="doc-endnotes"> <hr aria-label="Footnotes"/> <ol start="0"> <li id="fn:rss"> <p>For historic reasons, I have separate Atom and RSS feeds. Perhaps I should consider merging them? But it doesn&#39;t take much effort to publish in two subtly different formats. <a href="https://shkspr.mobi/blog/2026/05/rss-feeds-send-me-more-traffic-than-google/#fnref:rss" class="footnote-backref" role="doc-backlink">↩︎</a></p> </li> </ol> </div> <img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=70238&amp;HTTP_REFERER=Atom" alt="" width="1" height="1" loading="eager"/> C/C++ checklist challenges, solved - Trail of Bits Blog https://blog.trailofbits.com/2026/05/05/c/c-checklist-challenges-solved/ 2026-05-05T11:00:00.000Z <p>We recently added a <a href="https://appsec.guide/docs/languages/c-cpp/">C/C++ security checklist</a> to the Testing Handbook and <a href="https://blog.trailofbits.com/2026/04/09/master-c-and-c-with-our-new-testing-handbook-chapter/">challenged readers to spot the bugs in two code samples</a>: a deceptively simple Linux ping program and a Windows driver registry handler. If you found the <code>inet_ntoa</code> global buffer gotcha or the missing <code>RTL_QUERY_REGISTRY_TYPECHECK</code> flag, nice work. If not, here&rsquo;s a full walkthrough of both challenges, plus a deep dive into how the Windows registry type confusion escalates from a local denial of service to a kernel write primitive.</p> <p>Since we first released the new C/C++ security checklist, we also developed a new Claude skill, <a href="https://github.com/trailofbits/skills/tree/main/plugins/c-review">c-review</a>. It turns the checklist into bug-finding prompts that an LLM can run against a codebase. It&rsquo;s also platform and threat-model aware. Run these commands to install the skill:</p> <pre> claude skills add-marketplace https://github.com/trailofbits/skills claude skills enable c-review --marketplace trailofbits/skills </pre> <h2 id="the-linux-ping-program-challenge">The Linux ping program challenge</h2> <p>The Linux warmup challenge we showed you in the last blog post has an obvious command injection issue.</p> <figure class="highlight"> <pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;stdio.h&gt;</span><span class="cp"> </span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;stdlib.h&gt;</span><span class="cp"> </span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;string.h&gt;</span><span class="cp"> </span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;arpa/inet.h&gt;</span><span class="cp"> </span></span></span><span class="line"><span class="cl"><span class="cp"></span> </span></span><span class="line"><span class="cl"><span class="cp">#define ALLOWED_IP &#34;127.3.3.1&#34; </span></span></span><span class="line"><span class="cl"><span class="cp"></span> </span></span><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kt">char</span> <span class="n">ip_addr</span><span class="p">[</span><span class="mi">128</span><span class="p">];</span> </span></span><span class="line"><span class="cl"> <span class="k">struct</span> <span class="n">in_addr</span> <span class="n">to_ping_host</span><span class="p">,</span> <span class="n">trusted_host</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="c1">// get address </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nf">fgets</span><span class="p">(</span><span class="n">ip_addr</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">ip_addr</span><span class="p">),</span> <span class="n">stdin</span><span class="p">))</span> </span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="mi">1</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="n">ip_addr</span><span class="p">[</span><span class="nf">strcspn</span><span class="p">(</span><span class="n">ip_addr</span><span class="p">,</span> <span class="s">&#34;</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">)]</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="c1">// verify address </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nf">inet_aton</span><span class="p">(</span><span class="n">ip_addr</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">to_ping_host</span><span class="p">))</span> </span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="mi">1</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="kt">char</span> <span class="o">*</span><span class="n">ip_addr_resolved</span> <span class="o">=</span> <span class="nf">inet_ntoa</span><span class="p">(</span><span class="n">to_ping_host</span><span class="p">);</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="c1">// prevent SSRF </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="k">if</span> <span class="p">((</span><span class="nf">ntohl</span><span class="p">(</span><span class="n">to_ping_host</span><span class="p">.</span><span class="n">s_addr</span><span class="p">)</span> <span class="o">&gt;&gt;</span> <span class="mi">24</span><span class="p">)</span> <span class="o">==</span> <span class="mi">127</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="mi">1</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="c1">// only allowed </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nf">inet_aton</span><span class="p">(</span><span class="n">ALLOWED_IP</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">trusted_host</span><span class="p">))</span> </span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="mi">1</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="kt">char</span> <span class="o">*</span><span class="n">trusted_resolved</span> <span class="o">=</span> <span class="nf">inet_ntoa</span><span class="p">(</span><span class="n">trusted_host</span><span class="p">);</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="nf">strcmp</span><span class="p">(</span><span class="n">ip_addr_resolved</span><span class="p">,</span> <span class="n">trusted_resolved</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="mi">1</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="c1">// ping </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="kt">char</span> <span class="n">cmd</span><span class="p">[</span><span class="mi">256</span><span class="p">];</span> </span></span><span class="line"><span class="cl"> <span class="nf">snprintf</span><span class="p">(</span><span class="n">cmd</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">cmd</span><span class="p">),</span> <span class="s">&#34;ping &#39;%s&#39;&#34;</span><span class="p">,</span> <span class="n">ip_addr</span><span class="p">);</span> </span></span><span class="line"><span class="cl"> <span class="nf">system</span><span class="p">(</span><span class="n">cmd</span><span class="p">);</span> </span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="mi">0</span><span class="p">;</span> </span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre> </figure> <p>There are three validations that have to be bypassed before the <code>system</code> call can be reached with malicious inputs:</p> <ol> <li>The <a href="https://man7.org/linux/man-pages/man3/inet.3.html"><code>inet_aton</code> function</a> “converts the Internet host address from the IPv4 numbers-and-dots notation into binary form” and “returns nonzero if the address is valid, zero if not.” Theoretically, if we provide an invalid IPv4 string as input, then the program should return early.</li> <li>The <code>ntohl</code> call aims to prevent server-side request forgery (SSRF) attacks by disallowing addresses in 127.0.0.0/8 range.</li> <li>The parsed IP address is normalized with an <code>inet_ntoa</code> call and compared against the <code>ALLOWED_IP</code>. We are only allowed to ping localhost, which should not be possible given the SSRF check (making the code effectively broken with this configuration).</li> </ol> <p>The issue with the <code>inet_aton</code> function is that it <a href="https://sourceware.org/bugzilla/show_bug.cgi?id=20018">accepts trailing garbage</a>. This behavior is not documented on its man page, making it a likely source of vulnerabilities. In our challenge, one can simply send “127.0.0.1 ‘; anything #” as valid input.</p> <p>The gotcha with <code>inet_ntoa</code> is that it returns a pointer to a global buffer. Therefore, subsequent calls to the function overwrite previous outputs. In the challenge, <code>ip_addr_resolved</code> and <code>trusted_resolved</code> are the same pointer. When we provide “1.2.3.4” as input, <code>ip_addr_resolved</code> points to the string “1.2.3.4”, the SSRF check passes, the second call to <code>inet_ntoa</code> makes the <code>ip_addr_resolved</code> pointer point to &ldquo;127.3.3.1&rdquo;, and so the <code>strcmp</code> check passes too.</p> <p>There are a few more functions that return pointers to static buffers; these are documented in the new C/C++ Testing Handbook chapter.</p> <h2 id="the-windows-driver-registry-challenge">The Windows driver registry challenge</h2> <p>We showed you this Windows Driver Framework (WDF) request handler from a Windows driver and asked you to spot the bugs.</p> <figure class="highlight"> <pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="n">NTSTATUS</span> </span></span><span class="line"><span class="cl"><span class="nf">InitServiceCallback</span><span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="n">_In_</span> <span class="n">WDFREQUEST</span> <span class="n">Request</span> </span></span><span class="line"><span class="cl"><span class="p">)</span> </span></span><span class="line"><span class="cl"><span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">NTSTATUS</span> <span class="n">status</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="n">PWCHAR</span> <span class="n">regPath</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="kt">size_t</span> <span class="n">bufferLength</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="c1">// fetch the product registry path from the request </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="n">status</span> <span class="o">=</span> <span class="nf">WdfRequestRetrieveInputBuffer</span><span class="p">(</span><span class="n">Request</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">regPath</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">bufferLength</span><span class="p">);</span> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nf">NT_SUCCESS</span><span class="p">(</span><span class="n">status</span><span class="p">))</span> </span></span><span class="line"><span class="cl"> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="nf">TraceEvents</span><span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="n">TRACE_LEVEL_ERROR</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">TRACE_QUEUE</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="s">&#34;%!FUNC! Failed to retrieve input buffer. Status: %d&#34;</span><span class="p">,</span> <span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="n">status</span> </span></span><span class="line"><span class="cl"> <span class="p">);</span> </span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="n">status</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="cm">/* check that the buffer size is a null-terminated </span></span></span><span class="line"><span class="cl"><span class="cm"> Unicode (UTF-16) string of a sensible size */</span> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="n">bufferLength</span> <span class="o">&lt;</span> <span class="mi">4</span> <span class="o">||</span> </span></span><span class="line"><span class="cl"> <span class="n">bufferLength</span> <span class="o">&gt;</span> <span class="mi">512</span> <span class="o">||</span> </span></span><span class="line"><span class="cl"> <span class="p">(</span><span class="n">bufferLength</span> <span class="o">%</span> <span class="mi">2</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span> <span class="o">||</span> </span></span><span class="line"><span class="cl"> <span class="n">regPath</span><span class="p">[(</span><span class="n">bufferLength</span> <span class="o">/</span> <span class="mi">2</span><span class="p">)</span> <span class="o">-</span> <span class="mi">1</span><span class="p">]</span> <span class="o">!=</span> <span class="sa">L</span><span class="sc">&#39;\0&#39;</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="nf">TraceEvents</span><span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="n">TRACE_LEVEL_ERROR</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">TRACE_QUEUE</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="s">&#34;%!FUNC! Buffer length %d was incorrect.&#34;</span><span class="p">,</span> <span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="n">bufferLength</span> </span></span><span class="line"><span class="cl"> <span class="p">);</span> </span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="n">STATUS_INVALID_PARAMETER</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">ProductVersionInfo</span> <span class="n">version</span> <span class="o">=</span> <span class="p">{</span> <span class="mi">0</span> <span class="p">};</span> </span></span><span class="line"><span class="cl"> <span class="n">HandlerCallback</span> <span class="n">handlerCallback</span> <span class="o">=</span> <span class="n">NewCallback</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="kt">int</span> <span class="n">readValue</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="c1">// read the major version from the registry </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="n">RTL_QUERY_REGISTRY_TABLE</span> <span class="n">regQueryTable</span><span class="p">[</span><span class="mi">2</span><span class="p">];</span> </span></span><span class="line"><span class="cl"> <span class="nf">RtlZeroMemory</span><span class="p">(</span><span class="n">regQueryTable</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">RTL_QUERY_REGISTRY_TABLE</span><span class="p">)</span> <span class="o">*</span> <span class="mi">2</span><span class="p">);</span> </span></span><span class="line"><span class="cl"> <span class="n">regQueryTable</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">Name</span> <span class="o">=</span> <span class="sa">L</span><span class="s">&#34;MajorVersion&#34;</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="n">regQueryTable</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">EntryContext</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">readValue</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="n">regQueryTable</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">Flags</span> <span class="o">=</span> <span class="n">RTL_QUERY_REGISTRY_DIRECT</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="n">regQueryTable</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">QueryRoutine</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="n">status</span> <span class="o">=</span> <span class="nf">RtlQueryRegistryValues</span><span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="n">RTL_REGISTRY_ABSOLUTE</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">regPath</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">regQueryTable</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nb">NULL</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nb">NULL</span> </span></span><span class="line"><span class="cl"> <span class="p">);</span> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nf">NT_SUCCESS</span><span class="p">(</span><span class="n">status</span><span class="p">))</span> </span></span><span class="line"><span class="cl"> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="nf">TraceEvents</span><span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="n">TRACE_LEVEL_ERROR</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">TRACE_QUEUE</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="s">&#34;%!FUNC! Failed to query registry. Status: %d&#34;</span><span class="p">,</span> <span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="n">status</span> </span></span><span class="line"><span class="cl"> <span class="p">);</span> </span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="n">status</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="nf">TraceEvents</span><span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="n">TRACE_LEVEL_INFORMATION</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">TRACE_QUEUE</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="s">&#34;%!FUNC! Major version is %d&#34;</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="n">readValue</span> </span></span><span class="line"><span class="cl"> <span class="p">);</span> </span></span><span class="line"><span class="cl"> <span class="n">version</span><span class="p">.</span><span class="n">Major</span> <span class="o">=</span> <span class="n">readValue</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="n">version</span><span class="p">.</span><span class="n">Major</span> <span class="o">&lt;</span> <span class="mi">3</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="c1">// versions prior to 3.0 need an additional check </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="nf">RtlZeroMemory</span><span class="p">(</span><span class="n">regQueryTable</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">RTL_QUERY_REGISTRY_TABLE</span><span class="p">)</span> <span class="o">*</span> <span class="mi">2</span><span class="p">);</span> </span></span><span class="line"><span class="cl"> <span class="n">regQueryTable</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">Name</span> <span class="o">=</span> <span class="sa">L</span><span class="s">&#34;MinorVersion&#34;</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="n">regQueryTable</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">EntryContext</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">readValue</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="n">regQueryTable</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">Flags</span> <span class="o">=</span> <span class="n">RTL_QUERY_REGISTRY_DIRECT</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="n">regQueryTable</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">QueryRoutine</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="n">status</span> <span class="o">=</span> <span class="nf">RtlQueryRegistryValues</span><span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="n">RTL_REGISTRY_ABSOLUTE</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">regPath</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">regQueryTable</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nb">NULL</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nb">NULL</span> </span></span><span class="line"><span class="cl"> <span class="p">);</span> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nf">NT_SUCCESS</span><span class="p">(</span><span class="n">status</span><span class="p">))</span> </span></span><span class="line"><span class="cl"> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="nf">TraceEvents</span><span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="n">TRACE_LEVEL_ERROR</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">TRACE_QUEUE</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="s">&#34;%!FUNC! Failed to query registry. Status: %d&#34;</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="n">status</span> </span></span><span class="line"><span class="cl"> <span class="p">);</span> </span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="n">status</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="nf">TraceEvents</span><span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="n">TRACE_LEVEL_INFORMATION</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">TRACE_QUEUE</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="s">&#34;%!FUNC! Minor version is %d&#34;</span><span class="p">,</span> <span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="n">readValue</span> </span></span><span class="line"><span class="cl"> <span class="p">);</span> </span></span><span class="line"><span class="cl"> <span class="n">version</span><span class="p">.</span><span class="n">Minor</span> <span class="o">=</span> <span class="n">readValue</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nf">DoesVersionSupportNewCallback</span><span class="p">(</span><span class="n">version</span><span class="p">))</span> </span></span><span class="line"><span class="cl"> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">handlerCallback</span> <span class="o">=</span> <span class="n">OldCallback</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="nf">SetGlobalHandlerCallback</span><span class="p">(</span><span class="n">handlerCallback</span><span class="p">);</span> </span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre> </figure> <p>The intended behavior of the code is to read some software version information from the registry using the <code>RtlQueryRegistryValues</code> API, then select one of two possible callback functions depending on that version information.</p> <h3 id="an-attacker-controlled-registry-path">An attacker-controlled registry path</h3> <p>The first bug is that the path to the registry key is provided in the request, without validating the path string or checking that the caller is authorized to access the specified registry key. This means that anyone who can call into this handler can pick which registry key gets read, even if they ordinarily wouldn’t have access to that key. How this path string is interpreted depends on the <code>RelativeTo</code> parameter of the <code>RtlQueryRegistryValues</code> call. In this case, <code>RelativeTo</code> is set to <code>RTL_REGISTRY_ABSOLUTE</code>, which means that the path will be treated as an absolute path to a registry key object (e.g., <code>\Registry\User\CurrentUser</code>). There are two main reasons why this is a potential security issue.</p> <p>First, if an attacker can control which registry key is being read, then they can point it at a registry key they control the contents of, allowing them to further manipulate the driver behavior. This may lead to logical inconsistencies (e.g., the wrong callback being set) or, as we will see shortly, enable exploitation of security issues elsewhere in the code.</p> <p>Second, this enables a confused deputy attack that can be used to leak registry information that would normally be inaccessible to the user due to access controls. For example, a registry key might have a DACL applied that prevents normal users from enumerating its subkeys or reading any of the values inside those keys. Since the handler doesn’t check whether the call has sufficient rights to read the key, and the code emits a trace message and passes back the status code from <code>RtlQueryRegistryValues</code>, it can be used as an oracle to check for the existence of any registry key. It can also be used to leak any registry value named <code>MajorVersion</code> (and sometimes also <code>MinorVersion</code>) anywhere in the registry, but this is unlikely to be particularly useful in practice.</p> <h3 id="missing-type-checks-with-rtl_query_registry_direct">Missing type checks with RTL_QUERY_REGISTRY_DIRECT</h3> <p>The more serious bugs in this case arise from the flags set in the <code>RTL_QUERY_REGISTRY_TABLE</code> structs. The <code>RtlQueryRegistryValues</code> API takes in an array of these structs, terminated by an all-zero entry, to describe which registry values should be read from the specified key and how they should be processed and returned. There are two primary modes of operation here: callback or direct. In callback mode, which is the default, the <code>QueryRoutine</code> field of the struct points to a callback function that receives the value read from the registry. In direct mode, the <code>QueryRoutine</code> field is ignored and the value is instead written directly to a buffer whose location is passed in the <code>EntryContext</code> field. Direct mode is selected by including <code>RTL_QUERY_REGISTRY_DIRECT</code> in the <code>Flags</code> field.</p> <p>In our example, the <code>MajorVersion</code> value is read using the following code:</p> <figure class="highlight"> <pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="n">HandlerCallback</span> <span class="n">handlerCallback</span> <span class="o">=</span> <span class="n">NewCallback</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="kt">int</span> <span class="n">readValue</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="c1">// read the major version from the registry </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="n">RTL_QUERY_REGISTRY_TABLE</span> <span class="n">regQueryTable</span><span class="p">[</span><span class="mi">2</span><span class="p">];</span> </span></span><span class="line"><span class="cl"> <span class="nf">RtlZeroMemory</span><span class="p">(</span><span class="n">regQueryTable</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">RTL_QUERY_REGISTRY_TABLE</span><span class="p">)</span> <span class="o">*</span> <span class="mi">2</span><span class="p">);</span> </span></span><span class="line"><span class="cl"> <span class="n">regQueryTable</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">Name</span> <span class="o">=</span> <span class="sa">L</span><span class="s">&#34;MajorVersion&#34;</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="n">regQueryTable</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">EntryContext</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">readValue</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="n">regQueryTable</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">Flags</span> <span class="o">=</span> <span class="n">RTL_QUERY_REGISTRY_DIRECT</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="n">regQueryTable</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">QueryRoutine</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="n">status</span> <span class="o">=</span> <span class="nf">RtlQueryRegistryValues</span><span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="n">RTL_REGISTRY_ABSOLUTE</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">regPath</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">regQueryTable</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nb">NULL</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nb">NULL</span> </span></span><span class="line"><span class="cl"> <span class="p">);</span></span></span></code></pre> </figure> <p>Here, <code>RTL_QUERY_REGISTRY_DIRECT</code> is used to select direct mode, and the buffer points to <code>readValue</code>, which is an integer variable on the stack. You might notice something important, though: at no point has the code specified what type of value is being read, nor has it specified the size of the buffer. It is clear from the context that this code is expecting to read a <code>REG_DWORD</code>, but what if the <code>MajorVersion</code> value isn’t a <code>REG_DWORD</code>?</p> <h3 id="a-first-attempt-at-exploitation">A first attempt at exploitation</h3> <p>Let’s try to exploit this using a <code>REG_QWORD</code>. A <code>REG_DWORD</code> value is a 32-bit unsigned integer, whereas a <code>REG_QWORD</code> is a 64-bit unsigned integer, so if we make <code>MajorVersion</code> a <code>REG_QWORD</code> value instead, then we should be able to overwrite four bytes immediately after <code>readValue</code> on the stack. Since <code>HKEY_CURRENT_USER</code> is writable by low-privilege users, we can create a key somewhere in there, place a <code>REG_QWORD</code> value called <code>MajorVersion</code> in there, and pass the path of that key to the driver. And success, we get a BSOD!</p> <p>Except… it’s not quite what we wanted. The bugcheck code is <code>KERNEL_SECURITY_CHECK_FAILURE</code>, which isn’t really what we would expect if we successfully overwrote some of the stack. Why is this happening? The answer is in the <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-rtlqueryregistryvalues">documentation</a>:</p> <blockquote> <p>Starting with Windows 8, if an <code>RtlQueryRegistryValues</code> call accesses an untrusted hive, and the caller sets the <code>RTL_QUERY_REGISTRY_DIRECT</code> flag for this call, the caller must additionally set the <code>RTL_QUERY_REGISTRY_TYPECHECK</code> flag. A violation of this rule by a call from user mode causes an exception. A violation of this rule by a call from kernel mode causes a 0x139 bug check (<code>KERNEL_SECURITY_CHECK_FAILURE</code>).</p> <p>Only system hives are trusted. An <code>RtlQueryRegistryValues</code> call that accesses a system hive does not cause an exception or a bug check if the <code>RTL_QUERY_REGISTRY_DIRECT</code> flag is set and the <code>RTL_QUERY_REGISTRY_TYPECHECK</code> flag is not set. However, as a best practice, the <code>RTL_QUERY_REGISTRY_TYPECHECK</code> flag should always be set if the <code>RTL_QUERY_REGISTRY_DIRECT</code> flag is set.</p> <p>Similarly, in versions of Windows before Windows 8, as a best practice, an <code>RtlQueryRegistryValues</code> call that sets the <code>RTL_QUERY_REGISTRY_DIRECT</code> flag should additionally set the <code>RTL_QUERY_REGISTRY_TYPECHECK</code> flag. However, failure to follow this recommendation does not cause an exception or a bug check. This protective behavior was introduced as a response to <a href="https://learn.microsoft.com/en-us/security-updates/securitybulletins/2011/ms11-011">MS11-011</a>, in which this registry type confusion bug was first reported.</p> </blockquote> <p>To summarize, if you try to read from an untrusted registry hive using <code>RtlQueryRegistryValues</code> with <code>RTL_QUERY_REGISTRY_DIRECT</code> set but without also setting <code>RTL_QUERY_REGISTRY_TYPECHECK</code>, then Windows will automatically raise a bugcheck to crash the system and prevent the operation from succeeding.</p> <p>The <code>RTL_QUERY_REGISTRY_TYPECHECK</code> flag allows the caller to specify an expected type as part of the query table entry, thus mitigating the type confusion bug. Since this flag is not set in our example, a bugcheck will be triggered if we attempt to read from any registry hive other than the following trusted system hives:</p> <ul> <li><code>\REGISTRY\MACHINE\HARDWARE</code></li> <li><code>\REGISTRY\MACHINE\SOFTWARE</code></li> <li><code>\REGISTRY\MACHINE\SYSTEM</code></li> <li><code>\REGISTRY\MACHINE\SECURITY</code></li> <li><code>\REGISTRY\MACHINE\SAM</code></li> </ul> <p><code>HKEY_CURRENT_USER</code> is not included within this set, which explains why we saw the <code>KERNEL_SECURITY_CHECK_FAILURE</code> bugcheck when we tried to exploit it that way. This downgrades us from a potential kernel privilege escalation bug to a local denial of service. Still a bug, but not quite as exciting.</p> <h3 id="finding-writable-keys-in-trusted-hives">Finding writable keys in trusted hives</h3> <p>However, who says we can’t write values somewhere within these trusted hives? All it takes is a single key within one of those hives with a DACL that allows a lower-privileged user to write to it. Finding these isn’t too hard; the <a href="https://www.powershellgallery.com/packages/NtObjectManager/">NtObjectManager powershell module</a> has a command named <code>Get-AccessibleKey</code> that is perfect for the task:</p> <p><code>Get-AccessibleKey \Registry\Machine -Recurse -Access SetValue</code></p> <p>This command searches recursively within the <code>\Registry\Machine</code> object namespace for keys that the current process has permissions to set values within. Running it as a regular desktop user returns thousands of options that can be written without UAC elevation! Nice.</p> <p>However, for style points, we can go one step further. <a href="https://learn.microsoft.com/en-us/windows/win32/secauthz/mandatory-integrity-control">Mandatory integrity control (MIC)</a>, one of the key access control features in Windows that underpins UAC, allows processes to run with higher or lower privileges than would normally be assigned to the user that ran them. Most desktop processes run at the medium integrity level (IL). Elevating a process via UAC (often referred to as “run as administrator”) typically increases the process’s IL to high. There is also a low IL, which is often used to sandbox certain processes for security reasons, significantly limiting which resources they can access. Any securable object on Windows can have a mandatory label applied to its system access control list (SACL), and that mandatory label specifies the ILs that are allowed to access the object. The SACL is checked before the DACL, meaning that the IL check must pass even if the DACL would normally grant the user permissions to access the object. This means that a process running with a low-integrity security token cannot access a medium-integrity object, and a process running with a medium-integrity security token cannot access a high-integrity object. So, can we find any cases where we could write to one of the trusted system hives from a low-integrity process?</p> <p>To check for keys that are accessible at a low IL, the first thing we want to do is duplicate our process token and apply a low integrity label to it:</p> <p><code>$token = Get-NtToken -Primary -Duplicate -IntegrityLevel Low</code></p> <p>This gives us a copy of our current process’s security token that behaves as if we were running at a low IL. Using this, we then rerun the scan, passing in that modified token:</p> <p><code>Get-AccessibleKey \Registry\Machine -Recurse -Access SetValue -Token $token</code></p> <p>This does actually return a few results, on both Windows 10 and 11. Here are two of the most interesting:</p> <p><code>\REGISTRY\MACHINE\SOFTWARE\Microsoft\DRM</code> <code>\REGISTRY\MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\PlayReady\Troubleshooter</code></p> <p>Both of these keys allow a low-integrity token to write to them. The <code>DRM</code> key’s DACL has fairly complex permissions applied but grants the Set Value permission to the Everyone group. The <code>PlayReady\Troubleshooter</code> key’s DACL grants Full Control to Users, ALL APPLICATION PACKAGES, and ALL RESTRICTED APP PACKAGES. Either of these two keys can be abused to plant controlled registry values within a trusted system hive from a low privilege level.</p> <p>(Note: Whether or not the driver’s request endpoint can be called from a low IL is a different matter, but this is just for fun and style points, so let’s ignore that for now.)</p> <p>If we set a <code>REG_QWORD</code> value called <code>MajorVersion</code> in the <code>DRM</code> key, then pass that key’s path to the WDF handler, we can now overwrite four bytes of stack past the end of <code>readValue</code> with values that we control. Since <code>handlerCallback</code> was declared adjacent to <code>readValue</code>, there’s a chance that we can overwrite half of that function pointer! If that callback is called later, then we obtain partial control over the instruction pointer, which is a fairly strong primitive for local privilege escalation (LPE). This does depend on stack alignment, however, and it would not be surprising if the 32-bit <code>readValue</code> variable ended up 64-bit aligned, leaving a gap, so this approach may not get us far in practice.</p> <p>Can we do better?</p> <h3 id="a-string-is-a-type-of-integer-right">A string is a type of integer, right?</h3> <p>Ok, so far we’ve only explored what happens when we exploit the type confusion with <code>REG_QWORD</code>, but what happens if we use <code>REG_SZ</code>?</p> <p> <img src="https://blog.trailofbits.com/2026/05/05/c/c-checklist-challenges-solved/cc-checklist-challenges-solved-image-1_hu_fe8665a0cbfe6e6b.webp" alt="&ldquo;Samuel L. Jackson meme&rdquo;" width="972" height="794" loading="lazy" decoding="async" /> </p> <p>In the case of <code>REG_SZ</code> (i.e., a string value), the documentation says the following about <code>RtlQueryRegistryValues</code>’ behavior in direct mode:</p> <blockquote> <p>A null-terminated Unicode string (such as <code>REG_SZ</code>, <code>REG_EXPAND_SZ</code>): <code>EntryContext</code> must point to an initialized <code>UNICODE_STRING</code> structure. If the <code>Buffer</code> member of <code>UNICODE_STRING</code> is NULL, the routine allocates storage for the string data. Otherwise, it stores the string data in the buffer that <code>Buffer</code> points to.</p> </blockquote> <p>Let’s try exploiting this. <code>RtlQueryRegistryValues</code> will interpret the <code>EntryContext</code> field as if it were a <code>UNICODE_STRING</code> struct, but it’s actually pointing at <code>readValue</code>, which is an <code>int</code>. Here’s what a <code>UNICODE_STRING</code> looks like:</p> <figure class="highlight"> <pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">typedef</span> <span class="k">struct</span> <span class="n">_UNICODE_STRING</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">USHORT</span> <span class="n">Length</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="n">USHORT</span> <span class="n">MaximumLength</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="n">PWSTR</span> <span class="n">Buffer</span><span class="p">;</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="n">UNICODE_STRING</span><span class="p">,</span> <span class="o">*</span><span class="n">PUNICODE_STRING</span><span class="p">;</span></span></span></code></pre> </figure> <p>In the first call that the code makes to <code>RtlQueryRegistryValues</code>, when reading <code>MajorVersion</code>, the value of <code>readValue</code> has been initialized to zero. Since <code>readValue</code> is four bytes and a <code>USHORT</code> is two bytes, interpreting <code>readValue</code> as a <code>UNICODE_STRING</code> at that time will result in both <code>Length</code> and <code>MaximumLength</code> being zero and <code>Buffer</code> containing whatever’s immediately after <code>readValue</code> in the stack. Since the length of the buffer is zero, <code>RtlQueryRegistryValues</code> will just return <code>STATUS_BUFFER_TOO_SMALL</code> and not attempt to write to the <code>Buffer</code> field.</p> <p>However, let’s take a look at the second call to <code>RtlQueryRegistryValues</code>:</p> <figure class="highlight"> <pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="n">version</span><span class="p">.</span><span class="n">Major</span> <span class="o">=</span> <span class="n">readValue</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="n">version</span><span class="p">.</span><span class="n">Major</span> <span class="o">&lt;</span> <span class="mi">3</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="c1">// versions prior to 3.0 need an additional check </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="nf">RtlZeroMemory</span><span class="p">(</span><span class="n">regQueryTable</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">RTL_QUERY_REGISTRY_TABLE</span><span class="p">)</span> <span class="o">*</span> <span class="mi">2</span><span class="p">);</span> </span></span><span class="line"><span class="cl"> <span class="n">regQueryTable</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">Name</span> <span class="o">=</span> <span class="sa">L</span><span class="s">&#34;MinorVersion&#34;</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="n">regQueryTable</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">EntryContext</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">readValue</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="n">regQueryTable</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">Flags</span> <span class="o">=</span> <span class="n">RTL_QUERY_REGISTRY_DIRECT</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="n">regQueryTable</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">QueryRoutine</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="n">status</span> <span class="o">=</span> <span class="nf">RtlQueryRegistryValues</span><span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="n">RTL_REGISTRY_ABSOLUTE</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">regPath</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">regQueryTable</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nb">NULL</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nb">NULL</span> </span></span><span class="line"><span class="cl"> <span class="p">);</span> </span></span><span class="line"><span class="cl"> <span class="c1">// ... </span></span></span></code></pre> </figure> <p>This part of the code first checks if the <code>MajorVersion</code> value is less than three and, if so, reads the <code>MinorVersion</code> value using the same approach as before. A key observation here is that <code>readValue</code> is not reinitialized between the calls. This gives us some extra control: by leaving <code>MajorVersion</code> as a <code>REG_DWORD</code>, as originally intended by the code, we can have the first <code>RtlQueryRegistryValues</code> call load a value into <code>readValue</code>. Then, when the second call to <code>RtlQueryRegistryValues</code> is made, to read <code>MinorVersion</code>, we control the first four bytes of data pointed to by <code>EntryContext</code>. If <code>MinorVersion</code> is a <code>REG_SZ</code> value, a type confusion occurs where <code>RtlQueryRegistryValues</code> expects <code>EntryContext</code> to point to a <code>UNICODE_STRING</code>, causing the contents of the <code>MajorVersion</code> integer to be reinterpreted as the <code>Length</code> and <code>MaximumLength</code> fields. The only restriction is that we need the major version check to pass (i.e., <code>version.Major</code> must be less than 3) in order for the second registry query to take place. However, this turns out to be easy: if we set the <code>MajorVersion</code> value to <code>0xF000F002</code>, the code will interpret this as <code>-268374014</code> because <code>readValue</code> is a signed 32-bit integer. The <code>Length</code> and <code>MaximumLength</code> fields, however, are unsigned 16-bit integers, causing the <code>0xF000F002</code> value to get interpreted as the following when type confused as a <code>UNICODE_STRING</code>:</p> <figure class="highlight"> <pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="n">USHORT</span> <span class="n">Length</span> <span class="o">=</span> <span class="n">F000</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="n">USHORT</span> <span class="n">MaximumLength</span> <span class="o">=</span> <span class="n">F002</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="n">PWSTR</span> <span class="n">Buffer</span> <span class="o">=</span> <span class="o">????????</span><span class="err">`</span><span class="o">????????</span><span class="p">;</span></span></span></code></pre> </figure> <p>The <code>Buffer</code> field ends up pointing at whatever’s next in the stack. If we combine this current approach with the <code>REG_QWORD</code> trick from before, we can also overwrite four bytes of the <code>Buffer</code> pointer during the <code>MajorVersion</code> read. This means we partially control the address being written to, we fully control the length of what is written, and we can write any UTF-16 string there. This gets us a semi-controlled write-what-where primitive in the kernel. Nice!</p> <p>But can we do <em>even better</em>?</p> <h3 id="a-fully-controlled-stack-overwrite-with-reg_binary">A fully controlled stack overwrite with REG_BINARY</h3> <p>Let’s take a look at what happens if we try a <code>REG_BINARY</code> value instead. Here’s what the documentation has to say about such values in direct mode:</p> <blockquote> <p>Nonstring data with size, in bytes, greater than <code>sizeof(ULONG)</code>: The buffer pointed to by <code>EntryContext</code> must begin with a signed <code>LONG</code> value. The magnitude of the value must specify the size, in bytes, of the buffer. If the sign of the value is negative, <code>RtlQueryRegistryValues</code> will only store the data of the key value. Otherwise, it will use the first <code>ULONG</code> in the buffer to record the value length, in bytes, the second <code>ULONG</code> to record the value type, and the rest of the buffer to store the value data.</p> </blockquote> <p>This one is a bit more complicated, with two possible cases for the format of the buffer. In both cases, the buffer pointed to by <code>EntryContext</code> is expected to be prefilled with a signed <code>LONG</code> value that tells <code>RtlQueryRegistryValues</code> how large the buffer is. A <code>LONG</code> is just a 32-bit integer, so a signed <code>LONG</code> is functionally equivalent to <code>int</code> for this case. The interesting part is that this length value can either be positive or negative. If the value is negative, the API will copy the <code>REG_BINARY</code> data directly into the buffer pointed to by <code>EntryContext</code>. If the value is positive, it will first write the length of the <code>REG_BINARY</code> data into the first <code>ULONG</code> of the buffer, then it will write the <code>REG_BINARY</code> type value into the second <code>ULONG</code> of the buffer, and finally it will copy the <code>REG_BINARY</code> data into the remainder of the buffer.</p> <p>You may have figured out the exploit already here. The <code>MinorVersion</code> registry value is only read when the <code>MajorVersion</code> is less than 3. If we set <code>MajorVersion</code> to some negative number, this check will pass. This negative number ends up left in <code>readValue</code> for the second <code>RtlQueryRegistryValues</code> call. If the <code>MinorVersion</code> value is a <code>REG_BINARY</code>, <code>RtlQueryRegistryValues</code> treats the first <code>ULONG</code> in the “buffer” as being the signed length field. Since our “buffer” is just whatever was in <code>readValue</code> from the previous call, this causes <code>RtlQueryRegistryValues</code> to copy the contents of the registry value into the “buffer,” which is really just stack memory starting at <code>readBytes</code>. Since we control the magnitude of the negative number, we therefore control the purported length of the buffer, allowing us to control the length of the overwrite. And, since the contents of the <code>REG_BINARY</code> value can be anything we like, it means we control what is overwritten.</p> <p>For example, if we create a <code>REG_DWORD</code> value called <code>MajorVersion</code> with a value of <code>0xFFFFFFF4</code>, then create a <code>REG_BINARY</code> value called <code>MinorVersion</code> with a value of <code>00 00 00 00 DE AD BE EF DE AD BE EF</code>, this causes the first <code>RtlQueryRegistryValues</code> call to fill <code>readValue</code> with -12, which the second <code>RtlQueryRegistryValues</code> call interprets as a 12-byte buffer where only the binary should be copied. This results in <code>RtlQueryRegistryValues</code> copying <code>00 00 00 00</code> into <code>readValue</code>, then writing <code>DE AD BE EF DE AD BE EF</code> onto the stack afterwards. Assuming that the <code>handlerCallback</code> function pointer is stored after the <code>readValue</code> variable on the stack, we can now overwrite it with whatever we like. If this callback is invoked anywhere in the future, we gain control over the instruction pointer, leading to a kernel LPE.</p> <p>But can we do <em>even better still</em>? If you think you can, get in touch! We’d love to hear your tips and tricks.</p> <h2 id="your-turn">Your turn</h2> <p>These challenges only scratch the surface of what the <a href="https://appsec.guide/docs/languages/c-cpp/">C/C++ Testing Handbook chapter</a> covers—from seccomp sandbox escapes to Windows path traversal via WorstFit Unicode bugs. Read the chapter and follow the checklist against a codebase you know well. Pair it with a run of the <a href="https://github.com/trailofbits/skills/tree/main/plugins/c-review">c-review skill</a>, if you’re inclined. If you find a pattern we haven&rsquo;t documented yet, open a PR. We&rsquo;d especially love to hear from anyone who found a cleaner exploitation path for the driver challenge than the ones we showed here. And, as always, if you need help securing your C/C++ systems, <a href="https://www.trailofbits.com/contact/">contact us</a>.</p> Outrage is letting someone else set the frame - Westenberg 69f98b0fccfa0c0001fd4cfc 2026-05-05T06:20:33.000Z <img src="https://www.joanwestenberg.com/content/images/2026/05/photo-1750433595079-835ab0171e4a.jpeg" alt="Outrage is letting someone else set the frame"><p>William Randolph Hearst bought the New York Morning Journal in 1895 - and immediately started running stories designed to make his readers furious before they&#x2019;d finished their breakfast. The pages manufactured a mood, and that mood sold papers.</p><p>Three years later, when his correspondent Frederic Remington cabled from Cuba that there was no war to cover, Hearst replied with the line that became his epitaph: &#x201C;You furnish the pictures, and I&#x2019;ll furnish the war.&#x201D;</p><p>That was the original sin, and the original business model. Find a target, deliver the outrage daily, harvest the engagement and sell the audience to advertisers.</p><p>The economics haven&#x2019;t changed since 1895.</p><p>Walter Lippmann, writing in <em>Public Opinion</em> in 1922, described how a small class of editors and publicists had taken on the job of &#x201C;manufacturing consent&#x201D; by deciding which stories would be put in front of the public and to which framings they would be attached. He thought this was, on balance, fine. Most readers, he said, would never have the time or competence to form views from primary sources. Someone had to do the framing.</p><p><em>Someone.</em></p><p>A century on, the framing is automated // the framers are algorithms tuned to a single metric: <em>how long can we keep you scrolling?</em></p><p>Jonah Berger and Katherine Milkman, studying viral content for the <em>Journal of Marketing Research</em> in 2012, found that the emotion most reliably correlated with sharing was high-arousal anger. Neither sadness, contentment or even joy came close; anger was the engine. Every platform, and ever performer that lives on advertising eventually discovers this and bends its product around the discovery.</p><p>There is nothing neutral about the feed. </p><p>It&#x2019;s a slot machine built to dispense outrage on whatever schedule keeps you returning. The story in front of you was picked because an algorithm, somewhere, ran the numbers and concluded it would make you feel something corrosive enough to produce a reaction.</p><p>Whose ledger does that reaction land on? Theirs. Your time on the platform is sold to advertisers in increments, your annotated rage in the comments trains the recommendation model, your re-share recruits one more person into the same loop, and your dwell time on each story sharpens the algorithm&#x2019;s prediction of what will hold you next. The feeling moves through you and leaves a residue on the corporate balance sheet. You absorb the cost, and they book the revenue.</p><p>Chris Voss, the FBI&#x2019;s lead international kidnapping negotiator, wrote <em>Never Split the Difference</em> in 2016, on the principle that whoever feels more in a negotiation loses. Across the table from a hostage taker, Voss wouldn&apos;t match the emotional temperature of the room; he&apos;d lower it, keeping possession of his own pulse while everyone else lost theirs.</p><p>This is a useful posture to steal: you refuse to feel what you&#x2019;re being told to feel by people whose business model depends on the feeling, and the refusal is procedural. The feeling might turn out to be correct, or it might not; that&#x2019;s a separate question, evaluated later. Before the feeling arrives in full, you ask: who&#x2019;s pushing this into my field of view, what do they collect if I take the bait, what&#x2019;s the response they want from me, and does that response actually serve me?</p><p><em>Call it sequencing.</em></p><ul><li>Feel first and think second, the slot machine wins.</li><li>Think first and feel second (or not at all, depending), and the game stops paying out.</li></ul><p>Rage is metabolically expensive; borrowed rage even more so. You spend cognitive bandwidth on a quarrel imported from a stranger&#x2019;s algorithm, then arrive at your own work depleted, with less attention left for the people and projects you&#x2019;d actually choose. The outrage cycle runs on fuel siphoned from your real life. Mary Oliver asked what you plan to do with your one wild and precious life; for a lot of us the answer, when we&#x2019;re being honest, is: argue with people we&#x2019;ve never met about events we can&#x2019;t influence on a platform whose advertisers are paying for our distress.</p><p>But you can, of course, decline.</p><p>Tristan Harris, founding the Center for Humane Technology in 2018, framed it as recognising the asymmetry: a thousand engineers on one side of the screen, optimising for your attention; one tired person on the other side, trying to hold their ground. </p><p>Stop offering the response the system was built to extract, and watch what shows up in the space the rage used to occupy: the work you&#x2019;ve been avoiding, the conversation you owe someone you actually care about, the book on your nightstand, the neighbour you&#x2019;ve been meaning to call back. </p><p>The un-cinematic projects that don&#x2019;t trend and don&#x2019;t reward you with a hit of validation when you rage-post about them.</p><p>AKA: your actual life, previously crowded and smothered by the outrage.</p><div class="kg-card kg-cta-card kg-cta-bg-grey kg-cta-minimal " data-layout="minimal"> <div class="kg-cta-content"> <div class="kg-cta-content-inner"> <div class="kg-cta-text"> <p dir="ltr"><span style="white-space: pre-wrap;">Westenberg is designed, built and funded by Studio Self. </span><br><span style="white-space: pre-wrap;">Reach out and work with us.</span></p> </div> <a href="https://www.thisisstudioself.com/?ref=joanwestenberg.com" class="kg-cta-button " style="background-color: #000000; color: #ffffff;"> Learn more </a> </div> </div> </div> Night shift - James' Coffee Blog https://jamesg.blog/2026/05/05/night-shift 2026-05-05T00:00:00.000Z <p>Writing my post on <a href="https://jamesg.blog/2026/05/04/using-greyscale">how I use my phone in grayscale</a> yesterday got me thinking about other customisations I make to the computing devices I use. The first one that came to mind is that I keep my laptop, and by extension my external display when I am using it, in night shift mode at all times. <a href="https://support.apple.com/en-gb/118583">According to Apple, night shift mode</a> “adjusts the colours of your display to the warmer end of the spectrum – making the display easier on your eyes.”</p><p>I have had night shift mode enabled on my laptop for at least a year or two, and used night shift technology on previous computers in years prior. When night shift is enabled, my display feels less visually intense. I disabled the setting before writing this post and realised, as I feel with greyscale, that I had to toggle the mode back – the cool colours were too jarring now that I am used to night shift.</p><p>Night shift technology means that I see colours on computers differently. I even keep the setting on when I am designing websites and working with colours. My philosophy is that if text is readable in night shift mode, and has been designed with proper colour contrast in mind, it should look good when night shift mode is disabled. In contrast, if text is less readable in night shift mode, I may need to review my design.</p><p>Technically, night shift mode cannot be turned permanently on with an Apple Mac. Instead, I have a custom schedule set that enables night shift from 22:00 to 21:59, which means all day except for the minute between. I have the option selected to “Turn on until tomorrow” and have the colour temperature setting almost at the end of the spectrum to the “More warm” setting. Here is what the options I have set look like on my computer:</p><figure><picture><img alt="The Apple Night Shift window. This image is described in the previous paragraph." loading="lazy" src="https://editor.jamesg.blog/content/images/2026/05/nightshift.png" style=" max-width: 130%;"/></picture><div class="alt"><label><input aria-label="Toggle image alt text on screen" type="checkbox"/>ALT</label><div class="content">The Apple Night Shift window. This image is described in the previous paragraph.</div></div></figure><p>As with grayscale, night shift is part of how I experience my computer. I don’t think about night shift unless I am discussing it with someone.</p><p>In addition to using Apple’s night shift feature, I also have <a href="https://justgetflux.com/" rel="noreferrer">Flux</a> installed, an application that shifts the colour on your computer. I am not exactly sure how Flux interacts with the system-level settings, but I do know that my display gets warmer at night. I like the way I have things set up.</p><p>More broadly, I love that I can customise my computers in this way. Night shift makes it easier for me to work with my computer. This is especially important given the large amount of time I spend using computers. I don’t necessarily recommend having night shift mode on at all times, but I like it.</p> <a class="tag" href="https://jamesg.blog/2026/05/04/using-greyscale">how I use my phone in grayscale</a> <a class="tag" href="https://justgetflux.com/">Flux</a> <a class="tag" href="https://support.apple.com/en-gb/118583">According to Apple, night shift mode</a> Ribbon - a linkding client - Posts feed https://www.coryd.dev/posts/2026/ribbon-a-linkding-client 2026-05-04T22:08:00.000Z <p>I built a native iOS client for <a href="https://linkding.link">linkding</a> and launched it, as I usually do, with a sarcastic-ish Mastodon post. I'd been using my instance as a PWA (inasmuch as PWAs are supported on iOS) and found it to be <em>ok</em> but not terribly satisfying. I appreciated the existing clients, but didn't love them. I'd tried them all, but ended up generally using a <a href="https://apps.apple.com/us/app/linkding-web-extension/id6473717543">Safari extension</a> and the web app in Safari.</p> Bouncy castle, a Lego build and Children's Day - W18 - Joel's Log Files https://joelchrono.xyz/blog/2026-w18 2026-05-04T15:54:26.000Z <p>Obligatory May The 4th Be With You, by the way. Another week is over, I am still wearing my pajamas and enjoying a day off, which makes this Monday one of the best Mondays of the year thus far. Zero complaints!</p> <p>Here’s a question, when was the last time you actually just played like a kid? something like tag, or with toys and such? I did some fun stuff this week that made me feel twelve again, it was awesome. It was Children’s Day in Mexico on April 30th, so I guess it makes sense.</p> <ul> <li> <p>🧱 Had some time to assemble a Lego set! The <a href="https://www.lego.com/en-us/product/time-machine-from-back-to-the-future-77256">Time Machine from Back to the Future!</a>, I documented my progress <a href="https://polymaths.social/@joel/statuses/01KQ8Y6CCN668KA8VSBT291ERB">on my polymaths profile</a>, in case you want to see more pics.</p> </li> <li> <p>🥳 A friend of mine had a birthday party, and the highlight of the event was a bouncy castle! Most of the time people our age aren’t allowed to get into those, but this was exclusively for grown-up kids like me. We played for actual hours and all of my joints ended up kinda bruised and burned the next day. But it was absolutely worth it. I am really happy to have had that experience again. We played <em>tag</em> and similar games and ended up very tired, so fun!</p> </li> <li> <p>🐻 A while ago <a href="https://syls.blog/i-made-pixel-bears/">Syl made a pixel bear</a> for me! This was such a fun and unexpected gift to receive, but I hadn’t showed it off anywhere on my site yet. You can now find it on my footer section! Thank you again Syl, it’s a lovely gift. I also took it upon myself to make a few more pixel bears for other friends of mine, this is still work in progress though.</p> </li> <li> <p>🛍️ My parents returned from their short trip and brought back some epic stuff for me, I got two shirts from Avatar (my sister who they visited, picked them for me) and I am enamoured by the Appa one.</p> </li> <li> <p>🏕️ My church rented a <em>quinta</em> (a sort of ranch/country house where parties, weddings, quinceañeras and similar small events are held, often away from the city) for a day, to baptize a few new members of the congregation and celebrate Children’s Day at the same time! After the ceremonies, most families stayed to have lunch and enjoy the day, away from everything and enjoying the pool.</p> </li> <li> <p>🚲 For some reason… I have been interested on maybe acquiring a new bike and actually pedalling to places. I should find a used bycicle somewhere and get to it, I wonder if my shape is good enough for that, to be honest, but I want to try.</p> </li> <li> <p>🃏 I didn’t get in the pool myself, but I brought a bunch of card games, from <em>Exploding Kittens</em>, to <em>Coup</em>, to a pretty try-hard session of <em>Skull</em> and a pretty competitive run of <em>Oh Hell!</em> we also played your typical <em>Spoons</em> and <em>Cheat</em>. Seriously fun stuff when we got 6 or 7 players in many of these.</p> </li> <li> <p>🏀 During a break from card games I went to a small basketball court in the quinta, playing turns throwing the ball into the basket. I somehow did like 6 shots in a row that fell right in the center of the net. I was very surprised with myself to be honest.</p> </li> </ul> <figure class="img"> <picture> <source srcset="/assets/img/blogs/2026-05-04-weeknotes.webp" type="image/webp"/> <source srcset="/assets/img/blogs/2026-05-04-weeknotes.png" type="image/png"/> <img class="mx-auto" src="https://joelchrono.xyz/assets/img/blogs/2026-05-04-weeknotes.png" alt="A collage of the events happening this week. An assembled DeLorean, UFO 50 on my Nintendo Switch, a panel of a romance manga, a photo inside the bouncy castle, a frame from the 100 Meters movie, the Avatar t-shirts designs, my PSP with MGS Peace Walker, and a table with cards on top of it."/> </picture> <figcaption class="caption">A collage of the events happening this week. An assembled DeLorean, UFO 50 on my Nintendo Switch, a panel of a romance manga, a photo inside the bouncy castle, a frame from the 100 Meters movie, the Avatar t-shirts designs, my PSP with MGS Peace Walker, and a table with cards on top of it.</figcaption></figure> <h2 id="watching">Watching</h2> <p>Just yesterday I watched <strong>100 Meters</strong>, an anime movie, from the same author of <em>Orb: On the Movement of the Earth</em>—my favorite anime series watched this year so far—and it quickly showed me more of that spark that I got from Orb’s writing. This movie portrays the life of a child prodigy who loves to run, and helps another kid how to do it, with a quote that would kickstart a rivalry for years to come: <em>“Running the 100 meters faster than anyone else can solve almost anything.”</em></p> <p>Is that just a naive child with a lot to learn, or is it worth considering as a core value? There are many points this movie brings up as the film questions, celebrates and critiques the sport, as we wonder how we find the joy in giving all our body has to offer for something as seemingly simple as a 10 seconds sprint.</p> <p>The animation work here is incredible, switching styles a few times, from traditional animation, to some incredibly detailed rotoscoping, to an absolutely breath-taking continuous take, in ones of a race under heavy rain. This film is great, I implore you to watch it. There are a lot of life lessons to be learned and reflected upon with this movie. It’s raw and visceral, a work of art.</p> <h2 id="gaming">Gaming</h2> <p>I am happy to say that I have completed <strong>Metal Gear Solid: Peace Walker</strong>, which you probably already know since I mentioned it on my Monthly Summary a couple days ago. What a fantastic title that was, review coming soon though. Spoiler: go and play it.</p> <p>I decided to team up with <a href="https://jefklakscodex.com">Wouter</a> and play <strong>UFO 50</strong> at last. It’s a collection of 50 titles and has a sort of meta/alternate history background: these games were developed by a game company from the 80s, for their own console and all. Because of this, we agreed to treat these games as “rented” from Blockbuster or something, which is how I would have experienced these games in real life, playing one for a week straight and then moving to the next, finished or not. I am currently playing <strong>Barbuta</strong>, and having an interesting time. It’s pretty complicated and archaic, but it does have some charm to it, and getting familiar with the map and mechanics has been kinda satisfying. I may write mini-reviews for these.</p> <p>I am still playing <strong>Tomodachi Life: Living the Dream</strong> as well, and checking up with my Miis every day, there have been some fun shenanigans and things going on here, my Mii got married with Lucca, there are a couple of love triangles going on, and other drama that I find very funny and hilarious. My island is level 34 now. I should spend some time creating an island with an actual design though, I’ve been kinda lazy about it.</p> <h2 id="reading">Reading</h2> <ul> <li><strong>Smoking Behind the Supermarket with You</strong> - Up to chapter 60. I am fully caught up with this romance manga, it is a monthly release so now I have to find something else to read from start to finish I guess. I will patiently wait for how this story progresses. It’s really good stuff.</li> <li><strong>Heavenly Delusion</strong> - Up to chapter 43. I returned to this sci-fi mystery manga at last, I didn’t really read any new chapters, as I backtracked a few chapters to get familiar with where the story was going. It’s a very unique story with a confusing thread of events that have been slowly threading together. I really, really enjoy it so far.</li> <li><strong>Spy x Family</strong> - Up to chapter 133. A super fun arc just ended! and Anya got some really fun slice of life moments on this one. Enjoyable comedy as always.</li> <li><strong>Sakamoto Days</strong> - Up to chapter 229. Gotta admit I kinda lost the thread and need to read some more to get hooked again.</li> </ul> <h2 id="around-the-web">Around the Web</h2> <h3 id="blogposts">Blogposts</h3> <ul> <li><a href="https://brandons-journal.com/post/my-budget-emulation-pc">My Budget Emulation PC</a>, I’ve been wanting to do this too, but already got enough gaming devices.</li> <li><a href="https://brainbaking.com/post/2026/04/a-review-of-physical-nintendo-switch-publishers/">A Short Review Of Physical Nintendo Switch Publishers</a>. This is just a fun time, I am inclined to agree with the ratings given here.</li> <li><a href="https://anildash.com/2026/04/30/artemis-photos-flickr/">Why are the Artemis II photos on Flickr?</a>. A genuinely interesting look at how Flickr has evolved over the years. I am actually interested on revisiting that website. I actually made an account there when I was like 13 I think and shared some origami pictures on it.</li> <li><a href="https://hollie.eilloh.net/posts/putting-my-xteink-inside-my-lochby-mini-field-journal-makes-it-easy-to-take-notes-in-my-commonplace-book">Putting my XTEink inside my Lochby Mini Field Journal makes it easy to take notes in my commonplace book</a>. No more text is needed.</li> <li><a href="https://bojidar-bg.dev/blog/2026-04-29-garfield-attachment/">How I was cured of my Garfield attachment</a>. One of those totally random things I read and can’t stop. It’s a fun anecdote though.</li> </ul> <h3 id="youyube">YouYube</h3> <ul> <li><a href="https://youtu.be/it_EWg7ReYU">Deconstructing the Deadly Lighting of Resident Evil</a> - The production value of this video is off the charts, and the channel is surprisingly small. If you care about how videogames work I recommend keeping an eye on it.</li> <li><a href="https://youtu.be/-YQB2tE6-8U">Why Every Generation Uses This One Slang Word | Otherwords</a> - A very cool video. Watch it.</li> <li><a href="https://youtu.be/BzKXF0T75ps">Why aren’t actors ugly anymore?</a> - I keep noticing how perfect everyone looks on all the posters for Netflix produced movies (and most modern movies in general) and yeah, crazy stuff.</li> <li><a href="https://youtu.be/_GGO-IOQDoo">How We Keep Making Offices Worse</a> - This was kind of eye-opening. Very sad to see how things are. I am in one of those open office spaces and yeah, it’s meh.</li> </ul> <p>This is day 61 of <a href="https://100daystooffload.com">#100DaysToOffload</a></p> <p> <a href="mailto:me@joelchrono.xyz?subject=Bouncy castle, a Lego build and Children&#39;s Day - W18">Reply to this post via email</a> | <a href="https://fosstodon.org/@joel/116518368405523798">Reply on Fediverse</a> </p> [RSS Club] Where are you from? - Terence Eden’s Blog https://shkspr.mobi/blog/?p=71303 2026-05-04T11:34:40.000Z <p><mark><em>Psssst!</em> This <strong>top secret</strong> post is only available to RSS subscribers!</mark></p> <p>A little while ago I added some <a href="https://shkspr.mobi/blog/2025/09/reasonably-accurate-privacy-conscious-cookieless-visitor-tracking-for-wordpress/">locally hosted, privacy first stats</a> to my blog. Using an <a href="https://mailfud.org/geoip-legacy/">offline GeoIP service</a> I can get a <em>very</em> rough idea of where visitors are from.</p> <p>It doesn&#39;t deal with people using a VPN, or their mobile roaming to a different country, or rapid changes in IP allocation - but it&#39;s good enough for my purposes.</p> <p>Here&#39;s a quick table showing the vague distribution of RSS Club members.</p> <table> <thead> <tr> <th align="right"><strong>Country</strong></th> <th align="left"><strong>Flag</strong></th> <th align="right"><strong>Total Views</strong></th> <th align="right"><strong>Percentage</strong></th> </tr> </thead> <tbody> <tr> <td align="right">US</td> <td align="left">🇺🇸</td> <td align="right">6,242</td> <td align="right">24.1%</td> </tr> <tr> <td align="right">GB</td> <td align="left">🇬🇧</td> <td align="right">5,764</td> <td align="right">22.2%</td> </tr> <tr> <td align="right">DE</td> <td align="left">🇩🇪</td> <td align="right">1,947</td> <td align="right">7.5%</td> </tr> <tr> <td align="right">NL</td> <td align="left">🇳🇱</td> <td align="right">1,669</td> <td align="right">6.4%</td> </tr> <tr> <td align="right">CN</td> <td align="left">🇨🇳</td> <td align="right">1,027</td> <td align="right">4.0%</td> </tr> <tr> <td align="right">HK</td> <td align="left">🇭🇰</td> <td align="right">909</td> <td align="right">3.5%</td> </tr> <tr> <td align="right">AU</td> <td align="left">🇦🇺</td> <td align="right">770</td> <td align="right">3.0%</td> </tr> <tr> <td align="right">CA</td> <td align="left">🇨🇦</td> <td align="right">691</td> <td align="right">2.7%</td> </tr> <tr> <td align="right">FR</td> <td align="left">🇫🇷</td> <td align="right">605</td> <td align="right">2.3%</td> </tr> <tr> <td align="right">SE</td> <td align="left">🇸🇪</td> <td align="right">589</td> <td align="right">2.3%</td> </tr> <tr> <td align="right">JP</td> <td align="left">🇯🇵</td> <td align="right">442</td> <td align="right">1.7%</td> </tr> <tr> <td align="right">FI</td> <td align="left">🇫🇮</td> <td align="right">405</td> <td align="right">1.6%</td> </tr> <tr> <td align="right">CH</td> <td align="left">🇨🇭</td> <td align="right">395</td> <td align="right">1.5%</td> </tr> <tr> <td align="right">BR</td> <td align="left">🇧🇷</td> <td align="right">392</td> <td align="right">1.5%</td> </tr> <tr> <td align="right">ES</td> <td align="left">🇪🇸</td> <td align="right">345</td> <td align="right">1.3%</td> </tr> <tr> <td align="right">IT</td> <td align="left">🇮🇹</td> <td align="right">324</td> <td align="right">1.3%</td> </tr> <tr> <td align="right">PT</td> <td align="left">🇵🇹</td> <td align="right">285</td> <td align="right">1.1%</td> </tr> <tr> <td align="right">PL</td> <td align="left">🇵🇱</td> <td align="right">272</td> <td align="right">1.0%</td> </tr> <tr> <td align="right">BE</td> <td align="left">🇧🇪</td> <td align="right">249</td> <td align="right">1.0%</td> </tr> <tr> <td align="right">IN</td> <td align="left">🇮🇳</td> <td align="right">198</td> <td align="right">0.8%</td> </tr> <tr> <td align="right">CZ</td> <td align="left">🇨🇿</td> <td align="right">153</td> <td align="right">0.6%</td> </tr> <tr> <td align="right">TR</td> <td align="left">🇹🇷</td> <td align="right">134</td> <td align="right">0.5%</td> </tr> <tr> <td align="right">IE</td> <td align="left">🇮🇪</td> <td align="right">126</td> <td align="right">0.5%</td> </tr> <tr> <td align="right">BG</td> <td align="left">🇧🇬</td> <td align="right">121</td> <td align="right">0.5%</td> </tr> <tr> <td align="right">TW</td> <td align="left">🇹🇼</td> <td align="right">114</td> <td align="right">0.4%</td> </tr> <tr> <td align="right">SG</td> <td align="left">🇸🇬</td> <td align="right">110</td> <td align="right">0.4%</td> </tr> <tr> <td align="right">NZ</td> <td align="left">🇳🇿</td> <td align="right">99</td> <td align="right">0.4%</td> </tr> <tr> <td align="right">ZA</td> <td align="left">🇿🇦</td> <td align="right">97</td> <td align="right">0.4%</td> </tr> <tr> <td align="right">NO</td> <td align="left">🇳🇴</td> <td align="right">95</td> <td align="right">0.4%</td> </tr> <tr> <td align="right">AT</td> <td align="left">🇦🇹</td> <td align="right">91</td> <td align="right">0.4%</td> </tr> <tr> <td align="right">RU</td> <td align="left">🇷🇺</td> <td align="right">86</td> <td align="right">0.3%</td> </tr> <tr> <td align="right">DK</td> <td align="left">🇩🇰</td> <td align="right">84</td> <td align="right">0.3%</td> </tr> <tr> <td align="right">HU</td> <td align="left">🇭🇺</td> <td align="right">76</td> <td align="right">0.3%</td> </tr> <tr> <td align="right">GR</td> <td align="left">🇬🇷</td> <td align="right">64</td> <td align="right">0.2%</td> </tr> <tr> <td align="right">UA</td> <td align="left">🇺🇦</td> <td align="right">56</td> <td align="right">0.2%</td> </tr> <tr> <td align="right">IS</td> <td align="left">🇮🇸</td> <td align="right">49</td> <td align="right">0.2%</td> </tr> <tr> <td align="right">AE</td> <td align="left">🇦🇪</td> <td align="right">49</td> <td align="right">0.2%</td> </tr> <tr> <td align="right">GE</td> <td align="left">🇬🇪</td> <td align="right">44</td> <td align="right">0.2%</td> </tr> <tr> <td align="right">PR</td> <td align="left">🇵🇷</td> <td align="right">44</td> <td align="right">0.2%</td> </tr> <tr> <td align="right">LU</td> <td align="left">🇱🇺</td> <td align="right">42</td> <td align="right">0.2%</td> </tr> <tr> <td align="right">IL</td> <td align="left">🇮🇱</td> <td align="right">42</td> <td align="right">0.2%</td> </tr> <tr> <td align="right">PH</td> <td align="left">🇵🇭</td> <td align="right">41</td> <td align="right">0.2%</td> </tr> <tr> <td align="right">EE</td> <td align="left">🇪🇪</td> <td align="right">36</td> <td align="right">0.1%</td> </tr> <tr> <td align="right">AR</td> <td align="left">🇦🇷</td> <td align="right">36</td> <td align="right">0.1%</td> </tr> <tr> <td align="right">ID</td> <td align="left">🇮🇩</td> <td align="right">34</td> <td align="right">0.1%</td> </tr> <tr> <td align="right">MY</td> <td align="left">🇲🇾</td> <td align="right">31</td> <td align="right">0.1%</td> </tr> <tr> <td align="right">SI</td> <td align="left">🇸🇮</td> <td align="right">30</td> <td align="right">0.1%</td> </tr> <tr> <td align="right">BZ</td> <td align="left">🇧🇿</td> <td align="right">29</td> <td align="right">0.1%</td> </tr> <tr> <td align="right">TH</td> <td align="left">🇹🇭</td> <td align="right">29</td> <td align="right">0.1%</td> </tr> <tr> <td align="right">RO</td> <td align="left">🇷🇴</td> <td align="right">25</td> <td align="right">0.1%</td> </tr> <tr> <td align="right">VN</td> <td align="left">🇻🇳</td> <td align="right">24</td> <td align="right">0.1%</td> </tr> <tr> <td align="right">LT</td> <td align="left">🇱🇹</td> <td align="right">24</td> <td align="right">0.1%</td> </tr> <tr> <td align="right">MQ</td> <td align="left">🇲🇶</td> <td align="right">20</td> <td align="right">0.1%</td> </tr> <tr> <td align="right">KR</td> <td align="left">🇰🇷</td> <td align="right">20</td> <td align="right">0.1%</td> </tr> <tr> <td align="right">UN</td> <td align="left">🇺🇳</td> <td align="right">18</td> <td align="right">0.1%</td> </tr> <tr> <td align="right">RS</td> <td align="left">🇷🇸</td> <td align="right">17</td> <td align="right">0.1%</td> </tr> <tr> <td align="right">TN</td> <td align="left">🇹🇳</td> <td align="right">15</td> <td align="right">0.1%</td> </tr> <tr> <td align="right">CL</td> <td align="left">🇨🇱</td> <td align="right">14</td> <td align="right">0.1%</td> </tr> <tr> <td align="right">SK</td> <td align="left">🇸🇰</td> <td align="right">14</td> <td align="right">0.1%</td> </tr> <tr> <td align="right">MX</td> <td align="left">🇲🇽</td> <td align="right">14</td> <td align="right">0.1%</td> </tr> <tr> <td align="right">CO</td> <td align="left">🇨🇴</td> <td align="right">11</td> <td align="right">0.0%</td> </tr> <tr> <td align="right">BD</td> <td align="left">🇧🇩</td> <td align="right">10</td> <td align="right">0.0%</td> </tr> <tr> <td align="right">EC</td> <td align="left">🇪🇨</td> <td align="right">10</td> <td align="right">0.0%</td> </tr> <tr> <td align="right">EG</td> <td align="left">🇪🇬</td> <td align="right">10</td> <td align="right">0.0%</td> </tr> </tbody> </table> <p>There are a few more rows but, in the spirit of privacy, I&#39;ve not included some of the more unique countries. Not all of those are unique views - these are aggregate statistics. If your RSS reader is hosted in a different country - or on a large platform - it may only show up inaccurately.</p> <p>If you don&#39;t see your country in this list, please <a href="https://edent.tel/">drop me a comment via your favourite method</a>.</p> <img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=71303&amp;HTTP_REFERER=Atom" alt="" width="1" height="1" loading="eager"/> Quotes from Tufte’s course - James' Coffee Blog https://jamesg.blog/2026/05/04/quotes-from-tuftes-course 2026-05-04T00:00:00.000Z <p>I took Edward Tufte’s <a href="https://www.edwardtufte.com/online-course/">online course on analysing and presenting data</a>. It lasted four hours and was a mixture of reading from his books and listening to lecture material. The course was terrific and I learned so much. As I was taking the course, I took down a few quotes that stood out to me. I feel some of these points have applications that stretch further than data presentation.</p><p>Here is what I noted down. All quotes from Tufte.</p><ul><li>“It is not about how much information there is, but rather how effectively it is arranged.”</li><li>“The information is the interface.”</li><li>“They have come to your presentation for the <em>content.</em>” (Italics represent aural emphasis.)</li><li>“How do I/you/they really know that?”</li><li>“How could I/you/they possibly ever know that?”</li><li>“There are a million possible sequences, so let them [people] decide how to read it on their own.”</li></ul><p>I will be thinking about these for a while.</p><p>The last quote in particular was interesting to me. Early on in the course, Tufte walked through the visual layout of a U.S. National Weather Service website. Seen with his guidance, the information density and layout had new meaning – it was like I saw the page myself once and then saw it again with Tufte’s analysis. He noted how there was a lot of information on the page and people could go to whatever they needed or may find interesting, whether it be the temperature and summary forecast for the day or the more complex, data-rich weather maps.</p><p>The point I took away is that people will navigate how they want: an interface can put the information on the page and help people find for what they are looking but, ultimately, readers will read in their own way. With the web, people can scroll up and down to navigate information; people can become familiar with a website and skip to the part they want. <sup class="footnote-reference" id="f-1"><a href="https://jamesg.blog/longform-feed#1">1</a></sup></p><p>Tufte later went on to talk about how people read documents, which I think is the context in which I noted down the quote.</p><p>People skim, study, scrutinise, read, and re-read documents in their own way. When reading fiction, I sometimes read complex or important sentences twice to make sure I understand them. Whereas in another context, such as reading the news, I might skim more and focus on specific parts of the article – the first few paragraphs, the first sentences in the next few.</p><p>I don’t know exactly what analysis I have on this other than it makes sense, and that I never know what information someone might read or not read on a page. It is always interesting to learn about how people navigate information.</p> <div class="footnote-definition" id="1"><sup class="footnote-definition-label" id="f-2">1</sup> <p>One metaphor for skipping to a part of a web page is reading a book. I know to skip the first few pages that have meta information about a book. My cue of where to start reading is usually either the opening acknowledgement or the table of contents. I skim the first few pages so I can get to the main content in a book. So too do I scroll past different elements on web pages to find for what I am looking.</p> <a href="https://jamesg.blog/longform-feed#f-1">[↩]</a></div> <a class="tag" href="https://jamesg.blog/longform-feed#1">1</a> <a class="tag" href="https://jamesg.blog/longform-feed#f-1">[↩]</a> <a class="tag" href="https://www.edwardtufte.com/online-course/">online course on analysing and presenting data</a> Vikings and Mead - Cool As Heck https://cool-as-heck.blog/vikings-and-mead 2026-05-03T19:45:28.000Z <div>I've been thinking a lot about Vikings and mead lately, probably because I've recently started two batches fermenting in the basement: a spiced pomegranate and a pineapple coconut. It's funny how much mythology surrounds this drink versus what actually happened.</div> <div><br></div> <div>So here's the thing: Vikings didn't invent mead. Not even close. Archaeologists have found evidence of mead production in China from like 7000 BCE. That's roughly 8,000 years before the first Viking longship set sail. The stuff basically showed up independently all over the place: Africa, Greece, India. Turns out ancient humans everywhere figured out that honey + water + time = alcoholic goodness.</div> <div><br></div> <div>The Vikings were more like really enthusiastic fans who committed to the bit.<br> </div> <div><br></div> <div>If you were an average Viking just living your life, you probably weren't sipping mead daily. Honey was scarce in cold Scandinavia. Making mead took time and resources. It was the special occasion drink; feasts, weddings, religious ceremonies, sealing important deals.</div> <div><br></div> <div>For everyday drinking? Most Vikings had ale or beer. It was cheaper, easier to make, and safer than water since the fermentation process killed off bacteria. Kids even drank weak versions of it. I've heard that even today in Germany you can get your kid a weak ale with dinner.</div> <div><br></div> <div>Kind of makes you appreciate having access to whatever fruit you can find for experiments. Right now I'm curious how the pineapple coconut will turn out. I didn't really plan it out all that much, I'm mostly winging it, but I've learned enough over the last year or so to feel pretty comfortable with that.<br> </div> <div><br></div> <div>Okay, this part is my favorite. The Vikings didn't just drink mead, they literally believed it was magical.</div> <div><br></div> <div>There's this story about the Mead of Poetry. After a war between two groups of gods, they made peace by spitting into a bowl (yes, really). From that spit came Kvasir, the wisest being alive. Some dwarves killed him, mixed his blood with honey, and boom, magical mead that gave anyone who drank it the gift of poetry and wisdom.</div> <div><br></div> <div>Odin, being Odin, tricked his way into a mountain, seduced a giantess guarding the mead, drank ALL of it, and flew back to the gods as an eagle while spitting some out mid-flight. The stuff he spilled? That's why bad poets exist.</div> <div><br></div> <div>Also, in Valhalla there's a magical goat named Heiðrún standing on the roof eating tree leaves, and mead flows from her udders into a massive vat for fallen warriors. Norse mythology is something else.<br> </div> <div><br></div> <div>Here's a fun fact that doesn't make it into the movies: brewing was women's work. The mistress of the household was in charge of making ale and mead. It was a respected skill, often passed down through generations.</div> <div><br></div> <div>Some families had "magic sticks". These were wooden stirring sticks that harbored yeast from previous batches. They'd use the same stick to start fermentation, basically an early form of yeast cultivation. I'm over here pitching EC-1118 and D47 from a packet, but they were maintaining yeast colonies on birch sticks for generations. That's dedication.</div> <div><br></div> <div>At big feasts, the lady of the house would serve the first drink to her husband, then work her way through guests by rank. She basically controlled the whole vibe of the party.<br> </div> <div><br></div> <div>Did they actually drink from horns? Sometimes. Archaeologists have found plenty of drinking horns in Scandinavia, often decorated with silver or gold. But you can't set a horn down mid-drink without spilling, so they were more for ceremonial toasts.</div> <div><br></div> <div>For casual sipping throughout a meal? Wooden cups, bowls, imported glass for the wealthy. The famous Jelling Cup found in Denmark belonged to King Harald Bluetooth himself.</div> <div><br></div> <div>Mead's having a moment. Hundreds of new meaderies have opened in the last couple decades, and a lot of them lean hard into the Viking aesthetic. But I think what's cool is that the Vikings treated mead as more than just a beverage. It was about community, connection, marking important moments.</div> <div><br></div> <div>Maybe that's why it still resonates with hobbyists these days. In a world of mass-produced beer and cocktail trends, there's something deeply human about simply fermenting honey and water, waiting patiently, and sharing it with people you care about. It's a fun science experiment that also happens to be delicious and you can share it with your friends and family.</div> <div><br></div> <div>Anyway, that's where my head's been at since starting the spiced pomegranate and pineapple coconut batches. I even got some yeast similar to what the Vikings used for their ale and mead. I haven't decided what batch to use it with yet, but I'll try to keep posting updates on my batches here. TTFN. 🍺 🍯 </div> The way to save news is not to create monopolistic monocultures - Werd I/O 69f7630d825c3600019fd6a1 2026-05-03T15:00:29.000Z <p>Link: <a href="https://dicktofel.substack.com/p/some-rationalization-may-finally?ref=werd.io"><em>Some Rationalization May Finally Be Coming for Newsroom Intermediaries, by Richard J. Tofel in Second Rough Draft</em></a></p><p>In his latest post, Dick Tofel talks about a need for consolidation in organizations that support newsrooms (and, in fact, in newsrooms themselves).</p><blockquote>&#x201C;Devoting limited resources to competing services where one offering is superior not only leads those using the inferior service to poorer results, it also subsidizes entirely unnecessary administrative costs at the inferior service. And in circumstances where competing services are roughly equivalent, mere duplication can also be inefficient&#x2014;and, as noted above, may place an administrative burden on already over-stressed client newsrooms. Time is one of the scarcest resources of all.&#x201D;</blockquote><p>I&#x2019;m worried.</p><p>This isn&#x2019;t a criticism of Dick Tofel: he calls out the benefits of competition and the difficulty of determining winners in a market. But I do think there are two more things to consider.</p><p>The first is that I don&#x2019;t believe any intermediary service designed for newsrooms is optimal. That&#x2019;s not a criticism of <em>them</em>, either: <em>every</em> service has room to grow. Any time you remove competition from a market and hand it to a single privately-owned player (nationalized services are another thing entirely), the offerings stagnate because the driver to improve has gone away. Just ask anyone who remembers the web&#x2019;s Internet Explorer wilderness years before Firefox disrupted them and forced widespread standardization.</p><p>The second, and probably most important, is that funders are a narrow group of people with a narrow set of perspectives. Unless they&#x2019;ve done the work to be representative and inclusive in their work and culture, they may miss how one service serves a community better than another and erroneously mark them as duplicative. Or to put it another way, if there is any consolidation in <em>any</em> American market, I don&#x2019;t trust that organizations run by women and people of color won&#x2019;t be the ones to lose out.</p><p>This isn&#x2019;t anyone&#x2019;s intention, but reducing competition at any level &#x2014; funders, intermediaries, newsrooms, distributors &#x2014; has the potential to create monopolies that become gatekeepers for vulnerable communities who need more support, not less. I don&#x2019;t think that&#x2019;s what the moment we&#x2019;re living through needs. We need more ideas, more approaches, more funding, more communities served, and more diversity. The people who want to shut down an effective, independent press want to create a monoculture. The way to combat that is not to create another one.</p> Vertically Aligning Roman Numerals in Code - Terence Eden’s Blog https://shkspr.mobi/blog/?p=63775 2026-05-03T11:34:59.000Z <p>I have a PHP function which uses Roman Numerals. It looks like this:</p> <pre><code class="language-php">$romanNumerals = [ &#34;Ⅿ&#34; =&gt; 1000, &#34;ⅭⅯ&#34; =&gt; 900, &#34;Ⅾ&#34; =&gt; 500, &#34;ⅭⅮ&#34; =&gt; 400, &#34;Ⅽ&#34; =&gt; 100, &#34;ⅩC&#34; =&gt; 90, &#34;Ⅼ&#34; =&gt; 50, &#34;ⅩⅬ&#34; =&gt; 40, &#34;Ⅹ&#34; =&gt; 10, &#34;Ⅸ&#34; =&gt; 9, &#34;Ⅷ&#34; =&gt; 8, &#34;Ⅶ&#34; =&gt; 7, &#34;Ⅵ&#34; =&gt; 6, &#34;Ⅴ&#34; =&gt; 5, &#34;Ⅳ&#34; =&gt; 4, &#34;Ⅲ&#34; =&gt; 3, &#34;Ⅱ&#34; =&gt; 2, &#34;Ⅰ&#34; =&gt; 1 ]; </code></pre> <p>The problem is, the operators don&#39;t line up and the whole thing looks messy. Why? Because the <a href="https://shkspr.mobi/blog/2023/03/unicode-roman-numerals-and-screen-readers/">Unicode Roman Numerals</a> are <em>not</em> monospaced! <code>ⅭⅯ</code> is a different width to <code>ⅩC</code> and <code>Ⅷ</code> is only a single character! Copy the above to a text editor and see if you can get neat columns. I bet you can&#39;t!</p> <p>I&#39;m obsessed with <a href="https://shkspr.mobi/blog/2014/11/why-i-vertically-align-my-code-and-you-should-too/">vertically aligning my code</a>. So how to solve this ugly problem?</p> <p>The <a href="https://phpc.social/@Crell/115329116036130430">answer was simple</a>. Assign keys to the values and then flip the array!</p> <pre><code class="language-php">$romanNumerals = array_flip([ 1000 =&gt; &#34;Ⅿ&#34;, 900 =&gt; &#34;ⅭⅯ&#34;, 500 =&gt; &#34;Ⅾ&#34;, 400 =&gt; &#34;ⅭⅮ&#34;, 100 =&gt; &#34;Ⅽ&#34;, 90 =&gt; &#34;ⅩC&#34;, 50 =&gt; &#34;Ⅼ&#34;, 40 =&gt; &#34;ⅩⅬ&#34;, 10 =&gt; &#34;Ⅹ&#34;, 9 =&gt; &#34;Ⅸ&#34;, 8 =&gt; &#34;Ⅷ&#34;, 7 =&gt; &#34;Ⅶ&#34;, 6 =&gt; &#34;Ⅵ&#34;, 5 =&gt; &#34;Ⅴ&#34;, 4 =&gt; &#34;Ⅳ&#34;, 3 =&gt; &#34;Ⅲ&#34;, 2 =&gt; &#34;Ⅱ&#34;, 1 =&gt; &#34;Ⅰ&#34; ]); </code></pre> <p>There! Doesn&#39;t that look much neater!</p> <p><a href="https://libraries.mit.edu/150books/2011/05/11/1985/">As was written long ago</a>:</p> <blockquote><p>A computer language is not just a way of getting a computer to perform operations but rather … it is a novel formal medium for expressing ideas about methodology. Thus, programs must be written for people to read, and only incidentally for machines to execute.</p></blockquote> <img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=63775&amp;HTTP_REFERER=Atom" alt="" width="1" height="1" loading="eager"/> Open source maintainers need to go in with open eyes - Werd I/O 69f61044825c3600019fd69a 2026-05-02T14:55:00.000Z <p>Link: <a href="https://mirandaheath.website/report-on-burnout-in-open-source-software/?ref=werd.io"><em>Report on Burnout in Open Source Software, by Miranda Heath</em></a></p><p>This bleak report on burnout in open source software communities from last year has been doing the rounds. I think it&#x2019;s clearly indicative of where open source is at (and its trajectory), but the solutions aren&#x2019;t immediately clear &#x2014; we know this because similar concerns have been anecdotally highlighted by various people for well over a decade. However, this is the first formal research report I can remember reading.</p><p>It&#x2019;s pretty stark: 60% of open source maintainers work unpaid, 60% have quit or considered quitting, and 44% cite burnout specifically.</p><p>From the author:</p><blockquote>&#x201C;In my report, I draw upon a combination of academic literature and OSS community discussion to identify 6 factors that contribute OSS developer burnout: difficulty getting paid, workload and time commitment, maintenance work as unrewarding, toxic community behaviour, hyper-responsibility and pressure to prove oneself. I then make 4 broad recommendations for how to address it: pay OSS engineers, foster a culture of recognition and respect, grow the community and advocate for maintainers.&#x201C;</blockquote><p>The thing is: <em>who</em> is going to pay OSS engineers? Every attempt to get downstream users to pay out of the goodness of their hearts has failed at scale. There are certainly corporate sponsors of OSS maintainers already, when there&#x2019;s a clear link between an open source project and a company&#x2019;s bottom line. That could certainly be a broader standard, but there are also a ton of open source projects that tie less obviously into bottom lines, or are useful for communities outside large corporations.</p><p>Beyond the lack of direct compensation, it&#x2019;s also a pretty thankless job. Downstream users will often make demands of maintainers that don&#x2019;t take their contexts into account; people who are unpaid and overworked find themselves treated as if their users had paid them large sums of money. Open source users communities can sometimes also be wildly detached from reality; I&#x2019;ve had people tell me that the solution is to move beyond a money-driven society or to have a revolution to overthrow capitalism. I mean, sure, whatever, but is that going to happen tomorrow? In the meantime there&#x2019;s rent to be paid and food to buy.</p><p>The answer may be that, actually, open source doesn&#x2019;t work very well except (1) in mutualistic co-operative communities (2) as a strategic move by corporations who want to change the markets that affect them in some way (3) as a self-promotional way to gain more widespread recognition for your abilities. If that&#x2019;s the case, maintainers should go into it with open eyes: if they&#x2019;re successful, their work <em>will</em> be used by people who make a bunch of money without compensating them in any way.</p><p>If new maintainers understand that they&#x2019;re voluntarily signing up for extraction, I think it&#x2019;s uncomplicated. If they think they&#x2019;re going to &#x201C;win&#x201D; open source and make a living by giving their work away for free, they may be sorely disappointed.</p> Seasons - James' Coffee Blog https://jamesg.blog/2026/05/02/walking-5 2026-05-02T00:00:00.000Z <p>My first thought was the field of pink petals. Day by day, petals fall from the tree as green leaves grow from the blossom. <em>I wonder for how long the petals will keep falling.</em> There seem to be a million dots of pink in spring, from which there is a new perspective to see every day. Yesterday I saw a reflection. Today I saw growth.</p><p>I hear birds walking among the bushes; grey tits flutter their wings as they move from branch to branch. I paused to look at one bird and then I saw several. With every moment that passed I noticed something new – the stoic position of a bird perched atop a branch, more birds around the one that caught my attention. <em>The novel is on the periphery of the known.</em> The birdsong makes me smile – the sound of the new season.</p><p>Oddly, I started thinking of the prolonged period of cloudiness at the tail end of winter. I think winter came to mind because I have distance from it – today, I am with the weather to which I looked forward. I am not alone in being with winter in spring: the occasional brown leaf rests amid the growth of verdant bushes. I carry all the seasons with me. I have weathered weeks without sun, and now I walk among the blossom.</p><p>I continue on my walk, delighted by the warmth. The scent of grass permeates through the air. If spring had a scent let grass be it! If spring had a feeling, let it be the gentle breeze! But how could spring have but one of these phenomena? For spring is so abundant that there could not be one scent or feeling that encapsulates the season; the world is always bigger.</p><p>I look up to the sky and see incomprehensible distance, and wonder about all of the perspectives there are as yet undiscovered. The sky I see from the country path is infinite. How do you describe infinity? I feel a zest for life in every new question I ask. Does Nature end in space? How powerful the wondering.</p><p>Toward the end of my walk, I once again admire the pink petals which continue to fall as the sun dazzles through swaying branches. I pause so that I might make more of an impression in my mind of the blossom – that the beautiful, graceful image of falling blossom might make a memory that carries forward through the seasons. I love spring.</p> Making an original Jubilee line door button into a Hue light switch - localghost https://localghost.dev/blog/making-an-original-jubilee-line-door-button-into-a-hue-light-switch/ 2026-05-02T00:00:00.000Z <p>There are many things that make me extremely uncool, but one of my particularly nerdy interests is the London Underground. The evolution of its iconic map, showing long-forgotten stations; old wooden-floor train stock I remember from my childhood; the Hidden London tours of old stations, closed-off platforms and soot-covered stairwells with beautiful original tiling.</p> <div class="content-grid"> <figure> <picture><source type="image/webp" srcset="https://localghost.dev/img/BrnYiek36U-280.webp 280w, https://localghost.dev/img/BrnYiek36U-640.webp 640w, https://localghost.dev/img/BrnYiek36U-960.webp 960w" sizes="auto"><img loading="lazy" decoding="async" src="https://localghost.dev/img/BrnYiek36U-280.jpeg" alt="" width="960" height="1280" srcset="https://localghost.dev/img/BrnYiek36U-280.jpeg 280w, https://localghost.dev/img/BrnYiek36U-640.jpeg 640w, https://localghost.dev/img/BrnYiek36U-960.jpeg 960w" sizes="auto"></picture> <figcaption>"Station closed" poster at Aldwych</figcaption> </figure> <figure> <picture><source type="image/webp" srcset="https://localghost.dev/img/L4lK8_Ysbi-280.webp 280w, https://localghost.dev/img/L4lK8_Ysbi-640.webp 640w, https://localghost.dev/img/L4lK8_Ysbi-960.webp 960w" sizes="auto"><img loading="lazy" decoding="async" src="https://localghost.dev/img/L4lK8_Ysbi-280.jpeg" alt="" width="960" height="1280" srcset="https://localghost.dev/img/L4lK8_Ysbi-280.jpeg 280w, https://localghost.dev/img/L4lK8_Ysbi-640.jpeg 640w, https://localghost.dev/img/L4lK8_Ysbi-960.jpeg 960w" sizes="auto"></picture> <figcaption>Disused Jubilee line platform at Charing Cross, now used for filming</figcaption> </figure> </div> <p>I wouldn’t say I’m a Train Person per se - it’s the history I love, standing where people would have walked a hundred or more years ago. I think it satisfies the same part of my brain that loves secret passages in video games - something hidden and secret - and exploring long-abandoned buildings to learn about what came before in the Horizon games.</p> <p>The <a href="https://www.ltmuseum.co.uk/">London Transport Museum</a> (worth a visit, EXCELLENT shop) occasionally sells old bits of train and station paraphernalia on its website, including a load of original Jubilee line door button plates a few years back. Imagine my delight when I stumbled upon a <a href="https://www.reddit.com/r/LondonUnderground/comments/f3i98g/my_new_light_switch_i_made_from_a_decommissioned/">Reddit post</a> by a user called mw83 who turned a Jubilee line door button into a Philips Hue light switch, and wrote an <a href="https://www.instructables.com/Hacking-a-London-Underground-Jubilee-Line-Door-But/">Instructables tutorial</a> to boot! As it so happened, I was in need of both a Hue light switch for my office and a capital-P Project for my sabbatical, so this seemed like the perfect solution.</p> <p>(Here I’ll politely gloss over the fact that said sabbatical was in 2024, and I’ve only just finished.)</p> <div class="content-grid"> <picture><source type="image/webp" srcset="https://localghost.dev/img/RH3-zJZ-6x-280.webp 280w, https://localghost.dev/img/RH3-zJZ-6x-640.webp 640w, https://localghost.dev/img/RH3-zJZ-6x-960.webp 960w" sizes="auto"><img loading="lazy" decoding="async" src="https://localghost.dev/img/RH3-zJZ-6x-280.jpeg" alt="A wooden box containing a metal panel with a distinctive-looking square button in the middle. The button has an orange ring around it, and the word 'Open' with a pair of chevrons." width="960" height="1280" srcset="https://localghost.dev/img/RH3-zJZ-6x-280.jpeg 280w, https://localghost.dev/img/RH3-zJZ-6x-640.jpeg 640w, https://localghost.dev/img/RH3-zJZ-6x-960.jpeg 960w" sizes="auto"></picture> <picture><source type="image/webp" srcset="https://localghost.dev/img/6YOdn6Vsit-280.webp 280w, https://localghost.dev/img/6YOdn6Vsit-640.webp 640w" sizes="auto"><img loading="lazy" decoding="async" src="https://localghost.dev/img/6YOdn6Vsit-280.jpeg" alt="An action shot of my hand pressing the button - the orange ring has lit up." width="640" height="976" srcset="https://localghost.dev/img/6YOdn6Vsit-280.jpeg 280w, https://localghost.dev/img/6YOdn6Vsit-640.jpeg 640w" sizes="auto"></picture> </div> <p>I’d missed the door buttons on the Museum shop, but managed to snag a pair off eBay. They were absolutely filthy - covered in decades of underground grime.</p> <figure> <picture><source type="image/webp" srcset="https://localghost.dev/img/52-lhfmT-W-280.webp 280w, https://localghost.dev/img/52-lhfmT-W-640.webp 640w, https://localghost.dev/img/52-lhfmT-W-960.webp 960w" sizes="auto"><img loading="lazy" decoding="async" src="https://localghost.dev/img/52-lhfmT-W-280.jpeg" alt="A very grimy metal plate with a button. The button is a square with rounded corners, the plastic ring around the button so dirty it looks black. The button itself has a pair of raised chevrons pointing left, and the word 'Open'." width="960" height="1280" srcset="https://localghost.dev/img/52-lhfmT-W-280.jpeg 280w, https://localghost.dev/img/52-lhfmT-W-640.jpeg 640w, https://localghost.dev/img/52-lhfmT-W-960.jpeg 960w" sizes="auto"></picture> <figcaption>You've heard of Left Shark, this is Left Jubilee Line Door Button.</figcaption> </figure> <h2 id="cleaning-up" tabindex="-1">Cleaning up</h2> <p>The first step was to give them the scrubbing of a lifetime. I disassembled the buttons and got to work with a toothbrush. It took ages and it was boring as shit, but I did get the front plate gleaming. Even after cleaning, the orange glow rings were looking a bit worse for wear, with cracks that had quite a lot of dirt embedded.</p> <figure> <picture><source type="image/webp" srcset="https://localghost.dev/img/7Rz34eosVW-280.webp 280w, https://localghost.dev/img/7Rz34eosVW-640.webp 640w, https://localghost.dev/img/7Rz34eosVW-960.webp 960w" sizes="auto"><img loading="lazy" decoding="async" src="https://localghost.dev/img/7Rz34eosVW-280.jpeg" alt="The disassembled front plate, button and light ring. The dirt is very visible - it's caked all around the button fixture ." width="960" height="720" srcset="https://localghost.dev/img/7Rz34eosVW-280.jpeg 280w, https://localghost.dev/img/7Rz34eosVW-640.jpeg 640w, https://localghost.dev/img/7Rz34eosVW-960.jpeg 960w" sizes="auto"></picture> <figcaption>Look at all that grime!</figcaption> </figure> <div class="content-grid"> <figure> <picture><source type="image/webp" srcset="https://localghost.dev/img/vUWUETp-0H-280.webp 280w, https://localghost.dev/img/vUWUETp-0H-640.webp 640w, https://localghost.dev/img/vUWUETp-0H-960.webp 960w" sizes="auto"><img loading="lazy" decoding="async" src="https://localghost.dev/img/vUWUETp-0H-280.jpeg" alt="The back of the door button, with the back cover removed to reveal the electronics powering the button. There are large purple wires connecting the back cover to the board." width="960" height="1280" srcset="https://localghost.dev/img/vUWUETp-0H-280.jpeg 280w, https://localghost.dev/img/vUWUETp-0H-640.jpeg 640w, https://localghost.dev/img/vUWUETp-0H-960.jpeg 960w" sizes="auto"></picture> <figcaption>Peeking at the electronics</figcaption> </figure> <figure> <picture><source type="image/webp" srcset="https://localghost.dev/img/I4r3LYZDjO-280.webp 280w, https://localghost.dev/img/I4r3LYZDjO-640.webp 640w, https://localghost.dev/img/I4r3LYZDjO-960.webp 960w" sizes="auto"><img loading="lazy" decoding="async" src="https://localghost.dev/img/I4r3LYZDjO-280.jpeg" alt="The button components and front plate, cleaned and looking very shiny. The button is orange once again, though the orange light ring is still quite dirty" width="960" height="1280" srcset="https://localghost.dev/img/I4r3LYZDjO-280.jpeg 280w, https://localghost.dev/img/I4r3LYZDjO-640.jpeg 640w, https://localghost.dev/img/I4r3LYZDjO-960.jpeg 960w" sizes="auto"></picture> <figcaption>Good as new (almost)!</figcaption> </figure> </div> <h2 id="wiring-it-up" tabindex="-1">Wiring it up</h2> <p>The next step of the tutorial with the actual electronics was a bit, well, <a href="https://knowyourmeme.com/memes/how-to-draw-an-owl">“draw the rest of the owl”</a>. I had no idea what I was doing and it didn't really go into enough detail to hold my hand through it. Ultimately I hadn’t done much in the way of electronics since GCSE Physics 20 years ago - growing up I’d always resented that the boys’ school across the road got electronics classes, while the girls’ school did Textiles (though I do also love sewing, so it wasn’t all bad). So I had to go back to square one a little bit - studying the basics of building circuits to understand how I needed to get this thing wired up. I had an old Arduino in a drawer that I used as a starting point but had no idea how to make it do things. I stuck it into a breadboard and learned how to make button presses register using one of the Arduino tutorials.</p> <picture><source type="image/webp" srcset="https://localghost.dev/img/eGWlnqAyVI-280.webp 280w, https://localghost.dev/img/eGWlnqAyVI-640.webp 640w, https://localghost.dev/img/eGWlnqAyVI-960.webp 960w" sizes="auto"><img loading="lazy" decoding="async" src="https://localghost.dev/img/eGWlnqAyVI-280.jpeg" alt="An Arduino microcontroller connected to the button with some crocodile clips via a prototyping breadboard. There's a resistor and various wires plugged into the breadboard." width="960" height="1280" srcset="https://localghost.dev/img/eGWlnqAyVI-280.jpeg 280w, https://localghost.dev/img/eGWlnqAyVI-640.jpeg 640w, https://localghost.dev/img/eGWlnqAyVI-960.jpeg 960w" sizes="auto"></picture> <h2 id="programming-the-microcontroller" tabindex="-1">Programming the microcontroller</h2> <p>The author used an ESP32 microcontroller, so that's what I used.</p> <p>The tutorial included a link to the author’s <a href="https://github.com/mnkii/esp32-philips-hue-button">Github repo</a> with the code he used, so I forked it and set it up with my Hue system. You can <a href="https://github.com/sophiekoonin/esp32-philips-hue-button">see my fork on GitHub</a>.</p> <p>I started by using the Arduino IDE, but by the end of the project I'd graduated to using VSCode with <a href="https://platformio.org/">platformio</a> to flash the controller. In the time-honoured tradition of debugging with <code>console.log</code>, I got it to record button presses to the serial output so I could check whether the presses were actually being picked up.</p> <p>I had to get an API key for the Hue bridge using the <a href="https://developers.meethue.com/develop/get-started-2/">instructions</a> on the Hue developer website - you have to log into the bridge via your local network, and make requests to the bridge's API. I plugged the API key it gave me into the config in the code, and also got the IDs of the scenes and lights I wanted to target.</p> <figure> <picture><source type="image/webp" srcset="https://localghost.dev/img/uzvI3ShAqP-280.webp 280w, https://localghost.dev/img/uzvI3ShAqP-640.webp 640w, https://localghost.dev/img/uzvI3ShAqP-960.webp 960w" sizes="auto"><img loading="lazy" decoding="async" src="https://localghost.dev/img/uzvI3ShAqP-280.jpeg" alt="A Macbook is open next to the breadboard with the ESP32 connected via wires. The Macbook shows the Arduino IDE with the Hue switch code." width="960" height="720" srcset="https://localghost.dev/img/uzvI3ShAqP-280.jpeg 280w, https://localghost.dev/img/uzvI3ShAqP-640.jpeg 640w, https://localghost.dev/img/uzvI3ShAqP-960.jpeg 960w" sizes="auto"></picture> <figcaption>Getting the prototype working with the ESP32</figcaption> </figure> <h2 id="replacing-the-le-ds" tabindex="-1">Replacing the LEDs</h2> <p>The original LEDs wired into the panel need a lot of power, and can’t be powered off the same circuit as the microcontroller - I needed a separate 9V battery as well as a transistor to amplify the signal from the Arduino. Again, needed to do a fair bit of learning here. The tutorial named a type of transistor called a MOSFET, so I ordered one of those and stuck it into my breadboard. (Shout out to <a href="https://ghost.computer">Kara</a> for letting me bombard her with a million questions.)</p> <picture><source type="image/webp" srcset="https://localghost.dev/img/fhoh5OQxfW-280.webp 280w, https://localghost.dev/img/fhoh5OQxfW-640.webp 640w, https://localghost.dev/img/fhoh5OQxfW-960.webp 960w" sizes="auto"><img loading="lazy" decoding="async" src="https://localghost.dev/img/fhoh5OQxfW-280.jpeg" class="small" alt="A top-down photograph of the circuit setup on the breadboard, as described in the paragraph that follows." width="960" height="720" srcset="https://localghost.dev/img/fhoh5OQxfW-280.jpeg 280w, https://localghost.dev/img/fhoh5OQxfW-640.jpeg 640w, https://localghost.dev/img/fhoh5OQxfW-960.jpeg 960w" sizes="auto"></picture> <p>The left prong of the MOSFET (the gate) connects to the pin for the button on the ESP32, with a resistor in the middle; the middle prong (drain) goes to the button's negative wire; the rightmost prong (source) goes to ground. The current flows between the drain and the source, and the gate varies the voltage. There's <a href="https://theengineeringmindset.com/how-mosfets-work/">a good explainer on MOSFETs</a> on The Engineering Mindset. <strong>There are likely some resistors missing in this picture</strong> - this is not what went into the final thing!</p> <p>Some of the comments on the Instructables page had mentioned replacing the original LEDs with a modern LED strip so you don't have to use a battery, and 3D printing a replacement orange ring which they had helpfully uploaded the model file for. The comments didn’t mention how to set up the LED strip, so I had to figure that bit out.</p> <p>Interestingly, this is something I found Claude quite useful for: quizzing it about my setup and how I should integrate an LED strip. As someone with no Arduino experience, very little electronics experience but lots of programming knowledge, it was actually great to understand more about what I needed to do. I fed it the code for the button, the existing LED code, and the tutorial I was using. It told me about the FastLED library and recommended a type of LED strip I should be using which did indeed work a treat.</p> <p>This approach allowed me to ditch the transistor and battery altogether: the LEDs draw much less power so they could be powered off the microcontroller. I got some translucent orange PLA+ and 3D printed the new version of the ring, which has a channel inside to house the LED strip. The solder points on the LED strip were tiny as it was such a narrow strip, so I had to leave quite a bit of slack to avoid putting too much strain on the join and causing the solder to peel off.</p> <figure> <picture><source type="image/webp" srcset="https://localghost.dev/img/tUfRpQTEtc-280.webp 280w, https://localghost.dev/img/tUfRpQTEtc-640.webp 640w, https://localghost.dev/img/tUfRpQTEtc-938.webp 938w" sizes="auto"><img loading="lazy" decoding="async" src="https://localghost.dev/img/tUfRpQTEtc-280.jpeg" alt="A deconstructed view of the button: the orange 3D printed ring in its setting on the metal panel, with an LED strip inside the channel that runs around the edge. The LED strip is wired into the prototype board and sits next to the button panel which is also wired into the prototype board." width="938" height="940" srcset="https://localghost.dev/img/tUfRpQTEtc-280.jpeg 280w, https://localghost.dev/img/tUfRpQTEtc-640.jpeg 640w, https://localghost.dev/img/tUfRpQTEtc-938.jpeg 938w" sizes="auto"></picture> <figcaption>The LED strip was too short in this photo - it got bent at the join, and the solder peeled off. It didn't switch on after this photo was taken.</figcaption> </figure> <p>For the board, I used an <a href="https://www.adafruit.com/product/1609">Adafruit Perma-Proto</a>, which looks like a tiny breadboard that you can actually solder stuff to. (My soldering is fucking dreadful, so you’re not getting any close-ups of that.)</p> <picture><source type="image/webp" srcset="https://localghost.dev/img/f54vRZpBjV-280.webp 280w, https://localghost.dev/img/f54vRZpBjV-640.webp 640w" sizes="auto"><img loading="lazy" decoding="async" src="https://localghost.dev/img/f54vRZpBjV-280.jpeg" class="small" alt="The inside of the finished switch, with everything soldered onto a much smaller prototype board. The battery is gone. There is a narrow LED strip coming out from behind the button." width="640" height="981" srcset="https://localghost.dev/img/f54vRZpBjV-280.jpeg 280w, https://localghost.dev/img/f54vRZpBjV-640.jpeg 640w" sizes="auto"></picture> <p>Finally, my husband made a lovely wooden case for it. I also had to glue-gun a piece of wood between the board and the front plate to avoid it shorting.</p> <p>And now I have a fancy light switch! It can power the lights on and off, long-press to change brightness, and double-press to change scene. All powered by USB, no batteries necessary.</p> <video controls=""> <source src="https://localghost.dev/img/jubilee-button/demo.webm" type="video/webm"> <source src="https://localghost.dev/img/jubilee-button/demo.mp4" type="video/mp4"> A video showing me pressing the button to turn a Philips Hue Go on ond off. First I press it once to turn on the lights, then I double-press it several times to cycle through different scenes. The light changes colour to pink, then a warm yellow. Then I hold the button to dim the light, and finally press it again to turn it off. </video> <h2 id="acknowledgements" tabindex="-1">Acknowledgements</h2> <p>A big old thanks to the original author of the <a href="https://www.instructables.com/Hacking-a-London-Underground-Jubilee-Line-Door-But/">tutorial</a>, mw83 aka <a href="https://www.instructables.com/member/banjowise/">banjowise</a>, and <a href="https://www.instructables.com/member/pneuteboom/">pneuteboom</a> who uploaded the STL file for the 3D printed orange ring in the comments of that post!</p> April 2026 Summary - Joel's Log Files https://joelchrono.xyz/blog/april-2026-summary 2026-05-01T17:19:59.000Z <p>April is over, there were many birthdays and celebration parties from friends and family, there were also unexpected losses to mourn. My siblings visited for a while and we went out quite a lot. We returned to the moon and landed safely back on Earth, everyone I know of except Amin got an XTEINK X4, I got some new card games that everyone loved. I started to buy books from the Kobo store, I am yet to play Land of Eem again, and this Monthly Summary is a day late.</p> <p>Let us move.</p> <h2 id="️-podcasts">🎙️ Podcasts</h2> <p><img src="https://joelchrono.xyz/assets/img/blogs/2026-04/podcasts.webp" alt="Podcasts"/></p> <ul> <li><strong>Into The Aether</strong> - Regular weekly episode listening. Make sure to check their bonus episode on <a href="https://intothecast.transistor.fm/episodes/persona-4-golden-bonus-episode">Persona 4 Golden</a>, there’s also a bonus on PS Vita and its games on their Patreon, which I’m currently listening through.</li> <li><strong>Welcome to Night Vale</strong> - Same old night-time listening before going to sleep, I am relistening to old episodes though.</li> <li><strong>Eye of the Duck</strong> - A film podcast that I’ve always enjoyed, I listened to their episode on <em>The Matrix</em>.</li> <li><strong>Stuff You Should Know</strong> - Quite a lot of good episdoes this time, about Free Range Parenting, The History of Fire and <em>Roar</em>, the most dangerous movie ever made, pretty cool stuff.</li> <li><strong>Trash Taste</strong> - Listened to episode 296 and that was pretty much it. Lots of talk about anime movies and <em>My Hero Academia</em> for some reason, I can’t believe I’m kind of interested on giving that super long anime a watch…</li> <li><strong>Clarkesworld Magazine</strong> - I am relistening to their audio version of <em>The Indomitable Captain Holli</em>.</li> <li><strong>Wolf 359</strong> - I listened to episode 46: Boléro, which turned out to be the season finale. I am halfway through a special episode and then will continue to the next season!</li> </ul> <h2 id="-gaming">🕹 Gaming</h2> <p><img src="https://joelchrono.xyz/assets/img/blogs/2026-04/games.webp" alt="Games"/></p> <h3 id="completed">Completed</h3> <ul> <li> <p><strong>Resident Evil</strong> - I already shared plenty of thoughts on this game on <a href="https://joelchrono.xyz/blog/resident-evil/">its review</a> earlier in the month. This was a joy to play through and even though it shows its age, it’s still perfectly playable today. I particularly enjoyed the puzzle elements of this game compared to its sequel. Even though it was inferior in other aspects, the way it defined the fundamentals of what a survival horror should be like has remained to this day. Tank controls and all, this game was a delight, and is very fun when approached with the right mindset.</p> </li> <li> <p><strong>Metal Gear Solid Peace Walker</strong> - This title for the PSP was a grandiose adventure, I decided to start it on a whim a few days after completing Resident Evil, and I couldn’t stop. It became my immediate choice during my commute to work, always managing to complete a mission or two, or listen to a few of the audio logs while the bus rode on. The characters were awesome, the gameplay was great most of the time, and even if the controls felt limited and something actively annoying to use, this wasn’t the case most of the time. I’ll expand more on this as soon as I write my review for it. I got to the end and realized there’s an extra chapter that can be done to connect the story more with the rest of the series. I’ll probably keep playing it for another bit, but I’ll move on if it gets too grindy. It has been a joy so far, either way.</p> </li> </ul> <h3 id="started">Started</h3> <ul> <li> <p><strong>Tomodachi Life: Living the Dream</strong> - This game got me hooked for a whole couple of days, playing it like 15 hours on a Friday and Saturday. Since then it has been a bit more relaxed, only playing it for an hour or less each day to check up on my Miis. I’ve recreated a lot of characters, online friends and people I know in person. It is super fun and chaotic, and sharing what happens to my friends on group chats and such is very fun. I still need to look more into terraforming and creating my own items though.</p> </li> <li> <p><strong>Vampire Crawlers</strong> - I wish I could say more about this game, but since I my purchase of the previously mentioned title, I’ve only really played like half an hour of this so far. I am sure it’s fun, I only need to invest some time into it.</p> </li> </ul> <h3 id="ongoing">Ongoing</h3> <ul> <li> <p><strong>Metroid Fusion</strong> - A wonderful game I’ve completed before, which I’m enjoying again on my Miyoo Mini Flip. Feel free to <a href="https://joelchrono.xyz/blog/metroid-fusion/">check my review</a> for it.</p> </li> <li> <p><strong>Terranigma</strong> - An Action RPG for the SNES that I’ve been meaning to play once and for all, I’ve abandoned it before due to my own lack of commitment, but I want to give it a proper shot once more. It has been really fun, I love the combat mechanics, the music and the artwork, I’m looking forward to more of its story to develop.</p> </li> <li> <p><strong>Balatro</strong> - Just a few rounds that I played with friends. I also bought it for my phone, but I am still unable to win a run, rest in peace.</p> </li> </ul> <h2 id="-books">📚 Books</h2> <p><img src="https://joelchrono.xyz/assets/img/blogs/2026-04/books.webp" alt="Books"/></p> <h3 id="completed-1">Completed</h3> <ul> <li><strong>To Be Taught If Fortunate</strong> by Becky Chambers - This book was a welcome surprise for me, I enjoyed the pacing the characters and the science concepts that allowed the story to develop. There was a lot of variety in the alien life described, which I really loved. I already <a href="https://joelchrono.xyz/blog/to-be-taught-if-fortunate/">wrote a review</a> so you may as well give it a read.</li> </ul> <h3 id="ongoing-1">Ongoing</h3> <ul> <li><strong>Clarkesworld Magazine #211</strong> - I did finish <em>The Indomitable Captain Holli</em>, which was the longest of the entries in the magazine, a fantastic novella featuring a great concept and a lot of mysteries and things that are unexplained, but slowly unfold as the story continues, I highly recommend giving it a go. It’s <a href="https://clarkesworldmagazine.com/larson_04_24/">available online for free.</a></li> <li><strong>Strange Dogs</strong> - I feel bad because I still barely read this at all, I should move it to my XTEINK X4 so I actually make progress on it…</li> </ul> <h2 id="-manga">💬 Manga</h2> <p><img src="https://joelchrono.xyz/assets/img/blogs/2026-04/manga.webp" alt="Manga"/></p> <h3 id="started-1">Started</h3> <ul> <li><strong>Smoking Behind the Supermarket with You</strong> - A manga that I had added to my TBR list for years, but never got around to it until now. This is a manga about Sasaki, a middle-aged guy working for a <a href="https://en.wikipedia.org/wiki/Black_company_(Japan)">black corporation</a> who goes to the same supermarket at the end of each shift, to see Yamada, a cashier who he admires, and always cheers him up. Yamada has an alter ego called Tayama, who dresses and acts in a much “cooler” manner, and invites him for a smoke (behind the supermarket!) where she teases and tests him, to see if he doesn’t have bad intentions. A relationship develops from there that feels very human and real, as Tamaya is afraid of confessing shes lying to him, and Sasaki doesn’t want to be a bother for her either. I already caught up to the manga, which has a monthly release, and some incredible revelations have been teased on the following chapter. Looking forward to it all.</li> </ul> <h3 id="ongoing-2">Ongoing</h3> <ul> <li> <p><strong>Fly Me To The Moon</strong> - Up to chapter 289. I’ve been flying through the pages of this rom-com and really having fun with its characters and the diabetes moments in it. It was very much taken over by the manga above though during the last two weeks of April, I woulc be caught up with it otherwise.</p> </li> <li> <p><strong>Spy x Family</strong> - Up to chapter 133, I’ve read the weekly releases and have had a great time. There was some fun dynamics where Loid, Yor and her brother were stuck together in a hostage situation which turned into a very chaotic fun.</p> </li> <li> <p><strong>Yokohama Kaidashi Kikou</strong> - Up to chapter 70. This month I decided to read a couple of chapters of this masterpiece. Alpha has been walking and wandering without a set destination, really, and she managed to find an old and small airport. There she sees a plane upclose, and a couple more surprises. Lovely as always.</p> </li> <li> <p><strong>Kingdom</strong> - Up to chapter 871. I read quite a bit of chapters of this and quicly catched up, but there’s been a few that I missed since I started the other manga. In any case, this keeps being great.</p> </li> <li><strong>Dandadan</strong> - Up to chapter 229. I am a couple chapters behind, but having fun.</li> <li><strong>Blue Lock</strong> - Up to chapter 342. I am behind as well on this, but yea, just the usual.</li> <li><strong>Sakamoto Days</strong> - Up to chapter 229. I am super far behind.</li> </ul> <h2 id="-anime">📺 Anime</h2> <p><img src="https://joelchrono.xyz/assets/img/blogs/2026-04/anime.webp" alt="Anime"/></p> <ul> <li> <p><strong>Orb: On the Movements of the Earth</strong> - I completed this masterpiece, and I highly recommend everyone to give it a watch. I already wrote a pretty extensive set of paragraphs about it on my <a href="http://localhost:4000/blog/2026-w17/#watching">weeknotes</a>, where I basically wrote a short review of it. I may even write more about it in the future.</p> </li> <li> <p><strong>Jujutsu Kaisen Season 3</strong> - Up to episode 8. I was watching it with a sibling when she came to visit during Easter vacation, unfortunately I’ve not returned to it since then, I don’t know if I’ll get to continuing it, but the battle choreography and animation work seems to be top notch.</p> </li> </ul> <h2 id="-tv-shows">📺 TV Shows</h2> <ul> <li><strong>Malcom in the Middle: Life’s Still Unfair</strong> - My favorite sitcom of all time was next in the list of nostalgic TV shows to have a return to the screen. This one was very enjoyable for me! I think it managed to capture a lot of the original’s magic, it was extremely over the top multiple times and I loved it because of it. There were some things to criticize of course but overall, I recommend it, but you must watch the classic first.</li> </ul> <h2 id="-movies">🎥 Movies</h2> <p><img src="https://joelchrono.xyz/assets/img/blogs/2026-04/movies.webp" alt="Movies"/></p> <ul> <li> <p><strong>The Super Mario Galaxy Movie</strong> - The only reason I remember the plot of this movie it’s because of its similarity with the game, and also Fox McCloud shows up on it. Yeah it’s a Mario movie, I can’t expect a cinematic masterpiece out of it. Fun time though.</p> </li> <li> <p><strong>Project Hail Mary</strong> - A science fiction adaptation of <a href="https://joelchrono.xyz/blog/project-hail-mary/">the book of the same name</a>, it doesn’t really get a lot of the science to the screen, and a lot of things feel convenient because of it, but I really loved seeing the characters come to life, the visuals and the practical effects. The score was also pretty great! Not the best hard sci-fi ever, but a good flick nonetheless.</p> </li> </ul> <h2 id="-goals">✅ Goals</h2> <h3 id="new">New</h3> <ul class="task-list"> <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled"/>Make something to simplifly keeping track of these goals on Jekyll.</li> <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled"/>Create some <a href="https://departure.blog/pixel-bears/">pixel bears</a> for a few friends (2/10?) <progress id="file" value="2" max="10"></progress></li> <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled"/>Complete all the games from the UFO 50 compilation (0/50) <progress id="file" value="0" max="50"></progress></li> </ul> <h3 id="in-progress">In Progress</h3> <ul class="task-list"> <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled"/>A full website redesign (??)</li> <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled"/>Fully sorting and labelling everything on my shelves for once</li> <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled"/>Finish a pending commission for a friend</li> <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled"/>Finish a pending commission for my parents</li> <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled"/>Complete 15 videogames (4/15) <progress id="file" value="4" max="15"></progress></li> <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled"/>Complete 15 books (5/15) <progress id="file" value="5" max="15"></progress></li> <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled"/>Read the whole bible in a year (38/365) <progress id="file" value="38" max="365"></progress></li> <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled"/>Finish listening to Wolf 359 (45/61) <progress id="file" value="45" max="61"></progress></li> </ul> <p>This is day 60 of <a href="https://100daystooffload.com">#100DaysToOffload</a></p> <p> <a href="mailto:me@joelchrono.xyz?subject=April 2026 Summary">Reply to this post via email</a> | <a href="https://fosstodon.org/@joel/116508903799620992">Reply on Fediverse</a> </p> Thoughts on Leaving GitHub - Kev Quirk https://kevquirk.com/thoughts-on-leaving-github 2026-05-01T16:01:00.000Z <p>I've read a few posts about people leaving GitHub recently, and <a href="https://kevquirk.com/2026-04-29-1655">following my short note to the Fediverse</a> a number of people have piped up saying they're not fans of GitHub, either.</p> <p>From the reading I've done, these frustrations are usually threefold:</p> <ol> <li>Microsoft ownership</li> <li>Microsoft training Copilot on open source software</li> <li>Large amounts of downtime</li> </ol> <p>In all honesty, none of the factors above really bother me <em>that</em> much. I think that's because I don't rely on GitHub for anything significant. I'm not a professional software developer, so my livelihood doesn't depend on it.</p> <p>As for Copilot being trained on open source software, and <a href="https://sfconservancy.org/GiveUpGitHub/">them repeatedly ignoring the GPL</a> to do so, it does irk me, but I kind of expect shit like this from Microsoft at this point. I went into using GitHub assuming that any code I upload there can (and probably will) be used for shitty stuff.</p> <p>But even <em>that</em> isn't enough in isolation to put me off GitHub. The way I see it is that public code is for the public, and if Microsoft want to use my code in that way, while not ideal, doesn't piss me off <em>that</em> much.</p> <p>So why think about moving at all?</p> <p>Well, for me it's about reliance on big tech. I'm trying to reduce it where possible, but the social and "centre of mass" aspects of GitHub are giving me pause.</p> <p>For example, <a href="https://github.com/kevquirk/simple.css">the Simple.css repo</a> has a whopping 5,000 stars! Do I <em>really</em> want to lose that visibility?</p> <p>Buuuuuuuuuut, I can always redirect any popular repos to another platform, just like I did with <a href="https://github.com/kevquirk/512kb.club">512KB Club</a> when I handed that to <a href="https://btxx.org/">Brad</a>.</p> <p>Plus, let's be honest, it's all just popularity bullshit. It doesn't really mean anything. What's important is that the code is readily available for people to use.</p> <p>It's like leaving Facebook - when I was thinking about it, I was worried if I'd miss my friends or be out the loop. It's been over a decade at this point and I don't miss it one bit - no regrets whatsoever. I think moving off of GitHub would be the same.</p> <h2>So what am I doing?</h2> <p>I plan to slowly start migrating public repositories over to <a href="https://codeberg.org">Codeberg</a> so that all <a href="https://kevquirk.com/projects">my projects</a> are hosted there. I'll also use it as an opportunity to archive off any old repos that I no longer need.</p> <p>Codeberg also supports logging in with GitHub and Gitea, so anyone who contributes to my projects on GitHub, should be able to do so easily in Codeberg too.</p> <p>Then, for my private repos (of which there are many that host personal projects) I've installed <a href="https://kb.synology.com/en-uk/DSM/help/Git/git?version=7">Synology's Git server</a> on my Synology, and have been playing with that for a few days. It works extremely well, so all my private repos will live there, safe and sound, away from Microsoft's greasy mitts.</p> <h2>Final thoughts</h2> <p>Ultimately it's personal choice. For me it's about reducing my reliance on big tech, but also making my private repos more private.</p> <p>I won't be deleting my GitHub account though, as I think it will be important to use as a marker for anyone who wants to find my source code when it moves.</p> <p>Have you thought about leaving GitHub?</p> <div class="email-hidden"> <hr /> <p>Thanks for reading this post via RSS. RSS is ace, and so are you. ❤️</p> <p>You can <a href="mailto:19gy@qrk.one?subject=Thoughts%20on%20Leaving%20GitHub">reply to this post by email</a>, or <a href="https://kevquirk.com/thoughts-on-leaving-github#comments">leave a comment</a>.</p> </div> Claude Opus can now identify an author from their writing - Werd I/O 69f4ab53825c3600019fd666 2026-05-01T13:32:03.000Z <p>Link: <a href="https://www.theargumentmag.com/p/i-can-never-talk-to-an-ai-anonymously?ref=werd.io"><em>I can never talk to an AI anonymously again, by Kelsey Piper in The Argument</em></a></p><p>On first read, I found this a little bit sobering:</p><blockquote>&#x201C;But soon, the entire debate over internet anonymity will be as anachronistic as an iPod Touch. That&#x2019;s because Claude Opus 4.7 is here, and last week, I discovered it could identify me from text I had never published, text from when I was in high school, text from genres I have never publicly written in. And if it can identify me, soon, it will be able to identify many of you.&#x201D;</blockquote><p>Kelsey Piper plugged in 125 words from an unpublished essay and asked Opus 4.7 to identify the author. It listed her as the most likely answer.</p><p>I was skeptical, so I did the same: a few paragraphs from an essay, which I plugged into an account that isn&#x2019;t connected to me. Here&#x2019;s what Claude said:</p><blockquote>&#x201C;Based on the style, themes, and specific concerns in this snippet, my best guess is Ben Werdm&#xFC;ller.<br><br>Several signals point this way: the framing of the open social web as a movement rather than a technical project, the explicit linkage between Mastodon/Bluesky, journalism, and broader liberatory politics, the critique of newsroom demographics (&quot;very white, very coastal, wealthy&quot;), and the historical framing of the early web as an anti-gatekeeping movement. Werdm&#xFC;ller writes frequently about all of these topics together &#x2014; he works at ProPublica on technology, has a long history in the IndieWeb and federated social space (he co-founded Elgg and Known), and his personal blog regularly weaves these exact threads.<br><br>The voice also fits: declarative, slightly polemical, comfortable making sweeping movement-level claims, and willing to name class and racial dynamics directly rather than hedging.&#x201D;</blockquote><p>I&#x2019;ll take that as a compliment! But it&#x2019;s impressive that it was able to figure it out without searching.</p><p>But those are topics I write about regularly. When I gave it a snippet of my short fiction piece <a href="https://outmap.org/sharing-is-caring/?ref=werd.io">Sharing is Caring</a>, it wasn&#x2019;t able to find a match:</p><blockquote>&#x201C;The passage has hallmarks of contemporary speculative/dark YA or literary sci-fi short fiction &#x2014; body-swapping or consciousness-transfer premise, a class-inflected ethical horror (poor family selling a daughter&apos;s body to a wealthy disabled girl&apos;s family), tight close-third POV, terse interior monologue in italics-style cut-ins (&quot;Sure. No. Not a problem.&quot;), and the bleak ironic kicker about an eighteenth birthday. That cluster of moves shows up in writers like Ted Chiang, Ken Liu, Carmen Maria Machado, Alice Sola Kim, Karen Russell, or in anthology pieces from venues like Lightspeed, Tor.com, or various YA dystopia collections &#x2014; but none of those is a confident match.&#x201D;</blockquote><p>Again, I&#x2019;ll take the comparisons as a compliment, but there isn&#x2019;t enough of my fiction out there to make a real suggestion. That&#x2019;s going to be true of most people&#x2019;s writing &#x2014; unless they&#x2019;re publishing really regularly on the web, and have been for a while, the models might not know about them. Kelsey had a slightly different result: a school progress report was still identified as hers. But clearly, the reliability here is shakier.</p><p>In other words, we&#x2019;re a little way&#x2019;s off from AI being able to identify most people quite yet. But if you&#x2019;re publishing regularly, out in the open, you should expect your work to be fingerprinted. That <em>does</em> have implications for dissidents and people writing anonymously who have previously done so under their real names.</p>