Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ jobs:
- name: Release contract regression tests
if: matrix.go == '1.27.x'
run: make release-recipe-check
- name: Browser runner diagnostic regression tests (offline)
if: matrix.go == '1.27.x'
run: |
python3 -m unittest scripts/stealth-browser/test_run_cover.py
python3 scripts/stealth-browser/run_cover.py --self-test
python3 scripts/stealth-campaign.py --self-test
- name: Test with coverage
run: go test -shuffle=on -count=1 -covermode=atomic -coverprofile=coverage.out ./...
- name: Upload coverage
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/bin/
/dist/
/artifacts/
/output/playwright/
/testdata/stealth/pcaps/
/coverage.out
*.prof
Expand Down
72 changes: 72 additions & 0 deletions scripts/browser-connection-fixture/DIAGNOSTIC-2026-09-22.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Chrome connection reuse: bounded local diagnostic

## Scope

This is a six-trial, macOS arm64 loopback diagnostic, not a formal corpus,
packet-fingerprint comparison, production benchmark or proof of
indistinguishability. No AutoCAR client/proxy traffic was generated. The normal
WebDriver workload and historical samples were not changed.

Before paired collection, Chrome readiness passed with H3 navigation and fetch.
Playwright Firefox 156.0 (build 1549) failed two separately recorded readiness
attempts with `NS_ERROR_CONNECTION_REFUSED`, first at the fixture's explicit
port and then with a default-port origin. Firefox was excluded **before** the
paired plan was frozen; it supplies no usable samples or conclusion here. Its
failure cause remains unresolved.

## Fixed procedure

- Google Chrome **153.0.8010.52**, headless, controlled by Playwright CLI
**0.1.21**, with a fresh isolated session for every trial.
- Same H3-only fixture, certificate, origin (`https://127.0.0.1:53223`) and
browser configuration throughout. No TCP/H2 fallback or cookies set.
- Chrome lab flags: `--enable-quic`, `--origin-to-force-quic-on=127.0.0.1:53223`,
the certificate-specific `--ignore-certificate-errors-spki-list`, and
`--no-proxy-server`; no global trust-store change.
- Navigate to `/` and await load, then make one immediate fetch to
`/probe?trial=<label>` with `cache: "no-store"`. Only `credentials` changes
within each pair. Labels identify trials; they are not random payload seeds.
- Fixed order: omit/same-origin, same-origin/omit, omit/same-origin. No retries.
- Accept only HTTP 200 for both requests, browser Resource Timing `h3` for both,
and matching server log, response header and JSON connection identifiers.
- Plan frozen before the first paired navigation; 540-second whole-run limit,
40-second trial limit. Six paired trials completed in **26.69 seconds**.

## Observations

IDs are allocated once per admitted physical QUIC connection, not per request.
All six trials passed the acceptance checks.

| Trial | Fetch credentials | Navigation ID | Fetch ID | Distinct connections |
| --- | --- | ---: | ---: | ---: |
| pair1_omit | omit | 1 | 2 | 2 |
| pair1_same_origin | same-origin | 3 | 3 | 1 |
| pair2_same_origin | same-origin | 4 | 4 | 1 |
| pair2_omit | omit | 5 | 6 | 2 |
| pair3_omit | omit | 7 | 8 | 2 |
| pair3_same_origin | same-origin | 9 | 9 | 1 |

In this environment, changing fetch credentials mode changed navigation/fetch
connection reuse in all three pairs. This demonstrates a measurement confound
worth controlling, not a reason to rewrite a frozen campaign or add dummy
connections to a proxy. It does not establish behavior in Firefox, historical
browser versions, remote Linux, or real-world browsing workloads.

## Local provenance

The isolated collection is retained locally under
`output/playwright/browser-connections-20260922/`, ignored by Git. It contains
the driver, frozen plan, CLI command outputs, server NDJSON, readiness report,
configuration and results. Raw local artifacts and lab keys are not published;
the hashes below identify those retained files, not independently hosted
evidence. Browser executable/framework and runtime hashes matched before and
after collection. Result status remains `insufficient_evidence`.

| Artifact | SHA-256 |
| --- | --- |
| Frozen plan | `2e2c198009c35ec1572560eb023a5aedbc024b05d8e25b6f280908c1286975de` |
| Results | `9df847240cc8bfc87a6cb95802f32ad606d14e92c14a622e07a9a549b73a88c0` |
| Server request log | `20e92e868a5ba526a58234b266b9131fcd0120804f393d3e6ee57169a7114157` |
| Chrome framework | `f9a7642e684d186ccbce18204594171b4bc548276a7c9e546ef0e7ef97767ab3` |

This diagnostic deliberately does not change release evidence requirements.
59 changes: 59 additions & 0 deletions scripts/browser-connection-fixture/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Loopback HTTP/3 connection diagnostic

This small test origin assigns an ID to each admitted physical QUIC connection.
It helps inspect whether a browser navigation and subsequent fetch reuse a
connection. It is not an AutoCAR proxy, a formal corpus generator, or a
production traffic optimization.

An explicitly limited [Chrome diagnostic](DIAGNOSTIC-2026-09-22.md) records one
local six-trial run, including readiness failures and evidence boundaries.

## Run the local fixture

Provide an existing certificate whose SAN covers the chosen loopback address,
and its matching private key with the normal AutoCAR file permissions. The
fixture never generates or replaces credentials.

```sh
go run ./scripts/browser-connection-fixture \
--listen 127.0.0.1:0 \
--cert /absolute/path/lab.crt \
--key /absolute/path/lab.key \
--duration 180s --max-requests 64 --max-connections 32
```

Only the exact numeric addresses `127.0.0.1` and `[::1]` are allowed. Port zero
selects a free UDP port; the first JSON stdout record reports that address.
The server provides HTTPS over HTTP/3 only, with no TCP/H2 listener. Duration
must be positive and at most 600 seconds; request and lifetime admitted-connection
budgets are respectively 1–64 and 1–32. Excess requests receive 429 and excess
connections are closed. Duration expiry or interruption stops the listener.

In a fresh, dedicated browser profile configured for this lab's H3 origin and
certificate, navigate to `/`, then fetch `/probe?trial=s1-omit` using
`credentials: "omit"`. Repeat in another fresh profile with
`credentials: "same-origin"` and a different trial label. Labels accept 1–32
ASCII letters, digits, underscores or hyphens. Only body-free GET/HEAD requests
are accepted.

The response header `X-Diagnostic-Connection-ID`, `/probe` JSON and request
log's `connection_id` identify server-observed connections within this run.
Request logs contain only fixed fields and validated trial labels, not cookies,
authorization headers, arbitrary URLs or peer addresses. Do not infer browser
H3 from an exit code alone: verify navigation and fetch Resource Timing report
`h3`, and retain browser version, launch settings and server logs. Merely opening
an HTTPS URL does not configure H3; use browser-version-appropriate lab settings
and certificate trust or a narrowly scoped lab pin.

## Separate from the WebDriver workload diagnostic

The [existing runner](../stealth-browser/README.md#optional-credentials-diagnostic)
uses a numeric RFC1918 origin, its fixed bootstrap document and a verified
`download_1k` payload. It rejects loopback and this fixture does not implement
that workload contract. Its optional credentials diagnostic has a distinct
`insufficient_evidence` receipt; the default formal workload and receipt remain
unchanged.

Keep this `/probe` experiment in its own artifact directory and label it as
calibration/diagnosis. A local browser/version result neither reproduces an
older frozen campaign automatically nor establishes passive indistinguishability.
Loading