Skip to content

Split NetworkSpec into outbound/inbound directions - #996

Open
G4614 wants to merge 3 commits into
boxlite-ai:mainfrom
G4614:codex/runner-guest-port-connect
Open

Split NetworkSpec into outbound/inbound directions#996
G4614 wants to merge 3 commits into
boxlite-ai:mainfrom
G4614:codex/runner-guest-port-connect

Conversation

@G4614

@G4614 G4614 commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Summary

NetworkSpec modeled guest egress only; whether a box's exposed services are publicly reachable had no field anywhere. This PR reshapes the core type into two directions, and adapts every dependent surface to compile — without exposing inbound configuration yet (that's #1206, stacked on this).

Before/after

BoxOptions.network: NetworkSpec::Enabled{allow_net}|Disabled   (options.rs)
  <- BUG: egress only; no inbound reachability concept anywhere in BoxOptions
BoxOptions.network: NetworkSpec{                               (options.rs)
  outbound: OutboundNetworkSpec::Enabled{allow_net}|Disabled,   — unchanged semantics
  inbound:  InboundNetworkSpec::Enabled{allow_net}|Disabled,    — new: Enabled=public (default), Disabled=private
}
NetworkSpec::try_from(NetworkConfig{outbound, inbound})         — one validation point
  ├─ rejects disabled outbound + allow_net (as before)
  ├─ rejects non-empty inbound allow_net ("not supported yet" — no enforcement sink exists)
  └─ legacy flat wire shape still deserializes, with a deprecation warning
BoxOptions::sanitize                                            — repeats the inbound allowlist check at create,
                                                                  catching FFI callers that build NetworkSpec directly

CLI/serve/REST client and the C/Node/Python bindings are adapted to compile against the new shape; Go is untouched (C ABI unchanged). Inbound stays at its default on every surface until #1206.

Stack

  1. this PR — core reshape + compile adaptations (15 files)
  2. Expose inbound network policy across CLI, SDKs, serve, and NetworkInfo #1206 — expose inbound across CLI/SDKs/serve + NetworkInfo read side (stacked on this)
  3. Align REST network DTO with inbound/outbound core shape #1199 — apps/api REST DTO alignment (independent workspace, no compile coupling; can merge before or after)

Test plan

  • boxlite core 999/999, boxlite-cli 191/191, C 72/72 (1 ignored), Node 21/21
  • Python: cargo check -p boxlite-python --tests clean (cargo test -p boxlite-python fails to link libpython on current main in this environment — pre-existing, verified against a clean origin/main worktree)
  • make fmt:check:rust clean

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR restructures network configuration into nested outbound and inbound policies across the core, REST API, C, Node, and Python SDKs. It adds inbound service-access configuration, Node box tunnel APIs, validation coverage, preview-access E2E tests, and a CLI Tunnel command.

Changes

Network policy and REST integration

Layer / File(s) Summary
REST network contract and validation
apps/api/src/boxlite-rest/dto/*, apps/api/src/boxlite-rest/mappers/*, apps/api/src/boxlite-rest/boxlite-box.controller.ts, scripts/test/e2e/cases/test_box_management.py
REST DTOs now use nested outbound/inbound policies, reject legacy flat fields, map service_access to control-plane state, audit nested network data, and test public/private preview responses.
Core network model and REST serialization
src/boxlite/src/lib.rs, src/boxlite/src/litebox/init/tasks/*, src/boxlite/src/rest/types.rs, sdks/c/src/options.rs, sdks/c/include/boxlite.h
Runtime initialization and REST serialization consume nested outbound/inbound options, while the C API adds service-access configuration with validation.
Node and Python nested network bindings
sdks/node/src/options.rs, sdks/node/lib/native-contracts.ts, sdks/python/src/options.rs, sdks/python/src/lib.rs, sdks/python/boxlite/__init__.py, sdks/node/tests/*, sdks/python/tests/*, sdks/node/README.md, sdks/python/README.md
Node and Python bindings expose nested network specifications, convert inbound service access, and update SDK tests and examples.
Node box tunnel API
sdks/node/lib/simplebox.ts, sdks/node/tests/network-secrets.integration.test.ts, sdks/node/tests/options.test.ts, sdks/node/tests/skillbox.integration.test.ts
Node adds NetworkHandle, BoxTunnel, SimpleBox.network, and SimpleBox.tunnel, alongside legacy network-shape validation and updated integration expectations.
CLI network compatibility and entry point
src/cli/src/commands/serve/types.rs, src/cli/src/commands/serve/mod.rs, src/cli/src/cli.rs
CLI network requests support nested and legacy forms, reject mixed inputs, map service access, and register a Tunnel subcommand.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Suggested reviewers: dorianzheng

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 48.58% 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
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.
Title check ✅ Passed The title clearly and concisely describes the main change: splitting NetworkSpec into outbound and inbound directions.
Description check ✅ Passed The description covers the summary, before-and-after behavior, scope, compatibility, stacking, and verification results in sufficient detail.
✨ 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.

@G4614
G4614 force-pushed the codex/runner-guest-port-connect branch from 8246d7d to e83feda Compare July 17, 2026 05:57
@G4614
G4614 changed the base branch from stack/shared-forwarded-proxy to main July 17, 2026 05:58
@G4614 G4614 mentioned this pull request Jul 17, 2026
3 tasks
@zombee0

zombee0 commented Jul 19, 2026

Copy link
Copy Markdown

Does the current implementation establish a new runner-to-box connection for every HTTP request? Does each request require re-authentication? Is the user base characterized by low concurrency? Is the performance acceptable?

@G4614 G4614 changed the title feat(runner): proxy guest service ports refactor(tunnel): stream guest ports over HTTP Jul 21, 2026
@G4614
G4614 marked this pull request as ready for review July 21, 2026 07:09
@G4614
G4614 requested a review from a team July 21, 2026 07:09
@boxlite-agent

boxlite-agent Bot commented Jul 21, 2026

Copy link
Copy Markdown

📦 BoxLite review — couldn't complete

claude exited 1

stdout:
{"is_error":true,"duration_api_ms":0,"num_turns":1,"stop_reason":"stop_sequence","session_id":"b30446d4-8c10-4147-97f6-4e7d4daa8cc1","total_cost_usd":0,"usage":{"output_tokens_details":{"thinking_tokens":0},"input_tokens":0,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":0,"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":0,"ephemeral_5m_input_tokens":0},"inference_geo":"","iterations":[],"speed":"standard"},"modelUsage":{},"permission_denials":[],"terminal_reason":"api_error","fast_mode_state":"off","fast_mode_disabled_reason":"sdk_opt_in_required","subtype":"success","api_error_status":403,"result":"Your organization has disabled Claude subscription access for Claude Code · Use an Anthropic API key instead, or ask your admin to enable access","type":"result","duration_ms":286,"uuid":"3ba48784-0496-464d-b13a-7de33c245501"}

stderr:
<empty>

powered by BoxLite

@G4614
G4614 enabled auto-merge July 21, 2026 07:10

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/boxlite/src/net/mod.rs (1)

651-673: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Stale comments after removing into_fd(). The test no longer recovers an fd, but lines 651–653 and the trailing line 672 still describe fd recoverability / "SDK fd-bridge relies on it," which no longer holds. Trim these so the test intent stays accurate.

🤖 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 `@src/boxlite/src/net/mod.rs` around lines 651 - 673, Remove the stale
fd-recovery and SDK fd-bridge claims from the test comments surrounding
BoxInternalTunnel::from_local. Keep comments describing the Unix socketpair,
bidirectional AsyncRead/AsyncWrite behavior, and peer handling, but delete the
trailing comment about recovering an owned OS fd.
🧹 Nitpick comments (6)
src/cli/src/commands/tunnel.rs (1)

22-50: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

No test coverage for the new tunnel command.

execute has no accompanying unit test in this batch (unlike the port-validation checks mirrored in the SDK tunnel() bindings, which do have tests). Consider adding at least a test for the port == 0 rejection and the BoxEndpoint::UnixSocket error path.

🤖 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 `@src/cli/src/commands/tunnel.rs` around lines 22 - 50, Add unit tests for the
tunnel command’s execute flow, covering port == 0 rejection and the
BoxEndpoint::UnixSocket error path. Reuse existing CLI test patterns and
fixtures/mocks around execute, asserting the validation error and remote REST
profile error respectively without changing production behavior.
sdks/python/tests/test_tunnel.py (1)

38-38: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add comprehensive docstrings to public test functions.

As per coding guidelines, comprehensive docstrings must be written for all public functions and classes. While these are test functions, they are public and should include a brief docstring explaining the test's purpose.

  • sdks/python/tests/test_tunnel.py#L38-L38: Add a docstring to test_endpoint_returns_stable_unix_socket_path.
  • sdks/python/tests/test_tunnel.py#L49-L49: Add a docstring to test_connect_opens_fresh_sockets.
  • sdks/python/tests/test_tunnel.py#L73-L73: Add a docstring to test_tunnel_requires_a_started_box.
  • sdks/python/tests/test_dev_tunnel_e2e.py#L136-L136: Add a docstring to test_local_box_tunnel_endpoint_and_repeated_connects.
  • sdks/python/tests/test_dev_tunnel_e2e.py#L152-L152: Add a docstring to test_dev_cloud_tunnel_endpoint_and_repeated_connects.
🤖 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 `@sdks/python/tests/test_tunnel.py` at line 38, Add brief purpose-focused
docstrings to test_endpoint_returns_stable_unix_socket_path,
test_connect_opens_fresh_sockets, and test_tunnel_requires_a_started_box in
sdks/python/tests/test_tunnel.py at lines 38-38, 49-49, and 73-73, respectively.
Also document test_local_box_tunnel_endpoint_and_repeated_connects and
test_dev_cloud_tunnel_endpoint_and_repeated_connects in
sdks/python/tests/test_dev_tunnel_e2e.py at lines 136-136 and 152-152,
describing the behavior each test verifies.

Source: Coding guidelines

sdks/python/boxlite/simplebox.py (1)

216-222: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove redundant attribute check.

Since self._network is unconditionally initialized in __init__ (line 138), the hasattr check here is redundant and can be simplified.

♻️ Proposed refactor
     `@property`
     def network(self) -> _SimpleBoxNetwork:
         """Get the box-scoped network handle."""
-        if not hasattr(self, "_network"):
-            self._network = _SimpleBoxNetwork(self)
         return self._network
🤖 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 `@sdks/python/boxlite/simplebox.py` around lines 216 - 222, Update the network
property to return the already-initialized self._network directly, removing the
redundant hasattr check and lazy _SimpleBoxNetwork construction while preserving
the existing _SimpleBoxNetwork handle.
sdks/python/boxlite/sync_api/_network.py (1)

18-26: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add type hints for connect and endpoint return values.

To match the BoxTunnel interface in simplebox.py, consider adding explicit return type hints (socket.socket and str) to these methods.

Add the socket import at the top of the file:

import socket
♻️ Proposed refactor
-    def connect(self):
+    def connect(self) -> socket.socket:
         """Open a blocking socket to the target service."""
         tunnel = self._box._sync(self._tunnel.connect())
         tunnel.setblocking(True)
         return tunnel
 
-    def endpoint(self):
+    def endpoint(self) -> str:
         """Return the cloud URL or local Unix socket path."""
         return self._box._sync(self._tunnel.endpoint())
🤖 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 `@sdks/python/boxlite/sync_api/_network.py` around lines 18 - 26, Update the
synchronous tunnel methods connect and endpoint with explicit return annotations
of socket.socket and str, respectively, and import socket in the module so the
annotations resolve.
apps/api/src/boxlite-rest/boxlite-proxy.controller.ts (1)

195-208: 🚀 Performance & Scalability | 🔵 Trivial

Re: reviewer question on per-request runner connections/re-auth.

Confirming from this code: yes — every octet-stream tunnel request flowing through proxyNetworkTunnelproxyToRunner performs a fresh boxService.findOneByIdOrName DB lookup, a fresh runnerService.findOne, and instantiates a brand-new createProxyMiddleware(...) (with its own outgoing connection to the runner) per HTTP request. This mirrors the pre-existing pattern used by proxyExec/proxyFiles/proxyMetrics, so it isn't a regression from this PR, but it does mean concurrency/performance for guest-port tunneling is bounded by this per-request overhead (DB round-trip + new outbound connection) rather than a persistently reused runner connection. Worth keeping in mind if/when the expected request rate per tunnel grows, since (unlike the WS proxy in boxlite-ws-proxy.service.ts, which builds one createProxyMiddleware instance in the constructor) this path builds a new middleware instance on every call.

Also applies to: 242-290

🤖 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 `@apps/api/src/boxlite-rest/boxlite-proxy.controller.ts` around lines 195 -
208, Review the per-request connection behavior in proxyNetworkTunnel and
proxyToRunner, including the related logic around the alternate tunnel path, and
avoid creating a fresh createProxyMiddleware instance and runner lookup for
every streaming request. Reuse a persistent proxy middleware/runner connection
pattern comparable to boxlite-ws-proxy.service.ts while preserving
authentication, request routing, and octet-stream validation.
apps/runner/pkg/api/controllers/proxy.go (1)

263-264: 🚀 Performance & Scalability | 🔵 Trivial

Per-request transport prevents connection reuse.

NewGuestPortTransport builds a fresh transport that dials a new guest tunnel per request, and defer transport.CloseIdleConnections() discards any keep-alive connection once the handler returns, so nothing is reused across requests. This is fine for the expected low-concurrency preview traffic called out in the PR discussion, but if request volume grows, consider caching a transport per (boxId, port) to amortize tunnel setup.

🤖 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 `@apps/runner/pkg/api/controllers/proxy.go` around lines 263 - 264, Update the
proxy handler around NewGuestPortTransport to reuse transports across requests
by caching them per (boxId, port) pair instead of creating and closing one for
every request. Ensure cached transports remain available for connection reuse
and are safely managed when boxes or ports are no longer needed.
🤖 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 `@apps/api/src/boxlite-rest/boxlite-proxy.controller.ts`:
- Around line 182-212: Update proxyNetworkTunnel to await this.startHint(boxId,
authContext) before either the streaming proxyToRunner path or the
getPortPreviewUrl path, matching the startup handling in proxyExec, proxyFiles,
and proxyMetrics. Ensure the notification occurs after validating the port and
before any guest-facing request can autostart the box.

In `@sdks/python/boxlite/sync_api/_network.py`:
- Around line 35-38: Update SyncBoxTunnel's tunnel method to validate port
before calling _owner._create_tunnel, ensuring it is a valid integer port within
the supported range and raising the established input-validation error for
invalid values. Preserve the existing tunnel creation and SyncBoxTunnel return
flow for valid ports, matching SimpleBox._create_tunnel behavior.

In `@sdks/python/boxlite/sync_api/_simplebox.py`:
- Around line 258-264: Update the docstring of SyncSimpleBox.tunnel to describe
returning a lazy SyncBoxTunnel handle, not immediately opening a socket; mention
that callers must explicitly invoke .connect() to obtain the socket, while
leaving the method behavior unchanged.

In `@src/boxlite/src/litebox/box_impl.rs`:
- Around line 75-90: Update the listener accept loop around
listener.accept().await so transient accept errors are logged and the loop
continues instead of breaking and terminating the accept task. Preserve the
existing client-handling spawn and tunnel error logging, while reserving loop
termination only for explicitly unrecoverable listener states if such handling
already exists.

---

Outside diff comments:
In `@src/boxlite/src/net/mod.rs`:
- Around line 651-673: Remove the stale fd-recovery and SDK fd-bridge claims
from the test comments surrounding BoxInternalTunnel::from_local. Keep comments
describing the Unix socketpair, bidirectional AsyncRead/AsyncWrite behavior, and
peer handling, but delete the trailing comment about recovering an owned OS fd.

---

Nitpick comments:
In `@apps/api/src/boxlite-rest/boxlite-proxy.controller.ts`:
- Around line 195-208: Review the per-request connection behavior in
proxyNetworkTunnel and proxyToRunner, including the related logic around the
alternate tunnel path, and avoid creating a fresh createProxyMiddleware instance
and runner lookup for every streaming request. Reuse a persistent proxy
middleware/runner connection pattern comparable to boxlite-ws-proxy.service.ts
while preserving authentication, request routing, and octet-stream validation.

In `@apps/runner/pkg/api/controllers/proxy.go`:
- Around line 263-264: Update the proxy handler around NewGuestPortTransport to
reuse transports across requests by caching them per (boxId, port) pair instead
of creating and closing one for every request. Ensure cached transports remain
available for connection reuse and are safely managed when boxes or ports are no
longer needed.

In `@sdks/python/boxlite/simplebox.py`:
- Around line 216-222: Update the network property to return the
already-initialized self._network directly, removing the redundant hasattr check
and lazy _SimpleBoxNetwork construction while preserving the existing
_SimpleBoxNetwork handle.

In `@sdks/python/boxlite/sync_api/_network.py`:
- Around line 18-26: Update the synchronous tunnel methods connect and endpoint
with explicit return annotations of socket.socket and str, respectively, and
import socket in the module so the annotations resolve.

In `@sdks/python/tests/test_tunnel.py`:
- Line 38: Add brief purpose-focused docstrings to
test_endpoint_returns_stable_unix_socket_path, test_connect_opens_fresh_sockets,
and test_tunnel_requires_a_started_box in sdks/python/tests/test_tunnel.py at
lines 38-38, 49-49, and 73-73, respectively. Also document
test_local_box_tunnel_endpoint_and_repeated_connects and
test_dev_cloud_tunnel_endpoint_and_repeated_connects in
sdks/python/tests/test_dev_tunnel_e2e.py at lines 136-136 and 152-152,
describing the behavior each test verifies.

In `@src/cli/src/commands/tunnel.rs`:
- Around line 22-50: Add unit tests for the tunnel command’s execute flow,
covering port == 0 rejection and the BoxEndpoint::UnixSocket error path. Reuse
existing CLI test patterns and fixtures/mocks around execute, asserting the
validation error and remote REST profile error respectively without changing
production behavior.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 56686a1b-9c8c-4da7-9b22-b8e5d3ba672d

📥 Commits

Reviewing files that changed from the base of the PR and between 2411669 and 5c79e3e.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (68)
  • apps/api-client-go/api/openapi.yaml
  • apps/api/src/box/dto/create-box.dto.ts
  • apps/api/src/box/dto/port-preview-url.dto.ts
  • apps/api/src/box/services/box.service.spec.ts
  • apps/api/src/box/services/box.service.ts
  • apps/api/src/boxlite-rest/boxlite-proxy.controller.spec.ts
  • apps/api/src/boxlite-rest/boxlite-proxy.controller.ts
  • apps/api/src/boxlite-rest/boxlite-rest-routing.spec.ts
  • apps/api/src/boxlite-rest/boxlite-ws-proxy.service.ts
  • apps/api/src/main.ts
  • apps/common-go/pkg/proxy/proxy.go
  • apps/common-go/pkg/proxy/proxy_test.go
  • apps/infra/sst.config.ts
  • apps/proxy/pkg/proxy/get_box_target.go
  • apps/proxy/pkg/proxy/get_box_target_test.go
  • apps/runner/pkg/api/controllers/proxy.go
  • apps/runner/pkg/api/controllers/proxy_integration_test.go
  • apps/runner/pkg/api/controllers/proxy_test.go
  • apps/runner/pkg/api/server.go
  • apps/runner/pkg/boxlite/guest_port_tunnel.go
  • openapi/box.openapi.yaml
  • scripts/build/build-runtime.sh
  • scripts/test/e2e/cases/test_node_tunnel.py
  • scripts/test/e2e/cases/test_sdk_tunnel.py
  • scripts/test/e2e/sdks/node/e2e_tunnel.ts
  • sdks/c/Cargo.toml
  • sdks/c/include/boxlite.h
  • sdks/c/src/lib.rs
  • sdks/c/src/network.rs
  • sdks/c/src/tests.rs
  • sdks/go/constants.go
  • sdks/go/tunnel.go
  • sdks/node/Cargo.toml
  • sdks/node/lib/index.ts
  • sdks/node/lib/native-contracts.ts
  • sdks/node/lib/simplebox.ts
  • sdks/node/src/box_handle.rs
  • sdks/node/src/lib.rs
  • sdks/node/src/network.rs
  • sdks/node/tests/tunnel.test.ts
  • sdks/python/Cargo.toml
  • sdks/python/boxlite/__init__.py
  • sdks/python/boxlite/simplebox.py
  • sdks/python/boxlite/sync_api/__init__.py
  • sdks/python/boxlite/sync_api/_box.py
  • sdks/python/boxlite/sync_api/_network.py
  • sdks/python/boxlite/sync_api/_simplebox.py
  • sdks/python/pytest.ini
  • sdks/python/src/box_handle.rs
  • sdks/python/src/lib.rs
  • sdks/python/src/network.rs
  • sdks/python/tests/test_dev_tunnel_e2e.py
  • sdks/python/tests/test_tunnel.py
  • src/boxlite/Cargo.toml
  • src/boxlite/src/litebox/box_impl.rs
  • src/boxlite/src/litebox/mod.rs
  • src/boxlite/src/litebox/network.rs
  • src/boxlite/src/net/gvproxy/services.rs
  • src/boxlite/src/net/mod.rs
  • src/boxlite/src/rest/client.rs
  • src/boxlite/src/rest/litebox.rs
  • src/boxlite/src/rest/runtime.rs
  • src/boxlite/src/runtime/backend.rs
  • src/boxlite/tests/gvproxy_backend.rs
  • src/cli/src/cli.rs
  • src/cli/src/commands/mod.rs
  • src/cli/src/commands/tunnel.rs
  • src/cli/src/main.rs
💤 Files with no reviewable changes (2)
  • src/boxlite/src/net/gvproxy/services.rs
  • src/boxlite/tests/gvproxy_backend.rs

Comment thread apps/api/src/boxlite-rest/boxlite-proxy.controller.ts
Comment thread sdks/python/boxlite/sync_api/_network.py
Comment thread sdks/python/boxlite/sync_api/_simplebox.py
Comment thread src/boxlite/src/litebox/box_impl.rs Outdated
@G4614 G4614 changed the title refactor(tunnel): stream guest ports over HTTP refactor(tunnel): route SDK streams through HTTP CONNECT Jul 21, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

♻️ Duplicate comments (1)
apps/api/src/boxlite-rest/boxlite-proxy.controller.ts (1)

186-205: ⚠️ Potential issue | 🟠 Major

Missing startHint autostart notification.

proxyNetworkTunnel is missing the startHint autostart notification. Every other guest-facing proxy method (proxyExec, proxyFiles, proxyMetrics) calls await this.startHint(boxId, authContext) before proxying. Without startHint, tunneling into a guest port on a stopped box will trigger an auto-start/re-stop race because the control plane's sync-states will promptly stop the box when it sees desiredState=STOPPED.

🐛 Proposed fix
     if (port < 1 || port > 65535) {
       return res.status(400).json({ error: 'port must be between 1 and 65535' })
     }
 
+    await this.startHint(boxId, authContext)
+
     const { url: uri } = await this.boxService.getPortPreviewUrl(boxId, authContext.organizationId, port)
🤖 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 `@apps/api/src/boxlite-rest/boxlite-proxy.controller.ts` around lines 186 -
205, Update proxyNetworkTunnel to await this.startHint(boxId, authContext)
before requesting the preview URL or issuing the tunnel ticket, matching the
existing proxyExec, proxyFiles, and proxyMetrics flows so stopped boxes receive
the autostart notification first.
🧹 Nitpick comments (2)
openapi/box.openapi.yaml (1)

1298-1313: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use snake_case for API response properties.

The BoxServiceEndpoint schema introduces connectUri in camelCase, which is inconsistent with the snake_case convention used across the rest of the API (e.g., box_id, created_at, disk_size_gb).

  • openapi/box.openapi.yaml#L1298-L1313: Rename connectUri to connect_uri.
  • apps/api/src/boxlite-rest/boxlite-proxy.controller.ts#L186-L205: Return connect_uri instead of connectUri in the JSON response payload.
  • apps/api/src/boxlite-rest/boxlite-proxy.controller.spec.ts#L78-L99: Update the test assertion to expect connect_uri.
🤖 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 `@openapi/box.openapi.yaml` around lines 1298 - 1313, Rename the
BoxServiceEndpoint schema property from connectUri to connect_uri in
openapi/box.openapi.yaml (1298-1313), update the boxlite-proxy controller
response to return connect_uri in
apps/api/src/boxlite-rest/boxlite-proxy.controller.ts (186-205), and change the
corresponding assertion in
apps/api/src/boxlite-rest/boxlite-proxy.controller.spec.ts (78-99) to expect
connect_uri.
apps/runner/pkg/api/controllers/proxy_test.go (1)

19-46: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Test doesn't verify proxyBidirectionalStream waits for both directions to finish.

The test runs proxyBidirectionalStream via go and never synchronizes on its return, so it can't detect the single-<-done-receive bug (see proxy.go lines 159-171): the function could return well before guest's pong is actually delivered and the test would still pass. Consider running it synchronously (or with a completion channel) and asserting it returns only after both writes/reads complete.

🤖 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 `@apps/runner/pkg/api/controllers/proxy_test.go` around lines 19 - 46, The test
currently does not synchronize with proxyBidirectionalStream, so it cannot
verify completion of both relay directions. Update
TestProxyBidirectionalStreamRelaysBothDirections to use a completion channel or
equivalent synchronization, wait for proxyBidirectionalStream to return after
both ping and pong exchanges, and assert completion occurs only after both
writes and reads finish.
🤖 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 `@apps/proxy/pkg/proxy/proxy.go`:
- Around line 200-204: Update the http.Server initialization in the proxy setup
to add a suitable ReadTimeout alongside ReadHeaderTimeout, so routed non-CONNECT
requests have an overall read deadline. Keep the existing Addr, Handler, and
header-timeout configuration unchanged.

In `@apps/proxy/pkg/proxy/tunnel.go`:
- Around line 94-98: Update the TLS configuration in the HTTPS branch of the
tunnel connection logic to explicitly set MinVersion to tls.VersionTLS12 (or the
project’s approved TLS 1.3 minimum) when constructing tls.Config. Keep the
existing ServerName behavior and non-HTTPS dialing path unchanged.
- Around line 130-137: Add a CloseWrite method to bufferedConn that detects
whether the embedded net.Conn supports CloseWrite() error and forwards the call,
returning the underlying result; otherwise return an appropriate
unsupported-operation error. This ensures proxyTunnelStreams can detect and
half-close buffered connections.
- Around line 78-101: Update dialRunnerTunnel to enforce an explicit bounded
timeout for the runner-tunnel connection and TLS handshake, rather than relying
solely on the incoming request context. Configure the net.Dialer used by both
plain and TLS dialing with the established tunnel setup timeout, preserving
context cancellation while ensuring unreachable or slow runners cannot block
indefinitely.

In `@apps/runner/pkg/api/controllers/proxy.go`:
- Around line 159-171: Both bidirectional tunnel helpers return after only one
copy direction completes. In apps/runner/pkg/api/controllers/proxy.go lines
159-171, update proxyBidirectionalStream to wait for both done signals before
returning; apply the identical change in apps/proxy/pkg/proxy/tunnel.go lines
139-151 for proxyTunnelStreams. Preserve the existing two-goroutine streaming
behavior and deferred connection cleanup.

---

Duplicate comments:
In `@apps/api/src/boxlite-rest/boxlite-proxy.controller.ts`:
- Around line 186-205: Update proxyNetworkTunnel to await this.startHint(boxId,
authContext) before requesting the preview URL or issuing the tunnel ticket,
matching the existing proxyExec, proxyFiles, and proxyMetrics flows so stopped
boxes receive the autostart notification first.

---

Nitpick comments:
In `@apps/runner/pkg/api/controllers/proxy_test.go`:
- Around line 19-46: The test currently does not synchronize with
proxyBidirectionalStream, so it cannot verify completion of both relay
directions. Update TestProxyBidirectionalStreamRelaysBothDirections to use a
completion channel or equivalent synchronization, wait for
proxyBidirectionalStream to return after both ping and pong exchanges, and
assert completion occurs only after both writes and reads finish.

In `@openapi/box.openapi.yaml`:
- Around line 1298-1313: Rename the BoxServiceEndpoint schema property from
connectUri to connect_uri in openapi/box.openapi.yaml (1298-1313), update the
boxlite-proxy controller response to return connect_uri in
apps/api/src/boxlite-rest/boxlite-proxy.controller.ts (186-205), and change the
corresponding assertion in
apps/api/src/boxlite-rest/boxlite-proxy.controller.spec.ts (78-99) to expect
connect_uri.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2d7ebc8b-d917-4dde-a7e8-9664c51c7fa8

📥 Commits

Reviewing files that changed from the base of the PR and between 5c79e3e and 4ad710c.

📒 Files selected for processing (25)
  • apps/api/src/boxlite-rest/boxlite-proxy.controller.spec.ts
  • apps/api/src/boxlite-rest/boxlite-proxy.controller.ts
  • apps/api/src/boxlite-rest/boxlite-rest.module.ts
  • apps/api/src/boxlite-rest/boxlite-tunnel-ticket.service.spec.ts
  • apps/api/src/boxlite-rest/boxlite-tunnel-ticket.service.ts
  • apps/api/src/config/configuration.ts
  • apps/common-go/pkg/cache/redis_cache.go
  • apps/infra/sst.config.ts
  • apps/proxy/pkg/proxy/proxy.go
  • apps/proxy/pkg/proxy/tunnel.go
  • apps/proxy/pkg/proxy/tunnel_test.go
  • apps/runner/pkg/api/controllers/proxy.go
  • apps/runner/pkg/api/controllers/proxy_test.go
  • apps/runner/pkg/api/server.go
  • openapi/box.openapi.yaml
  • sdks/c/include/boxlite.h
  • sdks/c/src/network.rs
  • sdks/go/tunnel.go
  • sdks/node/src/network.rs
  • sdks/python/src/network.rs
  • src/boxlite/Cargo.toml
  • src/boxlite/src/litebox/network.rs
  • src/boxlite/src/rest/client.rs
  • src/boxlite/src/rest/litebox.rs
  • src/cli/src/commands/tunnel.rs
🚧 Files skipped from review as they are similar to previous changes (10)
  • apps/runner/pkg/api/server.go
  • src/cli/src/commands/tunnel.rs
  • src/boxlite/Cargo.toml
  • src/boxlite/src/rest/litebox.rs
  • sdks/node/src/network.rs
  • sdks/c/include/boxlite.h
  • sdks/c/src/network.rs
  • sdks/go/tunnel.go
  • sdks/python/src/network.rs
  • src/boxlite/src/litebox/network.rs

Comment thread apps/proxy/pkg/proxy/proxy.go
Comment thread apps/proxy/pkg/proxy/tunnel.go Outdated
Comment thread apps/proxy/pkg/proxy/tunnel.go
Comment thread apps/proxy/pkg/proxy/tunnel.go Outdated
Comment thread apps/runner/pkg/api/controllers/proxy.go Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/proxy/pkg/proxy/proxy.go (1)

103-113: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Initialize tunnelTickets when Redis is disabled.

The new cache is assigned only in the config.Redis != nil branch, while connectAwareHandler routes CONNECT requests in both modes. In a no-Redis configuration, tunnel handling can therefore dereference a nil cache or reject every ticket.

 	} else {
 		proxy.boxRunnerCache = common_cache.NewMapCache[RunnerInfo](ctx)
 		proxy.runnerCache = common_cache.NewMapCache[RunnerInfo](ctx)
 		proxy.boxPublicCache = common_cache.NewMapCache[bool](ctx)
 		proxy.boxAuthKeyValidCache = common_cache.NewMapCache[bool](ctx)
 		proxy.boxLastActivityUpdateCache = common_cache.NewMapCache[bool](ctx)
+		proxy.tunnelTickets = common_cache.NewMapCache[TunnelTicket](ctx)
 	}
🤖 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 `@apps/proxy/pkg/proxy/proxy.go` around lines 103 - 113, Initialize
proxy.tunnelTickets in the config.Redis == nil branch using the in-memory cache
implementation, alongside the other MapCache fields. Ensure connectAwareHandler
can access a non-nil tunnelTickets cache in both Redis-enabled and no-Redis
configurations.
♻️ Duplicate comments (2)
apps/proxy/pkg/proxy/proxy.go (1)

200-203: 🩺 Stability & Availability | 🟠 Major

Add read deadlines to the HTTP server.

This remains unresolved from the previous review: the server has neither ReadHeaderTimeout nor ReadTimeout. Non-CONNECT requests delegated to router can hold connections open with slow request bodies, exhausting proxy resources. Choose values compatible with supported request sizes and CONNECT handshakes.

🤖 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 `@apps/proxy/pkg/proxy/proxy.go` around lines 200 - 203, Update the http.Server
initialization in the proxy handler setup to configure both ReadHeaderTimeout
and ReadTimeout with finite values. Choose timeouts that allow supported request
sizes and CONNECT handshakes while preventing slow non-CONNECT requests routed
through router from holding connections indefinitely.

Source: Linters/SAST tools

apps/api/src/boxlite-rest/boxlite-proxy.controller.ts (1)

186-204: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

proxyNetworkTunnel is missing the startHint autostart notification.

Every other guest-facing proxy method (proxyExec, proxyFiles, proxyMetrics) calls await this.startHint(boxId, authContext) before proxying, because a proxied call into a stopped box's runtime auto-starts the VM (Box::live_state()) without the control-plane DB knowing — without startHint, sync-states will see desiredState=STOPPED and re-stop the box shortly after tunnel traffic arrives. proxyNetworkTunnel skips this call, so tunneling into a guest port on a stopped box is likely to trigger the exact auto-start/re-stop race the ensureStartedForProxy docstring describes.

🐛 Proposed fix
     if (port < 1 || port > 65535) {
       return res.status(400).json({ error: 'port must be between 1 and 65535' })
     }
 
+    await this.startHint(boxId, authContext)
+
     const { url: uri } = await this.boxService.getPortPreviewUrl(boxId, authContext.organizationId, port)
🤖 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 `@apps/api/src/boxlite-rest/boxlite-proxy.controller.ts` around lines 186 -
204, Update proxyNetworkTunnel to await startHint(boxId, authContext) before
requesting the port preview URL or issuing the tunnel ticket, matching the
startup notification flow used by proxyExec, proxyFiles, and proxyMetrics.
🧹 Nitpick comments (2)
sdks/python/boxlite/sync_api/_simplebox.py (1)

258-273: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document the public networking API contract completely.

The lazy-tunnel wording is now correct, but both public APIs should document their arguments/return values and the RuntimeError raised before startup, as required by the SDK guidelines.

📝 Suggested documentation
     def tunnel(self, port: int):
-        """Return a lazy tunnel handle; call ``connect()`` to open its socket."""
+        """Return a lazy tunnel handle for a guest port.
+
+        Args:
+            port: Guest port to expose.
+
+        Returns:
+            A tunnel handle; call ``connect()`` to open its socket.
+
+        Raises:
+            RuntimeError: If the box has not been started.
+        """

     `@property`
     def network(self):
-        """Get the box-scoped network handle."""
+        """Return the box-scoped network handle.
+
+        Raises:
+            RuntimeError: If the box has not been started.
+        """
🤖 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 `@sdks/python/boxlite/sync_api/_simplebox.py` around lines 258 - 273, Complete
the docstrings for the public SyncSimpleBox.tunnel and SyncSimpleBox.network
APIs, documenting the tunnel port argument, returned handle/value, and the
RuntimeError raised when the box has not started. Preserve the existing lazy
tunnel behavior and startup validation.

Source: Coding guidelines

src/boxlite/src/rest/client.rs (1)

346-346: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Tower Service::call requires poll_ready.

Calling .call() directly on a tower::Service without first ensuring .poll_ready() returns Ready violates the Tower service contract. Although hyper_rustls::HttpsConnector happens to always be ready without blocking, it's safer and more idiomatic to use tower::ServiceExt::oneshot.

🛠️ Proposed fix

Import ServiceExt at the use-site and apply .oneshot():

-        let io = tokio::time::timeout(TUNNEL_SETUP_TIMEOUT, connector.call(uri.clone()))
+        use tower::ServiceExt;
+        let io = tokio::time::timeout(TUNNEL_SETUP_TIMEOUT, connector.oneshot(uri.clone()))
🤖 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 `@src/boxlite/src/rest/client.rs` at line 346, Update the tunnel setup flow
around the connector service to import and use Tower’s ServiceExt::oneshot
instead of calling connector.call directly, ensuring readiness is polled before
dispatch while preserving the existing timeout and URI handling.
🤖 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 `@apps/proxy/pkg/proxy/proxy.go`:
- Line 202: Update the CONNECT handling around proxy.handleTunnelConnect and
connectAwareHandler so established hijacked tunnels are registered with
shutdownWg before being served and released when they close, ensuring graceful
shutdown waits for active CONNECT streams. Preserve the existing handler routing
while covering all CONNECT tunnel lifetimes.

---

Outside diff comments:
In `@apps/proxy/pkg/proxy/proxy.go`:
- Around line 103-113: Initialize proxy.tunnelTickets in the config.Redis == nil
branch using the in-memory cache implementation, alongside the other MapCache
fields. Ensure connectAwareHandler can access a non-nil tunnelTickets cache in
both Redis-enabled and no-Redis configurations.

---

Duplicate comments:
In `@apps/api/src/boxlite-rest/boxlite-proxy.controller.ts`:
- Around line 186-204: Update proxyNetworkTunnel to await startHint(boxId,
authContext) before requesting the port preview URL or issuing the tunnel
ticket, matching the startup notification flow used by proxyExec, proxyFiles,
and proxyMetrics.

In `@apps/proxy/pkg/proxy/proxy.go`:
- Around line 200-203: Update the http.Server initialization in the proxy
handler setup to configure both ReadHeaderTimeout and ReadTimeout with finite
values. Choose timeouts that allow supported request sizes and CONNECT
handshakes while preventing slow non-CONNECT requests routed through router from
holding connections indefinitely.

---

Nitpick comments:
In `@sdks/python/boxlite/sync_api/_simplebox.py`:
- Around line 258-273: Complete the docstrings for the public
SyncSimpleBox.tunnel and SyncSimpleBox.network APIs, documenting the tunnel port
argument, returned handle/value, and the RuntimeError raised when the box has
not started. Preserve the existing lazy tunnel behavior and startup validation.

In `@src/boxlite/src/rest/client.rs`:
- Line 346: Update the tunnel setup flow around the connector service to import
and use Tower’s ServiceExt::oneshot instead of calling connector.call directly,
ensuring readiness is polled before dispatch while preserving the existing
timeout and URI handling.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7e5dd83d-bc5d-41b0-a7e8-ece7c7072532

📥 Commits

Reviewing files that changed from the base of the PR and between 4ad710c and c0be538.

📒 Files selected for processing (14)
  • apps/api/src/boxlite-rest/boxlite-proxy.controller.spec.ts
  • apps/api/src/boxlite-rest/boxlite-proxy.controller.ts
  • apps/proxy/pkg/proxy/proxy.go
  • apps/proxy/pkg/proxy/tunnel.go
  • apps/proxy/pkg/proxy/tunnel_test.go
  • apps/runner/pkg/api/controllers/proxy.go
  • apps/runner/pkg/api/controllers/proxy_test.go
  • openapi/box.openapi.yaml
  • sdks/python/boxlite/sync_api/_network.py
  • sdks/python/boxlite/sync_api/_simplebox.py
  • sdks/python/tests/test_tunnel.py
  • src/boxlite/Cargo.toml
  • src/boxlite/src/litebox/box_impl.rs
  • src/boxlite/src/rest/client.rs
🚧 Files skipped from review as they are similar to previous changes (9)
  • apps/proxy/pkg/proxy/tunnel_test.go
  • apps/api/src/boxlite-rest/boxlite-proxy.controller.spec.ts
  • sdks/python/boxlite/sync_api/_network.py
  • openapi/box.openapi.yaml
  • src/boxlite/src/litebox/box_impl.rs
  • apps/proxy/pkg/proxy/tunnel.go
  • src/boxlite/Cargo.toml
  • apps/runner/pkg/api/controllers/proxy_test.go
  • apps/runner/pkg/api/controllers/proxy.go

Comment thread apps/proxy/pkg/proxy/proxy.go Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/proxy/pkg/proxy/tunnel.go (1)

117-123: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Path is stripped from CONNECT requests, causing tunnel failure.

Go's http.Request.Write method specifically handles the CONNECT method by using req.URL.Host as the Request-URI and completely ignoring the path. Because req is created with "http://"+host+path, req.URL.Host is populated, and req.Write(conn) will output CONNECT {host} HTTP/1.1. The runner will never receive the /v1/boxes/... path and will likely return a 404 Not Found.

To preserve the path in the Request-URI while maintaining the Host header, initialize the request with just the path.

🐛 Proposed fix
-	path := fmt.Sprintf("/v1/boxes/%s/network/tunnel?port=%d", url.PathEscape(boxID), port)
-	req, err := http.NewRequestWithContext(ctx, http.MethodConnect, "http://"+host+path, nil)
+	path := fmt.Sprintf("/v1/boxes/%s/network/tunnel?port=%d", url.PathEscape(boxID), port)
+	req, err := http.NewRequestWithContext(ctx, http.MethodConnect, path, nil)
 	if err != nil {
 		conn.Close()
 		return nil, err
 	}
 	req.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 `@apps/proxy/pkg/proxy/tunnel.go` around lines 117 - 123, Update the CONNECT
request construction near req.Host so http.NewRequestWithContext receives only
path rather than "http://"+host+path, then retain host in req.Host. Preserve the
existing context, method, error cleanup, and escaped tunnel path so req.Write
sends the full path as the Request-URI.
🤖 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 `@apps/proxy/pkg/proxy/tunnel.go`:
- Around line 28-37: Fix handleTunnelConnect and tunnelTarget so a plain box ID
from the Host header cannot authorize a tunnel: require and validate a securely
signed preview token or restored Proxy-Authorization/ticket credential, reject
decoding or validation failures, and verify the requested port is marked public
before calling getBoxRunnerInfo or dialing the runner.
- Line 63: Update the tunnel setup around proxyTunnelStreams to wrap clientConn
with bufferedConn, matching the existing runner connection handling, and pass
the wrapped client connection into proxyTunnelStreams so bytes already held by
hijacker.Hijack’s buffered.Reader are forwarded.

---

Outside diff comments:
In `@apps/proxy/pkg/proxy/tunnel.go`:
- Around line 117-123: Update the CONNECT request construction near req.Host so
http.NewRequestWithContext receives only path rather than "http://"+host+path,
then retain host in req.Host. Preserve the existing context, method, error
cleanup, and escaped tunnel path so req.Write sends the full path as the
Request-URI.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 18693219-6bf9-4a71-8f2e-fb66e2643ec1

📥 Commits

Reviewing files that changed from the base of the PR and between dda5848 and 18617ec.

📒 Files selected for processing (9)
  • apps/api/src/boxlite-rest/boxlite-proxy.controller.spec.ts
  • apps/api/src/boxlite-rest/boxlite-proxy.controller.ts
  • apps/infra/sst.config.ts
  • apps/proxy/pkg/proxy/proxy.go
  • apps/proxy/pkg/proxy/tunnel.go
  • apps/proxy/pkg/proxy/tunnel_test.go
  • openapi/box.openapi.yaml
  • src/boxlite/src/rest/client.rs
  • src/boxlite/src/rest/litebox.rs
💤 Files with no reviewable changes (2)
  • apps/proxy/pkg/proxy/proxy.go
  • apps/infra/sst.config.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/boxlite/src/rest/litebox.rs

Comment thread apps/proxy/pkg/proxy/tunnel.go
Comment thread apps/proxy/pkg/proxy/tunnel.go Outdated
@coderabbitai coderabbitai Bot mentioned this pull request Jul 21, 2026
3 tasks
@G4614

G4614 commented Jul 21, 2026

Copy link
Copy Markdown
Contributor Author

Split into #992 (core/OpenAPI), #994 (SDKs and SDK E2E), and #995 (API/proxy/runner); all three branches are rebuilt from the latest main.

@G4614 G4614 closed this Jul 21, 2026
auto-merge was automatically disabled July 21, 2026 22:53

Pull request was closed

@G4614 G4614 reopened this Jul 22, 2026
@G4614
G4614 marked this pull request as draft July 22, 2026 04:13
@codecov

codecov Bot commented Jul 22, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@G4614
G4614 force-pushed the codex/runner-guest-port-connect branch from 894039b to 8f50bcd Compare July 22, 2026 08:59
@G4614
G4614 force-pushed the codex/runner-guest-port-connect branch from 8f50bcd to 75751a9 Compare July 23, 2026 05:09
@G4614 G4614 closed this Jul 23, 2026
@G4614
G4614 force-pushed the codex/runner-guest-port-connect branch from 75751a9 to b1ba38a Compare July 23, 2026 05:16
@G4614 G4614 reopened this Jul 23, 2026
@G4614
G4614 force-pushed the codex/runner-guest-port-connect branch from 1182eb8 to ae6a01e Compare July 23, 2026 05:54
@G4614
G4614 force-pushed the codex/runner-guest-port-connect branch from e4b106c to 1874416 Compare July 28, 2026 05:12
@G4614

G4614 commented Jul 28, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (4)
scripts/test/e2e/cases/test_box_management.py (1)

16-27: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Avoid blocking the event loop in the async helper.

_preview_public_status() calls synchronous urlopen(), so a slow or unreachable endpoint blocks the pytest event loop for up to 15 seconds. Run the request via asyncio.to_thread() or an existing async HTTP client.

🤖 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 `@scripts/test/e2e/cases/test_box_management.py` around lines 16 - 27, Update
the async helper _preview_public_status to avoid calling synchronous
urllib.request.urlopen directly on the event loop; run the blocking request and
its HTTPError handling through asyncio.to_thread, or use the project’s existing
async HTTP client, while preserving the returned status-code behavior.
sdks/node/lib/simplebox.ts (1)

297-320: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Add contextual handling for tunnel promise rejections.

NetworkHandle.tunnel() and BoxTunnel.connect() directly await native/runtime operations without try/catch. Route failures through a shared mapper or catch and rethrow with operation context while preserving the original error.

As per coding guidelines, “Use error handling with try/catch blocks for async operations and promise rejections.”

🤖 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 `@sdks/node/lib/simplebox.ts` around lines 297 - 320, Add try/catch handling to
NetworkHandle.tunnel and BoxTunnel.connect for their awaited native operations,
rethrowing failures with operation-specific context while preserving the
original error as the cause. Keep the existing tunnel wrapping behavior and
connection return behavior unchanged on success, and use a shared error mapper
if one already exists.

Source: Coding guidelines

sdks/node/tests/skillbox.integration.test.ts (1)

41-55: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Cover inbound service-access forwarding.

Add inbound: { serviceAccess: "private" } to both the input and expected options so this contract test verifies that SkillBox preserves the security-sensitive policy.

Suggested test update
       network: {
         outbound: {
           mode: "enabled",
           allowNet: ["example.com"],
         },
+        inbound: {
+          serviceAccess: "private",
+        },
       },
@@
       outbound: {
         mode: "enabled",
         allowNet: ["example.com"],
       },
+      inbound: {
+        serviceAccess: "private",
+      },

As per coding guidelines, “Write unit tests for all public functions and critical business logic.”

🤖 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 `@sdks/node/tests/skillbox.integration.test.ts` around lines 41 - 55, Update
the SkillBox options contract test around the box construction and opts.network
assertion to include inbound.serviceAccess set to "private" in both the input
configuration and expected forwarded options, verifying that SkillBox preserves
this security-sensitive policy.

Source: Coding guidelines

sdks/c/src/options.rs (1)

129-195: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document the new public SDK API surface. The new setters and Python-visible network classes are public entry points but lack comprehensive API documentation.

  • sdks/c/src/options.rs#L129-L195: add Rustdoc covering accepted inputs, null behavior, return codes, and lifecycle-option semantics.
  • sdks/c/include/boxlite.h#L724-L728: document units, zero/default behavior, and supported runtime behavior for each lifecycle setter.
  • sdks/python/src/options.rs#L259-L307: add Python-facing docstrings for OutboundNetworkSpec, InboundNetworkSpec, and their constructors.

As per coding guidelines, “Write comprehensive docstrings for all public functions and classes.”

🤖 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 `@sdks/c/src/options.rs` around lines 129 - 195, The public options APIs lack
documentation. In sdks/c/src/options.rs lines 129-195, add Rustdoc to the new
setters covering accepted inputs, null-pointer behavior, return codes, and
lifecycle semantics; in sdks/c/include/boxlite.h lines 724-728, document units,
zero/default behavior, and supported runtime behavior for each lifecycle setter;
in sdks/python/src/options.rs lines 259-307, add Python-facing docstrings for
OutboundNetworkSpec, InboundNetworkSpec, and both constructors.

Source: Coding guidelines

🤖 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 `@apps/api/src/boxlite-rest/dto/create-box.dto.ts`:
- Around line 69-78: Update the DTO validation for the network property and the
nested NetworkSpecDto fields outbound and inbound to include `@IsObject`(),
ensuring array-valued inputs are rejected before nested validation. Add
regression tests covering network: [], { outbound: [] }, and { inbound: [] }.

In `@sdks/node/lib/simplebox.ts`:
- Around line 403-417: Validate SimpleBoxOptions.network during construction
against the NetworkSpec shape before assigning it to _boxOpts.network. Reject
arrays and objects missing either outbound or inbound, while preserving the
existing legacy string and mode/allowNet validation errors.

In `@sdks/python/README.md`:
- Around line 252-253: Update the surrounding README documentation for disabled
network mode to use the nested policy shape, specifically
outbound.mode="disabled" or
NetworkSpec(outbound=OutboundNetworkSpec(mode="disabled")); remove any remaining
flat mode="disabled" example.

---

Nitpick comments:
In `@scripts/test/e2e/cases/test_box_management.py`:
- Around line 16-27: Update the async helper _preview_public_status to avoid
calling synchronous urllib.request.urlopen directly on the event loop; run the
blocking request and its HTTPError handling through asyncio.to_thread, or use
the project’s existing async HTTP client, while preserving the returned
status-code behavior.

In `@sdks/c/src/options.rs`:
- Around line 129-195: The public options APIs lack documentation. In
sdks/c/src/options.rs lines 129-195, add Rustdoc to the new setters covering
accepted inputs, null-pointer behavior, return codes, and lifecycle semantics;
in sdks/c/include/boxlite.h lines 724-728, document units, zero/default
behavior, and supported runtime behavior for each lifecycle setter; in
sdks/python/src/options.rs lines 259-307, add Python-facing docstrings for
OutboundNetworkSpec, InboundNetworkSpec, and both constructors.

In `@sdks/node/lib/simplebox.ts`:
- Around line 297-320: Add try/catch handling to NetworkHandle.tunnel and
BoxTunnel.connect for their awaited native operations, rethrowing failures with
operation-specific context while preserving the original error as the cause.
Keep the existing tunnel wrapping behavior and connection return behavior
unchanged on success, and use a shared error mapper if one already exists.

In `@sdks/node/tests/skillbox.integration.test.ts`:
- Around line 41-55: Update the SkillBox options contract test around the box
construction and opts.network assertion to include inbound.serviceAccess set to
"private" in both the input configuration and expected forwarded options,
verifying that SkillBox preserves this security-sensitive policy.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 64ad9200-a213-44a3-8696-813f98c6914b

📥 Commits

Reviewing files that changed from the base of the PR and between dda5848 and 1874416.

📒 Files selected for processing (28)
  • apps/api/src/boxlite-rest/boxlite-box.controller.ts
  • apps/api/src/boxlite-rest/dto/create-box.dto.spec.ts
  • apps/api/src/boxlite-rest/dto/create-box.dto.ts
  • apps/api/src/boxlite-rest/mappers/box-to-box.mapper.spec.ts
  • apps/api/src/boxlite-rest/mappers/box-to-box.mapper.ts
  • scripts/test/e2e/cases/test_box_management.py
  • sdks/c/README.md
  • sdks/c/include/boxlite.h
  • sdks/c/src/options.rs
  • sdks/node/README.md
  • sdks/node/lib/native-contracts.ts
  • sdks/node/lib/simplebox.ts
  • sdks/node/src/options.rs
  • sdks/node/tests/network-secrets.integration.test.ts
  • sdks/node/tests/options.test.ts
  • sdks/node/tests/skillbox.integration.test.ts
  • sdks/python/README.md
  • sdks/python/boxlite/__init__.py
  • sdks/python/src/lib.rs
  • sdks/python/src/options.rs
  • sdks/python/tests/test_network_spec.py
  • sdks/python/tests/test_secret_substitution.py
  • sdks/python/tests/test_tcp_filter.py
  • src/boxlite/src/lib.rs
  • src/boxlite/src/litebox/init/tasks/guest_init.rs
  • src/boxlite/src/litebox/init/tasks/vmm_attach.rs
  • src/boxlite/src/litebox/init/tasks/vmm_spawn.rs
  • src/boxlite/src/rest/types.rs

Comment thread apps/api/src/boxlite-rest/dto/create-box.dto.ts Outdated
Comment thread sdks/node/lib/simplebox.ts
Comment thread sdks/python/README.md Outdated
@G4614

G4614 commented Jul 28, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@boxlite-agent boxlite-agent Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📦 BoxLite review — 1 issue

Comment on lines +82 to +87
network: req.body?.network
? {
outbound: req.body.network.outbound,
inbound: req.body.network.inbound,
}
: undefined,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Audit log loses legacy-shape network payload
if a client sends the now-rejected legacy flat network shape (mode/allow_net/service_access), req.body.network.outbound/inbound are both undefined so the audit trail records no network info for that (failed) request instead of what was actually submitted.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 (1)
sdks/python/tests/test_network_spec.py (1)

17-26: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add regression coverage for inbound service access.

The new InboundNetworkSpec.service_access path is part of this PR’s public API, but this test only verifies outbound fields. Add an inbound assertion, ideally also through BoxOptions, to catch binding or conversion regressions.

Suggested test extension
         spec = boxlite.NetworkSpec(
             outbound=boxlite.OutboundNetworkSpec(
                 mode="enabled",
                 allow_net=["example.com", "*.openai.com"],
-            )
+            ),
+            inbound=boxlite.InboundNetworkSpec(service_access="public"),
         )
 
         assert spec.outbound.mode == "enabled"
         assert spec.outbound.allow_net == ["example.com", "*.openai.com"]
+        assert spec.inbound.service_access == "public"
🤖 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 `@sdks/python/tests/test_network_spec.py` around lines 17 - 26, Add regression
coverage for the new inbound service-access API in the NetworkSpec test:
construct an InboundNetworkSpec with service_access and assert the nested
NetworkSpec value, preferably exercising the same configuration through
BoxOptions to verify binding and conversion.
🤖 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 `@src/cli/src/commands/serve/types.rs`:
- Around line 90-93: Update the legacy configuration representation used by
uses_legacy_fields to store allow_net as Option<Vec<String>> and detect it with
is_some(), so an explicitly provided empty array is still considered legacy.
Apply the empty-list default only when constructing the runtime options, not
during presence detection.

---

Nitpick comments:
In `@sdks/python/tests/test_network_spec.py`:
- Around line 17-26: Add regression coverage for the new inbound service-access
API in the NetworkSpec test: construct an InboundNetworkSpec with service_access
and assert the nested NetworkSpec value, preferably exercising the same
configuration through BoxOptions to verify binding and conversion.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ee6b632f-ce93-4e7e-9467-95e68a5ee453

📥 Commits

Reviewing files that changed from the base of the PR and between 1874416 and 3f22de3.

📒 Files selected for processing (6)
  • sdks/c/include/boxlite.h
  • sdks/c/src/options.rs
  • sdks/python/src/options.rs
  • sdks/python/tests/test_network_spec.py
  • src/cli/src/commands/serve/mod.rs
  • src/cli/src/commands/serve/types.rs
🚧 Files skipped from review as they are similar to previous changes (3)
  • sdks/c/include/boxlite.h
  • sdks/python/src/options.rs
  • sdks/c/src/options.rs

Comment thread src/cli/src/commands/serve/types.rs Outdated
@G4614 G4614 added e2e-local Triggers the local (in-process) E2E suite on the self-hosted runner and removed e2e-local Triggers the local (in-process) E2E suite on the self-hosted runner labels Jul 28, 2026
@G4614
G4614 requested a review from DorianZheng July 28, 2026 06:21
@G4614 G4614 added e2e-local Triggers the local (in-process) E2E suite on the self-hosted runner and removed e2e-local Triggers the local (in-process) E2E suite on the self-hosted runner labels Jul 28, 2026
@G4614
G4614 force-pushed the codex/runner-guest-port-connect branch from 0204a4f to d44b224 Compare July 28, 2026 11:21
@G4614
G4614 requested a review from a team as a code owner July 28, 2026 12:13
@G4614
G4614 force-pushed the codex/runner-guest-port-connect branch 2 times, most recently from ff32d14 to 071136f Compare August 6, 2026 05:11
Comment thread src/boxlite/src/runtime/types.rs
NetworkSpec was a single enum modeling guest egress only; whether a
box's exposed services are publicly reachable had no field anywhere.
Reshape it into a struct with two directions:

Before:
  BoxOptions.network: NetworkSpec::Enabled{allow_net}|Disabled
    <- egress only; no inbound reachability concept
After:
  BoxOptions.network: NetworkSpec{
    outbound: OutboundNetworkSpec::Enabled{allow_net}|Disabled,
    inbound:  InboundNetworkSpec::Enabled{allow_net}|Disabled,
  }

Inbound: Enabled = publicly reachable (default), Disabled = private.
Its allow_net exists for shape symmetry but is rejected when non-empty
(try_from and sanitize) until a runtime sink enforces it. The legacy
flat wire shape still deserializes (untagged fallback) with a
deprecation warning.

CLI/serve/REST client and the C/Node/Python bindings are adapted to
compile against the new shape without exposing inbound configuration;
those surfaces follow in a separate PR. Go is untouched (C ABI
unchanged).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@G4614
G4614 force-pushed the codex/runner-guest-port-connect branch from dc10e6b to f8b14f8 Compare August 12, 2026 05:46
@G4614 G4614 changed the title feat(network): expose service access option Split NetworkSpec into outbound/inbound directions Aug 12, 2026
NetworkSpec::enabled/disabled read as whole-spec constructors but only
set the outbound direction, leaving inbound at its default — invisible
at the call site now that inbound exists. Rename to
outbound_enabled/outbound_disabled and document what each leaves
untouched.

Same problem in the messages: errors saying "network.mode" point
nested callers at a field that no longer exists. Say
network.outbound.mode where the check is outbound-specific, and make
NetworkMode::from_str's message direction-neutral — it parses both
directions now, so "invalid network.mode" was wrong for inbound input.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@G4614
G4614 force-pushed the codex/runner-guest-port-connect branch from 446390a to 321a06f Compare August 12, 2026 11:52
Turning NetworkSpec into a two-direction struct broke every pre-split
Rust caller: brace-variant literals and match arms have no struct
equivalent, so there was no migration short of editing each site.

Give the name back to the outbound enum instead — same name, same
variants, same shape — and call the container NetworkPolicy. Pre-split
literals and match arms now compile untouched; assigning one to
BoxOptions::network needs only .into(), via a new
From<NetworkSpec> for NetworkPolicy.

OutboundNetworkSpec survives as a direction-explicit alias for new code,
since the bare name reads as if it covered both directions.

network_spec.rs::pre_split_network_spec_source_shape_still_compiles
pins the contract: it exercises the old literal, match, and assignment
forms, so a future reshape that breaks them fails the build.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

e2e-local Triggers the local (in-process) E2E suite on the self-hosted runner

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants