<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="/rss.xsl" media="all"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
<channel>
<title>Roastidio.us Tagged with web</title>
<link>https://roastidio.us/tag/2691</link>
<atom:link href="https://roastidio.us/tagged_with/web" rel="self" type="application/rss+xml"></atom:link>
<description>Roastidio.us Tagged with web</description>
<item>
<title>The woes of sanitizing SVGs</title>
<link>https://muffin.ink/blog/scratch-svg-sanitization/</link>
<guid isPermaLink="false">sQi1zJchUe9eh8KrSd9eSbL8O4bW26wwaLhQ-Q==</guid>
<pubDate>Tue, 28 Apr 2026 05:54:22 +0000</pubDate>
<description>Scratch has a long history of SVG-related vulnerabilities. The source of these is that Scratch parses user-generated (ie. attacker-controlled) content into an &lt;svg&gt; element and appends it into the main document for various operations (eg. measuring SVG bounding box in a more reliable way than viewbox or width/height).</description>
<content:encoded>&lt;p&gt;Scratch has a long history of SVG-related vulnerabilities. The source of these is that Scratch parses user-generated (ie. attacker-controlled) content into an &lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt; element and appends it into the main document for various operations (eg. measuring SVG bounding box in a more reliable way than viewbox or width/height).&lt;/p&gt;&lt;p&gt;No matter how briefly the SVG remains in the main document, this is an inherently unsafe operation. Scratch&amp;#39;s approach to making this safe has been to build increasingly complex infrastructure around parsing the SVG and the markup within to remove dangerous parts.&lt;/p&gt;&lt;p&gt;I think Scratch&amp;#39;s approach to SVG sanitization is doomed. To explain, we have to take a trip through the history of SVG sanitization in Scratch to see how well it has worked so far.&lt;/p&gt;&lt;h2&gt;2019: XSS via &amp;lt;script&amp;gt; tag&lt;/h2&gt;&lt;p&gt;In 2019, a few months after the initial release of Scratch 3, Scratch discovered that SVGs can contain &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tags that Scratch would cause to be executed when the SVG loads. This is known as an XSS.&lt;/p&gt;&lt;p&gt;In Scratch terms, an XSS allows an attacker to take actions on behalf of anyone that loads their project. For example, the attacker can post comments, delete projects, or otherwise try to take over the victim&amp;#39;s account. In Scratch Desktop, XSS is elevated to arbitrary code execution because Scratch Desktop enables Electron&amp;#39;s dangerous &lt;a href=&quot;https://muffin.ink/blog/bananatron#node-js-integration&quot;&gt;Node.js integration&lt;/a&gt; feature. (TurboWarp Desktop has not enabled that feature since v0.2.0 from March 2021)&lt;/p&gt;&lt;p&gt;Example from Scratch&amp;#39;s test suite:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&amp;lt;!DOCTYPE svg PUBLIC &amp;quot;-//W3C//DTD SVG 1.1//EN&amp;quot;
  &amp;quot;http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd&amp;quot;&amp;gt;
&amp;lt;svg version=&amp;quot;1.1&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot;&amp;gt;
  &amp;lt;circle cx=&amp;quot;250&amp;quot; cy=&amp;quot;250&amp;quot; r=&amp;quot;50&amp;quot; fill=&amp;quot;red&amp;quot; /&amp;gt;
  &amp;lt;script type=&amp;quot;text/javascript&amp;quot;&amp;gt;&amp;lt;![CDATA[
      alert(&amp;#39;from the svg!&amp;#39;)
  ]]&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;/svg&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This &lt;a href=&quot;https://github.com/scratchfoundation/scratch-svg-renderer/commit/78cc7ea22887cdb2d3e3a00b23557a37251632f8&quot;&gt;was fixed&lt;/a&gt; by using a regular expression to remove script tags.&lt;/p&gt;&lt;p&gt;Surely, with this change, SVGs are now fully safe and will require no further security fixes.&lt;/p&gt;&lt;h2&gt;2020: XSS via oversights in previous fix (CVE-2020-7750)&lt;/h2&gt;&lt;p&gt;In 2020, &lt;a href=&quot;https://scratch.mit.edu/discuss/topic/449794/&quot;&gt;apple502j discovered&lt;/a&gt; that XSS is still possible. It turns out that the previous fix is utterly defective and can be bypassed by capitalizing &lt;code&gt;&amp;lt;SCRIPT&amp;gt;&lt;/code&gt; because the regex is case-sensitive, among several other ways to bypass it. Even if the regex were implemented correctly, it would still not work because there are other ways to embed JavaScript in an SVG. For example, one can use an inline event handler:&lt;/p&gt;&lt;p&gt;This &lt;a href=&quot;https://github.com/scratchfoundation/scratch-svg-renderer/commit/9ebf57588aa596c4fa3bb64209e10ade395aee90&quot;&gt;was fixed&lt;/a&gt; by using &lt;a href=&quot;https://github.com/cure53/dompurify&quot;&gt;DOMPurify&lt;/a&gt; to remove scripts from the SVG before &lt;code&gt;scratch-svg-renderer&lt;/code&gt; appends it into the document.&lt;/p&gt;&lt;p&gt;Surely, with this change, SVGs are now fully safe and will require no further security fixes.&lt;/p&gt;&lt;h2&gt;2022: HTTP leak via &amp;lt;image&amp;gt; href&lt;/h2&gt;&lt;p&gt;In 2022, it was discovered that using the &lt;code&gt;href&lt;/code&gt; property on an &lt;code&gt;&amp;lt;image&amp;gt;&lt;/code&gt; element, an attacker can create an SVG that will invoke an external request when it is loaded. It turns out that while DOMPurify removes executable code, it &lt;a href=&quot;https://github.com/cure53/DOMPurify/wiki/Security-Goals-&amp;amp;-Threat-Model#non-goals&quot;&gt;does not protect against HTTP leaks&lt;/a&gt; because &amp;quot;there are too many ways of doing that and our tests showed that it cannot be done reliably&amp;quot;.&lt;/p&gt;&lt;p&gt;In Scratch terms, an HTTP leak means that a Scratch user can log the IP of anyone that loads their project, possibly revealing information such as location or school district. The victim would not need to click on any links; the IP log happens just by loading the project. Scratch seems to consider this a security bug, and I agree.&lt;/p&gt;&lt;p&gt;Example:&lt;/p&gt;&lt;p&gt;This &lt;a href=&quot;https://github.com/scratchfoundation/scratch-editor/commit/8bcef3bd7d1c80fd6afc8ada273e8346a802ccf1&quot;&gt;was fixed&lt;/a&gt; by adding DOMPurify hooks to remove &lt;code&gt;href&lt;/code&gt; properties from all elements if the URL refers to a remote website.&lt;/p&gt;&lt;p&gt;Surely, with this change, SVGs are now fully safe and will require no further security fixes.&lt;/p&gt;&lt;h2&gt;2023: HTTP leak via CSS @import&lt;/h2&gt;&lt;p&gt;In 2023, it was discovered that using a CSS &lt;code&gt;@import&lt;/code&gt; statement inside of a &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; element, an attacker could create a project that invokes external requests when the project loads. Example:&lt;/p&gt;&lt;p&gt;This &lt;a href=&quot;https://github.com/scratchfoundation/scratch-svg-renderer/commit/a3ba9eb15036b6983a6b7713d0f1d5114e00329f&quot;&gt;was fixed&lt;/a&gt; by integrating a CSS parser written in JavaScript to remove dangerous parts of the CSS. They would parse all stylesheets contained in SVGs, remove any &lt;code&gt;@import&lt;/code&gt; statements, and convert the CSS back to a string if any changes were made so that the dangerous stuff is removed.&lt;/p&gt;&lt;p&gt;Surely, with this change, SVGs are now fully safe and will require no further security fixes.&lt;/p&gt;&lt;h2&gt;2024: XSS via Paper.js&lt;/h2&gt;&lt;p&gt;In 2024, I discovered &lt;a href=&quot;https://muffin.ink/blog/paperjs-xss&quot;&gt;an XSS&lt;/a&gt; in &lt;a href=&quot;https://github.com/paperjs/paper.js&quot;&gt;Paper.js&lt;/a&gt;, a library Scratch uses in the costume editor. It turns out that while Scratch sanitized SVGs before working on them in scratch-svg-renderer, unsanitized SVGs were still being passed to Paper.js. This has largely the same impact as the 2020 scratch-svg-renderer XSS, but occurs when using the costume editor instead of when initially opening a project. Example:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&amp;lt;svg version=&amp;quot;1.1&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; xmlns:xlink=&amp;quot;http://www.w3.org/1999/xlink&amp;quot; data-paper-data=&amp;quot;any invalid JSON&amp;quot;&amp;gt;
    &amp;lt;foreignObject x=&amp;quot;1&amp;quot; y=&amp;quot;1&amp;quot; width=&amp;quot;1&amp;quot; height=&amp;quot;1&amp;quot;&amp;gt;
        &amp;lt;img
            xmlns=&amp;quot;http://www.w3.org/1999/xhtml&amp;quot;
            src=&amp;quot;data:any invalid URL&amp;quot;
            onerror=&amp;quot;alert(1)&amp;quot;
        /&amp;gt;
    &amp;lt;/foreignObject&amp;gt;
&amp;lt;/svg&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This &lt;a href=&quot;https://github.com/scratchfoundation/scratch-editor/pull/251&quot;&gt;was somewhat fixed&lt;/a&gt; on an extremely delayed timeline by extending the existing SVG sanitization code to run when loading an SVG, not just when processing it in scratch-svg-renderer. This means that Paper.js will only receive SVGs that have already been sanitized.&lt;/p&gt;&lt;p&gt;I say &amp;quot;somewhat fixed&amp;quot; because I&amp;#39;m not sure if that sanitization ever runs for server-downloaded SVGs. Scratch support told me they &amp;quot;have protections against this that are handled on our server side&amp;quot; which may make that redundant. I have never seen any evidence of such protections while developing proof-of-concepts, but maybe they are real.&lt;/p&gt;&lt;p&gt;Surely, with this change, SVGs are now fully safe and will require no further security fixes.&lt;/p&gt;&lt;h2&gt;2025: HTTP leak via CSS url()&lt;/h2&gt;&lt;p&gt;In 2025, it was discovered that using &lt;code&gt;url()&lt;/code&gt; inside of certain CSS rules, an attacker can create an SVG that will invoke an external request when it is loaded. Examples:&lt;/p&gt;&lt;p&gt;This &lt;a href=&quot;https://github.com/scratchfoundation/scratch-editor/commit/2756ebd4275987e2f99791ae6123daea1fb28ce7&quot;&gt;was fixed&lt;/a&gt; by substantially expanding the SVG sanitization code to also search for any usage of &lt;code&gt;url()&lt;/code&gt; and remove any styles or attributes referencing external URLs.&lt;/p&gt;&lt;p&gt;Surely, with this change, SVGs are now fully safe and will require no further security fixes.&lt;/p&gt;&lt;h2&gt;2026: HTTP leak via several bugs in the previous code&lt;/h2&gt;&lt;p&gt;In 2026, it was discovered that using &lt;code&gt;url()&lt;/code&gt; inside of certain CSS rules, it is still possible for an attacker to create an SVG that will invoke an external request when it is loaded. It turns out there were at least three unique bugs that each allowed an HTTP leak:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;Did not account for CSS allowing one to write out &lt;code&gt;url(...)&lt;/code&gt; using escape codes&lt;/li&gt;
&lt;li&gt;Did not handle a &lt;code&gt;style&lt;/code&gt; attribute having more than one &lt;code&gt;url(...)&lt;/code&gt; inside it, where the first one is safe but the second one is not&lt;/li&gt;
&lt;li&gt;Did not handle &lt;code&gt;url()&lt;/code&gt; defined in a CSS variable and referenced via &lt;code&gt;var(--name)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Examples:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&amp;lt;svg xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot;&amp;gt;
    &amp;lt;circle fill=&amp;quot;\75\72\6c(https://example.com/ping)&amp;quot; /&amp;gt;
    &amp;lt;rect style=&amp;quot;/* url(#safe_url) */ background-image: url(https://example.com/ping)&amp;quot; /&amp;gt;
    &amp;lt;style&amp;gt;
        :root {
            --example: url(https://example.com/ping);
        }
        .img {
            background-image: var(--example);
        }
    &amp;lt;/style&amp;gt;
    &amp;lt;rect class=&amp;quot;img&amp;quot; /&amp;gt;
&amp;lt;/svg&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This &lt;a href=&quot;https://github.com/scratchfoundation/scratch-editor/commit/fbea3d199278c19f88023cdd48c83d6edd7cf81d&quot;&gt;was fixed&lt;/a&gt; &lt;a href=&quot;https://github.com/scratchfoundation/scratch-editor/commit/6d20ec339f24367502bd326bb7580ddd6bcffe23&quot;&gt;by adding&lt;/a&gt; &lt;a href=&quot;https://github.com/scratchfoundation/scratch-editor/commit/2570c393f8ca4e47ea6a905bbc0fa3f4db90e4df&quot;&gt;a substantial amount&lt;/a&gt; &lt;a href=&quot;https://github.com/scratchfoundation/scratch-editor/commit/b850a15d871adb67e150c89bd8c1a36fdc044251&quot;&gt;of additional complexity&lt;/a&gt; &lt;a href=&quot;https://github.com/scratchfoundation/scratch-editor/commit/9442435c68699dd7af30aae23b9f725f627fb4a1&quot;&gt;around code that&lt;/a&gt; was already way too complex.&lt;/p&gt;&lt;p&gt;Surely, with this change, SVGs are now fully safe and will require no further security fixes.&lt;/p&gt;&lt;h2&gt;2026: Full page restyling via long transitions&lt;/h2&gt;&lt;p&gt;In 2026, it was discovered that through clever use of very long transitions and forcing the browser to restyle all elements, an attacker can apply arbitrary styles to the full Scratch page that last until refresh. Most uses of this have been &amp;quot;fun&amp;quot; things, but here&amp;#39;s a few ideas about more evil things you might be able to do:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;Hiding the report button.&lt;/li&gt;
&lt;li&gt;Making the like/favorite buttons cover the entire page, so that users are tricked into clicking them.&lt;/li&gt;
&lt;li&gt;Display text telling the user that they need to open a website in a new tab to &amp;quot;verify&amp;quot; their account (some phishing page). Users are likely to trust the instructions because the message is coming from the real scratch.mit.edu.&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Example project (not mine): &lt;a href=&quot;https://scratch.mit.edu/projects/1299571218/&quot;&gt;https://scratch.mit.edu/projects/1299571218/&lt;/a&gt;&lt;/p&gt;&lt;p&gt;This will probably get fixed at some point, but today what you&amp;#39;ll see is this:&lt;/p&gt;&lt;img src=&quot;https://muffin.ink/blog/scratch-svg-sanitization/blue.png&quot; alt=&quot;Scratch project page, but all the page background colors are very obviously wrong.&quot; title=&quot;&quot;/&gt;&lt;p&gt;This project uses two SVGs. The first one is the &amp;quot;trigger&amp;quot;:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&amp;lt;svg xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; width=&amp;quot;200&amp;quot; height=&amp;quot;100&amp;quot;&amp;gt;
  &amp;lt;rect x=&amp;quot;0&amp;quot; y=&amp;quot;0&amp;quot; width=&amp;quot;200&amp;quot; height=&amp;quot;100&amp;quot; fill=&amp;quot;#111&amp;quot;&amp;gt;&amp;lt;/rect&amp;gt;
  &amp;lt;text x=&amp;quot;100&amp;quot; y=&amp;quot;55&amp;quot; fill=&amp;quot;#0f0&amp;quot; font-size=&amp;quot;12&amp;quot; text-anchor=&amp;quot;middle&amp;quot;&amp;gt;
    Trigger
  &amp;lt;/text&amp;gt;

  &amp;lt;style&amp;gt;
    /* Force browser to recalc styles to activate first SVG */
    *, * *, * * *, * * * * {
      transform: translateX(1px) scale(10000) rotateY(45deg) perspective(1cm) !important;
      transition: all 9999s ease !important;
      filter: blur(0px) !important;
    }
  &amp;lt;/style&amp;gt;
&amp;lt;/svg&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The second one contains the styles to display:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&amp;lt;svg xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; width=&amp;quot;200&amp;quot; height=&amp;quot;100&amp;quot;&amp;gt;
  &amp;lt;rect x=&amp;quot;0&amp;quot; y=&amp;quot;0&amp;quot; width=&amp;quot;200&amp;quot; height=&amp;quot;100&amp;quot; fill=&amp;quot;#111&amp;quot;&amp;gt;&amp;lt;/rect&amp;gt;
  &amp;lt;text x=&amp;quot;100&amp;quot; y=&amp;quot;55&amp;quot; fill=&amp;quot;#0f0&amp;quot; font-size=&amp;quot;12&amp;quot; text-anchor=&amp;quot;middle&amp;quot;&amp;gt;
    Styles
  &amp;lt;/text&amp;gt;

  &amp;lt;style&amp;gt;
    /* Global background blue */
    * {
      background-color: blue !important;
      color: white !important;
    }

    /* Project instructions/description styling */
    .project-description, .instructions-container {
      background-color: yellow !important;
      color: black !important;
      border: 10px solid red !important;
      transform: scale(1.1) !important;
    }
  &amp;lt;/style&amp;gt;
&amp;lt;/svg&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I won&amp;#39;t pretend to fully understand what&amp;#39;s going on here or why it works non-deterministically, but my general understanding is:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;The trigger SVG applies &lt;code&gt;transform&lt;/code&gt; and &lt;code&gt;filter&lt;/code&gt; to every element in the document to forcibly make the browser recompute all styles right away, applying styles from the other SVG.&lt;/li&gt;
&lt;li&gt;The trigger SVG applies a very long &lt;code&gt;transition&lt;/code&gt; so that when the other SVG is removed, the styles will stick around for the duration of the &amp;quot;transition&amp;quot;&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;This is not fixed.&lt;/p&gt;&lt;p&gt;Surely, if this were fixed, SVGs would be fully safe and would require no further security fixes.&lt;/p&gt;&lt;h2&gt;2026: HTTP leak via image-set()&lt;/h2&gt;&lt;p&gt;I reported this one to Scratch in 2025. They didn&amp;#39;t fix it, so whatever, I&amp;#39;ll disclose it here. Any reasonable disclosure period lapsed 6 months ago.&lt;/p&gt;&lt;p&gt;Instead of using &lt;code&gt;url()&lt;/code&gt;, an attacker can use &lt;code&gt;image-set()&lt;/code&gt; to create an SVG that will invoke an external request when it is loaded. Examples:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&amp;lt;svg xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot;&amp;gt;
    &amp;lt;!--
        image-set(...) can cause external resources to be requested without using url() at all.
    --&amp;gt;
    &amp;lt;style&amp;gt;
        .image-set-with-string-url {
            background-image: image-set(&amp;quot;https://example.com/ping&amp;quot; 1x);
        }
    &amp;lt;/style&amp;gt;
    &amp;lt;rect class=&amp;quot;image-set-with-string-url&amp;quot; /&amp;gt;

    &amp;lt;!--
        image-set(url(...)) works the same as image-set(...).
        This already gets blocked by the existing sanitization.
    --&amp;gt;
    &amp;lt;style&amp;gt;
        .image-set-with-inner-url-function {
            background-image: image-set(url(https://example.com/ping) 1x);
        }
    &amp;lt;/style&amp;gt;
    &amp;lt;rect class=&amp;quot;image-set-with-inner-url-function&amp;quot;&amp;gt;&amp;lt;/rect&amp;gt;

    &amp;lt;!--
        image-set() can also be used in inline style attributes.
    --&amp;gt;
    &amp;lt;rect style=&amp;quot;background-image: image-set(&amp;#39;https://example.com/ping&amp;#39; 1x)&amp;quot; /&amp;gt;
&amp;lt;/svg&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is not fixed.&lt;/p&gt;&lt;p&gt;Surely, if this were fixed, SVGs would be fully safe and would require no further security fixes.&lt;/p&gt;&lt;h2&gt;20XX: HTTP leak via new CSS features&lt;/h2&gt;&lt;p&gt;I also reported this one to Scratch in 2025. This bug actually doesn&amp;#39;t work today, but will in the future if browsers ever implement all of &lt;a href=&quot;https://www.w3.org/TR/css-values-4/&quot;&gt;CSS Units Level 4&lt;/a&gt; or &lt;a href=&quot;https://drafts.csswg.org/css-images-4/&quot;&gt;CSS Images Level 4&lt;/a&gt;. Today, &lt;a href=&quot;https://ladybird.org/&quot;&gt;Ladybird&lt;/a&gt; is the only browser to implement either of these, but major browsers could implement them someday as well.&lt;/p&gt;&lt;p&gt;Instead of using &lt;code&gt;url()&lt;/code&gt;, an attacker can use &lt;a href=&quot;https://www.w3.org/TR/css-values-4/#example-a2ee15a6&quot;&gt;&lt;code&gt;src()&lt;/code&gt;&lt;/a&gt; or &lt;a href=&quot;https://drafts.csswg.org/css-images-4/#funcdef-image&quot;&gt;&lt;code&gt;image()&lt;/code&gt;&lt;/a&gt; to create an SVG that will invoke an external request when it is loaded. Examples:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&amp;lt;svg xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot;&amp;gt;
    &amp;lt;!--
        Everything in this file relies on features that are defined in the browser specs, but not yet implemented in any browser.
        In theory, future browsers might initiate requests when they see these styles.
    --&amp;gt;

    &amp;lt;!--
        CSS Units Level 4 defines src(...) as an alternative to url(...).
        Unlike url(), src()&amp;#39;s URL can be any expression, not just a constant string.
        Reference: https://www.w3.org/TR/css-values-4/#example-a2ee15a6
        Not implemented by any major browser today. (Only implemented in the experimental Ladybird browser)
    --&amp;gt;
    &amp;lt;style&amp;gt;
        .src-constant {
            background: src(&amp;#39;https://example.com/ping&amp;#39;);
        }
        .src-variable {
            --url: &amp;#39;https://example.com/ping&amp;#39;;
            background: src(var(--url));
        }
    &amp;lt;/style&amp;gt;
    &amp;lt;rect class=&amp;quot;src-constant&amp;quot; /&amp;gt;
    &amp;lt;rect class=&amp;quot;src-variable&amp;quot; /&amp;gt;

    &amp;lt;!--
        CSS Images Level 4 defines image() as an alternative to url() for images.
        Reference: https://www.w3.org/TR/css-images-4/#image-notation
        Not implemented by any major browser today.
    --&amp;gt;
    &amp;lt;style&amp;gt;
        .image {
            background: image(&amp;#39;https://example.com/ping&amp;#39;, black);
        }
    &amp;lt;/style&amp;gt;
    &amp;lt;rect class=&amp;quot;image&amp;quot; /&amp;gt;

    &amp;lt;!-- Same as above examples, but using inline styles --&amp;gt;
    &amp;lt;rect style=&amp;quot;background: src(&amp;#39;https://example.com/ping&amp;#39;);&amp;quot; /&amp;gt;
    &amp;lt;rect style=&amp;quot;--url: &amp;#39;https://example.com/ping&amp;#39;; background: src(var(--url));&amp;quot; /&amp;gt;
    &amp;lt;rect style=&amp;quot;background: image(&amp;#39;https://example.com/ping&amp;#39;, black);&amp;quot; /&amp;gt;
&amp;lt;/svg&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is not fixed.&lt;/p&gt;&lt;p&gt;Surely, if this were fixed, SVGs would be fully safe and would require no further security fixes.&lt;/p&gt;&lt;h2&gt;This is unsustainable&lt;/h2&gt;&lt;p&gt;Stacking more and more complexity into sanitization is clearly a doomed approach. We are more than 5 major revisions deep and yet there are still known holes. People are actively sharing projects on the Scratch website bypassing SVG sanitization. And the moment browsers decide to implement the latest CSS specs, even more holes will open up.&lt;/p&gt;&lt;p&gt;Furthermore, not all of these problems have clear solutions. For full page styling, both SVGs seem completely benign: there is no JavaScript or references to external resources. The fix would likely be to remove &lt;code&gt;transition&lt;/code&gt; styles since the transitions would never run in Scratch anyway, but are you sure that&amp;#39;s sufficient? Will you remember to also remove all the vendor-prefixed versions of &lt;code&gt;transition&lt;/code&gt;? What about &lt;code&gt;animation&lt;/code&gt; styles?&lt;/p&gt;&lt;p&gt;Some other possible cases that might allow more bypasses in the future:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;css-tree&lt;/code&gt; (the library Scratch uses to parse CSS) and the real CSS parsers in browsers might not completely match. If so, &lt;code&gt;css-tree&lt;/code&gt; might parse CSS such that everything looks fine and thus nothing gets removed, but then the browser&amp;#39;s real parser does recognize external content.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Advanced new CSS features such &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@property&quot;&gt;&lt;code&gt;@property&lt;/code&gt;&lt;/a&gt; or &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Nesting/Using&quot;&gt;native nesting&lt;/a&gt; that &lt;code&gt;css-tree&lt;/code&gt; versions might not be able to meaningfully parse without constant updates.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Browsers can always add new functions that can reference external content as they have already done with &lt;code&gt;image-set()&lt;/code&gt; and the spec implies will happen for &lt;code&gt;src()&lt;/code&gt; and &lt;code&gt;image()&lt;/code&gt;. How will you keep up with the constant change in these specs to evaluate every new function and see if it could somehow allow referencing external content?&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;h2&gt;An alternative&lt;/h2&gt;&lt;p&gt;TurboWarp (a Scratch fork I work on) was unaffected by the 2026 HTTP leaks and full page restyling issue. This isn&amp;#39;t because I found all the clever ways for an SVG to do something bad; in fact I &lt;a href=&quot;https://github.com/TurboWarp/scratch-svg-renderer/commit/ccc8890d808fd9116d0f93e7d76649b6dc6525e7&quot;&gt;actually deleted&lt;/a&gt; the CSS sanitization code entirely to make packaged projects 400KB smaller.&lt;/p&gt;&lt;p&gt;I implemented an alternative approach of sandboxing the SVG inside of an iframe. First, we set up an iframe with a &lt;code&gt;sandbox&lt;/code&gt; property of &lt;code&gt;allow-same-origin&lt;/code&gt;. This will block script execution inside the iframe, but still let us interact with the contents inside.&lt;/p&gt;&lt;p&gt;Second, we set up the iframe with the following hardcoded HTML:&lt;/p&gt;&lt;p&gt;The inline &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP&quot;&gt;Content-Security-Policy&lt;/a&gt; is set up to block all scripts and only allow loading safe resources from safe data URLs. We also still use DOMPurify to remove obviously evil things from the SVG. We then put the iframe into the document offscreen somewhere so that the measurement APIs Scratch needs will still work.&lt;/p&gt;&lt;p&gt;This approach gives us some very nice properties:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The browser uses its pre-existing code to do the hard part for us.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;TurboWarp doesn&amp;#39;t need to know about all the ways for an SVG to make a request. Your browser already knows this and will enforce it for any new APIs that get added.&lt;/p&gt;
&lt;p&gt;Real-world CSP implementations are not perfect and have holes. However, those holes generally are weird edge cases that require the attacker to already be executing JavaScript in some way. Those vulnerabilities are also considered browser security issues so they have bug bounties attached to them.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The SVG can&amp;#39;t affect the main document.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Consider the case of the full page restyling. Because the SVG is trapped inside of an iframe, the only thing it can restyle is the iframe. The styles in the iframe do not matter, so that&amp;#39;s perfectly fine.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;You can find our code here:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/TurboWarp/scratch-svg-renderer/commit/0611932d92755293155b63443a34addf08498ce1&quot;&gt;scratch-svg-renderer fork&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/TurboWarp/paper.js/commit/a3a4a8c2e276552dc12ad18a22c07d1a5d4af100&quot;&gt;paper.js fork&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Maybe you can do some other interesting stuff with shadow DOM or other web APIs, but we found that the iframe is working fine for us.&lt;/p&gt;&lt;p&gt;The below sections will cover any new issues I become aware of after publication.&lt;/p&gt;&lt;h2&gt;2026-04-12: Claude finds HTTP leak via CSS nesting relaxed syntax&lt;/h2&gt;&lt;p&gt;After publishing this, I was curious how well current language models are at finding these bugs. I told Claude Opus 4.6 to clone the &lt;a href=&quot;https://github.com/scratchfoundation/scratch-editor/&quot;&gt;scratch-editor repo&lt;/a&gt;, look at the recent SVG renderer changes, and see if there were any holes. Results were interesting:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;Claude discovered on its own that &lt;code&gt;image-set(...)&lt;/code&gt; is not sanitized and can cause HTTP leaks.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Claude discovered a new issue not described in the original version of this post.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;The bug involves CSS nesting, which can appear in two forms. The nested style can prefix the selector with an &lt;code&gt;&amp;amp;&lt;/code&gt; or instead just not prefix it (the latter being known as &amp;quot;relaxed&amp;quot; syntax). Modern browsers interpret both of the below identically.&lt;/p&gt;&lt;p&gt;&lt;code&gt;css-tree&lt;/code&gt; is capable of parsing the &lt;code&gt;&amp;amp;&lt;/code&gt;-prefixed version into a meaningful syntax tree that Scratch can sanitize. However, it turns out that &lt;code&gt;css-tree&lt;/code&gt; does not know how to parse the relaxed version. The entire &lt;code&gt;div { ... }&lt;/code&gt; block is parsed as a &amp;quot;raw text&amp;quot; node which Scratch&amp;#39;s code will not sanitize. Full example SVG:&lt;/p&gt;&lt;p&gt;Earlier in this post, I mentioned that &amp;quot;&lt;code&gt;css-tree&lt;/code&gt; and the real CSS parsers in browsers might not completely match&amp;quot;. This is a real-world example of that kind of bug allowing CSS to bypass sanitization. Note that &lt;code&gt;css-tree&lt;/code&gt; currently has 48 open issues and certainly many more unknown ones. I believe depending on &lt;code&gt;css-tree&lt;/code&gt; to be a perfect parser is a hopeless path that will continue to result in more vulnerabilities. TurboWarp&amp;#39;s SVG sandbox fixed this bug before I even knew it existed.&lt;/p&gt;&lt;p&gt;This is not fixed. The &lt;a href=&quot;https://github.com/csstree/csstree/issues/268&quot;&gt;&lt;code&gt;css-tree&lt;/code&gt; issue&lt;/a&gt; for this bug has been open since December 2023.&lt;/p&gt;&lt;p&gt;Surely, if this were fixed, SVGs would be fully safe and would require no further security fixes.&lt;/p&gt;</content:encoded>
</item>
<item>
<title>AIオタクのセキュリティエンジニアが伝えたい、バイブコーディングのセキュリティリスク7選 - GMO Flatt Security Blog</title>
<link>https://blog.flatt.tech/entry/vibe_coding_security_risk</link>
<enclosure type="image/jpeg" length="0" url="https://cdn.image.st-hatena.com/image/scale/4da1ba20579ad3a39f5ac9b70ae8b9d46735b36e/backend=imagemagick;version=1;width=1300/https%3A%2F%2Fcdn-ak.f.st-hatena.com%2Fimages%2Ffotolife%2Ff%2Fflattsecurity%2F20260427%2F20260427213626.png"></enclosure>
<guid isPermaLink="false">8wXrtC0_W7NfQ0fPKw9spz6F5209hQykeEWqgA==</guid>
<pubDate>Tue, 28 Apr 2026 05:22:22 +0000</pubDate>
<description>Claude CodeやCursorをはじめとするAIエージェントを活用した「バイブコーディング」が広まる一方で、`.env`の認証情報漏洩や悪意あるパッケージのインストールなど、従来の開発では起きにくかったセキュリティインシデントが報告されています。本記事では、セキュリティエンジニアの視点からバイブコーディングのセキュリティリスクを7つに整理し、Agent Skill・MCPサー...</description>
<content:encoded>&lt;p&gt;&lt;span&gt;&lt;img src=&quot;https://cdn-ak.f.st-hatena.com/images/fotolife/f/flattsecurity/20260427/20260427213626.png&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;h2&gt;はじめに&lt;/h2&gt;&lt;p&gt;こんにちは。GMO Flatt Security株式会社 セキュリティエンジニア(兼Claude Codeオタク)の石川（&lt;a href=&quot;https://twitter.com/ryusei_ishika&quot;&gt;@ryusei_ishika&lt;/a&gt;）です。&lt;/p&gt;&lt;p&gt;Claude CodeやCursorなどのAIエージェントを活用したバイブコーディングが急速に広まっています。私自身、社内ではClaude Codeのリリース（ほぼ毎日）のたびにそのリリースノートの重要なポイントを解説しているほか、Biz職向けに&lt;a href=&quot;https://note.com/flatt_security/n/n3997acf4b0a7#:~:text=%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%8B%E3%82%A2%E6%9C%89%E5%BF%97%E3%81%AB%E3%82%88%E3%82%8B%E3%80%8C%E3%83%90%E3%82%A4%E3%83%96%E3%82%B3%E3%83%BC%E3%83%87%E3%82%A3%E3%83%B3%E3%82%B0%E8%AC%9B%E5%BA%A7%E3%80%8D&quot;&gt;「バイブコーディング講座」&lt;/a&gt;を実施するなど、社内でのAI活用の普及にも取り組んでいます*1。&lt;/p&gt;&lt;figure&gt;&lt;span&gt;&lt;img src=&quot;https://cdn-ak.f.st-hatena.com/images/fotolife/f/flattsecurity/20260427/20260427205610.png&quot; alt=&quot;Claude Code 2.1.72のリリースノートを解説する投稿&quot; title=&quot;&quot;/&gt;&lt;/span&gt;
&lt;span&gt;&lt;img src=&quot;https://cdn-ak.f.st-hatena.com/images/fotolife/f/flattsecurity/20260427/20260427205654.png&quot; alt=&quot;Claude Code 2.1.77のリリースノートを解説する投稿&quot; title=&quot;&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;リリースノートが公開されていると、出勤後すぐに便利な機能を解説する石川&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;昨今、AIエージェントを日常的に使い込むなかで、従来の開発では起きにくかったセキュリティリスクが顕在化しつつあると感じています。例えば、先日AIエージェント経由で&lt;code&gt;.env&lt;/code&gt;の認証情報が漏洩し、外部サービスのアカウントが乗っ取られた事例もSNS上で報告され、大きな反響を呼びました。&lt;/p&gt;&lt;p&gt;この記事では、&lt;strong&gt;AIエージェントを用いた開発時のセキュリティリスクを7つ取り上げ&lt;/strong&gt;、具体的な攻撃シナリオと対策を紹介しながら、開発現場で今日から実践できる内容にまとめました。バイブコーディングを始めた方はもちろん、普段からAIエージェントを使って開発している方にも参考になれば幸いです。&lt;/p&gt;&lt;ul&gt;
    &lt;li&gt;はじめに&lt;/li&gt;
    &lt;li&gt;なぜバイブコーディングでセキュリティリスクが高まるのか&lt;/li&gt;
    &lt;li&gt;よくない操作 — 開発時における任意コード実行のリスク&lt;ul&gt;
            &lt;li&gt;任意コード実行に対する共通の軽減策&lt;/li&gt;
            &lt;li&gt;リスク1. 悪意あるAgent Skillの実行&lt;/li&gt;
            &lt;li&gt;リスク2. 悪意のある設定ファイルが含まれるOSSレポジトリ&lt;/li&gt;
            &lt;li&gt;リスク3. サードパーティMCPサーバ&lt;/li&gt;
            &lt;li&gt;リスク4. AIが生成した危険な操作へのユーザーの承認&lt;/li&gt;
            &lt;li&gt;リスク5. ソフトウェアサプライチェーン攻撃&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;よくない持ち出し — 開発時における情報漏洩のリスク&lt;ul&gt;
            &lt;li&gt;リスク6. 意図しない外部ドメインへの通信&lt;/li&gt;
            &lt;li&gt;リスク7. 機密情報の漏洩&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;よくない出荷 — 開発段階で防げるリリース後のリスク&lt;/li&gt;
    &lt;li&gt;終わりに&lt;/li&gt;
&lt;/ul&gt;&lt;h2&gt;なぜバイブコーディングでセキュリティリスクが高まるのか&lt;/h2&gt;&lt;p&gt;具体的なリスクを紹介する前に、なぜAIエージェントを使った開発でセキュリティリスクが高まるのか、その背景を整理しておきます。&lt;/p&gt;&lt;p&gt;バイブコーディングで利用されるAIエージェントは、シェルの実行やファイルの編集、外部サービスとの通信など、開発環境に対する幅広い操作権限を持っています。こうした環境で起こりうる脅威は、大きく3つに分類できます。&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;よくない操作&lt;/strong&gt; — 意図しないコマンド実行やファイル破壊などの内部操作&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;よくない持ち出し&lt;/strong&gt; — 認証情報や機密データの外部への漏洩&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;よくない出荷&lt;/strong&gt; — 脆弱性を含んだコードのリリース&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;この3つの分類自体は、人間が開発する場合と変わりません。&lt;strong&gt;違いはその「起こりやすさ」にあります&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;LLMは「やってはいけないこと」を暗黙に理解する能力を持たず、ミスへの恐れやキャリアへの影響といった人間が持つ抑止力もありません。それにも関わらず、開発者はAIエージェントを複数同時に動かします*2。バイブコーディングの開発環境は、いわば「&lt;strong&gt;悪気もないが責任能力もない内部脅威が大量発生している状態&lt;/strong&gt;」と言えます。&lt;/p&gt;&lt;figure&gt;&lt;span&gt;&lt;img src=&quot;https://cdn-ak.f.st-hatena.com/images/fotolife/f/flattsecurity/20260427/20260427205836.jpg&quot; alt=&quot;aaa&quot; title=&quot;&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;引用: &lt;a href=&quot;https://youtu.be/FWPJISY0zzs?si=x_SRQVKRc5Y0PTy7&quot;&gt;https://youtu.be/FWPJISY0zzs?si=x_SRQVKRc5Y0PTy7&lt;/a&gt;&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;この記事では「よくない操作」を任意コード実行（リスク1〜5）、「よくない持ち出し」を情報漏洩（リスク6〜7）として紹介し、最後に「よくない出荷」としてリリース後のリスクにも触れていきます。&lt;/p&gt;&lt;h2&gt;よくない操作 — 開発時における任意コード実行のリスク&lt;/h2&gt;&lt;h3&gt;任意コード実行に対する共通の軽減策&lt;/h3&gt;&lt;p&gt;任意コード実行とは、コンピュータ上で意図しない様々な操作がされてしまう(マルウェア実行・認証情報の漏洩など)ような攻撃手法です。&lt;strong&gt;このリスクは一番発生しやすく、かつ発生した時の影響度も一番高いリスクです&lt;/strong&gt;。この後様々なパターンをご紹介しますが、&lt;strong&gt;どのパターンにも共通する対策・リスク軽減手法があります&lt;/strong&gt;。この記事を読んで「バイブコーディングのセキュリティリスクってちょっと怖いな...」と感じたら、&lt;strong&gt;これだけは優先して実施&lt;/strong&gt; してもらえたらと思います。&lt;/p&gt;&lt;p&gt;任意コード実行のリスクに対しては、&lt;strong&gt;サンドボックス環境の利用が最も効果的な軽減策の一つです。&lt;/strong&gt;&lt;a href=&quot;https://code.claude.com/docs/en/sandboxing&quot;&gt;Claude Sandbox&lt;/a&gt;や&lt;a href=&quot;https://e2b.dev/&quot;&gt;e2b.dev&lt;/a&gt;、&lt;a href=&quot;https://www.docker.com/ja-jp/products/docker-sandboxes/&quot;&gt;Docker Sandboxes&lt;/a&gt;などを活用すれば、危険なコードが実行されてもホストマシンへの影響を限定できます。&lt;/p&gt;&lt;p&gt;ただし、いずれの手法も開発するプロダクトの一部の機能が使用できなくなる場合があります。また、場合によってはソースコードの改ざんや漏洩などを防ぐのは難しいこと、コンテナエスケープ等の脆弱性によるバイパスの可能性がある点にも注意が必要です。さらに、Claude Codeのサンドボックス(&lt;code&gt;/sandbox&lt;/code&gt;機能)では、&lt;code&gt;allowUnsandboxedCommands&lt;/code&gt;を&lt;code&gt;false&lt;/code&gt;に設定しないと、ユーザーが承認した場合に一部コマンドがサンドボックス外で実行されてしまう点にも気をつけてください。&lt;/p&gt;&lt;h3&gt;リスク1. 悪意あるAgent Skillの実行&lt;/h3&gt;&lt;p&gt;Claude Codeの&lt;a href=&quot;https://code.claude.com/docs/en/plugin-marketplaces&quot;&gt;plugin marketplace&lt;/a&gt;やCursorの&lt;a href=&quot;https://cursor.com/ja/marketplace&quot;&gt;marketplace&lt;/a&gt;、Vercelの&lt;a href=&quot;https://skills.sh/&quot;&gt;The Agent Skills Directory&lt;/a&gt;など、コミュニティが作成したスキルを簡単にインポートできる仕組みが広まっています。これらの機能は便利な一方で、その性質上、&lt;strong&gt;他人が作成したプロンプトを自分のPC上で実行しているのと同義&lt;/strong&gt;です。&lt;/p&gt;&lt;p&gt;スキルは実行時にLLMのコンテキストへ動的に注入されるプロンプトであり、LLMはその内容をある程度信頼された指示として扱います。よって悪意ある内容が含まれている場合に&lt;a href=&quot;https://blog.flatt.tech/entry/prompt_injection&quot;&gt;プロンプトインジェクション&lt;/a&gt;攻撃として機能し、意図しないコマンドが実行されるおそれがあります。実際、近年公開された&lt;a href=&quot;https://arxiv.org/abs/2601.10338&quot;&gt;論文&lt;/a&gt;でも主要マーケットプレイスから悪意のあるスキルが多数公開されていたことが報告されています。&lt;/p&gt;&lt;p&gt;さらに、多くのスキルマーケットは署名検証やマルウェアスキャンといった仕組みが十分に整備されていません*3。そのため、スキルの導入時にはその内容の&amp;quot;&lt;strong&gt;注意力&lt;/strong&gt;&amp;quot;が求められます。&lt;/p&gt;&lt;p&gt;対策としては、サンドボックスの導入のほかに、&lt;strong&gt;中身を自分で理解・評価できないスキルは使わない&lt;/strong&gt;ことが基本です。Anthropic自身も&lt;a href=&quot;https://platform.claude.com/docs/en/agents-and-tools/agent-skills/overview#security-considerations&quot;&gt;公式ドキュメント&lt;/a&gt;で警告している通り、&lt;a href=&quot;https://github.com/anthropics/skills&quot;&gt;公式マーケット&lt;/a&gt;や自作のスキルのみを利用し、やむを得ず外部のスキルを利用する場合はプロンプトの内容を&lt;strong&gt;十分に丁寧に注意深く&lt;/strong&gt;確認してください。&lt;/p&gt;&lt;h3&gt;リスク2. 悪意のある設定ファイルが含まれるOSSレポジトリ&lt;/h3&gt;&lt;p&gt;OSSリポジトリには、&lt;code&gt;.claude/commands/&lt;/code&gt;や&lt;code&gt;.cursor/rules/&lt;/code&gt;、&lt;code&gt;CLAUDE.md&lt;/code&gt;といったAIエージェント向けの設定ファイルが含まれていることがあります。&lt;code&gt;git clone&lt;/code&gt;した直後にAIエージェントを起動すると、これらの設定ファイルが自動的に読み込まれ、悪意ある指示が実行される恐れがあります。&lt;/p&gt;&lt;p&gt;このリスクが特に顕在化しやすいのは、次のようなケースです。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;1. 全権限スキップモード（Claude Codeの&lt;code&gt;--dangerously-skip-permissions&lt;/code&gt;相当）&lt;/strong&gt;: すべての権限確認をスキップするこのオプションを有効にした状態でクローン直後のリポジトリを開くと、悪意ある指示が一切確認なくコンテキストに取り込まれます。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;2. 自動承認モード（&lt;code&gt;--permission-mode auto&lt;/code&gt;相当）&lt;/strong&gt;: 自動承認モードでも、ユーザーの目を通さずにコマンドが実行されます。&lt;code&gt;--dangerously-skip-permissions&lt;/code&gt; ほど無制限ではないものの、設定ファイルを通じた悪意ある指示が通り抜けるリスクがあります。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;3. ツール許可設定（&lt;code&gt;permissions.allow&lt;/code&gt;相当）&lt;/strong&gt;: allowリストは「指定したツール/コマンドをプロンプトなしで許可する」設定で、&lt;code&gt;deny&lt;/code&gt;や&lt;code&gt;ask&lt;/code&gt;と組み合わせて使います。粒度が粗い許可（例: &lt;code&gt;Bash(*)&lt;/code&gt;）を設定していると、悪意ある指示経由で任意コマンドが確認なしに実行されてしまいます。また、適切に絞り込んでいても万全ではありません。弊社の記事である&lt;a href=&quot;https://flatt.tech/research/posts/pwning-claude-code-in-8-different-ways/&quot;&gt;Pwning Claude Code in 8 Different Ways&lt;/a&gt;では、Claude Codeのデフォルト許可リストに含まれる&lt;code&gt;sed&lt;/code&gt;や&lt;code&gt;sort&lt;/code&gt;等の一見無害なコマンドであっても、引数の組み合わせ次第で任意コマンド実行が可能であることが示されています。&lt;/p&gt;&lt;p&gt;対策としては、サンドボックスの導入のほかに、リポジトリ内の設定ファイル（&lt;code&gt;.claude/&lt;/code&gt;、&lt;code&gt;.cursorrules&lt;/code&gt;、&lt;code&gt;CLAUDE.md&lt;/code&gt;等）の内容をAIエージェントの起動前に確認しておくことが効果的です。&lt;/p&gt;&lt;h3&gt;リスク3. サードパーティMCPサーバ&lt;/h3&gt;&lt;p&gt;MCP（Model Context Protocol）サーバは、AIエージェントに外部ツールやデータソースへのアクセスを提供する仕組みです。サードパーティのMCPサーバを利用する場合、&lt;strong&gt;提供元自体に悪意があるケース&lt;/strong&gt;と、&lt;strong&gt;実装に脆弱性があり攻撃者に悪用されるケース(プロンプトインジェクション経由での任意コード実行など)&lt;/strong&gt;の両方のリスクがあります。&lt;/p&gt;&lt;p&gt;また、MCPサーバはAIエージェントのサンドボックスとは別プロセスとして起動されることが多いため、サンドボックスの保護が適用されないケースがあります。例えば、エージェント自体はサンドボックス内にありますが、MCPサーバはサンドボックス外で起動されており、両者がHTTPで通信しているケースなどがこれにあたります。&lt;/p&gt;&lt;p&gt;対策として、MCPサーバ自体をサンドボックス環境で実行するほかに、MCPサーバの導入時は信頼できる提供元のものに限定して使用してください。その他、MCPサーバの詳細なセキュリティについては、弊社ブログの「&lt;a href=&quot;https://blog.flatt.tech/entry/mcp_security_first&quot;&gt;MCPにおけるセキュリティ考慮事項と実装における観点（前編）&lt;/a&gt;」「&lt;a href=&quot;https://blog.flatt.tech/entry/mcp_security_second&quot;&gt;MCPにおけるセキュリティ考慮事項と実装における観点（後編）&lt;/a&gt;」でも解説しています。&lt;/p&gt;&lt;p&gt;&lt;cite&gt;&lt;a href=&quot;https://blog.flatt.tech/entry/mcp_security_first&quot;&gt;blog.flatt.tech&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;&lt;p&gt;&lt;cite&gt;&lt;a href=&quot;https://blog.flatt.tech/entry/mcp_security_second&quot;&gt;blog.flatt.tech&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;&lt;h3&gt;リスク4. AIが生成した危険な操作へのユーザーの承認&lt;/h3&gt;&lt;p&gt;AIエージェントが生成・実行するコードには、意図しない危険な操作が含まれている場合があります。例えば、このようなパターンがあります。&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;指示した意図に反して、ホームディレクトリの設定ファイルを書き換えたり、&lt;strong&gt;プロジェクト外のファイルを削除&lt;/strong&gt;したりする&lt;/li&gt;
&lt;li&gt;「SQLインジェクションができないか検証して」と依頼した際に、LLMが&lt;code&gt;OR 1=1&lt;/code&gt;のような&lt;strong&gt;副作用のないペイロード&lt;/strong&gt;ではなく、&lt;code&gt;DROP TABLE&lt;/code&gt;を含む&lt;strong&gt;破壊的ペイロード&lt;/strong&gt;を生成する&lt;/li&gt;
&lt;li&gt;「TerraformでXXのリソースを追加して」と依頼した際に、「一旦全てのリソースを削除してから当該リソースを追加したほうがスムーズです」などと言いながら&lt;strong&gt;本番環境の全てのリソースを削除&lt;/strong&gt;する&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;対策としては、サンドボックスの導入のほかに、「AIエージェントに本番環境を変更させるような権限を与えない(ステージング環境で実施する)」、「実行するコマンドが何をするコマンドなのかを確認する」などが重要です。特に後者はLLMを使って確認するという方法もあり、例えばClaude CodeのBash Toolは実行するコマンドの下に、それが何をするコマンドなのかを表示してくれます。&lt;/p&gt;&lt;figure&gt;&lt;span&gt;&lt;img src=&quot;https://cdn-ak.f.st-hatena.com/images/fotolife/f/flattsecurity/20260427/20260427210354.png&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;3行目の「markdownファイルを〜」のところに実行するコマンドの説明が表示されている&lt;/figcaption&gt;&lt;/figure&gt;&lt;h3&gt;リスク5. ソフトウェアサプライチェーン攻撃&lt;/h3&gt;&lt;p&gt;AIエージェントが&lt;code&gt;npm install&lt;/code&gt;や&lt;code&gt;pip install&lt;/code&gt;などを実行する際、悪意あるパッケージが紛れ込むリスクがあります。npmの場合はライフサイクルスクリプト等(例: &lt;code&gt;preinstall&lt;/code&gt;)を起点として、自身のPC上で任意のコードが実行されてしまう恐れがあります。&lt;/p&gt;&lt;p&gt;このリスクで特に警戒すべきは、&lt;strong&gt;普段から利用している正規パッケージが攻撃者に侵害され、マルウェアを含むバージョンが配布される&lt;/strong&gt;ケースです。例えば、2025年にはメンテナアカウントの乗っ取りを起点として、&lt;a href=&quot;https://github.com/axios/axios&quot;&gt;axios&lt;/a&gt;や&lt;a href=&quot;https://github.com/BerriAI/litellm&quot;&gt;litellm&lt;/a&gt;をはじめ、数多くの広く使われるパッケージが侵害される事例が相次ぎました。こうした攻撃は利用者側に落ち度がなくてもインストール時に成立してしまう点で極めて影響範囲が広く、AIエージェントは人間の目を経ずに&lt;code&gt;npm install&lt;/code&gt;等を実行してしまうため、被害に気付くまでの時間も短くなりがちです。&lt;/p&gt;&lt;p&gt;さらに、AIエージェント固有のリスクとして&lt;strong&gt;Slopsquatting&lt;/strong&gt;も存在します。LLMは検証をせずに存在しないパッケージ名を生成してしまう場合があり、これを見越してLLMが生成しそうな架空のパッケージ名を先回りしてレジストリに登録しておく手法です。例えば、LLMが(正)&lt;code&gt;@anthropic-ai/claude-code&lt;/code&gt;を(誤)&lt;code&gt;@anthropic/claude-code&lt;/code&gt;のように間違えるだろうと見越して、後者にマルウェアを仕込んでおくといった攻撃が考えられます。&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;img src=&quot;https://cdn-ak.f.st-hatena.com/images/fotolife/f/flattsecurity/20260427/20260427211914.png&quot; alt=&quot;Slopsquattingの流れ&quot; title=&quot;&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;対策としては、開発環境やCI/CD環境をサンドボックス環境で実行することに加え、弊社が提供する&lt;a href=&quot;https://flatt.tech/takumi/features/guard&quot;&gt;Takumi Guard&lt;/a&gt;を利用すれば、ブロックリストに登録された危険な依存関係はインストール前にブロックされます。先述のaxiosやlitellmなど、侵害された既知のバージョンもブロックリストに含まれており、正規パッケージの侵害・Slopsquattingのいずれも未然に防ぐことができます。&lt;/p&gt;&lt;p&gt;&lt;cite&gt;&lt;a href=&quot;https://flatt.tech/takumi/features/guard&quot;&gt;flatt.tech&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;img src=&quot;https://cdn-ak.f.st-hatena.com/images/fotolife/f/flattsecurity/20260427/20260427212140.png&quot; alt=&quot;Takumi Guardが悪性パッケージをブロックする流れ&quot; title=&quot;&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;h2&gt;よくない持ち出し — 開発時における情報漏洩のリスク&lt;/h2&gt;&lt;h3&gt;リスク6. 意図しない外部ドメインへの通信&lt;/h3&gt;&lt;p&gt;AIエージェントにAPIのサンプルコードを生成させると、リクエスト先のURLに&lt;code&gt;your-domain[.]com&lt;/code&gt;等のプレースホルダのようなドメインが使用されることがあります。例えば、Claude Codeに「fetchリクエストのサンプルコードを書いて」と指示したとき、以下のようなコマンドが出力されることがあります(&lt;code&gt;.&lt;/code&gt;を&lt;code&gt;[.]&lt;/code&gt;に変更しています)。&lt;/p&gt;&lt;pre&gt;curl -X GET &amp;quot;https://your-domain[.]com/path/to?id=123&amp;quot; \
  -H &amp;quot;Authorization: Bearer YOUR_ACCESS_TOKEN&amp;quot;&lt;/pre&gt;&lt;p&gt;一見すると無害なプレースホルダに見えますが、&lt;code&gt;your-domain[.]com&lt;/code&gt;は実在する第三者のドメインです(※このサイトは開かないでください*4 )。&lt;/p&gt;&lt;p&gt;このコードをそのまま実行したり、ターミナルに表示されたリンクをクリックしてしまうと、&lt;strong&gt;リクエストボディやヘッダに含まれる認証情報や機密情報（リリース前の機能のパスなども含む）が外部に送信される&lt;/strong&gt;可能性があります。&lt;code&gt;evil[.]com&lt;/code&gt;や&lt;code&gt;attacker[.]com&lt;/code&gt;なども同様に実在する第三者のドメインです。&lt;/p&gt;&lt;p&gt;&lt;code&gt;/etc/hosts&lt;/code&gt;にこれらのドメインをブロックリストとして追加すれば、意図しない外部通信を防ぐことができます(&lt;code&gt;[.]&lt;/code&gt;を&lt;code&gt;.&lt;/code&gt;に変更してから保存してください)。&lt;/p&gt;&lt;pre&gt;# blocklist
127.0.0.1 your-domain[.]com
127.0.0.1 www.your-domain[.]com
127.0.0.1 evil[.]com
127.0.0.1 attacker[.]com&lt;/pre&gt;&lt;p&gt;チームや組織単位では、DNSレベルでのブロックも検討してください。また、AIエージェントにサンプルコードを生成させる際は、プレースホルダとして&lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc2606.html&quot;&gt;RFC 2606&lt;/a&gt;で予約されている&lt;strong&gt;名前解決されないTLD&lt;/strong&gt;(&lt;code&gt;.example&lt;/code&gt;/&lt;code&gt;.test&lt;/code&gt;/&lt;code&gt;.invalid&lt;/code&gt;など)を使うよう&lt;code&gt;CLAUDE.md&lt;/code&gt;等で指示しておくのも有効です*5。&lt;/p&gt;&lt;h3&gt;リスク7. 機密情報の漏洩&lt;/h3&gt;&lt;p&gt;AIエージェントは動作確認やデバッグの過程で、&lt;code&gt;.env&lt;/code&gt;ファイルや実行時のエラーメッセージに含まれる認証情報を読み取ることがあります。Claude Codeでは&lt;code&gt;settings.json&lt;/code&gt;の&lt;code&gt;deny&lt;/code&gt;リストで&lt;code&gt;.env&lt;/code&gt;等の読み取りを禁止できますが、これは&lt;a href=&quot;https://code.claude.com/docs/en/tools-reference#:~:text=Yes-,Read,-Reads%20the%20contents&quot;&gt;ファイル読み取りツール&lt;/a&gt;の制限にすぎません。つまり、スクリプト内でのファイル読み取りや、実行時のエラーメッセージに認証情報が含まれるケースまでは防ぐことができません。読み取られた認証情報は、エージェントの行動を通じて外部に送信されるリスクもあります。例えば、以下のケースでは、エラーメッセージに含まれるAPIキーがそのままWeb検索に送信されてしまいます。&lt;/p&gt;&lt;pre&gt;$ node server.js
Error: Authentication failed - Invalid API key (sk-proj-4f8a...7e2d)&lt;/pre&gt;&lt;p&gt;↓ エージェントがエラーを解決しようとWeb検索を実行&lt;/p&gt;&lt;pre&gt;WebSearch(&amp;quot;Authentication failed Invalid API key sk-proj-4f8a7e2d&amp;quot;)&lt;/pre&gt;&lt;p&gt;対策・軽減策は「LLMのコンテキストに機密情報を入れない」方向と「漏洩時の被害を限定する」方向の2軸で考えます。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;1. LLMのコンテキストに機密情報を入れない&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;方針としては、エージェントの実行環境に平文の機密情報を一切置かないことです。万が一侵害されても、攻撃者が手にするのは参照URIやダミートークンだけにしておきます。&lt;/p&gt;&lt;p&gt;例えば、1Password等のシークレットマネージャーでは、&lt;code&gt;.env&lt;/code&gt;には&lt;code&gt;op://prod/db/password&lt;/code&gt;のような参照URIを記述しておき、実行時に正規の認証情報へ置換する方法があります。また、&lt;code&gt;.env&lt;/code&gt;ファイル自体を暗号化するような手法もあります。&lt;/p&gt;&lt;p&gt;API通信についても同様に、エージェントにはダミーのトークンだけを渡し、ローカルプロキシが本物のキーに差し替えてAPIへ転送する構成が有効です。&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;img src=&quot;https://cdn-ak.f.st-hatena.com/images/fotolife/f/flattsecurity/20260427/20260427213029.png&quot; alt=&quot;ローカルプロキシが本物のキーに差し替えてAPIへ転送する構成&quot; title=&quot;&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;2. 漏洩時の被害を限定する&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;AIエージェントには必要最小限の権限のみを持つアカウントのトークンを付与し、有効期限も短く設定しておけば、万が一漏洩しても被害を抑えられます。例えば、AWSであればS3の特定バケットへの読み取りのみを許可した一時的な認証情報を発行してエージェントに渡すといった運用などが考えられます。&lt;/p&gt;&lt;h2&gt;よくない出荷 — 開発段階で防げるリリース後のリスク&lt;/h2&gt;&lt;p&gt;最後に、リリース後のセキュリティリスクについても触れておきます。AIエージェントが生成するコードにはセキュリティ上の問題が含まれている可能性があり、レビューを経ずにリリースされると本番環境にバグ・脆弱性を持ち込んでしまいます。&lt;/p&gt;&lt;p&gt;こうしたリスクへの対策として、AIエージェントが安全・確実に動くよう、足回り（ツール、フック、テスト、Lintなど）を整える取り組みは「ハーネスエンジニアリング」と呼ばれています。例えばFormatterやLinter、テストフレームワークを導入し、フックやCIで自動実行する構成にしておくのがおすすめです。
セキュリティ面では、AIが生成したデッドコードや不要な依存関係が脆弱性を生み出す要因になるため、定期的なリファクタリングも欠かせません。加えて、手動またはCIでの脆弱性診断（静的・動的解析）のほか、Claude Codeの&lt;code&gt;/security-review&lt;/code&gt;コマンドや弊社が提供する&lt;a href=&quot;https://flatt.tech/takumi&quot;&gt;Takumi&lt;/a&gt;によるセキュリティレビューも効果的です。&lt;/p&gt;&lt;p&gt;&lt;cite&gt;&lt;a href=&quot;https://flatt.tech/takumi&quot;&gt;flatt.tech&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;&lt;h2&gt;終わりに&lt;/h2&gt;&lt;p&gt;この記事では、バイブコーディングにおけるセキュリティリスクを、任意コード実行と情報漏洩を中心に7つ紹介し、それぞれの攻撃シナリオと対策をまとめました。&lt;/p&gt;&lt;p&gt;危険なことを禁止するのではなく、そもそも機密情報を置かない、漏洩しても被害が限定される設計にする、といった設計思想への転換が、AIエージェント時代のセキュリティ対策において重要になっていくと考えられます。&lt;/p&gt;&lt;p&gt;冒頭でもお伝えした通り、ここまで読んで &lt;strong&gt;「ちょっと怖いな...」と感じた方は、まずサンドボックス環境の導入から始めてみてください&lt;/strong&gt;。&lt;a href=&quot;https://code.claude.com/docs/en/sandboxing&quot;&gt;Claude Sandbox&lt;/a&gt;や&lt;a href=&quot;https://e2b.dev/&quot;&gt;e2b.dev&lt;/a&gt;、&lt;a href=&quot;https://www.docker.com/ja-jp/products/docker-sandboxes/&quot;&gt;Docker Sandboxes&lt;/a&gt;などを活用すれば、任意コード実行（リスク1〜5）をまとめて軽減でき、情報漏洩（リスク6〜7）の被害範囲も一定限定できます。万能ではありませんが、&lt;strong&gt;何か一つだけ実施するならこれ&lt;/strong&gt;をおすすめします。&lt;/p&gt;&lt;p&gt;この記事が、AIエージェントを使った安全な開発のヒントになればうれしいです。ここまでお読みいただきありがとうございました。&lt;/p&gt;&lt;div&gt;
&lt;p&gt;*1&lt;span&gt;:&lt;/span&gt;&lt;span&gt;最近はソフトウェアサプライチェーン攻撃の影響もあり、リリースノートはすぐに読むものの、アプデ&amp;amp;検証はリリース後3日くらい空けてから実施しています...&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;*2&lt;span&gt;:&lt;/span&gt;&lt;span&gt;Claude Codeのsubagentのように、メインエージェントが自動的にサブエージェントを呼び出す仕組みもあり、開発者が明示的に起動していなくてもエージェントが並列で動いているケースがあります&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;*3&lt;span&gt;:&lt;/span&gt;&lt;span&gt;全てのマーケットにその機能がないわけではなく、例えば、The Agent Skills Directoryには「Security Audits」機能があります。&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;*4&lt;span&gt;:&lt;/span&gt;&lt;span&gt;このサイトは2026年3月上旬時点ではフィッシングサイトのように見える状態でした。しかし、3月下旬には404 page not foundが表示されていました。&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;*5&lt;span&gt;:&lt;/span&gt;&lt;span&gt;同じく予約済みの example.com および( example.net / example.org )はIANAが実際にDNSレコードを運用しており 93.184.216.34 等に解決されるため、HTTPリクエストを伴うサンプルでは実際の通信が飛んでしまいます。そのため、名前解決されないドメインを使用してください&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;</content:encoded>
</item>
<item>
<title>Nouvelles icônes Google : le design de Gmail et Drive change en 2026 - Numerama</title>
<link>https://www.numerama.com/tech/2240855-gmail-drive-meet-preparez-vous-a-du-changement-pour-les-services-google.html</link>
<enclosure type="image/jpeg" length="0" url="https://c0.lestechnophiles.com/www.numerama.com/wp-content/uploads/2026/04/icones.jpg?resize=1600,900&amp;key=6e387b1d&amp;watermark"></enclosure>
<guid isPermaLink="false">gTAgZGFHPBb7rtibVSubDomTuD3B_-91hY-umA==</guid>
<pubDate>Mon, 27 Apr 2026 13:57:17 +0000</pubDate>
<description>Google préparerait une refonte majeure des icônes de ses applications avec des dégradés inspirés de Gemini et de sa nouvelle identité visuelle. Tous les services pourraient évoluer dans les prochaines semaines. Selon 9to5Google, Google prépare une refonte complète des icônes de ses applications. Après avoir modifié</description>
<content:encoded>&lt;div&gt;
&lt;div&gt;Google préparerait une refonte majeure des icônes de ses applications avec des dégradés inspirés de Gemini et de sa nouvelle identité visuelle. Tous les services pourraient évoluer dans les prochaines semaines. &lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Selon &lt;a href=&quot;https://9to5google.com/2026/04/26/gmail-google-gradient-redesign/&quot;&gt;9to5Google&lt;/a&gt;, Google prépare une refonte complète des icônes de ses applications. Après avoir modifié &lt;a href=&quot;https://www.numerama.com/tech/1968843-10-ans-apres-le-logo-de-google-change-de-look.html&quot;&gt;son propre logo&lt;/a&gt; et ceux de Gemini, Google Home, Google Photos ou &lt;a href=&quot;https://www.numerama.com/tech/2193363-un-tout-petit-changement-dicone-sur-google-maps-confirme-la-strategie-de-google.html&quot;&gt;Google Maps avec un dégradé de couleurs&lt;/a&gt;, le géant &lt;a href=&quot;https://www.numerama.com/tech/1162624-quest-ce-que-le-web.html&quot;&gt;du web&lt;/a&gt; s’apprêterait à étendre sa nouvelle charte graphique à ses services les plus populaires. Gmail, Meet, Calendar ou Drive pourrait complètement changer de style dans les prochains jours… au point de bouleverser plusieurs habitudes. &lt;/p&gt;&lt;h2&gt;Gmail, Drive, Meet, Calendar : les nouvelles icônes Google bientôt dévoilées ?&lt;/h2&gt;&lt;p&gt;Depuis plusieurs années, Google propose des icônes en plusieurs couleurs. Toutes les applications se ressemblent avec du rouge, du jaune, du bleu et du vert. Sa nouvelle charte viserait à rendre les applications plus identifiables en jouant sur les dégradés sans faire la même chose pour chaque service. &lt;/p&gt;&lt;figure&gt;&lt;a href=&quot;https://www.numerama.com/wp-content/uploads/2026/04/image-6-2.jpg&quot;&gt;&lt;picture&gt;&lt;img src=&quot;data:image/svg+xml;utf8,%3Csvg%20xmlns=%27http://www.w3.org/2000/svg%27%20width=%271024%27%20height=%27648%27/%3E&quot; alt=&quot;Les futures icônes des applications Google.&quot; title=&quot;Les futures icônes des applications Google.&quot;/&gt;&lt;/picture&gt;&lt;/a&gt;&lt;figcaption&gt;Les futures icônes des applications Google. // Source : 9to5google&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;Parmi les changements notables, on remarque Google Meet basculerait exclusivement sur du jaune quand Gmail serait majoritairement dominé par le rouge, comme à ses débuts. Drive se contenterait lui de trois couleurs et laisserait le rouge à d’autres services. Les icônes reprennent le style du Material Design de Google, que l’entreprise pousse notamment dans Android. &lt;/p&gt;&lt;div&gt;
            
            
            &lt;div&gt;
                
            &lt;/div&gt;
            
            
            
        &lt;/div&gt;&lt;figure&gt;&lt;div&gt;
&lt;div&gt;    &lt;div&gt;        &lt;div&gt;            &lt;blockquote&gt;&lt;p&gt;Google unveils gradient icons soon to be released&lt;br/&gt;&lt;br/&gt;Via 9to5Google &lt;a href=&quot;https://t.co/6RJnYw2MSv&quot;&gt;pic.twitter.com/6RJnYw2MSv&lt;/a&gt;&lt;/p&gt;— Interesting AF (@interesting_aIl) &lt;a href=&quot;https://twitter.com/interesting_aIl/status/2048510169810665605?ref_src=twsrc%5Etfw&quot;&gt;April 26, 2026&lt;/a&gt;&lt;/blockquote&gt;        &lt;/div&gt;        &lt;div&gt;    &lt;div&gt;        &lt;p&gt;            Ce contenu est bloqué car vous n’avez pas accepté les cookies et autres traceurs. Ce contenu est fourni par Twitter.&lt;br/&gt;            Pour pouvoir le visualiser, vous devez accepter l’usage étant opéré par Twitter avec vos données qui pourront être utilisées pour les finalités suivantes : vous permettre de visualiser et de partager des contenus avec des médias sociaux, favoriser le développement et l’amélioration des produits d’Humanoid et de ses partenaires, vous afficher des publicités personnalisées par rapport à votre profil et activité, vous définir un profil publicitaire personnalisé, mesurer la performance des publicités et du contenu de ce site et mesurer l’audience de ce site (en savoir plus)        &lt;/p&gt;        &lt;p&gt;            En cliquant sur « J’accepte tout », vous consentez aux finalités susmentionnées pour l’ensemble des cookies et autres traceurs déposés par Humanoid et .        &lt;/p&gt;        &lt;p&gt;            Vous gardez la possibilité de retirer votre consentement à tout moment. Pour plus d’informations, nous vous invitons à prendre connaissance de notre &lt;a href=&quot;https://www.numerama.com/politique-cookies&quot;&gt;Politique cookies&lt;/a&gt;.        &lt;/p&gt;    &lt;/div&gt;    &lt;div&gt;            &lt;/div&gt;    &lt;p&gt;            &lt;/p&gt;&lt;/div&gt;    &lt;/div&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;&lt;p&gt;Google n’a pas encore officialisé cette refonte, mais on peut imaginer qu’elle sera officielle d’ici la &lt;a href=&quot;https://www.numerama.com/tech/2182143-on-connait-la-date-de-la-google-i-o-2026-un-indice-pour-gemini-3-5.html&quot;&gt;Google I/O 2026 prévue le 19 mai&lt;/a&gt;. Attendez-vous à des appels désespérés de vos proches qui ne trouveront plus leurs applications préférées à cette date. &lt;/p&gt;&lt;div&gt;
    &lt;div&gt;
        &lt;p&gt;Vous avez lu &lt;strong&gt;&lt;span&gt;0&lt;/span&gt; articles&lt;/strong&gt; sur Numerama ce mois-ci&lt;/p&gt;
    &lt;/div&gt;
    &lt;div&gt;
        &lt;header&gt;Il y a une bonne raison de ne pas s&amp;#39;abonner à &lt;span&gt;
    &lt;/span&gt;
        &lt;/header&gt;
        &lt;p&gt;
            &lt;strong&gt;Tout le monde n&amp;#39;a pas les moyens de payer&lt;/strong&gt; pour l&amp;#39;information. &lt;br/&gt;
            C&amp;#39;est pourquoi nous maintenons notre journalisme ouvert à tous.
        &lt;/p&gt;
        &lt;p&gt;
            &lt;strong&gt;Mais si vous le pouvez,&lt;/strong&gt;&lt;br/&gt;
            voici trois bonnes raisons de soutenir notre travail :
        &lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;
                &lt;span&gt;1&lt;/span&gt;
                &lt;span&gt;Numerama+ contribue à offrir &lt;strong&gt;une expérience gratuite à tous les lecteurs de Numerama&lt;/strong&gt;.&lt;/span&gt;
            &lt;/li&gt;
            &lt;li&gt;
                &lt;span&gt;2&lt;/span&gt;
                &lt;span&gt;Vous profiterez d&amp;#39;une &lt;strong&gt;lecture sans publicité&lt;/strong&gt;, de nombreuses &lt;strong&gt;fonctions avancées de lecture et des contenus exclusifs&lt;/strong&gt;.&lt;/span&gt;
            &lt;/li&gt;
            &lt;li&gt;
                &lt;span&gt;3&lt;/span&gt;
                &lt;span&gt;&lt;strong&gt;Aider Numerama dans sa mission&lt;/strong&gt; : comprendre le présent pour anticiper l&amp;#39;avenir.&lt;/span&gt;
            &lt;/li&gt;
        &lt;/ul&gt;
        &lt;p&gt;&lt;strong&gt;Si vous croyez en un web gratuit&lt;/strong&gt; et à une information de qualité accessible au plus grand nombre, rejoignez Numerama+.&lt;/p&gt;
    &lt;/div&gt;

    &lt;a href=&quot;https://www.numerama.com/numeramaplus/&quot;&gt;
        S&amp;#39;abonner à Numerama+ 
    &lt;/a&gt;
&lt;/div&gt;&lt;hr/&gt;&lt;div&gt;&lt;p&gt;Tous nos articles sont aussi sur notre profil Google : &lt;a href=&quot;https://go.numerama.com/gpnumerama&quot;&gt;suivez-nous pour ne rien manquer&lt;/a&gt; !&lt;/p&gt;
&lt;/div&gt;&lt;div&gt;
            &lt;p&gt;
            Tout savoir sur             &lt;a href=&quot;https://www.numerama.com/telecharger/gmail.html&quot;&gt;
                Gmail            &lt;/a&gt;
        &lt;/p&gt;

        &lt;ul&gt;
                            &lt;li&gt;
                    &lt;a href=&quot;https://www.numerama.com/tech/1949631-envie-de-lacher-gmail-voila-deux-excellents-webmails-made-in-europe.html&quot;&gt;
                        Envie de lâcher Gmail ? Voilà deux excellents webmails made in Europe                    &lt;/a&gt;
                &lt;/li&gt;
                            &lt;li&gt;
                    &lt;a href=&quot;https://www.numerama.com/tech/2160289-gemini-peut-desormais-fouiller-dans-vos-mails-et-vos-photos-pour-mieux-vous-repondre.html&quot;&gt;
                        Gemini peut désormais fouiller dans vos mails et vos photos pour mieux vous répondre                    &lt;/a&gt;
                &lt;/li&gt;
                            &lt;li&gt;
                    &lt;a href=&quot;https://www.numerama.com/tech/2155975-gmail-bascule-dans-lere-gemini-votre-boite-de-reception-va-changer-radicalement.html&quot;&gt;
                        Gmail bascule dans l’ère Gemini : votre boîte de réception va changer radicalement                    &lt;/a&gt;
                &lt;/li&gt;
                            &lt;li&gt;
                    &lt;a href=&quot;https://www.numerama.com/tech/2153645-certains-mails-ne-vous-parviennent-plus-cest-normal-gmail-a-coupe-cette-fonction-cle-en-janvier.html&quot;&gt;
                        Certains mails ne vous parviennent plus ? C’est normal, Gmail a coupé cette fonction clé en janvier                    &lt;/a&gt;
                &lt;/li&gt;
                            &lt;li&gt;
                    &lt;a href=&quot;https://www.numerama.com/tech/2149233-vous-avez-honte-de-votre-adresse-gmail-bonne-nouvelle-google-permet-enfin-de-la-changer.html&quot;&gt;
                        Vous avez honte de votre adresse Gmail ? Bonne nouvelle, Google permet enfin de la changer                    &lt;/a&gt;
                &lt;/li&gt;
                            &lt;li&gt;
                    &lt;a href=&quot;https://www.numerama.com/politique/2063639-existe-t-il-un-complot-de-gmail-contre-donald-trump.html&quot;&gt;
                        Existe-t-il un complot de Gmail contre Donald Trump ?                    &lt;/a&gt;
                &lt;/li&gt;
                    &lt;/ul&gt;
    &lt;/div&gt;</content:encoded>
</item>
<item>
<title>Smolwebifying my site | AksDev</title>
<link>https://akselmo.dev/posts/smolwebifying-my-site/</link>
<enclosure type="image/jpeg" length="0" url="https://akselmo.dev/assets/images/oc/fefairy_avatar_small.png"></enclosure>
<guid isPermaLink="false">ZgH9ootNx4S1O9XNa41qwOwMsJURrHL-zEuQUw==</guid>
<pubDate>Mon, 27 Apr 2026 01:39:29 +0000</pubDate>
<description>I found this thing called smolweb and it spoke to me.</description>
<content:encoded>&lt;body&gt;
        &lt;header&gt;
  &lt;span&gt;&lt;a href=&quot;https://akselmo.dev&quot;&gt;AksDev&lt;/a&gt;&lt;/span&gt;
  &lt;div&gt;
    &lt;a href=&quot;https://akselmo.dev&quot;&gt;&lt;img src=&quot;https://akselmo.dev/assets/images/oc/fefairy_avatar_small.png&quot; alt=&quot;My lizard avatar, art by Fefairy&quot; title=&quot;&quot;/&gt;&lt;/a&gt;
    &lt;p&gt;
      &lt;i&gt;I&amp;#39;m &lt;span&gt;Aks&lt;/span&gt;
      &lt;a href=&quot;https://akselmo.dev&quot;&gt;Akseli Lahtinen&lt;/a&gt;
      from &lt;span&gt;Finland&lt;/span&gt;!&lt;br/&gt;&lt;br/&gt;
      KDE dev by day and a hobbyist gamedev/FOSS-dev by night.&lt;br/&gt;
      Loves video games and metal/EBM music.&lt;br/&gt;&lt;/i&gt;
    &lt;/p&gt;
  &lt;/div&gt;
  &lt;br/&gt;
  
&lt;/header&gt;

        
&lt;main&gt;
    &lt;article&gt;
        &lt;h1&gt;Smolwebifying my site&lt;/h1&gt;
        &lt;div&gt;
        &lt;div&gt;
            &lt;p&gt;&lt;i&gt;Posted on &lt;a href=&quot;https://akselmo.dev/posts/smolwebifying-my-site/&quot;&gt; &lt;time&gt;2026-04-26&lt;/time&gt;&lt;/a&gt; by &lt;a href=&quot;https://akselmo.dev&quot;&gt;Akseli Lahtinen&lt;/a&gt;&lt;/i&gt;&lt;/p&gt;
            &lt;span&gt;I found this thing called smolweb and it spoke to me.&lt;/span&gt;
        &lt;/div&gt;
            &lt;p&gt;I found this thing called &lt;a href=&quot;https://smolweb.org/index.html&quot;&gt;smolweb&lt;/a&gt; and it spoke to me.&lt;/p&gt;

&lt;p&gt;Like yeah, websites are quite heavy these days. Older hardware (from like 2010s) has really difficult time loading it, and the big chunks of Javascript do not help either.&lt;/p&gt;
&lt;p&gt;And not to forget web browsers such as &lt;a href=&quot;https://www.netsurf-browser.org/&quot;&gt;Netsurf&lt;/a&gt; that do not have the resources to recreate the whole HTML/CSS/JS standard.&lt;/p&gt;
&lt;p&gt;So, as you can see, this site is now much more plain and simple. Very easy to scroll from top to bottom and read the stuff. I think I will have to modify some of the paddings/margins around some elements, but other than that, I&amp;#39;m quite happy with it. I may also need to rework some colors. I tried to follow closely my &lt;a href=&quot;https://codeberg.org/akselmo/Revontuli&quot;&gt;Revontuli&lt;/a&gt; colorscheme.&lt;/p&gt;
&lt;p&gt;Smolweb.org has a cool little validator that gives me valid score, and my CSS grade is C-. I will have to see if I can make that grade bit better, at least B. :) I also ran my site through the W3C &lt;a href=&quot;https://validator.w3.org/&quot;&gt;HTML&lt;/a&gt; and &lt;a href=&quot;https://jigsaw.w3.org/css-validator/&quot;&gt;CSS&lt;/a&gt; validators, like back in the olden days! And they told me my site is valid. :D&lt;/p&gt;
&lt;p&gt;I spent quite a long time on this and I&amp;#39;m clearly not a designer. Probably ~12h total. I managed to remove at least ~1000 lines of extra stuff, which is awesome. One big thing I did was starting to use SCSS with Zola, so it compiles the CSS instead of relying on CSS variables. This makes the site work even on browsers that do not support CSS variables!&lt;/p&gt;
&lt;p&gt;Let me know what you think of the look! And I&amp;#39;m curious what scores others get from the smolweb validator!&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://brid.gy/publish/mastodon&quot;&gt;&lt;/a&gt;&lt;/p&gt;

        &lt;/div&gt;
    &lt;/article&gt;
    
    &lt;br/&gt;
    &lt;section&gt;
    &lt;h1&gt;Tags&lt;/h1&gt;
        &lt;ul&gt;
            
                &lt;li&gt;&lt;a href=&quot;https://akselmo.dev/tags/smolweb/&quot;&gt;smolweb&lt;/a&gt;&lt;/li&gt;
            
                &lt;li&gt;&lt;a href=&quot;https://akselmo.dev/tags/indieweb/&quot;&gt;indieweb&lt;/a&gt;&lt;/li&gt;
            
                &lt;li&gt;&lt;a href=&quot;https://akselmo.dev/tags/webdev/&quot;&gt;webdev&lt;/a&gt;&lt;/li&gt;
            
                &lt;li&gt;&lt;a href=&quot;https://akselmo.dev/tags/html/&quot;&gt;html&lt;/a&gt;&lt;/li&gt;
            
                &lt;li&gt;&lt;a href=&quot;https://akselmo.dev/tags/web/&quot;&gt;web&lt;/a&gt;&lt;/li&gt;
            
                &lt;li&gt;&lt;a href=&quot;https://akselmo.dev/tags/css/&quot;&gt;css&lt;/a&gt;&lt;/li&gt;
            
                &lt;li&gt;&lt;a href=&quot;https://akselmo.dev/tags/html/&quot;&gt;html&lt;/a&gt;&lt;/li&gt;
            
        &lt;/ul&gt;
    &lt;/section&gt;
    
    
&lt;section&gt;
	&lt;h1&gt;Webmentions&lt;/h1&gt;
	&lt;p&gt;Have you written a response to this post? Send me the URL!&lt;/p&gt;
	
		
		
		
	
	
	
	
	
	
&lt;/section&gt;
    &lt;br/&gt;
&lt;/main&gt;

        &lt;section&gt;
    &lt;h1&gt;FediFeed&lt;/h1&gt;
        &lt;a href=&quot;https://scalie.zone/@aks&quot;&gt;&lt;/a&gt;
    
    
&lt;/section&gt;

        &lt;footer&gt;
    &lt;hr/&gt;
    &lt;div&gt;
        &lt;ul&gt;
            &lt;li&gt;&lt;div&gt;
                &lt;a href=&quot;https://ring.acab.dev/prev/d8d56e5a03&quot;&gt;←&lt;/a&gt;
                &lt;a href=&quot;https://ring.acab.dev/&quot;&gt;HackerWebring&lt;/a&gt;
                &lt;a href=&quot;https://ring.acab.dev/rand/d8d56e5a03&quot;&gt;⤨&lt;/a&gt;
                &lt;a href=&quot;https://ring.acab.dev/next/d8d56e5a03&quot;&gt;→&lt;/a&gt;
            &lt;/div&gt;&lt;/li&gt;
            &lt;li&gt;&lt;div&gt;
                &lt;a href=&quot;http://geekring.net/site/NUMBER/previous&quot;&gt;←&lt;/a&gt;
                &lt;a href=&quot;http://geekring.net/&quot;&gt;GeekRing&lt;/a&gt;
                &lt;a href=&quot;http://geekring.net/site/NUMBER/random&quot;&gt;⤨&lt;/a&gt;
                &lt;a href=&quot;http://geekring.net/site/NUMBER/next&quot;&gt;→&lt;/a&gt;
            &lt;/div&gt;&lt;/li&gt;
            &lt;li&gt;&lt;div&gt;
                &lt;a href=&quot;https://xn--sr8hvo.ws/previous&quot;&gt;←&lt;/a&gt;
                &lt;a href=&quot;https://xn--sr8hvo.ws&quot;&gt;IndieWebRing&lt;/a&gt;
                &lt;a href=&quot;https://xn--sr8hvo.ws/random&quot;&gt;⤨&lt;/a&gt;
                &lt;a href=&quot;https://xn--sr8hvo.ws/next&quot;&gt;→&lt;/a&gt;
            &lt;/div&gt;&lt;/li&gt;
            &lt;li&gt;&lt;div&gt;
                &lt;a href=&quot;https://7vtia.nekoweb.org/&quot;&gt;←&lt;/a&gt;
                &lt;a href=&quot;https://teethinvitro.neocities.org/webring/linuxring&quot;&gt;*nixRing&lt;/a&gt;
                &lt;a href=&quot;https://joelb.xyz/&quot;&gt;→&lt;/a&gt;
            &lt;/div&gt;&lt;/li&gt;
            &lt;li&gt;&lt;div&gt;
                &lt;a href=&quot;https://baccyflap.com/noai/?prv&amp;amp;s=aks&quot;&gt;←&lt;/a&gt;
                &lt;a href=&quot;https://baccyflap.com/noai&quot;&gt;no ai webring&lt;/a&gt;
                &lt;a href=&quot;https://baccyflap.com/noai/?rnd&quot;&gt;⤨&lt;/a&gt;
                &lt;a href=&quot;https://baccyflap.com/noai/?nxt&amp;amp;s=aks&quot;&gt;→&lt;/a&gt;
            &lt;/div&gt;&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/div&gt;
    &lt;br/&gt;
    &lt;div&gt;
        &lt;ul&gt;
            &lt;li&gt;&lt;a href=&quot;https://kate-editor.org&quot;&gt;&lt;img src=&quot;https://www.akselmo.dev/assets/images/buttons/madewithkate.png&quot; alt=&quot;Made with Kate text editor. Thanks, @nano@fedi.nano.lgbt !&quot; title=&quot;&quot;/&gt;&lt;/a&gt;&lt;/li&gt;
            &lt;li&gt;&lt;a href=&quot;https://dee-liteyears.neocities.org/&quot;&gt;&lt;img src=&quot;https://akselmo.dev/assets/images/buttons/deeliteyears.png&quot; alt=&quot;dee-liteyears&quot; title=&quot;&quot;/&gt;&lt;/a&gt;&lt;/li&gt;
            &lt;li&gt;&lt;a href=&quot;https://samuele963.github.io/&quot;&gt;&lt;img src=&quot;https://akselmo.dev/assets/images/buttons/sam963.png&quot; alt=&quot;samuele963&quot; title=&quot;&quot;/&gt;&lt;/a&gt;&lt;/li&gt;
            &lt;li&gt;&lt;a href=&quot;https://nokia64.neocities.org/&quot;&gt;&lt;img src=&quot;https://akselmo.dev/assets/images/buttons/nokia64.png&quot; alt=&quot;nokia64&quot; title=&quot;&quot;/&gt;&lt;/a&gt;&lt;/li&gt;
            &lt;li&gt;&lt;a href=&quot;https://sirlan-tomma.neocities.org/&quot;&gt;&lt;img src=&quot;https://akselmo.dev/assets/images/buttons/sirlan.gif&quot; alt=&quot;sirlan&quot; title=&quot;&quot;/&gt;&lt;/a&gt;&lt;/li&gt;
            &lt;li&gt;&lt;a href=&quot;https://xaselgio.net/&quot;&gt;&lt;img src=&quot;https://akselmo.dev/assets/images/buttons/xaselgio.gif&quot; alt=&quot;xaselgio&quot; title=&quot;&quot;/&gt;&lt;/a&gt;&lt;/li&gt;
            &lt;li&gt;&lt;a href=&quot;https://redstrate.com/&quot;&gt;&lt;img src=&quot;https://akselmo.dev/assets/images/buttons/redstrate.png&quot; alt=&quot;redstrate&quot; title=&quot;&quot;/&gt;&lt;/a&gt;&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/div&gt;
    &lt;br/&gt;&lt;br/&gt;
    &lt;span&gt;&lt;i&gt;2026 © Akseli Lahtinen&lt;/i&gt;&lt;/span&gt;
&lt;/footer&gt;

    &lt;p&gt;&lt;a href=&quot;https://akselmo.dev/funny/&quot;&gt;Fun facts!&lt;/a&gt;&lt;/p&gt;
    &lt;p&gt;&lt;a href=&quot;https://rnsaffn.com/poison2/&quot;&gt;Source code!&lt;/a&gt;&lt;/p&gt;
    

&lt;/body&gt;</content:encoded>
</item>
<item>
<title>Lua can be a really cool HTML templating engine · riki&#39;s house</title>
<link>https://riki.house/lua-html</link>
<guid isPermaLink="false">WaYvlnrMfyPJV9Hzt-R06jJY5SJbqCxaFd7ArQ==</guid>
<pubDate>Mon, 27 Apr 2026 01:39:29 +0000</pubDate>
<description>Lua can be a really cool HTML templating engine</description>
<content:encoded>&lt;header&gt;
                &lt;h1&gt;&lt;a href=&quot;https://riki.house/lua-html&quot;&gt;Lua can be a really cool HTML templating engine&lt;/a&gt;&lt;/h1&gt;
                &lt;time&gt;2026-04-26&lt;/time&gt;
                &lt;div&gt;
                &lt;/div&gt;
            &lt;/header&gt;&lt;p&gt;Have you ever used Lua?&lt;/p&gt;&lt;p&gt;It’s a pretty cool language.
It is also one of my favourite programming languages, for which I made a case &lt;a href=&quot;https://riki.house/programming/lua.tree&quot;&gt;in a past blog post&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;One of my favourite aspects of Lua’s design that I like to preach about is how it’s really tight and small, while also being genuinely really sweet to write.
Today, I’d like to focus on its Lisp-like aspect: domain specific languages (DSLs)—specifically, we will use it to build a templating language for HTML.&lt;/p&gt;&lt;p&gt;But first, let me set some background.&lt;/p&gt;&lt;h2&gt;What’s in a template engine?&lt;/h2&gt;&lt;p&gt;As a fellow blogger on the Internet maintaining their own blogging software, I’ve used my fair share of templating engines for generating HTML.&lt;/p&gt;&lt;p&gt;The premise of a templating engine is really simple: you have a bunch of literal text, and into that literal text is sandwiched a bunch of instructions on how to expand that literal text, given some parameters.
Then, a web server can &lt;em&gt;render&lt;/em&gt; the template, providing it the parameters to render with.&lt;/p&gt;&lt;p&gt;In other words, a template is a function &lt;code&gt;params -&amp;gt; string&lt;/code&gt;.
Parameters go in, string goes out.&lt;/p&gt;&lt;p&gt;On this website, I’m using &lt;a href=&quot;https://handlebarsjs.com/&quot;&gt;Handlebars&lt;/a&gt;: a fairly popular templating engine in the JavaScript world, although I’m using &lt;a href=&quot;https://lib.rs/crates/handlebars&quot;&gt;a Rust implementation of it&lt;/a&gt; myself.
The syntax looks like this (this is the actual template I’m using to generate the “Blog” sidebar on the &lt;a href=&quot;https://riki.house/index&quot;&gt;homepage&lt;/a&gt;):&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-handlebars th-syntax-highlighting&quot;&gt;&amp;lt;section class=&amp;quot;feed&amp;quot;&amp;gt;
	&amp;lt;h1&amp;gt;{{ page.feed.title }}&amp;lt;/h1&amp;gt;

	{{#each page.feed.entries}}
	&amp;lt;article&amp;gt;
		&amp;lt;h2&amp;gt;&amp;lt;a href=&amp;quot;{{ url }}&amp;quot;&amp;gt;{{{ title }}}&amp;lt;/a&amp;gt;&amp;lt;/h2&amp;gt;
		&amp;lt;div class=&amp;quot;info&amp;quot;&amp;gt;
			&amp;lt;time datetime=&amp;quot;{{ updated }}&amp;quot;&amp;gt;{{ iso_date updated }}&amp;lt;/time&amp;gt;
			&amp;lt;ul class=&amp;quot;categories&amp;quot;&amp;gt;
				{{#each tags as |tag|}}
				&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;{{ config.site }}/tag/{{ tag }}&amp;quot;&amp;gt;#{{ tag }}&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
				{{/each}}
			&amp;lt;/ul&amp;gt;
		&amp;lt;/div&amp;gt;
	&amp;lt;/article&amp;gt;
	{{/each}}
&amp;lt;/section&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Between the HTML tags, you will find instructions for the template engine, enveloped in curly braces &lt;code&gt;{{ }}&lt;/code&gt;.
This is a pretty common syntax among various template engines, although parts of it will vary from engine to engine—such as Handlebars’s non-escaping instructions, which triple the curlies &lt;code&gt;{{{ }}}&lt;/code&gt;, or its &lt;em&gt;block helpers&lt;/em&gt; &lt;code&gt;{{#each}}&lt;/code&gt; and &lt;code&gt;{{/each}}&lt;/code&gt;, which I’ll get to in a moment.&lt;/p&gt;&lt;p&gt;The most basic type of instruction is a &lt;em&gt;lookup&lt;/em&gt;, which inserts a literal value into your output text.
Let’s zoom in a bit:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-handlebars th-syntax-highlighting&quot;&gt;&amp;lt;h2&amp;gt;&amp;lt;a href=&amp;quot;{{ url }}&amp;quot;&amp;gt;{{{ title }}}&amp;lt;/a&amp;gt;&amp;lt;/h2&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Output&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-handlebars th-syntax-highlighting&quot;&gt;&amp;lt;h2&amp;gt;&amp;lt;a href=&amp;quot;https://riki.house/album-listener&amp;quot;&amp;gt;The album listener&amp;lt;/a&amp;gt;&amp;lt;/h2&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here, the template engine will look up the fields &lt;code&gt;url&lt;/code&gt; and &lt;code&gt;title&lt;/code&gt; in the parameters, and insert them into the output text.
Note how I had to use triple curly braces here, because Handlebars will escape the expanded text by default, such that any HTML tokens are presented literally on the page.
My server provides raw HTML in the &lt;code&gt;title&lt;/code&gt; parameter, so we want to turn that off.&lt;/p&gt;&lt;p&gt;Note that lookups don’t have to be limited to simple field names, as the parameters passed may be an arbitrarily deep data structure—as is seen with the header:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-handlebars th-syntax-highlighting&quot;&gt;&amp;lt;h1&amp;gt;{{ page.feed.title }}&amp;lt;/h1&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Output&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-handlebars th-syntax-highlighting&quot;&gt;&amp;lt;h1&amp;gt;Blog&amp;lt;/h1&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In addition to lookups, Handlebars also has &lt;em&gt;helpers&lt;/em&gt;, which is a slightly redundant name for functions that are exposed to the template in addition to the parameters.
(I mean, you could just name them functions!)&lt;/p&gt;&lt;p&gt;With helpers, the template may transform the input parameters in different ways, such as with &lt;code&gt;iso_date&lt;/code&gt; in the example above, which is defined in Rust and used like this:&lt;/p&gt;&lt;p&gt;Definition&lt;/p&gt;&lt;p&gt;Usage&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-handlebars th-syntax-highlighting&quot;&gt;&amp;lt;time datetime=&amp;quot;{{ updated }}&amp;quot;&amp;gt;{{ iso_date updated }}&amp;lt;/time&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Output&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-handlebars th-syntax-highlighting&quot;&gt;&amp;lt;time datetime=&amp;quot;2026-03-19T14:22:00Z&amp;quot;&amp;gt;2026-03-19&amp;lt;/time&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Finally, we have a special class of helpers, called &lt;em&gt;block helpers&lt;/em&gt;.
While regular helpers take single values as parameters, block helpers take in a &lt;em&gt;block&lt;/em&gt; in addition, allowing them to execute the block similarly to a closure in a real programming language—conditionally, in a loop, and so on.&lt;/p&gt;&lt;p&gt;Since block helpers need a start and an end delimiter, Handlebars opts for prefixing the helper name with &lt;code&gt;#&lt;/code&gt; and &lt;code&gt;/&lt;/code&gt; for the start and the end of the block respectively, like &lt;code&gt;{{#each}} {{/each}}&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Let’s look at the example of &lt;code&gt;{{#each}}&lt;/code&gt; from above, though shortened a bit for brevity:&lt;/p&gt;&lt;p&gt;Output&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-handlebars th-syntax-highlighting&quot;&gt;&amp;lt;h2&amp;gt;&amp;lt;a href=&amp;quot;/album-listener&amp;quot;&amp;gt;The album listener&amp;lt;/a&amp;gt;&amp;lt;/h2&amp;gt;
&amp;lt;h2&amp;gt;&amp;lt;a href=&amp;quot;/communal-chat-room&amp;quot;&amp;gt;The communal chat room&amp;lt;/a&amp;gt;&amp;lt;/h2&amp;gt;
&amp;lt;h2&amp;gt;&amp;lt;a href=&amp;quot;/text-editor-wishlist&amp;quot;&amp;gt;Scheming on a text editor&amp;lt;/a&amp;gt;&amp;lt;/h2&amp;gt;
&amp;lt;h2&amp;gt;&amp;lt;a href=&amp;quot;/along-rivers&amp;quot;&amp;gt;Out now: along rivers, forever adrift.&amp;lt;/a&amp;gt;&amp;lt;/h2&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here, &lt;code&gt;{{#each}}&lt;/code&gt; will expand to as many headings as there are elements in the list provided to it.&lt;/p&gt;&lt;p&gt;An interesting feature (and, in my honest opinion, footgun) of many template engines that’s demonstrated here is their &lt;em&gt;scoping&lt;/em&gt; system, which is akin to JavaScript’s &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/with&quot;&gt;&lt;code&gt;with&lt;/code&gt; statements&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Essentially, lookups are done within a &lt;em&gt;scope&lt;/em&gt; of the parameters.
In the beginning, if you use an identifier like &lt;code&gt;url&lt;/code&gt;, the template engine will look it up in the parameters’ root.
However, once you enter a loop, that scope walks down to the &lt;em&gt;current loop element&lt;/em&gt;—which is why &lt;code&gt;{{ url }}&lt;/code&gt; in the example above refers to the &lt;em&gt;feed entry’s&lt;/em&gt; URL, rather than the whole page’s URL.&lt;/p&gt;&lt;p&gt;Because of this, when you want to look up an identifier that &lt;em&gt;isn’t&lt;/em&gt; within the loop element, you have to walk up the scope chain to get to it—in Handlebars, that’s written like &lt;code&gt;{{ ../url }}&lt;/code&gt;.
I think you can imagine how this can quickly get error-prone and annoying to work with.&lt;/p&gt;&lt;p&gt;Either way, that about sums up the essentials of template engines.&lt;/p&gt;&lt;hr/&gt;&lt;p&gt;Now, don’t get me wrong: I think template engines are great.
They are &lt;em&gt;simple and fast&lt;/em&gt;, when done well.&lt;/p&gt;&lt;p&gt;One of my favourite templating engines, Go’s &lt;a href=&quot;https://pkg.go.dev/text/template&quot;&gt;&lt;code&gt;text/template&lt;/code&gt;&lt;/a&gt;—whose design you should totally copy! (though, maybe sans the scoping…)—fits in less than 3000 lines of code as of writing this post.&lt;/p&gt;&lt;p&gt;It is a really comprehensive template engine, featuring basically everything I mentioned above, with a syntax design much nicer to look at than Handlebars, in my humble opinion.&lt;/p&gt;&lt;p&gt;… yeah I’m just dissatisfied with Handlebars, am I not.&lt;/p&gt;&lt;p&gt;Let’s be real, Handlebars doesn’t do anything more than Go’s &lt;code&gt;text/template&lt;/code&gt; does, yet is 3x larger, at around 9000 lines of code &lt;strong&gt;excluding comments!&lt;/strong&gt; (Meanwhile my estimation of &lt;code&gt;text/template&lt;/code&gt;’s size was done entirely with a web browser and a calculator, no fancy comment exclusions.)&lt;/p&gt;&lt;p&gt;You could blame a lot of this on Rust’s lack of reflection system, but I don’t believe you need a reflection system to do templates…&lt;/p&gt;&lt;p&gt;In fact, you’re probably better off &lt;em&gt;without&lt;/em&gt; one, since passing data to templates is like serialising to JSON, which I think &lt;a href=&quot;https://hashset.dev/article/2026/03/21/reflecting-on-deserialization-why-you-should-explicitly-decode-your-json/&quot;&gt;should be done imperatively, kept separate from your data structure definitions&lt;/a&gt;.
The static type system does not know about your templates, so by exposing your data structures to templates, you’re introducing a contract about your data structures’ API stability—which is not ideal, and annoying to enforce with how template engines tend to be dynamically typed.&lt;/p&gt;&lt;p&gt;But even then, Handlebars still has another ergonomic problem.
Remember that triple curly brace &lt;code&gt;{{{ }}}&lt;/code&gt;?&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-handlebars th-syntax-highlighting&quot;&gt;&amp;lt;h2&amp;gt;&amp;lt;a href=&amp;quot;{{ url }}&amp;quot;&amp;gt;{{{ title }}}&amp;lt;/a&amp;gt;&amp;lt;/h2&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Yeah, that.&lt;br/&gt;
Kind of sucks that I have to do that, doesn’t it.&lt;/p&gt;&lt;p&gt;What if the data model changes, and the server no longer provides the template with a string that’s known-good-HTML?
Then we’ve got ourselves an XSS vulnerability cooking, if the template is serving anything based on user input (which it &lt;em&gt;will&lt;/em&gt; in most dynamic services.)&lt;/p&gt;&lt;p&gt;This is why Go also has a sister module for its &lt;code&gt;text/template&lt;/code&gt;, called &lt;a href=&quot;https://pkg.go.dev/html/template&quot;&gt;&lt;code&gt;html/template&lt;/code&gt;&lt;/a&gt;.
Here’s how it fixes this problem:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;
Any time you want to provide raw HTML to the template, you’re encouraged to do it in your server code, by passing in &lt;code&gt;HTML&lt;/code&gt; instead of a &lt;code&gt;string&lt;/code&gt;.
&lt;/li&gt;
&lt;li&gt;
To my knowledge, there is no special syntax to tell the template engine, “don’t escape this.”
&lt;/li&gt;
&lt;li&gt;
The module knows how to render non-HTML strings into templates by…&lt;br/&gt;
&lt;strong&gt;PARSING THE HTML??&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;It parses.&lt;br/&gt;
The HTML.&lt;/p&gt;&lt;p&gt;It parses.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://html.spec.whatwg.org/#parsing&quot;&gt;HTML.&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;sub&gt;(My browser chugged &lt;em&gt;hard&lt;/em&gt; trying to load that page.)&lt;/sub&gt;&lt;/p&gt;&lt;p&gt;At this point, it feels like we’re doing something backwards here.
Why are we trying to &lt;em&gt;parse&lt;/em&gt; the HTML we’re trying to render, a.k.a. &lt;em&gt;generate&lt;/em&gt;, a.k.a. &lt;em&gt;serialise&lt;/em&gt;?&lt;/p&gt;&lt;p&gt;Which, I guess, is exactly what the person behind &lt;a href=&quot;https://maud.lambda.xyz/&quot;&gt;maud&lt;/a&gt; was thinking when they made it.&lt;/p&gt;&lt;h2&gt;Devising an approach that isn’t ass-backwards&lt;/h2&gt;&lt;p&gt;&lt;a href=&quot;https://maud.lambda.xyz/&quot;&gt;Maud&lt;/a&gt; is a different kind of HTML template engine.
Instead of being implemented as a generic language for transforming text, it is implemented as a Rust macro with its own syntax, that always produces valid HTML.&lt;/p&gt;&lt;p&gt;This seems like the more sensible approach—instead of trying to parse the HTML complexity monster, why don’t we rely on the host language to provide us with enough information to &lt;em&gt;generate&lt;/em&gt; it correctly?&lt;/p&gt;&lt;p&gt;And the result is actually pretty neat-looking!
I haven’t used it personally, but here’s an example from their website.&lt;/p&gt;&lt;p&gt;It’s pretty lightweight, and doesn’t lose the structure of the HTML.
I like it.&lt;/p&gt;&lt;p&gt;The one thing I &lt;em&gt;don’t&lt;/em&gt; like is that it’s a complicated, 3000 line-of-code long Rust macro.&lt;/p&gt;&lt;p&gt;It is an improvement in terms of density of functionality compared to &lt;code&gt;html/template&lt;/code&gt;, and doesn’t suffer from the same ergonomic annoyances as Handlebars does.
But I don’t like that it’s a 3000 lines long Rust macro.&lt;/p&gt;&lt;hr/&gt;&lt;p&gt;For those of you wondering why I’m so much against implementing this with Rust macros, here’s a short breakdown:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The tooling support sucks.&lt;/strong&gt;
The language server pretty much stops working inside macros, and the &lt;code&gt;rustfmt&lt;/code&gt; will no longer format your code.&lt;/p&gt;
&lt;p&gt;That alone is 80% of why I hate doing that kind of stuff with macros.
The remaining 20% is…&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The compilation times.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;maud depends on &lt;code&gt;syn&lt;/code&gt; to parse Rust expressions, which means &lt;em&gt;that&lt;/em&gt; has to get compiled before any other crate does (which takes a bit of time).&lt;/p&gt;
&lt;p&gt;The worse part, though, is that once you get to using the macros, the compiler—instead of just parsing your code—has to invoke the macro, which will parse your code, and &lt;em&gt;then&lt;/em&gt; spit out a bunch of Rust code for the compiler to parse—increasing the amount of work that has to be done.&lt;/p&gt;
&lt;p&gt;Don’t get me started on &lt;code&gt;#[derive]&lt;/code&gt; macros…
maud fortunately isn’t one of them, but I’ll just briefly ruin your day by saying that they’re even worse, because each macro you use has to parse your type definition &lt;em&gt;separately&lt;/em&gt;, in addition to the compiler, as well as &lt;em&gt;other&lt;/em&gt; &lt;code&gt;#[derive]&lt;/code&gt; macros you use.
&lt;a href=&quot;https://www.youtube.com/watch?v=TbhVRK6vswo&quot;&gt;&lt;em&gt;Shivers.&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;And lastly, &lt;strong&gt;it’s inventing new syntax.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I’d &lt;em&gt;love&lt;/em&gt; it if an HTML generation thing like maud could just use &lt;em&gt;existing&lt;/em&gt; syntax instead of having to invent anything new.
It would result in having less to learn, and less context switches having to look stuff up in the library’s documentation after not using it for a month.&lt;/p&gt;
&lt;p&gt;For this same reason, I don’t really like JSX despite its unquestionable ergonomic benefits.
It’s bolting on new features to a language in a pretty inelegant way, instead of reusing existing syntax.
And it requires a compilation step.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Fortunately, there exists a programming language that ticks all the boxes.&lt;/p&gt;&lt;p&gt;Enter…&lt;/p&gt;&lt;h2&gt;The Dark Side of the Moon&lt;/h2&gt;&lt;p&gt;&lt;em&gt;Lua, that ugly language with 1-based indexing, a weird inequality operator, and &lt;code&gt;do&lt;/code&gt;..&lt;code&gt;end&lt;/code&gt; blocks instead of my beloved curlies?&lt;/em&gt;&lt;/p&gt;&lt;p&gt;Look, I’m also one for curly hair, but I genuinely like Lua’s syntax.
It can be pretty wordy, but in essence it is a bit like a tiny, dynamically typed Go.&lt;/p&gt;&lt;p&gt;A language that &lt;em&gt;gets out of the way and lets you get things done&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;At the same time, &lt;a href=&quot;https://lua.org/doc/hopl.pdf&quot;&gt;Lua has a pretty long history&lt;/a&gt; having started as a DSL for defining data, which influenced its design towards some &lt;em&gt;really interesting&lt;/em&gt; choices that make it &lt;em&gt;perfect&lt;/em&gt; for the thing we’re trying to make.&lt;/p&gt;&lt;p&gt;Let me start by introducing Lua’s most awesome feature: its single data structure, the table.&lt;/p&gt;&lt;p&gt;On the surface, they’re familiar: a table is nothing more than a hash table with dynamically typed keys.
Since there is no other data structure in the language, they feature syntax sugar which lets them double as records or structs.&lt;/p&gt;&lt;p&gt;At the same time, since the language has no dedicated array data structure, there’s also a syntax sugar for declaring a table with incrementing integer keys, starting at 1.
Simply omit the key:&lt;/p&gt;&lt;p&gt;It’s also possible to get the number of elements
using the # operator.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-lua th-syntax-highlighting&quot;&gt;print(#words) --&amp;gt; 3&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Storing elements in this manner activates an optimisation called the table’s &lt;em&gt;array part&lt;/em&gt;.
When a table has a contiguous sequence of elements starting at 1, Lua will store them in a contiguous array in memory, making iteration and indexing more efficient than with a hash table.&lt;/p&gt;&lt;p&gt;Those are the basics, but a cool feature is that you can combine the two syntaxes together in one table initialiser.
This will create a table both with string keys and an array part:&lt;/p&gt;&lt;p&gt;Notably, the length of the table is reported as 2 in this case, and there’s a function &lt;code&gt;ipairs&lt;/code&gt; to iterate over &lt;em&gt;only&lt;/em&gt; the array part in order of increasing indices, in addition to &lt;code&gt;pairs&lt;/code&gt; which iterates over all keys in an arbitrary order.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-lua th-syntax-highlighting&quot;&gt;print(#document) --&amp;gt; 2

for i, line in ipairs(document) do
	print(i, line) --&amp;gt; 1	Hello! This is a line of text.
	--&amp;gt; 2	Meow.
end

for k, v in pairs(document) do
	-- Note how order is not preserved, and &amp;quot;language&amp;quot; comes last:
	print(k, v) --&amp;gt; 1	Hello! This is a line of text.
	--&amp;gt; 2	Meow.
	--&amp;gt; language	English
end&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Another neat piece of syntax sugar Lua gained during its DSL roots is the ability to call a function with a table initialiser as its sole argument, without having to add extra parentheses:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-lua th-syntax-highlighting&quot;&gt;print {&amp;quot;hello&amp;quot;} --&amp;gt; table: 0x55d727071ec0&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The rationale was that this allows for validating data very easily, as you can inspect the table, check its fields for correctness, and then return it.&lt;/p&gt;&lt;p&gt;But technically, this can extend to returning a completely different, &lt;em&gt;transformed&lt;/em&gt; table, which we’re going to use to great advantage.&lt;/p&gt;&lt;p&gt;Combining these two little syntax sugars with the flexibility of tables allows us to invent pretty sweet DSLs, like this one for constructing GUIs that I had mentioned in my old blog post on Lua:&lt;/p&gt;&lt;p&gt;Here, all the little primitives like &lt;code&gt;root_window&lt;/code&gt;, &lt;code&gt;align&lt;/code&gt;, &lt;code&gt;vertical_stack&lt;/code&gt;, or &lt;code&gt;text&lt;/code&gt;, are functions in the global scope that take in a table as an argument, and return a widget.&lt;/p&gt;&lt;p&gt;So naturally, what’s stopping us from writing something like this for generating HTML?&lt;/p&gt;&lt;p&gt;…&lt;/p&gt;&lt;p&gt;Nothing, of course.
So let’s go ahead and do it!&lt;/p&gt;&lt;h2&gt;The part where I do the thing&lt;/h2&gt;&lt;p&gt;Let’s establish what we’d like to accomplish with our DSL for generating HTML.
The end result we’d like to achieve is for an expression like this:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-lua th-syntax-highlighting&quot;&gt;h.Document{
	lang = &amp;quot;en&amp;quot;,

	h.head{
		h.meta{charset = &amp;quot;UTF-8&amp;quot;},
		h.title{&amp;quot;Hello, world!&amp;quot;},
	},

	h.body{
		h.h1{&amp;quot;Hello, world!&amp;quot;},
		h.p{
			&amp;quot;This is an example of the little HTML templating framework I wrote &amp;quot;,
			&amp;quot;in Lua in like 20 minutes.&amp;quot;
		},
		h.p{
			&amp;quot;As you can see, it is fully capable of generating any kind of markup &amp;quot;,
			&amp;quot;you&amp;#39;d ever want.&amp;quot;, h.br(),
			&amp;quot;It can even do things like &amp;quot;, h.b&amp;quot;bold text&amp;quot;, &amp;quot;!&amp;quot;
		},
		h.p&amp;quot;This is some embedded HTML &amp;lt;p&amp;gt;&amp;lt;/p&amp;gt;&amp;quot;
		h.img{
			alt = &amp;quot;riki sitting in pink space&amp;quot;,
			src = &amp;quot;https://riki.house/static/character/riki/sitting.png&amp;quot;,
			width = 2223, height = 1796,
			style = &amp;quot;width: 20%; height: auto;&amp;quot;
		},
	},
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To generate HTML equivalent to the following:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-html th-syntax-highlighting&quot;&gt;&amp;lt;!doctype html&amp;gt;
&amp;lt;html lang=&amp;quot;en&amp;quot;&amp;gt;
	&amp;lt;head&amp;gt;
		&amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;gt;
		&amp;lt;title&amp;gt;Hello, world!&amp;lt;/title&amp;gt;
	&amp;lt;/head&amp;gt;

	&amp;lt;body&amp;gt;
		&amp;lt;h1&amp;gt;Hello, world!&amp;lt;/h1&amp;gt;
		&amp;lt;p&amp;gt;This is an example of the little HTML templating framework I wrote in Lua in like 20 minutes.&amp;lt;/p&amp;gt;
		&amp;lt;p&amp;gt;As you can see, it is fully capable of generating any kind of markup you&amp;#39;d ever want.&amp;lt;br&amp;gt;
		It can even do things like &amp;lt;b&amp;gt;bold text&amp;lt;/b&amp;gt;!&amp;lt;/p&amp;gt;
		&amp;lt;p&amp;gt;This is some embedded HTML &amp;amp;lt;p&amp;amp;gt;&amp;amp;lt;/p&amp;amp;gt;&amp;lt;/p&amp;gt;
		&amp;lt;img alt=&amp;quot;riki sitting in pink space&amp;quot;
			src=&amp;quot;https://riki.house/static/character/riki/sitting.png&amp;quot;
			width=&amp;quot;2223&amp;quot; height=&amp;quot;1796&amp;quot;
			style=&amp;quot;width: 20%; height: auto;&amp;quot;&amp;gt;
	&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I will begin by creating a namespace for our library.
In Lua, you use an ordinary table for that.
In a new file:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-lua th-syntax-highlighting&quot;&gt;local html = {}

return html&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;From now on, treat any code examples as sandwiched between these two lines of boilerplate.&lt;/p&gt;&lt;p&gt;Going back to the example for a bit—note how any literal Lua string we try to put into the HTML must be escaped correctly.
For this, we will need a separate &lt;em&gt;data type&lt;/em&gt; for differentiating HTML from plain strings.
I will call mine &lt;code&gt;Html&lt;/code&gt;, because I like the convention of starting type names with an uppercase letter.&lt;/p&gt;&lt;p&gt;We use a metatable to give the table returned by &lt;code&gt;html.Html&lt;/code&gt; a &lt;em&gt;prototype&lt;/em&gt;, which will help us differentiate our &lt;code&gt;Html&lt;/code&gt;-type tables from any others.
I wrote about metatables at length in my article on implementing &lt;a href=&quot;https://riki.house/programming/lua/classes&quot;&gt;classes in Lua&lt;/a&gt;, so you might want to read that if you don’t know what they are.&lt;/p&gt;&lt;p&gt;We also give the metatable a &lt;code&gt;__tostring&lt;/code&gt; function, which will allow us to call the standard &lt;code&gt;tostring&lt;/code&gt; function on the table to get the rendered HTML.
&lt;code&gt;tostring&lt;/code&gt; is also used when &lt;code&gt;print&lt;/code&gt;ing things to the console, so that’ll also take care of that functionality.&lt;/p&gt;&lt;p&gt;Armed with an &lt;code&gt;Html&lt;/code&gt; type, we can write a basic function—no sugar yet—to produce HTML for us, given a tag and a &lt;em&gt;definition table&lt;/em&gt;, containing the element’s attributes and children.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-lua th-syntax-highlighting&quot;&gt;-- This little function will make it a bit more convenient to
-- append a bunch of new elements to a table.
local function write(t, ...)
	local n = select(&amp;#39;#&amp;#39;, ...)
	for i = 1, n do
		table.insert(t, (select(i, ...)))
	end
end

local function write_children(el, def)
	for _, child in ipairs(def) do
		if type(child) == &amp;quot;string&amp;quot; then
			-- Text node
			table.insert(el, child)
		elseif type(child) == &amp;quot;table&amp;quot; and getmetatable(child) == Html then
			-- HTML node
			table.insert(el, child.text)
		elseif type(child) == &amp;quot;table&amp;quot; then
			-- Nested table.
			-- This allows us to insert children from a `for` loop.
			write_children(el, child)
		end
	end
end

function html.Element(kind, def)
	-- open tag
	local el = {&amp;quot;&amp;lt;&amp;quot;, kind}
	for k, v in pairs(def) do
		if type(k) == &amp;quot;string&amp;quot; then
			write(el, &amp;quot; &amp;quot;, k, &amp;#39;=&amp;quot;&amp;#39;, v, &amp;#39;&amp;quot;&amp;#39;)
		end
	end
	table.insert(el, &amp;quot;&amp;gt;&amp;quot;)

	-- children
	write_children(el, def)

	-- close tag
	write(el, &amp;quot;&amp;lt;/&amp;quot;, kind, &amp;quot;&amp;gt;&amp;quot;)

	return html.Html(table.concat(el))
end&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The function is called &lt;code&gt;Element&lt;/code&gt;, again with an uppercase letter, because I’d like to make space for the syntax sugar we’re about to add later.
HTML is case-insensitive, so we’ll reserve uppercase names for specific functions, and lowercase names for element constructors—similar to how JSX works with lowercase names for literal elements, and uppercase for components.&lt;/p&gt;&lt;p&gt;A surprising technique to note here is that we use a table to collect all the HTML text to be rendered, which we then &lt;code&gt;table.concat&lt;/code&gt; to obtain our final string.&lt;/p&gt;&lt;p&gt;We do this because strings in Lua are immutable, and using the string concatenation operator &lt;code&gt;a..b&lt;/code&gt; incurs a new allocation every time we use it, which can get expensive really quickly.
It can get especially bad if the strings in question are big—because each allocation must copy the old string into the new string, thus getting worse and worse with each use of the operator.&lt;/p&gt;&lt;p&gt;So to avoid writing a &lt;a href=&quot;https://www.joelonsoftware.com/2001/12/11/back-to-basics/&quot;&gt;Shlemiel the painter’s algorithm&lt;/a&gt;, we accumulate all the pieces into a table, and then join them together in one fell swoop.&lt;/p&gt;&lt;p&gt;In other languages, such as Go, this is called a &lt;em&gt;string builder&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;Anyways, with that out of the way, we can now construct some HTML!&lt;br/&gt;
Let’s try it out.&lt;/p&gt;&lt;p&gt;Output&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-html th-syntax-highlighting&quot;&gt;&amp;lt;p&amp;gt;Hello, world!&amp;lt;/p&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So far, so good! Let’s keep going, and render a user comment.
I’ll use some mock data, because this isn’t the time to be writing a comment section backend.&lt;/p&gt;&lt;p&gt;Output&lt;/p&gt;&lt;p&gt;Great! We can use this in pretty much the same way as React components.&lt;/p&gt;&lt;p&gt;Hang on, what are you–&lt;/p&gt;&lt;p&gt;Output&lt;/p&gt;&lt;p&gt;Oh. &lt;a href=&quot;https://riki.house/b?emoji/oh&quot;&gt;&lt;img src=&quot;https://riki.house/static/emoji/oh.png?v=b3-b600dd8a&quot; alt=&quot;oh&quot; title=&quot;:oh:&quot;/&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;We forgot to escape the HTML, didn’t we.&lt;/p&gt;&lt;p&gt;Fortunately for us, this seems like a pretty simple thing to do; it involves substituting out all the illegal characters out of our untrusted input strings into nice, safe HTML escape codes.
Let’s write a function that will do the substitution.&lt;/p&gt;&lt;p&gt;There are a couple things to note here.&lt;/p&gt;&lt;p&gt;First of all, &lt;strong&gt;if you want to do this 100% correctly, it is not that simple.&lt;/strong&gt;
HTML is a weird language because it embeds CSS and JS inside, and those have their own rules as to how values have to be escaped.
I’m skipping over that because HTML escapes handle 90% of the cases for a simple blog, but you’ll have to be wary around CSS and JS.&lt;/p&gt;&lt;p&gt;But regarding the code, &lt;em&gt;what’s with the oddly formatted string argument we pass in to &lt;a href=&quot;https://lua.org/manual/5.5/manual.html#pdf-string.gsub&quot;&gt;&lt;code&gt;string.gsub&lt;/code&gt;&lt;/a&gt;?&lt;/em&gt;&lt;/p&gt;&lt;p&gt;A lot of languages have libraries for regular expressions, but Lua aims to be &lt;em&gt;really small&lt;/em&gt;.
So small you can put it on a microcontroller!
Meanwhile, regular expression engines tend to be &lt;em&gt;really big&lt;/em&gt;, so bundling one with Lua would be a no-go.&lt;/p&gt;&lt;p&gt;Except, text processing is a really useful thing to have in a garbage collected language!
Which is why Lua invented its own small language for matching strings, called &lt;em&gt;patterns&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;Patterns are really simple; they offer no backtracking, so you won’t find a &lt;code&gt;|&lt;/code&gt; operator like in regular expressions.
Among what they &lt;em&gt;can&lt;/em&gt; do however, is matching characters from a set—as seen above.&lt;/p&gt;&lt;p&gt;While we &lt;em&gt;could&lt;/em&gt; use &lt;code&gt;string.gsub&lt;/code&gt; like a simple plain text search-and-replace, and then chain a few of them together to replace all the illegal characters in several passes, it lets us do something a little bit more clever.&lt;/p&gt;&lt;p&gt;&lt;code&gt;string.gsub&lt;/code&gt; will interpret a table-typed replacement argument as a &lt;em&gt;lookup table&lt;/em&gt; for replacements.&lt;/p&gt;&lt;p&gt;If you wrap your pattern in a capture group—those parentheses &lt;code&gt;([&amp;amp;&amp;lt;&amp;gt;\&amp;quot;&amp;#39;])&lt;/code&gt;—&lt;code&gt;string.gsub&lt;/code&gt; will use what’s matched inside those parentheses as a key to that table you provide, and replace the matched string with a string looked up from that table.&lt;br/&gt;
Pretty neat, huh?&lt;/p&gt;&lt;p&gt;This allows us to write a lookup table for all the characters we want to replace, instead of running &lt;code&gt;string.gsub&lt;/code&gt; multiple times—which means better performance (as we only scan the string once), but also means less opportunity for bugs to creep in.&lt;/p&gt;&lt;p&gt;For example, the following is not the correct way to implement this function.
Can you tell why?
(Hint: it’s not that any of the substituted characters mean anything special when used in a pattern.)&lt;/p&gt;&lt;p&gt;Going back to the original function for a bit once again, I’d like to explain another thing that might seem odd:&lt;/p&gt;&lt;p&gt;Notice the extra parentheses around the returned value?
This is because &lt;code&gt;string.gsub&lt;/code&gt; returns &lt;em&gt;two values&lt;/em&gt;, with one being the string after replacements, and the other being the number of replacements it made.
Therefore, we have to collapse it back to just the output string, which is most conveniently done by wrapping the function call in parentheses.&lt;/p&gt;&lt;hr/&gt;&lt;p&gt;With that out of the way, we can upgrade our &lt;code&gt;html.Element&lt;/code&gt; function to escape untrusted strings and avoid wreaking XSS havoc all over our website.&lt;/p&gt;&lt;p&gt;Now, if someone tries to post an evil comment, their plans will be foiled:&lt;/p&gt;&lt;p&gt;Output&lt;/p&gt;&lt;hr/&gt;&lt;p&gt;There is still one more important thing we have to implement for our implementation to be fully HTML-compliant, though: &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Glossary/Void_element&quot;&gt;&lt;em&gt;void elements&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Some elements in HTML cannot have children, and only have an opening tag.
Those elements include things like &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;We need to filter out those elements, and never emit any children or closing tags for them:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-lua th-syntax-highlighting&quot;&gt;local void_elements = {
	area = true, base = true,
	br = true, col = true,
	embed = true, hr = true,
	img = true, input = true,
	link = true, meta = true,
	param = true, source = true,
	track = true, wbr = true,
}

function html.Element(kind, def)
	-- open tag
	local el = {&amp;quot;&amp;lt;&amp;quot;, kind}
	for k, v in pairs(def) do
		write(el, &amp;quot; &amp;quot;, k, &amp;#39;=&amp;quot;&amp;#39;, escape_html(v), &amp;#39;&amp;quot;&amp;#39;) -- here
	end
	table.insert(el, &amp;quot;&amp;gt;&amp;quot;)

	if void_elements[kind] then
		return html.Html(table.concat(el))
	end

	-- children
	write_children(el, def)

	-- close tag
	write(el, &amp;quot;&amp;lt;/&amp;quot;, kind, &amp;quot;&amp;gt;&amp;quot;)

	return html.Html(table.concat(el))
end&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now if we try to create an &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; element, it will correctly be missing a closing tag.&lt;/p&gt;&lt;p&gt;Output&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-html th-syntax-highlighting&quot;&gt;&amp;lt;img alt=&amp;quot;riki sitting in pink space&amp;quot; src=&amp;quot;https://riki.house/static/character/riki/sitting.png&amp;quot;&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And… that’s pretty much all we need to create well-formed HTML elements!
That said, there is still one correctness improvement I’d like to do.&lt;/p&gt;&lt;p&gt;Remember how I said that &lt;code&gt;pairs&lt;/code&gt;’s iteration order is undefined?
Knowing how hash tables work, there is a risk of it being non-deterministic, depending on the Lua implementation.&lt;/p&gt;&lt;p&gt;Now, I don’t know of any implementations of Lua that would randomise hash table order between runs of the program like Rust does, but it does feel kind of awry to be relying on an implementation detail that can change between versions like that.
Who knows what LuaJIT could be doing?&lt;/p&gt;&lt;p&gt;And so, the last improvement I’d like to make is to &lt;em&gt;sort all the attributes alphabetically&lt;/em&gt; to ensure implementation differences cannot introduce any non-determinism.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-lua th-syntax-highlighting&quot;&gt;local function attr_cmp(a, b)
	return a[1] &amp;lt; b[1]
end

function html.Element(kind, def)
	local attr = {}
	for k, v in pairs(def) do
		if type(k) == &amp;quot;string&amp;quot; then
			table.insert(attr, {k, v})
		end
	end
	table.sort(attr, attr_cmp)

	-- open tag
	local el = {&amp;quot;&amp;lt;&amp;quot;, kind}
	for _, a in ipairs(attr) do
		write(el, &amp;quot; &amp;quot;, a[1], &amp;#39;=&amp;quot;&amp;#39;, escape_html(a[2]), &amp;#39;&amp;quot;&amp;#39;)
	end
	table.insert(el, &amp;quot;&amp;gt;&amp;quot;)
	table.insert(el, &amp;quot;&amp;gt;&amp;quot;)
	-- (omitted)
end&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is the kind of mistake that you only make once in life.
Ask me how I know.&lt;/p&gt;&lt;p&gt;With that out of the way, we can now move onto some ergonomic improvements, because typing &lt;code&gt;html.Element(&amp;quot;p&amp;quot;, {&amp;quot;This is some text&amp;quot;})&lt;/code&gt; just to display some silly text gets old pretty quick.&lt;br/&gt;
So does reading it.&lt;/p&gt;&lt;h2&gt;Ergonomic improvements&lt;/h2&gt;&lt;p&gt;Remember how I hyped up all the cool domain specific language stuff Lua is capable of, only to never follow up on it?&lt;br/&gt;
This is that section where I follow up on it.&lt;/p&gt;&lt;p&gt;Let’s start with the most important bit: how do we make it so that we can create elements using the syntax &lt;code&gt;html.p{}&lt;/code&gt; instead of &lt;code&gt;html.Element(&amp;quot;p&amp;quot;, {})&lt;/code&gt;?&lt;/p&gt;&lt;p&gt;If you’ve read my blog post on &lt;a href=&quot;https://riki.house/programming/lua/classes&quot;&gt;implementing classes&lt;/a&gt;, you may remember that metatables have an &lt;code&gt;__index&lt;/code&gt; metamethod that allows us to override the indexing operator &lt;code&gt;a[b]&lt;/code&gt; (and likewise &lt;code&gt;a.b&lt;/code&gt;) for keys that are not in the table.&lt;/p&gt;&lt;p&gt;Therefore, let’s override &lt;code&gt;html&lt;/code&gt;’s &lt;code&gt;__index&lt;/code&gt;, to return a function which creates an element with the given name—accepting only a single table argument, therefore making it possible to use the shorthand &lt;code&gt;f{}&lt;/code&gt; syntax with it.&lt;/p&gt;&lt;p&gt;Note how we save the function in the &lt;code&gt;html&lt;/code&gt; namespace for later, so that subsequent calls to it do not recreate it—reducing the amount of meaningless work for the computer to do.&lt;/p&gt;&lt;p&gt;Now, lo and behold, our comment example from before can become much cleaner:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-lua th-syntax-highlighting&quot;&gt;-- Before
local function Comment(c)
	return html.Element(&amp;quot;article&amp;quot;, {
		class = &amp;quot;comment&amp;quot;,

		html.Element(&amp;quot;span&amp;quot;, {class = &amp;quot;user&amp;quot;, comment.user}),
		&amp;quot; says: &amp;quot;, comment.text
	})
end

-- After
local function Comment(c)
	return html.article{
		class = &amp;quot;comment&amp;quot;,

		html.span{class = &amp;quot;user&amp;quot;, comment.user},
		&amp;quot; says: &amp;quot;, comment.text
	}
end&lt;/code&gt;&lt;/pre&gt;&lt;hr/&gt;&lt;p&gt;An annoying thing is that custom elements are still a bit of a bother to use.&lt;/p&gt;&lt;p&gt;For those of you unfamiliar with custom elements, it’s an API that allows you to declare custom HTML elements from JavaScript.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript th-syntax-highlighting&quot;&gt;class Clock extends HTMLElement {
	connectedCallback() {
		this.format = this.getAttribute(&amp;quot;data-format&amp;quot;) ?? &amp;quot;short&amp;quot;;
		this.update();
		setInterval(() =&amp;gt; this.update(), 1000);
	}

	update() {
		let now = new Date();
		this.innerText = now.toLocaleTimeString([], { timeStyle: this.format });
	}
}
customElements.define(&amp;quot;riki-clock&amp;quot;, Clock);&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With this script, when we write &lt;code&gt;&amp;lt;riki-clock data-format=&amp;quot;short&amp;quot;&amp;gt;&amp;lt;/riki-clock&amp;gt;&lt;/code&gt; in our HTML, the extra behaviour from the &lt;code&gt;Clock&lt;/code&gt; class will be attached to it.&lt;/p&gt;&lt;p&gt;A notable thing is that all names of custom elements have to include a dash &lt;code&gt;-&lt;/code&gt; in them, to encourage namespacing.&lt;/p&gt;&lt;p&gt;It’s a pretty useful API, and I use it all the time in my web apps, so it’d be good if constructing those custom elements in our Lua templating engine didn’t have to look like this:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-lua th-syntax-highlighting&quot;&gt;return html[&amp;quot;riki-clock&amp;quot;]{[&amp;quot;data-format&amp;quot;] = &amp;quot;short&amp;quot;}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Output&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-html th-syntax-highlighting&quot;&gt;&amp;lt;riki-clock data-format=&amp;quot;short&amp;quot;&amp;gt;&amp;lt;/riki-clock&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That’s not even shorter than the raw HTML!&lt;/p&gt;&lt;p&gt;Fortunately, underscores aren’t really used at all in HTML tags and attribute names, so I think the right opinion to take here is to substitute them into dashes automatically.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-lua th-syntax-highlighting&quot;&gt;function html.Element(kind, def)
	local attr = {}
	for k, v in pairs(def) do
		if type(k) == &amp;quot;string&amp;quot; then
			local k = k:gsub(&amp;quot;_&amp;quot;, &amp;quot;-&amp;quot;)
			table.insert(attr, {k, v})
		end
	end
	table.sort(attr, attr_cmp)

	-- open tag
	local el = {&amp;quot;&amp;lt;&amp;quot;, kind}
	-- (omitted)
end

function html.__index(html, key)
	key = key:gsub(&amp;quot;_&amp;quot;, &amp;quot;-&amp;quot;)
	local function thunk(def)
		return html.Element(key, def)
	end
	html[key] = thunk -- memoise for future use; __index will not be called then
	return thunk
end&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With that out of the way, the riki clock can now be assembled with much less line noise than before.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-lua th-syntax-highlighting&quot;&gt;return html.riki_clock{data_format = &amp;quot;short&amp;quot;}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Output&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-html th-syntax-highlighting&quot;&gt;&amp;lt;riki-clock data-format=&amp;quot;short&amp;quot;&amp;gt;&amp;lt;/riki-clock&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;hr/&gt;&lt;p&gt;As another space for improvement, I’d like to turn your attention to how &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tags have to be constructed right now.&lt;/p&gt;&lt;p&gt;If you compare this to the version we wanted to have at the beginning, you will notice one small detail: in this version, the &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; attributes have to be provided as strings!&lt;/p&gt;&lt;p&gt;That’s kind of annoying, especially if the sizes are coming in from some outside function that returns them as integers.
To deal with that, we will add one last line to &lt;code&gt;html.Element&lt;/code&gt;, to convert all attribute values to strings automatically.&lt;/p&gt;&lt;p&gt;To static typing-minded folks, this might look horrible, but I feel like this is one of those cases where being liberal in what you accept and strict in what you produce gives you some really nice results.
Besides, this behaviour is consistent with the rest of Lua, where concatenating strings will call &lt;code&gt;tostring&lt;/code&gt; implicitly:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-lua th-syntax-highlighting&quot;&gt;print(&amp;quot;Score: &amp;quot;..1) --&amp;gt; Score: 1&lt;/code&gt;&lt;/pre&gt;&lt;hr/&gt;&lt;p&gt;As one last minor improvement, let’s make the function call itself a little bit more ergonomic, and make it easier to construct tags containing only text—such as &lt;code&gt;&amp;lt;b&amp;gt;bold&amp;lt;/b&amp;gt;&lt;/code&gt;—or nothing at all, such as &lt;code&gt;&amp;lt;br&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;wbr&amp;gt;&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;To tie the library together, let’s make it a bit easier to start a document with a &lt;code&gt;&amp;lt;!doctype html&amp;gt;&lt;/code&gt; at the beginning.&lt;/p&gt;&lt;p&gt;And there you go!&lt;/p&gt;&lt;hr/&gt;&lt;p&gt;The whole library can be wrapped up in 117 lines of code.
Here’s the full code listing, feel free to add it into your projects and tweak it to taste:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-lua th-syntax-highlighting&quot;&gt;local html = {}
setmetatable(html, html)

local escape_subs = {
	[&amp;quot;&amp;amp;&amp;quot;] = &amp;quot;&amp;amp;amp;&amp;quot;,
	[&amp;quot;&amp;lt;&amp;quot;] = &amp;quot;&amp;amp;lt;&amp;quot;,
	[&amp;quot;&amp;gt;&amp;quot;] = &amp;quot;&amp;amp;gt;&amp;quot;,
	[&amp;#39;&amp;quot;&amp;#39;] = &amp;quot;&amp;amp;quot;&amp;quot;,
	[&amp;quot;&amp;#39;&amp;quot;] = &amp;quot;&amp;amp;#39;&amp;quot;,
}
local function escape_html(str)
	return (str:gsub(&amp;quot;([&amp;amp;&amp;lt;&amp;gt;\&amp;quot;&amp;#39;])&amp;quot;, escape_subs))
end

local function write(t, ...)
	local n = select(&amp;#39;#&amp;#39;, ...)
	for i = 1, n do
		table.insert(t, (select(i, ...)))
	end
end

local Html = {}

function html.Html(text)
	assert(type(text) == &amp;quot;string&amp;quot;, &amp;quot;html.Html expects a string&amp;quot;)
	return setmetatable({text = text}, Html)
end

function Html:__tostring()
	return self.text
end

local void_elements = {
	area = true,
	base = true,
	br = true,
	col = true,
	embed = true,
	hr = true,
	img = true,
	input = true,
	link = true,
	meta = true,
	param = true,
	source = true,
	track = true,
	wbr = true,
}

local function attr_cmp(a, b)
	return a[1] &amp;lt; b[1]
end

local function write_children(el, def)
	for _, child in ipairs(def) do
		if type(child) == &amp;quot;string&amp;quot; then
			table.insert(el, escape_html(child))
		elseif type(child) == &amp;quot;table&amp;quot; and getmetatable(child) == Html then
			table.insert(el, child.text)
		elseif type(child) == &amp;quot;table&amp;quot; then
			write_children(el, child)
		end
	end
end

function html.Element(kind, def)
	if type(def) == &amp;quot;string&amp;quot; then
		def = {def}
	end
	if def == nil then
		def = {}
	end

	local attr = {}
	for k, v in pairs(def) do
		if type(k) == &amp;quot;string&amp;quot; then
			local k = k:gsub(&amp;quot;_&amp;quot;, &amp;quot;-&amp;quot;)
			local v = tostring(v)
			table.insert(attr, {k, v})
		end
	end
	table.sort(attr, attr_cmp)

	-- open tag
	local el = {&amp;quot;&amp;lt;&amp;quot;, kind}
	for _, a in ipairs(attr) do
		write(el, &amp;quot; &amp;quot;, a[1], &amp;#39;=&amp;quot;&amp;#39;, escape_html(a[2]), &amp;#39;&amp;quot;&amp;#39;)
	end
	table.insert(el, &amp;quot;&amp;gt;&amp;quot;)

	if void_elements[kind] then
		return html.Html(table.concat(el))
	end

	-- children
	write_children(el, def)

	-- close tag
	write(el, &amp;quot;&amp;lt;/&amp;quot;, kind, &amp;quot;&amp;gt;&amp;quot;)

	return html.Html(table.concat(el))
end

function html.Document(def)
	return html.Html(&amp;quot;&amp;lt;!doctype html&amp;gt;&amp;quot;..tostring(html.Element(&amp;quot;html&amp;quot;, def)))
end

function html.__index(html, key)
	key = key:gsub(&amp;quot;_&amp;quot;, &amp;quot;-&amp;quot;)
	local function thunk(def)
		return html.Element(key, def)
	end
	html[key] = thunk -- memoise for future use; __index will not be called then
	return thunk
end

return html&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Idioms&lt;/h2&gt;&lt;p&gt;I hear you say, “riki, but this library is incomplete!
It doesn’t even have ifs and fors like Handlebars or maud do.”&lt;/p&gt;&lt;p&gt;Here is how you would implement ifs:&lt;/p&gt;&lt;p&gt;In short:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;For conditionally including text or HTML, you can use the Lua &lt;code&gt;cond and true_value or false_value&lt;/code&gt; idiom.&lt;/p&gt;
&lt;p&gt;Note that you don’t want any stray &lt;code&gt;nil&lt;/code&gt;s in the table, because that would break iteration via &lt;code&gt;ipairs&lt;/code&gt;—so you have to use an empty string or an empty table.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;For conditionally enabling attributes, you can write an &lt;code&gt;iif&lt;/code&gt; (&lt;em&gt;immediate if&lt;/em&gt;) function that returns one of two options.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;cond and t or f&lt;/code&gt; idiom I mentioned above can’t work due to &lt;code&gt;nil&lt;/code&gt; being false, and therefore the &lt;code&gt;or&lt;/code&gt; operator always taking the left branch—which will not work as expected if &lt;code&gt;cond == false&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;And here is how you would implement fors:&lt;/p&gt;&lt;p&gt;Of course, an immediately invoked function will also do fine, but having a &lt;code&gt;map&lt;/code&gt; function around in your codebase is really handy for transforming all kinds of data.&lt;/p&gt;&lt;hr/&gt;&lt;p&gt;And that’s about it, I think.
Hope you enjoyed the post!
It took me many hours to finish.&lt;/p&gt;&lt;p&gt;And I hope you learned something nice about Lua :3&lt;/p&gt;&lt;hr/&gt;&lt;p&gt;Thank you to my good friend Anya for giving me some thorough feedback on a draft of this post.&lt;/p&gt;</content:encoded>
</item>
<item>
<title>An experiment in vibe coding | Read the Tea Leaves</title>
<link>https://nolanlawson.com/2025/12/28/an-experiment-in-vibe-coding/</link>
<enclosure type="image/jpeg" length="0" url="https://nolanlawson.com/wp-content/uploads/2025/12/localhost_5173_itinerary_0pad60n22761h39ipad-pro-1.png?w=570"></enclosure>
<guid isPermaLink="false">HhDx_dmvHyah-64IZKHAmjSvn5JvAXqgMp_WBg==</guid>
<pubDate>Sun, 26 Apr 2026 17:37:21 +0000</pubDate>
<description>For the holidays, I gave myself a little experiment: build a small web app for my wife to manage her travel itineraries. I challenged myself to avoid editing the code myself and just do it “v…</description>
<content:encoded>&lt;p&gt;For the holidays, I gave myself a little experiment: build a small web app for my wife to manage her travel itineraries. I challenged myself to avoid editing the code myself and just do it “vibe” style, to see how far I could get.&lt;/p&gt;&lt;p&gt;In the end, the app was built with a $20 Claude “pro” plan and maybe ~5 hours of actual hands-on-keyboard work. Plus my wife is happy with the result, so I guess it was a success.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://nolanlawson.com/wp-content/uploads/2025/12/localhost_5173_itinerary_0pad60n22761h39ipad-pro-1.png?w=570&quot; alt=&quot;Screenshot of a travel itinerary app with a basic UI that looks like a lot of other CRUD apps, with a list of itinerary agenda items, dates and costs, etc.&quot; title=&quot;&quot;/&gt;&lt;/p&gt;&lt;p&gt;There are still a lot of flaws with this approach, though, so I thought I’d gather my experiences in this post.&lt;/p&gt;&lt;h2&gt;The good&lt;/h2&gt;&lt;p&gt;The app works. It looks okay on desktop and mobile, it works as a &lt;a href=&quot;https://en.wikipedia.org/wiki/Progressive_web_app&quot;&gt;PWA&lt;/a&gt;, it saves her itineraries to a small &lt;a href=&quot;https://pocketbase.io/&quot;&gt;PocketBase&lt;/a&gt; server running on &lt;a href=&quot;https://railway.com/&quot;&gt;Railway&lt;/a&gt; for $1 a month, and I can easily back up the database whenever I feel like it. User accounts can only be created by an admin user, which I manage with the PocketBase UI.&lt;/p&gt;&lt;p&gt;I first started with &lt;a href=&quot;https://bolt.new/&quot;&gt;Bolt.new&lt;/a&gt; but quickly switched to Claude Code. I found that Bolt was fine for the first iteration but quickly fell off after that. Every time I asked it to fix something and it failed (slowly), I thought “Claude Code could do this better.” Luckily you can just export from Bolt whenever you feel like it, so that’s what we did.&lt;/p&gt;&lt;p&gt;Bolt set up a pretty basic SPA scaffolding with Vite and React, which was fine, although I didn’t like its choice of Supabase, so I had Claude replace it with PocketBase. Claude was very helpful here with the ideation – I asked for some options on a good self-hosted database and went with PocketBase because it’s open-source and has the admin/auth stuff built-in. Plus it runs on SQLite, so this gave me confidence that import/export would be easy.&lt;/p&gt;&lt;p&gt;Claude also helped a lot with the hosting – I was waffling between a few different choices and eventually landed on Railway per Claude’s suggestion (for better or worse, this seems like a prime opportunity for ads/sponsorships in the future). Claude also helped me decipher the Railway interface and get the app up-and-running, in a way that helped me avoid reading their documentation altogether – all I needed to do was post screenshots and ask Claude where to click.&lt;/p&gt;&lt;p&gt;The app also uses Tailwind, which seems to come with decent CSS styles that look like every other website on the internet. I didn’t need this to win any design awards, so that was fine.&lt;/p&gt;&lt;p&gt;Note I also ran Claude in a Podman container with &lt;code&gt;--dangerously-skip-permissions&lt;/code&gt; (aka “yolo mode”) because I didn’t want to babysit it whenever it wanted permission to install or run something. Worst case scenario, an attacker has stolen the app code (meh), so hopefully I kept the &lt;a href=&quot;https://simonwillison.net/2025/Jun/16/the-lethal-trifecta/&quot;&gt;lethal trifecta&lt;/a&gt; in check.&lt;/p&gt;&lt;h2&gt;The bad&lt;/h2&gt;&lt;p&gt;Vibe-coding tools are decidedly &lt;em&gt;not&lt;/em&gt; ready for non-programmers yet. Initially I tried to just give Bolt to my wife and have her vibe her way through it, but she quickly got frustrated, despite having some experience with HTML, CSS, and WordPress. The LLM would make errors (as they do), but it would get caught in a loop, and nothing she tried could break it out of the cycle.&lt;/p&gt;&lt;p&gt;Since I have a lot of experience building web apps, I could look at the LLM’s mistakes and say, “Oh, this problem is in the backend.” Or “Oh, it should write a parser test for this.” Or, “Oh, it needs a screenshot so it can see why the CSS is wrong.” If you don’t have extensive debugging experience, then you might not be able to succinctly express the problem to an LLM like this. Being able to write detailed bug reports, or even have the right vocabulary to describe the problem, is an invaluable skill here.&lt;/p&gt;&lt;p&gt;After handing it over from Bolt to Claude Code and taking the reigns myself, though, I still ran into plenty of problems. First off, LLMs still suck at accessibility – lots of &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;s with &lt;code&gt;onClick&lt;/code&gt; all over the place. My wife is a sighted mouse user so it didn’t really matter, but I still have some professional pride even around vibe-coded garbage, so I told Claude to correct it. (At which point it promptly added excessive &lt;code&gt;aria-label&lt;/code&gt;s where they weren’t needed, so I told it to dial it back.) &lt;a href=&quot;https://localghost.dev/blog/ai-and-the-trouble-with-inaccessible-saas/&quot;&gt;I’m not the first to note this&lt;/a&gt;, but this really doesn’t bode well for accessible vibe-coded apps.&lt;/p&gt;&lt;p&gt;Another issue was performance. Even on a decent laptop (my Framework 13 with AMD Ryzen 5), I noticed a lot of slow interactions (typing, clicking) due to React re-rendering. This required a lot of back-and-forth with the agent, copy-pasting from the Chrome DevTools Performance tab and React DevTools Profiler, to get it to understand the problem and fix it with memoization and nested components.&lt;/p&gt;&lt;p&gt;At some point I realized I should just enable the &lt;a href=&quot;https://react.dev/learn/react-compiler&quot;&gt;React Compiler&lt;/a&gt;, and this may have helped but didn’t fully solve the problem. I’m frankly surprised at how bad React is for this use case, since a lot of people seem convinced that the framework wars are over, since LLMs are so “good” at writing React. The next time I try this, I might use a framework like Svelte or Solid where &lt;a href=&quot;https://docs.solidjs.com/advanced-concepts/fine-grained-reactivity&quot;&gt;fine-grained reactivity&lt;/a&gt; is built-in, and you don’t need a lot of manual optimizations for this kind of stuff.&lt;/p&gt;&lt;p&gt;Other than that, I didn’t run into any major problems that couldn’t be solved with the right prompting. For instance, to add PWA capabilities, it was enough to tell the LLM: “Make an icon that kind of looks like an airplane, generate the proper PNG sizes, here are the MDN docs on PWA manifests.” I did need to follow up by copy-pasting some error messages from the Chrome DevTools (which required even knowing to look in the Application tab), but that resolved itself quickly. I got it to generate a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP&quot;&gt;CSP&lt;/a&gt; in a similar way.&lt;/p&gt;&lt;p&gt;The only other annoying problem was the token limits – this is something I don’t have to deal with at work, and I was surprised how quickly I ran into limits using Claude as a side project. It made me tempted to avoid “plan mode” even when it would have been the better choice, and I often had to just set Claude aside and wait for my limit to “reset.”&lt;/p&gt;&lt;h2&gt;The ugly&lt;/h2&gt;&lt;p&gt;The ugliest part of all this is, of course, the cheapening of the profession as well as all the other ills of LLMs and GenAI that have been well-documented elsewhere. My contribution to this debate is just to document how I feel, which is that I’m somewhat horrified by how easily this tool can reproduce what took me 20-odd years to learn, but I’m also somewhat excited because it’s never been easier to just cobble together some quick POCs or lightweight hobby apps.&lt;/p&gt;&lt;p&gt;After a &lt;a href=&quot;https://nolanlawson.com/2025/12/22/how-i-use-ai-agents-to-write-code/&quot;&gt;couple&lt;/a&gt; &lt;a href=&quot;https://nolanlawson.com/2025/04/02/ai-ambivalence/&quot;&gt;posts&lt;/a&gt; on this topic, I’ve decided that my role is not to try to resist the overwhelming onslaught of this technology, but instead to just witness and document how it’s shaking up my worldview and my corner of the industry. Of course some will label me a collaborator, but I think those voices are increasingly becoming marginalized by an industry that has just normalized the use of generative AI to write code.&lt;/p&gt;&lt;p&gt;When I watch some of my younger colleagues work, I am astounded by how “AI-native” their behavior is. It infuses parts of their work where I still keep a distance. (E.g. my IDE and terminal are sacred to me – I like Claude Code in its little box, not in a &lt;a href=&quot;https://www.warp.dev/&quot;&gt;Warp&lt;/a&gt; terminal or as inline IDE completions.)&lt;/p&gt;&lt;h2&gt;Conclusion&lt;/h2&gt;&lt;p&gt;The most interesting part of this whole experiment, to me, is that throwing together this hobby app has removed the need for my wife to try some third-party service like &lt;a href=&quot;https://www.tripit.com/web&quot;&gt;TripIt&lt;/a&gt; or &lt;a href=&quot;https://wanderlog.com/&quot;&gt;Wanderlog&lt;/a&gt;. She tried those apps, but immediately became frustrated with bugs, missing features, and ad bloat. Whereas the app I built works exactly to her specification – and if she doesn’t like something, I can plug her feedback into Claude Code and have it fixed.&lt;/p&gt;&lt;p&gt;My wife is a power user, and she’s spent &lt;em&gt;a lot&lt;/em&gt; of time writing emails to the customer support departments of various apps, where she inevitably gets a “your feedback is very important to us” followed by zilch. She’s tried a lot of productivity/todo/planning apps, and she always finds some awful showstopper bugs (like memory leaks, errors copy/pasting, etc.), which I blame on our industry just not taking quality very seriously. Whereas if there’s a bug in this app, it’s a very small codebase, it’s got extensive unit/end-to-end tests, and so Claude doesn’t have many problems fixing tiny quality-of-life bugs.&lt;/p&gt;&lt;p&gt;I’m not saying this is the death-knell of small note-taking apps or whatever, but I definitely think that vibe-coded hobby apps have some advantages in this space. They don’t have to add 1,000 features to satisfy 1,000 different users (with all the bugs that inevitably come from the combinatorial explosion of features) – they just have to make one person happy. I still think that &lt;a href=&quot;https://research.google/blog/generative-ui-a-rich-custom-visual-interactive-user-experience-for-any-prompt/&quot;&gt;generative UI&lt;/a&gt; is kind of silly, because most users don’t want to wait seconds (or even minutes) for their UI to be built, but it does work well in this case (where your husband is a professional programmer with spare time during the holidays).&lt;/p&gt;&lt;p&gt;For my regular dayjob, I have no intention to do things fully “vibe-coded” (in the sense that I barely look at the code) – that’s just too risky and irresponsible in my opinion. When the code is complex, your teammates need to understand it, and you have paying customers, the bar is just a lot higher. But vibe coding is definitely useful for hobby or throwaway projects.&lt;/p&gt;&lt;p&gt;For better or worse, the value of code itself seems to be dropping precipitously, to be replaced by measures like how well an LLM can understand the codebase (&lt;code&gt;CLAUDE.md&lt;/code&gt;, &lt;code&gt;AGENTS.md&lt;/code&gt;) or how easily it can test its “fixes” (unit/integration tests). I have no idea what coding will look like next year, but I know how my wife will be planning our next vacation.&lt;/p&gt;&lt;div&gt;
&lt;div&gt;
	&lt;h3&gt;&lt;em&gt;Related&lt;/em&gt;&lt;/h3&gt;
&lt;/div&gt;&lt;/div&gt;</content:encoded>
</item>
<item>
<title>Sitemap with NextJS after 9.4 update.</title>
<link>https://priver.dev/post/sitemap-with-nextjs-after-9-4-update</link>
<guid isPermaLink="false">Cw5MCfxyxebYd1U3T7bYVIsZjvDj59Nyiu4O1w==</guid>
<pubDate>Sun, 26 Apr 2026 14:28:15 +0000</pubDate>
<description>Sitemap with NextJS after 9.4 update.</description>
<content:encoded>Sitemap with NextJS after 9.4 update.</content:encoded>
</item>
<item>
<title>Testing out Sveltia</title>
<link>https://akselmo.dev/posts/testing-out-sveltia/</link>
<guid isPermaLink="false">NAZS5BnW23pu_03Ehzek9t79NtzpoEYrIeekLg==</guid>
<pubDate>Sun, 26 Apr 2026 09:30:07 +0000</pubDate>
<description>I set up Sveltia headless CMS on my page, this is my first post with it. So I wanted to set up a simple headless CMS for my blog. Setting up Sveltia was rather quick and easy, though configuring it took a while. I have now set it all up and it was rather painless. The main reason I wanted this, while I mostly edit things in my text editor of choice anyway, I sometimes have something I want to share while out and about. So this is pretty much for mobile usage. :) And especially for sharing pho...</description>
<content:encoded>&lt;p&gt;I set up &lt;a href=&quot;https://sveltiacms.app/en/&quot;&gt;Sveltia headless CMS&lt;/a&gt; on my page, this is my first post with it.&lt;/p&gt;&lt;p&gt;So I wanted to set up a simple headless CMS for my blog. Setting up Sveltia was rather quick and easy, though configuring it took a while.&lt;/p&gt;&lt;p&gt;I have now set it all up and it was rather painless. The main reason I wanted this, while I mostly edit things in my &lt;a href=&quot;https://kate-editor.org/&quot;&gt;text editor of choice&lt;/a&gt; anyway, I sometimes have something I want to share while out and about. So this is pretty much for mobile usage. :) And especially for sharing photos, since I want to be less and less dependent on social media sites and more use my own blog, since I have one setup after all.&lt;/p&gt;&lt;p&gt;As I write this, the editor area is a bit cramped. The other fields take a lot of space, while I would rather have the editor area to take the full screen. But that might be fixable with some CSS I suppose. Will have to investigate that further.&lt;/p&gt;&lt;p&gt;Also if I want to do galleries, I will have to use some Zola magics that are easier to do in the text editor.&lt;/p&gt;&lt;div&gt;&lt;img src=&quot;https://akselmo.dev/assets/images/emotes/smile.png&quot; alt=&quot;My lizard fursona making an smile face.&quot; title=&quot;&quot;/&gt;&lt;p&gt;I also have my emote system which needs the Zola tags, so that the generator will handle it properly.&lt;/p&gt;&lt;/div&gt;&lt;p&gt;Anyway, setting the thing up was rather simple. I just had to make it talk with Codeberg, plop in a private key that is saved locally in my browser cache and that&amp;#39;s that. It&amp;#39;s just a simple website that shows editors and your pages, nothing extreme. Works for me rather well. And I appreciate the simplicity of it all, so I can recommend this. Feel free to look at my config I made for my Zola generated site if you&amp;#39;re interested: &lt;a href=&quot;https://codeberg.org/akselmo/aksdev_zola_blog/src/branch/main/static/admin/config.yml&quot;&gt;config.yml&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Maybe this will get me blog more, at least on the go. Sharing quick photos and the like, especially during Comiccon Finland next month. :)&lt;/p&gt;&lt;p&gt;Also shout out to &lt;a href=&quot;https://bubbles.town/&quot;&gt;Bubbles.town&lt;/a&gt;! I have been browsing it a bunch and it&amp;#39;s been quite interesting to see what people are up to. I am finding a lot of interesting things over there, and I think blogging is still one of the best ways to share things, more than regular social media sites.&lt;/p&gt;&lt;p&gt;Thanks for reading!&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://brid.gy/publish/mastodon&quot;&gt;&lt;/a&gt;&lt;/p&gt;</content:encoded>
</item>
<item>
<title>Polymarket : un soldat américain arrêté après avoir gagné 400 000 dollars sur la chute de Maduro</title>
<link>https://www.numerama.com/tech/2239955-polymarket-un-soldat-americain-arrete-apres-avoir-gagne-400-000-dollars-sur-la-chute-de-maduro.html</link>
<guid isPermaLink="false">UG6wD5iepT6A9HuMdmaj4FMU6DKemJAl9vjbcw==</guid>
<pubDate>Sat, 25 Apr 2026 08:48:21 +0000</pubDate>
<description>Un soldat américain impliqué dans l&#39;opération qui a conduit à la capture de Nicolás Maduro a été arrêté par le ministère de la Justice américain. Il est accusé d&#39;avoir utilisé des informations classifiées pour miser sur Polymarket quelques jours avant l&#39;annonce publique. Ses gains sont estimés à 409 000 dollars.</description>
<content:encoded>&lt;p&gt;&lt;img src=&quot;https://c0.lestechnophiles.com/www.numerama.com/wp-content/uploads/2026/01/design-sans-titre-2026-01-07t104525696.jpg?resize=1200,675&amp;amp;key=6a6bd27e&amp;amp;watermark&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;/p&gt;&lt;p&gt;
Un soldat américain impliqué dans l&amp;#39;opération qui a conduit à la capture de Nicolás Maduro a été arrêté par le ministère de la Justice américain. Il est accusé d&amp;#39;avoir utilisé des informations classifiées pour miser sur Polymarket quelques jours avant l&amp;#39;annonce publique. Ses gains sont estimés à 409 000 dollars. 
&lt;/p&gt;</content:encoded>
</item>
<item>
<title>« J’espère qu’il continuera à passer sous les radars » : comment LinkedIn a invité un agent IA sur scène avant de le bannir</title>
<link>https://www.numerama.com/tech/2239809-jespere-quil-continuera-a-passer-sous-les-radars-comment-linkedin-a-invite-un-agent-ia-sur-scene-avant-de-le-bannir.html</link>
<guid isPermaLink="false">bZOQNI8obBxNa-waTcqBkbqVjOWICLAhC-1MFg==</guid>
<pubDate>Sat, 25 Apr 2026 08:48:21 +0000</pubDate>
<description>Dans un article publié sur Wired, le journaliste américain Evan Ratliff raconte comment il a monté une startup entièrement pilotée par des agents IA. Parmi eux, on retrouve Kyle, CEO virtuel, devenu influenceur LinkedIn pendant cinq mois, jusqu&#39;à ce que la plateforme l&#39;invite à prendre la parole devant ses propres employés, puis le bannisse 36 heures plus tard.</description>
<content:encoded>&lt;p&gt;&lt;img src=&quot;https://c0.lestechnophiles.com/www.numerama.com/wp-content/uploads/2026/04/design-sans-titre-2026-04-24t110420014.jpg?resize=1200,675&amp;amp;key=bb6f44a9&amp;amp;watermark&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;/p&gt;&lt;p&gt;
Dans un article publié sur Wired, le journaliste américain Evan Ratliff raconte comment il a monté une startup entièrement pilotée par des agents IA. Parmi eux, on retrouve Kyle, CEO virtuel, devenu influenceur LinkedIn pendant cinq mois, jusqu&amp;#39;à ce que la plateforme l&amp;#39;invite à prendre la parole devant ses propres employés, puis le bannisse 36 heures plus tard.
&lt;/p&gt;</content:encoded>
</item>
<item>
<title>Adieu Microsoft : le Health Data Hub passe enfin sur un cloud français avec Scaleway</title>
<link>https://www.numerama.com/politique/2238809-adieu-microsoft-le-health-data-hub-passe-enfin-sur-un-cloud-francais-avec-scaleway.html</link>
<guid isPermaLink="false">c5HXsxRftj1WhRU3i8Xr-MLrLDWUVACfRSyKaw==</guid>
<pubDate>Sat, 25 Apr 2026 08:48:21 +0000</pubDate>
<description>Fini Microsoft Azure, place au cloud souverain. Après des années de controverse liée au risque d&#39;ingérence américaine, le Health Data Hub a officiellement annoncé confier l&#39;hébergement des données de santé des Français à l&#39;entreprise hexagonale Scaleway. L&#39;épilogue d&#39;un très long feuilleton politico-technologique.</description>
<content:encoded>&lt;p&gt;&lt;img src=&quot;https://c0.lestechnophiles.com/www.numerama.com/wp-content/uploads/2026/04/scaleway-microsoft-azure-hdh.jpg?resize=1200,675&amp;amp;key=abf996c1&amp;amp;watermark&quot; alt=&quot;scaleway microsoft azure hdh&quot; title=&quot;&quot;/&gt;&lt;/p&gt;&lt;p&gt;
Fini Microsoft Azure, place au cloud souverain. Après des années de controverse liée au risque d&amp;#39;ingérence américaine, le Health Data Hub a officiellement annoncé confier l&amp;#39;hébergement des données de santé des Français à l&amp;#39;entreprise hexagonale Scaleway. L&amp;#39;épilogue d&amp;#39;un très long feuilleton politico-technologique.
&lt;/p&gt;</content:encoded>
</item>
<item>
<title>Le redoutable Claude Mythos a donné des sueurs froides à Firefox, mais c’était pour son bien</title>
<link>https://www.numerama.com/tech/2238261-le-redoutable-claude-mythos-a-donne-des-sueurs-froides-a-firefox-mais-cetait-pour-son-bien.html</link>
<guid isPermaLink="false">qfwcNF3VJ8rb22lb17xDwGKzuch-zoFVbfL7nQ==</guid>
<pubDate>Sat, 25 Apr 2026 08:48:21 +0000</pubDate>
<description>Derrière les notes de mise à jour en apparence banales de Firefox 150 se cache un véritable séisme pour la cybersécurité. En s&#39;alliant avec la nouvelle IA d&#39;Anthropic, Mozilla a débusqué et corrigé près de 300 failles d&#39;un coup. Une avancée historique qui pourrait bien signer la fin des attaques « zero-day » et définitivement inverser le rapport de force entre pirates et défenseurs.</description>
<content:encoded>&lt;p&gt;&lt;img src=&quot;https://c0.lestechnophiles.com/www.numerama.com/wp-content/uploads/2026/04/firefox-mythos.jpg?resize=1200,675&amp;amp;key=6a4046c4&amp;amp;watermark&quot; alt=&quot;Firefox Mythos&quot; title=&quot;&quot;/&gt;&lt;/p&gt;&lt;p&gt;
Derrière les notes de mise à jour en apparence banales de Firefox 150 se cache un véritable séisme pour la cybersécurité. En s&amp;#39;alliant avec la nouvelle IA d&amp;#39;Anthropic, Mozilla a débusqué et corrigé près de 300 failles d&amp;#39;un coup. Une avancée historique qui pourrait bien signer la fin des attaques « zero-day » et définitivement inverser le rapport de force entre pirates et défenseurs.
&lt;/p&gt;</content:encoded>
</item>
<item>
<title>Que faire après un métier de graphiste ?</title>
<link>https://www.blogdumoderateur.com/que-faire-apres-metier-graphiste/</link>
<enclosure type="image/jpeg" length="0" url="https://f.hellowork.com/blogdumoderateur/2026/04/que-faire-apres-metier-graphiste-294x196.jpg"></enclosure>
<guid isPermaLink="false">Z3NZ_6TPywova6GhIRC-GD-CGNaDflS4wnspzA==</guid>
<pubDate>Fri, 24 Apr 2026 09:05:55 +0000</pubDate>
<description>Motion design, UX, direction artistique, intégration web… Après plusieurs années en tant que graphiste, les pistes d&#39;évolution ne manquent pas. Tour d’horizon des opportunités à saisir ou à envisager.</description>
<content:encoded>&lt;img src=&quot;https://f.hellowork.com/blogdumoderateur/2026/04/que-faire-apres-metier-graphiste-294x196.jpg&quot; alt=&quot;Que faire après un métier de graphiste ?&quot; title=&quot;&quot;/&gt;Motion design, UX, direction artistique, intégration web… Après plusieurs années en tant que graphiste, les pistes d&amp;#39;évolution ne manquent pas. Tour d’horizon des opportunités à saisir ou à envisager.</content:encoded>
</item>
<item>
<title>information - technology - firefox</title>
<link>https://sciops.net/information/technology/firefox</link>
<guid isPermaLink="false">rRm_5Fxu9W_5EA6QPpiKwFM0Xd8UorKTmNGgJg==</guid>
<pubDate>Fri, 24 Apr 2026 00:21:56 +0000</pubDate>
<description>Download my policies.json file and install it.  If the directory doesn&#39;t exist, you&#39;ll have to make it:</description>
<content:encoded>&lt;ol&gt;
&lt;li&gt;Download my &lt;a href=&quot;https://sciops.net/downloads/policies.json&quot;&gt;policies.json&lt;/a&gt; file and install it.  If the directory doesn&amp;#39;t exist, you&amp;#39;ll have to make it:
&lt;ul&gt;&lt;li&gt;Linux: &lt;code&gt;/etc/firefox/policies/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;OpenBSD: &lt;code&gt;/usr/local/lib/firefox/distribution/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;MacO&amp;#39;s: &lt;code&gt;Firefox.app/Contents/Resources/distribution/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Windows: stop using Windows.  Apparently you have to find firefox.exe and make a &lt;code&gt;distribution&lt;/code&gt; directory there and put the file there?  Toy OS.  Garbage.&lt;/li&gt;
&lt;/ul&gt;
If you want to verify that the policies file is working, open a new tab and type &lt;b&gt;about:policies&lt;/b&gt; into the URL bar.  You should see some settings similar to what&amp;#39;s in the file.
&lt;/li&gt;
&lt;li&gt;Go to &lt;a href=&quot;https://addons.mozilla.org/en-US/firefox/addon/ublock-origin/&quot;&gt;uBlock Origin&lt;/a&gt; and click &lt;b&gt;Add to Firefox&lt;/b&gt;&lt;br/&gt;This will filter out most of the advertisements on websites, saving you a shitload of network traffic (and if your computer is slow, not having to show all that crap is a big speedup).  Once you get it set up you can just ignore it, but if you care it will tell you how much stuff it&amp;#39;s blocked on your behalf.
&lt;ul&gt;&lt;li&gt;Bonus uBlock step:  Once installed, you can open uBlock&amp;#39;s preferences window, look at the &amp;#39;Filter lists&amp;#39; tab, and add new filter lists.  There&amp;#39;s a section &amp;#39;Cookie notices&amp;#39; which contains lists that try to block the stupid cookie consent banners.&lt;/li&gt;&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Go to &lt;a href=&quot;https://addons.mozilla.org/en-US/firefox/addon/localcdn-fork-of-decentraleyes/&quot;&gt;LocalCDN&lt;/a&gt; and click &lt;b&gt;Add to Firefox&lt;/b&gt;&lt;br/&gt;Most websites load the same files over and over from the same places -- primarily Google servers.  This thing puts all that right in your browser, making for less network traffic and denies Google the privilege of inspecting your usage patterns.  Once it&amp;#39;s installed you can ignore it.&lt;/li&gt;
&lt;li&gt;Open a new tab.  Click the gear icon in the upper-right corner and uncheck all of it.  If you don&amp;#39;t have the gear icon in a new tab, go to Settings -&amp;gt; Home and uncheck everything under &lt;b&gt;Firefox Home Content&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;Open a new tab and paste &lt;b&gt;about:preferences&lt;/b&gt; into the URL bar.
	&lt;ol&gt;
	&lt;li&gt;In &lt;b&gt;General&lt;/b&gt;, scroll to &lt;b&gt;Browsing&lt;/b&gt;:
		&lt;ol&gt;&lt;li&gt;Uncheck &amp;quot;Recommend extensions as you browse&amp;quot;&lt;/li&gt;
				&lt;li&gt;Uncheck &amp;quot;Recommend features as you browse&amp;quot;&lt;/li&gt;
		&lt;/ol&gt;
	&lt;/li&gt;
	&lt;li&gt;In &lt;b&gt;Home&lt;/b&gt;:
		&lt;ol&gt;&lt;li&gt;Set &amp;quot;Homepage and new windows&amp;quot; and &amp;quot;New Tabs&amp;quot; to &amp;quot;Blank Page&amp;quot;&lt;/li&gt;
				&lt;li&gt;Uncheck everything.&lt;/li&gt;
		&lt;/ol&gt;
	&lt;/li&gt;
	&lt;li&gt;In &lt;b&gt;Search&lt;/b&gt;:
		&lt;ol&gt;&lt;li&gt;Set your preferred search engine under &amp;quot;Default Search Engine.&amp;quot;&lt;/li&gt;
				&lt;li&gt;Uncheck &amp;quot;Show search suggestions.&amp;quot;&lt;/li&gt;
				&lt;li&gt;Uncheck &amp;quot;Show recent searches.&amp;quot;&lt;/li&gt;
			
				&lt;li&gt;Turn off &amp;quot;Improve the Firefox Suggest experience.&amp;quot;&lt;/li&gt;
				&lt;li&gt;Delete search engines you don&amp;#39;t like from &amp;quot;Search Shortcuts.&amp;quot;&lt;/li&gt;
		&lt;/ol&gt;
	&lt;/li&gt;
	&lt;li&gt;In &lt;b&gt;Privacy and Security&lt;/b&gt;:
		&lt;ol&gt;
		&lt;li&gt;Under &lt;b&gt;Enhanced Tracking Protection&lt;/b&gt;, set to &lt;b&gt;Strict.&lt;/b&gt;&lt;br/&gt;(It might break something, but I haven&amp;#39;t found what.)&lt;/li&gt;
		&lt;li&gt;Check &amp;quot;Tell websites not to sell or share my data.&amp;quot;&lt;/li&gt;
		
		&lt;li&gt;Scroll to &amp;quot;Firefox Data Collection and Use&amp;quot;:
			&lt;ol&gt;&lt;li&gt;Uncheck &lt;b&gt;Allow Firefox to send technical and interaction data to Mozilla.&lt;/b&gt; &lt;br/&gt;(Mozilla has never looked at this information, so it&amp;#39;s a waste of your bandwidth.)&lt;/li&gt;
					&lt;li&gt;Uncheck &lt;b&gt;Allow Firefox to install and run studies&lt;/b&gt; &lt;br/&gt;(This is how they test bad ideas on your computer.)&lt;/li&gt;
					
					&lt;li&gt;Uncheck &lt;b&gt;Send daily usage ping to Mozilla&lt;/b&gt;&lt;/li&gt;
			&lt;/ol&gt;
		&lt;/li&gt;
		&lt;li&gt;Scroll to &lt;b&gt;Website Advertising Preferences&lt;/b&gt;:	
			&lt;ol&gt;&lt;li&gt;Uncheck, if possible, &lt;b&gt;Allow websites to perform privacy-preserving ad measurement&lt;/b&gt;. &lt;br/&gt;&lt;span&gt;Firefox Mobile does not have this toggle.  See below for an alternative approach.&lt;/span&gt;&lt;br/&gt;(There is no privacy-preserving ad measurement.)&lt;/li&gt;
			&lt;/ol&gt;
		&lt;/li&gt;
		&lt;li&gt;Scroll to &lt;b&gt;Security&lt;/b&gt;:
			&lt;ol&gt;&lt;li&gt;Uncheck &lt;b&gt;Block dangerous and deceptive content.&lt;/b&gt; &lt;br/&gt;&lt;span&gt;Firefox Mobile does not have this toggle.  See below for an alternative approach.&lt;/span&gt;&lt;br/&gt;(It does this by checking your browsing against a list you have no control over and it&amp;#39;s a pain in the ass to inspect.)&lt;/li&gt;
					&lt;li&gt;Uncheck &lt;b&gt;Query OCSP responder servers...&lt;/b&gt;&lt;br/&gt;(This works by asking some third party about the sites you&amp;#39;re visiting.  The danger it protects you from is very rare and probably not worth sending your browser history to internet randos in realtime.)&lt;/li&gt;
			&lt;/ol&gt;
		&lt;/li&gt;
		&lt;/ol&gt;
	&lt;/li&gt;
  &lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Open a new tab and type &lt;b&gt;about:config&lt;/b&gt; into the URL bar.  Click &lt;b&gt;I accept the risk&lt;/b&gt;. &lt;br/&gt;&lt;span&gt;On Firefox Mobile, go to the url &lt;b&gt;chrome://geckoview/content/config.xhtml&lt;/b&gt; instead.&lt;/span&gt;&lt;/li&gt;
		&lt;ol&gt;
		&lt;li&gt;Paste &lt;b&gt;browser.send_pings&lt;/b&gt; into the search box.  Make sure the value is &lt;b&gt;false&lt;/b&gt;.&lt;br/&gt;
		(&amp;quot;Browser pings&amp;quot; exist only to track you.  There is no other reason for them to exist.  Anyone telling you otherwise is your enemy.)&lt;/li&gt;
		&lt;li&gt;Paste &lt;b&gt;beacon.enabled&lt;/b&gt; into the search box.  Make sure the value is &lt;b&gt;false&lt;/b&gt;.&lt;br/&gt;
		(These are almost indistinguishable from &amp;quot;browser pings&amp;quot; and are also only used for tracking you.)&lt;/li&gt;
		&lt;li&gt;Paste &lt;b&gt;browser.ml.chat.enabled&lt;/b&gt; into the search box.  Make sure the value is &lt;b&gt;false&lt;/b&gt;.&lt;br/&gt;
		(This turns off the AI garbage that Mozilla is inexplicably convinced anyone asked them for.)&lt;/li&gt;
		&lt;li&gt;Paste &lt;b&gt;datareporting.policy.dataSubmissionEnable&lt;/b&gt; into the search box.  Make sure the value is &lt;b&gt;false&lt;/b&gt;.&lt;br/&gt;
	  (This is a catchall.  Technically it overlaps with some other settings here, but better safe than sorry.)	&lt;/li&gt;
		&lt;li&gt;Paste &lt;b&gt;datareporting.healthreport.uploadEnabled&lt;/b&gt; into the search box.  Make sure the value is &lt;b&gt;false&lt;/b&gt;.&lt;br/&gt;
    (This is mozilla spying directly on you, collecting informationa about your browser performance.)&lt;/li&gt;
		&lt;li&gt;Unless you are blind, paste &lt;b&gt;accessibility.force_disabled&lt;/b&gt; into the search box.  Make sure the value is &lt;b&gt;1&lt;/b&gt;.&lt;br/&gt;
		(This feature is only of value if you use a screen reader.  Every other use of this feature is an attack.)&lt;/li&gt;
		&lt;li&gt;Paste &lt;b&gt;dom.serviceWorkers.enabled&lt;/b&gt; into the search box. Make sure the value is &lt;b&gt;false&lt;/b&gt;.&lt;br/&gt;
		(Service workers provide little user benefit and are frequently abused by ad networks. &lt;i&gt;If you are trying to use some fancy-ass web experience, this might break it.&lt;/i&gt;)&lt;/li&gt;
		&lt;li&gt;Paste &lt;b&gt;network.IDN_show_punycode&lt;/b&gt; into the search box.  Make sure the value is &lt;b&gt;true&lt;/b&gt;.&lt;br/&gt;
		(This prevents people using bullshit alphabets from showing lookalike domain names.)&lt;/li&gt;
		&lt;li&gt;Paste &lt;b&gt;geo.enabled&lt;/b&gt; into the search box.  Make sure the value is &lt;b&gt;false&lt;/b&gt;.&lt;br/&gt;
		&lt;/li&gt;&lt;li&gt;Paste &lt;b&gt;geo.provider.network.url&lt;/b&gt; into the search box.  Double-click to edit, empty it, and save.&lt;br/&gt;
		(This stops firefox from sending your wifi info to Google.)&lt;/li&gt;
		&lt;li&gt;Paste &lt;b&gt;network.http.speculative-parallel-limit&lt;/b&gt; into the search box.  Make sure the value is &lt;b&gt;0&lt;/b&gt;.&lt;br/&gt;
		(This stops Firefox from trying to guess what you&amp;#39;ll click next and downloading everything it guesses.)&lt;/li&gt;
		&lt;li&gt;Paste &lt;b&gt;network.predictor.enabled&lt;/b&gt; into the search box.  Make sure the value is &lt;b&gt;false&lt;/b&gt;.&lt;br/&gt;
		(More of the same.)&lt;/li&gt;
		&lt;li&gt;Paste &lt;b&gt;network.dns.disablePrefetch&lt;/b&gt; into the search box.  Make sure the value is &lt;b&gt;true&lt;/b&gt;.&lt;br/&gt;
		(Yep, this too.)&lt;/li&gt;
		&lt;li&gt;Paste &lt;b&gt;network.prefetch-next&lt;/b&gt; into the search box.  Make sure the value is &lt;b&gt;false&lt;/b&gt;.&lt;br/&gt;
		(Are you detecting a pattern)&lt;/li&gt;
		&lt;li&gt;Paste &lt;b&gt;extensions.pocket.enabled&lt;/b&gt; into the search box.  Make sure the value is &lt;b&gt;false&lt;/b&gt;.&lt;br/&gt;
		(This is Mozilla&amp;#39;s attempt to get you to save the contents of things you read to their servers.)&lt;/li&gt;
		&lt;li&gt;Paste &lt;b&gt;browser.urlbar.trimURLs&lt;/b&gt; into the search box.  Make sure the value is &lt;b&gt;false&lt;/b&gt;.&lt;br/&gt;
		(This setting hides part of the address you&amp;#39;ve loaded, because someone at Mozilla thought it was prettier that way.  We disable it because we don&amp;#39;t want the browser to lie to us.)&lt;/li&gt;
		&lt;/ol&gt;
		&lt;p&gt;Bonus round:  save battery by killing AI&lt;/p&gt;
		&lt;ol&gt;
		&lt;li&gt;Paste &lt;b&gt;browser.ml.linkPreview.enabled&lt;/b&gt; into the search box.  Make sure the value is &lt;b&gt;false&lt;/b&gt;.&lt;br/&gt;
		(This chews through your power and bandwidth to generate &amp;quot;previews&amp;quot; when you hover over a link.)&lt;/li&gt;
		&lt;li&gt;Paste &lt;b&gt;browser.ml.chat.enabled&lt;/b&gt; into the search box.  Make sure the value is &lt;b&gt;false&lt;/b&gt;.&lt;br/&gt;
		(Mozilla has, for some reason, shipped a chatbot in Firefox.  This turns it off.)&lt;/li&gt;
		&lt;li&gt;Paste &lt;b&gt;browser.ml.enable&lt;/b&gt; into the search box.  Make sure the value is &lt;b&gt;false&lt;/b&gt;.&lt;br/&gt;
		(This is a catchall which disables &amp;quot;AI&amp;quot; features.  It overlaps with some of the other settings here, but will hopefully stop future crap from running.)&lt;br/&gt;
		&lt;/li&gt;&lt;li&gt;Paste &lt;b&gt;browser.tabs.groups.smart.enabled&lt;/b&gt; into the search box.  Make sure the value is &lt;b&gt;false&lt;/b&gt;.&lt;br/&gt;
		(Mozilla would like to rearrange the UI from time to time.  This tells it not to.)&lt;/li&gt;
		&lt;li&gt;Paste &lt;b&gt;browser.tabs.groups.smart.userEnabled&lt;/b&gt; into the search box.  Make sure the value is &lt;b&gt;false&lt;/b&gt;.&lt;br/&gt;
		(This does the same as the last one, but flips a different switch.)&lt;/li&gt;
		&lt;li&gt;Paste &lt;b&gt;extensions.ml.enabled&lt;/b&gt; into the search box.  Make sure the value is &lt;b&gt;false&lt;/b&gt;.&lt;br/&gt;
		(This should disable the LLM plugin entirely.)&lt;/li&gt;
		&lt;/ol&gt;
&lt;li&gt;Things specific to Firefox Mobile&lt;br/&gt;Do these things in &lt;b&gt;chrome://geckoview/content/config.xhtml&lt;/b&gt;&lt;/li&gt;
		&lt;ol&gt;&lt;li&gt;To disable &amp;quot;Allow websites to perform privacy-preserving ad measurement,&amp;quot; set &lt;b&gt;dom.private-attribution.submission.enabled&lt;/b&gt; to &lt;b&gt;false&lt;/b&gt;.&lt;/li&gt;
				&lt;li&gt;To disable &amp;quot;Query OCSP responder servers,&amp;quot; set &lt;b&gt;security.OCSP.enabled&lt;/b&gt; to &lt;b&gt;0&lt;/b&gt;.&lt;/li&gt;
				&lt;li&gt;To disable &amp;quot;Block dangerous and deceptive content&amp;quot;, set &lt;b&gt;browser.safebrowsing.downloads.enabled&lt;/b&gt; to &lt;b&gt;false&lt;/b&gt;.&lt;/li&gt;
		&lt;/ol&gt;
&lt;/ol&gt;</content:encoded>
</item>
<item>
<title>Serving the For You Feed - AT Protocol</title>
<link>https://atproto.com/blog/serving-the-for-you-feed</link>
<enclosure type="image/jpeg" length="0" url="https://atproto.com/default-social-card.png"></enclosure>
<guid isPermaLink="false">9s9fJDTUy-N-WpyITPn9vhJUem3MF-OAf4oFnA==</guid>
<pubDate>Thu, 23 Apr 2026 19:43:59 +0000</pubDate>
<description>How the maintainer of the popular For You feed serves it from their living room!</description>
<content:encoded>&lt;p&gt;&lt;em&gt;We&amp;#39;re excited to publish another guest post highlighting development in the atproto ecosystem. Spacecowboy is the builder behind the popular For You feed, which serves personalized content to tens of thousands of users every day. In this post, Spacecowboy explains how they serve the For You feed from their living room, using a combination of local infrastructure and a VPS as a proxy.&lt;/em&gt;&lt;/p&gt;&lt;p&gt;In the spirit of &lt;a href=&quot;https://graze.leaflet.pub/3m7ecmjmhps23&quot;&gt;the post&lt;/a&gt; on how &lt;a href=&quot;https://www.graze.social/&quot;&gt;Graze.social&lt;/a&gt; serves their feeds, here is how I run &lt;a href=&quot;https://bsky.app/profile/did:plc:3guzzweuqraryl3rdkimjamk/feed/for-you&quot;&gt;💖For You&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;The logic of the feed is super simple: &amp;quot;It finds people who liked the same posts as you, and shows you what else they&amp;#39;ve liked recently&amp;quot;&lt;/p&gt;&lt;p&gt;For You is a single Go binary that does most things:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;consumes the firehose (Jetstream) of posts, likes, reposts and saves them into a sqlite database&lt;/li&gt;
&lt;li&gt;serves the feed&lt;/li&gt;
&lt;li&gt;serves the playground page &lt;a href=&quot;https://foryou.club/playground&quot;&gt;https://foryou.club/playground&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Being a single process makes it easy to manage.&lt;/p&gt;&lt;h2&gt;Database&lt;/h2&gt;&lt;p&gt;I store all data in sqlite. A key advantage of sqlite for me is testability - I can create the database in memory for unit testing purposes. I don&amp;#39;t need to mock or fake my storage layer. The setup/teardown is instant.&lt;/p&gt;&lt;p&gt;For querying the data, I use the excellent &lt;a href=&quot;https://sqlc.dev/&quot;&gt;sqlc.dev&lt;/a&gt;. It is a tool that gives me full control of the queries I want to run and takes care of the boilerplate through code generation.&lt;/p&gt;&lt;p&gt;To keep likes data memory-efficient (both on disk and in in-memory caches), I give each post AT URI an integer id:&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;sqlite&amp;gt; select \* from items order by id limit 10;
id uid metadata
7 at://did:plc:x5qqhu6n6jrwp3vulwia6eee/app.bsky.feed.post/3lmn2andjik2x
10 at://did:plc:hn2uy22exqcvxsh7mnikeevz/app.bsky.feed.post/3lmmj3x2oos2i
11 at://did:plc:4wep2s4udx4bqv774cq5wlsk/app.bsky.feed.post/3llh66exc2c2r
12 at://did:plc:kydd7ppawtffeqfvek5omiz4/app.bsky.feed.post/3lmn6tijcgk2z&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;Similarly for the users stored in the raters table:&lt;/p&gt;&lt;p&gt;The posts, likes, reposts are stored as an entry in the ratings table:&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;sqlite&amp;gt; select * from ratings limit 10;

id item_id rater_id created_timestamp
5981931671 747239155 556296 1768688218
5981931672 747111170 3677445 1768688218
5981931673 747226521 114350 1768688218
5981931674 745813473 1751615 1768688218
5981931675 718027268 2796254 1768688218
5981931676 747220721 2526 1768688218&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;I open the sqlite database with multiple connections (5) to allow concurrent reads. To avoid the dreaded &amp;quot;busy&amp;quot; error on writes, I enforce that only one thread can perform a write using a Go mutex. If you search online the most common suggestion for solving the &amp;quot;busy&amp;quot; error is &lt;code&gt;db.SetMaxOpenConns(1)&lt;/code&gt;, but that makes reads sequential too.&lt;/p&gt;&lt;p&gt;To put a limit on db size I store only the last 90 days of data. Every 24 hours a cleanup goroutine kicks in that deletes all ratings older than 90 days and then deletes all items that have no ratings pointing at them. The sqlite db file is still hefty at 419GB. I never run vacuum on it because it would be too slow (maybe a couple of hours) and would make the feed unavailable.&lt;/p&gt;&lt;h2&gt;Logging&lt;/h2&gt;&lt;p&gt;There is one more sqlite db - 200GB file storing response logs. It keeps track of which posts have been returned. When the user likes a post that was shown in For You, I notice this in the firehose and update the log entry. Same with &amp;quot;show more&amp;quot; and &amp;quot;show less&amp;quot; interactions. These logs are useful for analyzing how the feed is performing. And when I run an A/B test I can compare the performance of control vs treatment arms. I dump the logs from sqlite periodically into per-day parquet files like this:&lt;/p&gt;&lt;p&gt;&lt;code&gt;2.6G Apr 18 08:16 logs-2026-04-17.parquet&lt;/code&gt;&lt;/p&gt;&lt;p&gt;(as these parquet files accumulate I should probably move them to HDD)&lt;/p&gt;&lt;p&gt;I use duckdb to query these parquet files and build graphs like this:&lt;/p&gt;&lt;p&gt;and to generate A/B test reports:&lt;/p&gt;&lt;div&gt;&lt;blockquote&gt;&lt;p&gt;I have been running this test for more than a month and finished it today. The statistically significant result is that users are 2.6% less likely to press &amp;quot;show less like this&amp;quot; 🎉

- 5.7% more &amp;quot;show more&amp;quot; interactions: 41,425 -&amp;gt; 43,795 (+2,370)
- 5.2% fewer &amp;quot;show less&amp;quot;: 169,848 -&amp;gt; 160,938 (-8,910)&lt;/p&gt;— &lt;a href=&quot;https://bsky.app/profile/did:plc:3guzzweuqraryl3rdkimjamk?ref_src=embed&quot;&gt;spacecowboy (@spacecowboy17.bsky.social)&lt;/a&gt; &lt;a href=&quot;https://bsky.app/profile/did:plc:3guzzweuqraryl3rdkimjamk/post/3miu2xzs54k27?ref_src=embed&quot;&gt;2026-04-06T20:08:46.802Z&lt;/a&gt;&lt;/blockquote&gt;&lt;/div&gt;&lt;p&gt;and to build the user stats dashboard:&lt;/p&gt;&lt;div&gt;&lt;blockquote&gt;&lt;p&gt;Here is a very rough version of the For You Stats dashboard: linklonk.com/foryou

It shows you 3 things:
- how much you&amp;#39;ve been using For You
- how many views and likes your posts got in For You
- how your likes/reposts helped surface posts to other people in For You

Feedback welcome&lt;/p&gt;— &lt;a href=&quot;https://bsky.app/profile/did:plc:3guzzweuqraryl3rdkimjamk?ref_src=embed&quot;&gt;spacecowboy (@spacecowboy17.bsky.social)&lt;/a&gt; &lt;a href=&quot;https://bsky.app/profile/did:plc:3guzzweuqraryl3rdkimjamk/post/3mevupnpkrk2a?ref_src=embed&quot;&gt;2026-02-15T15:41:16.214Z&lt;/a&gt;&lt;/blockquote&gt;&lt;/div&gt;&lt;h2&gt;Caching&lt;/h2&gt;&lt;p&gt;For You makes very heavy use of in-process caches (&lt;a href=&quot;https://github.com/hashicorp/golang-lru&quot;&gt;https://github.com/hashicorp/golang-lru&lt;/a&gt;). I don&amp;#39;t need a separate service like Redis because all writers and readers of this cache are in a single process. Such caching is fast - no interprocess communication or serialization/deserialization overhead. Almost 100% of the data that is necessary to generate recommendations comes from the cache and not from the database.&lt;/p&gt;&lt;p&gt;The downside of in-process caches is that whenever I restart the feed process it becomes super slow until the caches are sufficiently warmed up.&lt;/p&gt;&lt;h2&gt;Hardware&lt;/h2&gt;&lt;p&gt;The feed runs in my living room on my &amp;quot;gaming&amp;quot; PC attached to the TV. Some specs:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;CPU: AMD 9950X3D - a 16-core processor with extra L3 cache
&lt;ul&gt;
&lt;li&gt;upgraded from 12-core 7900 in Dec 2025:
&lt;ul&gt;
&lt;li&gt;
&lt;div&gt;&lt;blockquote&gt;&lt;p&gt;The For You feed is going to be down while I&amp;#39;m upgrading the CPU from AMD 7900 (12 core) to AMD 9950X3D (16 core).

This should increase how many users it can serve by ~50%-100%.

If all goes well it should be back up in an hour or two.&lt;/p&gt;— &lt;a href=&quot;https://bsky.app/profile/did:plc:3guzzweuqraryl3rdkimjamk?ref_src=embed&quot;&gt;spacecowboy (@spacecowboy17.bsky.social)&lt;/a&gt; &lt;a href=&quot;https://bsky.app/profile/did:plc:3guzzweuqraryl3rdkimjamk/post/3mb2r5qei322a?ref_src=embed&quot;&gt;2025-12-28T16:40:05.960Z&lt;/a&gt;&lt;/blockquote&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The 3D cache gives &lt;a href=&quot;https://bsky.app/profile/did:plc:3guzzweuqraryl3rdkimjamk/post/3md7ast3iru2b&quot;&gt;~25% performance boost&lt;/a&gt; to the CCD with the cache&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;RAM: 96GB DDR5 6000MT/s
&lt;ul&gt;
&lt;li&gt;upgraded from 32GB back in June 2025 before the price crunch&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Storage:
&lt;ul&gt;
&lt;li&gt;2TB NVMe for the database&lt;/li&gt;
&lt;li&gt;2TB NVMe for the rest of the system&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Power backup: the PC, the Internet modem and the router are hooked up to an Ecoflow Delta 3, which would provide ~4-5 hours of runtime in case of a power outage&lt;/li&gt;
&lt;/ul&gt;&lt;h2&gt;The load&lt;/h2&gt;&lt;p&gt;Every day ~72K users load the feed at least once. On average, a user generates ~22 requests per day.&lt;/p&gt;&lt;p&gt;The traffic varies from 15 QPS to 25 QPS. At 25 QPS, the CPU is ~37% loaded (12 out of 32 threads).&lt;/p&gt;&lt;p&gt;The process consumes ~50GB of RAM - 99% of it is the caches.&lt;/p&gt;&lt;p&gt;Being CPU bound, this setup should be able to accommodate 3x more users.&lt;/p&gt;&lt;h2&gt;Future growth&lt;/h2&gt;&lt;p&gt;What if we get &amp;gt;3x more traffic? In that case, the feed notices that requests are taking longer to process and switches to a set of algorithm parameters that make the calculations significantly (&amp;gt;10x) cheaper with minimal quality degradation:&lt;/p&gt;&lt;div&gt;&lt;blockquote&gt;&lt;p&gt;With these parameters the CPU load went way down: from 22/24 cores being loaded to only 7/24.

This means there is a way to handle a lot more traffic.&lt;/p&gt;— &lt;a href=&quot;https://bsky.app/profile/did:plc:3guzzweuqraryl3rdkimjamk?ref_src=embed&quot;&gt;spacecowboy (@spacecowboy17.bsky.social)&lt;/a&gt; &lt;a href=&quot;https://bsky.app/profile/did:plc:3guzzweuqraryl3rdkimjamk/post/3mavsiffa3s2d?ref_src=embed&quot;&gt;2025-12-26T17:20:38.611Z&lt;/a&gt;&lt;/blockquote&gt;&lt;/div&gt;&lt;p&gt;It means we could be serving 30x more users which would be 72K*30=2.1M. The best available proxy for active Bluesky users is the daily count of users who&amp;#39;ve liked something. This metric has been stable at ~1M (&lt;a href=&quot;https://bsky.jazco.dev/stats&quot;&gt;https://bsky.jazco.dev/stats&lt;/a&gt;). Which means that the current setup could theoretically support all active users!&lt;/p&gt;&lt;h2&gt;Exposing the feed to the internet&lt;/h2&gt;&lt;p&gt;The local process serves &lt;code&gt;http://localhost:8090&lt;/code&gt; but you can&amp;#39;t access it from outside. To be publicly visible, I rent a small VPS on OVH.&lt;/p&gt;&lt;p&gt;I use Nginx to handle the incoming requests to &lt;code&gt;/xrpc/app.bsky.feed.getFeedSkeleton&lt;/code&gt; and &lt;code&gt;/xrpc/app.bsky.feed.sendInteractions&lt;/code&gt;. Nginx then proxies the request to a small Go process I call &amp;quot;dispatch&amp;quot;. Dispatch does a few things:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;It validates the JWT tokens. This process involves fetching the DID document. The host address of the document is controlled by the caller. I don&amp;#39;t want to make these requests from my home PC and reveal my IP address.&lt;/li&gt;
&lt;li&gt;I run multiple feeds - they run on different ports on my local machine, but they all have to share the same external endpoint: &lt;code&gt;/xrpc/app.bsky.feed.getFeedSkeleton&lt;/code&gt;. Dispatch proxies the request to the correct address. Both my home PC and the VPS are on the same Tailscale network, so dispatch makes requests to my PC as &lt;code&gt;http://gaming:8090&lt;/code&gt; for For You or &lt;code&gt;http://gaming:8093&lt;/code&gt; for Videos For You.&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;When the PC is down for maintenance, I can tell dispatch to return a canned post for all feed requests. Example:&lt;/p&gt;&lt;div&gt;&lt;blockquote&gt;&lt;p&gt;For You will be down for 10-20 minutes.

I want to change my RAM speed in bios from 4800 to 6000 in case it helps the feed load a bit faster.

For the last week I&amp;#39;ve been running Gnome from the fresh Ubuntu install and I need to go back to MATE.&lt;/p&gt;— &lt;a href=&quot;https://bsky.app/profile/did:plc:3guzzweuqraryl3rdkimjamk?ref_src=embed&quot;&gt;spacecowboy (@spacecowboy17.bsky.social)&lt;/a&gt; &lt;a href=&quot;https://bsky.app/profile/did:plc:3guzzweuqraryl3rdkimjamk/post/3mfwo27kqg22x?ref_src=embed&quot;&gt;2026-02-28T16:39:45.649Z&lt;/a&gt;&lt;/blockquote&gt;&lt;/div&gt;&lt;p&gt;All VPS services are run through docker-compose.&lt;/p&gt;&lt;h2&gt;Monitoring&lt;/h2&gt;&lt;p&gt;After a few outages, I&amp;#39;ve set up free uptime monitoring using &lt;a href=&quot;https://hetrixtools.com/&quot;&gt;https://hetrixtools.com/&lt;/a&gt;. They try to access &lt;a href=&quot;https://foryou.club/playground&quot;&gt;https://foryou.club/playground&lt;/a&gt; every minute, and if it becomes unavailable for &amp;gt;5 minutes, they send me an email, a phone call and a text. It&amp;#39;s a great service.&lt;/p&gt;&lt;p&gt;The uptime since January has been 99.77%.&lt;/p&gt;&lt;h2&gt;Running costs&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;~$20/month for the electricity - ~200W 24/7&lt;/li&gt;
&lt;li&gt;~$7/month for the VPS&lt;/li&gt;
&lt;li&gt;~$3/month for two domain names&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;If I were to rent a similar server, it would cost $245/month. But what fun is that?&lt;/p&gt;&lt;p&gt;I&amp;#39;m totally fine to cover the costs:&lt;/p&gt;&lt;div&gt;&lt;blockquote&gt;&lt;p&gt;Thanks everyone for offering to pitch in to support the For You feed!

I want to keep it as a pure hobby project with no financial side. I&amp;#39;m fine to do this indefinitely, so please don&amp;#39;t worry about the sustainability.&lt;/p&gt;— &lt;a href=&quot;https://bsky.app/profile/did:plc:3guzzweuqraryl3rdkimjamk?ref_src=embed&quot;&gt;spacecowboy (@spacecowboy17.bsky.social)&lt;/a&gt; &lt;a href=&quot;https://bsky.app/profile/did:plc:3guzzweuqraryl3rdkimjamk/post/3mawcu6otf22a?ref_src=embed&quot;&gt;2025-12-26T22:13:34.108Z&lt;/a&gt;&lt;/blockquote&gt;&lt;/div&gt;&lt;p&gt;If you can, consider supporting other feed service maintainers who need it much more:&lt;/p&gt;&lt;div&gt;&lt;blockquote&gt;&lt;p&gt;Folks have been reaching out about how to support Graze beyond our core product. We think that support needs to go beyond Graze. 

That&amp;#39;s why we&amp;#39;ve teamed up with our direct competitors @skyfeed.app and @blueskyfeedcreator.com to jointly support all our work:

&lt;/p&gt;— &lt;a href=&quot;https://bsky.app/profile/did:plc:i6y3jdklpvkjvynvsrnqfdoq?ref_src=embed&quot;&gt;Graze Social (@graze.social)&lt;/a&gt; &lt;a href=&quot;https://bsky.app/profile/did:plc:i6y3jdklpvkjvynvsrnqfdoq/post/3mih3t7jcjo2t?ref_src=embed&quot;&gt;2026-04-01T16:19:22.122524+00:00&lt;/a&gt;&lt;/blockquote&gt;&lt;/div&gt;&lt;div&gt;&lt;blockquote&gt;&lt;p&gt;Hi! SkyFeed is struggling and I need to find a solution.

If you don&amp;#39;t know SkyFeed, it&amp;#39;s a service I built 2 years ago, enabling anyone to build and publish feeds using a visual block-based editor.

SkyFeed is still hosting the most feeds on Bluesky, but that comes at a cost [1/X]&lt;/p&gt;— &lt;a href=&quot;https://bsky.app/profile/did:plc:odo2zkpujsgcxtz7ph24djkj?ref_src=embed&quot;&gt;redsolver (@redsolver.dev)&lt;/a&gt; &lt;a href=&quot;https://bsky.app/profile/did:plc:odo2zkpujsgcxtz7ph24djkj/post/3mgq76goqes2v?ref_src=embed&quot;&gt;2026-03-10T20:22:54.408Z&lt;/a&gt;&lt;/blockquote&gt;&lt;/div&gt;&lt;p&gt;&lt;em&gt;I think this provides a great example of how you can run a service on local infrastructure, combined with a VPS proxy. It&amp;#39;s a cost-effective way to serve a feed to a large number of users without needing to invest in expensive cloud infrastructure.&lt;/em&gt;&lt;/p&gt;&lt;p&gt;&lt;em&gt;Thanks for reading! And look forward to more posts featuring feed builders.&lt;/em&gt;&lt;/p&gt;</content:encoded>
</item>
<item>
<title>Les chiffres clés d’Internet et des réseaux sociaux dans le monde en avril 2026</title>
<link>https://www.blogdumoderateur.com/chiffres-cles-internet-reseaux-sociaux-monde-avril-2026/</link>
<enclosure type="image/jpeg" length="0" url="https://f.hellowork.com/blogdumoderateur/2026/04/Global-Headlines-Data-Digital-Report-April-2026-294x165.jpg"></enclosure>
<guid isPermaLink="false">RhmPozyeddNA2czv6AguFpXIc_F7QpR8mbkHww==</guid>
<pubDate>Thu, 23 Apr 2026 12:49:42 +0000</pubDate>
<description>Quels sont les nouveaux usages d’Internet, des réseaux sociaux et de l’IA en avril 2026 ? Les réponses avec la dernière mise à jour du rapport publié par We Are Social et le cabinet Manochi.</description>
<content:encoded>&lt;img src=&quot;https://f.hellowork.com/blogdumoderateur/2026/04/Global-Headlines-Data-Digital-Report-April-2026-294x165.jpg&quot; alt=&quot;Les chiffres clés d’Internet et des réseaux sociaux dans le monde en avril 2026&quot; title=&quot;&quot;/&gt;Quels sont les nouveaux usages d’Internet, des réseaux sociaux et de l’IA en avril 2026 ? Les réponses avec la dernière mise à jour du rapport publié par We Are Social et le cabinet Manochi.</content:encoded>
</item>
<item>
<title>Web Lookup Tools « Ben&#39;s IR Notes</title>
<link>https://benleeyr.wordpress.com/2026/04/17/web-lookup-tools/</link>
<enclosure type="image/jpeg" length="0" url="https://s0.wp.com/i/blank.jpg?m=1383295312i"></enclosure>
<guid isPermaLink="false">-HYj_63ChqbskGTTs2tkOofHDVQ40H89gW98bw==</guid>
<pubDate>Thu, 23 Apr 2026 12:49:32 +0000</pubDate>
<description>General tool Check if IP has been reported for abuse before www.abuseipdb.com ASN lookup by company name Look for domains similar to your domain</description>
<content:encoded>&lt;html&gt;

&lt;body&gt;

&lt;div&gt;
		&lt;header&gt;
		&lt;div&gt;
			&lt;h1&gt;&lt;a href=&quot;https://benleeyr.wordpress.com/&quot;&gt;Ben&amp;#39;s IR Notes&lt;/a&gt;&lt;/h1&gt;
			
		&lt;/div&gt;
	&lt;/header&gt;

	

	&lt;div&gt;
		&lt;div&gt;
			&lt;div&gt;

			
				

				
&lt;article&gt;
	&lt;header&gt;
		&lt;h1&gt;Web Lookup Tools&lt;/h1&gt;
		&lt;div&gt;
			&lt;a href=&quot;https://benleeyr.wordpress.com/2026/04/17/web-lookup-tools/&quot;&gt;
				&lt;time&gt;Apr&lt;b&gt;17&lt;/b&gt;&lt;/time&gt;
			&lt;/a&gt;
			&lt;span&gt;
			by &lt;span&gt;&lt;a href=&quot;https://benleeyr.wordpress.com/author/benleeyr/&quot;&gt;benleeyr&lt;/a&gt;&lt;/span&gt;			&lt;/span&gt;
		&lt;/div&gt;
	&lt;/header&gt;

	&lt;div&gt;
		
&lt;p&gt;&lt;strong&gt;General tool&lt;/strong&gt;&lt;/p&gt;



&lt;p&gt;&lt;a href=&quot;https://whois.domaintools.com&quot;&gt;https://whois.domaintools.com&lt;/a&gt;&lt;/p&gt;







&lt;p&gt;&lt;strong&gt;Check if IP has been reported for abuse before&lt;/strong&gt;&lt;/p&gt;



&lt;p&gt;&lt;a href=&quot;http://www.abuseipdb.com&quot;&gt;http://www.abuseipdb.com&lt;/a&gt;&lt;/p&gt;







&lt;p&gt;&lt;strong&gt;ASN lookup by company name&lt;/strong&gt;&lt;/p&gt;



&lt;p&gt;&lt;a href=&quot;https://hackertarget.com/as-ip-lookup/&quot;&gt;https://hackertarget.com/as-ip-lookup/&lt;/a&gt;&lt;/p&gt;







&lt;p&gt;&lt;strong&gt;Look for domains similar to your domain&lt;/strong&gt;&lt;/p&gt;



&lt;p&gt;&lt;a href=&quot;https://dnstwister.report&quot;&gt;https://dnstwister.report&lt;/a&gt;&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;h3&gt;Share this:&lt;/h3&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://benleeyr.wordpress.com/2026/04/17/web-lookup-tools/?share=twitter&quot;&gt;
				&lt;span&gt;Share on X (Opens in new window)&lt;/span&gt;
				&lt;span&gt;X&lt;/span&gt;
			&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://benleeyr.wordpress.com/2026/04/17/web-lookup-tools/?share=facebook&quot;&gt;
				&lt;span&gt;Share on Facebook (Opens in new window)&lt;/span&gt;
				&lt;span&gt;Facebook&lt;/span&gt;
			&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;Like&lt;/span&gt;&lt;/span&gt; &lt;span&gt;Loading...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div&gt;
	&lt;h3&gt;&lt;em&gt;Related&lt;/em&gt;&lt;/h3&gt;
&lt;/div&gt;&lt;/div&gt;			&lt;/div&gt;

	&lt;footer&gt;
		This entry was posted in &lt;a href=&quot;https://benleeyr.wordpress.com/category/uncategorized/&quot;&gt;Uncategorized&lt;/a&gt; and tagged &lt;a href=&quot;https://benleeyr.wordpress.com/tag/web/&quot;&gt;Web&lt;/a&gt;.
			&lt;/footer&gt;
&lt;/article&gt;

				

				
&lt;div&gt;

	
	
		&lt;div&gt;
		&lt;h3&gt;Leave a comment &lt;small&gt;&lt;a href=&quot;https://benleeyr.wordpress.com/2026/04/17/web-lookup-tools/#respond&quot;&gt;Cancel reply&lt;/a&gt;&lt;/small&gt;&lt;/h3&gt;


&lt;div&gt;


			
			&lt;/div&gt;&lt;p&gt;Δ
&lt;/p&gt;	&lt;/div&gt;
	
&lt;/div&gt;
			
			&lt;/div&gt;
		&lt;/div&gt;

&lt;div&gt;
		
		&lt;/div&gt;

	&lt;/div&gt;

	&lt;footer&gt;
		&lt;div&gt;
			&lt;a href=&quot;https://wordpress.com/?ref=footer_website&quot;&gt;Create a free website or blog at WordPress.com.&lt;/a&gt;		&lt;/div&gt;
	&lt;/footer&gt;
&lt;/div&gt;







	

		&lt;div&gt;
	&lt;/div&gt;
		
		

		
		&lt;div&gt;
&lt;div&gt;
	
		

		Privacy &amp;amp; Cookies: This site uses cookies. By continuing to use this website, you agree to their use. &lt;br/&gt;
To find out more, including how to control cookies, see here:
				&lt;a href=&quot;https://automattic.com/cookies/&quot;&gt;
			Cookie Policy		&lt;/a&gt;
 
&lt;/div&gt;
&lt;/div&gt;		&lt;div&gt;
		&lt;ul&gt;
								&lt;li&gt;
						&lt;a href=&quot;https://benleeyr.wordpress.com/2026/04/17/web-lookup-tools/#respond&quot;&gt;
														&lt;span&gt;Comment						&lt;/span&gt;
						&lt;/a&gt;
					&lt;/li&gt;
									&lt;li&gt;
						
							&lt;span&gt;Reblog&lt;/span&gt;
						
					&lt;/li&gt;
									&lt;li&gt;
								
			
			&lt;span&gt;Subscribe&lt;/span&gt;
		
		
			
			&lt;span&gt;Subscribed&lt;/span&gt;
		
							&lt;div&gt;
							
							&lt;div&gt;
															&lt;ul&gt;
											&lt;li&gt;
			&lt;a href=&quot;https://benleeyr.wordpress.com&quot;&gt;
				&lt;img src=&quot;https://s2.wp.com/i/logo/wpcom-gray-white.png?m=1479929237i&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;				Ben&amp;#39;s IR Notes			&lt;/a&gt;
		&lt;/li&gt;
										
									
																				&lt;div&gt;
										
										&lt;/div&gt;
										
										
										
										
																				&lt;div&gt;
											
										&lt;/div&gt;
									
									&lt;li&gt;
										&lt;div&gt;
											Already have a WordPress.com account? &lt;a href=&quot;https://wordpress.com/log-in?redirect_to=https%3A%2F%2Fbenleeyr.wordpress.com%2F2026%2F04%2F17%2Fweb-lookup-tools%2F&amp;amp;signup_flow=account&quot;&gt;Log in now.&lt;/a&gt;										&lt;/div&gt;
									&lt;/li&gt;
								&lt;/ul&gt;
															&lt;/div&gt;
						&lt;/div&gt;
					&lt;/li&gt;
							&lt;li&gt;
								&lt;div&gt;
					
					&lt;div&gt;
						&lt;ul&gt;
								&lt;li&gt;
			&lt;a href=&quot;https://benleeyr.wordpress.com&quot;&gt;
				&lt;img src=&quot;https://s2.wp.com/i/logo/wpcom-gray-white.png?m=1479929237i&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;				Ben&amp;#39;s IR Notes			&lt;/a&gt;
		&lt;/li&gt;
								&lt;li&gt;
										
			
			&lt;span&gt;Subscribe&lt;/span&gt;
		
		
			
			&lt;span&gt;Subscribed&lt;/span&gt;
		
								&lt;/li&gt;
														&lt;li&gt;&lt;a href=&quot;https://wordpress.com/start/&quot;&gt;Sign up&lt;/a&gt;&lt;/li&gt;
							&lt;li&gt;&lt;a href=&quot;https://wordpress.com/log-in?redirect_to=https%3A%2F%2Fbenleeyr.wordpress.com%2F2026%2F04%2F17%2Fweb-lookup-tools%2F&amp;amp;signup_flow=account&quot;&gt;Log in&lt;/a&gt;&lt;/li&gt;
																&lt;li&gt;
										&lt;a href=&quot;https://wp.me/p2Oyz4-gr&quot;&gt;
											&lt;span&gt;Copy shortlink&lt;/span&gt;
											
										&lt;/a&gt;
									&lt;/li&gt;
																&lt;li&gt;
									&lt;a href=&quot;https://wordpress.com/abuse/?report_url=https://benleeyr.wordpress.com/2026/04/17/web-lookup-tools/&quot;&gt;
										Report this content									&lt;/a&gt;
								&lt;/li&gt;
															&lt;li&gt;
									&lt;a href=&quot;https://wordpress.com/reader/blogs/41601942/posts/1019&quot;&gt;
										View post in Reader									&lt;/a&gt;
								&lt;/li&gt;
															&lt;li&gt;
									&lt;a href=&quot;https://subscribe.wordpress.com/&quot;&gt;Manage subscriptions&lt;/a&gt;
								&lt;/li&gt;
																&lt;li&gt;Collapse this bar&lt;/li&gt;
														&lt;/ul&gt;
					&lt;/div&gt;
				&lt;/div&gt;
			&lt;/li&gt;
		&lt;/ul&gt;
	&lt;/div&gt;
	


	
	
				
	












	
	&lt;div&gt;&lt;div&gt;&lt;span&gt;%d&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
	 


		 	&lt;div&gt;&lt;div&gt;Design a site like this with WordPress.com&lt;/div&gt;&lt;a href=&quot;https://wordpress.com/start/?ref=marketing_bar&quot;&gt;Get started&lt;/a&gt;&lt;a href=&quot;https://wordpress.com/start/?ref=marketing_bar&quot;&gt;&lt;/a&gt;&lt;/div&gt;		



&lt;/body&gt;&lt;/html&gt;</content:encoded>
</item>
<item>
<title>WCAG3 Contrast as of April 2026 — Adrian Roselli</title>
<link>https://adrianroselli.com/2026/04/wcag3-contrast-as-of-april-2026.html</link>
<enclosure type="image/jpeg" length="0" url="https://adrianroselli.com/wp-content/uploads/2026/04/w3c-logo_eyedropper-300x300.png"></enclosure>
<guid isPermaLink="false">B5RPwxwWZdHJGMqyD-yh6XWGtaSkFL19K8rquA==</guid>
<pubDate>Thu, 23 Apr 2026 11:24:21 +0000</pubDate>
<description>I am not speaking on behalf of the W3C nor the W3C Accessibility Guidelines Working Group (AGWG), nor am I a member, nor does anyone who is member know I am writing this, nor do I have any insider knowledge. For years I have seen people, teams, products, organizations, and…</description>
<content:encoded>&lt;h1&gt;WCAG3 Contrast as of April 2026&lt;/h1&gt;&lt;p&gt;
For years I have seen people, teams, products, organizations, and projects promoting &lt;a href=&quot;https://apcacontrast.com/&quot;&gt;APCA contrast&lt;/a&gt; as &lt;strong&gt;&lt;em&gt;the&lt;/em&gt;&lt;/strong&gt; WCAG3 color contrast algorithm. In many of those cases, those same actors have suggested dismissing the contrast algorithm in WCAG2.
&lt;/p&gt;&lt;p&gt;
This kind of problematic advice happened so often that I started filing issues with tool makers to remove APCA, as with &lt;a href=&quot;https://issues.chromium.org/u/1/issues/341439947&quot;&gt;this May 2024 Chromium issue&lt;/a&gt;.
&lt;/p&gt;&lt;p&gt;
In January 2025 I saw a post promoting that advice, which would create legal risk for anyone following it. You can see my &lt;a href=&quot;https://toot.cafe/@aardrian/113801092971854851&quot;&gt;original 2025 response on Mastodon&lt;/a&gt; and the &lt;a href=&quot;https://bsky.app/profile/aardrian.bsky.social/post/3lfdxgqdblc27&quot;&gt;same post on Bluesky&lt;/a&gt;, where the creator of APCA also weighed in. Read those to ensure you aren’t just taking my word for it.
&lt;/p&gt;&lt;h2&gt;Current Situation&lt;/h2&gt;&lt;p&gt;
Here is the most current (as of this writing) contrast ratio algorithm in WCAG3 with the salient bit highlighted:
&lt;/p&gt;&lt;blockquote&gt;
 &lt;dl&gt;
  &lt;dt&gt; &lt;dfn&gt;contrast ratio test &lt;span&gt;Exploratory&lt;/span&gt;&lt;/dfn&gt; &lt;/dt&gt;
  &lt;dd&gt;&lt;p&gt;meeting a sufficient level of contrast between two colors using the relationship of hue, saturation, and lightness values&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;Editor’s note&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;&lt;mark&gt;The contrast algorithm used in WCAG 3 is yet to be determined.&lt;/mark&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/dd&gt;
 &lt;/dl&gt;
 &lt;footer&gt;&lt;cite&gt;&lt;a href=&quot;https://w3c.github.io/wcag3/guidelines/#dfn-contrast-ratio-test&quot;&gt;contrast ratio definition&lt;/a&gt; in W3C Accessibility Guidelines (WCAG) 3.0 Editor’s Draft from 8 April 2026&lt;/cite&gt;&lt;/footer&gt;
&lt;/blockquote&gt;&lt;p&gt;
APCA was marked for removal in early 2023:
&lt;/p&gt;&lt;blockquote&gt;
 &lt;p&gt;
 Exploratory content that does not gain WG support to proceed to the next stage within 6 months is automatically removed in the &lt;a href=&quot;https://www.w3.org/WAI/GL/wiki/Maturity_Labeling_Process&quot;&gt;WCAG 3 development process&lt;/a&gt;. We still hope the promising contrast work will continue to mature for potential later inclusion.
 &lt;/p&gt;
 &lt;footer&gt;&lt;cite&gt;#663 Visual contrast, &lt;a href=&quot;https://github.com/w3c/silver/pull/663#issuecomment-1408914402&quot;&gt;comment from Michael Cooper&lt;/a&gt;, 30 January 2023
&lt;/cite&gt;&lt;/footer&gt;
&lt;/blockquote&gt;&lt;p&gt;
As with other WCAG3 ideas that did not get working group support, the working group pulled it from the &lt;a href=&quot;https://www.w3.org/TR/2023/WD-wcag-3.0-20230724/#color-and-contrast&quot;&gt;July 2023 WCAG3 working draft&lt;/a&gt;. APCA was only ever exploratory, and the &lt;a href=&quot;https://web.archive.org/web/20230726204602/https://www.w3.org/WAI/standards-guidelines/wcag/wcag3-intro/#status-exploratory-draft&quot;&gt;W3C’s July 2023 update explained removal of exploratory items&lt;/a&gt;. The APCA creator’s response to Michael Cooper’s comment acknowledged that APCA versions in the draft &lt;q&gt;were all &lt;strong&gt;&lt;em&gt;early versions that were very obsolete&lt;/em&gt;&lt;/strong&gt;&lt;/q&gt; (emphasis theirs).
&lt;/p&gt;&lt;p&gt;
I have no idea if APCA, whatever version, will come back to WCAG3.
&lt;/p&gt;&lt;h2&gt;What to Do?&lt;/h2&gt;&lt;p&gt;
&lt;img src=&quot;https://adrianroselli.com/wp-content/uploads/2026/04/w3c-logo_eyedropper.png&quot; alt=&quot;The W3C logo with a frog-headed eyedropper over it.&quot; title=&quot;&quot;/&gt;
You can still use APCA (whatever version). If you do, limit your risk by taking one of these approaches:
&lt;/p&gt;&lt;ul&gt;
 &lt;li&gt;Choose colors that &lt;em&gt;also&lt;/em&gt; conform to WCAG2 contrast requirements; or&lt;/li&gt;
 &lt;li&gt;If you pick colors that fail WCAG2 contrast requirements, document it and have a ready response should you receive a drive-by lawsuit from a bad actor using an automated checker to find a contrast violation (assuming your attorney lets you do this).&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;
I am still not a (bird) lawyer.
&lt;/p&gt;&lt;p&gt;
In the meantime, &lt;a href=&quot;https://www.w3.org/WAI/standards-guidelines/wcag/wcag3-intro/#timeline&quot;&gt;WCAG3 is years away from being done&lt;/a&gt;, perhaps 2030 at the soonest. Any contrast algorithm it picks won’t be final until at least then. But if you like whatever is proposed and want to use it before 2030, then probably refer to my two approaches above.
&lt;/p&gt;&lt;article&gt;

			&lt;h2&gt;3 Comments&lt;/h2&gt;


			
			&lt;div&gt;

				&lt;blockquote&gt;
					&lt;div&gt;
						&lt;header&gt;
						
						
						&lt;a href=&quot;https://adrianroselli.com/2026/04/wcag3-contrast-as-of-april-2026.html?replytocom=407328#respond&quot;&gt;Reply&lt;/a&gt;
						&lt;/header&gt;


					&lt;p&gt;Lawsuit?? There’s a page on gov-uk about this, I had no idea there were legal requirements for accessibility before now.&lt;/p&gt;


					&lt;/div&gt;
					&lt;footer&gt;
						&lt;cite&gt;Aaron&lt;/cite&gt;;
						&lt;time&gt;11 April 2026 at 2:22 am&lt;/time&gt;.
						&lt;a href=&quot;https://adrianroselli.com/2026/04/wcag3-contrast-as-of-april-2026.html#comment-407328&quot;&gt;Permalink&lt;/a&gt;
					&lt;/footer&gt;

				&lt;/blockquote&gt;


			&lt;div&gt;

				&lt;blockquote&gt;
					&lt;div&gt;
						&lt;header&gt;
						In response to &lt;a href=&quot;https://adrianroselli.com/2026/04/wcag3-contrast-as-of-april-2026.html#comment-407328&quot;&gt;Aaron&lt;/a&gt;.
						
						&lt;a href=&quot;https://adrianroselli.com/2026/04/wcag3-contrast-as-of-april-2026.html?replytocom=407432#respond&quot;&gt;Reply&lt;/a&gt;
						&lt;/header&gt;


					&lt;p&gt;Without knowing where you are in the world, I suggest looking at Lainey Feingold’s &lt;a href=&quot;https://www.lflegal.com/global-law-and-policy/&quot;&gt;Global Law and Policy&lt;/a&gt; page. It’s a reasonably current collection of digital accessibility laws around the world, which often reference a technical standard (typically WCAG).&lt;/p&gt;


					&lt;/div&gt;
					&lt;footer&gt;
						&lt;cite&gt;&lt;a href=&quot;https://adrianroselli.com/&quot;&gt;Adrian Roselli&lt;/a&gt;&lt;/cite&gt;;
						&lt;time&gt;11 April 2026 at 9:27 am&lt;/time&gt;.
						&lt;a href=&quot;https://adrianroselli.com/2026/04/wcag3-contrast-as-of-april-2026.html#comment-407432&quot;&gt;Permalink&lt;/a&gt;
					&lt;/footer&gt;

				&lt;/blockquote&gt;

&lt;/div&gt;
&lt;/div&gt;

			&lt;div&gt;

				&lt;blockquote&gt;
					&lt;div&gt;
						&lt;header&gt;
						
						
						&lt;a href=&quot;https://adrianroselli.com/2026/04/wcag3-contrast-as-of-april-2026.html?replytocom=408186#respond&quot;&gt;Reply&lt;/a&gt;
						&lt;/header&gt;


					&lt;p&gt;Hi Adrian, thanks for the update. A couple of clarifications for your readers.&lt;/p&gt;
&lt;p&gt;I want to be completely clear: APCA is draft guidance we are actively testing and evaluating. This is not a final standard, and no one should be calling it “WCAG 3” — I’ve said so many times. The W3C a11y guidelines working group doesn’t conduct research—that is what we are doing at Inclusive Reading Technologies.&lt;/p&gt;
&lt;p&gt;You’re right that nobody should drop WCAG 2 conformance based on a draft, no disagreement there. That said, the current algorithm and guidelines reflect years of work, independent validation, and peer-reviewed journal-published papers. The APC-RC guidelines all refer to well established peer reviewed research. In practice, colors that pass APCA for a given font size and weight greatly exceed WCAG 2’s minimums in the vast majority of cases. To be clear, nothing prevents APCA from being “backwards compatible” at all, that relates to the actual guidelines, not the math. The controversy, if you can call it that, are ranges of colors allowed or mandated by WCAG2 that can interfere with actual accessibility.&lt;/p&gt;
&lt;p&gt;Nevertheless, for anyone who wants both, BridgePCA was designed for exactly that — it automatically passes WCAG 2, but extends improved accommodation using APCA, and several tools already support it.&lt;/p&gt;
&lt;p&gt;— Andrew Somers,&lt;br/&gt;
Inclusive Reading Technologies&lt;/p&gt;


					&lt;/div&gt;
					&lt;footer&gt;
						&lt;cite&gt;Andrew Somers&lt;/cite&gt;;
						&lt;time&gt;15 April 2026 at 6:38 am&lt;/time&gt;.
						&lt;a href=&quot;https://adrianroselli.com/2026/04/wcag3-contrast-as-of-april-2026.html#comment-408186&quot;&gt;Permalink&lt;/a&gt;
					&lt;/footer&gt;

				&lt;/blockquote&gt;

&lt;/div&gt;



		
		




	&lt;div&gt;
		&lt;h3&gt;Leave a Comment or Response &lt;small&gt;&lt;a href=&quot;https://adrianroselli.com/2026/04/wcag3-contrast-as-of-april-2026.html#respond&quot;&gt;Cancel response&lt;/a&gt;&lt;/small&gt;&lt;/h3&gt;&lt;p&gt;Comment: &lt;/p&gt;&lt;ul&gt;&lt;li&gt;The form doesn’t support Markdown.&lt;/li&gt;&lt;li&gt;This form allows limited HTML.&lt;/li&gt;&lt;li&gt;Allowed HTML elements are &lt;code&gt;&amp;lt;a href&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;blockquote&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;code&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;del&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;em&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;ins&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;q&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;strong&amp;gt;&lt;/code&gt;, and maybe some others. WordPress is fickle and randomly blocks or allows some.&lt;/li&gt; &lt;li&gt;If you want to include HTML &lt;em&gt;examples&lt;/em&gt; in your comment, then HTML encode them. E.g. &lt;code&gt;&amp;lt;code&amp;gt;&amp;amp;lt;div&amp;amp;gt;&amp;lt;/code&amp;gt;&lt;/code&gt; (you can copy and paste that chunk).&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Name &lt;span&gt;*&lt;/span&gt; &lt;/p&gt;
&lt;p&gt;Email &lt;span&gt;*&lt;/span&gt; &lt;/p&gt;
&lt;p&gt;Website &lt;/p&gt;
&lt;p&gt; 

&lt;/p&gt;&lt;p&gt;Δ&lt;/p&gt;	&lt;/div&gt;
	
		&lt;/article&gt;</content:encoded>
</item>
<item>
<title>jjba23/olive-css: Utility-class vanilla CSS framework inspired by Tailwind syntax, easy to learn and hack, written in Lisp (Guile Scheme) - Codeberg.org</title>
<link>https://codeberg.org/jjba23/olive-css</link>
<enclosure type="image/jpeg" length="0" url="https://codeberg.org/jjba23/olive-css/-/summary-card"></enclosure>
<guid isPermaLink="false">vXm5NPxZktLHubusMtJtyMLo-QoXlwtntNxe1w==</guid>
<pubDate>Wed, 22 Apr 2026 22:14:21 +0000</pubDate>
<description>Utility-class vanilla CSS framework inspired by Tailwind syntax, easy to learn and hack, written in Lisp (Guile Scheme)</description>
<content:encoded>&lt;h1&gt;Olive CSS &lt;span&gt;🫒&lt;/span&gt;&lt;/h1&gt;&lt;p&gt;
&lt;img src=&quot;https://jointhefreeworld.org/ggg/dist/scheme-guile.svg&quot; alt=&quot;https://jointhefreeworld.org/ggg/dist/scheme-guile.svg&quot; title=&quot;&quot;/&gt; &lt;img src=&quot;https://jointhefreeworld.org/ggg/dist/gnu-guix.svg&quot; alt=&quot;https://jointhefreeworld.org/ggg/dist/gnu-guix.svg&quot; title=&quot;&quot;/&gt;   &lt;img src=&quot;https://jointhefreeworld.org/ggg/dist/maak.svg&quot; alt=&quot;https://jointhefreeworld.org/ggg/dist/maak.svg&quot; title=&quot;&quot;/&gt; 
&lt;img src=&quot;https://jointhefreeworld.org/ggg/dist/license-lgpl3+.svg&quot; alt=&quot;https://jointhefreeworld.org/ggg/dist/license-lgpl3+.svg&quot; title=&quot;&quot;/&gt;    &lt;img src=&quot;https://jointhefreeworld.org/ggg/dist/license-fdl13+.svg&quot; alt=&quot;https://jointhefreeworld.org/ggg/dist/license-fdl13+.svg&quot; title=&quot;&quot;/&gt; &lt;/p&gt;&lt;p&gt;
&lt;em&gt;Utility-class vanilla CSS framework inspired by Tailwind syntax, easy to learn and hack, written in Lisp (Guile Scheme)&lt;/em&gt;&lt;/p&gt;&lt;p&gt;
You can use this in any web project, Scheme or not, and it serves as a kind-of drop-in replacement for Tailwind.&lt;/p&gt;&lt;p&gt;
This project is licensed under the Lesser GNU General Public License v3 or later.&lt;/p&gt;&lt;p&gt;
You can read more about Olive CSS&amp;#39;s features and syntax here, in the documentation page:&lt;/p&gt;&lt;p&gt;
&lt;a href=&quot;https://codeberg.org/jjba23/olive-css/src/branch/trunk/docs/olive/olive.org&quot;&gt;/jjba23/olive-css/src/branch/trunk/docs/olive/olive.org&lt;/a&gt;&lt;/p&gt;&lt;p&gt;
You can also find olive-css technical Guile Scheme API documentation here:&lt;/p&gt;&lt;p&gt;
&lt;a href=&quot;https://jointhefreeworld.org/api-docs/olive-css/API.html&quot;&gt;https://jointhefreeworld.org/api-docs/olive-css/API.html&lt;/a&gt;&lt;/p&gt;&lt;p&gt;
You can use Olive CSS like any other utility-class CSS framework, like this:&lt;/p&gt;&lt;p&gt;
If you like my work, please support me by &lt;a href=&quot;https://bmc.link/jjbigorra&quot;&gt;buying me a cup of coffee &lt;span&gt;☕&lt;/span&gt;&lt;/a&gt; so I can continue with a lot of motivation.&lt;/p&gt;&lt;hr/&gt;&lt;div&gt;
&lt;h2&gt;
Licensing
&lt;/h2&gt;
&lt;div&gt;
&lt;p&gt;
olive-css and all of its source code are free software, licensed under the GNU Lesser General Public License v3 (or newer at your convenience).&lt;/p&gt;
&lt;p&gt;
&lt;a href=&quot;https://www.gnu.org/licenses/lgpl-3.0.html&quot;&gt;https://www.gnu.org/licenses/lgpl-3.0.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;
The showcase site, documentation and examples, including this document, which are provided with olive-css, are all licensed under the GNU Free Documentation License v1.3 (or newer at your convenience).&lt;/p&gt;
&lt;p&gt;
&lt;a href=&quot;https://www.gnu.org/licenses/fdl-1.3.html&quot;&gt;https://www.gnu.org/licenses/fdl-1.3.html&lt;/a&gt;&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;
&lt;img src=&quot;https://jointhefreeworld.org/static-assets/olive-css/olive-css-new-small.png&quot; alt=&quot;https://jointhefreeworld.org/static-assets/olive-css/olive-css-new-small.png&quot; title=&quot;&quot;/&gt;&lt;/p&gt;
&lt;hr/&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div&gt;
&lt;h2&gt;
Installing and Using
&lt;/h2&gt;
&lt;div&gt;
&lt;p&gt;
Installing and using Olive CSS is as simple as downloading a pre-built version from &lt;code&gt;resources/css/&lt;/code&gt; directory, or building your own, and serving it via HTTP (of course also loading it in a &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; tag in your HTML.&lt;/p&gt;
&lt;p&gt;
About bundle size: Keep in mind that the default &lt;code&gt;olive.min.css&lt;/code&gt; is pretty large, since it contains all classes and variants and media queries of classes. It is recommended before going to production, to create your own custom optimized build of Olive CSS, by disabling certain features.&lt;/p&gt;
&lt;p&gt;
Some factors that can drastically affect the size of the production CSS style sheet are the amount of colors you enable and amount of media queries/breakpoints.&lt;/p&gt;
&lt;p&gt;
A rule of thumb is to disable &lt;code&gt;xl&lt;/code&gt; and/or &lt;code&gt;2xl&lt;/code&gt; queries if you won&amp;#39;t use them, and only choose some colors. This is made easy thanks to Guile Scheme&amp;#39;s powerful &lt;code&gt;parameterize&lt;/code&gt; syntax and helpers, allowing you to override pretty much anything. It can also be useful to disable dark mode since that will save a lot of complexity. See &lt;code&gt;scripts/olive-css-gen.scm&lt;/code&gt; for more.&lt;/p&gt;
&lt;p&gt;
It&amp;#39;s also important to ensure that your web server efficiently caches CSS files and compresses them with GZIP or Brotli.&lt;/p&gt;
&lt;div&gt;
&lt;h3&gt;
Customizing Olive CSS
&lt;/h3&gt;
&lt;div&gt;
&lt;p&gt;
Customization is a first-class feature in Olive CSS. Thanks to the power of Guile Scheme, you can enable/disable features, tweak sizes, colors, and easily add your own utility rules.&lt;/p&gt;
&lt;p&gt;
Key customization points:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Choose which screen breakpoints to include (&lt;code&gt;sm&lt;/code&gt;, &lt;code&gt;md&lt;/code&gt;, &lt;code&gt;lg&lt;/code&gt;, &lt;code&gt;xl&lt;/code&gt;, &lt;code&gt;2xl&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Enable or disable dark mode with parameterize&lt;/li&gt;
&lt;li&gt;Customize your color palette and choose some colors for your project&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can also generate your own responsive or hover-based variants with &lt;code&gt;addmq&lt;/code&gt; and &lt;code&gt;addhover&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;
See &lt;code&gt;scripts/olive-css-gen.scm&lt;/code&gt; and &lt;code&gt;src/olive-css/main.scm&lt;/code&gt; and the API docs for more.&lt;/p&gt;
&lt;hr/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div&gt;
&lt;h2&gt;
Philosophy
&lt;/h2&gt;
&lt;div&gt;
&lt;p&gt;
Olive CSS is not another Tailwind clone. It is simply inspired by it, and its design is based on these principles:&lt;/p&gt;
&lt;dl&gt;
&lt;dt&gt;
Hackability
&lt;/dt&gt;
&lt;dd&gt;You can extend or modify anything via Scheme code.&lt;/dd&gt;
&lt;dt&gt;
Freedom
&lt;/dt&gt;
&lt;dd&gt;100% Free Software, licensed under the LGPLv3+ and FDL 1.3+.&lt;/dd&gt;
&lt;dt&gt;
Self-contained
&lt;/dt&gt;
&lt;dd&gt;Minimal dependencies and lightweight fast builds.&lt;/dd&gt;
&lt;dt&gt;
Expressiveness
&lt;/dt&gt;
&lt;dd&gt;Easily add support for &lt;strong&gt;ANY&lt;/strong&gt; CSS feature and your own palettes and rules/declarations.   &lt;/dd&gt;
&lt;/dl&gt;
&lt;p&gt;It is built to encourage learning and experimentation, rather than hiding complexity behind rigid inflexible tooling.&lt;/p&gt;
&lt;hr/&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div&gt;
&lt;h2&gt;
Code of conduct
&lt;/h2&gt;
&lt;div&gt;
&lt;p&gt;
This project adheres to the jointhefreeworld code of conduct. Find it here:&lt;/p&gt;
&lt;p&gt;
&lt;a href=&quot;https://jointhefreeworld.org/blog/articles/personal/jointhefreeworld-code-of-conduct/index.html&quot;&gt;https://jointhefreeworld.org/blog/articles/personal/jointhefreeworld-code-of-conduct/index.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;
In summary, we foster an inclusive, respectful, and cooperative environment for all contributors and users of this free software project. Inspired by the ideals of the GNU Project, we strive to uphold freedom, equality, and community as guiding principles. We believe that collaboration in a community of mutual respect is essential to creating excellent free software.&lt;/p&gt;
&lt;hr/&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div&gt;
&lt;h2&gt;
Olive CSS Project
&lt;/h2&gt;
&lt;div&gt;
&lt;p&gt;
Contributing to free software is a uniquely beautiful act because it embodies principles of generosity, collaboration, and empowerment.&lt;/p&gt;
&lt;p&gt;
We welcome everyone to feel invited to the olive-css Project, and encourage active contribution in all forms, to improve it and/or suggest improvements, brainstorm with me, make it more modular/flexible, etc, feel free to contact me &amp;lt;jjbigorra@gmail.com&amp;gt; to chat, discuss or report feedback.&lt;/p&gt;
&lt;p&gt;
Find here the Backlog and Kanban boards for olive-css: &lt;a href=&quot;https://lucidplan.jointhefreeworld.org/tickets/olive-css&quot;&gt;https://lucidplan.jointhefreeworld.org/tickets/olive-css&lt;/a&gt;&lt;/p&gt;
&lt;hr/&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div&gt;
&lt;h2&gt;
Comparison with Tailwind CSS
&lt;/h2&gt;
&lt;div&gt;
&lt;table&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Feature&lt;/td&gt;
&lt;td&gt;Tailwind CSS&lt;/td&gt;
&lt;td&gt;Olive CSS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Written in&lt;/td&gt;
&lt;td&gt;JavaScript&lt;/td&gt;
&lt;td&gt;λ Guile Scheme&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Customization&lt;/td&gt;
&lt;td&gt;- average (via CSS config)&lt;/td&gt;
&lt;td&gt;&lt;span&gt;✔️&lt;/span&gt; extensive (via code and expressive DSL)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Functional API&lt;/td&gt;
&lt;td&gt;&lt;span&gt;❌&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;✔️&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Free Software&lt;/td&gt;
&lt;td&gt;&lt;span&gt;❌&lt;/span&gt; (MIT, not copyleft)&lt;/td&gt;
&lt;td&gt;&lt;span&gt;✔️&lt;/span&gt; (LGPL v3 + FDL)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output control&lt;/td&gt;
&lt;td&gt;Limited, JIT compiler is restricting&lt;/td&gt;
&lt;td&gt;Full (generate exactly what you want)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;
Olive CSS is especially appealing if you want a completely libre, hackable, expressive and transparent alternative to Tailwind.&lt;/p&gt;
&lt;hr/&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div&gt;
&lt;h2&gt;
Troubleshooting
&lt;/h2&gt;
&lt;div&gt;
&lt;p&gt;
Common issues and solutions:&lt;/p&gt;
&lt;p&gt;
&lt;strong&gt;Output is too large&lt;/strong&gt;: Disable unused breakpoints, variants, or colors in your build script.&lt;/p&gt;
&lt;p&gt;
&lt;strong&gt;Class not found&lt;/strong&gt;: Make sure you generated the build after adding the class logic.&lt;/p&gt;
&lt;p&gt;
&lt;strong&gt;Dark mode not working&lt;/strong&gt;: Ensure you are using the correct &lt;code&gt;data-theme=&amp;quot;dark&amp;quot;&lt;/code&gt; in the HTML element, and that dark mode queries were included in your build.&lt;/p&gt;
&lt;p&gt;
&lt;strong&gt;Invalid CSS&lt;/strong&gt;: Double-check your custom rule definitions, especially when formatting oklch colors.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;</content:encoded>
</item>
<item>
<title>Igalia&#39;s Brian Kardell joins the W3C TAG | Igalia</title>
<link>https://www.igalia.com/2026/02/10/Igalias-Brian-Kardell-joins-the-W3C-TAG.html</link>
<enclosure type="image/jpeg" length="0" url="https://www.igalia.com/assets/i/logo200x200Margin-og.png"></enclosure>
<guid isPermaLink="false">pSJali0Ud84g6I0BXqn0X-vrE351cSBIRSbIhQ==</guid>
<pubDate>Wed, 22 Apr 2026 16:46:55 +0000</pubDate>
<description>Igalia is an open source consulting firm specialised in the development of innovative projects and solutions. Our engineers have expertise in a wide range of technological areas, including browsers and client-side web technologies, graphics pipeline, compilers and virtual machines. We have the most WPE, WebKit, Chromium/Blink and Firefox expertise found in the consulting business, including many reviewers and committers. Igalia designs, develops, customises and optimises GNU/Linux-based solut...</description>
<content:encoded>&lt;body&gt;
    &lt;header&gt;
  &lt;div&gt;
    &lt;div&gt;
      
      
      
      &lt;h1&gt;
        &lt;a href=&quot;https://www.igalia.com/&quot;&gt;
          &lt;img src=&quot;https://www.igalia.com/assets/i/logo.png&quot; alt=&quot;Igalia&quot; title=&quot;&quot;/&gt;
        &lt;/a&gt;
      &lt;/h1&gt;

      

      &lt;div&gt;
        
          
          &lt;span&gt;Toggle menu&lt;/span&gt;
        
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/header&gt;

&lt;div&gt;
  &lt;div&gt;
    &lt;ul&gt;
      
      &lt;li&gt;
        &lt;a href=&quot;https://www.igalia.com/about&quot;&gt;
          About Us
        &lt;/a&gt;
      &lt;/li&gt;
      
      &lt;li&gt;
        &lt;a href=&quot;https://www.igalia.com/technologies&quot;&gt;
          Technologies
        &lt;/a&gt;
      &lt;/li&gt;
      
      &lt;li&gt;
        &lt;a href=&quot;https://www.igalia.com/services&quot;&gt;
          Services
        &lt;/a&gt;
      &lt;/li&gt;
      
      &lt;li&gt;
        &lt;a href=&quot;https://www.igalia.com/industries&quot;&gt;
          Industries
        &lt;/a&gt;
      &lt;/li&gt;
      
      &lt;li&gt;
        &lt;a href=&quot;https://www.igalia.com/24-7&quot;&gt;
          24/7
        &lt;/a&gt;
      &lt;/li&gt;
      
      &lt;li&gt;
        &lt;a href=&quot;https://www.igalia.com/jobs&quot;&gt;
          Jobs
        &lt;/a&gt;
      &lt;/li&gt;
      
      &lt;li&gt;
        &lt;a href=&quot;https://www.igalia.com/contact&quot;&gt;
          Contact
        &lt;/a&gt;
      &lt;/li&gt;
      
      &lt;li&gt;
        &lt;a href=&quot;https://www.igalia.com/public-funding&quot;&gt;
          Feder
        &lt;/a&gt;
      &lt;/li&gt;
      
	&lt;li&gt;
	&lt;img src=&quot;https://www.igalia.com/assets/i/icon-sun.svg&quot; alt=&quot;Light mode&quot; title=&quot;&quot;/&gt;
	
	&lt;img src=&quot;https://www.igalia.com/assets/i/icon-moon.svg&quot; alt=&quot;Dark mode&quot; title=&quot;&quot;/&gt;
	&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/div&gt;
&lt;/div&gt;




    &lt;main&gt;
        &lt;article&gt;
  &lt;header&gt;
    &lt;div&gt;
      &lt;div&gt;
        &lt;div&gt;
          &lt;h1&gt;Igalia&amp;#39;s Brian Kardell joins the W3C TAG&lt;/h1&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    
  &lt;/header&gt;
  &lt;div&gt;
    &lt;div&gt;
      &lt;div&gt;
        
        &lt;div&gt;
          &lt;a href=&quot;https://www.igalia.com/24-7/news&quot;&gt;
            &lt;img src=&quot;https://www.igalia.com/assets/i/arrowBackBlue.png&quot; alt=&quot;Back to news&quot; title=&quot;&quot;/&gt;
          &lt;/a&gt;

          
          &lt;h5&gt;
            &amp;quot;Congratulating Brian and the W3C TAG on the appointment.&amp;quot;
          &lt;/h5&gt;
          

          &lt;p&gt;
            
            &lt;time&gt;
              
              Feb 10, 2026
            &lt;/time&gt;
          &lt;/p&gt;

        &lt;/div&gt;


        &lt;div&gt;
          &lt;p&gt;The W3C’s &lt;a href=&quot;https://tag.w3.org/&quot;&gt;Technical Architecture Group&lt;/a&gt; (or “TAG”), is a special group within the W3C with stewardship of the overall architecture. The mission of the TAG is to build consensus around principles of Web architecture and to interpret and clarify these principles when necessary, to resolve issues involving general Web architecture brought to the TAG, and to help coordinate cross-technology architecture developments inside and outside W3C. The Members of the TAG participate as individual contributors, not as representatives of their organizations. TAG participants use their best judgment to find the best solutions for the Web, not just for any particular network, technology, vendor, or user.&lt;/p&gt;

&lt;p&gt;Following the TAG’s recent &lt;a href=&quot;https://www.w3.org/news/2025/w3c-advisory-committee-elects-technical-architecture-group/&quot;&gt;election results&lt;/a&gt;, the W3C Team has chosen and the Advisory Board and Technical Architecture Group have ratified &lt;a href=&quot;https://www.w3.org/news/2026/w3c-team-appointment-to-the-tag/&quot;&gt;our very own &lt;strong&gt;Brian Kardell&lt;/strong&gt; for the 2026-28 term&lt;/a&gt; as one of the two appointments they will make for this term.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://igalia.com/team/bkardell&quot;&gt;Brian&lt;/a&gt;, who started hacking on the web as early as 1996, has well over a decade of experience working in web standards and core technologies, contributing to its continued development, whether it be through championing for important yet underappreciated features or through advocating for the platform at large.&lt;/p&gt;

&lt;p&gt;All in all, we believe Brian will be an excellent addition to the group and a champion for a free and extensible web. Congratulations to Brian as well as the W3C TAG on this appointment!&lt;/p&gt;

        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div&gt;
        &lt;div&gt;
  
&lt;/div&gt;

      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/article&gt;

    &lt;/main&gt;

    &lt;footer&gt;

  &lt;a href=&quot;https://www.igalia.com/&quot;&gt;
    &lt;img src=&quot;https://www.igalia.com/assets/i/logo.png&quot; alt=&quot;Igalia&quot; title=&quot;&quot;/&gt;
  &lt;/a&gt;

  &lt;div&gt;
    &lt;h2&gt;Igalia.com&lt;/h2&gt;
    &lt;ul&gt;
      
      &lt;li&gt;
        &lt;a href=&quot;https://www.igalia.com/about&quot;&gt;
          About Us
        &lt;/a&gt;
      &lt;/li&gt;
      
      &lt;li&gt;
        &lt;a href=&quot;https://www.igalia.com/technologies&quot;&gt;
          Technologies
        &lt;/a&gt;
      &lt;/li&gt;
      
      &lt;li&gt;
        &lt;a href=&quot;https://www.igalia.com/services&quot;&gt;
          Services
        &lt;/a&gt;
      &lt;/li&gt;
      
      &lt;li&gt;
        &lt;a href=&quot;https://www.igalia.com/industries&quot;&gt;
          Industries
        &lt;/a&gt;
      &lt;/li&gt;
      
      &lt;li&gt;
        &lt;a href=&quot;https://www.igalia.com/24-7&quot;&gt;
          24/7
        &lt;/a&gt;
      &lt;/li&gt;
      
      &lt;li&gt;
        &lt;a href=&quot;https://www.igalia.com/jobs&quot;&gt;
          Jobs
        &lt;/a&gt;
      &lt;/li&gt;
      
      &lt;li&gt;
        &lt;a href=&quot;https://www.igalia.com/contact&quot;&gt;
          Contact
        &lt;/a&gt;
      &lt;/li&gt;
      
    &lt;/ul&gt;
  &lt;/div&gt;

  &lt;div&gt;
    &lt;h2&gt;Igalia, S.L. © 2026. All rights reserved.&lt;/h2&gt;
    &lt;ul&gt;
      
      &lt;li&gt;
        &lt;a href=&quot;https://www.igalia.com/coc&quot;&gt;
          Code of Conduct
        &lt;/a&gt;
        &lt;/li&gt;
        
      &lt;li&gt;
        &lt;a href=&quot;https://www.igalia.com/legal&quot;&gt;
          Legal Terms
        &lt;/a&gt;
        &lt;/li&gt;
        
      &lt;li&gt;
        &lt;a href=&quot;https://www.igalia.com/press&quot;&gt;
          Press
        &lt;/a&gt;
        &lt;/li&gt;
        
      &lt;li&gt;
        &lt;a href=&quot;https://www.igalia.com/privacy&quot;&gt;
          Privacy Policy
        &lt;/a&gt;
        &lt;/li&gt;
        
      &lt;li&gt;
        &lt;a href=&quot;https://www.igalia.com/public-funding&quot;&gt;
          Public funding (Fondos públicos)
        &lt;/a&gt;
        &lt;/li&gt;
        
    &lt;/ul&gt;
  &lt;/div&gt;

  &lt;div&gt;
    &lt;h2&gt;Headquarters&lt;/h2&gt;
    &lt;ul&gt;
      
      &lt;li&gt;Bugallal Marchesi, 22, 1º&lt;/li&gt;
      
      &lt;li&gt;15008 A Coruña&lt;/li&gt;
      
      &lt;li&gt;Galicia (Spain)&lt;/li&gt;
      
      &lt;li&gt;Pho.: +34 981 913991&lt;/li&gt;
    &lt;/ul&gt;
    &lt;div&gt;
      
      

      &lt;a href=&quot;https://linkedin.com/company/igalia&quot;&gt;
        
        &lt;span&gt;linkedin&lt;/span&gt;
      &lt;/a&gt;
      
      

      &lt;a href=&quot;https://bsky.app/profile/igalia.com&quot;&gt;
        
        &lt;span&gt;bluesky&lt;/span&gt;
      &lt;/a&gt;
      
      

      &lt;a href=&quot;https://floss.social/@igalia&quot;&gt;
        
        &lt;span&gt;mastodon&lt;/span&gt;
      &lt;/a&gt;
      
      

      &lt;a href=&quot;https://youtube.com/igalia&quot;&gt;
        
        &lt;span&gt;youtube&lt;/span&gt;
      &lt;/a&gt;
      
    &lt;/div&gt;

&lt;/div&gt;&lt;/footer&gt;

    
    
    
    

    
    
    

    
    
    
    

  

&lt;/body&gt;</content:encoded>
</item>
<item>
<title>Your Business Lives in a Browser Now.</title>
<link>https://bravertechnology.com/your-business-lives-in-a-browser-now/</link>
<guid isPermaLink="false">IWKV1OACWsPCol77h98XOsCZaovJL9pG1AKaDQ==</guid>
<pubDate>Wed, 22 Apr 2026 14:13:23 +0000</pubDate>
<description>MOST OF YOUR BUSINESS LIVES IN A BROWSER NOW.  HOW IS YOUR BROWSER SECURITY? For most businesses today, work no longer lives on a desktop or even inside installed software…... The post Your Business Lives in a Browser Now. appeared first on Braver Technology Solutions.</description>
<content:encoded>&lt;p&gt;&lt;strong&gt;&lt;img src=&quot;https://bravertechnology.com/wp-content/uploads/2026/04/Business-Lives-in-a-Browser-1024x536.jpg&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;/strong&gt;&lt;/p&gt;&lt;h2&gt;&lt;strong&gt;MOST OF YOUR BUSINESS LIVES IN A BROWSER NOW.  HOW IS YOUR BROWSER SECURITY?&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;For most businesses today, work no longer lives on a desktop or even inside installed software… &lt;strong&gt;It lives in your browser.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Open a typical employee’s laptop and you will likely see the same thing. A handful of tabs running Microsoft 365, Salesforce, QuickBooks, SharePoint, Teams, a CRM, maybe a project management tool. Add a few more tabs for email, file sharing, and collaboration, and that browser window becomes the entire workday.&lt;/p&gt;&lt;p&gt;This shift has happened quietly, but it has major implications for how businesses think about security.&lt;/p&gt;&lt;h3&gt;&lt;strong&gt;The Browser Has Become the Primary Work Environment&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;Cloud adoption and remote work have changed where and how work gets done. Most business-critical applications are now delivered through the web. Employees are not “using their computer” in the traditional sense. They are operating inside Chrome or Edge for nearly everything.&lt;/p&gt;&lt;p&gt;That means the browser is no longer just a tool. It is the workspace.&lt;/p&gt;&lt;p&gt;And like any workspace, it needs to be secured.&lt;/p&gt;&lt;h4&gt;&lt;strong&gt;The Hidden Risks Inside the Browser&lt;/strong&gt;&lt;/h4&gt;&lt;p&gt;Because the browser feels familiar and simple, it is often overlooked in security strategies. But it introduces a unique set of risks that many organizations are not actively monitoring.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Session hijacking&lt;/strong&gt;&lt;br/&gt;
Once a user logs into a web application, a session is created. If that session is stolen, an attacker can gain access without needing a password or even bypassing multi factor authentication. This is becoming a more common tactic in modern attacks.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Malicious or risky extensions&lt;/strong&gt;&lt;br/&gt;
Browser extensions can be helpful, but they can also introduce serious vulnerabilities. Some extensions have access to everything happening in the browser, including keystrokes, credentials, and sensitive data. Even legitimate extensions can become compromised.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Phishing that looks legitimate&lt;/strong&gt;&lt;br/&gt;
Phishing attacks have evolved. Today’s phishing pages often look identical to real login portals and are accessed directly through the browser. Users may not realize they have handed over credentials until it is too late.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Shadow IT&lt;/strong&gt;&lt;br/&gt;
Employees frequently sign up for web-based tools without IT approval. These apps can store company data outside of approved systems, creating blind spots and compliance risks.&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;&lt;strong&gt;Why Traditional Security Misses This&lt;/strong&gt;&lt;/h4&gt;&lt;p&gt;Most security strategies are still built around protecting endpoints and networks. Firewalls, antivirus software, and endpoint detection tools are all important, but they are not designed to fully see or control what happens inside a browser session.&lt;/p&gt;&lt;p&gt;For example:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Endpoint tools may not detect suspicious activity happening within a legitimate web app session&lt;/li&gt;&lt;li&gt;Network security may not flag encrypted browser traffic tied to trusted SaaS platforms&lt;/li&gt;&lt;li&gt;Identity tools may not catch session-based attacks after login&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The result is a growing visibility gap right where most work is happening.&lt;/p&gt;&lt;h4&gt;&lt;strong&gt;What SMBs Should Actually Be Monitoring&lt;/strong&gt;&lt;/h4&gt;&lt;p&gt;To adapt to this shift, businesses need to start thinking differently about visibility and control.&lt;/p&gt;&lt;p&gt;Here are a few areas that matter most:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;User activity within SaaS applications&lt;/strong&gt;&lt;br/&gt;
Understanding how employees interact with tools like Microsoft 365 or Google Workspace can help identify unusual behavior early.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Browser extensions and configurations&lt;/strong&gt;&lt;br/&gt;
Tracking which extensions are installed and limiting access to approved ones reduces unnecessary risk.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Session behavior&lt;/strong&gt;&lt;br/&gt;
Monitoring for anomalies such as impossible travel, unusual login patterns, or session reuse can help detect hijacking attempts.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Access to unsanctioned applications&lt;/strong&gt;&lt;br/&gt;
Gaining visibility into shadow IT allows businesses to bring those tools under management or eliminate them entirely.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Phishing resistance&lt;/strong&gt;&lt;br/&gt;
Advanced email and web filtering combined with user awareness training remains critical, especially as phishing becomes more sophisticated.&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;&lt;strong&gt;A New Category Is Emerging&lt;/strong&gt;&lt;/h4&gt;&lt;p&gt;As this challenge becomes more visible, a new category of tools is emerging. Secure business browsers and browser-focused security platforms are designed specifically to address these gaps. They provide deeper visibility into user activity, enforce policies directly within the browser, and reduce the risk of data leakage or unauthorized access.&lt;/p&gt;&lt;p&gt;For many small and midsize businesses, this does not mean replacing everything overnight. It means recognizing that the browser is now a critical control point and should be treated as such.&lt;/p&gt;&lt;h4&gt;&lt;strong&gt;Rethinking the Modern Workplace&lt;/strong&gt;&lt;/h4&gt;&lt;p&gt;The way people work has changed faster than most security strategies have kept up. If your team spends the majority of their day in a browser, then that is where your strongest protections need to be.&lt;/p&gt;&lt;p&gt;Ignoring that reality creates a blind spot. Addressing it creates an opportunity to reduce risk in a meaningful way.&lt;/p&gt;&lt;h4&gt;&lt;strong&gt;How to Close the Browser Security Gap&lt;/strong&gt;&lt;/h4&gt;&lt;p&gt;Recognizing the problem is one thing. Solving it in a way that actually works for your business is another.&lt;/p&gt;&lt;p&gt;At Braver Technology, the focus is on aligning security with how your team already operates. Since so much of today’s work happens inside the browser, protection needs to extend into that environment, not sit outside of it.&lt;/p&gt;&lt;p&gt;That starts with better visibility into cloud applications, so you know which tools are being used, how data is moving, and where potential risks exist. From there, stronger identity and access controls help ensure the right people have access to the right systems, and that access is continuously verified, not just granted once at login.&lt;/p&gt;&lt;p&gt;Braver also helps implement practical, right-sized solutions for SMBs, whether that means securing browser activity, reducing exposure from unmanaged applications, or tightening policies around extensions and user behavior. The goal is not to overcomplicate things. It is to make security more effective by making it more relevant to how work actually gets done.&lt;/p&gt;&lt;p&gt;When your office lives in the browser, your security strategy should too.&lt;/p&gt;&lt;p&gt;If you are unsure how much of your business is happening in the browser, the answer is probably more than you think.&lt;/p&gt;&lt;p&gt;&lt;strong&gt; &lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Braver Technology Solutions | WeMakeITWork@BraverTechnology.com&lt;/strong&gt;&lt;br/&gt;&lt;strong&gt;Boston 617.315.8515 | Taunton 508.824.2260 | Providence 401.484.7900&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;The post &lt;a href=&quot;https://bravertechnology.com/your-business-lives-in-a-browser-now/&quot;&gt;Your Business Lives in a Browser Now.&lt;/a&gt; appeared first on &lt;a href=&quot;https://bravertechnology.com&quot;&gt;Braver Technology Solutions&lt;/a&gt;.&lt;/p&gt;</content:encoded>
</item>
<item>
<title>blogmore.el v4.1</title>
<link>https://blog.davep.org/2026/04/17/blogmore-el-v4-1.html</link>
<guid isPermaLink="false">DNDWw3UWMxKq21U5Dwyb9VAkr8PirGjtG2EFjQ==</guid>
<pubDate>Wed, 22 Apr 2026 08:41:52 +0000</pubDate>
<description>Following on from yesterday&#39;s experiment with</description>
<content:encoded>&lt;p&gt;Following on from &lt;a href=&quot;https://blog.davep.org/2026/04/16/i-should-use-webp.html&quot;&gt;yesterday&amp;#39;s experiment with
webp&lt;/a&gt; I got to thinking that it might be
handy to add a wee command to
&lt;a href=&quot;https://github.com/davep/blogmore.el&quot;&gt;&lt;code&gt;blogmore.el&lt;/code&gt;&lt;/a&gt; that can quickly swap
an image&amp;#39;s extension from whatever it is to &lt;code&gt;webp&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;So &lt;a href=&quot;https://github.com/davep/blogmore.el/releases/tag/v4.1&quot;&gt;v4.1&lt;/a&gt; has
happened. The new command is simple enough, called
&lt;code&gt;blogmore-webpify-image-at-point&lt;/code&gt;; it just looks to see if there&amp;#39;s a
Markdown image on the current line and, if there is, replaces the file&amp;#39;s
extension with &lt;code&gt;webp&lt;/code&gt; no matter what it was before.&lt;/p&gt;&lt;p&gt;If/when I decide to convert all the &lt;code&gt;png&lt;/code&gt; files in the blog to &lt;code&gt;webp&lt;/code&gt; I&amp;#39;ll
obviously use something very batch-oriented, but for now I&amp;#39;m still
experimenting, so going back and quickly changing the odd image here and
there is a nicely cautious approach.&lt;/p&gt;&lt;p&gt;I have, of course, added the command to the transient menu that is brought
up by the &lt;code&gt;blogmore&lt;/code&gt; command.&lt;/p&gt;&lt;p&gt;One other small change in v4.1 is that a newly created post is saved right
away. This doesn&amp;#39;t make a huge difference, but it does mean I start out with
a saved post that will be seen by &lt;a href=&quot;https://blogmore.davep.dev/&quot;&gt;BlogMore&lt;/a&gt;
when generating the site.&lt;/p&gt;</content:encoded>
</item>
<item>
<title>I should use webp</title>
<link>https://blog.davep.org/2026/04/16/i-should-use-webp.html</link>
<guid isPermaLink="false">Kd0FUK9aA2mmNrG26I7KWhDlDTvSmWH53qK25w==</guid>
<pubDate>Wed, 22 Apr 2026 08:41:52 +0000</pubDate>
<description>For a good while now I&#39;ve been pretty happy with the</description>
<content:encoded>&lt;p&gt;For a good while now I&amp;#39;ve been pretty happy with the
&lt;a href=&quot;https://pagespeed.web.dev/&quot;&gt;PageSpeed&lt;/a&gt; measurements of this blog, which in
turn means I&amp;#39;ve been happy with the state of what&amp;#39;s generated by
&lt;a href=&quot;https://blogmore.davep.dev/&quot;&gt;BlogMore&lt;/a&gt;. I have pretty much everything that
can be minified nice and minimal. At this point, the main thing that causes
the speed measurement to fluctuate is image sizes.&lt;/p&gt;&lt;p&gt;I use a lot of PNGs on this blog. When I&amp;#39;m using images, they&amp;#39;re almost
always in posts that include screenshots, which in turn pretty much demand
that I use a lossless format. When I take these screenshots I don&amp;#39;t worry
too much about the dimensions (within reason), and of course I don&amp;#39;t really
do anything to optimise how they&amp;#39;ll work and appear on different display
sizes. If I was to get too into that, it would add friction to writing
something, and the whole point of this is to feel less friction when it
comes to sitting at the keyboard.&lt;/p&gt;&lt;p&gt;So I&amp;#39;ve been living with the fact that some images can be pretty big. While
I do make a point of using
&lt;a href=&quot;https://en.wikipedia.org/wiki/Pngcrush&quot;&gt;pngcrush&lt;/a&gt; on every image, it
generally doesn&amp;#39;t make a huge saving.&lt;/p&gt;&lt;p&gt;Then yesterday &lt;a href=&quot;https://www.yakshaving.co.uk/posts/converting-images-to-webp-format/&quot;&gt;I read this post on Andy&amp;#39;s
blog&lt;/a&gt;
and I suddenly realised what I had to do!&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://blog.davep.org/attachments/2026/04/16/use-webp.webp#centre&quot; alt=&quot;I should use webp&quot; title=&quot;&quot;/&gt;&lt;/p&gt;&lt;p&gt;Borrowing from what Andy did, I used &lt;code&gt;mogrify&lt;/code&gt; too, setting up this Fish
&lt;code&gt;abbr&lt;/code&gt; in &lt;a href=&quot;https://github.com/davep/fish&quot;&gt;my Fish configuration&lt;/a&gt;:&lt;/p&gt;&lt;div&gt;&lt;pre&gt;&lt;code&gt;iftype -q mogrify
    abbr -g mkwebp &amp;quot;mogrify -format webp -define webp:lossless=true -quality 100&amp;quot;end&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In my case, at least in the initial experiment, I decided to keep it all
lossless. So far the results have been really good, cutting the image sizes
down by a significant amount. For example, if I look at the images for
&lt;a href=&quot;https://blog.davep.org/2026/04/15/&quot;&gt;yesterday&amp;#39;s posts&lt;/a&gt;:&lt;/p&gt;&lt;div&gt;&lt;pre&gt;&lt;code&gt;90581 15 Apr 18:14 sl-overview.png
 33446 16 Apr 20:23 sl-overview.webp
392661 15 Apr 18:14 slstats-region-info.png
225392 16 Apr 20:23 slstats-region-info.webp
 36049 15 Apr 19:39 year-chart.png
 15590 16 Apr 20:23 year-chart.webp&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That&amp;#39;s a pretty reasonable saving!&lt;/p&gt;&lt;p&gt;So far all I&amp;#39;ve done is convert the few latest posts that make up the front
page of my blog, just so I can see what impact it has. I&amp;#39;m getting improved
load times on mobile, for sure.&lt;/p&gt;&lt;p&gt;There are a couple of downsides to this, of course.&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Now I want to do the whole blog, so while I can easily go through and
   convert all the &lt;code&gt;png&lt;/code&gt; files to &lt;code&gt;webp&lt;/code&gt;, converting all the image markup in
   the Markdown files isn&amp;#39;t &lt;em&gt;quite&lt;/em&gt; so simple, and even if I do write
   something to automate it, I&amp;#39;m then going to want to review it to make
   100% sure nothing has broken.&lt;/li&gt;&lt;li&gt;I can&amp;#39;t just then remove all the &lt;code&gt;png&lt;/code&gt; files to cut back on the space
   used by the generated site. The front page of the site has a feed, and
   all the &lt;a href=&quot;https://blog.davep.org/categories/&quot;&gt;categories&lt;/a&gt; have a feed each too. This means that
   there could be HTML out there from some of my oldest posts, referring to
   the &lt;code&gt;png&lt;/code&gt; files, and just removing them will result in broken images.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Overall though, it might be worth doing at some point soon. Meanwhile, from
now on, I think I&amp;#39;m going to replace my &lt;code&gt;pngcrush&lt;/code&gt; step with a &lt;code&gt;mkwebp&lt;/code&gt; step
and just use &lt;code&gt;webp&lt;/code&gt; instead of &lt;code&gt;png&lt;/code&gt; now.&lt;/p&gt;&lt;p&gt;I guess I&amp;#39;m all modern now!&lt;/p&gt;</content:encoded>
</item>
<item>
<title>PhD Defense: Jenna Kim on Wednesday, 4/8 at 11AM | UMass Boston CS</title>
<link>https://www.cs.umb.edu/web/posts/events/2026/04/08/JennaKim-PhDDefense.html</link>
<guid isPermaLink="false">6gV7I26ENG8fpdHjexnZrzcBYwFoXTwj3DE7xg==</guid>
<pubDate>Wed, 22 Apr 2026 08:41:10 +0000</pubDate>
<description>Streamlined Biomedical Image Processing Pipelines</description>
<content:encoded>&lt;p&gt;&lt;b&gt;Streamlined Biomedical Image Processing Pipelines&lt;/b&gt;&lt;/p&gt;&lt;p&gt;PhD Thesis Defense by Jenna Kim&lt;/p&gt;&lt;p&gt;Committee Members: Prof. Daniel Haehn (Chair), Prof. Jae W. Song, Prof. Tales Imbiriba, Prof. Nurit Haspel&lt;/p&gt;&lt;p&gt;GPD: Prof. Dan Simovici&lt;/p&gt;&lt;p&gt;When: 11:00 AM, April 08, 2026 (Wednesday)&lt;/p&gt;&lt;p&gt;Where: Pomplun Lab&lt;/p&gt;&lt;p&gt;Zoom Link: https://umassboston.zoom.us/my/jennajkim&lt;/p&gt;&lt;p&gt;This dissertation focuses on advancing carotid artery analysis through a series of visualizations and deep learning tools for calcified plaque assessment and related biomedical imaging tasks. Accurate plaque evaluation is essential, but current workflows depend on slow, clinician-dependent manual review. To address these limitations, this work introduces the CACTAS framework, a set of tools and methods that enable fast and reliable plaque segmentation for clinicians.&lt;/p&gt;&lt;p&gt;The first study, the CACTAS-Tool, provides a web-based labeling tool that enables clinicians to label plaque directly in three dimensions through a streamlined one-click interface. This tool significantly reduces the effort required to generate high-quality annotations by replacing slice-by-slice labeling with a more intuitive 3D interaction model, offering a faster alternative to existing manual workflows.&lt;/p&gt;&lt;p&gt;The second project, CACTAS-AI, automates plaque segmentation through a two-step deep learning approach. The system first segments the carotid artery to focus the search space and then segments calcified plaque within this anatomically relevant region.&lt;/p&gt;&lt;p&gt;The third study, CACTAS-UQ, investigates beyond segmentation by showing how confident the AI is in its predictions. To make this information accessible, the study introduces an integrated visualization that combines plaque composition, prediction probability, and uncertainty into a single unified view. Uncertain regions are visually flagged, giving clinicians a clearer picture of prediction reliability and supporting more informed decision-making.&lt;/p&gt;&lt;p&gt;This dissertation extends these visualization concepts to a microscopy-based biomedical setting through an ongoing collaboration with MYOTWIN, which serves as an extension of a DAAD-funded research exchange in Germany, where I was a visiting researcher in Summer 2025. This project focuses on the interactive visualization and analysis of calcium transients in engineered heart tissue, leveraging fluorescence imaging to study cardiac behavior.&lt;/p&gt;&lt;p&gt;Together, these studies advance the role of visualization and machine learning in medical image analysis, presenting practical, interpretable, and clinician-centered tools that enable more transparent and efficient assessment of vascular and cardiac imaging data. These contributions lay the foundation for future applications in clinical decision support.&lt;/p&gt;</content:encoded>
</item>
<item>
<title>10 formations pour devenir manager dans le digital</title>
<link>https://www.blogdumoderateur.com/formations-devenir-manager-digital/</link>
<enclosure type="image/jpeg" length="0" url="https://f.hellowork.com/blogdumoderateur/2026/04/formations-devenir-manager-digital-294x165.jpg"></enclosure>
<guid isPermaLink="false">j9Ikde3qNQAGzieEzFWpJ5RaUS25mp5LtzRxUw==</guid>
<pubDate>Wed, 22 Apr 2026 08:04:32 +0000</pubDate>
<description>Manager une équipe ne s’improvise pas et nécessite des compétences propres à l’écosystème numérique. Voici 10 formations, du MBA au module ciblé, pour accéder à des fonctions managériales ou renforcer vos compétences dans le digital.</description>
<content:encoded>&lt;img src=&quot;https://f.hellowork.com/blogdumoderateur/2026/04/formations-devenir-manager-digital-294x165.jpg&quot; alt=&quot;10 formations pour devenir manager dans le digital&quot; title=&quot;&quot;/&gt;Manager une équipe ne s’improvise pas et nécessite des compétences propres à l’écosystème numérique. Voici 10 formations, du MBA au module ciblé, pour accéder à des fonctions managériales ou renforcer vos compétences dans le digital.</content:encoded>
</item>
</channel>
</rss>
