From becd8137f5a48eadd89eb7cde0aa4f2b68300b57 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 19:43:56 +0900 Subject: [PATCH 01/13] test(mv3): reject raw WebDriver error retention --- tests/test_mv3_compatibility_contract.py | 58 ++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/tests/test_mv3_compatibility_contract.py b/tests/test_mv3_compatibility_contract.py index 10872ddac..603f1edcd 100644 --- a/tests/test_mv3_compatibility_contract.py +++ b/tests/test_mv3_compatibility_contract.py @@ -6,6 +6,7 @@ import pathlib import runpy import unittest +import unittest.mock ROOT = pathlib.Path(__file__).resolve().parents[1] FIXTURE = ROOT / "tests" / "fixtures" / "mv3_basic" @@ -162,6 +163,63 @@ def test_runner_reports_repeated_trial_pass_rate(self) -> None: with self.subTest(expected=expected): self.assertIn(expected, runner) + def test_webdriver_errors_do_not_retain_raw_response_payloads(self) -> None: + """WebDriver failures must not copy browser-controlled values into evidence logs.""" + + namespace = runpy.run_path(str(RUNNER), run_name="mv3_contract") + json_request = namespace["_json_request"] + http_module = namespace["http"] + + class FakeResponse: + def __init__(self, status: int, body: bytes) -> None: + self.status = status + self.body = body + + def read(self, _limit: int) -> bytes: + return self.body + + class FakeConnection: + def __init__(self, response: FakeResponse) -> None: + self.response = response + + def request(self, *_args: object, **_kwargs: object) -> None: + return None + + def getresponse(self) -> FakeResponse: + return self.response + + def close(self) -> None: + return None + + raw_secret = "secret-token /home/runner/private https://example.invalid" + cases = ( + FakeResponse(500, raw_secret.encode("utf-8")), + FakeResponse( + 200, + json.dumps( + { + "value": { + "error": "unknown error", + "message": raw_secret, + } + } + ).encode("utf-8"), + ), + ) + for response in cases: + with self.subTest(status=response.status): + with unittest.mock.patch.object( + http_module.client, + "HTTPConnection", + return_value=FakeConnection(response), + ): + with self.assertRaises(RuntimeError) as raised: + json_request(9515, "GET", "/status") + rendered = str(raised.exception) + self.assertNotIn("secret-token", rendered) + self.assertNotIn("/home/runner/private", rendered) + self.assertNotIn("example.invalid", rendered) + def test_workflow_runs_the_real_browser_lane_without_model_credentials(self) -> None: """Compatibility evidence must execute Chromium and never require LLM secrets.""" From a446017f6c1673e56e044f7c9af3c97e307bae9c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 19:46:41 +0900 Subject: [PATCH 02/13] fix(mv3): redact WebDriver protocol error payloads --- scripts/ci/run_mv3_compatibility.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/scripts/ci/run_mv3_compatibility.py b/scripts/ci/run_mv3_compatibility.py index 28a3fb1e2..c13f59ee1 100755 --- a/scripts/ci/run_mv3_compatibility.py +++ b/scripts/ci/run_mv3_compatibility.py @@ -92,7 +92,7 @@ def _json_request( if method not in {"GET", "POST", "DELETE"}: raise ValueError("unsupported ChromeDriver method") if not path.startswith("/") or "://" in path or any(char in path for char in "\r\n"): - raise ValueError("invalid ChromeDriver path") + raise ValueError("invalid WebDriver path") body = None if payload is None else json.dumps(payload).encode("utf-8") connection = http.client.HTTPConnection("127.0.0.1", driver_port, timeout=timeout) @@ -108,8 +108,7 @@ def _json_request( if len(raw) > MAX_WEBDRIVER_RESPONSE_BYTES: raise RuntimeError("WebDriver response exceeded the bounded JSON limit") if response.status >= 400: - detail = raw.decode("utf-8", errors="replace") - raise RuntimeError(f"WebDriver HTTP {response.status}: {detail}") + raise RuntimeError(f"WebDriver HTTP {response.status} error") finally: connection.close() @@ -118,7 +117,7 @@ def _json_request( raise RuntimeError("WebDriver returned a non-object JSON payload") value = decoded.get("value") if isinstance(value, dict) and value.get("error"): - raise RuntimeError(f"WebDriver error: {value.get('error')}: {value.get('message')}") + raise RuntimeError("WebDriver returned a protocol error") return decoded From 104db69626ffe5dfd6eda2449c9ebb252b8170a7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 19:52:13 +0900 Subject: [PATCH 03/13] fix(mv3): preserve ChromeDriver path diagnostic --- scripts/ci/run_mv3_compatibility.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/ci/run_mv3_compatibility.py b/scripts/ci/run_mv3_compatibility.py index c13f59ee1..d2e9c3e7d 100755 --- a/scripts/ci/run_mv3_compatibility.py +++ b/scripts/ci/run_mv3_compatibility.py @@ -92,7 +92,7 @@ def _json_request( if method not in {"GET", "POST", "DELETE"}: raise ValueError("unsupported ChromeDriver method") if not path.startswith("/") or "://" in path or any(char in path for char in "\r\n"): - raise ValueError("invalid WebDriver path") + raise ValueError("invalid ChromeDriver path") body = None if payload is None else json.dumps(payload).encode("utf-8") connection = http.client.HTTPConnection("127.0.0.1", driver_port, timeout=timeout) From 08cc9a7fe7bb52529fe4e773ab2aa927c5c5b173 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 20:09:40 +0900 Subject: [PATCH 04/13] docs(mv3): trace WebDriver diagnostic redaction boundary --- docs/doctoring/mv3-compatibility.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/doctoring/mv3-compatibility.md b/docs/doctoring/mv3-compatibility.md index 571c49329..b6601600c 100644 --- a/docs/doctoring/mv3-compatibility.md +++ b/docs/doctoring/mv3-compatibility.md @@ -46,6 +46,10 @@ Restart persistence and extension update migration are separate compatibility cl Content-script injection and content-script JavaScript isolation are separate compatibility claims. Active PR #61 writes `window.originweaveWorldSentinel = "page"` in the fixture page's main world and repeatedly publishes that value through one controlled DOM attribute. The content script assigns the same global name to `"extension"` in its own execution world, waits a bounded interval, and only reports the existing compatibility surface ready when it simultaneously observes the page's published `page` value and its own `extension` value. If both scripts share one JavaScript global namespace, the page publisher changes to `extension` and real-browser compatibility fails. DOM sharing here is deliberate test evidence, not permission for arbitrary page content to become trusted instruction or Agent authority. +## WebDriver diagnostic trust boundary + +The compatibility runner treats remote-end WebDriver diagnostics as untrusted evidence input. The W3C WebDriver 2 July 2026 Working Draft defines remote errors with a standardized error code plus implementation-defined human-readable `message` and `stacktrace` fields and optional implementation-defined `data`; the specification's user-prompt example demonstrates that page-originated prompt text can appear in error data. OriginWeave therefore retains only bounded local classifications needed to diagnose the harness, such as the HTTP status or a fixed protocol-error category, and does not copy raw remote response bodies, messages, stack traces, or data into CI/audit exceptions. This is an evidence-provenance and diagnostic-redaction boundary, not browser policy authority and not a claim that WebDriver errors authenticate the browser or page. + ## Supply-chain and repeatability evidence The CI lane downloads the exact Chrome/ChromeDriver version from the official Chrome for Testing public bucket, records SHA-256 receipts for the downloaded archives, verifies the runtime-reported browser version, and emits bounded JSON compatibility evidence. A future release-quality matrix should additionally pin published artifact digests or equivalent immutable supply-chain identity when the upstream distribution exposes that identity in an authoritative machine-readable form. @@ -67,3 +71,5 @@ Chrome for Developers. (n.d.). *Manifest file format*. Google. Retrieved August Bynens, M. (2023, June 12). *Chrome for Testing*. Chrome for Developers. https://developer.chrome.com/docs/automation-and-testing/chrome-for-testing Google Chrome Labs. (2026, July 21). *Chrome for Testing availability*. https://googlechromelabs.github.io/chrome-for-testing/ + +World Wide Web Consortium. (2026, July 2). *WebDriver* (Working Draft). https://www.w3.org/TR/2026/WD-webdriver2-20260702/ From 107d96eb1b484730cef3e45f0df6c297c486aaaa Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 20:11:24 +0900 Subject: [PATCH 05/13] docs(changelog): record WebDriver diagnostic redaction --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f747adeae..6c78a71ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,6 +70,7 @@ All notable changes to OriginWeave are documented in this file. The format follo ### Security +- Redacted raw WebDriver HTTP response bodies and protocol diagnostics from the pinned-Chromium MV3 evidence runner while retaining bounded HTTP-status and fixed protocol-error classification, preventing browser-controlled diagnostic text from entering CI/audit exceptions. - Explicit proxy server identifiers require ASCII decimal port tokens before numeric range parsing, preventing Rust-specific leading-plus spellings from widening proxy authority. - Raw page content cannot become a trusted instruction. - Raw secrets are rejected and secret-capable actions require an opaque broker handle. From ca9386a9433eca62806c45e6a87485ea6a64c6b3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 21:08:03 +0900 Subject: [PATCH 06/13] test(mv3): redact page-controlled evidence diagnostics --- ...mv3_fixture_evidence_redaction_contract.py | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 tests/test_mv3_fixture_evidence_redaction_contract.py diff --git a/tests/test_mv3_fixture_evidence_redaction_contract.py b/tests/test_mv3_fixture_evidence_redaction_contract.py new file mode 100644 index 000000000..333d85b76 --- /dev/null +++ b/tests/test_mv3_fixture_evidence_redaction_contract.py @@ -0,0 +1,94 @@ +"""Regression contracts for MV3 evidence diagnostic redaction.""" + +from __future__ import annotations + +import pathlib +import runpy +import unittest +import unittest.mock + +ROOT = pathlib.Path(__file__).resolve().parents[1] +RUNNER = ROOT / "scripts" / "ci" / "run_mv3_compatibility.py" +SENSITIVE_MARKERS = ( + "secret-token", + "/home/runner/private", + "example.invalid", +) + + +class ManifestV3FixtureEvidenceRedactionTests(unittest.TestCase): + """Prevent page-controlled fixture values from entering CI diagnostics.""" + + def assert_redacted(self, rendered: str) -> None: + """Require representative token, path, and URL material to be absent.""" + + for marker in SENSITIVE_MARKERS: + with self.subTest(marker=marker): + self.assertNotIn(marker, rendered) + + def test_fixture_timeout_does_not_echo_page_controlled_values(self) -> None: + """A non-converging fixture must report local classifications, not DOM values.""" + + namespace = runpy.run_path(str(RUNNER), run_name="mv3_fixture_redaction_contract") + wait_for_extension_evidence = namespace["_wait_for_extension_evidence"] + sensitive = "secret-token /home/runner/private https://example.invalid" + hostile = { + "content": sensitive, + "storage": sensitive, + "storagePersistence": sensitive, + "workerReply": sensitive, + "workerState": sensitive, + "workerStartCount": "0", + "dnr": sensitive, + "tabs": sensitive, + "windows": sensitive, + "scripting": sensitive, + "scriptingExecuted": sensitive, + "commands": sensitive, + "sidePanel": sensitive, + "bookmarks": sensitive, + "history": sensitive, + } + + with unittest.mock.patch.dict( + wait_for_extension_evidence.__globals__, + { + "_execute": unittest.mock.Mock(return_value=hostile), + "FIXTURE_TIMEOUT_SECONDS": 1.0, + }, + ), unittest.mock.patch.object( + namespace["time"], + "monotonic", + side_effect=(0.0, 0.0, 2.0), + ), unittest.mock.patch.object(namespace["time"], "sleep", return_value=None): + with self.assertRaises(RuntimeError) as raised: + wait_for_extension_evidence(9515, "session", "initialized") + + self.assert_redacted(str(raised.exception)) + + def test_click_mismatch_does_not_echo_page_controlled_text(self) -> None: + """A click post-condition mismatch must not retain returned DOM text.""" + + namespace = runpy.run_path(str(RUNNER), run_name="mv3_click_redaction_contract") + exercise_real_click = namespace["_exercise_real_click"] + element_key = namespace["W3C_ELEMENT_KEY"] + sensitive = "secret-token /home/runner/private https://example.invalid" + responses = ( + {"value": {element_key: "element.one"}}, + {}, + {"value": {element_key: "element.two"}}, + {"value": sensitive}, + ) + + with unittest.mock.patch.dict( + exercise_real_click.__globals__, + {"_json_request": unittest.mock.Mock(side_effect=responses)}, + ): + with self.assertRaises(RuntimeError) as raised: + exercise_real_click(9515, "session") + + self.assert_redacted(str(raised.exception)) + + +if __name__ == "__main__": + unittest.main() From 3ed5d7e8cf77547c96feff2cfb24c46d74a73ebb Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 21:09:42 +0900 Subject: [PATCH 07/13] fix(mv3): redact page-controlled evidence diagnostics --- scripts/ci/run_mv3_compatibility.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/scripts/ci/run_mv3_compatibility.py b/scripts/ci/run_mv3_compatibility.py index d2e9c3e7d..7e615a963 100755 --- a/scripts/ci/run_mv3_compatibility.py +++ b/scripts/ci/run_mv3_compatibility.py @@ -198,6 +198,7 @@ def _wait_for_extension_evidence( } deadline = time.monotonic() + FIXTURE_TIMEOUT_SECONDS latest: dict[str, str] = {} + latest_worker_start_count = 0 while time.monotonic() < deadline: value = _execute(driver_port, session_id, script) if isinstance(value, dict): @@ -206,13 +207,20 @@ def _wait_for_extension_evidence( worker_start_count = int(latest.get("workerStartCount", "0")) except ValueError: worker_start_count = 0 + latest_worker_start_count = worker_start_count if worker_start_count > 0 and all( latest.get(key) == item for key, item in expected.items() ): return latest time.sleep(0.1) + mismatched_surfaces = [ + key for key, item in expected.items() if latest.get(key) != item + ] + if latest_worker_start_count <= 0: + mismatched_surfaces.append("workerStartCount") raise RuntimeError( - f"MV3 fixture did not converge: expected={expected!r}, observed={latest!r}" + "MV3 fixture did not converge; mismatched surfaces=" + + ",".join(mismatched_surfaces) ) @@ -252,7 +260,7 @@ def _exercise_real_click(driver_port: int, session_id: str) -> str: _webdriver_path(session_id, f"/element/{safe_output}/text"), ).get("value") if text != "clicked": - raise RuntimeError(f"real click post-condition failed: {text!r}") + raise RuntimeError("real click post-condition failed") return str(text) From 8ab0c84643a4c761f873e193d65e39efd6a9fc28 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 06:03:19 +0900 Subject: [PATCH 08/13] test(mv3): cover remaining remote diagnostic echoes --- ...mv3_fixture_evidence_redaction_contract.py | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/tests/test_mv3_fixture_evidence_redaction_contract.py b/tests/test_mv3_fixture_evidence_redaction_contract.py index 333d85b76..20986406a 100644 --- a/tests/test_mv3_fixture_evidence_redaction_contract.py +++ b/tests/test_mv3_fixture_evidence_redaction_contract.py @@ -89,6 +89,88 @@ def test_click_mismatch_does_not_echo_page_controlled_text(self) -> None: self.assert_redacted(str(raised.exception)) + def test_driver_startup_timeout_does_not_echo_remote_error_details(self) -> None: + """Startup timeout evidence must classify failure without retaining exception text.""" + + namespace = runpy.run_path(str(RUNNER), run_name="mv3_driver_startup_redaction_contract") + wait_for_driver = namespace["_wait_for_driver"] + sensitive = "secret-token /home/runner/private https://example.invalid" + + with unittest.mock.patch.dict( + wait_for_driver.__globals__, + { + "_json_request": unittest.mock.Mock(side_effect=RuntimeError(sensitive)), + "STARTUP_TIMEOUT_SECONDS": 1.0, + }, + ), unittest.mock.patch.object( + namespace["time"], + "monotonic", + side_effect=(0.0, 0.0, 2.0), + ), unittest.mock.patch.object(namespace["time"], "sleep", return_value=None): + with self.assertRaises(RuntimeError) as raised: + wait_for_driver(9515) + + self.assert_redacted(str(raised.exception)) + + def test_browser_version_mismatch_does_not_echo_remote_capability_value(self) -> None: + """A pin mismatch must not copy the WebDriver capability value into evidence.""" + + namespace = runpy.run_path(str(RUNNER), run_name="mv3_browser_version_redaction_contract") + run_browser_pass = namespace["_run_browser_pass"] + sensitive = "secret-token /home/runner/private https://example.invalid" + + class FakeDriver: + def terminate(self) -> None: + return None + + def wait(self, timeout: float) -> int: + _ = timeout + return 0 + + def kill(self) -> None: + return None + + def fake_json_request( + _driver_port: int, + method: str, + path: str, + _payload: object = None, + **_kwargs: object, + ) -> dict[str, object]: + if method == "POST" and path == "/session": + return { + "value": { + "sessionId": "session.one", + "capabilities": {"browserVersion": sensitive}, + } + } + if method == "DELETE": + return {"value": None} + raise AssertionError(f"unexpected request: {method} {path}") + + with unittest.mock.patch.dict( + run_browser_pass.__globals__, + { + "_free_loopback_port": unittest.mock.Mock(return_value=9515), + "_wait_for_driver": unittest.mock.Mock(return_value=None), + "_json_request": fake_json_request, + }, + ), unittest.mock.patch.object( + namespace["subprocess"], + "Popen", + return_value=FakeDriver(), + ): + with self.assertRaises(RuntimeError) as raised: + run_browser_pass( + pathlib.Path("/controlled/chrome"), + pathlib.Path("/controlled/chromedriver"), + "http://127.0.0.1:9516/page.html", + "/controlled/profile", + "initialized", + ) + + self.assert_redacted(str(raised.exception)) + if __name__ == "__main__": unittest.main() From 967990b6810f1a0aec2e9b5cba597e8456d5744f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 06:04:32 +0900 Subject: [PATCH 09/13] fix(mv3): redact startup and capability diagnostics --- scripts/ci/run_mv3_compatibility.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/scripts/ci/run_mv3_compatibility.py b/scripts/ci/run_mv3_compatibility.py index 7e615a963..df48df90d 100755 --- a/scripts/ci/run_mv3_compatibility.py +++ b/scripts/ci/run_mv3_compatibility.py @@ -125,16 +125,15 @@ def _wait_for_driver(driver_port: int) -> None: """Wait for the exact local ChromeDriver process to become ready.""" deadline = time.monotonic() + STARTUP_TIMEOUT_SECONDS - last_error: Exception | None = None while time.monotonic() < deadline: try: status = _json_request(driver_port, "GET", "/status", timeout=1.0) if status.get("value", {}).get("ready") is True: return - except (OSError, ValueError, RuntimeError, json.JSONDecodeError) as exc: - last_error = exc + except (OSError, ValueError, RuntimeError, json.JSONDecodeError): + pass time.sleep(0.1) - raise RuntimeError(f"ChromeDriver did not become ready: {last_error}") + raise RuntimeError("ChromeDriver did not become ready") def _execute(driver_port: int, session_id: str, script: str) -> Any: @@ -322,8 +321,7 @@ def _run_browser_pass( ) if browser_version != PINNED_CHROME_VERSION: raise RuntimeError( - f"unexpected Chrome version: expected {PINNED_CHROME_VERSION}, " - f"got {browser_version!r}" + f"ChromeDriver browser version did not match pinned {PINNED_CHROME_VERSION}" ) _json_request( @@ -534,4 +532,4 @@ def main() -> int: if __name__ == "__main__": - raise SystemExit(main()) + raise SystemExit(main()) \ No newline at end of file From e5f10800a3a486aaeae8a29a37c0a79cc59c6b7e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 06:05:16 +0900 Subject: [PATCH 10/13] docs(mv3): define complete diagnostic redaction boundary --- docs/doctoring/mv3-compatibility.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/doctoring/mv3-compatibility.md b/docs/doctoring/mv3-compatibility.md index b6601600c..2e7abe5d1 100644 --- a/docs/doctoring/mv3-compatibility.md +++ b/docs/doctoring/mv3-compatibility.md @@ -48,7 +48,7 @@ Content-script injection and content-script JavaScript isolation are separate co ## WebDriver diagnostic trust boundary -The compatibility runner treats remote-end WebDriver diagnostics as untrusted evidence input. The W3C WebDriver 2 July 2026 Working Draft defines remote errors with a standardized error code plus implementation-defined human-readable `message` and `stacktrace` fields and optional implementation-defined `data`; the specification's user-prompt example demonstrates that page-originated prompt text can appear in error data. OriginWeave therefore retains only bounded local classifications needed to diagnose the harness, such as the HTTP status or a fixed protocol-error category, and does not copy raw remote response bodies, messages, stack traces, or data into CI/audit exceptions. This is an evidence-provenance and diagnostic-redaction boundary, not browser policy authority and not a claim that WebDriver errors authenticate the browser or page. +The compatibility runner treats remote-end WebDriver diagnostics and returned capability values as untrusted evidence input. The W3C WebDriver 2 July 2026 Working Draft defines remote errors with a standardized error code plus implementation-defined human-readable `message` and `stacktrace` fields and optional implementation-defined `data`; the specification's user-prompt example demonstrates that page-originated prompt text can appear in error data. OriginWeave therefore retains only bounded locally owned classifications needed to diagnose the harness. HTTP failures retain only the numeric status; protocol failures use a fixed category; startup retry exceptions are not interpolated into the terminal timeout; and a browser-version mismatch records only the expected pinned version rather than the WebDriver-reported value. DOM fixture mismatches retain only fixed surface names, and click post-condition failures retain no returned page text. Raw remote response bodies, messages, stack traces, data, capability values, DOM values, and other browser-derived exception text do not enter CI/audit exceptions. This is an evidence-provenance and diagnostic-redaction boundary, not browser policy authority and not a claim that WebDriver errors or capabilities authenticate the browser or page. ## Supply-chain and repeatability evidence From e96c982b6ed7ff7b80891bd60efc5d443555d4b6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 06:07:14 +0900 Subject: [PATCH 11/13] docs(mv3): refresh diagnostic review date --- docs/doctoring/mv3-compatibility.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/doctoring/mv3-compatibility.md b/docs/doctoring/mv3-compatibility.md index 2e7abe5d1..5ee3b9c52 100644 --- a/docs/doctoring/mv3-compatibility.md +++ b/docs/doctoring/mv3-compatibility.md @@ -1,7 +1,7 @@ # Manifest V3 compatibility evidence baseline - **Status:** Active implementation evidence for issue #27 -- **Reviewed:** 2026-08-11 +- **Reviewed:** 2026-09-04 - **Pinned browser:** Chrome for Testing `150.0.7871.129`, Chromium revision `r1639810` OriginWeave uses Chromium as its compatibility kernel, so browser-extension compatibility must be demonstrated with executable Chromium evidence rather than inferred from architecture alone. The protected-main lane exercises a controlled unpacked Manifest V3 extension against one exact Chrome for Testing build and proves service-worker, content-script, storage, declarative-network-request, tabs, windows, scripting, commands, side-panel, bookmarks/history read compatibility, restart persistence, repeatability, and one real WebDriver click/post-condition. Active stacked compatibility work adds downloads, bounded bookmark/history mutation, profile isolation, explicit extension update/version-migration evidence, and an exact content-script isolated-world check. OriginWeave does **not claim 100% Chrome extension compatibility**. From 0b1ddf37aa7ff017ef0974b5304cd0ba30630231 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 07:16:51 +0900 Subject: [PATCH 12/13] docs(mv3): preserve current WebDriver diagnostic traceability --- docs/doctoring/mv3-compatibility.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/doctoring/mv3-compatibility.md b/docs/doctoring/mv3-compatibility.md index 07731c19a..0a5c4aca3 100644 --- a/docs/doctoring/mv3-compatibility.md +++ b/docs/doctoring/mv3-compatibility.md @@ -1,7 +1,7 @@ # Manifest V3 compatibility evidence baseline - **Status:** Active implementation evidence for issue #27 -- **Reviewed:** 2026-08-21 +- **Reviewed:** 2026-09-04 - **Pinned browser:** Chrome for Testing `150.0.7871.129`, Chromium revision `r1639810` OriginWeave uses Chromium as its compatibility kernel, so browser-extension compatibility must be demonstrated with executable Chromium evidence rather than inferred from architecture alone. The protected-main lane exercises a controlled unpacked Manifest V3 extension against one exact Chrome for Testing build and proves service-worker, content-script, storage, declarative-network-request, tabs, windows, scripting, commands, side-panel, bookmarks/history read compatibility, restart persistence, repeatability, and one real WebDriver click/post-condition. Active stacked compatibility work adds downloads, bounded bookmark/history mutation, profile isolation, explicit extension update/version-migration evidence, and an exact content-script isolated-world check. OriginWeave does **not claim 100% Chrome extension compatibility**. @@ -48,6 +48,8 @@ For downloads compatibility specifically, the current official Chrome Extensions RFC 9112 requires a well-formed HTTP/1.1 status-line and a message body that matches the announced framing. W3C WebDriver sends commands over that HTTP transport. When ChromeDriver returns a malformed status-line or an incomplete body, the compatibility runner raises only `WebDriver transport protocol failure`; when a WebDriver response supplies a recognized protocol error, it retains only an allow-listed error code. Raw status-line text, partial body bytes, paths, URLs, browser messages, or tokens must not enter exception text or trial evidence. This classification lets `main` record the failure in `trial_results` instead of aborting the compatibility run with an unclassified parser exception. +The 2 July 2026 WebDriver Working Draft defines remote error responses with a standardized error code plus implementation-defined `message` and `stacktrace` strings and optional `data`; its user-prompt example shows page-originated prompt text in `data.text`. OriginWeave therefore treats remote-end diagnostic text and returned capability values as untrusted evidence input. The compatibility runner retains only locally owned or explicit allow-list classifications: HTTP status, a reviewed WebDriver error code, `failure_kind`, sanitized fixture-surface tokens, and the expected pinned browser version. Raw response bodies, `message`, `stacktrace`, `data`, capability values, startup exception text, DOM values, and returned page text are not copied into CI/audit exceptions. This is an evidence-provenance boundary, not browser-policy authority and not an authentication claim about WebDriver, Chromium, or page content. + ## ChromeDriver startup-record robustness boundary ChromeDriver startup stdout is diagnostic input, not authority. The compatibility runner retains at most `MAX_CHROMEDRIVER_STARTUP_LINE_BYTES + 1` bytes from one record and drains the remainder through bounded reads. A prefixed record that is oversized, lacks the required terminal period, carries a non-decimal port, or names a port outside `1..65535` is treated as non-authoritative and ignored while the existing bounded startup wait continues. A later well-formed candidate can therefore recover from malformed-but-expected startup diagnostics without turning the malformed record into success. @@ -95,3 +97,5 @@ Google Chrome Labs. (2026, July 21). *Chrome for Testing availability*. https:// Fielding, R., Nottingham, M., & Reschke, J. (Eds.). (2022). *HTTP/1.1* (RFC 9112). Internet Engineering Task Force. https://doi.org/10.17487/RFC9112 World Wide Web Consortium. (2018, June 5). *WebDriver* (W3C Recommendation). https://www.w3.org/TR/2018/REC-webdriver1-20180605/ + +World Wide Web Consortium. (2026, July 2). *WebDriver* (Working Draft). https://www.w3.org/TR/2026/WD-webdriver2-20260702/ From be2ecda617fdf428fa47b2b431ef7ba0bd5e52b7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 20:12:51 +0900 Subject: [PATCH 13/13] test(mv3): isolate redaction fixture filesystem Signed-off-by: Seongho Bae --- .../test_mv3_fixture_evidence_redaction_contract.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/tests/test_mv3_fixture_evidence_redaction_contract.py b/tests/test_mv3_fixture_evidence_redaction_contract.py index 20986406a..f79511672 100644 --- a/tests/test_mv3_fixture_evidence_redaction_contract.py +++ b/tests/test_mv3_fixture_evidence_redaction_contract.py @@ -4,6 +4,7 @@ import pathlib import runpy +import tempfile import unittest import unittest.mock @@ -151,21 +152,19 @@ def fake_json_request( with unittest.mock.patch.dict( run_browser_pass.__globals__, { - "_free_loopback_port": unittest.mock.Mock(return_value=9515), + "_start_chromedriver": unittest.mock.Mock( + return_value=(FakeDriver(), 9515) + ), "_wait_for_driver": unittest.mock.Mock(return_value=None), "_json_request": fake_json_request, }, - ), unittest.mock.patch.object( - namespace["subprocess"], - "Popen", - return_value=FakeDriver(), - ): + ), tempfile.TemporaryDirectory() as profile_dir: with self.assertRaises(RuntimeError) as raised: run_browser_pass( pathlib.Path("/controlled/chrome"), pathlib.Path("/controlled/chromedriver"), "http://127.0.0.1:9516/page.html", - "/controlled/profile", + profile_dir, "initialized", )