Posts in Shell (20 found)

Microsoft Patches More Than 1,400 Security Flaws

Microsoft today issued updates to fix more than 1,400 security vulnerabilities in its Windows operating systems and other software. Today’s epic patch batch dwarfs September’s record of nearly 1,000 fixes, which was already about ten times the normal monthly patch load from Redmond. Just one of the weaknesses patched this week is considered a “zero day,” or already exploited in the wild. CVE-2026-96951 is a security feature bypass in the Windows Shell that affects Windows 10 and 11 systems, as well as some Server versions. Windows 10 users might be wondering if they can still expect security updates after this month. Microsoft had been slated to stop shipping updates for Windows 10 systems in October 2025, but ended up adding another year of free updates after considerable pushback. A few months ago, Microsoft quietly extended that support for another year (until October 2027). At least 78 of the flaws fixed this month are rated “critical,” meaning they could be exploited with little or no action by the user, and most of those critical bugs allow an attacker to execute code of their choice on the vulnerable system. While Microsoft said there are no signs anyone is exploiting it yet, a security feature bypass in the .NET Framework ( CVE-2026-108398 ) was publicly disclosed prior to today, potentially giving attackers advance time to work out how to exploit the bug. Satnam Narang at Tenable said the number of flaws fixed this month far surpasses the previous record-setting year 2020, when Microsoft patched 1,261 CVEs. “We’ve now crossed the 4,000 CVE mark with two months left to go,” he said. Microsoft credits large language models for the high volume of bug discovery, and presumably AI gets credit for many of the patches used to fix these flaw. Meanwhile, security experts have been warning that these unusually large patch volleys from Microsoft are placing extra strain on corporate security teams, which often have to test individual updates before deploying them across a network to ensure the fixes don’t cause problems for other software. Tyler Reguly , associate director of security research and development at Fortra , notes that just 26 Microsoft vulnerabilities appeared in the “Known Exploited Vulnerabilities” (KEV) catalog maintained by the U.S. Cybersecurity & Infrastructure Security Agency (CISA) so far this year, and security teams would do well to prioritize patching those 26 vulnerabilities first. “Security leaders should focus on exploitable risk, and ask whether their teams have the clarity, capacity and support to act,” Reguly said. Which vulnerabilities deserve the most scrutiny in your organization? The folks at Cisco Talos have a useful roundup of the more noteworthy flaws that Microsoft fixed today, and of course the SANS Internet Storm Center has a reliable roundup that indexes vulnerabilities by severity, CVE number and affected software.

0 views
./techtipsy 2 days ago

Faithfully reimplementing an obscure Estonian Flash game for modern browsers: GTA õppesõit

Play it here! (desktop browsers only) Or try the “remastered” one! (desktop and mobile) There used to be an Adobe Flash game where the goal was to pass a driving test, with the instructor being ready to berate you whenever you screwed up. It’s virtually unknown outside of Estonia, but I remember playing it as a child, and it was a lot of fun. Sadly, Flash is no longer relevant, and the original website has gone offline a long time ago. For a while, the only way to experience what the game is, was this YouTube video showing its gameplay. Luckily, backups of the original Flash game exist in the Internet Archive , and I also learned that this website hosts it with a solution that translates the original game, albeit with less than stellar results. To preserve a piece of media reliably over a long time period, you need to format shift it. I decided to use a similar idea to preserve this game by putting my responsible vibe coding hat on and turning this into an authentic recreation that runs on modern browsers, including all the tiny details and admittedly horrible key bindings. After all, making old games work on modern browsers is so hot right now. For this, I relied on my homegrown custom sandbox (Podman and a shell script), Claude Code, and Opus 5.5. The workflow was quite simple, with the main goal being to offload as much of the work as possible, and to ensure that the agent can check its work along the way. By the time it was specced and a few clarifying questions covered, it executed it and the end result was playable the first time. On a high level, the process was this: I expected a lot more trouble than that. But no, it just works. Smartphones weren’t a thing back when this game was made, so don’t expect to play the game on those, yet. Performance is horrible though, all the assets are downloaded separately. But don’t you worry, this game is now ✨ remastered! ✨ find every single possible reference and backup/archive of this game online take the existing YouTube video as a reference point in case assets are missing or broken let Claude Code unpack and go through the assets and code let Claude Code play through the actual game on an actual Adobe Flash installation, using prepared scenario scripts put together a spec document read through it myself for confirmation execute on it let Claude Code play through the whole game itself to validate the results in an actual browser play the game myself

0 views
xenodium 2 days ago

agent-shell 0.87 updates

It's October. New month, new agent-shell update. If you missed the last post, have a look at the 0.78 update . While the latest changes are focused primarily on bugfixes and polish, we have a handful of new features too. As usual, this post showcases highlights, but please check out the full list of changes if you're after the nitty-gritty. agent-shell brings your favorite AI agents to Emacs , keeping you at home: familiar buffers and bindings, no terminal emulation. All powered by ACP, the Agent Client Protocol . I introduced just over a year ago, with support for two agents: Claude and Gemini. Today, it speaks to 23. Development hasn't let up either. Improvements and new features land every month. Thank you to all users, contributors, and sponsors. A couple more join the family, bringing it to 23 agents . As usual, they're all reachable via , or go straight to the new ones: With the ability to prompt at any time ( see my last post ), you can queue prompts or steer agents straight from the persistent shell prompt. Animating the prompt ties it all together: it's now obvious at a glance that the agent is busy, and that anything else you submit will queue. Check out for relevant customizations (more on that shortly). Thanks to @cark for the proposal . The header's busy indicator has been animated for some time now. Amongst the existing customizations, could turn it off altogether. Having said that, there's now a middle ground: use static text via . Just set it to a string like so: If you'd like to use smarter logic, you can now set it to a function as follows: takes all the same forms, so the prompt's own animation can also be a list of frames, a static string, or a function. With the new setting (enabled by default), pending prompts are joined (separated by blank lines) and submitted as a single turn. Queued prompts are typically follow-ups to the same instruction, so the agent now sees them together rather than acting on the first before reading the rest. If you prefer the previous behaviour, disable it with: Thank you @Gleek for the proposal . While on the subject of queueing, cancelling now offers to continue with the queue rather than quietly dropping it, and killing a shell now warns you about the pending work you'd lose. and completion used to bundle two things: how to complete, and when to pop up. Turning the popping off lost completion altogether, leaving corfu and company users out. They're now decoupled. The completion functions are always registered, so , corfu and company work either way. Two options decide whether completion pops as you type: for and for . For example, listing files can be slow in a large project, so to only complete on demand while keeps popping: Trailing spaces (and mention quoting) are now only inserted on a finished completion, fixing the stray spaces corfu and company users were seeing. Thanks to @bcc32 for proposing the split and separate and options in #537 , and to @jcubic for filing it and flagging the stray spaces. and ( and ) now move between blocks. Thank you @OSadovy for #876 . Chat mode draws "Me" and "Agent" labels (or rather "Claude", "Codex" etc.). If the current style isn't your jam, now lets you draw them yourself. It's handed an alist with and (either or ), and must return a single-line propertized string. The property is honored, so labels can also be rendered as images. Thanks to @nhojb for the idea. Transcripts now open with YAML frontmatter, including a start timestamp as follows: Thank you @vermiculus for #854 . While on topic, transcripts also got a little sturdier, thanks to @mrcnski 's proposal . now pastes images straight from the system clipboard ( #853 ), no external utilities needed, the same way Org mode handles it. Pasted image previews also survive being queued or steered, thanks to @Gleek for #852 . Codex gained for setting config options at session start, reasoning effort included. For example: Thank you @ianthetechie for #855 . Tool call output can be fairly long. A single chunky line was enough to bog a buffer down. Long tool output lines are now limited ( #844 by @Gleek ), and fallback titles are truncated so they no longer flash chunkily as they stream. File summaries also count distinct files now, so reading the same file three times counts as one file rather than three ( #863 , also by @Gleek ). While we have a handful of new features, the bulk of this release went into fixes. Performance and responsiveness: New shells now show the last few release posts on the welcome screen, so they are a little more discoverable. A few additions for package authors building on . returns non-nil once the handshake, authentication, session and any default options are all done. Until then, subscribe to the new event via ( #860 ). Agent config options are now reachable from elisp rather than the UI alone: agent-shell-gc joins the lot, bringing buffer garbage collection (auto-kill) to . If you peeked at the commit logs for the period, you'll see it's been a busy few weeks. Since the last post, 125 commits shipped, 33 issues have been closed and 15 pull requests merged. As of this writing, the backlog sits at 4 open issues and 6 open PRs (versus 16 and 6 last time around). Zooming out a little, here's how the backlog has tracked: As usual, this chart was generated using the skill shared in my emacs-skills repo. Four open issues is the lowest it's been since the project's first week, which I'm pleased about. All of this requires daily attention. Tokens keep your agents running. Sponsors keep running. is built and maintained by me, an indie dev, while the tools it competes with have well-funded teams behind them. Vendor-neutral tooling matters more than ever. Using at work? Ask your employer to sponsor too. Companies can contribute at a scale individuals can't. Thank you to all contributors for these improvements! stands on acp.el to speak ACP and shell-maker for its shell foundations, so fixes landing there reach you just the same: As of this writing, we're 88% of the way to the 150 monthly sponsors goal . If is part of your daily workflow, please consider becoming a monthly sponsor (or ask your employer to). Not in a position to sponsor? A GitHub star helps with exposure and brings in new users. Junie (JetBrains), #847 by @thanhvg . Start it with . OpenClaw , #878 by @jtmoulia . Start it with . Emphasis no longer swallows asterisks inside inline code ( #870 ). Bold around inline code now renders, as does a trailing quote or header line. Streamed tables keep their alignment and markup ( #868 ). no longer mangles bullets carrying bold text ( #867 ). Expanding or collapsing an item inside a group now works mid-stream. Chat mode no longer hides notices rendered above the live prompt. The graphical header no longer gets cut off when showing cost ( #849 ). Terminal hyperlinks (osc-8) are rendered ( #850 by @gnufied ). Severe cursor lag from large collapsed tool outputs is gone ( #839 ). Bare references are now found in linear time ( #848 by @catern ), and failed searches leave point where it was. Heartbeat ticks skip redrawing when the busy frames haven't changed. In acp.el , draining the agent's output is now bounded in how long it holds the main thread ( #32 by @moyotar ), so a chatty agent no longer makes Emacs feel laggy. In shell-maker , launching a shell with enabled no longer hangs Emacs ( #842 ). Forking resumes the session, and keeps the fork rather than falling back to another session if that resume fails ( #871 ). Grouped config options are handled ( #851 ), and a missing config change event is now emitted ( #864 ). is preserved when displaying asynchronously ( #846 by @bcc32 ), and docstring command binding specifiers are fixed ( #845 , also by @bcc32 ). Shell command output now runs in the shell's directory via its file handler ( #879 by @junyi-hou ), which matters for TRAMP users. The README gained guidance for agents hanging at "Loading…" over TRAMP ( #872 ), which turned out to be about and direct async processes rather than a bug. returns when the shell last saw a submitted prompt or an incoming notification ( #856 ), handy for idle tracking. writes the session's ACP traffic to a file and reveals it in , simplifying the bug-filing process. #797 : Render ACP elicitation forms ( @OSadovy ) #844 : Limit long tool output lines ( @Gleek ) #845 : Fix docstring command binding specifiers ( @bcc32 ) #846 : Preserve when displaying asynchronously ( @bcc32 ) #847 : Add Junie ( @thanhvg ) #848 : Find bare path:line references in linear time ( @catern ) #850 : Add support for rendering browse-url-data with osc-8 sequences ( @gnufied ) #852 : Preserve pasted image previews in queued and steered prompts ( @Gleek ) #854 : Emit parse-able YAML frontmatter for transcripts ( @vermiculus ) #855 : Add tunable for default Codex config options ( @ianthetechie ) #862 : Mention agent-shell-gc package in the README ( @izeigerman ) #863 : Count distinct files in read, edit, and delete summaries ( @Gleek ) #876 : Move by blocks with beginning-of-defun and end-of-defun ( @OSadovy ) #878 : Add OpenClaw agent support ( @jtmoulia ) #879 : Run command output in shell directory via file handler ( @junyi-hou ) acp.el #32 : Bound how long a single drain holds the main thread ( @moyotar ) shell-maker #46 : Fix byte-compile warnings, runtime cl-lib, defvar ordering and lazy-org declarations ( @tothedarktowercame ) shell-maker #48 : Quote mode-map before splicing into eval'd define-derived-mode ( @necaris )

0 views
Farid Zakaria 1 weeks ago

Nix wrote half of my debugger

A few days ago I wrote about Rewind VM , a deterministic VM where every run of a Nix build is a pure function of its inputs, thread schedule included! I have been using it to find, reproduce and solve numerous race conditions in Nix builds, but it’s been making me feel a little crazy . What is this superpower, and why is nobody else using it? The tool has quickly grown a source panel, stack frames, bookmarks, a “Compare” tab, a lot more gdb support, thread lanes, which show who held the CPU at every step, and “Check from here”, which helps find the exact step where a race condition happens. I went into each new feature thinking it would be a big code-lift but I kept running into the same thing: the hard part of each feature was already done, and Nix had done it. A debugger for instance, needs the exact inputs of the program, its debug symbols, its sources, the sources of every library under it, and a way for someone else to get all of that on their machine. That’s exactly what a derivation is, and Nix makes that easy. A classic simple example of a race condition is two threads depositing into one bank account. Each deposit reads the balance, writes a line to the ledger, then stores the balance plus the deposit. If one teller runs between the other’s read and store, it writes a stale balance over the other’s deposits. 💥 A teller that runs between the other’s read and store writes a stale balance over the other’s deposits. 1 teller 1 balance teller 2 read 150 read 150 store 200 store 150 + 10 160 teller 1's last deposit is gone Rewind’s VM has one CPU, so its first run passes too. runs the build again under perturbed schedules, each asking the guest kernel to reschedule at different steps, and narrows the first failure down to one step: Running this check on my laptop took 11 seconds. The two runs are the same machine, exit for exit, until step 3237, where only the failing one gets a reschedule. The argument to is a derivation and can be a flake reference. The beauty of Nix is that it knows the inputs of a derivation, so that’s all we need to make sure the run is reproducible. The derivation’s inputs are the source, the compiler, the libraries, the kernel, and the VM’s configuration realises the derivation’s inputs, packs the closure into a read-only erofs image, and boots the VM on it. The run’s id is a hash of its inputs, the way a store path is, and prints the command that makes it again: When the build succeeds, the guest reports each output’s NAR hash, and checks it against the host’s copy and against every binary cache Nix substitutes from, by fetching only the : This helps us validate that the build within the VM is the same as the build on my laptop, and that the VM’s run is reproducible by Nix. Debugging code is much simpler when you are looking at the source. A new panel in the Rewind app or in the terminal shows the source of the program at the playhead, and the stack frames that called it. The debugger needs to know where the sources are, and Nix gives us that for free too. nixpkgs builds the packages with , and the debug info is cached on cache.nixos.org as a output. We can leverage debuginfod to fetch the debug info and the sources by build ID, so we can see the source of any binary in the VM, including the Linux kernel! 😲 Sometimes though, the source is not enough, and we need to see the state of the program. opens gdb on a fork of the run at the playhead, with every thread of the process. The debugger can set breakpoints, watchpoints, and inspect memory, registers and variables. At step 3249, just before teller 2 gets the CPU back, we can watch the variable and continue until it changes. Nothing gdb does changes the recording, and we can rewind to the same step and fork again, or fork at any other step, and gdb will see the same state. If gdb is not enough, opens a shell in the VM at a step with any package from nixpkgs on its . It is one more closure packed into one more image. Rewind makes it very easy to compare two runs. The Compare tab in the app, or in the terminal, shows the last shared events of both runs, and the first event that differs. The Compare tab puts both runs’ events side by side from just before they part, with what differs marked. Teller 2 starts from 150 in the failing run and from 200 in the passing one: Rewind’s VM has one CPU, so at any step exactly one thread is running. A race is a question of order: which thread ran when. The trace does not answer that directly. It records what threads did, a write, an open, a fork, an exit, a signal, but not who was running in the gaps between those events. Rewind can fill in the gaps without recording anything new. Every run replays exactly, so it can walk any stretch of a run one step at a time and, at each step, ask the guest kernel which thread is on the CPU. In the failing run, teller 1 (Thread 140) is interrupted at step 3238, halfway through a deposit: it has read the balance but not stored it. Teller 2 (Thread 141) gets the CPU for a single step, 3239, long enough to read the balance, and then teller 1 gets it back and finishes. 2 Finding one bad interleaving raises the next question: how likely is it? Is the passing run the normal case, or did it get lucky? normally answers that by building the whole derivation again under many schedules. With , it starts from a run you already have instead, at any step you pick_. It forks the run there once per schedule, each fork taking a different order of threads from that step on, and counts how many end differently. Everything before the step stays exactly as it was. We can apply this technique to the bank example, starting from the step where the two runs diverged, 3,221. When we run 16 schedules from there, all 16 lose money 3 : We can do the same thing from the Rewind App with “Check from here” in the context menu of the playhead. It forks the run at the playhead and runs each fork under a different schedule, counting how many end differently. bookmarks the playhead’s step with a note. Bookmarks are kept with the run, and they travel in its export, so the person I hand the run to opens it with my notes on the timeline: Each one is a derivation in the flake, with one bug and one fix: Most of the hard work of a debugger is already done by Nix. Rewind adds a little more, but the rest is already there: the inputs, the sources, the debug symbols, and a way for someone else to get all of that on their machine. When you start with something hermetic like a Nix derivation, you can get a debugger for free. On my 16-core laptop, lost money in 396 of 1,000 runs. Pinned to a single core with , it lost money in none of 1,000: one core alone rarely switches threads in the middle of a deposit, which is why Rewind perturbs the schedule.  ↩ It is a little hard to see since the event from Teller 2 is tucked underneath the Orange bar.  ↩ Schedule 0 is the run itself, which passed. Every other line is a fork, and is failing because the balance came up short.  ↩ read 150 read 150 store 200 store 150 + 10 160 teller 1's last deposit is gone Rewind’s VM has one CPU, so its first run passes too. runs the build again under perturbed schedules, each asking the guest kernel to reschedule at different steps, and narrows the first failure down to one step: Running this check on my laptop took 11 seconds. The two runs are the same machine, exit for exit, until step 3237, where only the failing one gets a reschedule. Free thing one: the inputs The argument to is a derivation and can be a flake reference. The beauty of Nix is that it knows the inputs of a derivation, so that’s all we need to make sure the run is reproducible. The derivation’s inputs are the source, the compiler, the libraries, the kernel, and the VM’s configuration realises the derivation’s inputs, packs the closure into a read-only erofs image, and boots the VM on it. The run’s id is a hash of its inputs, the way a store path is, and prints the command that makes it again: When the build succeeds, the guest reports each output’s NAR hash, and checks it against the host’s copy and against every binary cache Nix substitutes from, by fetching only the : This helps us validate that the build within the VM is the same as the build on my laptop, and that the VM’s run is reproducible by Nix. Free thing two: every symbol and every source Debugging code is much simpler when you are looking at the source. A new panel in the Rewind app or in the terminal shows the source of the program at the playhead, and the stack frames that called it. The debugger needs to know where the sources are, and Nix gives us that for free too. nixpkgs builds the packages with , and the debug info is cached on cache.nixos.org as a output. We can leverage debuginfod to fetch the debug info and the sources by build ID, so we can see the source of any binary in the VM, including the Linux kernel! 😲 gdb, on a fork of any step Sometimes though, the source is not enough, and we need to see the state of the program. opens gdb on a fork of the run at the playhead, with every thread of the process. The debugger can set breakpoints, watchpoints, and inspect memory, registers and variables. At step 3249, just before teller 2 gets the CPU back, we can watch the variable and continue until it changes. Nothing gdb does changes the recording, and we can rewind to the same step and fork again, or fork at any other step, and gdb will see the same state. If gdb is not enough, opens a shell in the VM at a step with any package from nixpkgs on its . It is one more closure packed into one more image. Compare two runs Rewind makes it very easy to compare two runs. The Compare tab in the app, or in the terminal, shows the last shared events of both runs, and the first event that differs. The Compare tab puts both runs’ events side by side from just before they part, with what differs marked. Teller 2 starts from 150 in the failing run and from 200 in the passing one: Who had the CPU Rewind’s VM has one CPU, so at any step exactly one thread is running. A race is a question of order: which thread ran when. The trace does not answer that directly. It records what threads did, a write, an open, a fork, an exit, a signal, but not who was running in the gaps between those events. Rewind can fill in the gaps without recording anything new. Every run replays exactly, so it can walk any stretch of a run one step at a time and, at each step, ask the guest kernel which thread is on the CPU. In the failing run, teller 1 (Thread 140) is interrupted at step 3238, halfway through a deposit: it has read the balance but not stored it. Teller 2 (Thread 141) gets the CPU for a single step, 3239, long enough to read the balance, and then teller 1 gets it back and finishes. 2 Check from here Finding one bad interleaving raises the next question: how likely is it? Is the passing run the normal case, or did it get lucky? normally answers that by building the whole derivation again under many schedules. With , it starts from a run you already have instead, at any step you pick_. It forks the run there once per schedule, each fork taking a different order of threads from that step on, and counts how many end differently. Everything before the step stays exactly as it was. We can apply this technique to the bank example, starting from the step where the two runs diverged, 3,221. When we run 16 schedules from there, all 16 lose money 3 : We can do the same thing from the Rewind App with “Check from here” in the context menu of the playhead. It forks the run at the playhead and runs each fork under a different schedule, counting how many end differently. Bookmarks bookmarks the playhead’s step with a note. Bookmarks are kept with the run, and they travel in its export, so the person I hand the run to opens it with my notes on the timeline: Other examples to try Each one is a derivation in the flake, with one bug and one fix: : the deadlock from the last post. : the lost update above. : a SIGCHLD that arrives between a flag check and , so the parent sleeps until ’s ten second timeout kills it. : one process rewrites a config file in place while another rereads it. On many cores it fails nearly every time; on one CPU only some schedules land the reader between the truncate and the last write. On my 16-core laptop, lost money in 396 of 1,000 runs. Pinned to a single core with , it lost money in none of 1,000: one core alone rarely switches threads in the middle of a deposit, which is why Rewind perturbs the schedule.  ↩ It is a little hard to see since the event from Teller 2 is tucked underneath the Orange bar.  ↩ Schedule 0 is the run itself, which passed. Every other line is a fork, and is failing because the balance came up short.  ↩

0 views
Farid Zakaria 1 weeks ago

Rewind VM: a flaky build you only catch once

If a test fails on CI and nobody can reproduce it, did it really fail? 🧘 Nix gives me a build that is a function of its inputs via the extensional model. The same derivation, produces the same store path, and if I am lucky, the same bytes. What Nix does not give me is the same run . 1 A test suite with a race in it may pass on my laptop, fail once on CI, and when I rebuild it to look, it passes again. If you have experienced bugs like this, you know that it can be frustrating. You are effectively at times trying to find the needle in the haystack . I wrote recently that the Nix sandbox is a hidden input to a derivation. The same is true of the thread schedule. The order the kernel happens to run your processes in decides whether some builds pass, and nothing within the derivation records it. These are the hidden inputs to a build that can make it flaky. Are we left to hoping that we will find the needle in the haystack? Or is there a way to make the run a function of its inputs too? I built Rewind VM to make the schedule an input too. It runs a Nix build, a test suite or any Linux command inside a KVM virtual machine whose every run is a function of its inputs. The same inputs give the same run, at the same steps, every time . You can replay a failure, scrub through it, read any file as it was at any point, and fork it under a different thread interleaving. ✨ Confused? Yes it sounds like magic. The best way to explain it is with short demo. Let’s investigate the dining philosophers problem. The problem is that the five philosophers sit at a round table with a fork between each of them. A philosopher needs both forks beside them to eat. A philosopher may pick up one fork at a time. 2 f0 f1 f2 f3 f4 P0 eats P1 eats P2 eats P3 eats P4 eats five philosophers, five forks P0 picks up f0 P0 picks up f1 and eats P0 puts both down P2 picks up f2 P2 picks up f3 and eats P2 puts both down everyone reaches for the left fork each waits on a neighbor's fork nobody can ever eat: deadlock The philosophers take turns, until all five pick up their left fork at once. If all five pick up their left fork before any of them reaches for a right one, every fork is taken and every philosopher waits on a neighbor who is also waiting. Deadlock . 3 On my laptop 14 of 100 rebuilds deadlocked. 💣 builds the same derivation the way the Nix sandbox would, inside the deterministic VM. Oh darn, it passed! That means it will pass forever right? Not quite. The VM is deterministic, but the guest kernel’s scheduler is not. It can reschedule threads at different points in the program, and that can change the outcome. To find the other interleavings, runs the build again under perturbed schedules , we effectively ask the guest kernel to reschedule at different steps which causes a different sequence of events. runs one VM per core by default and stops after the first batch of schedules where the exit code differs. Note A failing schedule on its own is not that super helpful. Schedule 1 which had deadlocked likely perturbs every step from the start of the build to the end, and most of those perturbations have nothing to do with the deadlock. To help with this, narrows it: it shrinks the window of steps the schedule may perturb, first pulling in the end and then the start, reruns the build for each candidate window, and keeps the smallest one that still deadlocks. For our deadlock problem, it might be easier to see the last few lines of the log and see that all five philosophers have picked up their left fork and are waiting for the right one. We can also inspect the events, which are the same as the log but with timestamps and thread IDs. What if I’m not familiar with the VM? Can we look around? Yes! drops you into a shell inside the VM at any step, in a process’s working directory, with the build’s environment, while everything else in the VM stays stopped. We can use to bring gdb into that shell, and gdb can attach to the stuck process. 4 All five philosophers are on line 27, waiting for their second fork. f0 f1 f2 f3 f4 P0 P1 P2 P3 P4 left fork first every wait is on a held fork: a ring , and each work on a “throwaway” fork of the run at a step, so nothing they do changes the recording. You can use to do a “real” fork: it branches a run at a step under another schedule. A recording does not have to stay on the machine that made it. packs a run into a single file, with the VM’s kernel, its input image and the keyframes, so another machine with the same CPU vendor can replay it. 5 You are no longer beholden to a random flake on CI. Run the tests under on a CI machine with KVM, upload the failing run’s as a build artifact, and you can reproduce the bug perfectly on your machine. How do we fix the deadlock? We number the forks and always pick up the lower numbered one first. The last Philosopher now reaches for fork 0 before fork 4, so the waits can never cause a deadlock. f0 f1 f2 f3 f4 P0 P1 P2 P3 eats P4 lower numbered fork first P4 holds nothing, so f4 stays free We can then run to test the fix. Before the fix, 9 of the same 64 schedules deadlocked. Rewind VM includes some tutorials with more examples of using to find and fix bugs if you want to explore further. The VM has a single vCPU on stock KVM, so guest code runs on the real CPU at close to native speed. 6 What breaks determinism in a normal VM is everything that reaches the guest from outside its instruction stream: timer interrupts, clocks, random numbers, I/O completions, and any other event Rewind removes each of those or replaces it with a value it controls. Keeping account of every event and the step it happened at, it can replay the same run. If you squint, a run is a derivation. Its inputs are the kernel, the initramfs, a root filesystem (a Nix closure packed into a read-only image), the command, a seed and a schedule. Change any of them and you get a different run. Change none of them and you get the same one. The command is open source under the MIT license. 7 There is also a desktop app that gives a friendlier view of a recorded run. You can drag the playhead, view the build log, the process tree and the files at any event. “Open shell” and “Attach gdb” open a terminal pane on a fork at the playhead. I pointed Rewind at some tools I use every day to see what we can find. Each of these is reported upstream with a fix. On NixOS there is a module: If you have a test that fails on CI once a week, I would like to hear whether Rewind catches it and helped you debug it. Don’t just add a and paper over your concurrecy failures anymore, replay them. 🔁 A run is the sequence of events that happen in a process to produce a result. A build is a run of a derivation.  ↩ The world is a metaphor for the thread scheduler. Each philosopher is a thread and each fork a mutex.  ↩ The program stops making progress and never exits, so the check phase runs it under , which kills it and exits with status 124.  ↩ There is actually native support for gdb in the VM already. You can also use to bring in any other tool you want to use.  ↩ Without , writes only the run’s trace, its events and output, and leaves out the kernel, the input image and the keyframes. The file is much smaller and enough to read the run with and . It can’t be replayed or forked, though, so , and need the full export.  ↩ GNU hello build from nixpkgs takes 11.7 s in the VM vs. 14.3 s without it on the same laptop.  ↩ The guest kernel patch is GPL-2.0 alongside Linux.  ↩ The NixOS module can do it automatically for you and without the setting Rewind falls back to a coarser clock.  ↩ f0 f1 f2 f3 f4 P0 eats P1 eats P2 eats P3 eats P4 eats five philosophers, five forks P0 picks up f0 P0 picks up f1 and eats P0 puts both down P2 picks up f2 P2 picks up f3 and eats P2 puts both down everyone reaches for the left fork each waits on a neighbor's fork nobody can ever eat: deadlock The philosophers take turns, until all five pick up their left fork at once. If all five pick up their left fork before any of them reaches for a right one, every fork is taken and every philosopher waits on a neighbor who is also waiting. Deadlock . 3 On my laptop 14 of 100 rebuilds deadlocked. 💣 builds the same derivation the way the Nix sandbox would, inside the deterministic VM. Oh darn, it passed! That means it will pass forever right? Not quite. The VM is deterministic, but the guest kernel’s scheduler is not. It can reschedule threads at different points in the program, and that can change the outcome. To find the other interleavings, runs the build again under perturbed schedules , we effectively ask the guest kernel to reschedule at different steps which causes a different sequence of events. runs one VM per core by default and stops after the first batch of schedules where the exit code differs. Note A failing schedule on its own is not that super helpful. Schedule 1 which had deadlocked likely perturbs every step from the start of the build to the end, and most of those perturbations have nothing to do with the deadlock. To help with this, narrows it: it shrinks the window of steps the schedule may perturb, first pulling in the end and then the start, reruns the build for each candidate window, and keeps the smallest one that still deadlocks. For our deadlock problem, it might be easier to see the last few lines of the log and see that all five philosophers have picked up their left fork and are waiting for the right one. We can also inspect the events, which are the same as the log but with timestamps and thread IDs. What if I’m not familiar with the VM? Can we look around? Yes! drops you into a shell inside the VM at any step, in a process’s working directory, with the build’s environment, while everything else in the VM stays stopped. We can use to bring gdb into that shell, and gdb can attach to the stuck process. 4 All five philosophers are on line 27, waiting for their second fork. f0 f1 f2 f3 f4 P0 P1 P2 P3 P4 left fork first every wait is on a held fork: a ring , and each work on a “throwaway” fork of the run at a step, so nothing they do changes the recording. You can use to do a “real” fork: it branches a run at a step under another schedule. A recording does not have to stay on the machine that made it. packs a run into a single file, with the VM’s kernel, its input image and the keyframes, so another machine with the same CPU vendor can replay it. 5 You are no longer beholden to a random flake on CI. Run the tests under on a CI machine with KVM, upload the failing run’s as a build artifact, and you can reproduce the bug perfectly on your machine. How do we fix the deadlock? We number the forks and always pick up the lower numbered one first. The last Philosopher now reaches for fork 0 before fork 4, so the waits can never cause a deadlock. f0 f1 f2 f3 f4 P0 P1 P2 P3 eats P4 lower numbered fork first P4 holds nothing, so f4 stays free We can then run to test the fix. Before the fix, 9 of the same 64 schedules deadlocked. Rewind VM includes some tutorials with more examples of using to find and fix bugs if you want to explore further. What is Rewind VM? The VM has a single vCPU on stock KVM, so guest code runs on the real CPU at close to native speed. 6 What breaks determinism in a normal VM is everything that reaches the guest from outside its instruction stream: timer interrupts, clocks, random numbers, I/O completions, and any other event Rewind removes each of those or replaces it with a value it controls. Keeping account of every event and the step it happened at, it can replay the same run. If you squint, a run is a derivation. Its inputs are the kernel, the initramfs, a root filesystem (a Nix closure packed into a read-only image), the command, a seed and a schedule. Change any of them and you get a different run. Change none of them and you get the same one. The command is open source under the MIT license. 7 There is also a desktop app that gives a friendlier view of a recorded run. You can drag the playhead, view the build log, the process tree and the files at any event. “Open shell” and “Attach gdb” open a terminal pane on a fork at the playhead. Footguns One vCPU. Threads interleave but never run in parallel, so races that need two cores at once are out of reach. No preemption between system calls. A thread spinning on a flag without yielding stalls the VM. AMD needs once per boot for the exact clock. 8 A run replays only on the CPU vendor it was made on , AMD from Zen 2 on. No network besides loopback, and x86_64 Linux hosts with KVM only. Nix : dies of when exits between two writes. It never failed in 20,000 runs on my laptop and failed on the first run in Rewind. #16546 , fixed by #16547 ( case study ). Nix : several processes creating a new store at once fail with “database is busy”, as seen on Hydra. #15987 , fixed by #16554 . nixd : formatter output over 64 KiB hangs the language server. #899 , fixed by #900 . jujutsu : three tests fail about half the time on tmpfs, because operations ending in the same millisecond are ordered by a random id. #10306 , fixed by #10307 . A run is the sequence of events that happen in a process to produce a result. A build is a run of a derivation.  ↩ The world is a metaphor for the thread scheduler. Each philosopher is a thread and each fork a mutex.  ↩ The program stops making progress and never exits, so the check phase runs it under , which kills it and exits with status 124.  ↩ There is actually native support for gdb in the VM already. You can also use to bring in any other tool you want to use.  ↩ Without , writes only the run’s trace, its events and output, and leaves out the kernel, the input image and the keyframes. The file is much smaller and enough to read the run with and . It can’t be replayed or forked, though, so , and need the full export.  ↩ GNU hello build from nixpkgs takes 11.7 s in the VM vs. 14.3 s without it on the same laptop.  ↩ The guest kernel patch is GPL-2.0 alongside Linux.  ↩ The NixOS module can do it automatically for you and without the setting Rewind falls back to a coarser clock.  ↩

0 views
Brain Baking 1 weeks ago

Favourites of September 2026

The calendar claims it’s October already but the temperatures are still in denial. I can’t remember ever experiencing the start of this month that warm. It’s getting scary! See what I did there? Because of October? Spooky month? Scary? No? Worth a shot. What happened in September? More letter writing! The snail mail project is going well. I keep track of the amount of outbound letters: the grand total for the last two months is 20 handwritten letters. If you’d like to chime in, please let me know so we can become pen pals! More than 50% of these offline conversations fizzle out after one or two letters so it’s only the die-hards that are willing to invest some time that get to see the best I’ve got. Previous month: August 2026 . Contrary to the summary of August that listed two board games, I have finally picked up video gaming again after a summer holiday hiatus—which in case of a lecturer is two months. Yup. Come join us! I also started (re)playing some older Castlevania games in anticipation of Belmont’s Curse . The Telwynium Book 1-2-3 is an episodic eighties Sierra-on-line inspired high fantasy game crafted by the same person that gifted us the The Drifter experience. They’re free on and will be bundled as one big adventure when the final episode releases somewhere in 2027. I enjoyed playing through them but there’s nothing especially remarkable here. Castlevania: Rondo of Blood does not need an introduction. When it comes to the release window and the platform it was released on, is the black sheep of the ‘Vania family. Luckily, it got a remaster on the PSP although I’m not a fan of those graphics. Review to follow soon. The venerable Jeremy Parish recently worked through Rondo as part of his Turbo Works Gaiden 01 . If you’re interested in the weird history of this game, I recommend you watch his video instead of reading my crappy review: Castlevania: Dracula’s Kiss , also known as Dracula X in the US, is a “port” (although not really) of Rondo of Blood for the SNES. It’s… not good. The “NES Hard (TM)” part here borders to the unfair, the level design is bland and the best parts of the original didn’t make it to Nintendo’s console. Castlevania: Bloodlines is a very good 2D action game that feels a tad different—perhaps mostly because you’re technically playing it on SEGA’s MegaDrive? It also released a year before Vampire’s Kiss here in Europe. The soundtrack took the obvious hit but is still surprisingly catchy. I started playing Eastward as a break from ‘Vanias. The pixel art and music is amazing! And so is the first chapter but then it suddenly changes pace. The endless blabbering that even the “skip dialogue” button can’t fully fast-forward is a bit painful. I’m 8 hours in at the end of chapter 4. I still want to know where this postapocalyptic strange tale is going; but wow this game has pacing issues… Related topics: / metapost / By Wouter Groeneveld on 2 October 2026.  Reply via email . Speaking of Rondo of Blood , I liked Indie Gamer Chick’s review and analysis of the game. This one’s from 2019 but I didn’t know about it yet: Piers Cawley ran a micro bakery on Emacs and PostgreSQL ! Lyteforce Games writes about Combolands , an indie game that he summarises as “ Balatro with buildings ”. I have yet to check it out. The Ruby/Rails world has a big problem: DHH. What to do when your favourite programming language has a fascist problem? I feel for every Rails fan. Please stop the ridiculous Omarchy investments ! The list of fashware keeps on growing and growing. The amount of people I know—including myself—truly being excited about software development keeps on shrinking and shrinking. See also Chad’s I am retiring from tech to live offline . Perhaps Chad wants to write me a letter. You know, using a thing called a pen that emits not HTTP return codes but ink? Nick also writes about the end of a craft . I think you can see this one coming. More AI drama in the KDE world as explained by Nate. Not a blog but a very cool online fountain pen catalogue by George Starcher. Notchoc rebooted their blog and started using Makko . It’s always interesting to see new small tools like these pop up. I don’t feel the need to change my setup just yet. Jason Murphy talks us through a pen nib swap: switching one architect grind for the other . Toni writes something bang-on: having kids changes everything . I really liked Paul Taylor’s morning routine checklist system that leveraged Alfred to the max. Did you know that freeing up memory in C/C++ by calling does not necessarily free memory? Daniel Lemire explains why . Lee Reamsnyder writes about friendship and thanksgiving aptly calling it friendsgiving memories. In case you need to quickly look up a Race for the Galaxy card: use the RFTGPics service . Tourist World for the win. I had to jump through a few more hoops than usual to get SDKMan to work with the Fish shell .

0 views
Jason Scheirer 2 weeks ago

A Revival of Sorts: Where I Wound up With my iPod Classic 7th Gen

I’ve been using my iPod classic as my sole music player all summer now. The replaced screen, battery, storage, and front shell are all steady. I’ve found myself on an alternate build of Rockbox called Rockpod ; the main thing I like about it is very superficial: it changes the theme colors based on the playing track’s album art. It also claims other features that probably matter like being flash-aware so it treats flash storage like flash and not like spinning metal. However, the firmware also chokes on MP3s that are not 44kbps. So I have to re-encode everything that isn’t that bitrate. So now I have a system that works: I’m not a huge fan of LLMs, but they can reduce toil in some cases. OpenCode was running a free trial of Ox Alpha one long weekend so I used the free tokens to help me shape my itch-scratch tool: . I use the script to see what files are missing from the iPod, re-encodes them as needed, and checks Musicbrainz for missing album art. It can also update the stock firmware’s itunesdb, which was a nice to have when the tokens were free but I don’t use much. In all, this piece of 2005 technology still feels good to use in 2026 because everything in 2026 sucks so badly . Reboot the iPod in stock firmware for disk use (it randomly trips on its feet and stops working when you copy lots of files) Treat the music library not as a synced copy of my core music library but a transformed copy – the Rhythmbox library is the point of truth and the iPod data is an adaptation of it that plays on the iPod.

0 views
xenodium 3 weeks ago

agent-shell 0.78 updates

Another month, another agent-shell update. If you missed the last post, have a look at the 0.73 update . As usual, this post showcases highlights, but please check out the full list of changes if you're after the nitty-gritty. agent-shell is a native Emacs mode to interact with AI agents powered by ACP ( Agent Client Protocol ). Two agents join the family, available as usual via , or explicitly as follows: Likely the most impactful feature in the release. It's highly discoverable and plenty useful, so I'm expecting a fair amount of uptake. A typical shell experience offers a prompt. Users type and submit their commands and wait for the command to finish before the shell prompt is offered again. Deriving from , was no different. That is, until now. As of v0.78, offers a writeable prompt at the end of the buffer at all times. Submit, and the prompt comes right back while the agent is busy handling the turn. Type and submit again and will automatically queue your request if necessary. While queueing itself isn't a new feature, the shell prompt queueing route offers a freebie in terms of cognitive load. Submit prompts as you used to (via binding), and let decide whether to handle now or queue for later. The persistent prompt is enabled by default via . If this isn't your cup of tea, it can be easily disabled with: The viewport's compose buffer is there either way, and submitting from it mid-turn routes through exactly the same logic. As of this release, can also steer turns (provided the agent supports the ACP extension). Steering enables you to course correct in-flight prompts without cancelling or waiting for the prompt processing to finish. As of today, I'm aware of and handling ACP steering, but please reach out if you know of others. Steering an in-flight turn via offers a similar experience to the existing . That is, prompting the user for text in the minibuffer. Having said that, we now have a new and shiny persistent prompt, and as we now know, the binding automatically queues if needed. From the same prompt you can now also steer by submitting via the binding. Huge thanks to @OSadovy , who took on the legwork for steering in #777 . With and respectively queueing and steering as needed, the default behaviour is configurable via (queues by default) while the (or ) route uses (steers by default). Both customizations accept a function, so swapping would offer steering and queueing with something like: Both apply wherever a prompt is submitted mid-turn, the shell prompt and the viewport's compose buffer alike. As emacsers, we want all sorts of customizations, so custom functions can be used too, if you'd like something a little different from what's offered. While we could already paste screenshots from the clipboard, we can now drag and drop files from external file managers onto either shell or viewport buffers. Images get a preview, anything else is attached as an link. Thank you @dustinfarris for #825 . While on topic, @dustinfarris fixed file mentions carrying whitespace in paths ( #824 ). After all this time, I had no idea the temporary thumbnail generated by macOS's screenshot utility is draggable, and so you can now drop it straight into your session. Thanks to @dustinfarris for the tip! If you'd like to keep an eye on token cost, headers can now show the session's cumulative cost, right after the context usage indicator. Currently off by default, so opt in with: Keep in mind cost is displayed for agents reporting cost via ACP. Thank you @mrcnski for #834 . folds lots away by default (tool calls, thinking, groups), requiring additional help if we want closer integration. Thanks to @mrcnski 's contribution in #832 , folded fragments now respect . Searching also folds back what it expanded once you're done, groups included. Also thanks to @Gleek for #827 : expanding a fragment no longer clobbers 's match data. Slash command completion is now offered more idiomatically across all three surfaces (shell prompt, viewport compose buffer, minibuffer): it only kicks in when whitespace alone precedes the . Thank you @izeigerman for #810 . File completion after is unchanged. We now have , which grabs the most recent output regardless of point location, so you can pull the latest response from anywhere in buffer. Links got a handful of fixes/improvements worth mentioning: on next/previous page now moves N interactions instead of one, and a negative prefix pages the other way. Moving forward past the newest interaction restores a parked compose snapshot, so does what pressing the key N times does, rather than stopping short of your draft. Thank you @liaowang11 for #813 . Following on from last month's table work, plain data rows now get a face of their own, and every table face inherits from one base face. If you'd like to restyle tables wholesale, you now have a single place to do it. Thank you @mrcnski for #822 . returns the current ACP session ID. On that note, there's also for when you just want it in your kill ring. was a bit narrow, so it's been replaced by , an alist of whatever OpenCode advertises under "Available config options" when starting a new shell: Options are applied in the order listed, and order matters. Thank you @nhojb for #739 . Four more joining the lot: With the new renderer offering a richer Markdown experience for some time now, the deprecated renderer is gone. If you peeked at the commit logs for the period, you'll see it's been another busy month. Since the last post, 153 commits shipped, 32 issues have been closed and 25 pull requests merged. As of this writing, the backlog sits at 16 open issues and 6 open PRs (versus 11 and 5 last time around). Zooming out a little, here's how the backlog has tracked since March: Side note: this chart was generated using the skill shared in my emacs-skills repo. From the graph, it's evident when I became a father , but you can also see I managed to bring things back down, hovering at a fairly stable level since. All of this requires daily attention 👉 hint hint 👈 These days (especially at the workplace), vendor-neutral tooling matters more than ever, and there are a couple of ways to help keep going. Some cost money, others just a click. All are appreciated ;) is built and maintained by me, an indie dev, while the tools it often competes with at the workplace have well-funded teams behind them. Time spent on is time away from other work that pays the bills, so if it's useful to you, please consider sponsoring the project. And if your employer benefits from your use, nudge them to chip in too, they can typically contribute at a scale individuals can't. GitHub stars help with exposure, attracting new users and potential sponsors. Starring agent-shell costs nothing and can potentially help bring in more funding, so if you don't mind a couple of clicks, the project can really use another GitHub star . Thank you to all contributors for these improvements! Liking ? Would like to see it evolve? Consider sponsoring the effort. Antigravity (Google), #795 by @tychoish . Start it with . Qoder CLI , #831 by @unship . Start it with . File references inside spans are now linked ( #781 , #782 , thanks @OSadovy ). Links inside tables are now actionable. Links are now more robust while streaming. Links to local directories now open in . The cursor-sensor hint is no longer misaligned on links. Session lists are now paginated under the hood, requesting available sessions until agents run out ( #809 by @KarimAziev ). If you'd rather cap your session list, takes a positive integer. Restarting shells now preserves window arrangement. agent-shell-org-config : Keep agent, project, and skill definitions in org-roam. agent-shell-permission-transient : A compact Transient interface for responding to queued permission requests. agent-shell-queue : A flexible prompt/shell queue for sessions. agent-shell-queue-transient : A Transient interface for the prompt queue in . #739 : Add opencode model variant customization ( @nhojb ) #772 : Ask about killing the shell only when that is what was asked for ( @OSadovy ) #777 : Steer prompts into the running turn instead of queueing them ( @OSadovy ) #794 : Mark restored prompts with ( @OSadovy ) #795 : Add ( @tychoish ) #796 : Add link ( @tychoish ) #801 : Strip copies in the substring filter, not via yank-handler ( @OSadovy ) #802 : Fix chat prompt indentation ( @nhojb ) #804 : Stop the live prompt flickering on every keystroke ( @alberti42 ) #808 : Fix viewport prompt font-lock face precedence ( @liaowang11 ) #809 : Handle ACP session/list pagination ( @KarimAziev ) #810 : Slash command completion at prompt start ( @izeigerman ) #812 : Display Pi terminal output in agent-shell ( @timfel ) #813 : Add prefix-arg page jumps to viewport paging ( @liaowang11 ) #821 : Check dependencies against the floors ( @alberti42 ) #822 : Give plain table data rows a face ( @mrcnski ) #824 : Quote file mentions whose path holds whitespace ( @dustinfarris ) #825 : Attach files dropped on a shell or viewport buffer ( @dustinfarris ) #827 : Preserve isearch match data when expanding fragments ( @Gleek ) #828 : Add to Related projects ( @lllShamanlll ) #830 : Fix provider icon downloads via LobeHub CDN ( @tjohnman ) #831 : Add Qoder CLI ACP backend ( @unship ) #832 : Respect ( @mrcnski ) #834 : Show session cost in the header ( @mrcnski ) #837 : Add link to README ( @mrcnski )

0 views

Data Broker Radaris Loses Domains in Privacy Fight

The consumer data broker Radaris.com has long had a reputation for ignoring requests to remove personal information from its vast empire of people-search services online. That reputation caught up with the company recently in a lawsuit alleging Radaris violated a New Jersey privacy law that provides for hefty fines against data brokers that publish personal information on state law enforcement officials. In the face of repeated stonewalling and prevarication by attorneys for Radaris, the judge in the case ordered that radaris.com and more than a dozen other data broker domains be transferred to the plaintiffs. The radaris.com website, prior to the domain transfer to Atlas. In February 2024, Radaris was sued by Atlas Data Privacy Corp , a company that has been pursuing data brokers alleged to be violating a New Jersey statute called Daniel’s Law . The statute allows state law enforcement officials, government personnel, judges and their families to have their information completely removed from commercial data brokers and people-search services, and provides for fines of $1,000 per violation against companies that ignore removal requests. Less than a month after Atlas sued Radaris, KrebsOnSecurity published a deep dive into the Radaris co-founders — Igor and Dmitry Lubarsky (also spelled Lybarsky) — Russian-born brothers living in Massachusetts who operate a dizzying array of people-search companies as well as a number of Russian language dating services and affiliate programs. Attorneys for the Lubarsky brothers threatened to sue for defamation if the story wasn’t removed and an apology issued. Their attorney asserted that our reporting was wildly inaccurate, and that the true owners of the company were Ukrainians living in Ukraine. The Lubarsky brothers Dmitry or “Dan” (left) and Gary/Igor. KrebsOnSecurity doubled down and showed how the Lubarsky brothers built and operated Radaris and other data broker companies using a fictitious CEO’s name . Our follow-up story noted that Radaris’s attorney — a lawyer with the Boston Law Group named Val Gurvits — admitted his clients had invented the CEO pseudonym “ Gary Norden ,” and that Radaris also had issued multiple press releases over the years that quoted the fake CEO while seeking money from potential investors. Attorneys for Radaris waited until the last minute to appear in court and contest what was all but certain to be a default judgment in favor of the plaintiffs, and then told the court that Atlas had failed to serve the real owners and operators of Radaris and several of its sister data broker companies. Atlas re-filed the lawsuit in June 2025, this time dramatically expanding the number of Radaris family data brokers accused of violating Daniel’s Law. Matt Adkisson , president and CEO of Atlas, said Radaris turned to a tried-and-true playbook: Delaying in court until the last possible minute, and playing shell games with Radaris’s true country of origin and the individuals listed as owners and operators of these sites. “We refer to this period as their island-hopping phase. Privacy policies changed constantly, and new entities kept appearing from places like the Marshall Islands, the British Virgin Islands, and Seychelles,” Adkisson told KrebsOnSecurity. “Behind the scenes, it felt like a shell game. Defense lawyers told the court that certain entities merely operated the domains and were the proper parties to sue. But by the time a judgment neared, those entities would be discarded and new entities would appear. Meanwhile, the lawyers claimed the other entities that actually owned the domains should not be held responsible.” Adkisson said when the defendants updated their terms of service to state that Radaris was suddenly managed by a company in the Marshall Islands, Atlas hired an investigator in that country and soon learned the brand new entity that Radaris claimed was managing the company didn’t even exist yet. Mr. Gurvits stepped forward as Radaris’s attorney in a class action lawsuit the company temporarily lost in 2017 because it never contested the claim in court. When the plaintiffs told the judge they couldn’t collect on the $7.5 million default judgment, the court ordered the domain registry Verisign to transfer the radaris.com domain name to the plaintiffs. Mr. Gurvits appealed that verdict, arguing the lawsuit hadn’t named the actual owners of the Radaris domain name — a Cyprus company called Bitseller Expert Limited  — and thus taking the domain away would be a violation of their due process rights. The judge in the 2017 case ruled in Radaris’ favor — halting the domain transfer — and told the plaintiffs they could refile their complaint. Soon after, the operator of Radaris changed from Bitseller to Andtop Company , an entity  formed  (PDF) in the  Marshall Islands in Oct. 2020. The plaintiffs never re-filed their lawsuit. A mind map of various entities tied to Radaris and the company’s co-founders. Click to enlarge. “That seemed to be their modus operandi,” said Raj Parikh , a partner at PEM Law in New Jersey who handles most of the Daniel’s Law litigation for Atlas. “In the past, they won by attrition. Plaintiffs’ attorneys tired of the procedural games and just gave up. That strategy worked for a decade, and it probably would have worked in this case too, since any financial recovery from foreign actors will be difficult. But we were acutely aware of the threat this website posed to law enforcement officers and other public officials in New Jersey, and decided early on to commit whatever time and resources were necessary to remove that threat.” On August 26, the judge in the New Jersey case found the defendants were given multiple chances to appear and defend the claims against them but had failed to do so. Mr. Gurvits declined to comment on the case, saying it had been assigned to another attorney, a Mr. Victor Worms . In response to questions, Mr. Worms asserted the New Jersey court transferred Radaris.com to Atlas as part of a default judgment against Radaris.com, which is not a legal entity. “We have made a motion to vacate that default judgment on the grounds that it is void since a non-entity has no legal capacity to sue or be sued,” Worms replied. “We also intend to pursue all appropriate appeals because we believe the transfer of Radaris.com amounts to a forfeiture in violation of various constitutional principles.” While radaris.com still comes up prominently in results when searching online for U.S. residents by name, the domain no longer sells detailed personal dossiers on millions of Americans. Its homepage now displays a notice from Atlas, as well as links to our previous reporting on Radaris. Atlas told KrebsOnSecurity that it has obtained more than 10,000 emails and documents in the course of litigation, and that those messages confirm our previous reporting on the owners and operators of Radaris and its myriad companies. Atlas said the emails clearly establish that the nominal legal vehicles — Radaris America, Inc. ; Bitseller Expert Limited ; Digital Orbit Corp ; Core Solutions Group Inc ; Lucky Solutions Inc ; Virtura Corp ; Veripages Inc. ; Nuform Solutions Inc. ; Growth Data Advisors Inc. ; Property Experts, Inc — are all administered by the same three or four people from the same mailboxes, share one bank or payment card set, and are all managed from one virtual office address. “The corpus establishes, with documentary evidence generated independently by banks, payment processors, hosting providers, registrars, software-as-a-service vendors and the operators’ own systems, that radaris.com and at least twenty-five other people-search websites are one operation run by a small Boston-area group whose administrative, financial and technical functions sit on the difive.com mail domain and its successors (centerex.com, scienteco.com, eprofit.com, realmo.com, pub360.com),” reads a summary shared by Atlas. Atlas said the emails show Radaris.com earns approximately $42,000 a month, while Veripages.com earns around $45,000 monthly via its partnership with the Lifetime Value Company , a marketing and advertising firm whose brands include PeopleLooker , PeopleSmart , NumberGuru , and Bumper , a car history site. According to Atlas, the emails also showed the Radaris family of websites earns as much as $25,000 each month from their partnership with Onerep , a company that claims to help people remove their information from people-search sites. In March 2024, KrebsOnSecurity revealed how the Belarusian founder of Onerep had launched and operated dozens of people-search sites over the years and was continuing to operate one of them (Nuwber), effectively spreading the disease and selling the cure. The domain radaris.com now redirects to this notice from Atlas about the court-ordered domain transfer. All told, the New Jersey court has so far transferred 14 domain names from the Radaris family of companies to Atlas. Radaris.com now redirects to a notice of the court-ordered domain transfer. The Radaris family of companies is still potentially facing fines of $1,000 per alleged violation of Daniel’s Law. For the time being, however, Daniel’s Law is facing a constitutional challenge from virtually all of the 150 other consumer data broker firms being sued by Atlas. The data broker industry responded by having at least 70 of the Atlas lawsuits moved to federal court, challenging the New Jersey statute as overly broad and a violation of the First Amendment. The U.S. Court of Appeals for the Third Circuit has not yet issued a decision on the constitutional challenge, but either way the case is widely expected to be appealed all the way to the U.S. Supreme Court. Meanwhile, at least 14 other states have now passed laws modeled after the New Jersey statute, with more states considering similar measures. However, West Virginia’s Daniel’s Law was ruled facially unconstitutional under the First Amendment by a federal district court in August 2025. Justin Sherman is a privacy expert and author of the forthcoming book “The Middlemen,” which examines how the data broker industry powers modern surveillance. Sherman said federal lawmakers have long faced intense lobbying by the technology industry against more restrictive U.S. data privacy laws, but that many powerful industries are now working against passing comprehensive data privacy legislation. “These days at the federal level, add in the intense amount of lobbying against these laws from social media companies, big tech, cryptocurrency firms, and now AI proponents in the mix who claim that limiting their data scraping is somehow going to collapse the whole U.S. economy under Chinese rule,” he said. Sherman said people-search companies will continue to thrive unless and until Congress enacts meaningful consumer privacy and data protection laws that are relevant to life in the 21st century. That’s because virtually all state privacy laws exempt records that might be considered “public” or “government” documents, including voting registries, property filings, marriage certificates, motor vehicle records, criminal records, court documents, death records, professional licenses, bankruptcy filings, and more. At least 25 states have passed or implemented laws requiring age verification for residents seeking to access adult content online, but there is no federal law that limits how the companies that are scanning everyone’s drivers license can use, share or keep the data provided. Had such restrictions been enshrined in law, we may have avoided the recent breach at IDScan.net , which exposed the drivers license information on more than 153 million Americans when the records were briefly turned into a point-and-click identity theft service on the dark web. “The average person can look at Daniel’s Law and have a perfectly normal reaction, which is that everyone should be covered, not just police and judges,” Sherman said. “But we don’t need more wake-up calls. We’ve had eight million wake-up calls already on the need for better privacy laws. The lack of comprehensive federal privacy law is not for a lack of knowledge, and anyone claiming otherwise is either not reading the news or kidding themselves.”

0 views
dfir.ch 4 weeks ago

Living Inside the Shell: zsh Modules on macOS

When investigating shell-based activity on macOS, it is tempting to focus on the usual suspects: , , , , and similar utilities. But itself provides considerably more functionality than simply executing commands. macOS uses as the default interactive shell, and zsh ships with a module system that can extend the shell with networking, file manipulation, extended-attribute access, and other functionality. Functionality commonly associated with separate utilities can instead be performed by builtins inside the already-running process. As a result, there may be no corresponding , , or process for an analyst to find. Detection gaps can arise when detection logic relies primarily on process execution and command-line telemetry.

0 views
Himanshu Anand 1 months ago

I Missed One TLB Shootdown and Somehow Ended Up Controlling a Page Table

I used a public Linux kernel bug to make one CPU keep using a memory mapping after another CPU had removed it. I then persuaded Linux to recycle the physical page behind that stale mapping as a page table. My unprivileged process could still write to the old page, while Linux now treated the same bytes as part of its memory map. That chain produced one real flag from Google’s kernelCTF service. This was a 1/n day exploit, not a new vulnerability: the bug, reproducer and fix were already public. My work was the long and occasionally silly trip from “stale read” to a complete exploit. (It took me a too mcuh time to write this blog in a way it is useful, soemthign what I would have prefered from other blogs, Feel free to let me know if it can be improved and written more reader/begginer friendly) Authorized lab only: I tested Google’s kernelCTF target and an isolated local QEMU virtual machine with networking disabled. Do not run kernel exploits on everyday computers, shared machines, production systems or anything you do not own and have permission to test. I had some free time (again), so I started writing a kernel exploit as a hobby project. This is apparently what passes for recreation once normal hobbies stop looking complicated enough. The previous kernel exploit I had developed was during a training class, It was useful and I learned a lot, but there is a small problem with learning kernel exploitation: if you do not practice afterward, the knowledge does not remain neatly arranged in your head and mine had become a box of unlabeled dataset. I remembered the broad ideas that there are pages, page tables, allocators, races and many ways to crash a virtual machine. The precise details had quietly left the building. This work is also not my day job I do not do offensive security or exploit development professionally and learning this area takes a lot of focused free time. Reading one function often means reading five structures, three commits and a mailing list thread before realizing the first function was not important. My first instinct was to re read all the theory until I felt ready (That is a comfortable trap) There is always one more memory management sections to understand and “ready” can remain three chapters away forever. So I decided to choose a real finish line instead. I wanted an exact kernel, a public bug, an objective success condition and nowhere to hide behind “the idea should work.” If the exploit did not cross the boundary and produce the expected result, it did not work. Google kernelCTF is a continuous Linux kernel exploitation challenge. Google boots an exact kernel build inside a virtual machine and runs the submitted exploit as user ID or UID, . UID 1000 is an ordinary user, not root. The protected flag is available to root in the initial guest environment, outside the challenge process’s NsJail sandbox. The job is to begin inside that restricted process and gain enough control of the guest Linux kernel to reach the flag. That boundary: QEMU runs the guest, but I did not escape QEMU. Google’s host was not a target. The outer jail was not a target The solid line in this diagram is the entire claim. My local lab reproduced the important guest details so I could reboot, add logs, attach a debugger and compare two kernels without wasting the official service’s time. A local root shell or synthetic flag is evidence that a chain is developing. Only Google’s endpoint can return an official kernelCTF flag. The flag could not be impressed by a crash, a screenshot or a paragraph explaining why the final stage probably worked. There are also two common labels worth clearing up. A zero-day is a vulnerability not publicly known to defenders when exploitation starts. A one-day uses a bug or patch that is already public. This project was firmly a 1/n day I was learning how to exploit a known defect. Each kernelCTF submission receives an ID. Mine became An earlier submission, , already occupied the version/base slot for , so mine was not the first submission for that base That changes reward and review possibilities, but it does not change whether returned a flag. Google can separately review a submission for a novel exploitation technique and for reliability. Public evidence shows one official capture for . It does not reveal which numbered run succeeded or a success percentage, so I will not manufacture either number. “One flag” is both less exciting and more accurate. I wanted a target with a public patch and a public reproducer. That gave me somewhere concrete to begin while still leaving the exploit engineering unsolved. The target I chose was tracked as CVE-2025-37964. The vulnerable build was , at exact commit . The fixed comparison was , containing stable commit . The public report and reproducer showed stale memory access caused by an ordering problem in x86 Translation Lookaside Buffer handling. The actual vulnerable component is the target’s . That sentence is important because the eventual exploit uses enough other kernel machinery to make blame wander around. The naming became stranger than the code. Stable 6.12 had an attempted backport related to upstream commit , but operations relevant to the protocol landed in the wrong order on that branch. The stable commit corrected that order. The current public CVE records do not map the stable-only correction cleanly. CVE-2025-40174 appears in later paperwork and discussion, but I am not relabeling the exploit as definitely targeting that CVE. The code relationship between and is clear. The administrative relationship between the CVE records remains unresolved. I started by reading the patch and immediately met , , , and PCID. This was the point where my box of training-class cables tipped onto the floor. The kernel documentation helped me rebuild the picture. The kernel is the trusted part of the operating system that schedules work, manages memory, talks to hardware and enforces access between programs. My exploit begins in user space , where normal applications run with limited rights. When a program needs the kernel to do something privileged, it uses a system call , usually shortened to syscall. Calling to request memory, to release it or opening a file all cross that user/kernel boundary through syscalls. I kept one imaginary program in mind while reading I Call it Maple, Maple asks Linux for one writable page and stores there. Maple receives a virtual address, perhaps . That address belongs to Maple’s process address space , the private-looking range of addresses it can use. Inside the target kernel, the whole address space is represented by , while regions within it are represented by . Threads that share an address space share the same . When Maple requested one mapped region, Linux recorded a Virtual Memory Area or VMA. A VMA says that one continuous address range exists and has particular properties: writable or read-only, file-backed or anonymous, and so on. The official process-address documentation describes the VMA as the metadata for the range. It is not the physical memory itself. That distinction initially felt fussy, then became central to the exploit. Linux can retain a VMA while removing the page-table entry for one page inside it. The region still exists as an idea, but the route to the physical bytes is temporarily gone. Linux divides virtual and physical memory into pages. On this target, the pages I cared about were 4 KiB. Maple’s virtual page is connected to a page in physical Random Access Memory , or RAM, through a hierarchy of page tables. The Linux page-table documentation explains the real multilevel walk; my working picture was simpler: The final Page Table Entry or PTE, stores the physical page number and permission bits It is one row in the official map. The CPU follows those rows when Maple reads or writes V. Following several page table levels on every memory access would be painfully slow, so each CPU core keeps recent answers in a fast local cache called the Translation Lookaside Buffer or TLB. The page table says where V officially goes. The TLB lets a core remember “V goes to P” without walking the map again. Modern x86 processors can cache translations from multiple address spaces. A Process-Context Identifier or PCID, tags entries so the CPU can tell which address space an answer belongs to. The exact target’s and track which memory map and TLB generation a CPU believes it has loaded. Now Maple runs on CPU0 and CPU1. CPU0 has cached . CPU1 removes Maple’s PTE because Maple asked Linux to discard that page. The page table no longer contains the route, but CPU0’s hardware cache does not read minds. Linux must make CPU0 forget. That coordinated invalidation is a TLB shootdown . Usually Linux identifies processors using the address space and sends the needed notification, often through an inter processor interrupt. When the protocol works, CPU0’s next access cannot use its old shortcut. It walks the current table and sees that the page is absent. The bug was a schedule where both ways of noticing the change were missed. I found it easiest to picture two people updating a sign while passing through a doorway. CPU0 was switching from a helper task into my reader task. CPU1 was removing the reader’s PTE. Linux maintained both a generation number saying how new the address space was and a CPU mask saying which cores were using it. Each core looked at a piece of state that was reasonable at that instant. Together, those observations created a hole. CPU1 missed CPU0 when selecting remote recipients because CPU0 had not yet published the new map. CPU0 missed the newer generation locally because it had already sampled the old value. The remembered translation survived between those two decisions. The race sits around and the /TLB-generation protocol in the exact . documents the generation invariant. The stable fix restores the ordering; its compiler is not a magic cross-CPU hardware fence. I could observe stale access on . The same phased test ran two million attempts on without reporting stale access. That is a useful vulnerable-versus-fixed comparison, not a proof about every schedule on every machine. The reproducer had answered the first question. CPU0 could still read P. But P still belonged to my own mapping, and an unusual route to my own bytes is not root. Then came the useful thought: the TLB remembers a physical destination, not the reason those bytes were allocated. If Linux releases P and later reuses P as a page table, CPU0’s stale route still ends at P. My process thinks it is writing an old data page. Linux thinks those same bytes describe mappings. That was the whole exploit in one diagram. Unfortunately, there were many ways to fall between the boxes. I began in the traditional way: two threads, two CPUs and optimism. The writer on CPU1 removed the mapping while the reader ran on CPU0. Millions of attempts later, optimism had produced a very stable failure. The public reproducer showed that the bug existed, but my exploit needed one particular context switch. CPU0 had to move from a helper’s address space into the reader’s address space during CPU1’s removal. Random wakeups did not reliably create that edge. I changed the scheduler shape instead of merely increasing the attempt count. The helper and reader were pinned to CPU0; the writer was pinned to CPU1. The reader slept and the helper woke it near the measured point where CPU1 would remove the PTE. The sleep/wake mechanism was a fast userspace mutex or futex . A futex lets a thread wait efficiently in the kernel until another thread wakes it I was not controlling the scheduler, I was arranging the room so the helper to reader switch became the obvious next move. Direct wakeups and an unphased helper each completed recorded campaigns of two million attempts without a stale result. Scheduler shaping produced the stale oracle The answer was not more lottery tickets, It was changing which lottery I was entering. Most attempts still had to fail and that created the next practical problem. Every try needed to remove a PTE, test for stale access and restore the page for another try. After a win, however, I needed to stop restoring it and eventually release the physical page. I chose one page from , a Linux interface that creates memory with special secret memory handling. The useful property here was not secrecy It’s lifecycle control. During retries, with could remove the page’s PTE while the underlying memory object stayed alive. A losing read could fault the page back in and prepare another attempt After a stale win, I could take a one time path toward releasing it. Linux calls the bookkeeping object for one or more physically contiguous pages a folio . My carrier was one page, so “secret folio” mostly meant “the kernel object still owning these 4 KiB.” The local in this security_flow checkout is an older safe milestone with an eight-page oracle pool. It does not reclaim the page or write a PTE. The immutable complete source contains the one-page chain. My first success check was embarrassingly trusting: put known bytes in the page, remove the mapping and celebrate if CPU0 reads the known bytes. There was another explanation, CPU0 might take a normal page fault. Linux could rebuild the PTE and return exactly the bytes I expected The output would look successful while the stale translation had never survived. So every attempt received a changing marker Then I checked , the kernel interface that reports properties of a process’s virtual pages. An oracle is just an observable test for hidden state mine combined the marker with the present bit. The sequence I accepted was strict: pagemap said present before removal, absent after removal, CPU0 returned the current attempt’s marker, then pagemap still said absent twice. The marker proved the bytes were current. The absent checks proved a normal fault had not quietly recreated the PTE. At this point I had the stale route I had wanted, I assumed releasing and reclaiming the page would be the easier part. This was incorrect in a way the allocator was happy to demonstrate for quite some time. I won the race, unmapped the page and allocated replacements. Nothing reused it I allocated more, Still nothing my first response to an allocator mystery was, naturally, more spray. It was the exploit-development version of pressing an elevator button harder. Eventually I stopped trying to persuade the allocator and traced the page’s lifetime. Tracefs showed an invisible temporary reference in CPU0’s , a deferred batch used for locked-memory accounting. Linux still had one hand on the folio, so it could not recycle the physical page regardless of how much memory I sprayed around it. The hidden reference came from refaulting the locked secret memory VMA during retries. This was where the earlier VMA distinction stopped being vocabulary and became the answer: the mapping’s region remained valid and faulting inside it could put the folio into deferred accounting. On this exact target, a page-aligned beyond the end of reached and emptied CPU0’s deferred batch. I performed that drain before the race. A successful stale read then avoided another refault, so it did not add the hidden reference back. The allocator had not been random or stubborn. My ownership model had been wrong. Draining the reference made release possible, but release usually comes with a dangerous side effect. removes a VMA and normally participates in invalidating translations. A fresh flush would erase the stale TLB entry I had spent all this effort preserving. I needed the final unmap to complete the ownership paperwork without finding anything left to clear. The race had already removed the leaf PTE. I kept neighboring mappings alive in the same Page Middle Directory or PMD, region, then unmapped only the one page secret VMA. On this target, one PTE page contains 512 entries and covers a 2 MiB PMD region. The neighbors kept the surrounding page-table structure alive. With no leaf entry left and no enclosing table becoming empty, the final had no useful translation left to invalidate. CPU0 could then watch the physical page being cleared through its stale route. That runtime observation became the gate before reclaim. This was fragile. A context switch, unrelated mapping change, pending flush or PCID rollover could still destroy the stale entry. The exploit checked that the route survived rather than assuming the diagram had authority over the machine. Now I could release P while CPU0 retained . The next question was how to make Linux reuse exactly P as a PTE page, then identify which PTE page it had become. I reserved a sparse 1 GiB virtual arena. “Sparse” means the address range was large without immediately consuming 1 GiB of physical RAM. I created its upper page-table levels first. Later, touching one address in each new 2 MiB region mainly forced Linux to allocate PTE pages. This is allocator grooming : arranging allocations and frees to influence what receives a reusable chunk. A Page Frame Number , or PFN, identifies a physical RAM page. I wanted the old secret-page PFN to return as the PFN of one arena PTE page. PTE-shaped bytes were not enough. Random values become persuasive after a long debugging session. I needed each candidate table to identify its owner. I encoded the arena region number with pairs of PTE slots. In each pair, one populated slot represented a zero bit and the other represented a one bit. Start and commit anchors showed that construction had completed. Slots that should be empty also had to remain empty. CPU0 took two full 4 KiB snapshots through the stale alias and required them to match. The decoder checked the anchors, pair code, common zero-page frame, permissions, physical range, and required empty slots. Only then did I accept both “this is a PTE page” and “this is the PTE page for arena region N.” One PTE page maps 2 MiB, so the 1 GiB arena offered 512 candidates. On this target the exploit retained 256 conditioner tables and resumed candidate allocation at region 257. Those numbers are measured behavior for this build, not laws of the Linux allocator. Recognizing a page table was exciting, but the bytes could still have been a convincing coincidence. I wanted a harmless proof before using a system wide target. The exploit therefore has a separate mode. It allocates a canary page containing known data and reads that page’s legitimate PTE through the reclaimed stale window. Then it copies the entry into a destination PTE slot. The destination address had never been accessed, or walked , by a CPU. That detail mattered. If a CPU had already cached a translation for the destination, changing its PTE would require another invalidation and that invalidation could also destroy my stale window. An untouched slot had no old TLB answer to forget. The canary became readable through both virtual addresses. A write through one appeared through the other and the raw entry matched. The exploit then cleared the forged entry and checked cleanup. That was the page-table-control proof. It ran in mode. The official capture came from a separate mode. They were not two stages observed in one lucky official run, and I do not want a smooth story to blur that boundary. For the authorized challenge finish, I used a known technique involving . Linux consults this setting when a process crashes and generates a core dump. If the pattern begins with a pipe, the kernel launches the named helper with root privileges. This finish was prior art, not my new idea. The interesting work in my chain was reaching controlled PTEs reliably enough to use it. PTEs in my fingerprint pointed to Linux’s shared read-only zero page, which revealed the physical location of that known page. Symbols from the exact gave the fixed relationship between and the physical page containing . I installed a writable mapping for that target page in the never-walked destination slot. CPU1 wrote and read back a NUL-terminated helper pattern. A copy of the exploit was already held in memory-backed file descriptor 666. After a controlled crash, the root helper used the supplied process information to recover the required descriptors and read only the challenge flag. Before triggering it, the exploit cleared the forged PTE. The helper later made a best-effort reset of to . It did not save and restore the previous value, and a reset failure was silent. “Best effort” is the honest description. The exploit validates physical-address width, page alignment, guest RAM bounds, PTE flags, kernel physical alignment, and two equivalent symbol calculations before writing. On this target identifies the crashing process in the initial process namespace and supplies a process file descriptor. The helper uses to recover descriptors. Locally, the chain now had a harmless proof mode and a separate flag mode. I packaged the official archive and expected the race to be the unreliable part. The archive refused to race at all. My first official archive checked for an invariant Time Stamp Counter or TSC. The TSC is a fast processor clock and I used it to coordinate the two CPUs. The code required the processor feature bit promising that the counter behaved consistently. Google’s CPU profile hid that feature bit. The exploit exited before making one attempt. The check had confused a label with the property I needed. The two virtual CPUs’ counters could still be usable together even when that advertised bit was absent. I replaced the feature-bit assumption with measurement. The corrected code exchanged timestamps between CPUs 32 times, used the minimum round trip to estimate offset, bounded the result, and required at least 75 percent of samples to agree within a latency-based tolerance. A preserved local run measured an offset of cycles while reporting . The corrected archive went on to produce the one official flag capture. That proves one official capture. It does not reveal a run ordinal or establish a success rate. The stale-only local milestone still requires invariant, constant, nonstop TSC; the immutable submitted exploit contains the measured cross-CPU version. After all those detours, this is the end to end path without introducing anything new. Normal exploit logs told me what my program believed: markers, pagemap state, decoded arena region and which checks passed. They were cheap enough for repeated race attempts, but they could not explain an invisible kernel reference. The GNU Debugger or GDB, let me stop a virtual CPU inside functions such as and inspect registers and control flow. It was excellent for understanding a path and terrible for preserving race timing. A breakpoint can make the execution you wanted to observe disappear simply by observing it. Tracefs is Linux’s event tracing interface. It records selected kernel events while the system continues running. Allocation, free and drain traces answered “who still owns this page?” more directly than another breakpoint could. The two kernel files in the lab also have annoyingly similar jobs and very different forms. is the compressed bootable kernel image QEMU loads. is the uncompressed kernel executable with symbols and, when available, debug information. GDB needs the exact matching the booted ; a nearby build can produce beautifully named nonsense. Tracing and debugging both distort the experiment. GDB pauses CPUs. Tracefs changes timing and memory activity. I used them to understand mechanisms, then returned to fresh untraced boots for race evidence. The local checkout contains only the stale milestone. The complete , , and implementation and build integration are in the public PR branch. A runner means the virtual machine booted and output was captured. The security result is on or on . Networking remains disabled. Start a debug copy of the matching QEMU command with . The first option opens a local debugger port; the second pauses the guest before execution. These breakpoints are for learning the path. Remove them and use fresh boots for timing observations. Check that optional event paths exist before enabling them. This privileged diagnostic run is not the UID-1000 exploit proof, and tracing can change both timing and allocation behavior. Do not fabricate output. Do not publish the signed flag or the sensitive root screenshot. The public reproducer proved stale access but not the page lifecycle I needed. Random wakeups lost millions of attempts, which forced me to shape the helper to reader transition instead of running longer. My first marker could not separate a stale hit from a normal fault; pagemap made that claim testable. More spray could not beat ; tracefs found the hidden reference. Finally, the first official archive trusted a missing TSC feature bit and never raced; measuring the clocks fixed it. None was cinematic Together they were most of the work. The vulnerability was public, Stale-TLB exploitation after physical-page reuse was public. Generic freed-page-to-PTE conversion, zero-page anchoring, and the helper finish all had prior art. Project Zero’s stale-TLB work and page-use-after-free to PTE work are useful background. My narrower contribution was fitting a retryable one-page secret memory carrier, changing marker plus pagemap oracle, deferred-reference drain, empty final unmap, conditioned PTE allocation, pair-coded owner fingerprint, separate harmless canary proof and never-walked destination into one end-to-end chain for this exact target. I searched Google’s public kernelCTF corpus at commit from 2026-08-06. I found no match there for , , , this CVE, or the pair-coded protocol. That supports a novelty case; it cannot search private work or every unindexed experiment, and Google decides the category. The complete submission is public in Google security-research PR #418 . The PR is open and unmerged. Its immutable head is . Pinned links are available for the full exploit source , Makefile and kernelXDK integration , and technical documentation . The repository review source is 3,226 lines. The inside the submitted archive is 3,179 lines because the review copy includes the kernelXDK symbol-wrapper route. Both counts are correct for different copies. The successful archive was named in my private working tree and in the public submission layout. Its SHA-256 is: It contains exactly , , and . The earlier archive that stopped at the TSC check has different bytes and a different hash. PR #418’s checks and discussion expose the CVE mapping dispute. The technical stable correction is ; the public CNA and spreadsheet mapping remains unresolved, with CVE-2025-40174 complicating the record. Public code does not mean the PR has been reviewed, merged or that the metadata has been adjudicated. has public evidence for one official flag. already occupied the version/base slot and that fact alone does not reveal which vulnerability used. The remaining review question was the discretionary novel-technique category. A working exploit, a first slot and a reward are three different facts. The finished chain can be compressed into one sentence: one CPU kept an obsolete route, Linux recycled the destination as a page table and my ordinary process edited the map. Building it felt nothing like one sentence. I had to relearn memory-management basics, make the scheduler produce one edge, distinguish a stale hit from a fault, find a reference I could not see, make an unmap boring, label hundreds of page tables, preserve one untouched destination and finally replace a confident clock check with an actual clock measurement. The flag was a clean finish line, but the useful part was being forced to answer each question with evidence. Rereading theory would have made me feel less rusty. Watching a supposedly free page refuse to move taught me why it was not free. The honest ending is still the scoreboard: captured one official flag; had the base slot; PR #418 is public and unmerged; the CVE metadata is unresolved. No new bug, no invented reliability number, no mysterious years of kernel wisdom. Just some free time, a lot of rebooting, and one CPU that forgot to forget. If you are still reading this, you are awesome. Thanks for sticking with me! The exact two-CPU guest starts my exploit as UID 1000 inside the challenge jail. The exploit reserves a sparse 1 GiB arena and prepares page-table candidates that can later identify themselves with pair coded region numbers. It creates one page, writes a changing marker, and drains CPU0’s deferred locked memory batch so no hidden reference keeps the folio alive. A helper and reader share CPU0 while the writer runs on CPU1. Futex wakeups shape the helper-to-reader switch around the writer’s operation. In the winning interleaving, CPU1 removes the PTE but does not include CPU0 in the shootdown. CPU0 has already read the old generation and skips its local flush. CPU0 reads the current marker through its stale TLB entry. Pagemap says the PTE remains absent before and after the read, ruling out a normal fault. The exploit unmaps the already-empty one-page VMA while keeping neighboring mappings alive. The physical page is released without an effective invalidation, and CPU0 verifies that its stale route survived. Touching conditioned arena regions makes Linux allocate PTE pages. The old secret page is recycled as one of them. Two identical snapshots plus anchors, zero-page entries, permissions, empty slots, and the pair code identify the owning arena region. In a separate run, the exploit copies an owned canary PTE into a never-walked destination and proves both aliases reach the same page. In mode, the exploit instead maps the physical page containing , writes and verifies the helper pattern, clears the forged entry, and triggers the helper. The helper reads the protected flag and attempts a best-effort reset. Google’s service records one successful flag for . Exact release and UID 1000 in QEMU. GDB stopped in with matching symbols. Pagemap showing present, absent, current marker, still absent. Tracefs showing the relevant physical page’s release and allocation. Separate harmless output. Local stale marker and fixed comparison. Redacted ledger entry containing no secret data. Write a tiny C program that opens and reads a file. Run and watch the syscalls cross from user space into the kernel. Allocate one page with , write a value, and inspect . Connect the address range to a VMA before worrying about PTE bits. Draw the path from virtual page to PTE to physical RAM to TLB. The official page-table and process-address documents are good companions once the picture is clear. Write two harmless threads, pin them to CPUs, and use a futex to change their wakeup order. Observe timing; do not begin with a kernel bug. Boot a disposable QEMU guest with networking disabled. Learn which file is , which is , and where the guest/QEMU/host boundaries sit. Attach GDB to the guest and stop at a harmless syscall. Then enable one tracefs event and connect one trace line to one action in your program. Read a public patch and reproducer. Try to state the broken invariant in one sentence before building anything. Build evidence in small rungs: controlled observation, controlled read, controlled write, harmless canary, then the authorized target outcome. Count failed attempts and preserve raw logs. A success proves exploitability; it does not retroactively make every failed run disappear. Keep vulnerable-kernel work inside an isolated system you own or are explicitly authorized to test. Linux kernel documentation kernelCTF rules Linux CNA record for CVE-2025-37964 Public CVE discussion and reproducer Original upstream ordering fix Correct 6.12.y stable fix Exact vulnerable x86 TLB source Exact target memory-management structures Exact target x86 TLB state structures Linux page-table documentation Linux process-address and VMA documentation Linux pagemap documentation documentation Project Zero stale-TLB exploitation precedent Project Zero page-UAF-to-PTE precedent Google’s public kernelCTF exploit corpus Public submission PR #418 Immutable full exploit source Immutable exploit documentation

0 views
Krebs on Security 1 months ago

Microsoft Plugs Nearly 1,000 Security Holes

Microsoft Corp. today issued updates to plug at least 974 security holes in its Windows operating systems and other software, by far its biggest single patch batch ever. Microsoft says artificial intelligence is helping to speed the discovery of vulnerabilities, but security experts warn that many organizations already are struggling to prioritize the more human-intensive endeavor of testing and deploying so many fixes each month. Image: Shutterstock.com, Kirill Makarov. This month’s patch bundle obliterates the software giant’s previous record set in July , when it released updates for at least 570 security vulnerabilities. September’s Patch Tuesday brings this year’s total to more than 2,600, more than twice Microsoft’s previous record-setting patch year in 2020 (1,245) and with three more months to go. There are two “zero-day” flaws fixed this month that are being actively exploited: both CVE-2026-81963 and CVE-2026-85880 allow an attacker to elevate their privileges on Windows system. Fully 113 of the bugs addressed today earned Microsoft’s “critical” rating, meaning they could be abused by malware or miscreants to seize control over a vulnerable Windows machine with little or no help from the user. Among the more serious critical flaws this month is CVE-2026-69730 , a DNS weakness present in Windows Server 2012 onward and on Windows 10. Microsoft warns that an unauthenticated attacker could leverage this weakness simply by sending a specially crafted packet to an affected system, and that it is likely to be exploited. Also scary is CVE-2026-69829 , a critical, remote code execution flaw in the Windows Shell. This vulnerability has a CVSS base score of 9.8 (10 is the most severe), and can be exploited with low attack complexity, no privileges, and no user interaction. Microsoft’s summary of the security updates released today. Image: msrc.microsoft.com. Microsoft is hardly alone in shipping monster patch bundles lately. Many other large software companies, including Adobe, Cisco, Google, Mozilla and Oracle, all have recently credited AI-assisted research with increasing their patch cadence and volume (Google said today it is now going to ship security updates every two weeks). Tyler Reguly , associate director of security research and development at Fortra , said one core challenge with deploying Windows updates is that they need to be tested before being installed across an organization because not all third-party software works seamlessly in the face of changes to the underlying operating system. “It’s time to put our CISOs and CSOs on notice,” Reguly said. “How are you helping your teams through these difficult times? Do you have your teams deploy after hours and on weekends to avoid disruption to the business environment? Do you reward them for that effort? Time to dig into your budget and buy dinner for your teams that are working on Saturday to get patches rolled out before users return to work on Monday.” Satnam Narang is senior staff research engineer at Tenable . Narang said it’s important to recognize that while the number of vulnerabilities being patched by Microsoft is rising, the number of flaws that can and will affect most organizations remains quite low. “AI-assisted vulnerability discovery in 2026 is creating larger haystacks, but it isn’t finding more needles,” he said. “It’s critical that organizations understand which vulnerabilities actually apply to them, whether they pose a threat by being reachable and exploitable, and prioritize remediation based on this risk context.” Of course, regular Windows users don’t need to test patches before deploying them, but they still need to open Windows Update periodically or else assent to the program’s nag notices about pending updates. And at the rate these Windows patch releases are ballooning in size, it’s probably best not to let them pile up month after month. Enterprise Windows admins will want to keep an eye on askwoody.com for news of any updates that appear to be causing problems. As always, the SANS Internet Storm Center has a per-patch breakdown ordered by severity and urgency.

0 views
Alex White's Blog 1 months ago

From Omarchy to Pop_OS!

I fell for the hype for a bit and was playing around with Omarchy on my ThinkPad. While I did like some of the opioniated setup (the theming system is very cool), a number of things bothered me. First and foremost, the controversy surrounding DHH is hard to ignore. I wasn't particularly well versed in it, but after doing some research I didn't feel great about using the distro. The second issue was the amount of AI. I said in my original article that some of the AI integration was pretty cool, and I still think so. Debugging a system crash with a model or leveraging AI to understand complex config files is a great use. But the problem is that everything is AI. It's a vibe coded Arch config, and that doesn't instill a ton of confidence when it comes to a daily driver. All that to say, I'm now testing out Pop_OS! with the new Cosmic desktop. I've used Pop_OS! before, back when it was basically a GNOME shell extension. I even own a System76 laptop and Launch Heavy keyboard. I support what the company is doing, and their CEO as done actual good (like fighting against age verification in OSS). It's a product I can feel good about supporting. So far, I'm pretty impressed! Cosmic has come a long way since the last time I tried out a beta. The theming is goregous and very customizable, workspaces is done very well, and the tiling is better than what's on offer in Omarchy land. I love that things were crafted rather than slopped together, and being built on Rust is a strong foundation. There's still some things missing, but overall I look forward to daily driving Pop_OS! and Cosmic. Heck, I might even try revitalizing my interest in coding by creating a app or two using their UI kit!

0 views
Rob Zolkos 1 months ago

HEY, watch this!

37signals recently released the HEY CLI , and it is perfect for your AI agents. It gives them a command-line interface to your email and calendar. They can search your email, read a thread, draft a reply, move mail between boxes, and manage your calendar on your behalf. The CLI is also open source on GitHub , and it makes HEY scriptable. One command in particular turns it from a toolbox you call into something that listens: . That opens up useful possibilities that HEY itself does not offer today. That malleability is the point. The CLI gives you the pieces; what you do with them is limited mostly by your imagination. Imagine you use a monitoring service called Acme Monitor. It sends everything from the same address: . Most of those emails are hourly status updates: Those can arrive in the Imbox with everything else from Acme Monitor, but I do not want every routine update waiting for my attention. I want them marked as seen automatically. Every so often the status is less reassuring: That one should remain unseen in the Imbox. Something is wrong, and you want to see it. Once a month, the same address sends this: That belongs in Paper Trail with your other invoices and receipts. HEY lets you decide where email from a sender should go. That works well until one sender sends three very different kinds of email. I want routine updates marked as seen, degraded alerts left unseen in the Imbox, and invoices moved to Paper Trail. There is no rule in HEY for “mark routine messages from this sender as seen, leave degraded alerts unseen, and file invoices in Paper Trail.” But now we can make one. First, let into the Imbox as normal. Now watch the Imbox for new mail: When an email arrives, prints one JSON object. A shortened event looks like this: The posting’s is the email subject. It also carries the sender and the posting ID needed to move it or mark it as seen. This example uses to inspect the event. Create : Make it executable: Then connect it to the watch: Every new Imbox event is handed to the script as JSON on standard input. The script also receives useful environment variables such as , , and . Using prevents ordinary moves from triggering the rule again, while the first check limits it to newly added threads rather than replies. Known “all systems operational” status threads are marked as seen. A degraded, outage, or incident alert is left unseen in the Imbox. An invoice, receipt, or payment confirmation moves to Paper Trail. Anything unexpected also remains unseen rather than being hidden by an overly broad rule. That is an attention and filing rule HEY does not offer in its interface, built from three CLI commands and a few lines of shell. Keep running under , , or your preferred service manager if you want the rule to stay active. You do not need to write this script yourself. The HEY CLI includes a bundled agent skill for Claude Code and Codex, and today’s coding agents can create, test, and put a watcher like this in place for you. Give the agent the outcome you want: The generated script is still worth reviewing before you turn it loose on your inbox, but the skill means you do not need to learn every command or write the shell from scratch. The subject rule is only the start. The CLI gives scripts and agents access to the same email and calendar you work with in HEY. With the HEY CLI, you could: adds the missing trigger. Instead of running a command yourself, HEY can tell your program that something changed. With , you could: A watch event includes the sender, subject, summary, box, whether the posting includes attachments, and other details. When that is not enough, a script or agent can read the full thread with . The Acme Monitor rule is deterministic. If the sender and subject match, move the email. There is no reason to ask a language model to make that decision. Real inboxes are not always so tidy. A supplier may use inconsistent subjects, or a message may look routine until you read the body. An agent can wake when reports new mail, read the complete thread, then summarize it, suggest a destination, apply a label, or prepare a draft reply. I would still begin conservatively. Give the agent a narrow job and keep sending or destructive actions behind human approval until you trust the workflow. HEY’s live connection acts as a doorbell: it says a box or calendar changed, then the CLI reads an incremental feed to find out what happened. If that connection drops while remains running, it catches up before continuing. That makes it much safer than treating a transient WebSocket notification as the source of truth. The HEY CLI lets an agent manage your inbox, but you do not need an agent to benefit from it. Sometimes five lines of shell are the better tool. Start with the annoying rule you cannot express today. Let HEY tell your script when something changed, then make the decision as simple or as sophisticated as it needs to be. Build a morning briefing from today’s calendar, new mail, Reply Later, and Set Aside. Search years of email before a client call, then summarize the decisions and outstanding commitments. Read a long thread and prepare a draft reply for you to approve. Review first-time senders in The Screener, read what they sent, and recommend who to let through. Mark routine status reports as seen but leave an outage unseen and send a desktop notification. Move receipts into Paper Trail only when the message has an attachment or matches a supplier-specific rule. Wake an agent to summarize mail from an important customer and prepare the next action. Refresh an e-ink household agenda when a shared calendar changes.

0 views
Brain Baking 1 months ago

Favourites of August 2026

I am typing this from the teachers’ lounge: the preparations for the new academic year have officially begun, and with it, the summer holiday is gone. Time is a weird concept: on the one hand, two months of school holidays felt endless in various good and bad ways, and on the other hand, I can’t even remember what we did to pass the time. A couple of bike rides, visits to the family, swimming, or something like that? When people tell us “it’ll get better”—referring to the challenges of being a young parent—that’s not very helpful as “better” is firmly placed in a distant future. Yet without fully realising it, the months fly by. How? Previous month: July 2026 . Nothing? The kids have kept us busy and during the evenings I preferred grabbing a book instead of a screen. We did buy and play a few board games: Cozy Stickerville : a very simple game in which you gradually fill the board (the terrain) with stickers that starts out as a tiny village but hopefully evolves into a bustling town. It feels a bit like an offline Sim City game with some elements of Animal Crossing ? I do have a problem with the term “cozy” here: often, a card instructs you to do either A or B resulting in sticker X or Y. The problem is that you never know what the (dis)advantages are, resulting in a lot of stress to do the right thing for our city! I also wonder whether it can be replayed on the other side of the board. Pirates of Maracaibo : I’ve been (dead-)eyeing on a cool pirate-themed board game for a while and it came down to either this or Dead Reckoning which is a much heavier game with 4X vibes. In other words: something I won’t ever be able to bring to the table that often. Pirates is a hidden race game in which you speed along the Caribbean Islands to reach the port of Maracaibo (three times). Along the way you’ve got plenty of options to plunder, explore the island, and dig up and hide all kids of treasure. X marks the spot! Board games too are getting expensive. We paid for Pirates , a standard box with mostly cardboard and paper (cards) materials. It was in a brick & mortar store—one of the few ones left —so you might be able to get a slightly better deal online. I don’t have too many links to share this time as the holiday business didn’t allow for much casual surfing. I almost forgot how to even run a command in a shell. I do believe this may be the bottom of the rabbit hole for me for quite some time. I now have a wide variety of makes, models, nibs, and materials. More than enough to do pretty much anything I’d want. Enough to keep me writing for several years no matter what might happen. I hope I too reach that bottom soon. It’s getting a bit silly (and expensive). Okay so maybe I lied. Apparently, I did stock up on links. Related topics: / metapost / By Wouter Groeneveld on 4 September 2026.  Reply via email . Mary at From the Pen Cup stated Don’t Toss It! Mail It! ; a lovely way to upcycle cardboard boxes from various food supplies. Just turn them into postcards! I don’t remember where this came from but this YouTube short shows how to cross-write in a journal. I’ve never tried that before and it certainly makes re-reading sections challenging. A The Fountain Pen Network thread discloses how to use and take care of flex nibs. Some links include guides to Copperplate writing. Adam Le Doux open sourced two GBA game engines . April from Ink Flexibilities maintains a Best Modern Flex Fountain Pen Nibs guide . Conclusion: just get yourself a Pilot Custom Heritage 912 with the FA nib. Jim P. agrees with the above pen selection . I especially like his wording of “reaching the bottom of the rabbit hole”: On that same site: how to practice calligraphy with flex nibs . There’s a lot of controversy surrounding the use of agentic AI in open source projects. The Linux Kernel is no exception , unfortunately. Is that too turning into slop? What on earth are we doing? Renzo Thönen, who still develops Commodore 64 games, wrote about his complicated relationshiop with EGA colours . I liked Frank’s solution to the annoying LED problem in hotel rooms: put a black sticker on it! (in Dutch) Retro Junk goes back to the 90s with a look at typical board games from then . Damion Schubert wrote a series of posts to discuss his top 100 board games of all time . You know I’m a sucker for these kinds of lists. Kimberly from The Pen Addict reflects on her five years there . The post links to a lot of good other pen-related resources if you’re into that kind of thing. Ruben Schade asks: will there be another one? . My answer is always a resounding yes . Regret, here I come! I discovered Melanie Richard’s blog . She has a the cool idea of the subdomain where she puts photos of drawings or sewing/crochet projects. Cheryl Jones shows off her Pilot Capless . Wait, that thing has a stacked nib? That’s just crazy! Miss Booleana writes not just about the work-life balance but the child-work-life balance . (in German) If you ever wondered how the Internet Archive digitises books : now you know. Another blog recommendation: The Typewriter Revolution by Richard Polt. You can even write him a letter, but I reckon he expects them to be typed, not written. Michael Gisiger philosophises on the freedom not to give up on the old . (in German) Rachel’s experience report of the 2026 DC Pen show taught me wax seals can be multi-layered. Divya over at The Monadic Sheep introduces us to the wonderful world of Canves in GNU Emacs . The trend line in that popular “ my nine games that I grew up with ” side is depressing. Minecraft , Splatoon , and Apex Legends , is that all you think made you you ? Time to dive into the indie world, I guess. While trying to create my own list, half of the games I want to enter don’t even appear in the list. That might explain a thing or two. Ruben’s retro motherboard rack on shelves is a neat way to store a lot of old hardware builds. In a more recent post, he also explains how he powers them (with a picoPSU). To close of with video games, Syl posted her favourite game screenshots . I think I’ll steal that idea for the near future. Van Dieman’s puts out amazing shimmering ink . I’ve got to try one of those. For the hardcore pen nerds out there: Kyuseido Tokyo creates stacked nibs .

0 views

Verifying (simple) C in Isabelle/HOL with AutoCorres

This post details the first steps of verifying a C function in Isabelle/HOL using AutoCorres. It'll go over the basic setup for Isabelle and AutoCorres, what AutoCorres gives you, and how we can verify some basic properties of a function (here, the sum of a list.) I use the latest versions of Isabelle and AutoCorres available at time of writing (24/08/26). This is not official documentation for AutoCorres, nor may it be 100% correct in all places. All the proofs go through, but I do not work on AutoCorres, nor have I used it professionally; most of my experience is hobby verification. However, I have found resources on it are woefully lacking, so I wished to introduce some more. Much of this information has been gleaned from the official documentation (which can be found at the aforementioned,) and this course . When it runs, the slides/similar may be removed for some time - they exist on the internet archive also. This article assumes a little either Isabelle/HOL or general verification knowledge, but I try to explain wherever feasible. A bit of C knowledge is required as well, and so is a little knowledge about program verification - a little Hoare logic, and the like. I'll try to explain as much as I can without being excessively verbose, and much of it is very searchable. You can find Isabelle here . Install as is appropriate for your platform. You can decide whether to put in your path or not. First, pick a directory for your project. It may be feasible to use AutoCorres globally, but I wouldn't recommend it for versioning reasons. Then, AutoCorres can be found by scrolling down here . (It will probably take you here , whereupon you should scroll down to the latest AutoCorres release). You only need the AutoCorres download, as it bundles the C parser. Take that file, and extract it in your directory of choice. Then we need to build AutoCorres. From the directory where you unpacked it, run This'll take a second. Replace paths as appropriate. Note that is not the architecture you are on - it determines how the tool translates various C sizes into Isabelle. This will need to be the same architecture you launch with later. There are some others, and on a related note: It may not all make sense yet, but it may answer some questions. I recommend making a little shell script for this step ( perhaps.) You'll need to invoke Isabelle in a manner similar to the following: Remember that needs to be the same as earlier. Now that we have Isabelle running, we can use AutoCorres to generate Isabelle versions of C files. The manner in which this is performed is long, complex, and interesting - I recommend a rabbit hole evening - but in short, the C parser first translates it to a deep embedding in a language called Simpl, and then AutoCorres takes this Simpl representation and turns it into a monadic shallow embedding best it can. Here's what we'll be verifying today: The use of unsigned will be expanded on later. We'll put it in a C file named . At this point, your directory should look something like or similar. Begin a regular Isabelle theory, importing AutoCorres (and whatever else you want): We then need to let AutoCorres perform its magic. First, we "install" the C file with the C parser: You can use to see what this defines. Of particular interest is . Then, AutoCorres: The idea the C parser and AutoCorres use for verifying C is that it is reasonable to do a very direct translation of C to Simpl, and then a refinement to the monadic representation. The C parser is correct through inspection, and extensive testing. However, the translation of Simpl to the monadic form is verified - there exist Isabelle-checkable proofs that show that the monadic representation, however different it may be, behaves identically to the Simpl equivalent. This makes the monadic form a refinement of Simpl, and is why the things we prove about the monadic forms translate back down to C. This might also take a second. We choose to use . This makes unsigned integers perform modular arithmetic instead of using overflow checks; this has positives and negatives. Read the README for more. Again, you can use to see what this gives you. Of particular interest is . This is the monadic embedding of our function. We then need to enter the locale (think of it as an environment) defined by the C parser and AutoCorres, so your file should look something like: We do our work in this locale. If you haven't already, and . The former is the deep embedding produced by the C parser, and the latter is the result of AutoCorres's shallow embedding. These can be unfolded with and respectively. You can examine the types of things with ctrl-hover (cmd-hover on Macs.) Also examine . The type for the monadic state used by AutoCorres is called (You may see it displayed as in some places.) It's a record containing fields for each type of pointer used; ours only uses , so it only contains information for 32 bit words. We can examine the extract and update functions used in with and . Which monad AutoCorres chooses to embed a function into depends on the function, and can also be configured. This function is simple enough that it can be encoded with purely , but others include , (option with state,) and . Yes, this is a reasonable question to ask. What does it actually mean to verify this function? Generally, there are a few reasons to verify something: We'll look at all three. The not failing example will go into quite a lot of depth, whereas the correctness example will go into much less, only covering broad strokes. This is because the proofs are quite similar, and if you find the former excessively verbose, you might want to skip to the latter. Let's consider what we need for this function to not fail in C. The obvious constraint is that must be defined for all . Using , we can state this as a definition: Now we have our suitable precondition, let's set up our "doesn't fail" lemma. We expect that: We can state this using the combinator . The NF stands for , and it adds the additional condition that our program does not fail in some way during execution. Precisely what we want! It takes three arguments: The arguments are: Here, is used because of the choice of state monad AutoCorres makes. Note that our postcondition takes both the state and the return value . When we use with , will be our state . So, our lemma then becomes: If we have our list defined properly and some property Q, and we run our program, then it does not fail and Q is still true. If you're familiar with Hoare logic, you'll know we probably want to use some sort of weakest precondition reasoning. A weakest precondition is roughly "what is the smallest amount of information we need to know for this to be true", which allows us to simplify our proof obligations. Indeed, AutoCorres provides us with a family of tactics such as and . However, I find it nice to start these proofs by unfolding the function at hand, and applying or similar to get some simplification going. Then we can apply to apply relevant weakest precondition rules automatically. This should leave you with a state something like: I highly recommend using the Query tab of jedit throughout (or equivalents like .) will come in handy, and it's a nice fuzzy search. will do what's on the tin, and will find theorems that could apply. As we have a while-loop, a reasonable step is to add an invariant. An invariant is something that is invariant over the loop - it is always true, at the top of every loop cycle, and right after the loop finishes. This is how we conclude things about what a loop does. Indeed, we can see a theorem of use: Most of the time, the prefixes can be omitted. So first, we add an invariant, and then we can use to transform our into theorems we can work with. The invariants we care about right now are: We also care that this loop terminates, so let's add a suitable measure. is our invariant, and is the termination measure for the loop. Note the type conversion in . Then, again: This'll probably give you a few more normal looking goals. can come in handy again to chunk these down. This leaves me with: The first we talked about earlier - we can solve it by unfolding via , and basic reasoning. For the second, it seems obvious - why hasn't solved it? (If it were on s, it certainly would have.) Alas, it's on s, which as we have chosen to use modular arithmetic, are slightly less nice. It's hard to search for theorems involving as it's so overloaded, but luckily here finds . The final goal is more interesting. The initial intuition might be to use again, but this leaves us with nasty metavariables because of the chaining nature of the binds, and the fact that AutoCorres here is slightly too general. With a little searching we have the following: But we only really need to be identical to . We could instantiate manually each time, or we can make a little helper lemma (which I will do.) Above this lemma, we add Then we can apply twice (There are two binds.) We hence have: It's finally time to start unfolding the definition of so we can crunch down these last few goals. If we hit the first goal with: we're left with which takes out nicely. Tip: I fiddled with this for a bit manually, but if it looks fiddly and obvious, there's a good chance sledgehammer can do it. Finally, the last two are easy: We have a proof of non-failure! This is very exciting. The final proof is as follows: Then, what does it mean for our function to be correct? Well, a reasonable definition is that it produces the output we expect. We then need to figure out what "output we expect" means. We could have also defined the sum of a list as similar to the following recursive function: Note that we explicitly check for 0 to ensure the recursion terminates (We can't pattern match on 0/Suc, as we're working with s.). Unfortunately, this function's termination can't be proven automatically due to the use of a , which Isabelle isn't as good with as its own s. This is why we use a instead of , and we must do the termination proof ourselves: We also delete from the default simp set because it seems to cause some solvers to loop. We can set up our lemma like before, but this time, we use the parameter: We want the result to be equivalent to summing the entire list with our recursive function. We also don't bother to prove that it doesn't fail here. We can begin as before, omitting the step. We then need to annotate with a suitable invariant. A hint: We want the sum at the end to be correct, so a good invariant should capture correctness at every step , which gives us full correctness when the loop finishes. We have to manually a few times as we deleted it from the simp set, but otherwise the proof is very straightforward. Mine came out to be: We could take this one step further if we wanted, and define a bijection between and assuming our precondition, and then also show that our own spec is equivalent to the sum of that list. That'd give us even more confidence our function is correct. I'm personally pretty convinced, but you're welcome to try this yourself! We could then finally prove that if some property is true for the sum of a list, it's true for the result of our program. I won't detail this one; it should follow reasonably easily from the former two. So, what have we (hopefully) learnt? I hope this has been informative! (With less fixing of indentation for the web, sorry): A bit underwhelming for how long it took to explain, perhaps! To prove it never "fails" (what failing means is another question) To prove it produces some desired output To prove it holds some desired property is a , as perhaps expected. has been converted into a . is our state for the function, and carries information as mentioned about the heap. We use the perhaps confusing for pointer addition. You can write this in jedit with . We also need an explicit type conversion , as you can add negatives to a pointer, so the argument is an (this is not unsigned int; that's . here corresponds to Isabelle , which is signed.) If: The list is defined properly Some other property Q is true Then: Our function does return successfully, so That property is still true. A precondition function. A computation function. A postcondition function. (for termination) (for no-failure) (for simplified correctness) How to install and setup Isabelle and AutoCorres What it means to prove things about programs How to set up appropriate proofs How to prove them What tools we have available and how we can search for more

0 views
Himanshu Anand 1 months 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
iDiallo 1 months ago

Foot Guns for Sale

I don't think it's going to work out the way everybody thinks it will. The current narrative, at least the one pushed by the companies selling the shovels, is that AI will become centralized. Anthropic and OpenAI will offer safe, vetted AI. Developers will become mere prompt-engineers, submitting requests to these benevolent gatekeepers. They have tamed the dragon. We will benefit only if we become tenants to their API-driven fiefdoms, paying by the token for the privilege of renting intelligence. “They didn’t care that they’d seen it work in practice because they already knew it couldn’t work in theory” - Clay Shirky I complain about AI frequently on this blog, but not because I don’t think it is useful. I use it quite frequently on my day to day. But what I hate is hype and fake narratives. In fact, I believe that the opposite of their narrative will come true. Technology will continue to improve whether Moore’s Law becomes a relic of the past or not. It’s been called dead, yet CPUs and GPUs are becoming faster than ever. I do not believe for one second that developers with pre-LLM experience will end up on the losing side. And if technology continues to improve, then we won’t need OpenAI or Antropic in the future. We will be able to load large open models right into our powerful personal computers with more than acceptable inference speeds. Unless you believe that computers have reached their zeniths and it is all stagnation going forward. The gap between frontier models and open-source alternatives is already all about specialty, and it will continue to narrow. And when developers ubiquitously have access to local models, they will have access to everything. Right now, companies are hoping that developers will use their AI and remain within their ecosystems. They're building guardrails, imposing limits, and designing their models to serve corporate interests first. They see developers as customers, not as threats to their business model. But I’ve seen how easy it is to switch from one frontier model to the next. In fact, some developers in my team accidentally switched by selecting the “auto” mode on their IDE. They didn’t realise that every subsequent request was from a different model. It’s the developer who will end up benefiting from this far more than the corporations will. One developer recently spent his evenings using an AI agent to reverse engineer every peripheral within arm's reach. From those devices, I’ve come away with a full plaintext command shell inside my microphone, a webcam whose activity LED I can switch off while it records, and a key light that hands out memory writes to anyone on the WiFi. He documented the entire process, implemented his own firmware update utilities, and completely enumerated the functionality of devices that were never designed to be user-serviceable. AI gave him the ability to fully control hardware that he has paid for in his own terms. Another developer created OpenLogi , a local-first alternative to Logitech’s software used to remap your own mouse button. The manufacturer was forcing users to create an online account to have access to the hardware they had paid for. OpenLogi gives full control back to the user. This is what happens when developers have the tools and the motivation to bypass corporate control. And AI is about to make this kind of reverse engineering and alternative-building dramatically more accessible. At the speed of large language models, an activist could create a brand new rotating messaging platform every week to avoid the prying eyes of an oppressive government. Someone else can review his own model and add some self improving features. In fact, you could explore new AI paradigms. Most developers I know have a side project they don't have time to work on. With AI, they will have the ability to execute on those ideas. These days, I'm able to run Deepseek on my own $400 local machine at a much slower speed, but I'm not in a hurry. Give it a couple years, I could run something even more powerful. I don't need a project management tool where I have to pay monthly. My actual needs are much simpler. For example, I actually like Jira, despite how much I complain about it on this blog. But I don't need to have Jira for myself on my personal projects. I can build a tool that works solely for my needs. I can easily build applications in environments I am not too familiar with but that are more appropriate for the task. I can build prototypes in a couple hours now and throw them away if they don't match my initial expectation. I can do so much more. Dario Amodei and others are trying to scare us with the capability of AI. They sell the fear of superintelligent systems that will render human developers obsolete or dependent. But this is not what's going to happen. What's going to happen is we will not need Anthropic anymore. Yes, they will have the high-end hardware. But we don't need high-end hardware the same way most people did not need a professional camera. All they needed was a crappy camera with a good filter to post on Instagram. Flickr was superior to Instagram, but the superior technology lost to the one that was "good enough" and in everyone's hands. The funny thing in all this is that by making everything AI-dependent, by building their moats and their guardrails and their API toll booths, companies like OpenAI and Anthropic are selling foot guns. They are building the dark fibers of our era, the infrastructure that developers will eventually subvert, repurpose or simply bypass. Because eventually, we won’t ask for permission. We can just do whatever we want with tools freely available to anyone. We own our devices. We own our data. And soon, we'll own the intelligence on our own terms, without a subscription fee and without a corporate overlord. At the very least, we will get free GPUs .

0 views
Stone Tools 2 months ago

AppleWorks on the Apple II

Dig, if you will, a picture. It's November 1983 and throngs of people are engaged in literal fisticuffs, " dozens of people were injured, some even hospitalized ," to claim the last homely, pug-nosed Cabbage Patch Kid from the local Zayre's for Christmas shopping. In video footage, a store manager climbs a display, brandishing a baseball bat, trying to will order onto the chaos. The riots make the local, then national news, and eventually have a page on Wikipedia . It's a striking exclamation mark to punctuate 1983, with dark implications for the coming new year. Had we sunk so low? Then, at the start of December, a Christmas miracle. The hottest pop star in the world released one of the most influential music videos of all time on MTV. You know the one, where Michael turns into a werewolf, but then he's a zombie, and they do that dance, and the electronic beat "ur URRR adugga dugga dugga dugga dugga dug ur URRR adugga dugga dugga dugga dugga dug", and the Vincent Price laugh, and those crazy eyes at the end. The video for "Thriller" released simultaneous to the riots, and karmic balance was restored. Huzzah! Maybe 1984 wouldn't be so bad after all. 1984 wasn't just "not bad," it was radical. I tried to find "cutest adorable chubby-cheeked preschoolers Thriller dance" but this will do. Pop-culture nerds, in particular, would gorge themselves that year with Ghostbusters , The Terminator , Gremlins , Nightmare on Elm Street , Beverly Hills Cop , Revenge of the Nerds , Splash , Red Dawn , The NeverEnding Story , The Adventures of Buckaroo Banzai , Lynch's Dune , Karate Kid , Repo Man, This is Spinal Tap , Sixteen Candles , The Transformers toys and show, Teenage Mutant Ninja Turtles comic, Stop Making Sense concert film, Prince's Purple Rain , Do They Know It's Christmas? , Neuromancer , Elite , Karateka , King's Quest , the Soviet boycott of the Olympics , and the first MTV Video Music Awards. "Thriller" lost "Video of the Year" to "You Might Think", but did win "Best Choreography" and "Viewer's Choice." Oh, and Apple aired a commercial during Super Bowl XVIII. You know the one, with the skinheads in grey, and the oppressive fascism, and the woman in red shorts, and the sledgehammer, and that electronic siren "BEE oooh.... BEE oooh", and "1984 won't be like 1984." Apple introduced the Macintosh, a system Steve Jobs said was so easy to learn, "You can sit your grandmother in front of this computer and she’ll figure out how it works." That's an interesting way to describe a computer that internal documentation shows "we will not attempt to position the product in any way as a 'home' computer." Meanwhile, that same year, the Apple II series sold another one million units while Apple was taking its third stab at obsoleting those machines. Having shrugged off the Apple III, then the Lisa, Apple II fans would be forgiven for looking at the Macintosh's anemic launch software library (essentially just MacWrite and MacPaint ) and shrugging in agreement with Clara Peller's 1984 catch-phrase, "Where's the beef?" Tom Weishaar, writing for Open-Apple newsletter in July 1985 (a rough year for Apple), "For six years now Apple's management has been trying to build a computer better than the Apple II. In all these years, after all this money, only the Apple II has ever shown a profit. Buyers are looking for tools they can use. The Apple II has plenty of power to be useful." VisiCalc was early, clear proof that innovation isn't measured in megahertz. By 1984, developers had a lot of expertise in making the most of limited hardware. Look at the strides made in six years of software research on the Atari 2600, for example. Practice makes perfect. Rupert (later "Robert") Lissner heard the call. While the Macintosh was an interesting promise of a computing future that may or may not come to fruition, there were millions of Apple II owners with immediate needs. AppleWorks fulfilled those needs, growing a strong fanbase, spawning dedicated newsletters, lots of books, add-ons, and more. When Apple finally abandoned their 8-bit line, AppleWorks singlehandedly kept those machines productive for another decade . My only experience with AppleWorks was in the early OS X days, and that version had nothing to do with what we're looking at today. That and this only share a name; the Apple II version holds a legacy. Version 3 of AppleWorks was, according to a Compute! Magazine review, when the promise of "integration" finally paid off. Much of what AppleWorks 4 and 5 do, 3 can do with plug-ins. Version 3 is also when the program became Y2K compatible. It should be more than enough to develop a strong understanding of what made it such a favorite among the Apple II faithful. Thus far, I've only looked at one other "integrated" software package: Pipedream , on the Acorn Archimedes. It put forth the hypothesis that word processing, spreadsheets, and databases are not three separate applications, they are one and the same, chemically. While I was interested in its proposition, I was cool on its execution. It's an acquired taste. Where Pipedream blurred the line between applications, AppleWorks is very much three apps in one; a veritable turducken of productivity software. Maybe I'm a little unfair with that analogy. A turducken is a forced alliance of three fowl under, let's call it "extreme duress." AppleWorks, on the other hand, is a joyful alliance, with each individual app supporting the other two. I'd argue the word processing benefits the most, but to categorize this as merely three applications which happen to ship in one box, would miss the turkey for the duck. With 128KB of RAM and an entire suite of software loaded up, AppleWorks has 23KB remaining to do real work, as indicated by the memory counter in the bottom right of the screen. That's not a lot, but it's a bit dependent on how hardcore your work is. For example, my pre-edited text for this blog post hit 9,000 words and 43KB; the database merge (I'll talk about later) hit 24KB. Both are substantial projects, so if you're just dashing off letters, giving publishers a chance to get in the ground floor of your burgeoning career as the author of legally distinct James Bond-alike, James Epoxy, you'll be fine. Chronicling Agent Epoxy's actual adventures will likely require you to scope documents chapter-by-chapter. In the wake of the release of AppleWorks , a decent amount of third-party software was published to "add value" to this triple-threat. Using ad-hoc plug-in strategies (not originally part of the program proper), the already stuffed AppleWorks could become almost an operating system unto itself, hosting not just enhancements to the core functionality, but entirely new applications like screen savers and a MacPaint clone. Other companies took a less invasive approach to enhancing AppleWorks , offering pre-built documents ready for mail merge, recipes, phone directories, and the like. I saw a reference to a fantasy football management suite called FantasyWorks , but couldn't find any disk images for it. I suspect there's an entire world of unarchived AppleWorks enhancements sitting around on forgotten floppies. One product I did dig up, and which inspired my project for this investigation, promised to turn AppleWorks into HyperCard . When I learned of this program's existence, I had the reaction of a dog being asked, "Do you wanna go outside?" StoryWorks , written by Robert C. Moore and published by Teachers' Idea and Information Exchange, promises we can "create incredibly powerful 'hypertext' applications (stacks) (and) create 'knowledge base' stacks." That is a tall promise for an 8-bit machine, and it is one the provided example files don't quite live up to. What it can do is create "choose your own adventure" style stories. It takes a bit of boilerplate to adhere to StoryWorks's programming rules, and I will need to keep track of which "segments" (like HyperCard "cards") link to which. This sounds like perfect tasks for the database and spreadsheet to help manage. Then, I'll do some writing and formatting in the word processor, and next thing you know the potential and promise of AppleWorks's "integration" will be realized. Or so the theory goes. Navigating AppleWorks's file management can feel a little pre-Cambrian, not yet evolved into the complex, multicellular operating system you're reading this very blog within. Hardware-wise, we have the disk and the RAM. Files must be "on the desktop" to be used, which suggests some kind of "load from disk into RAM" step is necessary. That is precisely the case. First, we have to let AppleWorks know which disk is our "current disk," shown in the upper left corner of the "desktop" screen. It's akin to interfacing with a single folder in your file system, and the visual metaphor of tabbed folders is used to assist with navigating the Desktop. Once we have files on the desktop, fast switching via lets us bounce rapidly between documents making edits, while AppleWorks preserves our changes. Ready to quit? Not so fast, buddy. Sure AppleWorks appears to have preserved your work, but did you SAVE YOUR WORK ? Thus far, our changes are only committed to the Desktop and, as established when we loaded from disk into Desktop, the Desktop is not the disk. From the Desktop, we need to "Save Desktop files to disk" to really save onto disk. Earlier, I noted the "default folder" and here's where it will come into play. Saving work to disk will save everything to the default folder, regardless of its original position on disk. I wound up duplicating files into lots of fun, new locations on disk, rather than overwriting the originals, as I learned the program. But, at least my files are safe. They were saved carefully, after all. Two core tenets of AppleWorks 's approach to integration are its clipboard and keyboard shortcuts. Both work hard to bridge the natural divide between applications. The clipboard lets us move data between applications, and the keyboard shortcuts let us move muscle memory between applications. This mostly works, as when doing cut/copy/paste, text insertion/deletion, finding information, printing, and other relatively generic tools. The shared keyboard shortcuts fall apart, somewhat, in their overly ambitious attempt to enforce uniformity across all apps, even when they don't make intuitive sense. Let's look at the 'Zoom' command as a prime example. First, think about a command called 'Zoom' and envision what the result should be when invoked in a word processor context, then in a spreadsheet, and finally in a database. What did you picture in your mind's eye? What does it mean to 'Zoom'? Contrast that with to 'Find' text, which does exactly the same thing in all apps. Keyboard shortcuts aren't evenly distributed across modules, either. Printer options are available in the word processor and spreadsheet, but not the database. "Find" works across all modules, but "Replace" doesn't work in the spreadsheet. will split a window in a spreadsheet, ala VisiCalc's split window function. However, the word processor does not accept this command, though it would be a very reasonable expectation for that to work, as we saw in PaperClip on the Atari 8-bits. I certainly recognize that each program will necessarily have unique needs. However, I'm not convinced the mental fortitude required to remember how one command differs from application to application is any easier or harder than just learning a new keyboard command specific to each application. At this point, I've written thousands of words in AppleWorks and I can say confidently: the word processor is good. I have throttled the emulator to run at 1x speed, so I'm not completely head-in-the-sand about the reality of using the program on period equipment. Whatever horsepower I throw at it, it remains performant and a joy to type in. Of course, we have the usual list of modern gotchas, like the lack of international character input, and the printer-centric formatting options (which do not survive ASCII export). But the basic act of getting words on screen is smooth, along with minimal chrome which keeps us informed of the state of the writing environment. The chrome serves the document, ensuring the writer is never lost. The upper two lines show the ruler, what file we're working on, what mode we're in, and what hitting will do right now. In the above case, here in the Printer Options screen, will return me to the REVIEW/ADD/CHANGE screen. This clear explanation of what navigation buttons will do was something I appreciated about Bank Street Writer , and I'm happy to see a mature version of it in AppleWorks . The rule shows our tab stops and their respective styles, but doesn't show an indicator for the right margin. Editing tab stops is as easy as editing a line of text, thanks to fixed-width type. Just type a character for the tab styling you want at the place you want it, including centering and decimal alignment. The bottom displays a running line count and where the cursor currently sits. The bottom left has what seems like pretty useless information to me, "Type entry or use Apple commands." as a gentle reminder of how to use the program, I guess? The bottom right shows how to reach Help. That gives the UI four lines, with 20 devoted to writing. It is enough, though I do wish for a live word counter. AppleWorks 3 brings integrated spell check, with reviews of the time saying it is better than any of the third-party spell-checkers that preceded it. That's a strange note, because I find its usage convoluted. Invoked by , to "verify" the document, the "Options" it presents are obtuse and unintuitive, but all its asking is if we want to replace words one at a time or in a list, and do we want a post-verification summary of everything changed? If so, how do we want the summary presented, printed or on screen? The "Summary" is where we finally can see our word count, plus a list of all the weirdo words we used and how we corrected them individually, along with the occurrence count for each word. I'm not clear what I'm supposed to get out of knowing I used the word "turducken" 8 times. Maybe this is to help encourage the writer to mix things up? I refuse. As is typical of word processors of the day, a vast amount of the program's features are related to printer formatting and control. I do not own a printer, and "printing" to an ASCII file strips away most of the fun stuff, like bold and superscript. The spreadsheet portion is both amazing and boring. It's VisiCalc , with minor changes to meet the keyboard shortcuts (i.e. no slash menu command) and basic UI chrome of the suite. It does almost everything VisiCalc does, on a much larger worksheet and without quite as strict a memory barrier. If you have the RAM, even in this economy , you can eat VisiCalc's lunch. It also has many of the same frustrations as VisiCalc , including row-based vs. column-based calculation order, which can require a full document double-calculation to synchronize early formulas with later cell values. We're also stuck with the archaic (even by this time) "one column width for all cells," rather than per-column width adjustments. There are no graphing tools, and what options are available are so limited as to be effectively useless in a Lotus 1-2-3 world into which AppleWorks 3 was born; we'll need to rely on third party solutions to this problem. Copying complex formulas with relative cell references still requires manually selecting "relative" for every single reference in every single cell copied to a new position. Just a quick note to those cloning popular apps: you don't need to clone the terrible parts. Still, VisiCalc once turned the Apple II into a must-purchase investment and birthed an entirely new genre of business software. Now, that watershed event is collapsed into one subset of bullet points in a longer feature list on the back of the product packaging. The flat-file database module is uninspired, which is surprising to me because it is the module upon which this entire program was built. With a great word processor, and effectively a full clone of VisiCalc included, I expected similar depth from the database. Setting up fields and records, searching, sorting, and filtering, are all simple enough to accomplish. Its adherence to AppleWorks's common keyboard commands makes it pretty trivial to search for records, edit text, delete records, and print. Yet there is so much it doesn't do, I can't help but feel disappointed. To start, fields cannot be assigned value types, something even dBASE on CP/M could do years earlier. Everything is a string, without even the crudest form of data validation. AppleWorks 4 would gain the ability to at least set a field to be a number vs. text, though still no Booleans or field widths, for example. When setting up a contacts database, a common use case is to have a "notes" field to remember things like family members, follow-up discussion topics, and the like. Thanks to a limit of about 70 characters per field, no single field in AppleWorks can hold that much information. I guess the solution is to set up half a dozen "memo" fields, just in case? That kind of workaround feels quite silly to me. The remainder of the module is focused on generating "reports" and "layouts," the difference being "for printing" or "for screen." This has its place, perhaps to show a subset of data and focus on the important stuff, or to copy some data over to the word processor. Otherwise, there's just nothing to get excited about here. I can't even pretend to be excited for the purpose of writing a fun blog. Moving on! The early days of productivity took some time to figure out how best to handle object selection and manipulation. On 8-bit systems especially, modality ruled the day. Today we've settled on the paradigm, where we select an object to manipulate, then choose the manipulation. Modality-driven software was the opposite. First, choose an action, then choose the object to affect, i.e. . To move a paragraph, we first select , to indicate our intention to "move." The system switches to a text selection mode, allowing us to highlight the text we wish to move. Finally, we position the cursor at the new location for the text, hit , and the text is moved. Its backwards, relatively speaking, but easy enough to adjust to. Thanks to AppleWork s integration, we can copy/paste between any two Desktop documents. It's a little strange, because copy and paste are both considered "copy" operations, via . We copy to the clipboard from application A, then we copy from the clipboard into application B, using menu prompts along the way to specify the direction of the copy. Copying has its quirks, though. Copying between two documents of the same type behaves as expected. Across application types, copying into the word processor fares best, and most closely meets expectations. But we still encounter broken formatting, like how a line of text that spans multiple cells in the spreadsheet will break apart into discrete, tab-width sized chunks in the word processor. What we actually want is to do is "print" to the clipboard, as backward as that sounds. Depending on the module, different print options become available, to define what part of the data we want to print, and how to format it. Once done, printing to is an option. The end result is much cleaner data that is closer to WYSIWYG over the normal clipboard copy command. "Print to clipboard" is the only useful cross-application option, in my testing. As the connoisseur of modern computer software that you are, I know you know that however good something is, it could always be a little better. Most software requires us to beg and plead for the developer to make the improvements that will ease our mortal burdens. Some software embraces the notion of "extension," allowing itself to be a mere vessel for thoughts yet unthought, ideas yet unrealized. When you bought AppleWorks , it never occurred to you that you'd even want it to do outlining, until you saw ThinkTank and now you kinda wish you had something like that. AppleWorks is shockingly accommodating to our wishes. One of the core features of Lissner's engine is a crazy-efficient memory management assembly routine. This gives, what by all rights should be "not enough computer," the superpower of fast app switching. Especially with a ProDisk (hard drive) attached, AppleWorks can become essentially a graphical shell for the Apple II. A number of companies took advantage of this and created various add-on packages for AppleWorks . Beagle Bros, JEM Software, Pinpoint Publishing, and PBI Software collectively published over 100 extensions. They had a lot of competitive overlap, but provided add-ons like: This was all well and good, but there was no official plug-in architecture for AppleWorks ; it was every developer for themselves. Installation routines were effectively "patches" to the base application, and there was no guarantee that patch A wouldn't step on patch B's toes during the installation process. It put some burden on the end-user to detangle things like installation order, or even compatibility with other add-ons of interest. Beagle Bros wanted to solve that problem once and for all. TimeOut was their solution to a simple, universal plug-in architecture for AppleWorks . Developers targeting TimeOut could rely on it to do the down-and-dirty interfacing with AppleWorks's memory management routines, without having to worry about patching or memory collisions. For the end-user, adding new TimeOut modules to their system was as simple as copying a single file to the AppleWorks directory. This worked like a champ, and eventually led to Beagle Bros being the contractor for AppleWorks 3 . As the main app developer, they could steer its growth to align with their own vision, and so TimeOut became part of the foundation of AppleWorks proper. This then made it super simple to produce updates of significant value to the end-user, because those upgrades already existed in the form of TimeOut extensions. And so, AppleWorks 3 received its first built-in spell checker as a result. As time went on, the outliner, expanded Desktop, better clipboards, and the like all became built-in features to AppleWorks . The big one, from my perspective, is TimeOut UltraMacros. I have it installed as an extension here in AppleWorks 3 , and in AppleWorks 5 it became a built-in feature. With it, we get a kind of mini-programming language, which includes if/then/else statements, variables, loops, value comparisons, and more than enough to fill out a 110-page manual. Macros work across all AppleWorks apps, and since the language is identical across apps, if you know how to script one, you know how to script all of them. Still a few more years until the 6502 reaches its maximum potential. With the powerful combination of AppleWorks + UltraMacros + StoryWorks , we have a veritable turducken of creative utility. In later writeups espousing the benefits of the AppleWorks plug-in system, it was suggested that future application authors would be writing exclusively with AppleWorks as the hosting application in mind. I can see the appeal. Unfortunately, StoryWorks didn't get that memo, so the workflow between AppleWorks and StoryWorks isn't the seamless experience I would have liked. They are two completely separate programs; quit one to enter the other for each phase of the writing/debugging development cycle. Let's all take a moment to re-appreciate our multitasking operating systems. Now, I've never written a Choose Your Own Adventure (CYOA) before. In browsing the books of my youth, I can see how AppleWorks's integration could be useful in designing such a thing. I can keep multiple documents open simultaneously, and jump quickly between them with . Here's my plan. With the spreadsheet, I'll keep a list of page branches. This will sketch out the core content on each page, with page numbers showing where each choice leads. In the database, I'll set up a template for the "programming" information for each page, based on the spreadsheet sketch. The database's 70-character limit on fields means I can't type the entire story into the database, but I can rough out a plot point or two. Then, in the word processor, I'll merge the database into a template to save me from tedious, manual boilerplate formatting. Once merged, if I've done my job right, that should build a skeletal game I can dry run through StoryWorks . "It's so simple, I can't believe people struggle to write games," the author scoffed. OK, look, I was out of pocket and I take it all back. In the previous section I was a naive child, unaware of his own ignorance. That was "Then Me" and he was a fool. "Now Me" is wiser, less prone to shooting his mouth off about things he doesn't understand. Making games is hard, I can admit that now. My biggest stumbling block is conceptually very simple: I don't know how to write a CYOA. What seemed like a clear plan in my mind turns out to not be that in practice. I thought, "Start with a rough sketch, start filling in details, repeat until done." would work. So, fine, all this really means is that I'm not a game designer, a fact I actually already knew. Looking through websites about how to create such games, everyone seems to have a different take. Spreadsheets as an organizational tool comes up a lot, as does sketching out decision trees, for which the "Paint" add-on might work, but I have a better plan. For my first attempt, I simply don't have the wherewithal to create something this intricate. My goal is to push the software, not my brain, so I'm going to be a thief and steal someone else's idea. Or rather, I'm going to steal someone else's structure. The original Choose Your Own Adventure series, the "fourth best selling children's series of all time," was established by Edward Packard in 1976. Relaunched by Chooseco in 2010 , the classics have been reprinted in handy box sets, and even new adventures have been published. If you've ever wished for a Cthulhu-themed CYOA, aimed at readers 9 - 12, your very specific prayer has been answered. The original CYOA books are also available on the Internet Archive's "Open Library" program. My intention was to get "inspired by" those, but instead I'm going to swipe the basic decision tree from one, and write my own story. There is a spy adventure called "The Deadly Shadow," by Richard Brightfield, that I will lean on for the heavy lifting of providing the structure for my story about low-rent super agent, James Epoxy. Ah, let's just go ahead and call it a parody at this point. There's no point in kidding myself that I'm about to do anything original. In working through my failed attempts, I encountered some frustrating limitations of AppleWorks integration. In AppleWorks , we have an option to set numbered "markers" throughout a word processing document. These are invisible tags that can be jumped to, by number, for quick navigation to known locations. StoryWorks relies on these to delineate story "segments." Just as AppleWorks can jump to markers, so too does StoryWorks use the same principle to jump to segments. The difference is that StoryWorks will only display text up to the next segment marker, effectively splitting the text into something akin to HyperCard "cards." And so, like HyperCard , StoryWorks also calls a collection of cards a "stack." In setting up my word processing template, I added segment markers where appropriate. Merging in the test data went smoothly, the result of which was a new document written to disk as ASCII. Maybe you already smell the trouble? ASCII conversion stripped out all non-printable tags and markers, rendering it StoryWorks- incompatible. That means adding StoryWorks markers must be done post data merge. Luckily, I have UltraMacros installed, so complex, repetitive, menu-driven marker creation is a simple self-defined keystroke away. In fact, because macros are just "a list of keyboard commands performed in order" I should be able to semi-automate find-and-replace placeholder text of my own design with a real StoryWorks marker. If I run that a few times I can prep my document for StoryWorks ingestion lickety-split. Does anyone still use that term? Where did that phrase come from, anyway? One thing I've quickly learned is that debugging is tricky. StoryWorks will parse the AppleWorks document and list its errors, but in practice I found that an early error can cascade, generating pages of errors. All we can really do is fix the first one and test again, hoping the later ones will disappear. So the proper strategy is to test early, test often. Before there is anything resembling a cohesive narrative, we need to make sure the skeleton of the project, as merged from the database, compiles and works. For this purpose, it actually does help to have even truncated page descriptions populated from the database, just to get a yes/no understanding if navigation is working. First, here's the template I've come up with. and placeholders in the template align with field names in the database. During a merge, possible field names are presented in a list for insertion, making it impossible to accidentally mistype one. means "don't insert a blank line if this database entry is blank." will always insert something , even if there is no entry in a record. Here's what a representative record looks like. You can see the field names are referenced in the template, above. As I mentioned earlier, the merge gives us an ASCII text file. That file, before adding true markers via macros, looks like this; note the text I use as a placeholder for where the macro should place a real page marker. I see that the mail merge did not follow through on the promise of skipping blank entries. I also see that, despite showing me centered text on screen in my template, that was stripped from the document as well. I have quite a bit of cleanup to do to tighten up these layouts. This is becoming work. Next, I'll run the macro I created to automate marker insertion. Macros are action-for-action transcripts of the keyboard actions required to accomplish a task, including cursor repositioning or text cleanup to prepare for the next step. Simple macros can be built simply, because macros are defined in the language of the AppleWorks user . I cannot praise this approach to macro scripting enough, and I will bring it up every opportunity I can. The more I encounter it, the more I honestly feel something fundamental has been taken from users over time. It's one thing to allow someone to record their actions blindly, with a slightly patronizing "There, there, don't worry your sweet little head about how this all works." It's quite another thing to tell a user, "Hey, you know all those tools you've been learning? You can use those exact same skills in powerful new ways." It would be relatively simple then to teach someone how to wrap an existing macro in a loop or decision construct ( UltraMacros can do these things and more), building up a foundation of self-confidence. It must surely be far more accessible than whatever Google is proposing. I mean, speaking of Cthulhu! After running my macro to the end of the document (holding down the macro shortcut auto-repeated until finished), here's the final result. I have to to "zoom" into the document and reveal the hidden formatting codes, but so far so good. Everything appears to be ready for StoryWorks . Fingers crossed. Hot dang, got it on the first try! (as far as you know) Now that the basic structure seems to be in working order, the last thing to do is write an entire novel. *dry cough * What a delightful piece of software; a delicious turducken! It will make excellent sandwiches for tomorrow's lunch. Easy to learn, easy to use, and even relatively complex cross-application actions are achievable with a gentle learning curve. I found I only needed manuals and books for very rare, "Can AppleWorks even do this?" questions, and very rarely for, "I'm stumped." Of the word processors I've covered to date it's easily my favorite. Typing is responsive and editing is intuitive. I like the advanced tools, like markers, though I think other tools could be made easier to use or expanded. Keyboard shortcuts became second-nature very rapidly. Tasks I usually dread, like mail merge, are trivially accomplished. The database " is ." It exists. It's fine. Perhaps its simplicity is a virtue, to some degree? I'm not personally smitten, but I'm glad to have it, and it did solve a real problem for me with the CYOA construction. I enjoyed the spreadsheet as much as I did VisiCalc , which is to say it's very good but Lotus 1-2-3 still wins the day. Still, it's a lot of bang for your buck, considering it's only 1/3 of this package. Being able to easily share data with the other modules elevates its usefulness over VisiCalc . Its convenience gives it a clear win. Plus, it didn't include "Copilot integration" long before Microsoft removed it from Excel . Perhaps the biggest takeaway for me is the stark reminder of how much can be achieved with so little: so little CPU, so little RAM, so little hard disk space. One can't help but ponder, "If this is what 128KB can do, imagine applying the same discipline toward 1MB of RAM." Writing for inCider , Oct. 1989, Senior Editor Paul Statt compared the development cycle of Lotus 1-2-3 Release 3 (a rather notorious, oft-delayed "update") with AppleWorks 3 . Both were initially created by lone developers, Jonathan Sachs and Rupert Lissner. Release 3 of Lotus took a team of 40 developers years to write and shipped on 14 floppy discs. AppleWorks 3 was written by three Beagle Bros developers and shipped on two double-sided discs. An update to Lotus likely meant also updating one's computer to a 286 with 1MB of RAM and a hard drive. AppleWorks 3 ran on the exact same 128KB machine the version 1 ran on. Almost 40 years later, his complaint feels uncomfortably modern, don't you think? In recent news we read of ways to whittle Microsoft's 1GB weather app "down to" 130MB RAM consumption. While on the other hand, we have something like Weatherbot , occupying 6MB RAM on Windows 11 and about 2MB on System 7.5.5 (yes, it runs natively on both and more!) The call for an efficient use of system resources is clearly still championed by some, but I don't see much of a call for brutal efficiency. Using 1/100 the RAM of a Microsoft-made app is fantastic, don't get me wrong, but let's get far more ambitious. No more thinking in megabytes, think in kilobytes ; express ambition by orders of magnitude . AppleWorks singlehandedly kept Apple IIs in production use for decades , well into the GUI era, well past their "prime." We deserve such longevity again. We need it. There is a financial cudgel of planned obsolescence that beats us down for lunch money on a regular basis. We're told it's for progress, but that proposition holds no tether to reality when we can see and touch a 128KB rebuttal that proves the bully a liar. Don't worry, I wouldn't leave you hanging. The beginning of my James Epoxy CYOA; pause to read. The description of Dimitrius is straight from the source book; I did not set that up for my running gag! Ways to improve the experience, notable deficiencies, workarounds, and notes about incorporating the software into modern workflows (if possible). As with many tools of this type and era, the program's emphasis on "printing" limits our formatting options pretty drastically. We can only embed non-printing printer codes into a document, and that requires setting toggles for (say) bold to start and stop at specific points in the page. It's anachronistic in all of the non-nostalgic, annoying ways. Late in my review cycle I came across a project keeping ProDOS alive on real Apple II hardware. The last official version of Apple ProDOS was 2.0.3 in 1993, but this project is at 2.4.3 with 2.5 on the way. John Brooks has been maintaining this for years now. It includes a kind of app fast launcher called Bitsy Bye which might smooth the process of switching between apps (like between AppleWorks and StoryWorks , in my case), if for no other reason than it appears to eliminate keystrokes and simplify file navigation. 1984 rocked. AppleWin x64 1.32.00 on Windows 11 Emulating a prohibitively expensive Enhanced Apple IIe. All cards and hard drive included, maybe $8,000? ($22K in 2026) 3x machine speed and enhanced disk access Super Serial Card Mockingboard C Disk II w/two floppy drives Hard Disk Controller with 5MB ProDrive Z80 SoftCard RamWorks III (3MB) AppleWorks 3 TimeOut UltraMacros In the database, will "zoom in" and "zoom out" between the record list and an individual record. In the spreadsheet, it will toggle display of raw values or formulas embedded in the cells. In the word processor, it will reveal hidden formatting, like for printing, navigation tags, bold/italic format codes, carriage returns, and so on. Graphing and plotting spreadsheet data, for the Lotus -envious Expanding the limits of open documents, clipboard, database size A full clone of MacPaint Appointment calendar High-resolution font support The new version of AppleWin x64 makes it super simple to install a whole host of fun circuit boards into various slots. You can build the pimped out Apple IIe of your dreams effortlessly. I mostly ran at 300% CPU speed and experienced no quirks, repeating keys, crashes, or anything else unbecoming of a well-behaved computer. AppleWorks never crashed; the recent update to AppleWin x64 worked perfectly. I did have AppleWorks fail to bring in some fields when I copied from the database into the word processor. CiderPress2 works perfectly for opening Apple II disk images. Hard drive images are of file extension, and CiderPress2 can manage these, including copying file structures between disk images to cobble together your own custom disk from others. CiderPress2 can natively open AppleWorks documents to show formatted content. Personally, I found it better to print from AppleWorks to an ASCII file, then copy out the text from that via CiderPress2 . Doing so will ensure no hidden AppleWorks formatting codes are copied over. Modality The modal nature of the editing tools limits the power of macros. If we could select some text, then apply a macro to that selection, that would be fantastic. Markdown keyboard shortcuts would be a breeze! StoryWorks The biggest issue I have is how there is no option for exporting standalone stories. Being able to build a bootable Apple II floppy would turn it into a great, simple game maker; translations of existing CYOA adventures would almost be self-coding. I would also like for it to respect formatting codes, like centering. Spreadsheet Though opening DIF files is an option, I had zero luck getting it to open my VisiCalc DIF exports. It doesn't match the integration goals of the program, but I did miss the slash menu; it could have been a nice alternate UI for those transitioning to AppleWorks . Editing cells, such as to change a cell from a label to a value, always tripped me up. Considering this is version 3, well after Lotus 1-2-3 hit the scene, I do wish for better control over column widths and some concession to graph making. 3D spreadsheets, linking a cell in one sheet to an entirely different file, would be useful; this feature debuted in AppleWorks 5 . Database Basic concerns about being unable to apply value types to fields, or any kind of data input validation, are addressed in AppleWorks 4 and 5. Form layout formatting tools are overly simplistic. Being able to merge into the word processor directly from disk, rather than from the clipboard, would be helpful. Word Processor I don't have much to complain about. It is more robust than it first appears, with a gentle learning curve. I think word count should be surfaced to the main UI, and the on-screen ruler could be more informative.

0 views
Jason Tucker 2 months ago

What My Nest Hub Does Besides Show Family Photos

There's a Google Nest Hub sitting on a shelf under the living room TV, Google hasn't killed off this device (yet) so lets make it do more than show photos. Out of the box it's a photo frame with a clock on it, which is fine, but a screen that's already sitting there, already networked, already close to the action, felt like it should be doing more. It lives there muted, isn't really used for smart home stuff, it could be with one of the Home Assistant apps (docker containers) but I haven't explored that too much. I've seen plenty of home theater builds where people wire up a display that shows what's currently playing , the title, the poster art, how much longer it runs. I don't have a home theater. I have a living room. But I liked the idea anyway, mostly for one specific reason: when I start something on the Apple TV, I want to glance over and see when it's going to be done. If I pause it and start it back up later, I want that same glance to show me fresh info, not what was true an hour ago. The other thing I wanted from that same shelf display had nothing to do with video at all. Most of the cameras around the house already show up as HomeKit notifications in the corner of the Apple TV screen, so those are covered. The driveway PTZ camera is the odd one out. It catches a lot of things that aren't really notification-worthy, someone walking past on the sidewalk, the neighbor's cat cutting through the yard, kids messing around near the roses out front, so it's never wired into HomeKit alerts. I still wanted an easy way to glance at it without grabbing a phone, just not as an interruption on the TV itself. Also, our TV isn't on 24/7, we watch non linear tv so we don't have the news just running non stop or a tv show we dont care about. With the TV off you could glance at this screen on the shelf and see whats going on. Perfect. Two jobs, one screen, and neither one has anything to do with family photos or what this device was even designed for, I love stuff like this. When someone or something shows up on the driveway camera, the shelf display should switch over to a low-res feed for a minute or two, then go back to normal on its own. No notification, no phone, just a glance if I happen to be in the room. When something starts playing on the living room Apple TV, whether that's a movie through Moonfin, a show, or a YouTube video, the same display should flash a card: the title, the channel or show it belongs to, and a projected finish time. If I pause and resume, it should refresh with an updated time, not just repeat the same stale card from when I first hit play. Home Assistant already had entities for both halves of this. A media_player tracking the Apple TV over AirPlay, and a media_player for the Nest Hub through the Cast integration. On paper, wiring the driveway camera side up was trivial, one action that plays a camera stream to a cast target. The now-playing card looked just as simple. It wasn't. The driveway camera automation worked on the first try: The now-playing card did not. My first attempt looked just as reasonable, a media_player.play_media call pointed at a generated thumbnail: I wired that into an automation that triggered on the Apple TV's media_title changing, pulled the artwork, and cast it with a title and subtitle overlay. Home Assistant reported success every single time. The media player's state cycled through idle, buffering, playing. The title updated. Everything about the API said it worked. The Nest Hub's screen didn't change. At all. The obvious suspect was the image itself, maybe the thumbnail was broken or the fetch was failing silently. I checked. It wasn't. Next I suspected the Cast integration's URL validation, which turned out to be real but unrelated. Home Assistant's Cast component refuses to cast a media_content_id that resolves to its own host, whether that's a cloud URL or a plain LAN address: That fix is real and worth keeping on its own merits. Reference local files through media-source://media_source/local/ instead of building a URL by hand, and Home Assistant resolves it into a signed, cast-safe URL automatically. That killed the error message. It did not put anything on the screen. I kept narrowing it down. Real Jellyfin artwork over a plain HTTP URL, nothing. A photoMediaMetadata type instead of the generic one, nothing. The simplest possible test, a public image URL with zero moving parts: Home Assistant logged a clean success again. The Nest Hub sat there showing its ambient screensaver like nothing had happened. At that point there was only one conclusion left. This specific device does not render generic image casts, on this Home Assistant version, full stop. Every layer I'd been debugging, metadata types, signed URLs, content types, was real, but none of it was the actual problem. The driveway camera automation had been working the entire time, on the same Nest Hub, through the same Cast integration. I'd been treating it as a separate feature instead of a clue, because casting a camera stream feels conceptually different from casting a photo. It isn't, not to the device. A camera stream is video. That's the whole difference. This Nest Hub renders video casts fine and just does nothing visible with a plain image, no matter how it's addressed or labeled. Somewhere in the Cast "Default Media Receiver" on this device, static images and video streams take separate code paths, and only one of them is wired up to actually paint pixels. The fix, once I stopped fighting it, was almost funny. Stop casting an image, and cast a 30 second silent video of the image instead. I think 30 seconds is plenty of time to see whats playing, what the time it will be done is. ffmpeg was already sitting on the Home Assistant host, bundled in for camera and streaming support. That meant the whole now-playing card could be one generated clip: thumbnail on the left, channel name and title on the right, and a big, clearly labeled projected finish time pinned to the bottom. Cast that as video/mp4, and the Nest Hub showed it immediately. media_duration finally reported a real number instead of 0. This device I suppose only likes video casted to it, so we did just that. The text gets written to files first, then read back with drawtext's textfile option, instead of sitting inline in the filter graph. Real titles contain colons, quotes, and apostrophes that would otherwise collide with ffmpeg's own filter syntax. That one decision avoided a whole second debugging session. The thumbnail-left, text-right layout worked fine for YouTube, where the source thumbnail is already roughly square. It looked wrong for anything coming through Moonfin, though. Movie and show posters are portrait, and cropping one down into a small square just showed an oddly zoomed sliver of the art. Jellyfin and Moonfin content gets a different treatment now: a full-bleed backdrop image filling the whole frame, with the movie or show's logo graphic centered near the bottom over a dark scrim. When an item doesn't have a logo in Jellyfin, which happens more than you'd expect, the title gets drawn as bold text in that same spot instead. Picking the right artwork took its own fallback chain. Episodes rarely carry their own backdrop or logo, so the automation walks up to the season, then the series, until it finds one. Movies have neither a parent nor a series to fall back to, so they just use their own artwork directly. Deciding whether an item qualifies for this treatment at all turned into the hardest bug of the whole project. Even once the hero layout worked, it only showed up sometimes. The automation would flip between the backdrop treatment and the plain classic layout across nearly identical Jellyfin sessions, with no obvious pattern and nothing useful in the logs. This is the kind of bug that eats a weekend for a reason that has nothing to do with ffmpeg or Home Assistant at all. The automation decides which layout to use with one boolean: An earlier version of that line just chained the three checks together with a plain "and." That seemed fine, but "and" in this language doesn't actually hand back true or false, it hands back whichever value made the decision. In this case, that was a Jellyfin ID, not a clean yes or no. Most of the time that didn't matter, since the ID still counted as "something" wherever it showed up. But one specific check in Home Assistant is pickier about what counts as true, and an ID string doesn't qualify. So the same value passed one check and quietly failed another a few lines later. Forcing it into a real true or false fixed it for good. That's what "sometimes works, sometimes doesn't" really was: one value being read two different ways by two parts of the automation that never agreed on what counted as true. shell_command doesn't run through a real shell. I assumed Home Assistant's shell_command integration passed commands through /bin/sh, the way you'd expect. It doesn't. For templated commands it execs the rendered, split argument list directly. A plain redirect using the greater-than sign silently did nothing, no error, no file, no clue. I stopped relying on shell syntax entirely and wrote text out from a oneliner Python instead: A family member was watch a show on Jellyfin at their house and it threw this code off a bit. It was reporting the show they were watching not the show I was watching and their AppleTV was named "Apple TV" as well. Two Jellyfin clients were both named "Apple TV" and both running Moonfin for tvOS, one on the LAN, one connecting in over Tailscale from somewhere else. The only field that told them apart was ipAddress. Pinning the lookup to this Apple TV's reserved address fixed it for good: Once there were three visual layouts to choose between, I ran into a wall: these shell command templates don't support if/else logic, just simple substitution. So instead of one command that branches internally, there are three separate commands now, one per layout, and the automation picks which one to call. ffmpeg also choked when I fed it two images at once for the hero layout, a backdrop and a logo composited together. It would just hang. The fix was to flatten the images into a single frame first, then loop that one frame into the finished clip as a separate step. A fresh video starting actually trips two triggers at once. Without a small delay on both, one would fire and finish before the other had even started, so the second run would cast right on top of the first. Giving both triggers the same five-second wait fixed it. And a couple smaller ones: filenames need to be unique per run, since a superseded automation run keeps writing files in the background even after being canceled, and those files could clobber a newer run's output. Tagging every filename with the run's own ID took care of it. Both automations target the same media_player, so they need to stay out of each other's way. The now-playing automation checks that nobody's currently at the driveway before it takes over the display, so a movie card can't interrupt a driveway alert that's already showing: The driveway automation is the simpler of the two, since a camera stream is already video and never ran into the image problem at all: The now-playing automation is longer, mostly because it tries to figure out a good title, channel, and thumbnail no matter what's playing, Jellyfin through Moonfin, a YouTube video, or anything else the Apple TV reports. The load bearing part is really just three steps buried inside it: write the channel, title, and finish time out to text files, composite them into a video with ffmpeg, then cast that video with play_media. Everything else, the Jellyfin session lookup, the YouTube thumbnail fallback, is optional flourish on top of that core trick. A few notes if you're adapting either of these. The IP addresses and device ID are specific to this household and need to change for anyone reusing them. The two DejaVuSans font paths point at fonts bundled with other, unrelated HACS integrations, since there's no dedicated system font directory on this Home Assistant OS install. It works, but it's not something I'd call the right way to do it, just what was already sitting on the disk. Start something through Moonfin and the shelf display switches to the full backdrop treatment. When the movie or show has a logo, it sits centered near the bottom over the artwork. Without one, the title takes its place instead. YouTube and anything else keeps the original layout, thumbnail on the left, channel and title on the right. Either way, the same "Ends @ 1:47PM" line sits at the bottom, and it's gone again after about thirty seconds, cleaning up its own temp files as it goes. Pause the video and start it again later, and the same trigger fires again with a fresh finish time, since the automation reads the Apple TV's current position and duration each time rather than remembering the first calculation. Walk up the driveway while something's playing, and the driveway feed wins instead, then hands the screen back once things are quiet. None of the individual pieces here are exotic. ffmpeg's drawtext filter, media-source:// URIs, a REST session lookup, are all fairly ordinary Home Assistant tools. What made the difference was realizing this device already had a working video cast sitting right next to the broken image cast, and that the two behaving differently was the whole answer. A photo frame turned out to be a fine screen for showing more than photos, once I stopped asking it to show a photo. We sometimes build things just to see if we can, sometimes we build things because its fun. Sure some of this is redundant such as the end time for a Jellyfin video but YouTube doesnt display this so why not. Have you built anything like this? I'd love to hear it in the comments below! Make a quick free acocunt here and leave a comment. It keeps the spammers out and lets me know people care about my adventures in technology.

0 views