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.
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/CHANGELOG.md +27 -1
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/PKG-INFO +24 -1
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/README.md +23 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/agentic_stealth_browser.egg-info/PKG-INFO +24 -1
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/agentic_stealth_browser.egg-info/SOURCES.txt +3 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/core/agent_browser.py +3 -1
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/adapters/__init__.py +3 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/adapters/agentic_stealth_mcp.py +11 -3
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/adapters/base.py +14 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/adapters/cdp_bridge.py +29 -3
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/adapters/playwright_mcp.py +34 -15
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/mcp_server.py +76 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/mcp_session_isolation.py +16 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/pyproject.toml +1 -1
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/recovery/anti_block_orchestrator.py +36 -9
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/stealth/advanced_stealth.py +181 -94
- agentic_stealth_browser-2.6.0/tests/stealth_harness/__init__.py +0 -0
- agentic_stealth_browser-2.6.0/tests/stealth_harness/collect.py +164 -0
- agentic_stealth_browser-2.6.0/tests/stealth_harness/test_stealth.py +137 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_adapter_agentic_stealth_mcp.py +28 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_adapter_cdp_bridge.py +25 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_adapter_playwright_mcp.py +48 -13
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_recovery_state_machine.py +22 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_stealth_modules.py +9 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/.env.example +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/CODE_OF_CONDUCT.md +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/CONTRIBUTING.md +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/LICENSE +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/MANIFEST.in +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/SECURITY.md +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/agentic_stealth_browser.egg-info/dependency_links.txt +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/agentic_stealth_browser.egg-info/entry_points.txt +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/agentic_stealth_browser.egg-info/requires.txt +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/agentic_stealth_browser.egg-info/top_level.txt +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/ai/__init__.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/ai/ai_hooks.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/assets/hn-demo.gif +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/audit/__init__.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/audit/logger.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/behavior/__init__.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/behavior/adaptive_tuner.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/behavior/human_behavior.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/behavior/orchestration.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/behavior/persona_rotator.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/canary/__init__.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/canary/badge_gen.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/canary/dashboard_gen.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/canary/history.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/canary/py.typed +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/canary/readme_gen.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/canary/runner.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/canary/score.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/canary/sites.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/canary/templates/badge.svg.j2 +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/canary/templates/dashboard.html.j2 +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/core/__init__.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/core/account_health.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/core/account_warming.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/core/connection_pool.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/core/error_messages.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/core/feature_flags.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/core/py.typed +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/core/session_checkpoint.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/core/types.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/docs/ATTACH_OVER_CDP.md +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/docs/README.md +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/docs/agentic-stealth-browser-analysis-report.md +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/docs/canary/README.md +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/docs/canary/badge.svg +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/docs/canary/history.jsonl +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/docs/canary/index.html +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/docs/canary.md +0 -0
- {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
- {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
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/docs/plans/misc/GOAL_MODE_HERMES_BROWSER_DASHBOARD.md +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/docs/plans/misc/HN_POST.md +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/docs/plans/misc/OPEN_SOURCE_READINESS.md +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/docs/plans/misc/RELEASE_AUTONOMOUS_PLAN.md +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/examples/recipes/01_cloudflare_bypass.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/examples/recipes/02_linkedin_search.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/examples/recipes/03_amazon_product.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/examples/recipes/README.md +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/examples/recipes/linkedin.md +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/linkedin/__init__.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/linkedin/actions.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/mcp_security.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/plugins/__init__.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/plugins/template/__init__.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/plugins/template/plugin.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/Dockerfile +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/__init__.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/adapters/_jsonrpc_stdio.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/approval_gate.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/audit_enrichment.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/cli.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/dashboard_adapter_bridge.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/deprecations.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/docker-compose.yml +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/docker-healthcheck.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/hermes_dashboard.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/mcp_input_validator.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/metrics.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/otel_export.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/policy_engine.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/profiler.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/rate_limiter.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/sdk/__init__.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/sdk/client.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/workflow_orchestrator.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/proxy/__init__.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/proxy/proxy_manager.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/recovery/__init__.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/recovery/detectors.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/recovery/explain_blocked.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/scraping/__init__.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/scraping/scraper.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/scripts/canary_run.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/scripts/evaluate_stealth.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/scripts/generate-demo-gif.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/scripts/health_check.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/scripts/hn-demo.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/scripts/make-gif.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/scripts/migrate_v1_to_v2.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/scripts/perf_benchmark.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/scripts/setup_rbb.sh +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/sessions/__init__.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/sessions/cookie_manager.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/sessions/session_manager.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/setup.cfg +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/stealth/__init__.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/stealth/cache.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/stealth/firefox_adapter.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/stealth/headers.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/stealth/presets.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/stealth/profiles.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/stealth/tls_fingerprint.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/stealth/tls_ja3_ja4.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/debug_nowsecure.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/detection_check.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/detection_runner.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/fingerprint_scorecard.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/run_detection_tests.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_account_health.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_account_warming.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_adaptive_tuner.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_attach_over_cdp.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_backend_adapter_contract.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_basic.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_cdp_snapshots.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_connection_pool.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_contract_agent_browser.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_cookie_manager_unit.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_dashboard_protocol_bridge.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_detectors.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_differential.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_docker_config.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_e2e_anti_block_recovery.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_e2e_protected_sites_placeholder.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_golden_master.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_hermes_dashboard.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_human_behavior_fuzz.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_integration_modules.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_linkedin_actions.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_mcp_contract.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_mcp_server_runtime.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_mcp_stealth_workflows.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_mcp_url_safety.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_metrics_collector_unit.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_mutation_stealth.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_persona_rotator.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_phase7_fixes.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_player.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_proxy_config_unit.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_proxy_manager.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_rate_limiter_unit.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_recorder.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_recovery.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_recovery_phase1.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_rehearsal_validation.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_selector_auto_heal.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_session_checkpoint.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_stealth_cache.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_stealth_coverage.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_stress_multi_account.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_tls_ja3_ja4.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_workflow_orchestrator.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_workflow_schema.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_workflow_schema_unit.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/tests/test_workflow_versioning.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/workflows/__init__.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/workflows/action_interpreter.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/workflows/library/auto-session-test.yaml +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/workflows/library/common/login.yaml +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/workflows/library/common/verify-email.yaml +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/workflows/library/linkedin/send-connection-request.yaml +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/workflows/library/meta-test.yaml +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/workflows/library/simple-test.yaml +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/workflows/library/teach-test/test-teach.yaml +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/workflows/library/upwork/add-portfolio-item.yaml +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/workflows/library/upwork/apply.yaml +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/workflows/library/upwork/edit-title.yaml +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/workflows/library/upwork/submit-proposal.yaml +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/workflows/library/upwork/update-rate.yaml +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/workflows/player.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/workflows/recorder.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/workflows/recovery.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/workflows/schema.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/workflows/selector_generator.py +0 -0
- {agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/workflows/test-echo.yaml +0 -0
- {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.
|
|
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.
|
|
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.
|
|
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(
|
|
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
|
{agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/adapters/__init__.py
RENAMED
|
@@ -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.
|
|
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
|
-
"
|
|
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
|
-
|
|
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
|
{agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/adapters/cdp_bridge.py
RENAMED
|
@@ -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,
|
|
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
|
-
|
|
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
|
-
#
|
|
39
|
-
#
|
|
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@
|
|
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.
|
|
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("
|
|
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(
|
|
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(
|
|
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
|
-
#
|
|
195
|
-
#
|
|
196
|
-
|
|
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
|
-
"
|
|
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
|
-
|
|
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(
|
{agentic_stealth_browser-2.5.0 → agentic_stealth_browser-2.6.0}/production/mcp_session_isolation.py
RENAMED
|
@@ -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.
|
|
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"
|