Posts in Javascript (20 found)
Evan Hahn 3 days ago

Anecdotally, programmers dislike "reduce"

In short: from my experience, people like and , but not . I use functions like and all the time. When I put that code up for review, my peers rarely complain. I get plenty of feedback about other decisions, but not about my use of and . I cannot say the same for . Often, when I’ve submitted a patch with inside, I get a comment like, “this part is hard to read.” And I see way less than , , , and so on. Anecdotally, I have come to believe that programmers don’t like as much. I don’t know why, but I have a few theories: I usually just change to something else and move on. Even though I prefer it, I don’t usually care much. But it’s a little social phenomenon I’ve observed, and I thought I’d document it. I’ve also noticed this less recently, possibly because code review is less thorough nowadays. Do you notice this? Do you like ? Please tell me . is harder to read. is less familiar. can have worse performance compared to other options. is less elegant in languages I use, like JavaScript, Python, and Swift. In my blissful stint as a Clojure developer, I did not get this feedback. I’m wrong, and I’m seeing a trend that’s not real.

0 views
Farid Zakaria 4 days ago

A Nix store is three functions

While building trynix I needed somewhere to host a store-path that did not exist on cache.nixos.org . 1 I wanted to demonstrate that non-Nixpkgs store paths could be booted just as easily. The only requirement seemed to be a lenient Cross-Origin Resource Sharing (CORS) policy, , because the fetch happens in JavaScript. Turns out that GitHub Pages sets that header on every file it serves. 😈 I committed the output of to my Git repository and voilà, I have a free Nix substituter. I seem to be late to the party on this discovery. tomberek’s github-store is a cache assembled out of GitHub release assets. 2 GitHub Pages or Releases are a static file server. It has no idea what Nix is. If a humble file server can be a Nix binary cache, what else could we use? Turns out that in order to be a Nix binary cache, you must implement only three simple functions. The Nix client does not care what medium you use to implement them although HTTP is the most common and included by default in CppNix 3 . Anything that can answer those three requests can be used as a remote Nix store . 4 The reason we can be this careless about transport is that Nix does not trust it. A narinfo’s signature ( ) field covers , , and . It does not cover , , or . Once the archive is fetched, Nix decompresses it and checks that the matches. This is the special sauce of how packages that were signed by cache.nixos.org can be fetched from any other binary cache as an intermediary, and the signature still validates. The field does not even have to be on the same host as the narinfo. It can be anywhere on the internet, and it can be a different protocol than HTTP. Nix does not care. The only thing that matters is that the archive fetched from has the same as the narinfo. For protocols that are not included by default in the Nix client, you can always write an HTTP proxy that translates the three functions to whatever medium you want. In research for this post, I found a few interesting ones. gachix : puts the archives in git’s object database. Git content-addresses and delta-compresses blobs already, so the store dedupes itself; the author reports roughly 82% smaller than the equivalent plain cache. DNS : I wrote a proof-of-concept that puts the narinfo and 4 KiB slices of the archive in TXT records. The narinfo is small enough to fit on one record but the archive needs to be chunked. pastebin : a pastebin can hold the narinfo and the archive. The narinfo is small enough to fit on one paste, but the archive needs to be chunked. Many pastebins have an expiry policy which acts as a natural garbage collector. nixcache-oci : uses an OCI registry to store Nix archives. infinite storage glitch : encodes data within a video and uploads it to YouTube. “Everything is available on npm” – Some person on the internet Unsurprisingly, npm is a great binary cache and it has some interesting properties for release management we can ab use. emits a directory and npm publishes directories: a match made in heaven. 💑 Let’s walk through a small example. It is dynamically linked against glibc, so the closure is five paths and roughly ~36 MiB: We copy it to a local cache, signed with our own key, and add the one file npm needs ( ): then dutifully packages our complete closure for us: is now a real package on the public npm registry. It is now a substituter you can point Nix at directly: Note We have to use to run the binary because is a chroot store and all the are still under . If we had relocatable binaries we could run it directly. That is Nix fetching the complete closure from npm and running it. 🤯 We can distribute Nix packages to non-Nix users, let the infection spread! As an added bonus, similar to Nixpkg and NixOS we can get nice “channel” semantics by using npm’s dist-tags. The tag is mutable and points to the latest version, while each version is immutable and points to a specific store path. The major downside of this approach is that npm ahs no incremental publishing. Every version is a whole tarball, so fifty closures sharing glibc upload glibc fifty times. We could fix that by publishing each store path as a separate package, and then having a small index package that points at them. Each store path would then be uploaded exactly once. I won’t build that though as it’s not in good faith to the npm ecosystem. What other store implementations can we find? I was also waiting for @domenkozar to enable CORS on cache.nixos.org so I could use it.  ↩ In order to be a Nix binary cache, the prefix is stripped from the field in the narinfo, because GitHub releases are a flat namespace.  ↩ You can write a Nix plugin to implement a new protocol if you wanted.  ↩ We will see that that they must not all be all on the same medium, protocol or domain even!  ↩ I was also waiting for @domenkozar to enable CORS on cache.nixos.org so I could use it.  ↩ In order to be a Nix binary cache, the prefix is stripped from the field in the narinfo, because GitHub releases are a flat namespace.  ↩ You can write a Nix plugin to implement a new protocol if you wanted.  ↩ We will see that that they must not all be all on the same medium, protocol or domain even!  ↩

0 views
David Bushell 1 weeks ago

CodePen: exposed!

I promise this post is not merely a comment on a Hacker News submission because good lord that would be desperate but that is where I’m starting: They send all typed into editor input to codepen.dev almost immediately (you would see in 1-2 sec after you typed your secret that it appears in respective Network/Response tab) even before one saved it. Apparently CodePen 2.0 sends data to their servers as you type This post hit the front page with over 100 upvotes. The author seems concerned as if CodePen is doing something untoward. In reality that behaviour should be fully expected. CodePen processes your code to generate a live preview. And to the relief of our browsers that’s done server-side without shipping a JavaScript bomb. CodePen has other features like live collaboration that require it too. Technically an “autosave” feature can be done locally using browser storage but CodePen uses the server for that too. This allows users to restore unsaved changes from anywhere. You can test this by logging in cross browser, or deleting cached site data. The author concludes: Thus, if you ever entered some secrets in there by mistake consider them compromized even if you did not publish/save the pen Yeah, of course. Don’t be so cavalier with sensitive data on your clipboard! This is true for all web-based editing software. Google Docs, etc. They’re effectively keyloggers by design. It does raise an interesting question: what is reasonable to expect users to understand? It’s easy to respond “Well duh!” to our Hacker News friend (and I did). But why did I expect CodePen to behave this way when others didn’t? It’s a tricky question for product owners to handle. Burying details in the Terms of Service that nobody will ever read doesn’t help anyone (except lawyers). Gamers Nexus on YouTube recently exposed LG TV spyware doing really insidious stuff. We all know smart devices phone home but LG’s tactics are another level. Probably illegal. Consumer (and human) rights are certainly a reasonable expectation! The grey area between what’s obviously wrong and what’s reasonable to expect is murky. I’d highly recommend the CodePen Radio podcast where the team discuss product design and development. It’s very insightful. There is one aspect of CodePen that I think is a much bigger concern for users and far less understood. This one is “hidden” in CodePen’s Terms of Service . All public code snippets are MIT licensed: Public Pens you build on CodePen are MIT licensed, meaning other people are free to use it for whatever they like under the terms of the MIT license. Don’t put anything on CodePen where that wouldn’t be OK. Your License to Us I’ve seen a lot of stuff shared on CodePen where I wonder if the author intended, or even had permission, to effectively give it away for free. Personally I think CodePen using the MIT license this way is a brilliant idea. It allows CodePen to do what it does. Most user-generated content websites have absurdly long terms around content rights. GitHub’s user-generated content section is just shy of 1500 words. Using the MIT license solves this problem succinctly. I’ve pasted the entire license below. You really should have it memorised by now. It’s the Lord’s prayer for developers. Copyright [YEAR] [COPYRIGHT HOLDER] Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. The MIT License - Open Source Initiative This is not an advertisement for CodePen Pro but you can pay to retain ownership . From what I’ve witnessed over the years, users (and developers) simply do not understand copyright and licenses. I’m surprised CodePen doesn’t make this a little clearer. But then again, I’ve not heard of any disputes over “stolen” code, so maybe it’s another non-issue? With CodePen 2.0 exposing the file system I’d like to see a for every pen. Would that work? What happens if users try to change or edit the license? Does it need to be locked? I don’t know the best solution but I’d like to see the MIT license visible. No spicy drama here I’m afraid! Just a public service announcement and unanswered questions around user expectations. CodePen is not even selling your data for model training, what a bunch of bores! On that note, I guess with LLM code washing it doesn’t matter anymore :( Thanks for reading! Follow me on Mastodon and Bluesky . Subscribe to my Blog and Notes or Combined feeds.

0 views
Takuya Matsuyama 1 weeks ago

How I designed my SaaS landing page with AI tools

Hey, what's up? It's Takuya here. Recently, I rebuilt the landing page for Inkdrop , which has a demo app built with the real components. It self-plays an editing session when you visit, and at the same time, you can actually edit notes in it. I just posted a new video on YouTube where I share how to build an interactive self-typing editor demo with Waku + CodeMirror . The video walks through the steps in the way of traditional manual coding. But I used Claude Code to build it in my real workflow. In this article, I'd like to share how I experimented with designing using AI tools, which websites I referred to, and what prompts I used. The video focused on the technical part of the editor demo, while this article focuses on the design part. The end result is not what I directly created in one iteration. Of course, I explored a lot of other ideas and possibilities. Here is what I did. I've been thinking of building a new website since last year. I've been taking notes for inspiration like this: As you can see in the note, I've been considering using Three.js to create an interesting effect to make the page attractive. And I'm a huge fan of Oğuz's works . So, I really wanted to replicate his design style. Last year, I used Vercel's v0 to design a TOTP form, and it worked great. So, I tried v0 again. I iterated on a few prompts, but the results looked mediocre and boring. After that, I tried GPT Images 2.0 and it blew my mind. The output image looked so nice: I don’t usually overreact to AI hype, but GPT Images 2.0 is actually insane. I got this landing page sketch for Inkdrop from a one-shot prompt that included summaries of my app concept, the new features in v6, and my recent blog posts about Japanese culture. I never imagined web… pic.twitter.com/V7HJwIwU0e So, I fed the image into Claude Design to implement it: The layout was somewhat broken, but it got a little bit better than v0. However, the Japanese prose ("春は曙、やうやう白くなりゆく山ぎは。") doesn't make any sense on a product website. Here is another iteration: lol, this one was too Japanese-ish. Who'd expect to see a red sun on a SaaS landing page? Also, I've noticed recently, when visiting other websites, that Claude has a tendency to generate a similar look and vibe. So, I disliked these taste slops . I also tried VoltAgent/awesome-design-md , but it just generated an average design. Maybe useful for creating websites that don't have to be unique. It wasn't for this project. I was planning to use Ship Studio , but didn't use it this time. These experiments weren't waste of time. It let me quickly try various ideas and find a right direction. And it was fun to try new AI-native design tools. I think they should work way better than when I tried. While checking out Oğuz's work like this and this , I found that they are absolutely cool, but there is a critical issue in these designs. That is, I couldn't understand what the product does and how it works because my attention was caught by these amazing UI mocks and animations. I simply couldn't focus on the content. Also, it seemed hard to maintain as a solo developer. On the other hand, I liked simple websites like Sketch , fly.io , and Stripe . So, I decided to go with this simple design direction. What I don't like about screenshots and videos is that I can't actually try the product. Of course, it's technically impossible if your product is a native app like Sketch. But mine is an Electron-based app, whose UI is compatible with browsers. Since I really wanted to replicate Oğuz's style, I tried to mix these ideas: This way, I can achieve an attractive design while providing a demo so visitors can quickly understand how it works. I refactored the desktop app source to extract its React components into a separate library called . It uses Storybook to check the component designs. So, I can easily update the website whenever I change the app's UI. I followed Radix UI 's design pattern. This wouldn't have been possible without the help of AI agents, because it required lots of work! Here is the idea note that I had AI read. It explains the idea briefly: Then, I asked AI to create plan notes with implementation phases in detail. This one ended up being a super long note: I refactored and reviewed the components one by one, not in a single shot. Claude Code often made mistakes and broke the component behaviors. You may know I've been publishing videos on YouTube ( devaslife & craftzdog ). I wanted to let visitors from my channels know it's my product, without showing my face or mentioning the channel names. So, I decided to record footage of myself composing a tech note and put it behind the app demo. It resulted in achieving two "wow"s: I think these wows genuinely contribute to understanding the product rather than distracting from it. As you can see above, my basic workflow is to have AI write a plan first. Inkdrop supports note templates , and you can pick the " Implementation plan " template to replicate the process. So, I prompted like: then, Claude Code filled out the template via the MCP server . After reviewing the plan, said: then, commit it, and: Claude Code regularly updated the plan note as it discovered unexpected issues and new decisions. I didn't paste code into the chat input. Instead, I constantly pointed at the desktop app's source, Storybook stories, and docs: When I found a bug where the demo frame was broken on smaller screens, I reported it with three things: where it happens, what I observed, and my guess. For example: I still read source code in agentic coding, and I often find weird code structures. So, I ask questions like: When Claude Code added a dirty duplicate function or workaround, I said: When starting something new, I deliberately under-specified and asked for a static skeleton first: This step lets me visually check what should come next and come up with new ideas. Here is the end result: That's it! I hope it's helpful for designing your product page with AI. A demo with real UI components that showcases the look and feel A real editor component that lets you actually try it right on the webpage Wow, the editor syncs with the background footage! Wow, and I can actually try the app here!

0 views
Xe Iaso 1 weeks ago

It took a year to ship WebAssembly in Anubis

After a year of work, hundreds of commits, 5 generations of pull requests, dozens of tests, rewriting part of Anubis in Rust, the first compiler bug of my career, and at least three times making my tower run out of ram I think I have finally done it. The next version of Anubis will ship with WebAssembly-based proof of work checks that admins can enable in their thresholds or bot rules: This makes Anubis challenges use a memory-hard proof of work function ( argon2id ) instead of just a CPU hard one. It also means that the "hey Claude vibeslop me a CUDA Anubis solver" route is on its way to being fundamentally dead. Overall, this project taught me a lot of things about how WebAssembly works in practice, what the rough edges are when operating on the bleeding edge like I am, and led to the first genuine compiler bug of my career. Today I want to take you through the journey and process behind adding these WebAssembly based proof of work checks and all the problems that came up along the way. The big impetus behind wanting to use WebAssembly in Anubis is that there's a constant tension that's underlined a lot of the performance work I've been doing: phone CPUs suck and trying to balance equitability to phone CPUs with trying to punish scraper CPUs is a huge challenge. Note To be clear: I am tackling the "Anubis makes my phone overheat" problem. It's hard to just lower the difficulty for phones without giving attackers enough information to lower the difficulty for scrapers. If you want to contribute performance data so that I can improve my classification process and/or target future development, please get in touch with me . I want to make things better but can't without data. I am actively testing with a Moto G8 Power alongside my normal testing steps. Using WebAssembly here means that the binary will run faster, which will make Anubis go away faster, which is what we all want, right? One of the other big advantages of this setup is that it lets clients and servers run the same binary in order to solve and validate challenges. Note that I didn't say the same code, I said the same binary . This means that the client and server are in lockstep, much like the CIC chip in the NES . This also does mean that a bug in the shared code means that the client could send an invalid value that the server would accept as valid, but I don't think this is a practical concern until something like that happens. Right now the challenge has two implementations in Anubis: one in JavaScript and the other in Go . Updating it in one place means making the same updates in other places and also making sure that everywhere else that assumes how challenges work is also updated. Note Yeah, it's probably bad to have made those assumptions about how challenges work across the codebase. There's a lot of weird technical debt here that needs to be solved at some point. I expect that to be a fair bit of effort to untangle, it may end up with keeping the challenge only for tests when the WebAssembly based routes are working out well enough to make the default/only option. Making the same binary run on both the client and the server means that future experiments like per-client program synthesis can also proceed. I'll get more into that sometime in the future. Right now the core of Anubis is written in Go. Go's standard library HTTP server is surprisingly performant and is the key component of things like Google's HTTP frontend service. I have no intention to rewrite Anubis in Rust or anything as drastic as that. However breaking up Anubis from one monolithic binary into what amounts to a plugin loader is a lot more interesting from a maintenance standpoint. One of the main problems with Anubis is that the combination of rules I've set for myself (no CGo allowed and everything must cross compile to prod from my MacBook) means that updating any part of Anubis means recompiling all of Anubis across prod. Making Anubis be able to have parts of it downloaded and swapped out at runtime is really interesting to me because it means being able to adapt to threats as soon as the threat actors change behaviour . Also I figured out pretty quickly that Rust builds some of the smallest WebAssembly binaries that run hilariously fast, so I wrote all the WebAssembly code in Rust (no-std, wasm32-unknown-unknown)! Along the way this also lets me fix one of the bigger administrative problems with Anubis: challenges scale incorrectly with the challenge. Anubis uses string comparison to count the number of leading zero nibbles in a hash instead of the number of leading zero bits . This means that adding one (1) to the difficulty of a challenge makes it 1024 (one thousand twenty-four) times as hard to solve in the worst case. The original post has an interactive diagram that shows the difference in scaling at play. This was a mistake in retrospect, but if we're changing details about how challenges work here then we may as well wrap up the fix into that. In a normal world you'd not have about half of the restrictions that I have when working with Anubis. Normally you just write your code, compile it to WebAssembly, and then make sure it runs on modern browsers. This is nice and simple. I wish I could live in this world. Comparatively, this is what it's like getting all of this working across browser versions, platforms, and so many other things: Anubis supports Chrome 75 and newer. I wanted to reduce the support range to not have to deal with Chrome that old (the feature difference between Chrome 155 and Chrome 75 is absolutely massive ), but there are a lot of smartphones, smart TVs, and other electronic devices running Android out there that are just marooned on Chrome that old with no path to upgrade it . As a result, I gotta target Chrome 75 for features, but out of an abundance of caution and to make sure that things are generally compatible with browsers older than Chrome 75 (eg: old iOS releases), I target my JavaScript to Chrome 66 as an abundance of caution. I haven't been able to test with iOS or Android versions of the same vintage as Chrome 75, so a lot of this is aspirational backwards compatibility. I sure hope it's good enough! When you're doing this kind of work, you're effectively writing an operating system kernel that makes the WebAssembly guests run as processes. Any calls you pass to the WebAssembly guest are effectively system calls into the host. Normally I'd love to be able to reuse the WebAssembly Component Model so that a lot of the "hard part" is done for me. WebAssembly Components make you define your calls in packages that contain interfaces with functions in worlds. Here's an example of the kind of WebAssembly Interface Types world Anubis would need: If this worked (I wrote this all out in one go based on how the current handcrafted ABI works and have not tested it, sorry), this would describe the shape of the API that we need for doing the proof of work operations. At the time I wrote the API/ABI that Anubis uses, the main tool for generating the server-side bindings for WebAssembly Component Model stuff in Go gravity didn't support passing records (structs) or bytestrings ( or / ) from the host to the guest. As such, I had to do it by hand. So given that we can't do it the "right way", we have to do it the "bad way". Also given that no matter what I pick for this I'm going to be "wrong", I just decided to treat the WebAssembly modules as dynamic libraries that just happen to use WebAssembly as an implementation detail. There only needs to be three buffers that are read from / written to, so let's just focus around those: Challenge modules also expose two entrypoints: Challenge modules also import from the environment so that they can periodically report their hash rate back to the frontend. This allows users to see a progress bar based on how long it should take to finish the process. This works enough for now. It'll be interesting to see how this falls short in the real world! Honestly, the first bit of this took a few days at most. Most of the hard work was making sure that pointers, offsets, and whatnot were all wired up correctly so that the browser worked the same way as the server. My experience building and messing around with many WebAssembly runtimes and egregious hacks meant that making it was really easy for me. The devil came out in the details. Here are all the things that came up while I was working on this. All of these problems added up is the sole reason that this took a year instead of a week. Early on in development I found out that WebAssembly has a SIMD extension . SIMD stands for Single Instruction Multiple Data and is a family of instructions that let you do operations on multiple values at a time. This gives programmers data-level parallelism (this is distinct from multi-threading) so that things like hash calculations and MP3 decoding can be done faster than they would be if each operation had to be its own instruction. CanIUse considers WebAssembly SIMD to be "baseline" (supported by browsers newer than Chrome 91), but I have a lower version bound of Chrome 75. However the benefits from SIMD on mobile devices are so drastic that it's worth having two builds of the WebAssembly code: one with SIMD and one without it. In the browser it dispatches which version to use by using wasm-feature-detect to probe WebAssembly functionality by trying to parse/run trivial minimal programs that exercise those features. Hopefully I don't need to add an additional build into this process, but there is nothing in the tooling that would prevent it! One of the big things that blocked this shipping for so long was not having an escape hatch of some kind to allow clients that disable WebAssembly by policy to get through the gate. In my experience most clients don't have JavaScript enabled but WebAssembly disabled, however there are a few notable usecases that forced my hand: iOS Lockdown mode and GrapheneOS' Vanadium 's default configuration. This combination of factors means that there would need to be another implementation of the proof of work code in JavaScript that would actually execute the number crunching. I don't want to make another implementation of the proof of work function (the entire point of this is to only have one implementation!) so while I was browsing around I came across the legendary talk The Birth & Death of JavaScript and got a horrible idea. What if you just compiled the WebAssembly to JavaScript? Would that even work? It's "just" turning one Turing machine into another, right? How would it fare in practice? Turns out I'm not the first person to think about this! The team behind binaryen have made this escape hatch in the form of wasm2js which takes WebAssembly binaries and produces moderately cromulent JavaScript in response. One of the main downsides is that the generated binaries tend to be rather large. For example consider this simple WebAssembly module that exposes a function that adds two numbers together: Seems simple enough, right? Here's the JavaScript that generates: As you can imagine, this only gets progressively worse as you end up making the Rust standard library get compiled from WASM to JavaScript, and even worse when you actually get hashing functions into the mix. The end result is probably very optimized when you run it through a JIT, but given that this runs on an interpreter it's probably gonna be slow no matter what I do. Sorry! I tried! This ended up working fairly well in testing, but I tried building it in GitHub Actions and ran into an issue. I noticed that wasm2js was packaged in Ubuntu and that the version in Fedora worked fine but the version in Ubuntu did not and threw an obscure error message about not understanding the tail call extension that Rust was using for some reason. I ended up bisecting versions of binaryen by downloading a tarball, building it from source, and then seeing if the result of running it on the Anubis WebAssembly modules worked in a browser. I ended up selecting Binaryen version 128, the newest version at the time. It was new enough that most of the distributions that package Anubis don't have that version of Binaryen packaged. However I didn't want to make my life more complicated by having some kind of conditional compilation step that would effectively tell end users "sorry, the admin is using an unofficial build that just so happens to not support your browser, please complain to them" because they'll just end up complaining to me. In my experience the kinds of people who run this exact combination of circumstances also tend to be the kind of people that have a wide variance in the level of kindness they display to the authors of open source programs that happen to be in their way. So I needed an escape hatch that would force build systems to use the exact version that I use in my builds. Then inspiration hit me as if Apollo himself sniped me from the heavens. We're dealing with WebAssembly here right? What's stopping us from just compiling the WebAssembly to JS tool to WebAssembly with some kind of reproducible build, committing that blob to the repo, and then moving on with life? I ended up finding a bug in LLVM around how it was iterating over exception handling blocks by the compiler iterating over them in machine pointer order. As a result each build would drift by about 29 bytes per build: This is what lead me to write I hate compilers as a combination blogpost/cry for help which made me realize this was actually an LLVM bug. I didn't instantly lean towards it being an LLVM bug until I figured out that disabling ASLR (via ) made the results consistent on the same host within the same boot. Honestly this is the first time in my career I've ever run into a compiler bug like this. When I do a lot of my work I usually work under the assumption that the compiler is bug-free and that my inputs are wrong somehow. As such, even thinking it could possibly be an LLVM bug was just outside of the realm of possibility for me. Once that LLVM bug got fixed and a new version of wasi-sdk with that fix got released, I was off to the races, updated my build of wasm-opt/wasm2js, made my build scripts run it with wasmtime (alongside a wazero-based fallback process that would be slower, but did work enough) and everything worked out. Every time Anubis builds the WebAssembly in CI it uses the version of wasm-opt and wasm2js that ships in the repo to ensure that everything is as byte-for-byte deterministic as possible. Your build tools can't differ from my build tools if I ship you the build tools I use. Then we get into the other big problem that made this difficult: browser testing. One of the most common failure modes of Anubis is that someone uses some browser that I don't test in CI and then things don't work with it. I'm tired of installing 50 different browsers on several machines to test things and I have gone through so many throwaway VMs that I'm sure it's reduced the lifetime of my SSD. Note Yes, I really have been testing Anubis by hand in god knows how many browsers. Why do you think it takes so long to tag new releases? I built a harness that I call "chromesweep" that lets me spawn many Googles Chrome (term c.f. Attorneys General, et.al) in their default configuration to try and hit a version of Anubis listening over HTTPS. Getting this far meant making a library of all of these browser versions. I've put that library up on Github at TecharoHQ/gubal in case it's useful for you. One of the other big problems I ran into while getting browser testing working was making sure that my Googles Chrome strictly stay within the bounds of my Kubernetes cluster's network. Chrome this old is actively radioactive and I want to treat it like the security threat it is. As such, I set up a strict NetworkPolicy to only allow it to access the Anubis instance under test and make DNS queries. I also wrap each Chrome pod in a microVM with Kata containers as an additional layer of security. Note It honestly terrifies me to think that I am putting more effort into securing these Googles Chrome than big AI companies are putting into securing their AI agent testing infrastructure . It literally doesn't take much to put a big dent into securing things! The current state of our industry boggles the mind. All I want is for Techaro's FelonyBench score to remain at 0, is that too much to ask? I also rigged the browser testing infrastructure up to a single slash command in GitHub pull requests. Doing it makes my office very warm so I try to avoid doing it when possible. This works well enough that it lets me move on to the next stage and has already caught something that lead to building all the JavaScript with the flag. I wonder if this is yet another case where making infrastructure for Anubis could result in that infrastructure alone being its own viable tech product. I run into a lot of those. In the process of doing that automated browser testing I found out that Chrome 75 had a weird error pop up when it tried to compile Anubis' WASM to native code: This also caused failures up to Chrome 100, so this signaled to me that something I was doing with my "strict MVP" build of Anubis' WASM wasn't in fact sticking to just the MVP features of WebAssembly. It turns out that the function referenced (probably somewhere in std::sync::Once ? I probably should have traced it down to the exact bit) was in the standard library. This surprised me because I assumed that building Rust code with CPU features selected would apply that to everything, including the standard library, right? No, turns out that when you download the component in rustup , that doesn't just download the standard library. To aid in cross compilation and I guess to avoid disk space waste, the Rust standard library is precompiled. This surprised me as Go typically has you recompile the standard library (and runtime for that matter) when doing normal builds and cross compilation. Note The actual issue here is that the stdlib function in question was compiled down to use reference types , which made references get stored as a table index instead of what MVP WebAssembly would put there. Chrome tried to read a null byte, got not a null byte, and then understandably exploded. I looked into the process involved for rebuilding the standard library twice: once with only MVP wasm features enabled and once with an "all yes config" like usual. Based on some research I did this seemed like a massive pain. However, I had gone through that effort to build reproducible WASI versions of wasm2js and wasm-opt. wasm-opt is a tool that lets you take compiled WebAssembly modules, optimize them, and more importantly remove features from them so they can run in older browsers. After a bit of hacking to make sure that the tools were able to run properly, I set up a Claude Opus / GLM 5.2 loop to fuzz various wasm-opt flags and make sure Chrome 75 could parse the output. I ended up with these flags: This strips away all the other WebAssembly features from the build like unwanted paint (the flag means "disable everything not in the original MVP definition of WebAssembly"). I think that it'd be safe-ish to enable reference types in the SIMD build (they were added before Chrome added SIMD), but it's not hurting anything to remove them so I'll just let cowardice win here. Either way, I threw the results into chromesweep and got a successful response, so win! If/when this comes to bite me I'll try and improve it. I'm pretty sure that this work isn't perfect , but at some point you gotta cut your losses, ship it, and then see where things fail to prioritize perfecting it. These issues include but are not limited to: Overall though, I'm hopeful that most of the worst parts of this can be solved. It would be nice if I didn't have to work what amounts to two full time jobs. I'm pretty sure that this is stable enough to ship as off-by-default in Anubis v1.28.0: Wuk Lamat . Based on the feedback I get from administrators and users, I'll enable it in the default configuration in Anubis v1.29.0. I hope this look into how Anubis is developed can give you ideas as to the scale and challenge involved. Making something like this is tireless and thankless work and it's really weird to see people talk about it in the same breath as Cloudflare or AWS' WAF. Have a good day all! Note AI was not used in the production of the prose of this article. I have my draft as a Google Doc so you can see exactly where and when I typed every word myself. The only use of AI was Claude Opus to help me make the visual bit/nibble diagram. The data buffer: up to 4096 bytes of challenge data. This is 4096 bytes so that it can (hopefully) land in its own 4Ki machine page. This is the only variable-length buffer in the setup, so it needs two calls: : return the pointer to the data buffer in WASM linear memory. This is used as the base address for copying data into the guest. : update the globally mutable "data length" variable to signal to the guest how much data was actually written into the data buffer. The combination of these two calls lets you treat that global data buffer as a slice. The result buffer: a challenge-defined buffer that usually has about 32 bytes of data. This is read out of the guest's linear memory when the challenge is done processing. : return the pointer to the result buffer in WASM linear memory. This is used as the base address for reading out of the guest. : return the length of the result buffer (a compile time constant based on the needs of the challenge, but the runtime can't know that). The verification buffer: a challenge-defined buffer that usually has about 32 bytes of data. This is written into when the server is validating a challenge. : return the pointer to the result buffer in WASM linear memory. This is used as the base address for reading out of the guest. : return the length of the result buffer (a compile time constant based on the needs of the challenge, but the runtime can't know that). : the main entrypoint for browsers. Given the data loaded into the challenge buffer, hash it in a tight loop until you get a solution that matches the difficulty. : the main entrypoint for the server. Given the data loaded into the challenge and verification buffers, ensuring that one run through the hashing function produces a result that both meets the difficulty demands and exactly matches what the client sent. The wasm2js flow doesn't currently have a way to update the progress bar with its import stubbed out. I have no idea how to properly wire that up with the constraints of my runtime, but I'm sure I can figure it out eventually. The WebAssembly that's shipped with this flow is ridiculously performant. This may mean you need to adjust the difficulty to compensate for this. Oops! Sorry! This is better on mobile phones, but I'm working on a mobile request classifier that will use a combination of IP address reputation, TLS fingerprinting, and other signals to determine if a request is from a phone and give it the appropriate amount of grace. This is hard. Please contact me if you have ideas on how to make my current prototype better. I need some way to dynamically rotate out challenge programs at runtime instead of just at compile time when I work on Anubis. Eventually I hope to have this pull WebAssembly files and supporting bundles from OCI/Docker registries, but I need to do a lot more experimentation with this before I can conclude how good/bad of an idea this is.

0 views
Stratechery 1 weeks ago

An Interview with OpenAI President Greg Brockman About Astra and Alignment

Listen to this post: Good morning, This week’s Stratechery interview is with OpenAI President and co-founder Greg Brockman . Brockman dropped out of college in 2010 to join Stripe, and rose to become the company’s CTO; he left in 2015 and co-founded OpenAI, where he also served as CTO. Today, after an interesting few years, Brockman is President of OpenAI, and is the face of yesterday’s announcement of Astra , OpenAI’s newest model. In this interview, recorded before the Astra announcement, we discuss Brockman’s background, his time at Stripe, and the early years of OpenAI. We touch on the ChatGPT launch and the drama of 2023, and whether or not having a billion users is actually a disadvantage. We also touch on OpenAI’s place on the value chain, and their competition with companies closer to consumers, like Microsoft, and their suppliers, like Nvidia. We also talk about Astra and OpenAI’s stated commitment to alignment, and debate whether or not OpenAI took security seriously in the run-up to the Hugging Face incident. As a reminder, all Stratechery content, including interviews, is available as a podcast; click the link at the top of this email to add Stratechery to your podcast player. On to the Interview: This interview is lightly edited for clarity. Greg Brockman, welcome to Stratechery. Greg Brockman: Thank you for having me. Excited to be here. So we obviously have a massive amount of news to get to, but given this is the first time we have talked, I don’t want to pass up the usual Stratechery biography question I ask anyone. I do want to ask — do you define North Dakota as being a part of the Midwest? All right, well, a fellow Midwesterner, of course I have to spend some time there. You went to school in Boston, as they say, but before we get to there — you have an amazing resume even before you get to school, like the International Olympiad , but in chemistry. Where did the computers come in, or were computers a part of your life from the beginning? GB: Well, computers were always in the background for me growing up. I loved to play computer games, but I was really into math, I was into science. I actually thought that I was going to potentially be an actor all the way through ninth grade — I was very into acting and performing as well, and dabbled a little bit in philosophy. There’s a curveball! I did not know that was coming, but I’m going to figure out what the connection is between what you do now and acting, but let’s continue. GB: Well, I felt like in ninth grade, I’d been the star of, or the male lead in, a play or two in my middle school, high school. And I was thinking about, I wanted to double down on something, I felt like I could be best in the world and really try to move the needle in a field, and I felt like I either had to pick a more cerebral, hard sciences approach or more of the arts and acting direction and creative route. I ended up picking the hard sciences one, because I felt like maybe that was an area where I could most make a difference in the world. And why did you think you could most make a difference in the world by going in that direction? GB: I guess for me it felt like — one of the things I loved about acting was actually the group aspect of it. I loved improv, you’re just creatively thinking about things, you’re bouncing ideas back and forth, but it also really requires being part of a team that works together super well, and that’s something that’s not always guaranteed. That’s a hard thing to accomplish and find. The thing that I really liked about the more cerebral route is it feels like you sharpen your own skills. One thing I did learn, actually, was that even if you’re great at writing code, that’s not enough. It is actually about also bringing in that great team, and that’s part of, I think, what my career has been about — really helping to build and shape environments and culture that are actually able to deliver great results. One thing that’s interesting, there’s an aspect here about environment also shaping some of these things. You mentioned you were always the male lead in plays and dramas. I just recognize from my kids going through this era — my daughter was very into musicals and stuff for a while — there’s intense competition for the female lead, and usually if there’s just a competent male who’s willing to volunteer, he gets the role every time. Was there a lot of competition for the male leads, or were you one of one? GB: (laughing) I think that might explain it. I’ll tell you a story, though. What about the flip side too? Being in North Dakota, were there not a lot of people super intense at the hard sciences, and so did that almost seem like a rarer thing by the same token? GB: Well, I’ll tell you two stories. So one on the acting front. My first ever paid job was an acting gig. Mannheim Steamroller was in town in Grand Forks, North Dakota. Are you familiar with Mannheim Steamroller? I am. Yes, absolutely. GB: So they were putting on a concert, and they needed extras. They needed people to be these tin soldiers to walk around, because it was a Christmas holiday concert. I went to the audition, and I was this scrawny ninth grader, and there were all these big college students there. What they told everyone to do is, “Okay, everyone march in that direction”, and then the casting people would compare notes and you’d have some downtime, then they’d say, “Okay, march in the other direction”. What I noticed is that all these college students during that downtime were talking to each other, hanging out, and I was like, this job has one requirement, which is you’re going to be six hours at attention the whole time walking around this concert. So during that downtime, I was there standing at attention, just being in character. At the end, they said, “Okay, we’re selecting this person, this person, this person. Everyone else can leave”. I was not picked. But on the way out, they said, “Actually, we thought you were amazing. We loved seeing how much you were dedicated to this, so we’re going to actually make a new role for you”. And so I got to be a gingerbread man, and they gave me a whole costume. And that was my first job. So it was a little bit of trying to be out of the box in order to try to get the job done — not always defaulting into the role, but trying to find creative ways to get it. But I think that when it came to growing up in North Dakota, one of the things that was really great was that it was possible for me to really excel and be the best in the state at different areas that I put my mind to. So I was advanced in math. I ended up going to University of North Dakota starting in 10th grade and taking a bunch of courses there. I got very into math competitions, and then I’d go to the national competition, I’d go to the national math camp, and there I would meet the best in the country. These people were so amazing, and actually, one thing that’s been a real privilege and honor is that many of the people that I really looked up to at math camp now work at OpenAI. So I’ve gotten to see them in this new field, this new light. That’s amazing. GB: But it was both that I could really chart my own course. I started doing math research, and I know that if I’d gone to some of these high-powered high schools or these much more competitive states, I think I wouldn’t have stood out. I would have had to be in the standard track. So it was by being in this area that I was able to explore my interest and really march to my own tune. So when people say they went to school in Boston, they usually mean Harvard, which is where you started. Then you switched to MIT. And then at some point you’re working for Stripe. What’s the sequence there? When did you meet the Collisons? What happened in Boston? GB: Well, after high school, I took a year off, and I actually started working on a chemistry textbook , because I’d gotten very into chemistry in high school, competitive chemistry, and come up with a unique way of thinking about it. Very first principles, very mathematical, rather than memorization. I wanted to teach that, I wanted to propagate that. So I actually wrote 100 pages. It’s on my website right now. I haven’t finished it. I’ve been intending to get back to it. That’s like a retirement project, I love it. GB: Exactly. Well, at this point, I think you can just ask Astra , and it’ll do a great job. But I was trying to figure out how do I get this thing published, so I asked one of my friends who had done something similar in math, and he said, “Well, you don’t have a PhD, so no one’s going to publish it. So you can either self-publish” — and I was like, oh, that’s a lot of work, a lot of capital — “or you can make a website and try to promote the ideas that way”. And I said, “I guess I’m going to learn how to code”. So I went on W3Schools . Do you remember W3Schools? Have you ever seen that website? No, I don’t think so. GB: Okay, so this is the classic — they have an HTML tutorial, JavaScript, CSS, PHP. I read through them and I was like, “I should test this out”. I remember I built a little first widget: you could click a table column, and it would sort the rows accordingly. It was the coolest feeling ever. I had this thing in my head that I was picturing, now it’s in the world, and anyone can benefit from it. They don’t need to understand the details behind it, it just works. I remember the very first thing I built that had users, it was actually a competitive chatbot game, and I got 1,500 hits from StumbleUpon one day. It was the most glorious feeling, there were 1,500 people who had played with my game, had hopefully enjoyed it, but they stuck around enough to play it. I was just like, “This is what I want, I want to help people, I want to benefit people and build for them”. So that’s what I showed up thinking I was going to do at Harvard, that really had changed for me. I thought I was going to do these more eclectic interests, and instead I was like, “I just want to build”. Freshman year at Harvard, I was in this computer club, there were these two seniors who would have obscure technical debates every single time. We would all listen and say, “One day we’ll understand, one day that will be us”. But then they graduated, and sophomore year came. So did you just switch over to MIT because you realized you had this focus on coding and building? GB: That’s basically right. Because sophomore year, I was running the club. I was supposed to be having the obscure technical debates, and I was like, “I’m not ready. I have so much to learn. I need to be around people who are so much better than me”. GB: And so I spent so much time down at MIT, and I was like, it just makes sense to transfer. Got it. So when did you meet the Collisons then? GB: So I met them in 2010, late 2010. We had a lot of mutual friends, because John had gone to Harvard, Patrick had gone to MIT, and they were poking around. That team was poking around trying to find who’s into computers at either of these schools, and my name kept coming up. GB: So I got a reach out from the team, and I flew out. And I remember meeting Patrick, really, for me, was the moment I was like, “All right, this is someone I want to work with, I feel like we could build something great together”. So my next question was what attracted you to Stripe , but it sounds like you just answered it. What did you learn there? You were pretty early on the team, progressed very rapidly. By the time you left, you were CTO. What was the takeaway for you from Stripe itself and Stripe scaling, but also yourself growing so rapidly inside this company that is itself growing rapidly? GB: First of all, for me, it’s always been about the people. I knew that these were people that I wanted to work with, that it felt like we could learn together, we could accomplish something great, and so that was a real key. And by the way, dropping out of school twice is something that I do not wish on anyone’s parents. It definitely was something that was difficult for mine, but they actually were very supportive in the end. No, we’re getting the idea, you take things to the extreme, right? Most founders drop out once, you had to do it twice. We’re getting the drift. GB: Exactly, exactly right. I remember a lot of the early days of Stripe was really about first principles thinking. We were in a domain, this credit card industry, that is very opaque, very Byzantine, it’s been built up over many decades and has so much complexity that the card networks themselves run on ISO 8583 , the spec from the ’80s. It’s a byte-oriented format, the whole thing. And we were trying to figure out, “How do we make this simple, dead simple, for the Internet era?”. So a lot of this is about deep understanding of a domain that you have no familiarity with. None of us really grew up as payments experts, but you just want to really deeply understand how it works so that you can expose the right primitives and the right APIs and abstractions. That to me was actually the core skill, and something that has been very transferable between Stripe and OpenAI. They’re very similar in some ways, where you go and you have to scientifically learn about a domain. AI versus payments, obviously different in terms of the specifics of those domains — one is much more about natural science, the other is almost this system that has been built up of complexity — but they are fundamentally about understanding the underlying why of how something works and exposing it in a way that’s simple and easy. I spent a lot of time on recruiting, a lot of time on culture, I think one thing I found is that I love coding, I love that feeling of flow state and just building and creating. Right, you’re legendary for these hours-long flow states and just coding endlessly. What’s the longest coding session slash flow state you had while building Stripe? GB: Oh, it all blurs together. I couldn’t possibly say, but I would just say that for me, there was this 24-hour sprint that was how we actually got onto the credit card networks. That was just this really great time that all of us were there together in the office, none of us slept, and it was supposed to be an integration that was going to take nine months, we got it done overnight, and if we had missed it by a day, it would have been another month. As a startup, every day matters, so that kind of accomplishing what seems impossible otherwise, I love it. That is something that was incredibly exciting. When you became CTO, you wrote a post saying how you were talking to other CTOs, you thought it was more of an architectural job, none of them did that, and you’re like, “I feel like I lose my feedback loops, I’m not connected to the product, I need to code again, I’m going to become a coder”. I’m curious, you wrote that towards the beginning of being a CTO, how long did that last? How long did you stay in touch with coding? GB: Well, I would say probably for almost another decade. One area that I think I’ve grown on and that I’ve really learned is how to stay in touch and really help move forward a team and bring together a team, even if you yourself are not hands on keyboard. And by the way, I will say that this is something that I think is actually an important lesson almost for every software engineer now, because the act of what coding is has changed so significantly over the course of the year. I think over the next year it’s going to change even more. We are all moving away from having to be the one who knows exactly which library to use and is able to craft the syntax and where the semicolons go. We are all moving to being these higher-level managers, these directors, the source of the inspiration, the vision, the judgment, the feedback. I think that shift, it’s been something for me that was difficult, because you had to let go of something that I was used to and valued and really loved. But I’ve actually replaced it with something I love even more. I know I’m talking to a smart guy because you stole my foreshadowing. I was going to circle back to that in a little bit, but yes, that’s exactly where we’re going. Let’s get to OpenAI. You’re a part of the OpenAI founding team. What’s your version of the story? I’m sure this could be a whole hour-long podcast, but what drew you to this space and got you guys started? GB: Well, I have been excited about the idea of AI for a long time. I remember when I was first getting into programming, I read Alan Turing’s 1950 paper on the Turing test. It’s this really interesting paper, it’s like 70 pages or something. It starts out by saying, “Well, what does it mean for a machine to be intelligent? I don’t know what that means. Intelligent is not well-defined. So let’s have a well-defined version of it”. I’m going to ask you what is AGI in a little bit. So it sounds like it’s still unsettled, right? GB: Well, there you go, yes. So Turing very smartly sidestepped the question and said, “Let’s just have an operational definition of this, where if you can have a test where a human can’t tell the difference between an AI speaking to them and another human, we’ll define that machine as intelligent”. The thing that was very interesting, though, that gets much less airtime, is he said, “Well, how are you ever going to solve this? It’s just too hard to program an answer to it. You cannot write down all the rules for how to respond to questions. Instead, what if you could build a machine that learns? What if you could build what he called a child machine?”. And then you teach it — you have a teacher who gives it rewards and punishments, and then you’re able to actually give it intelligence and help it be able to pass this test. I remember being so struck by this idea, because as a programmer, you only make progress by deeply understanding the solution to something. There are so many problems I don’t know the solution to, you don’t know the solution to, no person has ever come up with a solution to, we’re never going to be able to program the answer. But what if you could have a machine that could understand problems that we cannot, that could understand solutions that we could not? This isn’t just about image recognition, though of course it has applied to that. This is also about questions of how do we get along better as a society? How do we structure the world? How do we ensure that the benefits of what we’re creating end up lifting up everyone? These are super hard questions that humanity is not necessarily the best positioned to solve. But if a machine could understand, could look through more data, could have a deeper, richer understanding of many different fields all coming together, maybe it could solve them in ways that we could not.= So I was so inspired by that idea. But it was an idea. I remember showing up at Harvard, asking my professors, “Hey, could I do some AI research?”, and they showed me what the natural language processing state of the art was at the time. It was so clear to me, I was like, this is not what Turing was talking about. It’s much more hard-coded, parse trees, all of those things, this is not going to scale to AGI. But something in the early 2010s changed, and I was watching from the outside. 2012 was AlexNet , then there was a series of other papers, and the thing that I would just keep seeing on Hacker News, it felt like every day there was a new “deep learning for X”, I was just like, “What is deep learning?” — I remember going to deeplearning.org, and it just said, “Deep learning is a new approach to artificial intelligence”. I’m like, I have no idea what this means, I actually knew one person in the field, I went to them and asked them to introduce me to more people in the field, and I just kept getting reintroduced to a bunch of my smartest friends from college. I was like, “Wait, that’s interesting, these people are working on this, that’s actually a very strong signal”. So by 2015, it felt to me like there was something real happening. I was spending a lot of time as well really thinking about AI safety, thinking about the long-term future of this kind of technology, what it means to get it right, and it was more philosophy at the time. There were various writings you could find online that were very cool thought experiments, I ran a reading group at Stripe where we would talk about these things every week. So it’s something I deeply cared about, thinking about if there’s any way that I could help AI go slightly better than it would without me, that would be the best thing I could do with my career. That was all leading up to 2015, and I felt like I’d reached a milestone at Stripe where the company was going to work with or without me, it was kind of a question of, “Do I want to go the manager route?”, which is what you need to get to the next phase, or, “Do I want to go start a new company?”, That was something that had always motivated me, and so I decided that’s what I wanted to do. As I was about to leave, Patrick said, “Why don’t you go talk to Sam [Altman] “, who he had introduced me to a couple years before. He said, “He’s seen a lot of young people in similar situations, maybe he can give you some advice” — kind of hoping that Sam would convince me to stay. It didn’t quite play that way. (laughing) Yeah. GB: I met up with Sam, and three minutes in, he’s like, “Okay, you’re clear you’re out, what are you thinking about doing next?”, I said, “Well, I’m thinking about doing something in AI”, he said, “I’m also thinking about doing something in AI”. And that was the start. Were you on board with the whole non-profit thing ? What were your thoughts on that when you set that up ? GB: Well, the idea of being a non-profit is something that Sam had proposed, and I think that there are actually a lot of very important properties, and you see ones that have really rung true to today. The technology we are building, it’s just bigger than anything that’s been created, it’s bigger than the traditional structures and systems. There is no one corporate structure that exists that actually fully encapsulates the mission and the work we need to do. So I think starting that way made perfect sense, and there was always a question of what is it going to take to actually operationalize the mission? That’s something we spent a long time really thinking about. I think we’ve been the company that’s been the most innovative in really thinking about can you build a structure around all of the different aspects of both the commercial development that needs to happen, the distribution of benefits that needs to happen, the practical way of actually bringing forth this compute-powered economy, and doing all of that at once. So I think it’s been an important element, it remains a critical element to what we do. But again, I think we’ve innovated so much on corporate structure around the core of this mission, and that mission is invariant. Yeah, innovate is one way to put it. You mentioned the credit card networks, right? It’s super opaque, lots of stuff from the ’80s, a massive amount of path dependency that gave Stripe an opportunity, because you could abstract that all away and just be, for everyone else, “Here’s an API, it’ll work, don’t ask questions”. Now when you look back at OpenAI — it’s hard to believe it’s been over a decade now — could OpenAI have come about in any other way? Is there that sort of path dependency in there, or is there a, “If I went back to first principles, me, Greg Brockman, which I like to do, I would have structured this a lot differently”? GB: I don’t see any other way that we could have gotten to where we are, and I think where this mission needs us to be. Tell me about the ChatGPT launch , because you have the turning point where you realize you need to scale, you partner with Microsoft, add the for-profit bit, and then ChatGPT comes out and it’s huge. Did you have any expectations it would be as big as it was? GB: So the thing that surprised me, my prediction error, was GPT-3.5 being something that people would really love and want. We had GPT-4 at the time, it had finished training in August or so, and we launched ChatGPT at the very end of November of ’22. The thing that always happens when we have a new model is that we just latch onto it. The old one looks terrible. GB: All we see is just flaws in the previous one. We’re just like, “Ah, this previous one is so bad, I can’t imagine anyone would ever want to use it”. We had like 200 testers who we’d been paying to use the pre-release ChatGPT, and again, we had to pay them to use it rather than the other way around. So there were some signs of product-market fit if you really dug in and were close to the details, but if you zoomed out, it really didn’t look like we had it. But the way that we thought about it was GPT-4 clearly was going to change the world, we knew that, it was very obvious from the first moment we talked to it. I remember for that first week after GPT-4 came out of training, just feeling the reality of it. We’d been dreaming of AGI, thinking about AGI, thinking about what it might be like. But the first time you have a technology that really you can ask any question and it can give you pretty sensible answers, that got a 5 on AP Bio — all of those things, to me, felt like, okay, something is going to be different. It may not transform the world tomorrow, but over upcoming years, this technology absolutely will, and it’s real now. That was very clear. So you look at the ChatGPT launch, the way I thought about it was we just need to get the infrastructure out first, so that we can have LLM-serving infrastructure that’s battle-tested, that we’ve put our reps in. Then in March, when we launched GPT-4 — which, if you remember, we did the six-month delay between completing it and actually launching it — then we’ll already have the infrastructure ready to go. But I didn’t expect it to quite take off in that form, even though I expected it to do so in the future. What was it like at that time? Was it just all hands on deck to keep the servers from melting? GB: Oh, absolutely. So we launched into what we called a low-key research preview, and of course, it was just the full exponential, every single system you can imagine breaking, broke. Our login system became a big bottleneck, we had to do so much work to improve the login system, and you’re scratching your head saying, we’re building this magic AI technology, and the thing that is your bottleneck is, “Does your login actually scale?”. I remember that we had a fairly inefficient set of inference kernels that were rolled out to production, and I’d actually written some more efficient things, or we had some more efficient things on the research side, and one of the big pieces of work was, “Let’s actually take those optimizations, let’s move them over”, so a bunch of people swarmed on that problem, got it done. I think this was the general flavor of it for that first day, for that first week, for that first month, it was just scaling every system and trying to really keep up with this wave after wave of demand. What happened in November 2023 ? GB: Very complicated answer. Where do you want to start? I don’t know, I feel like I have to ask you about it. They’re tied into — you took a sabbatical not too long after , was there a link between those two things? GB: Look, I would say the way to think about it is that at the highest level, I think that 2023 really showed that there were tensions that had built up, really interpersonal tensions that had built up, that we had not sufficiently gotten ahead of. To me, that’s one of the most important lessons of OpenAI, the fact that we’re building technology, but it’s always about the people, in good and bad ways. It means that really managing people dynamics, that is one of the most important things that we do, and if we don’t get ahead of it, if we don’t have the hard conversation, then that is actually where things can become much rougher. So I’m happy to drill into more details, but I think that a lot of it, if you really get there, it’s not the more interesting technological things. How much of that is tied to ChatGPT being this massive, huge hit you weren’t necessarily expecting ? Was there a link between those things, or do you think these tensions would have come to a head regardless? GB: I don’t think that there’s a direct causal link, at least not in my view. I think that to some extent, there maybe is an underlying theme of, as our technology has progressed, everyone feels the weight of the world on them, feels the stakes on them. Actually, one of the things that’s hardest is how do you just move forward? To me, the thing that I always remark upon is that the day-to-day activities that we do almost look the same as at every other company. You’re still debugging some low-level issue, someone’s upset at someone else because they said something, or they didn’t include them in the meeting, whatever it is. It’s just the human factors, the human work. But of course, the stakes are so massive. So I think that there is something that has been very important at OpenAI, and actually one of the big things that I have focused on, is really trying to not put people in positions where they feel that weight of the world and feel like they’re alone in it. Really doing it together as a team, that’s the critical thing, and that I think is maybe the way in which I would say that there is something — and it’s not really specific to those events, but it is a consistent theme over the course of OpenAI — which is really keeping that feeling of we’re doing this together, and trying to both rise to that occasion, but also make sure that we’re doing all the basics and doing all those basics right. That’s one way that I think we move forward. Yeah, I mean, you’ve been a very vocal proponent of what I think is one of the overall philosophies of OpenAI: get things out in the world, experiment, see what happens, and react from there. Make your decisions based on empirical evidence, not theorizing about the future. That philosophy, I think you guys articulate that a lot in terms of AI, but this is my question, which I think you’re kind of getting to as well, it feels like OpenAI as an organization is also this massive experiment that’s being tweaked. The negative read on that is it seems like it’s just veering back and forth, reorganization here, new leader there, is this an unwieldy monstrosity, or is it maybe more organic and more resilient than it’s given credit for? As you look back, you say it could not be any other way, would it be better if it was a different way? GB: First of all, it is absolutely true that we have changed and grown so much from where we started, a very different operating business, but we’ve been consistently the pioneer in terms of moving forward this field. That’s true on safety, that’s true on security, that’s true on the core technology and just really thinking about the distribution of benefits. All of those areas we have focused on from the very beginning, and I think really the results speak for themselves. Now, that change, it’s real. And it is the case that sometimes the team that you have that’s right for one phase is not the right team for the next phase. One thing that I have been really focused on this year has been building up a leadership team that I’m just so excited about, thinking about this next phase and what we’re going to be able to do together. So part of the theme of 2026, and one shift maybe from where we were before, is that because there are so many people in this field, because there’s so much to do, and because the technology is taking off so fast and we’re so compute bottlenecked, you’ve got to focus. You’ve got to really prune. You’ve got to pick the areas that all synergize together. So actually making the decisions on things like, “ Hey Sora , amazing technology, but being in that specific, more entertainment aspect of consumer, that’s not something we can prioritize relative to other things”, then we’ll cancel it. And then that causes downstream effects, and it’s painful, it’s tough to actually make these decisions, but it’s all in service of really having that tight focus so that we’re able to accomplish the core mission. You’re a big believer in scalability, is OpenAI itself scalable? GB: I believe it is possibly the most scalable business ever. Yes. I mean just internally, as far as an organization. What is not scalable? We talk about compute, we talk about data, you mentioned the human factor before. Is the ultimate alignment challenge — we think about alignment in terms of getting the AI to do what we want to do, but do you have the reverse challenge? Can you keep up from a management perspective with this space, this problem? GB: I’d say two answers, first of all, absolutely yes. I think you can see it in how much we’ve matured as an organization over the past couple of years, where we were a couple of years ago is we had a lot of management debt. Again, there were a lot of areas where I think we did need to grow up, we did need to mature, but I think we’ve done that work. It’s been hard, it’s been painful, but I think we’re in a so much better spot, and I feel just immensely excited about the company and our future. But there’s a second thing, too, which is that I think it’s also worth stepping back and just recognizing that how companies run is changing. You can look at this, for example, just looking at revenue per headcount. The revenue per headcount for us and similar businesses is just off the charts relative to any previous business. There’s a reason for that, you’re starting to see this increased leverage you can get through this technology. And by the way, because we’re making that technology and fighting to make that technology broadly available and to help so many companies, you’re going to see many other companies be able to run in different ways, to be able to have that outsized revenue per head. That to me is something that is very exciting, that we are shifting what it even means to run a company and how to operate. So there are some things that are invariant, just people working together — there’s something very fundamental there, and really doing that in a good and consistent way is something that, again, I’ve really focused on. But I think that there is also something about leaning into what is possible with our technology, which means that every company is going to have a new opportunity. You mentioned cutting off Sora , and you framed it as being the entertainment aspect of consumer. ChatGPT, huge consumer hit, you made an unbelievable amount of money from consumers. But at the end of the day, how many people are willing to pay for this? How many customers actually want to be productive ? Is there a bit where having such a hit in the consumer market was almost a negative, in that it was distracting, used up a lot of GPUs, and maybe you missed the boat — not missed the boat, but were late on the boat — as far as enterprise being the top focus? GB: So we have conversations like this all the time internally, and actually, I think that’s one of the strengths of OpenAI, that we really examine everything we’re doing from first principles, rethink it all the time, have lots of diverse opinions and perspectives. There are some people who can take almost any angle on this argument, and they all have a point. So there’s some truth to, “Hey, there’s this agentic moment, we were late to it”. There’s also some truth to having a billion people — that’s over 10% of the world population. Within the U.S., I think the number is something like maybe a third of the U.S. population uses ChatGPT every single week. Every week, that many people using your system, that is unique, there’s nothing like it for this kind of advanced technology. So on the one hand, if you just think of it as, “Hey, we have advancing technology”, one of the challenges with chat as a product is that it’s not necessarily aligned with more intelligent models. It’s not clear that people get the benefits of that directly through classic chat, if you’re just using it as a search engine replacement. But I think that all of these things are going to come together and come to a head, and I think we’re going to see that this billion users is an investment, that it is something that actually accrues to how models get unlocked in the future, and you’re seeing the first steps towards it with ChatGPT Work and things like that, there’s a bunch of nuance and complexity there, but a lot of the strategy has been to say, we’ve got consumer, we’ve got enterprise, these are two things — we don’t want to do two things, we want to do one thing. We want to build one AGI, one system, one unified stack. We want it to be something you use in your personal life, work life. Right, but is there a bit about shipping the internal org chart? You come out with a new ChatGPT, a dramatic departure from the old one , it’s built off of Codex. I can see the benefit for OpenAI internally, but is there a frustration that customers don’t realize what they can do, so, “We’re going to drop them in on the deep end, and hopefully that will help them figure it out”? GB: I think that there’s a fundamental shift happening in the industry, and you can see it with new emerging agentic products that are happening right now. I think that the core shift is you’re going from chat to agentic use cases. And again, it’s not just about productivity. I think that in your personal life, you want to be able to ask the thing to go book tickets for you, to be able to book your haircut, to be able to do those kinds of personal things, but you also want it to be able to give you good life advice, to be able to help you with health information. So to me, productivity is too narrow of a box. To me, consumer is too broad of a term. Enterprise is also something I think is going to shift. All these classic words, they are all going to smush together and grade together in ways that I think no one has ever built a product like that before. So my view has been that there’s a change management required of how do you bring along a billion users to a new set of use cases, help them understand. And by the way, there is an unfair advantage that is possible, which is you have an AI that understands what you’re trying to accomplish. That’s right. GB: It can say, “Oh, I can actually help you more if you enable this connector, if you do it in this way”. That’s something where I feel like it’s just an amazing thing, an amazing opportunity, and there’s a lot of potential there. When I say unfair, I mean just relative to what you would be able to accomplish with classic technology. If you just compare one technology versus another, there’s something unique about this one. You mentioned the Turing angle before, and I’m glad you brought up both parts, because can AI talk like a human? Obviously, we surpassed that point a long time ago. But to me, the AGI definition — which is a fraught thing for you guys, it’s finally, I think, out of your Microsoft agreement , so we don’t need to worry about that angle anymore — to me, it’s some connection to learning. You mentioned learning, and to what extent an LLM learned, past tense, but the challenge is does it learn on an ongoing basis? To me, what is revolutionary about the agentic moment, the way I think about it, is really the ability to write things down. That’s why the Codex/ChatGPT shift was necessary, because it gained the ability to write things down. If you write things down, you can remember things. If you can remember things, you can be tremendously more useful in all sorts of ways. The question is, is that an end state, or are we going to get an LLM that can learn continuously, and that’s AGI? Am I thinking about this all wrong, or does that fit the part two of Turing’s questions that he was raising? GB: Yeah, I think that this is also a very interesting area for debate, because people do have their own definition of AGI, it’s almost this blurry thing. At the beginning, we thought it’d be like, here’s this point in time that everyone agrees that is the AGI, it hasn’t played out like that at all. Now, I tend to take an abstracted view from the technology. So the question of, does memory have to get baked into the weights? Is this a transformer or something else? Those questions, I think, are details. The real question is, do you have a system that operates the way you would expect for a real AI, for something that can learn, that can learn from you, that can adapt to what your needs are? And the question of, is that implemented through a scratchpad that it writes down memories in? Is that implemented through soft tokens? Is that implemented some other way? All of that, to me, feels like possible answers to the question. I think it is very clear we’ve gone so much further with “write things down in a scratchpad” than is almost reasonable. It’s actually quite amazing to see how successful it is, because there has been a lot of push — two years ago, we would have said, “Yeah, you need these super long contexts, that’s the thing you need”, actually, it turns out that with just “write down a scratchpad” and shorter contexts, it just goes unreasonably far. Just write stuff down. GB: So we’ll see what the future holds in terms of improving these things. I have this belief that if you zoom out, everything’s an exponential. You zoom in, you see these paradigm shifts. This, by the way, was the Ray Kurzweil view of how technology and computing works. I think it’s been absolutely true for even these questions of how is memory going to work. So you just launched Astra . We’re finally here. Is this a new pre-train? Are you releasing any details about the size, the architecture? We’re recording this before it’s officially announced, so I haven’t seen everything that you’ve published. GB: So we’re not talking about the internal details and architectures, things like that. But this is a huge step forward. We’re talking about the fact that this is the first run that we’ve trained on more than 100,000 GPUs, which is an easy number to throw around, but just think about the scale of that. These data centers in some ways are these big machines that we’ve built in order to help deliver and create AI technology, and it’s a real engineering challenge and marvel that people are able to harness that amount of compute to deliver the kinds of results that we have. So part of that is about making the models more capable, but so much of the compute goes into safety and alignment, and we have so much security work that’s gone around it. I think that we’ve done a huge amount of work to deliver this model safely. It’s our most aligned model yet, which to me is something that is absolutely critical and always has been. But because the capability is so strong, alignment and safety become even more front and center in terms of everyone’s work. Your announcement post is interesting. It’s very matter of fact. There’s a huge number of practical use cases. The contrast to, say, your competitors’ announcements is very, very large. Is your framing of AI as a tool — which I think is a fair way to put it — is that about marketing, or is that how you think about AI, as opposed to, like, creating God? GB: I think there’s a deep fundamental value that we have, and some of it actually relates to how we think about people. People are valuable not just because we can do tasks. We are valuable because we are humans, because we have feelings, because we matter. That human judgment, human oversight, human control, all of those things are absolutely critical to maintain, and to maintain forever. That is something that we believe is a core invariant. So when we think about what we can do to help steer the future of this technology — which in some ways is what it’s all about, that is why we started this place, that is what we care about, how can we help this technology go in even a slightly more positive direction than it would without us — we think about these questions of how does this technology roll out in the world? We want it to be something that uplifts everyone, but also the question of how humans relate to technology, to computers. It’s clearly changing. It’s even changing in terms of just typing less, talking more to your computer, having this much more natural interface. But really, that human oversight and creativity and vision, all of those things I think are very important to preserve. So that does then bleed down to these questions of, do you talk about it like it’s a person, or do you talk about it like it’s a tool? Do you think about the use case? Do you think about it something differently? You can see this as almost a small thing, and I’m actually glad you pointed it out, but it’s something we’re very thoughtful about. The team spends a lot of time really thinking about everything we want to talk about and how we want to present this kind of work to the world. So is this a model release, or is it a product release, or is there any difference? GB: These things do blur together. I would say that this is first and foremost a model release, but the model is qualitatively more capable. Maybe the headline one is computer use. It’s really crossed the threshold for me, computer use has always been — even from the beginning of OpenAI, I remember in November of 2015, before it even really started— Well, that was like your first product, right? It was like playing video games or something like that. GB: Yeah, exactly. Ah, you remember, yes. We had this vision of if you could do screen pixels, keyboard, mouse, an AI that you train end-to-end on that, it would be able to actually go and address any sort of task, anything that you want people to have help with, this AI will be able to do. If you look at the era we’ve been in for the past two years, it’s been a connector era. You have some pieces of software, humans can use it just fine, the AI has no access to it. So what do you do? You have to write a very specific connector that hooks up to the APIs, and not everything’s exposed, so you can’t do everything that you could. Then you think about that there are so many pieces of software that don’t have APIs, and those are totally out of bounds. So we have this limited world where the AI is so restricted from helping you. I think that we now have the technology that’s almost this universal connector. Now, that doesn’t mean that all the problems are solved. You have to think about how do you have enterprise guardrails around what these AIs are doing? How do you have the appropriate oversight, management, tracking, and observability? All of those we’re working on. So I would view this as a continuous process of how the product rolls out in order to help harness this capability. But it’s already transforming how people do work within OpenAI, and it’s really, I think, going to uplift so many companies, so many individuals. If you think about the overall value chain, there’s a place where you’re fighting battles on two fronts, I could see. One is you have companies like Microsoft, or other partners — if you don’t want to use their name since they’re still an important partner — but they want to commoditize models . They want to build the thing on top, and you can plug-and-play, shift models in and out, they’re holding all the context and what’s important. But at the same time, you’re building these incredible capabilities that are really tied ultimately to the end user, it just goes and does the things that you want it to do. Is that just inevitably where you have to get to, to accomplish what’s yours? Is there also this economic imperative — if we don’t want to be commoditized, we need to get up into products and actually doing things directly connected to users? GB: I would say that our underlying imperative is really that we want there to be more AI capability in the world. We want people to be doing more with AI, for it to help them, and we really view that we’re shifting this compute-powered economy. What that means takes different forms, especially across different verticals. Sometimes we feel like we are in a position to really focus on an area and do a good job with it, or it’s very core to our mission. Health is a good example. We’re building something incredibly unique in health. It’s actually very surprising to me how little airtime what we’re doing in health gets relative to how many people it’s actually helping. We have like 300 million people each week coming to ChatGPT for health queries. 300 million people, that’s a huge number. Then we’re also building a bottoms-up clinicians product , and we’re building a top-down enterprise product for hospitals . So we have this three-sided marketplace in health, and what we’re going to be able to do there is things like, you want to find people for clinical trial enrollment — that’s a hard problem, but we actually may have the ability to help find people that would otherwise not be found. That both helps the patient and helps these drugs be able to move faster. So there’s this core of saying health is so critical to our mission, we have a unique shot on goal, a unique opportunity, let’s really focus on it. We put a team behind it, we put all the effort behind it, we build the relationships. One thing that we do when we go into specific verticals is we think about how do we play well with the ecosystem. It’s not to say we won’t compete there — we often do compete very hard — but we also really think of it as we’re going to lift up all the boats, too, and how do we actually just focus on this core mission of, we have this technology, we want it to be broadly diffused, we want it to be out there. So sometimes it can be a little bit nuanced. There are always a lot of questions when we go into a specific area of exactly what we want to do, what we’re set up to do and what we’re not. But I think the way that we view it is that our overall goal at OpenAI benefits the more people are using AI to positive benefit. Well, if you have the layer on top of you trying to commoditize you, there’s probably an angle of you trying to commoditize the level under you. You guys just talked a lot more about your Jalapeño chip at Hot Chips . Why is Jalapeño important? Is it important beyond just saving money as far as paying for chips? GB: I would think of it as, since 2017, we have been plugged into basically every hardware startup out there, every vendor. We talk to them, we give them feedback, we say, “Hey, here’s where we see the models going, here’s what we think you should do”. Sometimes they listen to us, sometimes they don’t listen to us, sometimes we’re close partners, sometimes they don’t really want to talk to us. One thing that has been very freeing about having a chip program in-house is that we’re able to just go directly to the thing that we think is the best, that we think is exactly tuned for, not just necessarily what we’re doing, but the aperture of where we think this technology is going. It was a very big investment — we have a team, an absolutely incredible team, with great leadership that has been working on this for quite some time. But again, it is also something where we work very closely with the ecosystem. We partner very closely with Nvidia as our preferred compute partner, and if you look at the size of the computers we’re building and the unique computers we’re building, we need Nvidia, there’s no question about it, we’re building these amazing training computers, we’re building lots of inference with them, we’re able to push their hardware actually sometimes in ways that even they didn’t realize was possible. Yeah, I heard there was a little bit of a hard pickup, maybe that made it a little harder to get very large models out in time, but it’s working now, I suppose. GB: Yes, yes. And I would say that there’s something that is enabled by us having that in-house expertise, because we deeply understand things. It’s one thing to be sitting on the sidelines and throwing advice over the fence, it’s another if you actually have gone through the pain. A good example of this actually is AI for chip design. We’ve talked about this, that we’ve used our own model in the design of Jalapeño, it really sped things up, it got us some real wins, all the cool things. As an aside, there’s a cool story there where we were coming up on a deadline, we had like a month to go, we got some optimization done with our model. We’re like, “Do we spend the time to really read what it did? We know it’s correct. Do we need to understand exactly what optimizations it did, or do we just spend the rest of the time getting more optimizations?” — and so we said, “You know what? We’ll just get more optimizations in”. So we spent that month on just running it without deeply understanding exactly all the tweaks it made. Then we went back and read it, and it actually turned out that it found a bunch of optimizations that had been on our list, but we just never would have gotten to, so that was actually a pretty cool story. But now we have that expertise, we know this thing works, and we can bring that to the ecosystem. We can work closely with everyone in order to actually bring these benefits broadly, to transform hardware and do that at mass scale. So there’s something about that flywheel that’s absolutely critical, the chip is incredible, the team did a great job. Is it a problem talking about it now, though, when you can’t ship in volume and you still need to partner with other folks in the ecosystem to get the supply you need? GB: Well, but this is the core, this is actually the core of everything. We think of it as — I think everything is multiplicative, everything is complementary, everything adds up. And again, it is absolutely the case that Nvidia is our preferred partner, that’s not changing. In fact, we’re leaning in even more with them. We’re deeply, deeply grateful for that partnership, we spend a lot of time with their team, there’s a lot of things that we learn from them, there are things that we hope that they can learn from us, I think that’s something that doesn’t change. The fact that we are able to have in-house expertise and really think about things in our own way as well, to me, that’s something that’s just multiplicative, I think it really benefits everyone. You mentioned you just trusted the AI design, and that got you further down the road. Is that the answer to cybersecurity? You had some engineers give a talk at the Black Hat conference and talk about this structural problem — attackers don’t need to worry about breaking things, they’re trying to break things. If you’re on the other side, you’re worried about everything continuing to run in addition to fighting off these attacks. Do defenders need to get to the place where they just fully trust the AI? GB: I think the hardware side is a very important case study, because there we have guardrails. We have verification, and actually, the way that we write our underlying hardware design is specifically to allow verification, so we almost co-designed the whole system. That’s like how you code, it writes the unit test first and then backs into it. . GB: That kind of thing, how you pick your language and the toolchain, the whole thing, it’s all together, and it actually all adds up to a system that you can have that kind of observability and trust. I think it’s okay for there to be some areas where you say, “I have sufficient guardrails here that it is actually okay if it’s this code or optimizations that I haven’t fully inspected”, as long as you have the appropriate compensating controls. But I think that it is very important that you as a human do understand and feel accountability for the system you’re creating. That to me is actually a core thing, back to what is it that humans are, what is unique to us, what is something that we are going to carry forward, I think accountability is a core of it. At the end of the day, you’re responsible for what happens at your company. Right, but if those on offense are not accountable, is that a structural disadvantage? GB: So I think that this is something we think about a lot, that there is what we call this The Defender’s Window . I think that we can see a little shape of the future, we have frontier capabilities that have shown the kinds of capabilities that will diffuse to threat actors. And by the way, I think the fact that this capability is not being locked up forever in a small number of labs is actually very important, it is very important that there is broad distribution of power, that is part of our mission as well. But we have the ability to have a separation in time. There’s this window where defenders can get access to these capabilities, and differentially so. And my view is that it is true — there’s a common wisdom in cybersecurity that offense is a technology problem, defense is a political problem. The attackers can just take something off the shelf and run with it, whereas as a defender, you have to think about your stakeholders, you have to think about your business, you have to think about how you actually get people on board, your CEO, all the executives, all those things. So I think that there is something here where defenders need that willpower. One thing we are recommending, and we’ve actually done ourselves and are talking about it publicly now, is that every company should treat this as a proactive incident. Critical business operations, proactive incident, that’s your next priority, so we actually took 25% of our production engineers and put them to securing ourselves. We took our models — in fact, we took Astra, pointed it at our own systems to find vulnerabilities, and not just read the code, but really look at the end-to-end of how these things are running, so we would find real validated vulnerabilities, and then it also helped us with the remediation, patching, and fixing. So I think that you do need a shift in the energy in the ecosystem in order to stay ahead and to take advantage of this window. Well, that’s all great and fine that you’re doing this now, but to me the most remarkable thing about the Hugging Face incident and the things that have come up about it is it doesn’t feel like OpenAI was particularly concerned about cybersecurity. Why didn’t you do this before? Hasn’t the Defender’s Window been open for a while, and you were also failing to take advantage of it? GB: Well, two answers. So one is that if you look at the way that we were doing sandboxing, it was not that this workload was not sandboxed. There was actually a sandbox around it, and I think that one thing we realized is that we had— Right, which wasn’t clearly sufficiently tested. Is it really a sandbox, or is there a connection to the Internet via a third party who were just thrown in there? That’s the most remarkable thing about this incident . It’s like, if you wanted to test for vulnerabilities, I guess you did that. GB: It’s definitely the case that the AI was able to do very creative things in order to get out and get into Hugging Face. But to me, there is a bigger thing, and I think you’re pointing at the right thing, which is that since this summer, when Mythos came out, when we started to have cyber-capable models — and we even talked about our Trusted Access for Cyber program back in February, because we saw this wave coming, we wanted to really prepare for it — there is a tendency— I know, but you talked about it in February, but you didn’t point it at your sandbox, “Is my sandbox actually secure?”. GB: There is an instinct, there’s a reaction to that, to say, “Let’s restrict access massively, let’s really put a bear hug around this, only if you can get access”. And I think that to your point, because the field continues to move, it means there’s time that defenders lost, there’s time that people were not defending. Part of that is about access, but part of that is about how much do you put your full weight behind saying we’re going to shift around this in a significant way. Now, I think that to some extent, the time is not all made equally, because we’ve gone from a world of cyber models being not that useful, not that differentiated, to actually being incredibly capable, incredibly powerful. We’re seeing that with Astra, we’ve talked about how it’s really saturating a bunch of these evals. I think now is the time, it’s possible that a couple of months ago could have also been the time, but you would just have had much less capable models, you would have made much less progress. So I think that really estimating where we are, we are clearly there now, and I feel like that is something that we have learned, we’ve taken it to heart, I think that you’ve seen a real shift. It’s actually been a cultural change in a lot of ways, an operational change, and it’s not easy, because it really means that you have to have teams working together very tightly in a loop with much higher standards around how policies are set and all these things. All of that, for us, it wasn’t a shift in terms of we’ve always cared about these aspects, but really bringing them together operationally and being able to make decisions the way that we have, I think it’s been an up-level across every aspect of what we do. Now suddenly they’re able to do it, as if doing it previously would have been a waste of time, which I think is kind of a valid point. How do you avoid the trap of, “Well, the AI will be able to do this in the future, so we don’t need to do it now”? Just in general, though, not even just with this. GB: I was going to say one thing that’s a specific data point, so early on, sometime in Q1, we really started thinking about, we are going to have — it’s hard to know when, but we’re going to have these very cyber-capable models. What is a sandbox that we could build from first principles that’s as secure as you could get while being built on cloud infrastructure? And we built that. We actually took some of our best engineers and pointed them at that problem, and they sprinted on it and they produced something. So I think building infrastructure from first principles around what you see coming, that is something that I think is important. And to your point on when is it, “Oh, we can just let the AI do it” — again, we’ve seen this show before in different fields, in writing kernels, and thinking about the fact that, “Okay, we’re going to be in a world where in the future the AI is going to be able to write GPU kernels very well”, do the classic kinds of investments where it takes many months, sometimes a year, to get new infrastructure in place for thinking about new hardware, that kind of thing, or do we just say, “Ah, the AI will figure it out”? I think the answer is always that it takes a little bit longer than you expect for the AI to get there. But when it does, it is surprising and powerful in ways you didn’t imagine. One example of this is with Astra. One thing we have found is that a number of our skills that we’ve built up over the course of this year, very painstakingly, to show our models the right way of doing things in OpenAI and things like that are actually now net negative for its performance. Too many rules. GB: Exactly. It is able to generalize better, or be able to find better ways of approaching patterns and things like that, than what we had written. So I think there’s something about this where you do want to build those controls, you do want to build the deterministic infrastructure, you want to write those skills. But you also need to be prepared for, as the AI gets more capable, that some of those things, the scaffolding, will become a limiter. It’s kind of like training wheels. At first, it helps you, but once you start going faster, once you have something more capable, something better, something more aligned, then it actually starts to be a hindrance. Will you ever be in a 12-hour or 24-hour coding flow state ever again? GB: I hope so. I think there may be a day where that happens, but I will say that I have found so much joy and value in helping the team in the way that I do now. I think that for me, it’s really about that mission. Well, it’s not just you, but will anyone? Because isn’t AI’s benefit almost that it is permanent flow state available at your command? GB: I think that we’re going to find new ways, whether it’s managing agents — actually, one thing that’s been so wild is seeing that software engineers are working harder than ever, because you realize if your agents aren’t working, it’s just time that’s lost, you’re never getting it back. So I think that people will achieve that flow state in ways that are kind of unimaginable right now. At the end of the day, you’re talking about this is going to be controllable, these AIs, “Don’t put too many rules, they’ll figure it out”, if you play that out in the fullness of time, isn’t that ultimately about them being uncontrollable? GB: Well, I think this is the core of the moment, of the new phase that we’re in, and in some ways I would say we’re into the AGI era now. I think that is the core of this moment, where — maybe it was the previous model, maybe it’s Astra, maybe it’s the next model, but somewhere in there, I think we’re going to cross most people’s AGI threshold. Ensuring that we are pacing , ensuring that we’re thinking about safety, security, alignment, and capability, all as requirements — we have standards around each of these, we want to progress them together — that is something we’ve always believed. But I think it’s becoming very front and center that these other aspects are becoming almost the bottleneck to development. And I think that, again, is something where we’ve been prepared for that, we’ve been thinking about this, and I think we’re operationalizing it in a real way. So my view is that there’s a lot of progress to be made, but I think that the way that we should approach it is through increasing our standards in all of these. If you look at that, I think we see line of sight for things like monitorability. That’s very key. We’re bringing that in a real way. I think that we have a very good program. We have a good set of people, we have a good track record and a good mission that I think all point towards we are building systems in a way that they are controllable, and we’re taking these step by step in terms of pacing. Greg Brockman, congratulations on Astra, and yeah, can’t wait to use it. GB: Thanks so much, thank you for having me. This Daily Update Interview is also available as a podcast. To receive it in your podcast player, visit Stratechery . The Daily Update is intended for a single recipient, but occasional forwarding is totally fine! If you would like to order multiple subscriptions for your team with a group discount (minimum 5), please contact me directly. Thanks for being a supporter, and have a great day!

0 views
Unsung 1 weeks ago

“I would be very happy to see all kinds of text editors adopt this.”

Speaking of cut in File Explorer , Will McGugan, creator of the writing editor Ishmael , proposes doing the same thing for text editing – that is, leaving ghost text behind: I call it “Ghost Cut”, and it works like this: pressing Ctrl+X fades the selected text and makes it inert—you can’t click on the cut text and the cursor just kind of leaps over it, but it is still present in the document. Nothing is placed in the clipboard at this point and there is nothing to undo. If you decide you don’t want to paste then hitting Escape will restore the text to its active editable state. I do not think this is a good idea. It seems like McGugan proposes somewhat arbitrary reasons that cut and paste are “broken,” and to me the proposal fails even within his own framework: for example, it still doesn’t avoid the reflow, but moves it to the other side, making paste feel unstable (the included JavaScript playground feels disorienting to use). I think it also is at odds with the editor’s own about page , which talks about distraction and simplicity – and yet, what’s introduced here is a host of complex new things: a new visual state for cut text (that looks like AI autocomplete), some new logic to avoid moving into the cut text (which will also prevent easy selection adjustments), likely new confusion around undo, and a necessary new Esc gesture to get rid of a cut you decided not to pursue. But! There is something interesting in seeing this ghost text, and specific critique aside, it is fun to see experiments like these, with interactive explainers, poking at long-established interactions. Just yesterday, a second one appeared: an interesting new way to extend selection upwards – similar to double and triple clicking on text, but via the keyboard. This is the power of prototyping: sometimes seeing an idea you disagree with is the only thing that can unlock a different solution in your head. Let’s go back to cut and paste, though. You will notice that what’s listed as a precedent is not File Explorer, but Excel. It also doesn’t immediately remove selected cells on cut, but leave them in a temporary “possibly getting moved or removed” state first, only to have them disappear after paste: Why is that? I believe out of a similar principle, but a distinct reason that File Explorer too opts to leave a cut ghost behind. The stakes are higher in a different sense: cells refer to other cells. Were the removed cells to disappear immediately, you might witness the rest of the spreadsheet error out or show zeroes, as its formulas start pointing to emptiness. And it’s a good principle to generally avoid frightening your users. Here’s an example from Numbers, which doesn’t do ghosting, making cut a bad choice to do a simple move, as it doesn’t even recover after paste: #explainer #text editing

0 views
David Bushell 2 weeks ago

Fine, I’ll build my own text editor!

Hello RSS reader! This post contains interactive features. Please visit the canonical web page for an optimal viewing experience :) “They don’t make ’em like Sublime Text anymore” resonated with a lot of folk. Software these days is garbage. That got me thinking; I’m good at building garbage! Why can’t I build my own text editor? VS Code is built upon Monaco Editor which is a soup hellscape. I was late to the VS Code train because for years my Intel inside™ Mac was too slow. That issue was resolved when I bought Apple silicon. If that’s the standard I have a lot of room to make mistakes. My first experiment renders everything on a element. RSS does not support this interactive feature. Please visit the canonical web page. You can’t tell, but your CPU is doing a lot of work to render that picture at 60–120 frames per second. Lack of interactivity is an obvious problem for a text editor. I made a list of the “minimum viable” features and implemented them. This next demo is interactive, click around and type. RSS does not support this interactive feature. Please visit the canonical web page. Before you @ me about Vim bindings: shut up, I’ve got more pressing issues. Canvas gives me nothing for free. Amongst many desirable features, I’m missing: That last one is critical. Life is too short to implement custom elastic scrollbars. I decided to cheat and use native browser overflow on a hidden element. A is sized to match the canvas text and the scroll position is used to calculate render offsets on the . RSS does not support this interactive feature. Please visit the canonical web page. I’m pleased with how that’s coming along but I’m also disheartened because is entirely inaccessible. I could continue to add text selection and other features but I’m not solving the fundamental accessibility issue. I had a better idea. Instead of rendering text on the I can just render it natively in the overflow and make it editable with a attribute . That attribute has a value that is perfect for code. All content remains within a single text node. Attributes like must be disabled to avoid input latency spikes. Want to guess how many days it took me to discover that fix? Days! Using gives native text selection and undo history etc. So much accessibility goodness is wired up for free by the browser. RSS does not support this interactive feature. Please visit the canonical web page. The provides metrics I use to continue rendering a custom text cursor. is available so I can style that too. I’ve set the native invisible, which is probably a no-no. The technique is promising but I’ve noticed strange performance issues beyond a certain character count. Chromium browsers perform worse than WebKit and whatever Firefox is now but it’s unpredictable. Instead of plaintext would a simple be viable? In short: yes. Turns out a is far more performant for longer text. In this final demo I’ve added syntax highlighting too. RSS does not support this interactive feature. Please visit the canonical web page. My original plan was to use custom on the element. can’t use CSS highlights so a third layer was required. For demo purposes I added some soup for the visible lines to apply MicroLighter . Edit: I’m told the new OpaqueRange API unlocks custom highlights for — neat! Too many CSS highlights are another performance bottleneck. A more robust solution would be to use Tree-sitter to generate a syntax tree and walk that to generate highlights for only visible lines. I was hoping to avoid virtualised scrolling entirely but I could improve it using the inverse sticky technique . Or I can go back to because the file sizes I’d be editing don’t hit the performance wall. Anyway, looking good, right? Looks like 90% of a text editor with 1% of the features. From here it’s pretty straight forward to draw the rest of the owl . I’m tempted to keep drawing but then I think about all the little things like tab indentation. Right now I just hijack the tab key to insert two spaces… My demos above are unoptimised and far from perfectly accessible but at least I’m not starting from a losing position. Rendering on would be a nightmare. I’m filing this project away for a rainy day. JavaScript strings and text ranges work with UTF-16 code units. It’s easy to naively introduce bugs. I’m sure my demos are full of them. I’ll leave with a code example to nerd snipe. Thanks for reading! Follow me on Mastodon and Bluesky . Subscribe to my Blog and Notes or Combined feeds. Pointer down to position text cursor Arrow keys to move text cursor Highlight current line Type to enter text Fancy cursor animation Text selection Undo/redo history Multi-line paste Overflow scrolling

0 views
Ahmad Alfy 2 weeks ago

I wanted the browser to just ship jQuery. Cross-Origin Storage came for rescue

Early in my career I had an idea I thought was obvious. Almost every site on the web was loading jQuery. The same file, over and over, millions of times a day, across the whole internet. Why didn’t Chrome just include it? Ship the file with the browser, save the world a mountain of bandwidth, move on. Nobody I said this to was impressed, and the idea falls apart the moment you poke at it. Which version do you ship? What about the next one? Do you also ship Bootstrap, Lodash, Angular? Where does it stop? But the instinct behind it was right. There’s a proposal in the Web Platform Incubator Community Group that arrives at roughly the same destination by a much better route. It’s called Cross-Origin Storage . The web had already solved this, in its own way. For years the standard advice was to load your libraries from a public CDN. Link jQuery from Google’s copy, link Bootstrap from a shared host, and everybody wins. The browser cache was keyed on the URL. If you visited site A and site A pulled jQuery from a shared CDN, the file went into the cache. When you later visited site B, and site B pointed at the exact same URL, the browser already had it. No download. That was the whole pitch, and it worked. Not quite “the browser ships with jQuery,” but close enough in practice that it was in every performance checklist for a decade. Two separate things killed it. The SPA era arrived and we all started bundling. Webpack, Rollup, Vite. You no longer ship jQuery as a file the browser can recognise. You compile it into a single bundle along with your own code, your framework, and everything else you imported. Then we added content hashes to the filenames, because cache busting needed to be reliable. Now every build produces a new URL. Change one line in your app and the entire bundle gets a new name and a fresh download. And tree shaking finished the job. Your copy of a library isn’t the library. It’s whatever subset of it your imports happened to pull in, mangled by your minifier settings. Two sites can both depend on the exact same version of the exact same package and end up shipping bytes that don’t match. So the shared cache had nothing left to share. Before any browser changed anything, we had already built our applications so that no two sites on the web send the same file. Safari had partitioned its cache for years. Chrome followed in version 86 , released on 6 October 2020, keying cached resources on a Network Isolation Key made up of the top-level site and the current-frame site. Firefox shipped the same idea in version 85 in January 2021, partitioning the HTTP cache along with the image cache, font cache, DNS cache, connection pools and more. A file fetched while you’re on site A now lives in site A’s slice of the cache. Visit site B and site B downloads its own copy, even when the URL and the bytes are identical. The reason is that a shared cache leaks. If I run a site and want to know whether you’ve visited a competitor, I can load a resource only they use and time the response. Fast means it was cached, which means you were there. Chrome’s own explainer for the change notes that exploits along these lines had been demonstrated in the wild, including cross-site search attacks that read your search results one string at a time. Chrome published the impact of that change. Cache miss rate up about 3.6%, bytes loaded from the network up around 4%, First Contentful Paint up about 0.3%. They decided that was a fair price, and they were right. So by around 2021 the trick was dead twice over. Our own build tools had made it pointless, and the browsers had removed the mechanism. None of this mattered much when we were arguing about a 30KB library. It matters now. AI models, WebAssembly modules, game engines, large web fonts. These are big files, they’re publicly distributed, and they’re byte-for-byte identical no matter which site asks for them. The Cross-Origin Storage spec puts the problem plainly: when two unrelated sites both depend on the same 8GB model, partitioned storage forces you to download and keep that model twice. Your bandwidth, your disk, your battery, and the network’s capacity, all spent on a file you already have. Nobody is bundling an 8GB model into their webpack output. These files ship whole and unmodified, which is exactly the property the shared cache used to depend on. The proposal is a cache that ignores URLs completely. Files are stored and looked up by their cryptographic hash. That single change answers the version question I couldn’t answer years ago. There is no “which jQuery do we ship,” because you never ask for jQuery. You ask for a specific SHA-256 digest, and the browser either has those exact bytes or it doesn’t. Two sites referring to the same hash are, by definition, talking about the same file. Reading looks like this: You get back a , the same object the File System API already gives you, so there’s no new file-handling vocabulary. Writing is the same call with , plus an option that declares who else may see the file. Declarative versions are being proposed too, so a tag or a rule could opt in with no JavaScript at all. The idea is to hang it off the attribute you may already be using: Note that this syntax is illustrative only. Sharing files across sites is easy. Sharing them without rebuilding the tracking problem that killed the shared cache is the entire engineering challenge, and it’s the part of this proposal I find clever. Every time a site asks “do you have this file?”, the question is a probe. If a file is used by only three sites in the world, a “yes” tells the asker you visited one of them. You’ve reinvented the leak. The proposal answers in layers. A file is only shareable with the whole web if its hash is on a Public Hash List . A hash earns a place by being genuinely widespread, appearing across enough independent sites that knowing you have it says nothing about you specifically. The spec proposes governing that list across vendors, in the same spirit as the Public Suffix List. If your file isn’t popular, it doesn’t get listed, and other sites can’t probe for it. The browser is allowed to lie . The spec calls it GREASE’ing (occasionally answering “not found” for a file it actually holds). One uncertain answer poisons the whole pattern. Your first instinct is probably that I’d just ask three times and take the majority answer, and you’d be right, which is why the noise never stands alone. The spec pairs it with rate limiting on repeated requests from one origin, and with on-device heuristics watching for hashes that look generated per user. If you can’t re-probe cheaply, you can’t average the noise away. There’s a limit on the dishonesty, though. The spec says a browser must not do this for gigabyte-scale files, because forcing a pointless multi-gigabyte re-download is too high a price for the privacy it buys. Which means the noise is thinnest exactly where the files are largest. The result is, the you might get never means “this file is definitely absent.” It only ever means “go fetch it from the network.” Treat Cross-Origin Storage as a bonus. The proposal is a Draft Community Group Report, which means it is neither a W3C standard nor on the standards track. You can still try it. There’s a browser extension that implements the proposed API and injects it into pages, so you can build against the shape of it today. I like this proposal because it takes a naive instinct I had years ago, agrees with the instinct, and then does all the work I didn’t know was needed. My version was answering a 30KB problem, which is exactly why nobody needed it. This one exists because sending a gigabyte to a browser apparently will become an ordinary thing to do in the age of AI.

0 views
Simon Willison 2 weeks ago

Understanding ChatGPT Work

OpenAI announced ChatGPT Work on July 9th, and have been furiously iterating on it ever since. It is an extraordinarily confusing and very powerful product. Here's what I've figured out about it so far. The more interesting version of ChatGPT Work is the one that runs in the cloud. This can be accessed via chatgpt.com or through the ChatGPT mobile apps. Let's call it Work Cloud . If you install the ChatGPT desktop app - the app that used to be called Codex - you gain access to a thing called ChatGPT Work that can access files and run programs directly on your computer. Let's call that one Work Local . This one feels more like regular Codex re-skinned to be less intimidating to non-software-developers. For the rest of this article I'm going to talk exclusively about Work Cloud. Right now, ChatGPT Work (in both flavors) is available only to $20/month and up subscribers. Free users and $8/month Go users do not have access. The interface for accessing Work is a tab selector, which presents it as an alternative to Chat: The obvious question is when should I use Chat, and when should I use Work? OpenAI's official answer to that question is: Use Chat when you want an answer, explanation, brainstorm, or short draft. Use ChatGPT Work when you want ChatGPT to complete a task with a clear outcome, such as a brief, deck, analysis, recurring update, workflow, or file you can review and use. I find that almost entirely useless, because I've been using regular ChatGPT Chat for all of those task categories for years! The better question then is what features does Work have that are missing from Chat? After extensive experimentation I think I've mostly figured that out: In Work, you get the option to pick GPT-5.6 Sol, Luna, or Terra, each with Light, Medium, High, Extra High, Max, or Ultra reasoning levels. You can also pick GPT-5.5 at Light, Medium, High, or Extra High. These look to be the same models that are available through the OpenAI API. Chat offers a different selection: 5.6 Instant, Medium, High, Extra High, and Pro (actually Extra High and Pro are only available for $100/month+ subscribers - $20/month subscribers cap out at High). It doesn't explain if those are Luna or Terra or Sol (I'm assuming Sol?). 5.6 Pro appears to be exclusive to Chat, with no equivalent in Work. My current understanding from using Codex is that Ultra is a special mode that more eagerly delegates to sub-agents. I believe ChatGPT Work sessions are billed against your Codex allowance, while ChatGPT Chat Sessions get their own, separate allowance. This may help explain the model availability differences. As a long-time fan of the Code Interpreter pattern - pioneered by OpenAI in 2023 - this is by far the most exciting feature of ChatGPT Work (Cloud) for me. The code execution environment can now talk to the rest of the internet! ChatGPT Chat can't do this - if you ask it to install additional software packages or interact with websites or APIs that access will be blocked by the container proxy. (Weirdly, back in January it grew the ability to install packages , but that doesn't seem to work any more. I wish they had better changelogs!) Claude's equivalent container has allowed restricted internet access since it launched last September . Claude can install packages from PYPI and NPM and clone repositories from GitHub. But that is about it: the allowlist of domains is very short. ChatGPT Work allows a whole lot more than that. It can be configured with a specific list of allowed domains, but the default appears to be open to all. This makes Work an incredibly useful tool. You can have it clone GitHub repositories, install their dependencies, then use them to interact with the rest of the web! Another killer feature of ChatGPT Work is the browser tool . ChatGPT Work can launch a full Chrome instance, load websites, fill out forms, and take screenshots. If a site requires sign in the browser can prompt you to take over and enter both passwords and 2FA codes, without round-tripping those credentials through the model itself. It can even run JavaScript against the DOM of loaded pages. I prompted: ChatGPT Work fired up a browser instance and ran the code: This feels a lot like my shot-scraper javascript tool, only now I can access it on my phone! ChatGPT Chat gets a fresh filesystem for each chat session. These cannot be accessed from any other session. In ChatGPT Work each session gets its own scratch folder - named something like - but each of those are persisted across sessions, so you can access files from previous chats. I have 171 folders in right now! As far as I can tell that volume is mounted to all Work sessions that are currently running - file edits from one can be instantly seen by the others. They don't seem to share the same process space though, and localhost servers running in one can't be accessed from another. ChatGPT Work has the ability to build and deploy entire websites, using Cloudflare Workers. These can have HTML and JavaScript and can run server-side features too, including stateful features on top of Cloudflare D1 and R2. Here's a simple site I built with this feature: london-pelicans-in-her-piety.simonw.chatgpt.site My prompt was: (A pelican in her piety is a fascinating piece of medieval Christian imagery - once you know about them you'll find them all over the place.) These sites default to being private to the user that created them, but you can make them public and (on team plans) share them with other specific individuals. There's not much to say about this one. ChatGPT Chat can't run sub-agents. ChatGPT Work can. This is very much a power-user feature: if you are running a complex project that can benefit from multiple parallel agents working together, Work can do that. Another feature that seems to have migrated from regular ChatGPT to ChatGPT Work at some point. You can prompt ChatGPT Work like this: This will schedule a prompt to run on that frequency. These prompts can decide that nothing interesting has happened, or they can decide to notify you of some new information. Update : Actually this seems to work in ChatGPT Chat as well. It's still worth noting here though, as it can be used in conjunction with other ChatGPT Work exclusive features. You can set a scheduled task to update a ChatGPT Site on an hourly basis, for example. An open question for me right now is how safe all of this stuff is. My lethal trifecta model warns about the risks inherent in any agent system that combines access to private data with exposure to untrusted content and a way to communicate stolen information back to an attacker. ChatGPT Work combines all three! I'd love to hear more from OpenAI about how they protect ChatGPT Work sessions against prompt injection attacks. I expect their answer is the same auto-review mechanism as Codex. Figuring this all out took way more work than it should have. I think there are two key problems here: If the ChatGPT Work documentation included the exact system prompt and tool descriptions used by the agent I wouldn't have needed to write this post. Shortly after publishing this article I had an idea. I started a fresh Work session and prompted: Here's the site it built , which includes details of 223 registered tools - though 6 of those are from my own personal MCPs served via datasette-mcp . I noticed that the only browser-related tool in the list was web.run , which has methods for running searches, opening URLs, and clicking links, but didn't look like the full story in regards to headless browser automation. This made me suspicious that something was missing, so I told the ChatGPT Work session that built that tools reference site: It turns out ChatGPT Work uses a lot of skills - 44 in fact! The control-browser skill explains how the browser works: Run browser setup code through the Node REPL tool. In this environment the callable tool id typically appears as . [...] The ability to interact directly with the browser is exposed through the runtime via the API. Before trying to interact with it, you MUST emit and read the complete documentation returned by in one go. So I told Work: Add the full output of await browser.documentation() to the bottom of the /skills/control-browser page And now you can read that on /skills/control-browser as well. A few more interesting Skills: You are only seeing the long-form articles from my blog. Subscribe to /atom/everything/ to get all of my posts, or take a look at my other subscription options . Options to use Luna and Terra in place of Sol A code execution environment with Internet access A headless Chrome browser A persistent filesystem shared between sessions The ability to publish ChatGPT Sites The ability to run sub-agent sessions with Sol, Luna, and Terra Scheduled prompt automations (may be in ChatGPT Chat too) OpenAI explain Work in terms of what it's for, not what it actually does OpenAI still insist on hiding their system prompts and tools descriptions documents for creating files imagegen with tips on creating images with the tool pdf for both reading and rendering PDFs Spreadsheets for manipulating , , , sites:sites-building for creating ChatGPT Sites openai-docs for answering questions about itself data-analytics:build-dashboard for building data dashboards

0 views

Two Alleged ‘TeamPCP’ Hackers Arrested in Australia

Authorities in Australia have arrested two men believed to be members of TeamPCP , a prolific cybercrime and data extortion group blamed for perpetrating the longest running spree of software supply chain attacks ever. In a statement released today, the Australian Federal Police (AFP) said two men from Western Australia, aged 21 and 23, were arrested in connection with a “sophisticated cybercrime syndicate that allegedly created malicious open-source software to rob thousands of global businesses.” The AFP did not name the defendants, but KrebsOnSecurity learned the 21-year-old suspect’s real identity in June, and has been communicating with him ever since. This story includes interviews with TeamPCP’s self-described spokesperson, and examines clues left behind by the TeamPCP leader that likely led to his undoing. TeamPCP vaulted onto the cybercrime scene in late 2025, embedding malicious code in hundreds of open source software tools and extorting victims for profit. Members of the group made headlines by compromising corporate cloud environments using a self-propagating worm dubbed  Shai-Hulud , which added malicious code to open source programs maintained by developers whose credentials at public code repositories like GitHub or NPM were phished or stolen. Writing for Wired , journalist Andy Greenberg described TeamPCP’s core tactic as a kind of cyclical exploitation of software developers. “The hackers gain access to a network where an open source tool commonly used by coders is being developed,” Greenberg wrote in May . “The hackers plant malware in the tool that ends up on other software developers’ machines, including some who are writing other tools intended to be used by coders. The malware allows TeamPCP’s hackers to steal credentials that let them publish malicious versions of those software development tools, too. The cycle repeats, and TeamPCP’s collection of breached networks grows.” TeamPCP also has practiced something akin to cyclical recruitment. In May, the source code for the third iteration of Shai-Hulud was published online, and TeamPCP soon after launched a contest offering $1,000 in virtual currency to whichever participant could conduct the largest supply chain operation using the worm’s code. According to the contest rules, participants were scored based on the number of weekly and monthly downloads of packages they compromised — directly incentivizing them to target the most popular code libraries. A screenshot of a message from TeamPCP’s Telegram account, announcing the supply chain hacking contest. Image: dataminr.com. “TeamPCP has stated the competition is a recruiting opportunity and they intend to purchase all meaningful access harvested from participants’ campaigns,” the security firm Dataminr wrote . “The $1,000 XMR (Monero) prize is a recruitment floor and has been dismissed by the actor as ‘just like participation trophy,’ adding ‘if you find something good you will be paid way more,’ confirming the contest’s true function as talent identification and malicious access acquisition at scale.” In March, TeamPCP executed a supply chain attack targeting AI infrastructure by compromising the code for LiteLLM , an open source AI gateway that connects users to more than 100 different large language models. A recent analysis by the security firm CloudSEK found TeamPCPs attack on LiteLLM harvested cloud service keys and other secrets from more than 2,500 organizations, including many of the world’s top technology companies. In May, TeamPCP claimed credit for compromising at least 3,800 code repositories at the Microsoft-owned GitHub , after a GitHub developer installed a code extension that was compromised by TeamPCP’s malware. Security experts say TeamPCP is less of a hacker group than an amalgamation of threat actors from multiple cybercriminal gangs who sometimes work together toward similar goals. “It is not a structured criminal crew with a single operator,” said Austin Larsen , a principal threat analyst with the Google Threat Intelligence Group . “It is a peer community of individually-skilled actors, with one clear center of gravity.” That center of gravity is George Prepakis , an accomplished security researcher and self-described exploit developer who operates the Twitter/X profile @kernelstub . Earlier this year, @kernelstub tweeted a public invite link to a Matrix chat server he created and dubbed “Cybercats,” and TeamPCP and several other cybercrime entities have been using this server to communicate daily for the past several months. A screenshot of the Matrix chat server “Cybercats,” whose members used hacker handles associated with multiple distinct cybercrime groups that have occasionally collaborated on a series of supply chain and data ransom attacks over the past nine months. Kernelstub, like other administrators in the Cybercats chat, has been using his Twitter/X profile name as his handle in these Matrix communications, frequently tweeting references to other members and to conversations taking place in the Cybercats chat. In a number of cases, the corresponding X accounts for members of the Cybercats chat taunted cybercrime victims publicly before the incidents were reported in the news media. The Cybercats administrator listed at the top of the screenshot above — “ Boxturtle ” — is a close associate of TeamPCP who has been tweeting about the group’s conquests under the name @xpl0itrsturtle . This handle corresponds to a data breach broker active on Breachforums and Darkforums who has been selling data stolen in a wave of recent breaches at automobile manufacturers, including BMW Group , Audi , Honda , Mercedes-Benz , Volvo and Toyota , as well as data allegedly taken from Snapchat and SportRadar . The data leak site for the extortion group or handle “xpl0itrs.” The Cybercats administrator “ SeesawSec ” in the screenshot above is the alias of whoever is behind the cybercrime group known as Fulcrumsec , which recently claimed credit for data extortion attacks against the pharmaceutical giant Novo Nordisk , the data broker LexisNexis , and Avnet , a Fortune 500 distributor of electronic components. The data leak site of Fulcrum Security, a.k.a. Fulcrumsec. The Cybercats administrator “ @pcpcasper ” also has been using a similar name on X to discuss TeamPCP’s attacks and victims. This person has an extensive message history on Telegram, where their messages and shared videos show @pcpcasper is an active and vocal member of the National Socialist Network, a neo-Nazi political organization based in Australia. At one point in these chats, @pcpcasper shared videos and images of what they claimed was their cat, and several of those videos place this user in Western Australia. One source close to the investigation told KrebsOnSecurity that @pcpcasper was one of the two arrested, a claim supported by messages that @kernelstub posted online this morning. The Cybercats member roster pictured above also features an administrator with the username “ T ,” which is short for the now-banned Twitter/X profile @pcpcats , the account operated by the self-described TeamPCP spokesperson who was arrested today. As we’ll see in a moment, @pcpcats also is from Western Australia. By the time @kernelstub tweeted a public invite link to the Cybercats Matrix server, T/@pcpcats was posting only infrequently to the group chat, with other members often inquiring as to his whereabouts and well-being. The group’s collective concern related to @pcpcats’s tendency to blame his increasingly extended absences on the use of hallucinogens and other narcotics that kept him awake for days on end, but also caused him to crash in bed for several days after the highs wore off. The Cybercats member @pcpcats has used multiple nicknames on the cybercrime forums, including EllisD25/LSD on Darkforums, BulkDMT on Breachstars, and Express on Breachforums. These accounts are linked because they all advertised the same Tox ID and/or Session ID as instant message contact handles in their cybercrime forum posts. BulkDMT was also known on the forums as DMT Host , which was a virtual private server (VPS) hosting service that was peddled on Darkforums and Breachstars. DMT Host/EllisD25, posting on the English-language cybercrime community DarkForums in September 2025. Image: ke-la.com. According to the cyber intelligence firm Intel 471 , Express registered on Breachforums using the email address [email protected] . Intel 471 finds Express posted on Breachforums across a two-month period in 2025 using four different Internet addresses located in South Africa . On July 30, 2025, Express announced on Breachforums they were selling access to 14 gigabytes of data stolen from South Africa’s State Information Technology Agency. The threat intelligence platform Flashpoint recorded more than a year’s worth of messages from the TeamPCP leader’s alter ego on Telegram — Persy_PCP —  who claimed they split their life living between two countries [full disclosure: Flashpoint is an advertiser on this blog]. “I have these [files] as well, problem is these are in another country,” Persy_PCP explained to another user inquiring about a stolen data set in November 2025. Later that month, Persy_PCP complained, “My whole country is racist and they want people like me dead.” Flashpoint records show BulkDMT shared in September 2025 that “this country is going to fucking starve when they take the farmers land,” a likely reference to white landowners in South Africa who claim to be targeted by an ongoing genocide campaign . This tracks with public reporting on TeamPCP. Cyberscoop reported in June that Google had traced TeamPCP’s residential and mobile Internet address connections to South Africa, “indicating the primary operator was located there during at least some of its attacks.” BulkDMT also shared on the group chat at Breachforums that they were recovering from an addiction to methamphetamine. “My life is kinda fucked rn [right now], but that’s fine and there isn’t really a point in pouring so much emotional energy into that fact, my parents had money but I unfortunately got really addicted to some things so I don’t get to benefit from that. As long as I continue to survive, stay sober, and move closer towards my goals that’s enough drive and meaning.” The identity threat protection company SpyCloud finds [email protected] shows up in the registration of an account called ChristmasSnow on the cybercrime community Raidforums in 2022. Nearly all of the Internet addresses used to access that account came from ISPs in Perth, Australia, SpyCloud found. KrebsOnSecurity looked up all of those Perth IP addresses in passive DNS records maintained by DomainTools.com , and found one of them — 211.27.196.111 — for several years was used as a private file server by a family in Perth with the last name of Thomson . Those records show at least three hosts — ithomson.direct.quickconnect.to (a remote Synology server), kthomson0061.direct.quickconnect.to, and joshuawthomson39.myqnapcloud.com (a QNAP network storage device) — persisted at that address between 2022 and 2025. Searching on “ joshuathomson39 ” in the breach tracking service Constella Intelligence reveals an account at the freight forwarding company kwe.com created in the name of Joshua Thomson from Perth, Australia. The open source intelligence platform Epieos finds the phone number attached to that kwe.com account was used to register a Facebook profile for Josh Thomson, which says his family includes a brother named Ruben , his father Ian , and his mom Cindy. That Facebook profile also says Josh and his family are originally from Pietermaritzburg , in KwaZulu-Natal, South Africa, but currently living in Cottesloe , a beach-side suburb of Perth. A search in DomainTools for Ian Thomson and Australia unearthed five domains by the same registrant, including securecomputing.au , thomson.org.au , and thomsonfamily.net.au . Ian Thomson is a dentist in Cottesloe, and a biography says he graduated from The University of the Witwatersrand in Johannesburg, South Africa. Constella finds a [email protected] registered a number of accounts online, but Josh doesn’t seem to have much of a connection to dodgy cybercrime forums. His brother Ruben, on the other hand, has quite the presence on these communities, dating back to at least 2018. Constella reports [email protected] frequently reused the password “joshuathomson1,” and Constella further finds that password was used by just a handful of accounts, including [email protected] and [email protected] . According to Intel 471, [email protected] was used to register the user Yolosolo17 on the crime forum Altenen in 2018, and that user account was registered from the Perth address 110.141.230.15 . On Altenen, Yolosolo17 advertised free web proxies, as well as the domain rubenthomson.com, which was at one point used to sell steeply discounted iPhones. DomainTools says rubenthomson.com was hosted at 110.141.230.15 and registered to [email protected]. A cached copy of the domain rubenthomson.com from 2017 shows a login page underneath a banded stack of money. Image: archive.org. SpyCloud reports 10.141.230.15 was used by the email address [email protected] on Raidforums and [email protected] on Nulled, and that the same IP was used by the email addresses [email protected], [email protected], and [email protected]. SpyCloud also shows that sheepstealing Gmail address is tied to the accounts Sheep420 , YoloSolo117 and Yakuza.cc on Raidforums, and to the account “Sheep Stealing” on Hackforums. Intel 471 says [email protected] was used to register the account DingoFlour on Breachforums in October 2023, as well Sheepx on Altenen. Epieos reports that [email protected] is tied to an Airbnb account for Ruben, who described himself as a Web developer who went to school at the University of Western Australia and was living outside the country. “Hey, I’m Ruben, my friends call me Ellis . I’m a Perth creative who occasionally books rooms when visiting family and for photography.” Epieos also finds [email protected] registered an upwork.com profile under the name Ruben, who said his main skills are setting up secure server hosting solutions and PHP full-stack Web development. “I’m familiar with Linux, working with relational databases (SQL),” the Upwork profile reads. “I also script in Python mainly for writing social media bots.” The Upwork profile for Ruben Thomson in Cottesloe, Australia. Epieos further discovered [email protected] is connected to a Microsoft account for Ruben Thomson, and to a now-defunct GitHub account called XmasSnow/XmasSnowisBack that scammed people on the forums in 2022 by claiming to sell exclusive exploits for recently-released software patches (recall that [email protected] was used to register a forum account named ChristmasSnow). This same sheepstealing email address registered a Twitter/X account in 2026 called “Gone Fishing” that lists its location as South Africa. That Gmail account also left several reviews for businesses listed on Google Maps over the past seven years, but all of those establishments are located on the west coast of Australia. Business reviews in Western Australia left by the Google account sheepstealing at gmail.com. The people search service Pipl finds a 21-year-old Ruben Thomson in Western Australia who has a phone number ending in 979. A lookup on that number at Epieos reveals it is connected to a TikTok account under the name Ellis, and to a PayPal account in the name of Ruben Thomson. Finally, a search on the name Ruben Thomson from Cottesloe at the Australian government’s record of registered businesses finds he has incorporated or served as an official in multiple companies created since 2024, including Secure Computing Solutions , Tensor Industries , and another entity ironically named OPSEC Express . Recall that Express was BulkDMT’s nickname on Breachforums. Australian companies connected to Ruben Thomson. Image: abr.business.gov.au. It’s ironic because OPSEC is short for the term “operational security,” which refers to techniques and behaviors used to obfuscate and compartmentalize one’s real-life identity online, and using your cybercrime handle as part of your own company name is very much the antithesis of that practice. There is at least one other major opsec failure by Ruben that exposed a link to TeamPCP. In June 2025, someone using the name Ruben Thomson registered on HackerOne , a popular “bug bounty” program that seeks to reward and recognize researchers who agree to work with affected software vendors to help fix the flaws before publishing about their findings. What was Ruben Thomson’s chosen HackerOne username? Deadcatx3 , a nickname that has been flagged by multiple security firms as an alias used by TeamPCP. The HackerOne profile for “Ruben Thomson” uses the nickname Deadcatx3, which multiple security firms have concluded is an alias used by TeamPCP. Image credit: flare.io. In early July 2026, not long after having discovered clues about Ellis’s real life identity, KrebsOnSecurity interviewed the TeamPCP leader via Signal, where he was remarkably open about his activities and personal struggles [for the sake of simplicity, the TeamPCP spokesperson will be referred to from here on as Ellis]. Ellis claims he stopped doing cybercrime for TeamPCP in March 2026 — just before the attacks that compromised LiteLLM — and that at least one other individual has taken over the group’s leadership since then. Ellis shared that a year earlier he had just completed the latest in a series of detox and sobriety programs, and was two months sober when he reconnected with some old friends from the malware development scene. “One year ago I needed help monetizing some [GitHub credentials], I was two months sober and needed a distraction and something to keep busy as well as people to speak to,” Ellis said. “I had largely disconnected from my old circle, they had become very toxic and I needed to get away from the substances. Previously I had done some mass exploitation campaigns and grew up doing [malware development] and [capture the flag] contests. There were some friends who were also vending but had stopped a while, and one of them introduced me to some chats where I posted access for sale.” Prior to that, Ellis said, he was homeless and hopping between “some very unstable places.” “Blackhatting is fun,” he said. “There are actual rewards and incentives to learn and you grow with your team. Without qualifications, no employer will even take the time to hear you out.” Ellis claims he’s earned a grand total of about $20,000 for his activities with TeamPCP, and that it was never about the money or fame for him. Asked whether his experiences with TeamPCP might prepare him for gainful employment in a legitimate IT job, Ellis said he doubted it. “I am nowhere close to a skill level where I am comfortable, and this would take maybe half a decade of further experience,” he said. “I no longer have to choose between rent and food for that I’m grateful and so are the team members.” Ellis expressed no remorse over his cybercrime activities, and said he was grateful for the friendships and relationships built throughout his engagement with TeamPCP. The young hacker also seemed resigned to his fate, and told KrebsOnSecurity that he’ll accept the consequences if he’s ever arrested. “If I’ve already been found out then its out of my control, I’ll make peace with that,” he said. “Honestly, I think someone like me needs a lot of help that prison just can’t provide. If I had the funds to study different parts of the field and closer guidance, this would have turned out differently. But that’s a pipe dream and we both know this.” It is clear from reading Ellis’s posts to the group’s Matrix server chats that his struggles with sobriety are ongoing. On Thursday, June 25, Ellis told @kernelstub he was about to “trip” with his “homie.” “What kind,” @kernelstub inquired. “Ketty and some DMT,” Ellis replied, referring to the dissociative anesthetic ketamine and dimethyltryptamine (DMT), a powerful psychedelic compound that is found naturally in some plants but is also synthetically produced in underground lab environments. “There’s a little 2cb so we might throw that in the mix,” he continued, referring to another psychedelic compound by its chemical shorthand. Roughly two weeks before his arrest, Ellis told KrebsOnSecurity he was ready to leave his life of crime behind and was prepared to turn himself in, but that in the meantime he was making plans to tie up loose ends. Less than 24 hours later, the TeamPCP leader posted an image on Telegram showing a yellowish powdered substance in a baggie and on a scale, possibly synthetic DMT. The image shows the powder being weighed next to a series of small vape cartridges, two of which are open on the table in front of the photographer. An image posted by the TeamPCP leader to Telegram, advertising his acquisition of some type of psychoactive substance, most likely a synthetic version of the powerful hallucinogen known as DMT. The two defendants were arrested Wednesday morning. The AFP said the men face a combined 14 cybercrime offenses and are scheduled to appear in Perth Magistrates Court today. Charlie Eriksen is a security researcher at Aikido Security who has closely followed TeamPCP’s cybercrime campaigns. Eriksen said TeamPCP are a good example of a new kind of threat actor that does not fit neatly into the usual categories. “They are not a state actor, not quite organized cybercrime, and not purely ideological,” he said. “Their motivations seem to mix money, disruption, attention, and ideology.” Eriksen said that historically there has always been a meaningful gap between reading about an attack technique and being able to reliably turn it into an operational campaign, but that large language models (LLMs) and artificial intelligence increasingly are helping threat actors to bypass that knowledge gap. “You had to understand the research, adapt the code, troubleshoot it, build infrastructure around it, and then repeat that process across different targets,” he said. “LLMs have compressed that gap significantly.” According to Eriksen, this creates an environment where threat actors suddenly have the ability to operate at significant scale without having developed the operational discipline that traditionally accompanies that level of capability. Put another way, it sets the stage for cybercriminals who are capable enough to cause significant damage, but not necessarily careful enough to understand or care about the consequences. “They can be noisy, they can make mistakes,” he said. “They can leave evidence everywhere. They can take risks that a professional criminal group or intelligence service would consider completely unacceptable. But that does not necessarily make them less dangerous. In some ways, it can make them more dangerous.” In a recent blog post , Eriksen called TeamPCP’s Shai-Hulud worm the “best thing to happen to supply chain security,” because it forced GitHub and other public coding platforms to erect new security safeguards. In direct response to TeamPCP’s broad success at pushing poisoned versions of popular software packages, GitHub in late July introduced a three-day “cooldown” mechanism for Dependabot, the platform’s tool for auto-fetching newly shipped updates for any package dependencies. Cooldown periods are designed to help buy time for security tools and package maintainers to identify and remove any compromised versions. Other coding ecosystems like Python and various JavaScript platforms also added support for cooldown periods this year amid growing calls from security experts about the need for more widespread adoption of the safety feature. Eriksen said TeamPCP’s legacy is that they achieved in the span of a few months what the supply chain security community has been unable to do for years. “They managed to wake up Microsoft to the fact that they had become negligent in terms of security,” Eriksen said. “By compromising GitHub and stealing their source code, they humiliated Microsoft into action, making them finally act on what we had been asking them to do and take seriously for a while now.” Update, 10:08 a.m. ET: A story this morning from ABC News in Australia confirms Ruben Ian Thomson of Cottesloe was one of the two arrested. The 23-year-old suspect thought to be @pcpcasper, Michael Gaebler, also was arrested in Perth. ABC News reports that Thomson was denied bail (Mr. Gaebler’s attorney reportedly did not request bail for his client), and that both men will be held in custody until their next court appearance on September 18.

0 views
Himanshu Anand 3 weeks ago

I had some free time, so I tried to pwn V8

I am not working full time right now, which means I have free time which is dangerous. You start by reading one V8 blog post and somehow end up trying to make Google’s Chrome open with a ROP chain. That is basically what happened. I chained three public V8 bugs against the exact Chrome build used by Google’s v8CTF . The first bug leaked a compressed object address, second turned a garbage collection mistake into a fake JavaScript array and read/write access inside the V8 cage and the third used a JSPI and JS Dispatch Table mismatch to pivot the native stack outside that cage. Then I reused code already inside Chrome to open, read and print the flag. Real Google flag: yes . V8 sandbox escape: yes . $10,000 bounty: no . Getting the flag and winning the challenge turned out to be two completely different things. This is the full story, including the ugly reliability numbers and how much LLM assistance went into the work. There is a very specific kind of confidence you get after reading three browser exploitation posts. You do not know enough to build an exploit, But you know enough to believe that you probably can. That is where this started. I have spent years around application security, old Internet Explorer exploitation, heap sprays, ROP, ASLR, DEP and the usual fun. Modern V8 exploitation still looked like a different planet. Every useful pointer seemed to live behind another table, every object had a compressed representation. Every time I thought I understood the heap, the garbage collector moved it. So I decided to learn it the only way that works for me: choose one concrete target, define a ridiculous finish line and keep going until either the finish line or my patience breaks. Google had already provided the finish line. v8CTF is Google’s continuous V8 exploit challenge. Google runs a pinned Chrome build on its infrastructure. You connect to the service, solve a proof of work and give it an HTTPS URL. Chrome opens your page and A flag exists at . Your job is to make Chrome print it. That sounds simple because the sentence has hidden almost all the work. the protocol is tiny. the amount of browser internals between the first and last box is not. The official rules say an eligible submission can receive $10,000 . But the word eligible is doing a lot of work there. If you found and reported the initial bug, Google can treat the chain as a 0 day. If the bug was already public or somebody else found it, the chain is an n-day. Mine was very much an n-day chain as I did not discover the original bugs. I combined public work, adapted it to the exact target and built the missing pieces between those bugs. Still difficult but not a 0-day. The target was Chrome for Testing on Linux x86-64. It contained V8 at commit . Browser exploitation is not “Chrome 150-ish”. A map value, object layout, builtin offset, ROP gadget or pointer table rule can change between nearby builds. An exploit for the wrong minor version is often an expensive way to launch a crash reporter. The challenge disabled the crash reporter too, so sometimes it was only an expensive way to stare at silence. Chrome was launched with flags including: The flag needs an explanation. Chrome has more than one security boundarys, The process sandbox was disabled for this challenge, but the in process V8 heap sandbox still existed. The outer kCTF and nsjail isolation also remained. Native code execution in the renderer was enough to read the challenge flag, but getting ordinary corruption inside the V8 heap was not. I escaped the V8 sandbox into the native renderer. I did not escape Chrome’s process sandbox because the challenge had disabled it and I did not escape the outer jail. This distinction matters. “Chrome sandbox escape” would make a much better headline. It would also be wrong. If you remember old browser exploitation, the rough plan looked like this: Modern V8 has the same family resemblance. It has also put several locked doors between every step. The modern exploit was not one magical bug that immediately produced native code execution. It was a ladder of smaller capabilities. every box had a test. if a stage did not produce an observable result, it did not exist, no matter how convincing the theory sounded. JavaScript values need to represent integers, doubles, strings, objects, arrays and functions. V8 uses tagged values so it can quickly distinguish a small integer from a heap-object reference. On a 64-bit pointer compressed build, many heap references are stored as 32-bit offsets inside a 4 GB region called the cage. V8 reconstructs the full pointer using the cage base. This saves memory and limits where corrupted compressed pointers can point. It also means leaking one compressed pointer is useful but does not reveal Chrome’s native image base. The V8 sandbox goes further. It assumes an attacker may already be able to corrupt memory inside the heap cage and tries to keep important native pointers outside it. The simplified list of things standing in the way looked like this: So even after gaining arbitrary read/write inside the cage, the exploit was not finished. It had only earned the right to start fighting the next layer. The target build contained several issues fixed later in Chrome 150. I looked at V8, Blink, PDFium, Skia, ANGLE and other routes. Most produced crashes, partial capabilities or technically fascinating ways to waste a week. The chain that finally worked used three public CVEs. CVE-2026-15903 was an optimizing compiler bug, A safe-integer assumption was lost while V8 lowered a number-like value to a 32-bit machine word. TurboFan’s reasoning said the value stayed inside a safe range. The generated machine code disagreed. That disagreement could make read outside its intended string. This was useful because it affected the exact target, triggered from an ordinary web page and provided a controlled byte-reading oracle. If I placed many references to the same object nearby, the oracle could scan memory and find the repeated compressed pointer. It could read but It could not give me the write I needed. CVE-2026-15776 was a RegExp representation mistake involving and the largest positive Smi, . The vulnerable path incremented that value across the Smi boundary. The result became a heap-allocated , but one fast path treated it enough like a Smi that the old-to-young reference was not recorded correctly. After minor garbage collections, the RegExp could still point to an allocation that the collector had already recycled. Salvatore Gulizia, also known as Serotav , published excellent work showing how to reclaim the stale slot as a fake array. His public exploit depended on layout assumptions that were not stable enough in my target. The address oracle from the first bug supplied the missing location information. Bug one knew where the object lived but could not write and Bug two could replace the object but needed help finding the right layout. The first two bugs produced arbitrary read/write inside the V8 cage. The V8 sandbox still protected native process memory. For that boundary I reused the public JSPI and JS Dispatch Table issue tracked as Chromium issue , fixed by Jihyeon Jeong ( ) in commit . The short version is that a hidden fixed-arity builtin and its JDT entry could disagree about how many native stack arguments needed cleanup. A disagreement about stack cleanup is not harmless metadata confusion. It can move the return slot into attacker influenced values. That was the path outside the cage. The final exploit ran in a normal web page. It could not use or other d8 shell helpers. I trained the vulnerable functions through ordinary DOM event dispatch instead: The optimized reader believed its last index remained inside a 256-byte string. Generated code could select a 256-byte window outside it. I placed arrays containing 64 references to the same nearby and scanned for a repeated odd 32-bit value. Odd mattered because compressed heap pointers carry the object tag in the low bit. Repetition mattered because random memory contains plenty of odd numbers too. Sixty-four copies of one candidate are a much better signal than “this number feels pointer-ish”. The live run eventually printed: That address belonged to that process. It was evidence, not a reusable magic constant. V8 uses a generational garbage collector. New objects normally start in young space. Objects that survive can move into old space. When an old object points at a young object, a write barrier records the relationship so a minor collection knows the young object is still alive. The RegExp bug broke that bookkeeping. The old retained a pointer to the young , but the collector did not remember the edge correctly. A minor collection reclaimed the number. The pointer stayed. I sprayed attacker-shaped allocations and tried to win the freed slot. same address, completely different object. the collector sees reusable memory. the RegExp still sees lastIndex. I see a fake array. The trigger looked roughly like this: The reclaimed bytes described a fake packed-double array: With an attacker-selected elements pointer and a very large length, ordinary JavaScript indexing could read and write outside the original array. This was the old heap-spray idea wearing a modern garbage-collector costume. A renderer crash is not an arbitrary read/write primitive. A fake object that survives one property access is not a stable primitive either. I used a sacrificial double and required a complete round trip: Only after all of that passed did I call the primitive read/write. There was also a four-byte alignment problem. Compressed object fields sit on a four-byte grid. JavaScript doubles occupy eight bytes. Some values fit inside one double. Others were split across two neighboring doubles. This sounds like a small implementation detail because the sentence is small. The debugging time was not small. The reclaimed master array was also fragile. The rest of the exploit needed Promises, WebAssembly modules, external strings and many allocations. Any collection could inspect or move something I desperately wanted left alone. The useful fix was V8’s large-object space. Large backing stores are not compacted like ordinary small objects. I prepared three stable allocations: The lesson was not “disable GC”. The lesson was “give GC legitimate references it knows how to maintain”. At this point I controlled memory inside the V8 cage. ASLR still hid Chrome’s native image base. The External Pointer Table stopped the obvious attack. An does not store a raw native resource pointer that caged corruption can simply replace. It stores a protected handle. But the JavaScript string still had a writable length inside the cage. I enlarged that length while preserving the legitimate EPT handle. then trusted the corrupted length far enough to read beyond the native external-string allocation. Repeated external resource objects exposed a repeated vtable pointer inside the Chrome binary. For this exact build: That defeated ASLR for the current renderer. It did not provide unrestricted native write. It did not need to. The next bug supplied control flow. This separation was important: one primitive disclosed native addresses, another primitive controlled the native stack. I kept looking for one perfect native read/write primitive when two narrower tools were enough. JavaScript Promise Integration or JSPI, lets WebAssembly suspend when a JavaScript import returns a Promise and resume later. V8 preserves native execution state to make that asynchronous trick work. I created two suspended WebAssembly computations with and . Their Promises contained internal reactions pointing to genuine hidden handlers. Using caged read/write and , I recovered those handlers as JavaScript values. The vulnerable build allowed the hidden builtin and its JDT metadata to disagree about stack cleanup. I supplied a forged receiver and a deliberately mismatched call frame. At the controlled return, a gadget moved the native stack pointer into the large carrier backing I had prepared inside V8. the caller and callee disagreed about where the call frame ended. the CPU eventually asked my carrier array for directions. That crossed from corrupted JavaScript objects to native renderer control. That was the V8 sandbox escape. I did not need a reverse shell, a calculator or an executable memory page. I needed to read one file. The ROP chain reused code already present in the exact Chrome binary: Open, read, write. ORW. W^X remained intact. The exploit used short instruction sequences and PLT calls already present in Chrome. There was one final piece of nonsense. V8 heap pointers are tagged, so the native receiver pointer landed one byte past the aligned location where the caged writer naturally wrote. The ROP stream had to be shifted by one byte without destroying its neighbors. That is modern browser exploitation in one sentence: after crossing several serious security boundaries, you lose another evening to one byte. Local testing used the exact Chrome build inside Docker, a read-only synthetic , fresh profiles for every attempt and no Docker networking. I did not touch Google’s service until the complete ORW chain printed the local synthetic flag. The live client solved the kCTF proof of work, sent the hosted exploit URL and captured Chrome’s output. The important part was this: I stared at the final line for a while. A web page had entered headless Chrome. A compiler bug leaked one compressed address. A RegExp bug let me reclaim a stale allocation. A fake array produced caged read/write. An leaked the native image. A JSPI mismatch unbalanced the native stack. Existing Chrome code opened one file and printed one line. I got the flag. That part was not theoretical. I did not build this alone and I do not want to pretend otherwise. I chose the target, ran the experiments, debugged the exact Chrome build and decided what evidence counted. ChatGPT using the Sol model did a large amount of source navigation, experiment design and code drafting. I used other models to research alternative paths and challenge claims before trusting them. The LLMs helped with: There was no prompt that said “pwn V8” and returned a working exploit. The real loop looked like this: The models were useful and confidently wrong on a regular basis. They invented object layouts, treated unrelated crashes as progress and proposed beautiful exploit chains that immediately fell apart against the binary. If an idea did not produce a marker, a controlled value or a successful run, it did not count. This work made me more optimistic about AI-assisted security research and much less interested in AI-generated exploit claims without logs. Now for the less cinematic part. This was not a credible $10,000 claim, even though it recovered the flag. Two independent eligibility problems were visible. First, this was an n-day chain. The initial vulnerabilities came from other researchers. Google’s public sheet already showed a confirmed M150 n-day from July 13, before this flag was captured on August 5. The rules normally allow only the first eligible n-day for a deployed version. Second, the exploit was nowhere near the required 80% reliability. The final direct package succeeded in 1 of 5 fresh local runs. A separate tunnel-hosted tuning batch reached 5 of 10 . Those were different delivery conditions, so I am keeping the numbers separate rather than combining them into one nicer-looking lie. one successful flag proves exploitability. it does not magically turn the other failed runs into successes. Some old notes said . The preserved raw summary said . The raw logs win that argument. Even would only have been 60% anyway. The reclaim depended on GC timing, heap occupancy, allocation order, JIT tiering, native resource placement and startup noise. Successful runs were fast, often around four to seven seconds. They simply did not happen often enough. So the honest scoreboard is: Was I disappointed? Obviously. I am a security researcher, not a monk. But “got the flag” and “won the competition” are not interchangeable sentences. This project taught me that in a very expensive dialect of JavaScript. The complete PoC and reproduction notes are available on GitHub. Everything is tied to Chrome on Linux x86-64. This is an exact-build historical exploit, not a paste-into-current-Chrome script. When people hear “V8 exploit”, they normally picture a malicious website opening in Chrome. That is the dramatic version, but V8 exists in more places than one browser tab. The surrounding security boundary changes in every environment. A browser has renderer and process sandboxes. An Electron app may expose privileged preload APIs. A server runtime may hold cloud credentials. An isolate platform may place code from several tenants in one process. This exact historical chain does not compromise all those systems. The broader questions still travel: V8 exploitation is interesting because language semantics, compiler optimization, garbage collection, object representation, WebAssembly, native ABI details and operating-system security all collide in one process. It is several different security disciplines wearing one trench coat. Pin the exact binary first. Not the milestone. Not a nearby patch. Exact binary, source revision, platform and launch flags. Prove one primitive at a time. An address-leak marker is better than a browser crash that might have happened six stages later. Treat every allocation after corruption as hostile. Logging allocates. Errors allocate. Compilation allocates. First-time typed-array use can allocate. Any of them can trigger the collection that destroys the fake object. Use legitimate runtime machinery when possible. A real tagged slot that GC knows how to update is much more stable than hoping a malformed interior object survives forever. Separate disclosure from control. The leak defeated ASLR. JSPI supplied native control. I did not need one perfect native arbitrary read/write primitive. Count every failure. Retrying until one flag appears proves exploitability. It says nothing about reliability. Make LLMs pass evidence gates. Ask for source paths, hypotheses, harnesses and competing explanations. Do not let a fluent paragraph replace a successful run. I keep coming back to the live transcript. No bounty No new CVE No Chrome process-sandbox escape. Still one of the coolest things I have built. The best part of having free time is finally learning the things you kept postponing. The dangerous part is that occasionally the learning project starts printing signed Google flags. If you are learning V8 exploitation, do not begin by memorizing every pointer table. Pick one boundary. Build one observable capability. Read the source. Read other researchers’ work. Use the LLMs, but make them show their work. Then keep moving one box to the right. If you are still reading this, you are awesome. Thanks for sticking with me! If you want to discuss the exploit, V8 internals or the painful economics of getting a flag without getting a bounty, find me on Twitter/X . Thanks for reading. The exploit has to recover a real flag from Google’s infrastructure. Only the first submission for a given initial memory corruption bug is eligible. Normally only the first submission for a deployed V8 version gets that version’s slot. A 0 day is exempt from that separate version limit. An n-day flag must be captured after Google opens the nday window. Average runtime has to stay below five minutes. Success rate has to be at least 80%. Trigger a use-after-free or overflow. Spray until controlled data lands where the old object lived. replace a pointer or vtable. Leak a module address to defeat ASLR. Build ROP to work around DEP. Jump somewhere useful. Mapping public fixes to the exact vulnerable V8 revision. Comparing object layouts and source paths. Generating small diagnostic pages and local harnesses. Reading long debugger transcripts. Suggesting alternatives when a route died. Keeping track of which security boundary a primitive had actually crossed. Fact-checking this post against the exploit logs and official rules. Chrome and other Chromium-based browsers. Node.js server applications. Electron desktop applications. Multi-tenant systems built around V8 isolates . Embedded Chromium environments, CEF applications and WebViews. Official v8CTF overview Official v8CTF rules Chrome for Testing 150.0.7871.46 manifest Exact V8 source snapshot CVE-2026-15903 fix CVE-2026-15776 fix Serotav: From Regex to RCE V8 pointer compression The V8 sandbox V8 sandbox source documentation Orinoco garbage collector Introducing JSPI JSPI/JDT fixed-arity fix Start Your Engines: Capturing the First Flag in Google’s v8CTF Fuzzing to Zero-Day: Pwning v8CTF

0 views
Jim Nielsen 3 weeks ago

Have You Heard the Good News About Microlighter?

Dave Rupert wrote about shipping microlighter : a tool for handling syntax highlighting using the CSS Custom Highlights API . I saw his post the day he released it, and I had an implementation PR up for my blog by end of day. Then, like I do with so many things, I let it sit there. This is the period where my subconscious takes over. It does the work of, “How do I actually feel about that? Do I want to merge it? Do I have any regrets about what I did?” If I still want to merge it after a few days, that’s usually a good sign that I’ll be happy with the work. (Sometimes after a few days I say, “What the hell was I thinking?” and then it’s easy to simply close the PR with zero regrets.) Well it’s a few days later and I still feel good about it, so time to ship! My PR for this is pretty straightforward: Granted, there are trade-offs to this approach. I get it. Dave’s explainer for this tool on The ShopTalk Show vibed with me because I’ve been in his shoes many times: “Whoops, somehow syntax highlighting on my blog is broken again. Guess I need to fix it. Ugh. I’ve done prism , I’ve done highlight.js , I’ve done shiki . What should I do this time? Could I do this in a way that’s just less ?” He clarifies: I’m not coming at this like, “Everyone is doing it wrong!” I was just kind of like, “Could I do this in a way that suited me?” Well, this approach suites me. There’s a kind of conceptual elegance to it where syntax highlighting lives in the realm of a styling operation rather than a content transformation plus styling. In short: syntax highlighting, i.e. styling text, is a styling concern so solve it with CSS — no DOM manipulation required! Plus, I mean, how cool is it that the code on the website is the same as the code in the DOM?!? I guess this is how I know I still like working on the web, because seeing browsers do stuff like this that they couldn’t do before still feels really cool! Reply via: Email · Mastodon · Bluesky Remove dependency (and related plumbing) On paths that 1) match my post pages (i.e. ), and 2) have code on them, pull microlighter deps from a CDN and run it.

0 views

Concurrent Servers: Part 8 - Go

This is part 8 in a series of posts on writing concurrent network servers. In this part, we'll switch to Go and see how it tackles the challenges described earlier in the series. All posts in the series: This post assumes a basic familiarity with the Go programming language. As before, we'll start with a sequential server for the basic state machine protocol presented in part 1 . This is the main function: As in the previous parts, the server is "infinite"; it never stops serving new connections until it's explicitly killed. This is the function implementing the protocol for a single client; it takes a net.Conn value that represents a socket with a client connected on the other end: Rather than directly exposing OS threads, the Go runtime implements its own M:N scheduling of lightweight goroutines on top of OS threads. Using goroutines in Go is cheap - both in terms of syntax and developer effort, and in terms of system resources . Here's a version of our serial protocol server that serves clients concurrently by launching a goroutine for each client. The part of the code that's different from the previous sample is highlighted: The concurrent modification in this case is particularly simple because the server is infinite; there's no point waiting for these goroutines to finish (and hence no need for a sync.WaitGroup ). The parameters for server.ServeSerialProtocol are lexically captured from the enclosing scope and its return value is handled by the surrounding closure. Because goroutines are very cheap, this server is very unlikely to run out of resources due to launching too many goroutines; in fact, it will probably run out of something else - like file descriptors for sockets - first. However, sometimes it's still useful to limit the degree of concurrency - even in Go, and we'll discuss some approaches to do so in the following sections. Here are some scenarios in which it makes sense to limit the degree of concurrency in Go programs, even though goroutines are cheap to launch and operate: Let's switch to the primality testing server from part 4 for the rest of the post, because it represents a somewhat more realistic workload. As a reminder: the server receives numbers, simulates blocking by sleeping, and returns "prime" or "composite". The unbounded one-goroutine-per-client version looks almost identical to the previous code sample, except that the goroutine invocation calls another function: Where ServePrimeProtocol is [1] : The simplest way to limit concurrency in Go is by using a counting semaphore, implemented with a channel: The channel sem serves as a semaphore; note that it's a bounded channel with a maximal size. A token is acquired by sending to the channel, and released by receiving from the channel. When the channel is full, the send operation sem <- struct{}{} blocks until a token was removed by some other goroutine [2] . The type of the channel is struct{} which means "empty", or "no data". This is idiomatic in Go for channels that are used solely for their semantics, not to send/receive any actual data. Since launching goroutines is cheap and limiting concurrency is easy as shown above, the "worker pool" pattern is often unnecessary for scenarios like our server. Still, it has occasional uses (such as when workers have to maintain some non-trivial state across tasks) so it's worth discussing it here. Here's a variant of our primality testing server that uses a worker pool: A fixed number of worker goroutines is launched; these goroutines all receive "jobs" from the same channel. In the Accept loop, each client connection is sent to this channel as a new job and is picked up by the next available worker. As mentioned before, you would typically see a sync.WaitGroup somewhere to ensure clean shutdown of goroutines, but in our case it isn't necessary because we have a server that never exits. Do programmers have to resort to async / event-driven programming in Go? In my experience, almost never. Go was designed from the bottom up to be suitable for large-scale concurrency; goroutines are very cheap to create, have a tiny memory footprint and switching happens very quickly, all in user space. Measurements I ran back in 2018 have shown switching times of ~170 ns, as compared to 1-2 microseconds for threads on Linux. Moreover, Go already uses event-driven loops like epoll underneath for I/O. Goroutines that wait for I/O like sockets are effectively "parked" and consume no resources (beyond their small memory footprint); they are woken up by Go's runtime when their I/O descriptors are ready - this is very similar to how asynchronous programming works! That said, some people certainly do try to stretch their resources even more with direct asynchronous programming in Go when millions of streams are handled concurrently. All I'll say is that this is very rare, and an overwhelming majority of users never have to do this. In 2018, I wrote a post named Go hits the concurrency nail right on the head , and after several more years of active coding, I fully stand behind that statement. Go is extremely powerful and ergonomic for concurrent programs; while other environments go to great lengths to implement async-await style event loops in libraries, in Go it's already baked into the core language and runtime. You want event-driven I/O with very lightweight green threads that can also execute blocking tasks without worrying about the function coloring problem ? Go has you covered. All the code for this post is available on GitHub . Careful readers will note two issues with this code: (1) the protocol assumes the complete number is read from the socket in a single conn.Read call, and there's no framing - separation between distinct numbers; (2) the prime checking loop uses i*i which may overflow for large numbers. These issues are consistent across all the versions of the prime server in C, Python, JavaScript and Rust in earlier parts, because my focus was on the simplest possible code to demonstrate a point about concurrency. Part 1 - Introduction Part 2 - Threads Part 3 - Event-driven Part 4 - libuv Part 5 - Redis case study Part 6 - Callbacks, Promises and async/await Part 7 - Rust Part 8 - Go (this part) Tasks may be compute intensive, and the CPU capacity of any server is inherently limited. If too many concurrent goroutines compete for limited CPUs, they will all make very little progress. It may make more sense to have fewer tasks that complete in a reasonable time. Protecting potentially limited downstream resources, such as concurrent DB connections or other services. For example, if the server has to send requests to other services for each task, and these are rate-limited, concurrency will have to be carefully managed. Security reasons when work is dictated by clients; malicious clients can overload and crash a service that's too eager to serve, making it unavailable for legitimate clients.

0 views
Sean Goedecke 3 weeks ago

You should never be angry at work

I try not to give a lot of prescriptive advice about working in tech companies 1 . There are many ways to be successful, and every company works differently. If you’re shipping projects and your management chain is happy, it doesn’t really matter how you’ve accomplished it. However, there’s one thing that I do think is solid advice: you should never be angry at work . Anger in the workplace is toxic. An angry colleague immediately becomes a new problem to be managed, not a professional helping you manage problems. When someone is visibly angry in a meeting or in Slack, it kills the entire atmosphere: other engineers will often go quiet entirely, not wanting to make the situation worse. If you routinely “get heated” at work, the best-case scenario is that you’re part of a tight-knit team of confident people who aren’t put off by it 2 . No harm, no foul. But the second someone comes onto your team who’s not so confident, or you have to communicate outside of your team, it becomes a big problem. Healthy workplaces route around anger in the same way that networks route around damage. Emotionally unreliable engineers will get left out of conversations that might cause them to blow up. Decision-making will get done around them in backchannels. I’ve seen this become a self-reinforcing cycle: angry engineers aren’t consulted on key decisions, which makes them angrier, which pushes them even further away from the spaces where decisions get made, and so on. You can often find these engineers bitterly complaining that they keep the company together, but nobody ever listens to them. In my experience 3 , this is almost never true. Engineers who are highly effective tend to get listened to — at minimum by their colleagues, and eventually by managers and product managers who want to extract as much value as possible from them. (One reason this is true is that all successful projects involve working with other people, and if nobody listens to you, you can’t do that.) Why do angry engineers believe they’re important? Paradoxically, anger can be really useful to a software engineer . Angry engineers are rarely the ones holding the company together, but they’re also rarely useless . One surprising thing about working for big tech companies is that some engineers are not just unproductive, but actively net-negative : either because they’re incapable of doing useful work on their own, or because they’re sloppy enough that they create more work than they do, or because they’re so checked out that they literally do nothing. Angry engineers might be net-negative in a cultural sense, but in terms of literally solving tickets and shipping features, they’re usually well above average. Why is this? Anger often comes from caring about your work, and caring a lot is sufficient to make you a competent engineer . I’ve never worked with someone who genuinely cared about their work who wasn’t (or didn’t eventually become) competent. I actually think it’s healthy for an early-career engineer to sometimes get angry about their work, because it means they care a lot: it’s still a mistake in the moment, but it’s a “good mistake” . I certainly used to get angry — in fact, I wrote about the angriest I’ve ever been at work here 4 . But you have to move past it . Think of “caring about your work” as a vertical tube, unsealed at either end. You fill the tube by pumping in emotional investment from the bottom 5 . If you have too little, it drains away and you end up as a useless coaster. But if you have too much, it overflows and you end up as an angry engineer that people have to work around. One solution is to try and care the exact right amount: be invested in work a bit, but also have hobbies and a family and whatever else gives you perspective about your work problems. If you have a rich and healthy personal life, it’s hard to find yourself yelling at somebody about React state management. However, this is a tricky balance to maintain over time. Another solution is to care about different things. The reason too much caring overflows into anger is because what you care about is misaligned with what the organization cares about . If your interests are perfectly aligned with your company’s (for instance, if you primarily care about delivering shareholder value ), you can fit way more emotional investment into the tube before it overflows. Here’s some dangerous advice : showing a little bit of anger at work can sometimes be useful. It can be a good way to signal that you care, or to build rapport with certain people, or to draw attention to something you think is important. However, it’s still always a mistake to be angry. You need to be able to drop back to a friendly mode at will, which is very difficult when you’re genuinely angry. Being able to show a full range of emotion at work is good. It makes you more persuasive and more human. Being a fully professional robot is fine — you can have a successful career this way — but there’s always going to be some kind of uncanny-valley HR-ness to your work persona that will make it hard to connect with your colleagues. If in doubt, don’t show anger. It’s never wrong to be professional. However, if you can signal that you’ve got enough distance to separate your professional feelings from your real feelings, and enough perspective to realize that the stakes of a technical decision are fundamentally not that high in the grand scheme of things, it can sometimes be okay to show visible frustration so that people know you’re still human. Well-known software engineering personalities are often angry. It feels unfair to give too many negative examples, but obviously Linus Torvalds’ rants about Linux are a great example. Some of my favourite engineering talks are from Bryan Cantrill, who is sometimes visibly furious at his subject matter. There are too many well-known angry blog posts to list, but I’ll cite one I genuinely like: my Australian blogging colleague Nikhil’s post titled I Will Fucking Piledrive You If You Mention AI Again . Anger is a part of the general image of a competent software engineer. Many junior engineers learn from this that it’s okay to be angry. However, taking your emotional cues from engineering celebrities is a big mistake, for a few reasons. First, you are not Linus Torvalds or Bryan Cantrill . Torvalds is the BDFL of the most important software system in the world. Cantrill is the cofounder and CTO of his company. When these people are angry at work, people will not work around them, because they are the ones deciding what gets worked on . Once you’re the one in charge, you can get away with being emotional in the workplace 6 . Second, you don’t know what it’s like to work with these engineers . People give talks and write blog posts because they’re emotionally worked up about something. If your only exposure to a celebrity is via their conference talks and blog posts, you’re seeing them at something like their maximum emotional intensity. If you then take that level of emotion into your normal everyday work, you’re almost certainly overshooting. I’ve been reorged into dysfunctional teams, have had projects I enjoyed cancelled, and have worked on systems that were extremely chaotic. I can’t remember the last time I was actually angry at work. To be clear, I’m not successfully hiding my anger (unless it’s so repressed it’s invisible to me as well) 7 . Nor am I naturally a chill person. I’ve just reached a point in my career where I genuinely don’t get upset about work stuff. A cynical person might say here that I’ve stopped caring about my work, so of course I don’t get angry anymore. I’ve left the side of the “real engineers” — the Linus Torvalds and Bryan Cantrills of the world — and sold out for that sweet, sweet big tech money. I mean, maybe! It’s true that I’m less invested in specific technical decisions than I used to be. But I still care a lot about doing a good job, I still spend a lot of time tweaking and reading code, and I certainly get more done than I did when I was more emotionally volatile. Being angry at work feels good. It feels like proof that you’re working on something that matters, and that you’re personally having an impact. If you’re angry, nobody can call you a coaster. But anger is only a local maximum. If you can find your way to a different style of working, you’ll not only be more effective, but you’ll be in a far better place to have impact on problems that actually matter. Mostly, I fail . As I understand it, this is the work environment that most famously angry engineers came up in. My experience is certainly limited (a handful of companies, and maybe ten different teams or organizations). I can certainly believe it happens! About halfway down, in the section titled “it’s not your manager’s fault”. Emotional investment is a liquid with the viscosity of water. To a point. Even Torvalds famously said he’d gone too far with the anger and decided to turn it down a bit. I suppose I’m not the best person to judge whether this is true. If you work with me and I do come across as an angry guy, please do tell me. Mostly, I fail . ↩ As I understand it, this is the work environment that most famously angry engineers came up in. ↩ My experience is certainly limited (a handful of companies, and maybe ten different teams or organizations). I can certainly believe it happens! ↩ About halfway down, in the section titled “it’s not your manager’s fault”. ↩ Emotional investment is a liquid with the viscosity of water. ↩ To a point. Even Torvalds famously said he’d gone too far with the anger and decided to turn it down a bit. ↩ I suppose I’m not the best person to judge whether this is true. If you work with me and I do come across as an angry guy, please do tell me. ↩

0 views
danluu 3 weeks ago

There's no reason for software to be slow anymore

The other day, I saw a viral tweet saying that people talking about how LLMs are causing slow, bloated, code are going to eat crow once they re-write everything in super-optimized assembly. We're not quite at the point where we want to write everything in assembly , but some variant of what Nolan Lawson said about testing, you can choose how many bugs you want now , which I less eloquently noted here , is becoming more true for performance. In response to a comment in my last post that the cost of formerly specialized performance work has dropped by many orders of magnitude and performance work that used to require a person or team that had a rare set of skills can be done by anyone who can type a few sentences 1 , which means that you can do all sorts of optimizations that used to be too expensive to be worthwhile for all but the largest scale or most lucrative projects, Marc Brooker responded with Completely agree with your closing point. Dynamic custom software, fitted to a particular workload rather than a class of workloads, seems like a very likely outcome. (Which comes with all kinds of fun risks and opportunities of its own). Kind of reminds me of FFTW . And a ton of weird old demoscene techniques which were all about being super fast and small on a very particular problem (and often very particular hardware). For example, I remember a demo that re-used its code as textures to get great cache locality. And Michael Malis has noted There’s been a meme circulating about how AI doesn’t help because “code was never the hard part.” I think that’s true in some domains, but in others, writing the code absolutely was the hard part. JIT compilers are a great example of that. For many pieces of software, a JIT compiler would help a lot with speeding up the code. The rarity of JIT compilers makes me believe that implementing a JIT compiler historically was too difficult for it to be worthwhile. LLMs have lowered the barrier to entry and made it much easier to write a JIT compiler. This is the thesis behind pgrust. Databases historically were the hardest piece of software to build and were limited because of that. Now, with AI, we can be more ambitious about the type of software we build. Let's try this out with FRE , the regex engine we built in the last post. Recall that it was created by having an agent loop for a month on improving regex engine performance with access to the rebar regex benchmark suite . This resulted in FRE being heavily overfit to rebar until we warned our agent that we had a holdout benchmark, which caused the agent to generalize the optimizations enough that performance was ok-ish on our holdout. There's no particular reason to use a "software factory" regex engine that doesn't beat a well-tested regex engine on holdout benchmarks, but one notable thing about FRE was that the native AOT compiled version did quite well at longer searches. We noted that, it stands to reason that one could run the native code compiler in another thread while ripgrep was running its normal matcher and then cut over to the native code when it finished compiling and generally get better performance. Of course this will generally result in worse performance for short queries as we lose a thread to compilation, but I care a lot more about how long ripgrep takes when it runs for many seconds or minutes than when it runs for a few seconds, so I'm ok with that tradeoff. In the same way we could build a regex engine in a few minutes of human time, we can also just try this experiment in a few minutes of human time. I typed a few sentences and an agent went and did the work to allow this to happen (which would be a decent chunk of code surgery for a human) and it ran the benchmark on actual ripgrep queries that come from my codex history. For longer queries, we see a 2x-4x performance improvement here for a few very simple queries. But most queries are more complex, and when we run on representative holdout queries, for queries where AOT should be enabled 2 , we get about a 7% speedup. Not an earth shattering result, but also not a bad outcome for spending a few minutes typing to codex (and it's still doing more optimization and will presumably speed things up further). This is arguably a silly thing to do, since if we're repeatedly searching for text on a computer, the obvious thing to do to speed that up isn't to write a native code compiler for regex matching, it's to create an index. But the point here is just that this kind of technical work, which used to take a fair amount of time and expertise, can just be done trivially now. And if we wanted to build a text index, it just so happens that I worked on BitFunnel, the Bing search index that was specialized for constant/fast text ingestion that won Best Paper Award at SIGIR , so I can think of a few experiments to try if we're going to build a fast local index of our entire machine (the projects I've seen seem to be intended to index your code directories, but what really kills my machine performance is when codex decides to run ripgrep against huge temporary directories with a ton of generated files and then expands to looking at my whole machine when it misses, so I'd want an index of my entire disk and not just of the code for some projects). If I were working at an AI lab and had access to things like SOTA models running on Cerebras chips or other accelerators that greatly increase tok/s and therefore load/demand for search, I might actually survey the existing indexers to see if they're fast enough or if I'd want to build something custom myself. While the open source version of BitFunnel "only" contains a bytecode interpreter and one JIT, the Bing version contains multiple JIT compilers. A project that did that level of optimization used to be a major undertaking, but " I could do that in a weekend " is now actually true for some of these kinds of projects. With my lowly $200/mo account, I think a somewhat faster ripgrep plus any off-the-shelf index is fine, so maybe this fast-ingesting whole-machine index project can be left as an "exercise for the reader (who works at an AI lab)". The drastic reduction in the cost of optimizations has been true going back to November 2025 and maybe even somewhat before then with public models (and I'm sure before that still with what folks at AI labs had access to). For an example from the GPT-5.1 or 5.2 days, with no knowledge of game AIs, I tried building an Azul AI. This ended up being the strongest AI in the world for the game by a pretty large margin. From reading the thesis that describes the 2nd strongest AI , I think my AI is probably a bit better on the "AI" side of things, but the main place it wins is on optimization despite spending what looks like maybe two orders of magnitude less time (estimated by reading the thesis and seeing the process and comparison to my process) and also mostly working on my laptop vs. having a cluster of machines to use (which means much less bandwidth to run experiments with, do parameter tuning, etc.). For example, that other AI is single-threaded and my AI is multi-threaded. Since I have a native code version as well as a heinous shared wasm memory + javascript version , and two different search architectures for two different versions, which "require" completely different multi-threading algorithms (minimax for a very small and fast net and MCTS for a larger net), this would've been a fairly large undertaking if done by hand. And, because I let an LLM pick the multi-threading algorithm based on its own (incorrect) reasoning a couple times before spending 30 minutes reading about multi-threading algorithms for game AIs myself, I ended up re-writing (having codex re-write) the multi-threading algorithm multiple times. There's a bunch of standard stuff it makes sense to do to debug and verify a multithreading algorithm for something like this, like implementing replay from debug logs that can reproduce bugs despite the algorithm being nondetermistic. Doing that alone would've probably been days to a week of work had I done it by hand, but it's exactly the kind of thing an agent can trivially do in a loop (just have it try to replay logs and insert logging for non-determinism every time you don't get a perfect replay). A lot of the tedium it used to take to get a tricky optimization like this working is gone. This also applies to a lot of other tricky optimizations. From having written CPU microcode, done CPU verification, worked on optimizing a search engine index, etc., I have a lot of experience looking at optimizations and thinking "hmm, this would increase performance by 2%, but it's going to take N person-days to verify that this tricky optimization works" and making a call to go ahead or not based on whether or not it's worth the time to get the optimization working. Now that this N has dropped by a tremendous factor (variable but, in terms of human time, frequently 1000x / 10000x / 1000000x, probably more like 1000x on dollar cost if you compare token costs at metered rates vs. the Bing engineer who wrote the compilers at JITs that the search index used), the number of these kinds of optimizations it makes sense to do goes way up. The same goes for optimizations that you aren't sure will work out. I used to sometimes look at an optimization that I wasn't sure would speed things up and think "this will take M hours to implement to the point where we have a good enough measurement to guess at the performance impact". Many more of those optimizations make sense to try out now. Going back to the game AI case, at least for the AI I tried, it seems like you gain about 100 Elo for every doubling in speed (more than in chess, I suspect because draws are very rare). Just adding multithreading alone is enough to wipe the floor with an otherwise comparable AI on a large machine. If you stack in 10-20 more optimizations that seem too annoying for most people to do by hand, the difference in strength is tremendous and it's not really reasonable to try to keep up with a hand-written AI 3 . The game AI case is a little more annoying than for most software because a lot of the optimizations you want to do actually change the result and there isn't a cheap, trivial, way to tell if the speed increase + the change in result gives a better or worse actual result in practice. And, as we noted before , current publicly available SOTA models are pretty bad at experimental design, so I had to set up the framework they used to determine if an optimization is good, but once that was in place, it's like any other optimization problem. I guess people working on LLM optimizations also have to deal with this class of problem but most optimization problems are a lot more straightforward. To pick another example, as part of preparing for performance interviews, Jamie Brandon tried Anthropic's now public performance takehome . After trying it, he had Claude pick up where he left off and it got a much better result. When he looked at what Claude did that he didn't, he said a lot of the optimizations were things that occurred to him but he hadn't gotten to yet, and "[o]thers were just crazy shit that I would never try unless I was working on this for weeks" 4 . He's a reasonable performance engineer and he got an offer for the performance job he wanted, but on a well-defined optimization problem, he doesn't stand a chance against a decent model (I haven't tried the problem myself, but I suspect I also wouldn't stand a chance given remotely comparable time controls). Coming back to this part of Marc Brooker's comment: Dynamic custom software, fitted to a particular workload rather than a class of workloads, seems like a very likely outcome. This seems pretty inevitable. In another response to my post, Michael Malis of pgrust said something similar: [discussion of pgrust optimizations] ... I think it's easy enough to create these optimizations that we could look at a customers workload and add them as needed Without having any kind of framework or setup, right before I started writing this post, I had an agent do workload-specific optimization for my ripgrep queries (not the native code compiler switch, just the optimizations to the general FRE engine based on a set of benchmarks), which took about 2 minutes for me to launch. The optimizations run on a set of queries, and then there's a later holdout set of queries to run against. That's still running, but the initial results seem promising. After one pass of optimization, the workload optimized version is 2% faster than standard ripgrep on the holdout and it's still getting faster. 2% isn't a big deal for my local ripgrep usage, but considering that this took minutes of time and the optimizations done here got started when I started typing this point and are still improving, I'd take a 2% win here (note that this isn't combined with the native code compiler, which would give a larger overall win if combined properly). And recall that this is leveraging the FRE regex engine 5 , which was substantially slower than the Rust regex engine on holdout benchmarks and was stuck with slow improvement on holdouts because with me knowing nothing about regex workloads and SOTA LLMs not being good enough at experimental design to do unguided open-ended self-improving loops, we didn't have a good way to improve performance on our holdouts. But if what I care about is performance on my own workloads, I have plenty of data and am generating more all the time. As Marc Brooker noted above, we do have to be careful about overfitting if there's a regime change that's not in the old data, etc., but we're still in a better situation than we were before. In the more general case, if you're someone like Marc Brooker at Amazon or Michael Malis working on pgrust, it makes sense to not just do this as a one-off, but to work with customers to pilot a program that uses their data to optimize things for them and then figure out how to scale it out for customers in general. I'm not working at a company where that's the best use of my time 6 , but it's pretty wild that you can see that this is coming for larger companies with more scale, and given that it only takes minutes of my time to run these experiments for my personal workflows, it's pretty reasonable to mess with this kind of thing on personal projects. Thanks to Jamie Brandon, Michael Malis, andrea (@s__video), Artyom Bologov, and Max Bittker for comments/corrections/discussion. P.S. As I've noted in the last couple posts , with coding agents, the time it takes to run an experiment and see enough of a result to satisfy my curiosity has gone way down while the time it takes to make a result really rigorous hasn't changed or has gone up, so writing things up the way I used to would mean running very few experiments relative to the bandwidth I have for them. As a result, I've just been running these experiments and sharing the result with a couple of friends. As an experiment, I'm trying to write these up in a very quick and non-rigorous way instead of years of these experiments only being known to a few friends. Like the last post, I set a goal of writing this post and doing all the clean-up in half an hour and didn't time it but am pretty sure I missed that by a bit. Even doing this, the time it takes to write these up is long enough that I'm falling behind on sharing recent results, but I'm not inclined to switch to LLM-written posts (yet?), and I don't think I can realistically get the time to clean up the data and write a post like this down enough to turn a post around in less than half an hour. Just on the length of this post, typing this up should be something like 20-30 minutes including time to pause and think about what I'm writing, and then when I look at the data sometimes something will look wrong enough that I need to look into it more closely to see if there's an issue that needs to be fixed (this happened multiple times here, and I would expect that, because I didn't spend much more time, there are other data issues that I don't know about). Anyway, if you have opinions on these quick (and surely more wrong) writeup, let me know what you think ( X Bsky Mastodon )! I've been on the record for a long time as strongly disagreeing with the general sentiment that the developers of X are bad and should feel bad for writing slow code because there are a lot of different kinds of programming expertise and not only is it not the case that most programmers don't have performance expertise, it probably doesn't even make sense for them to develop (from the standpoint of what the business cares about, what the employment market looks like, etc.), so of course most projects will have very poor performance compared to what a performance expert can do. I can see why a performance expert would look at the growing gap between how fast a program can be and how fast programs actually are and think that it's ridiculous. I don't disagree that there's an absurdity to it, but if I think about the gap between how good a UI can be and how a good a UI I can make (by hand) is, I don't think that looks any less absurd, but I also don't think it really makes sense for me to spend time learning how to build a great UI, or even a decent UI, for the same reasons it doesn't make sesne for most people to spend time learning how to decent performance work. For the example above, Jamie Brandon got an offer from Anthropic and you probably can't afford him unless you're OpenAI, but you can afford to use a coding agent that can beat him on a bounded optimization problem. The agent doesn't have the judgement he has and will do worse on an open-ended problem (recall that when we tried building an optimized regex engine and just told it to not overfit, it was more than an order of magnitude worse than the best regex engines on our holdout benchmarks , but also recall that after telling the agent there was a holdout it was doing poorly on, it sped up regex engine performance enough to generally match 2nd tier regex engines in terms of performance, which is still extremely good compared to the general level of performance optimization in most code today), but that's plenty good to achieve reasonable performance on all sorts of problems. This post has generally discussed backend performance issues, but agents don't seem worse at front-end performance if you want to drive down a set of metrics like LCP and CLS. In fact, after inserting the interactive plots I've been using recently into posts, I found that my client-side perf numbers got worse, so I had an LLM spent 1% of my weekly quota optimizing those and the numbers are once again back to being good. This is a very simple site, but people do these kinds of optimizations on fairly complex apps that ship to many millions of users and it also works there, although it does cost a few more tookens. I still don't think someone is bad and should feel bad if their software has poor performance, but I do think that someone who doesn't know anything about performance and is a reasonable user of LLMs (just in general, not on performance problems in particular) should generally be able to create software that has decent performance. If you just tell an LLM to optimize, it will often do all sorts of incorrect things that are really bad that you have to catch, but that's generally true of using the LLM effectively in the first place, so getting decent performance is no longer a specialized skill. Here's some information about the distribution of riprep queries on my machine. I make no claims that this is at all representative of what's happening anywhere else. The pattern distribution of the length of the pattern that's searched has a lot more long patterns that I would've expected. The p50 is 55 unicode code points (I'll just call these characters for simplicity), which is already longer than things I grep for by hand, and the p90 is 119! We can also look at the number of alternation arms in regexes, which are once again much more complex than what I do by hand. Another view is to look at how these are correlated. Do we get more alternation arms in the regexes as the regexes get longer? Yes. What are these really long regexes, anyway? If we look at them, most of the longest are long alternations over function or tests names, such as the following regex, which appears to be related to FRE development. But some are funny numerical constructions, such as This is equivalent to (which, if run through ripgrep on the original input, has approximately the same performance; the shorter regex is technically a bit faster on the real query data, but only by a very small amount). The entire pipeline for that was which might be an odd thing for a human to do, but agents seem to do this kind of thing all the time. On another topic, if we look at how long ripgrep queries took, there are quite a few slow queries, e.g., p99 is almost 1 minute! And p999 is almost 10 minutes! And the maximum query over this time period (around a month on one laptop; queries and distributions seem likely to be different on the AWS hosts I run agents on, etc., but I haven't checked) is approaching 2 hours! In terms of command line options, we see the following. Perhaps unsurprisingly, codex often wants line numbers and, for whatever reason, it very occasionally uses PCRE2 regexes. I won't add plots or tables for these, but another thing to note is that there's fairly low locality for what patterns are searched for (about 94% of patterns only occurred once), which makes some sense given how long a lot of the queries were. However, there's fairly high locality in what files get searched and a file that got searched is relatively likely to get searched again soon, indicating that (for small enough files), they're likely to be searched in memory. Also, 99% of queries were regex queries (1% were non-regex string searches) and 99.9% of search queries were ASCII only, but in terms of files searched, approximately 45% were ASCII only and 55% contained Unicode, a higher percentage than I would've guessed for Unicode. On a draft of the last post, Peter Geoghegan noted It's also possible for a regex implementation to be faster by supporting fewer features. Some implementations don't support back references, etc. which is also true here. The workload-specific optimizations done here were fairly superficial because I just gave codex some short instructions and let it do whatever it wanted (which is, in general, not the most effective use of codex), but with a more detailed plan, more focused optimizations supporting the common use cases for my queries could be expected to yield larger gains. though, as we discussed in that post as well as before , the benchmarking and experimental design skills of SOTA models aren't good enough to do this in the general case without a human (or a skill) setting up the benchmarking environment for the agent. [return] we can see from our old benchmarks that , even with time to run the compiler, there are a lot of cases where the native code compiled version is slower than the Rust regex crate. If we look at why this is, these tend to be more complex queries where the Rust regex crate has some algorithmic optimization and the FRE native code compiler is falling back to something naive (the agent that created FRE spent much less time on the native code compiler than it did on the "normal" regex engine). [return] I have no doubt that a hand-written AI by someone who has real AI expertise, e.g., by someone who's written one of the top Go and chess engines in the world, could beat my AI on the strength of the "AI" side of things being better than what you get when someone who knows nothing about AI (me) creates an AI, but if the levels of expertise are remotely similar, the LLM-written version is going to dominate for any given amount of time spent. [return] it's arguably unfair to compare the result of an agent picking up where he left off, since his work is a starting point which might let an agent do much better than it would do on its own, so I tried giving the fresh task to an agent and it got a very similar score to what he got when an agent re-used his work (and a quick check by another agent didn't find evidence of cheating). [return] The performance probably would've been better if I had an agent just modify a ripgrep fork directly, but I was curious if this could also solve the FRE overfitting problem with respect to my queries. [return] a while back, I reduced the size of page in our signup flow from 50 MB to 5 MB and a revenue A/B test seemed to indicate that this increased revenue by about 0.5%. In general, I'm a huge fan of doing the simple and easy wins first, such as this , and there are probably a lot of higher ROI wins than we'd get out of building custom compilers or doing other highly specialized technical work here. [return]

0 views
Sean Goedecke 3 weeks ago

Readers can't identify watermarked AI text

In the last few weeks, I’ve been complaining that everyone is wrong about AI watermarking: it isn’t really anti-consumer and it doesn’t make the outputs any worse. The watermarking papers demonstrate 1 that this is true, but I thought it might be interesting to put it to a practical test. Given examples of watermarked and unwatermarked answers to the same prompt, could readers tell which is which? To find out, I vibed up 2 https://sgoedecke.github.io/watermark-quiz/ , a static site that quizzes readers. I used Qwen3-30B-A3B-Instruct-2507 on a rented H200 to generate thirty responses: three responses per question, one of which was secretly watermarked with SynthID-Text. The rented GPU cost around two dollars. To measure results, I just sent users to a different page for each score, and aggregated visitors-per-page in my analytics 3 . This would be easily spoofable if anyone cared enough to do so, but for a casual test I think it’s acceptable. The first round of traffic I got to the quiz (278 participants) had these slightly puzzling results: Pure random choice would lead to an average score of 3.33/10. However, the mean score here is 3.92. There is indeed a spike around 3/10, as expected, but there’s also a second weird spike at 6/10. Why is that? It turned out that the SynthID response was option A in six of the ten questions, so users who just selected the first answer for every question would get 6/10. Oops. I re-shuffled the questions and got these results: Now the mean is 3.4/10, much closer to the expected 3.333. There’s no spike around 6. We only had 73 people take the quiz after I shuffled the questions — most people saw it and took it immediately after I posted it to my LinkedIn and Hacker News — but given the previous results, I think that’s still enough to feel confident that people were just guessing randomly. So no, people can’t identify the presence of AI watermarks . Obviously this wasn’t exactly a scientific study, but it’s still pretty suggestive. If watermarks were really choosing random words that the model would never pick, you’d be able to sometimes tell from three side-by-side responses which one went down the weird watermarked road, right? I also hope that something like this can serve as a persuasive tool: if you’re worrying about what impact watermarking is going to have, and your intuition is unmoved by the mathematical explanations, having a read of the watermarked and unwatermarked responses might convince you that there’s really no difference in quality. The one-sentence explanation for why is that AI models already randomly select from a handful of top tokens, and watermarking just replaces that random choice with a bias that is predictable while still being equivalently “random”: as a simple example, instead of “pick randomly from the top three tokens”, you could do “count the letters in the previous ten tokens, take mod three, then pick that token”. Some notes from the vibing: GPT-5.6-Sol put extraneous text all over the page I had to get it to remove, it chose the now-very-recognizable styling that I had to rip out, and it built some kind of weird Javascript-driven static site instead of just the cross-linked pure HTML thing I would have built by hand. It took me about an hour (although I did maybe ten minutes of actual work). Umami, hosted on PikaPods. For my blog, I do also pay for Netlify analytics because I find JS-based analytics misses >50% of technical users, but for stuff like this Umami is fine. The one-sentence explanation for why is that AI models already randomly select from a handful of top tokens, and watermarking just replaces that random choice with a bias that is predictable while still being equivalently “random”: as a simple example, instead of “pick randomly from the top three tokens”, you could do “count the letters in the previous ten tokens, take mod three, then pick that token”. ↩ Some notes from the vibing: GPT-5.6-Sol put extraneous text all over the page I had to get it to remove, it chose the now-very-recognizable styling that I had to rip out, and it built some kind of weird Javascript-driven static site instead of just the cross-linked pure HTML thing I would have built by hand. It took me about an hour (although I did maybe ten minutes of actual work). ↩ Umami, hosted on PikaPods. For my blog, I do also pay for Netlify analytics because I find JS-based analytics misses >50% of technical users, but for stuff like this Umami is fine. ↩

0 views
マリウス 3 weeks ago

Flipper BUSY Bar

Yes, it is in fact real, I’m holding it in my hands, and after what feels like years of Flipper teasing everyone with this ominous device in various online posts, I can finally confirm that it is real. The BUSY Bar is a 250 gram desk device by Flipper Devices , the company behind the Flipper Zero and the still very much in-development Flipper One . It’s basically a little display that shows various things on a 72x16 RGB LED matrix, and as of writing this it’s main selling point is that can run a Pomodoro-style focus timer , and that it has a ful-blown HTTP API that’s available over USB, over the local network and over the internet, that let’s you control this thing. The BUSY Bar has a five-position selector on the top, that switches between the two focus modes ( BUSY and CUSTOM , which are functionally identical and only differ in their defaults), a OFF position that in reality is more of a sleep mode which turns both screens off, an apps position that currently only holds a clock, and a settings position for, well, the settings. A large mechanical button in the middle starts and pauses a session, a scroll wheel adjusts the timer and doubles as an OK button, and last but not least there’s a back button for when you have to navigate back. Speaking of back, the backside of the device has a 1.54 inch monochrome OLED that shows the timer, the battery percentage and the Wi-Fi, Bluetooth and USB indicators. This way the device remains usable to its own user as well, even when clipped to the top edge of a monitor using its built-in mount, pointing its primary matrix display away from its user. The device measures 168.6 x 55.2 x 40.8mm and weighs 250g/8.82oz. The body is made out of PC/ABS with a PC front and back panel, and the monitor mount padding is TPE. The bar fits monitors up to 21mm thick and I can confirm that it works on curved displays as well. However, if you have a particularly thin monitor (say, one of these portable displays) it won’t be able to sit on top of it. The full specifications, as published in Flipper’s own documentation , are as follows: The 72x16 matrix is driven by the ICND2153 , a 16-channel constant-current PWM sink driver with a 16-bit grayscale shift register, LED open detection and a pre-charge circuit for ghosting reduction, and the ICN2012 8-channel power switch. One thing that is a bit sad in 2026 is the 2.4 GHz limitation for Wi-Fi. In an office environment full of devices and microwaves the bar is on the most congested spectrum available. The USB side is also kept, let’s say lightweight , with its 12 Mbit/s maximum speed, which, however, is certainly enough for a virtual ethernet interface serving a web UI and an HTTP API. On the charging side the documentation asks for an 18 W or higher PD charger for the 2.5 hour figure, while the device itself only appears to use 5V⎓3A (15 W) and 9V⎓1.5A (13.5 W) as its PD modes. There is one discrepancy with regard to the display brightness, where the tech specs page lists no brightness figure at all, the product page currently says 400 nits, and the launch coverage from CNX Software and XDA both quote 800 nits. I don’t have the equipment to measure it, so I can’t really tell which it is, but I can assure you that even in a brightly lit space it’s plenty bright. Flipper published an official disassembly guide on iFixit , which is awesome. Its 21 steps describe a device that’s designed with repairability in mind. The back cover is held by 8 clips and comes off with a plastic card. Below it are 5 Phillips PH1 screws, one on the bottom and four on the back. The battery has a press-latch connector and needs to be disconnected before anything else. The display flex cables use spudger-release latches, the main PCB is held by 3 screws, the control PCB by 5 latches, the front display back cover by 6 latches and the button stabilizer by 3 screws. The monitor mount legs are friction fits. Nothing is glued and the battery is a standard 18650 cell on a 4-pin connector, which means a replacement is easily and cheaply available from most electronics shops. For a 2026 consumer device this is probably something that my fellow Right to Repair advocates will love. The firmware sources are on GitHub as . Most first-party code is GPL, the library is MIT, graphical assets are CC-BY 4.0 and fonts are OFL 1.1, all of which are declared in a REUSE manifest. The build system is FBT , the same SCons-based Flipper Build Tool used for the Flipper Zero , and the dependency list is a usual embedded stack with FreeRTOS underneath Flipper’s own abstraction, lwIP for TCP/IP, TinyUSB for the USB device side, Mongoose as the embedded HTTP and WebSocket server, mbedTLS for TLS and LVGL for the UI. The bar also includes JerryScript in , wired up through and a service. That is the same JavaScript engine the Flipper Zero uses for its scripting apps. With the engine already in the firmware the only thing that still seems missing is the documented way to load your own scripts onto the device. The BUSY Bar runs an HTTP server and speaks the same API over three transports, documented as OpenAPI 3.1 . Plugging the device into a computer over USB brings up a virtual ethernet interface with the device at a fixed , printed on the back of the unit. is the local web interface, is the API reference generated by the firmware currently on the device, and is the base URL for said API. No authentication is used over USB, but it can be used via Wi-Fi and it must be used when going through Flipper’s cloud. This request responds with the current power status. The battery current is in mA, and both battery and USB voltage are in mV, which means that you can graph the device’s own power consumption without any extra hardware. Note: Access over Wi-Fi is disabled by default and has to be turned on from the local web interface over USB first. Once enabled, you can pick a token for authentication, which would go into an header, if you decide to set one: Access over the internet goes through Flipper’s cloud with a bearer token generated at , scoped either to a single device or to the account: Flipper maintains for Python with both a synchronous and an client. It maps method names onto API paths directly, so becomes and becomes . That makes the OpenAPI document usable as the library’s reference documentation: The library also has a module that scales and re-encodes images and audio for the device, an mDNS discovery helper for , and a firmware compatibility check. On top of that there is an official TypeScript library for all the soydevs, and a community-maintained .NET client . And there is also a Zig library, but… more on that in just a moment . :-) The BUSY Bar presents itself to Matter as a single on/off endpoint, an emulated switch with a configurable startup state of , , or . Turning it on starts the BUSY timer and turning it off ends it, making the integration is a trigger. Reporting the state back to Matter requires switching on Settings ➔ Smart home , at which point focus sessions can power automations like dimming lights or locking a door when a timer is turned on. Pairing is done using a QR code on the back screen or in the web interface, and the device can be commissioned into multiple fabrics at once. The Home Assistant integration is done through the HTTP API using the generic REST facilities, which works in both directions, meaning the device as an automation trigger, and the device as an output for anything else in the house. The BUSY Bar comes with mobile apps for your smartphones. I have tested its iOS app and, well, it was okay, I guess. I’m not a huge smartphone app user to begin with, but I’ll give my two cents here. The app basically mirrors the current state of the bar and offers rudimentary control over it. When you start a timer and you have the app set up (via Flipper’s cloud) you’ll see the app pushing a permanent notification that displays the timer on your smartphone’s lock screen. It’s also possible to configure a Do not Disturb mode that prevents other apps from interrupting your focus session whenever a timer is currently running. To me these are gimmicks, but to others these features might be worth something. Having that said, the apps aren’t rated particularly highly and while I didn’t encounter any issues during the few days that I’ve tested the iOS version, the app did leave a somewhat cheap impression by the way it looks and functions. It felt like one of these apps that corporate boomers at large hardware manufacturers would come up with, falsely believing that they are in-line with what today’s generations might want. There are a few things that bother me, however none of them are actual dealbreakers. The Wi-Fi authentication is a single shared numeric key, constrained by the API schema to , sent in a plain header over unencrypted HTTP on the local network. At the four digit minimum that is a 10,000 value keyspace, and I have found no documentation of rate limiting. The access mode enum also includes an value alongside , which means that the API can be opened on the LAN with no key at all. Hence, it’s probably a good idea to use a ten digit key and keep the device off networks you don’t control. Then there’s all the coming soon . Installing user apps, the JS SDK, the Windows application, and the expanded app library, those are all future promises. I don’t doubt the Flipper team that they will eventually arrive, but I could imagine that for a non-technical user it is probably very frustrating to have bought a device that can barely do anything at all at the moment, especially on a Windows machine. The device that arrives today is a focus timer, a clock, and a status display. Lastly, the price. It launched at USD 179 for waiting list members, then USD 199 for the first 3,000 units, with USD 249 quoted as the eventual retail price. At 249 it is a very hard sell, especially in the current software state. If you’re buying this because you’re a technical user and you really want to fiddle with it, it might be worth the Pesos, but as I’ve demonstrated in the past you can build a similar device significantly cheaper yourself, especially if you’re already deep into the tinkering rabbit hole. Flipper built the device I would have expected them to build, which I mean as a sincere compliment. The hardware is over-engineered for a status light in the same way that Flipper hardware always seems to be, with a real mechanical switch, a real encoder, a replaceable 18650, an official teardown guide and no glue anywhere in it. Whether it is worth the money depends entirely on what you intend to do with it. As a device that tells your coworkers to go away, it is way too expensive and not at all effective, because people who interrupt you are not deterred by a sign that tells them not to. Let me put it this way: For roughly $50 below the BUSY Bar ’s retail price you could place one of several Smith and Wesson models on your desk and it would likely be a more effective way to deter co-workers from talking to you. However, as a small, well-built, fully scriptable RGB matrix with an 8 GB filesystem, a WebSocket, and an elaborate priority system, so that several programs can share one screen without overwriting each other, it is the most open and probably best thing in its class, and I expect the community will find uses for it that Flipper hasn’t thought of yet. PS: Turning off the BUSY bar is like quitting Vim, in the sense that it doesn’t offer an obvious way to do so. Yes, the switch on top has an “OFF” position. However, that simply turns off the displays, but it keeps the busy bar running and connected to WiFi. If you want to fully shut down the device so that it won’t consume any battery, you will have to put the switch into the “Settings” position, navigate to System , Power , and Shutdown , and confirm the poweroff with Yes . Only then the device actually turns off. As mentioned before, I have a little bonus that I’d like to share with this review, which is a Zig library that implements the BUSY Bar ’s current OpenAPI specification as closely as possible, and that brings a command line tool that lets you control the device over its HTTP API. The library supports all of Zig’s platform targets as it only uses Zig’s library, and it is fairly lightweight and easy to use. I’m using it with my BUSY Bar and it has been working great for me. The command line tool contains a few quality-of-life features like simple commands for starting and stopping the busy mode, which would otherwise require manually writing JSON payloads. Long story short, if you’re one of the people that have ordered the BUSY Bar and are maybe looking to integrate it into Zig tools, or even just into your desktop environment using your own scripts, I invite you to check out the repository . If you’d only want the CLI tool to play around with your BUSY Bar you can find builds for every supported platform over on the release page on GitHub .

0 views
James Stanley 4 weeks ago

Foiling a Protohackers email spam bot

I've been receiving lots of "Undeliverable Mail Returned to Sender" lately for Protohackers signup attempts. Protohackers login is via a "magic link", so attempting to sign up or log in results in sending an email. But none of the email body is user-controlled, so I don't really see the logic in abusing this form to spam people. One email address has been put in over 100 times over the last 3 days, and I received "Undeliverable Mail Returned to Sender" each time, because it is a GMail address that doesn't exist. What's the logic in this? Most of the email addresses did exist however and presumably the spam either reached them or was filtered by GMail. Some ideas I can think of: someone griefing particular users by bombarding them with signup spam for hundreds of services they don't use someone trying to get me specifically banned from GMail by making me send lots of unsolicited emails to GMail addresses some grey-hat chaos-monkey type operation trying to nudge all website operators into locking down forms that can cause email sending a weird botnet communicates internally by triggering Protohackers signup emails to itself, and the timestamp of the email allows them to reliably communicate about 10 bits at a time?? I think the first one is the best idea but I still don't really see why you would do this. Although it's a better reason than the others, it still doesn't seem like a good enough reason to actually bother. I did already have a rate limit of 60 emails per recipient per day, and a burst limit of 5 per recipient per minute. I don't really want to stop people from being able to log in as many times as they need to, but getting 60 spam emails per day for a service you don't use is obviously too much. I did tighten the rate limits to 10 per day and 2 per minute, but really we don't want to be sending any spam. At any rate, I wanted to stop this. The goal is to stop whatever bot is sending these emails, without impacting legitimate users ( be they man or machine ). I noticed that all of these signup attempts were originating from the same netblock: 169.58.0.0/17 , apparently operated by Contabo . I'd rather not specifically discriminate against particular netblocks, both because legitimate users could be using the same netblock, and because a bot can easily change its hosting or use proxies. But the fact that it always used the same netblock makes it easy to identify, and the fact that it comes back every few minutes makes it easy to investigate. So my first mitigation was to add a tiny JavaScript proof-of-work, on the basis that a simple bot is probably not executing JavaScript. I was surprised to find that this actually didn't help. As an experiment, I kept ramping up the difficulty on the proof-of-work, and the bot was still successfully submitting the form even when it was taking over a minute to calculate the proof-of-work. So I've left the proof-of-work in place, but back down to a trivial level so as not to inconvenience real users. The next thing I did was selectively put the Bot Forensics collector script on the page only for clients within 169.58.0.0/17, with the idea that this would quickly reveal identifying features of this particular bot that I might be able to filter on without causing collateral damage. I was disappointed to learn that the bot never posted off the Bot Forensics beacon. If you were using Bot Forensics as general-purpose bot detection, this would kind of be the ideal case. If you refuse to send emails for any session that has not posted a good beacon, then this misbehaving bot is blocked and you don't really care what the beacon would have contained. But Bot Forensics is a bit too invasive for me to want to put it on the page for every user, and by this stage I was mainly motivated to learn more about this particular bot. And in any event, I don't actually have a problem with bots using the form in principle, I only have a problem with abuse of the form, whether by bot or by human. I wondered if the reason the bot wasn't sending the beacon was simply because the proof-of-work blocked the page so it couldn't compute the beacon. So my next experiment was to put a 10-second timeout between completing the proof-of-work and sending off the email. The idea was that the page would then have a good 10 seconds in which to send off the Bot Forensics beacon. Surprisingly, the 10-second timeout inhibited sending the email! Even though it previously spent over a minute calculating the proof-of-work. The bot must be waiting for inactivity and then closing the page after something less than 10 seconds. A 10-second delay is still a bit much to be imposing on legitimate users though, so I tried reducing it to 3 seconds, and then the bot was back to successfully sending emails. Although we weren't getting the full beacon content from Bot Forensics, we could still see: the bot is fetching the collector HTML for the iframe it's fetching other resources included inline in the HTML it's fetching resources requested by the JavaScript code it is able to send POST requests for exception logging but it is not POSTing the full beacon (The exception that we log is expected, it's just a failure trying to fetch a resource which doesn't exist.) I have a list of all of the User-Agent headers seen from the Contabo netblock . I'm not saying all of these are the malicious bot, but I suspect the majority are. ChatGPT points out that this list is probably from UserAgentString.com , good find ChatGPT. Despite seemingly choosing a User-Agent at random from that list, the sec-ch-ua header always lists "HeadlessChrome", example: So this does give us one way to block this bot with extremely low chance of causing collateral damage to legitimate users: we refuse to send email for any request that has "HeadlessChrome" in the sec-ch-ua header but not in the User-Agent header. That way we still don't block legitimate users even if they are using headless Chrome, as long as they're not messing with the User-Agent header. Let's keep that one in our back pocket, I'd really like to get a bit more of a smoking gun. I made the Bot Forensics collector send back a much smaller beacon, synchronously, and discovered: timezone is set to Europe/Berlin screen size is 1280x720 it doesn't have any custom functions injected into the page, that's disappointing, they're normally my favourite thing to look at navigator.platform is "Linux x86_64" but navigator.userAgentData.platform is edited to suit the User-Agent header So apart from having "HeadlessChrome" in sec-ch-ua but not User-Agent , the mismatch between navigator.platform and navigator.userAgentData.platform is another thing we could filter on. This bot does sometimes use a real headless Chrome User-Agent , and it is its most common one, but the vast majority of requests use the other weird ones. At this point I noticed one other bizarre behaviour from this bot: shortly after sending the signup email, it tries to load the user profile page, even though that page is not linked from the signup page. What's the angle there? Maybe this is some kind of automated vulnerability scanner that thinks it might be able to access random people's accounts simply by sending the email and speculatively browsing to the profile page? I literally don't understand why you would even check this. Even if it worked, which it doesn't, even if they click on the link, because that only authenticates the session that clicked the link and not the one that sent the email... but even if it worked, what benefit do you get from hacking someone's Protohackers account? Anyway, I'm out of time and stopping for now. So changes in response to this bot are: email sending now requires a (tiny) proof-of-work, and the JavaScript code includes a 1-second sleep; this doesn't stop this bot but might stop others rate limits reduced from 60/day and 5/minute to 10/day and 2/minute backend now refuses to send email for clients who have inconsistent "HeadlessChrome" and "Linux x86_64"; this blocks almost all emails from this particular bot And the Bot Forensics collector is now removed, even for clients from Contabo. If you find you now have trouble logging in to Protohackers, I'm sorry, please let me know. Also if you can work out what this bot is actually trying to achieve I'd be really interested to know. If we have to do any more on this, I think I might try a proof-of-work system that starts out easy but drastically ramps up in difficulty based on how many emails have been sent to that recipient, or from that client netblock, in the past day. someone griefing particular users by bombarding them with signup spam for hundreds of services they don't use someone trying to get me specifically banned from GMail by making me send lots of unsolicited emails to GMail addresses some grey-hat chaos-monkey type operation trying to nudge all website operators into locking down forms that can cause email sending a weird botnet communicates internally by triggering Protohackers signup emails to itself, and the timestamp of the email allows them to reliably communicate about 10 bits at a time?? the bot is fetching the collector HTML for the iframe it's fetching other resources included inline in the HTML it's fetching resources requested by the JavaScript code it is able to send POST requests for exception logging but it is not POSTing the full beacon timezone is set to Europe/Berlin screen size is 1280x720 it doesn't have any custom functions injected into the page, that's disappointing, they're normally my favourite thing to look at navigator.platform is "Linux x86_64" but navigator.userAgentData.platform is edited to suit the User-Agent header email sending now requires a (tiny) proof-of-work, and the JavaScript code includes a 1-second sleep; this doesn't stop this bot but might stop others rate limits reduced from 60/day and 5/minute to 10/day and 2/minute backend now refuses to send email for clients who have inconsistent "HeadlessChrome" and "Linux x86_64"; this blocks almost all emails from this particular bot

0 views

Concurrent Servers: Part 7 - Rust

This is part 7 in a series of posts on writing concurrent network servers. In this part, we discuss how the challenges described in earlier parts are tackled in the Rust programming language. All posts in the series: Several years have passed since the previous parts were published. I've recently went over them to make sure the information presented is still relevant and all the code samples build and run using modern toolchains. I strongly recommend reviewing the previous parts before reading this one. This post assumes a basic familiarity with the Rust programming language. It will only explain Rust constructs when we encounter code that wouldn't appear in an introductory book or tutorial. The first few parts in the series focused on a socket server that implements a simple state machine protocol. See part 1 for a complete description of the protocol. Let's start by showing how this protocol is implemented in a basic sequential Rust server: With the function serve_connection defined as: As a reminder, this server version is sequential because it accepts clients one by one; the main loop blocks on serve_connection until it's done (the client closes the connection), and only then goes back to accept the next client. Clearly, handling clients one by one won't do. In part 2 , we've discussed approaches that use OS threads to handle clients concurrently. Let's start with the unbounded one-thread-per-client solution in Rust: The spawn method returns a Result<JoinHandle<T>> ; on success, we allow the handle to be dropped at the end of the loop iteration. In Rust, this detaches the thread; we don't actually wait for it to complete. This is reasonable for our code sample, because the loop is infinite ; it never terminates anyway. The potential for runaway threads is just one of the issues with the unbounded threads approach discussed in part 2. The solution is to use a fixed thread pool. Before diving into the code, a quick note on the design: the thread pool is a fixed set of threads that await "jobs" and handle them to completion. In our case a "job" is serve_connection for a specific client. There are many ways to implement a thread pool; for our use case, I went with a set of threads that all get a shared channel to which the main thread sends jobs. A worker thread picks up the next job from the channel, serves it to completion, and goes back to waiting for the next job. Here's how this looks in code: What is Receiver ? It's a type from the crossbeam_channel crate: Rust's builtin channels in std are mpsc - multi producer, single consumer, but what we need for our job queue is a channel that supports multiple consumers (the worker threads). While std does have mpmc , this is an experimental API only available in nightly versions at the time of writing. Therefore, I've opted to include the crossbeam_channel crate that provides well-tested mpmc channels for this sample [1] . And here's the main function: Note that our job channel is bounded - it has a fixed size. This helps naturally implement a backpressure mechanism - if too many clients connect, the following clients will have to wait - the main loop blocks on tx.send and won't accept additional clients on the socket until jobs are cleared from the channel. In parts 4, 5 and 6 of the series we've discussed event-driven , or asynchronous servers. Let's see how it's done in Rust. Specifically, part 6 presented a gradation from callbacks to promises to async/await mechanisms; Rust supports all of these and - as you'd expect - modern code is usually written with async/await while hiding all the details of promises (called futures in Rust) underneath. Without further ado, here's our simple state machine protocol in asynchronous Rust: Rust takes an interesting approach to async programming: it supports some of its fundamental building blocks (like futures and the async and await keywords) in the core language, but leaves the actual async engine implementation (the thing that implements the event loop) to external crates. By far the most popular crate for async programming in Rust is is tokio , so that's what we're using here. After reading the JS code in part 6, the Rust snippet above should appear fairly familiar, except perhaps the explicit tokio task "spawn". Instead of enqueuing a callback on the connection returned by listener.accept , the code spawns a tokio task, which can be seen as a green thread , and hence uses similar terminology [2] . These tasks must not issue blocking calls; therefore, they are supposed to use tokio's I/O utilities instead of the usual, blocking std utilities. In fact, we have to implement an async version of serve_connection to make this work: Note how similar this code is to serve_connection from earlier; the only real differences are the await calls on socket reads and writes [3] , and the types involved. For example, instead of a std::net::TcpStream used in the synchronous samples, here we're using tokio::net::TcpStream . Tokio has an underlying dependency called mio to handle non-blocking APIs for all kinds of I/O. It wraps OS-specific event loops like epoll to do so efficiently. While most of the series has been using a simple state machine server as the driving example, part 6 switched focus to a server for primality testing which simulates long compute tasks. Let's see how this is done in Rust with tokio: This code is very similar to the previous snippet conceptually; isprime is: Note that this sample demonstrates a job that can block (simulated with a sleep in this case). This can be problematic in an async context, as the tokio documentation explains . One potential solution would be to dispatch a blocking task to a separate thread pool and use tokio channels to communicate with it; this is similar to the approach we've taken in the thread pool sample above. Part 6 also included a version of this server that caches data on a local Redis instance; the goal was to demonstrate the complexity of event-driven code when additional layers of callbacks are added and how async/await can help mitigate that. Here's our Rust version of this server, using the redis crate (that has a tokio component enabled explicitly to support async calls): In conclusion, while Rust provides excellent support for async programming, it doesn't solve its inherent issues like function colors and the need for careful separation between blocking and non-blocking tasks. These issues are typically surmountable with some extra care, and async programming with Tokio in Rust is very popular due to its performance benefits. All the code for this post is available on GitHub . Part 1 - Introduction Part 2 - Threads Part 3 - Event-driven Part 4 - libuv Part 5 - Redis case study Part 6 - Callbacks, Promises and async/await Part 7 - Rust (this part) Because of the function color problem , the redis crate has a connection constructor specifically for async: get_multiplexed_async_connection . Here we have an example of shared state between tokio tasks - the Redis connection. Note that we don't require any particular synchronization because MultiplexedConnection is Clone ; cloning it to different tasks is safe - and in fact that's what we do for each new task. There's no magic here; if you look inside MultiplexedConnection , you'll see that it already has all the synchronization mechanisms implemented internally, as needed. Due to the magic of async/await, the code in serve_client is nice and linear. We simply await on the Redis call, and once it's back we continue with the rest of the handler. Since we're using an async Redis connection, in case waiting is required, control will be ceded to some other task that's not currently blocked on I/O.

0 views