Pin Web oracle proxy sessions per batch

This commit is contained in:
Victor Kuznetsov committed 2026-09-16 16:17:36 -07:00
1 parent 804735aa48
commit 37431c9aeb
7 files changed
+97 -18

No files matched your search

+4
View File
@@ -47,6 +47,10 @@ real browser. Proxy credentials are read only through a slot's named environment
and never enter a manifest or log. Dotenv interpolation may derive routed proxy URLs from
one secret key without mutating the process environment. A selected proxy context may
accept that proxy's TLS interception certificate; direct contexts keep normal TLS checks.
ThorData rotating endpoints are pinned to one manifest-derived sticky session for the
life of a Web batch so one browser context does not change exit identity between page
requests. This is route continuity, not account or IP failover; a refusal still ends on
the selected slot and never selects another route automatically.
Upload readiness is surface-specific: OpenAI reaches it at page load but keeps background
requests alive, while Microsoft and Meta require network idle. Preserve exact provider
errors as indeterminate evidence rather than retrying or interpreting them as clean.
+8
View File
@@ -88,6 +88,14 @@ OpenAI, Microsoft, and Meta Web slots may each name one of them through
verify egress before relying on it, and never automatically change route after
a refusal. Bound a batch before starting.
ThorData user-and-password endpoints without `sessid` rotate the exit on each
request, which breaks a browser page whose resources must share one network
identity. The runner therefore derives one sticky `sessid` from the immutable
manifest hash and keeps it for that batch. A new prepared batch gets a new
session; rerunning or resuming the same batch keeps its session while the proxy
retains it. An explicit `sessid` in the configured route is preserved. This is
session continuity, not permission to retry a refused batch on another route.
### OpenAI API
Use `check-openai` with one explicit upload acknowledgement. Keep named API keys
+1
View File
@@ -34,6 +34,7 @@
# ThorData residential proxies (optional; only for explicitly selected Web oracle routes)
# Generate these URLs in the ThorData endpoint generator; do not commit credentials.
# The Web runner pins a rotating user-and-password route to one sticky session per manifest.
# THORDATA_ROUTE_US=http://td-customer-USERNAME-country-US:PASSWORD@HOST.pr.thordata.net:9999
# THORDATA_ROUTE_DE=http://td-customer-USERNAME-country-DE:PASSWORD@HOST.pr.thordata.net:9999
# THORDATA_ROUTE_GB=http://td-customer-USERNAME-country-GB:PASSWORD@HOST.pr.thordata.net:9999
+21
View File
@@ -386,6 +386,16 @@ navigation before each upload, does not retry, and stores the settled page text.
Each completed verdict is persisted immediately with its own observation
timestamp. A later navigation or upload failure preserves those completed
rows and leaves unobserved rows unresolved.
ThorData [user-and-password endpoints without a `sessid` rotate the exit on each
request](https://doc.thordata.com/doc/proxies/residential-proxies/session-control).
That is unsuitable inside one browser context: page resources can
arrive through different exits and trigger the provider's bot checks. For a
ThorData hostname, the runner derives a non-secret sticky session id from the
immutable manifest hash and appends a 90-minute session window when the
configured username has no explicit session. The same batch therefore keeps
one route while the provider retains it, a new prepared batch gets a distinct
route, and an operator-supplied `sessid` remains unchanged. This pins a selected
route; it does not retry or fail over after a refusal.
For a selected proxy route it enables certificate handling in that isolated
Chromium launch and context; direct launches keep Playwright's normal TLS
verification. A 2026-09-07 preflight sent one read-only request through each
@@ -423,6 +433,17 @@ for the same processed source hash. Keep each route in its own immutable batch;
do not splice an isolated-runner failure and a first-party browser or API verdict into
one response.
On 2026-09-16, a rotating ThorData browser batch demonstrated the failure mode:
one upload reached OpenAI's generic error, four encountered Cloudflare, and ten
became unreachable after the browser context closed. Five fresh connections to
the same rotating endpoint had five distinct exits, while five connections with
one sticky session had one exit. After session pinning, one direct route and
five independently prepared country routes all reached the upload handler with
the same prepared test image, but every route returned OpenAI's exact generic
error. Record those responses as `indeterminate`. The result distinguishes the
fixed session-continuity defect from the remaining anonymous-Web availability
problem; further route changes are not a substitute for an API verdict.
## OpenAI API
The one automated path calls the hardened metadata-independent adapter already
+16 -3
View File
@@ -5,6 +5,7 @@ from __future__ import annotations
import logging
import os
import re
from dataclasses import dataclass
from datetime import UTC, datetime
from time import monotonic
@@ -125,8 +126,10 @@ def parse_provider_result(surface: str, page_text: str) -> WebVerdict:
def proxy_settings(
slot: oracles.ExecutionSlot,
environ: Mapping[str, str] = os.environ,
*,
sticky_session_id: str | None = None,
) -> ProxySettings | None:
"""Resolve a secret proxy URL without putting it in a manifest or log."""
"""Resolve a secret proxy URL and pin rotating ThorData routes per batch."""
variable = slot.get("proxy_url_env")
if variable is None:
return None
@@ -142,7 +145,13 @@ def proxy_settings(
server = f"{server}:{parsed.port}"
settings: ProxySettings = {"server": server}
if parsed.username is not None:
settings["username"] = unquote(parsed.username)
username = unquote(parsed.username)
is_thordata = parsed.hostname.endswith((".thordata.net", ".thordata.online"))
if is_thordata and sticky_session_id is not None and "-sessid-" not in username.casefold():
if re.fullmatch(r"[A-Za-z0-9]{1,32}", sticky_session_id) is None:
raise ValueError("sticky_session_id must be 1-32 ASCII letters or digits")
username = f"{username}-sessid-{sticky_session_id}-sesstime-90"
settings["username"] = username
if parsed.password is not None:
settings["password"] = unquote(parsed.password)
return settings
@@ -287,7 +296,11 @@ def run_web_batch(
if slot is None:
raise ValueError("Playwright batches require an explicit execution slot")
secret_values = dict(environ) if env_file is None else oracles.secret_environment(env_file, environ=environ)
proxy = proxy_settings(slot, secret_values)
proxy = proxy_settings(
slot,
secret_values,
sticky_session_id=f"oracle{results['manifest_sha256'][:16]}",
)
pending = [
(manifest_row, result_row)
for manifest_row, result_row in zip(manifest["rows"], results["rows"], strict=True)
+5 -2
View File
@@ -80,11 +80,14 @@ def test_web_results_survive_later_browser_failure(monkeypatch, tmp_path):
"slot": {},
"rows": [{"artifact_id": "first", "upload_path": "one"}, {"artifact_id": "second", "upload_path": "two"}],
}
results = {"rows": [{"watermark_result": None}, {"watermark_result": None}]}
results = {
"manifest_sha256": "0" * 64,
"rows": [{"watermark_result": None}, {"watermark_result": None}],
}
saved = []
monkeypatch.setattr(web.oracles, "verify_batch", lambda *a, **k: {})
monkeypatch.setattr(web.oracles, "load_batch", lambda *a: (manifest, results))
monkeypatch.setattr(web, "proxy_settings", lambda *a: None)
monkeypatch.setattr(web, "proxy_settings", lambda *a, **k: None)
monkeypatch.setattr(web.oracles, "record_result", lambda *a, **k: saved.append(k))
def submit(*args, **kwargs):
+42 -13
View File
@@ -13,6 +13,18 @@ if TYPE_CHECKING:
from pathlib import Path
def _thordata_slot() -> oracles.ExecutionSlot:
return {
"name": "openai-thordata-us",
"surface": "openai-web",
"account_label": None,
"browser_profile": None,
"google_account_index": None,
"network_label": "thordata-us-residential",
"proxy_url_env": "THORDATA_ROUTE_US",
}
@pytest.mark.parametrize(
("surface", "page_text", "watermark", "provenance"),
[
@@ -171,6 +183,31 @@ def test_proxy_settings_are_loaded_from_an_environment_reference() -> None:
}
def test_thordata_proxy_settings_pin_one_exit_for_the_browser_session() -> None:
proxy = web.proxy_settings(
_thordata_slot(),
{"THORDATA_ROUTE_US": "http://route:secret@gateway.pr.thordata.net:9999"},
sticky_session_id="oracle0123456789abcdef",
)
assert proxy == {
"server": "http://gateway.pr.thordata.net:9999",
"username": "route-sessid-oracle0123456789abcdef-sesstime-90",
"password": "secret",
}
def test_thordata_proxy_settings_preserve_an_explicit_session() -> None:
proxy = web.proxy_settings(
_thordata_slot(),
{"THORDATA_ROUTE_US": ("http://route-sessid-manual-sesstime-15:secret@gateway.pr.thordata.net:9999")},
sticky_session_id="oracle0123456789abcdef",
)
assert proxy is not None
assert proxy["username"] == "route-sessid-manual-sesstime-15"
def test_proxy_settings_do_not_treat_a_missing_secret_as_direct_access() -> None:
slot: oracles.ExecutionSlot = {
"name": "openai-west",
@@ -290,27 +327,18 @@ def test_run_web_batch_loads_the_selected_proxy_from_dotenv(
tmp_path: Path,
tmp_clean_png: Path,
) -> None:
slot: oracles.ExecutionSlot = {
"name": "openai-thordata-us",
"surface": "openai-web",
"account_label": None,
"browser_profile": None,
"google_account_index": None,
"network_label": "thordata-us-residential",
"proxy_url_env": "THORDATA_ROUTE_US",
}
manifest_path = oracles.prepare_batch(
"openai-web",
[tmp_clean_png],
output_dir=tmp_path / "oracle-batch",
repository_root=oracles.REPOSITORY_ROOT,
slot=slot,
slot=_thordata_slot(),
)
env_path = tmp_path / ".env"
env_path.write_text(
"THORDATA_PASSWORD=test-thordata-password\n"
"THORDATA_ROUTE_US=http://td-customer-user-country-US:${THORDATA_PASSWORD}"
"@proxy.thordata.test:9999\n",
"@proxy.pr.thordata.net:9999\n",
encoding="utf-8",
)
proxies: list[dict[str, str] | None] = []
@@ -339,10 +367,11 @@ def test_run_web_batch_loads_the_selected_proxy_from_dotenv(
)
assert report["watermark_results"] == {"not_detected": 1}
manifest_sha256 = json.loads((manifest_path.parent / "results.json").read_text(encoding="utf-8"))["manifest_sha256"]
assert proxies == [
{
"server": "http://proxy.thordata.test:9999",
"username": "td-customer-user-country-US",
"server": "http://proxy.pr.thordata.net:9999",
"username": (f"td-customer-user-country-US-sessid-oracle{manifest_sha256[:16]}-sesstime-90"),
"password": "test-thordata-password",
}
]