Problem / Motivation
The Go SDK exposes a useful Sandbox.Files() facade, but filesystem calls cannot currently select the sandbox user. This is observable against an AGS-managed E2B-compatible sandbox whose envd requires an explicit user:
failed to stat /tmp: no user specified
The gap affects all current Go filesystem operations:
- HTTP file operations:
Read, Write, and WriteFiles
- Connect RPC operations:
List, Stat/Exists, Remove, Rename, MakeDir, and WatchDir
CommandOptions.User already carries a user for process RPCs, but Files has no equivalent capability. Hard-coding root would fix one environment while preventing correct multi-user isolation.
What E2B currently standardizes
There is no language-neutral E2B type specifically named ReadOptions or WriteOptions. The compatibility contract is the filesystem capability plus its wire behavior, and each official SDK expresses it idiomatically:
- The official TypeScript SDK defines
FilesystemRequestOpts, FilesystemReadOpts, FilesystemWriteOpts, FilesystemListOpts, and WatchOpts. user is a common per-operation request option.
- The official Python SDK exposes the same capabilities as keyword arguments such as
user, request_timeout, gzip, use_octet_stream, metadata, depth, and watch flags.
- For
GET/POST /files, the official SDKs send the identity as the username query parameter.
- For filesystem Connect RPCs, the official SDKs send
Authorization: Basic base64("<user>:"). The filesystem protobuf request messages do not contain a username field.
This means a Go API does not need to copy the TypeScript object shape, but it must preserve these semantics and wire mappings.
Related work: #1126 and #1261 cover Python SDK user isolation. This issue tracks the Go SDK surface and the E2B-compatible HTTP/RPC propagation rules.
Proposed Solution
Add an immutable user-scoped filesystem view:
// ForUser returns a filesystem view that executes every operation as user.
// It does not mutate f; views for different users may be used concurrently.
// An empty user preserves the unscoped behavior of Sandbox.Files().
func (f *Files) ForUser(user string) *Files
Example:
files := sandbox.Files()
rootFiles := files.ForUser("root")
if _, err := rootFiles.Stat(ctx, "/tmp"); err != nil {
return err
}
nobodyFiles := files.ForUser("nobody")
if _, err := nobodyFiles.MakeDir(ctx, "/tmp/nobody-work"); err != nil {
return err
}
ForUser should return a shallow, immutable view over the same sandbox transports. It must not mutate the original Files value or store user identity globally on Sandbox.
Transport mapping
| Go operation |
envd transport |
Scoped behavior |
Read |
GET /files |
add username=<user> query parameter |
Write, WriteFiles |
POST /files |
add username=<user> query parameter to every upload |
List, Stat, Exists, Remove, Rename, MakeDir, WatchDir |
filesystem Connect RPC |
add Authorization: Basic base64("<user>:") |
The user must not be inserted into filesystem RPC request bodies. Existing X-Access-Token and traffic-token headers must be retained.
When no user scope is selected, existing SDK behavior should remain unchanged: do not add username or filesystem Basic auth implicitly as part of this feature.
Backward compatibility
- Keep all existing
Files method signatures unchanged in the first change.
sandbox.Files().Read(...) and other unscoped calls retain current behavior.
ForUser is additive.
- A user scope is attached to the returned view, not to
context.Context and not to global mutable client state.
Future operation-specific options
User identity and operation tuning are different concerns. ForUser should own identity; future options should describe only the operation they configure.
The following is a proposed semantic direction, not a requirement for the first PR:
// ReadOptions controls a buffered file read.
type ReadOptions struct {
// Gzip asks envd to gzip the response on the wire.
Gzip bool
}
// FileUploadMode selects how a write is encoded on the wire.
type FileUploadMode uint8
const (
// FileUploadAuto lets the SDK choose based on data and envd capabilities.
FileUploadAuto FileUploadMode = iota
// FileUploadMultipart forces multipart/form-data.
FileUploadMultipart
// FileUploadOctetStream requests application/octet-stream.
FileUploadOctetStream
)
// WriteOptions controls one or more file uploads.
type WriteOptions struct {
// Gzip compresses the upload and implies octet-stream when supported.
Gzip bool
// UploadMode selects automatic, multipart, or octet-stream upload.
UploadMode FileUploadMode
// Metadata is persisted as file extended attributes when envd supports it.
Metadata map[string]string
}
// ListOptions controls directory traversal.
type ListOptions struct {
// Depth is the maximum listing depth. Zero means the SDK default.
Depth int
}
// WatchOptions controls filesystem event delivery.
type WatchOptions struct {
// Recursive watches nested directories.
Recursive bool
// IncludeEntry asks envd to attach best-effort entry metadata.
IncludeEntry bool
// AllowNetworkMounts permits watches on supported network filesystems.
AllowNetworkMounts bool
}
Go already has context.Context for cancellation and deadlines, so a generic RequestTimeout or Signal field should not be copied from the TypeScript API without a distinct Go use case. Likewise, User should not be repeated in every options struct.
The E2B TypeScript/Python read(format=...) API can return text, bytes, or a stream dynamically. Go should not vary a method's return type through an option. If needed, use separate typed operations such as Read, ReadBytes, and Open/Reader, sharing semantic read options where appropriate.
Because existing Go interfaces may depend on the current method signatures, the exact Phase 2 exposure (ReadWithOptions, a source-call-compatible variadic form, or a versioned API) should be decided separately. The structs above define the semantic boundaries, not that migration mechanism.
Alternatives Considered
Add one FileOptions to every method
This repeats User across all calls and mixes stable identity scope with unrelated operation controls. It also encourages a large catch-all options type containing fields irrelevant to most methods.
Define separate HTTP and RPC options
Types such as HTTPFileOptions and RPCFileOptions expose an envd implementation detail to SDK callers. A caller should select a user and an operation, not know whether that operation currently uses REST or Connect RPC.
Put the user into filesystem RPC bodies
This does not match the official E2B clients or protobuf contract. E2B-compatible filesystem RPC identity is carried through Basic auth metadata.
Store the user in context.Context
This makes a required execution identity implicit and weakly typed. Context should continue to carry cancellation/deadline/request-scoped metadata, not replace a visible SDK capability.
Always execute filesystem operations as root
This hides the immediate error but breaks permission testing and multi-user isolation, and differs from explicit E2B user selection.
Acceptance Criteria
sandbox.Files().ForUser("root").Read/Write/WriteFiles sends username=root on /files requests.
sandbox.Files().ForUser("root").List/Stat/Exists/Remove/Rename/MakeDir/WatchDir sends Authorization: Basic cm9vdDo= on filesystem RPCs.
- Existing access-token and traffic-token headers remain present.
ForUser does not mutate the original view; two views for different users can be used concurrently without races or identity leakage.
- Unscoped calls retain their current request shape.
- Unit tests assert both HTTP query propagation and RPC header propagation.
- An integration test demonstrates at least one filesystem operation against an envd deployment that otherwise returns
no user specified.
Additional Context
Problem / Motivation
The Go SDK exposes a useful
Sandbox.Files()facade, but filesystem calls cannot currently select the sandbox user. This is observable against an AGS-managed E2B-compatible sandbox whose envd requires an explicit user:The gap affects all current Go filesystem operations:
Read,Write, andWriteFilesList,Stat/Exists,Remove,Rename,MakeDir, andWatchDirCommandOptions.Useralready carries a user for process RPCs, butFileshas no equivalent capability. Hard-codingrootwould fix one environment while preventing correct multi-user isolation.What E2B currently standardizes
There is no language-neutral E2B type specifically named
ReadOptionsorWriteOptions. The compatibility contract is the filesystem capability plus its wire behavior, and each official SDK expresses it idiomatically:FilesystemRequestOpts,FilesystemReadOpts,FilesystemWriteOpts,FilesystemListOpts, andWatchOpts.useris a common per-operation request option.user,request_timeout,gzip,use_octet_stream,metadata,depth, and watch flags.GET/POST /files, the official SDKs send the identity as theusernamequery parameter.Authorization: Basic base64("<user>:"). The filesystem protobuf request messages do not contain a username field.This means a Go API does not need to copy the TypeScript object shape, but it must preserve these semantics and wire mappings.
Related work: #1126 and #1261 cover Python SDK user isolation. This issue tracks the Go SDK surface and the E2B-compatible HTTP/RPC propagation rules.
Proposed Solution
Add an immutable user-scoped filesystem view:
Example:
ForUsershould return a shallow, immutable view over the same sandbox transports. It must not mutate the originalFilesvalue or store user identity globally onSandbox.Transport mapping
ReadGET /filesusername=<user>query parameterWrite,WriteFilesPOST /filesusername=<user>query parameter to every uploadList,Stat,Exists,Remove,Rename,MakeDir,WatchDirAuthorization: Basic base64("<user>:")The user must not be inserted into filesystem RPC request bodies. Existing
X-Access-Tokenand traffic-token headers must be retained.When no user scope is selected, existing SDK behavior should remain unchanged: do not add
usernameor filesystem Basic auth implicitly as part of this feature.Backward compatibility
Filesmethod signatures unchanged in the first change.sandbox.Files().Read(...)and other unscoped calls retain current behavior.ForUseris additive.context.Contextand not to global mutable client state.Future operation-specific options
User identity and operation tuning are different concerns.
ForUsershould own identity; future options should describe only the operation they configure.The following is a proposed semantic direction, not a requirement for the first PR:
Go already has
context.Contextfor cancellation and deadlines, so a genericRequestTimeoutorSignalfield should not be copied from the TypeScript API without a distinct Go use case. Likewise,Usershould not be repeated in every options struct.The E2B TypeScript/Python
read(format=...)API can return text, bytes, or a stream dynamically. Go should not vary a method's return type through an option. If needed, use separate typed operations such asRead,ReadBytes, andOpen/Reader, sharing semantic read options where appropriate.Because existing Go interfaces may depend on the current method signatures, the exact Phase 2 exposure (
ReadWithOptions, a source-call-compatible variadic form, or a versioned API) should be decided separately. The structs above define the semantic boundaries, not that migration mechanism.Alternatives Considered
Add one
FileOptionsto every methodThis repeats
Useracross all calls and mixes stable identity scope with unrelated operation controls. It also encourages a large catch-all options type containing fields irrelevant to most methods.Define separate HTTP and RPC options
Types such as
HTTPFileOptionsandRPCFileOptionsexpose an envd implementation detail to SDK callers. A caller should select a user and an operation, not know whether that operation currently uses REST or Connect RPC.Put the user into filesystem RPC bodies
This does not match the official E2B clients or protobuf contract. E2B-compatible filesystem RPC identity is carried through Basic auth metadata.
Store the user in
context.ContextThis makes a required execution identity implicit and weakly typed. Context should continue to carry cancellation/deadline/request-scoped metadata, not replace a visible SDK capability.
Always execute filesystem operations as root
This hides the immediate error but breaks permission testing and multi-user isolation, and differs from explicit E2B user selection.
Acceptance Criteria
sandbox.Files().ForUser("root").Read/Write/WriteFilessendsusername=rooton/filesrequests.sandbox.Files().ForUser("root").List/Stat/Exists/Remove/Rename/MakeDir/WatchDirsendsAuthorization: Basic cm9vdDo=on filesystem RPCs.ForUserdoes not mutate the original view; two views for different users can be used concurrently without races or identity leakage.no user specified.Additional Context