Skip to content

Make SourceHunt sandbox execution pluggable - #200

Merged
ropoctl merged 4 commits into
mainfrom
feat/pluggable-sandbox-backends
Sep 3, 2026
Merged

Make SourceHunt sandbox execution pluggable#200
ropoctl merged 4 commits into
mainfrom
feat/pluggable-sandbox-backends

Conversation

@ropoctl

@ropoctl ropoctl commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Summary

  • introduce runtime-neutral sandbox backend and instance boundaries for SourceHunt
  • make Clearwing request closed logical environment profiles, features, and sanitizer support instead of images, distro packages, or setup commands
  • preserve Docker as the zero-configuration default, with image selection and package mapping private to that adapter
  • add a synchronous, concurrent JSON-RPC 2.0 adapter over Unix sockets and inherited Unix stream socket descriptors

The hunter model does not invent environment setup. Build-system detection deterministically maps repository markers to a closed profile and feature vocabulary. Environment preparation is separate from execution: it receives no repository or secrets, while project commands run afterward in a no-network sandbox.

The public start policy is also provider-neutral. It carries SourceHunt policy, semantic isolation level, resource limits, mounts, environment, and working directory; Docker network, seccomp, capability, image, and runtime flags never cross the RPC boundary.

An fd endpoint adopts the inherited connected socket. Clearwing closes that descriptor with the RPC connection, so the supervisor keeps only its side of the socketpair and can reliably observe disconnects.

Version 1 intentionally passes approved host paths and therefore targets local or shared-filesystem supervisors. Arbitrary remote workspace transport remains outside this contract.

Protocol

The backend prepares an environment and returns an opaque provider reference. Clearwing passes that reference back when starting a sandbox and never interprets it as an image, pod, filesystem path, or provider-native identifier.

JSON-RPC methods cover capabilities, capacity, environment ensure/release, sandbox start/exec/stop, file read/write, and tree copy. A single reader thread multiplexes concurrent synchronous calls by request id.

Validation

  • full non-Docker suite: 3,316 passed, 2 skipped
  • focused sandbox/remediation suite: 175 passed
  • focused Ruff checks and formatting passed
  • Docker integration tests remain unavailable in this environment because no Docker daemon is present

Repository-wide Ruff and mypy have pre-existing failures outside the changed sandbox files, so they are not claimed as clean here.

Coordination

The repository conflict-check skill was unavailable, so open pull requests were inspected manually. PR #190 has textual overlap in SourceHunt sandbox files for writable-workspace handling, but it does not introduce a competing backend/runtime abstraction.

@ropoctl
ropoctl requested a review from ehartford as a code owner September 1, 2026 18:35
@ropoctl ropoctl added the enhancement New feature or request label Sep 1, 2026
@ropoctl ropoctl changed the title Add pluggable SourceHunt sandbox backends Make SourceHunt sandbox execution pluggable Sep 1, 2026
- Docker "enhanced" isolation honors the operator-configured gVisor runtime
  (e.g. kata-runtime, runsc-kvm) instead of hardcoding "runsc", which could
  silently downgrade isolation to plain runc when a validated non-runsc
  runtime was requested. The runtime name stays a Docker-adapter detail and
  never crosses the neutral SandboxRunConfig or the RPC boundary.
- Buffer the JSON-RPC reader (makefile("rb")) so a large response no longer
  forces one recv() per byte, which pinned the shared reader thread and
  stalled every concurrent worker.
- Bound the Docker build with a wall-clock watchdog; the previous
  read-stdout-to-EOF-then-wait(timeout) path never bounded a build hung with
  stdout still open (e.g. a stalled base-image pull).
- Keep the shared JSON-RPC connection alive across one malformed or oversized
  response: a well-framed invalid frame fails only its matching call, and an
  oversized frame is drained to the next boundary instead of tearing down
  every in-flight and future call on the connection.
- Release the socket descriptor on every failure path via reader-thread
  teardown, not just the explicit close() path, fixing an fd leak on peer
  disconnect.
- Report a non-positive call timeout before writing the request so the backend
  does not execute the method's side effects for an already-abandoned call.

Adds regression tests for each fix.

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

lgtm with some minor feedback

Comment thread clearwing/sandbox/backend.py Outdated
Comment thread clearwing/sandbox/backend.py Outdated
@ropoctl
ropoctl merged commit e2db883 into main Sep 3, 2026
1 check passed
@ehartford

Copy link
Copy Markdown
Contributor

um ok

@ehartford

Copy link
Copy Markdown
Contributor

I was literally working on it.

@ehartford

Copy link
Copy Markdown
Contributor

never mind then, I guess you got it.

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

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants