Skip to content

add networking policy interface and image build#99

Merged
Dingway98 merged 3 commits into
mainfrom
add-networking-and-image-build
Jul 8, 2026
Merged

add networking policy interface and image build#99
Dingway98 merged 3 commits into
mainfrom
add-networking-and-image-build

Conversation

@Dingway98

@Dingway98 Dingway98 commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Changes

Validation

  • ruff check
  • pytest

Note

Medium Risk
Touches sandbox networking (egress policy), large artifact uploads, and subprocess/Docker execution locally; streaming auth retry behavior changed for non-replayable bodies.

Overview
v0.93.0 expands the sandbox SDK with network egress controls, a Docker-to-Hyperbrowser image build pipeline, and chunked runtime file transfer, mirrored in sync and async managers.

Networking: New SandboxNetworkPolicy is exposed on sandbox detail and can be set at create time (allowInternetAccess, allowOut, denyOut). Running sandboxes support update_network / clear_network via PUT /sandbox/{id}/network, with local handle state updated after changes.

Custom images: Adds API wrappers for /images/builds (create, get, complete, cancel, poll) plus high-level build_image_from_dockerfile / build_image_from_docker_image that run local docker buildx / export / gzip packaging, upload the artifact, and optionally wait for completion. Shared helpers enforce linux/amd64, derive imageInit from container config, and clean up temp tags/containers.

Files & transport: upload_stream and download_stream send/receive bodies in chunks; runtime transport gains stream_bytes and skips token-refresh retries when request bodies are not replayable (streaming uploads).

API model alignment: list_images accepts filters/pagination; image/snapshot list responses include pagination metadata; snapshot listing supports multi-status, search, and limit ≤ 100; process wire fields accept camelCase aliases.

Reviewed by Cursor Bugbot for commit cfadb08. Bugbot is set up for automated code reviews on this repo. Configure here.

Comment thread hyperbrowser/client/managers/async_manager/sandbox.py
Comment thread hyperbrowser/client/managers/sandboxes/image_build.py

@devin-ai-integration devin-ai-integration 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.

Devin Review found 3 potential issues.

Open in Devin Review

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Streamed file upload sends empty data when authentication token expires mid-request

A streamed file upload silently sends empty content on retry (content=content at hyperbrowser/client/managers/async_manager/sandboxes/sandbox_transport.py:177) after the stream has already been fully consumed by the first attempt, so the uploaded file ends up empty.

Impact: Users uploading files via upload_stream will get a silently corrupted (empty) upload whenever the runtime token expires during the request.

Mechanism: async generator exhaustion on 401 retry path

The upload_stream method at hyperbrowser/client/managers/async_manager/sandboxes/sandbox_files.py:494 passes _aiter_stream_content(stream, ...) — an async generator — as the content parameter to request_json.

This flows through to _request at hyperbrowser/client/managers/async_manager/sandboxes/sandbox_transport.py:146-182. On the first call to _send (line 158-166), httpx consumes the async generator to send the request body. If the server returns 401 (expired token), the retry at lines 171-179 passes the same content object — now an exhausted async generator — to _send again. httpx iterates the exhausted generator and sends an empty body.

The PR widened the content type from Optional[Union[str, bytes]] (always replayable) to Optional[Any] (line 35) specifically to support generators, but the retry logic was not updated to account for non-replayable content.

(Refers to lines 168-180)

Prompt for agents
The _request method in the async RuntimeTransport retries the request on 401 by re-sending the same content parameter. When content is an async generator (as used by upload_stream via _aiter_stream_content), the generator is exhausted after the first request and the retry sends an empty body.

The same issue exists in the sync transport at hyperbrowser/client/managers/sync_manager/sandboxes/sandbox_transport.py:166-178.

Possible approaches:
1. Detect non-replayable content (generators/iterators) and skip the 401 retry, letting the error propagate.
2. Buffer the content into bytes before sending so it can be replayed.
3. Accept a content factory (callable) instead of raw content, so the stream can be recreated for retry.
4. Have upload_stream set allow_refresh=False or use a separate code path that doesn't retry.

Approach 1 is the simplest and safest — if the content is a generator, don't attempt to retry since the data can't be replayed.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Streamed file upload sends empty data when authentication token expires mid-request (sync client)

A streamed file upload silently sends empty content on retry (content=content at hyperbrowser/client/managers/sync_manager/sandboxes/sandbox_transport.py:175) after the stream has already been fully consumed by the first attempt, so the uploaded file ends up empty.

Impact: Users uploading files via the sync upload_stream will get a silently corrupted (empty) upload whenever the runtime token expires during the request.

Mechanism: sync generator exhaustion on 401 retry path

The sync upload_stream method at hyperbrowser/client/managers/sync_manager/sandboxes/sandbox_files.py:462 passes _iter_stream_content(stream, ...) — a generator — as the content parameter to request_json.

This flows through to _request at hyperbrowser/client/managers/sync_manager/sandboxes/sandbox_transport.py:144-180. On the first call to _send (line 156-164), httpx consumes the generator. If the server returns 401, the retry at lines 169-178 passes the same exhausted generator to _send again, sending an empty body.

The PR widened the content type from Optional[Union[str, bytes]] (always replayable) to Optional[Any] (line 35) to support generators, but the retry logic was not updated.

(Refers to lines 166-178)

Prompt for agents
The _request method in the sync RuntimeTransport retries the request on 401 by re-sending the same content parameter. When content is a generator (as used by upload_stream via _iter_stream_content), the generator is exhausted after the first request and the retry sends an empty body.

See the async counterpart at hyperbrowser/client/managers/async_manager/sandboxes/sandbox_transport.py:168-180 which has the same issue.

Possible approaches:
1. Detect non-replayable content (generators/iterators) and skip the 401 retry.
2. Buffer the content into bytes before sending.
3. Accept a content factory callable.
4. Have upload_stream use a separate code path that doesn't retry.

Approach 1 is simplest — check if content is a generator/iterator and skip retry.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

default=None,
exclude=None,
)
limit: Optional[int] = Field(default=None, ge=1, le=100)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 SandboxSnapshotListParams limit constraint reduced from unbounded to max 100

The limit field on SandboxSnapshotListParams now has le=100 constraint (sandbox.py:324), and the test helper SNAPSHOT_LIST_LIMIT was reduced from 200 to 100 (tests/helpers/sandbox.py:8). The e2e tests were also updated to use limit=100 instead of limit=200. This is a breaking change for any existing code that passes limit > 100 — it will now raise a Pydantic ValidationError at construction time rather than letting the API handle it. The same constraint was added to SandboxImageListParams.limit (sandbox.py:281). This seems intentional to match API-side limits.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes because the backend now cannot do beyond 100

@cursor cursor 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.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit d72874c. Configure here.

Comment thread hyperbrowser/client/managers/sandboxes/image_build.py
@Dingway98 Dingway98 enabled auto-merge July 8, 2026 04:13
@Dingway98 Dingway98 added this pull request to the merge queue Jul 8, 2026
@Dingway98 Dingway98 removed this pull request from the merge queue due to a manual request Jul 8, 2026
@Dingway98 Dingway98 added this pull request to the merge queue Jul 8, 2026
@Dingway98 Dingway98 removed this pull request from the merge queue due to a manual request Jul 8, 2026
@Dingway98 Dingway98 merged commit 1c1f8c0 into main Jul 8, 2026
3 checks passed
@Dingway98 Dingway98 deleted the add-networking-and-image-build branch July 8, 2026 04:15
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