Skip to content

Add databricks sandbox forward - #7395

Open
jaecho-db wants to merge 9 commits into
mainfrom
jaecho-db/sandbox-port-forward
Open

jaecho-db wants to merge 9 commits into
mainfrom
jaecho-db/sandbox-port-forward

Conversation

@jaecho-db

@jaecho-db jaecho-db commented Nov 7, 2026 •

Copy link
Copy Markdown

Changes

Add databricks sandbox forward [sandbox-id] -L LOCAL:REMOTE.... Each -L serves http://127.0.0.1:LOCAL and relays HTTP and WebSocket traffic to the app on sandbox port REMOTE through the workspace's /sandbox-proxy/sandboxes/<id>/ports/<port>/ route, authenticated as the caller. Without an ID it uses the default sandbox.

Every forwarded request carries the caller's credentials, so a forward:

  • binds to loopback and only accepts requests addressed to localhost;
  • refuses cross-site browser requests (by Origin and Sec-Fetch-Site), so neither a DNS-rebinding page nor a page on another site can use it, including over WebSockets;
  • replaces the local client's Authorization, X-Databricks-*, and S2S-* headers with the CLI's;
  • forwards a path only if each segment decodes once to plain text other than . or .., so every request stays on the port route. Segments with ;, a backslash, a control character, a malformed escape, or an encoded /, \, %, ?, or # get a local 400, the same rule as the Python SDK's forward.

Remote ports outside 10000-65535 are rejected up front, and a stopped sandbox is started before forwarding, like sandbox ssh.

Replaces #7367, which was opened from a fork.

Why

Users can open apps running in their sandbox without setting up SSH. This is the CLI counterpart of the Python SDK's SandboxExt.forward (renamed from port_forward). The command is named forward, not port-forward, because it carries HTTP and WebSocket only for now. That leaves room for TCP support later without a new command.

Tests

  • Unit tests for -L parsing, the request rewrite and its guards, WebSocket relay, auth failures, and sandbox resolution. Acceptance tests for argument and lookup errors.
  • Manually against a staging workspace: HTTP and WebSocket traffic, credential stripping, refused ports and hosts, an unknown sandbox, and Ctrl-C.

This pull request and its description were written by Isaac.

@jaecho-db
jaecho-db marked this pull request as ready for review November 7, 2026 07:00
@jaecho-db
jaecho-db requested review from a team as code owners November 7, 2026 07:00
@jaecho-db
jaecho-db requested a review from rugpanov November 7, 2026 07:00
@github-actions

github-actions Bot commented Nov 7, 2026 •

Copy link
Copy Markdown
Contributor

Commit: d2f31c689 | Workflow run

Regression test report

Selected 4 changed tests (limit=20, 0 not selected)

4 tests: regression=4 unreleased=0 coverage=0 failing=0
test branch main (a572447) latest (v1.26.0)
TestAccept/cmd/sandbox/forward/forward ✅ ❌ ❌
TestAccept/cmd/sandbox/forward/invalid-forward ✅ ❌ ❌
TestAccept/cmd/sandbox/forward/no-default ✅ ❌ ❌
TestAccept/cmd/sandbox/forward/not-found ✅ ❌ ❌
TestAccept/cmd/sandbox/forward/forward: branch ✅ | main (a572447) ❌ | latest (v1.26.0) ❌

main (a572447):

acceptance_test.go:1373: Diff:
        --- cmd/sandbox/forward/forward/output.txt
        +++ /tmp/TestAcceptcmdsandboxforwardforward1139534726/002/output.txt
        @@ -2,27 +2,35 @@
         === The command reports the forward once it is listening
         
         >>> retry --until Press Ctrl-C to stop. cat LOG.forward
        -Forwarding http://127.0.0.1:[LOCAL_PORT] -> happy-panda-1234:10080
        -Press Ctrl-C to stop.
        +Error: unknown shorthand flag: 'L' in -L
         
        -=== A local request reaches the sandbox app through the forward route
        +Usage:
        +  databricks sandbox [command]
         
        ->>> curl -sS -X POST -H X-Databricks-Org-Id: 999 -d ping http://127.0.0.1:[LOCAL_PORT]/api/items?page=2
        -hello from the sandbox app
        +Available Commands:
        +  config      Configure a Sandbox's name and auto-stop policy
        +  create      Create a new Sandbox environment
        +  default     Set the default Sandbox for SSH
        +  delete      Delete a Sandbox environment
        +  list        List your Sandbox environments
        +  register    Register this machine for sandbox SSH access
        +  ssh         SSH into a Sandbox environment
        +  ssh-key     Manage SSH keys registered with the sandbox service
        +  start       Start a stopped Sandbox environment
        +  status      Show Sandbox environment status
        +  stop        Stop a running Sandbox environment
         
        -=== The workspace receives the CLI's routing header in place of the caller's
        +Flags:
        +  -h, --help   help for sandbox
         
        ->>> print_requests.py //sandbox-proxy
        -{
        -  "headers": {
        -    "X-Databricks-Org-Id": [
        -      "[WORKSPACE_ID]"
        -    ]
        -  },
        -  "method": "POST",
        -  "path": "/sandbox-proxy/sandboxes/happy-panda-1234/ports/10080/api/items",
        -  "q": {
        -    "page": "2"
        -  },
        -  "raw_body": "ping"
        -}
        +Global Flags:
        +      --debug            enable debug logging
        +  -o, --output type      output type: text or json (default text)
        +  -p, --profile string   ~/.databrickscfg profile
        +  -t, --target string    bundle target to use (if applicable)
        +
        +Use "databricks sandbox [command] --help" for more information about a command.
        +
        +retry: condition not met after 30 attempts
        +
        +Exit code: 1
        
    acceptance_test.go:1151: 
        LOG.forward: Error: unknown shorthand flag: 'L' in -L
        LOG.forward: 
        LOG.forward: Usage:
        LOG.forward:   databricks sandbox [command]
        LOG.forward: 
        LOG.forward: Available Commands:
        LOG.forward:   config      Configure a Sandbox's name and auto-stop policy
        LOG.forward:   create      Create a new Sandbox environment
        LOG.forward:   default     Set the default Sandbox for SSH
        LOG.forward:   delete      Delete a Sandbox environment
        LOG.forward:   list        List your Sandbox environments
        LOG.forward:   register    Register this machine for sandbox SSH access
        LOG.forward:   ssh         SSH into a Sandbox environment
        LOG.forward:   ssh-key     Manage SSH keys registered with the sandbox service
        LOG.forward:   start       Start a stopped Sandbox environment
        LOG.forward:   status      Show Sandbox environment status
        LOG.forward:   stop        Stop a running Sandbox environment
        LOG.forward: 
        LOG.forward: Flags:
        LOG.forward:   -h, --help   help for sandbox
        LOG.forward: 
        LOG.forward: Global Flags:
        LOG.forward:       --debug            enable debug logging
        LOG.forward:   -o, --output type      output type: text or json (default text)
        LOG.forward:   -p, --profile string   ~/.databrickscfg profile
        LOG.forward:   -t, --target string    bundle target to use (if applicable)
        LOG.forward: 
        LOG.forward: Use "databricks sandbox [command] --help" for more information about a command.
FAIL

latest (v1.26.0): same output as main (a572447)

TestAccept/cmd/sandbox/forward/invalid-forward: branch ✅ | main (a572447) ❌ | latest (v1.26.0) ❌

main (a572447):

acceptance_test.go:1373: Diff:
        --- cmd/sandbox/forward/invalid-forward/output.txt
        +++ /tmp/TestAcceptcmdsandboxforwardinvalid-forward2165609123/002/output.txt
        @@ -1,3 +1,108 @@
        -Error: invalid -L "8080": expected LOCAL:REMOTE ports, e.g. 8080:10080
        -Error: invalid -L "8080:8080": remote port must be between 10000 and 65535
        -Error: required flag(s) "forward" not set
        +Error: unknown shorthand flag: 'L' in -L
        +
        +Usage:
        +  databricks sandbox [command]
        +
        +Available Commands:
        +  config      Configure a Sandbox's name and auto-stop policy
        +  create      Create a new Sandbox environment
        +  default     Set the default Sandbox for SSH
        +  delete      Delete a Sandbox environment
        +  list        List your Sandbox environments
        +  register    Register this machine for sandbox SSH access
        +  ssh         SSH into a Sandbox environment
        +  ssh-key     Manage SSH keys registered with the sandbox service
        +  start       Start a stopped Sandbox environment
        +  status      Show Sandbox environment status
        +  stop        Stop a running Sandbox environment
        +
        +Flags:
        +  -h, --help   help for sandbox
        +
        +Global Flags:
        +      --debug            enable debug logging
        +  -o, --output type      output type: text or json (default text)
        +  -p, --profile string   ~/.databrickscfg profile
        +  -t, --target string    bundle target to use (if applicable)
        +
        +Use "databricks sandbox [command] --help" for more information about a command.
        +
        +Error: unknown shorthand flag: 'L' in -L
        +
        +Usage:
        +  databricks sandbox [command]
        +
        +Available Commands:
        +  config      Configure a Sandbox's name and auto-stop policy
        +  create      Create a new Sandbox environment
        +  default     Set the default Sandbox for SSH
        +  delete      Delete a Sandbox environment
        +  list        List your Sandbox environments
        +  register    Register this machine for sandbox SSH access
        +  ssh         SSH into a Sandbox environment
        +  ssh-key     Manage SSH keys registered with the sandbox service
        +  start       Start a stopped Sandbox environment
        +  status      Show Sandbox environment status
        +  stop        Stop a running Sandbox environment
        +
        +Flags:
        +  -h, --help   help for sandbox
        +
        +Global Flags:
        +      --debug            enable debug logging
        +  -o, --output type      output type: text or json (default text)
        +  -p, --profile string   ~/.databrickscfg profile
        +  -t, --target string    bundle target to use (if applicable)
        +
        +Use "databricks sandbox [command] --help" for more information about a command.
        +
        +Manage Databricks Sandbox environments.
        +
        +Sandbox provides SSH-accessible development environments backed by
        +microVM isolation. Each sandbox is a personal sandbox with pre-installed
        +tooling (Python, Node.js, Rust, Databricks CLI) and persistent storage.
        +
        +Getting started:
        +  databricks auth login --host https://...   # authenticate to a Databricks workspace
        +  databricks sandbox register                # generate and register an SSH key
        +  databricks sandbox create                  # provision a sandbox (becomes your default)
        +  databricks sandbox ssh                     # SSH to your default sandbox
        +
        +Common workflows:
        +  databricks sandbox ssh                     # SSH to your default sandbox
        +  databricks sandbox ssh my-project          # SSH to a named sandbox
        +  databricks sandbox list                    # list your sandboxes
        +  databricks sandbox create                  # create a new sandbox
        +  databricks sandbox delete my-project       # delete a sandbox
        +  databricks sandbox status my-project       # show sandbox status
        +
        +Usage:
        +  databricks sandbox [command]
        +
        +Available Commands:
        +  config      Configure a Sandbox's name and auto-stop policy
        +  create      Create a new Sandbox environment
        +  default     Set the default Sandbox for SSH
        +  delete      Delete a Sandbox environment
        +  list        List your Sandbox environments
        +  register    Register this machine for sandbox SSH access
        +  ssh         SSH into a Sandbox environment
        +  ssh-key     Manage SSH keys registered with the sandbox service
        +  start       Start a stopped Sandbox environment
        +  status      Show Sandbox environment status
        +  stop        Stop a running Sandbox environment
        +
        +Flags:
        +  -h, --help   help for sandbox
        +
        +Global Flags:
        +      --debug            enable debug logging
        +  -o, --output type      output type: text or json (default text)
        +  -p, --profile string   ~/.databrickscfg profile
        +  -t, --target string    bundle target to use (if applicable)
        +
        +Use "databricks sandbox [command] --help" for more information about a command.
        +
        +Unexpected success
        +
        +Exit code: 1

latest (v1.26.0): same output as main (a572447)

TestAccept/cmd/sandbox/forward/no-default: branch ✅ | main (a572447) ❌ | latest (v1.26.0) ❌

main (a572447):

acceptance_test.go:1373: Diff:
        --- cmd/sandbox/forward/no-default/output.txt
        +++ /tmp/TestAcceptcmdsandboxforwardno-default985660670/002/output.txt
        @@ -1 +1,29 @@
        -Error: no default sandbox configured — run `databricks sandbox create` to provision one, or `databricks sandbox default <id>` to set an existing sandbox as the default
        +Error: unknown shorthand flag: 'L' in -L
        +
        +Usage:
        +  databricks sandbox [command]
        +
        +Available Commands:
        +  config      Configure a Sandbox's name and auto-stop policy
        +  create      Create a new Sandbox environment
        +  default     Set the default Sandbox for SSH
        +  delete      Delete a Sandbox environment
        +  list        List your Sandbox environments
        +  register    Register this machine for sandbox SSH access
        +  ssh         SSH into a Sandbox environment
        +  ssh-key     Manage SSH keys registered with the sandbox service
        +  start       Start a stopped Sandbox environment
        +  status      Show Sandbox environment status
        +  stop        Stop a running Sandbox environment
        +
        +Flags:
        +  -h, --help   help for sandbox
        +
        +Global Flags:
        +      --debug            enable debug logging
        +  -o, --output type      output type: text or json (default text)
        +  -p, --profile string   ~/.databrickscfg profile
        +  -t, --target string    bundle target to use (if applicable)
        +
        +Use "databricks sandbox [command] --help" for more information about a command.
        +

latest (v1.26.0): same output as main (a572447)

TestAccept/cmd/sandbox/forward/not-found: branch ✅ | main (a572447) ❌ | latest (v1.26.0) ❌

main (a572447):

acceptance_test.go:1373: Diff:
        --- cmd/sandbox/forward/not-found/output.txt
        +++ /tmp/TestAcceptcmdsandboxforwardnot-found954983308/002/output.txt
        @@ -1 +1,29 @@
        -Error: no sandbox named "no-such-box" — `databricks sandbox list` shows available IDs
        +Error: unknown shorthand flag: 'L' in -L
        +
        +Usage:
        +  databricks sandbox [command]
        +
        +Available Commands:
        +  config      Configure a Sandbox's name and auto-stop policy
        +  create      Create a new Sandbox environment
        +  default     Set the default Sandbox for SSH
        +  delete      Delete a Sandbox environment
        +  list        List your Sandbox environments
        +  register    Register this machine for sandbox SSH access
        +  ssh         SSH into a Sandbox environment
        +  ssh-key     Manage SSH keys registered with the sandbox service
        +  start       Start a stopped Sandbox environment
        +  status      Show Sandbox environment status
        +  stop        Stop a running Sandbox environment
        +
        +Flags:
        +  -h, --help   help for sandbox
        +
        +Global Flags:
        +      --debug            enable debug logging
        +  -o, --output type      output type: text or json (default text)
        +  -p, --profile string   ~/.databrickscfg profile
        +  -t, --target string    bundle target to use (if applicable)
        +
        +Use "databricks sandbox [command] --help" for more information about a command.
        +

latest (v1.26.0): same output as main (a572447)

@eng-dev-ecosystem-bot

eng-dev-ecosystem-bot commented Nov 7, 2026 •

Copy link
Copy Markdown
Collaborator

✅ Integration test report

Commit: 4ee9807

Run: 42966486541

Env ✅​pass 🙈​skip Time
✅​ aws linux-2core-8gb 271 19 4:09
✅​ aws-windows-latest-4core-16gb 273 17 3:37
✅​ azure linux-2core-8gb 270 19 4:07
✅​ azure-windows-latest-4core-16gb 272 17 3:35
✅​ gcp linux-2core-8gb 271 19 4:18
✅​ gcp-windows-latest-4core-16gb 273 17 3:22
Top 3 slowest tests (at least 2 minutes):
duration env testname
3:31 aws-windows-latest-4core-16gb TestAccept
3:29 azure-windows-latest-4core-16gb TestAccept
3:17 gcp-windows-latest-4core-16gb TestAccept

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

(from my agent) There is no acceptance test for an actual forward: the Forwarding http://127.0.0.1:N -> id:10080 / Press Ctrl-C to stop. output and a request going through the new /sandbox-proxy/sandboxes/<id>/ports/<port>/ route are only covered by unit tests against the handler. Could one acceptance test run the command in the background with -L 0:10080, curl the printed URL against a testserver handler for that route, and print the request it received? Or is that not worth it with the current harness?

Comment thread cmd/sandbox/portforward.go Resolved
Comment thread cmd/sandbox/portforward.go Resolved
Comment thread cmd/sandbox/portforward.go Resolved
Comment thread cmd/sandbox/portforward.go Resolved
@jaecho-db
jaecho-db force-pushed the jaecho-db/sandbox-port-forward branch from 95ca5f1 to 3532674 Compare November 10, 2026 01:09
@jaecho-db
jaecho-db requested a review from pietern November 10, 2026 01:14
@pietern
pietern requested a review from a team November 10, 2026 15:49
@jaecho-db jaecho-db changed the title Add databricks sandbox port-forward Add databricks sandbox forward Nov 11, 2026
Comment thread cmd/sandbox/resolve.go Resolved
Comment thread cmd/sandbox/resolve.go Resolved
Comment thread cmd/sandbox/resolve.go Resolved
Comment thread cmd/sandbox/resolve.go Resolved
Comment thread cmd/sandbox/forward.go Resolved
jaecho-db and others added 7 commits November 11, 2026 18:42
Forward local ports to apps in a sandbox: each -L LOCAL:REMOTE serves
http://127.0.0.1:LOCAL and relays HTTP and WebSocket traffic through the
workspace's /sandbox-proxy/sandboxes/<id>/ports/<port>/ route with the
caller's credentials. Forwards accept only localhost-addressed requests and
refuse dot-segment paths, so neither a DNS-rebinding page nor a crafted path
can use those credentials outside the port route.

Co-authored-by: Isaac <no-reply@databricks.com>
…oxes

Refuse browser requests from other sites (Origin, Sec-Fetch-Site) so a web
page can't use a forward, including over WebSockets. Reject remote ports
outside 10000-65535 at parse time, start a stopped sandbox before
forwarding like `sandbox ssh`, also serve the IPv6 loopback, and keep
net/http's error reports in the debug log.

Co-authored-by: Isaac <no-reply@databricks.com>
The sandbox command group is now visible as a Beta feature, so the new
subcommand belongs in the release notes.

Co-authored-by: Isaac <no-reply@databricks.com>
- Share sandbox resolution with `sandbox ssh` (resolveSandbox), and reject
  IDs that are not a single path segment. A 400 from the lookup now fails
  instead of warning.
- Let a loopback Origin take precedence over Sec-Fetch-Site, so a frontend
  on http://localhost:N can call the printed http://127.0.0.1:N URL.
- Send the package's X-Databricks-Org-Id routing header, as the sandbox API
  does.
- Add an acceptance test that forwards a request end to end.

Co-authored-by: Isaac <no-reply@databricks.com>
Match the Python SDK's port forward: forward a request only if each path
segment decodes once to plain text other than "." or "..". Segments with
";", a backslash, a control character, a malformed escape, or an encoded
"/", "\", "%", "?", or "#" get a local 400. Also drop caller-supplied S2S-*
headers alongside X-Databricks-*.

Co-authored-by: Isaac <no-reply@databricks.com>
The command relays HTTP and WebSocket traffic, so "port-forward" suggests
generic TCP forwarding like `ssh -L` that it doesn't provide. A
protocol-neutral name lets TCP support land later without a new command.
This matches the Python SDK, where `SandboxExt.port_forward` becomes
`SandboxExt.forward`.

Co-authored-by: Isaac <no-reply@databricks.com>
…box hint

- Forward top-level GET and HEAD navigations from other sites, as the SDK
  does, so links and sign-in redirects to the app work. Iframes, fetches and
  cross-site form posts are still refused.
- Share the hint that ends the missing- and stale-default errors, and point
  users at setting an existing sandbox as the default.
- Say "Could not resolve sandbox" when the lookup only warns.

Co-authored-by: Isaac <no-reply@databricks.com>
@jaecho-db
jaecho-db force-pushed the jaecho-db/sandbox-port-forward branch from 9a6a405 to ffb0d8c Compare November 11, 2026 18:44
Co-authored-by: Isaac <no-reply@databricks.com>
@jaecho-db
jaecho-db requested a review from HsingTang November 12, 2026 05:32
@jaecho-db
jaecho-db enabled auto-merge November 12, 2026 19:25

This branch has not been deployed

No deployments
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.

4 participants