Skip to content

[AI-1663] Sandbox proxy broker under Daytona netleash: tunnel allowed domains, CA per run - #1113

Merged
kanat merged 9 commits into
acceleratefrom
connectors/sandbox-proxy-netleash
Oct 21, 2026
Merged

kanat merged 9 commits into
acceleratefrom
connectors/sandbox-proxy-netleash

Conversation

@kanat

@kanat kanat commented Oct 21, 2026 •

Copy link
Copy Markdown

Linear: AI-1663 — the proxy sandbox broker (#1104) did not work under Daytona's netleash (staging E2E 2026-10-21). This PR fits it: Daytona forces egress through netleash by allow list, netleash holds the proxy credential, the proxy tunnels allowed domains blind and terminates TLS only for a grant's host.

Flow

sequenceDiagram
    participant S as Daytona sandbox (code, root)
    participant N as Daytona netleash
    participant P as Router egress proxy (internal/sandbox/egress)
    participant H as Host
    S->>N: CONNECT host:443 (HTTPS_PROXY = netleash)
    Note over N: DomainAllowList = grants' hosts ∪ allowed_domains<br/>other names: 403 / not resolved
    N->>P: CONNECT host:443 + Proxy-Authorization (OutboundProxyUrl userinfo)
    alt host a grant covers
        P->>S: TLS with router CA (sandbox trusts it via per-run CA env)
        P->>H: request, placeholder → token (resolved per request)
        H->>P: response
        P->>S: response, token → placeholder
    else host of allowed_domains only
        P->>H: dial (public addresses only, else 502)
        P-->>S: bytes as they are: no TLS of ours, no token, nothing read
    else any other host / IP literal / port ≠ 443
        P->>S: 403
    end
Loading

What changed

internal/sandbox/egress
├── proxy.go     ServeHTTP: granted → MITM (as before); allowed only → passthrough(); else 403
├── store.go     Record.Allowed (config's allowed_domains as hosts)
└── broker.go    Allowed normalised; Env = placeholders only; RunEnv = CA vars;
                 bundle from /etc/daytona/netleash/ca.crt first; record kept while allowed domains exist
internal/sandbox/broker.go      Mount.RunEnv (env per command / run of code)
internal/sandbox/daytona        OutboundProxyUrl kept; DomainAllowList = grants ∪ config (no proxy host);
                                NO_PROXY dropped; RunEnv on CodeRun, setup and mkdir
internal/session                Allowed passed to the broker; network_allow_list refused with broker proxy
internal/egress/public.go       DialPublic (the dial NewClient makes)

Probe facts (our Daytona org, 2026-10-21, sandboxes deleted)

Fact Log
Netleash replaces all 9 proxy/CA env vars set at create (EnvVars lose) netleash-probe/q1-env.log, q3-cmdenv.log
ExecuteCommand + WithCommandEnv wins netleash-probe/q3-cmdenv.log
CodeRun + CodeRunParams.Env wins (all 5 CA vars ours) pr-w14-fix/coderun-probe/coderun.log (20:54:32Z)
Sandbox HTTPS_PROXY = http://172.20.0.1:18080, no userinfo coderun-probe/coderun.log
/etc/daytona/netleash/ca.crt read-only bind mount: Mozilla roots + netleash CA netleash-probe/q1-ca.log, q1-bind.log
Code runs as uid 1001 with passwordless sudo → root netleash-probe/q2.log
OutboundProxyUrl alone forces nothing; DomainAllowList forces (direct :443 intercepted, other names 403) netleash-probe/q4.log, qb.log, qb3.log
OutboundProxyUrl refuses blocked ranges; accepts hostname + userinfo netleash-probe/q5.log

Rules → tests

Rule Test Mutation
Passthrough only for allowed domains egress TestOnlyAnAllowedDomainOnPort443IsTunnelled M1
…only on port 443, no IP literal same M2, M3
Blind tunnel: no router cert, no token TestAnAllowedDomainIsTunnelledAsItIs M4
Granted beats allowed TestAHostBothGrantedAndAllowedIsAGrants M5
Non-public resolution refused (502) TestAnAllowedDomainResolvingToANonPublicAddressIsNotDialed M6, M29
Tunnel keeps early bytes, ends with host TestATunnelToAnAllowedDomainKeepsTheTLSHelloAndEndsWithTheHost M7, M8
Record kept with allowed domains; carries them normalised TestTheRecordHoldsTheConfigsAllowedDomains M9–M13
Bundle from netleash ca.crt TestTheBundleHoldsTheSandboxsRootsAndTheCA M14
Credential only in proxy URL; CA env per run TestTheProviderHoldsTheProxyCredentialAndEveryRunTrustsTheCA M15–M18
RunEnv on CodeRun, setup, mkdir; DomainAllowList = grants ∪ config, no proxy host; NO_PROXY gone; OutboundProxyUrl kept daytona TestTheSandboxGoesThroughTheProxyToItsGrantsAndAllowedDomains M19–M25
network_allow_list / open network refused with broker proxy TestTheProxyBrokerRefusesAConfigItCannotServe M26, M28
Session passes every allowed domain (two in the test) TestTheProxyBrokerSendsTheSandboxThroughTheProxy M27, C22
Sandbox ending ends the tunnel to an allowed domain egress TestATunnelToAnAllowedDomainEndsWithTheSandbox R2, R3
Host matched and dialed without a trailing dot; empty host refused egress TestAHostWithATrailingDotIsTheHostWithout trailing-dot probe
Allowed domain with no name (*, *., .., *.*) is skipped TestTheRecordHoldsTheConfigsAllowedDomains R1.3
Grant withdrawn on an allowed host ends its open tunnel egress TestAGrantWithdrawnOnAnAllowedHostEndsItsOpenTunnel C8
Close deletes a record with allowed domains TestClosingEndsTheCredentialOfARecordWithAllowedDomains B3
No grant with a credential: empty mount and no record, whatever is allowed TestNoGrantWithACredentialGivesNoProxyWhateverIsAllowed C7
A grant the broker leaves out stays out of the allow list daytona TestAGrantTheBrokerLeavesOutIsNotInTheAllowList B2
Daytona broker unchanged (control, bodies from a probe on base 98410fe) TestDaytonasBrokerSendsDaytonaWhatItDidBefore —

All 29 mutations killed.

Suites: go test -tags integration -p 1 on internal/sandbox/..., internal/session, internal/config, cmd/router: pass. Full run: only known base failures (ChatLogins, SessionVerbs, TurnRecording) and internal/store hitting the 10 m default timeout in TestShardedStoreSuite (package untouched).

Rulings (orchestrator; Kanat may overturn)

  • One PR, size stop 750 (tests + docs; deletions count).
  • With broker proxy, network_allow_list is refused at session start: the proxy reaches hosts by name only.
  • When every grant is withdrawn but the config has allowed domains, the egress record stays, so those domains keep working.

Unverified

  • That netleash sends allow-listed hosts on to OutboundProxyUrl (CONNECT chaining) — staging E2E, AI-1663 point 4.

🤖 Generated with Claude Code

kanat and others added 3 commits October 21, 2026 16:52
…, fit Daytona's netleash (WIP, oversize)

- egress proxy: a CONNECT to one of the config's allowed domains (Record.Allowed) on 443 gets a
  blind TCP tunnel through internal/egress.DialPublic: no MITM, no token, nothing logged; granted
  hosts keep MITM + substitution, a host both granted and allowed is a grant's; anything else 403.
- broker: the proxy URL with its credential goes to Daytona alone (OutboundProxyUrl); the sandbox
  env holds placeholders only; CA env vars (+ CURL_CA_BUNDLE) move to Mount.RunEnv, sent on every
  ExecuteCommand (WithCommandEnv) and CodeRun (CodeRunParams.Env); the bundle starts from
  /etc/daytona/netleash/ca.crt when present. A record keeps the allowed domains once every grant
  is withdrawn.
- daytona: DomainAllowList = grants' hosts + config domains, never the proxy host; NO_PROXY and
  the proxy-in-CIDR path removed. session: network_allow_list refused with broker proxy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…x code has root

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ends

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added the docs label Oct 21, 2026
kanat and others added 6 commits October 21, 2026 17:44
…nding a tunnel and a withdrawn grant on an allowed host

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ntial grants with allowed domains

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… netleash reaching the proxy unverified

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…the allow list

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…tleash bypass

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@kanat
kanat marked this pull request as ready for review October 21, 2026 21:58
@kanat
kanat merged commit a20ae0e into accelerate Oct 21, 2026
11 of 12 checks passed
@kanat
kanat deleted the connectors/sandbox-proxy-netleash branch October 21, 2026 22:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant