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
4 changes: 3 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,14 @@ jobs:
- name: Release contract regression tests
if: matrix.go == '1.27.x'
run: make release-recipe-check
- name: Browser runner diagnostic regression tests (offline)
- name: Browser and pilot 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
python3 scripts/stealth-pilot-config.py self-test
sh -n scripts/stealth-pilot.sh
- name: Test with coverage
run: go test -shuffle=on -count=1 -covermode=atomic -coverprofile=coverage.out ./...
- name: Upload coverage
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ integration-netem: build

stealth-tools-check: release-recipe-check
$(GO) test -race ./scripts/stealth-probe
GOPROXY=off $(GO) test ./scripts/stealth-pilot
GOPROXY=off $(GO) test ./scripts/stealth-pilot ./scripts/stealth-build-info
sh -n scripts/stealth-active.sh scripts/stealth-hysteria.sh scripts/stealth-passive.sh scripts/stealth-campaign-smoke.sh scripts/stealth-pilot.sh scripts/stealth-offline-container.sh scripts/stealth-full-lab.sh scripts/stealth-full-offline.sh
sh -n scripts/check-stealth-hysteria-update-disabled.sh
./scripts/check-stealth-hysteria-update-disabled.sh
Expand Down
30 changes: 25 additions & 5 deletions docs/STEALTH-PILOT.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,12 @@ The default pilot records two independent samples for each product in three
- `parallel_20`: issue twenty concurrent 1 KiB requests through one client
process, allowing each proxy to multiplex streams over its H3 connection.

New AutoCAR runs use `--transport h3` and `--h3-fingerprint chrome-2026-10`,
with `github.com/apernet/quic-go` pinned to
`v0.63.1-0.20261004180939-a10df75c260c`.
New AutoCAR runs use `--transport h3` and `--h3-fingerprint chrome-2026-10`.
The web-H3 module identity remains `github.com/apernet/quic-go`, but its
effective source is the reviewed `github.com/cppla/quic-go` replacement; the
original `require` version alone does not identify the code being tested.
Native transport and the standard H3 control still use official QUIC.
See [dependency maintenance](DEPENDENCY-MAINTENANCE.md) for the current pins.
The baseline runs the pinned standard profile with Chrome QUIC parroting enabled,
its `Gecko` mode disabled, and a real reverse-proxy masquerade. Both proxies fetch the same
private deterministic HTTP origin. The H3 control fetches the equivalent
Expand All @@ -55,8 +58,25 @@ The baseline stays frozen; it no longer shares AutoCAR's exact QUIC revision.
New AutoCAR variant labels and effective descriptors record the current profile.
Do not rewrite old preregistrations or result labels: earlier captures remain
evidence only for their recorded source, dependency and profile. This update
does not enable TLS resumption or 0-RTT for the Chrome H3 client and does not
establish a passive-identification advantage.
does not opt this pilot into TLS resumption or 0-RTT and does not establish a
passive-identification advantage. Each workload starts a fresh client process;
this pilot does not exercise the separate `chrome-2026-10-resume` reconnect
mode or a real browser's warm connection.

Before any server starts, the runner reads the two compiled Go binaries with
the offline `scripts/stealth-build-info` helper; it never executes a binary to
discover its dependencies. The retained `build-provenance.json` records their
SHA-256 hashes, compiler/target, command package, and requested versus effective
sources/checksums for the selected QUIC and uTLS modules. It omits arbitrary
build settings, linker arguments, local paths and unrelated dependencies.
The configuration generator validates this report against both binary hashes
and embeds it in the effective AutoCAR descriptor, whose checksum is frozen by
the campaign driver. Missing metadata, local replacements for those selected
modules, mismatched targets, unexpected fields or missing required modules fail
closed. The report describes embedded build metadata, not a signed build
attestation or a vulnerability scan. Hashes identify the binaries; metadata
alone does not prove source contents or describe build tags and experiments.
Historical descriptors and frozen results are not rewritten.

## Running

Expand Down
33 changes: 21 additions & 12 deletions scripts/check-dependency-boundary.sh
Original file line number Diff line number Diff line change
Expand Up @@ -138,10 +138,6 @@ if [[ -n ${local_replacements} ]]; then
fi

while IFS= read -r -d '' source_file; do
if grep -qE '["`]github\.com/cppla/(utls|quic-go)(/[^"`[:space:]]*)?["`]' "${source_file}"; then
echo "error: ${source_file#./} imports the replacement path directly; retain the original module import path" >&2
status=1
fi
if imports=$(grep -nE "${FORBIDDEN_HYSTERIA_GO_PATTERN}" "${source_file}"); then
echo "error: Go source references the prohibited Hysteria application module:" >&2
while IFS= read -r match; do
Expand All @@ -159,12 +155,16 @@ done < <(find . -type f -name '*.go' ! -path './.git/*' -print0)

# Go permits escaped interpreted-string import paths. Require their canonical
# spelling too, so literal path checks cannot miss a second module identity.
# Tokenize only enough to distinguish import declarations from comments,
# ordinary strings and rune literals. This stays offline and does not run Go.
escaped_imports=$(
python3 - <<'PY'
# Check replacement identities only inside actual import declarations: metadata
# strings may name a source without importing it. The separate application-module
# whole-source prohibition above remains unchanged. This stays offline.
invalid_imports=$(
python3 - "${ALLOWED_UTLS_REPLACEMENT}" "${ALLOWED_WEB_QUIC_REPLACEMENT}" <<'PY'
from pathlib import Path
import re
import sys

replacement_paths = sys.argv[1:]

tokens = re.compile(
r'//[^\n]*|/\*[\s\S]*?\*/|"(?:\\[\s\S]|[^"\\])*"|'
Expand All @@ -175,7 +175,10 @@ tokens = re.compile(
for path in Path(".").rglob("*.go"):
if ".git" in path.parts or not path.is_file():
continue
source = path.read_text(encoding="utf-8")
# Preserve CR bytes so raw-string interpretation matches Go, not Python's
# universal-newline conversion to LF.
with path.open(encoding="utf-8", newline="") as handle:
source = handle.read()
importing = grouped = False
for match in tokens.finditer(source):
token = match.group()
Expand All @@ -191,14 +194,20 @@ for path in Path(".").rglob("*.go"):
if token.startswith('"') and "\\" in token:
line = source.count("\n", 0, match.start()) + 1
print(f"{path}:{line}: escaped import path")
else:
# Go discards carriage returns inside raw string literals.
imported = token[1:-1].replace("\r", "") if token.startswith(chr(96)) else token[1:-1]
if any(imported == name or imported.startswith(name + "/") for name in replacement_paths):
line = source.count("\n", 0, match.start()) + 1
print(f"{path}:{line}: imports the replacement path directly; retain the original module import path")
importing = grouped
elif token == ")" or (token == ";" and not grouped):
importing = False
PY
)
if [[ -n ${escaped_imports} ]]; then
echo "error: Go import paths must use unescaped canonical spelling:" >&2
printf '%s\n' "${escaped_imports}" >&2
if [[ -n ${invalid_imports} ]]; then
echo "error: Go imports must retain original module identities and unescaped canonical spelling:" >&2
printf '%s\n' "${invalid_imports}" >&2
status=1
fi

Expand Down
Loading
Loading