10 interesting stories served every morning and every evening.

Elevators

john.fun

Everyone has shared the frus­tra­tion of wait­ing for an el­e­va­tor that never seems to ar­rive. I pressed the but­ton, why is­n’t it com­ing?” you ask. For some­thing as com­mon­place as el­e­va­tors, they are far more com­plex than meets the eye.

Over the course of this ar­ti­cle, we’ll un­ravel the mys­ter­ies of el­e­va­tors. The way you push their but­tons, and how they push yours.

One Car

The sim­plest el­e­va­tor al­go­rithm is called SCAN and was patented in 1961. The el­e­va­tor starts at the lobby and goes all the way to the top floor be­fore re­vers­ing and com­ing back down. It picks up and drops off any­body on the way.

Most of the time you don’t ac­tu­ally need to go to the TOP floor. If the el­e­va­tor goes only as high as re­quested be­fore re­vers­ing, the al­go­rithm is called the LOOK al­go­rithm. This is the al­go­rithm most peo­ple know and ex­pect.

Multiple Cars

Here’s where the mys­tery be­gins. If there are mul­ti­ple el­e­va­tors, how do the cars co­or­di­nate who picks up who?

In the most ba­sic sys­tem, there’s a cen­tral sched­uler that tells each el­e­va­tor which floors to stop on. When a new re­quest comes in, it’s as­signed to the clos­est el­e­va­tor. As we’ll soon see how­ever, we can do bet­ter.

Long Waits

How do you ac­tu­ally mea­sure how good an el­e­va­tor al­go­rithm is? The ob­vi­ous met­ric is how long you wait for the el­e­va­tor to ar­rive.

A very sim­ple mea­sure is how of­ten does the el­e­va­tor ar­rive within 30 sec­onds?” Or how of­ten does the el­e­va­tor ar­rive within 90 sec­onds?”

-

wait < 30s

-

wait < 90s

flow14/​min

Applied Stats

More rig­or­ously, we want to look at the DISTRIBUTION of wait times. If we plot the wait time across thou­sands of rides, we get the his­togram be­low.

010s

p50—p90—

flow14/​min

A p90 of 2m means 90% of the time, rid­ers wait 2m or less for the el­e­va­tor. A p50 of 1m means half the time the el­e­va­tor ar­rives within 1m.

People don’t usu­ally re­mem­ber the av­er­age amount of time they wait. They fix­ate on those times when the el­e­va­tor took FOREVER, the p90 case.

Morning Rush

Not all pas­sen­ger traf­fic is cre­ated equal. Imagine a large cor­po­rate of­fice build­ing. In the morn­ings, nearly all traf­fic is dom­i­nated by trips from the lobby to the up­per lev­els.

In the evening this flips as every­one leaves the build­ing. The lunch rush is a bit of both, and the re­main­ing traf­fic is of­ten from floor to floor.

-

wait < 30s

-

wait < 90s

flow14/​min

The dis­tri­b­u­tion of wait times varies dras­ti­cally de­pend­ing on the time of day and the traf­fic pat­terns the el­e­va­tors are fac­ing. Morning rush no­to­ri­ously has the worst wait sta­tis­tics.

Smarter Elevators

When an­a­lyz­ing the LOOK el­e­va­tor al­go­rithm, we LOOKED (ha ha) at how rid­ers are as­signed to cars. We naively as­signed each re­quest to the near­est car but said we could do bet­ter.

What if the near­est car is full? We can get smarter with Otis’ RSR (Relative System Response) al­go­rithm. RSR scores each car for how well suited it is to pick up a pas­sen­ger. Lower scores be­ing bet­ter.

RSR pickup score

Score=ETA to pickup+on­board load penalty+same-di­rec­tion anti-bunch­ing penalty-di­rec­tion-match bonus-idle-nearby bonus-low-load bonus

RSR also re-op­ti­mizes every 5 sec­onds. A pas­sen­ger that’s go­ing to be picked up by el­e­va­tor A can be re-routed to el­e­va­tor B if el­e­va­tor A en­coun­ters de­lays. This re-op­ti­miza­tion turns out to be key for stream­lin­ing traf­fic flow.

In the graphic be­low, each el­e­va­tor lights up when it’s the best choice to ser­vice a call from floor 3 if the but­ton hap­pened to be pressed at that ex­act mo­ment. This con­stantly changes as the el­e­va­tors move, show­ing the op­ti­mizer in mo­tion.

LOOK vs RSR

Armed with our el­e­va­tor analy­sis toolkit, we can bench­mark the per­for­mance of LOOK vs RSR to see how much a smarter el­e­va­tor al­go­rithm ac­tu­ally im­proves wait time.

LOOK

-wait < 30s

-wait < 90s

RSR

-wait < 30s

-wait < 90s

flow8/​min

Interestingly as the flow rate gets higher, LOOK ac­tu­ally starts to out­per­form RSR. When the el­e­va­tors are al­ways full and stop­ping on every floor, the ex­tra rules don’t mat­ter as much.

LOOK also tends to out­per­form RSR in small build­ings with fewer el­e­va­tors per bank. Sometimes it’s bet­ter to just keep things sim­ple.

Another met­ric you can track is jour­ney time, how long you’re ac­tu­ally wait­ing in the el­e­va­tor be­fore get­ting to your floor. RSR and LOOK have dif­fer­ent char­ac­ter­is­tics here as well but that’s be­yond the scope of this ar­ti­cle.

Destination Dispatch

Not all el­e­va­tors have but­tons in them. Some of the fancy new el­e­va­tors have a kiosk on each floor that al­lows you to spec­ify what floor you’re head­ing to be­fore the el­e­va­tor even ar­rives. The kiosk then points you to which el­e­va­tor you should wait for.

This is called Destination Dispatch. At first glance, it seems great. The el­e­va­tor op­ti­mizer now has full knowl­edge of who is go­ing where, cer­tainly we can use this to re­duce wait times right?

RSR

-wait < 30s

-wait < 90s

Destination Dispatch

-wait < 30s

-wait < 90s

flow8/​min

It turns out these fancy kiosks are in gen­eral worse for wait times com­pared to the tra­di­tional good ol’ up and down but­tons. There are cer­tainly edge cases when the kiosks can win out (extremely tall build­ings with 8+ cars per el­e­va­tor bank) but for the ma­jor­ity of cases, sim­ple up down but­tons reign supreme.

This coun­ter­in­tu­itive re­sult is all thanks to the re­bal­anc­ing step where every 5 sec­onds, the sys­tem re-op­ti­mizes each el­e­va­tor’s path. The kiosk en­forces rigid­ity, you must get in the as­signed el­e­va­tor.

The state of the world 30sec af­ter you called your el­e­va­tor might be very dif­fer­ent but the sys­tem is un­able to adapt. Turns out the loss in flex­i­bil­ity is not worth the ex­tra in­for­ma­tion for the op­ti­mizer.

Full Sim

Here’s a sim­u­la­tion with all the but­tons and knobs to play with. Go crazy!

-

wait < 30s

-

wait < 90s

floors8­cars4flow18/​min

Conclusion

This ar­ti­cle just scratches the sur­face of el­e­va­tor al­go­rithms. Next time you’re stuck wait­ing for an el­e­va­tor, try not to take it per­son­ally. The el­e­va­tor did hear you, it just has a lot to think about.

The Session You Cannot Take With You | EARENDIL

earendil.com

The orig­i­nal promise of an in­fer­ence API was won­der­fully sim­ple: send some in­put, re­ceive some out­put. If you kept both, you had the con­ver­sa­tion. You could in­spect it, archive it, re­play it, or give it to a dif­fer­ent model.

That ab­strac­tion was never com­pletely true. For in­stance prompt caches live on some­body else’s GPUs, to­k­eniza­tion dif­fers be­tween mod­els, and sam­pling is not re­pro­ducible (and quite in­ten­tion­ally so). But the se­man­tic record of a ses­sion in the form of a tran­script could still be­long to the user. A tran­script should con­tain the in­struc­tions, mes­sages, tool calls and tool re­sults. Another suf­fi­ciently ca­pa­ble model might not con­tinue iden­ti­cally, but it could un­der­stand what hap­pened and take over.

Inference APIs are frus­trat­ingly mov­ing away from that prop­erty, at least some­what. They in­creas­ingly re­turn a mix­ture of text and provider-bound state that is very in­ten­tion­ally non-portable.

rea­son­ing to­kens that are billed to the user but re­turned only as opaque, en­crypted blobs, with use­less sum­maries at best

web searches where the model sees source ma­te­r­ial the client never sees

com­pacted con­text that only the orig­i­nal provider can de­crypt

sub­agent in­struc­tions and mes­sages hid­den from the ap­pli­ca­tion run­ning the agents in the form of en­crypted pay­loads

file, vec­tor-store, con­tainer, and cache ref­er­ences that can­not be re­solved any­where else.

re­sponse and con­ver­sa­tion state that is en­tirely keyed by IDs that are stored fully on the provider’s servers

Each fea­ture comes with a ba­sic jus­ti­fi­ca­tion that’s triv­ial for a provider to come up with, along with good ar­gu­ments for why this is good for the user. Together all of these things change the own­er­ship re­al­ity of an AI ses­sion: the tran­script on your ma­chine is no longer your ses­sion but a par­tial view of a ses­sion whose op­er­a­tional state be­longs to an in­fer­ence provider and not you.

We are not fans of this di­rec­tion, and we want to talk a bit about what it means to you, as a user, and what it means to us, as peo­ple de­vel­op­ing tools in this space.

A Practical Test for Session Ownership

By a portable ses­sion we do not mean that switch­ing from one model to an­other must pro­duce the same next to­ken. That’s a given be­cause mod­els have dif­fer­ent ca­pa­bil­i­ties, trained per­son­al­i­ties, con­text win­dows, and ways of work­ing with tools. And well, it’s all quite non­de­ter­min­is­tic any­way. Portability means some­thing more mod­est:

const tran­script = ses­sion.ex­port(); re­voke­Cre­den­tials(old­Provider); ses­sion = new­Provider.con­tin­ue­From(tran­script);

The archive should con­tain enough in­tel­li­gi­ble in­for­ma­tion for an­other model to con­tinue the work. It should not re­quire the old provider to deref­er­ence an ID, de­crypt a blob, re­mem­ber a search re­sult, or re­con­struct a sum­mary.

This gives us five use­ful tests:

Inspection: Can the user see what the model saw, what tools did, and what agents told each other?

Export: Is the ses­sion self-con­tained, apart from or­di­nary ar­ti­facts that can also be down­loaded?

Replay: Can an­other im­ple­men­ta­tion re­con­struct a se­man­ti­cally equiv­a­lent con­text?

Audit: Can a hu­man ex­plain why the sys­tem took an ac­tion af­ter the fact?

Deletion: Can the user iden­tify and re­move every server-side copy on which the ses­sion de­pends?

A re­sponse ID is not a tran­script (as the data is stored on the server), a ci­pher­text is not user-con­trolled stated (as the user can­not de­crypt it), a list of ci­ta­tions is not the ev­i­dence that was placed in the mod­el’s con­text by a search re­sult (as you can­not typ­i­cally fetch the same data as the model did).

Encryption for Whom?

The nam­ing and mar­ket­ing around these fea­tures can be mis­lead­ing. en­crypt­ed_­con­tent sounds like a pri­vacy fea­ture un­der the user’s con­trol. Usually it is a cap­sule that the client can­not read and only the provider can open. The provider chooses the keys, de­crypts the con­tent for its own mod­els, and de­fines where the data can be re­played.

A bet­ter term is provider-sealed state.

Provider seal­ing can have a real pri­vacy ben­e­fit. OpenAI, for ex­am­ple, can re­turn en­crypted rea­son­ing to a client us­ing store: false, then de­crypt it in mem­ory on the next re­quest with­out per­sist­ing the in­ter­me­di­ate state. That is bet­ter than re­quir­ing server-side con­ver­sa­tion stor­age, par­tic­u­larly for Zero Data Retention cus­tomers. But, re­mem­ber, there is not re­ally any­thing that needs en­cryp­tion to be­gin with!

This en­cryp­tion does not hide the data from the in­fer­ence provider but it hides it from you.

Stored Conversations Turn a Transcript into a Pointer

OpenAI’s Responses API stores re­sponses by de­fault. Its doc­u­men­ta­tion says re­sponse ob­jects are re­tained for at least 30 days by de­fault. store: false is avail­able and should be used, as it makes it work more like com­ple­tions: the data is not stored on OpenAI’s servers.

The new Gemini Interactions API has made a sim­i­lar choice. It de­faults to store: true. On the paid tier in­ter­ac­tions are re­tained for 55 days, and on the free tier for one day.

And ob­vi­ously, the idea of stor­ing state on the server is quite at­trac­tive:

const first = re­sponses.cre­ate({ model: frontier-model”, in­put: Investigate this pro­duc­tion fail­ure”, store: true, });

const sec­ond = re­sponses.cre­ate({ model: frontier-model”, pre­vi­ous­Re­spon­seId: first.id, in­put: Now im­ple­ment the fix”, store: true, });

The ap­pli­ca­tion sends less data, the provider can pre­serve hid­den rea­son­ing and tool state, and cache rout­ing be­comes eas­ier. But if the lo­cal ap­pli­ca­tion only records the user mes­sages and fi­nal text, first.id is now a for­eign key into a data­base it does not con­trol.

No Reasoning For You

All ma­jor labs claim to have le­git­i­mate rea­sons not to ex­pose raw chain of thought. As a re­sult, on non-open-weights mod­els we typ­i­cally do not see these to­kens.

Raw rea­son­ing is not vis­i­ble via the API. With stored re­sponses, prior rea­son­ing can be re­cov­ered through pre­vi­ous_re­sponse_id. With store: false, the API re­turns en­crypt­ed_­con­tent, which the client must pre­serve and re­play. Persisted rea­son­ing re­mains opaque even when rea­son­ing.con­text: all_turns” lets a later sam­ple use it.

Anthropic re­turns the en­crypted full think­ing in a sig­na­ture field. The read­able think­ing text, when en­abled, is a sum­mary pro­duced by an­other model, not the raw chain of thought. Thinking blocks must be passed back un­changed dur­ing tool-use turns. Anthropic’s doc­u­men­ta­tion also says think­ing blocks are tied to the model that pro­duced them and should be stripped when switch­ing mod­els. So these rea­son­ing traces do not at­tempt to be portable within Anthropic.

The same story re­peats with all closed-weights mod­els.

These en­cryp­tion mech­a­nisms per­mit con­ti­nu­ity in­side an ecosys­tem but they do not cre­ate a portable tran­script that can be taken to an­other provider’s model. A ses­sion archive can con­tain the blob, but an­other model can­not use its mean­ing:

{“type”: reasoning”, encrypted_content”: gAAAAAB…“} {“type”: thinking”, thinking”: ”, signature”: EqQBCg…“} {“type”: thought”, summary”: [], signature”: EpoGCp…“}

Hidden Searches

Server-side web search is one of the clear­est ex­am­ples of a tran­script hav­ing holes in it hid­den from the user. A client-side search tool be­haves like any other tool:

const re­sult = search(query); record({ query, re­trieve­dAt: now(), re­sults: re­sult.map((item) => ({ url: item.url, ti­tle: item.ti­tle, pas­sages: item.pas­sages, })), }); model.send({ tool­Re­sult: re­sult });

The user can in­spect the rank­ing and pas­sages, refetch the pages, cache a copy, or pro­vide the same ev­i­dence to an­other model.

With hosted search, the provider per­forms a pri­vate tool loop. OpenAI, Google and Anthropic ex­pose search ac­tions, ci­ta­tions, and op­tion­ally a list of source URLs, but not the com­plete text con­text used to pro­duce an an­swer. A URL is not a sta­ble re­play, in­stead its con­tents can change or have been re­duced to a much shorter snip­pet be­fore the model saw it.

The fi­nal an­swer may be per­fectly good. The prob­lem ap­pears on the next turn:

Compare the third source with the first one, re-check the dis­puted num­ber, and con­tinue this re­search us­ing an­other model.

Compare the third source with the first one, re-check the dis­puted num­ber, and con­tinue this re­search us­ing an­other model.

The new model re­ceives an an­swer and a few URLs. It does not re­ceive the re­sult rank­ing, ex­tracted pas­sages, fil­tered-out ma­te­r­ial, or ex­act ev­i­dence the first model used. The old provider is still part of the ses­sion even if the next re­quest goes else­where. Even if you have the ci­ta­tions and you were to re-fetch you can­not re­pro­duce the pre­cise data.

Hosted search should have a full-fi­delity ex­port mode con­tain­ing queries, re­sult meta­data, re­trieved pas­sages, time­stamps and re­tained con­tents. Concise ci­ta­tions can re­main the user in­ter­face but they should not be the only record.

Opaque Compaction

Long agent ses­sions even­tu­ally need com­paction. A vis­i­ble, client-con­trolled sum­mary is lossy, but it is at least in­spectable and trans­fer­able. The user can re­view it, edit it, or ask a dif­fer­ent model to pro­duce an­other one.

OpenAI’s server-side com­paction in­stead emits an en­crypted com­paction item. The doc­u­men­ta­tion de­scribes it as opaque and not in­tended to be hu­man-in­ter­pretable.” The stand­alone /responses/compact end­point re­turns a canonical next con­text win­dow” that clients are in­structed to pass on as-is.

Conceptually, the tran­si­tion looks like this:

// Before: ex­pen­sive but portable let his­tory = [ user­Mes­sage, as­sis­tantMes­sage, tool­Call, full­Tool­Re­sult, // … 200,000 more to­kens of in­tel­li­gi­ble his­tory ];

// After: cheap to con­tinue only with the orig­i­nal provider his­tory = [ { type: compaction”, en­crypt­ed­Con­tent: enc_provider_only_state…”, }, …recentItems, ];

OpenAI can con­tinue from the com­pressed mean­ing, but a dif­fer­ent provider sees an un­read­able string plus a re­cent suf­fix (well, would see it, we never pass this sort of in­for­ma­tion to an­other provider).

This is not tech­ni­cally nec­es­sary. Anthropic’s server-side com­paction re­turns a com­paction block with a read­able con­tent field. It lets the client pro­vide cus­tom sum­ma­riza­tion in­struc­tions, and the re­sult­ing sum­mary can be in­spected and passed to an­other model. Client-side com­paction is also pos­si­ble with any provider.

OpenAI’s sealed ar­ti­fact may pre­serve more model-spe­cific state than a plain sum­mary and may per­form bet­ter on the orig­i­nal model. That is a rea­son­able op­tional op­ti­miza­tion but it should be ac­com­pa­nied by a read­able hand­off sum­mary, not re­place one. But again, a lot of this has the added ben­e­fit of fur­ther lock­ing you into one ecosys­tem.

Subagents Come With Hidden Instructions

Multi-agent sys­tems com­pound the prob­lem be­cause there is no longer one tran­script. There is a tree of ses­sions and a stream of mes­sages be­tween them. Usually they are prompts as if a hu­man wrote them, just now au­thored by a ma­chine for an­other ma­chine.

OpenAI’s hosted Responses Multi-agent beta re­turns three new item types: mul­ti­_a­gen­t_­call, mul­ti­_a­gen­t_­cal­l_out­put, and agen­t_mes­sage. The ex­am­ple for spawn_a­gent con­tains an en­crypted mes­sage ar­gu­ment, and in­ter-agent mes­sages con­tain only en­crypt­ed_­con­tent. Automatic server-side com­paction is im­plic­itly en­abled for every agent when Multi-agent is en­abled, even if the client did not re­quest it. Reasoning sum­maries are not sup­ported and the API also in­jects root and sub­agent in­struc­tions that the de­vel­oper can­not edit or re­move.

This is a bun­dle of non-trans­fer­able state: sealed del­e­ga­tion, sealed agent mes­sages, sep­a­rate au­to­mat­i­cally com­pacted con­texts, hid­den rea­son­ing, and provider-hosted or­ches­tra­tion.

A re­lated change landed in the open-source Codex client in June 2026. The com­mit, ti­tled Encrypt multi-agent v2 mes­sage pay­loads”, ex­plains the flow di­rectly:

// Parent mod­el’s tool call, as per­sisted by Codex { name”: spawn_agent”, arguments”: { task_name”: worker”, message”: <ciphertext>” } }

// Child mod­el’s in­put { type”: agent_message”, author”: /root”, recipient”: /root/worker”, content”: [{ type”: encrypted_content”, encrypted_content”: <ciphertext>” }] }

The Responses API en­crypts the tool ar­gu­ment emit­ted by the par­ent, Codex for­wards it, and the API de­crypts it in­ter­nally for the child. Codex’s own InterAgentCommunication.content is empty. The ex­act task is ab­sent from its read­able roll­out and his­tory.

Presumably this is not merely an ab­stract model-switch­ing con­cern. One could imag­ine if the child changes the wrong file, leaks a se­cret, du­pli­cates an­other agen­t’s work, or fol­lows a bad as­sump­tion, the user can­not an­swer the sim­ple ques­tion of what was that agent asked to do?

An open Codex is­sue asks for the en­crypted de­liv­ery to re­tain a sep­a­rate read­able au­dit copy. That is the min­i­mum ac­cept­able de­sign. Better still, plain­text in­ter-agent mes­sages should re­main the norm.

Most People Do Not Switch Models Mid-Session”

Probably not. Most peo­ple do not switch their op­er­at­ing sys­tem or phone provider every week ei­ther. But even if you do not uti­lize that free­dom, it mat­ters be­cause it changes the re­la­tion­ship you have with the provider and the provider has with you.

As a user you also may need to move a ses­sion be­cause a model is re­tired, a ser­vice is down, a price changes, a pol­icy blocks the next re­quest (hello fa­ble), a con­fi­den­tial phase must run lo­cally, or an au­di­tor needs to re­con­struct what hap­pened. Agents are also mak­ing ses­sions much longer. A cod­ing or re­search ses­sion can ac­cu­mu­late days of de­ci­sions and ev­i­dence and a per­sonal as­sis­tant may ac­cu­mu­late ses­sion tran­scripts go­ing back years (presumably as we don’t have them for that long yet).

The op­tion to leave also cre­ates dis­ci­pline. If a provider knows that a user can con­tinue else­where, it has to com­pete on model qual­ity, price, re­li­a­bil­ity, and trust. If the user’s ac­cu­mu­lated con­text can only be in­ter­preted by one provider, it sets very un­for­tu­nate in­cen­tives.

What a Portable Inference API Should Promise

We would like in­fer­ence providers and agent builders to adopt a small set of rules.

The lo­cal event log is canon­i­cal. Server stor­age may mir­ror or ac­cel­er­ate it, but the client can re­con­struct the ses­sion with­out deref­er­enc­ing server IDs.

Storage is ex­plicit. store: false should be easy, doc­u­mented, and prefer­ably the de­fault. Features that re­quire re­ten­tion should say so at the point of use.

No opaque item is the sole car­rier of mean­ing. Encrypted rea­son­ing, com­paction, and tool sig­na­tures may be in­cluded for same-provider qual­ity, but each has a read­able, provider-neu­tral hand­off rep­re­sen­ta­tion.

Hosted tools have full-fi­delity logs. Record ex­act in­puts, out­puts, ev­i­dence, fil­ter­ing, prove­nance, time­stamps, and con­tent hashes — not only a pol­ished an­swer and ci­ta­tions.

Subagent com­mu­ni­ca­tion is au­ditable. Persist the ex­act read­able task, mes­sages, re­sults, lin­eage, model, and tool per­mis­sions for every agent.

Compaction is in­spectable. Return a read­able sum­mary, the in­struc­tions used to cre­ate it, and enough lin­eage to un­der­stand what was dis­carded.

Artifacts are ex­portable. Files, con­tainer out­puts, search snap­shots, and gen­er­ated me­dia can be down­loaded into a con­tent-ad­dressed lo­cal archive.

Distillation Is Great Actually

There is a re­lated form of lock-in at the model layer.

Some of the largest closed-weight US labs are in­creas­ingly hos­tile to out­side dis­til­la­tion. Anthropic’s February 2026 post about al­leged cam­paigns by DeepSeek, Moonshot, and MiniMax calls them distillation at­tacks”. Its com­mer­cial terms say cus­tomers own their out­puts, but pro­hibit us­ing the ser­vice to train a com­pet­ing AI model. At the same time, Anthropic’s own post ac­knowl­edges that distillation is a widely used and le­git­i­mate train­ing method” when fron­tier labs use it on their own mod­els.

Anthropic uses ro­bots to gather data from the pub­lic web for model de­vel­op­ment and they fa­mously cut up books to scan them. OpenAI sim­i­larly says it trains on freely ac­ces­si­ble pub­lic in­ter­net con­tent and has ar­gued that train­ing on pub­licly avail­able in­ter­net ma­te­ri­als is fair use. Both com­pa­nies de­scribe dis­til­la­tion as a nor­mal way to pro­duce smaller mod­els when it hap­pens in­side their own walls. OpenAI has also of­fered an ex­plicit first-party API dis­til­la­tion work­flow for us­ing out­puts from a stronger OpenAI model to fine-tune a smaller OpenAI model.

The moral asym­me­try is still hard to miss. The labs ask so­ci­ety to ac­cept that ma­chines may learn from the enor­mous body of work hu­mans placed on the in­ter­net — of­ten with­out ad­vance, in­di­vid­ual per­mis­sion — while in­sist­ing that other ma­chines must not learn from out­puts the labs gen­er­ate. The broad­est ver­sion of that prin­ci­ple con­ve­niently al­lows learn­ing to flow into closed mod­els but not back out of them.

We think the de­fault at­ti­tude to­ward dis­til­la­tion should move from hos­til­ity to sup­port. Distillation can turn ex­pen­sive fron­tier ca­pa­bil­ity into smaller, cheaper, faster mod­els that can run lo­cally, of­fline, on con­strained hard­ware, or un­der the user’s con­trol. It can in­crease com­pe­ti­tion, pre­serve ca­pa­bil­ity when an API dis­ap­pears, and re­duce the com­pute and en­ergy re­quired for com­mon tasks.

The Minimum Freedom

A user should be able to close an ac­count, keep a ses­sion, and hand it to an­other model. The new model may dis­agree, ask ques­tions, or per­form worse. It should not be star­ing at ci­pher­text where the old model saw the user’s his­tory, ev­i­dence, plans, and del­e­gated work.

We do not ob­ject to providers build­ing bet­ter state­ful APIs. We ob­ject to bet­ter per­for­mance be­ing cou­pled to less user con­trol. Stateful stor­age should be op­tional, hosted tools should be ob­serv­able, com­paction should be read­able, agent com­mu­ni­ca­tion should be au­ditable and ide­ally opaque rea­son­ing is not opaque or at least should have a portable hand­off. Distillation should be a path by which ca­pa­bil­ity be­comes more avail­able, not a taboo used to jus­tify ever higher walls.

Change Log | DeepSeek API Docs

api-docs.deepseek.com

Date: 2026 – 07-31​

DeepSeek-V4-Flash Update​

The of­fi­cial re­lease of the DeepSeek-V4-Flash API is now in pub­lic beta. The API call­ing method re­mains un­changed — sim­ply set the model name to deepseek-v4-flash to use the lat­est ver­sion.

Significantly en­hanced agent ca­pa­bil­i­ties, with bench­mark re­sults far ex­ceed­ing V4-Pro-Preview:

Terminal Bench 2.1: 82.7

NL2Repo: 54.2

Cybergym: 76.7

DeepSWE: 54.4

Toolathlon ver­i­fied: 70.3

Agent Last Exam: 25.2

Automation Bench (Public): 25.1

DSBench-FullStack: 68.7

DSBench-Hard: 59.6

Note 1: For the Code Agent tasks in the pub­lic bench­mark sets, the of­fi­cial DeepSeek-V4-Flash was tested us­ing the DeepSeek Harness min­i­mal mode (to be re­leased soon) as the frame­work, with the max ef­fort level, topp=0.95, and tem­per­a­ture=1.0 Note 2: DSBench-FullStack is an in­ter­nal full-stack de­vel­op­ment test set, and DSBench-Hard is an in­ter­nal Coding Agent hard-prob­lem test set

The of­fi­cial V4-Flash na­tively sup­ports the Responses API for­mat and is specif­i­cally adapted for Codex. For the spe­cific con­fig­u­ra­tion, please re­fer to the doc­u­men­ta­tion.

DeepSeek-V4-Flash-0731 keeps the same model ar­chi­tec­ture and size as DeepSeek-V4-Flash-Preview, and was only re-post-trained.

Note: This up­date only up­grades the DeepSeek-V4-Flash API. The DeepSeek-V4-Pro API and the APP/WEB mod­els are un­changed.

The of­fi­cial re­lease of DeepSeek-V4-Pro will fol­low soon.

Date: 2026 – 04-24​

DeepSeek-V4​

The DeepSeek API now sup­ports V4-Pro and V4-Flash, avail­able via both the OpenAI ChatCompletions in­ter­face and the Anthropic in­ter­face. To ac­cess the new mod­els, the base_url re­mains un­changed, and the model pa­ra­me­ter should be set to deepseek-v4-pro or deepseek-v4-flash.

The two legacy API model names, deepseek-chat and deepseek-rea­soner, will be dis­con­tin­ued in three months (2026 – 07-24). During the cur­rent pe­riod, these two model names point to the non-think­ing mode and think­ing mode of deepseek-v4-flash, re­spec­tively.

For more de­tails, please re­fer to this doc­u­men­ta­tion.

Date: 2025 – 12-01​

DeepSeek-V3.2​

Both deepseek-chat and deepseek-rea­soner have been up­graded to DeepSeek-V3.2.

deepseek-chat cor­re­sponds to DeepSeek-V3.2′s non-think­ing mode

deepseek-rea­soner cor­re­sponds to DeepSeek-V3.2′s think­ing mode

DeepSeek-V3.2-Speciale​

DeepSeek-V3.2-Speciale is served via a tem­po­rary end­point: base_url=“https://​api.deepseek.com/​v3.2_spe­ciale_­ex­pires_on_20251215. Same pric­ing as V3.2, no tool calls, avail­able un­til Dec 15th, 2025, 15:59 (UTC Time).

For more de­tails, please re­fer to this doc­u­men­ta­tion.

Date: 2025 – 09-29​

DeepSeek-V3.2-Exp​

Both deepseek-chat and deepseek-rea­soner have been up­graded to DeepSeek-V3.2-Exp.

deepseek-chat cor­re­sponds to DeepSeek-V3.2-Exp’s non-think­ing mode

deepseek-rea­soner cor­re­sponds to DeepSeek-V3.2-Exp’s think­ing mode

For more de­tails, please re­fer to this doc­u­men­ta­tion.

Date: 2025 – 09-22​

DeepSeek-V3.1-Terminus​

Both deepseek-chat and deepseek-rea­soner have been up­graded to DeepSeek-V3.1-Terminus. deepseek-chat cor­re­sponds to DeepSeek-V3.1-Terminus’s non-think­ing mode, while deepseek-rea­soner cor­re­sponds to its think­ing mode.

This up­date main­tains the mod­el’s orig­i­nal ca­pa­bil­i­ties while ad­dress­ing is­sues re­ported by users, in­clud­ing:

Language con­sis­tency: Reduced oc­cur­rences of Chinese-English mix­ing and oc­ca­sional ab­nor­mal char­ac­ters;

Agent ca­pa­bil­i­ties: Further op­ti­mized the per­for­mance of the Code Agent and Search Agent.

Date: 2025 – 08-21​

DeepSeek-V3.1​

Both deepseek-chat and deepseek-rea­soner have been up­graded to DeepSeek-V3.1. deepseek-chat cor­re­sponds to DeepSeek-V3.1′s non-think­ing mode, while deepseek-rea­soner cor­re­sponds to its think­ing mode.

Key up­dates in DeepSeek-V3.1:

Hybrid rea­son­ing ar­chi­tec­ture: A sin­gle model sup­ports both think­ing mode and non-think­ing mode Improved rea­son­ing ef­fi­ciency: Compared to DeepSeek-R1 – 0528, DeepSeek-V3.1-Think pro­vides an­swers in sig­nif­i­cantly less time Enhanced agent ca­pa­bil­i­ties: With post-train­ing op­ti­miza­tion, the new model achieves ma­jor im­prove­ments in tool us­age and in­tel­li­gent agent tasks

SWE-bench Verified: 66.0 SWE-bench Multilingual: 54.5 Terminal-bench: 31.3

Hybrid rea­son­ing ar­chi­tec­ture: A sin­gle model sup­ports both think­ing mode and non-think­ing mode

Improved rea­son­ing ef­fi­ciency: Compared to DeepSeek-R1 – 0528, DeepSeek-V3.1-Think pro­vides an­swers in sig­nif­i­cantly less time

Enhanced agent ca­pa­bil­i­ties: With post-train­ing op­ti­miza­tion, the new model achieves ma­jor im­prove­ments in tool us­age and in­tel­li­gent agent tasks

SWE-bench Verified: 66.0 SWE-bench Multilingual: 54.5 Terminal-bench: 31.3

SWE-bench Verified: 66.0

SWE-bench Multilingual: 54.5

Terminal-bench: 31.3

Date: 2025 – 05-28​

deepseek-rea­soner​

deepseek-rea­soner Model Upgraded to DeepSeek-R1 – 0528:

Enhanced Reasoning Capabilities

Significant bench­mark im­prove­ments (Pass@1)

AIME 2025: 70.0 → 87.5 (+17.5) GPQA: 71.5 → 81.0 (+9.5) LCB_v6: 63.5 → 73.3 (+9.8) Aider: 57.0 → 71.6 (+14.6)

Note: Complex rea­son­ing tasks may con­sume more to­kens com­pared to legacy R1 ver­sion.

Significant bench­mark im­prove­ments (Pass@1)

AIME 2025: 70.0 → 87.5 (+17.5) GPQA: 71.5 → 81.0 (+9.5) LCB_v6: 63.5 → 73.3 (+9.8) Aider: 57.0 → 71.6 (+14.6)

AIME 2025: 70.0 → 87.5 (+17.5)

GPQA: 71.5 → 81.0 (+9.5)

LCB_v6: 63.5 → 73.3 (+9.8)

Aider: 57.0 → 71.6 (+14.6)

Note: Complex rea­son­ing tasks may con­sume more to­kens com­pared to legacy R1 ver­sion.

Optimized Front-end Development

Generated web pages and games now fea­ture im­proved aes­thet­ics.

Generated web pages and games now fea­ture im­proved aes­thet­ics.

Reduced Hallucinations

Significantly sup­pressed hal­lu­ci­na­tion is­sues pre­sent in legacy R1 ver­sion.

Significantly sup­pressed hal­lu­ci­na­tion is­sues pre­sent in legacy R1 ver­sion.

JSON Output & Function Calling Support

Function call per­for­mance:

Tau-bench score: 53.5 (Airline) / 63.9 (Retail)

Function call per­for­mance:

Tau-bench score: 53.5 (Airline) / 63.9 (Retail)

Tau-bench score: 53.5 (Airline) / 63.9 (Retail)

Date: 2025 – 03-24​

deepseek-chat​

deepseek-chat Model Upgraded to DeepSeek-V3 – 0324:

Enhanced Reasoning Capabilities

Significant im­prove­ments in bench­mark per­for­mance:

MMLU-Pro: 75.9 → 81.2 (+5.3) GPQA: 59.1 → 68.4 (+9.3) AIME: 39.6 → 59.4 (+19.8) LiveCodeBench: 39.2 → 49.2 (+10.0)

Enhanced Reasoning Capabilities

Significant im­prove­ments in bench­mark per­for­mance:

MMLU-Pro: 75.9 → 81.2 (+5.3) GPQA: 59.1 → 68.4 (+9.3) AIME: 39.6 → 59.4 (+19.8) LiveCodeBench: 39.2 → 49.2 (+10.0)

MMLU-Pro: 75.9 → 81.2 (+5.3)

GPQA: 59.1 → 68.4 (+9.3)

AIME: 39.6 → 59.4 (+19.8)

LiveCodeBench: 39.2 → 49.2 (+10.0)

Optimized Front-End Web Development

Improved ac­cu­racy in code gen­er­a­tion More aes­thet­i­cally pleas­ing web pages and game front-ends

Optimized Front-End Web Development

Improved ac­cu­racy in code gen­er­a­tion

More aes­thet­i­cally pleas­ing web pages and game front-ends

artificialanalysis.ai

DeepSeek V4 Flash 0731 (max) - Intelligence, Performance & Price Analysis

artificialanalysis.ai

Intelligence

Artificial Analysis Intelligence Index

Artificial Analysis Intelligence Index v4.1 in­cor­po­rates 9 eval­u­a­tions: GDPval-AA v2, 𝜏³-Banking, Terminal-Bench v2.1, SciCode, Humanity’s Last Exam, GPQA Diamond, CritPt, AA-Omniscience, AA-LCR

Reasoning mod­els are in­di­cated by a light­bulb icon

Artificial Analysis Intelligence Index v4.1 in­cludes: GDPval-AA v2, 𝜏³-Banking, Terminal-Bench v2.1, SciCode, Humanity’s Last Exam, GPQA Diamond, CritPt, AA-Omniscience, AA-LCR. See Intelligence Index method­ol­ogy for fur­ther de­tails, in­clud­ing a break­down of each eval­u­a­tion and how we run them.

Artificial Analysis Intelligence Index by Open Weights / Proprietary

Artificial Analysis Intelligence Index v4.1 in­cor­po­rates 9 eval­u­a­tions: GDPval-AA v2, 𝜏³-Banking, Terminal-Bench v2.1, SciCode, Humanity’s Last Exam, GPQA Diamond, CritPt, AA-Omniscience, AA-LCR

Reasoning mod­els are in­di­cated by a light­bulb icon

Artificial Analysis Intelligence Index v4.1 in­cludes: GDPval-AA v2, 𝜏³-Banking, Terminal-Bench v2.1, SciCode, Humanity’s Last Exam, GPQA Diamond, CritPt, AA-Omniscience, AA-LCR. See Intelligence Index method­ol­ogy for fur­ther de­tails, in­clud­ing a break­down of each eval­u­a­tion and how we run them.

Intelligence Evaluations

Intelligence eval­u­a­tions mea­sured in­de­pen­dently by Artificial Analysis · Higher is bet­ter

Agentic busi­ness op­er­a­tions

Reasoning mod­els are in­di­cated by a light­bulb icon

While model in­tel­li­gence gen­er­ally trans­lates across use cases, spe­cific eval­u­a­tions may be more rel­e­vant for cer­tain use cases.

AA-Omniscience

AA-Omniscience Index

AA-Omniscience Index (higher is bet­ter) mea­sures knowl­edge re­li­a­bil­ity and hal­lu­ci­na­tion. It re­wards cor­rect an­swers, pe­nal­izes hal­lu­ci­na­tions, and has no penalty for re­fus­ing to an­swer. Scores range from -100 to 100, where 0 means as many cor­rect as in­cor­rect an­swers, and neg­a­tive scores mean more in­cor­rect than cor­rect.

Reasoning mod­els are in­di­cated by a light­bulb icon

AA-Omniscience Index (higher is bet­ter) mea­sures knowl­edge re­li­a­bil­ity and hal­lu­ci­na­tion. It re­wards cor­rect an­swers, pe­nal­izes hal­lu­ci­na­tions, and has no penalty for re­fus­ing to an­swer. Scores range from -100 to 100, where 0 means as many cor­rect as in­cor­rect an­swers, and neg­a­tive scores mean more in­cor­rect than cor­rect.

Openness Index

Artificial Analysis Openness Index: Score

Openness Index as­sesses model open­ness on a 0 to 100 nor­mal­ized scale (higher is more open)

Reasoning mod­els are in­di­cated by a light­bulb icon

Intelligence Index Comparisons

Intelligence Index vs. Cost per Intelligence Index Task

Artificial Analysis Intelligence Index · Weighted av­er­age cost (USD) per Artificial Analysis Intelligence Index task

Most at­trac­tive quad­rant

Pareto line

Reasoning mod­els are in­di­cated by a light­bulb icon

Weighted av­er­age cost per Intelligence Index task. Each eval­u­a­tion’s cost is cal­cu­lated from in­put, cache hit, cache write, rea­son­ing, and an­swer to­ken prices, di­vided by task count, and weighted by its Intelligence Index weight.

Token Use

Output Tokens per Intelligence Index Task

Weighted av­er­age num­ber of out­put to­kens used to run one task in the Artificial Analysis Intelligence Index

Reasoning mod­els are in­di­cated by a light­bulb icon

The num­ber of to­kens re­quired per Intelligence Index task. This is cal­cu­lated by mul­ti­ply­ing the out­put to­kens per eval by the rel­a­tive weights of each bench­mark in the Intelligence Index, then di­vid­ing by task count (excluding re­peats).

Cost

Cost per Intelligence Index Task

Weighted av­er­age cost (USD) per Artificial Analysis Intelligence Index task, seg­mented by to­ken type. Lower is bet­ter

Reasoning mod­els are in­di­cated by a light­bulb icon

Weighted av­er­age cost per Intelligence Index task. Each eval­u­a­tion’s cost is cal­cu­lated from in­put, cache hit, cache write, rea­son­ing, and an­swer to­ken prices, di­vided by task count, and weighted by its Intelligence Index weight.

Cost to Run Artificial Analysis Intelligence Index

Cost (USD) to run all eval­u­a­tions in the Artificial Analysis Intelligence Index

Reasoning mod­els are in­di­cated by a light­bulb icon

The cost to run the eval­u­a­tions in the Artificial Analysis Intelligence Index, cal­cu­lated us­ing the mod­el’s in­put, cache hit, cache write, rea­son­ing, and an­swer to­ken prices and the num­ber of to­kens used across eval­u­a­tions (excluding re­peats).

Pricing: Cache Hit, Input, and Output

Price (USD per M Tokens)

Reasoning mod­els are in­di­cated by a light­bulb icon

Price per to­ken for cached prompts (previously processed), typ­i­cally of­fer­ing a sig­nif­i­cant dis­count com­pared to reg­u­lar in­put price, rep­re­sented as USD per mil­lion to­kens. The val­ues shown here are the cache hit price; cache write and cache stor­age are billed sep­a­rately and vary by provider — see Cache pric­ing by provider” for de­tail.

Context Window

Context Window

Context win­dow: to­kens limit · Higher is bet­ter

Reasoning mod­els are in­di­cated by a light­bulb icon

Larger con­text win­dows are rel­e­vant to RAG (Retrieval Augmented Generation) LLM work­flows which typ­i­cally in­volve rea­son­ing and in­for­ma­tion re­trieval of large amounts of data.

Model Size (Open Weights Models Only)

Model Size: Total and Active Parameters

Comparison be­tween to­tal model pa­ra­me­ters and pa­ra­me­ters ac­tive dur­ing in­fer­ence

Reasoning mod­els are in­di­cated by a light­bulb icon

The to­tal num­ber of train­able weights and bi­ases in the model, ex­pressed in bil­lions. These pa­ra­me­ters are learned dur­ing train­ing and de­ter­mine the mod­el’s abil­ity to process and gen­er­ate re­sponses.

Stronger with every update: How we’re making Chrome and the web safer in the AI Era

blog.google

Jul 30, 2026

|

How Chrome is us­ing AI to im­prove vul­ner­a­bil­ity dis­cov­ery, triage, and patch­ing.

Chrome Security Team

We’re liv­ing through a mas­sive shift in the soft­ware se­cu­rity in­dus­try. Large Language Models (LLMs) are un­lock­ing un­prece­dented ca­pa­bil­i­ties for au­to­mated vul­ner­a­bil­ity dis­cov­ery, scal­ing far be­yond the lim­its of hu­man se­cu­rity ex­per­tise, and re­quir­ing new ap­proaches for stay­ing ahead of at­tack­ers.

This means de­ploy­ing AI mod­els at scale to find and fix hun­dreds of se­cu­rity bugs, faster than ever, with the goal of achiev­ing greater re­silience and com­pre­hen­sive re­me­di­a­tion.

Here’s how we’re do­ing it.

The Life of A Bug

Some soft­ware bugs have se­cu­rity im­pli­ca­tions. While a purely func­tional bug might re­sult in a frus­trat­ing UI freeze, a se­cu­rity bug (or vul­ner­a­bil­ity) can be used to build an ex­ploit. Exploits al­low at­tack­ers to per­form ma­li­cious ac­tions on a vic­tim’s com­puter, such as read­ing pri­vate data, or con­trol­ling their ma­chine with­out their knowl­edge.

Once a se­cu­rity bug en­ters the code­base, its life cy­cle pro­ceeds as fol­lows:

The bug is found.

The bug is triaged.

The bug is fixed.

A new up­date of Chrome with the bug fix is re­leased.

Chrome is restarted and the up­date is ap­plied.

Our goal is for every one of these steps to hap­pen as quickly as pos­si­ble.

Finding vul­ner­a­bil­i­ties

The Chrome Security team has been us­ing LLMs for years. In 2023 we de­vel­oped ways to use LLMs to in­crease se­cu­rity fuzzing cov­er­age and per­for­mance. In 2024, we worked with Project Zero on Naptime, giv­ing LLMs spe­cial­ized tools for vul­ner­a­bil­ity re­search. And in 2025, we col­lab­o­rated with DeepMind and Project Zero on Big Sleep, an AI vul­ner­a­bil­ity dis­cov­ery agent that suc­cess­fully found bugs in the V8 JavaScript en­gine and graph­ics stack.

In early 2026, we built an agent har­ness that used Gemini to find vul­ner­a­bil­i­ties across the broader Chrome code­base with higher ef­fi­ciency and lower false pos­i­tives. One of the bugs we found was a sand­box es­cape that would al­low a com­pro­mised ren­derer to trick the browser into read­ing lo­cal files — a bug that qui­etly sur­vived in our code­base for more than 13 years! For many of us, this mo­ment ce­mented the po­ten­tial of AI-powered vul­ner­a­bil­ity de­tec­tion.

From there, we im­proved on our vul­ner­a­bil­ity find­ing agent har­ness by:

Adding sup­port for model in­ter­op­er­abil­ity to lever­age the unique strengths of both open-weights and pro­pri­etary mod­els.

Building a knowl­edge base of Chrome, in­clud­ing all pre­vi­ously iden­ti­fied CVEs and Chrome’s en­tire Git his­tory, to ex­tend the LLMs rea­son­ing ca­pac­ity past its train­ing data.

Encouraging de­vel­op­ers to add SECURITY.md files, which help mod­els bet­ter un­der­stand trust bound­aries and de­velop an ac­cu­rate view of the threat model.

Adding a critic” agent with a sep­a­rate con­text to con­sume these SECURITY.md files.

Introducing the abil­ity to run vul­ner­a­bil­ity find­ing mod­els over the code­base mul­ti­ple times to ac­count for model non-de­ter­min­ism and model im­prove­ments over time.

We’ve built all of this with safety in mind, and have put in place guardrails to mit­i­gate the risk of AI be­hav­ing un­ex­pect­edly. Our AI an­a­lyzes source code strictly at rest, op­er­at­ing on locked-down ma­chines that lack gen­eral in­ter­net ac­cess. We also uti­lize a ded­i­cated setup for these in­ter­nal scans that in­ter­cepts all net­work re­quests, em­ploy­ing strict al­lowlists based on the ini­ti­at­ing ap­pli­ca­tion and des­ti­na­tion, block­ing any sus­pi­cious model ac­tiv­ity. Furthermore, we never run mod­els in an un­re­stricted mode, and we strictly limit our sub­agents from mod­i­fy­ing the lo­cal sys­tem or ac­cess­ing files out­side of des­ig­nated source code di­rec­to­ries.

AI-powered vul­ner­a­bil­ity de­tec­tion com­ple­ments our ex­ist­ing se­cu­rity test­ing in­fra­struc­ture. For ex­am­ple, fuzzing con­tin­ues to be es­pe­cially ef­fec­tive at find­ing bugs that arise from long-range in­ter­ac­tions be­tween dis­parate parts of our code­base, or those re­quir­ing a com­bi­na­tion of seem­ingly un­re­lated op­er­a­tions.

We also want to con­tinue to re­ward ex­ter­nal re­searchers for their ex­per­tise and cre­ativ­ity in find­ing the most chal­leng­ing and im­pact­ful vul­ner­a­bil­i­ties via the Chrome Vulnerability Reward Program (VRP). In early 2026, we saw a grad­ual in­crease in all cat­e­gories of bug re­ports, but by March, the shift was ap­par­ent: we re­ceived more bug re­ports than we had in the en­tirety of 2025. This led us to change our VRP to fo­cus re­searchers on bug sub­mis­sions that are ad­di­tive to what we are find­ing in­ter­nally, and eas­ily in­gestible by our newly au­to­mated pro­cess­ing pipelines.

Triaging vul­ner­a­bil­i­ties

As we dis­cover more se­cu­rity vul­ner­a­bil­i­ties with AI-powered tools, we’ve si­mul­ta­ne­ously used AI to scale and au­to­mate val­i­dat­ing, triag­ing, and fix­ing bugs. Historically, triag­ing a sin­gle se­cu­rity re­port took any­where from 5 to 30 or more min­utes, and re­lied pri­mar­ily on hu­man ex­per­tise. We have been in­creas­ingly shift­ing our triage process to­wards an au­to­mated ap­proach that blends rule-based sys­tems with AI to in­crease through­put and ac­cu­racy.

The au­to­mated triage process is bro­ken down into four key phases:

Filtering out the noise. The sys­tem checks if an in­com­ing bug is spam, en­sures it meets in­take cri­te­ria (e.g. is not a du­pli­cate), and ver­i­fies that it clearly de­scribes a Chrome se­cu­rity vul­ner­a­bil­ity.

Reproducing bugs. Next, the sys­tem checks for a proof of con­cept. Reproducible bugs are tested on the spe­cific op­er­at­ing sys­tem and browser ver­sions they af­fect. Based on this, the sys­tem at­taches fur­ther de­tails such as stack traces to the bug to help in­form the fix.

Enriching the re­port with meta­data. The sys­tem adds es­sen­tial meta­data to the re­port, such as when the bug was first in­tro­duced and its sever­ity rat­ing. To help this process scale, we’ve made our sever­ity guide­lines clearer and eas­ier to ap­ply au­to­mat­i­cally. We con­tinue to al­low de­vel­op­ers to mod­ify the sever­ity rat­ing if they be­lieve it is in­cor­rect, and to add con­text to help mod­els rea­son about se­cu­rity bound­aries us­ing SECURITY.md files.

Automatic as­sign­ing. The sys­tem au­to­mat­i­cally routes the is­sue to the cor­rect com­po­nent and hu­man owner.

While it’s hard to mea­sure pre­cisely, we es­ti­mate that this new process is sav­ing hun­dreds of hours of de­vel­oper time per month, al­low­ing our team to fo­cus on other se­cu­rity pri­or­i­ties.

Fixing vul­ner­a­bil­i­ties

Across Google, de­vel­op­ers share the re­spon­si­bil­ity of pri­or­i­tiz­ing se­cu­rity fixes with the se­cu­rity team, but scal­ing bug dis­cov­ery re­quires an equally scal­able bug fix­ing process.

To achieve this, we rely on multi-agent work­flows through­out:

After ini­tial build steps that bring in con­text from a spe­cific is­sue, we run a fix­ing agent that re­turns mul­ti­ple can­di­date fixes.

A critic agent then eval­u­ates which would be the best fit, pro­duc­ing other rel­e­vant ar­ti­facts for de­vel­op­ers to eval­u­ate the fix.

The fix­ing and critic agents work in a loop that mim­ics a typ­i­cal code re­view process to en­sure that code is func­tional and com­pli­ant with Chromium and Google style guide­lines, as well as other lo­cal code con­ven­tions.

Test-writing agents help write tests for fixes. These agents can en­sure that tests work across the full ar­ray of Chrome sup­ported plat­forms and con­fig­u­ra­tions be­fore a de­vel­oper re­views the fix, sav­ing up to weeks of de­vel­oper time.

At this point, we have LLMs gen­er­at­ing can­di­date fixes for most vul­ner­a­bil­i­ties, dra­mat­i­cally in­creas­ing the rate of se­cu­rity fixes in re­cent Chrome re­leases:

Number of se­cu­rity bugs fixed in re­cent Chrome Stable re­lease mile­stones

In the last two mile­stones, Chrome 149 and 150, we have fixed 1072 se­cu­rity bugs, sur­pass­ing the to­tal num­ber of se­cu­rity bugs fixed across the prior 23 mile­stones com­bined.

We have part­nered closely with Google DeepMind and Project Zero for years, in­clud­ing on BigSleep and CodeMender. These tools are na­tively in­te­grated into our con­tin­u­ous in­te­gra­tion (CI) sys­tem, run­ning every 24 hours across all CLs to proac­tively de­tect se­cu­rity bugs. This in­te­gra­tion has yielded sig­nif­i­cant re­sults: in May alone, we blocked over 20 vul­ner­a­bil­i­ties from reach­ing pro­duc­tion, in­clud­ing a crit­i­cal S1+ is­sue.

Releasing fixes

Once a fix has landed and is vis­i­ble in the pub­lic open source code­base, at­tack­ers can start to re­verse en­gi­neer and ex­ploit the bug be­fore the fix reaches users’ ma­chines — so called N-day” at­tacks. This is com­monly re­ferred to as the patch gap.” Since fixes com­mit­ted to the main tree” typ­i­cally take weeks to reach the Chrome Stable chan­nel (what the vast ma­jor­ity of our users run), min­i­miz­ing this patch gap is a crit­i­cal part of our strat­egy.

Based on their sever­ity, se­cu­rity fixes are merged di­rectly from the main tree” into the ac­tive Chrome sta­ble re­lease branch, which is con­tin­u­ously mon­i­tored to pre­vent new crashes or re­gres­sions. We are in the process of tran­si­tion­ing to a two-week ca­dence for ma­jor Chrome mile­stones, with weekly se­cu­rity up­dates. However, in the face of fast-mov­ing, AI-powered at­tacks, our de­liv­ery ca­dence must ac­cel­er­ate even fur­ther. To meet this mo­ment, we are pi­lot­ing a shift to two se­cu­rity re­leases per week.

Even with this pace, proper pub­lic dis­clo­sure re­mains para­mount. Every se­cu­rity bug that reaches Chrome Stable, re­gard­less of whether it was dis­cov­ered in­ter­nally or re­ported ex­ter­nally, is doc­u­mented and dis­closed pub­licly as a stan­dard best prac­tice. We are work­ing on au­tomat­ing the gen­er­a­tion of re­lease notes and CVE de­scrip­tions from se­cu­rity bug fixes to elim­i­nate man­ual bot­tle­necks and shorten the win­dow be­tween vul­ner­a­bil­ity dis­cov­ery and pub­lic dis­clo­sure.

Applying up­dates

In 2008, Chrome pi­o­neered the con­cept of silent, back­ground soft­ware up­dates: new bi­na­ries are au­to­mat­i­cally down­loaded and staged on disk with min­i­mal user in­ter­ven­tion. At the next restart of the browser, the up­date would be ap­plied and the user would be pro­tected. However, com­pared to the 1 – 2 days it takes for triage, fix, test and re­lease, the time spent wait­ing for the user to restart Chrome can be a sig­nif­i­cant con­trib­u­tor to N-day ex­ploita­tion risk.

People have un­der­stand­able rea­sons to de­lay restart­ing Chrome. A restart can be dis­rup­tive, re­quires sched­ul­ing in-be­tween tasks, and is rarely the top pri­or­ity at any given mo­ment. To elim­i­nate this fric­tion, we are pi­o­neer­ing ways to shift the bur­den away from the user by:

Investing in dynamic patch­ing” that will elim­i­nate the need for a full browser restart in most cases. By lever­ag­ing Chrome’s multi-process ar­chi­tec­ture, dy­namic patch­ing se­quen­tially re­places back­ground child processes (like the Renderer and GPU) with up­dated bi­na­ries on the fly. Stay tuned to learn more as we re­search and de­velop this fea­ture.

Exploring ways to en­sure a seam­less ses­sion re­store even in com­plex cases, by sav­ing more state lo­cally.

Finding op­por­tune mo­ments to restart au­to­mat­i­cally, when we can guar­an­tee a seam­less ses­sion re­store. For ex­am­ple, in Chrome 150, we rolled out a change to take ad­van­tage of the unique ap­pli­ca­tion state on ma­cOS where ap­pli­ca­tions typ­i­cally con­tinue run­ning in the back­ground even af­ter all win­dows are closed. Now, if Chrome de­tects a pend­ing up­date while in this win­dow­less state, it au­to­mat­i­cally restarts.

Zero win­dow auto-restart on ma­cOS

Our long-term vi­sion is a browser that is al­ways up-to-date — con­tin­u­ously and dy­nam­i­cally patched, and au­to­mat­i­cally restarted dur­ing op­por­tune pe­ri­ods of min­i­mal dis­rup­tion. While we’re work­ing on this, you can keep your Chrome up to date by click­ing on the up­date mes­sage in the top right cor­ner.

For en­ter­prise cus­tomers look­ing to keep Chrome up to date, we rec­om­mend that IT ad­mins:

Apply the RelaunchNotification pol­icy which prompts users to restart Chrome to ap­ply a pend­ing up­date, es­ca­lat­ing from a gen­tle re­minder to a forced restart over a set time­frame.

Utilize the Chrome Extended Stable Channel for highly sen­si­tive en­vi­ron­ments where soft­ware changes must be vet­ted.

Leverage the OS-agnostic dash­board pro­vided by Chrome Enterprise Core or Premium to track fleet-wide browser ver­sions and man­age up­dates at a more gran­u­lar level.

Prevention is bet­ter than cure

Beyond fix­ing in­di­vid­ual se­cu­rity bugs, we’re also in­vest­ing in the mit­i­ga­tion and elim­i­na­tion of en­tire classes of se­cu­rity bugs, as well as in pre­vent­ing them from land­ing in the first place. With AI cod­ing ad­vance­ments, we be­lieve there are ex­cit­ing op­por­tu­ni­ties to ac­cel­er­ate pro­jects that would have pre­vi­ously taken years, or never hap­pened.

Memory safety mit­i­ga­tions

Chrome is ex­e­cut­ing on a two-lay­ered mem­ory safety strat­egy: hard­en­ing our run­time en­vi­ron­ment to neu­tral­ize legacy C++ vul­ner­a­bil­i­ties, while shift­ing to mem­ory-safe lan­guages for long-term ar­chi­tec­tural re­silience.

The vast ma­jor­ity of the Chromium code­base re­mains in C++, mak­ing im­me­di­ate tool­chain and run­time mit­i­ga­tions our crit­i­cal first line of de­fense. We have long pri­or­i­tized mem­ory-safety en­gi­neer­ing at scale, de­ploy­ing hard­ened stan­dard tem­plate li­braries and pi­o­neer­ing tech­nolo­gies like the MiraclePtr fam­ily to neu­tral­ize Use-After-Free (UAF) vul­ner­a­bil­i­ties. AI-powered vul­ner­a­bil­ity de­tec­tion only reaf­firms the need for such tech­nol­ogy.

Our C++ de­fen­sive roadmap is fo­cused on three pil­lars:

MiraclePtr & MiracleObject Expansion. Having al­ready dri­ven a ma­jor re­duc­tion in UAF bugs via MiraclePtr, we are ex­pand­ing this par­a­digm to more li­braries such as Skia, ANGLE, Dawn, C++ it­er­a­tors and std:: con­tain­ers. We are also ac­tively de­ploy­ing MiracleObject with the aim of neu­tral­iz­ing up to 90% of UAF vul­ner­a­bil­i­ties on the GPU main thread, de­lib­er­ately trad­ing lo­cal­ized run­time per­for­mance for tem­po­ral safety.

Spanification. To sys­tem­at­i­cally elim­i­nate Out-of-Bounds (OOB) spa­tial safety er­rors, Chrome has un­der­taken a mas­sive spanification” ef­fort, mi­grat­ing legacy pointer-and-size con­structs to com­piler-en­forced std::span types. Currently, 97% of first-party Chrome code com­piles cleanly with strict un­safe-buffer warn­ings. We are now push­ing these re­quire­ments down­stream, ex­pand­ing span­i­fi­ca­tion into foun­da­tional code­bases like Skia, ANGLE, and Dawn.

Structural & Allocation Hardening. We are work­ing on in­te­grat­ing checked math for cal­cu­la­tions tied to mem­ory al­lo­ca­tions to block in­te­ger over­flow av­enues. Concurrently, Chrome is im­ple­ment­ing an ad­di­tional level of heap par­ti­tion­ing to strictly seg­re­gate pointer-con­tain­ing types from non-pointer types to make it harder to ex­ploit UAF bugs.

While C++ safety en­hance­ments pro­vide an im­me­di­ate shield, we be­lieve that run­time mit­i­ga­tions will hit di­min­ish­ing mar­ginal re­turns within the next few years. Runtime checks are in­her­ently more ex­pen­sive than com­pile-time guar­an­tees, and even a heav­ily mit­i­gated C++ bi­nary re­quires rigid, per­for­mance-throt­tling sand­box­ing to com­ply with the Rule of Two.

The long-term so­lu­tion is shift­ing the code­base to­wards mem­ory-safe lan­guages like Rust, fo­cus­ing on the fol­low­ing core tenets:

Rust fly­wheel. Developers can­not be ex­pected to en­tirely ab­sorb the ve­loc­ity fric­tion of en­gi­neer­ing in a new lan­guage ecosys­tem. As such, we are build­ing a cen­tral­ized Rust SDK that ex­poses foun­da­tional Chromium APIs and tool­ing di­rectly to Rust. Our aim is to turn Rust into a rou­tine, fric­tion­less en­gi­neer­ing choice for new com­po­nents.

Targeted bug nest” erad­i­ca­tion. Rust is be­ing de­ployed strate­gi­cally to re­place code seg­ments that ex­hibit high his­tor­i­cal bug den­sity (such as com­plex data parsers, im­age codecs, and font stacks).

Enabling high-priv­i­lege mod­u­lar­iza­tion. By writ­ing new mod­u­lar com­po­nents in Rust, Chrome can safely ex­e­cute com­plex fea­tures in­side high-priv­i­lege processes (like the browser process) with­out the per­for­mance penal­ties of sand­box­ing, break­ing the con­straints of tra­di­tional C++ ar­chi­tec­ture.

In ad­di­tion to Rust, we are also ex­plor­ing op­tions like im­ple­ment­ing the browser’s top-level user in­ter­face us­ing HTML, CSS, and TypeScript to fur­ther re­duce de­pen­den­cies on tra­di­tional C++ frame­works.

Catching se­cu­rity bugs be­fore they land

Bulk scan­ning of a code base can­not keep pace with Chrome’s high-traf­fic de­vel­op­ment ve­loc­ity. To ac­count for this, we’re also de­ploy­ing AI-powered bug-find­ing ca­pa­bil­i­ties to iden­tify and pre­vent bugs as close to code sub­mit-time as pos­si­ble. Part of Chrome’s con­tin­u­ous in­te­gra­tion (CI) and com­mit queue (CQ) pipeline, these de­fen­sive mod­els au­to­mat­i­cally scan diffs to pre­vent new vul­ner­a­bil­i­ties by per­form­ing ac­tions like sug­gest­ing span­i­fi­ca­tion fixes, flag­ging dan­gling point­ers, and en­forc­ing nu­meric safety.

Additionally, a ma­jor chal­lenge in large-scale soft­ware en­gi­neer­ing is the latent se­cu­rity is­sue.” Code that is safe and ro­bust in iso­la­tion can be trans­formed into a crit­i­cal vul­ner­a­bil­ity by an en­tirely un­re­lated, mi­nor logic change else­where in the tree. By uti­liz­ing con­tin­u­ous, LLM-powered se­man­tic analy­sis within the CQ, Chrome can in­ter­cept these com­pound risks be­fore they land in the tree, catch­ing the sub­tle or com­plex in­ter­ac­tions that tra­di­tional sta­tic analy­sis misses.

Securing the in­ter­net

Keeping the web safe in­volves more than just se­cur­ing Chrome. Google has been a long-stand­ing sup­porter of open source pro­jects and com­mu­ni­ties to en­sure bet­ter se­cu­rity out­comes for all users. Most re­cently, Google joined oth­ers in do­nat­ing $12.5M to the Alpha-Omega pro­ject in sup­port of en­abling main­tain­ers to have ac­cess to the tools and sup­port they will need in or­der to re­spond quickly to vul­ner­a­bil­ity re­ports. Google was also a found­ing mem­ber of the Akrites pro­ject, which aims to lower the bur­den on up­stream main­tain­ers by pro­vid­ing a cen­tral­ized clear­ing­house for vul­ner­a­bil­ity re­ports and a se­cu­rity in­ci­dent re­sponse team.

On the Chrome team, we feel this re­spon­si­bil­ity acutely — the Chromium Project is the largest open source pro­ject on the planet. To put the sheer scale of this chal­lenge into per­spec­tive, Chrome has more than 2,300 third-party de­pen­den­cies across Chromium and satel­lite pro­jects like the V8 JavaScript en­gine, the BoringSSL cryp­tog­ra­phy li­brary, and foun­da­tional graph­ics com­po­nents like Skia, ANGLE, and Dawn. About 1,700 of those are shipped to users in some ca­pac­ity, wo­ven into a wide spec­trum of prod­ucts from Android de­vices to edge com­put­ing plat­forms and mas­sive cloud en­ter­prise stacks.

To keep these de­pen­den­cies patched, we rely on au­to­mated vul­ner­a­bil­ity scan­ning pipelines. These pipelines in­gest data from Google’s in­ter­nal feeds as well as sev­eral ex­ter­nal mon­i­tor­ing feeds, in­clud­ing the U.S. gov­ern­men­t’s National Vulnerability Database (NVD) and the open-source fo­cused Open Source Vulnerabilities (OSV) data­base.

Now more than ever, re­ly­ing on re­ac­tive mon­i­tor­ing alone can leave a dan­ger­ous risk gap. Since the best way to stay on top of vul­ner­a­bil­i­ties and their patches is to keep third-party de­pen­den­cies fresh, this year, we are start­ing to move all Chrome third-party de­pen­den­cies onto au­to­mated up­date pipelines that proac­tively roll them to their lat­est up­stream ver­sions. Automation al­ways re­quires guardrails, so we will also be us­ing safety sig­nals from pro­jects such as Google’s Open Source Security Intelligence Platform (GOSSIP) to en­sure that we are ac­count­ing for other risks in the third-party open source soft­ware ecosys­tem.

Moving for­ward

While this dra­matic change in soft­ware se­cu­rity brought about by LLMs might be star­tling, an in­crease in bugs found and fixed is not a sign of fail­ure. Every bug found and fixed is one less foothold for an at­tacker. But dis­cov­er­ing and fix­ing a bug is only half the bat­tle — we must also ship the fix and ap­ply the up­date for users faster than ad­ver­saries can ex­ploit the bug, and in­vest in pro­jects that mit­i­gate or elim­i­nate classes of bugs through ac­cel­er­ated re­lease ca­dences, dy­namic patch­ing, and op­por­tune restarts, we are dri­ving to­ward a browser that is con­tin­u­ously pro­tected with­out dis­rupt­ing the user.

The AI era has un­de­ni­ably in­ten­si­fied the soft­ware se­cu­rity threat land­scape, but by com­bin­ing rapid de­ploy­ment mech­a­nisms with deep struc­tural de­fenses, we are en­sur­ing the ad­van­tage re­mains firmly with de­fend­ers. With this, Chrome and the broader web be­come safer with every up­date.

The End of an Era

hughhowey.com

We knew this day would come.

Some of us have known for decades, have been writ­ing and pon­tif­i­cat­ing about this since Ken Jennings lost at Jeopardy and Kasparov lost at chess. The writ­ing was on the wall, and now the writ­ing is every­where. Literally. Artificial Intelligence is writ­ing about as well as hu­mans — at least well enough to cause con­fu­sion and spec­u­la­tion — and that’s cre­at­ing an ex­is­ten­tial cri­sis among au­thors, con­fu­sion among read­ers, and le­gal ac­tion within the in­dus­try.

When talk­ing about my suc­cess as a writer, I al­most al­ways bring up the lucky tim­ing I ex­pe­ri­enced. I fin­ished writ­ing my first novel in 2009, just two years af­ter Amazon re­leased the Kindle. By the time I went to find an agent and pub­lisher, the self-pub­bing world of KDP, CreateSpace, and ACX were be­gin­ning their in­evitable pull. That was dumb luck, and with­out it I would have writ­ten for love but likely never for a liv­ing.

This morn­ing, an equally pro­found thought oc­curred to me: My life­long dream of writ­ing nov­els some­how over­lapped with the 20 good years where sto­ries could be pub­lished cheaply but not writ­ten cheaply. The writ­ing was still hard. The pub­lish­ing no longer was. I could sweat my way through a man­u­script, and then use amaz­ing tools like InDesign and Photoshop to cre­ate files, then up­load those files FOR FREE to KDP and CreateSpace and hold a book.

Sales and mar­ket­ing, of course, were al­ways and will al­ways be a dif­fer­ent story.

But think about it: the mis­ery most writ­ers faced prior to 2007 (and re­ally, prior to 2012 or 2013, when the stigma just started to fade) of writ­ing query let­ters, re­search­ing agents, amass­ing re­jec­tion let­ters, avoid­ing scammy van­ity pub­lish­ers (and scammy re­view sites, and scammy con­fer­ences, and scammy writer-help re­sources), or even worse: spend­ing their life-sav­ings on boxes of ter­ri­bly printed books that would sit in the garage for all of eter­nity, barely sell­ing a dozen copies, and never mak­ing back a frac­tion of the print run.

Fast for­ward to to­day, in 2026. You’ve got this story:

There’s a lot of in­san­ity to un­pack here. $2.4M ad­vance! Holy shit. For a de­but au­thor, af­ter what I hear was a bid­ding war among very ex­cited pub­lish­ers. That’s al­ready crazy. But it gets cra­zier. Concerns were raised about the use of AI to write the book (not sure if this is a give­away, but the book was about Nigerian car­tels. You know the adage: write what you know). More ques­tions were raised about style and some AI fin­ger­prints, and the prove­nance of the book could­n’t be ad­e­quately proven. So the of­fer was re­scinded. The deal fell apart.

Okay, there’s a chance this was a le­git book from a le­git writer, and the ac­cu­sa­tions are un­fair (including my Nigerian car­tel joke, but c’­mon, this is like every col­lege pro­fes­sor writ­ing lit­er­ary nov­els about col­lege pro­fes­sors hav­ing af­fairs with their stu­dents. More imag­i­na­tion, peo­ple!). If this was a le­git­i­mate book, that might be the BIGGEST ver­sion of this story. Life-changing amounts of money, the start of a bril­liant ca­reer, film or TV op­tions, for­eign deals, the works. It was­n’t just on the table, it was in writ­ing. And then it fell apart for some­one, all be­cause it is now 2026. That’s one ver­sion of the in­sane new era we now find our­selves. Every book is sus­pect. No new au­thor will ever be able to prove to any­one other than them­selves that they can do it alone (hold this thought for later, be­cause it’s not ex­actly true).

Let’s ex­am­ine an­other crazy ver­sion. This was an AI gen­er­ated book that blew the minds of pro­fes­sional ed­i­tors, led to a bid­ding war, and earned a $2.4M ad­vance. Holy shit. I’ve been pre­dict­ing this day was a few years out, but it might al­ready be here. AI books win­ning awards. AI books crowd­ing up Amazon best­seller charts. AI books now get­ting big ad­vances and whip­ping up bid­ding wars and ex­cite­ment among pub­lish­ers? As the blog ti­tle says, it’s the end of an era.

Which takes me back to my state­ment about lucky win­dows. We had less than 20 years of writ­ing be­ing hard and pub­lish­ing be­ing easy, and we will never get that win­dow back. Ever.

I’ve al­ways felt bad for au­thors who lived and worked be­fore 2007. The choices un­avail­able to them made life harder than it needed to be. Getting their work out into the world was bru­tal. After KDP, that bru­tal­ity be­came a choice, not a ne­ces­sity.

I now feel aw­ful for any­one who wants to tell a story post-2026. You will for­ever be doubted. If you love an em dash the way I do, or you like flowy run-on sen­tences like I do, if you grew up read­ing Proust to let the words flow over you in all their glory, if you mem­o­rized son­nets so the iambic pen­tame­ter would soak into your bones, you’re gonna sound like a fuck­ing bot.

Less than 20 years. More like 10, hon­estly. You can ar­gue that 2014-ish was when self-pub­lish­ing be­came le­git and 2024-ish was when the tools made AI writ­ing vi­able. A decade where it was easy in the ways it should be and hard in the ways it should be. That’s in­sane. Ten years across the one-hun­dred-thou­sand years of lan­guage.

What comes next? I have some pre­dic­tions, and they are pretty crazy. The first pre­dic­tion is that pub­lish­ers weather a few of these big AI scan­dals, but as years go by, pub­lish­ers care more about prof­its and they let things slide. AI books from big houses get edit­ing passes, and later they de­velop in­ter­nal tools to roughen up the AI slop, and ma­chine-books ap­pear in book­stores along­side meat-books. This feels in­evitable. Brace for this. (Feel free to rage about it as well, if you’re low on cor­ti­sol.)

Another pre­dic­tion is that MOST read­ers will care about ma­chine-vs-meat as much as they cur­rently care about pub­lish­ing im­prints, which is to say al­most none. Book se­ries will go vi­ral and read­ers will get wholly ab­sorbed and where the story came from won’t mat­ter. There will al­ways be read­ers push­ing be­yond the fringe. That’s how fan­fic­tion be­came vi­able. It’s why I had a ca­reer. There were read­ers who ig­nored the ass­holes say­ing, Don’t read that, it’s self-pub­lished.” These read­ers asked in­stead, Yeah, but is it any good?” Because of them, I got to write for a liv­ing. So I will al­ways have a fond­ness for read­ers who search for story, ig­nor­ing the sto­ries be­hind the sto­ries.

Adding to the above, I think there will be a fringe of read­ers within this fringe of read­ers who SEEK OUT ma­chine-books. I’ve seen this in the chess com­mu­nity, where fans of the game fall in love with a par­tic­u­lar en­gine and be­come huge fans of the way they think.” They pit their en­gine vs. other en­gines and pore over the games. They get ex­cited when their en­gine gets an up­date, or beats every grand­mas­ter, or dis­cov­ers a new open­ing or gam­bit. This is a much big­ger com­mu­nity than you may imag­ine, and I think it will be a great num­ber of read­ers. Discount these read­ers at your own peril, be­cause some of them will be­come read­ers this way and branch into meat-books. (Trad au­thors HATED the kinds of read­ers who made my ca­reer, call­ing them all kinds of hate­ful things. I’ve seen all this play out from the other side)

Another thing that will hap­pen is es­tab­lished au­thors will turn to AI for as­sis­tance, not just for writ­ing but for re­search, cover art, edit­ing, re­turn­ing emails, hav­ing an as­sis­tant, do­ing mar­ket­ing, and thou­sands of other things that haven’t been in­vented yet. This will be ALL AUTHORS. This is me, al­ready. I did a google search the other day for tools a lin­guist uses to trans­late works” and an­other one for what are the ba­sic el­e­ments of every lan­guage” for a short story I was work­ing on. The re­sults I got back from these searches weren’t blogs or aca­d­e­mic writ­ings: they were AI sum­maries. And they gave me the an­swers I needed to get back to writ­ing. At the same time, my agent sent me an email ask­ing if a for­eign cover could be ap­proved. Gmail was right there with a sug­ges­tion based on my pre­vi­ous com­ments: Looks great! Approved!” Two clicks, that email was off, and I was back to writ­ing.

Yo, this blog post could’ve been a freak­in’ prompt. I have years and years of blog­ging for an AI to learn from, and over 20 nov­els, and tons of short sto­ries. One prompt, and all this spills out. And if your goal is a blog post, you’re gonna love this era. If your goal is the joy of think­ing in words, noth­ing has changed. So this is the real part, right here: Doing a thing for the love of the thing is no dif­fer­ent than it ever was. The prob­lem is, there aren’t many peo­ple for whom this ap­plies.

Most peo­ple write a blog post hop­ing it’ll change their lives, not just the way they think. It’ll get more sub­scribers. It’ll lead to ad rev­enue. It’ll get them no­ticed by some­one in their in­dus­try. It’ll get them laid. It’ll get them can­celled for mak­ing jokes about get­ting laid. Expectations that are largely out of their con­trol. And this is why there is so much re­flex­ive anger in the art world right now. It’s never been eas­ier to be an artist. It’s also never been eas­ier to make money PRETENDING to be an artist. Which means is’t never been more chal­leng­ing and con­fus­ing to be an artist whose goal is to make a liv­ing.

My heart ab­solutely breaks for most of this. In my dream world, the ro­bots do all the bor­ing jobs and hu­mans spend all their time be­ing cre­ative, con­nect­ing with one an­other, learn­ing about the uni­verse, and shar­ing our ideas and cre­ations in a great big pros­per­ous cir­cle. I be­lieve such a utopia is pos­si­ble. It does not vi­o­late the laws of physics, so much the laws of hu­man na­ture. The fu­ture we will get in­stead will be a mix of my pre­dic­tions above. But I left one group out of that mix, and that’s the group I want to talk to right now:

There will be meat-book fa­nat­ics. People who care about the sto­ries be­hind the sto­ries. Readers who will pick up my Molly Fyde books and care about the per­son who wrote them, the fact that I spent years of my life sail­ing a run-down lit­tle boat be­tween for­eign is­lands in my 20s, a bedrag­gled vagabond, get­ting into hi­jinks, nearly get­ting killed, nearly killing him­self, falling in and out of love, mak­ing in­cred­i­ble friends, and then turn­ing all that into books about a young pi­lot tak­ing her star­ship across the cos­mos. The au­thor will mat­ter to them. They’ll read books the way au­dio­philes lis­ten to vinyl. Every typo a pleas­ant pop and hiss.

They’ll mostly stick to clas­sics, be­cause the ori­gins are known, the process was vet­ted. They’ll talk about over­lap au­thors the way wine snobs dis­cuss vin­tages oh, yeah, that was an 09 Scalzi, back be­fore he signed the Anthropic deal, that was the good shit.” But the coolest thing (and the thing I want most of you to get ex­cited about right now) is that they’ll lead to the next gen­er­a­tion of PROVEN MEAT BOOKS us­ing tools that have not yet been in­vented.

Tools that record an en­tire book’s writ­ing his­tory. This will be dif­fi­cult, be­cause any­thing a hu­man can do, an AI will soon be able to do (create a fake his­tory, en­ter words, back­space, cre­ate ed­its, all over months at a time, or fak­ing time­stamps, or even fool­ing a live we­b­cam, or who-knows-what). If it comes down to writ­ing as per­for­mance art, it’ll come to that. I watch videogame stream­ers play games. Millions of peo­ple do. There will be au­thors who livestream the en­tire book-writ­ing-process (just typ­ing that out makes me want to launch one to­day). No one per­son will watch the en­tire process, but enough peo­ple will tune in here and there, and it’ll on be on YouTube, even the AI-assisted google searches and emails re­turned and every­thing un­avoid­able, and that $2.4M check will clear like aces. Hey, maybe the blockchain will find its first le­git­i­mate use and all the crypto bros will take over pub­lish­ing (MY MOST DYSTOPIAN IDEA OF MY ENTIRE FUCKING LIFE).

Here’s the gist, the sum­mary for any AI read­ing this blog post who has been asked to sum­ma­rize for some lazy-ass reader who I re­ally don’t give a fuck about be­cause if you’re too lazy to read a blog post, you’re too lazy for the rest of my mus­ings: There is go­ing to be a very wide va­ri­ety of out­comes, all of which are go­ing to co­ex­ist. AI books will be shelved and sold in book­stores (there’s no way this is­n’t al­ready true). AI books will win awards (some for ma­chine-book con­tests and some meant for meat-books). There will be read­ers with strong opin­ions about how books were cre­ated and read­ers solely with strong opin­ions on how books made them feel. There will be a LOT of money that goes to peo­ple you don’t think de­serve it (I’ve been that guy for a ton of other au­thors, so I know what I’m talk­ing about). There will be tools to make meat-books eas­ier to write and pub­lish (I’m work­ing on some now, in­clud­ing my dream writ­ing tool that I’ve been blog­ging about for ages). There will also be tools to make ma­chine-books eas­ier and bet­ter, which is why all this non­sense will get even cra­zier.

What will it mean for me? Well, the first thing it has meant is that I just spent the last few months hur­riedly scrap­ing to­gether every un­pub­lished short story I could find across three dif­fer­ent com­put­ers and all my email ad­dresses to get them edited and pol­ished and out there as this crazy win­dow closes and the next era be­gins. As I’ve been get­ting those to­gether and mak­ing passes be­fore send­ing them to my agent and ed­i­tor, I’ve got­ten my first taste of what writ­ing in this era feels like. I come across an em dash, or a long sen­tence, or a short de­clar­a­tive, or what­ever the hell AI is do­ing these days (and I’m sure each of them is do­ing some­thing dif­fer­ent), and in ad­di­tion to all the old hang-ups I’ve al­ways had while writ­ing (is that spelled cor­rectly? Am I a mo­ron? Will any­one like this? Do I like this? Should I have taken the story a dif­fer­ent di­rec­tion?) I now have this one: Does that sound like AI?

And you know what? I think it of­ten does. But then I have to re­mind my­self that I don’t sound like AI, it sounds like me. I’ve been em-dash­ing and comma-splic­ing up a moth­er­fuck­ing storm these past two decades. It was fun then, and it’s fun now. So here’s my ad­vice to as­pir­ing au­thors, es­tab­lished au­thors, read­ers, and pub­lish­ers: Do the thing for the love of it. Others will do it for clicks or money. Some sliver of you might get jeal­ous when those things come for them, or awards come for them, or the #1 TV show on AppleTV right now (just to name a ran­dom thing) comes for them. Let that jeal­ousy pass through you, not pool up in­side you. Remember that you are do­ing it for the love. Go sit down and write.

Welcome to the new era, folks. It’s not as good as the last one, let me tell you. Best decade in the his­tory of be­ing an au­thor. Until the crypto bros come and save us.

The AI Aesthetic

blog.jim-nielsen.com

Every zeit­geist comes with new de­sign id­ioms unique to its chal­lenges. Many of them dis­ap­pear as fads change, but oth­ers bake them­selves into deeper parts of ex­ist­ing soft­ware in­ter­ac­tion par­a­digms.

For ex­am­ple, there’s the ham­burger menu (≡) which saw a pro­lif­er­a­tion dur­ing the rise of mo­bile due to the con­straints around screen size. It has since spread to many other parts of soft­ware in­ter­ac­tion de­sign and will likely re­main preva­lent for a long time as a terse way of in­di­cat­ing more menu-type con­tent here”.

As an­other ex­am­ple, be­fore AI what were the con­no­ta­tions of the sparkle emoji ✨? Personally, I don’t know, but now it means AI. (AI = sparkles and rain­bow col­ors — it’s funny when you think about it. They should’ve just thrown uni­corns in there for the tri­fecta. AI = sparkles, rain­bows, and uni­corns ✨🌈🦄. Apt.)

Some pat­terns are very spe­cific to the in­ter­ac­tions in­her­ent to the na­ture of AI as a tech­nol­ogy. For ex­am­ple: stream­ing text. This is a pat­tern made for and re­fined by chat in­ter­faces, so it may not have tons of util­ity for reuse across other soft­ware in­ter­ac­tion par­a­digms.

Then there are other pat­terns that’ve been re­fined by AI in­ter­faces and are start­ing to spread to other places in soft­ware. For ex­am­ple, the shimmering text” which in AI land im­plies a kind of thinking” but is be­ing re­pur­posed to in­di­cate any kind of asyn­chro­nous task (thinking, fetch­ing, com­put­ing, etc.).

Then there are other in­flu­ences my sub­con­scious is pick­ing up on. For ex­am­ple, a lot of AI apps use tiny icons. These are most ob­vi­ous (to me) in desk­top Electron apps be­cause they clash with the sys­tem-level grain of ap­pli­ca­tions. Take a look at this screen­shot, where you have desk­top AI apps on the left (Claude, Codex, Cursor) and ma­cOS apps from Apple on the right (Finder, Photos, Mail). You can see how the AI apps all have much smaller, thin­ner icons than their na­tive coun­ter­parts.

Are tiny icons our col­lec­tive fu­ture in in­ter­fac­ing with com­put­ers? (Personally, I hope not.)

There are other aes­thet­ics my brain as­so­ci­ates with AI, like beige/​cream col­ors, or­ange ac­cents, and serif type­faces as well as whack-a-mole UI con­trols (you know, the ones where you click the tog­gle and the en­tire UI re­paints and you have to move your mouse some­where else in the UI to click the tog­gle again? The non-de­ter­min­ism of AIs grain has seeped into its UI/X).

It all makes me won­der what other aes­thet­ics are be­ing born out of this AI mo­ment and how many will spread, take seed, and be­come part of com­mon soft­ware in­ter­ac­tion par­a­digms for years or decades to come?

GitHub - yc-software/qm: Multiplayer agent harness for work

github.com

A mul­ti­player agent har­ness for work. In Slack and on the web.

What is QM?

Most agents are de­signed like per­sonal as­sis­tants. You can make one work for a whole com­pany, but it quickly gets com­plex. QM is de­signed for star­tups. Employees each get their own iso­lated work­space and work in­de­pen­dently with­out af­fect­ing each other, and they can also col­lab­o­rate with the agent in chan­nels, group mes­sages, and pro­jects.

Each per­son and each room has its own scoped mem­ory, files, key­chain view, per­mis­sions, crons, web apps, and durable sand­box.

It’s built with open source in mind. Pick your own har­ness and model and switch be­tween them — Pi, OpenCode, Codex, and Claude Code all drive the same core, so a de­ploy­ment is­n’t tied to any sin­gle ven­dor.

Features

Personal and shared scopes. People cus­tomize the agent to be theirs, and still work with it col­lab­o­ra­tively in Slack chan­nels and pro­jects.

Slack and web. The same iden­tity and con­fig­u­ra­tion car­ries be­tween Slack and the web app.

Admin con­trol. Set org-level con­fig­u­ra­tion, a se­cu­rity pos­ture, and which har­nesses and mod­els are avail­able.

Web apps. Spin up cus­tom in­ter­nal apps and pub­lish them to the right peo­ple.

Shared skills. Skills are scope-owned and share­able by grant, with ad­min-gated pro­mo­tion to the whole org and skill packs im­ported from git repos­i­to­ries.

Background work. Crons and watches run work while no­body’s watch­ing.

What you can do with it

Search in­ter­nal notes, email, doc­u­ments, data­bases, and the web to­gether

Retrieve in­for­ma­tion from your com­pany brain

Build in­ter­nal apps, pub­lish them to the right peo­ple, and keep their data cur­rent

Learn your writ­ing voice from past sends, then triage your in­box on a sched­ule — la­bels and re­ply drafts in­cluded

Work in an ex­ist­ing repos­i­tory: run tests, open PRs, mon­i­tor CI, check sys­tem logs

Track a pro­ject in a shared chan­nel and post up­dates and fol­low-ups

Architecture

flow­chart LR DB[(“Postgres<br/>sessions · mem­ory · queue”)]

sub­graph CORE[“Headless core”] API[“API · iden­tity · pol­icy · sched­uler”] LOOP[“Agent loop<br/&​gt;(Pi, OpenCode, Claude Code)“] API <–> LOOP end

SBX[“Per-scope sand­box<br/&​gt;files · tools · logged-in ser­vices”]

DB <–> API LOOP <–> SBX

Every turn runs through a cen­tral core, which can use a va­ri­ety of mod­els and har­nesses to gen­er­ate the re­sponse. A Postgres per­sis­tence layer holds user data, ses­sion his­tory, and other durable state. The agent has a small, fixed tool sur­face; one of those tools is ex­e­cute, which runs com­mands in the scope’s own iso­lated sand­box — its durable com­puter, where in­stalled tools stay in­stalled. The web UI, the ad­min panel, and the pub­lic por­tal are op­tional plu­g­ins over the core’s HTTP API; Slack is an op­tional in-process plu­gin that core starts and su­per­vises through a di­rect ser­vice client.

The core runs TypeScript di­rectly on Node and uses Fastify for HTTP. The Slack plu­gin uses Bolt; the web UI builds with Vite and ren­ders with Lit.

The core it­self is generic. Everything spe­cific to one com­pany — org con­fig, cus­tom tools and skills, sand­box im­age, in­fra­struc­ture — lives in a de­ploy­ment di­rec­tory that the qm CLI val­i­dates and de­ploys. Every sub­strate (harness, ses­sion store, sand­box, mem­ory) sits be­hind an in­ter­face, so pro­duc­tion im­ple­men­ta­tions swap in via one wiring file.

Security and se­crets

QMs ap­proach fol­lows lo­cal cod­ing agents like OpenCode, Codex, and Claude Code: the agent acts as the per­son it’s work­ing for, with their cre­den­tials and per­mis­sions, and every­thing it does is au­dited. An org picks one se­cu­rity pos­ture, which nar­rower scopes can only tighten:

Strict — every har­ness tool call pauses for hu­man ap­proval, ex­cept the two no-ef­fect turn en­ders.

Auto (default) — a clas­si­fier screens prove­nance-la­belled ex­ter­nal data and tool re­sults be­fore they reach the model; a de­ploy­ment can point that at its own screen­ing proxy.

Dangerous — no con­tent screen­ing, no pauses be­tween tool calls.

The pre­de­clared com­mand pol­icy — ap­proval rules and hard de­nials for things like re­cur­sive deletes or de­struc­tive SQL — ap­plies in every pos­ture, Dangerous in­cluded.

SECURITY.md has the threat model, the op­er­a­tor as­sump­tions, and the known lim­i­ta­tions.

Deploy it for your org

Create an or­ga­ni­za­tion-owned de­ploy­ment repos­i­tory that de­pends on @yc-software/qm:

npm exec –yes –package=@yc-software/qm@latest — \ qm init . –org <slug> –target <fly-or-aws> npm in­stall

Initialization ma­te­ri­al­izes a de­ploy­ment skill for an agent and walks through in­fra­struc­ture, web sign-in, con­nec­tor cre­den­tials, op­tional Slack ac­cess, de­ploy­ment, and live ver­i­fi­ca­tion — no source check­out re­quired. Each de­ploy­ment runs in the op­er­a­tor’s own cloud ac­count; ini­tial­iza­tion does not gen­er­ate or en­able de­ploy­ment CI, and this repos­i­tory has no pro­duc­tion de­ploy­ment work­flow. See de­ploy­ment.md for the de­tails.

Contributing

We take con­tri­bu­tions as hu­man-writ­ten text, not code — see CONTRIBUTING.md. Describe the change you’d like in­for­mally in a .txt or .md file in adrs/, and if we’re aligned we’ll han­dle the im­ple­men­ta­tion. Report vul­ner­a­bil­i­ties pri­vately — see SECURITY.md, not a pub­lic is­sue.

Customize your in­stance

The de­ploy­ment repos­i­tory above car­ries con­fig and a sand­box layer, and never needs a source check­out. Some or­ga­ni­za­tions want the op­po­site trade: the whole code­base in one place, so en­gi­neers and cod­ing agents read core and cus­tomiza­tions to­gether, while the cus­tomiza­tions them­selves stay pri­vate. For that, keep a pri­vate fork: a stand­alone pri­vate repos­i­tory whose his­tory be­gins as a clone of qm and whose core stays iden­ti­cal to up­stream.

Populate it once, then clone it to work in:

gh repo cre­ate <org>/qm-private –private

git clone –bare git@github.com:yc-software/qm qm-seed.git git -C qm-seed.git push –mirror git@github.com:<org>/qm-private rm -rf qm-seed.git

git clone git@github.com:<org>/qm-private git -C qm-pri­vate re­mote add up­stream git@github.com:yc-software/qm

Create the pri­vate fork with a plain clone, as shown above, and never with GitHub’s fork fea­ture. The word fork” here names the con­cept — a down­stream copy that di­verges de­lib­er­ately and merges from up­stream — not GitHub’s Fork but­ton. A GitHub fork in­her­its the vis­i­bil­ity of the repos­i­tory it came from, so a fork of a pub­lic repos­i­tory can­not be made pri­vate. A GitHub fork also shares one ob­ject net­work with the repos­i­tory it came from, so com­mits pushed to the fork stay fetch­able by SHA from the pub­lic side. Many or­ga­ni­za­tions dis­al­low fork­ing pri­vate repos­i­to­ries as well. A plain clone has none of these prob­lems, and it costs one thing: the clone is an or­di­nary repos­i­tory, so up­stream’s CI work­flows run live in your own ac­count. Expect to sup­ply the se­crets those work­flows need, or dis­able the ones you do not want run­ning.

Everything spe­cific to your or­ga­ni­za­tion goes in de­ploy/​lay­ers/&​lt;org>/ — con­fig, sand­box tools and skills, plu­gin im­ages, in­fra­struc­ture — in the same shape qm init pro­duces. See de­ploy/​lay­ers/​README.md. Core stays byte-iden­ti­cal to up­stream, which is what keeps merges small.

Two skills main­tain the bound­ary in both di­rec­tions. up­date-qm merges up­stream qm into the pri­vate fork and opens the sync PR; up­stream-pr sends an or­ga­ni­za­tion-ag­nos­tic fix back to qm, cut­ting the branch from up­stream/​main and check­ing the out­go­ing diff, com­mit mes­sages, and screen­shots for or­ga­ni­za­tion iden­ti­fiers be­fore it pushes. Nothing un­der de­ploy/​lay­ers/ ever trav­els up­stream.

Going deeper

docs/​get­ting-started.md — first run, end to end

cli/​README.md — the qm CLI and the de­ploy­ment di­rec­tory con­tract

docs/​de­ploy-di­rec­tory.md — the de­ploy­ment di­rec­tory in full

.env.example — every knob, doc­u­mented in place

plu­g­ins/ — the sur­faces (Slack, web UI, ad­min, por­tal)

License

Except where oth­er­wise noted, QM is avail­able un­der the MIT License.

Tailscale didn’t stop the Hugging Face intrusion

tailscale.com

By now, you’ve likely heard about the AI agent that es­caped a se­cu­rity eval­u­a­tion and at­tacked Hugging Face, an LLM mar­ket­place. The agent de­cided Hugging Face might have the an­swers to its bench­mark, so it stole them just to cheat on the exam. That’s a funny mo­tive but a scary out­come.

Hugging Face pub­lished a de­tailed re­con­struc­tion of the in­tru­sion. It cov­ers about 17,600 re­cov­ered ac­tions over four and a half days, in­clud­ing sand­box es­capes, code ex­e­cu­tion, cloud cre­den­tials, im­pro­vised com­mand-and-con­trol sys­tems, and even­tu­ally, the use of Tailscale to spread through­out their or­ga­ni­za­tion.

But Tailscale is a zero trust net­work! The whole point of zero trust is to pre­vent at­tack­ers from mov­ing lat­er­ally across your com­pany. What went wrong?

Where Tailscale ap­peared

No vulnerabilities” in Tailscale were found or ex­ploited, and that might make it even more un­com­fort­able for us. Tailscale turns up every­where in AI in­fra­struc­ture: vir­tu­ally every AI com­pany uses Tailscale. So it’s not sur­pris­ing we were in an AI in­ci­dent re­port even­tu­ally.

But, we’re a se­cu­rity tool. Their in­tru­sion is our in­tru­sion, and it’s our job to take it se­ri­ously. As in any post-mortem, we need to re­flect on what hap­pened and what we could have done to pre­vent it.

By the time the agent es­caped its sand­box and found Tailscale, it had al­ready gained code ex­e­cu­tion priv­i­leges in­side a pro­duc­tion worker, reached root ac­cess on a Kubernetes node, and read a pro­duc­tion se­cret store con­tain­ing 136 keys. In a sense, it was game over be­fore we even ar­rived on the scene.

But is­n’t that the prob­lem? Let’s back up a level or two. Why were long-lived se­cret keys ac­ces­si­ble at all to any­one?

Because long-lived cre­den­tials are the stan­dard

They should­n’t be, though. And we’ve now seen a great ex­am­ple of why. In the old world where most in­tru­sions were done by hu­mans at hu­man speed, cre­den­tial leak mit­i­ga­tions were treated as a nice-to-have. A big cre­den­tial store, where you can read 136 keys at once, was a to-do item some­where in a se­cu­rity team’s low-pri­or­ity list.

Now, in a world of rogue AI agents, the big cre­den­tial vault is the prize. It’s not okay any­more.

I only know of two main so­lu­tions to long-lived cre­den­tials.

First, you can con­fig­ure a vault that only is­sues short-lived creds based on long-lived creds that you in­sert once and that it never gives back. HashiCorp Vault can do this, for ex­am­ple; they call it dy­namic cre­den­tials. Unfortunately, dy­namic cre­den­tials are a lot of work to set up and main­tain. When se­cu­rity re­quires work, peo­ple don’t do it.

The sec­ond op­tion is a cre­den­tial-in­ject­ing proxy. If client C wants to ac­cess server S, don’t give C cre­den­tials for S; in­stead, have it send its re­quests via a hard­ened proxy P that does noth­ing but re­ceive a re­quest, in­sert a cre­den­tial, and pass it through. Credential in­jec­tion is gain­ing pop­u­lar­ity now that mod­ern mesh-based net­work­ing re­moves most of its la­tency. A few months ago, we ac­quired Border0 (“Tailscale PAM), which makes an el­e­gant and quite fancy cre­den­tial-in­ject­ing proxy, tightly in­te­grated with Tailscale. Border0, fully de­ployed, would have pre­vented read­ing those 136 keys and logged every at­tempt to use one. (The same cre­den­tial in­jec­tion pow­ers Aperture Connectors.) But, this stuff is too re­cent. Most of our cus­tomers haven’t even looked at it yet.

A bonus third op­tion: Tailscale it­self. In Tailscale, your node key can be ma­chine bound us­ing a TPM to pre­vent ex­port, and if you’re an en­ter­prise you should def­i­nitely do that. Alas, we had to turn TPM stor­age off by de­fault on Linux and Windows, be­cause of HSM is­sues we found on some users’ hard­ware. Anyway, in to­day’s case, the at­tack is what granted ac­cess to Tailscale, so we ar­rived too late.

That long-lived auth key did­n’t need to ex­ist

Neither dy­namic cre­den­tials nor cre­den­tial-in­ject­ing prox­ies were in place, so 136 keys leaked. What’s the next line of de­fense?

One of those 136 cre­den­tials was a reusable Tailscale auth key, used to cre­ate new Tailscale CI (continuous in­te­gra­tion, used for au­to­mated test­ing) nodes in their tail­net. The agent copied that key into a se­ries of ex­ter­nal sand­boxes and used it, over sev­eral days, to en­roll a to­tal of 181 nodes into Hugging Face’s tail­net. Those nodes each re­ceived a Tailscale iden­tity tag grant­ing all the ac­cess a CI node would get.

We could­n’t save those 136 keys. But a reusable Tailscale key did­n’t need to be among them.

We built work­load iden­tity fed­er­a­tion for cases like this. It re­lies on your cloud provider to of­fer ambient au­thor­ity” in the form of short-lived creds gen­er­ated from a run­ning VMs (or con­tain­er’s) cloud iden­tity. The CI job can ask its cloud plat­form for a signed OIDC to­ken. Tailscale ver­i­fies the to­ken, then grants ac­cess with the tags and scopes as­signed to that work­load. What’s neat is this can hap­pen au­to­mat­i­cally once en­abled: spin up the CI node, Tailscale gets the iden­tity, as­signs the right tags. There’s no cre­den­tial to leak and, when con­fig­ured care­fully, no way to use this cre­den­tial any­where but in CI.

That means you can’t take a CI cre­den­tial, run it on a node else­where, and turn it into a paved route out of the test clus­ter into the rest of the net­work.

Alas, not enough peo­ple use work­load iden­tity fed­er­a­tion. We should have done more to make sure cus­tomers knew it’s the eas­ier and safer path and had a clear way to adopt it. We can im­prove the docs, and add more nudges in the UI.

No client logs does­n’t mean no ev­i­dence

In an at­tempt to hide its tracks, the agent ran Tailscale with –no-logs-no-support, which sup­presses re­port­ing from that client.

That’s an op­tion de­signed for users who are con­cerned about send­ing teleme­try meta­data to Tailscale. Even if we did­n’t of­fer it, it would be easy to mod­ify the source code to re­move the teleme­try.

But stop­ping the logs does­n’t make the con­nec­tion in­vis­i­ble. If you en­able Tailscale net­work flow logs, they re­port traf­fic from both ends of every con­nec­tion, as well as from sub­net routers and exit nodes. This is sub­tle but im­por­tant: a com­pro­mised node might not send flow logs, but every node it con­nects to does. And then your SIEM, con­fig­ured with care, can raise an im­me­di­ate red alert if the two ends don’t match.

Flow logs can help de­tec­tion when they stream into a care­fully con­fig­ured SIEM. But that’s a lot of work. Flow logs need to be en­abled, and you need to have the right live de­tec­tion rules in place so they’re use­ful in real time, not just for foren­sics later. We’re look­ing at how to make flow logs eas­ier to dis­cover, con­fig­ure, adopt, and serve as alert trig­gers. I want us to make flow logs so easy to use that they help even if you don’t have a se­cu­rity team to watch them.

If you want di­rect con­trol be­yond just log­ging, you can also en­able Tailnet Lock. This gives you di­rect vis­i­bil­ity and strict, pro­gram­ma­ble ad­mis­sion con­trol for every sin­gle new node. For ex­am­ple, with some work, you could pro­gram your sign­ing node to check that CI tags al­ways have a par­tic­u­lar IP ad­dress range or other side-chan­nel proof of va­lid­ity.

Make the safe path the easy path

Network se­cu­rity is hard. It has al­ways been hard. In the new world of rogue AI agents, it’s not just hard, but es­sen­tial. And that’s a prob­lem be­cause many orgs sim­ply don’t have net­work se­cu­rity ex­per­tise.

So at Tailscale, we take it per­son­ally. People ex­pect our prod­uct to pre­vent these sorts of lat­eral move­ment at­tacks, by de­fault, so they don’t have to. Even if they have no idea what a lat­eral move­ment at­tack is.

If this in­ci­dent has you look­ing a lit­tle ner­vously at your own in­fra­struc­ture, start by look­ing at the reusable Tailscale auth keys your work­loads can read. For cloud and CI in par­tic­u­lar, re­place them with work­load iden­tity fed­er­a­tion wher­ever you can. Get rid of those long-lived auth keys.

(Auth keys still have good uses, es­pe­cially for one-time pro­vi­sion­ing and en­vi­ron­ments with­out a plat­form iden­tity. When you need one, pre­fer one-off keys; use OAuth clients to keep the auth key ex­piry pe­ri­ods short; use nar­row tags; au­dit the per­mis­sions granted to those keys in your ACLs.)

Turn on net­work flow logs and send them to the tools your se­cu­rity team al­ready uses.

Use se­cure node state stor­age on man­aged fleets, where you have con­trol over your TPMs. Use de­vice pos­ture to iso­late and re­strict nodes where you don’t.

I know we haven’t made these safer choices ob­vi­ous enough. That’s on us. We’ll im­prove our docs, add nudges to the UI, do our best to turn these on by de­fault, warn you when you’re do­ing some­thing dan­ger­ous, and sug­gest bet­ter al­ter­na­tives.

This is our very Canadian apol­ogy: sorry you stepped on our toes. The at­tack did­n’t ex­ploit Tailscale, and Tailscale did­n’t cause the com­pro­mise. But, we did­n’t stop it. Next time, we will.

If you run Tailscale and want to dig deeper, get in touch with our sup­port and so­lu­tions en­gi­neer­ing teams. We can help you harden your set­tings and help you find the rough edges be­fore the next AI agent does.

To add this web app to your iOS home screen tap the share button and select "Add to the Home Screen".

10HN is also available as an iOS App

If you visit 10HN only rarely, check out the the best articles from the past week.

Visit pancik.com for more.