dotnet

Stars and mentions

Last 30 days, Oct 4 – Nov 2

GitHub stars930
mentions119
014284256Oct 4Oct 18Nov 2
UTC days · repositories dotnet owns52 repositories

Package downloads

Cumulative recorded downloads · since 2025-10-18

crates.io—
npm363.2M
PyPI—
090.8M181.6M272.4M363.2MOct 4Oct 18Nov 2
Oct 26crates.io: 0/1 packages observed · npm: 1/12 packages observed · PyPI: 0/3 packages observed

Across 16 known packages in repositories dotnet owns. Cumulative totals include recorded downloads before the selected range. Missing observations leave gaps; totals may be incomplete. PyPI excludes known mirrors.

Top repositories

dotnet’s repositories ranked by pull request authors, as on trending.

Loading

Contributes to

Repositories outside dotnet’s account where dotnet opened or reviewed pull requests in the past 365 days.

Who noticed dotnet

Everyone who starred or forked dotnet’s repositories since the archive began in 2011.

Notable accounts

Ordered by the stars their own repositories have received.

Biggest fans

Accounts that starred the most of these repositories.

Breakout weeks

Each repository’s best week of new stars, with the release published around it.

Mentions

118posts
Nov 32 posts
  1. Stop Pulling a NuGet Package for zstd. .NET 11 Has ZstandardStream TL;DR: .NET 11 puts Zstandard (zstd) right next to GZipStream and BrotliStream. new ZstandardStream(output, CompressionLevel.Optimal) is the whole setup, and HttpClient can decompress zstd responses with one flag. .NET 11 is at Release Candidate 2, which ships with a go-live support license, but it is not GA yet. Everything below was run on the .NET 11 RC 1 SDK. The package nobody wanted to own zstd is everywhere now. Log pipelines, databases, and HTTP all speak it. .NET, meanwhile, made you pick a third-party wrapper, check that its native binary ships for every platform you deploy to, and hope it is still maintained next year. It is the dependency that shows up in every security review and that nobody remembers adding. What .NET 11 adds The new types live in System.IO.Compression: - ZstandardStream - a Stream, used exactly like GZipStream. - ZstandardEncoder and ZstandardDecoder - span-based, no stream needed. - ZstandardDictionary - train a dictionary for lots of small, similar payloads. - ZstandardCompressionOptions - quality, window size, checksums, long-distance matching. On the HTTP side, DecompressionMethods.Zstandard and ZstandardCompressedContent are new too. The minimal version If you have ever written a GZipStream, you already know this API: using System.IO.Compression; byte[] raw = File.ReadAllBytes("app.log"); using var output = new MemoryStream(); using (var zstd = new ZstandardStream(output, CompressionLevel.Optimal)) { zstd.Write(raw); } byte[] compressed = output.ToArray(); using var input = new MemoryStream(compressed); using var unzstd = new ZstandardStream(input, CompressionMode.Decompress); using var back = new MemoryStream(); unzstd.CopyTo(back); Swap GZipStream for ZstandardStream and the rest of your code does not notice. I ran all three built-in compressors over the same payload: 2,000 lines of fake JSON logs, 188,010 bytes, all at CompressionLevel.Optimal. | Format | Compressed size | 200 runs on my machine | |---|---|---| | GZip | 13,499 bytes | 218 ms | | Brotli | 11,936 bytes | 299 ms | | Zstandard | 11,891 bytes | 150 ms | Smallest and fastest in one row. It is a synthetic, very repetitive payload and one run on one machine, so treat the timings as a hint, not a benchmark. The .NET team's own numbers on the Canterbury Corpus are in the Preview 1 release notes linked below. Want more squeeze? ZstandardCompressionOptions takes a Quality from -131072 to 22, and the default is 3. var options = new ZstandardCompressionOptions { Quality = 19, AppendChecksum = true }; using var zstd = new ZstandardStream(output, options); At quality 19 with a checksum, the same log payload came out at 7,535 bytes. The realistic one: thousands of tiny messages Compression is bad at tiny payloads. A 150-byte JSON event has almost no repetition inside it, so there is little to squeeze. The repetition is between messages: the same keys, the same enum values, the same shape, every time. That is what zstd dictionaries are for. You train one on sample messages, then both sides use it: using System.IO.Compression; // training: byte[][] of real messages, e.g. the last 1,000 events // message: one new event as byte[] byte[] samples = [.. training.SelectMany(b => b)]; int[] lengths = [.. training.Select(b => b.Length)]; using ZstandardDictionary dictionary = ZstandardDictionary.Train(samples, lengths, maxDictionarySize: 4096); byte[] buffer = new byte[1024]; ZstandardEncoder.TryCompress( message, buffer, out int written, dictionary, ZstandardCompressionOptions.DefaultWindowLog2); byte[] restored = new byte[message.Length]; ZstandardDecoder.TryDecompress( buffer.AsSpan(0, written), restored, out int read, dictionary); I trained a 4,096-byte dictionary on 1,000 order.created events, then compressed 200 new events it had never seen: | | Average size | |---|---| | Raw JSON | 152.2 bytes | | zstd, no dictionary | 134.7 bytes | | zstd with dictionary | 38.6 bytes | Without a dictionary, zstd barely dented them. With one, the average message went from 152.2 bytes to 38.6. That is the difference between "compression is not worth it for our queue" and "why did we not do this sooner". HttpClient speaks zstd now Automatic decompression is one flag: using var client = new HttpClient(new SocketsHttpHandler { AutomaticDecompression = DecompressionMethods.Zstandard | DecompressionMethods.GZip }); string body = await client.GetStringAsync("https://example.com/api/report"); With that handler, the client sent Accept-Encoding: gzip, zstd. My local test server answered 8,000 bytes of JSON as 33 bytes of zstd, and GetStringAsync handed back all 8,000 characters. Going the other way, ZstandardCompressedContent compresses a request body while it is sent and sets Content-Encoding: zstd for you: var json = new StringContent(payload, Encoding.UTF8, "application/json"); await client.PostAsync(url, new ZstandardCompressedContent(json)); 300 copies of a small JSON object reached my test server as 36 bytes. Make sure the server actually accepts zstd request bodies before you turn this on. Can I use it? - .NET 11 only. The types ship in the shared framework; my test apps had no package references at all. - Status: .NET 11 is at RC 2 with a go-live license; GA is still ahead. Gotchas 1. The int after the dictionary is not quality. In TryCompress(source, dest, out written, dictionary, 3), that 3 is windowLog2, and the call threw ArgumentOutOfRangeException: windowLog2 ('3') must be greater than or equal to '10'. Pass ZstandardCompressionOptions.DefaultWindowLog2 (23), or set quality with ZstandardDictionary.Create(buffer, quality). 2. Both sides need the same dictionary. Decoding a dictionary-compressed message without it did not throw; TryDecompress just returned false. Version your dictionaries and ship the id with the message. 3. HttpClient does not opt in by itself. A default HttpClient sent no Accept-Encoding at all. When my test server sent zstd anyway, the default client got the 33 raw zstd bytes, not the JSON. 4. Preview samples are already stale. The Preview 1 notes use WindowLog; the RC has WindowLog2, MinWindowLog2, and MaxWindowLog2. Trust the API docs over old blog snippets, this one included once GA lands. Wrap-up Zstandard is now just another Stream in the box, with dictionaries and HTTP support on top. Grab the .NET 11 RC SDK, swap one GZipStream for ZstandardStream, and see what your payloads say. Sources - What's new in .NET 11 libraries - Microsoft Learn - .NET 11 Preview 1 libraries release notes - dotnet/core - ZstandardStream class - .NET API reference - dotnet/runtime #119575 - Zstandard compression support - dotnet/runtime #123531 - Zstandard in DecompressionMethods - .NET 11 Release Candidate 2 - .NET Blog This article was written with the help of AI and checked against the sources above.

  2. Ran an experiment that ran for a day that resulted in ~90 PRs to improve the performance of asp.net Core: aspnetcore#70219 Fun facts: - These were using Github's new cloud agents support that shipped this week at GHU. - Opus 5.5 is very good - ~$230 in tokens. #dotnet #aspnetcore

    111 likes · 10 reposts · 8,526 views
  3. 【CVE-2026-111497】Brotliの脆弱性(Broccoli2Shell) 概要 Googleが開発するデータ圧縮ライブラリ「Brotli」において、細工された圧縮ストリームを展開(デコード)する際に境界外書き込みが発生する脆弱性が公表された。未認証の遠隔の攻撃者から悪意あるBrotli圧縮ストリームを処理させられることで、隣接するデコーダー状態メモリの破壊、機密メモリ内容の漏洩、サービス拒否(DoS)、または任意のコード実行(RCE)に至る可能性がある1。 項目 内容 CVE ID CVE-2026-111497 対象ソフトウェア Google Brotli(C言語デコーダー実装 c/dec/decode.c) CVSSスコア ・9.2 Critical (CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N — Google CNA / GitHub) ・9.8 Critical (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H — NVD) ・8.1 High / Important (CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H — Microsoft / Red Hat / AWS) 脆弱性種別 CWE-252: 戻り値の未チェック(Unchecked Return Value)、CWE-787: 境界外書き込み(Out-of-bounds Write) 発見・報告者 Khai Tran 氏、Sylvie Mayer 氏(OpenAI Codex Security) 悪用・KEV登録状況 Imperva社がNode.js 24.22.0(Linux arm64公式イメージ)環境でのRCE実証を報告。2026年10月27日付で米国CISA KEVに追加(対策期限: 2026年11月10日) 本脆弱性は、発見から波及範囲の判明に至る経緯の中で以下の3つの通称で呼ばれている。 Node2Shell: 2026年10月21日のNode.jsセキュリティリリース公表後、Imperva Red TeamがNode.js組み込みBrotliデコーダーにおける解析およびRCE実証に命名した呼称である4。 HTTP2Shell: 当初はNode.jsに関連して「Node2Shell」と呼称されていたが、根本原因がHTTP通信(Content-Encoding: br)等で広く利用されるBrotliのC言語実装にあり、複数製品が影響を受けることから「HTTP2Shell」とも呼称されている(HTTP/2プロトコル自体の脆弱性ではない)5。 Broccoli / Broccoli2Shell: セキュリティ研究者のJuliano Rizzo氏が「Node2Shellという名称は影響範囲を狭く見せてしまう」として「Broccoli」と呼ぶことを提唱したことに端を発し、追跡サイト「broccoli2shell.com」等で用いられている呼称である6。 影響を受けるバージョンと修正バージョン 1. Brotli の影響範囲と修正状況 Brotliの公式C実装において、バージョン 1.0.3 から 1.2.0 までのデコーダーが影響を受ける。各系統の修正バージョンは以下の通りである1。 系統 / ブランチ 影響を受けるバージョン 修正版 補足 1.0 系 >= 1.0.3, < 1.0.10 1.0.10 コミット 50a9b70a(0ed4268a のバックポート) 1.1 系 >= 1.1.0, < 1.1.1 1.1.1 コミット da980636(0ed4268a のバックポート) 1.2 系 >= 1.2.0, < 1.2.1 1.2.1 コミット b418fa81 / 0ed4268a 2. 主要ランタイム・言語パッケージ・アプライアンスの対応状況 BrotliのC言語デコーダーをバンドルまたはバインディングとして組み込んでいる各ランタイム・パッケージおよびネットワーク製品の対応状況は以下の通りである。 ランタイム / 製品 影響を受けるバージョン 修正版 補足 Node.js (v24 LTS) 24.x(< 24.22.1) v24.22.1 / v24.22.2 3 v24.22.1 で修正(v24.22.2 はリグレッション修正版) Node.js (v22 LTS) 22.x(< 22.23.4) v22.23.4 組み込みBrotliの更新(2026年10月セキュリティリリース) Node.js (v26 Current) 26.x(< 26.12.1) v26.12.1 組み込みBrotliの更新(EOL系統の <= 20.x, 23.x, 25.x も影響対象) Bun >= 1.0.21, < 1.4.4 1.4.4 8 バンドルするBrotliを 1.2.0 から 1.2.1 へ更新(GHSA-663w-cv8g-jj2p) Python (brotli / PyPI) >= 1.0.3, < 1.2.1 1.2.1 7 PyPI上の公式C拡張パッケージ(GHSA-9v8h-qqgm-4m35) Python (brotlicffi / PyPI) >= 1.0.3, < 1.2.1.0 1.2.1.0 Brotli C実装のCFFIバインディング(GHSA-9v8h-qqgm-4m35) .NET 8 / 9 / 10 8.0.0〜8.0.32 9.0.0〜9.0.21 10.0.0〜10.0.13 Runtime 8.0.33 Runtime 9.0.22 Runtime 10.0.14 9 SDKは 8.0.133/8.0.427、9.0.123/9.0.320、10.0.114/10.0.403 で修正 Palo Alto Networks PAN-OS PAN-OS 12.2 / 12.1 / 11.2 の未修正版 12.2.4-h1 12.1.7-h11 等 6 データプレーンでTLS復号とBrotli検査を有効化している場合のみ影響(デフォルト無効) 3. 主要Linuxディストリビューションの対応状況 OSの共有ライブラリ(libbrotli)およびディストリビューション提供パッケージにおける対応状況は以下の通りである。ディストリビューションによってはNode.jsや.NETパッケージがOS標準の共有ライブラリ(libbrotli)を動的リンクしているため、影響有無がパッケージ単位で異なる点に注意が必要である。 ディストリビューション 対象パッケージ 修正版 補足 Amazon Linux 2023 brotli, libbrotli, python3-brotli nodejs24, dotnet8.0 1.1.0-1.amzn2023.0.2 アドバイザリ: ALAS2023-2026-3334(brotli)、ALAS2023-2026-3330(nodejs24)、ALAS2023-2026-3367(dotnet8.0)。nodejs22・dotnet9.0・dotnet10.0 は影響なし Ubuntu brotli, libbrotli1, python3-brotli dotnet8, dotnet10, dotnet11 24.04 LTS: 1.1.0-2ubuntu0.1 22.04 LTS: 1.0.9-2ubuntu0.1 アドバイザリ: USN-8956-1(Brotli)、USN-8958-1(.NET 8.0.33 / 10.0.14) Debian brotli, libbrotli1 Trixie (13): 1.1.0-2+deb13u1 Bookworm (12): 1.0.9-2+deb12u1 DSA-6582-1 / DLA-4855-1。Debianの nodejs と python-brotlicffi はシステムの brotli を利用 RHEL (Red Hat) brotli, nodejs:24, dotnet8.0, firefox, thunderbird 提供済(各RHSA) RHSB-2026-015(Important)。RHEL 8/9の nodejs:22、RHEL 10の nodejs22/nodejs24、および dotnet9.0/10.0/11.0 はシステムの brotli を利用 4. AWS マネージドサービスの対応状況 AWSの 2026-155-AWS で公表されている主要マネージドサービスの対応状況は以下の通りである10。 対象サービス / リソース 修正版 補足 AWS Lambda(Node.js 22/24/26、.NET 8/9/10) 更新済 マネージドランタイム・ベースイメージ更新済。コンテナイメージ利用時は最新ベースイメージでの再ビルドが必要(Python/Ruby/provided.al2023 はBrotli非同梱) AWS Elastic Beanstalk (Node.js) 6.11.12 Node.js 24.22.1 および 22.23.4 を含むプラットフォームバージョン 6.11.12 を提供済 AWS App Runner / Amplify / CodeBuild / Synthetics 更新済 App Runnerは2026年10月26日に全リージョン更新済(未デプロイのサービスは1回再デプロイが必要) 影響を受ける条件 本脆弱性は、ライブラリがシステム上に存在するだけでは発火せず、「信頼できないBrotli圧縮ストリームを、対象アプリケーションやプロキシがデコード(展開)する」 処理が行われた際に成立する6。 1. 前提となるシステム・ネットワーク構成 攻撃が成立する代表的な経路は以下の4パターンである。 パターン1:クライアントからの圧縮HTTPリクエストボディを展開するサーバー / API クライアントから送信された Content-Encoding: br ヘッダー付きのリクエストボディを、ミドルウェア(Node.jsのExpress 5.0.0〜5.3.0やbody-parser 2.0.0〜2.3.0における raw() / json() 等)やバックエンドアプリケーションが自動または明示的にBrotli展開している構成(なお、素の node:http サーバー自体は単体ではリクエストボディを自動展開しない)。 パターン2:外部サーバーからの圧縮HTTPレスポンスを展開するHTTPクライアント / クローラー 接続先サーバーから返却された Content-Encoding: br レスポンスを自動展開する構成(Node.jsやBunの組み込み fetch()、Brotliパッケージがインストールされた環境でのPython urllib3 / requests / aiohttp / httpx[brotli] 等)。 多くのHTTPクライアントはリクエスト時に Accept-Encoding: br を送信したかどうかにかかわらず、レスポンスヘッダーに Content-Encoding: br が含まれていれば自動的にBrotliデコーダーを呼び出す。そのため、任意のURLを取得するWebhook、リンクプレビュー、クローラー、または非暗号化通信経路上の中間者攻撃において、悪意あるサーバーから一方的に Content-Encoding: br のレスポンスを返されるだけでトリガーされ得る。 パターン3:ファイアウォール等のデータプレーンにおけるトラフィック検査 Palo Alto NetworksのPAN-OS(PA-Series / VM-Series)において、データプレーン上でTLS/SSL復号(Decryption)とBrotliインスペクションの両方が有効化されている場合、通過するBrotliトラフィックをファイアウォール自身が展開するため影響を受ける。 パターン4:デコーダーAPIの直接呼び出し・ファイル処理 アーカイブ、キャッシュ、メッセージキュー等の外部データを、node:zlib(brotliDecompress*、createBrotliDecompress)、Web標準の DecompressionStream、Pythonの brotli.decompress()、.NETの BrotliStream / BrotliDecoder 等で直接展開している場合。 ! 影響を受けないケース: 圧縮(エンコード)のみ・静的 .br ファイル配信のみの構成: 脆弱性はデコーダー(c/dec/decode.c)に存在するため、信頼できるデータをサーバー側でBrotli圧縮して送信するのみの構成や、事前圧縮された静的な .br ファイルをそのまま配信するだけの構成ではデコード処理が行われず、影響を受けない。 .NET のデフォルト挙動: Microsoftのアドバイザリによれば、ASP.NET Coreおよび HttpClient はデフォルトではBrotli圧縮データを自動展開しない。自動展開でBrotliを有効化するよう明示的に設定変更している場合、または System.IO.Compression(BrotliStream / BrotliDecoder)で信頼できないデータを直接展開している場合のみ影響を受ける9。 2. スタンドアロン実行ファイル・コンテナイメージに関する注意点 自己完結型バイナリ(Self-Contained / 単一実行ファイル): .NETのSelf-ContainedデプロイやNativeAOTでビルドされたアプリケーション、およびBunの bun build --compile で生成された実行ファイルは、ビルド時点のランタイム(および脆弱なBrotliデコーダー)をバイナリ内部に埋め込んでいるため、ランタイム更新後にアプリケーションの再ビルドと再デプロイが必要である。 動的リンクとバンドルの違い: Red Hat Enterprise LinuxやDebian、Amazon Linux 2023では、パッケージによってBrotliを内部バンドルしているもの(例:AL2023およびRHEL 8/9の nodejs24、dotnet8.0)と、OSの共有ライブラリ libbrotli を動的リンクしているもの(例:Debianの nodejs、RHEL 8/9およびAL2023の nodejs22、dotnet9.0、dotnet10.0)が混在している。したがって、OSパッケージとランタイムの双方を確認・更新し、実行中のプロセスを再起動する必要がある。 3. 自環境の確認方法 1. Node.js のバージョンおよびバンドルされている Brotli バージョンの確認 node --version node -p "process.versions.brotli" 2. Bun のバージョン確認(1.4.4 以上であるか) bun --version 3. .NET ランタイムのバージョン確認(8.0.33 / 9.0.22 / 10.0.14 以上であるか) dotnet --list-runtimes 4. Python パッケージの確認(Brotli 1.2.1 以上、brotlicffi 1.2.1.0 以上であるか) python3 -m pip show brotli brotlicffi 5. Linux OSパッケージおよびロード済み共有ライブラリの確認 dpkg -l | grep -i brotli # Debian / Ubuntu rpm -qa | grep -i brotli # RHEL / Amazon Linux sudo grep -l brotlidec /proc/[0-9]*/maps 2>/dev/null 脆弱性の技術的な仕組み 前提:Brotliのブロック切り替えと高速パス(Fast Path)/ 安全パス(Safe Path) Brotliの圧縮ストリームは複数のブロックに分割されており、デコーダーはストリームを読み進めながらリテラル(tree_type = 0)、挿入・コピーコマンド(tree_type = 1)、距離コード(tree_type = 2)のブロックタイプを切り替える。 ブロックタイプを切り替える際、デコーダーは c/dec/decode.c 内の DecodeBlockTypeAndLength() を呼び出して次のブロックタイプ番号とブロック長をビットストリームから読み取る。このときBrotliデコーダーは、入力バッファに十分なデータがある場合に境界チェックを省略して高速に処理する Fast Path(safe = 0) と、入力不足を検査しながら処理する Safe Path(safe = 1) の2つの経路を持っている。 原因:戻り値の未チェック(CWE-252)と境界外書き込み(CWE-787) 脆弱性の根本原因は、c/dec/decode.c 内の DecodeBlockTypeAndLength() が返す真偽値 BROTLI_BOOL の仕様と、呼び出し元における戻り値の未チェックにある1。 戻り値 BROTLI_FALSE における2種類の意味の混在: 修正前の DecodeBlockTypeAndLength() は戻り値として単なる真偽値(BROTLI_BOOL)を返していた。この関数は、Safe Pathで入力ビットが一時的に不足している場合(!SafeReadSymbol や !SafeReadBlockLength)に BROTLI_FALSE を返すだけでなく、ストリームのブロックタイプ最大数 max_block_type が 1 以下であるにもかかわらずブロック切り替えが要求されたというストリーム構文エラー(フォーマット不正) の場合(if (max_block_type <= 1) return BROTLI_FALSE;)にも同じ BROTLI_FALSE を返していた。 Fast Path(safe = 0)での戻り値の無視: 修正前のコードでは、Fast Path用のラッパー関数 DecodeLiteralBlockSwitch()、DecodeCommandBlockSwitch()、DecodeDistanceBlockSwitch() の戻り値型が static void と定義されており、内部で呼び出す Decode*BlockSwitchInternal(0, s) の戻り値を完全に無視していた。さらに呼び出し元のマクロ BROTLI_SAFE(METHOD) も、safe == 0 の分岐では戻り値を検証せずにそのまま処理を継続する実装となっていた。 不正な内部状態でのデコード継続と境界外書き込み: 細工された圧縮ストリームによって本来起こり得ないブロック切り替えが要求された際、脆弱なデコーダーは構文エラーで停止せず、「追加入力が必要である」状態と誤認する、あるいはFast Path上でエラーを無視して不整合な内部状態のまま処理を継続してしまう。その結果、内部バッファの終端を越えて攻撃者のバイト列が書き込まれ、隣接するデコーダー状態メモリ(BrotliDecoderState)の破壊や機密メモリの漏洩、さらには任意のコード実行(RCE)へとつながる。 攻撃フロー 修正パッチ Googleの公式リポジトリにおける修正コミット 0ed4268a(および各ブランチへの反映コミット 50a9b70a、da980636、b418fa81)では、以下の変更が行われている2。 c/include/brotli/decode.h に、ブロック切り替えのフォーマット異常を表す新しいエラーコード BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH = -17 を追加。 DecodeBlockTypeAndLength() および各 Decode*BlockSwitch*() 関数の戻り値型を BROTLI_BOOL / void から BrotliDecoderErrorCode に変更し、max_block_type <= 1 のフォーマット異常時は BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH、入力不足時は BROTLI_DECODER_NEEDS_MORE_INPUT、成功時は BROTLI_DECODER_SUCCESS を明示的に返すように修正。 Safe PathとFast Pathの両方でステータスコードを検証し、BROTLI_DECODER_SUCCESS 以外の場合は即座に saveStateAndReturn へジャンプして処理を中断するマクロ BROTLI_SAFE_WITH_STATUS(METHOD) を導入。 c/dec/decode.c // google/brotli commit 0ed4268a より抜粋 -static BROTLI_INLINE BROTLI_BOOL DecodeBlockTypeAndLength( +static BROTLI_INLINE BrotliDecoderErrorCode DecodeBlockTypeAndLength( int safe, BrotliDecoderState* s, int tree_type) { brotli_reg_t max_block_type = s->num_block_types[tree_type]; const HuffmanCode* type_tree = &s->block_type_trees[ @@ -1187,7 +1187,7 @@ brotli_reg_t* ringbuffer = &s->block_type_rb[tree_type * 2]; brotli_reg_t block_type; if (max_block_type <= 1) { - return BROTLI_FALSE; - return BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH; } /* Read 0..15 + 3..39 bits. */ @@ -1197,11 +1197,13 @@ } else { BrotliBitReaderState memento; BrotliBitReaderSaveState(br, &memento); - if (!SafeReadSymbol(type_tree, br, &block_type)) return BROTLI_FALSE; - if (!SafeReadSymbol(type_tree, br, &block_type)) { - return BROTLI_DECODER_NEEDS_MORE_INPUT; - } if (!SafeReadBlockLength(s, &s->block_length[tree_type], len_tree, br)) { s->substate_read_block_length = BROTLI_STATE_READ_BLOCK_LENGTH_NONE; BrotliBitReaderRestoreState(br, &memento); - return BROTLI_FALSE; - return BROTLI_DECODER_NEEDS_MORE_INPUT; } } ... ringbuffer[0] = ringbuffer[1]; ringbuffer[1] = block_type; - return BROTLI_TRUE; - return BROTLI_DECODER_SUCCESS; } -static BROTLI_INLINE BROTLI_BOOL DecodeLiteralBlockSwitchInternal( +static BROTLI_INLINE BrotliDecoderErrorCode DecodeLiteralBlockSwitchInternal( int safe, BrotliDecoderState* s) { - if (!DecodeBlockTypeAndLength(safe, s, 0)) { - return BROTLI_FALSE; - BrotliDecoderErrorCode result = DecodeBlockTypeAndLength(safe, s, 0); - if (result != BROTLI_DECODER_SUCCESS) { - return result; } PrepareLiteralDecoding(s); - return BROTLI_TRUE; - return BROTLI_DECODER_SUCCESS; } -static void BROTLI_NOINLINE DecodeLiteralBlockSwitch(BrotliDecoderState* s) { - DecodeLiteralBlockSwitchInternal(0, s); +static BROTLI_NOINLINE BrotliDecoderErrorCode +DecodeLiteralBlockSwitch(BrotliDecoderState* s) { - return DecodeLiteralBlockSwitchInternal(0, s); } 呼び出し元のマクロも以下のように BROTLI_SAFE_WITH_STATUS へ置き換えられ、safe フラグの有無にかかわらずエラー発生時にデコードが即座に停止するようになった。 c/dec/decode.c +/* NB: METHOD should return BROTLI_DECODER_SUCCESS, BROTLI_DECODER_ERROR_*, or - BROTLI_DECODER_NEEDS_MORE_INPUT; the later two break the processing. */ +#define BROTLI_SAFE_WITH_STATUS(METHOD) \ - { \ - BrotliDecoderErrorCode status; \ - if (safe) { \ - status = Safe##METHOD; \ - } else { \ - status = METHOD; \ - } \ - if (status != BROTLI_DECODER_SUCCESS) { \ - result = status; \ - goto saveStateAndReturn; \ - } \ - } 対策と緩和策 1. 恒久対策:修正バージョンへのアップデートと再ビルド 利用しているOSパッケージ、言語ランタイム、および依存ライブラリを修正バージョンへ更新し、稼働中のプロセスを再起動する。また、単一実行ファイル化(Bunの bun build --compile、.NETのSelf-Contained / NativeAOT)やDockerコンテナイメージを利用している場合は、更新後のランタイム・ベースイメージを用いて再ビルドおよび再デプロイを実施する必要がある。 各環境におけるアップデートコマンド例# 1. Amazon Linux 2023 の場合(ALAS2023-2026-3334 / 3330 / 3367) sudo dnf update brotli nodejs24 dotnet8.0 -y 2. Ubuntu / Debian の場合(USN-8956-1 / USN-8958-1 / DSA-6582-1) sudo apt update && sudo apt install --only-upgrade brotli libbrotli1 python3-brotli -y 3. Node.js の更新(nvm を利用している場合) nvm install 24.22.1 # または 22.23.4 / 26.12.1 4. Python パッケージ(PyPI)の更新(1.2.1 / 1.2.1.0 以上) pip install -U "Brotli>=1.2.1" "brotlicffi>=1.2.1.0" 5. Bun の更新(1.4.4 以上) bun upgrade 2. 一時的な緩和策(アップデートが直ちに実施できない場合) 各ベンダーのアドバイザリおよび技術資料では、即座にパッチを適用できない環境向けに以下の緩和策が案内されている。 WAFやリバースプロキシにおける受信 Content-Encoding: br リクエストの遮断: AWSのセキュリティブレティン 2026-155-AWS 等では、クライアントから送信される Content-Encoding: br ヘッダー付きHTTPリクエストをWAFやプロキシで拒否する緩和策が提示されている。AWSが公開しているAWS WAFカスタムルールの設定例(Countモードで検証後に "Count": {} を "Block": {} に変更する手順)は以下の通りである。なお、AWS WAFは受信リクエストのみを検査するため、アプリケーションが外部サーバーから取得するレスポンスには効かない点に留意が必要である10。 { "Name": "Node2Shell_BROTLI", "Priority": 2, "Statement": { "RegexMatchStatement": { "RegexString": "^\s*br\s*$", "FieldToMatch": { "SingleHeader": { "Name": "content-encoding" } }, "TextTransformations": [ { "Priority": 0, "Type": "LOWERCASE" } ] } }, "Action": { "Count": {} }, "RuleLabels": [ { "Name": "Node2Shell_Brotli" } ], "VisibilityConfig": { "SampledRequestsEnabled": true, "CloudWatchMetricsEnabled": true, "MetricName": "Node2Shell_BROTLI" } } HTTPクライアントおよびPython環境におけるBrotliデコードの抑止: 注意点(Accept-Encoding ヘッダー変更の限界): リクエストヘッダーの Accept-Encoding から br を除外したとしても、悪意あるサーバーはそれを無視して Content-Encoding: br のレスポンスを送りつけることができるため、クライアント側で自動解凍自体が有効なままでは対策として不十分である。 Bun: セキュリティアドバイザリ(GHSA-663w-cv8g-jj2p)では、fetch 呼び出し時に decompress: false を渡すことで、Accept-Encoding: br の送信を抑止するとともにレスポンス自体の自動デコードを無効化する回避策が案内されている8。 Python: 直ちにアップグレードできない場合の緩和策として、Brotli および brotlicffi パッケージ(ならびに httpx[brotli] 等のextra指定)自体をアンインストールし、urllib3、requests、aiohttp 等でBrotliデコーダーが呼び出されない状態(gzip / deflate へのフォールバック)にする方法が挙げられている6。 PAN-OS: ホットフィックス適用までの回避策としてCLIから set system setting brotli-encoding disable を実行(またはWeb UIの Device > Setup > Content-ID から Allow Brotli Encoding のチェックを解除)してBrotliインスペクションを無効化する手順が案内されている。 参考資料・出典一覧 CVE.org - CVE-2026-111497 Google Brotli GitHub Repository - Commit 0ed4268a Node.js - Wednesday, October 21, 2026 Security Releases Imperva Blog - Node2Shell: From Brotli Data to Remote Code Execution (CVE-2026-111497) JPCERT/CC - BrotliのC言語実装における境界外書き込みの脆弱性(CVE-2026-111497)に関する注意喚起 (JPCERT-AT-2026-0034) Broccoli2Shell: Brotli decoder RCE (CVE-2026-111497) in Node.js, Python and more GitHub Advisory Database - GHSA-9v8h-qqgm-4m35 (PyPI brotli / brotlicffi) Bun Security Advisory - GHSA-663w-cv8g-jj2p Microsoft .NET Security Advisory - CVE-2026-111497: Out of bounds write in Brotli leads to RCE AWS Security Bulletin - 2026-155-AWS: CVE-2026-111497: Native Brotli Decoder Remote Code Execution and Memory Disclosure

  4. Microsoft Just Shipped Visual Studio's October Update. Here's What It Actually Changes. Visual Studio used to ship big releases every couple of years. Now it ships something every month, and the October update (18.11) is the one where the monthly cadence stops looking like a gimmick and starts looking like a strategy. Version 18.11.0 landed on October 13, followed by 18.11.1 on October 20 and 18.11.2 on October 25. The headline items are a rebuilt GitHub Copilot chat, bulk merge conflict resolution, commit-to-working-tree comparison, and Hot Exit. The smaller changes matter too, because they fix things developers have been voting on for years. The Problem It's Solving Most of this release is about friction. Developers lose unsaved work when the IDE closes. They resolve forty merge conflicts one file at a time when they already know which side wins. They wait for a 20-project solution to finish building after project three has already failed. They reach for the mouse to reorder tabs. None of that is glamorous, and that's the point. Microsoft's own framing is "deep platform integration of AI, stronger fundamentals, and improved performance." The October update is the "stronger fundamentals" half of that sentence, plus a structural change to how the AI half is built. How It Actually Works The new Copilot chat. This is the one with architectural consequences. Visual Studio now has a Copilot chat experience built on the Copilot SDK, the same foundation behind the Copilot CLI and the GitHub Copilot app. It started in July with Agent (Preview), and 18.11 extends the SDK to the whole chat stack, including Ask mode, Agent mode, and the underlying infrastructure. Microsoft lists three gains: agent workflows that need less back-and-forth, longer sessions with better prompt caching, and consistent behavior across Copilot surfaces. To turn it on, go to Tools → Options → GitHub → Copilot → Copilot Chat, check "Use new Copilot chat experience," and restart. It's opt-in, so you choose when to switch. Git, three ways. Right-click a commit in the Git Repository window and choose Compare with Working Tree to diff that commit against your current code, committed and uncommitted changes together, without checking anything out. This one is useful in refactors. For merges, Keep All Current and Take All Incoming now work on any file, folder, or the repo root under Unmerged Changes in Git Changes, so one action resolves everything beneath it. This builds on the worktree support and Git submodule integration that arrived in 18.9. Hot Exit. When enabled, Visual Studio saves session state on close and restores it on reopen. That covers unsaved files, saved files with unsaved changes, undo history including Ctrl+Z, and your tab and window layout. It's off by default. Enable it under Tools → Options → Projects and Solutions → General by selecting "Reopen unsaved documents on solution load or empty environment." The new Markdown template in the New File dialog (Ctrl+N) works with it, so scratch notes survive a restart. Stop build on first failure. Another opt-in setting, under Tools → Options → Projects and Solutions → Build and Run, called "Stop solution build after first failure." Visual Studio stops queuing projects and safely halts the ones in flight, and the build still reports as failed rather than canceled. Microsoft's test showed four regular builds averaging 26 seconds with it off and 1.5 seconds with it on, a 94% cut in post-failure time. That's one solution, and the release notes say results vary. Long dependency chains and heavily parallel builds should benefit most. Fonts and Colors. The legacy dialog has been rebuilt on Unified Settings, with category navigation, search, inline previews, and a grid view. It now participates in the settings.json model, so changes made in the UI show up in the file and vice versa. That makes IDE customization something you can version and share. The small ones. Ctrl+Numpad 1 through 9 jumps to numbered document tabs. Ctrl+Shift+PgUp and PgDn move tabs, with pinned and unpinned tabs staying separated, and the shortcuts take over the old Text Editor bindings for Edit.ViewTopExtend and Edit.ViewBottomExtend. Ctrl+NumPad0 resets editor zoom to 100%. A Quick Info hover delay setting (50 to 10000 ms, default 150) stops tooltips popping up over the line you're aiming for. Open Folder now respects the same restore settings that solutions do. What Developers Are Actually Using It For The October features are fresh, so I'd rather not invent adoption stories. What the release notes do make clear is what each feature is built for. Compare with Working Tree is for refactoring and for answering "what have I changed since that commit?" without a stash or a checkout. Bulk conflict resolution is for large merges where you've already decided the policy, such as taking incoming on generated files or keeping current on a vendored folder. Fail-fast builds are for large solutions where a broken base project makes the rest of the build pointless. Hot Exit is for anyone who has closed the IDE with a dozen dirty buffers. The Copilot work from earlier in the cycle is where the agentic usage shows up. Version 18.9 added a Git agent that reviews uncommitted changes and commits, with inline comments, on both GitHub and Azure DevOps repos. Version 18.10 let it explore pull requests in chat, with navigable links to comments and files. It also added Bring Your Own Key in preview, supporting Microsoft Foundry, OpenAI, Anthropic, and Ollama, though only for the new Agent (Preview). The earlier BYOK path in the old Ask and Agent modes is no longer supported. Version 18.8 added built-in .NET and Azure skills, off by default. Two smaller details are worth knowing for agent work. Version 18.9 added low, medium, and high thinking-effort controls for supported models, plus a usage view, because Copilot moved to usage-based billing on token consumption. And since 18.7, Visual Studio has applied trust validation for MCP servers. It compares each server's configuration and its tools, prompts, resources, and instructions against the last approved fingerprint, and prompts you to re-approve if anything changed. That one is on by default. For anyone wiring MCP servers into an IDE agent, it's the most important security feature in the cycle. Why This Is a Bigger Deal Than It Looks There are two reasons. First, the SDK consolidation. When Visual Studio's chat runs on the same Copilot SDK as the CLI and the Copilot app, the IDE stops being a special case that gets agent features months late. Microsoft says it plainly: Visual Studio gets better as Copilot gets better. For a product with this much legacy surface area, that's a significant change in how AI features reach users. The cost is churn. The BYOK breaking changes in 18.10 show that migrating onto the new harness will break things for some users along the way. Second, the cadence. Since 18.6 in May, there has been a numbered update every month, with weekly point releases between them. The September rollout shipped alongside a long list of .NET and ASP.NET Core security fixes, and 18.11.2 includes three more advisories. A faster release train means more frequent patches, and also more frequent regressions. 18.11.2 lists a known issue where F5 fails to launch the Blazor WebAssembly debug adapter, and earlier point releases fixed Copilot chat failing to open. The monthly rhythm works if you treat the first two weeks of each cycle as a soft launch. Also keep in mind that three of the most useful October features, Hot Exit, fail-fast builds, and the new chat, ship disabled. If you update and see no difference, that's why. Availability and Access Visual Studio 2026 is available from the Visual Studio download page, and 18.11.2 is the current build. Existing installs update through the Visual Studio Installer. The Copilot features require a GitHub Copilot plan, and Copilot usage now counts against token-based limits. Org-level custom agents and custom instructions require a GitHub organization. Pull request browsing and review in the IDE sits under Tools → Options → Preview Features. The full list of changes and bug fixes is in the official release notes, and feature feedback goes through Developer Community. --- Visual Studio is becoming a client for a shared agent runtime, and the rest of the release is Microsoft paying down small debts to keep people from leaving. Both changes are real. Only the second one is finished. Follow for more coverage on MCP, agentic AI, and AI infrastructure.

    5 reactions · 4 comments
  5. Node2ShellはNode.jsだけの話ではない。Brotliの欠陥でPythonも対象、CVE-2026-111497の該当判定 「Node2Shell? うちは Node.js を使っていないから関係ないな」。10月下旬にこのニュースを見て、そう判断しかけた方もいると思います。ところが、Node.js を使っていなくても確認が必要な場合があります。Node2Shell(HTTP2Shell とも呼ばれる、CVE-2026-111497)の正体は、Node.js ではなく Google の圧縮ライブラリ Brotli の欠陥でした。Python の Brotli パッケージ(入っていると requests などが自動で使う)、Bun、Envoy、Git for Windows にも同じ欠陥がありました。10月27日には、米国の CISA(サイバーセキュリティ・社会基盤安全保障庁)が、実際に悪用が確認された脆弱性の一覧に追加しています。 この記事は、Node.js・Python・Bun でアプリを作っている人や、Windows で Git for Windows を使っている人向けです。読み終えると、次の3つが分かります。 なぜ「Node.js の脆弱性」として広まったのか(原因) 自分の環境が該当するか、どこまで上げればよいか(言語・ツール別の判定) 呼び名・深刻度・RCE の範囲など、記事ごとに食い違う情報のどれを信じればよいか 情報は2026年10月29日時点のものです。技術的な解析は Imperva と OpenSourceMalware の記事が詳しく、Python・curl・Git for Windows の動作検証は技術ブログの lilting が詳しいです。どれも末尾のリンクから読めます。 ! 先に答え(2026年10月29日時点) 出回った言い方 一次情報での答え Node.js の脆弱性 欠陥があるのは Google の Brotli。Node.js は同梱していた Brotli を直した側 Node.js を使っていなければ無関係 Python(Brotli パッケージがあると requests 等が使う)・Bun・Envoy・Git for Windows も対象 Node2Shell と HTTP2Shell は別の脆弱性 どちらも CVE-2026-111497 の1件 深刻度は High? Critical? どちらも正しい。評価した組織が違う 素の Node.js ならどこでも RCE される 実証は Node.js 24.22.0(arm64 と x86-64)で報告された。24.22.0 用の攻撃データは 22.23.3・26.12.0 では通らず、ビルドごとに調整が要る 実際の攻撃はまだ? CISA が10月27日に、悪用が確認された脆弱性の一覧(KEV)に追加した。攻撃通信を観測したという報告もある 自分は該当する? 使っている言語ではなく、「信頼できない相手から来た Brotli データを展開するか」と「同梱している Brotli が直った版か」で決まります。判定だけ知りたい方は「自分は関係ある? 言語・ツール別の判定」へ進んでください。 Brotli とは何で、何が壊れていたのか Brotli は Google が作った圧縮形式で、Web では HTTP の Content-Encoding: br として使われます。欠陥があったのは、圧縮データを元に戻す部分(デコーダ)の C 言語実装です。壊れた圧縮データを渡すと、デコーダが内部バッファの外へ書き込んでしまいます(境界外書き込み)。本来触ってはいけないメモリを書き換えるため、条件がそろうと遠隔からのコード実行(RCE)につながります。 影響を受ける版: 1.0 系は 1.0.3〜1.0.9、1.1 系は 1.1.0、1.2 系は 1.2.0 修正版: 1.0.10 / 1.1.1 / 1.2.1(日本時間の10月22日 04:27 公開) CVE の採番: Google 発見: OpenAI Codex Security なぜ「Node.js の脆弱性」として広まったのか 広く知られるきっかけが、10月21日の Node.js 定例セキュリティリリースだったからです。流れを表にまとめます(日付は日本時間)。 日付 出来事 2月27日 Brotli のリポジトリに修正コミット。メッセージは「Detect and report invalid stream earlier.」(壊れたストリームを早めに検出して報告する)の1行で、脆弱性とは書かれていない。修正版の公開まで約237日 10月21日 Node.js が定例セキュリティリリースで10件を修正。その1件がこの欠陥 10月22日 Brotli の修正版を公開。Imperva が「Node2Shell」の題で RCE の実証記事を公開 10月23日 OpenSourceMalware が Python への波及を公表 10月24日 Envoy がアドバイザリを公開。Git for Windows に Issue #6514 が立つ Imperva はセキュリティ企業です。実証の対象が Node.js で、記事の題にも Node.js が入りました。そのため、「Node.js の脆弱性」として広まりました。 ところが、Node.js が直した中身は Brotli のコードです。Node.js は Brotli を自分のリポジトリに同梱していて、今回は Brotli の修正コミットを cherry-pick で取り込みました。cherry-pick は、別のリポジトリの修正部分だけを持ってくる操作です。 つまり Node.js は、同梱していた Brotli を直した側であって、欠陥の持ち主ではありません。 Python への波及を最初にまとめた OpenSourceMalware(OSM)も、この点を「この脆弱性は、そもそも Node.js のものではない」と書いています。 OSM の原文(英語) The vulnerability isn't really in Node.js at all Node.js 以外に対象になるもの(Python・Bun・Envoy・Git for Windows) 原因が Brotli のデコーダにあるので、同じデコーダを積んでいるものは確認が必要です。10月29日までに、次のものが分かっています。 Python: Brotli / brotlicffi パッケージ。どちらかが入っていると、urllib3 / requests、httpx(brotli extra)、aiohttp、Scrapy が br の応答を自動で展開します Bun: 1.4.4 で同梱の Brotli を更新しました Envoy / Istio: Brotli の展開(decompressor)を設定した構成が対象です curl: リンクしている Brotli のライブラリ次第です Git for Windows: 2.56.0.windows.3 までは、同梱の brotli が 1.2.0 のままでした。10月26日に公開された 2.56.0.windows.4 で 1.2.1 に更新されています このほかにも、対象として次のものが見つかっています。 .NET: 修正版は 8.0.33 / 9.0.22 / 10.0.14 です(.NET Framework は対象外)。BrotliStream などで自分で展開する処理が対象です。HttpClient の応答の自動展開と ASP.NET Core のリクエスト展開は、既定では無効です Go の andybalholm/brotli: Brotli を Go に移植したライブラリです。v1.2.7 に同じ修正が移植されました Linux の git の HTTPS 通信: git が使う libcurl が Brotli 付きでビルドされていれば対象です。oss-security への投稿によれば、多くのディストリの libcurl は Brotli 付きです Git for Windows のメンテナは、10月24日の Issue で次のように書いています。 昨日の時点では、これは「ただの」node2shell だと言われていた。だから Git for Windows は無関係に見えた。 メンテナの原文(英語、Git for Windows Issue #6514) Yesterday, the word on the street was still that this was "only" node2shell, and therefore Git for Windows looked unaffected. メンテナ自身も、名前を見て「自分は関係ない」と判断しかけていたわけです。「Node2Shell」という名前は、最初に話題になった場所を示しているだけで、影響範囲を示してはいません。 該当するかは「言語」ではなく2つの条件で決まる 対象の部品が入っていても、すぐに攻撃されるとは限りません。どの環境から先に上げるかは、次の2つの条件で決まります。 条件1: 信頼できない相手から来た Brotli データを展開するか。 典型的な経路は2つあります。 外部サーバーの応答を、Node.js の fetch() や Python の requests などで受け取る。応答の中身は相手のサーバーが決めます。攻撃者が用意したサーバーなら、壊れたデータを返せます 受け取ったリクエスト本文を、ミドルウェアが展開する。Imperva は、Express 5 / body-parser 2 の raw()・json() がリクエスト本文を展開する経路で実証しています 一方、素の Node.js の HTTP サーバーは、リクエストを受け取っただけでは本文を展開しません(Imperva の記事による)。 条件2: 同梱している Brotli が直った版か。 これは言語やツール本体の版で決まります。Brotli を自分で入れていなくても、ランタイムやツールの中に入っていることがあります。 特に先に上げるべきなのは、外部の URL を取ってくる機能を持つアプリです。Webhook、URL のプレビュー表示、クローラ、「この URL の内容を取ってきて」と頼める機能などがこれに当たります。 下の図の「Brotli デコーダを積んだもの」は、前の節で挙げた Node.js・Python の Brotli / brotlicffi・Bun・Envoy・Git for Windows などです。 自分は関係ある? 言語・ツール別の判定 ! 判定の前に、落とし穴が2つあります。 版番号: Node.js は修正を cherry-pick で取り込みました。そのため、修正版でも process.versions.brotli の表示は修正前と変わりません(lilting の検証では、22.23.4 でも 1.1.0 のまま)。Node.js では、Brotli の版ではなく node -v で Node.js 本体の版を見ます。ただし、ディストリが提供する nodejs はシステムの libbrotli を使うことがあります(Ubuntu・Debian の告知)。その場合は、ディストリの告知で判断します スキャナ: Node.js はランタイムそのものなので、npm audit には出ません。Python では、10月23日に OSM が「Brotli・brotlicffi の PyPI パッケージは、どの脆弱性データベースにも載っていない」と書きました。翌24日には、pip-audit が検出するようになっています(lilting の検証)。スキャナが何も言わないことを、安全の根拠にしないでください 対象 何を見るか OK の基準・対処 優先して上げるべき使い方 Node.js node -v 22・24・26 系は、それぞれ 22.23.4 / 24.22.1 / 26.12.1 以上。それ以外の系列(20 以下など)は修正されないので、サポート中の系列へ移る fetch() で外部 URL を取る。Express 5 / body-parser 2 で br の本文を受ける Python Brotli / brotlicffi が入っているか Brotli 1.2.1 / brotlicffi 1.2.1.0 以上。Linux ディストリビューション(以下ディストリ)が提供するパッケージは、ディストリの更新で上げる requests・httpx・aiohttp 等で外部 URL を取る Bun bun --version 1.4.4 以上。コンパイル済みの実行ファイルは作り直しが必要(Aikido の記述) 外部 URL を取る Envoy Brotli decompressor を設定しているか 1.40.0 / 1.39.3 / 1.38.6 / 1.37.8 / 1.36.12 以上 設定している構成すべて Istio Brotli の展開を有効にしているか(EnvoyFilter など) 1.31.2 / 1.30.6 / 1.29.9 以上(Istio の告知 ISTIO-SECURITY-2026-007) 有効にしている構成すべて Git for Windows git version 2.56.0.windows.4 以上(10月26日公開。同梱の brotli は 1.2.1) 信頼できない HTTPS サーバーへの clone / fetch curl どの Brotli にリンクしているかは、ビルドした配布元次第 配布元(OS・ディストリ・同梱しているツール)の更新情報を確認して上げる。Windows 標準の curl は Brotli 非対応(lilting の検証) --compressed などで、信頼できない URL の応答を展開させる Linux の libbrotli ディストリのセキュリティ情報 ディストリの更新を当てる。版番号を変えずに修正だけ移植する(バックポート)ことがあるので、版番号ではなくディストリの告知で判断する libbrotli を使うアプリが外部データを展開する Bun 1.4.4 のリリースノート(公式ブログ)は、Security の節で Brotli に触れていますが、CVE 番号は書かれていません。CVE 番号で探すと見落としやすいので注意してください。 主なディストリの対応状況は、10月29日時点で次のとおりです。 Ubuntu: brotli は jammy(22.04)・noble(24.04)などで修正済みです(USN-8956-1 / USN-8956-2)。jammy 以降の nodejs はシステムの brotli を使うため「影響なし」とされています。focal(20.04)は、brotli が「評価中(Needs evaluation)」、Brotli を同梱する nodejs が「vulnerable」のままです Debian: brotli は bookworm(DLA-4855-1)と trixie(DSA-6582-1)で修正済みです。ディストリ版の nodejs と trixie の python-brotlicffi は、トラッカー上はまだ「vulnerable」の表示です。ただし、トラッカーの注記では、どちらもシステムの brotli を使うとされています 同じディストリの中でも、パッケージごとに状況が違います。使っているパッケージごとに告知を確かめてください。 Node.js・Python・Git for Windows については、表に書ききれない確かめ方を補足します。 Node.js: node -v で本体の版を見る node -v Docker イメージの中の Node.js を見る例(<image> は自分のイメージ名に置き換える) docker run --rm <image> node -v 出力を表の基準と比べます。手元の Node.js だけでなく、Docker イメージや、Node.js を同梱して配っているバイナリの中の版も見てください(Imperva の推奨)。 Python: Brotli / brotlicffi が入っているかを見る 確かめたい venv を有効にしてから実行する python -m pip show Brotli brotlicffi 出力の Version: の行を、表の基準と比べます。 どちらも見つからなければ、requests などは br の応答を展開しません(OSM の解説と lilting の検証)。ただし、Brotli を別の形で同梱したライブラリや外部ツールを使っている場合は、そちらも確かめてください。また、venv ごとに入っているパッケージは違います。アプリごと・コンテナごとに見てください。 Git for Windows: git version で版を見る git version git version 2.56.0.windows.4 以上と表示されれば、修正版です。10月26日に公開され、Issue #6514 もクローズされました。 同梱の brotli の版は、Git for Windows のインストール先にある etc/package-versions.txt で確かめられます。Git Bash からは次のように見られます。 grep brotli /etc/package-versions.txt 64ビット版の修正版なら mingw-w64-ucrt-x86_64-brotli 1.2.1-1 と出る 上げられるまでの間は、信頼できない HTTPS サーバーへの clone / fetch を避けるのが安全です(lilting の見解)。 呼び名・深刻度・RCE の範囲・悪用、食い違いの答え合わせ ここまでで判定はできます。この節では、調べると記事ごとに食い違って見える4つの点を、一次情報で確かめます。 Node2Shell と HTTP2Shell は同じ1件 Node2Shell は Imperva の記事タイトルが元です。HTTP2Shell は、セキュリティ企業 Aikido の記事の見出し「Node2Shell evolves to HTTP2Shell」(Node2Shell が HTTP2Shell に進化した)に出てくる呼び方です。見出しは、HTTP を使うライブラリやアプリにも影響が広がったことを伝えるものです。ただし、Aikido は呼び名を変えた理由を本文で説明していません。検索すると Broccoli2Shell という呼び方も出てきます。どれも指しているのは CVE-2026-111497 の1件です。 もう1つ紛らわしいのが、2024年の別の脆弱性です。「Node.js Brotli 脆弱性」で検索すると、検索エンジンの AI 要約が CVE-2024-22025 を返すことがあります。これは2024年に見つかった、fetch() が Brotli データを展開するときにサービス停止(DoS)を起こす問題で、今回とは別件です。 High と Critical 9.2 は、評価した組織が違う 深刻度の点数は CVSS という共通の尺度で付けます。CVE を採番した Google は、CVSS 4.0 で 9.2(Critical)と評価しました。GitHub のアドバイザリと NVD(米国の脆弱性データベース)もこの値を載せています。 一方、Node.js は自分のリリース表でこの件を High と書きました。評価したのは Node.js 自身で、対象も Node.js という1つの製品です。評価した組織も、評価の対象にした製品も違うので、どちらも間違いではありません。 RCE の実証は広がったが、成否はビルドで違う 「素の Node.js で RCE できる」という言い方も出回っています。最初に Imperva が実証に使ったのは、公式の Node.js 24.22.0 Linux arm64 イメージです(経路は fetch() と Express 5 / body-parser 2)。成功までには何回か試す必要がありました。 その後、セキュリティ企業の ELLIO が、Node.js 24.22.0 の x86-64 でも RCE に成功したと報告しました。成功した構成は4つで、node:http のサーバーから node:zlib を呼んで本文を展開するもの、公式の slim イメージ、Express 5.3.0(express.json())、Koa 3(@koa/bodyparser)です。node:http の例は、フレームワークを挟まず、アプリのコードが自分で本文を展開する構成です。ELLIO も、素の Node.js の HTTP サーバーはリクエストを受け取っただけでは Brotli の本文を展開しないと書いています(条件1 と同じ)。「node:http を使っているだけで RCE される」という意味ではありません。 一方、24.22.0 用に調整した攻撃データは、22.23.3 と 26.12.0 では1万回試しても成功しませんでした。ELLIO は、これらの版には別の調整が要るとしています。攻撃を成功させるには、ビルドごとに作り込む必要があるということです。「どの環境でもすぐ RCE される」わけではありません。 Python については、RCE を試した報告はまだありません。OSM は、壊れたデータを渡すとデコーダが誤った結果を返すところまでを確かめています。 ただし、24.22.0 用の攻撃データが通らなかったからといって、ほかの版が安全だという証明にはなりません。判定で該当した環境は、実証の有無にかかわらず上げてください。 実際の攻撃は、すでに確認されている 10月25日の時点では、悪用の報告は見当たりませんでした。その後、状況が変わっています。CISA は10月27日に、この CVE を KEV(Known Exploited Vulnerabilities、実際に悪用が確認された脆弱性の一覧)に追加しました。米国の政府機関には、11月10日までの対応が求められています。 ELLIO も10月26日の追記で、自社のセンサーで実際の攻撃通信を観測したと報告しています。狙いは Node.js 24.22.0 の x86-64 で、ELLIO の検証では、観測した攻撃データそのものは成功しませんでした。攻撃を再現するコード(PoC)も公開されて出回っています。 ただし、CISA は KEV に追加した根拠となった具体的な事例を公表していません。どの製品のどの使い方が狙われたのかは、まだ分かりません。 まだ分かっていないこと 10月29日時点で、次の点はまだ決着していません。 Ubuntu の focal(20.04)は、修正版が出ていません。Debian のディストリ版 nodejs と trixie の python-brotlicffi は、トラッカー上「vulnerable」の表示が残っています Python で RCE が可能かどうかを検証した報告は、見当たりません CISA が KEV に追加した根拠の事例は公表されておらず、どこが狙われたのかは分かっていません ブラウザや、Node.js を内蔵するアプリ(Electron など)については、この記事では扱いません。影響の有無を示す一次情報が見当たらないためです。 おわりに Node2Shell の原因は Node.js ではなく Brotli の欠陥で、Node.js は直した側でした。呼び名が違っても指しているのは1件で、深刻度の違いは評価した組織の違いです。RCE の成否はビルドによって違いますが、実際の攻撃はすでに確認されています。 「Node.js は使っていないから関係ない」と思った方も、Python の venv と git version を一度確かめてみてください。判断の材料は「Node2Shell」という名前ではなく、手元にあるパッケージとツールの版です。 参考リンク Node.js: Security Releases: October 21, 2026 GitHub Advisory: GHSA-9v8h-qqgm-4m35(PyPI Brotli / brotlicffi) NVD: CVE-2026-111497 google/brotli: Releases Git for Windows Issue #6514: Bundled brotli is still 1.2.0 in 2.56.0.windows.3 Imperva: Node2Shell: From Brotli Data to Remote Code Execution OpenSourceMalware: Node2Shell affects Python Envoy: GHSA-3wvj-j6qm-62mr Istio: ISTIO-SECURITY-2026-007 Bun: Bun v1.4.4 Aikido: Node2Shell evolves to HTTP2Shell lilting: Brotli CVE-2026-111497 beyond Node.js, tested with Python, curl, and Git for Windows CISA: Known Exploited Vulnerabilities Catalog(CVE-2026-111497) ELLIO: Node2Shell(CVE-2026-111497)の検証記事 Git for Windows: v2.56.0.windows.4 Ubuntu: CVE-2026-111497 Debian Security Tracker: CVE-2026-111497 .NET: dotnet/announcements #494 oss-security: Brotli CVE-2026-111497 and git-remote-https andybalholm/brotli: v1.2.7 の更新履歴

  6. Где Runtime Async в .NET 11 экономит память, а где нет Всем привет, меня зовут Сергей Прощаев, я Tech Lead и руководитель направления Java | Kotlin разработки в FinTech & E-commerce и преподаю на курсах разработки и архитектуры в ОТУС. В этой статье расскажу про Runtime Async в .NET 11: что меняется, когда приостановкой async-методов занимается рантайм, а не код от компилятора, и где выигрыш пропадает. На собеседованиях по C# годами звучит один и тот же вопрос: «во что компилятор превращает async-метод?». Правильный ответ знают все: в структуру с конечным автоматом, методом MoveNext и билдером задачи. В .NET 11 у этого ответа появляется оговорка «если вы не включили runtime-async». Я прогнал бенчмарки команды .NET на RC2 сам: аллокации совпали с опубликованными до байта, но только при одном условии, о котором в анонсах не пишут. Сразу оговорюсь, почему про C# пишет человек с JVM. Мой основной стек — Java и Kotlin, и именно поэтому за асинхронностью в .NET интересно следить со стороны: в 2023 году Java выпустила виртуальные потоки, а команда .NET свой эксперимент с зелёными потоками отложила и пошла улучшать async/await. В ноябре ожидается релиз .NET 11, где Runtime Async уже есть, но как preview-функция: для своего кода её включают явно, а поведение проверяют на своей нагрузке. С изменениями в .NET 11 разобраться проще, если понимать, как async/await работает в классической реализации. Эту тему подробнее разберём 29 октября в 20:00 на бесплатном открытом уроке «Магические слова async/await». Три платформы, три ответа на один вопрос Задача у всех одна: поток не должен простаивать, пока сервис ждёт ответа от базы или соседнего API. Решают её по-разному. Компиляторы Kotlin и классического C# преобразуют асинхронные функции в конечные автоматы. Kotlin превращает каждую suspend-функцию в автомат с полем label и скрытым параметром Continuation. Компилятор C# делает почти то же: генерирует структуру, хранит в её полях локальные переменные, которые живут через await, и раскладывает тело метода по состояниям внутри MoveNext. JVM и CLR исполняют уже сгенерированный код и дают инфраструктуру продолжений, но сам автомат не строят. // (Kotlin) suspend-функция: компилятор добавит скрытый параметр Continuation // и превратит тело в конечный автомат с полем label suspend fun price(id: Int): Int { val base = loadBase(id) // точка приостановки, пример схематичный return base + tax(base) }sourcecraft.dev Java пошла другим путём. В JDK 21, вышедшем 19 сентября 2023 года, появились виртуальные потоки (JEP 444): код остаётся синхронным и блокирующим, а приостановкой и возобновлением занимается JVM, снимая стек виртуального потока с несущего. // (Java 21, схематично) код блокирующий, приостанавливает и возобновляет его JVM try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { executor.submit(() -> callBlockingApi(id)); }sourcecraft.dev Команда .NET попробовала тот же путь. За одиннадцать дней до выхода JDK 21, 8 сентября 2023 года, она опубликовала итоги эксперимента с зелёными потоками. Вывод был сдержанным: зелёные потоки возможны, но это вторая модель программирования рядом с async/await, и они плохо уживаются друг с другом. Самая наглядная цифра из отчёта: 100 миллионов минимальных вызовов P/Invoke на зелёном потоке заняли около 1800 мс вместо 300 мс. Э ксперимент поставили на паузу и решили улучшать существующую модель. Следующим шагом стал эксперимент runtime-async в .NET 9, а итогом — Runtime Async в .NET 11. На схеме ниже три подхода сведены рядом, чтобы было видно, где живёт конечный автомат в каждом из них. Главная мысль схемы: .NET 11 занял середину. Программная модель остаётся прежней, с Task, await и окраской методов, но приостановку и возобновление организуют VM и JIT, у которых больше информации. Виртуальные потоки позволяют во многих сценариях писать блокирующий код без async-сигнатур по всему стеку вызовов. .NET от окраски методов не отказывается и выигрывает за счёт того, что ничего не надо переписывать. Что именно меняется под капотом В классической схеме компилятор C# для каждого async-метода выпускает метод-обёртку, тип конечного автомата, поля для захваченного состояния и тело MoveNext. Упрощённо это выглядит так: // (C#, упрощённо) что компилятор делает из async-метода без runtime-async public Task<int> GetPriceAsync(int id) { var sm = new GetPriceAsyncStateMachine { Id = id, State = -1 }; sm.Builder = AsyncTaskMethodBuilder<int>.Create(); sm.Builder.Start(ref sm); return sm.Builder.Task; } struct GetPriceAsyncStateMachine : IAsyncStateMachine { public int State; public int Id; public AsyncTaskMethodBuilder<int> Builder; private TaskAwaiter<int> _awaiter; public void MoveNext() { // switch по State, получение awaiter, // AwaitUnsafeOnCompleted при незавершённой задаче, SetResult в конце } public void SetStateMachine(IAsyncStateMachine stateMachine) { } }sourcecraft.dev С runtime-async компилятор не генерирует пару IAsyncStateMachine и MoveNext. Он оставляет компактный IL, ставит методу флаг async (MethodImplOptions.Async, значение 0x2000 по черновику изменений ECMA-335), а точки приостановки кодирует вызовами методов AsyncHelpers: для Task и ValueTask это перегрузки Await, для прочих awaiter-ов AwaitAwaiter и UnsafeAwaitAwaiter. Для оптимизации важнее всего пара «вызов async-метода и сразу подходящий Await». Приостановку, сохранение нужного состояния и возобновление на другом потоке организуют VM и JIT. Так что меняется не столько место, где строится прежний автомат, сколько распределение ответственности, и поэтому JIT может оптимизировать границы между async-методами, а не только код внутри каждого. Включается фича свойством в файле проекта, выключается для проекта другим свойством. В .NET 11 это по-прежнему preview: <!-- (XML, csproj) включение Runtime Async для своего кода --> <PropertyGroup> <TargetFramework>net11.0</TargetFramework> <Features>$(Features);runtime-async=on</Features> </PropertyGroup> <!-- (XML, csproj) отключение для проекта --> <PropertyGroup> <UseRuntimeAsync>false</UseRuntimeAsync> </PropertyGroup>sourcecraft.dev Есть деталь, которую легко пропустить: сами библиотеки .NET 11 уже собраны с runtime-async=on и конечных автоматов от компилятора больше не содержат. Ваш код при этом может оставаться в классическом режиме, обе модели работают вместе. Получается, что, обновившись на .NET 11, вы уже исполняете runtime-async внутри BCL, даже если флаг не трогали. Что это даёт: пять наблюдений Я бы предпочёл не принимать «быстрее и чище» на веру, поэтому собрал репозиторий dotnet-runtime-async-demo и прогнал его на .NET 11 RC2 (SDK 11.0.100-rc.2) в GitHub Actions на ubuntu-24.04. Классическую схему для отдельного метода оставляет экспериментальный атрибут [RuntimeAsyncMethodGeneration(false)], как у Тауба, поэтому оба варианта работают на одном рантайме и отличаются только тем, как их собрал компилятор. Для сверки рядом цифры из документации .NET 11 и разбора производительности Стивена Тауба. Аллокации между прогонами стабильны, время скачет, поэтому опираюсь на байты. Наблюдение 1. Живые стек-трейсы стали короче в разы. Три вложенных async-метода, внутренний создаёт new StackTrace(). В классическом режиме у меня 13 кадров, из них 3 — AsyncMethodBuilderCore.Start. С runtime-async кадров 4, и это ровно цепочка вызовов. В документации 13 и 5: там Main тоже асинхронный, а мой пробник входит в цепочку из синхронного Main через GetAwaiter().GetResult(), так что точное число кадров зависит от точки входа: (text, вывод с runtime-async=on, results/stack_runtime.txt) at Runtime.InnerAsync() at Runtime.MiddleAsync() at Runtime.OuterAsync() at Program.<Main>$(String[] args)sourcecraft.dev Важная оговорка из той же документации: стек-трейсы исключений из catch выглядят одинаково в обоих режимах, их давно чистит ExceptionDispatchInfo. Выигрывают профилировщики, отладчик и всё, что смотрит на стек во время работы. С виртуальными потоками Java сходство здесь концептуальное: при реальной приостановке await физический стек в .NET по-прежнему разматывается, Runtime Async оптимизирует существующую модель, а не заменяет её стековыми потоками. Наблюдение 2. Когда Runtime Async устраняет промежуточные задачи. Самый интересный механизм — слияние вызовов. Если A ждёт B, а B ждёт C, в классической схеме у каждого метода свой сгенерированный автомат и свой результат-задача. Автомат при этом не обязательно попадает в кучу: на синхронном пути структура живёт на стеке, а платим мы в основном за задачи-результаты. JIT с runtime-async распознаёт пару «вызов метода, возвращающего Task, и сразу await» и передаёт результат дальше обычным значением. Задача материализуется только на внешней границе, где её действительно кто-то ждёт как объект. На схеме ниже эта разница показана на цепочке из трёх методов. Главная мысль схемы: контракт Task никуда не делся, он переехал туда, где задача действительно нужна. На рисунке благоприятный случай: вся цепочка собрана в новом режиме и вызовы идут подряд. В смешанной цепочке с классическими методами или когда задачу кладут в коллекцию и вешают продолжение руками, объект по-прежнему создаётся. Бенчмарк Тауба с двумя уровнями вложенности я повторил в BenchmarkDotNet 0.16.0-preview.2 с MemoryDiagnoser, в обычном режиме, когда каждый бенчмарк идёт в отдельном процессе: Сценарий Аллокации у меня Аллокации у Тауба Цепочка завершилась синхронно, классический async 144 B 144 B Цепочка завершилась синхронно, runtime-async 0 B 0 B Цепочка с Task.Yield, классический async 248 B 248 B Цепочка с Task.Yield, runtime-async 168 B 168 B Байты совпали до единицы. По времени у меня синхронная цепочка ускорилась с 26,95 до 10,72 нс, у Тауба с 21,221 до 6,151 нс: порядок тот же, машины разные. Наблюдение 3. Глубокие цепочки с исключением дешевеют на порядок. Рекурсивная цепочка бросает исключение на дне. На глубине 30 без приостановки классический вариант аллоцирует 84,2 KB, runtime-async — 5,71 KB, у меня и у Тауба одинаково. По времени у меня 56,6 мкс против 13,0 мкс. Пример синтетический: в реальном сервисе картину изменят локальная обработка исключений, логирование, повторы и настоящие точки приостановки. Наблюдение 4. Сборка становится вдвое меньше. Приложение из десяти вложенных async-методов в классическом режиме дало SizeProbe.dll размером 10 752 байта, а с runtime-async — 5632 байта, байт в байт как в разборе. Для одного сервиса это мелочь, в крупном приложении с множеством своих async-методов эффект может накапливаться. Размер итогового NativeAOT-образа и готовых сторонних сборок я не мерил, так что количественного вывода для них не делаю. Наблюдение 5. Продолжения перестали таскать пустой контекст. Это изменение не про флаг, по документации оно работает в .NET 11 для всех. Раньше каждое продолжение снимало и восстанавливало ExecutionContext со значениями AsyncLocal, даже когда восстанавливать было нечего. Теперь рантайм этот цикл пропускает. Пункт из документации, я его не мерил: в сервисе с трассировкой и областями логирования контекст часто непустой, и эффект там, по моей гипотезе, скромный. Где выигрыш пропадает Первое, и для меня главное: результат зависит от того, как запущен замер. Сначала я гонял те же бенчмарки с флагом --inProcess, когда BenchmarkDotNet исполняет их в своём процессе, и написал свой пробник на GC.GetTotalAllocatedBytes, который тоже работает в одном процессе. Оба показали другую картину: Сценарий, в одном процессе Классический async Runtime-async Цепочка завершилась синхронно 144 B 72 B Цепочка с Task.Yield 192 B 240 B Причину расхождения я не установил, поэтому не считаю эти цифры свойством самой реализации. Методики тоже не одинаковые: мой пробник считает аллокации всего процесса, включая пул потоков, а не одного вызова. Оба режима шли на одной модели процессора, AMD EPYC 9V74, так что разница в железе маловероятна. Для выводов об аллокациях беру стандартный запуск BenchmarkDotNet в отдельном процессе: эти значения совпали с опубликованными. Абсолютное время отличается между окружениями, и я считаю его результатом конкретного прогона, а не свойством .NET 11. Внутрипроцессные цифры оставляю отдельным наблюдением, которое ещё надо проверить. Практический вывод: сравнивайте замеры, запущенные одинаково, на одной версии SDK и рантайма, иначе вы меряете методику, а не механизм. Время на коротких цепочках вообще ненадёжно. У Тауба исключение на глубине 1 с приостановкой в runtime-async медленнее в 1,3 раза, а у меня быстрее: 7,709 мкс против 6,438 мкс. Байты в обоих прогонах те же: 1,68 KB против 1,4 KB. Второе: это микробенчмарки. Типовой запрос ждёт базу миллисекунды, а экономия измеряется наносекундами. Могу себе представить нагрузочный тест I/O-bound API, где с флагом латентность не изменится, и это будет честный результат. Заметнее эффект там, где много синхронно завершающихся await (кэши, парсеры, глубокие конвейеры), и в метриках GC, а не во времени ответа. Третье: фича в preview. Документация прямо просит присылать отчёты о пропускной способности и размере библиотек в обе стороны, положительные и отрицательные. До стабилизации производительность и детали поведения могут меняться от сборки к сборке. Переменные окружения DOTNET_RuntimeAsync и UNSUPPORTED_RuntimeAsync больше не поддерживаются, включение теперь через Features, отключение через UseRuntimeAsync=false. Четвёртое: всё, что опирается на внутренности старой схемы, стоит проверить. Кадров билдера в живом стеке у меня стало 0 вместо 3, и если в коде или в инструментах кто-то разбирает стек по именам вида <Method>d__0 или по кадрам MoveNext, их больше не найдут. Я бы проверил: самописные логгеры, которые склеивают стек в строку; правила группировки ошибок в системе мониторинга; диагностику, которая ищет конечный автомат через reflection. Пятое: поддержка не сплошная. Runtime-async работает с четырьмя типами возврата: Task, ValueTask, Task<T> и ValueTask<T>. По разбору Тауба, async void, async-итераторы и собственные task-like типы со своими билдерами пока остаются на классическом преобразовании. Для пограничных случаев, вроде by-ref локальных переменных и await в обработчиках исключений, сверяться стоит с поведением текущего компилятора, а не только с черновиком ECMA-335. Что проверить у себя Мой вариант порядка, если сервис уже на .NET 10 и переезд на .NET 11 запланирован: Обновить проект на .NET 11 без флага и снять базовые метрики: аллокации в секунду, паузы GC, p99 латентности. Часть выигрыша вы получите уже здесь, потому что BCL собрана в новом режиме. В отдельной ветке включить <Features>$(Features);runtime-async=on</Features> и прогнать те же замеры на той же версии SDK и рантайма, тем же способом. Так эффект обновления BCL отделяется от эффекта флага. Сравнивать аллокации, а не только время. Найти горячий путь с глубокой цепочкой await и написать на него бенчмарк BenchmarkDotNet с MemoryDiagnoser. Там будет видно, сливаются ли вызовы. Проверить всё, что читает стек: логирование, APM-агент, группировку ошибок. Если что-то ломается, лучше узнать до GA, а не после. Если встали ограничения, отключить фичу для проекта через <UseRuntimeAsync>false</UseRuntimeAsync> и оставить отчёт команде .NET. Сроки поджимают: по официальной политике поддержки .NET 8 и .NET 9 перестают поддерживаться 10 ноября 2026 года, а .NET 11 RC2 вышел 13 октября с go-live лицензией. Окно для экспериментов как раз сейчас. Вывод Самое интересное здесь, на мой взгляд, не наносекунды, а выбор архитектуры. Java с виртуальными потоками позволила во многих сценариях писать блокирующий код без async-сигнатур, переложив приостановку на JVM, хотя асинхронные API в экосистеме никуда не делись. Kotlin до сих пор строит конечный автомат в компиляторе, ровно как C# последние двенадцать лет. .NET в 2023 году отказался от зелёных потоков и пошёл третьим путём: модель программирования оставили прежней, а реализацию перенесли туда, где у неё больше информации. Для разработчика на C# это означает простую вещь: ответ «async — это синтаксический сахар над конечным автоматом» теперь неполный. Компилятор больше не выпускает привычный MoveNext, приостановку и возобновление организуют VM и JIT, и JIT может оптимизировать границы между вложенными async-методами, потому что видит цепочку вызовов. Для разработчика на JVM это хороший повод посмотреть на корутины Kotlin иначе: компиляторная схема не единственная, и платформа с похожей моделью только что показала, сколько она стоит. Ссылка на мой репозиторий с экспериментом Начать стоит с README.md: там все таблицы этой статьи, включая разницу между запуском в одном процессе и в отдельных. shared/Chains.cs — измеряемые цепочки. src/AsyncProbe/Program.cs — стек-трейсы и аллокации. bench/AsyncBench/Program.cs — бенчмарки. ./run.sh — собирает всё с нуля и сверяет результат с инвариантами из expected.txt. ИСТОЧНИКИРепозиторий с экспериментом What's new in .NET 11 runtime, Microsoft Learn, обновлено для RC2 Toub S., Performance Improvements in .NET 11, .NET Blog, 15.09.2026 Runtime-async, черновик изменений ECMA-335, dotnet/runtime Green Thread Experiment Results, dotnet/runtimelab #2398, 08.09.2023 .NET 9 Runtime Async Experiment, dotnet/runtime #94620 .NET and .NET Core Support Policy Announcing .NET 11 Release Candidate 2, .NET Blog, 13.10.2026 JDK 21, OpenJDK Kotlin Coroutines, KEEP Чтобы расти до уровня Senior C#-разработчика и браться за более сложные задачи, важно не только уверенно писать код, но и понимать, как работает .NET под капотом. На курсе «C#-разработчик. Продвинутый уровень» можно углубить эти знания, научиться находить узкие места в приложениях и принимать более обоснованные архитектурные решения. Пройдите вступительный тест, чтобы оценить свой текущий уровень, выявить пробелы и понять, какие навыки стоит подтянуть для следующего карьерного шага.

  7. Reddit · · r/dotnet · u/shanewas726 · dotnet/core

    CVE-2026-111497: a Brotli decoder bug forced an out-of-band .NET release (10.0.14) on Oct 25 Microsoft released .NET 10.0.14 on 25 October as an out-of-band security update, and the stated reason is CVE-2026-111497, an out-of-bounds write (CWE-787) in the Brotli decoder bundled with .NET. CVSS 8.1, publicly disclosed, exploitation marked more likely. Affected and fixed: - .NET 8 before 8.0.33 → 8.0.33 - .NET 9 before 9.0.22 → 9.0.22 - .NET 10 before 10.0.14 → 10.0.14 (SDK 10.0.114 or 10.0.403) - .NET Framework is not affected The same release completes the October 13 fixes for CVE-2026-70197 (SocketAsyncEventArgs) and CVE-2026-102870 (Blazor WebAssembly debugger), which had shipped incomplete, and adds CVE-2026-114148 for Linux source-build tarballs. Exposure needs a call path that decompresses attacker-controlled Brotli: request decompression with br enabled, HttpClient AutomaticDecompression including Brotli, or BrotliStream/BrotliDecoder on untrusted input. None of that is on by default, and response compression alone does not hit it. Until you update, dropping Brotli from those paths is a workable stopgap. Framework-dependent apps need the host runtime updated and restarted. Self-contained apps need a rebuild and republish, and the Blazor WebAssembly Server/DevServer packages have to be bumped separately because the shared runtime update does not cover them. Advisory: announcements#494 Release notes: github.com/dotnet/core/blob/main/release-notes/10…

    51 points · 11 comments
  8. Brotli CVE-2026-111497: Memory Corruption, Information Disclosure, and RCE via Crafted Compressed Streams 1. Basic Information - Article Name: Wednesday, October 21, 2026 Security Releases - Source: Node.js project - Published Date: October 21, 2026 - Original Source: Node.js project - Related Information Sources: GitHub Advisory: Python Brotli / brotlicffi, Istio Brotli Requirements, Envoy Brotli Advisory, BleepingComputer, JPCERT/CC, Microsoft .NET Advisory, Imperva, OpenAI - Related Malware, Threat Groups, CVEs, Products: CVE-2026-111497, Google Brotli, Node.js, .NET, Python Brotli, Bun, Envoy, Istio - Severity: High 2. Executive Summary The Brotli stream decoder incorrectly treats invalid block switching as a request for additional input rather than a permanent error, writing data beyond the 8-byte internal buffer. Decompressing attacker-controlled Brotli data can cause process crashes or disclose process memory, and may allow remote code execution (RCE) when additional conditions are met. Public proofs of concept and research demonstrations exist, but the reviewed public sources do not report malicious exploitation in the wild. 3. Attack Flow The following are separate paths leading to the same decoder vulnerability. Sending requests to a server and retrieving responses as a client are independent attack vectors and do not form a single mandatory sequence of steps. Server Decompression of Compressed Requests 1. An attacker sends a crafted Brotli stream as an HTTP request or API input. 2. An application or runtime decompresses the data using the vulnerable decoder. 3. Writing to the internal buffer continues even after invalid block switching, causing an out-of-bounds write. 4. This leads to process crashes, memory information disclosure, and, when memory layout and protection mechanism conditions are met, remote code execution (RCE) under the process privileges. Client Decompression of Compressed Responses 1. An attacker induces a backend client, crawler, proxy, AI agent, or similar entity to retrieve an attacker-controlled URL. 2. The client receives a response marked with Content-Encoding: br and automatically decompresses it. 3. The same decoder flaw is triggered inside the client process, potentially leading to crashes, memory information disclosure, and code execution if additional conditions are met. The following table organizes the attack vectors and investigation perspectives. RCE, subsequent child processes, and outbound communications do not occur in all environments. | Attack Stage | Attacker Action / Technique | Tools / Communications | Defender Indicators (Prerequisites / Artifacts) | | --- | --- | --- | --- | | Vector A: Server-Side Request Decompression (Initial Transmission) | Sends a crafted Brotli compressed stream to the server as an HTTP request or API input. | HTTP/HTTPS communication, POST/PUT requests, Content-Encoding: br | Operation of runtimes equipped with vulnerable decoders, and automatic request body decompression settings (such as Express middleware configurations) | | Vector A: Server-Side Request Decompression (Internal Processing) | The server application decompresses a stream containing invalid block switching, triggering an out-of-bounds write. | Internal decoder calls in Node.js, .NET, Python, Envoy, Istio, etc. | Decoder processing errors, process termination (crashes), segmentation faults (segfaults), core dumps | | Vector B: Client-Side Response Decompression (Initial Inducement) | Induces crawlers, backend integration processes, AI agents, etc., to access an attacker-managed URL. | Induced requests to external URLs, external API fetches | Automatic response decompression must be enabled in the component fetching the URL (e.g., built-in fetch()) | | Vector B: Client-Side Response Decompression (Internal Processing) | The client process decompresses the received malicious Brotli response, triggering the same vulnerability. | HTTP responses, Content-Encoding: br, client internal decoders | Abnormal termination of the client process, connection drops, service stoppages, generation of abnormal child processes | | Post-Exploitation (Remote Code Execution & Impact Spread) | Triggers code execution under process privileges or memory information disclosure when memory layout and mitigation conditions are met. | Unauthorized shell commands (e.g., /bin/sh), outbound communication, memory dumps | Suspicious child process launches from web server/client processes, unknown outbound communications | Running an HTTP server on a vulnerable Node.js runtime does not, by itself, cause received bodies to be decompressed with Brotli. A configured request parser or another component must actually decompress the input. Imperva demonstrated RCE on Node.js 24.22.0 (Linux arm64) through both the built-in fetch() response-decompression path and the request-decompression path in a configured Express/body-parser application. The same input was rejected by patched version 24.22.1. These results do not establish successful exploitation on untested builds or architectures. This vulnerability is not specific to HTTP/2. Although nicknames such as "HTTP2Shell," "Node2Shell," and "Broccoli2Shell" exist, the fundamental attack condition is a vulnerable Brotli decoder decompressing data controlled by an attacker. 4. Attacker Position and Execution Location For the server-side vector, the prerequisite is the ability to send compressed requests from the internet or an internal network. For the client-side vector, the requirement is the ability to cause the target to retrieve and decompress an attacker-controlled response. Memory corruption itself occurs inside processes such as Node.js, .NET, Python, or reverse proxies that invoke the Brotli library. 5. Visibility for Victims and Administrators - Victims: May perceive the issue as service unresponsiveness, connection drops, or 5xx errors. - Administrators: May detect decoder process segmentation faults (segfaults), core dumps, unexpected process restarts, or suspicious child process creation and external communications immediately following HTTP request processing. 6. Success and Failure Conditions Success Conditions - The vulnerable Brotli decoder binary is incorporated into the running process. - In .NET environments, this applies to Brotli-enabled request decompression, HttpClient automatic response decompression, or explicit use of BrotliStream / BrotliDecoder. Default configurations of ASP.NET Core and HttpClient do not automatically decompress, and .NET Framework is not affected. Envoy and Istio are also affected when the Brotli decompressor is explicitly enabled. - The target process actually performs decompression processing on attacker-controlled Brotli data. - Whether RCE succeeds also depends on the target version, CPU architecture, memory layout, and OS mitigations such as ASLR. Failure Conditions and Mitigations - Update to patched versions provided by respective vendors, such as Google Brotli (1.0.10, 1.1.1, 1.2.1) or Node.js (22.23.4, 24.22.1, 26.12.1). - Until patching is complete, disable or exclude Brotli request and response decompression processing enabled in existing environments. Microsoft advises against enabling previously unused decompression features as part of a mitigation. - Reject Brotli before decompression, following OpenAI's guidance. Simply removing br from Accept-Encoding or stopping outbound compression on CDNs or web servers does not protect incoming request or client-side response decompression paths. Even if decompression is stopped in Envoy or Istio, compressed data may flow to backend components, so remaining vulnerable decoders must also be protected. - After updating, restart related processes and ensure container images are rebuilt and redeployed. Updating only the operating system's Brotli library may leave vulnerable runtime-bundled copies unchanged. 7. What Happens Upon Success Successful exploitation can crash the decoder process (DoS), disclose process memory, or, when additional conditions are met, execute code with the process's privileges. In .NET environments, this may lead to privilege escalation depending on the application configuration and execution context. 8. Observable Logs The following are investigation ideas (Inference). Available logs vary by product and configuration and do not represent confirmed IOCs observed in active exploitation environments. Normal Content-Encoding: br traffic or data lengths alone cannot be used to conclusively determine an attack. - Email: No email-initiated attack activities specific to this vulnerability have been reported. - Proxy / SWG / DNS: Content-Encoding: br traffic, abnormal responses, and communication histories to backend fetch destinations. - Endpoint / EDR: Segfaults in Node.js, dotnet, Python, or proxies, core dumps, abnormal child process launches, and unexpected allocations of executable memory regions. - Identity / IdP: The reviewed sources do not report authentication IOCs specific to this vulnerability. Investigate possible service account abuse following successful RCE separately. - SaaS / Cloud: Container restarts, Pod CrashLoopBackOff states, spikes in load balancer 5xx errors, and differences in deployed image digests. - Network: Brotli streams chronologically matching decoder errors, repetitive communications from the same source, and request-response flows immediately preceding crashes. 9. Attack Success Determination If the only evidence is the arrival of crafted Brotli data or traffic matching a PoC fingerprint, classify it as an attack attempt. The occurrence of decoder errors or process crashes indicates potential memory corruption but does not immediately signify successful RCE. Confirm initial execution only when there is execution evidence, such as suspicious child processes launched by the decoder process, process telemetry matching attacker commands, or equivalent evidence. Imperva's RCE results were obtained in a research environment using Node.js 24.22.0 (Linux arm64) and specific test configurations; they do not establish reliable reproduction across all environments. 10. Investigation Playbook 1. Trigger: Crashes or segfaults in processes that decompress Brotli data, core dumps, detected PoC fingerprints, or suspicious child processes. 2. Initial Verification: Verify incorporated decoder copies, runtime and package versions, request/response decompression settings, and process restart times. 3. Endpoint Investigation: Preserve and investigate process trees before and after crashes, core dumps, memory faults, launched shells, download tools, and file modification histories. 4. Authentication & Cloud: Check service account and Workload Identity usage history, secret access logs, and cloud API operation logs. 5. Post-Exploitation Tracking: Track traces of credential access, persistence configurations, lateral movement, and data exfiltration. 6. Containment: Temporarily halt suspicious Brotli decompression processing, update to vendor-patched versions, restart processes, and rebuild container images. 7. Judgment Categories: Evaluate distinctly across stages including reachability, decoder errors, crashes, memory information disclosure, RCE, and subsequent compromises. 11. Defense and Detection Ideas - Single Event: Alerts for segfault occurrences in Brotli decompression processes coupled with preceding br encoding communications. - Chronological Correlation: Perform correlation analysis from receiving crafted compressed streams -> decoder errors or process crashes -> process restarts -> generation of suspicious child processes and outbound communications. - Proactive Threat Hunting: Cross-search SBOMs, container image layers, binary dependencies, and runtime-bundled modules to comprehensively identify vulnerable Brotli copies. - Logging Considerations: Identifying causal relationships becomes difficult if TLS-terminated plaintext payloads, client-side response headers, and process crash telemetry are not preserved. - Priority Controls: Prioritize applying vendor-patched versions, restarting processes, rebuilding and redeploying containers, and disabling unnecessary automatic Brotli decompression features. 12. Facts / Inference / Hypothesis Facts - The Node.js project explained the out-of-bounds write mechanism against the 8-byte internal buffer in Brotli and released patched versions. - JPCERT/CC provided guidance on affected versions of Google Brotli and the scope of impact on widespread embedded products. - Imperva demonstrated RCE under specific conditions in a research environment running Node.js 24.22.0 (Linux arm64). - The reviewed public sources do not report malicious exploitation in the wild. Inference - Not only web servers, but also backend clients and crawlers that retrieve data from external URLs and decompress Brotli responses serve as critical attack surfaces. - In asset management, it is necessary to track not only direct package dependencies but also container images, vendor-embedded libraries, and statically linked binaries. Hypothesis No additional hypotheses. Unconfirmed items are listed in "Uncertainties and Additional Investigation." 13. MITRE ATT&CK Mapping | ID | Technique | Confidence | Basis | | --- | --- | --- | --- | | T1190 | Exploit Public-Facing Application | medium | Inference: Mapped to the assumed path where internet-facing applications decompress Brotli requests controlled by attackers. This does not indicate observed active exploitation, and this basis does not apply to paths where clients decompress malicious responses or internal services not exposed externally. | 14. Uncertainties and Additional Investigation - The presence of active exploitation by malicious actors, patch availability status across all embedded products, and detailed RCE success conditions per product and architecture remain unconfirmed. The existence of public PoCs cannot be treated as confirmation of production environment compromise. 15. Impact on SOCs and Organizations Even for organizations that do not directly develop or operate Node.js or .NET internally, API clients, service meshes, proxies, Python packages, and container images bundling the Brotli library may be affected. Create software inventories from both public web services and batch processing tasks retrieving external URLs, and verify patch application and process restarts for each embedded copy. 16. Summary by Target Audience - For SOCs: Monitor process crashes, suspicious child process launches, and outbound communications occurring immediately after Brotli decompression processing by correlating them chronologically with request and response traffic. - For Administrators: Promptly apply patched versions provided by product vendors, and ensure process restarts and container image rebuilds/redeployments are fully completed. - For Users: Follow update guidance and restart procedures provided by service administrators.

    1 reaction
  9. CIをローカルPCで再現実行したらPCが悲鳴を上げたので、原因を全部調べた話 個人開発で、動画サイト(ASP.NET Core + React + PostgreSQL)を作っています。品質は「本番並み」を目標にしていて1、合流の前にはビルド・lint・型・全テスト・e2e・秘密情報の走査をすべて流し、全部通ったものだけをマージする決まりにしています。 ところが、別の検証で GitHub Actions の無料枠を使い切ってしまいました。そこで CI と同じ手順を手元で 1 回で流すスクリプト(以下、ローカル CI)を作り、合流の前に必ず流すことにしました。この記事は、そのローカル CI を自宅の PC で回し続けたら PC が重くなり、原因を測って直していった記録です。 ! 開発の多くは Claude Code に任せていて、モデルごとに役割を分けています。 Claude Opus 5.5: 全体の取りまとめとレビュー Claude Sonnet 5.5: 機能ごとの実装(何本か並行で動かす) Claude Fable 5.1: 原因の見立ての検証 この記事の調査も Opus と Sonnet が行い、出てきた原因の見立てを Fable に確かめさせています。 環境 自宅の PC OS Windows(Docker Desktop・WSL2) CPU Core i7-9700(8 コア 8 スレッド) メモリ 16GB(Docker Desktop に回せるのは 7.7GB) ディスク SATA の SSD 512GB テストの多くは、本物の PostgreSQL・S3 互換のストレージ(Garage)・ffmpeg を Docker で動かして確かめます。結合テストは Testcontainers、e2e は Playwright です。ローカル CI を 1 回流すと、PC の上で多くのコンテナと、数百のアプリのホストが動きます。 何が起きたか 仕事では速い Mac を使っていて、そこで Claude Code に任せているのと同じくらいの量の作業を、自宅の PC にも並べていました。開発の作業を 3 本並べ、ローカル CI が 2 本重なって流れることもありました。自宅の PC でどのくらいの負荷まで耐えられるかを測る、という意味もありました。 結果、動いている間は PC がすごく重くなり、ほかの作業ができなくなりました。性能の差の分はしょうがない。ただ、それとは別に無駄が無いかを調べることにしました。 調べると、起きていたのは次の 2 つでした。 メモリが足りない。 会社の PC より性能の低い PC に、同じくらいの量の作業を並べたのが原因でした。ただ、テストのプロセスが捨てたはずのメモリを抱えたままになる無駄も重なっていました(後の「3. テストのホストが解放されない」)。 空きメモリが数十 MB まで落ちた(普段のローカル CI の回で最小 68MB。バックエンドのテストだけを計測の道具を付けて流した調査の回では 34MB) バックエンドのテストのプロセスが 11〜16GB を握っていた ローカル CI は 1 回 1 時間くらいかかっていた覚えがある(時間の記録は残っていない。記録の残っている 10/9 の 6 回は、1 回 20〜30 分ほどで、6 回ともどこかのテストが落ちて流し直していた)。毎回違うテストが時間切れで落ちた ローカル CI が 2 本重なった回は、どちらの負荷で落ちたのか分けられず、その結果は当てにならなかった ディスクの空きが無くなる。 ローカル CI の最中に C ドライブの空きが 0 バイトになり、Docker も WSL も応答しなくなりました。こちらは、もともと空きが 30GB 足らずだった C ドライブに、テストのたびに作る DB を消さずに、1 回のローカル CI で約 8GB ずつ書いていたのが原因でした(後の「2. ローカル CI を重ねない・テストの DB を消す」)。 どこに時間がかかっているか いちばん重かった頃の、手順ごとの時間の記録は残っていません。残っているのは、10/9 のバックエンドのテストが 10 分 52 秒〜18 分 47 秒かかっていた、という記録です。そこでこの節では、改善の途中で取ったバックエンドのテストの内訳と、直した後の今のローカル CI(約 19 分)の手順ごとの時間を使って、何が重いのかを見ていきます。 結論から書くと、次の 3 つが分かりました。 今のローカル CI でも、時間の約半分はバックエンドのテストが占めている そのバックエンドのテストは、8 コアのうち半分も使えていなかった。並列で流せる部分は、いちばん長いテストのまとまり 1 つで決まり、ffmpeg を使うテストは単独で 3 分以上かかっていた テスト 1 件ごとの準備では、パスワードのハッシュの計算が重く、しかも 1 件の中で何度も繰り返されていた もう 1 つ、時間の表には出てこない問題として、メモリの使い方がありました(後の「3. テストのホストが解放されない」)。 それぞれ、測った値で見ていきます。まず、直した後の今のローカル CI の、手順ごとの時間です。 手順 時間 バックエンドのテスト(xUnit。1,752 件。Docker の PostgreSQL・ffmpeg を使う) 9 分 28 秒 e2e(Playwright。173 件。テストの実行だけ。DB などの起動は除く) 約 3 分 24 秒 フロントエンドのテスト(Vitest。1,822 件) 2 分 48 秒 そのほか(ビルド・型・lint・脆弱な依存・秘密の走査、e2e の環境の起動など) 約 3 分 合計 約 19 分 19 分のうち約 9 分半がバックエンドのテストです。速くするなら、まずここを見ることになります。 そのバックエンドのテストを、さらに割りました(改善の途中の回。1,339 件・8 分 9 秒)。 段階 時間 見えたこと 並列の部分 276 秒 8 コアのうち平均 3.8 しか使えていない。最も長い 1 つのテストのまとまり(xUnit の collection。まとまりの中のテストは順番に動く)の時間で決まる ffmpeg を Docker で動かすテスト(単独) 196 秒 ほかと重ねずに流している(理由は後の「2.」) そのほか(単独) 6 秒 並列の部分は、いちばん長いまとまりが終わるのを待つ形なので、ほかのまとまりを速くしても縮みません。縮めるには、テスト 1 件ごとの重さを減らす必要があります。 そこで、テスト 1 件ごとの準備の時間を、ほかの負荷が無い状態で 30 回ずつ測りました(中央値)。 処理 時間 テスト専用の DB をひな形からコピーする 25 ms アプリのホストを立ち上げる(DB の作成を含む) 129 ms パスワードのハッシュを 1 回計算する(PBKDF2・10 万回) 54 ms ffmpeg の docker run を 1 回起動する 約 330 ms パスワードのハッシュ 1 回(54 ms)は、DB のコピー(25 ms)の倍かかります。テストの準備では 1 件ごとに会員を 3〜6 人作るので、この計算が 1 件の中で 3〜6 回繰り返されていました。 直していった順番 1. 並行の作業を減らす 作業ごとにテスト用のコンテナと画面のビルドが動くので、8 コア・16GB で 3 本は多すぎました。同時に動かすのを 2 本にしました。 2. ローカル CI を重ねない・テストの DB を消す ローカル CI が 2 本同時に流れていたので、ローカル CI のスクリプト自身が最初に鍵(フォルダ)を取り、一度に 1 本しか流れないようにしました。2 本目は 1 本目が終わるまで待つことになりますが、負荷の山は 1 本分になり、取り合いで落ちて流し直すことも無くなります。 git worktree で作業ツリーが分かれていても同じ鍵を見るよう、置き場所はリポジトリの本体の側にする GATE_LOCK="$(cd "$(git rev-parse --git-common-dir)/.." && pwd)/.claude/gate.lock" mkdir -p "$(dirname "$GATE_LOCK")" mkdir は「無ければ作る」を 1 回の操作で行うので、2 本が同時に来ても片方だけが取れる if ! mkdir "$GATE_LOCK" 2>/dev/null; then echo "別のローカル CI が動いています" >&2 cat "$GATE_LOCK/owner" >&2 # 持ち主(PID・開始時刻・作業ツリー)を表示する exit 1 fi (持ち主を $GATE_LOCK/owner に書き、失敗・中断でも EXIT の trap で owner と鍵のフォルダを消す) テストは 1 件ごとに専用の DB を作りますが、消していませんでした。1 回のローカル CI で約 8GB を書き込み、C ドライブを使い切っていたので、終わったら消すようにしました。 // 作る: マイグレーション済みの「ひな形」からコピーする(1 個 25ms) command.CommandText = $"CREATE DATABASE \"{name}\" TEMPLATE \"{TemplateName}\""; // 消す: 接続が残っていても強制的に切る。後片付けなので、失敗してもテストは落とさず警告にする command.CommandText = $"DROP DATABASE IF EXISTS \"{name}\" WITH (FORCE)"; ffmpeg を Docker で動かすテストは、ほかと重なると起動が遅れて落ちていたので、ほかと重ねずに流すようにしました。これでその揺れは止まりましたが、テストの時間は約 7 分延びました。 ! テストが毎回違う所で落ちる原因について、メイン(Opus)が最初に立てた見立ては「DB を消していないので、ディスクの I/O が詰まってテストが遅れている」でした。Fable に検証させると、DB の作成は 1 回のローカル CI で合計約 40 秒・平均約 18MB/s の書き込みで、SSD を詰まらせる量ではありませんでした。DB を消す直しが主に効くのは容量で、テストが遅れる主な原因ではありませんでした。落ちていたテストの多くは、テスト自身の同期の競合でした。 3. テストのホストが解放されない(ASP.NET Core の既知の不具合) テストは 1 件ごとにアプリのホスト(WebApplicationFactory)を立ち上げて捨てます。計測の道具を足して調べると、869 個のホストが、捨てた後も 1 つ残らずメモリに残り、合計 16GB に達していました。 原因は ASP.NET Core の既知の不具合でした。回数制限のミドルウェア(RateLimitingMiddleware)が、ポリシーの有無に関係なく内部に限定器(レート制限器。PartitionedRateLimiter。100ms のタイマー付き)を作り、それを破棄しません。タイマーが static のキューから生き続け、ホスト全体を参照し続けます。 dotnet/aspnetcore #66434 RateLimitingMiddleware never disposes PartitionedRateLimiter causing memory leak(2026-10-26 時点で未修正。修正の PR #66435 も未マージ) 本番は 1 プロセスに 1 ホストなので影響しません。テストのように何百もホストを作ると溜まります。 テストの側で、起動時に組み上がったパイプラインを受け取り、自分のホストの限定器を捕まえて、DI コンテナの破棄と一緒に止めるようにしました。 // 起動時に、パイプラインの先頭に「後ろ全部」を受け取る印を置く。リクエストごとの処理は増えない internal sealed class RateLimiterCaptureFilter(RateLimiterReleaser releaser) : IStartupFilter { public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next) { return app => { app.Use(rest => { releaser.Capture(rest); // パイプラインをたどって RateLimitingMiddleware の _endpointLimiter を捕まえる return rest; }); next(app); }; } } RateLimiterReleaser は DI のシングルトンで、捕まえた限定器をコンテナの破棄のときに Dispose します。中身はリフレクションで private のフィールドをたどるので、上流が直ったら外す前提です。「この仕組みを入れないとホストが残る」ことを確かめるテストも付けておき、上流が直ってそのテストが落ちたら外す合図にしています。 直したことで、捨てたホストが残らなくなりました。下の表は、原因を調べるために、計測の道具を付けてバックエンドのテストだけを流した回(調査の回)の値です。 項目 直す前の調査の回 直した後の調査の回 捨てた後も残るホスト 869 2 残るメモリ 16.06GB 247MB 空きメモリの最小 34MB 2,854MB バックエンドのテスト 17 分 22 秒 7 分 39 秒 時間の効果は、調査の回とは別に、普段のローカル CI(全部の手順を流す)の回で比べました。どちらの列も、その 1 回のローカル CI の中で測った値です。 項目 直す前のローカル CI の回 直した後のローカル CI の回 ローカル CI の全体 約 20 分 約 16 分 バックエンドのテスト 10 分 33 秒 7 分 29 秒 空きメモリの最小 68MB 3,282MB 直す前は、メモリが尽きて、Windows がメモリの中身をディスクに退避させながら動いていた(ページング)分、遅くなっていました。どれだけ遅くなるかは回ごとにぶれます(バックエンドのテストは、普段のローカル CI の回で 10 分 33 秒〜18 分 47 秒、計測の道具を付けた調査の回で 17 分 22 秒)。調査の回は道具を付けている分も重く、差の理由までは切り分けていません。 ! このメモリの滞留も、原因を突き止めるまでに、見立てを 3 回外しています(同時に生きているホストが多すぎる・GC が回収を後回しにしている・ワーキングセットの急落をゴミの回収と読んだ)。最後は、GC のヒープ・Private Bytes・生きているホストの数を測って、参照の経路をたどって決めました。 4. テストの準備で、同じ重い計算を毎回していた テストの準備では、1 件ごとに同じパスワードの会員を 3〜6 人作り、そのたびにパスワードのハッシュ(既定の PasswordHasher。PBKDF2・10 万回。1 回 54ms)を計算し直していました。テスト 1 件あたり 0.16〜0.32 秒が、毎回同じ結果を作るためだけに使われていました。 同じパスワードのハッシュは、テストのプロセスの中で 1 回だけ作って再利用するようにしました。 // キーはパスワードだけ(PasswordHasher<TUser> は user を使わない) private static readonly ConcurrentDictionary<string, string> HashCache = new(StringComparer.Ordinal); user.PasswordHash = HashCache.GetOrAdd(password, static (p, u) => Hasher.HashPassword(u, p), user); ログインの照合は本物の計算のままなので、確かめている中身は変わりません。同じパスワードの会員どうしでハッシュ(ソルト)が同じになりますが、これはテストの種データだけの話です。再利用しても会員ごとに自分のパスワードでしかログインできないことは、テストで確かめました。 壁時計は 446 秒 → 391 秒、テストのプロセスの CPU 時間は 637 秒 → 465 秒になりました。ただ、前後は 1 回ずつしか測っていません。壁時計の差(54 秒)は、同じコードを 2 回測ったときの差(43 秒)と同じくらいしかなく、CPU 時間は前を 1 回しか測っていないのでぶれが分かりません。効果の大きさは確かめきれていません。効果の根拠は仕組み(54ms の計算が 2 人目以降で無くなる)です。 5. e2e がたまに時間切れで落ちる(localhost の落とし穴) 最近、e2e が毎回違う所で時間切れになりました。調べると、e2e のアプリが DB に localhost でつないでいたのが原因でした。 Docker の公開は 127.0.0.1:5432(IPv4)だけ。IPv6 の ::1:5432 には誰も待ち受けていない この PC では、localhost を引くと ::1 が先に返る この PC では、待ち受けの無いポートへの接続が拒否されるまで約 2 秒かかる(.NET で測って 2,031〜2,133ms。127.0.0.1 の空いているポートでも同じだったので、::1 に限らず、この環境の性質。原因は確かめていない) Npgsql は接続の時間切れを、接続先の数で等分して順に試す。時間切れを 2 秒にしていたので、新しい接続のたびに約 1 秒を ::1 で失い、残りで間に合わないと失敗する const postgres = { // compose は postgres を 127.0.0.1 にだけ公開する。localhost だと ::1 を先に試して、拒否を待つ間に時間を失う host: '127.0.0.1', // ... }; 接続先を 127.0.0.1 にすると、比べた e2e の 1 回で、DB の接続の失敗は 0 件になり、ログインの遅い側(95 パーセンタイル)は 1,087ms → 165ms になりました。 結果 いちばん上の行(記憶)を除き、どれも普段のローカル CI(全部の手順を流す)の回の記録の値です。バックエンドのテストは、そのローカル CI の中の 1 手順の時間です。 時点 ローカル CI の全体 バックエンドのテスト いちばん重かった頃(作業を 3 本並べていた頃) 1 時間くらい(記憶。記録なし) 記録なし 改善の前(10/9) 20〜30 分ほど(6 回で 2 時間 10 分。6 回とも失敗) 10 分 52 秒〜18 分 47 秒 ホストの解放の前(10/10) 約 20 分 10 分 33 秒 ホストの解放の後(10/10) 約 16 分 7 分 29 秒 ハッシュの再利用の後(10/12) 約 15 分 6 分 34 秒 今(10/23) 約 19 分 9 分 28 秒 今は、テストの数がバックエンド 1,340 → 1,752 件、フロントエンド 1,627 → 1,822 件に増えた中で、約 19 分で全部通っています。 分かったこと 重さの原因は、性能の低い PC に作業を並べすぎたことと、捨てたはずのものが残り続けること(ASP.NET Core の既知の不具合で、テストが捨てたアプリのホストがメモリに残る・テストの DB を消し忘れて残る)の重なりでした。作業を減らし、ローカル CI を重ねないようにし、残るものを片付けると、ローカル CI は 1 時間くらいかかっていた覚えのある頃から、15〜20 分で安定して通るようになりました 毎回同じ結果を作り直していないかも見るべき所でした。テストの準備で、同じパスワードのハッシュを何度も計算していました。結果が同じになる重い処理は、作り直さずに 1 回だけ作って再利用することが大切です それでも残る差は、PC の性能の差です。8 スレッド・16GB では、Docker とテストとブラウザが取り合い、時間を測るテストがたまに揺れます 原因の見立ては外れる。テストが落ちる原因の最初の推測は、別のモデルに検証させたら大部分が誤りでした。推測のまま直していたら、効かない直しに時間を使うところでした。直す前に測り、見立てを確かめるのが近道です おわりに 自宅の PC で、測っては直してを繰り返した記録です。手元で CI を回している方や、同じように PC が重くなって困っている方、どなたかの参考になれば嬉しいです。 ご一読ありがとうございました。 おまけ: Windows + Docker Desktop で決めていること イメージのビルドの中で dotnet restore や pnpm install をしない。 セキュリティソフトが HTTPS を検査して独自の証明書を差し込むので、コンテナの中から取りに行くと証明書の誤りになります。検証を切る代わりに、ホストでビルドした成果物を COPY で入れています(例外は ffmpeg の apt だけ。apt は HTTP でも、パッケージの署名を確かめてから入れます) AI のエージェントから Docker Desktop を起動しない。 Claude のデスクトップアプリ(MSIX パッケージ)から起動したプロセスは AppData の下が仮想化され、Docker が自分のソケットを片付けられず、次の起動で落ちました。Docker は自分でスタートメニューから起動します 固まったら、残ったプロセスを止めて WSL を終わらせる。 画面は「Engine running」なのに命令が返らない、Quit しても中のプロセスが残って起動し直せない、ということがあります。残ったプロセスを止め、wsl --terminate docker-desktop を流してから起動し直します。スリープの後は、docker info がすぐ返るかを確かめてから使います ローカル CI を流す前に、ディスクの空きとテストのコンテナの残骸を見る。 WSL2 の仮想ディスクは自動では縮まず、空きが尽きると Docker も WSL も応答しなくなります 公開は 127.0.0.1 の必要なポートだけ。 アプリのイメージは、基底のイメージをダイジェストで固定し、root でない利用者で動かす 脚注 自分の好きな SNS を自分で作り、本番の運用に耐えられる品質まで仕上げる試みです。作りながらたまった知識はノウハウとして残し、学習にもしています。 ↩︎

  10. There's an experiment to create an LLVM-based Native AOT tool chain. I sincerely hope they work on this properly, so that we can have a properly optimized AOT. But I'm not sure if it will end up with less quirks. github.com/dotnet/runtimelab/tree/feature/NativeA…

Trending awards, every completed day and week
Gold
111st place finishes
Silver
132nd place finishes
Bronze
43rd place finishes
Awarded repositories
9Owned by dotnet now

Repositories

Ordered by gold, then silver, then bronze.

  1. coreclrBest: 1st by stars, 3,160 stars510
  2. aspnetcoreBest: 1st by stars, 2,140 stars300
  3. corefxBest: 1st by stars, 1,455 stars260
  4. wpfBest: 1st by stars, 880 stars120
  5. roslynBest: 2nd by PR authors, 19 PR authors020
  6. mauiBest: 2nd by stars, 1,021 stars011
  7. machinelearningBest: 2nd by stars, 678 stars010
  8. runtimeBest: 3rd by forks, 83 forks002
  9. winformsBest: 3rd by stars, 568 stars001

A repository earns an award for finishing first, second or third in a completed trending day or week, ranked by new stars, new forks or pull request authors. Awards follow a repository through renames and transfers and count toward its current owner. Weeks run Monday to Sunday, UTC.