Skip to content

Commit 4a252dc

Browse files
committed
fix(sdk/go): harden Go SDK from deep review findings
Security: replace http.DefaultClient with 30s-timeout oidcHTTPClient across all 5 OIDC call sites, add sync.Once to callback server result delivery, validate URL scheme before browser exec (Windows cmd.exe injection prevention), add io.LimitReader (1 MiB) to all unbounded io.ReadAll sites. Correctness: fix interactiveSession goroutine leak (add context cancellation to Close), clone globalRevisions before in-place sort (data race), add FailedPrecondition/ResourceExhausted/OutOfRange to gRPC error mapping with Cause preservation, add ErrTransportNotAvailable for file client transport gating, propagate structpb conversion errors through SandboxPolicyToProto. Validation: add negative ListOptions checks to sandbox and provider clients, add FR-007 validation parity (sandboxName/remotePath) to 7 fake client methods across 4 files, deep-copy NetworkMiddlewares and L7 Params/Mcp fields in fake sandbox policy. Tests: add mutex to 4 sandbox mock handlers, strengthen exec/file test assertions, verify all 12 sub-client accessors, fix watch stop idempotency test. Docs: add "default" workspace argument to 80+ SDK calls across 16 documentation files, add MITM warning to TLSConfig.Insecure godoc. Signed-off-by: Roland Huß <rhuss@redhat.com>
1 parent 2061693 commit 4a252dc

103 files changed

Lines changed: 1442 additions & 4131 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎mise.lock‎

Lines changed: 2 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎sdk/go/README.md‎

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,19 +52,19 @@ if err != nil {
5252
defer client.Close()
5353

5454
// Create a sandbox and wait until it's ready
55-
sandbox, err := client.Sandboxes().Create(ctx, "my-sandbox", &v1.SandboxSpec{
55+
sandbox, err := client.Sandboxes().Create(ctx, "default", "my-sandbox", &v1.SandboxSpec{
5656
Template: &v1.SandboxTemplate{Image: "python:3.12"},
5757
}, nil)
5858
if err != nil {
5959
log.Fatal(err)
6060
}
61-
sandbox, err = client.Sandboxes().WaitReady(ctx, sandbox.Name)
61+
sandbox, err = client.Sandboxes().WaitReady(ctx, "default", sandbox.Name)
6262
if err != nil {
6363
log.Fatal(err)
6464
}
6565

6666
// Run a command
67-
result, err := client.Exec().Run(ctx, sandbox.Name,
67+
result, err := client.Exec().Run(ctx, "default", sandbox.Name,
6868
[]string{"python3", "-c", "print('hello from sandbox')"},
6969
v1.ExecOptions{},
7070
)
@@ -192,6 +192,21 @@ See the [oidc package docs](https://pkg.go.dev/github.com/NVIDIA/OpenShell/sdk/g
192192

193193
See the [Getting Started](https://ro14nd.de/openshell-sdk-go/getting-started.html) guide for the full walkthrough.
194194

195+
## Migrating from v0.0.101
196+
197+
The pre-1.0 SDK intentionally includes source-incompatible API corrections:
198+
199+
- `TCP.Listen` returns a `ForwardListener` lifecycle handle. The SDK owns the
200+
accept loop; callers dial `Addr()` and call `Close()` instead of calling
201+
`Accept()` or passing the handle to `http.Serve`.
202+
- Resource operations take an explicit workspace, and workspace-bearing domain
203+
types preserve that scope.
204+
- Several public struct field orders changed. Use keyed struct literals.
205+
- Initialisms use Go spelling, including `JSONRPCMaxBodyBytes`.
206+
207+
These changes are intentional while the module remains below v1. Update callers
208+
as one migration rather than relying on the v0.0.101 API shape.
209+
195210
### Inference Route Management
196211

197212
Configure how inference requests are routed for a workspace:
@@ -253,7 +268,7 @@ consumers import a single package. See the [Architecture](https://ro14nd.de/open
253268
| Provider profiles (list, import, lint, update) | `ProfileInterface` | [Profiles](https://ro14nd.de/openshell-sdk-go/api/profiles.html) |
254269
| Credential refresh (configure, rotate, status) | `RefreshInterface` | [Refresh](https://ro14nd.de/openshell-sdk-go/api/refresh.html) |
255270
| Service exposure (expose, list, delete) | `ServiceInterface` | [Services](https://ro14nd.de/openshell-sdk-go/api/services.html) |
256-
| File transfer (upload, download) | `FileInterface` | [Files](https://ro14nd.de/openshell-sdk-go/api/files.html) |
271+
| File transfer API (transport capability-gated) | `FileInterface` | [Files](https://ro14nd.de/openshell-sdk-go/api/files.html) |
257272
| Policy management (draft review, approve, reject, merge, global policy) | `PolicyInterface` | [Policy](https://ro14nd.de/openshell-sdk-go/api/policy.html) |
258273
| Sandbox logs (streaming retrieval) | `SandboxInterface` | [Sandboxes](https://ro14nd.de/openshell-sdk-go/api/sandboxes.html) |
259274
| Workspace management (create, get, list, delete, members) | `WorkspaceInterface` | [Workspaces](https://ro14nd.de/openshell-sdk-go/api/workspaces.html) |

‎sdk/go/buf.gen.yaml‎

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,19 @@
33

44
# Code generation for the Go SDK. buf compiles the proto module with its own
55
# compiler and runs protoc-gen-go / protoc-gen-go-grpc from mise-managed
6-
# binaries. Limited to the client-surface closure (openshell, datamodel,
7-
# sandbox, options, inference).
6+
# binaries. The public SDK entry points are declared here; imported schemas
7+
# are included automatically, so their canonical dependency closure is
8+
# generated without duplicating a filename list in the generation scripts.
89
version: v2
910

1011
inputs:
11-
- directory: proto
12-
paths:
13-
- proto/openshell.proto
14-
- proto/datamodel.proto
15-
- proto/sandbox.proto
16-
- proto/options.proto
17-
- proto/inference.proto
12+
- proto_file: proto/openshell.proto
13+
- proto_file: proto/inference.proto
1814

1915
plugins:
2016
- local: protoc-gen-go
21-
out: .
17+
out: sdk/go
18+
include_imports: true
2219
opt:
2320
- module=github.com/NVIDIA/OpenShell/sdk/go
2421
- Mopenshell.proto=github.com/NVIDIA/OpenShell/sdk/go/proto/openshellv1
@@ -27,7 +24,8 @@ plugins:
2724
- Moptions.proto=github.com/NVIDIA/OpenShell/sdk/go/proto/optionsv1
2825
- Minference.proto=github.com/NVIDIA/OpenShell/sdk/go/proto/inferencev1
2926
- local: protoc-gen-go-grpc
30-
out: .
27+
out: sdk/go
28+
include_imports: true
3129
opt:
3230
- module=github.com/NVIDIA/OpenShell/sdk/go
3331
- Mopenshell.proto=github.com/NVIDIA/OpenShell/sdk/go/proto/openshellv1

‎sdk/go/buf.yaml‎

Lines changed: 0 additions & 19 deletions
This file was deleted.

‎sdk/go/docs/src/api/config.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Retrieve and update configuration for sandboxes and the gateway.
99
Retrieve the current configuration for a specific sandbox.
1010

1111
```go
12-
config, err := client.Config().GetSandbox(ctx, "sandbox-123")
12+
config, err := client.Config().GetSandbox(ctx, "default", "sandbox-123")
1313
if err != nil {
1414
log.Fatal(err)
1515
}
@@ -34,7 +34,7 @@ fmt.Printf("Gateway settings revision: %d\n", config.SettingsRevision)
3434
Apply a configuration update. The update is validated before being applied.
3535

3636
```go
37-
result, err := client.Config().Update(ctx, &v1.ConfigUpdate{
37+
result, err := client.Config().Update(ctx, "default", &v1.ConfigUpdate{
3838
Name: "sandbox-123",
3939
SettingKey: "idle_timeout",
4040
SettingValue: &v1.SettingValue{

‎sdk/go/docs/src/api/exec.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Execute commands in a sandbox with three modes: one-shot (`Run`), streaming
1010
Execute a command and collect all output into a single result.
1111

1212
```go
13-
result, err := client.Exec().Run(ctx, "sbx-123", []string{"ls", "-la"})
13+
result, err := client.Exec().Run(ctx, "default", "sbx-123", []string{"ls", "-la"})
1414
if err != nil {
1515
log.Fatal(err)
1616
}
@@ -35,7 +35,7 @@ The SDK collects all streamed events, assembles stdout/stderr, and returns a sin
3535
Execute a command and process output chunks as they arrive.
3636

3737
```go
38-
stream, err := client.Exec().Stream(ctx, "sbx-123", []string{"tail", "-f", "/var/log/app.log"})
38+
stream, err := client.Exec().Stream(ctx, "default", "sbx-123", []string{"tail", "-f", "/var/log/app.log"})
3939
if err != nil {
4040
log.Fatal(err)
4141
}
@@ -67,7 +67,7 @@ fmt.Println("Exited with:", exitCode)
6767
Open a bidirectional terminal session with a command.
6868

6969
```go
70-
session, err := client.Exec().Interactive(ctx, "sbx-123", []string{"/bin/bash"}, 80, 24)
70+
session, err := client.Exec().Interactive(ctx, "default", "sbx-123", []string{"/bin/bash"}, 80, 24)
7171
if err != nil {
7272
log.Fatal(err)
7373
}

‎sdk/go/docs/src/api/files.md‎

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,33 +2,35 @@
22

33
Accessor: `client.Files()`
44

5-
Upload and download files to and from sandboxes. Uses gRPC streaming for efficient
6-
transfer of large files.
5+
`FileInterface` reserves the upload and download API while keeping sandbox lookup
6+
and SSH-session lifecycle behavior stable. The standalone SDK does not currently
7+
ship an SSH file-transfer transport, so both operations return
8+
`v1.ErrTransportNotAvailable` before performing local validation or gateway RPCs.
79

810
## Upload
911

10-
Upload a file from the local filesystem to a path inside a sandbox.
11-
The file is streamed in chunks using client-side gRPC streaming.
12+
Detect transport availability programmatically:
1213

1314
```go
14-
err := client.Files().Upload(ctx, "sandbox-123", "./data/config.yaml", "/app/config.yaml")
15-
if err != nil {
15+
err := client.Files().Upload(ctx, "default", "sandbox-123", "./data/config.yaml", "/app/config.yaml")
16+
if errors.Is(err, v1.ErrTransportNotAvailable) {
17+
// Use another transfer mechanism until an SSH transport is available.
18+
} else if err != nil {
1619
log.Fatal(err)
1720
}
18-
fmt.Println("File uploaded successfully")
1921
```
2022

2123
## Download
2224

23-
Download a file from a sandbox to the local filesystem.
24-
The file is received in chunks using server-side gRPC streaming.
25+
`Download` has the same capability gate:
2526

2627
```go
27-
err := client.Files().Download(ctx, "sandbox-123", "/app/output.log", "./output.log")
28-
if err != nil {
28+
err := client.Files().Download(ctx, "default", "sandbox-123", "/app/output.log", "./output.log")
29+
if errors.Is(err, v1.ErrTransportNotAvailable) {
30+
// Use another transfer mechanism until an SSH transport is available.
31+
} else if err != nil {
2932
log.Fatal(err)
3033
}
31-
fmt.Println("File downloaded successfully")
3234
```
3335

3436
See also: [Error Handling](../error-handling.md), [Testing](../testing.md)

‎sdk/go/docs/src/api/policy.md‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ go through a draft, review, and approval cycle before being applied.
1010
Retrieve the current draft policy for a sandbox.
1111

1212
```go
13-
draft, err := client.Policy().GetDraft(ctx, "my-sandbox")
13+
draft, err := client.Policy().GetDraft(ctx, "default", "my-sandbox")
1414
if err != nil {
1515
log.Fatal(err)
1616
}
@@ -25,7 +25,7 @@ for _, chunk := range draft.Chunks {
2525
Approve all pending chunks in a single operation.
2626

2727
```go
28-
result, err := client.Policy().ApproveAllDraftChunks(ctx, "my-sandbox")
28+
result, err := client.Policy().ApproveAllDraftChunks(ctx, "default", "my-sandbox")
2929
if err != nil {
3030
log.Fatal(err)
3131
}
@@ -38,7 +38,7 @@ fmt.Printf("Approved %d chunks (skipped %d), policy version: %d\n",
3838
Check the current policy enforcement status for a sandbox.
3939

4040
```go
41-
status, err := client.Policy().GetStatus(ctx, "my-sandbox")
41+
status, err := client.Policy().GetStatus(ctx, "default", "my-sandbox")
4242
if err != nil {
4343
log.Fatal(err)
4444
}
@@ -51,7 +51,7 @@ fmt.Printf("Active version: %d, revision status: %s\n",
5151
List all policy revisions for a sandbox.
5252

5353
```go
54-
revisions, err := client.Policy().List(ctx, "my-sandbox")
54+
revisions, err := client.Policy().List(ctx, "default", "my-sandbox")
5555
if err != nil {
5656
log.Fatal(err)
5757
}
@@ -66,7 +66,7 @@ for _, rev := range revisions {
6666
Reject a specific draft chunk, providing a reason.
6767

6868
```go
69-
err := client.Policy().RejectDraftChunk(ctx, "my-sandbox", "chunk-abc", "Too permissive")
69+
err := client.Policy().RejectDraftChunk(ctx, "default", "my-sandbox", "chunk-abc", "Too permissive")
7070
if err != nil {
7171
log.Fatal(err)
7272
}
@@ -77,7 +77,7 @@ if err != nil {
7777
Modify the proposed rule in a draft chunk before approval.
7878

7979
```go
80-
err := client.Policy().EditDraftChunk(ctx, "my-sandbox", "chunk-abc", &v1.NetworkPolicyRule{
80+
err := client.Policy().EditDraftChunk(ctx, "default", "my-sandbox", "chunk-abc", &v1.NetworkPolicyRule{
8181
Name: "allow-api",
8282
Endpoints: []v1.PolicyNetworkEndpoint{{
8383
Host: "api.example.com",

‎sdk/go/docs/src/api/profiles.md‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ credentials, and model mappings for providers like OpenAI, Anthropic, or custom
1010
List all provider profiles visible to the current user.
1111

1212
```go
13-
profiles, err := client.Providers().Profiles().List(ctx)
13+
profiles, err := client.Providers().Profiles().List(ctx, "default")
1414
if err != nil {
1515
log.Fatal(err)
1616
}
@@ -24,7 +24,7 @@ for _, p := range profiles {
2424
Import one or more provider profiles from configuration items.
2525

2626
```go
27-
result, err := client.Providers().Profiles().Import(ctx, []v1.ProfileImportItem{
27+
result, err := client.Providers().Profiles().Import(ctx, "default", []v1.ProfileImportItem{
2828
{
2929
Profile: v1.ProviderProfile{
3030
DisplayName: "OpenAI",
@@ -44,14 +44,14 @@ fmt.Printf("Imported: %v\n", result.Imported)
4444
Update a profile using optimistic concurrency control via the resource version.
4545

4646
```go
47-
profile, err := client.Providers().Profiles().Get(ctx, "profile-id")
47+
profile, err := client.Providers().Profiles().Get(ctx, "default", "profile-id")
4848
if err != nil {
4949
log.Fatal(err)
5050
}
5151

5252
profile.DisplayName = "Updated Provider"
5353
result, err := client.Providers().Profiles().Update(
54-
ctx,
54+
ctx, "default",
5555
profile.ID,
5656
profile.ResourceVersion,
5757
v1.ProfileImportItem{Profile: *profile, Source: "manual"},
@@ -73,7 +73,7 @@ items := []v1.ProfileImportItem{
7373
Source: "manual",
7474
},
7575
}
76-
result, err := client.Providers().Profiles().Lint(ctx, items)
76+
result, err := client.Providers().Profiles().Lint(ctx, "default", items)
7777
if err != nil {
7878
log.Fatal(err)
7979
}

0 commit comments

Comments
 (0)