Skip to content

Implement Docker support, WebUI API, and stealth extension#719

Open
aleks1k wants to merge 1 commit intobrowser-use:mainfrom
MinglesAI:openclaw-custom
Open

Implement Docker support, WebUI API, and stealth extension#719
aleks1k wants to merge 1 commit intobrowser-use:mainfrom
MinglesAI:openclaw-custom

Conversation

@aleks1k
Copy link

@aleks1k aleks1k commented Feb 28, 2026

OpenClaw custom: Docker, WebUI API, stealth extension, docs" -b "Custom build for OpenClaw: Docker/docker-compose tweaks, WebUI HTTP API (browser_api), startup scripts, stealth extension, docs (WEBUI_HTTP_API.md, CLI_AND_DOCKER_CDP.md). No browser profiles or secrets in repo.


Summary by cubic

Adds Dockerized startup browser with CDP forwarding, a small WebUI HTTP API for direct browser control, and a stealth extension to make automation more stable and easier to integrate. Also adds docs and safer compose defaults so Python/CLI can connect on :9222 without exposing sensitive ports.

  • New Features

    • Docker: socat forwards 9222→9223, startup browser via supervisord, localhost bindings for 7788/5901/9222, and volumes for data/profiles/output.
    • WebUI API: stable endpoints (goto, get_url, get_title, screenshot) plus agent actions (run_agent, stop_agent, pause_resume_agent, clear_agent). Auto-connects to BROWSER_CDP when set.
    • Stealth: bundled extension loaded via Playwright launch args to reduce detection.
    • Docs: WEBUI_HTTP_API.md and CLI_AND_DOCKER_CDP.md; README notes for using CDP from host.
    • Utilities: verify_playwright.py to sanity-check Chromium.
  • Migration

    • Set LAUNCH_BROWSER_AT_STARTUP=true and BROWSER_CDP=http://127.0.0.1:9222 to control the container’s browser; Chrome listens on 9223 internally.
    • Use PLAYWRIGHT_LAUNCH_ARGS (includes --remote-debugging-port=9223 and the stealth extension path) and BROWSER_USER_DATA=/profiles/agent-default; mount ./profiles to persist.
    • Call API via /gradio_api/call/ or gradio_client (see WEBUI_HTTP_API.md).

Written for commit b074df1. Summary will update on new commits.

@CLAassistant
Copy link

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

9 issues found across 16 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="scripts/startup_browser.py">

<violation number="1" location="scripts/startup_browser.py:33">
P2: startup_browser.py only strips the "--user-data-dir=..." form. If PLAYWRIGHT_LAUNCH_ARGS contains the two-token form ("--user-data-dir /path"), user_data_dir remains None and the script falls back to a default profile while still passing the raw --user-data-dir arguments into Chromium, ignoring the intended profile. Handle the two-token form as well to avoid silently using the wrong profile.</violation>
</file>

<file name="docs/CLI_AND_DOCKER_CDP.md">

<violation number="1" location="docs/CLI_AND_DOCKER_CDP.md:79">
P2: Documentation suggests pointing CDP to a server IP without warning that the remote debugging port is unauthenticated and should be restricted; this can lead to exposing full browser control if 9222 is reachable publicly.</violation>
</file>

<file name="stealth-extension/stealth.js">

<violation number="1" location="stealth-extension/stealth.js:1">
P2: The stealth overrides are wrapped in a bare arrow function that is never invoked, so none of the property overrides execute.</violation>

<violation number="2" location="stealth-extension/stealth.js:14">
P2: `navigator.permissions` getter returns a new object each access, so the later override of `query` does not persist and the notifications-specific behavior is never applied.</violation>

<violation number="3" location="stealth-extension/stealth.js:43">
P2: Syntax error: window.chrome object literal is not closed, so the script cannot parse.</violation>

<violation number="4" location="stealth-extension/stealth.js:49">
P2: Using HTMLNavigator.prototype will throw ReferenceError in standard browsers; the Navigator interface is the correct prototype for userAgent overrides.</violation>
</file>

<file name="stealth-extension/manifest.json">

<violation number="1" location="stealth-extension/manifest.json:14">
P2: manifest.json is missing the closing `}` for the top-level object, making the JSON invalid and causing the extension to fail to load.</violation>
</file>

<file name="src/webui/browser_api.py">

<violation number="1" location="src/webui/browser_api.py:137">
P2: api_screenshot promises a base64 image but can return a filesystem path when "path" is present, breaking the API contract and potentially leaking local paths to consumers expecting base64 data.</violation>
</file>

<file name="docker-compose.yml">

<violation number="1" location="docker-compose.yml:68">
P2: noVNC is exposed on 0.0.0.0 while the default VNC_PASSWORD is empty, which results in a blank VNC password being generated and allows unauthenticated access to the VNC/noVNC UI from any host.</violation>
</file>

Since this is your first cubic review, here's how it works:

  • cubic automatically reviews your code and comments on bugs and improvements
  • Teach cubic by replying to its comments. cubic learns from your replies and gets better over time
  • Add one-off context when rerunning by tagging @cubic-dev-ai with guidance or docs links (including llms.txt)
  • Ask questions if you need clarification on any suggestion

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

user_data_dir = None
if config.extra_browser_args:
for a in config.extra_browser_args:
if a.startswith("--user-data-dir="):
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: startup_browser.py only strips the "--user-data-dir=..." form. If PLAYWRIGHT_LAUNCH_ARGS contains the two-token form ("--user-data-dir /path"), user_data_dir remains None and the script falls back to a default profile while still passing the raw --user-data-dir arguments into Chromium, ignoring the intended profile. Handle the two-token form as well to avoid silently using the wrong profile.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/startup_browser.py, line 33:

<comment>startup_browser.py only strips the "--user-data-dir=..." form. If PLAYWRIGHT_LAUNCH_ARGS contains the two-token form ("--user-data-dir /path"), user_data_dir remains None and the script falls back to a default profile while still passing the raw --user-data-dir arguments into Chromium, ignoring the intended profile. Handle the two-token form as well to avoid silently using the wrong profile.</comment>

<file context>
@@ -0,0 +1,66 @@
+    user_data_dir = None
+    if config.extra_browser_args:
+        for a in config.extra_browser_args:
+            if a.startswith("--user-data-dir="):
+                user_data_dir = a.split("=", 1)[1].strip()
+            else:
</file context>
Fix with Cubic

If the host is not the same machine as the server (e.g. you’re on a laptop and the container runs on a server), replace `127.0.0.1` with the server’s IP or hostname:

```python
CDP_URL = "http://YOUR_SERVER_IP:9222"
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Documentation suggests pointing CDP to a server IP without warning that the remote debugging port is unauthenticated and should be restricted; this can lead to exposing full browser control if 9222 is reachable publicly.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docs/CLI_AND_DOCKER_CDP.md, line 79:

<comment>Documentation suggests pointing CDP to a server IP without warning that the remote debugging port is unauthenticated and should be restricted; this can lead to exposing full browser control if 9222 is reachable publicly.</comment>

<file context>
@@ -0,0 +1,133 @@
+If the host is not the same machine as the server (e.g. you’re on a laptop and the container runs on a server), replace `127.0.0.1` with the server’s IP or hostname:
+
+```python
+CDP_URL = "http://YOUR_SERVER_IP:9222"
+```
+
</file context>
Fix with Cubic

get: () => [1, 2, 3, 4, 5],
});

Object.defineProperty(navigator, 'permissions', {
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: navigator.permissions getter returns a new object each access, so the later override of query does not persist and the notifications-specific behavior is never applied.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At stealth-extension/stealth.js, line 14:

<comment>`navigator.permissions` getter returns a new object each access, so the later override of `query` does not persist and the notifications-specific behavior is never applied.</comment>

<file context>
@@ -0,0 +1,51 @@
+        get: () => [1, 2, 3, 4, 5],
+    });
+
+    Object.defineProperty(navigator, 'permissions', {
+        get: () => ({
+            query: () => Promise.resolve({ state: 'granted' }),
</file context>
Fix with Cubic

get: () => window.chrome,
});

Object.defineProperty(HTMLNavigator.prototype, 'userAgent', {
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Using HTMLNavigator.prototype will throw ReferenceError in standard browsers; the Navigator interface is the correct prototype for userAgent overrides.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At stealth-extension/stealth.js, line 49:

<comment>Using HTMLNavigator.prototype will throw ReferenceError in standard browsers; the Navigator interface is the correct prototype for userAgent overrides.</comment>

<file context>
@@ -0,0 +1,51 @@
+        get: () => window.chrome,
+    });
+
+    Object.defineProperty(HTMLNavigator.prototype, 'userAgent', {
+        get: () => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
+    });
</file context>
Fix with Cubic

@@ -0,0 +1,51 @@
() => {
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The stealth overrides are wrapped in a bare arrow function that is never invoked, so none of the property overrides execute.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At stealth-extension/stealth.js, line 1:

<comment>The stealth overrides are wrapped in a bare arrow function that is never invoked, so none of the property overrides execute.</comment>

<file context>
@@ -0,0 +1,51 @@
+() => {
+    Object.defineProperty(navigator, 'webdriver', {
+        get: () => false,
</file context>
Fix with Cubic

canary: false,
loadTimes: () => {},
csi: () => {},
;
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Syntax error: window.chrome object literal is not closed, so the script cannot parse.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At stealth-extension/stealth.js, line 43:

<comment>Syntax error: window.chrome object literal is not closed, so the script cannot parse.</comment>

<file context>
@@ -0,0 +1,51 @@
+        canary: false,
+        loadTimes: () => {},
+        csi: () => {},
+    ;
+
+    Object.defineProperty(navigator, 'chrome', {
</file context>
Fix with Cubic

"permissions": [
"webNavigation",
"tabs"
]
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: manifest.json is missing the closing } for the top-level object, making the JSON invalid and causing the extension to fail to load.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At stealth-extension/manifest.json, line 14:

<comment>manifest.json is missing the closing `}` for the top-level object, making the JSON invalid and causing the extension to fail to load.</comment>

<file context>
@@ -0,0 +1,14 @@
+  "permissions": [
+    "webNavigation",
+    "tabs"
+  ]
</file context>
Fix with Cubic

if isinstance(out, dict) and "base64" in out:
return out["base64"]
if hasattr(out, "get"):
return out.get("base64") or out.get("path")
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: api_screenshot promises a base64 image but can return a filesystem path when "path" is present, breaking the API contract and potentially leaking local paths to consumers expecting base64 data.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/webui/browser_api.py, line 137:

<comment>api_screenshot promises a base64 image but can return a filesystem path when "path" is present, breaking the API contract and potentially leaking local paths to consumers expecting base64 data.</comment>

<file context>
@@ -0,0 +1,165 @@
+            if isinstance(out, dict) and "base64" in out:
+                return out["base64"]
+            if hasattr(out, "get"):
+                return out.get("base64") or out.get("path")
+        return None
+    except Exception as e:
</file context>
Fix with Cubic


# VNC Settings
- VNC_PASSWORD=${VNC_PASSWORD:-youvncpassword}
- VNC_PASSWORD=${VNC_PASSWORD:-}
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: noVNC is exposed on 0.0.0.0 while the default VNC_PASSWORD is empty, which results in a blank VNC password being generated and allows unauthenticated access to the VNC/noVNC UI from any host.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docker-compose.yml, line 68:

<comment>noVNC is exposed on 0.0.0.0 while the default VNC_PASSWORD is empty, which results in a blank VNC password being generated and allows unauthenticated access to the VNC/noVNC UI from any host.</comment>

<file context>
@@ -43,30 +44,35 @@ services:
 
       # VNC Settings
-      - VNC_PASSWORD=${VNC_PASSWORD:-youvncpassword}
+      - VNC_PASSWORD=${VNC_PASSWORD:-}
 
     volumes:
</file context>
Fix with Cubic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants