Skip to content

fix(computer-server): stop exposing an unauthenticated server to the network by default (#1892) - #1899

Open
ashutoshjoshi1 wants to merge 2 commits into
trycua:mainfrom
ashutoshjoshi1:fix/computer-server-default-bind-1892
Open

fix(computer-server): stop exposing an unauthenticated server to the network by default (#1892)#1899
ashutoshjoshi1 wants to merge 2 commits into
trycua:mainfrom
ashutoshjoshi1:fix/computer-server-default-bind-1892

Conversation

@ashutoshjoshi1

@ashutoshjoshi1 ashutoshjoshi1 commented Jun 15, 2026

Copy link
Copy Markdown

…network by default (#1892)

By default the server bound 0.0.0.0 with auth OFF whenever CONTAINER_NAME wasn't set (local mode). So anyone on your LAN — or any website you happened to visit — could run shell commands, read/write files, and open a PTY shell.

Now,
Local mode now binds 127.0.0.1
If you really want a public --host in local mode, you now have to opt in with CUA_ALLOW_INSECURE=1 Added an Origin check on /ws, /cmd and /pty so a random web page can't drive your local server.

Summary by CodeRabbit

  • Bug Fixes

    • Server now enforces local-only binding by default in local mode, preventing accidental exposure of the unauthenticated service on public interfaces. Override available via environment variable.
    • Added cross-site origin validation for sensitive API endpoints to block malicious cross-site browser requests.
  • Tests

    • Added comprehensive test coverage for secure binding and origin validation.

…network by default (trycua#1892)

By default the server bound 0.0.0.0 with auth OFF whenever CONTAINER_NAME wasn't set (local mode). So anyone on your LAN — or any website you happened to visit — could run shell commands, read/write files, and open a PTY shell.

Now,
Local mode now binds 127.0.0.1
If you really want a public --host in local mode, you now have to opt in with CUA_ALLOW_INSECURE=1
Added an Origin check on /ws, /cmd and /pty so a random web page can't drive your local server.
Copilot AI review requested due to automatic review settings June 15, 2026 15:33
@vercel

vercel Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

@ashutoshjoshi1 is attempting to deploy a commit to the Cua Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6e0957d6-698c-4957-92cd-d52caed19938

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds two security controls to computer-server: a fail-closed bind-host resolver that defaults to 127.0.0.1 in local mode (overridable via CUA_ALLOW_INSECURE=1 or CONTAINER_NAME), and a CrossSiteOriginGuard ASGI middleware that blocks browser cross-site requests to /ws, /cmd, and /pty with HTTP 403 or WebSocket close 1008. A new test module covers both controls.

Changes

Secure-by-default networking

Layer / File(s) Summary
Fail-closed bind-host resolution in CLI
libs/python/computer-server/computer_server/cli.py
Adds InsecureBindError, _env_truthy, _is_loopback_host, and resolve_bind_host() enforcing loopback-only binding in local mode. Updates --host to default=None with new help text. Updates main() to call resolve_bind_host(), handle InsecureBindError with exit, and pass the resolved host to Server.
CrossSiteOriginGuard ASGI middleware
libs/python/computer-server/computer_server/main.py
Adds CrossSiteOriginGuard class that parses the Origin header on requests to /ws, /cmd, /pty, and /pty/*, allows absent or loopback origins, and rejects cross-site and null origins with HTTP 403 or WebSocket close 1008. Registers the middleware on the FastAPI app.
Test coverage for both controls
libs/python/computer-server/tests/test_secure_bind.py
New test module with a clean_env fixture, TestResolveBindHost asserting fail-closed defaults and override behavior, an async _run_guard ASGI helper, and TestCrossSiteOriginGuard verifying HTTP 403, WebSocket close 1008, PTY subpath protection, null-origin rejection, and pass-through for unprotected paths.

Sequence Diagram(s)

sequenceDiagram
    participant Browser
    participant CrossSiteOriginGuard
    participant FastAPI
    participant Server

    rect rgba(255, 100, 100, 0.5)
        Note over Browser,Server: Startup — bind-host resolution
        Server->>Server: resolve_bind_host(args.host)
        alt local mode, no CONTAINER_NAME
            Server->>Server: bind to 127.0.0.1
        else CUA_ALLOW_INSECURE=1 or CONTAINER_NAME set
            Server->>Server: bind to requested_host or 0.0.0.0
        else insecure request in local mode
            Server->>Server: raise InsecureBindError → exit
        end
    end

    rect rgba(100, 100, 255, 0.5)
        Note over Browser,FastAPI: Runtime — cross-site origin guard
        Browser->>CrossSiteOriginGuard: Request to /cmd, /ws, /pty
        CrossSiteOriginGuard->>CrossSiteOriginGuard: Parse Origin header
        alt No Origin or loopback
            CrossSiteOriginGuard->>FastAPI: Forward request
            FastAPI->>Browser: Normal response
        else Cross-site or null Origin (HTTP)
            CrossSiteOriginGuard->>Browser: 403 JSON error
        else Cross-site or null Origin (WebSocket)
            CrossSiteOriginGuard->>Browser: WS close 1008
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Poem

🐇 Hoppin' through the network field,
I patched the gate so threats are healed.
Loopback first, the rule is clear,
Cross-site browsers? Not in here!
With 403 and close code eight,
No sneaky origins past my gate. 🔒

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the main security fix: preventing exposure of an unauthenticated server to the network by default, which is the primary objective of this changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds secure-by-default networking protections to prevent accidental public exposure of an unauthenticated server and to block cross-site browser requests from driving sensitive endpoints.

Changes:

  • Introduces resolve_bind_host (and InsecureBindError) to default binds to loopback in local/unauthenticated mode and require explicit opt-in for public interfaces.
  • Adds CrossSiteOriginGuard ASGI middleware to reject cross-site browser requests for shell/file/PTY surfaces while leaving /mcp and /status untouched.
  • Adds unit tests covering both bind-host resolution and cross-site origin guarding behavior.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
libs/python/computer-server/tests/test_secure_bind.py New tests for bind-host fail-closed behavior and cross-site origin guard behavior.
libs/python/computer-server/computer_server/main.py Adds CrossSiteOriginGuard middleware and wires it into the ASGI app.
libs/python/computer-server/computer_server/cli.py Implements secure bind-host resolution and updates CLI defaults/logging to use it.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread libs/python/computer-server/computer_server/cli.py Outdated
Comment thread libs/python/computer-server/tests/test_secure_bind.py
Comment thread libs/python/computer-server/computer_server/main.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
libs/python/computer-server/tests/test_secure_bind.py (2)

106-110: 💤 Low value

Consider adding IPv6 loopback origin test.

The parametrized test covers localhost and 127.0.0.1 but not the IPv6 loopback ::1. Adding this would ensure IPv6 loopback origins are correctly allowed.

💡 Suggested addition
-    `@pytest.mark.parametrize`("origin", ["http://localhost:3000", "http://127.0.0.1:8080"])
+    `@pytest.mark.parametrize`("origin", ["http://localhost:3000", "http://127.0.0.1:8080", "http://[::1]:8080"])
     async def test_loopback_origin_passes_through(self, origin):
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@libs/python/computer-server/tests/test_secure_bind.py` around lines 106 -
110, The test_loopback_origin_passes_through method parametrizes over only IPv4
loopback addresses (localhost and 127.0.0.1) but lacks coverage for IPv6
loopback. Add an IPv6 loopback origin (formatted as http://[::1]:PORT) to the
parametrize decorator's origin list to ensure IPv6 loopback origins are
correctly handled by the guard function.

48-55: ⚡ Quick win

Consider adding test for empty string host.

Given the potential security concern with empty string in _LOOPBACK_HOSTS, adding a test case for "" would help document the expected behavior and catch regressions.

💡 Suggested test addition
     `@pytest.mark.parametrize`("host", ["127.0.0.1", "localhost", "::1"])
     def test_explicit_loopback_allowed_in_local_mode(self, clean_env, host):
         assert resolve_bind_host(host) == host
 
+    def test_empty_string_host_defaults_to_loopback_in_local_mode(self, clean_env):
+        # Empty string should be treated as "no host specified" and default to loopback
+        # (not pass through as-is, which would bind to all interfaces)
+        result = resolve_bind_host("")
+        assert result == "127.0.0.1", "Empty host should default to loopback, not pass through"
+
     `@pytest.mark.parametrize`("host", ["0.0.0.0", "192.168.1.10", "::"])
     def test_explicit_public_host_refused_in_local_mode(self, clean_env, host):
         with pytest.raises(InsecureBindError):
             resolve_bind_host(host)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@libs/python/computer-server/tests/test_secure_bind.py` around lines 48 - 55,
Add a test case for empty string host to verify the behavior of
resolve_bind_host("") function, which will help document the expected behavior
and prevent regressions related to security concerns with empty strings in
_LOOPBACK_HOSTS. You can add this either as a separate test method or include it
as a parameter in the existing parametrized tests
(test_explicit_loopback_allowed_in_local_mode or
test_explicit_public_host_refused_in_local_mode, depending on whether empty
string should be allowed or rejected).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@libs/python/computer-server/computer_server/cli.py`:
- Around line 13-14: Remove the empty string from the _LOOPBACK_HOSTS frozenset
definition to eliminate the security bypass. The empty string should not be
considered a loopback host since socket binding resolves it to 0.0.0.0.
Additionally, add explicit handling for the empty string case in the
resolve_bind_host() function to ensure it is properly validated and rejected
with an InsecureBindError if passed as the host parameter, rather than allowing
it through as a loopback host.

---

Nitpick comments:
In `@libs/python/computer-server/tests/test_secure_bind.py`:
- Around line 106-110: The test_loopback_origin_passes_through method
parametrizes over only IPv4 loopback addresses (localhost and 127.0.0.1) but
lacks coverage for IPv6 loopback. Add an IPv6 loopback origin (formatted as
http://[::1]:PORT) to the parametrize decorator's origin list to ensure IPv6
loopback origins are correctly handled by the guard function.
- Around line 48-55: Add a test case for empty string host to verify the
behavior of resolve_bind_host("") function, which will help document the
expected behavior and prevent regressions related to security concerns with
empty strings in _LOOPBACK_HOSTS. You can add this either as a separate test
method or include it as a parameter in the existing parametrized tests
(test_explicit_loopback_allowed_in_local_mode or
test_explicit_public_host_refused_in_local_mode, depending on whether empty
string should be allowed or rejected).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b573e04a-a7ae-4c5f-968f-aeb105abbad7

📥 Commits

Reviewing files that changed from the base of the PR and between edbcf3b and ac0ef1c.

📒 Files selected for processing (3)
  • libs/python/computer-server/computer_server/cli.py
  • libs/python/computer-server/computer_server/main.py
  • libs/python/computer-server/tests/test_secure_bind.py

Comment thread libs/python/computer-server/computer_server/cli.py Outdated
… test skip scope)

Review follow-ups on the trycua#1892 hardening:
  - Drop "" from _LOOPBACK_HOSTS — an empty/whitespace host binds all interfaces
    (INADDR_ANY), so it's refused in local mode instead of treated as loopback.
  - Match the Origin header case-insensitively (key.lower()) so a server that
    doesn't normalize header casing can't slip a differently-cased Origin past
    the cross-site guard.
  - Narrow test import guards from `except Exception` to `except ImportError` so a
    real regression in cli/main fails the suite instead of silently skipping.
@ashutoshjoshi1

Copy link
Copy Markdown
Author

The failing check is the Vercel docs preview — this PR is Python-only (computer-server) and doesn't touch docs/

@f-trycua

Copy link
Copy Markdown
Collaborator

Heads up: the binding half of this shipped via #1845 (merged) — local mode now defaults to 127.0.0.1, closing the LAN-exposure vector. The valuable part still unique to this PR is the Origin check on /ws//cmd//pty (the "a random web page can drive your localhost" CSRF vector) plus the CUA_ALLOW_INSECURE=1 opt-in. Worth rebasing this onto main (it'll conflict on the binding default now) and getting its CI to actually run so the Origin-check hardening can land.

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.

3 participants