---
id: 6e0bce9e22a4bf7d9672c59e67bcf5ec
author: Lemon Mint
title: 'gosuda.org/chromenet: Pilha de Rede do Chromium para Go, Construída de Forma Reproduzível a partir de uma Versão Fixada'
description: CGO_ENABLED=0 http.RoundTripper em Go que envia requisições pela própria pilha //net do Chromium (HTTP/1.1, HTTP/2, QUIC/HTTP/3, BoringSSL) em um worker HQQ isolado, com código-fonte, toolchains e seis binários embutidos fixados por SHA-256 e compilados em sandboxes gvx.
language: pt
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` é um `http.RoundTripper` em Go que envia requisições pela própria pilha de rede do Chromium. A pilha é `//net` de uma versão oficial do Chromium: suas implementações de HTTP/1.1, HTTP/2 e QUIC/HTTP/3, o BoringSSL, a configuração TLS do Chrome e o Chrome Root Store. Ela roda em um processo de trabalho separado, `chrome_net_server`, e o lado Go permanece Go puro (`CGO_ENABLED=0`), comunicando-se com o processo de trabalho por RPC de memória compartilhada [HQQ](https://gosuda.org/hqq).

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

## Por que Executar a Pilha de Rede do Chromium

Um programa Go que acessa a web com `net/http` apresenta um cliente Go ao servidor. Esse é o padrão correto para o tráfego entre serviços. Não é adequado quando o comportamento do servidor depende do tipo de cliente que ele acredita estar atendendo: endpoints que exigem um navegador, middlewares anti-bot, CDNs que entregam versões de protocolo diferentes conforme as impressões digitais de TLS e HTTP/2, e testes que precisam reproduzir a experiência de um navegador real.

A biblioteca padrão do Go não consegue reproduzir o comportamento de rede de um navegador. O `crypto/tls` possui sua própria ordem de suítes de cifras e seu próprio conjunto de extensões, e o `net/http` possui suas próprias configurações de HTTP/2. Bibliotecas que imitam um navegador copiando os bytes de seu ClientHello continuam precisando ser copiadas a cada nova versão. O `chromenet` segue outro caminho: não imita a pilha de rede do Chromium, mas a executa.

Medido na engine `darwin/arm64` com um serviço público de impressão digital (`tls.peet.ws`), a configuração padrão reporta:

| Propriedade                          | Valor observado                                                        |
| :----------------------------------- | :--------------------------------------------------------------------- |
| Protocolo negociado                  | `h2`                                                                   |
| JA4                                  | `t13d1516h2_8daaf6152771_806a8c22fdea`                                 |
| Impressão digital HTTP/2 (formato Akamai) | `1:65536;2:0;4:6291456;6:262144\|15663105\|0\|m,a,s,p`            |
| User-Agent                           | Chrome de desktop na versão principal da engine, por exemplo `Chrome/157.0.0.0` |

Com `ForceHTTP3`, as requisições para `cloudflare-quic.com` e `www.google.com` foram respondidas por HTTP/3. Esses números provêm de uma única plataforma em um único dia. A engine é a própria pilha, portanto eles mudam quando a versão fixada do Chromium muda.

## Como Usar

`NewTransport` retorna um `http.RoundTripper`. Use-o onde você usaria um `http.Transport`:

```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))
}
```

Na primeira requisição, o módulo grava o binário embutido do processo de trabalho para a sua plataforma no diretório de cache do usuário, verifica seu digest SHA-256 com `gosuda.org/hqq/secureexec`, inicia-o com um vínculo de vida com o processo pai (`--parent-stdin`, para que ele encerre quando o seu processo encerrar) e abre um pool de sessões. Nada precisa ser instalado.

Alguns comportamentos seguem o `http.Transport` para que o `http.Client` funcione sem alterações:

- O Transport não segue redirecionamentos e não mantém cookies. O `http.Client` faz ambos, como ocorre com o `http.Transport`.
- Respostas comprimidas com gzip, deflate, Brotli ou zstd chegam decodificadas, e `resp.Uncompressed` é verdadeiro.
- Uma requisição mantém uma sessão da engine até que o corpo de sua resposta seja fechado. Feche os corpos.
- Cancelar o contexto da requisição cancela a requisição dentro do Chromium.
- Falhas de rede são valores `*client.NetError` que carregam o código e o nome do erro do Chromium:

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

### Opções

| Campo                         | Efeito                                                                                                     |
| :---------------------------- | :--------------------------------------------------------------------------------------------------------- |
| `UserAgent`                   | Substitui o User-Agent padrão do Chrome. Um cabeçalho `User-Agent` da requisição continua prevalecendo.    |
| `AcceptLanguage`              | Substitui `en-US,en;q=0.9`.                                                                                |
| `Proxy`                       | Regras de proxy do Chromium, como `http://127.0.0.1:8080` ou `socks5://127.0.0.1:1080`. Vazio significa conexão direta. |
| `DisableHTTP2`, `DisableQUIC` | Desativam os protocolos.                                                                                   |
| `ForceHTTP3`                  | Usa QUIC para todas as origens, sem aguardar um anúncio `Alt-Svc`.                                         |
| `InsecureSkipVerify`          | Aceita qualquer certificado de servidor.                                                                   |
| `Pool`                        | Sessões da engine a serem usadas. Nil usa `client.DefaultPool()`, que permite 64 requisições simultâneas.  |

Transports com opções iguais compartilham um único `URLRequestContext` do Chromium, de modo que pools de conexões, sessões HTTP/2 e sessões QUIC são reutilizados entre as requisições. Definir `HQQ_CHROME_NET_SERVER=/caminho/para/chrome_net_server` substitui o processo de trabalho embutido, por exemplo por uma build local.

## Como Funciona

| Camada              | O que é executado ali                                                                                                                                                       |
| :------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Seu programa        | `http.Client` e `chromenet.Transport`, em Go puro.                                                                                                                          |
| Fronteira de processo | RPC de memória compartilhada HQQ. A interface é `chrome_net.idl`, compilada por `hqq-bindgen` no cliente Go e na camada de integração do servidor em C.                     |
| `chrome_net_server` | Uma pequena camada em C++ sobre o Chromium: uma thread de rede que possui todos os objetos de `//net`, e threads de sessão que bloqueiam uma requisição até que a thread de rede responda. |
| Chromium `//net`    | `URLRequestContext`, `URLRequest`, sessões HTTP/2 e QUIC, BoringSSL.                                                                                                        |

A IDL possui seis operações relevantes: criar uma engine a partir de opções `chave=valor`; iniciar uma requisição com seu cabeçalho (`MÉTODO URL` mais as linhas de cabeçalho) e o tamanho do corpo; escrever os blocos do corpo da requisição; aguardar o cabeçalho da resposta; ler o corpo decodificado; e destruir os handles. Um handle de requisição declara `@cancel`, de modo que uma chamada abandonada interrompe a requisição dentro do Chromium, em vez de deixá-la ser executada até o fim.

Isolar a pilha em um processo de trabalho é uma propriedade de segurança tanto quanto de empacotamento. O código de rede do Chromium interpreta entradas hostis provenientes da internet aberta. Se ele falhar, o seu serviço Go não falha junto: a chamada retorna um erro, a sessão é descartada, e o cliente inicia um novo processo de trabalho na próxima vez que precisar dele.

## Construído de Forma Reproduzível, Fixado por SHA-256

Um binário de 25 MB que contém o Chromium só é confiável se for possível afirmar exatamente o que entrou nele. O `chromenet` acompanha o Chromium Stable da mesma forma que o `gosuda.org/sqlite3` acompanha o SQLite: o código-fonte nunca é vendorizado, apenas fixado.

| Fixação                                                                                                                   | Onde                           |
| :------------------------------------------------------------------------------------------------------------------------ | :----------------------------- |
| Versão do Chromium e seu arquivo oficial de código-fonte `-lite`                                                          | `third_party/chromium/VERSION` |
| SHA-256 do arquivo e de cada ferramenta de build que a versão fixa: clang, Rust, GN, ninja, o runtime do clang para Windows, xwin | `third_party/chromium/SHA256`  |
| Versões do MSVC CRT e do Windows SDK obtidas via xwin                                                                     | `third_party/chromium/WINSDK`  |
| A versão do runtime de sandbox (`rungv`), o digest da imagem guest, a data do snapshot do Debian                          | `scripts/engine/BUILDENV`      |
| O runtime C do HQQ                                                                                                        | `go.mod` e `go.sum`            |

A fixação atual é o Chromium `157.0.8103.38`. Um download que não corresponda à sua fixação interrompe o build. A versão aplica exatamente um patch ao Chromium: no Linux, o `chrome_net_server` é compilado sem NSS e verifica certificados de servidor apenas contra o Chrome Root Store, de modo que o binário não precisa de nada além da libc.

As engines Linux e Windows são compiladas dentro de sandboxes [gvx](https://gosuda.org/blog/posts/gvx-cross-platform-linux-containers-z48916d01): processos não privilegiados com uma imagem guest fixada e sem daemon no host. As engines Windows são compiladas de forma cruzada no Linux, com o clang-cl e o lld-link do Chromium, contra o Windows SDK que o xwin obtém da Microsoft. As engines macOS são compiladas nativamente em um runner macOS, porque o build do Chromium para macOS exige o Xcode.

Recompilar o mesmo commit produziu binários idênticos byte a byte em execuções de CI separadas. Os binários de release são idênticos byte a byte aos que uma execução anterior de CI compilou para o commit que incorporou a engine ao monorepo, nas seis plataformas.

| Plataforma      | Tamanho do binário |
| :-------------- | -----------------: |
| `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 |

As engines Linux dependem apenas de `libc`, `libm`, `libdl`, `libpthread` e `libgcc_s`, e exigem glibc 2.31 ou mais recente.

### Acompanhando o Chromium e as Releases

Um workflow agendado verifica o Chromium Stable a cada seis horas. Para uma nova versão, ele reescreve as fixações, compila e testa as seis engines em uma branch `chromium/<versão>`, atesta os binários e, em um único commit, grava na `main` as fixações junto com os binários embutidos, seus digests e os textos de licença. No monorepo, arquivos `OWNERS` por arquivo concedem ao bot do workflow exatamente esses arquivos, de modo que a atualização é integrada sem intervenção humana; já as mudanças na própria receita de build (a fixação do Windows SDK e o patch) ainda exigem um responsável.

Cada binário de release traz uma atestação de proveniência de build emitida pelo workflow que o compilou:

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

Os textos de licença do que cada binário contém (Chromium, BoringSSL, QUICHE e o restante do grafo de build) são gerados pelo próprio `tools/licenses/licenses.py` do Chromium e distribuídos com a release como `NOTICE_<os>_<arch>.txt`.

## Limites

- `DialWebSocket` é um cliente RFC 6455 em Go puro e não usa a pilha do Chromium.
- `DialWebTransport` é uma implementação em processo. Ela não alcança um servidor.
- As impressões digitais padrão de JA4 e HTTP/2 são as da versão fixada do Chromium. O Chromium as altera de versão para versão, e o `chromenet` acompanha essas mudanças.
- Os binários macOS são assinados ad-hoc e não são notarizados.
- Um processo de trabalho consome memória. Cada binário da engine ocupa de 24 a 28 MB em disco, e o runtime do Chromium inicia um pool de threads e uma thread de rede.
- Uma sessão da engine é mantida até que o corpo da resposta seja fechado. Um programa que vaza corpos de resposta esgotará o pool.

## Fonte

O repositório é [github.com/gosuda/chromenet](https://github.com/gosuda/chromenet), espelhado de `//net/chromenet` no monorepo gosuda. A receita de build está em `scripts/engine`, a integração da engine em `engine/`, e a interface em `chrome_net.idl`.
