---
id: 6e0bce9e22a4bf7d9672c59e67bcf5ec
author: Lemon Mint
title: 'gosuda.org/chromenet: Chromiums Netzwerk-Stack für Go, reproduzierbar aus einem fixierten Release erstellt'
description: CGO_ENABLED=0 Go http.RoundTripper, der Anfragen über den eigenen //net-Stack von Chromium (HTTP/1.1, HTTP/2, QUIC/HTTP/3, BoringSSL) in einem isolierten HQQ-Worker sendet, mit Quellcode, Toolchains und sechs eingebetteten Binärdateien, die über SHA-256 fixiert sind und in gvx-Sandboxes erstellt wurden.
language: de
date: 2026-11-09T00:00:00Z
path: /chromenet
go_package: gosuda.org/chromenet
go_repourl: https://github.com/gosuda/chromenet.git
category: network
---

`gosuda.org/chromenet` ist ein Go-`http.RoundTripper`, der Anfragen über den eigenen Netzwerk-Stack von Chromium sendet. Dieser Stack ist `//net` aus einer offiziellen Chromium-Version: deren HTTP/1.1-, HTTP/2- und QUIC/HTTP/3-Implementierungen, BoringSSL, die TLS-Konfiguration von Chrome und der Chrome Root Store. Er läuft in einem separaten Worker-Prozess, `chrome_net_server`, während die Go-Seite reines Go bleibt (`CGO_ENABLED=0`) und über [HQQ](https://gosuda.org/hqq) Shared-Memory-RPC mit dem Worker kommuniziert.

```bash
go get gosuda.org/chromenet@v0.1.10157081033800
```

## Warum Chromiums Netzwerk-Stack ausführen

Ein Go-Programm, das mit `net/http` über das Web kommuniziert, tritt gegenüber dem Server als Go-Client auf. Das ist für die Kommunikation zwischen Diensten der richtige Standard. Es ist jedoch die falsche Wahl, wenn das Verhalten des Servers davon abhängt, für welche Art von Client er sein Gegenüber hält: browsergesteuerte Endpunkte, Anti-Bot-Middleware, CDNs, die unterschiedlichen TLS- und HTTP/2-Fingerprints unterschiedliche Protokollversionen ausliefern, sowie Tests, die exakt das Erlebnis eines echten Browsers reproduzieren müssen.

Die Standardbibliothek von Go kann das Wire-Verhalten eines Browsers nicht abbilden. `crypto/tls` besitzt eine eigene Reihenfolge der Cipher-Suites und einen eigenen Satz an Erweiterungen, und `net/http` verwendet eigene HTTP/2-Einstellungen. Bibliotheken, die einen Browser imitieren, indem sie dessen ClientHello-Bytes kopieren, müssen dies mit jeder Version erneut tun. `chromenet` geht den anderen Weg: Es imitiert den Chromium-Netzwerk-Stack nicht, sondern führt ihn tatsächlich aus.

Gemessen auf der `darwin/arm64`-Engine gegen einen öffentlichen Fingerprint-Dienst (`tls.peet.ws`) meldet die Standardkonfiguration:

| Eigenschaft                        | Beobachteter Wert                                                       |
| :--------------------------------- | :---------------------------------------------------------------------- |
| Ausgehandeltes Protokoll           | `h2`                                                                    |
| JA4                                | `t13d1516h2_8daaf6152771_806a8c22fdea`                                  |
| HTTP/2-Fingerprint (Akamai-Format) | `1:65536;2:0;4:6291456;6:262144\|15663105\|0\|m,a,s,p`                  |
| User-Agent                         | Desktop-Chrome in der Hauptversion der Engine, z. B. `Chrome/157.0.0.0` |

Mit `ForceHTTP3` wurden Anfragen an `cloudflare-quic.com` und `www.google.com` über HTTP/3 beantwortet. Diese Werte stammen von einer Plattform an einem einzelnen Tag. Die Engine ist der Stack, daher ändern sich die Werte, wenn sich die gepinnte Chromium-Version ändert.

## Verwendung

`NewTransport` gibt einen `http.RoundTripper` zurück. Verwenden Sie ihn überall dort, wo Sie `http.Transport` verwenden würden:

```go
package main

import (
	"fmt"
	"io"
	"net/http"

	"gosuda.org/chromenet"
)

func main() {
	client := &http.Client{Transport: chromenet.NewTransport(chromenet.Options{})}

	resp, err := client.Get("https://example.com/")
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Println(resp.Proto, resp.StatusCode, len(body))
}
```

Bei der ersten Anfrage schreibt das Modul das eingebettete Worker-Binary für Ihre Plattform in das Benutzer-Cache-Verzeichnis, prüft dessen SHA-256-Digest mit `gosuda.org/hqq/secureexec`, startet es mit einer Lebensleine zum Elternprozess (`--parent-stdin`, sodass es beendet wird, wenn Ihr Prozess endet) und öffnet einen Session-Pool. Es muss nichts installiert werden.

Einige Verhaltensweisen entsprechen `http.Transport`, damit `http.Client` unverändert funktioniert:

- Der Transport folgt keinen Weiterleitungen und speichert keine Cookies. `http.Client` tut beides, wie auch bei `http.Transport`.
- Mit gzip, deflate, Brotli oder zstd komprimierte Antworten werden dekodiert übermittelt, und `resp.Uncompressed` ist `true`.
- Eine Anfrage belegt eine Engine-Session, bis der Response-Body geschlossen wird. Schließen Sie die Bodys.
- Das Abbrechen des Anfragekontexts bricht die Anfrage innerhalb von Chromium ab.
- Netzwerkfehler sind `*client.NetError`-Werte, die den Chromium-Fehlercode und -namen enthalten:

```go
var ne *client.NetError
if errors.As(err, &ne) {
	fmt.Println(ne.Code, ne.Name) // -105 ERR_NAME_NOT_RESOLVED
}
```

### Optionen

| Feld                          | Wirkung                                                                                                                             |
| :---------------------------- | :---------------------------------------------------------------------------------------------------------------------------------- |
| `UserAgent`                   | Ersetzt den standardmäßigen Chrome-User-Agent. Ein `User-Agent`-Anfrageheader hat weiterhin Vorrang.                                |
| `AcceptLanguage`              | Ersetzt `en-US,en;q=0.9`.                                                                                                           |
| `Proxy`                       | Chromium-Proxy-Regeln wie `http://127.0.0.1:8080` oder `socks5://127.0.0.1:1080`. Leer bedeutet direkte Verbindung.                 |
| `DisableHTTP2`, `DisableQUIC` | Schalten die Protokolle ab.                                                                                                         |
| `ForceHTTP3`                  | Verwendet QUIC für jeden Ursprung, ohne auf eine `Alt-Svc`-Ankündigung zu warten.                                                   |
| `InsecureSkipVerify`          | Akzeptiert jedes Serverzertifikat.                                                                                                  |
| `Pool`                        | Zu verwendende Engine-Sessions. Nil verwendet `client.DefaultPool()`, das 64 gleichzeitige Anfragen erlaubt.                        |

Transports mit gleichen Optionen teilen sich einen Chromium-`URLRequestContext`, sodass Verbindungspools, HTTP/2-Sessions und QUIC-Sessions über Anfragen hinweg wiederverwendet werden. Mit `HQQ_CHROME_NET_SERVER=/path/to/chrome_net_server` wird der eingebettete Worker ersetzt, etwa durch einen lokalen Build.

## Wie es funktioniert

| Schicht             | Was dort läuft                                                                                                                                                                                    |
| :------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Ihr Programm        | `http.Client` und `chromenet.Transport`, reines Go.                                                                                                                                               |
| Prozessgrenze       | HQQ-Shared-Memory-RPC. Die Schnittstelle ist `chrome_net.idl`, kompiliert von `hqq-bindgen` in den Go-Client und die C-Server-Glue.                                                              |
| `chrome_net_server` | Eine kleine C++-Schicht auf Chromium: ein Netzwerk-Thread, der jedes `//net`-Objekt besitzt, sowie Session-Threads, die bis zur Antwort des Netzwerk-Threads auf eine Anfrage blockieren.          |
| Chromium `//net`    | `URLRequestContext`, `URLRequest`, HTTP/2- und QUIC-Sessions, BoringSSL.                                                                                                                          |

Die IDL umfasst sechs relevante Operationen: Erstellen einer Engine aus `key=value`-Optionen, Starten einer Anfrage mit ihrem Kopf (`METHOD URL` plus Headerzeilen) und der Body-Länge, Schreiben von Request-Body-Chunks, Warten auf den Antwortkopf, Lesen des dekodierten Bodys sowie Zerstören der Handles. Ein Anfrage-Handle deklariert `@cancel`, sodass ein verworfener Aufruf die Anfrage innerhalb von Chromium unterbricht, anstatt sie bis zum Ende laufen zu lassen.

Die Isolation des Stacks in einem Worker-Prozess ist ebenso eine Sicherheitseigenschaft wie eine Frage der Paketierung. Der Netzwerkcode von Chromium verarbeitet feindliche Eingaben aus dem offenen Internet. Stürzt er ab, stürzt Ihr Go-Dienst nicht mit ab: Der Aufruf schlägt mit einem Fehler fehl, die Session wird verworfen, und der Client startet beim nächsten Bedarf einen neuen Worker.

## Reproduzierbar gebaut, per SHA-256 gepinnt

Ein 25 MB großes Binary, das Chromium enthält, ist nur dann vertrauenswürdig, wenn Sie genau angeben können, was hineingeflossen ist. `chromenet` folgt Chromium Stable so, wie `gosuda.org/sqlite3` SQLite folgt: Der Quellcode wird nie vendored, sondern ausschließlich gepinnt.

| Pin                                                                                                                                                  | Ort                            |
| :--------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------- |
| Chromium-Version und ihr offizielles `-lite`-Quellarchiv                                                                                             | `third_party/chromium/VERSION` |
| SHA-256 des Archivs und aller Toolchains, die diese Version festlegt: clang, Rust, GN, ninja, die clang-Laufzeit für Windows, xwin                    | `third_party/chromium/SHA256`  |
| MSVC-CRT- und Windows-SDK-Versionen, die über xwin bezogen werden                                                                                    | `third_party/chromium/WINSDK`  |
| Die Version der Sandbox-Laufzeit (`rungv`), der Digest des Gast-Images, das Debian-Snapshot-Datum                                                    | `scripts/engine/BUILDENV`      |
| Die HQQ-C-Laufzeit                                                                                                                                   | `go.mod` und `go.sum`          |

Der aktuelle Pin ist Chromium `157.0.8103.38`. Ein Download, der nicht mit seinem Pin übereinstimmt, bricht den Build ab. Das Release wendet genau einen Patch auf Chromium an: Unter Linux wird `chrome_net_server` ohne NSS gebaut und prüft Serverzertifikate ausschließlich gegen den Chrome Root Store, sodass das Binary nichts außer libc benötigt.

Die Linux- und Windows-Engines werden in [gvx](https://gosuda.org/blog/posts/gvx-cross-platform-linux-containers-z48916d01)-Sandboxes gebaut: unprivilegierte Prozesse mit einem gepinnten Gast-Image und ohne Host-Daemon. Die Windows-Engines werden unter Linux mit clang-cl und lld-link von Chromium gegen das Windows SDK cross-kompiliert, das xwin von Microsoft bezieht. Die macOS-Engines werden nativ auf einem macOS-Runner gebaut, weil der macOS-Build von Chromium Xcode benötigt.

Das erneute Bauen desselben Commits erzeugte in getrennten CI-Läufen byte-identische Binaries. Die Release-Binaries sind auf allen sechs Plattformen byte-identisch mit denjenigen, die ein früherer CI-Lauf für den Commit erstellte, der die Engine im Monorepo einführte.

| Plattform       | Binärgröße |
| :-------------- | ---------: |
| `linux/amd64`   |    27.9 MB |
| `linux/arm64`   |    27.4 MB |
| `darwin/amd64`  |    26.2 MB |
| `darwin/arm64`  |    23.9 MB |
| `windows/amd64` |    27.3 MB |
| `windows/arm64` |    25.8 MB |

Die Linux-Engines hängen nur von `libc`, `libm`, `libdl`, `libpthread` und `libgcc_s` ab und benötigen glibc 2.31 oder neuer.

### Verfolgung von Chromium und Releases

Ein geplanter Workflow prüft alle sechs Stunden Chromium Stable. Bei einem neuen Release schreibt er die Pins neu, baut und testet alle sechs Engines auf einem Branch `chromium/<version>`, attestiert die Binaries und committet die Pins zusammen mit den eingebetteten Binaries, deren Digests und den Lizenztexten in einem einzigen Commit auf `main`. Im Monorepo gewähren pro Datei gültige `OWNERS` dem Bot des Workflows genau diese Dateien, sodass das Update ohne menschlichen Schritt einfließt. Änderungen am Build-Rezept selbst (der Windows-SDK-Pin und der Patch) benötigen weiterhin einen Owner.

Jedes Release-Binary trägt eine Build-Provenance-Attestation des Workflows, der es gebaut hat:

```bash
gh attestation verify chrome_net_server_linux_amd64 --repo gosuda/chromenet
```

Die Lizenztexte aller Bestandteile der einzelnen Binaries (Chromium, BoringSSL, QUICHE und der übrige Build-Graph) werden von Chromiums eigenem `tools/licenses/licenses.py` erzeugt und zusammen mit dem Release als `NOTICE_<os>_<arch>.txt` ausgeliefert.

## Grenzen

- `DialWebSocket` ist ein reiner Go-Client nach RFC 6455 und verwendet nicht den Chromium-Stack.
- `DialWebTransport` ist eine In-Process-Implementierung. Sie erreicht keinen Server.
- Die standardmäßigen JA4- und HTTP/2-Fingerprints sind die der gepinnten Chromium-Version. Chromium ändert sie von Release zu Release, und `chromenet` folgt.
- Die macOS-Binaries sind ad hoc signiert und nicht notarisiert.
- Ein Worker-Prozess kostet Speicher. Jedes Engine-Binary ist 24 bis 28 MB groß auf dem Datenträger, und die Laufzeit von Chromium startet einen Thread-Pool und einen Netzwerk-Thread.
- Eine Engine-Session wird bis zum Schließen des Response-Bodys gehalten. Ein Programm, das Response-Bodys nicht schließt, erschöpft den Pool.

## Quelle

Das Repository ist [github.com/gosuda/chromenet](https://github.com/gosuda/chromenet) und wird aus `//net/chromenet` im gosuda-Monorepo gespiegelt. Das Build-Rezept liegt in `scripts/engine`, die Engine-Glue in `engine/` und die Schnittstelle in `chrome_net.idl`.
