Sub-issue of #13236 (ADR-009 steps 3-6), scoped to step 6 only.
What's still hand-rolled
api/playwright.py calls services/playwright_service.py's PlaywrightService/playwright_service()/get_playwright_service() directly (via from services.playwright_service import (get_playwright_service, playwright_service, ...)), not autobot_shared.browser.get_browser(). browser_backends/container.py already wraps this exact stack for the canonical interface — api/playwright.py is a second, parallel path to the same container that bypasses the registry (and, before #16544, bypassed its own URL guard too).
Whether is_url_allowed (api/browser_mcp.py) still exists as a regex allowlist, or has already been retired, needs re-checking at the time this is picked up — api/browser_mcp.py's current docstring says "There is no URL allowlist" but that may describe send_to_browser_vm's own guard rather than every caller; confirm before assuming this half is already done.
Why this needs its own review, not a mechanical migration
The whole router (api/playwright.py) is admin-gated (dependencies=[Depends(check_admin_permission)]) and several of its routes (test_frontend, send_test_message) deliberately target AutoBot's own internal frontend (ServiceURLs.FRONTEND_LOCAL) — they are not public-URL fetches and must keep working after any migration. #13236's own text calls for enumerating real internal-host usage before touching the allowlist, rather than guessing at the exception list.
Acceptance criteria
Related
Sub-issue of #13236 (ADR-009 steps 3-6), scoped to step 6 only.
What's still hand-rolled
api/playwright.pycallsservices/playwright_service.py'sPlaywrightService/playwright_service()/get_playwright_service()directly (viafrom services.playwright_service import (get_playwright_service, playwright_service, ...)), notautobot_shared.browser.get_browser().browser_backends/container.pyalready wraps this exact stack for the canonical interface —api/playwright.pyis a second, parallel path to the same container that bypasses the registry (and, before #16544, bypassed its own URL guard too).Whether
is_url_allowed(api/browser_mcp.py) still exists as a regex allowlist, or has already been retired, needs re-checking at the time this is picked up —api/browser_mcp.py's current docstring says "There is no URL allowlist" but that may describesend_to_browser_vm's own guard rather than every caller; confirm before assuming this half is already done.Why this needs its own review, not a mechanical migration
The whole router (
api/playwright.py) is admin-gated (dependencies=[Depends(check_admin_permission)]) and several of its routes (test_frontend,send_test_message) deliberately target AutoBot's own internal frontend (ServiceURLs.FRONTEND_LOCAL) — they are not public-URL fetches and must keep working after any migration. #13236's own text calls for enumerating real internal-host usage before touching the allowlist, rather than guessing at the exception list.Acceptance criteria
api/playwright.pyand classify each as "arbitrary caller-supplied URL" (should route throughget_browser()) vs "deliberately internal, own frontend" (should NOT be forced through the public-URL guard)autobot_shared.browser.get_browser(), notservices.playwright_servicedirectlyis_url_allowed(api/browser_mcp.py) — retire it if still a live regex allowlist, or close this criterion as already done with the commit that did it, if it's goneapi/playwright.pyhas no remaining directservices.playwright_serviceimport for the migrated routesRelated
capture_screenshotin security(browser): validate the URL in capture_screenshot (#13204) #16544; this step is the architectural migration, not the security gap