agentic-stealth-browser 2.5.0__tar.gz → 2.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (210) hide show
  1. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/CHANGELOG.md +27 -1
  2. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/PKG-INFO +24 -1
  3. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/README.md +23 -0
  4. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/agentic_stealth_browser.egg-info/PKG-INFO +24 -1
  5. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/agentic_stealth_browser.egg-info/SOURCES.txt +3 -0
  6. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/core/agent_browser.py +3 -1
  7. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/adapters/__init__.py +3 -0
  8. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/adapters/agentic_stealth_mcp.py +11 -3
  9. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/adapters/base.py +14 -0
  10. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/adapters/cdp_bridge.py +29 -3
  11. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/adapters/playwright_mcp.py +34 -15
  12. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/mcp_server.py +76 -0
  13. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/mcp_session_isolation.py +16 -0
  14. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/pyproject.toml +1 -1
  15. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/recovery/anti_block_orchestrator.py +36 -9
  16. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/stealth/advanced_stealth.py +181 -94
  17. agentic_stealth_browser-2.6.0/tests/stealth_harness/__init__.py +0 -0
  18. agentic_stealth_browser-2.6.0/tests/stealth_harness/collect.py +164 -0
  19. agentic_stealth_browser-2.6.0/tests/stealth_harness/test_stealth.py +137 -0
  20. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_adapter_agentic_stealth_mcp.py +28 -0
  21. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_adapter_cdp_bridge.py +25 -0
  22. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_adapter_playwright_mcp.py +48 -13
  23. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_recovery_state_machine.py +22 -0
  24. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_stealth_modules.py +9 -0
  25. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/.env.example +0 -0
  26. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/CODE_OF_CONDUCT.md +0 -0
  27. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/CONTRIBUTING.md +0 -0
  28. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/LICENSE +0 -0
  29. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/MANIFEST.in +0 -0
  30. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/SECURITY.md +0 -0
  31. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/agentic_stealth_browser.egg-info/dependency_links.txt +0 -0
  32. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/agentic_stealth_browser.egg-info/entry_points.txt +0 -0
  33. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/agentic_stealth_browser.egg-info/requires.txt +0 -0
  34. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/agentic_stealth_browser.egg-info/top_level.txt +0 -0
  35. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/ai/__init__.py +0 -0
  36. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/ai/ai_hooks.py +0 -0
  37. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/assets/hn-demo.gif +0 -0
  38. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/audit/__init__.py +0 -0
  39. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/audit/logger.py +0 -0
  40. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/behavior/__init__.py +0 -0
  41. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/behavior/adaptive_tuner.py +0 -0
  42. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/behavior/human_behavior.py +0 -0
  43. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/behavior/orchestration.py +0 -0
  44. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/behavior/persona_rotator.py +0 -0
  45. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/canary/__init__.py +0 -0
  46. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/canary/badge_gen.py +0 -0
  47. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/canary/dashboard_gen.py +0 -0
  48. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/canary/history.py +0 -0
  49. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/canary/py.typed +0 -0
  50. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/canary/readme_gen.py +0 -0
  51. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/canary/runner.py +0 -0
  52. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/canary/score.py +0 -0
  53. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/canary/sites.py +0 -0
  54. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/canary/templates/badge.svg.j2 +0 -0
  55. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/canary/templates/dashboard.html.j2 +0 -0
  56. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/core/__init__.py +0 -0
  57. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/core/account_health.py +0 -0
  58. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/core/account_warming.py +0 -0
  59. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/core/connection_pool.py +0 -0
  60. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/core/error_messages.py +0 -0
  61. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/core/feature_flags.py +0 -0
  62. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/core/py.typed +0 -0
  63. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/core/session_checkpoint.py +0 -0
  64. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/core/types.py +0 -0
  65. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/docs/ATTACH_OVER_CDP.md +0 -0
  66. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/docs/README.md +0 -0
  67. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/docs/agentic-stealth-browser-analysis-report.md +0 -0
  68. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/docs/canary/README.md +0 -0
  69. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/docs/canary/badge.svg +0 -0
  70. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/docs/canary/history.jsonl +0 -0
  71. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/docs/canary/index.html +0 -0
  72. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/docs/canary.md +0 -0
  73. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/docs/plans/2026-06-01-v2.4.0-attach-mode-hardening.md +0 -0
  74. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/docs/plans/2026-06-03-v2.5.0-real-backend-adapters.md +0 -0
  75. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/docs/plans/misc/GOAL_MODE_HERMES_BROWSER_DASHBOARD.md +0 -0
  76. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/docs/plans/misc/HN_POST.md +0 -0
  77. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/docs/plans/misc/OPEN_SOURCE_READINESS.md +0 -0
  78. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/docs/plans/misc/RELEASE_AUTONOMOUS_PLAN.md +0 -0
  79. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/examples/recipes/01_cloudflare_bypass.py +0 -0
  80. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/examples/recipes/02_linkedin_search.py +0 -0
  81. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/examples/recipes/03_amazon_product.py +0 -0
  82. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/examples/recipes/README.md +0 -0
  83. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/examples/recipes/linkedin.md +0 -0
  84. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/linkedin/__init__.py +0 -0
  85. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/linkedin/actions.py +0 -0
  86. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/mcp_security.py +0 -0
  87. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/plugins/__init__.py +0 -0
  88. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/plugins/template/__init__.py +0 -0
  89. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/plugins/template/plugin.py +0 -0
  90. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/Dockerfile +0 -0
  91. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/__init__.py +0 -0
  92. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/adapters/_jsonrpc_stdio.py +0 -0
  93. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/approval_gate.py +0 -0
  94. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/audit_enrichment.py +0 -0
  95. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/cli.py +0 -0
  96. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/dashboard_adapter_bridge.py +0 -0
  97. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/deprecations.py +0 -0
  98. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/docker-compose.yml +0 -0
  99. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/docker-healthcheck.py +0 -0
  100. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/hermes_dashboard.py +0 -0
  101. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/mcp_input_validator.py +0 -0
  102. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/metrics.py +0 -0
  103. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/otel_export.py +0 -0
  104. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/policy_engine.py +0 -0
  105. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/profiler.py +0 -0
  106. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/rate_limiter.py +0 -0
  107. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/sdk/__init__.py +0 -0
  108. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/sdk/client.py +0 -0
  109. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/workflow_orchestrator.py +0 -0
  110. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/proxy/__init__.py +0 -0
  111. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/proxy/proxy_manager.py +0 -0
  112. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/recovery/__init__.py +0 -0
  113. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/recovery/detectors.py +0 -0
  114. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/recovery/explain_blocked.py +0 -0
  115. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/scraping/__init__.py +0 -0
  116. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/scraping/scraper.py +0 -0
  117. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/scripts/canary_run.py +0 -0
  118. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/scripts/evaluate_stealth.py +0 -0
  119. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/scripts/generate-demo-gif.py +0 -0
  120. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/scripts/health_check.py +0 -0
  121. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/scripts/hn-demo.py +0 -0
  122. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/scripts/make-gif.py +0 -0
  123. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/scripts/migrate_v1_to_v2.py +0 -0
  124. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/scripts/perf_benchmark.py +0 -0
  125. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/scripts/setup_rbb.sh +0 -0
  126. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/sessions/__init__.py +0 -0
  127. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/sessions/cookie_manager.py +0 -0
  128. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/sessions/session_manager.py +0 -0
  129. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/setup.cfg +0 -0
  130. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/stealth/__init__.py +0 -0
  131. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/stealth/cache.py +0 -0
  132. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/stealth/firefox_adapter.py +0 -0
  133. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/stealth/headers.py +0 -0
  134. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/stealth/presets.py +0 -0
  135. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/stealth/profiles.py +0 -0
  136. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/stealth/tls_fingerprint.py +0 -0
  137. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/stealth/tls_ja3_ja4.py +0 -0
  138. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/debug_nowsecure.py +0 -0
  139. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/detection_check.py +0 -0
  140. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/detection_runner.py +0 -0
  141. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/fingerprint_scorecard.py +0 -0
  142. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/run_detection_tests.py +0 -0
  143. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_account_health.py +0 -0
  144. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_account_warming.py +0 -0
  145. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_adaptive_tuner.py +0 -0
  146. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_attach_over_cdp.py +0 -0
  147. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_backend_adapter_contract.py +0 -0
  148. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_basic.py +0 -0
  149. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_cdp_snapshots.py +0 -0
  150. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_connection_pool.py +0 -0
  151. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_contract_agent_browser.py +0 -0
  152. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_cookie_manager_unit.py +0 -0
  153. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_dashboard_protocol_bridge.py +0 -0
  154. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_detectors.py +0 -0
  155. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_differential.py +0 -0
  156. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_docker_config.py +0 -0
  157. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_e2e_anti_block_recovery.py +0 -0
  158. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_e2e_protected_sites_placeholder.py +0 -0
  159. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_golden_master.py +0 -0
  160. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_hermes_dashboard.py +0 -0
  161. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_human_behavior_fuzz.py +0 -0
  162. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_integration_modules.py +0 -0
  163. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_linkedin_actions.py +0 -0
  164. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_mcp_contract.py +0 -0
  165. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_mcp_server_runtime.py +0 -0
  166. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_mcp_stealth_workflows.py +0 -0
  167. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_mcp_url_safety.py +0 -0
  168. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_metrics_collector_unit.py +0 -0
  169. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_mutation_stealth.py +0 -0
  170. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_persona_rotator.py +0 -0
  171. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_phase7_fixes.py +0 -0
  172. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_player.py +0 -0
  173. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_proxy_config_unit.py +0 -0
  174. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_proxy_manager.py +0 -0
  175. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_rate_limiter_unit.py +0 -0
  176. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_recorder.py +0 -0
  177. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_recovery.py +0 -0
  178. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_recovery_phase1.py +0 -0
  179. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_rehearsal_validation.py +0 -0
  180. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_selector_auto_heal.py +0 -0
  181. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_session_checkpoint.py +0 -0
  182. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_stealth_cache.py +0 -0
  183. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_stealth_coverage.py +0 -0
  184. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_stress_multi_account.py +0 -0
  185. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_tls_ja3_ja4.py +0 -0
  186. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_workflow_orchestrator.py +0 -0
  187. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_workflow_schema.py +0 -0
  188. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_workflow_schema_unit.py +0 -0
  189. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_workflow_versioning.py +0 -0
  190. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/workflows/__init__.py +0 -0
  191. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/workflows/action_interpreter.py +0 -0
  192. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/workflows/library/auto-session-test.yaml +0 -0
  193. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/workflows/library/common/login.yaml +0 -0
  194. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/workflows/library/common/verify-email.yaml +0 -0
  195. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/workflows/library/linkedin/send-connection-request.yaml +0 -0
  196. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/workflows/library/meta-test.yaml +0 -0
  197. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/workflows/library/simple-test.yaml +0 -0
  198. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/workflows/library/teach-test/test-teach.yaml +0 -0
  199. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/workflows/library/upwork/add-portfolio-item.yaml +0 -0
  200. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/workflows/library/upwork/apply.yaml +0 -0
  201. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/workflows/library/upwork/edit-title.yaml +0 -0
  202. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/workflows/library/upwork/submit-proposal.yaml +0 -0
  203. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/workflows/library/upwork/update-rate.yaml +0 -0
  204. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/workflows/player.py +0 -0
  205. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/workflows/recorder.py +0 -0
  206. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/workflows/recovery.py +0 -0
  207. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/workflows/schema.py +0 -0
  208. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/workflows/selector_generator.py +0 -0
  209. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/workflows/test-echo.yaml +0 -0
  210. {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/workflows/variable_resolver.py +0 -0
@@ -9,6 +9,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ## [2.6.0] — Stealth & MCP Hardening (2026-07-12)
13
+
14
+ ### Added
15
+ - **`attach_mode` param** (`stealth/advanced_stealth.py`): `get_stealth_script(..., attach_mode=True)` skips `navigator.platform` / WebGL vendor-renderer / screen-DPR overrides when attaching to a real browser, so the injected fingerprint no longer contradicts the real OS/GPU. `core/agent_browser.py`'s `attach_over_cdp()` now passes `attach_mode=True`.
16
+ - **`AdapterToolError`** (`production/adapters/`): raised when an MCP tool result carries `isError: true` — previously a failed `navigate`/`click`/`fill` call reported silent success.
17
+ - **Canonical `connected` field** on adapter `status()` (`production/adapters/`), with a `running` back-compat alias.
18
+ - **CDP bridge profile resolution** (`production/adapters/cdp_bridge.py`): `launch(profile)` now accepts either a raw `ws/wss/http(s)` endpoint or a profile name resolved via `CDP_ENDPOINT_<PROFILE>` / `CDP_ENDPOINT` env, aligning its signature with the `BackendAdapter` protocol and sibling adapters.
19
+ - Adapter, stealth, and recovery unit tests covering the above (`tests/test_adapter_*.py`, `tests/test_stealth_modules.py`, `tests/test_recovery_state_machine.py`).
20
+ - `CLAUDE.md`: contributor guidance (setup, test commands mirroring CI, architecture map, conventions).
21
+
22
+ ### Fixed
23
+ - **Per-patch isolation** (`stealth/advanced_stealth.py`): each stealth patch now runs in its own try/catch, so one API mismatch no longer aborts every patch queued after it.
24
+ - **Rotation failure correctness** (`recovery/anti_block_orchestrator.py`): a failed `ROTATE_*` action now returns `False` and records `_update_recovery_history(success=False)` instead of silently succeeding; per-attempt session/proxy names get a uuid suffix to avoid collisions; a redundant tentative history update was removed.
25
+ - **playwright-mcp pin** (`production/adapters/playwright_mcp.py`): pinned to `@playwright/mcp@0.0.78` with corrected `browser_navigate`/`browser_click`/`browser_type` schema (`element`/`target`/`text`).
26
+
27
+ ### Security
28
+ - **WebRTC leak fix** (`stealth/advanced_stealth.py`): `onicecandidate` is now rebound per-instance instead of on the shared prototype (no cross-instance/global leak); `addEventListener('icecandidate', ...)` listeners are now filtered too; `createDataChannel` restored to a real passthrough.
29
+ - **PolicyEngine + ApprovalGate wiring** (`production/mcp_server.py`): both now sit in the `tools/call` dispatch path and workflow replay. Fail-open by default — no policy files means allow-all and auto-approve, so existing flows are unchanged — operators opt into enforcement via `STEALTH_MCP_POLICY` env or an approval callback.
30
+ - **Honesty docstring** (`production/mcp_session_isolation.py`): documents that `SessionEnforcer` is defined and tested but NOT wired into the dispatch path — inert until a multi-client transport exists (single-client stdio has no per-context token).
31
+
32
+ ### Changed
33
+ - `core/agent_browser.py`: `attach_over_cdp()` passes `attach_mode=True` through to the stealth script generator (see Added).
34
+
35
+ ---
36
+
12
37
  ## [2.5.0] — Real Dashboard Backend Adapters (#444) (2026-06-07)
13
38
 
14
39
  ### Added
@@ -405,7 +430,8 @@ See the full v0.9.0 milestone and the stacked PRs #383/#384/#385 for implementat
405
430
 
406
431
  ---
407
432
 
408
- [unreleased]: https://github.com/shanewas/agentic-stealth-browser/compare/v2.3.0...HEAD
433
+ [unreleased]: https://github.com/shanewas/agentic-stealth-browser/compare/v2.6.0...HEAD
434
+ [2.6.0]: https://github.com/shanewas/agentic-stealth-browser/releases/tag/v2.6.0
409
435
  [2.3.0]: https://github.com/shanewas/agentic-stealth-browser/releases/tag/v2.3.0
410
436
  [2.1.1]: https://github.com/shanewas/agentic-stealth-browser/releases/tag/v2.1.1
411
437
  [2.1.0]: https://github.com/shanewas/agentic-stealth-browser/releases/tag/v2.1.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentic-stealth-browser
3
- Version: 2.5.0
3
+ Version: 2.6.0
4
4
  Summary: Production-grade, human-mimicking browser automation framework for autonomous agents. Survives modern anti-bot systems.
5
5
  Author-email: Shane W <shanewas@users.noreply.github.com>
6
6
  License-Expression: MIT
@@ -224,6 +224,29 @@ live E2E (`RUN_E2E_ANTI_BLOCK=1` to enable).
224
224
 
225
225
  ---
226
226
 
227
+ ## New in v2.6.0
228
+
229
+ - **Stealth patch isolation** — per-patch try/catch in `advanced_stealth.py`; one
230
+ API mismatch no longer aborts every patch queued after it
231
+ - **Attach-mode fingerprint coherence** — new `attach_mode` skips
232
+ navigator.platform / WebGL vendor-renderer / screen-DPR overrides that would
233
+ otherwise contradict a real attached browser's OS/GPU
234
+ - **WebRTC leak hardening** — per-instance `onicecandidate` rebind (no
235
+ prototype-global leak); the `addEventListener('icecandidate')` variant is
236
+ now filtered too
237
+ - **MCP policy/approval gates** — `PolicyEngine` + `ApprovalGate` wired into
238
+ the dispatch path, fail-open by default (opt-in enforcement via
239
+ `STEALTH_MCP_POLICY` env)
240
+ - **Adapter `isError` surfacing** — failed MCP tool calls now raise
241
+ `AdapterToolError` instead of reporting silent success; playwright-mcp
242
+ pinned to 0.0.78 with the corrected tool schema
243
+ - **Recovery rotation-failure correctness** — failed rotations now propagate
244
+ and record history instead of silently succeeding
245
+
246
+ See [CHANGELOG.md](CHANGELOG.md) for the full release history.
247
+
248
+ ---
249
+
227
250
  ## New in v2.5.0
228
251
 
229
252
  - **BackendAdapter protocol** — pluggable execution backends (M0–M4 shipped)
@@ -185,6 +185,29 @@ live E2E (`RUN_E2E_ANTI_BLOCK=1` to enable).
185
185
 
186
186
  ---
187
187
 
188
+ ## New in v2.6.0
189
+
190
+ - **Stealth patch isolation** — per-patch try/catch in `advanced_stealth.py`; one
191
+ API mismatch no longer aborts every patch queued after it
192
+ - **Attach-mode fingerprint coherence** — new `attach_mode` skips
193
+ navigator.platform / WebGL vendor-renderer / screen-DPR overrides that would
194
+ otherwise contradict a real attached browser's OS/GPU
195
+ - **WebRTC leak hardening** — per-instance `onicecandidate` rebind (no
196
+ prototype-global leak); the `addEventListener('icecandidate')` variant is
197
+ now filtered too
198
+ - **MCP policy/approval gates** — `PolicyEngine` + `ApprovalGate` wired into
199
+ the dispatch path, fail-open by default (opt-in enforcement via
200
+ `STEALTH_MCP_POLICY` env)
201
+ - **Adapter `isError` surfacing** — failed MCP tool calls now raise
202
+ `AdapterToolError` instead of reporting silent success; playwright-mcp
203
+ pinned to 0.0.78 with the corrected tool schema
204
+ - **Recovery rotation-failure correctness** — failed rotations now propagate
205
+ and record history instead of silently succeeding
206
+
207
+ See [CHANGELOG.md](CHANGELOG.md) for the full release history.
208
+
209
+ ---
210
+
188
211
  ## New in v2.5.0
189
212
 
190
213
  - **BackendAdapter protocol** — pluggable execution backends (M0–M4 shipped)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentic-stealth-browser
3
- Version: 2.5.0
3
+ Version: 2.6.0
4
4
  Summary: Production-grade, human-mimicking browser automation framework for autonomous agents. Survives modern anti-bot systems.
5
5
  Author-email: Shane W <shanewas@users.noreply.github.com>
6
6
  License-Expression: MIT
@@ -224,6 +224,29 @@ live E2E (`RUN_E2E_ANTI_BLOCK=1` to enable).
224
224
 
225
225
  ---
226
226
 
227
+ ## New in v2.6.0
228
+
229
+ - **Stealth patch isolation** — per-patch try/catch in `advanced_stealth.py`; one
230
+ API mismatch no longer aborts every patch queued after it
231
+ - **Attach-mode fingerprint coherence** — new `attach_mode` skips
232
+ navigator.platform / WebGL vendor-renderer / screen-DPR overrides that would
233
+ otherwise contradict a real attached browser's OS/GPU
234
+ - **WebRTC leak hardening** — per-instance `onicecandidate` rebind (no
235
+ prototype-global leak); the `addEventListener('icecandidate')` variant is
236
+ now filtered too
237
+ - **MCP policy/approval gates** — `PolicyEngine` + `ApprovalGate` wired into
238
+ the dispatch path, fail-open by default (opt-in enforcement via
239
+ `STEALTH_MCP_POLICY` env)
240
+ - **Adapter `isError` surfacing** — failed MCP tool calls now raise
241
+ `AdapterToolError` instead of reporting silent success; playwright-mcp
242
+ pinned to 0.0.78 with the corrected tool schema
243
+ - **Recovery rotation-failure correctness** — failed rotations now propagate
244
+ and record history instead of silently succeeding
245
+
246
+ See [CHANGELOG.md](CHANGELOG.md) for the full release history.
247
+
248
+ ---
249
+
227
250
  ## New in v2.5.0
228
251
 
229
252
  - **BackendAdapter protocol** — pluggable execution backends (M0–M4 shipped)
@@ -182,6 +182,9 @@ tests/test_workflow_orchestrator.py
182
182
  tests/test_workflow_schema.py
183
183
  tests/test_workflow_schema_unit.py
184
184
  tests/test_workflow_versioning.py
185
+ tests/stealth_harness/__init__.py
186
+ tests/stealth_harness/collect.py
187
+ tests/stealth_harness/test_stealth.py
185
188
  workflows/__init__.py
186
189
  workflows/action_interpreter.py
187
190
  workflows/player.py
@@ -2274,7 +2274,9 @@ class AgentBrowser:
2274
2274
  if apply_stealth:
2275
2275
  session_name = (self.session or {}).get("name", "default-session")
2276
2276
  fp_seed = f"agentic-{session_name}-canvas-v4"
2277
- stealth_script = get_stealth_script(fingerprint_seed=fp_seed)
2277
+ stealth_script = get_stealth_script(
2278
+ fingerprint_seed=fp_seed, attach_mode=True
2279
+ )
2278
2280
  try:
2279
2281
  await ctx.add_init_script(stealth_script)
2280
2282
  stealth_installed = True
@@ -9,6 +9,7 @@ Public surface:
9
9
  Capability - the feature enum
10
10
  AdapterLaunchError - cannot start (runtime)
11
11
  AdapterCapabilityError - cannot perform a requested action
12
+ AdapterToolError - MCP tool call reported isError: true
12
13
  AdapterNotFoundError - get_adapter() was called with an unknown name
13
14
  BACKEND_REGISTRY - mutable dict[name -> Adapter class]
14
15
  get_adapter(name) - registry lookup with friendly error
@@ -21,6 +22,7 @@ from production.adapters.base import (
21
22
  AdapterCapabilityError,
22
23
  AdapterLaunchError,
23
24
  AdapterNotFoundError,
25
+ AdapterToolError,
24
26
  BackendAdapter,
25
27
  Capability,
26
28
  )
@@ -103,6 +105,7 @@ __all__ = [
103
105
  "AdapterCapabilityError",
104
106
  "AdapterLaunchError",
105
107
  "AdapterNotFoundError",
108
+ "AdapterToolError",
106
109
  "BACKEND_REGISTRY",
107
110
  "BackendAdapter",
108
111
  "Capability",
@@ -24,6 +24,7 @@ from production.adapters._jsonrpc_stdio import JsonRpcStdioClient
24
24
  from production.adapters.base import (
25
25
  AdapterCapabilityError,
26
26
  AdapterLaunchError,
27
+ AdapterToolError,
27
28
  Capability,
28
29
  )
29
30
 
@@ -118,7 +119,7 @@ class AgenticStealthMCPAdapter:
118
119
  "protocolVersion": "2024-11-05",
119
120
  "clientInfo": {
120
121
  "name": "agentic-stealth-browser-dashboard",
121
- "version": "2.5.0",
122
+ "version": "2.6.0",
122
123
  },
123
124
  "capabilities": {},
124
125
  },
@@ -199,9 +200,11 @@ class AgenticStealthMCPAdapter:
199
200
  return str(path)
200
201
 
201
202
  async def status(self) -> dict[str, Any]:
203
+ alive = self._proc is not None and self._proc.returncode is None
202
204
  return {
203
205
  "backend": self.name,
204
- "running": self._proc is not None and self._proc.returncode is None,
206
+ "connected": alive,
207
+ "running": alive, # back-compat alias; "connected" is canonical
205
208
  "headless": self._headless,
206
209
  "profile": self._profile,
207
210
  }
@@ -215,7 +218,12 @@ class AgenticStealthMCPAdapter:
215
218
  response = await self._client.request(
216
219
  "tools/call", {"name": name, "arguments": arguments}
217
220
  )
218
- return response.get("result", {})
221
+ result = response.get("result", {})
222
+ if result.get("isError"):
223
+ raise AdapterToolError(
224
+ f"MCP tool {name!r} failed: {result.get('content')!r}"
225
+ )
226
+ return result
219
227
 
220
228
  def _require_capability(self, cap: Capability) -> None:
221
229
  """Raise AdapterCapabilityError when the active adapter does not
@@ -46,6 +46,20 @@ class AdapterCapabilityError(RuntimeError):
46
46
  """
47
47
 
48
48
 
49
+ class AdapterToolError(RuntimeError):
50
+ """Raised when an MCP tool call completes as a valid JSON-RPC response
51
+ but reports a tool-level failure (``result["isError"] is True``).
52
+
53
+ MCP servers report a failed tool execution (bad selector, navigation
54
+ timeout, etc.) as a *successful* JSON-RPC call whose result carries
55
+ ``isError: true`` — not as a JSON-RPC protocol-level error. Without this
56
+ exception, adapters would silently treat a failed navigate/click/fill/
57
+ screenshot as success. Distinct from AdapterLaunchError (process/handshake
58
+ failures) and the plain RuntimeError raised by JsonRpcStdioClient for
59
+ JSON-RPC protocol errors (e.g. unknown method).
60
+ """
61
+
62
+
49
63
  class AdapterNotFoundError(LookupError):
50
64
  """Raised by get_adapter() when the requested name is not in
51
65
  BACKEND_REGISTRY. Inherits from LookupError (the natural superclass for
@@ -14,6 +14,7 @@ running and reachable at the CDP endpoint.
14
14
 
15
15
  from __future__ import annotations
16
16
 
17
+ import os
17
18
  from typing import Any, Optional
18
19
 
19
20
  from playwright.async_api import (
@@ -72,17 +73,24 @@ class CDPBridgeAdapter:
72
73
  return capability in self.capabilities()
73
74
 
74
75
  # ------------------------------------------------------------------ launch
75
- async def launch(self, endpoint: str, headless: bool = True) -> None:
76
+ async def launch(self, profile: str, headless: bool = True) -> None:
76
77
  """Connect to an existing CDP endpoint. Does NOT spawn a new browser.
77
78
 
78
79
  Args:
79
- endpoint: ws:// or wss:// URL of the Chrome DevTools Protocol server.
80
+ profile: either a raw ws:// / wss:// / http(s):// CDP endpoint URL
81
+ (back-compat with direct callers of this adapter), or a
82
+ profile name — consistent with the base.py contract and the
83
+ M2/M3 adapters — resolved to an endpoint via the
84
+ ``CDP_ENDPOINT_<PROFILE>`` (or generic ``CDP_ENDPOINT``)
85
+ environment variable.
80
86
  headless: cosmetic flag, passed to spec but the remote browser's
81
87
  display state is determined by its own launch args.
82
88
 
83
89
  Raises:
84
- AdapterLaunchError: if the connection cannot be established.
90
+ AdapterLaunchError: if the connection cannot be established, or
91
+ (for a profile name) if no endpoint is configured for it.
85
92
  """
93
+ endpoint = self._resolve_endpoint(profile)
86
94
  self._endpoint = endpoint
87
95
  self._playwright = await async_playwright().start()
88
96
  try:
@@ -103,6 +111,23 @@ class CDPBridgeAdapter:
103
111
  self._context = await self._browser.new_context()
104
112
  self._owns_page = True
105
113
 
114
+ # ponytail: profile -> endpoint resolution is a flat env-var lookup, not
115
+ # a real profile registry. Fine while operators set one CDP target per
116
+ # profile by hand; upgrade to a config-file-backed mapping if/when the
117
+ # dashboard needs to drive many named CDP profiles at once.
118
+ @staticmethod
119
+ def _resolve_endpoint(profile: str) -> str:
120
+ if profile.startswith(("ws://", "wss://", "http://", "https://")):
121
+ return profile
122
+ env_key = f"CDP_ENDPOINT_{profile.upper().replace('-', '_')}"
123
+ endpoint = os.environ.get(env_key) or os.environ.get("CDP_ENDPOINT")
124
+ if not endpoint:
125
+ raise AdapterLaunchError(
126
+ f"No CDP endpoint configured for profile {profile!r}; "
127
+ f"set {env_key} or CDP_ENDPOINT to a ws:// CDP URL"
128
+ )
129
+ return endpoint
130
+
106
131
  # ------------------------------------------------------------------ close
107
132
  async def close(self) -> None:
108
133
  """Tear down. Honors the v2.4.1 attach contract: only closes owned pages.
@@ -180,6 +205,7 @@ class CDPBridgeAdapter:
180
205
  return {
181
206
  "backend": self.name,
182
207
  "connected": connected,
208
+ "running": connected, # back-compat alias; "connected" is canonical
183
209
  "endpoint": self._endpoint,
184
210
  "owns_page": self._owns_page,
185
211
  }
@@ -20,6 +20,7 @@ from production.adapters._jsonrpc_stdio import JsonRpcStdioClient
20
20
  from production.adapters.base import (
21
21
  AdapterCapabilityError,
22
22
  AdapterLaunchError,
23
+ AdapterToolError,
23
24
  Capability,
24
25
  )
25
26
 
@@ -35,11 +36,13 @@ def _subprocess_env() -> dict[str, str]:
35
36
  }
36
37
 
37
38
 
38
- # Pin a specific playwright-mcp version. The latest is fine; pin for
39
- # reproducibility. Update this when a new release is tested.
39
+ # Pinned playwright-mcp version (not @latest — @latest is not a pin and lets
40
+ # upstream tool-name/behavior changes silently break this adapter). Update
41
+ # this deliberately when a new release has been tested against the tool
42
+ # names below.
40
43
  PLAYWRIGHT_MCP_NPX_ARGS = [
41
44
  "-y",
42
- "@playwright/mcp@latest",
45
+ "@playwright/mcp@0.0.78",
43
46
  "--isolated",
44
47
  ]
45
48
 
@@ -130,7 +133,7 @@ class PlaywrightMCPAdapter:
130
133
  "protocolVersion": "2024-11-05",
131
134
  "clientInfo": {
132
135
  "name": "agentic-stealth-browser",
133
- "version": "2.5.0",
136
+ "version": "2.6.0",
134
137
  },
135
138
  "capabilities": {},
136
139
  },
@@ -175,34 +178,45 @@ class PlaywrightMCPAdapter:
175
178
  pass # already dead
176
179
 
177
180
  # ------------------------------------------------------------------ actions
181
+ # Verified against @playwright/mcp 0.0.78 (playwright-core coreBundle.js
182
+ # tool schemas, packages/playwright-core/src/tools/backend/{snapshot,navigate}.ts):
183
+ # browser_click / browser_type take `target` (required) + `element`
184
+ # (optional, human-readable description) — no `selector` key. `target` is
185
+ # NOT limited to snapshot refs: targetLocators() only treats it as a ref
186
+ # when it matches /^(f\d+)?e\d+$/, otherwise it's used directly as a
187
+ # Playwright selector string. So a CSS selector in `target` works with no
188
+ # browser_snapshot round-trip.
178
189
  async def navigate(self, url: str) -> None:
179
190
  self._require_capability(Capability.NAVIGATE)
180
- await self._call_tool("playwright_navigate", {"url": url})
191
+ await self._call_tool("browser_navigate", {"url": url})
181
192
 
182
193
  async def click(self, selector: str) -> None:
183
194
  self._require_capability(Capability.CLICK)
184
- await self._call_tool("playwright_click", {"selector": selector})
195
+ await self._call_tool(
196
+ "browser_click", {"element": selector, "target": selector}
197
+ )
185
198
 
186
199
  async def fill(self, selector: str, value: str) -> None:
187
200
  self._require_capability(Capability.FILL)
188
- await self._call_tool("playwright_fill", {"selector": selector, "value": value})
201
+ await self._call_tool(
202
+ "browser_type", {"element": selector, "target": selector, "text": value}
203
+ )
189
204
 
190
205
  async def screenshot(self, path: Optional[str] = None) -> str:
191
206
  self._require_capability(Capability.SCREENSHOT)
192
207
  if path is None:
193
208
  path = "screenshot.png"
194
- # playwright-mcp exposes playwright_take_screenshot (or browser_take_screenshot,
195
- # depending on version). Try the common names; the test will accept any.
196
- try:
197
- await self._call_tool("browser_take_screenshot", {"filename": path})
198
- except RuntimeError:
199
- await self._call_tool("playwright_take_screenshot", {"filename": path})
209
+ # 0.0.78 schema: browser_take_screenshot takes `filename` (optional str).
210
+ # No fallback tool name needed pinned version only exposes this one.
211
+ await self._call_tool("browser_take_screenshot", {"filename": path})
200
212
  return str(path)
201
213
 
202
214
  async def status(self) -> dict[str, Any]:
215
+ alive = self._proc is not None and self._proc.returncode is None
203
216
  return {
204
217
  "backend": self.name,
205
- "running": self._proc is not None and self._proc.returncode is None,
218
+ "connected": alive,
219
+ "running": alive, # back-compat alias; "connected" is canonical
206
220
  "headless": self._headless,
207
221
  "profile": self._profile,
208
222
  }
@@ -216,7 +230,12 @@ class PlaywrightMCPAdapter:
216
230
  response = await self._client.request(
217
231
  "tools/call", {"name": name, "arguments": arguments}
218
232
  )
219
- return response.get("result", {})
233
+ result = response.get("result", {})
234
+ if result.get("isError"):
235
+ raise AdapterToolError(
236
+ f"MCP tool {name!r} failed: {result.get('content')!r}"
237
+ )
238
+ return result
220
239
 
221
240
  def _require_capability(self, cap: Capability) -> None:
222
241
  """Raise AdapterCapabilityError when the active adapter does not
@@ -29,7 +29,9 @@ from mcp_security import (
29
29
  MCPSecurityContext,
30
30
  sanitize_tool_description,
31
31
  )
32
+ from production.approval_gate import ApprovalDecision, ApprovalGate
32
33
  from production.mcp_input_validator import InputValidationError, validate_tool_input
34
+ from production.policy_engine import PolicyEngine
33
35
 
34
36
 
35
37
  JSONRPC_VERSION = "2.0"
@@ -294,6 +296,28 @@ class StealthMCPServer:
294
296
  self._bundled_workflow_root = None
295
297
  self._tools: Dict[str, ToolSpec] = self._build_tools()
296
298
 
299
+ # Security gates wired into the dispatch path (see handle_jsonrpc tools/call
300
+ # and _tool_stealth_replay). Posture:
301
+ # - PolicyEngine: step-type / domain allow-lists. Enforced by default from
302
+ # policy YAML in ~/.agentic-browser/policies. With no policy files the
303
+ # default policy allows everything (fail-open), so normal flows are unchanged.
304
+ # Set STEALTH_MCP_POLICY to activate a named loaded policy.
305
+ # - ApprovalGate: sensitive-action approval. Permissive by default — there is
306
+ # no human in the loop over headless stdio, so we auto-approve to avoid
307
+ # deadlocking launch/navigate/replay. An operator (SDK/dashboard) enables
308
+ # real enforcement by installing an approval callback via
309
+ # self._approval_gate.set_allow_callback(...).
310
+ self._policy_engine = PolicyEngine()
311
+ try:
312
+ self._policy_engine.load_policies()
313
+ except Exception:
314
+ pass
315
+ active_policy = os.getenv("STEALTH_MCP_POLICY")
316
+ if active_policy:
317
+ self._policy_engine.set_active(active_policy)
318
+ self._approval_gate = ApprovalGate(auto_approve_known_domains=True)
319
+ self._approval_gate.set_allow_callback(lambda req: ApprovalDecision.ALLOWED)
320
+
297
321
  def _get_agent_browser_cls(self):
298
322
  if self._agent_browser_cls is not None:
299
323
  return self._agent_browser_cls
@@ -1507,6 +1531,38 @@ class StealthMCPServer:
1507
1531
 
1508
1532
  workflow = load_workflow(str(resolved_workflow_path))
1509
1533
 
1534
+ # Security gate: enforce policy (step-type / domain allow-lists) on every step
1535
+ # before executing any of them. Default policy is fail-open; operator policy
1536
+ # YAML makes this deny blocked step types / domains and require approval.
1537
+ for idx, step in enumerate(workflow.steps):
1538
+ decision = self._policy_engine.check_step(
1539
+ step.type, str(step.params.get("url", ""))
1540
+ )
1541
+ if not decision["allowed"]:
1542
+ raise ToolError(
1543
+ "MCP_POLICY_DENIED",
1544
+ f"Workflow step {idx} ('{step.type}') denied by policy: {decision['reason']}",
1545
+ {
1546
+ "step_index": idx,
1547
+ "step_type": step.type,
1548
+ "reason": decision["reason"],
1549
+ },
1550
+ )
1551
+ if decision.get("approval_required"):
1552
+ appr = self._approval_gate.check_sensitive(
1553
+ step.type, step.params, str(session_name or "")
1554
+ )
1555
+ if appr.decision != ApprovalDecision.ALLOWED:
1556
+ raise ToolError(
1557
+ "MCP_APPROVAL_REQUIRED",
1558
+ f"Workflow step {idx} ('{step.type}') requires approval: {appr.reason}",
1559
+ {
1560
+ "step_index": idx,
1561
+ "step_type": step.type,
1562
+ "request_id": appr.request_id,
1563
+ },
1564
+ )
1565
+
1510
1566
  if session_name:
1511
1567
  resolved_name, browser = await self._resolve_browser(session_name)
1512
1568
  else:
@@ -1755,6 +1811,26 @@ class StealthMCPServer:
1755
1811
  msg_id, self._tool_result(payload, is_error=True)
1756
1812
  )
1757
1813
 
1814
+ # Security gate: sensitive-action approval before handler execution.
1815
+ # No-op for non-sensitive tools; permissive unless an operator installs an
1816
+ # approval callback (see __init__). Blocks on DENIED/PENDING when enforced.
1817
+ gate = self._approval_gate.check_sensitive(
1818
+ tool_name, validated_args, str(validated_args.get("session_name") or "")
1819
+ )
1820
+ if gate.decision != ApprovalDecision.ALLOWED:
1821
+ payload = self._tool_error_payload(
1822
+ "MCP_APPROVAL_REQUIRED",
1823
+ f"Action '{tool_name}' requires approval: {gate.reason}",
1824
+ {
1825
+ "tool": tool_name,
1826
+ "request_id": gate.request_id,
1827
+ "decision": gate.decision.value,
1828
+ },
1829
+ )
1830
+ return self._jsonrpc_result(
1831
+ msg_id, self._tool_result(payload, is_error=True)
1832
+ )
1833
+
1758
1834
  try:
1759
1835
  payload = await tool.handler(validated_args)
1760
1836
  return self._jsonrpc_result(
@@ -5,6 +5,17 @@ Prevents cross-session data access:
5
5
  - One session's tool calls cannot access another session's browser instance.
6
6
  - session_name must match the caller's authorized session.
7
7
  - Token-based session binding for concurrent clients.
8
+
9
+ NOT YET INTEGRATED (honesty note): SessionEnforcer is defined and unit-tested but
10
+ is NOT called from the MCP dispatch path in production/mcp_server.py. Its model
11
+ isolates *concurrent client contexts* from each other via per-context tokens, but
12
+ the current MCP transport is single-client stdio and carries no per-call context
13
+ token to key check_access() on. With one trust domain there is nothing to isolate
14
+ from, and _resolve_browser() already rejects unknown/closed sessions — so wiring a
15
+ constant global token here would be security theater, not enforcement. This class
16
+ becomes meaningful only under a multi-client transport (e.g. HTTP/SSE) that supplies
17
+ a real per-client context token to bind_session()/check_access(). Until then it
18
+ enforces nothing at runtime; do not rely on it for isolation.
8
19
  """
9
20
 
10
21
  from __future__ import annotations
@@ -48,6 +59,11 @@ class SessionEnforcer:
48
59
  Each call context is associated with a token. Tools can only operate on
49
60
  sessions bound to their call context's token. A single client/context cannot
50
61
  access another context's browser instances.
62
+
63
+ NOT WIRED: no call site in production/mcp_server.py's dispatch path. Requires a
64
+ multi-client transport that supplies a per-context token — the current stdio
65
+ transport does not. See the module docstring. This is a defined-but-inert
66
+ security control, not an active one.
51
67
  """
52
68
 
53
69
  def __init__(self) -> None:
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "agentic-stealth-browser"
7
- version = "2.5.0"
7
+ version = "2.6.0"
8
8
  description = "Production-grade, human-mimicking browser automation framework for autonomous agents. Survives modern anti-bot systems."
9
9
  readme = "README.md"
10
10
  license = "MIT"