Skip to content

Sandbox API - #3520

Merged
GregorShear merged 1 commit into
masterfrom
greg/sandbox-gql
Oct 5, 2026
Merged

GregorShear merged 1 commit into
masterfrom
greg/sandbox-gql

Conversation

@GregorShear

@GregorShear GregorShear commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Adds GraphQL API for Linux sandboxes backed by Fly.io Sprites. Users (and their agents) can create a sandbox with flowctl preinstalled, run shell commands asynchronously, inspect output, restore the initial baseline, and delete the sandbox.

  • Client launches a command in the sandbox and then may disconnect as soon as the command starts running.
  • The command runs indefinitely after client disconnects (setting max_run_after_disconnect=0 on the exec call to Sprite API)
  • stdin, stdout, and stderr remain inside the sandbox (explicitly not stored in the control plane DB) and are discarded on reset
  • Client may poll the stdout to watch the command progress, or poll for an exit code and read stdout all at once after the command has finished.

The main implementation is split across three files:

  • graphql/sandboxes.rs defines our sandboxes API and enforces authorization
  • src/sandboxes.rs implements sandbox lifecycle and behavior, including our shell scripts and persisted stdout/stderr
  • src/sprites.rs wraps the native Sprites APIs and handles transport and response decoding.
mutation {
  sandboxExecute(
    catalogName: "gregCo/my-sandbox"
    command: "for i in $(seq 100); do echo \"tick $i\"; sleep 1; done"
  ) {
    stdoutPath
  }
}
# {
#   "data": {
#     "sandboxExecute": {
#       "stdoutPath": ".estuary/command/162b42343380028d/stdout"
#     }
#   }
# }

query {
  sandboxFileRead(
    catalogName: "gregCo/my-sandbox"
    path: ".estuary/command/162b42343380028d/stdout" # ^^ from above
    offset: 0
    # limit: 100
  ) 
  {
    base64
    utf8
    offset
  }
}
# This command is still running...
# {
#   "data": {
#     "sandboxFileRead": {
#       "base64": "dGljayAxCnRpY2sgMgp0aWNrIDMKdGljayA0CnRpY2sgNQp0aWNrIDYKdGljayA3CnRpY2sgOAp0aWNrIDkKdGljayAxMAp0aWNrIDExCnRpY2sgMTIKdGljayAxMwo=",
#       "utf8": "tick 1\ntick 2\ntick 3\ntick 4\ntick 5\ntick 6\ntick 7\ntick 8\ntick 9\ntick 10\ntick 11\ntick 12\ntick 13\n",
#       "offset": 95
#     }
#   }
# }

There are some scenarios where a sprite could get orphaned, for example if the delete call to the sprites API fails when the user is deleting a sandbox - the control plane record gets removed, but we don't retry the sprite API. A reaper could clean up orphans, but that's future work.

Tests are also coming later, trying to keep this light for now.

@GregorShear
GregorShear force-pushed the greg/sandbox-gql branch 2 times, most recently from ce10c95 to 5791c96 Compare September 22, 2026 21:23
@GregorShear GregorShear changed the title Sandbox gql api Add sandbox creation, execution, inspection, and deletion Sep 22, 2026
@GregorShear
GregorShear added this pull request to stack #3522 September 22, 2026 21:41
@GregorShear GregorShear changed the title Add sandbox creation, execution, inspection, and deletion Add sandbox API backed by Fly.io Sprites Sep 23, 2026
@GregorShear
GregorShear removed this pull request from stack #3522 September 23, 2026 12:39
Comment thread crates/control-plane-api/src/sprites.rs Fixed
Comment thread crates/control-plane-api/src/sprites.rs Fixed
@GregorShear GregorShear changed the title Add sandbox API backed by Fly.io Sprites Sandbox API Sep 23, 2026
@jshearer
jshearer marked this pull request as ready for review October 5, 2026 16:22
@strix-security

strix-security Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

Strix Security Review

No security issues found.

Review summary

Reviewed the Sandbox API end-to-end: the GraphQL surface and authorization wiring (graphql/sandboxes.rs), the sandbox lifecycle/scripting layer (sandboxes.rs), the Fly.io Sprites HTTP/WebSocket client (sprites.rs), plus the SQL migration, .sqlx query metadata, and the capability addition (CreateSandbox).

Authorization is enforced correctly. sandboxCreate requires the CreateSandbox capability (bundled only under Admin) verified against the catalog_name prefix via verify_authorization; every other mutation and query (sandboxExecute, sandboxCancel, sandboxReset, sandboxDelete, sandboxFileRead, sandbox, sandboxes) resolves the sandbox by user_id == claims.sub, so only the creating user can act on their own sandbox. All resolvers call env.claims(), blocking unauthenticated access, and the Sprites client fails closed when SPRITES_TOKEN is unset.

The CodeQL SSRF flags at sprites.rs:101 and sprites.rs:121 are false positives: the URL path segment name is always the server-generated sprite handle (sbx- + a macaddr8 id), never attacker-controlled. The only user-influenced URL component is the session_id read back in sandboxCancel, but it appears in a path segment after the fixed name segment against the fixed api.sprites.dev host, so it cannot redirect to another tenant or host. The free-form path in sandboxFileRead can traverse within the sandbox VM, but that provides no capability beyond the arbitrary shell execution sandboxExecute already grants to the same user in the same sprite. All SQL uses sqlx macros with bound parameters; no secrets are committed; the Sprites token is not logged; and command output is bounded (1 MiB per file read, 2 MiB collected). No security issues found.

Updated for 4e6e1f0.


Reviewed by Strix
Re-run review 路 Configure security review settings

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

We talked offline and you addressed the feedback I had. LGTM!

@GregorShear
GregorShear merged commit e6d0bd2 into master Oct 5, 2026
7 of 11 checks passed
@GregorShear
GregorShear deleted the greg/sandbox-gql branch October 5, 2026 21:51
@github-actions github-actions Bot added pending:migration Merged, contains a database migration awaiting application pending:agent Merged, in the control-plane-agent image, and not yet rolled to flow-agent pending:agent-api Merged, ships via Deploy agent-api, and not yet deployed pending:flowctl Merged, changes the flowctl binary, and not in a published release labels Oct 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pending:agent Merged, in the control-plane-agent image, and not yet rolled to flow-agent pending:agent-api Merged, ships via Deploy agent-api, and not yet deployed pending:flowctl Merged, changes the flowctl binary, and not in a published release pending:migration Merged, contains a database migration awaiting application

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants