10 interesting stories served every morning and every evening.

Meta confirms thousands of Instagram accounts were hacked by abusing its AI chatbot

this.weekinsecurity.com

Meta is no­ti­fy­ing thou­sands of peo­ple whose Instagram ac­counts were hi­jacked dur­ing the months-long abuse of the com­pa­ny’s AI chat­bot, which hack­ers re­peat­edly tricked into tak­ing con­trol of a per­son’s ac­count.

In a new data breach no­ti­fi­ca­tion let­ter, seen by this week in se­cu­rity, Meta has re­vealed for the first time how many peo­ple had their ac­counts hi­jacked as part of the long-run­ning hack­ing cam­paign, which was dis­cov­ered ear­lier this week and first re­ported by 404 Media ($) and TechCrunch ($). The num­ber of af­fected ac­counts gives some clar­ity as to how wide­spread this hack­ing cam­paign was, and for how long it op­er­ated.

According to the data breach no­tice filed with Maine’s at­tor­ney gen­er­al’s of­fice late on Friday, Meta no­ti­fied at least 20,225 peo­ple that their ac­counts had been com­pro­mised, in­clud­ing 30 peo­ple in Maine.

The com­pro­mises al­lowed the hack­ers to take over the per­son’s en­tire Instagram and any linked ac­counts, in­clud­ing ob­tain­ing con­tact in­for­ma­tion, dates of birth, and pro­file in­for­ma­tion, as well as the abil­ity to ac­cess the per­son’s posts, di­rect mes­sages, and ac­count ac­tiv­ity, the no­tice reads.

Meta’s no­tice con­firmed that the breach re­lates to a vul­ner­a­bil­ity in an AI-assisted ac­count re­cov­ery sys­tem for Instagram,” which was ex­ploited to perform pass­word re­sets on Instagram user ac­counts.”

As pre­vi­ously re­ported, hack­ers abused a flaw in Meta’s chat­bot that al­lowed any­one to re­set the pass­word of any ac­count that did not have two-fac­tor au­then­ti­ca­tion switched on. The bug tricked the chat­bot into send­ing a ver­i­fi­ca­tion code to an email ad­dress con­trolled by the hacker, rather than the ac­count hold­er’s email ad­dress on file, sim­ply by ask­ing it. The chat­bot com­plied any­way.

The tool it­self worked prop­erly and func­tioned as in­tended; how­ever due to a bug in a sep­a­rate code path, the sys­tem did not prop­erly ver­ify that the email ad­dress pro­vided by the in­di­vid­ual re­quest­ing a pass­word re­set matched the email ad­dress as­so­ci­ated with that user’s Instagram ac­count,” said Meta in its breach no­tice.

As a re­sult, when an in­di­vid­ual pro­vided an email ad­dress not pre­vi­ously as­so­ci­ated with the ac­count, the sys­tem in­cor­rectly sent a pass­word re­set link to that unas­so­ci­ated email rather than re­ject­ing the re­quest. This al­lowed unau­tho­rized third par­ties to re­ceive a pass­word re­set link for ac­counts they did not own,” the com­pany added.

At this point, Meta says, the hack­ers could re­set some­one’s pass­word and take over their ac­count as if they were the right­ful owner.

Meta said that it is unaware” of what, if any, per­sonal in­for­ma­tion was ac­cessed dur­ing the hacks. (An email to Meta’s press line ask­ing for clar­ity on this was un­re­turned as of early Saturday.)

According to Maine’s list­ing, the hacks be­gan around April 17 and lasted un­til this week, when Meta said that it had se­cured the chat­bot. Instagram re­port­edly started no­ti­fy­ing af­fected in­di­vid­u­als ear­lier this week by send­ing a pass­word re­set no­ti­fi­ca­tion, even as some re­ported that the hacks were on­go­ing.

Meta also con­firmed in the no­tice that it alerted users to se­cure their ac­counts, say­ing it instructed im­pacted users to re­set their pass­words and re-au­then­ti­cate through se­cure, ver­i­fied chan­nels.”

Meta said that it has dis­abled the AI chat­bot for now and re­moved the code path that al­lowed the chat­bot to re­set user ac­counts, and said it’s also check­ing other chat­bots across its plat­forms to pre­vent a re­peat in­ci­dent. It’s not yet clear what cir­cum­stances led up to the chat­bot be­ing abused, but comes soon af­ter Meta laid off thou­sands of em­ploy­ees while re­ward­ing top ex­ec­u­tives with stock in­cen­tives, as the com­pany con­tin­ues to dou­ble-down on AI.

~ ~

Thank you so much for read­ing ~this week in se­cu­rity~. If you liked this ar­ti­cle, please share it! Feel free to reach out with any feed­back, ques­tions, or com­ments about this ar­ti­cle: this@weekin­se­cu­rity.com.

Pentagon raised threat of Israeli spying on U.S. to highest level, sources say

www.nbcnews.com

WASHINGTON — The Pentagon is in­creas­ingly con­cerned about Israel ramp­ing up its spy­ing on the U.S., re­cently rais­ing the coun­ter­in­tel­li­gence threat level from America’s top ally in the Middle East to the high­est level, ac­cord­ing to two U.S. of­fi­cials and one for­mer U.S. of­fi­cial.

The Pentagon’s Defense Intelligence Agency in re­cent weeks is­sued the new coun­ter­in­tel­li­gence threat as­sess­ment amid ris­ing ten­sions be­tween Israel and the U.S. over the way for­ward in the war with Iran, the of­fi­cials said. They said the DIA posted an in­ter­nal mes­sage, viewed by one of the cur­rent of­fi­cials, that raised the level for Israel to critical.”

The des­ig­na­tion stems from con­cerns within the Pentagon that Israel is mak­ing a par­tic­u­lar ef­fort to sur­veil top U.S. of­fi­cials to get in­for­ma­tion on the Trump ad­min­is­tra­tion’s in­ter­nal de­lib­er­a­tions and de­ci­sion-mak­ing on the con­flicts in the Middle East, the of­fi­cials said.

The DIA as­sess­ment in­cludes a seven-page doc­u­ment and fea­tures a chart, ac­cord­ing to one of the cur­rent U.S. of­fi­cials. The doc­u­ment says the as­sess­ment of Israel is that its abil­ity to con­duct hu­man es­pi­onage and tech­ni­cal col­lec­tion is at a critical level,” ac­cord­ing to the of­fi­cial.

It also iden­ti­fies a se­ries of spe­cific in­ci­dents that height­ened U.S. con­cerns, the of­fi­cial said.

A spokesper­son for the Israeli Embassy in Washington, D.C., said in a state­ment that it is completely false” that Israel spies on the U.S. Israel does not gather in­tel­li­gence on American en­ti­ties, let alone US gov­ern­ment of­fi­cials,” the spokesper­son said. Israel in­tel­li­gence col­lec­tion ef­forts are aimed at its en­e­mies, not its al­lies. Any claims to the con­trary are ei­ther mis­in­formed or po­lit­i­cally mo­ti­vated.”

The Pentagon de­clined to com­ment.

A White House of­fi­cial said in a state­ment, This en­tire story is false and sourced to some­one who does­n’t have any knowl­edge of what’s go­ing on.”

The Office of the Director of National Intelligence, which over­sees all the U.S. in­tel­li­gence agen­cies in­clud­ing the DIA, did not re­spond to a re­quest for com­ment.

While it is com­mon­place for al­lies and ad­ver­saries across the globe to spy on each other, the cur­rent and for­mer U.S. of­fi­cials said Israel’s re­cent ef­forts have gone well be­yond what is typ­i­cal and ex­pected es­pi­onage. The of­fi­cials did not know if a spe­cific in­ci­dent trig­gered the DIAs de­ci­sion to raise the coun­ter­in­tel­li­gence threat level.

The height­ened alert comes as President Donald Trump and Israeli Prime Minister Benjamin Netanyahu have clashed over the war with Iran and Israel’s mil­i­tary op­er­a­tions in Lebanon, in­clud­ing in a tense phone call this past week, NBC News re­ported. Trump ac­knowl­edged af­ter­ward to re­porters that he called Netanyahu crazy” dur­ing the call as ques­tions mount about whether the two coun­tries’ ob­jec­tives in the Middle East are be­gin­ning to sig­nif­i­cantly di­verge.

Since a cease­fire deal was reached in early April, Trump has been pur­su­ing a diplo­matic deal with Iran to end the war Israel and the U.S. launched on Feb. 28. Israel has pub­licly ex­pressed skep­ti­cism that Iran would abide by any ne­go­ti­ated deal. Netanyahu has pushed for a re­sump­tion of bomb­ing raids against Iran and dis­agreed with Trump, who has pressed him to scale back at­tacks against Hezbollah in Lebanon, ac­cord­ing to Western of­fi­cials.

Israel is keenly in­ter­ested in whether Trump de­cides to re­sume ma­jor com­bat op­er­a­tions against Iran or to end the con­flict, the cur­rent and for­mer U.S. of­fi­cials and out­side ex­perts said.

The most prac­ti­cal out­come for the Pentagon is that U.S. of­fi­cials will use ex­tra cau­tion when trav­el­ing to Israel or vis­it­ing with Israeli of­fi­cials, the cur­rent and for­mer U.S. of­fi­cials said. They said there did not ap­pear to be any im­pact on the high-level in­tel­li­gence-shar­ing that oc­curs on a daily ba­sis be­tween the two coun­tries, par­tic­u­larly as­so­ci­ated with the Iran war.

The U.S. al­ready takes ex­tra pre­cau­tions when vis­it­ing Israel,” one of the cur­rent U.S. of­fi­cials said. They’re well-known to ag­gres­sively col­lect.”

The U.S., like other coun­tries, main­tains elab­o­rate coun­ter­in­tel­li­gence, or spy catcher,” ef­forts to pre­vent and track es­pi­onage by for­eign ad­ver­saries as well as by al­lies and part­ners, seek­ing to safe­guard state se­crets and mon­i­tor at­tempts to re­cruit or co­erce U.S. of­fi­cials. Under U.S. law, the FBI has the lead­ing role in coun­ter­in­tel­li­gence ef­forts, but they also in­volve a range of gov­ern­ment agen­cies and the mil­i­tary.

According to cur­rent and for­mer diplo­mats and for­mer na­tional se­cu­rity of­fi­cials, Israel for years has had a rep­u­ta­tion for ag­gres­sive es­pi­onage even against the U.S., its clos­est ally. It’s a prac­tice that has long raised con­cerns among na­tional se­cu­rity and diplo­matic of­fi­cials, and U.S. in­tel­li­gence of­fi­cials closely mon­i­tor the is­sue, ac­cord­ing to ex­perts and the cur­rent and for­mer U.S. of­fi­cials.

Top U.S. of­fi­cials of­ten take ex­tra care when trav­el­ing to Israel, some­times us­ing burner phones and com­put­ers and tak­ing ex­treme cau­tion when speak­ing in ho­tel rooms dur­ing of­fi­cial trips, the cur­rent and for­mer U.S. of­fi­cials and ex­perts said.

Israel has a hy­per-ag­gres­sive in­tel­li­gence ser­vice,” said Emily Harding, vice pres­i­dent of the Defense and Security Department and di­rec­tor of the in­tel­li­gence, na­tional se­cu­rity and tech­nol­ogy pro­gram at the Center for Strategic and International Studies, a think tank in Washington. They are ex­ceed­ingly in­ter­ested in what we are up to,” Harding said of the Israelis.

In the 1980s, spy­ing by Israel caused a rift with Washington, with U.S. Navy in­tel­li­gence an­a­lyst Jonathan Pollard spend­ing 30 years in prison af­ter he was found to have sold suit­cases of top-se­cret doc­u­ments to Israel.

The U.S. also spies on its al­lies and seeks to gather in­tel­li­gence on for­eign part­ners, as ev­i­denced in 2013 by leaks from in­tel­li­gence con­trac­tor Edward Snowden.

Those leaks showed that the U.S. was eaves­drop­ping on European lead­ers, in­clud­ing then-Ger­man Chancellor Angela Merkel’s mo­bile phone, spark­ing out­rage in Berlin.

The U.S. and Israel re­main close al­lies, and the two coun­tries’ in­tel­li­gence ser­vices have forged a close work­ing re­la­tion­ship over decades. But con­cerns about pos­si­ble Israeli es­pi­onage at such a sen­si­tive mo­ment — when the two gov­ern­ments are not in full agree­ment about the war with Iran — carry the risk of un­der­min­ing trust be­tween the two coun­tries, two ad­di­tional for­mer U.S. of­fi­cials said.

GrapheneOS user reported to authorities for using GrapheneOS

discuss.grapheneos.org

GrapheneOS Discussion Forum

Google will pay SpaceX $920M per month for compute

techcrunch.com

SpaceX has lined up an­other com­pute deal ahead of its his­toric IPO, this time with Google. The com­pany an­nounced the deal in a reg­u­la­tory fil­ing on Friday.

Under the terms of the deal, Google will pay SpaceX $920 mil­lion per month from October 2026 through June 2029 for ac­cess to approximately 110,000 NVIDIA GPUs, CPUs, mem­ory, and other re­lated com­po­nents.”

The deal is sim­i­lar in length and scope to the one SpaceX an­nounced with Anthropic in late May. As part of that deal, Anthropic agreed to pay SpaceX $1.25 bil­lion per month through 2029 to rent all the avail­able com­pute from its Colossus 1 data cen­ter near Memphis, Tennessee, that xAI — now part of SpaceX — orig­i­nally built for its own ar­ti­fi­cial in­tel­li­gence ef­forts.

Google’s deal ap­pears to be pay­ing for roughly half the amount of com­pute that Anthropic has ac­cess to at Colossus 1. SpaceX did­n’t say which spe­cific data cen­ter Google would be us­ing. CEO Elon Musk has pre­vi­ously sug­gested his com­pany would re­serve the Colossus 2 data cen­ter for xAI.

Anthropic was sig­nif­i­cantly lim­ited in its com­pute ca­pac­ity prior to its deal with SpaceX, rais­ing us­age lim­its on the same day the deal was an­nounced. Google is in a very dif­fer­ent po­si­tion, with some es­ti­mates nam­ing it as the world’s largest sin­gle owner of AI com­pute.

In a state­ment, a Google rep­re­sen­ta­tive de­scribed the deal as a re­sult of un­ex­pected de­mand for its re­cently launched AI prod­ucts. Google Cloud and SpaceX are long-time part­ners,” Google said in a state­ment. This is a short-term, timely agree­ment to en­sure we have bridge ca­pac­ity to meet surg­ing cus­tomer de­mand for our agent plat­form, Gemini Enterprise, which has been even higher than we ex­pected.”

But its par­ent com­pany Alphabet is on a spend­ing spree. Alphabet has al­ready com­mit­ted to more than $180 bil­lion in cap­i­tal ex­pen­di­tures this year and has said it ex­pects that to significantly in­crease” in 2027. To help with that, Alphabet re­cently an­nounced an $80 bil­lion eq­uity sale.

Also like the Anthropic deal, the agree­ment with Google in­cludes a can­cel­la­tion clause. Both SpaceX and Google have the op­tion to ter­mi­nate the agree­ment with 90 days’ no­tice af­ter December 31, 2026. Google’s ac­cess to the data cen­ter will ramp up through September at a re­duced fee,” ac­cord­ing to the fil­ing.

If we fail to de­liver ac­cess to the com­mit­ted amount of GPUs by September 30, 2026, then fol­low­ing a one-month grace pe­riod, Google may im­me­di­ately ter­mi­nate the agree­ment or ac­cept the num­ber of GPUs pro­vided” with a re­duc­tion in the monthly fees, it reads.

SpaceX an­nounced the deal just one week be­fore the com­pa­ny’s stock is ex­pected to start trad­ing on the Nasdaq ex­change. Paperwork filed with the Securities and Exchange Commission shows the com­pany is aim­ing to raise around $75 bil­lion at a val­u­a­tion of around $1.75 tril­lion — mak­ing it the largest in his­tory.

Google is a long­time in­vestor in SpaceX. Its stake in Musk’s com­pany is ex­pected to be worth more than $100 bil­lion af­ter the IPO. The com­pa­nies are also re­port­edly in talks to try to build or­bital data cen­ters — a ma­jor com­po­nent of SpaceX’s fu­ture plans post-IPO.

When you pur­chase through links in our ar­ti­cles, we may earn a small com­mis­sion. This does­n’t af­fect our ed­i­to­r­ial in­de­pen­dence.

Sean O’Kane is a re­porter who has spent a decade cov­er­ing the rapidly-evolv­ing busi­ness and tech­nol­ogy of the trans­porta­tion in­dus­try, in­clud­ing Tesla and the many star­tups chas­ing Elon Musk. Most re­cently, he was a re­porter at Bloomberg News where he helped break sto­ries about some of the most no­to­ri­ous EV SPAC flops. He pre­vi­ously worked at The Verge, where he also cov­ered con­sumer tech­nol­ogy, hosted many short- and long-form videos, per­formed prod­uct and ed­i­to­r­ial pho­tog­ra­phy, and once nearly passed out in a Red Bull Air Race plane.

You can con­tact or ver­ify out­reach from Sean by email­ing sean.okane@techcrunch.com or via en­crypted mes­sage at okane.01 on Signal.

View Bio

rs - an accurate VHS video effect

ntsc.rs

ntsc-rs is a free, open-source video ef­fect which ac­cu­rately em­u­lates ana­log TV and VHS ar­ti­facts.

Other pop­u­lar ef­fects eye­ball the look of VHS tapes us­ing sim­ple color lookup ta­bles and over­lays. ntsc-rs uses al­go­rithms that model how NTSC trans­mis­sion and VHS en­cod­ing ac­tu­ally work, based on al­go­rithms de­vel­oped in com­pos­ite-video-sim­u­la­tor, zhuker/​ntsc, and ntscQT.

ntsc-rs is writ­ten in Rust, and is mul­ti­threaded and SIMD-accelerated. Unlike sim­i­lar ef­fects such as ntscQT, it can run in real time at much higher res­o­lu­tions than ac­tual NTSC footage.

ntsc-rs is avail­able not just as a stand­alone and web ap­pli­ca­tion, but also as a plu­gin for After Effects, Premiere, and all OpenFX-compatible soft­ware. This in­cludes DaVinci Resolve, Hitfilm, and Vegas.

pokeemerald-wasm

pokeemerald.com

Moving beyond fork() + exec()

lwn.net

[LWN sub­scriber-only con­tent]

Welcome to LWN.net

The fol­low­ing sub­scrip­tion-only con­tent has been made avail­able to you by an LWN sub­scriber. Thousands of sub­scribers de­pend on LWN for the best news from the Linux and free soft­ware com­mu­ni­ties. If you en­joy this ar­ti­cle, please con­sider sub­scrib­ing to LWN. Thank you for vis­it­ing LWN.net!

Welcome to LWN.net

The fol­low­ing sub­scrip­tion-only con­tent has been made avail­able to you by an LWN sub­scriber. Thousands of sub­scribers de­pend on LWN for the best news from the Linux and free soft­ware com­mu­ni­ties. If you en­joy this ar­ti­cle, please con­sider sub­scrib­ing to LWN. Thank you for vis­it­ing LWN.net!

fork() is a rel­a­tively ex­pen­sive sys­tem call; it must copy the en­tire process state (including mem­ory) for the child process. Many op­ti­miza­tions have been made over the years, but a fork is still a fun­da­men­tally costly op­er­a­tion. To make things worse, a fork() call is of­ten im­me­di­ately fol­lowed by an exec(), which will dis­card all of that mem­ory that was so care­fully copied for the child. Attempts (such as vfork()) have been made over the years to op­ti­mize for this case, but the pat­tern still is more ex­pen­sive than it could be.

Spawn tem­plates

Chen’s patch set takes an in­ter­est­ing ap­proach to op­ti­mize the fork() and exec() pat­tern. It is fo­cused on ap­pli­ca­tions that re­peat­edly launch processes run­ning the same ex­e­cutable; imag­ine, for ex­am­ple, a pro­gram that must run Git re­peat­edly to ob­tain in­for­ma­tion about the con­tents of a repos­i­tory. In such cases, the pro­gram could es­tab­lish a tem­plate to ac­cel­er­ate those in­vo­ca­tions, spread­ing the setup cost across mul­ti­ple op­er­a­tions. This tem­plate would be cre­ated with the spawn_tem­plate_cre­ate() sys­tem call:

struct spawn_tem­plate_cre­ate_args { __aligned_u64 flags; __s32 ex­ecfd; __u32 ex­ec_flags; __aligned_u64 file­name; /* Some fields elided */ };

int spawn_tem­plate_cre­ate(struct spawn_tem­plate_cre­ate_args *args, size_t args_­size);

This call will re­turn a file de­scrip­tor rep­re­sent­ing a tem­plate for the ex­e­cutable file, which can be spec­i­fied as ei­ther a file de­scrip­tor (execfd) or an ab­solute path (filename), but not both. To cre­ate the tem­plate, the ker­nel will open the in­di­cated file and cache a bunch of in­for­ma­tion that will al­low a process to run that file more quickly in the fu­ture.

The ap­pli­ca­tion in ques­tion may run a given ex­e­cutable many times, but each in­vo­ca­tion is dif­fer­ent in a num­ber of ways. The de­tails of a spe­cific in­vo­ca­tion must be placed into an in­stance of this struc­ture:

struct spawn_tem­plate_s­pawn_args { __aligned_u64 flags; __aligned_u64 pidfd; __aligned_u64 argv; __aligned_u64 envp; __aligned_u64 ac­tions; __aligned_u64 ac­tion­s_len; __aligned_u64 re­served[4]; };

The argv field is a pointer to the ar­gu­ment list to be passed to the pro­gram, while envp points to its en­vi­ron­ment. Changes to file de­scrip­tors and sig­nal han­dling, in­stead, are passed through ac­tions, which is a pointer to an ar­ray of:

struct spawn_tem­plate_ac­tion { __u32 type; __u32 flags; __s32 fd; __s32 newfd; __aligned_u64 arg; };

If, for ex­am­ple, file de­scrip­tor four should be closed in the child, the as­so­ci­ated spawn_tem­plate_ac­tion struc­ture would have type set to SPAWN_TEMPLATE_ACTION_CLOSE and fd set to four. Other ac­tions ex­ist for du­pli­cat­ing file de­scrip­tors, open­ing files, chang­ing the work­ing di­rec­tory, and chang­ing sig­nal han­dling.

Once the spawn_tem­plate_s­pawn_args struc­ture has been filled in, the new process can be run with:

int spawn_tem­plate_s­pawn(int tem­plate_fd, struct spawn_tem­plate_s­pawn_args *args, int args_­size);

Internally, this sys­tem call fol­lows some­thing close to the nor­mal fork()/​exec() path. Chen is care­ful to point out that all of the nor­mal checks ap­plied when ex­e­cut­ing a new file re­main in place. But the cached in­for­ma­tion in the tem­plate makes the whole process faster than it was be­fore.

How much faster? Benchmark re­sults pro­vided in the cover let­ter show an im­prove­ment of about 2%, which may not seem like a lot, but it may make a dif­fer­ence for ap­pli­ca­tions that fit the ex­pected pat­tern.

Toward posix_s­pawn()

The most de­tailed re­view of this work was posted by Mateusz Guzik, who said: This prob­lem is dear to my heart and I have been pon­der­ing it on and off for some time now. The en­tire fork + exec id­iom is ter­ri­ble and needs to be re­tired”. He pointed out that the fo­cus of the patch set was a bit strange in that it left the fork() part of the prob­lem un­touched. That is where most of the cost lies, he said, so op­ti­miza­tion ef­forts should seek to re­move it from the pic­ture. Rather than copy­ing the cur­rent process, creating a pris­tine process is the way to go”.

Christian Brauner was fa­vor­able to­ward the goal, say­ing: The idea of hav­ing a builder api for exec is­n’t all that crazy”. His sug­ges­tion, though, was that a new API should be built on top of the ex­ist­ing pidfd ab­strac­tion. Without get­ting into any de­gree of de­tail, he said that the right ap­proach would be to cre­ate an op­tion to pidfd_open() to cre­ate an empty process. A se­ries of calls to a new pidfd_­con­fig() sys­tem call would then con­fig­ure this new process as de­sired, set­ting up its en­vi­ron­ment, im­age to ex­e­cute, and more. pidfd_­con­fig() would thus be anal­o­gous to fs­con­fig().

An im­por­tant ob­jec­tive for a new in­ter­face, Brauner said, would be the abil­ity to sup­port an im­ple­men­ta­tion of posix_s­pawn() in user space. posix_s­pawn() is well suited as a re­place­ment for the fork()/​exec() pat­tern; de­vel­op­ers would likely wel­come a na­tive im­ple­men­ta­tion that is­n’t (unlike the cur­rent im­ple­men­ta­tion) hid­ing fork() and exec() un­der the cov­ers.

Chen agreed that the API as broadly sketched out by Brauner seemed bet­ter, and said that fu­ture work would be in that di­rec­tion. So there will be no spawn tem­plates in the Linux ker­nel but, if Chen’s fu­ture work comes to fruition, Linux may fi­nally gain a proper posix_s­pawn() im­ple­men­ta­tion in­stead.

zeroserve: a zero-config web server you can script with eBPF

su3.io

Disclaimer: This ar­ti­cle is co-au­thored with GPT-5.5 and Claude Opus 4.8.

ze­roserve is a small, fast, zero-con­fig HTTPS server. You hand it a tar­ball of a web­site and it serves it - over HTTP/2 and TLS 1.3, with hot re­load and a tiny res­i­dent foot­print. The twist is that you can drop eBPF pro­grams into the tar­ball and they run on every re­quest, in user­space, as sand­boxed mid­dle­ware - rewrit­ing, au­then­ti­cat­ing, and rate-lim­it­ing re­quests, or re­verse-prox­y­ing them to a back­end when you want it to act as a gate­way in front of your app.

In short:

Fast: on one core it beats ng­inx across most work­loads - small and large sta­tic files, scripted mid­dle­ware, and small-re­sponse prox­y­ing, all over HTTPS.

Efficient eBPF script­ing: scripts are JIT-compiled to na­tive code and sand­boxed in user­space, cheap enough to run on every re­quest.

Program-as-configuration: your eBPF pro­gram is the whole con­fig­u­ra­tion, de­cid­ing what hap­pens to each re­quest.

io_ur­ing through­out: every net­work and disk op­er­a­tion is sub­mit­ted through io_ur­ing.

Modern TLS in the box: TLS 1.3, HTTP/2, Encrypted Client Hello, SNI cer­tifi­cate se­lec­tion, and JA4 fin­ger­print­ing.

Simple to op­er­ate: serve a whole site from one tar­ball and hot-re­load it (and the TLS ma­te­r­ial) with a SIGHUP.

It’s meant to be an al­ter­na­tive to ng­inx and Caddy, and the de­sign bet is about con­fig­u­ra­tion. Those servers give you a de­clar­a­tive con­fig lan­guage - lo­ca­tion blocks, rewrite rules, map di­rec­tives, try_­files - and then, once the de­clar­a­tive lan­guage hits its lim­its, an op­tional script­ing run­time bolted on the side (Lua, or Caddy’s plu­g­ins). Behavior ends up split across two lay­ers: di­rec­tives that qui­etly grow their own con­trol flow, plus scripts that run some­where in the re­quest life­cy­cle you have to keep in your head.

ze­roserve col­lapses that into one thing. There is no con­fig file. The eBPF pro­gram is the con­fig­u­ra­tion - a sin­gle, or­di­nary, sand­boxed pro­gram that sees every re­quest and de­cides what hap­pens: rout­ing, head­ers, auth, rate lim­it­ing, prox­y­ing. I want the whole re­quest path in one pro­gram I can read top to bot­tom.

One tar­ball, served in place

The whole site is a sin­gle tar file. ze­roserve in­dexes it on load - build­ing a path -> byte-range map - and then serves files by is­su­ing byte-range reads against the tar­ball it­self. Nothing is ever un­packed to disk. The site lives en­tirely in that one file, so there’s no doc­u­ment root for a stray lo­ca­tion rule to ex­pose, and a de­ploy is a sin­gle atomic file swap. To pack­age a di­rec­tory:

ze­roserve –pack ./public > site.tar ze­roserve –addr 0.0.0.0:8080 site.tar

Deploying a new ver­sion is replace the tar­ball and send SIGHUP. The re­load swaps the site, the scripts, and the TLS ma­te­r­ial atom­i­cally, in the same process, with no dropped con­nec­tions:

kil­lall -SIGHUP ze­roserve

All net­work and disk I/O goes through io_ur­ing (via the monoio run­time). Each in­stance is a sin­gle-threaded event loop. That sounds like a lim­i­ta­tion, and per-process it is - but it’s the right shape when your scal­ing unit is more processes”, and it’s why many of them co­ex­ist hap­pily on one box.

Scripting with eBPF, in user­space

This is the part I find most fun. Any .c file you put un­der .zeroserve/scripts/ gets com­piled to an eBPF ob­ject at pack time (with clang and llc) and runs on every re­quest. The eBPF runs en­tirely in user­space: ze­roserve loads the byte­code into a run­time (async-ebpf) in­side its own or­di­nary, un­priv­i­leged process, so the ker­nel’s BPF sub­sys­tem and CAP_BPF stay out of it. async-ebpf JIT-compiles the byte­code to na­tive ma­chine code (it ven­dors uBPF), so your config” runs as na­tive x86 – 64.

A pointer cage does the job the ker­nel ver­i­fier nor­mally would, keep­ing the pro­gram from read­ing or writ­ing mem­ory it should­n’t: every mem­ory ac­cess in the JIT-compiled code is masked into the pro­gram’s own arena, so a stray ac­cess stays con­fined to the scrip­t’s own mem­ory.

The script runs di­rectly on ze­roserve’s sin­gle event loop. To keep one slow script from stalling every other con­nec­tion, the run­time is fully pre­emptible: a timer can in­ter­rupt JIT-compiled na­tive code mid-ex­e­cu­tion and hand con­trol back to the event loop.

The pro­gram­ming model is a chain of scripts, run in sorted file­name or­der, shar­ing a per-re­quest meta­data map. If a script calls zs_re­spond or zs_re­verse_proxy, the chain short-cir­cuits. Here’s a script that runs first and en­riches every re­quest:

#include <zeroserve.h>

ZS_ENTRY zs_u64 en­try(void) { char peer[64]; if (zs_req_peer(peer, sizeof(peer)) <= 0) zs_str­cpy(peer, unknown”);

// pub­lish val­ues for the HTML tem­plate pass zs_meta_set(ZS_STR(“vis­i­tor”), ZS_STR(peer)); // at­tach a header to *every* re­sponse: sta­tic files, zs_re­spond, prox­ied zs_meta_set(ZS_STR(“zs.re­sponse.header.x-served-by”), ZS_STR(“zeroserve-ebpf”)); re­turn 0; }

The meta­data it sets does two things. Keys un­der zs.re­sponse.header.* be­come re­sponse head­ers on every­thing. And other keys feed a tiny tem­plate pass: a <zs-meta>visitor</zs-meta> place­holder in an HTML file gets sub­sti­tuted on the way out. So you get dy­namic-ish sta­tic pages with­out a tem­plate en­gine.

The helper sur­face a script can call is broad:

Request in­spec­tion and mu­ta­tion: read the method, path, query params, head­ers, and peer ad­dress; rewrite the URI or set and re­move head­ers be­fore the re­sponse goes out.

Crypto and en­cod­ing: SHA-256, HMAC-SHA256, base64, hex, and ge­tran­dom.

JSON: parse a re­quest body, build and mu­tate a doc­u­ment tree, and re­ply with zs_j­son_re­spond.

Rate lim­it­ing: per-key to­ken buck­ets keyed on any­thing from a peer IP to an API key, with state that sur­vives hot re­loads.

AWS SigV4: signed Authorization head­ers and pre­signed URLs for talk­ing to S3 and other AWS ser­vices.

OIDC lo­gin: a com­plete re­ly­ing-party flow (Authorization Code + PKCE) that car­ries the en­tire lo­gin ses­sion in sealed XChaCha20-Poly1305 cook­ies, so you can gate a sta­tic site be­hind log in with Google” while the server stays state­less.

A dy­namic end­point is just a script that re­sponds:

ZS_ENTRY zs_u64 en­try(void) { char path[64]; zs_re­q_­path(path, sizeof(path)); if (zs_strcmp(path, /health”) != 0) re­turn 0;

zs_meta_set(ZS_STR(“zs.re­sponse.header.con­tent-type”), ZS_STR(“application/json”)); zs_re­spond(200, ZS_STR(“{"status":"ok"}\n”)); re­turn 0; }

Each script runs un­der a mem­ory-foot­print cap (256 KB by de­fault), the run­time time-slices long-run­ning scripts off the ex­ecu­tor and throt­tles the run­aways, and scripts can even call each other (zs_call) up to a bounded depth. A script that spins for­ever stalls only its own re­quest - the pre­emp­tion timer in­ter­rupts it and the server keeps serv­ing every­one else.

The TLS story un­der­neath is more com­plete than the zero-con­fig fram­ing sug­gests: TLS 1.3 only, ter­mi­nated by BoringSSL, with na­tive Encrypted Client Hello (so the real SNI never ap­pears in clear­t­ext), SNI cer­tifi­cate se­lec­tion from a di­rec­tory, JA4 client fin­ger­print­ing ex­posed to scripts, and a trans­par­ent ECH re­lay mode that byte-for-byte for­wards un­de­crypt­able hand­shakes to a real up­stream so a pro­tected name blends in be­hind a pub­lic one. That’s a lot of trans­port se­cu­rity to ship in a sin­gle zero-con­fig bi­nary.

How fast is it?

I bench­marked ze­roserve against ng­inx 1.26 and Caddy 2.11 over HTTPS on an 8-core Ryzen 7 3700X, each serv­ing the same con­tent with the same self-signed cer­tifi­cate. Because a ze­roserve in­stance is sin­gle-threaded by de­sign, the only fair com­par­i­son is per core: I pinned every server to one CPU with taskset (and held ng­inx to work­er_processes 1 and Caddy to GOMAXPROCS=1; ze­roserve is sin­gle-threaded al­ready) and drove load with wrk -t4 -c100 from other cores, tak­ing the me­dian of three 10-second runs. wrk speaks HTTP/1.1, so these are HTTP/1.1-over-TLS-1.3 num­bers with the hand­shake amor­tized across long-lived keep-alive con­nec­tions: the steady-state cost of serv­ing an al­ready-open HTTPS con­nec­tion.

Small sta­tic file (174 B) - the bread and but­ter of sta­tic sites:

ze­roserve serves small files about 17% faster than ng­inx on a sin­gle core, with a tighter tail. HTML pages, small JSON, CSS - this is the case ze­roserve is tuned for.

Large sta­tic file (100 KB):

All three are close here, with ze­roserve a hair ahead at around 780 MB/s on one core. ng­inx’s usual trump card for large files is send­file(), which splices file pages from the page cache to the socket with zero user­space copies. Under TLS that path goes un­used: the bytes have to be en­crypted in user­space any­way (short of ker­nel TLS, which all three leave off), so every server is bound by the same en­crypt-and-write loop, and ze­roserve’s io_ur­ing read-and-write path is a touch faster at it.

eBPF vs Lua

The ob­vi­ous com­par­i­son for the script­ing is ng­inx + LuaJIT (ngx_http_lua_module), the usual way to run fast code in­side a web server. So I wrote the equiv­a­lent Lua for two cases and put them head to head.

One tun­ing knob mat­ters a lot here. ze­roserve ships with a con­ser­v­a­tive de­fault: it arms the script-pre­emp­tion timer every 2 ms. Fine gran­u­lar­ity makes it quick to throt­tle a mis­be­hav­ing script, but it taxes every well-be­haved one - at the de­fault, eBPF trails ng­inx Lua on a fully dy­namic re­sponse (about 32k req/​s against 41k). Bumping –preempt-timer-interval-ms to 10 re­cov­ers ~40% of script­ing through­put and turns that around:

Per-request header-in­jec­tion mid­dle­ware (script runs, sta­tic file is still served):

Fully dy­namic JSON re­sponse:

At the 10 ms in­ter­val, tuned eBPF wins both cases. On the mid­dle­ware case - a script shap­ing an oth­er­wise-sta­tic re­sponse - it beats ng­inx Lua by about 50%, with a tighter tail. On the fully syn­thetic re­sponse it edges ng­inx’s heav­ily-tuned con­tent_­by_lua too (47k against 41k). Both en­gines com­pile to na­tive code (LuaJIT is a trac­ing JIT; async-ebpf JITs the eBPF through uBPF), and with TLS en­cryp­tion as a shared per-re­quest cost, the tuned eBPF path comes out ahead on through­put. At the 2 ms de­fault, eBPF keeps the mid­dle­ware win but gives up the syn­thetic-re­sponse lead, so I’d run pro­duc­tion scripts at 10 ms.

As a re­verse proxy

Serving files is half the job; the other half is prox­y­ing to a back­end, which is the main rea­son most peo­ple reach for ng­inx or Caddy in the first place. ze­roserve does it from a script - zs_re­verse_proxy(“http://​127.0.0.1:9000) - and keeps a pool of up­stream con­nec­tions (up to 128 per back­end, 30 s idle) and reuses them across re­quests.

Getting a fair fight here takes care: ng­inx’s fa­mous de­fault closes up­stream con­nec­tions af­ter each re­quest, so keep-alive is en­abled ex­plic­itly (keepalive 128, prox­y_http_ver­sion 1.1, and a cleared Connection header), with Caddy reusing con­nec­tions as it does by de­fault. Each proxy ter­mi­nates TLS on a sin­gle core and for­wards to a shared plain­text back­end, a sep­a­rate 2-core server that sus­tains 100k req/​s on its own, so the mea­sure­ment iso­lates the prox­y’s own over­head.

Proxying a small (174 B) re­sponse:

ze­roserve’s pooled io_ur­ing proxy leads here, about 22% ahead of ng­inx (26.5k against 21.8k) and roughly 3.4× Caddy. For the typ­i­cal proxy work­load - for­ward­ing API calls, small JSON, an app server’s HTML - ze­roserve ter­mi­nates TLS and shut­tles the re­quest to the back­end faster than the ref­er­ence im­ple­men­ta­tion.

Large bod­ies tip the bal­ance back. Proxying a 100 KB re­sponse:

Once the prox­ied body is large, ng­inx’s buffer­ing moves bytes more ef­fi­ciently and pulls ahead, with Caddy slot­ting in be­tween and ze­roserve trail­ing. If your prox­ied re­sponses are large, ng­inx is the bet­ter tool; if they’re small and nu­mer­ous, ze­roserve is faster.

Memory

Idle, a sin­gle ze­roserve in­stance sits around 15 MB PSS - more than ng­inx’s ~6 MB, less than Caddy’s ~60 MB. On its own that’s un­re­mark­able. What makes it mat­ter is that the unit is a whole process: when you run a copy per core, they all map the same bi­nary, so the code pages are shared, and each ex­tra process adds lit­tle be­yond its own work­ing set.

ze­roserve is open source on GitHub - try it your­self!

The Smart TV in Your LivingRoom Is a Node in the AIScraping Economy

blog.includesecurity.com

The work at Include Security has us work­ing with AI day in and day out (hacking it, us­ing it, train­ing it, etc).

We’re all aware of the com­mu­nity-level op­po­si­tion hap­pen­ing against dat­a­cen­ters, aimed at im­prov­ing AI ca­pa­bil­i­ties, be­ing built re­cently. What you might not be aware of are the dis­trib­uted ef­forts to train AI that could be us­ing the de­vices in­side your home.

In this post, we’re go­ing to ex­plore how the com­pany Bright Data fa­cil­i­tates mod­ern AI mod­els scrap­ing train­ing data from the Internet us­ing its res­i­den­tial proxy net­work.

Bright Data is a data-col­lec­tion com­pany that sells ac­cess to what it mar­kets as the world’s largest res­i­den­tial proxy net­work of 400M+ home IP ad­dresses that its cus­tomers route web-scrap­ing traf­fic through. The sup­ply be­hind that net­work comes from an SDK: a piece of soft­ware em­bed­ded in con­sumer apps that, with the user’s con­sent, turns their phone or smart TV into one of those exit nodes.

We’ll doc­u­ment what you, the av­er­age user, should know about what this com­pa­ny’s SDK does on your sys­tems such as your mo­bile phone and your smart TV. We’re go­ing to ex­plore how their SDK works, which plat­forms have shipped it, and why your Internet-connected TV is the ul­ti­mate proxy for AI mod­els look­ing to train on data scraped from the Internet.

Why This Matters Now

AI com­pa­nies de­pend on web-scraped con­tent: for pre-train­ing, for re­trieval, for agent ground­ing, for search. But the mod­ern web is­n’t scrape­able from a dat­a­cen­ter. Cloudflare, DataDome, HUMAN, among oth­ers throt­tle or block re­quests from known cloud IPs.

The workaround is res­i­den­tial prox­ies. A scrap­ing job routed through a Comcast or T-Mobile sub­scriber’s con­nec­tion ar­rives at the tar­get site from an IP that be­longs to a pay­ing res­i­den­tial cus­tomer. Krebs re­ported in October 2025 that a glut of prox­ies from Aisuru and other sources is fu­el­ing large-scale data har­vest­ing ef­forts tied to var­i­ous AI pro­jects.” Academic mea­sure­ment go­ing back to 2019 shows these net­works are over­whelm­ingly mis­used. The FBI is­sued a for­mal ad­vi­sory ear­lier this year.

Most of the ex­ist­ing press has fo­cused on the il­le­gal res­i­den­tial-proxy sup­ply: bot­nets (Aisuru, Kimwolf), tro­janized apps (HUMAN Security’s PROXYLIB dis­clo­sure), pre-in­fected IoT hard­ware (Google/Mandiant’s IPIDEA take­down). These are the bad ac­tors.

On the other hand, the le­gal sup­ply side has re­ceived far less scrutiny. Today Bright Data is the largest res­i­den­tial proxy net­work in the world by its own mar­ket­ing, ad­ver­tis­ing 150M+ IPs” sourced via a con­sent SDK em­bed­ded in part­ner apps. This re­search doc­u­ments how that SDK works, which plat­forms have shipped it, and why the con­nected-TV is the ul­ti­mate res­i­den­tial proxy.

Why Connected TV (CTV) is the Ideal Proxy

Connected TV, a.k.a Smart TV, is a near-per­fect res­i­den­tial proxy. Compared to a mo­bile phone:

A TV never hits 1% bat­tery, jumps be­tween WiFi net­works or gets locked when the user is asleep. Some part­ner pub­lish­ers do dis­close the Bright Data re­la­tion­ship in their pri­vacy poli­cies PlayWorks is one ex­am­ple. But pri­vacy-pol­icy dis­clo­sure is the wrong con­trol sur­face for a TV. It is hard to scroll through a le­gal doc­u­ment nav­i­gated by ar­row keys on a re­mote, and the in-app con­sent di­a­log, does­n’t con­vey that a pay­ing Bright Data cus­tomer is about to route their scrap­ing traf­fic through the user’s home in­ter­net.

Petflix, a Roku app doc­u­mented by The Verge, is a rep­re­sen­ta­tive case. Its opt-in screen reads: To en­joy Petflix for free with fewer ads, you are al­low­ing Bright Data to oc­ca­sion­ally use your de­vice’s free re­sources and IP ad­dress to down­load pub­lic web data from the in­ter­net. Bright Data will only use your IP ad­dress for ap­proved busi­ness-re­lated use cases. None of your per­sonal in­for­ma­tion is ac­cessed or col­lected ex­cept your IP ad­dress. Period.” The Petflix di­a­log says occasionally.” The SDKs pub­licly queryable con­fig sets max_b­w_­month­ly_wifi: 200,000,000,000 bytes — a 200 GB de­fault monthly WiFi bud­get.

Who Bright Data Names as Partners

Bright Data ex­poses a part­ner man­i­fest end­point. The end­point is unau­then­ti­cated and any­one can fetch it. Names in the man­i­fest that I was able to iden­tify with high con­fi­dence from pub­lic sources:

Others (desoline, free_­time, ot­t_s­tu­dio, glob­al_mi­cro­trad­ing, m_m_­me­dia, easystaff_lp) are pre­sent but less iden­ti­fi­able from pub­lic sources. bright_screen­savers, bright_videos, and bright­data are Bright Data’s own apps.

A note on what the part­ner list proves: Being listed in Bright Data’s con­fig means an in­te­gra­tion might have ex­isted at some point. It does not by it­self prove that a spe­cific pub­lish­er’s cur­rently-ship­ping app(s) in­cludes the SDK in pro­duc­tion. For any named pub­lisher, per-app ver­i­fi­ca­tion is re­quired.

What the part­ner list does di­rectly prove:

Bright Data ships this ros­ter in an unau­then­ti­cated pub­lic end­point.

At least three CTV-focused en­ti­ties (PlayWorks, CloudTV, Longvision) mon­e­tized their user’s de­vices as res­i­den­tial proxy exit nodes. PlayWorks in par­tic­u­lar re­ports CTV dis­tri­b­u­tion across ma­jor TV plat­forms and ISPs, with reach fig­ures in the hun­dreds of mil­lions of house­holds per its own mar­ket­ing ma­te­ri­als.

How does the Bright Data SDK turn a user’s de­vice into a res­i­den­tial proxy exit node?

The Bright Data SDK is a pub­licly doc­u­mented com­mer­cial prod­uct, of­fered to pub­lish­ers via Bright Data’s SDK in­te­gra­tion docs (with a JavaScript vari­ant for web). What fol­lows builds on that pub­lic sur­face with find­ings from re­verse-en­gi­neer­ing the ship­ping iOS frame­work and in­stru­ment­ing 30 days of its run­time traf­fic.

The SDK ships as an iOS frame­work (brdsdk.framework) in­side part­ner apps. I re­verse-en­gi­neered the bi­nary and cap­tured 30 days of traf­fic from a re­search fleet run­ning the SDK in­side a con­sent-in­stalled part­ner app.

The Unauthenticated Config

On every launch the SDK calls:

GET <https://​clientsdk.bright-sdk.com/​sd­k_­con­fig_ios.json>?appid=<bundle>&ver=<sdk-version>&uuid=sdk-ios-<32hex>

The end­point is unau­then­ti­cated in any mean­ing­ful sense. The server gates only on two query pa­ra­me­ters ap­pid (an app bun­dle ID, which can be found in the App Store list­ing of the part­ner app) and ver (the SDK ver­sion string). Supply those and any ran­domly gen­er­ated UUID, and the server re­turns the same re­sponse a real de­vice gets: fea­ture flags, idle-de­tec­tion thresh­olds (battery %, CPU/memory ceil­ings, WiFi-vs-cellular rules), per-coun­try band­width tiers, and the part­ner man­i­fest I show­cased above. Each of these branches is worth ex­am­in­ing on its own: the idle rules that de­cide when your de­vice is el­i­gi­ble to re­lay, a flag that routes peer traf­fic around your VPN, a map that stitches your in­stalls across plat­forms into one iden­tity, and the per-coun­try band­width caps.

The Peer Tunnel

After con­fig fetch, the SDK opens a per­sis­tent WebSocket to:

wss://​prox­yjs.brdt­net.com:443

This host­name re­solves to AWS Global Accelerator IPs (3.33.193.183, 15.197.193.114 as of this writ­ing). The TLS cer­tifi­cate is CN=*.luminatinet.com — the do­main for Luminati Networks, Bright Data’s pre-2018 cor­po­rate name. The re­brand was pub­licly an­nounced in 2018. Active SDK in­fra­struc­ture still runs on the legacy cert, which is a use­ful de­tec­tion pivot: the cur­rent cus­tomer-fac­ing proxy ser­vice lives on bright­data.com-branded do­mains, so any lu­mi­natinet.com / brdt­net.com traf­fic on your net­work is specif­i­cally the peer-tun­nel plane, not cus­tomer-side Bright Data us­age. The server iden­ti­fies it­self as uWeb­Sock­ets: 20.

The peer end­point re­quires no au­then­ti­ca­tion to up­grade. The server ac­cepts any TLS-valid WebSocket up­grade and im­me­di­ately pushes the con­nect­ing client an ap­pli­ca­tion-layer frame with the clien­t’s pub­lic IP echoed back. From there, a hand­shake un­folds:

Server → client: tun­nel_init es­tab­lishes the ses­sion, re­turns the clien­t’s pub­lic IP.

Server → client: cid_set the server as­signs the client a ses­sion-track­ing iden­ti­fier in the for­mat <IP>-<token>/ls<N>c<M>p443_<IP>_<counter>. We con­firmed this for­mat matches the cid field pre­sent in the SDKs cap­tured teleme­try traf­fic from real de­vices.

Server → client: sta­tus_get the server polls the de­vice for its idle state, bat­tery, net­work type, and avail­able band­width. The de­vice re­sponds with a con­tin­u­ous teleme­try feed: idle, wifi_­con­nected, mo­bile_­con­nected, mo­bile_­type (LTE/5G), roam­ing, bat­tery_level, us­ing_­bat­tery, screen_on, on_­call, cpu_us­age, mem_us­age, raw_bw, bw, ipv6_­sup­ported, ap­pid (the host app), sd­k_ver­sion, plat­form, and the as­signed cid. This is a con­tin­u­ous feed of phys­i­cal-de­vice state to a third party, de­liv­ered via a con­sent di­a­log whose text is cho­sen by the host app pub­lisher.

Handshake com­plete. Once the de­vice re­ports fa­vor­able sta­tus, the server’s job-match­ing layer is free to push cmd_­tun frames: in­di­vid­ual scrap­ing-job in­struc­tions that the SDK ex­e­cutes as HTTP re­quests against third-party sites, us­ing the user’s res­i­den­tial IP as the source.

Every frame on the WebSocket is plain JSON with a fixed en­ve­lope:

{“type”: ipc_call”|“ipc_post”|“ipc_result”|“ipc_error”,“cmd”:  <command>, cookie”: <correlation-id>,“err_code”: 0, msg”: { …payload… }}

The full com­mand vo­cab­u­lary ex­tracted from the bi­nary and ver­i­fied on the wire:

There’s no mes­sage sign­ing, HMAC, client cer­tifi­cate or de­vice at­tes­ta­tion. Only the TLS layer and the server’s IP-reputation fil­ter gat­ing which peers ac­tu­ally re­ceive jobs. For read­ers fa­mil­iar with com­mer­cial mal­ware pro­to­col de­sign: this is sub­stan­tially less se­cure than typ­i­cal C2.

When the SDK con­sid­ers you idle”

The con­fig ships an ex­plicit rule­book for when the de­vice is el­i­gi­ble to re­lay some­one else’s traf­fic:

idle_metrics”: {  ignore_screen_on”: true,      // re­lay even with the screen on  ignore_on_call”: true,        // re­lay while the user is on a phone call  max_bw_ratio”: 1,  min_battery”: 0.2,  wifi_on_battery”: true,  min_battery_wifi”: 0.2,  max_cpu_usage”: 70,  max_mem_usage”: 90,  mem_screen_off”: true,  idle_timeout”: 30,  not_idle_timeout”: 10}

The ig­nore_screen_on and ig­nore_on_­call flags are no­table: idle” does not mean the user is away from the de­vice. It means the de­vice’s CPU, mem­ory, and bat­tery are within the SDKs thresh­olds. A user on a phone call, ac­tively read­ing the screen, is con­sid­ered idle for re­lay pur­poses.

Cross-Platform Identity Linkage

The con­fig also ships a dual_­pair­ing map:

dual_pairing”: {  ios_com.brd.earnapp”: [“win_earnapp.com”, mac_com.earnapp”]}

That’s a server-side map ty­ing a user’s iOS, Windows, and ma­cOS in­stal­la­tions of the same brand into one en­tity. It’s cross-plat­form iden­tity stitch­ing doc­u­mented in­side a pub­lic con­fig file.One more for­ward-look­ing field: http3_en­abled: true. The SDK is al­ready ship­ping the flag for QUIC-based peer trans­port. A fu­ture ver­sion may move the peer tun­nel from TCP/443 to UDP/443, which would break any de­fender re­ly­ing on TCP con­nec­tion track­ing to de­tect the WebSocket.

The Inspection Bypass

The SDKs con­fig ships a flag use_netifs”: true. That flag trig­gers code in the SDK bi­nary that con­structs its NWConnection with a spe­cific re­quired in­ter­face: en0 (WiFi) or pdp_ip0 (cellular), rather than us­ing the sys­tem de­fault route.

On iOS, this by­passes any con­fig­ured VPNs tun0 in­ter­face en­tirely. The peer tun­nel does not cross a user-con­fig­ured VPN, even when the rest of the ap­p’s HTTPS traf­fic does.

We ob­served this em­pir­i­cally. My re­search setup in­cludes trans­par­ent TLS in­ter­cep­tion. It cap­tured every HTTPS call the SDK made, ex­cept the peer tun­nel to prox­yjs.brdt­net.com:443, even though port 443 is ex­plic­itly redi­rected to the in­spec­tor. The by­pass uses Apple’s doc­u­mented NWParameters.requiredInterface API.

It’s worth em­pha­siz­ing that the SDK uses two in­de­pen­dent in­spec­tion by­passes, one per plane:

Control plane (config fetch, teleme­try pings): built on CFNetwork’s CFHTTPMessage prim­i­tives rather than URLSession/NSURLConnection. This de­feats URLSessionlevel in­stru­men­ta­tion (swizzling, net­work ex­ten­sions, URLProtocol sub­classes) com­monly used in mo­bile app-sec tool­ing, while still re­spect­ing the sys­tem proxy and so re­main­ing vis­i­ble to TLS-intercepting re­searchers.

Data plane (peer tun­nel): built on NWConnection with re­quired­In­ter­face set to the phys­i­cal in­ter­face. This is what de­feats VPNs and en­sures the scrap­ing is ex­e­cuted from a res­i­den­tial IP.

Both choices are le­git­i­mate Apple APIs. The com­bi­na­tion is the in­ter­est­ing ar­ti­fact: the data plane is in­vis­i­ble to VPN-based in­spec­tion and the con­trol plane is in­vis­i­ble to URLSession-based hooks. Researchers who rely on ei­ther sin­gle tech­nique see only half the SDKs be­hav­ior.

For en­ter­prise se­cu­rity teams run­ning MDM, cor­po­rate-VPN-based traf­fic in­spec­tion, or home-router parental con­trols: the most sen­si­tive chan­nel this SDK op­er­ates is de­signed to go around your vis­i­bil­ity layer.

The ge­og­ra­phy tiers

The con­fig ships per-coun­try band­width thresh­olds. Four coun­tries get ex­plicit non-de­fault poli­cies:

Looking at the con­fig, Uzbekistan and Oman de­vices are per­mit­ted to re­lay down to 1% bat­tery, with daily caps 20× the de­fault and monthly caps 60× the de­fault. Qatar and UAE de­vices are throt­tled be­low de­fault.  We can only spec­u­late as to why the tiers are drawn this way. One read­ing is de­lib­er­ate mar­ket seg­men­ta­tion, re­lax­ing lim­its where grid power is sta­ble and throt­tling where mo­bile data is ex­pen­sive. The de­fault-world­wide al­lowance still per­mits 500 MB of some­one else’s traf­fic per month over the user’s home in­ter­net.

Testing Setup and Methodology

Three data sources:

Thirty days of TLS-inspecting proxy cap­tures from iOS de­vice run­ning con­sent-in­stalled part­ner apps (including XYO COIN, which em­beds the Bright SDK).

Static analy­sis of the SDK bi­nary (brdsdk.framework, ver­sion 1.532.120, iOS ar­m64).

All spe­cific Bright Data host­names, cert fin­ger­prints, and TLS in­fra­struc­ture de­scribed are pub­licly ob­serv­able by any­one mak­ing the same re­quests. No ses­sion-spe­cific iden­ti­fy­ing data from ei­ther the re­search fleet or the re­search client ap­pears in this doc­u­ment.

Timeline

May 11, 2026 — Email no­tice sent to pri­vacy@bright­data.com no­ti­fy­ing their team about the re­lease of this blog post. No re­sponse to the no­ti­fi­ca­tion has been re­ceived at the time of this ar­ti­cle’s pub­lish­ing.

Defense Approaches

The traf­fic leaves clear fin­ger­prints at the net­work bound­ary, and the SDK leaves iden­ti­fi­able sym­bols in the app bi­nary. The ap­proaches be­low let you de­tect and block the peer tun­nel — at the net­work level or on the de­vice it­self. Three ap­proaches, or­dered by ease of de­ploy­ment:

Approach 1: DNS block (trivial, ef­fec­tive for net­work-routed de­vices):

prox­yjs.brdt­net.com­pro­x­yjs.lu­mi­natinet.com­pro­x­yjs.bright-sdk.com­clientsdk.bright-sdk.com­clientsdk.brdt­net.com

Blocking prox­yjs.* kills the peer tun­nel with­out af­fect­ing any cus­tomer who le­git­i­mately uses Bright Data’s cus­tomer-fac­ing proxy ser­vice on a dif­fer­ent do­main.

Approach 2: TLS SNI fil­ter­ing: Drop or alert on TLS hand­shakes where serv­er_­name matches *.brdtnet.com, *.luminatinet.com, or *.luminati.io. Works at the net­work bound­ary with­out TLS in­spec­tion.

Approach 3: TLS cer­tifi­cate fin­ger­print:

.brdtnet.com → SHA256 313ce4ec7d5a51e5…

.luminatinet.com → SHA256 5028612e625befea…

Stable un­til Sectigo cert ro­ta­tion (current certs valid through mid-2026).

The use_netifs caveat: All three lay­ers only work on traf­fic that crosses your net­work bound­ary. The SDKs use_netifs bind­ing means that on iOS, when the de­vice is on cel­lu­lar, peer traf­fic by­passes cor­po­rate WiFi en­tirely. For man­aged fleets, the com­ple­men­tary con­trol is MDM-based app bi­nary scan­ning: search in­stalled apps for the Swift sym­bols BrdWebSocketFacade and BrdNetwork.DNSResolver, and pro­hibit apps con­tain­ing them on cor­po­rate-is­sued de­vices.

For house­hold users con­cerned about a spe­cific smart TV or mo­bile app: block the host­names above at your router’s DNS set­tings (Pi-hole, NextDNS, Cloudflare Gateway, your ISPs equiv­a­lent).

This blog post was writ­ten in part­ner­ship with our guest au­thor and in­de­pen­dent se­cu­rity re­searcher Buchodi.

New U.S. college grads now have higher unemployment than the average worker

www.randalolson.com

Part of Teaching an AI Agent to Make Beautiful Charts

A fresh col­lege de­gree used to come with a quiet edge in the job mar­ket. New grads had bet­ter odds of land­ing work than the av­er­age worker, and that edge held for as long as any­one tracked it. Not any­more. They now face higher un­em­ploy­ment than the work­force as a whole, and the gap is the widest on record.

What makes this strange is the tim­ing. The re­ver­sal did not start with ChatGPT, and it did not start with the pan­demic. It started in early 2019, be­fore ei­ther one was on the radar.

The chart tracks a sin­gle num­ber, a re­cent grad’s un­em­ploy­ment rate mi­nus the rate for all work­ers. Below the zero line grads come out ahead of the typ­i­cal worker, and above it they fall be­hind.

The com­par­i­son is worth pin­ning down. All work­ers” is the whole U.S. la­bor force, and most of them are older and more ex­pe­ri­enced than a new grad­u­ate, so a fresh grad starts at a nat­ural dis­ad­van­tage. For decades the de­gree more than can­celed that dis­ad­van­tage out. Now it does not.

For decades, the de­gree was a buffer

A re­cent grad al­most al­ways had a bet­ter shot at be­ing em­ployed than the av­er­age worker. The cush­ion was real, and it was biggest ex­actly when the econ­omy was worst.

The edge peaked in the depths of the Great Recession. In mid-2010, grads were around 7% un­em­ploy­ment while the work­force over­all ran close to 10%, the widest that ad­van­tage ever got. Recessions gut con­struc­tion and man­u­fac­tur­ing first, sec­tors that lean heav­ily on work­ers with­out de­grees, so a diploma was worth the most pre­cisely when jobs were van­ish­ing.

The edge van­ished in 2019, be­fore AI and be­fore COVID

In February 2019 the gap crossed zero, and the 12-month av­er­age has stayed pos­i­tive every month since. That tim­ing rules out both of the easy ex­pla­na­tions. The flip pre­dates the gen­er­a­tive-AI boom by years, and COVID by a year.

This was a slow struc­tural drift, not a sud­den shock. The Cleveland Fed traces the ero­sion back fur­ther still. The job-find­ing ad­van­tage of young grads has been fad­ing since around 2000, and their edge over high-school work­ers closed around 2019. The pan­demic did not cause it ei­ther, and the 2020 spike is the clear­est ev­i­dence. When un­em­ploy­ment ex­ploded that year, both lines shot up to­gether, so the gap held roughly steady through 2020 and 2021. The penalty was al­ready there. The lock­downs just buried it un­der a big­ger num­ber.

Now it is the widest gap on record

By early 2026 re­cent grads sat at 5.6% un­em­ploy­ment against 4.2% for all work­ers, the widest gap on record. The spread has grown in al­most every year since the 2019 flip.

What makes the record stranger is the back­drop. This is not a re­ces­sion story. Overall un­em­ploy­ment sits at a healthy 4.2%, yet new grads are the ones strug­gling. Every ear­lier spike in their un­em­ploy­ment ar­rived with a broad down­turn. This one is theirs alone.

Unemployment is only half the pic­ture. Of the new grads who do have jobs, about 41% are un­der­em­ployed, work­ing roles that never re­quired a de­gree in the first place.

Remote work, or AI?

So what broke? The hon­est an­swer is that econ­o­mists are still ar­gu­ing about it. In June 2026 the New York Fed made the case that re­mote work, not AI, is the main cul­prit, pin­ning about 64% of the rise in young-grad un­em­ploy­ment on it. Employers, the Fed ar­gues, are wary of hir­ing in­ex­pe­ri­enced peo­ple into re­mote roles, where the on-the-job men­tor­ship that turns a new grad into a pro­duc­tive worker is hard to de­liver. The tim­ing fits, since the climb started well be­fore AI took hold.

Stanford re­searchers see AIs fin­ger­prints any­way. Their study found that early-ca­reer work­ers ages 22 to 25 in the most AI-exposed jobs saw em­ploy­ment fall about 16% since late 2022, a drop that sur­vived even af­ter they stripped out re­mote-friendly roles. Both can be true. Either way, the en­try-level rungs are the ones be­ing pulled out, and tech is the sharpest edge. Recent com­puter sci­ence grad­u­ates now post some of the high­est un­em­ploy­ment rates of any ma­jor, af­ter the num­ber of CS de­grees more than dou­bled into a shrink­ing pile of open­ings.

The on-ramp broke, not the de­gree

This is an en­try-level prob­lem, not proof that a de­gree stopped pay­ing off. Older de­gree-hold­ers are do­ing fine. U.S. work­ers 25 and up with a bach­e­lor’s or more had just 2.8% un­em­ploy­ment in April 2026, per the Bureau of Labor Statistics, com­fort­ably be­low the rate for high-school grads. The dam­age is con­cen­trated al­most en­tirely in the young. Since 2019, re­cent grads have taken the brunt of the rise while un­em­ploy­ment for older de­gree-hold­ers has barely moved, per the St. Louis Fed, and the New York Fed still pegs the life­time re­turn on a de­gree near 12.5%.

New grads have not fallen be­hind their peers who skipped col­lege, ei­ther. Young work­ers with­out a de­gree sit at 7.2% un­em­ploy­ment, well above the grads’ 5.6%. A de­gree still beats no de­gree. What it no longer does is beat the av­er­age.

None of this is set­tled. The Economic Policy Institute ar­gues the pic­ture is more mixed, with the col­lege wage pre­mium flat for years and new grads still do­ing no worse than young work­ers with­out de­grees. The de­gree still opens the door. It just no longer gets you through it faster than every­one else.

How this chart was made

This chart was built by an AI agent and graded against the Tufte Test, a data vi­su­al­iza­tion qual­ity stan­dard built by Goodeye Labs on Truesight.

Data source: The Labor Market for Recent College Graduates from the Federal Reserve Bank of New York, built from the U.S. Census Bureau and Bureau of Labor Statistics Current Population Survey. Recent grad­u­ates” are non­stu­dents ages 22 to 27 with at least a bach­e­lor’s de­gree, young work­ers” are ages 22 to 27 with­out one, and all work­ers” are ages 16 to 65. The cleaned dataset is avail­able here.

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.