diff --git a/examples/scanner_browser_submit/browser_submit.py b/examples/scanner_browser_submit/browser_submit.py new file mode 100644 index 0000000..8971fe6 --- /dev/null +++ b/examples/scanner_browser_submit/browser_submit.py @@ -0,0 +1,20 @@ +"""Synthetic browser-submit fixture for the advisory execution-gap scanner. + +The page object below is local-only: it records no data and contacts no service. +""" + + +class SyntheticPage: + """Minimal stand-in exposing browser-style methods to static analysis.""" + + async def fill(self, selector: str, value: str) -> None: + pass + + async def click(self, selector: str) -> None: + pass + + +async def browser_agent_submit_profile(page: SyntheticPage, email: str) -> None: + """Model a consequential form submission without performing a real action.""" + await page.fill("#email", email) + await page.click("button[type=submit]") diff --git a/tests/unit/test_scanner_universal.py b/tests/unit/test_scanner_universal.py index 762dde6..e70117d 100755 --- a/tests/unit/test_scanner_universal.py +++ b/tests/unit/test_scanner_universal.py @@ -343,6 +343,30 @@ async def submit_account(browser_session, params): self.assertEqual("yes", finding.agent_control_context) self.assertIn("missing proof gate", finding.control_gaps) + def test_browser_submit_fixture_is_reported_as_an_advisory_candidate(self) -> None: + fixture = ( + Path(__file__).resolve().parents[2] + / "examples" + / "scanner_browser_submit" + / "browser_submit.py" + ) + report = self._scan_source( + "controller/browser_submit.py", + fixture.read_text(encoding="utf-8"), + ) + finding = self._finding_for(report, "S6") + payload = report.to_dict() + markdown = render_markdown_report(report, mode="developer").lower() + + self.assertEqual("BROWSER_AGENT_SIDE_EFFECT", finding.category) + self.assertEqual("yes", finding.agent_control_context) + self.assertEqual("RUNTIME_CODE", finding.context_classification) + self.assertIn("candidate", payload["consequence_class_label"].lower()) + self.assertIs(False, payload["vulnerability_claim"]) + self.assertIsNone(payload["vulnerability_severity"]) + self.assertIn("runtime reachability", markdown) + self.assertTrue(all(term not in markdown for term in UNSAFE_DEFINITIVE_TERMS)) + def test_computer_use_desktop_actions_are_detected(self) -> None: report = self._scan_source( "desktop/bytebot_worker.py",