Shellsharks Blogroll - BlogFlock 2025-11-19T11:12:37.389Z BlogFlock Werd I/O, cool-as-heck, destructured, Evan Boehs, Aaron Parecki, <span>Songs</span> on the Security of Networks, Adepts of 0xCC, cmdr-nova@internet:~$, Sophie Koonin, Westenberg, fLaMEd, Hey, it's Jason!, gynvael.coldwind//vx.log (pl), Johnny.Decimal, Terence Eden’s Blog, James' Coffee Blog, Molly White, joelchrono, Robb Knight, Trail of Bits Blog, Posts feed, Kev Quirk Why Do You Need Big Tech for Your SSG? - Kev Quirk https://kevquirk.com/blog/why-do-you-need-big-tech-for-your-ssg/ 2025-11-19T09:57:00.000Z <p style="font-size: 1.2em;">A look at why small, personal websites don’t need big-tech static hosting, and how a simple local build and rsync workflow gives you faster deploys, more control, and far fewer dependencies.</p> <p>OK, so <a href="https://blog.cloudflare.com/18-november-2025-outage/">Cloudflare shit the bed yesterday</a> and the Internet went into meltdown. A config file grew too big and half the bloody web fell over.</p> <p><a href="https://afranca.com.br/the-fragile-web-we-built">How fragile</a>.</p> <p>It got me thinking about my fellow small-web compatriots, their SSG workflows, and why on earth so many rely on services like Cloudflare Pages and Netlify. For personal sites it feels incredibly wasteful: you’re spinning up a VM, building your site, pushing the result to their platform, then tearing the VM down again.</p> <p>Why not just build the site on your local machine? You’re not beholden to anyone, and you can host your site anywhere you like.</p> <ul> <li>✅ No CI/CD pipeline.</li> <li>✅ No big tech — just you and your server.</li> <li>✅ No VMs spinning up and down at the speed of a thousand gazelles.</li> </ul> <h2 id="how-to-build-and-deploy-automatically">How to build and deploy automatically</h2> <p>All you need is a hosting package that supports SSH (or FTP if you must) and a small script to build your site and rsync any changes. Here’s the core of my deployment script:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/bash</span> <span class="nb">set</span> <span class="nt">-e</span> <span class="nv">LOCAL_DIR</span><span class="o">=</span><span class="s2">"/path/to/your/site/source"</span> <span class="nv">REMOTE</span><span class="o">=</span><span class="s2">"user@your-server.example.com"</span> <span class="nv">REMOTE_DIR</span><span class="o">=</span><span class="s2">"/path/to/your/website/files/yoursite.com/public_html"</span> <span class="nb">cd</span> <span class="s2">"</span><span class="nv">$LOCAL_DIR</span><span class="s2">"</span> <span class="o">||</span> <span class="nb">exit </span>1 <span class="c"># --- Build Jekyll site ---</span> <span class="nb">echo</span> <span class="s2">"🏗️ Building Jekyll site..."</span> bundle <span class="nb">exec </span>jekyll build <span class="nt">--quiet</span> <span class="nb">echo</span> <span class="s2">"✅ Build complete"</span> <span class="c"># --- Sync _site to remote server ---</span> <span class="nb">echo</span> <span class="s2">"🚀 Deploying to server..."</span> rsync <span class="nt">-az</span> <span class="nt">--checksum</span> <span class="nt">--delete</span> <span class="nt">--omit-dir-times</span> <span class="nt">--quiet</span> <span class="se">\</span> <span class="s2">"</span><span class="nv">$LOCAL_DIR</span><span class="s2">/_site/"</span> <span class="se">\</span> <span class="s2">"</span><span class="nv">$REMOTE</span><span class="s2">:</span><span class="nv">$REMOTE_DIR</span><span class="s2">/"</span> <span class="c"># --- Fin ---</span> <span class="nb">echo</span> <span class="s2">"✅ Deployment complete"</span> </code></pre></div></div> <p>Here’s what it does:</p> <ol> <li>Jumps into the directory where my source website files live.</li> <li>Builds the Jekyll site locally.</li> <li>Syncs the built files to my server over SSH, deleting anything I’ve removed locally.</li> </ol> <p>That’s it. And that’s all it needs to do. With these few lines of Bash, I can deploy anywhere, without waiting for someone else’s infrastructure to spin up a build container.</p> <p>My full script also checks the git status, commits changes, and clears the Bunny CDN cache, but none of that’s required. The snippet above does everything Cloudflare Pages and similar services do — and does it much quicker. My entire deploy, including the extras, takes about eight seconds.</p> <h2 id="final-thoughts">Final thoughts</h2> <p>If you’re hosting with one of the big static hosting platforms, why not consider moving away and actually owning your little corner of the web? They’re great for complex projects, but unnecessary for most personal sites.</p> <p>Then, the next time big tech has a brain fart, your patch of the web will probably sail right through it.</p> <div class="email-hidden"> <hr> <p>Thanks for reading this post via RSS. RSS is great, and you're great for using it. ❤️</p> <p> <a href="mailto:72ja@qrk.one?subject=Why Do You Need Big Tech for Your SSG?">Reply to this post by email</a> </p> </div> Snow - James' Coffee Blog https://jamesg.blog/2025/11/19/snow/ 2025-11-19T09:54:55.000Z <p><em>The snow looks like drops of moonlight</em> I thought to myself as the cool light from the street lamps illuminated the falling snow. <em>It’s beautiful. I have never seen anything like it.</em></p><p>Earlier this week I saw light rain fall that looked almost like it was snow – the light reflected off the falling rain in a way closer to snow than water. Although I am not sure if what I saw was exactly snow, my hopes were raised that it may snow properly this week. The air has been getting cooler; the outdoors feels evermore like winter. Yesterday evening, at around 9 o’clock, my hope for snow came to reality. I looked out the window after reading a book for an hour and noticed the heavy clouds and the white drops of snow.</p><p>The last time I saw snow was more than a year ago. Despite my hope that it would snow – one I shared excitedly with people in conversation this week – it took me a moment to internalise that it was now snowing. Then, my mind turned to the beauty of the snow fall – the way that the light breeze blew the snow over the grasses illuminated only by streetlight.</p><p>Time froze. I was eager to take in as much of the snow as possible, knowing that, like all seasons and weathers, snow is fleeting. Would the snow be here in the morning? I wasn’t sure, but I knew the snow was there, in the present moment, right in front of me. All that matters is it is snowing now – drops of moonlight fall from the heavens onto the frosting countryside grass.</p><p>My gaze was focused on the areas under the streetlights, which illuminated the snow and, over time, showed how the snow was accumulating. <em>This isn’t the kind of snow that will lay there until morning,</em> I knew from having seen many snows growing up; maybe there would be a dusting on the next day.</p><p>This morning, I woke up and saw on the horizon a white skyline, above which was the increasingly light blue that comes at 7am in November here in Scotland. Under the white skyline, there were hills dusted in snow. The coat of snow on the hills – enveloping a few almost like a cold blanket – brought to mind last night at which I stood by the window and, for how long I do not know, gazed out and watched the snow paint the countryside with brushstrokes of white whose full beauty would appear in the morning.</p> 22.00.0169 An invoice disappears - Johnny.Decimal https://johnnydecimal.com/22.00.0169/ 2025-11-19T00:49:03.000Z <h1 id="an-invoice-disappears">An invoice disappears</h1> <p>Well this is weird. I just lost an invoice, as in one that I was <em>sure</em> I had raised a couple of weeks ago. Here&#39;s how I discovered it, and what I&#39;ll do to make sure it doesn&#39;t happen again.</p> <h3 id="the-invoice">The invoice</h3> <p>I don&#39;t raise many invoices manually. This one was to a friend, whose domain name I manage. It&#39;s a fancy expensive domain so, when it renews, my company bills his company for it.</p> <p>I raised it the other week. I remember doing it. And yet now: no trace. A puzzle.</p> <h3 id="discovering-its-loss">Discovering its loss</h3> <p>I know I&#39;m not going entirely mad because, in reviewing my Small Business category <code>13 Money earned, spent, saved, &amp; owed</code> this morning, I saw a task due next week:</p> <p>▷ <strong>Check that [name] has paid his invoice</strong><br> ▷ <em>Due: 25 Nov</em></p> <p>– and I thought to have a quick look. So I&#39;d done some things right:</p> <ol> <li>Set a quick reminder to myself, in a trusted place.</li> <li>Made sure that I actually saw that reminder, by reviewing my system regularly.</li> </ol> <blockquote> <p>I&#39;ll show you how I do this in the upcoming series &#39;Task and Project Management using the Johnny.Decimal system&#39;. Will be released on <a href="https://johnnydecimal.com/14.02/">JDU</a> in the next couple of weeks.</p> </blockquote> <h3 id="the-vanishing-act">The vanishing act</h3> <p>I went to look for this invoice in the only place that it could possibly exist: my <a href="https://stripe.com">Stripe</a> console. It just isn&#39;t there. No trace.</p> <p>I&#39;m deeply confused by this, but whatever. No point dwelling; let&#39;s just make sure it doesn&#39;t happen again.</p> <h3 id="deliberate-record-keeping">Deliberate record-keeping</h3> <p>Last time, I raised the invoice in Stripe and that was it. Other than leaving myself the follow-up task, I didn&#39;t record its existence anywhere else.</p> <p>If only I had a predefined ID for this sort of thing. Oh wait, is that <code>13.23 Invoices &amp; sales for your work</code> at the door? Oh, come in old friend.</p> <h3 id="update-found-it">Update: found it!</h3> <p>Talk about real-time updates. Gripping stuff.</p> <p>So there <em>isn&#39;t</em> only one place that this could possibly exist. There are two: the other being my <a href="https://xero.com">Xero</a> account.</p> <p>This makes our solution more interesting. Why did I choose Xero over Stripe in this instance?<sup><a href="#user-content-fn-australian" id="user-content-fnref-australian" data-footnote-ref="" aria-describedby="footnote-label">1</a></sup> How would I know which to choose in the future?</p> <h3 id="solution-new-ops-manual">Solution: new ops manual</h3> <p>This is a textbook ops manual. Next time I need to raise an invoice, I need to be following a process. Last time, I just made it up on the fly.</p> <p>So here&#39;s what my new <code>13.23+OPS1 Raise an invoice</code> says:</p> <ol> <li>Raise all invoices directly in Xero, because this integrates with your accounting system.</li> <li>Raise the invoice, and create a note at <a href="https://jdhq.johnnydecimal.com/sbs/13.23/"><code>13.23</code></a> with its number and a link.</li> <li>Create a new customer record at <a href="https://jdhq.johnnydecimal.com/sbs/33.11/"><code>33.11</code></a> and from there, link to <code>13.23</code>.</li> </ol> <p>That&#39;s it. Three easy steps; but now they&#39;re unambiguous, and I won&#39;t make the same silly mistake again.</p> <hr> <p><em>100% human. 0% AI. Always.</em></p> <section data-footnotes="" class="footnotes"><h2 class="sr-only" id="footnote-label">Footnotes</h2> <ol> <li id="user-content-fn-australian"> <p>I&#39;ll tell you why. Because this invoice has nothing to do with Johnny.Decimal, really. In my mind, that&#39;s what the Stripe/Xero split is. Stripe was JD stuff, Xero was more fundamental company stuff. Logically, I now understand, this makes no sense. <a href="#user-content-fnref-australian" data-footnote-backref="" aria-label="Back to reference 1" class="data-footnote-backref">↩</a></p> </li> </ol> </section> The State of the Open Social Web - Werd I/O 691d00cbb05e17000104fa81 2025-11-18T23:50:45.000Z <img src="https://werd.io/content/images/2025/11/getty-images-soH32BfLLdg-unsplash.jpg" alt="The State of the Open Social Web"><p>With <a href="https://blog.joinmastodon.org/2025/11/the-future-is-ours-to-build-together/?ref=werd.io">today&#x2019;s news of a new leadership team for Mastodon</a>, I thought it would be a good time to take stock of the state of the open social web.</p><p>I&#x2019;m not new to this space, or an impartial observer: I co-founded <a href="https://elgg.org/?ref=werd.io">Elgg</a>, one of the first open source social networking platforms, over twenty years ago. It was used by governments, Fortune 500 companies, global NGOs, and universities, and embraced early open standards. <a href="https://www.wired.com/2014/09/known/?ref=werd.io">Known</a>, a social publishing platform I co-founded, was used by media companies to build award-winning communities. And I&#x2019;m on the board of <a href="https://anew.social/?ref=werd.io">A New Social</a>, a non-profit dedicated to bridging open social platforms.</p><h3 id="what-is-the-open-social-web">What is the open social web?</h3><p>When you think of social media, many of the platforms you think of are what we call <em>proprietary</em>: their underlying software is private to the companies that build them, and it&#x2019;s very difficult to move your data or your connections anywhere else. They are, in a very real way, closed. The <a href="https://indieweb.org/?ref=werd.io">indie web movement</a> goes so far as to call them <em>silos</em>.</p><p>These proprietary silos include:</p><ul><li><a href="https://x.com/?ref=werd.io">X</a> (formerly Twitter): now owned by Elon Musk, <a href="https://www.pbs.org/newshour/politics/how-elon-musk-uses-his-x-social-media-platform-to-amplify-right-wing-views?ref=werd.io">who actively promotes far-right voices on the platform and in its algorithms</a>.</li><li>Meta&#x2019;s platforms: chiefly <a href="https://facebook.com/">Facebook</a>, <a href="https://instagram.com/?ref=werd.io">Instagram</a>, <a href="https://threads.com/?ref=werd.io">Threads</a>, and <a href="https://whatsapp.com/?ref=werd.io">WhatsApp</a>. (A caveat for Threads follows below.) Meta was, of course, credibly accused of <a href="https://www.amnesty.org/en/latest/news/2022/09/myanmar-facebooks-systems-promoted-violence-against-rohingya-meta-owes-reparations-new-report/?ref=werd.io">substantially contributing to the genocide in Myanmar</a> by Amnesty and others. Owners of pages across Meta properties increasingly find that they need to pay to reach their followers.</li><li><a href="https://www.tiktok.com/?ref=werd.io">TikTok</a>, which is <a href="https://www.npr.org/2025/10/06/nx-s1-5560216/who-is-larry-ellison-the-billionaire-trump-friend-whos-part-of-the-tiktok-takeover?ref=werd.io">now controlled by the Trump-aligned Ellison family</a>.</li><li><a href="https://linkedin.com/?ref=werd.io">LinkedIn</a>, the business network owned by Microsoft, which has placed itself in the middle of many hiring and job-seeking processes, and whose professional subscription costs between $30 and $100 a month.</li></ul><h3 id="why-should-you-care">Why should you care?</h3><p>Anyone whose brand or livelihood depends on these networks has created risk for themselves: there&#x2019;s nothing to stop any of them from changing their business policies in a detrimental way, as X did <a href="https://www.npr.org/2023/04/12/1169269161/npr-leaves-twitter-government-funded-media-label?ref=werd.io">when it labeled NPR as state-affiliated media</a>. Any of these networks could disappear, <a href="https://www.brookings.edu/articles/tiktok-may-not-be-chinese-owned-anymore-but-there-still-is-a-privacy-problem/?ref=werd.io">as TikTok almost did in the US</a> before being strong-armed into selling its US business to a Trump ally. And referrals to websites could dry up, <a href="https://digitalcontentnext.org/blog/2024/09/12/how-metas-news-ban-reshaped-canadian-media/?ref=werd.io">as happened in Canada when Meta stopped linking to news sites</a>.</p><p>Proprietary silos each have an owner that can, ultimately, do what it wants with them. At best, that can mean that users receive content through curated feeds that might suppress certain kinds of content (including links to certain publishers) and promote others. At worst, it means that traffic and reach could disappear at any time.</p><p>In contrast, open social web platforms are designed <em>not</em> to be silos. While each platform is built by a core vendor or community, they run on open protocols that <em>anyone</em> can build software for.</p><p>Remember AOL? That was a closed silo. To publish content, you needed to have a relationship with AOL the company. But you don&#x2019;t need to have a relationship with anyone in particular to publish a website.</p><p>The open social web works the same way as the web itself: it&#x2019;s permissionless. You don&#x2019;t need to have a relationship with anyone in particular to have a profile and gain reach. And that means nobody can take it away from you.</p><p>Just as AOL became less and less relevant, because maintaining a relationship with AOL the company was more friction than simply publishing a website, silos will become less important and the open social web will become more important over time.</p><p>It&#x2019;s early days in that movement. The user bases are still small in comparison. Today, these networks are still mostly filled with early adopters. In my direct experience working with <a href="https://propublica.org/?ref=werd.io">ProPublica</a>, they&#x2019;re more likely to engage with journalism and information, more likely to donate to non-profit causes, and more likely to re-share according to their values &#x2014; perhaps because they&#x2019;re people who have self-selected to move away from proprietary silos. That means these networks are worth engaging with now &#x2014; and because they will continue to grow, doing so is a good investment in the future.</p><p>So what <em>are</em> those open social web platforms and what is their status today?</p><div class="kg-card kg-callout-card kg-callout-card-green"><div class="kg-callout-emoji">&#x2709;&#xFE0F;</div><div class="kg-callout-text">I write about technology that serves journalism and democracy, and work with newsrooms, open source movements, and startups. <a href="https://werd.io/#/portal/subscribe" rel="noreferrer"><b><strong style="white-space: pre-wrap;">Sign up for a free newsletter subscription</strong></b></a> or <a href="https://werd.io/call/" rel="noreferrer"><b><strong style="white-space: pre-wrap;">book a call to see how I can help you</strong></b></a>.</div></div><h3 id="prevailing-open-social-web-networks">Prevailing open social web networks</h3><p>There are two main open social web networks. This isn&#x2019;t so much a rivalry as a technical consideration: each is based on a different open protocol.</p><p>I&#x2019;ll describe them both in turn.</p><h4 id="the-fediverse">The Fediverse</h4><p>You&#x2019;ve probably heard of <a href="https://joinmastodon.org/?ref=werd.io">Mastodon</a>. When Musk bought Twitter in late 2022, this was the first network that people flocked to, although it had been running for many years before that.</p><p>It can be hard for newcomers to get their head around. Whereas to join a silo network you just go to that network&#x2019;s website and sign up, Mastodon is best thought of as a co-operative network of smaller communities, each with their own rules and culture. That means that signing up involves choosing a community you trust and signing up to <em>that</em>, which is a big ask. How, after all, do you know? In reality, you can&#x2019;t go wrong by joining <a href="https://mastodon.social/?ref=werd.io">mastodon.social</a>, the flagship community run by the project itself.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://werd.io/content/images/2025/11/image.png" class="kg-image" alt="The State of the Open Social Web" loading="lazy" width="1594" height="975" srcset="https://werd.io/content/images/size/w600/2025/11/image.png 600w, https://werd.io/content/images/size/w1000/2025/11/image.png 1000w, https://werd.io/content/images/2025/11/image.png 1594w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Source: </span><a href="https://blog.joinmastodon.org/2025/11/mastodon-4.5/?ref=werd.io" rel="noreferrer"><span style="white-space: pre-wrap;">Mastodon blog</span></a></figcaption></figure><p>But you don&#x2019;t need to use Mastodon&#x2019;s software to join the network. This brings us back to Meta&#x2019;s Threads: it has support for the underlying protocol, so you can actually talk to and follow Mastodon users from there (and they can follow you). For publishers, <a href="https://flipboard.com/?ref=werd.io">Flipboard</a> has become <a href="https://thelettertwo.com/2024/12/15/behind-flipboard-fediverse-embrace-mike-mccue-interview/?ref=werd.io">&#x201C;a Fediverse browser&#x201D;</a> (particularly in tandem with its newsreader <a href="https://surf.social/?ref=werd.io">Surf</a>, which I use every day), while the <a href="https://www.newsmastfoundation.org/?ref=werd.io">Newsmast Foundation</a> is working to onboard newsrooms and surface trustworthy journalism on the network. <a href="https://ghost.org/?ref=werd.io">Ghost</a> and <a href="https://wordpress.org/plugins/activitypub/?ref=werd.io">WordPress</a> both now have extensive support (Ghost&apos;s keeps getting slicker and slicker). And there&#x2019;s a long tail of other platforms like <a href="https://bonfirenetworks.org/?ref=werd.io">Bonfire</a> and <a href="https://friendi.ca/?ref=werd.io">Friendica</a> that are compatible, too, all powered by the underlying <a href="https://activitypub.rocks/?ref=werd.io">ActivityPub</a> protocol.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://werd.io/content/images/2025/11/image-3-1.png" class="kg-image" alt="The State of the Open Social Web" loading="lazy" width="2000" height="1333" srcset="https://werd.io/content/images/size/w600/2025/11/image-3-1.png 600w, https://werd.io/content/images/size/w1000/2025/11/image-3-1.png 1000w, https://werd.io/content/images/size/w1600/2025/11/image-3-1.png 1600w, https://werd.io/content/images/2025/11/image-3-1.png 2000w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Source: </span><a href="https://techcrunch.com/2025/01/20/flipboards-new-app-surf-adds-its-own-video-feed-too/?ref=werd.io" rel="noreferrer"><span style="white-space: pre-wrap;">Flipboard Surf, via TechCrunch</span></a></figcaption></figure><p>Regardless, Mastodon is definitely the flagship. It&#x2019;s also the only European major social network. A longtime German entity, <a href="https://blog.joinmastodon.org/2024/04/mastodon-forms-new-u.s.-non-profit/?ref=werd.io#mastodons-non-profit-status-in-germany">its non-profit status was stripped some years ago</a> (the team says it was never told why), <a href="https://blog.joinmastodon.org/2025/11/the-future-is-ours-to-build-together/?ref=werd.io">and it&#x2019;s now in transition to becoming a Belgian AISBL</a>. Backers include Craig Newmark, Twitter co-founder Biz Stone (who once sat on the advisory board for Elgg, the open source social networking platform I co-founded), and Stack Overflow co-founder Jeff Atwood.</p><p>Critics say it hasn&#x2019;t focused enough on user experience, that it feels too different from other networks, and that it can be a bit nerdier. The thing is, there&#x2019;s still everything to play for here: a solid foundation, millions of dedicated users, and a governance and business model that&#x2019;s very different to the Silicon Valley norm. And at the time of writing, Mastodon just announced a new executive team that includes a lead dedicated to the communities it runs. That new lead, Hannah Aubry, is incredibly important. Bluesky has made great strides by focusing on the culture of its own flagship site, and there&#x2019;s much more that Mastodon could do here.</p><p>I&#x2019;m personally more excited about Mastodon than ever. Even if it fails &#x2014; which I don&#x2019;t believe it will &#x2014; it&#x2019;s forging a brave and different path.</p><p><strong>Quick facts about the Fediverse:</strong></p><ul><li>Flagship platform: <a href="https://joinmastodon.org/?ref=werd.io">Mastodon</a></li><li>Fastest place to sign up: <a href="https://mastodon.social/?ref=werd.io">mastodon.social</a></li><li>Other platforms: <a href="https://ghost.org/?ref=werd.io">Ghost</a>, <a href="https://flipboard.com/?ref=werd.io">Flipboard</a>, <a href="https://pixelfed.social/?ref=werd.io">Pixelfed</a>, <a href="https://friendi.ca/?ref=werd.io">Friendica</a>, <a href="https://fediverse.party/?ref=werd.io">etc</a></li><li>Funding model: Mastodon is becoming a Belgian non-profit. It also has a US 501(c)3 and an original German entity that it is moving away from. It is funded through donations and <a href="https://joinmastodon.org/hosting?ref=werd.io">its own managed hosting services</a>.</li><li>Advocacy groups: <a href="https://socialwebfoundation.org/?ref=werd.io">Social Web Foundation</a></li><li>Underlying protocol: <a href="https://activitypub.rocks/?ref=werd.io">ActivityPub</a></li></ul><h4 id="the-atmosphere">The ATmosphere</h4><p>The network built on the <a href="https://atproto.com/?ref=werd.io">AT Protocol</a> &#x2014; often playfully called &#x201C;The ATmosphere&#x201D; &#x2014; functions a bit differently. On Mastodon, you join a neighborhood; on Bluesky, you rent a storage unit that any app can access.</p><p><a href="https://bsky.social/?ref=werd.io">Bluesky</a> began as an internal Twitter project championed by then-CEO Jack Dorsey. Concerned about political pressure on Twitter&#x2019;s content decisions, he imagined moving parts of the platform&#x2019;s governance onto an open protocol that nobody could fully control. In that vision, Twitter itself might eventually run on the underlying protocol.</p><p>In its earliest phase, Bluesky was essentially a working group of open-source developers exploring what a &#x201C;locked-open&#x201D; social protocol might look like. <a href="https://bsky.app/profile/jay.bsky.team?ref=werd.io">Jay Graber</a> quickly emerged as the clearest technical and organizational leader, and she ultimately convinced Dorsey to spin the project out as an independent company.</p><p>Because of that lineage, the flagship Bluesky app looks and feels like Twitter in many ways. When the site opened to the public, most of the people who left Elon Musk&#x2019;s X chose Bluesky because it felt more familiar and easier to understand than Mastodon. Over time, the team found itself building not just a protocol, but a full social platform with moderation, recommendation features, and active community management. It&#x2019;s now the de facto open social web network for journalists, writers, and other public intellectuals.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://werd.io/content/images/2025/11/image-1.png" class="kg-image" alt="The State of the Open Social Web" loading="lazy" width="2000" height="1111" srcset="https://werd.io/content/images/size/w600/2025/11/image-1.png 600w, https://werd.io/content/images/size/w1000/2025/11/image-1.png 1000w, https://werd.io/content/images/size/w1600/2025/11/image-1.png 1600w, https://werd.io/content/images/2025/11/image-1.png 2160w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Source: </span><a href="https://bsky.social/about/blog/04-21-2025-verification?ref=werd.io" rel="noreferrer"><span style="white-space: pre-wrap;">Bluesky blog</span></a></figcaption></figure><p>Dorsey&#x2019;s involvement has been a point of criticism, but ironically, Bluesky&#x2019;s emphasis on community health is part of what pushed him away. His original aim was to avoid building trust and safety systems altogether: he sees moderation as a path to censorship rather than a requirement for healthy online spaces. Once Bluesky embraced trust and safety as core ethical work rather than something to outsource to the protocol, he walked away and shifted his support to Nostr, a more libertarian network designed to minimize moderation.</p><p>All of this sits on AT Protocol&#x2019;s underlying model. While Mastodon is a series of federated communities, on Bluesky, every profile is actually a storage unit containing that user&#x2019;s data. When you follow someone, you&#x2019;re really subscribing to updates to their data. Other applications beyond the flagship Bluesky app can also write to your data store: <a href="https://leaflet.pub/home?ref=werd.io">Leaflet</a> is a blogging platform, <a href="https://www.graze.social/?ref=werd.io" rel="noreferrer">Graze</a> lets you build custom feeds according to your interests, and so on.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://werd.io/content/images/2025/11/image-4.png" class="kg-image" alt="The State of the Open Social Web" loading="lazy" width="1000" height="738" srcset="https://werd.io/content/images/size/w600/2025/11/image-4.png 600w, https://werd.io/content/images/2025/11/image-4.png 1000w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Source: </span><a href="https://www.graze.social/blog/making-it-easier-than-ever?ref=werd.io" rel="noreferrer"><span style="white-space: pre-wrap;">Graze blog</span></a></figcaption></figure><p>By default, your data is stored with Bluesky, but there are other options. The best known and in many ways the most exciting is <a href="https://blackskyweb.xyz/?ref=werd.io">Blacksky</a>, Rudy Fraser&#x2019;s Black-owned datastore and social application. <a href="https://www.reuters.com/business/media-telecom/european-project-eurosky-aims-reduce-reliance-us-tech-giants-2025-07-15/?ref=werd.io">Eurosky</a>, meanwhile, is an emerging attempt to build outside of North American jurisdiction.</p><p><strong>Quick facts about the ATmosphere:</strong></p><ul><li>Flagship platform: <a href="https://bsky.social/?ref=werd.io">Bluesky</a></li><li>Fastest place to sign up: <a href="https://bsky.app/?ref=werd.io">bsky.app</a></li><li>Other platforms: <a href="https://blackskyweb.xyz/?ref=werd.io">Blacksky</a>, <a href="https://www.graze.social/?ref=werd.io">Graze</a>, <a href="https://leaflet.pub/?ref=werd.io">Leaflet</a>, <a href="https://apps.apple.com/us/app/flashes-for-bluesky/id6741443033?ref=werd.io">Flashes</a>, <a href="https://techcrunch.com/2025/06/13/beyond-bluesky-these-are-the-apps-building-social-experiences-on-the-at-protocol/?ref=werd.io">etc</a></li><li>Funding model: Bluesky is a VC-funded startup that has <a href="https://www.texau.com/profiles/bluesky?ref=werd.io">raised at least $36M to date</a>. It hasn&#x2019;t deployed a business model yet.</li><li>Advocacy groups: <a href="https://freeourfeeds.com/?ref=werd.io">Free Our Feeds</a></li><li>Underlying protocol: <a href="https://atproto.com/?ref=werd.io">AT Protocol</a></li></ul><h4 id="protocol-tldr">Protocol TL;DR</h4><p><strong>If you don&#x2019;t care about protocols at all, here&#x2019;s the summary:</strong></p><ul><li>The Fediverse = small communities talking to each other</li><li>AT Protocol = personal data pods that apps plug into</li><li>Both = avoid lock-in, centralized power, and unpredictable corporate incentives</li></ul><h4 id="other-alternatives">Other alternatives</h4><p>The Fediverse and the ATmosphere aren&#x2019;t the only open social web networks, although they are by far the most prominent.</p><p><a href="https://nostr.com/?ref=werd.io">Nostr</a> and <a href="https://farcaster.xyz/?ref=werd.io">Farcaster</a> have both attracted a crowd that&#x2019;s heavy on crypto and libertarian ideologies. (<a href="https://techcrunch.com/2025/11/12/jack-dorsey-funds-divine-a-vine-reboot-that-includes-vines-video-archive/?ref=werd.io">diVine</a>, funded by Dorsey and run by Twitter OG Rabble, rebuilds Vine on the Nostr network.) Given his relationship with trust and safety, it shouldn&#x2019;t be a surprise that these networks are very technically pure but low on community safety or culture-building.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://werd.io/content/images/2025/11/image-2.png" class="kg-image" alt="The State of the Open Social Web" loading="lazy" width="787" height="800" srcset="https://werd.io/content/images/size/w600/2025/11/image-2.png 600w, https://werd.io/content/images/2025/11/image-2.png 787w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Source: </span><a href="https://www.nos.social/blog/nostr-user-led-social-media?ref=werd.io" rel="noreferrer"><span style="white-space: pre-wrap;">Nos Social blog</span></a></figcaption></figure><p>Meanwhile, <a href="https://www.projectliberty.io/?ref=werd.io">Project Liberty</a> is a $500M endeavor funded by <a href="https://www.projectliberty.io/people/frank-mccourt/?ref=werd.io">Frank McCourt</a>, which has built its own open social layer, <a href="https://www.frequency.xyz/?ref=werd.io">Frequency</a>. It&#x2019;s also <a href="https://www.projectliberty.io/governance/?ref=werd.io">funded policy blueprints</a> and <a href="https://www.projectliberty.io/news/utah-digital-choice-act/?ref=werd.io">real legislation</a> designed to give users more control over their social media.</p><p>And <a href="https://indieweb.org/?ref=werd.io">the indie web</a>, which builds decentralized social functionality on top of web fundamentals like HTML, should not be discounted. Its focus on a world where everyone&#x2019;s profile is a website that is completely unique to them but can still communicate and share together has led to an enthusiastic community that&#x2019;s been growing for over a decade. I think the indie web and the open social web are complimentary: you can build a following on the open social web <em>and</em> build an amazing indie web website that really represents you.</p><h3 id="working-across-networks">Working across networks</h3><p>The protocols are not as important as the people and communities who connect across them. Part of the ethos of openness is that it&#x2019;s not about building a &#x201C;winner&#x201D;: nobody should be locked into any platform or technology. These movements are also too young for everyone to have converged on one underlying protocol; we&#x2019;re still at the early innovation stage.</p><p>I&#x2019;m on the board of <a href="https://anew.social/?ref=werd.io">A New Social</a>, a non-profit whose mission is to provide bridges between protocols, creating a single, unified open social web. Its product <a href="https://fed.brid.gy/?ref=werd.io">Bridgy Fed</a> has long allowed you to follow Fediverse profiles on the ATmosphere and vice versa. And its new product, Bounce, <a href="https://blog.anew.social/bounce-a-cross-protocol-migration-tool/?ref=werd.io">allows you to easily move your profile from one network to the other, bringing your network with you</a>. The result is even less lock-in: you can decide which network makes the most sense for you.</p><p>Earlier in this piece, I also mentioned <a href="https://surf.social/?ref=werd.io">Surf</a>, which allows you to find news stories and conversations you care about across all the open social web networks. I&#x2019;ve curated a <a href="https://surf.social/feed/surf%2Fcustom%2F01jf18nkhmm632b8qtret48113?ref=werd.io">feed of non-profit US newsrooms</a> and one of <a href="https://surf.social/feed/surf%2Fcustom%2F01jtzwwt6re2q9s45jxnq36dbt?ref=werd.io">investigative tech journalism</a> that are also <a href="https://bsky.app/profile/did:plc:77tdak46psveqneyegsdyc7l/feed/speaking-trut?ref=werd.io">available</a> as <a href="https://bsky.app/profile/did:plc:77tdak46psveqneyegsdyc7l/feed/speakingtruth?ref=werd.io">Bluesky feeds</a>.</p><p><a href="https://buffer.com/?ref=werd.io">Buffer</a> is one of many social scheduling tools that support both networks. <a href="https://fedica.com/?ref=werd.io">Fedica</a> allows you to analyze your stats and surfaces characteristics of your community.</p><p>These are the kinds of tools that could only exist on the open social web. A network like X would charge these providers a ton of money; others would simply block them as a threat to their business models. But on the open social web, where the open protocols are permissionless and open to all, they can thrive.</p><h3 id="it%E2%80%99s-not-zero-sum">It&#x2019;s not zero sum</h3><p>There&#x2019;s no reason you need to pick just one. Like many people, I maintain profiles <a href="https://werd.social/@ben?ref=werd.io">on Mastodon</a> and <a href="https://bsky.app/profile/werd.io?ref=werd.io">Bluesky</a>, as well as <a href="https://werd.io/">through my blog</a> and on <a href="https://threads.com/@ben.werdmuller?ref=werd.io">Threads</a>, and I&#x2019;ve found that the conversations across all of them are different. There&#x2019;s a lot to be gained on each &#x2014; but, again, <a href="https://fed.brid.gy/?ref=werd.io">Bridgy Fed</a> and <a href="https://blog.anew.social/bounce-a-cross-protocol-migration-tool/?ref=werd.io">Bounce</a> mean you can try one without worrying about missing out.</p><p>The most important thing is to take back ownership of your community and your relationships online. The silo networks have done their best to become intermediaries between you and your connections, forcing you to pay to reach people who have already subscribed to you. They&#x2019;ve partnered with authoritarian governments and caused great harm through their negligence and blinkered self-interest.</p><p>Another world is possible, and it&#x2019;s only just beginning. Go grab your profile and get started.</p> Home alone gaming - W46 - Joel's Log Files https://joelchrono.xyz/blog/w46 2025-11-18T23:50:00.000Z <p>Long weekends while home alone are the best! You are going to find quite a lot of gaming was done this time around, however, I also catched up on quite a bit of manga, and went through some painful stuff at work, but nothing too bad.</p> <ul> <li> <p>🎨 I have completed and <a href="https://codeberg.org/joelchrono/AdwaitaPod-Arcticons-rockbox-360p">published</a> my AdwaitaPod-based theme (with Arcticons) for the Innioasis Y1! Feel free to give it a download if you get the device, and check the original theme as well if you want to try it on an iPod or something.</p> </li> <li> <p>💻 My coworker got a vacation on Friday and it was pain. Some critical machinery malfunctioned at the worst possible moment. I had to set up a plan B in case the automation/maintenance team couldn’t manage to repair it in time. I was on-call during Saturday too, thankfully things turned out fine.</p> </li> <li> <p>🏠 I stayed home alone for pretty much the whole week. I survived thanks to the existence of cereal and buying lunch at my workplace, I spent most of my time early in the week working on my theme, then I played a bunch of videogames during the weekend.</p> </li> <li> <p>🐕 My doggo still doesn’t want to use his front leg, I guess there was some nerve damage after all, he doesn’t seem to mind that much so, I’ll just try to get over it, I guess? I wonder if there’s some other therapy to try.</p> </li> <li> <p>🛍️ <em>El Buen Fin</em> (The Good Weekend) is the mexican equivalent to Black Friday, and I fell for it. I acquired <em>Crypt Custodian</em> and <em>Dragon Quest I &amp; II HD-2D Remake</em> for Nintendo Switch. I also acquired <em>Streets of Rage 4 Anniversary Edition</em>, which arrived early in the week and I have already completed the campaign so that’s fine.</p> </li> <li> <p>🍕 Went out for some pizzas on Sunday night with friends, it was pretty great and we got to drink some Horchata which is always a big plus.</p> </li> <li> <p>🎵 Even if I don’t have a music/listening section, I think I can always say some album or song that is currently on my mind. Right now it would be: <em>Daydream</em> by Tatsuro Yamashita</p> </li> </ul> <h2 id="reading">Reading</h2> <p>When it comes to manga I am actually rather impressed with myself. After finishing <em>Ariadne in the Blue Sky</em>, I got to catch up on a lot of other stuff! Here’s it. When it comes to books, not that bad.</p> <ul> <li><strong>Exit Strategy</strong> - Up to chapter 4. There’s some characters that made a comeback and it’s been really interesting to watch it unfold. I hope to finish this book this week!</li> <li><strong>Blue Lock</strong> - Up to chapter 325. The match against Nigeria resulted in an absolute victory for the Japanese team. It was an absolutely bloodbath, but the next two matches looks like they’ll be a real challenge, France and England are next.</li> <li><strong>Chainsaw Man</strong> - Up to chapter 220. Crazy stuff is happening as always, for example, a whole city was turned into a weapon.</li> <li><strong>Frieren: Beyond Journey’s End</strong> - Up to chapter 142. Finally returned to Frieren, and I love these characters so much. The current arc is super stressful though, and the action is about to begin.</li> <li><strong>Sakamoto Days</strong> - Up to chapter 227. With Sakamoto out of commission, everyone tries to escape, and new enemies have showed up, crazy action sequences upcoming.</li> </ul> <h2 id="gaming">Gaming</h2> <p>This was definitely a week filled with gaming to the brim! I will even make the format a little different because I got a lot to say about a few of the titles here.</p> <h3 id="completed">Completed</h3> <h4 id="streets-of-rage-4">Streets of Rage 4</h4> <p>I didn’t expect this at all, but in a moment of weakness, I bought the physical <em>Anniversary Edition</em> of this game, and as soon as I put the cartridge on, we just couldn’t put it down.</p> <p>An incredible soundtrack took over my speakers, and we just had to start it immmediately. I played it with my friends and completed the campaign in one sitting.</p> <p>The simple combat, the animation, the artstyle, the excellent dynamic musical score. This game is probably the best the franchise has to offer, a glorious return to form for anyone who missed the franchise on the Sega Genesis.</p> <p>Solo, I did a single run of the roguelike mode, and died on the first boss fight, and I decided to focus on other games for now. I can see myself returning to this plenty of times with friends.</p> <h3 id="ongoing">Ongoing</h3> <h4 id="the-hundred-line-last-defense-academy">The Hundred Line: Last Defense Academy</h4> <p>This game’s the reason my weeknotes are a day late, and the main thing that occupied my time. I started it and “finished” its story months ago. If you are curious, this is officially my most played game on my Nintendo Switch, and it has managed to hook me quite a bit. After beating the game once, the game allows you to play it a second time. However, this playthrough lets you make decisions throughout the story, creating deviations that amount to a total of <strong>one hundred endings</strong>. This mix between tactics RPG and visual novel is really cool.</p> <p>This has been an extremely interesting choice from the developers, and it has allowed me to see how every character in the game reacts to the outcomes at hand. It’s fun, stressful, terrifying and exciting. So far, I have gone through 11 endings. Multiple writers were involved, some routes are shorter than others, some moments are samey, and others are absolutely insane. There’s a lot to like, and I absolutely love (most of) these characters. The game lets you skip battles you already played and return to previous branches at any point, it’s unbelievable.</p> <h4 id="streets-of-rage-2">Streets of Rage 2</h4> <p>After a 10 hour-long session of <em>The Hundred Line</em>, I felt like trying out something quick before going to sleep. Alas, <em>Streets of Rage 2</em> seemed worth trying since I had recently fiddled with the first one and this one was considered the best of the franchise.</p> <p>And well, yes, that music told me everything I needed to know. It was cleaner and more polished than the first one. I got to the 3rd stage in a few minutes and things were pretty cool, I must say that the second boss flying on a jetpack was very annoying though.</p> <h4 id="others">Others</h4> <ul> <li><strong>CrossCode</strong>: I should have focused more on this one, if I’m honest, all I did this time was focus on getting leveled up equipment and progressing through the story. For now, I joined a guild, I unlocked my way to the dungeon, and now I just want to get a bit more XP before venturing inside!</li> <li><strong>Astro Boy: Omega Factor</strong>: I returned to this game after a break and it’s honestly just awesome plain fun. A beat’em up platformer that goes through lots of iconic moments of the franchise, fun stuff!</li> <li><strong>Monster Hunter Rise</strong>: To break things up I also progressed a bit on the high rank quests of this game, I went for a Rathian, defeated her, and called it a day.</li> <li><strong>Spelunky</strong>: After me and my friends beat <em>Streets of Rage 4</em>, we felt like trying something a little more chaotic for a bit, so we tried a couple runs of this game, and of course, failed miserably.</li> <li><strong>Slice &amp; Dice</strong>: I hadn’t played this much but during a meet-up with other S&amp;D friends we decided to do a race to see who could win a run first. I didn’t win, but trying was fun!</li> </ul> <h2 id="around-the-web">Around the Web</h2> <p>I read most of these earlier in the week and you can tell, lol.</p> <h3 id="blogposts">Blogposts</h3> <ul> <li><a href="https://thatalexguy.dev/posts/a-morning-of-physical-media/">A Morning of Physical Media</a> - There’s just something about the physical interaction with stuff that is unmatched.</li> <li><a href="https://tadaima.bearblog.dev/learning-spanish-update/">Learning Spanish Update</a> - It’s weird to see <em>La Rosa de Guadalupe</em> mentioned in a blog I follow.</li> <li><a href="https://manuelmoreale.com/thoughts/input-diet">Input diet</a> - yet another good post about media intake and managing that stuff.</li> <li><a href="http://www.bjoreman.com/diary/2025/2025-11-12.html">Offline first?</a> - Just being a little more offline, and interacting with less short/quick/easy stuff and replace it with meaningful things.</li> <li><a href="https://brainbaking.com/post/2025/11/why-i-dont-need-a-steam-machine/">Why I Don’t Need a Steam Machine</a> - Wouter has a big backlog, and so do I.</li> <li><a href="https://blog.laurahargreaves.com/self-hosting-for-everyone/">Self-Hosting for Everyone</a> - Laura is a new discovery, and she is a self-hosting sicko, I have enjoyed her blogposts so far!</li> <li><a href="https://brandons-journal.com/my-relationship-with-stuff/">My Relationship with Stuff</a> - Curating is better than hoarding, don’t just collect for the sake of it. Figure out what you actually want! I gotta remember this.</li> </ul> <h3 id="videos">Videos</h3> <ul> <li><a href="https://youtu.be/NBZv0_MImIY">Mind if I complaing for 15 minutes?</a> - I love JadenAnimations and this video is one that I’m sure many will find relatable (not me, I am glad I’ve avoided all that stuff).</li> <li><a href="https://youtu.be/B38CY-4Rd6s">Daft Punk Alive 2007 full concert</a> - I had no idea this was a thing that existed, but I am extremely, extremely happy that it does. Just take a break from everything and experience this album as if it’s 2007.</li> <li><a href="https://youtu.be/7GeCq1qwqjc">Why I uninstalled ublock origin, this is WAY better!</a> - This is an interesting plugin, although I don’t know if I’ll actually use it.</li> </ul> <p> <a href="mailto:me@joelchrono.xyz?subject=Home alone gaming - W46">Reply to this post via email</a> | <a href="https://fosstodon.org/@joel/commmentsid">Reply on Fediverse</a> </p> 2025 Stickers Redux - Robb Knight • Posts • Atom Feed https://rknight.me/blog/2025-stickers-redux/ 2025-11-18T12:25:06.000Z <p>Now the dust has settled on the <a href="https://rknight.me/blog/pirate-bay-training-data-stickers/">piracy stickers</a>, I checked how many I had left and it was a few more than I thought so I'm putting them back up for sale until they run out. Here's what I have available:</p> <ul> <li>Four pack of the Don't Know Don't Care Don't @ Me stickers. There's only two of these available. <a href="https://buy.stripe.com/dRmdR90Ew2QKdfC4Uc2kw04">Buy</a></li> <li>Training Data double pack. 2 × Pirate Bay, 2 × Piracy Ad. I have 20 or so packs of these. <a href="https://buy.stripe.com/14AaEXaf6ezs7Vi86o2kw02">Buy</a></li> <li>Pirate Bay four pack. There's 10 or so of these available. <a href="https://buy.stripe.com/5kQeVdcnebng8ZmgCU2kw03">Buy</a></li> </ul> <p>I will do another run of the <a href="https://rknight.me/blog/dont-at-me-stickers/">Sparkly Websites stickers</a> very soon — I don't have any of those left right now. As always, the <a href="/shop">shop page</a> has all the links as well, at least until I decide on a better system for selling stickers.</p> We found cryptography bugs in the elliptic library using Wycheproof - Trail of Bits Blog https://blog.trailofbits.com/2025/11/18/we-found-cryptography-bugs-in-the-elliptic-library-using-wycheproof/ 2025-11-18T12:00:00.000Z <p>Trail of Bits is publicly disclosing two vulnerabilities in <a href="https://www.npmjs.com/package/elliptic">elliptic</a>, a widely used JavaScript library for elliptic curve cryptography that is downloaded over 10 million times weekly and is used by close to 3,000 projects. These vulnerabilities, caused by missing modular reductions and a missing length check, could allow attackers to forge signatures or prevent valid signatures from being verified, respectively.</p> <p>One vulnerability is still not fixed after a 90-day disclosure window that ended in October 2024. It remains unaddressed as of this publication.</p> <p> <img src="https://blog.trailofbits.com/img/wycheproof-elliptic-library/wycheproof-1.png" alt="indutny/elliptic" /> </p> <p>I discovered these vulnerabilities using <a href="https://github.com/C2SP/wycheproof">Wycheproof</a>, a collection of test vectors designed to test various cryptographic algorithms against known vulnerabilities. If you’d like to learn more about how to use Wycheproof, check out <a href="https://appsec.guide/docs/crypto/wycheproof/">this guide I published</a>.</p> <p>In this blog post, I’ll describe how I used Wycheproof to test the elliptic library, how the vulnerabilities I discovered work, and how they can enable signature forgery or prevent signature verification.</p> <p> <img src="https://blog.trailofbits.com/img/wycheproof-elliptic-library/wycheproof-2.png" alt="C2SP/wychproof" /> </p> <h2 id="methodology">Methodology</h2> <p>During my internship at Trail of Bits, I wrote a <a href="https://appsec.guide/docs/crypto/wycheproof/">detailed guide</a> on using Wycheproof for <a href="https://appsec.guide/docs/crypto/">the new cryptographic testing chapter of the Testing Handbook</a>. I decided to use the elliptic library as a real-world case study for this guide, which allowed me to discover the vulnerabilities in question.</p> <p>I wrote a Wycheproof testing harness for the elliptic package, as described in the guide. I then analyzed the source code covered by the various failing test cases provided by Wycheproof to classify them as false positives or real findings. With an understanding of why these test cases were failing, I then wrote proof-of-concept code for each bug. After confirming they were real findings, I began the coordinated disclosure process.</p> <h2 id="findings">Findings</h2> <p>In total, I identified five vulnerabilities, resulting in five CVEs. Three of the vulnerabilities were minor parsing issues. I disclosed those issues in a public pull request against the repository and subsequently requested CVE IDs to keep track of them.</p> <p>Two of the issues were more severe. I disclosed them privately using the GitHub advisory feature. Here are some details on these vulnerabilities.</p> <h3 id="cve-2024-48949-eddsa-signature-malleability">CVE-2024-48949: EdDSA signature malleability</h3> <p>This issue stems from a missing out-of-bounds check, which is specified in the <a href="https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-5.pdf#page=40">NIST FIPS 186-5</a> in section 7.8.2, “HashEdDSA Signature Verification”:</p> <blockquote> <p>Decode the first half of the signature as a point <code>R</code> and the second half of the signature as an integer <code>s</code>. Verify that the integer <code>s</code> is in the range of <code>0 ≤ s &lt; n</code>.</p></blockquote> <p>In the elliptic library, the check that <code>s</code> is in the range of <code>0 ≤ s &lt; n</code>, to verify that it is not outside the order <code>n</code> of the generator point, is never performed. This vulnerability allows attackers to forge new valid signatures, <code>sig'</code>, though only for a known signature and message pair, <code>(msg, sig)</code>.</p> $$ \begin{aligned} \text{Signature} &= (msg, sig) \\ sig &= (R||s) \\ s' \bmod n &== s \end{aligned} $$<p>The following check needs to be implemented to prevent this forgery attack.</p> <figure class="highlight"> <pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(</span><span class="nx">sig</span><span class="p">.</span><span class="nx">S</span><span class="p">().</span><span class="nx">gte</span><span class="p">(</span><span class="nx">sig</span><span class="p">.</span><span class="nx">eddsa</span><span class="p">.</span><span class="nx">curve</span><span class="p">.</span><span class="nx">n</span><span class="p">))</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="kc">false</span><span class="p">;</span> </span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre> </figure> <p>Forged signatures could break the consensus of protocols. Some protocols would correctly reject forged signature message pairs as invalid, while users of the elliptic library would accept them.</p> <h3 id="cve-2024-48948-ecdsa-signature-verification-error-on-hashes-with-leading-zeros">CVE-2024-48948: ECDSA signature verification error on hashes with leading zeros</h3> <p>The second issue involves the ECDSA implementation: valid signatures can fail the validation check.</p> <p>These are the Wycheproof test cases that failed:</p> <ul> <li><code>[testvectors_v1/ecdsa_secp192r1_sha256_test.json][tc296]</code> special case hash</li> <li><code>[testvectors_v1/ecdsa_secp224r1_sha256_test.json][tc296]</code> special case hash</li> </ul> <p>Both test cases failed due to a specifically crafted hash containing four leading zero bytes, resulting from hashing the hex string 343236343739373234 using SHA-256:</p> <figure class="highlight"> <pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">00000000690ed426ccf17803ebe2bd0884bcd58a1bb5e7477ead3645f356e7a9</span></span></code></pre> </figure> <p>We’ll use the secp192r1 curve test case to illustrate why the signature verification fails. The function responsible for verifying signatures for elliptic curves is located in <code>lib/elliptic/ec/index.js</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">EC</span><span class="p">.</span><span class="n">prototype</span><span class="p">.</span><span class="n">verify</span> <span class="o">=</span> <span class="n">function</span> <span class="nf">verify</span><span class="p">(</span><span class="n">msg</span><span class="p">,</span> <span class="n">signature</span><span class="p">,</span> <span class="n">key</span><span class="p">,</span> <span class="n">enc</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">msg</span> <span class="o">=</span> <span class="n">this</span><span class="p">.</span><span class="nf">_truncateToN</span><span class="p">(</span><span class="n">new</span> <span class="nf">BN</span><span class="p">(</span><span class="n">msg</span><span class="p">,</span> <span class="mi">16</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></code></pre> </figure> <p>The message must be hashed before it is parsed to the <code>verify</code> function call, which occurs outside the elliptic library. According to <a href="https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-5.pdf#page=34">FIPS 186-5</a>, section 6.4.2, “ECDSA Signature Verification Algorithm,” the hash of the message must be adjusted based on the order <code>n</code> of the base point of the elliptic curve:</p> <blockquote> <p>If <code>log2(n) ≥ hashlen</code>, set <code>E = H</code>. Otherwise, set <code>E</code> equal to the leftmost <code>log2(n)</code> bits of <code>H</code>.</p></blockquote> <p>To achieve this, the <code>_truncateToN</code> function is called, which performs the necessary adjustment. Before this function is called, the hashed message, <code>msg</code>, is converted from a hex string or array into a number object using <code>new</code> <code>BN(msg, 16)</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">EC</span><span class="p">.</span><span class="n">prototype</span><span class="p">.</span><span class="n">_truncateToN</span> <span class="o">=</span> <span class="n">function</span> <span class="nf">_truncateToN</span><span class="p">(</span><span class="n">msg</span><span class="p">,</span> <span class="n">truncOnly</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">var</span> <span class="n">delta</span> <span class="o">=</span> <span class="n">msg</span><span class="p">.</span><span class="nf">byteLength</span><span class="p">()</span> <span class="o">*</span> <span class="mi">8</span> <span class="o">-</span> <span class="n">this</span><span class="p">.</span><span class="n">n</span><span class="p">.</span><span class="nf">bitLength</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">delta</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">msg</span> <span class="o">=</span> <span class="n">msg</span><span class="p">.</span><span class="nf">ushrn</span><span class="p">(</span><span class="n">delta</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></code></pre> </figure> <p>The delta variable calculates the difference between the size of the hash and the order <code>n</code> of the current generator for the curve. If <code>msg</code> occupies more bits than <code>n</code>, it is shifted by the difference. For this specific test case, we use secp192r1, which uses 192 bits, and SHA-256, which uses 256 bits. The hash should be shifted by 64 bits to the right to retain the leftmost 192 bits.</p> <p>The issue in the elliptic library arises because the <code>new BN(msg, 16)</code> conversion removes leading zeros, resulting in a smaller hash that takes up fewer bytes.</p> <figure class="highlight"> <pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">690ed426ccf17803ebe2bd0884bcd58a1bb5e7477ead3645f356e7a9</span></span></code></pre> </figure> <p>During the delta calculation, <code>msg.byteLength()</code> then returns 28 bytes instead of 32.</p> <figure class="highlight"> <pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="nx">EC</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">_truncateToN</span> <span class="o">=</span> <span class="kd">function</span> <span class="nx">_truncateToN</span><span class="p">(</span><span class="nx">msg</span><span class="p">,</span> <span class="nx">truncOnly</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kd">var</span> <span class="nx">delta</span> <span class="o">=</span> <span class="nx">msg</span><span class="p">.</span><span class="nx">byteLength</span><span class="p">()</span> <span class="o">*</span> <span class="mi">8</span> <span class="o">-</span> <span class="k">this</span><span class="p">.</span><span class="nx">n</span><span class="p">.</span><span class="nx">bitLength</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></code></pre> </figure> <p>This miscalculation results in an incorrect delta of <code>32 = (288 - 192)</code> instead of <code>64 = (328 - 192)</code>. Consequently, the hashed message is not shifted correctly, causing verification to fail. This issue causes valid signatures to be rejected if the message hash contains enough leading zeros, with a probability of 2<sup>-32</sup>.</p> <p>To fix this issue, an additional argument should be added to the verification function to allow the hash size to be parsed:</p> <figure class="highlight"> <pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="nx">EC</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">verify</span> <span class="o">=</span> <span class="kd">function</span> <span class="nx">verify</span><span class="p">(</span><span class="nx">msg</span><span class="p">,</span> <span class="nx">signature</span><span class="p">,</span> <span class="nx">key</span><span class="p">,</span> <span class="nx">enc</span><span class="p">,</span> <span class="nx">msgSize</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="nx">msg</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">_truncateToN</span><span class="p">(</span><span class="k">new</span> <span class="nx">BN</span><span class="p">(</span><span class="nx">msg</span><span class="p">,</span> <span class="mi">16</span><span class="p">),</span> <span class="kc">undefined</span><span class="p">,</span> <span class="nx">msgSize</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></span><span class="line"><span class="cl"><span class="nx">EC</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">_truncateToN</span> <span class="o">=</span> <span class="kd">function</span> <span class="nx">_truncateToN</span><span class="p">(</span><span class="nx">msg</span><span class="p">,</span> <span class="nx">truncOnly</span><span class="p">,</span> <span class="nx">msgSize</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kd">var</span> <span class="nx">size</span> <span class="o">=</span> <span class="p">(</span><span class="k">typeof</span> <span class="nx">msgSize</span> <span class="o">===</span> <span class="s1">&#39;undefined&#39;</span><span class="p">)</span> <span class="o">?</span> <span class="p">(</span><span class="nx">msg</span><span class="p">.</span><span class="nx">byteLength</span><span class="p">()</span> <span class="o">*</span> <span class="mi">8</span><span class="p">)</span> <span class="o">:</span> <span class="nx">msgSize</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="kd">var</span> <span class="nx">delta</span> <span class="o">=</span> <span class="nx">size</span> <span class="o">-</span> <span class="k">this</span><span class="p">.</span><span class="nx">n</span><span class="p">.</span><span class="nx">bitLength</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></code></pre> </figure> <h2 id="on-the-importance-of-continuous-testing">On the importance of continuous testing</h2> <p>These vulnerabilities serve as an example of why continuous testing is crucial for ensuring the security and correctness of widely used cryptographic tools. In particular, Wycheproof and other actively maintained sets of cryptographic test vectors are excellent tools for ensuring high-quality cryptography libraries. We recommend including these test vectors (and any other relevant ones) in your CI/CD pipeline so that they are rerun whenever a code change is made. This will ensure that your library is resilient against these specific cryptographic issues both now and in the future.</p> <h2 id="coordinated-disclosure-timeline">Coordinated disclosure timeline</h2> <p>For the disclosure process, we used GitHub’s integrated security advisory feature to privately disclose the vulnerabilities and used the <a href="https://github.com/github/securitylab/blob/main/docs/report-template.md">report template</a> as a template for the report structure.</p> <p>July 9, 2024: We discovered failed test vectors during our run of Wycheproof against the elliptic library.</p> <p>July 10, 2024: We confirmed that both the ECDSA and EdDSA module had issues and wrote proof-of-concept scripts and fixes to remedy them.</p> <h3 id="for-cve-2024-48949">For CVE-2024-48949</h3> <p>July 16, 2024: We disclosed the EdDSA signature malleability issue using the GitHub security advisory feature to the elliptic library maintainers and created a private pull request containing our proposed fix.</p> <p>July 16, 2024: The elliptic library maintainers confirmed the existence of the EdDSA issue, merged our proposed <a href="https://github.com/indutny/elliptic/commit/7ac5360118f74eb02da73bdf9f24fd0c72ff5281">fix</a>, and created a new version without disclosing the issue publicly.</p> <p>Oct 10, 2024: We requested a CVE ID from MITRE.</p> <p>Oct 15, 2024: As 90 days had elapsed since our private disclosure, this vulnerability became public.</p> <h3 id="for-cve-2024-48948">For CVE-2024-48948</h3> <p>July 17, 2024: We disclosed the ECDSA signature verification issue using the GitHub security advisory feature to the elliptic library maintainers and created a private pull request containing our proposed fix.</p> <p>July 23, 2024: We reached out to add an additional collaborator to the ECDSA GitHub advisory, but we received no response.</p> <p>Aug 5, 2024: We reached out asking for confirmation of the ECDSA issue and again requested to add an additional collaborator to the GitHub advisory. We received no response.</p> <p>Aug 14, 2024: We again reached out asking for confirmation of the ECDSA issue and again requested to add an additional collaborator to the GitHub advisory. We received no response.</p> <p>Oct 10, 2024: We requested a CVE ID from MITRE.</p> <p>Oct 13, 2024: Wycheproof test developer Daniel Bleichenbacher independently discovered and disclosed <a href="https://github.com/indutny/elliptic/issues/321">issue #321</a>, which is related to this discovery.</p> <p>Oct 15, 2024: As 90 days had elapsed since our private disclosure, this vulnerability became public.</p> Open Source Power - Werd I/O 691b6b922e52e00001b22a26 2025-11-17T18:38:10.000Z <p>[<a href="https://blog.muni.town/open-source-power/?ref=werd.io">Erlend Sogge Heggen</a>]</p><p>I&#x2019;m extremely on board with this whole essay.</p><blockquote>&#x201C;Since its inception the free and open source software movement has lacked a theory of change beyond the liberation of computer code. Liberation of human beings by way of a <a href="https://theanarchistlibrary.org/library/lewis-herber-murray-bookchin-towards-a-liberatory-technology?ref=werd.io">liberatory technology</a> was always a secondary and oftentimes incompatible concern for Open Source, since laborers having agency of their work (and how it may be exploited) is in conflict with the inviolable liberties of an Open Source computer program.&#x201D;</blockquote><p>As the author points out, open source has facilitated an upwards transfer of wealth and power from contributors and project communities themselves to the larger companies that can make use of this free labor to enrich their own endeavors. In a world where tech has been enabling fascist autocrats in the building of anti-democratic movements and governments, that&#x2019;s particularly troubling. And it&#x2019;s dressed up as some kind of altruistic public good, even when the end user is causing great harm.</p><p>The traditional open source / free software demand that software be made available freely for <em>any</em> use feels outdated in the current context. It&#x2019;s a very libertarian rather than pro-social view of the world. In our current world, it&#x2019;s particularly worth re-examining permissive licenses and considering what they are actually permissive of.</p><p>The suggestion here is that commercial use by large corporations is charged for. I think that makes sense: it&#x2019;s a way of funding development that also preserves the underlying availability of open software and the sustainability of building it. But I&#x2019;d honestly go further.</p><p>Do open source maintainers want their software to be used by ICE, for example? You don&#x2019;t have to allow it to be. There&#x2019;s no magical law of the universe that means you lose part of your soul if you say &#x201C;no&#x201D;. Software is a creative work, and the software you build is <em>your</em> creative work. You get to decide who uses it and under what terms. It&#x2019;s completely reasonable to apply your values in making those decisions.</p><p>[<a href="https://blog.muni.town/open-source-power/?ref=werd.io">Link</a>]</p> Weeknote #1975 - Robb Knight • Posts • Atom Feed https://rknight.me/blog/weeknote-1975/ 2025-11-17T12:35:04.000Z <p>I've narrowed down the inks I'm going to try for finding my perfect pink ink to a handful. I'm planing on ordered <a href="https://mountainofink.com/blog/taccia-momo-pink">Taccia Momo</a> first because this is the one that's been recommended to me the most. I also updated my <a href="http://www.fpc.ink/users/95147">ink list on FPC</a>.</p> <p>I've added some more pens to my wanted list including the <a href="https://www.penaddict.com/blog/2025/3/30/diplomat-viper-fountain-pen-review">Diplomat Viper</a>, the <a href="https://cultpens.com/collections/monteverde-mp1">Monteverde MP1</a>, and the new <a href="https://cultpens.com/products/twsbi-eco-fountain-pen-cosmos-blue-with-onyx">TWSBI Eco Cosmos Blue with Onyx</a>. I also found somewhere selling the discontinued Pink Eco at retail price so I might need to buy that first.</p> <p>I've backed <a href="https://www.indiegogo.com/en/projects/bonfire/community">Bonfire on IndieGoGo</a> because I'm fascinated by what they're trying to do.</p> <p>Despite not being an iOS developer (nor do I have any intention of becoming one), <a href="https://twostraws.gumroad.com/l/everything-but-the-code/blackfriday25">Everything but the Code</a> is 50% off for Black Friday and aside from some app-specific things, seems like it'll have a lot of useful stuff in it.</p> <h3>Links</h3> <p><a href="https://flareapp.moe">Flare</a> is a new unified app for connecting to multiple social networks like Mastodon and Bluesky. I don't I need this but it's interesting nonetheless.</p> <p><a href="https://lowtechguys.com/grila/">Grila</a> is a keyboard-driven calendar app for MacOS which reminds me a lot of <a href="https://rknight.me/save/godspeed">Godspeed</a>.</p> <p><a href="https://bapsi.micro.blog/2025/11/14/taking-stock-of-my-tools.html">Taking stock of my tools</a> by Britney Winthrope is a great post with an new (to me) way of categorising tools you use (the CODE framework - Capture, Organize, Distill, Express).</p> <p><a href="https://www.olleewatch.com">The Ollee watch</a> is back in stock but I resisted for the time being.</p> <p><a href="https://publicdomainreview.org/collection/japanese-fireworks-catalogues/">These Japanese firework illustrations</a> are gorgeous. This also led to me to find <a href="https://uk.bookshop.org/p/books/affinities-a-journey-through-images-from-the-public-domain-review-adam-green/6233224?ean=9780500025208&amp;next=t&amp;affiliate=9706">Affinities</a> which is a book by The Public Domain Review. Right onto my wishlist that goes.</p> <p><a href="https://mutant.tech/demo">Mutant Standard emoji</a> is a fun new emoji set.</p> OpenBenches 💖 OpenStreetMap - Terence Eden’s Blog https://shkspr.mobi/blog/?p=63630 2025-11-17T12:34:41.000Z <p>When Liz and I created <a href="https://openbenches.org/">the OpenBenches website</a>, it was just designed to be a fun way for people to record memorial benches. Since then things have got out of hand and we now have over thirty-nine <em>thousand</em> benches recorded!</p> <p>Our plan was never to compete with something like OpenStreetMap. The OSM project is vast, complex, and brilliant - we are small, simple, and <em>differently</em> brilliant. But, over the years, people have repeatedly asked if there&#39;s any way to combine the two data sets.</p> <p>This has proved logistically complex for several reasons.</p> <ol> <li>Our users aren&#39;t experienced mappers. <ul> <li>Most of our entries are uploaded with fairly fuzzy GPS co-ordinates. Mobile phones aren&#39;t always the best at accurate locations and, besides, people tend to stand away from the bench when taking its photo. So our data isn&#39;t quite at the level of quality rightly demanded by OSM.</li> </ul></li> <li>OSM didn&#39;t have a tag specifically for memorial benches. <ul> <li>We started out site in 2017. OSM <a href="https://wiki.openstreetmap.org/wiki/Tag:memorial%3Dbench">added the <code>Tag:memorial=bench</code> in 2021</a>. Up until then, there wasn&#39;t a great way to record that a bench was a memorial.</li> </ul></li> <li>Data licencing is complicated. <ul> <li>We chose the Creative Commons Attribution ShareAlike licence - it seemed like a good idea at the time! OSM use ODbL which is <a href="https://blog.openstreetmap.org/2017/03/17/use-of-cc-by-data/">subtly incompatible</a>. As such, OSM volunteers asked us to sign a waiver so they could use the data - which we happily did.</li> </ul></li> <li>Adding or editing data on OSM can be complicated. <ul> <li>OpenBenches is designed to be an upload-and-forget process. It doesn&#39;t matter much to us if a bench is recorded a dozen metres away from its true location. But that isn&#39;t the way OSM works. We didn&#39;t want to bulk upload data which was inaccurate, incomplete, or inappropriate. Luckily, there are now tools to help with that!</li> </ul></li> </ol> <p>Things have been working away in the background. Some people have <a href="https://wiki.openstreetmap.org/wiki/Key:openbenches:id?uselang=en-GB">manually added <code>Key:openbenches:id</code> to appropriate benches</a>, and others have edited our database to make the locations closer to reality.</p> <p>And now, thanks to the sterling work of the brilliant <a href="https://pietervdvn.me/">Pieter Vander Vennet</a> we&#39;re moving to our next phase of increased collaboration!</p> <p>Firstly, there are about 1,060 benches on OpenStreetMap which have an OpenBenches ID. I&#39;ve taken all those OSM IDs and put them into our database. Which means that the OpenBenches website can display a button like this:</p> <img src="https://shkspr.mobi/blog/wp-content/uploads/2025/10/OpenBenches-website-with-an-OSM-link.webp" alt="OpenBenches website with an OSM link." width="1434" height="472" class="aligncenter size-full wp-image-63633"/> <p>One click and you&#39;re looking at OSM - ready to investigate, edit, or admire.</p> <p>But what about the <em>other</em> 38,000 benches? Well, that&#39;s where <a href="https://mapcomplete.org/">MapComplete</a> comes in. MapComplete is sort of like Pokémon Go for maps. As you wander this Earth, you can complete little quests to help improve OpenStreetMap. For example, on the &#34;Pubs&#34; quest, you can add details of all the pubs you visit.</p> <img src="https://shkspr.mobi/blog/wp-content/uploads/2025/10/MapComplete-screenshot-with-various-questions-about-a-pub.webp" alt="MapComplete screenshot with various questions about a pub." width="640" height="898" class="aligncenter size-full wp-image-63634"/> <p>With the &#34;Bench&#34; quest, it is a little different. If an OpenBench is sufficiently nearby an OSM bench, you&#39;ll get the option to link the two with a couple of clicks.</p> <img src="https://shkspr.mobi/blog/wp-content/uploads/2025/10/MapComplete-screenshot-showing-two-benches-being-linked.webp" alt="MapComplete screenshot showing two benches being linked." width="1214" height="816" class="aligncenter size-full wp-image-63635"/> <p>But there are <em>loads</em> of benches we have discovered which aren&#39;t in the OSM database. In which case, you can add a new bench to OSM using the data from OpenBenches!</p> <img src="https://shkspr.mobi/blog/wp-content/uploads/2025/10/MapRoulette-screenshot-adding-a-new-bench.webp" alt="MapRoulette screenshot adding a new bench." width="640" height="635" class="aligncenter size-full wp-image-63636"/> <p>This has been <a href="https://community.openstreetmap.org/t/guided-mapping-import-of-openbenches-org/97455">a couple of years in the making</a> - but it looks like most of the kinks are now sorted out. I&#39;m sure there will be a few early problems, and no doubt a bit of late-night bug fixing, but I hope that this is the start of something long-lasting. The joy of decentralised sites using open data is that we can all build on each others&#39; work in a spirit of fun and exploration.</p> Automating my reading progress updates - Posts feed https://www.coryd.dev/posts/2025/automating-my-reading-progress-updates 2025-11-16T18:13:00.000Z <p>I've tracked my reading progress on my site for a bit now. I'd originally done this by fetching my progress from external APIs and sources on platforms like <a href="https://oku.club">Oku</a>, fetching and parsing the DOM on the <a href="https://thestorygraph.com">StoryGraph</a> and eventually importing and managing my own data. For years I've been reading and listening to audiobooks in Apple's Books app. Much like Apple's other media apps (music and TV, namely), Books has slowly moved in a direction that makes importing your own content increasingly difficult, while slowly pushing storefront elements into the home section of the app until they effectively took over.</p> <p>What tethered me to Apple Books for so long was my investment in the reading streak I'd maintained. But that streak is simply a number, a number that I can and have replicated here. That streak, as of writing, stands at 2,095 days. I've finally uninstalled Books, let the streak lapse there and moved my reading activity to <a href="https://www.audiobookshelf.org">Audiobookshelf</a>.</p> <p><a href="https://www.audiobookshelf.org">Audiobookshelf</a> is heavily focused on audiobooks, but also supports reading ebooks in myriad formats. It also does a wonderful job of tying different formats together for the same book should you have it as, say, an audiobook, an epub, a mobi file — you name it. I have my audiobooks there and I've added my ebooks. There are quite a few audiobook clients for <a href="https://www.audiobookshelf.org">Audiobookshelf</a>, which makes sense given the app's focus. I haven't found an app that supports its ebook functionality.<sup id="fnref:1"><span>1</span></sup> For a consistent experience, I've added my instance of the app to my iOS device homescreens via Safari.</p> <p>I track my reading progress as a percentage of completion and increment my reading streak whenever progress is updated. My reading streak table consists of a <code>days_read</code> <code>integer</code> and a <code>last_read_date</code> <code>datetime</code>. This is handled by a trigger:</p> <copyable-code snippet-slug="/snippets/update-days-readsql-trigger-2025-11-16-09-25-10"> <noscript> <p><a href="https://www.coryd.dev/snippets/update-days-readsql-trigger-2025-11-16-09-25-10">View snippet source</a></p> </noscript> </copyable-code> <p>And the function it runs:</p> <copyable-code snippet-slug="/snippets/update-days-readsql-function-2025-11-16-09-26-22"> <noscript> <p><a href="https://www.coryd.dev/snippets/update-days-readsql-function-2025-11-16-09-26-22">View snippet source</a></p> </noscript> </copyable-code> <p>Up until now, I've manually updated reading progress via a widget on my <a href="https://filamentphp.com">Filament</a> dashboard. This was effective, but a bit tedious. However, since <a href="https://www.audiobookshelf.org">Audiobookshelf</a> has <a href="https://api.audiobookshelf.org">a rich API</a>, I dove in to see what data I could retrieve programmatically. I arrived at a <a href="https://laravel.com">Laravel</a> command that runs every 15 minutes. This command fetches items in progress, then fetches details for each item (with <code>expanded</code> set to <code>1</code> and <code>progress</code> included). The response will then include <code>userMediaProgress</code> which, in turn, contains <code>ebookProgress</code> and <code>progress</code>. Audiobookshelf tracks distinct progress states for ebooks and audiobooks that are bundled together. The full command looks like this:</p> <copyable-code snippet-slug="/snippets/syncbookprogresscommandphp-2025-11-16-09-45-54"> <noscript> <p><a href="https://www.coryd.dev/snippets/syncbookprogresscommandphp-2025-11-16-09-45-54">View snippet source</a></p> </noscript> </copyable-code> <p>Once the progress is retrieved, it updates the progress for the book in my site's database via <a href="https://postgrest.org">PostgREST</a> by looking it up via the ISBN and, failing that, the title. As I read, <a href="https://www.audiobookshelf.org">Audiobookshelf</a> records my progress, my site polls my instance of the app and updates each book's progress and my ongoing reading streak.</p> <hr> <p>In addition to automating my progress tracking, I've updated my book data importer to fetch data from <a href="https://www.audiobookshelf.org">Audiobookshelf</a>. It is also able to fetch data from <a href="https://books.google.com">Google Books</a> and <a href="https://openlibrary.org">Open Library</a> but, since I've consolidated my reading to <a href="https://www.audiobookshelf.org">Audiobookshelf</a>, it made sense to import data from it directly to better align the record in my database with the source record. This import is second hand, in a sense as <a href="https://www.audiobookshelf.org">Audiobookshelf</a> imports from other sources (Apple Books, funnily enough, is my default), but provides the best results:</p> <copyable-code snippet-slug="/snippets/importbookcommandphp-2025-11-16-10-08-36"> <noscript> <p><a href="https://www.coryd.dev/snippets/importbookcommandphp-2025-11-16-10-08-36">View snippet source</a></p> </noscript> </copyable-code> <p>This process is kicked off by clicking an Import Book command on my dashboard, which opens a modal with a dropdown to select the API source and an import for the required ID for that source.</p> <p>With all that configured, I can quickly import data on the book I'm reading and progress will be automatically recorded as I read (or listen) to it.</p> <div class="footnotes" role="doc-endnotes"><hr><ol><li class="footnote" id="fn:1" role="doc-endnote"><p>If there is one, <a href="https://www.coryd.dev/contact">let me know</a>. <span>↩</span></p></li></ol></div> <img src="https://stats.coryd.dev/count?p=/posts/2025/automating-my-reading-progress-updates&t=Automating+my+reading+progress+updates&r=rss" style="position:absolute;left:-9999px;"> Updating forgejo's robots.txt - Posts feed https://www.coryd.dev/posts/2025/updating-forgejos-robotstxt 2025-11-15T22:10:00.000Z <p>I've moved all of my personal, private projects over to <a href="https://git.coryd.dev">my own forgejo instance</a>. It's been reliable and an altogether simple transition — I even have it mirroring the <a href="https://git.coryd.dev/cdransf/ai.robots.txt">ai.robots.txt</a> repo.</p> <p>While most of my projects on the instance are private (like the source for this site and for <a href="https://www.coryd.dev/posts/2025/i-made-a-music-app">Cadence</a>), I wanted the <code>robots.txt</code> file for forgejo to align with <a href="https://www.coryd.dev/robots.txt">this site's</a>. Thankfully, updating the forgejo <code>robots.txt</code> is straightforward. I'm deploying my instance in a <a href="https://en.wikipedia.org/wiki/Docker_(software)">Docker</a> container using <a href="https://coolify.io">Coolify</a> and update the file as follows:</p> <ol> <li>ssh into the server where the container is running.</li> <li>Run <code>docker ps -a | grep forgejo</code> to get the forgejo container name.</li> <li>Log into the container using <code>docker exec -it &lt;NAME&gt; sh</code>.</li> <li>Navigate to forgejo's public directory: <code>cd data/gitea/public</code>.</li> <li>Open <code>robots.txt</code>: <code>vi robots.txt</code>.</li> <li>Add entries as you see fit.</li> </ol> <p>I've mirrored my site's <code>robots.txt</code> to my forgejo instance and I've also included the rules <a href="https://codeberg.org/robots.txt">from Codeberg</a>'s (up until the <code># Codeberg-specific changes</code> to the end of the file). <a href="https://git.coryd.dev/robots.txt">You can view the full file here</a>.</p> <p><code>robots.txt</code> is, of course, a voluntary mechanism, but including these directives is still prudent.</p> <img src="https://stats.coryd.dev/count?p=/posts/2025/updating-forgejos-robotstxt&t=Updating+forgejo%27s+robots.txt&r=rss" style="position:absolute;left:-9999px;"> Small Web, Big Voice - Kev Quirk https://kevquirk.com/blog/small-web-big-voice/ 2025-11-15T16:22:00.000Z <div class="link"> <h2>Small Web, Big Voice</h2> <span>by Andre Franca</span> <p>Andre argues that independent blogging isn’t about scale at all, but about integrity — choosing a place you control, writing in your own voice, and keeping the web human.</p> <p><a class="button" target="_blank" href="https://afranca.com.br/small-web-big-voice">Read Post →</a></p> <hr class="email-hidden"> </div> <p>I read this post this morning while I was perusing my RSS feeds with a coffee. Firstly, I’m not sharing this because Andre called out my blog (although being bundled with people like <a href="https://rachsmith.com/">Rach</a> and <a href="https://manuelmoreale.com/">Manu</a> is <em>extremely</em> flattering). I’m sharing it because I agree with everything he says in the post.</p> <p>Having a place on the web that I’m 100% in control of, where I can share my own thoughts, feelings, and opinions is very powerful for me. Over time this blog has evolved from me sharing technical posts most of the time, to a legit personal blog with a technical twist.</p> <p>Despite what sites like <em>ProBlogger</em> say, I don’t have a niche, and I don’t try to grow my audience (<a href="https://kevquirk.com/blog/revisiting-the-web-analytics-rabbit-hole/">I don’t even know how big my audience is</a>). I just write whatever is on my mind at any given time, and people usually get in touch with me to discuss the topic. It’s fantastic.</p> <p>If you’re on the fence about starting a blog, I implore you to do so. It’s probably the best thing I’ve ever done with a computer. If you, please drop me an email with the link - I love discovering new blogs!</p> <div class="email-hidden"> <hr> <p>Thanks for reading this post via RSS. RSS is great, and you're great for using it. ❤️</p> <p> <a href="mailto:72ja@qrk.one?subject=Small Web, Big Voice">Reply to this post by email</a> </p> </div> Gadget Review: Benfei USB-C Video Capture ★★★★★ - Terence Eden’s Blog https://shkspr.mobi/blog/?p=64444 2025-11-15T12:34:43.000Z <p>Want to capture video from your phone or console? You <em>could</em> just point a camera at the screen, but a more sensible way to do it is to capture the video directly via USB-C.</p> <p>The good folks at Benfei have sent me another gadget to review! This is a <a href="https://amzn.to/47L0br2">USB-C Video/Audio capture</a> dongle. Plug one end into a device and the other into your computer - it will show up as a USB video capture device.</p> <img src="https://shkspr.mobi/blog/wp-content/uploads/2025/11/Benfei-USB-C-Video.webp" alt="A long USB-C cable with a box in the middle." width="1024" height="722" class="aligncenter size-full wp-image-64497"/> <p>Notice the extra USB socket there?</p> <h2 id="usb-power"><a href="https://shkspr.mobi/blog/2025/11/gadget-review-benfei-usb-c-video-capture/#usb-power">USB Power</a></h2> <p>One great thing about this device is that it has USB Power Delivery pass through. This means you can charge your device while grabbing video from it. That&#39;s more than a &#34;nice to have&#34; - the Nintendo Switch will refuse to output video over USB-C unless it is connected to a power supply.</p> <p>The capture device claims to be able to pass through 100W - I don&#39;t have any devices which need that much power, but my <a href="https://shkspr.mobi/blog/2023/10/gadget-review-plugable-usb-c-voltage-amperage-meter-240w/">USB-C Power Meter</a> showed devices happily slurping down between 5W and 20W depending on the device I was using.</p> <p>So how does it do?</p> <h2 id="video-and-audio"><a href="https://shkspr.mobi/blog/2025/11/gadget-review-benfei-usb-c-video-capture/#video-and-audio">Video and Audio</a></h2> <p>It is limited to 1080p @ 60Hz, which is good enough for most things.</p> <p>Here&#39;s a short clip from the Nintendo Switch:</p> <p></p><div style="width: 620px;" class="wp-video"><video class="wp-video-shortcode" id="video-64444-3" width="620" height="349" preload="metadata" controls="controls"><source type="video/mp4" src="https://shkspr.mobi/blog/wp-content/uploads/2025/11/Benfei-Switch.mp4?_=3"/><a href="https://shkspr.mobi/blog/wp-content/uploads/2025/11/Benfei-Switch.mp4">https://shkspr.mobi/blog/wp-content/uploads/2025/11/Benfei-Switch.mp4</a></video></div><p></p> <p>And here&#39;s a capture from my Android phone:</p> <p></p><div style="width: 620px;" class="wp-video"><video class="wp-video-shortcode" id="video-64444-4" width="620" height="349" preload="metadata" controls="controls"><source type="video/mp4" src="https://shkspr.mobi/blog/wp-content/uploads/2025/11/Benfei-Android-Video.mp4?_=4"/><a href="https://shkspr.mobi/blog/wp-content/uploads/2025/11/Benfei-Android-Video.mp4">https://shkspr.mobi/blog/wp-content/uploads/2025/11/Benfei-Android-Video.mp4</a></video></div><p></p> <h2 id="linux"><a href="https://shkspr.mobi/blog/2025/11/gadget-review-benfei-usb-c-video-capture/#linux">Linux</a></h2> <p>For the nerds amongst us, this shows up in <code>lsusb</code> as <code>345f:2130 MACROSILICON USB3 Video</code> which should be <a href="https://linux-hardware.org/?id=usb:345f-2130">well supported</a>.</p> <p>OBS Studio was able to capture the video and audio input perfectly:</p> <img src="https://shkspr.mobi/blog/wp-content/uploads/2025/11/OBS.webp" alt="The OBS software showing video from a console." width="1440" height="1002" class="aligncenter size-full wp-image-64496"/> <p>It is the epitome of Plug &amp; Play. Shove one end into your device and plug the other end into your computer&#39;s USB-C port. That&#39;s it. Done. No software to install, no drivers to download, no switches to flip. There&#39;s also a handy adapter if you want to use a USB-A socket - although it will need to support USB 3 speeds.</p> <h2 id="limitations"><a href="https://shkspr.mobi/blog/2025/11/gadget-review-benfei-usb-c-video-capture/#limitations">Limitations</a></h2> <p>As with most HDMI devices, it will refuse to stream video protected by HDCP DRM. That means you <em>probably</em> can&#39;t stream your Netflix / Disney / Whatever subscription to your laptop.</p> <p>It is limited to stereo sound. I couldn&#39;t convince the Nintendo Switch to output surround sound.</p> <p>Obviously, it only works with devices which have USB-C <em>video</em> output. Modern Android and most hand-held consoles will work. Your PS5 won&#39;t.</p> <p>So what about those devices without USB-C?</p> <h2 id="bonus-hdmi-dongle"><a href="https://shkspr.mobi/blog/2025/11/gadget-review-benfei-usb-c-video-capture/#bonus-hdmi-dongle">Bonus HDMI Dongle!</a></h2> <p>So you&#39;re a wannabe Twitch streamer, or you just want to capture something from your HDMI output? The good folks at Benfei also sent me their <a href="https://amzn.to/47uq1AG">HDMI Capture Dongle</a> to review.</p> <img src="https://shkspr.mobi/blog/wp-content/uploads/2025/11/HDMI-capture.webp" alt="A short USB-C cable with an HDMI port." width="1024" height="768" class="aligncenter size-full wp-image-64500"/> <p>There&#39;s absolutely nothing else to say about this one. It has the same internals - <code>345f:2130 MACROSILICON USB3 Video</code> - and works exactly the same.</p> <p>Shove an HDMI cable in there and you&#39;re good to go,</p> <h2 id="price"><a href="https://shkspr.mobi/blog/2025/11/gadget-review-benfei-usb-c-video-capture/#price">Price</a></h2> <p>The USB-C to USB-C cable <a href="https://amzn.to/47L0br2">a surprisingly reasonable £15</a>. If you need to capture video for presentations or streaming, it will do the job splendidly. The cable is long enough to drape from a machine to a source - and the Power Delivery is useful.</p> <p>The HDMI capture is <a href="https://amzn.to/47uq1AG">only £12</a>. They both work identically well and are supported on Linux.</p> <p>Highly recommended!</p> Level up your Solidity LLM tooling with Slither-MCP - Trail of Bits Blog https://blog.trailofbits.com/2025/11/15/level-up-your-solidity-llm-tooling-with-slither-mcp/ 2025-11-15T12:00:00.000Z <p>We’re releasing <a href="https://github.com/trailofbits/slither-mcp">Slither-MCP</a>, a new tool that augments LLMs with Slither’s unmatched static analysis engine. Slither-MCP benefits virtually every use case for LLMs by exposing Slither’s static analysis API via tools, allowing LLMs to find critical code faster, navigate codebases more efficiently, and ultimately improve smart contract authoring and auditing performance.</p> <h2 id="how-slither-mcp-works">How Slither-MCP works</h2> <p>Slither-MCP is an MCP server that wraps Slither’s static analysis functionality, making it accessible through the Model Context Protocol. It can analyze Solidity projects (Foundry, Hardhat, etc.) and generate comprehensive metadata about contracts, functions, inheritance hierarchies, and more.</p> <p>When an LLM uses Slither-MCP, it no longer has to rely on rudimentary tools like grep and <code>read_file</code> to identify where certain functions are implemented, who a function’s callers are, and other complex, error-prone tasks.</p> <p>Because LLMs are probabilistic systems, in most cases they are only probabilistically correct. Slither-MCP helps set a ground truth for LLM-based analysis using traditional static analysis: it reduces token use and increases the probability a prompt is answered correctly.</p> <h3 id="example-simplifying-an-auditing-task">Example: Simplifying an auditing task</h3> <p>Consider a project that contains two ERC20 contracts: one used in the production deployment, and one used in tests. An LLM is tasked with auditing a contract’s use of <code>ERC20.transfer()</code>, and needs to locate the source code of the function.</p> <p>Without Slither-MCP, the LLM has two options:</p> <ol> <li> <p>Try to resolve the import path of the ERC20 contract, then try to call <code>read_file</code> to view the source of <code>ERC20.transfer()</code>. This option usually requires multiple calls to <code>read_file</code>, especially if the call to <code>ERC20.transfer()</code> is through a child contract that is inherited from ERC20. Regardless, this option will be error-prone and tool call intensive.</p> </li> <li> <p>Try to use the grep tool to locate the implementation of <code>ERC20.transfer()</code>. Depending on how the grep tool call is structured, it may return the wrong ERC20 contract.</p> </li> </ol> <p>Both options are non-ideal, error-prone, and not likely to be correct with a high interval of confidence.</p> <p>Using Slither-MCP, the LLM simply calls <code>get_function_source</code> to locate the source code of the function.</p> <h2 id="simple-setup">Simple setup</h2> <p>Slither-MCP is easy to set up, and can be added to Claude Code using the following command:</p> <figure class="highlight"> <pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">claude mcp add --transport stdio slither -- uvx --from git+https://github.com/trailofbits/slither-mcp slither-mcp</span></span></code></pre> </figure> <p>It is also easy to add Slither-MCP to Cursor by adding the following to your <code>~/.cursor/mcp.json</code>:</p> <figure class="highlight"> <pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="s2">&#34;mcpServers&#34;</span><span class="o">:</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="s2">&#34;slither-mcp&#34;</span><span class="o">:</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="s2">&#34;command&#34;</span><span class="o">:</span> <span class="s2">&#34;uvx --from git+https://github.com/trailofbits/slither-mcp slither-mcp&#34;</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="s2">&#34;env&#34;</span><span class="o">:</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="s2">&#34;PYTHONUNBUFFERED&#34;</span><span class="o">:</span> <span class="s2">&#34;1&#34;</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="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre> <figcaption><span>Figure 1: Adding Slither-MCP to Cursor</span></figcaption> </figure> <p>For now, Slither-MCP exposes a subset of Slither’s analysis engine that we believe LLMs would have the most benefit consuming. This includes the following functionalities:</p> <ul> <li> <p>Extracting the source code of a given contract or function for analysis</p> </li> <li> <p>Identifying the callers and callees of a function</p> </li> <li> <p>Identifying the contract’s derived and inherited members</p> </li> <li> <p>Locating potential implementations of a function based on signature (e.g., finding concrete definitions for <code>IOracle.price(...)</code>)</p> </li> <li> <p>Running Slither’s exhaustive suite of detectors and filtering the results</p> </li> </ul> <p>If you have requests or suggestions for new MCP tools, <a href="https://github.com/trailofbits/slither-mcp/issues">we’d love to hear from you</a>.</p> <h2 id="licensing">Licensing</h2> <p>Slither-MCP is licensed AGPLv3, the same license Slither uses. This license requires publishing the full source code of your application if you use it in a web service or SaaS product. For many tools, this isn’t an acceptable compromise.</p> <p>To help remediate this, we are now offering dual licensing for both Slither and Slither-MCP. By offering dual licensing, Slither and Slither-MCP can be used to power LLM-based security web apps without publishing your entire source code, and without having to spend years reproducing its feature set.</p> <p>If you are currently using Slither in your commercial web application, or are interested in using it, please <a href="https://www.trailofbits.com/contact/">reach out</a>.</p> 22.00.0168 How to remember the Markdown link syntax - Johnny.Decimal https://johnnydecimal.com/22.00.0168/ 2025-11-15T02:16:10.000Z <h1 id="how-to-remember-the-markdown-link-syntax">How to remember the Markdown link syntax</h1> <p>In <a href="https://daringfireball.net/projects/markdown/">Markdown</a>, a universally-handy text formatting language, you create a link like this:</p> <p><code>[Title of link](https://…)</code></p> <p>Easy enough, but there&#39;s a bunch to remember there. Which comes first, the title or the link? And which is in square brackets, which in regular brackets?</p> <p>Get any of those things wrong, and your link won&#39;t work.</p> <h3 id="name-and-address-please-sir">&#39;Name and address please, sir&#39;</h3> <p>Let&#39;s do the stuff inside the brackets first. When you get pulled over by the cops you&#39;d never be asked for your &#39;address and name&#39;, would you? Same here.<sup><a href="#user-content-fn-innocent" id="user-content-fnref-innocent" data-footnote-ref="" aria-describedby="footnote-label">1</a></sup></p> <p><strong>Name and address</strong> in that order.</p> <h3 id="addresses-contain-numbers">Addresses contain numbers</h3> <p>Those regular brackets <code>()</code> live on the keys <code>9</code> and <code>0</code>.</p> <p><strong>What contains numbers? An address</strong>.</p> <p>What doesn&#39;t? Your name.<sup><a href="#user-content-fn-andre3000" id="user-content-fnref-andre3000" data-footnote-ref="" aria-describedby="footnote-label">2</a></sup></p> <h3 id="thats-it">That&#39;s it</h3> <p><code>[Johnny](90 Main St)</code></p> <hr> <p><em>100% human. 0% AI. Always.</em></p> <section data-footnotes="" class="footnotes"><h2 class="sr-only" id="footnote-label">Footnotes</h2> <ol> <li id="user-content-fn-innocent"> <p>Yeah, yeah, you&#39;re innocent. Save it for the judge. <a href="#user-content-fnref-innocent" data-footnote-backref="" aria-label="Back to reference 1" class="data-footnote-backref">↩</a></p> </li> <li id="user-content-fn-andre3000"> <p>Unless you&#39;re <a href="https://en.wikipedia.org/wiki/Andr%C3%A9_3000">this guy</a>. <a href="#user-content-fnref-andre3000" data-footnote-backref="" aria-label="Back to reference 2" class="data-footnote-backref">↩</a></p> </li> </ol> </section> Maple Vanilla Mead - Cool As Heck https://cool-as-heck.blog/maple-vanilla-mead 2025-11-14T13:14:14.000Z <div>My next experimental mead recipe is going to be a maple vanilla mead. Here's the base recipe: </div> <div><br></div> <div>- 2lb raw acacia blossom honey (primary)</div> <div>- 12oz Vermont maple syrup (primary)</div> <div>- 1 vanilla bean (secondary) </div> <div>- 2 whole allspice berries (secondary) </div> <div>- 6g of oak chips (secondary)</div> <div>- 4-6oz Vermont maple syrup for back sweetening and taste</div> <div><br></div> <div>Yeast: Lalvin 71B</div> <div><br></div> <div>This will hit about 12% ABV. This will be my first time working with acacia honey, raw honey, maple syrup, and oak chips, so this is going to be fun and interesting. 🍷 </div> Giving My Jekyll Site a CDN Front End - Kev Quirk https://kevquirk.com/blog/giving-my-jekyll-site-a-cdn-front-end/ 2025-11-14T12:55:00.000Z <p style="font-size: 1.2em;">I've managed to get my Jekyll based site working behind Bunny CDN, while maintaining my .htaccess redirects. Here's how I did it...</p> <h1 id="giving-my-jekyll-site-a-cdn-front-end">Giving My Jekyll Site a CDN Front End</h1> <p>Since <a href="https://kevquirk.com/blog/switching-back-to-jekyll-building-my-own-cms/">switching back to Jekyll</a> recently, I’ve been running this site on a Ionos-hosted VPS, then using a little deploy script to build the site and rsync it up.</p> <p>This all worked fine, but I really wanted to use <a href="https://bunny.net?ref=gnn7bkvipc">Bunny CDN</a> for more than just hosting a few images and my custom font. Being a static site, I could have dumped everything onto their storage platform, but I have a metric tonne of redirects in a <code class="language-plaintext highlighter-rouge">.htaccess</code> file from various platform migrations <a href="https://kevquirk.com/design-history/">over the years</a>.</p> <p>Bunny’s Edge Platform could have handled these, but with the number of redirects I have, it would have been a slog to maintain. So I assumed I’d never be able to put Bunny in front of my Jekyll site easily and went about my business.</p> <p>💡 Then I had an epiphany.</p> <p>What if I created a Bunny pull zone that uses <code class="language-plaintext highlighter-rouge">kevquirk.com</code> as the public domain, then set up a separate domain on my VPS, host the site there, and use that as the pull zone origin?</p> <p>My theory was that Bunny would still be requesting content from the VPS, so my <code class="language-plaintext highlighter-rouge">.htaccess</code> redirects might still work.</p> <p>…turns out, they did.</p> <h2 id="some-small-bugs">Some small bugs</h2> <p>I duplicated my live site so I could experiment safely. The setup looked like this:</p> <ul> <li><code class="language-plaintext highlighter-rouge">test.kevquirk.com</code> - the domain configured in the Bunny pull zone</li> <li><code class="language-plaintext highlighter-rouge">src.qrk.one</code> - the origin domain on my VPS, where the site actually lives</li> </ul> <p>The first thing I had to do was update the <code class="language-plaintext highlighter-rouge">url</code> field in my Jekyll <code class="language-plaintext highlighter-rouge">_config.yml</code> from <code class="language-plaintext highlighter-rouge">kevquirk.com</code> to <code class="language-plaintext highlighter-rouge">test.kevquirk.com</code>, rebuild the site, and upload it to <code class="language-plaintext highlighter-rouge">src.qrk.one</code>.</p> <p>Now, you might be thinking, <em>“Kev, why build the site with the wrong domain?”</em></p> <p>But I haven’t. By building the site with the test domain, all links point to <code class="language-plaintext highlighter-rouge">test.kevquirk.com/...</code>. If I built it with the origin domain, all internal links would lead to the wrong place. They would still work, but the site would be served from <code class="language-plaintext highlighter-rouge">src.qrk.one</code>, which is not what I want.</p> <p>Next up was redirect testing. I visited <code class="language-plaintext highlighter-rouge">/feed</code>, which should hit <code class="language-plaintext highlighter-rouge">.htaccess</code> and redirect to <code class="language-plaintext highlighter-rouge">/feed.xml</code>. The redirect worked fine, but the resulting URL was being served from the origin domain.</p> <p>So instead of seeing <code class="language-plaintext highlighter-rouge">test.kevquirk.com/feed.xml</code> I saw <code class="language-plaintext highlighter-rouge">src.qrk.one/feed.xml</code>.</p> <p>This happened because Bunny requested the file from the origin using its own hostname, not the hostname I typed. In simple terms:</p> <ul> <li>I visited <code class="language-plaintext highlighter-rouge">test.kevquirk.com/feed</code>.</li> <li>Bunny checked its cache. It wasn’t there, so it asked my origin for the file.</li> <li>But Bunny made that request using its own hostname (<code class="language-plaintext highlighter-rouge">src.qrk.one</code>), not the one I typed.</li> <li>Apache saw the request coming from <code class="language-plaintext highlighter-rouge">src.qrk.one/feed</code> and applied the <code class="language-plaintext highlighter-rouge">.htaccess</code> redirect to <code class="language-plaintext highlighter-rouge">/feed.xml</code>.</li> <li>Apache then rebuilt the redirect URL using the hostname it was given, which was <code class="language-plaintext highlighter-rouge">src.qrk.one</code>.</li> </ul> <p>So Apache went:</p> <blockquote> <p>“Oh, you want <code class="language-plaintext highlighter-rouge">/feed</code>? Sure. That’s at <code class="language-plaintext highlighter-rouge">src.qrk.one/feed.xml</code>. Here ya go…”</p> </blockquote> <h2 id="fixing-the-problem">Fixing the problem</h2> <p>This would not break anything for visitors, but I didn’t want <code class="language-plaintext highlighter-rouge">src.qrk.one</code> appearing anywhere. It looked messy.</p> <p>Two changes fixed it:</p> <ol> <li>Enable <strong>Forward Host Headers</strong> in my Bunny pull zone.</li> <li>Add <code class="language-plaintext highlighter-rouge">test.kevquirk.com</code> as a domain alias of <code class="language-plaintext highlighter-rouge">src.qrk.one</code> on my VPS.</li> </ol> <p>Forward Host Headers makes Bunny tell the VPS the hostname the visitor used. So instead of:</p> <blockquote> <p>“I’m asking for this on behalf of <code class="language-plaintext highlighter-rouge">src.qrk.one</code>.”</p> </blockquote> <p>Bunny says:</p> <blockquote> <p>“I’m asking for this on behalf of <code class="language-plaintext highlighter-rouge">test.kevquirk.com</code>, not <code class="language-plaintext highlighter-rouge">src.qrk.one</code>.”</p> </blockquote> <p>The domain alias ensures Apache accepts that hostname and serves it correctly.</p> <p>Magic. 🪄🐇</p> <p>The other thing to double-check is that every page sets a proper <code class="language-plaintext highlighter-rouge">canonical</code> URL. The origin domain is publicly accessible, so crawlers need to know which domain is the real one. That should always be the Bunny pull zone domain.</p> <p>In Jekyll this is simple. Add the following to the <code class="language-plaintext highlighter-rouge">head</code> section of your layout:</p> <div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;link</span> <span class="na">rel=</span><span class="s">"canonical"</span> <span class="na">href=</span><span class="s">"{{ page.url | absolute_url }}"</span><span class="nt">&gt;</span> </code></pre></div></div> <h2 id="the-final-straight">The final straight</h2> <p>With the redirect behaviour sorted, the last step was to add a purge step to my deploy script so Bunny knows to fetch the latest version whenever I publish a new post or update something.</p> <p>Here’s the snippet I added:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># --- Clear Bunny Cache --- echo "🗑 Clearing Bunny Cache..." PURGE_RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" -X POST \ -H "AccessKey: $BUNNY_ACCESS_KEY" \ "https://api.bunny.net/pullzone/$PULL-ZONE-ID/purgeCache") if [ "$PURGE_RESPONSE" -ne 200 ] &amp;&amp; [ "$PURGE_RESPONSE" -ne 204 ]; then echo "⚠️ Bunny purge failed (HTTP $PURGE_RESPONSE)" exit 1 fi </code></pre></div></div> <p>I set my Bunny API key and Pull Zone ID as variables at the top of the script. The <code class="language-plaintext highlighter-rouge">if</code> statement simply says, <em>“If the response isn’t 200 or 204, tell Kev what went wrong.”</em></p> <p>And that is it. Last night I flipped the switch. Bunny CDN now sits in front of the live site. I also moved the VPS from Ionos to Hetzner because Ionos now charge extra for a Plesk licence. I went with <a href="https://hestiacp.com/">Hestia</a> as the control panel on the new server.</p> <p>If you spot any bugs, please do let me know, but everything should be hopping along nicely now. (See what I did there? God I’m funny!)</p> <div class="email-hidden"> <hr> <p>Thanks for reading this post via RSS. RSS is great, and you're great for using it. ❤️</p> <p> <a href="mailto:72ja@qrk.one?subject=Giving My Jekyll Site a CDN Front End">Reply to this post by email</a> </p> </div> 22.00.0167 An(other) advantage of the creative pattern - Johnny.Decimal https://johnnydecimal.com/22.00.0167/ 2025-11-14T00:43:29.000Z <h1 id="another-advantage-of-the-creative-pattern">An(other) advantage of the creative pattern</h1> <p>Our <a href="https://johnnydecimal.com/15.02/">creative system</a> here at JDHQ is in heavy use with production of the upcoming <a href="https://johnnydecimal.com/14.02/">JDU</a> series &#39;task and project management&#39;, seeing me create ID <code>50092</code> yesterday. We&#39;ll crack that hundred before long.</p> <p>Previous creative projects -- recording the workshop, say -- tended to sit in their own isolated world. They take up a bunch of disk space, and if you&#39;re on a laptop you don&#39;t want to (or simply can&#39;t) carry those files around forever.</p> <p>You produce a series, upload the final videos, and archive the original files. So it feels simpler to have all of those files in one folder, and you can just move that folder around.</p> <p>This is a problem, though, if you ever want to re-edit those videos. To correct a mistake, say. Now you have to get the vidoes off &#39;the archive drive&#39; and put them back either <em>exactly</em> where they were, which involves you remembering exactly where that was, or you have to re-point your video editing software to their new location. <a href="https://www.blackmagicdesign.com/products/davinciresolve/">DaVinci Resolve</a> calls this &#39;re-linking the media&#39;.</p> <p>Neither of us are video editing professionals. This is an exercise fraught with danger that often results in us looking at this dreaded screen. <a href="https://kagi.com/search?q=relink+media+davinci+resolve+site%3Ablackmagicdesign.com&r=au&sh=ZwVf-4o74LOS-cQZeX2yiw">We&#39;re not the only ones</a>.</p> <picture class="JDImage6 astro-3zw7efbj"> <source media="(prefers-color-scheme: light) and (min-width: 600px)" class="astro-3zw7efbj"> <source media="(prefers-color-scheme: dark) and (min-width: 600px)" class="astro-3zw7efbj"> <source media="(prefers-color-scheme: light) and (max-width: 599px)" class="astro-3zw7efbj"> <source media="(prefers-color-scheme: dark) and (max-width: 599px)" class="astro-3zw7efbj"> <img alt="Screenshot of a video timeline. Most of the icons representing video frames are red and have a question mark icon. Not good." class=" astro-3zw7efbj" loading="lazy" src="https://johnnydecimal.com/img/v6/22.00.0167A-Davinci-unlinked--0-cx-698x522.png" width="698" height="522"> <figcaption class="astro-3zw7efbj">Figure 22.00.0167A. DaVinci Resolve&#39;s dreaded &#39;unlinked media&#39; icon.</figcaption> </picture> <h3 id="what-if-the-files-never-moved">What if the files never moved?</h3> <p>With the creative pattern, the path to the file <em>never changes</em>. It&#39;s always (truncated for simplicity)<br> <code>D25/50-59/50082/file.mov</code>.</p> <p>What changes is whether the original video files are actually at that location on disk. On my laptop, I&#39;ve told <a href="https://syncthing.net">Syncthing</a>, which <a href="https://johnnydecimal.com/22.00.0119/">keeps our files in sync</a> across the world, to remove those files. Lucy, our editor, still holds a copy.</p> <p>I&#39;ll eventually remove all of these files from both laptops. We say that I&#39;m &#39;dehydrating&#39; those folders. At this point, launching DaVinci will look like the screenshot above. But the only thing I&#39;ll need to do if Lucy wants to edit a video is &#39;rehydrate&#39; those files, synchronising them across the network from our server in Melbourne.</p> <p>Nothing changed from DaVinci&#39;s perspective. Nothing ever will. Bye bye, red timeline of confusion.</p> <hr> <p><em>100% human. 0% AI. Always.</em></p> Mead Update - November 13, 2025 - Cool As Heck https://cool-as-heck.blog/mead-update-november-13-2025 2025-11-13T21:05:22.000Z <div>This week I deoxidized and stabilized the pumpkin mead after back sweetening it last week. It turned out pretty good! If I had to do it again I would go a little less heavy on the amount of spices or the amount of time I left them in. Still, it turned out nice and I can't wait to see how it tastes after a few weeks in the bottle. </div> <div><br></div> <div><figure class="attachment attachment--preview attachment--jpg"> <img alt="Uploaded image" data-lightbox-full-url="https://pagecord.com/cdn-cgi/image/width=1600,height=1200,format=webp,quality=90/https://storage.pagecord.com/ajp4de9at04j9wohxy1etpcmarzt" src="https://pagecord.com/cdn-cgi/image/width=1600,height=1200,format=webp,quality=90/https://storage.pagecord.com/ajp4de9at04j9wohxy1etpcmarzt"> </figure></div> <div>I've got a batch of ginger peach cyser going now. Well, it's just peach at the moment. It still has a week or two of fermentation left before I can test it, rack it, and add the ginger. I also got some peach syrup to use as a sweetener if it needs a little more post-fermentation sugar or an extra kick of peach flavor.</div>