From bdd1fac453851378b4049ea2cc5d76169c1bd4f4 Mon Sep 17 00:00:00 2001 From: Jiri Sveceny Date: Tue, 28 Apr 2026 10:47:21 +0200 Subject: [PATCH 01/19] Openapi for network rules --- packages/api/internal/api/api.gen.go | 348 ++++++++++++++------------- spec/openapi.yml | 37 +++ 2 files changed, 223 insertions(+), 162 deletions(-) diff --git a/packages/api/internal/api/api.gen.go b/packages/api/internal/api/api.gen.go index af4998a59c..c9ca343f7c 100644 --- a/packages/api/internal/api/api.gen.go +++ b/packages/api/internal/api/api.gen.go @@ -899,6 +899,21 @@ type SandboxNetworkConfig struct { // MaskRequestHost Specify host mask which will be used for all sandbox requests MaskRequestHost *string `json:"maskRequestHost,omitempty"` + + // Rules Per-domain transform rules applied to matching egress HTTP/HTTPS requests. Keys are domain patterns (e.g. "api.openai.com", "*.openai.com"). A domain listed here is not automatically allowed — use allowOut to permit the traffic. + Rules *map[string][]SandboxNetworkRule `json:"rules,omitempty"` +} + +// SandboxNetworkRule Transform rule applied to egress requests matching a domain pattern. +type SandboxNetworkRule struct { + // Transform Transformations applied to matching egress requests before forwarding. + Transform *SandboxNetworkTransform `json:"transform,omitempty"` +} + +// SandboxNetworkTransform Transformations applied to matching egress requests before forwarding. +type SandboxNetworkTransform struct { + // Headers HTTP headers to inject or override in matching requests. An existing header with the same name is replaced. Values are plain strings; secret resolution happens client-side before sending to the API. + Headers *map[string]string `json:"headers,omitempty"` } // SandboxOnTimeout Action taken when the sandbox times out. @@ -1452,6 +1467,9 @@ type PutSandboxesSandboxIDNetworkJSONBody struct { // DenyOut List of denied CIDR blocks or IP addresses for egress traffic. Domain names are not supported for deny rules. DenyOut *[]string `json:"denyOut,omitempty"` + + // Rules Per-domain transform rules. Replaces all existing rules when provided. + Rules *map[string][]SandboxNetworkRule `json:"rules,omitempty"` } // PostSandboxesSandboxIDRefreshesJSONBody defines parameters for PostSandboxesSandboxIDRefreshes. @@ -12264,168 +12282,174 @@ func RegisterHandlersWithOptions(router gin.IRouter, si ServerInterface, options // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "AAD///0lcSkyLAEA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/spec/openapi.yml b/spec/openapi.yml index f5c8960ed7..accb1f4b24 100644 --- a/spec/openapi.yml +++ b/spec/openapi.yml @@ -282,6 +282,36 @@ components: maskRequestHost: type: string description: Specify host mask which will be used for all sandbox requests + rules: + type: object + description: > + Per-domain transform rules applied to matching egress HTTP/HTTPS requests. + Keys are domain patterns (e.g. "api.openai.com", "*.openai.com"). + A domain listed here is not automatically allowed — use allowOut to permit the traffic. + additionalProperties: + type: array + items: + $ref: '#/components/schemas/SandboxNetworkRule' + + SandboxNetworkRule: + type: object + description: Transform rule applied to egress requests matching a domain pattern. + properties: + transform: + $ref: '#/components/schemas/SandboxNetworkTransform' + + SandboxNetworkTransform: + type: object + description: Transformations applied to matching egress requests before forwarding. + properties: + headers: + type: object + description: > + HTTP headers to inject or override in matching requests. + An existing header with the same name is replaced. Values are plain strings; + secret resolution happens client-side before sending to the API. + additionalProperties: + type: string SandboxAutoResumeEnabled: type: boolean @@ -2395,6 +2425,13 @@ paths: description: List of denied CIDR blocks or IP addresses for egress traffic. Domain names are not supported for deny rules. items: type: string + rules: + type: object + description: Per-domain transform rules. Replaces all existing rules when provided. + additionalProperties: + type: array + items: + $ref: '#/components/schemas/SandboxNetworkRule' allow_internet_access: type: boolean description: From b25dc55b1b5f02ff14c0ef3128cee65a630ce8c9 Mon Sep 17 00:00:00 2001 From: Jiri Sveceny Date: Tue, 28 Apr 2026 10:47:43 +0200 Subject: [PATCH 02/19] Orchestrator gRPC for network rules --- packages/orchestrator/orchestrator.proto | 14 +- .../pkg/grpc/orchestrator/orchestrator.pb.go | 687 ++++++++++++------ 2 files changed, 469 insertions(+), 232 deletions(-) diff --git a/packages/orchestrator/orchestrator.proto b/packages/orchestrator/orchestrator.proto index 37a75e3914..abb18c3ee2 100644 --- a/packages/orchestrator/orchestrator.proto +++ b/packages/orchestrator/orchestrator.proto @@ -72,11 +72,23 @@ message SandboxNetworkConfig { optional SandboxNetworkIngressConfig ingress = 2; } +message SandboxNetworkTransform { + map headers = 1; +} + +message SandboxNetworkRule { + optional SandboxNetworkTransform transform = 1; +} + +message SandboxNetworkDomainRules { + repeated SandboxNetworkRule rules = 1; +} + message SandboxNetworkEgressConfig { repeated string allowed_cidrs = 1; repeated string denied_cidrs = 2; - repeated string allowed_domains = 3; + map rules = 4; } message SandboxNetworkIngressConfig { diff --git a/packages/shared/pkg/grpc/orchestrator/orchestrator.pb.go b/packages/shared/pkg/grpc/orchestrator/orchestrator.pb.go index bd60a3ead8..45cfe61ed8 100644 --- a/packages/shared/pkg/grpc/orchestrator/orchestrator.pb.go +++ b/packages/shared/pkg/grpc/orchestrator/orchestrator.pb.go @@ -443,20 +443,162 @@ func (x *SandboxNetworkConfig) GetIngress() *SandboxNetworkIngressConfig { return nil } +type SandboxNetworkTransform struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Headers map[string]string `protobuf:"bytes,1,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (x *SandboxNetworkTransform) Reset() { + *x = SandboxNetworkTransform{} + if protoimpl.UnsafeEnabled { + mi := &file_orchestrator_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SandboxNetworkTransform) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SandboxNetworkTransform) ProtoMessage() {} + +func (x *SandboxNetworkTransform) ProtoReflect() protoreflect.Message { + mi := &file_orchestrator_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SandboxNetworkTransform.ProtoReflect.Descriptor instead. +func (*SandboxNetworkTransform) Descriptor() ([]byte, []int) { + return file_orchestrator_proto_rawDescGZIP(), []int{4} +} + +func (x *SandboxNetworkTransform) GetHeaders() map[string]string { + if x != nil { + return x.Headers + } + return nil +} + +type SandboxNetworkRule struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Transform *SandboxNetworkTransform `protobuf:"bytes,1,opt,name=transform,proto3,oneof" json:"transform,omitempty"` +} + +func (x *SandboxNetworkRule) Reset() { + *x = SandboxNetworkRule{} + if protoimpl.UnsafeEnabled { + mi := &file_orchestrator_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SandboxNetworkRule) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SandboxNetworkRule) ProtoMessage() {} + +func (x *SandboxNetworkRule) ProtoReflect() protoreflect.Message { + mi := &file_orchestrator_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SandboxNetworkRule.ProtoReflect.Descriptor instead. +func (*SandboxNetworkRule) Descriptor() ([]byte, []int) { + return file_orchestrator_proto_rawDescGZIP(), []int{5} +} + +func (x *SandboxNetworkRule) GetTransform() *SandboxNetworkTransform { + if x != nil { + return x.Transform + } + return nil +} + +type SandboxNetworkDomainRules struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Rules []*SandboxNetworkRule `protobuf:"bytes,1,rep,name=rules,proto3" json:"rules,omitempty"` +} + +func (x *SandboxNetworkDomainRules) Reset() { + *x = SandboxNetworkDomainRules{} + if protoimpl.UnsafeEnabled { + mi := &file_orchestrator_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SandboxNetworkDomainRules) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SandboxNetworkDomainRules) ProtoMessage() {} + +func (x *SandboxNetworkDomainRules) ProtoReflect() protoreflect.Message { + mi := &file_orchestrator_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SandboxNetworkDomainRules.ProtoReflect.Descriptor instead. +func (*SandboxNetworkDomainRules) Descriptor() ([]byte, []int) { + return file_orchestrator_proto_rawDescGZIP(), []int{6} +} + +func (x *SandboxNetworkDomainRules) GetRules() []*SandboxNetworkRule { + if x != nil { + return x.Rules + } + return nil +} + type SandboxNetworkEgressConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - AllowedCidrs []string `protobuf:"bytes,1,rep,name=allowed_cidrs,json=allowedCidrs,proto3" json:"allowed_cidrs,omitempty"` - DeniedCidrs []string `protobuf:"bytes,2,rep,name=denied_cidrs,json=deniedCidrs,proto3" json:"denied_cidrs,omitempty"` - AllowedDomains []string `protobuf:"bytes,3,rep,name=allowed_domains,json=allowedDomains,proto3" json:"allowed_domains,omitempty"` + AllowedCidrs []string `protobuf:"bytes,1,rep,name=allowed_cidrs,json=allowedCidrs,proto3" json:"allowed_cidrs,omitempty"` + DeniedCidrs []string `protobuf:"bytes,2,rep,name=denied_cidrs,json=deniedCidrs,proto3" json:"denied_cidrs,omitempty"` + AllowedDomains []string `protobuf:"bytes,3,rep,name=allowed_domains,json=allowedDomains,proto3" json:"allowed_domains,omitempty"` + Rules map[string]*SandboxNetworkDomainRules `protobuf:"bytes,4,rep,name=rules,proto3" json:"rules,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` } func (x *SandboxNetworkEgressConfig) Reset() { *x = SandboxNetworkEgressConfig{} if protoimpl.UnsafeEnabled { - mi := &file_orchestrator_proto_msgTypes[4] + mi := &file_orchestrator_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -469,7 +611,7 @@ func (x *SandboxNetworkEgressConfig) String() string { func (*SandboxNetworkEgressConfig) ProtoMessage() {} func (x *SandboxNetworkEgressConfig) ProtoReflect() protoreflect.Message { - mi := &file_orchestrator_proto_msgTypes[4] + mi := &file_orchestrator_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -482,7 +624,7 @@ func (x *SandboxNetworkEgressConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxNetworkEgressConfig.ProtoReflect.Descriptor instead. func (*SandboxNetworkEgressConfig) Descriptor() ([]byte, []int) { - return file_orchestrator_proto_rawDescGZIP(), []int{4} + return file_orchestrator_proto_rawDescGZIP(), []int{7} } func (x *SandboxNetworkEgressConfig) GetAllowedCidrs() []string { @@ -506,6 +648,13 @@ func (x *SandboxNetworkEgressConfig) GetAllowedDomains() []string { return nil } +func (x *SandboxNetworkEgressConfig) GetRules() map[string]*SandboxNetworkDomainRules { + if x != nil { + return x.Rules + } + return nil +} + type SandboxNetworkIngressConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -518,7 +667,7 @@ type SandboxNetworkIngressConfig struct { func (x *SandboxNetworkIngressConfig) Reset() { *x = SandboxNetworkIngressConfig{} if protoimpl.UnsafeEnabled { - mi := &file_orchestrator_proto_msgTypes[5] + mi := &file_orchestrator_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -531,7 +680,7 @@ func (x *SandboxNetworkIngressConfig) String() string { func (*SandboxNetworkIngressConfig) ProtoMessage() {} func (x *SandboxNetworkIngressConfig) ProtoReflect() protoreflect.Message { - mi := &file_orchestrator_proto_msgTypes[5] + mi := &file_orchestrator_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -544,7 +693,7 @@ func (x *SandboxNetworkIngressConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxNetworkIngressConfig.ProtoReflect.Descriptor instead. func (*SandboxNetworkIngressConfig) Descriptor() ([]byte, []int) { - return file_orchestrator_proto_rawDescGZIP(), []int{5} + return file_orchestrator_proto_rawDescGZIP(), []int{8} } func (x *SandboxNetworkIngressConfig) GetTrafficAccessToken() string { @@ -574,7 +723,7 @@ type SandboxCreateRequest struct { func (x *SandboxCreateRequest) Reset() { *x = SandboxCreateRequest{} if protoimpl.UnsafeEnabled { - mi := &file_orchestrator_proto_msgTypes[6] + mi := &file_orchestrator_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -587,7 +736,7 @@ func (x *SandboxCreateRequest) String() string { func (*SandboxCreateRequest) ProtoMessage() {} func (x *SandboxCreateRequest) ProtoReflect() protoreflect.Message { - mi := &file_orchestrator_proto_msgTypes[6] + mi := &file_orchestrator_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -600,7 +749,7 @@ func (x *SandboxCreateRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxCreateRequest.ProtoReflect.Descriptor instead. func (*SandboxCreateRequest) Descriptor() ([]byte, []int) { - return file_orchestrator_proto_rawDescGZIP(), []int{6} + return file_orchestrator_proto_rawDescGZIP(), []int{9} } func (x *SandboxCreateRequest) GetSandbox() *SandboxConfig { @@ -635,7 +784,7 @@ type SandboxCreateResponse struct { func (x *SandboxCreateResponse) Reset() { *x = SandboxCreateResponse{} if protoimpl.UnsafeEnabled { - mi := &file_orchestrator_proto_msgTypes[7] + mi := &file_orchestrator_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -648,7 +797,7 @@ func (x *SandboxCreateResponse) String() string { func (*SandboxCreateResponse) ProtoMessage() {} func (x *SandboxCreateResponse) ProtoReflect() protoreflect.Message { - mi := &file_orchestrator_proto_msgTypes[7] + mi := &file_orchestrator_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -661,7 +810,7 @@ func (x *SandboxCreateResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxCreateResponse.ProtoReflect.Descriptor instead. func (*SandboxCreateResponse) Descriptor() ([]byte, []int) { - return file_orchestrator_proto_rawDescGZIP(), []int{7} + return file_orchestrator_proto_rawDescGZIP(), []int{10} } func (x *SandboxCreateResponse) GetClientId() string { @@ -685,7 +834,7 @@ type SandboxUpdateRequest struct { func (x *SandboxUpdateRequest) Reset() { *x = SandboxUpdateRequest{} if protoimpl.UnsafeEnabled { - mi := &file_orchestrator_proto_msgTypes[8] + mi := &file_orchestrator_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -698,7 +847,7 @@ func (x *SandboxUpdateRequest) String() string { func (*SandboxUpdateRequest) ProtoMessage() {} func (x *SandboxUpdateRequest) ProtoReflect() protoreflect.Message { - mi := &file_orchestrator_proto_msgTypes[8] + mi := &file_orchestrator_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -711,7 +860,7 @@ func (x *SandboxUpdateRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxUpdateRequest.ProtoReflect.Descriptor instead. func (*SandboxUpdateRequest) Descriptor() ([]byte, []int) { - return file_orchestrator_proto_rawDescGZIP(), []int{8} + return file_orchestrator_proto_rawDescGZIP(), []int{11} } func (x *SandboxUpdateRequest) GetSandboxId() string { @@ -746,7 +895,7 @@ type SandboxDeleteRequest struct { func (x *SandboxDeleteRequest) Reset() { *x = SandboxDeleteRequest{} if protoimpl.UnsafeEnabled { - mi := &file_orchestrator_proto_msgTypes[9] + mi := &file_orchestrator_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -759,7 +908,7 @@ func (x *SandboxDeleteRequest) String() string { func (*SandboxDeleteRequest) ProtoMessage() {} func (x *SandboxDeleteRequest) ProtoReflect() protoreflect.Message { - mi := &file_orchestrator_proto_msgTypes[9] + mi := &file_orchestrator_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -772,7 +921,7 @@ func (x *SandboxDeleteRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxDeleteRequest.ProtoReflect.Descriptor instead. func (*SandboxDeleteRequest) Descriptor() ([]byte, []int) { - return file_orchestrator_proto_rawDescGZIP(), []int{9} + return file_orchestrator_proto_rawDescGZIP(), []int{12} } func (x *SandboxDeleteRequest) GetSandboxId() string { @@ -795,7 +944,7 @@ type SandboxPauseRequest struct { func (x *SandboxPauseRequest) Reset() { *x = SandboxPauseRequest{} if protoimpl.UnsafeEnabled { - mi := &file_orchestrator_proto_msgTypes[10] + mi := &file_orchestrator_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -808,7 +957,7 @@ func (x *SandboxPauseRequest) String() string { func (*SandboxPauseRequest) ProtoMessage() {} func (x *SandboxPauseRequest) ProtoReflect() protoreflect.Message { - mi := &file_orchestrator_proto_msgTypes[10] + mi := &file_orchestrator_proto_msgTypes[13] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -821,7 +970,7 @@ func (x *SandboxPauseRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxPauseRequest.ProtoReflect.Descriptor instead. func (*SandboxPauseRequest) Descriptor() ([]byte, []int) { - return file_orchestrator_proto_rawDescGZIP(), []int{10} + return file_orchestrator_proto_rawDescGZIP(), []int{13} } func (x *SandboxPauseRequest) GetSandboxId() string { @@ -857,7 +1006,7 @@ type SandboxCheckpointRequest struct { func (x *SandboxCheckpointRequest) Reset() { *x = SandboxCheckpointRequest{} if protoimpl.UnsafeEnabled { - mi := &file_orchestrator_proto_msgTypes[11] + mi := &file_orchestrator_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -870,7 +1019,7 @@ func (x *SandboxCheckpointRequest) String() string { func (*SandboxCheckpointRequest) ProtoMessage() {} func (x *SandboxCheckpointRequest) ProtoReflect() protoreflect.Message { - mi := &file_orchestrator_proto_msgTypes[11] + mi := &file_orchestrator_proto_msgTypes[14] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -883,7 +1032,7 @@ func (x *SandboxCheckpointRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxCheckpointRequest.ProtoReflect.Descriptor instead. func (*SandboxCheckpointRequest) Descriptor() ([]byte, []int) { - return file_orchestrator_proto_rawDescGZIP(), []int{11} + return file_orchestrator_proto_rawDescGZIP(), []int{14} } func (x *SandboxCheckpointRequest) GetSandboxId() string { @@ -909,7 +1058,7 @@ type SandboxCheckpointResponse struct { func (x *SandboxCheckpointResponse) Reset() { *x = SandboxCheckpointResponse{} if protoimpl.UnsafeEnabled { - mi := &file_orchestrator_proto_msgTypes[12] + mi := &file_orchestrator_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -922,7 +1071,7 @@ func (x *SandboxCheckpointResponse) String() string { func (*SandboxCheckpointResponse) ProtoMessage() {} func (x *SandboxCheckpointResponse) ProtoReflect() protoreflect.Message { - mi := &file_orchestrator_proto_msgTypes[12] + mi := &file_orchestrator_proto_msgTypes[15] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -935,7 +1084,7 @@ func (x *SandboxCheckpointResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxCheckpointResponse.ProtoReflect.Descriptor instead. func (*SandboxCheckpointResponse) Descriptor() ([]byte, []int) { - return file_orchestrator_proto_rawDescGZIP(), []int{12} + return file_orchestrator_proto_rawDescGZIP(), []int{15} } type RunningSandbox struct { @@ -952,7 +1101,7 @@ type RunningSandbox struct { func (x *RunningSandbox) Reset() { *x = RunningSandbox{} if protoimpl.UnsafeEnabled { - mi := &file_orchestrator_proto_msgTypes[13] + mi := &file_orchestrator_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -965,7 +1114,7 @@ func (x *RunningSandbox) String() string { func (*RunningSandbox) ProtoMessage() {} func (x *RunningSandbox) ProtoReflect() protoreflect.Message { - mi := &file_orchestrator_proto_msgTypes[13] + mi := &file_orchestrator_proto_msgTypes[16] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -978,7 +1127,7 @@ func (x *RunningSandbox) ProtoReflect() protoreflect.Message { // Deprecated: Use RunningSandbox.ProtoReflect.Descriptor instead. func (*RunningSandbox) Descriptor() ([]byte, []int) { - return file_orchestrator_proto_rawDescGZIP(), []int{13} + return file_orchestrator_proto_rawDescGZIP(), []int{16} } func (x *RunningSandbox) GetConfig() *SandboxConfig { @@ -1020,7 +1169,7 @@ type SandboxListResponse struct { func (x *SandboxListResponse) Reset() { *x = SandboxListResponse{} if protoimpl.UnsafeEnabled { - mi := &file_orchestrator_proto_msgTypes[14] + mi := &file_orchestrator_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1033,7 +1182,7 @@ func (x *SandboxListResponse) String() string { func (*SandboxListResponse) ProtoMessage() {} func (x *SandboxListResponse) ProtoReflect() protoreflect.Message { - mi := &file_orchestrator_proto_msgTypes[14] + mi := &file_orchestrator_proto_msgTypes[17] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1046,7 +1195,7 @@ func (x *SandboxListResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxListResponse.ProtoReflect.Descriptor instead. func (*SandboxListResponse) Descriptor() ([]byte, []int) { - return file_orchestrator_proto_rawDescGZIP(), []int{14} + return file_orchestrator_proto_rawDescGZIP(), []int{17} } func (x *SandboxListResponse) GetSandboxes() []*RunningSandbox { @@ -1068,7 +1217,7 @@ type CachedBuildInfo struct { func (x *CachedBuildInfo) Reset() { *x = CachedBuildInfo{} if protoimpl.UnsafeEnabled { - mi := &file_orchestrator_proto_msgTypes[15] + mi := &file_orchestrator_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1081,7 +1230,7 @@ func (x *CachedBuildInfo) String() string { func (*CachedBuildInfo) ProtoMessage() {} func (x *CachedBuildInfo) ProtoReflect() protoreflect.Message { - mi := &file_orchestrator_proto_msgTypes[15] + mi := &file_orchestrator_proto_msgTypes[18] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1094,7 +1243,7 @@ func (x *CachedBuildInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use CachedBuildInfo.ProtoReflect.Descriptor instead. func (*CachedBuildInfo) Descriptor() ([]byte, []int) { - return file_orchestrator_proto_rawDescGZIP(), []int{15} + return file_orchestrator_proto_rawDescGZIP(), []int{18} } func (x *CachedBuildInfo) GetBuildId() string { @@ -1122,7 +1271,7 @@ type SandboxListCachedBuildsResponse struct { func (x *SandboxListCachedBuildsResponse) Reset() { *x = SandboxListCachedBuildsResponse{} if protoimpl.UnsafeEnabled { - mi := &file_orchestrator_proto_msgTypes[16] + mi := &file_orchestrator_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1135,7 +1284,7 @@ func (x *SandboxListCachedBuildsResponse) String() string { func (*SandboxListCachedBuildsResponse) ProtoMessage() {} func (x *SandboxListCachedBuildsResponse) ProtoReflect() protoreflect.Message { - mi := &file_orchestrator_proto_msgTypes[16] + mi := &file_orchestrator_proto_msgTypes[19] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1148,7 +1297,7 @@ func (x *SandboxListCachedBuildsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxListCachedBuildsResponse.ProtoReflect.Descriptor instead. func (*SandboxListCachedBuildsResponse) Descriptor() ([]byte, []int) { - return file_orchestrator_proto_rawDescGZIP(), []int{16} + return file_orchestrator_proto_rawDescGZIP(), []int{19} } func (x *SandboxListCachedBuildsResponse) GetBuilds() []*CachedBuildInfo { @@ -1262,133 +1411,162 @@ var file_orchestrator_proto_rawDesc = []byte{ 0x78, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x49, 0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x48, 0x01, 0x52, 0x07, 0x69, 0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, 0x88, 0x01, 0x01, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x65, 0x67, 0x72, 0x65, 0x73, 0x73, 0x42, 0x0a, - 0x0a, 0x08, 0x5f, 0x69, 0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, 0x22, 0x8d, 0x01, 0x0a, 0x1a, 0x53, - 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x45, 0x67, 0x72, - 0x65, 0x73, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x23, 0x0a, 0x0d, 0x61, 0x6c, 0x6c, - 0x6f, 0x77, 0x65, 0x64, 0x5f, 0x63, 0x69, 0x64, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, - 0x52, 0x0c, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x43, 0x69, 0x64, 0x72, 0x73, 0x12, 0x21, - 0x0a, 0x0c, 0x64, 0x65, 0x6e, 0x69, 0x65, 0x64, 0x5f, 0x63, 0x69, 0x64, 0x72, 0x73, 0x18, 0x02, - 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x6e, 0x69, 0x65, 0x64, 0x43, 0x69, 0x64, 0x72, - 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x5f, 0x64, 0x6f, 0x6d, - 0x61, 0x69, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x61, 0x6c, 0x6c, 0x6f, - 0x77, 0x65, 0x64, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x22, 0xb4, 0x01, 0x0a, 0x1b, 0x53, - 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x49, 0x6e, 0x67, - 0x72, 0x65, 0x73, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x35, 0x0a, 0x14, 0x74, 0x72, - 0x61, 0x66, 0x66, 0x69, 0x63, 0x5f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, - 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x12, 0x74, 0x72, 0x61, 0x66, - 0x66, 0x69, 0x63, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x88, 0x01, - 0x01, 0x12, 0x2f, 0x0a, 0x11, 0x6d, 0x61, 0x73, 0x6b, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x0f, - 0x6d, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x88, - 0x01, 0x01, 0x42, 0x17, 0x0a, 0x15, 0x5f, 0x74, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x5f, 0x61, - 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x42, 0x14, 0x0a, 0x12, 0x5f, - 0x6d, 0x61, 0x73, 0x6b, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x68, 0x6f, 0x73, - 0x74, 0x22, 0xb2, 0x01, 0x0a, 0x14, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x43, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x28, 0x0a, 0x07, 0x73, 0x61, - 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x53, 0x61, - 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x07, 0x73, 0x61, 0x6e, - 0x64, 0x62, 0x6f, 0x78, 0x12, 0x39, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, - 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, - 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, - 0x35, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x65, - 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x22, 0x34, 0x0a, 0x15, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, - 0x78, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x22, 0xc3, 0x01, 0x0a, - 0x14, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, - 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x61, 0x6e, 0x64, 0x62, - 0x6f, 0x78, 0x49, 0x64, 0x12, 0x3a, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, - 0x6d, 0x70, 0x48, 0x00, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x88, 0x01, 0x01, - 0x12, 0x38, 0x0a, 0x06, 0x65, 0x67, 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1b, 0x2e, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, - 0x6b, 0x45, 0x67, 0x72, 0x65, 0x73, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x48, 0x01, 0x52, - 0x06, 0x65, 0x67, 0x72, 0x65, 0x73, 0x73, 0x88, 0x01, 0x01, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x65, - 0x6e, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x65, 0x67, 0x72, 0x65, - 0x73, 0x73, 0x22, 0x35, 0x0a, 0x14, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x44, 0x65, 0x6c, - 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x61, - 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x73, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x49, 0x64, 0x22, 0x70, 0x0a, 0x13, 0x53, 0x61, 0x6e, - 0x64, 0x62, 0x6f, 0x78, 0x50, 0x61, 0x75, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x0a, 0x08, 0x5f, 0x69, 0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, 0x22, 0x96, 0x01, 0x0a, 0x17, 0x53, + 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x54, 0x72, 0x61, + 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x3f, 0x0a, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, + 0x78, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, + 0x6d, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, + 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x1a, 0x3a, 0x0a, 0x0c, 0x48, 0x65, 0x61, 0x64, 0x65, + 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, + 0x02, 0x38, 0x01, 0x22, 0x5f, 0x0a, 0x12, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x4e, 0x65, + 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x3b, 0x0a, 0x09, 0x74, 0x72, 0x61, + 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x53, + 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x54, 0x72, 0x61, + 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x48, 0x00, 0x52, 0x09, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, + 0x6f, 0x72, 0x6d, 0x88, 0x01, 0x01, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, + 0x66, 0x6f, 0x72, 0x6d, 0x22, 0x46, 0x0a, 0x19, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x4e, + 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x75, 0x6c, 0x65, + 0x73, 0x12, 0x29, 0x0a, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x13, 0x2e, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, + 0x6b, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x22, 0xa1, 0x02, 0x0a, + 0x1a, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x45, + 0x67, 0x72, 0x65, 0x73, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x23, 0x0a, 0x0d, 0x61, + 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x5f, 0x63, 0x69, 0x64, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x0c, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x43, 0x69, 0x64, 0x72, 0x73, + 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x65, 0x6e, 0x69, 0x65, 0x64, 0x5f, 0x63, 0x69, 0x64, 0x72, 0x73, + 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x6e, 0x69, 0x65, 0x64, 0x43, 0x69, + 0x64, 0x72, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x5f, 0x64, + 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x61, 0x6c, + 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x3c, 0x0a, 0x05, + 0x72, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x53, 0x61, + 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x45, 0x67, 0x72, 0x65, + 0x73, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x52, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x1a, 0x54, 0x0a, 0x0a, 0x52, 0x75, + 0x6c, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x53, 0x61, 0x6e, 0x64, + 0x62, 0x6f, 0x78, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, + 0x52, 0x75, 0x6c, 0x65, 0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, + 0x22, 0xb4, 0x01, 0x0a, 0x1b, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x4e, 0x65, 0x74, 0x77, + 0x6f, 0x72, 0x6b, 0x49, 0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x12, 0x35, 0x0a, 0x14, 0x74, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x5f, 0x61, 0x63, 0x63, 0x65, + 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, + 0x52, 0x12, 0x74, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, + 0x6f, 0x6b, 0x65, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x2f, 0x0a, 0x11, 0x6d, 0x61, 0x73, 0x6b, 0x5f, + 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x48, 0x01, 0x52, 0x0f, 0x6d, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x48, 0x6f, 0x73, 0x74, 0x88, 0x01, 0x01, 0x42, 0x17, 0x0a, 0x15, 0x5f, 0x74, 0x72, 0x61, + 0x66, 0x66, 0x69, 0x63, 0x5f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, + 0x6e, 0x42, 0x14, 0x0a, 0x12, 0x5f, 0x6d, 0x61, 0x73, 0x6b, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x22, 0xb2, 0x01, 0x0a, 0x14, 0x53, 0x61, 0x6e, 0x64, + 0x62, 0x6f, 0x78, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x28, 0x0a, 0x07, 0x73, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x0e, 0x2e, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x52, 0x07, 0x73, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x12, 0x39, 0x0a, 0x0a, 0x73, 0x74, + 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, + 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x35, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x74, 0x69, 0x6d, + 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x22, 0x34, 0x0a, 0x15, + 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, + 0x49, 0x64, 0x22, 0xc3, 0x01, 0x0a, 0x14, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, + 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x09, 0x73, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x49, 0x64, 0x12, 0x3a, 0x0a, 0x08, 0x65, 0x6e, + 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x48, 0x00, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x54, + 0x69, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, 0x38, 0x0a, 0x06, 0x65, 0x67, 0x72, 0x65, 0x73, 0x73, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, + 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x45, 0x67, 0x72, 0x65, 0x73, 0x73, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x48, 0x01, 0x52, 0x06, 0x65, 0x67, 0x72, 0x65, 0x73, 0x73, 0x88, 0x01, 0x01, + 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x65, 0x6e, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x42, 0x09, 0x0a, + 0x07, 0x5f, 0x65, 0x67, 0x72, 0x65, 0x73, 0x73, 0x22, 0x35, 0x0a, 0x14, 0x53, 0x61, 0x6e, 0x64, + 0x62, 0x6f, 0x78, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x5f, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x49, 0x64, 0x12, - 0x1f, 0x0a, 0x0b, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x64, - 0x12, 0x19, 0x0a, 0x08, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x64, 0x22, 0x54, 0x0a, 0x18, 0x53, - 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x70, 0x6f, 0x69, 0x6e, 0x74, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x61, 0x6e, 0x64, 0x62, - 0x6f, 0x78, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x61, 0x6e, - 0x64, 0x62, 0x6f, 0x78, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x49, 0x64, 0x22, + 0x70, 0x0a, 0x13, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x50, 0x61, 0x75, 0x73, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x61, 0x6e, 0x64, 0x62, 0x6f, + 0x78, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x61, 0x6e, 0x64, + 0x62, 0x6f, 0x78, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, + 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x65, 0x6d, 0x70, + 0x6c, 0x61, 0x74, 0x65, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x49, - 0x64, 0x22, 0x1b, 0x0a, 0x19, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x43, 0x68, 0x65, 0x63, - 0x6b, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xc7, - 0x01, 0x0a, 0x0e, 0x52, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, - 0x78, 0x12, 0x26, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x0e, 0x2e, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, - 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, - 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x39, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, - 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, - 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, - 0x65, 0x12, 0x35, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, - 0x07, 0x65, 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x22, 0x44, 0x0a, 0x13, 0x53, 0x61, 0x6e, 0x64, - 0x62, 0x6f, 0x78, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x2d, 0x0a, 0x09, 0x73, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x52, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x53, 0x61, 0x6e, 0x64, - 0x62, 0x6f, 0x78, 0x52, 0x09, 0x73, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x65, 0x73, 0x22, 0x71, - 0x0a, 0x0f, 0x43, 0x61, 0x63, 0x68, 0x65, 0x64, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x6e, 0x66, - 0x6f, 0x12, 0x19, 0x0a, 0x08, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x64, 0x12, 0x43, 0x0a, 0x0f, - 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, - 0x70, 0x52, 0x0e, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d, - 0x65, 0x22, 0x4b, 0x0a, 0x1f, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x4c, 0x69, 0x73, 0x74, - 0x43, 0x61, 0x63, 0x68, 0x65, 0x64, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x28, 0x0a, 0x06, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x73, 0x18, 0x01, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x43, 0x61, 0x63, 0x68, 0x65, 0x64, 0x42, 0x75, 0x69, - 0x6c, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x06, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x73, 0x32, 0xbb, - 0x03, 0x0a, 0x0e, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x12, 0x37, 0x0a, 0x06, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, 0x15, 0x2e, 0x53, 0x61, - 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x43, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x06, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x12, 0x15, 0x2e, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, - 0x70, 0x74, 0x79, 0x12, 0x34, 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x16, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, - 0x70, 0x74, 0x79, 0x1a, 0x14, 0x2e, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x4c, 0x69, 0x73, - 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x06, 0x44, 0x65, 0x6c, - 0x65, 0x74, 0x65, 0x12, 0x15, 0x2e, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x44, 0x65, 0x6c, - 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, - 0x74, 0x79, 0x12, 0x35, 0x0a, 0x05, 0x50, 0x61, 0x75, 0x73, 0x65, 0x12, 0x14, 0x2e, 0x53, 0x61, - 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x50, 0x61, 0x75, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x64, 0x22, 0x54, 0x0a, 0x18, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x43, 0x68, 0x65, 0x63, + 0x6b, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, + 0x0a, 0x73, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x09, 0x73, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, + 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x62, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x64, 0x22, 0x1b, 0x0a, 0x19, 0x53, 0x61, 0x6e, 0x64, 0x62, + 0x6f, 0x78, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xc7, 0x01, 0x0a, 0x0e, 0x52, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, + 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x12, 0x26, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, + 0x78, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, + 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x39, 0x0a, 0x0a, + 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x73, 0x74, + 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x35, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x74, + 0x69, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x22, 0x44, + 0x0a, 0x13, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x09, 0x73, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, + 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x52, 0x75, 0x6e, 0x6e, 0x69, + 0x6e, 0x67, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x52, 0x09, 0x73, 0x61, 0x6e, 0x64, 0x62, + 0x6f, 0x78, 0x65, 0x73, 0x22, 0x71, 0x0a, 0x0f, 0x43, 0x61, 0x63, 0x68, 0x65, 0x64, 0x42, 0x75, + 0x69, 0x6c, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x19, 0x0a, 0x08, 0x62, 0x75, 0x69, 0x6c, 0x64, + 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, 0x75, 0x69, 0x6c, 0x64, + 0x49, 0x64, 0x12, 0x43, 0x0a, 0x0f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, + 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0e, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x22, 0x4b, 0x0a, 0x1f, 0x53, 0x61, 0x6e, 0x64, 0x62, + 0x6f, 0x78, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x61, 0x63, 0x68, 0x65, 0x64, 0x42, 0x75, 0x69, 0x6c, + 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x28, 0x0a, 0x06, 0x62, 0x75, + 0x69, 0x6c, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x43, 0x61, 0x63, + 0x68, 0x65, 0x64, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x06, 0x62, 0x75, + 0x69, 0x6c, 0x64, 0x73, 0x32, 0xbb, 0x03, 0x0a, 0x0e, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x37, 0x0a, 0x06, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x12, 0x15, 0x2e, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x53, 0x61, 0x6e, 0x64, 0x62, + 0x6f, 0x78, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x37, 0x0a, 0x06, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x15, 0x2e, 0x53, 0x61, 0x6e, + 0x64, 0x62, 0x6f, 0x78, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x43, 0x0a, 0x0a, 0x43, 0x68, 0x65, - 0x63, 0x6b, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x19, 0x2e, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, - 0x78, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x43, 0x68, 0x65, 0x63, - 0x6b, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, - 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x61, 0x63, 0x68, 0x65, 0x64, 0x42, 0x75, 0x69, 0x6c, - 0x64, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x20, 0x2e, 0x53, 0x61, 0x6e, - 0x64, 0x62, 0x6f, 0x78, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x61, 0x63, 0x68, 0x65, 0x64, 0x42, 0x75, - 0x69, 0x6c, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x2f, 0x5a, 0x2d, - 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, - 0x6f, 0x6d, 0x2f, 0x65, 0x32, 0x62, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x69, 0x6e, 0x66, 0x72, 0x61, - 0x2f, 0x6f, 0x72, 0x63, 0x68, 0x65, 0x73, 0x74, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x62, 0x06, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x34, 0x0a, 0x04, 0x4c, 0x69, 0x73, + 0x74, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x14, 0x2e, 0x53, 0x61, 0x6e, 0x64, + 0x62, 0x6f, 0x78, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x37, 0x0a, 0x06, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x15, 0x2e, 0x53, 0x61, 0x6e, 0x64, + 0x62, 0x6f, 0x78, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x35, 0x0a, 0x05, 0x50, 0x61, 0x75, 0x73, + 0x65, 0x12, 0x14, 0x2e, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x50, 0x61, 0x75, 0x73, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, + 0x43, 0x0a, 0x0a, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x19, 0x2e, + 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x70, 0x6f, 0x69, 0x6e, + 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x53, 0x61, 0x6e, 0x64, 0x62, + 0x6f, 0x78, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x61, 0x63, 0x68, + 0x65, 0x64, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, + 0x1a, 0x20, 0x2e, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x61, + 0x63, 0x68, 0x65, 0x64, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x42, 0x2f, 0x5a, 0x2d, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, + 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x65, 0x32, 0x62, 0x2d, 0x64, 0x65, 0x76, + 0x2f, 0x69, 0x6e, 0x66, 0x72, 0x61, 0x2f, 0x6f, 0x72, 0x63, 0x68, 0x65, 0x73, 0x74, 0x72, 0x61, + 0x74, 0x6f, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1403,68 +1581,78 @@ func file_orchestrator_proto_rawDescGZIP() []byte { return file_orchestrator_proto_rawDescData } -var file_orchestrator_proto_msgTypes = make([]protoimpl.MessageInfo, 19) +var file_orchestrator_proto_msgTypes = make([]protoimpl.MessageInfo, 24) var file_orchestrator_proto_goTypes = []interface{}{ (*SandboxConfig)(nil), // 0: SandboxConfig (*SandboxAutoResumeConfig)(nil), // 1: SandboxAutoResumeConfig (*SandboxVolumeMount)(nil), // 2: SandboxVolumeMount (*SandboxNetworkConfig)(nil), // 3: SandboxNetworkConfig - (*SandboxNetworkEgressConfig)(nil), // 4: SandboxNetworkEgressConfig - (*SandboxNetworkIngressConfig)(nil), // 5: SandboxNetworkIngressConfig - (*SandboxCreateRequest)(nil), // 6: SandboxCreateRequest - (*SandboxCreateResponse)(nil), // 7: SandboxCreateResponse - (*SandboxUpdateRequest)(nil), // 8: SandboxUpdateRequest - (*SandboxDeleteRequest)(nil), // 9: SandboxDeleteRequest - (*SandboxPauseRequest)(nil), // 10: SandboxPauseRequest - (*SandboxCheckpointRequest)(nil), // 11: SandboxCheckpointRequest - (*SandboxCheckpointResponse)(nil), // 12: SandboxCheckpointResponse - (*RunningSandbox)(nil), // 13: RunningSandbox - (*SandboxListResponse)(nil), // 14: SandboxListResponse - (*CachedBuildInfo)(nil), // 15: CachedBuildInfo - (*SandboxListCachedBuildsResponse)(nil), // 16: SandboxListCachedBuildsResponse - nil, // 17: SandboxConfig.EnvVarsEntry - nil, // 18: SandboxConfig.MetadataEntry - (*timestamppb.Timestamp)(nil), // 19: google.protobuf.Timestamp - (*emptypb.Empty)(nil), // 20: google.protobuf.Empty + (*SandboxNetworkTransform)(nil), // 4: SandboxNetworkTransform + (*SandboxNetworkRule)(nil), // 5: SandboxNetworkRule + (*SandboxNetworkDomainRules)(nil), // 6: SandboxNetworkDomainRules + (*SandboxNetworkEgressConfig)(nil), // 7: SandboxNetworkEgressConfig + (*SandboxNetworkIngressConfig)(nil), // 8: SandboxNetworkIngressConfig + (*SandboxCreateRequest)(nil), // 9: SandboxCreateRequest + (*SandboxCreateResponse)(nil), // 10: SandboxCreateResponse + (*SandboxUpdateRequest)(nil), // 11: SandboxUpdateRequest + (*SandboxDeleteRequest)(nil), // 12: SandboxDeleteRequest + (*SandboxPauseRequest)(nil), // 13: SandboxPauseRequest + (*SandboxCheckpointRequest)(nil), // 14: SandboxCheckpointRequest + (*SandboxCheckpointResponse)(nil), // 15: SandboxCheckpointResponse + (*RunningSandbox)(nil), // 16: RunningSandbox + (*SandboxListResponse)(nil), // 17: SandboxListResponse + (*CachedBuildInfo)(nil), // 18: CachedBuildInfo + (*SandboxListCachedBuildsResponse)(nil), // 19: SandboxListCachedBuildsResponse + nil, // 20: SandboxConfig.EnvVarsEntry + nil, // 21: SandboxConfig.MetadataEntry + nil, // 22: SandboxNetworkTransform.HeadersEntry + nil, // 23: SandboxNetworkEgressConfig.RulesEntry + (*timestamppb.Timestamp)(nil), // 24: google.protobuf.Timestamp + (*emptypb.Empty)(nil), // 25: google.protobuf.Empty } var file_orchestrator_proto_depIdxs = []int32{ - 17, // 0: SandboxConfig.env_vars:type_name -> SandboxConfig.EnvVarsEntry - 18, // 1: SandboxConfig.metadata:type_name -> SandboxConfig.MetadataEntry + 20, // 0: SandboxConfig.env_vars:type_name -> SandboxConfig.EnvVarsEntry + 21, // 1: SandboxConfig.metadata:type_name -> SandboxConfig.MetadataEntry 3, // 2: SandboxConfig.network:type_name -> SandboxNetworkConfig 2, // 3: SandboxConfig.volumeMounts:type_name -> SandboxVolumeMount 1, // 4: SandboxConfig.auto_resume:type_name -> SandboxAutoResumeConfig - 4, // 5: SandboxNetworkConfig.egress:type_name -> SandboxNetworkEgressConfig - 5, // 6: SandboxNetworkConfig.ingress:type_name -> SandboxNetworkIngressConfig - 0, // 7: SandboxCreateRequest.sandbox:type_name -> SandboxConfig - 19, // 8: SandboxCreateRequest.start_time:type_name -> google.protobuf.Timestamp - 19, // 9: SandboxCreateRequest.end_time:type_name -> google.protobuf.Timestamp - 19, // 10: SandboxUpdateRequest.end_time:type_name -> google.protobuf.Timestamp - 4, // 11: SandboxUpdateRequest.egress:type_name -> SandboxNetworkEgressConfig - 0, // 12: RunningSandbox.config:type_name -> SandboxConfig - 19, // 13: RunningSandbox.start_time:type_name -> google.protobuf.Timestamp - 19, // 14: RunningSandbox.end_time:type_name -> google.protobuf.Timestamp - 13, // 15: SandboxListResponse.sandboxes:type_name -> RunningSandbox - 19, // 16: CachedBuildInfo.expiration_time:type_name -> google.protobuf.Timestamp - 15, // 17: SandboxListCachedBuildsResponse.builds:type_name -> CachedBuildInfo - 6, // 18: SandboxService.Create:input_type -> SandboxCreateRequest - 8, // 19: SandboxService.Update:input_type -> SandboxUpdateRequest - 20, // 20: SandboxService.List:input_type -> google.protobuf.Empty - 9, // 21: SandboxService.Delete:input_type -> SandboxDeleteRequest - 10, // 22: SandboxService.Pause:input_type -> SandboxPauseRequest - 11, // 23: SandboxService.Checkpoint:input_type -> SandboxCheckpointRequest - 20, // 24: SandboxService.ListCachedBuilds:input_type -> google.protobuf.Empty - 7, // 25: SandboxService.Create:output_type -> SandboxCreateResponse - 20, // 26: SandboxService.Update:output_type -> google.protobuf.Empty - 14, // 27: SandboxService.List:output_type -> SandboxListResponse - 20, // 28: SandboxService.Delete:output_type -> google.protobuf.Empty - 20, // 29: SandboxService.Pause:output_type -> google.protobuf.Empty - 12, // 30: SandboxService.Checkpoint:output_type -> SandboxCheckpointResponse - 16, // 31: SandboxService.ListCachedBuilds:output_type -> SandboxListCachedBuildsResponse - 25, // [25:32] is the sub-list for method output_type - 18, // [18:25] is the sub-list for method input_type - 18, // [18:18] is the sub-list for extension type_name - 18, // [18:18] is the sub-list for extension extendee - 0, // [0:18] is the sub-list for field type_name + 7, // 5: SandboxNetworkConfig.egress:type_name -> SandboxNetworkEgressConfig + 8, // 6: SandboxNetworkConfig.ingress:type_name -> SandboxNetworkIngressConfig + 22, // 7: SandboxNetworkTransform.headers:type_name -> SandboxNetworkTransform.HeadersEntry + 4, // 8: SandboxNetworkRule.transform:type_name -> SandboxNetworkTransform + 5, // 9: SandboxNetworkDomainRules.rules:type_name -> SandboxNetworkRule + 23, // 10: SandboxNetworkEgressConfig.rules:type_name -> SandboxNetworkEgressConfig.RulesEntry + 0, // 11: SandboxCreateRequest.sandbox:type_name -> SandboxConfig + 24, // 12: SandboxCreateRequest.start_time:type_name -> google.protobuf.Timestamp + 24, // 13: SandboxCreateRequest.end_time:type_name -> google.protobuf.Timestamp + 24, // 14: SandboxUpdateRequest.end_time:type_name -> google.protobuf.Timestamp + 7, // 15: SandboxUpdateRequest.egress:type_name -> SandboxNetworkEgressConfig + 0, // 16: RunningSandbox.config:type_name -> SandboxConfig + 24, // 17: RunningSandbox.start_time:type_name -> google.protobuf.Timestamp + 24, // 18: RunningSandbox.end_time:type_name -> google.protobuf.Timestamp + 16, // 19: SandboxListResponse.sandboxes:type_name -> RunningSandbox + 24, // 20: CachedBuildInfo.expiration_time:type_name -> google.protobuf.Timestamp + 18, // 21: SandboxListCachedBuildsResponse.builds:type_name -> CachedBuildInfo + 6, // 22: SandboxNetworkEgressConfig.RulesEntry.value:type_name -> SandboxNetworkDomainRules + 9, // 23: SandboxService.Create:input_type -> SandboxCreateRequest + 11, // 24: SandboxService.Update:input_type -> SandboxUpdateRequest + 25, // 25: SandboxService.List:input_type -> google.protobuf.Empty + 12, // 26: SandboxService.Delete:input_type -> SandboxDeleteRequest + 13, // 27: SandboxService.Pause:input_type -> SandboxPauseRequest + 14, // 28: SandboxService.Checkpoint:input_type -> SandboxCheckpointRequest + 25, // 29: SandboxService.ListCachedBuilds:input_type -> google.protobuf.Empty + 10, // 30: SandboxService.Create:output_type -> SandboxCreateResponse + 25, // 31: SandboxService.Update:output_type -> google.protobuf.Empty + 17, // 32: SandboxService.List:output_type -> SandboxListResponse + 25, // 33: SandboxService.Delete:output_type -> google.protobuf.Empty + 25, // 34: SandboxService.Pause:output_type -> google.protobuf.Empty + 15, // 35: SandboxService.Checkpoint:output_type -> SandboxCheckpointResponse + 19, // 36: SandboxService.ListCachedBuilds:output_type -> SandboxListCachedBuildsResponse + 30, // [30:37] is the sub-list for method output_type + 23, // [23:30] is the sub-list for method input_type + 23, // [23:23] is the sub-list for extension type_name + 23, // [23:23] is the sub-list for extension extendee + 0, // [0:23] is the sub-list for field type_name } func init() { file_orchestrator_proto_init() } @@ -1522,7 +1710,7 @@ func file_orchestrator_proto_init() { } } file_orchestrator_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SandboxNetworkEgressConfig); i { + switch v := v.(*SandboxNetworkTransform); i { case 0: return &v.state case 1: @@ -1534,7 +1722,7 @@ func file_orchestrator_proto_init() { } } file_orchestrator_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SandboxNetworkIngressConfig); i { + switch v := v.(*SandboxNetworkRule); i { case 0: return &v.state case 1: @@ -1546,7 +1734,7 @@ func file_orchestrator_proto_init() { } } file_orchestrator_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SandboxCreateRequest); i { + switch v := v.(*SandboxNetworkDomainRules); i { case 0: return &v.state case 1: @@ -1558,7 +1746,7 @@ func file_orchestrator_proto_init() { } } file_orchestrator_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SandboxCreateResponse); i { + switch v := v.(*SandboxNetworkEgressConfig); i { case 0: return &v.state case 1: @@ -1570,7 +1758,7 @@ func file_orchestrator_proto_init() { } } file_orchestrator_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SandboxUpdateRequest); i { + switch v := v.(*SandboxNetworkIngressConfig); i { case 0: return &v.state case 1: @@ -1582,7 +1770,7 @@ func file_orchestrator_proto_init() { } } file_orchestrator_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SandboxDeleteRequest); i { + switch v := v.(*SandboxCreateRequest); i { case 0: return &v.state case 1: @@ -1594,7 +1782,7 @@ func file_orchestrator_proto_init() { } } file_orchestrator_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SandboxPauseRequest); i { + switch v := v.(*SandboxCreateResponse); i { case 0: return &v.state case 1: @@ -1606,7 +1794,7 @@ func file_orchestrator_proto_init() { } } file_orchestrator_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SandboxCheckpointRequest); i { + switch v := v.(*SandboxUpdateRequest); i { case 0: return &v.state case 1: @@ -1618,7 +1806,7 @@ func file_orchestrator_proto_init() { } } file_orchestrator_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SandboxCheckpointResponse); i { + switch v := v.(*SandboxDeleteRequest); i { case 0: return &v.state case 1: @@ -1630,7 +1818,7 @@ func file_orchestrator_proto_init() { } } file_orchestrator_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RunningSandbox); i { + switch v := v.(*SandboxPauseRequest); i { case 0: return &v.state case 1: @@ -1642,7 +1830,7 @@ func file_orchestrator_proto_init() { } } file_orchestrator_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SandboxListResponse); i { + switch v := v.(*SandboxCheckpointRequest); i { case 0: return &v.state case 1: @@ -1654,7 +1842,7 @@ func file_orchestrator_proto_init() { } } file_orchestrator_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CachedBuildInfo); i { + switch v := v.(*SandboxCheckpointResponse); i { case 0: return &v.state case 1: @@ -1666,6 +1854,42 @@ func file_orchestrator_proto_init() { } } file_orchestrator_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RunningSandbox); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_orchestrator_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SandboxListResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_orchestrator_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CachedBuildInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_orchestrator_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*SandboxListCachedBuildsResponse); i { case 0: return &v.state @@ -1682,13 +1906,14 @@ func file_orchestrator_proto_init() { file_orchestrator_proto_msgTypes[3].OneofWrappers = []interface{}{} file_orchestrator_proto_msgTypes[5].OneofWrappers = []interface{}{} file_orchestrator_proto_msgTypes[8].OneofWrappers = []interface{}{} + file_orchestrator_proto_msgTypes[11].OneofWrappers = []interface{}{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_orchestrator_proto_rawDesc, NumEnums: 0, - NumMessages: 19, + NumMessages: 24, NumExtensions: 0, NumServices: 1, }, From 8569059ac3c06bab96f071a88d11cdf667f020e0 Mon Sep 17 00:00:00 2001 From: Jiri Sveceny Date: Tue, 28 Apr 2026 10:48:17 +0200 Subject: [PATCH 03/19] Bump orchestrator verions with network rules support --- packages/orchestrator/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/orchestrator/main.go b/packages/orchestrator/main.go index d4b8347dae..b1b415d520 100644 --- a/packages/orchestrator/main.go +++ b/packages/orchestrator/main.go @@ -7,7 +7,7 @@ import ( "github.com/e2b-dev/infra/packages/orchestrator/pkg/tcpfirewall" ) -const version = "0.1.0" +const version = "0.2.0" var commitSHA string From 6627f2240e6baa38f4a5ae556124ef508f9448bd Mon Sep 17 00:00:00 2001 From: Jiri Sveceny Date: Tue, 28 Apr 2026 11:08:52 +0200 Subject: [PATCH 04/19] Validate and propagate sbx network rules to orchestrator --- .../api/internal/handlers/sandbox_create.go | 134 +++++++++++++++++- .../internal/handlers/sandbox_create_test.go | 3 +- packages/api/internal/handlers/sandbox_get.go | 19 +++ .../handlers/sandbox_network_update.go | 15 +- .../internal/orchestrator/create_instance.go | 20 ++- .../internal/orchestrator/update_network.go | 3 + packages/db/pkg/types/types.go | 13 +- packages/shared/pkg/featureflags/flags.go | 2 + 8 files changed, 196 insertions(+), 13 deletions(-) diff --git a/packages/api/internal/handlers/sandbox_create.go b/packages/api/internal/handlers/sandbox_create.go index a987f43296..7c7df1f776 100644 --- a/packages/api/internal/handlers/sandbox_create.go +++ b/packages/api/internal/handlers/sandbox_create.go @@ -47,6 +47,12 @@ const ( // Network validation error messages ErrMsgDomainsRequireBlockAll = "When specifying allowed domains in allow out, you must include 'ALL_TRAFFIC' in deny out to block all other traffic." + + maxNetworkRuleDomains = 10 + maxNetworkRuleTransformsPerDomain = 1 + maxNetworkRuleDomainLen = 128 + maxNetworkRuleHeaderNameLen = 64 + maxNetworkRuleHeaderValueLen = 256 ) func (a *APIStore) PostSandboxes(c *gin.Context) { @@ -174,7 +180,7 @@ func (a *APIStore) PostSandboxes(c *gin.Context) { var network *types.SandboxNetworkConfig if n := body.Network; n != nil { - if err := validateNetworkConfig(n); err != nil { + if err := validateNetworkConfig(ctx, a.featureFlags, teamInfo.Team.ID, n); err != nil { telemetry.ReportError(ctx, "invalid network config", err.Err, telemetry.WithSandboxID(sandboxID)) a.sendAPIStoreError(c, err.Code, err.ClientMsg) @@ -189,6 +195,7 @@ func (a *APIStore) PostSandboxes(c *gin.Context) { Egress: &types.SandboxNetworkEgressConfig{ AllowedAddresses: sharedUtils.DerefOrDefault(n.AllowOut, nil), DeniedAddresses: sharedUtils.DerefOrDefault(n.DenyOut, nil), + Rules: apiRulesToDBRules(n.Rules), }, } @@ -514,7 +521,33 @@ func splitHostPortOptional(hostport string) (host string, port string, err error return host, port, nil } -func validateNetworkConfig(network *api.SandboxNetworkConfig) *api.APIError { +func apiRulesToDBRules(apiRules *map[string][]api.SandboxNetworkRule) map[string][]types.SandboxNetworkRule { + if apiRules == nil { + return nil + } + + dbRules := make(map[string][]types.SandboxNetworkRule, len(*apiRules)) + for domain, rules := range *apiRules { + dbDomainRules := make([]types.SandboxNetworkRule, 0, len(rules)) + for _, r := range rules { + dbRule := types.SandboxNetworkRule{} + + if r.Transform != nil { + dbRule.Transform = &types.SandboxNetworkTransform{ + Headers: sharedUtils.DerefOrDefault(r.Transform.Headers, nil), + } + } + + dbDomainRules = append(dbDomainRules, dbRule) + } + + dbRules[domain] = dbDomainRules + } + + return dbRules +} + +func validateNetworkConfig(ctx context.Context, featureFlags featureFlagsClient, teamID uuid.UUID, network *api.SandboxNetworkConfig) *api.APIError { if network == nil { return nil } @@ -550,7 +583,11 @@ func validateNetworkConfig(network *api.SandboxNetworkConfig) *api.APIError { denyOut := sharedUtils.DerefOrDefault(network.DenyOut, nil) allowOut := sharedUtils.DerefOrDefault(network.AllowOut, nil) - return validateEgressRules(allowOut, denyOut) + if err := validateEgressRules(allowOut, denyOut); err != nil { + return err + } + + return validateNetworkRules(ctx, featureFlags, teamID, network.Rules) } // validateEgressRules validates egress allow/deny rules: @@ -593,3 +630,94 @@ func validateEgressRules(allowOut, denyOut []string) *api.APIError { return nil } + +func validateNetworkRules(ctx context.Context, featureFlags featureFlagsClient, teamID uuid.UUID, rules *map[string][]api.SandboxNetworkRule) *api.APIError { + if rules == nil { + return nil + } + + if !featureFlags.BoolFlag(ctx, featureflags.NetworkTransformRulesFlag, featureflags.TeamContext(teamID.String())) { + return &api.APIError{ + Code: http.StatusBadRequest, + Err: fmt.Errorf("team %s is not allowed to use network transform rules", teamID), + ClientMsg: "Network transform rules are not available for your team.", + } + } + + if len(*rules) > maxNetworkRuleDomains { + return &api.APIError{ + Code: http.StatusBadRequest, + Err: fmt.Errorf("too many rule domains: %d (max %d)", len(*rules), maxNetworkRuleDomains), + ClientMsg: fmt.Sprintf("Network rules can have at most %d domains.", maxNetworkRuleDomains), + } + } + + for domain, domainRules := range *rules { + if len(domain) == 0 { + return &api.APIError{ + Code: http.StatusBadRequest, + Err: errors.New("rule domain must not be empty"), + ClientMsg: "Rule domain must not be empty.", + } + } + + if len(domain) > maxNetworkRuleDomainLen { + return &api.APIError{ + Code: http.StatusBadRequest, + Err: fmt.Errorf("rule domain %q exceeds max length %d", domain, maxNetworkRuleDomainLen), + ClientMsg: fmt.Sprintf("Rule domain %q exceeds maximum length of %d characters.", domain, maxNetworkRuleDomainLen), + } + } + + if _, err := idna.Lookup.ToASCII(domain); err != nil { + return &api.APIError{ + Code: http.StatusBadRequest, + Err: fmt.Errorf("rule domain %q is not a valid domain: %w", domain, err), + ClientMsg: fmt.Sprintf("Rule domain %q is not a valid domain name.", domain), + } + } + + if len(domainRules) > maxNetworkRuleTransformsPerDomain { + return &api.APIError{ + Code: http.StatusBadRequest, + Err: fmt.Errorf("domain %q has %d transforms (max %d)", domain, len(domainRules), maxNetworkRuleTransformsPerDomain), + ClientMsg: fmt.Sprintf("Domain %q can have at most %d transform rule.", domain, maxNetworkRuleTransformsPerDomain), + } + } + + for _, rule := range domainRules { + if rule.Transform == nil { + continue + } + + headers := sharedUtils.DerefOrDefault(rule.Transform.Headers, nil) + for name, value := range headers { + if len(name) == 0 { + return &api.APIError{ + Code: http.StatusBadRequest, + Err: fmt.Errorf("header name in rule for domain %q must not be empty", domain), + ClientMsg: fmt.Sprintf("Header name in rule for domain %q must not be empty.", domain), + } + } + + if len(name) > maxNetworkRuleHeaderNameLen { + return &api.APIError{ + Code: http.StatusBadRequest, + Err: fmt.Errorf("header name %q in rule for domain %q exceeds max length %d", name, domain, maxNetworkRuleHeaderNameLen), + ClientMsg: fmt.Sprintf("Header name %q in rule for domain %q exceeds maximum length of %d characters.", name, domain, maxNetworkRuleHeaderNameLen), + } + } + + if len(value) > maxNetworkRuleHeaderValueLen { + return &api.APIError{ + Code: http.StatusBadRequest, + Err: fmt.Errorf("value for header %q in rule for domain %q exceeds max length %d", name, domain, maxNetworkRuleHeaderValueLen), + ClientMsg: fmt.Sprintf("Value for header %q in rule for domain %q exceeds maximum length of %d characters.", name, domain, maxNetworkRuleHeaderValueLen), + } + } + } + } + } + + return nil +} diff --git a/packages/api/internal/handlers/sandbox_create_test.go b/packages/api/internal/handlers/sandbox_create_test.go index 7bc88e64dd..46837a0037 100644 --- a/packages/api/internal/handlers/sandbox_create_test.go +++ b/packages/api/internal/handlers/sandbox_create_test.go @@ -278,7 +278,8 @@ func TestValidateNetworkConfig(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() - err := validateNetworkConfig(tt.network) + mockFF := handlersmocks.NewMockFeatureFlagsClient(t) + err := validateNetworkConfig(context.Background(), mockFF, uuid.Nil, tt.network) if tt.wantErr { if err == nil { diff --git a/packages/api/internal/handlers/sandbox_get.go b/packages/api/internal/handlers/sandbox_get.go index db1af5f23a..0e76441c1c 100644 --- a/packages/api/internal/handlers/sandbox_get.go +++ b/packages/api/internal/handlers/sandbox_get.go @@ -46,9 +46,28 @@ func dbNetworkConfigToAPI(network *dbtypes.SandboxNetworkConfig) *api.SandboxNet if egress.AllowedAddresses != nil { result.AllowOut = &egress.AllowedAddresses } + if egress.DeniedAddresses != nil { result.DenyOut = &egress.DeniedAddresses } + + if egress.Rules != nil { + apiRules := make(map[string][]api.SandboxNetworkRule, len(egress.Rules)) + for domain, dbRules := range egress.Rules { + apiDomainRules := make([]api.SandboxNetworkRule, 0, len(dbRules)) + for _, r := range dbRules { + apiRule := api.SandboxNetworkRule{} + if r.Transform != nil { + apiRule.Transform = &api.SandboxNetworkTransform{ + Headers: &r.Transform.Headers, + } + } + apiDomainRules = append(apiDomainRules, apiRule) + } + apiRules[domain] = apiDomainRules + } + result.Rules = &apiRules + } } return result diff --git a/packages/api/internal/handlers/sandbox_network_update.go b/packages/api/internal/handlers/sandbox_network_update.go index 0b68d983a2..629ec9c55e 100644 --- a/packages/api/internal/handlers/sandbox_network_update.go +++ b/packages/api/internal/handlers/sandbox_network_update.go @@ -13,10 +13,7 @@ import ( "github.com/e2b-dev/infra/packages/shared/pkg/telemetry" ) -func (a *APIStore) PutSandboxesSandboxIDNetwork( - c *gin.Context, - sandboxID string, -) { +func (a *APIStore) PutSandboxesSandboxIDNetwork(c *gin.Context, sandboxID string) { ctx := c.Request.Context() var err error @@ -53,7 +50,15 @@ func (a *APIStore) PutSandboxesSandboxIDNetwork( return } - if apiErr := a.orchestrator.UpdateSandboxNetworkConfig(ctx, team.ID, sandboxID, allowedEntries, deniedEntries, body.AllowInternetAccess); apiErr != nil { + if apiErr := validateNetworkRules(ctx, a.featureFlags, team.ID, body.Rules); apiErr != nil { + a.sendAPIStoreError(c, apiErr.Code, apiErr.ClientMsg) + + return + } + + rules := apiRulesToDBRules(body.Rules) + + if apiErr := a.orchestrator.UpdateSandboxNetworkConfig(ctx, team.ID, sandboxID, allowedEntries, deniedEntries, rules, body.AllowInternetAccess); apiErr != nil { telemetry.ReportErrorByCode(ctx, apiErr.Code, "error updating sandbox network config", apiErr.Err) a.sendAPIStoreError(c, apiErr.Code, apiErr.ClientMsg) diff --git a/packages/api/internal/orchestrator/create_instance.go b/packages/api/internal/orchestrator/create_instance.go index 15f78aa008..3cdcf17faa 100644 --- a/packages/api/internal/orchestrator/create_instance.go +++ b/packages/api/internal/orchestrator/create_instance.go @@ -55,17 +55,33 @@ type SandboxMetadata struct { // allow/deny entry lists. It splits allowed entries into CIDRs and domains, // and adds the default nameserver when domains are present so the sandbox can // resolve them. -func buildEgressConfig(allowedEntries, deniedEntries []string) *orchestrator.SandboxNetworkEgressConfig { +func buildEgressConfig(allowedEntries, deniedEntries []string, rules map[string][]types.SandboxNetworkRule) *orchestrator.SandboxNetworkEgressConfig { allowedAddresses, allowedDomains := sandbox_network.ParseAddressesAndDomains(allowedEntries) if len(allowedDomains) > 0 { allowedAddresses = append(allowedAddresses, sandbox_network.DefaultNameserver) } + orchRules := make(map[string]*orchestrator.SandboxNetworkDomainRules, len(rules)) + for domain, domainRules := range rules { + orchRuleList := make([]*orchestrator.SandboxNetworkRule, 0, len(domainRules)) + for _, r := range domainRules { + orchRule := &orchestrator.SandboxNetworkRule{} + if r.Transform != nil { + orchRule.Transform = &orchestrator.SandboxNetworkTransform{ + Headers: r.Transform.Headers, + } + } + orchRuleList = append(orchRuleList, orchRule) + } + orchRules[domain] = &orchestrator.SandboxNetworkDomainRules{Rules: orchRuleList} + } + return &orchestrator.SandboxNetworkEgressConfig{ AllowedCidrs: sandbox_network.AddressStringsToCIDRs(allowedAddresses), DeniedCidrs: sandbox_network.AddressStringsToCIDRs(deniedEntries), AllowedDomains: allowedDomains, + Rules: orchRules, } } @@ -79,7 +95,7 @@ func buildNetworkConfig(network *types.SandboxNetworkConfig, allowInternetAccess } if network != nil && network.Egress != nil { - orchNetwork.Egress = buildEgressConfig(network.Egress.AllowedAddresses, network.Egress.DeniedAddresses) + orchNetwork.Egress = buildEgressConfig(network.Egress.AllowedAddresses, network.Egress.DeniedAddresses, network.Egress.Rules) } if network != nil && network.Ingress != nil { diff --git a/packages/api/internal/orchestrator/update_network.go b/packages/api/internal/orchestrator/update_network.go index f54ca2661e..cfceb45d30 100644 --- a/packages/api/internal/orchestrator/update_network.go +++ b/packages/api/internal/orchestrator/update_network.go @@ -26,12 +26,14 @@ func (o *Orchestrator) UpdateSandboxNetworkConfig( sandboxID string, allowedEntries []string, deniedEntries []string, + rules map[string][]types.SandboxNetworkRule, allowInternetAccess *bool, ) *api.APIError { network := &types.SandboxNetworkConfig{ Egress: &types.SandboxNetworkEgressConfig{ AllowedAddresses: allowedEntries, DeniedAddresses: deniedEntries, + Rules: rules, }, } orchNetwork := buildNetworkConfig(network, allowInternetAccess, nil) @@ -49,6 +51,7 @@ func (o *Orchestrator) UpdateSandboxNetworkConfig( sbx.Network.Egress = &types.SandboxNetworkEgressConfig{ AllowedAddresses: allowedEntries, DeniedAddresses: deniedEntries, + Rules: rules, } if allowInternetAccess != nil { diff --git a/packages/db/pkg/types/types.go b/packages/db/pkg/types/types.go index a1444001a7..996dd1523f 100644 --- a/packages/db/pkg/types/types.go +++ b/packages/db/pkg/types/types.go @@ -63,9 +63,18 @@ func (r BuildReason) Value() (driver.Value, error) { const PausedSandboxConfigVersion = "v1" +type SandboxNetworkTransform struct { + Headers map[string]string `json:"headers,omitempty"` +} + +type SandboxNetworkRule struct { + Transform *SandboxNetworkTransform `json:"transform,omitempty"` +} + type SandboxNetworkEgressConfig struct { - AllowedAddresses []string `json:"allowedAddresses,omitempty"` - DeniedAddresses []string `json:"deniedAddresses,omitempty"` + AllowedAddresses []string `json:"allowedAddresses,omitempty"` + DeniedAddresses []string `json:"deniedAddresses,omitempty"` + Rules map[string][]SandboxNetworkRule `json:"rules,omitempty"` } const AllowPublicAccessDefault = true diff --git a/packages/shared/pkg/featureflags/flags.go b/packages/shared/pkg/featureflags/flags.go index 83a4a0c11c..200e977db8 100644 --- a/packages/shared/pkg/featureflags/flags.go +++ b/packages/shared/pkg/featureflags/flags.go @@ -119,6 +119,8 @@ var ( ExecutionMetricsOnWebhooksFlag = NewBoolFlag("execution-metrics-on-webhooks", false) // TODO: Remove NLT 20250315 SandboxLabelBasedSchedulingFlag = NewBoolFlag("sandbox-label-based-scheduling", false) OptimisticResourceAccountingFlag = NewBoolFlag("sandbox-placement-optimistic-resource-accounting", false) + + NetworkTransformRulesFlag = NewBoolFlag("network-transform-rules", env.IsDevelopment()) ) type IntFlag struct { From 10844a33f1c621fcf4f6e30885f0c9f8cadc5894 Mon Sep 17 00:00:00 2001 From: Jiri Sveceny Date: Tue, 28 Apr 2026 11:19:47 +0200 Subject: [PATCH 05/19] Use static domain validator --- packages/api/go.mod | 1 + packages/api/go.sum | 2 ++ packages/api/internal/handlers/sandbox_create.go | 5 +++-- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/api/go.mod b/packages/api/go.mod index dbb7cbe3c8..1029f2035f 100644 --- a/packages/api/go.mod +++ b/packages/api/go.mod @@ -16,6 +16,7 @@ tool ( ) require ( + github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 github.com/bsm/redislock v0.9.4 github.com/caarlos0/env/v11 v11.3.1 github.com/e2b-dev/infra/packages/auth v0.0.0 diff --git a/packages/api/go.sum b/packages/api/go.sum index e01f66740d..6f0f7441bd 100644 --- a/packages/api/go.sum +++ b/packages/api/go.sum @@ -82,6 +82,8 @@ github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7D github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-sdk-go v1.55.8 h1:JRmEUbU52aJQZ2AjX4q4Wu7t4uZjOu71uyNmaWlUkJQ= github.com/aws/aws-sdk-go v1.55.8/go.mod h1:ZkViS9AqA6otK+JBBNH2++sx1sgxrPKcSzPPvQkUtXk= github.com/aws/aws-sdk-go-v2 v1.41.6 h1:1AX0AthnBQzMx1vbmir3Y4WsnJgiydmnJjiLu+LvXOg= diff --git a/packages/api/internal/handlers/sandbox_create.go b/packages/api/internal/handlers/sandbox_create.go index 7c7df1f776..6a4b09e9c9 100644 --- a/packages/api/internal/handlers/sandbox_create.go +++ b/packages/api/internal/handlers/sandbox_create.go @@ -11,6 +11,7 @@ import ( "strings" "time" + "github.com/asaskevich/govalidator" "github.com/gin-gonic/gin" "github.com/google/uuid" "github.com/launchdarkly/go-sdk-common/v3/ldcontext" @@ -669,10 +670,10 @@ func validateNetworkRules(ctx context.Context, featureFlags featureFlagsClient, } } - if _, err := idna.Lookup.ToASCII(domain); err != nil { + if !govalidator.IsDNSName(domain) { return &api.APIError{ Code: http.StatusBadRequest, - Err: fmt.Errorf("rule domain %q is not a valid domain: %w", domain, err), + Err: fmt.Errorf("rule domain %q is not a valid domain", domain), ClientMsg: fmt.Sprintf("Rule domain %q is not a valid domain name.", domain), } } From bdf89200dff37da24fbcaeb53fc390633ded6e93 Mon Sep 17 00:00:00 2001 From: Jiri Sveceny Date: Tue, 28 Apr 2026 11:20:20 +0200 Subject: [PATCH 06/19] Tests for network rules object --- .../internal/handlers/sandbox_create_test.go | 236 ++++++++++++++++++ 1 file changed, 236 insertions(+) diff --git a/packages/api/internal/handlers/sandbox_create_test.go b/packages/api/internal/handlers/sandbox_create_test.go index 46837a0037..257ce4ac7e 100644 --- a/packages/api/internal/handlers/sandbox_create_test.go +++ b/packages/api/internal/handlers/sandbox_create_test.go @@ -7,6 +7,7 @@ import ( "fmt" "net/http" "net/http/httptest" + "strings" "testing" "github.com/gin-gonic/gin" @@ -778,6 +779,241 @@ func createTestTemplate(ctx context.Context, t *testing.T, db *testutils.Databas return templateID } +func ffEnabled(t *testing.T) *handlersmocks.MockFeatureFlagsClient { + t.Helper() + ff := handlersmocks.NewMockFeatureFlagsClient(t) + ff.EXPECT().BoolFlag(mock.Anything, mock.Anything, mock.Anything).Return(true) + + return ff +} + +func ffDisabled(t *testing.T) *handlersmocks.MockFeatureFlagsClient { + t.Helper() + ff := handlersmocks.NewMockFeatureFlagsClient(t) + ff.EXPECT().BoolFlag(mock.Anything, mock.Anything, mock.Anything).Return(false) + + return ff +} + +func ffUnused(t *testing.T) *handlersmocks.MockFeatureFlagsClient { + t.Helper() + + return handlersmocks.NewMockFeatureFlagsClient(t) // no expectations — must not be called +} + +func rulesMap(entries map[string][]api.SandboxNetworkRule) *map[string][]api.SandboxNetworkRule { + return &entries +} + +func simpleRule(headers map[string]string) api.SandboxNetworkRule { + h := headers + + return api.SandboxNetworkRule{ + Transform: &api.SandboxNetworkTransform{Headers: &h}, + } +} + +func TestValidateNetworkRules(t *testing.T) { + t.Parallel() + + teamID := uuid.New() + + tests := []struct { + name string + rules *map[string][]api.SandboxNetworkRule + setupFF func(t *testing.T) *handlersmocks.MockFeatureFlagsClient + wantCode int + wantMsg string // substring of ClientMsg; empty means expect no error + }{ + // ── nil / empty ────────────────────────────────────────────────────────── + { + name: "nil rules are valid", + rules: nil, + setupFF: ffUnused, + }, + { + name: "empty rules map is valid", + rules: rulesMap(map[string][]api.SandboxNetworkRule{}), + setupFF: ffEnabled, + }, + // ── feature flag ───────────────────────────────────────────────────────── + { + name: "feature flag disabled returns 400", + rules: rulesMap(map[string][]api.SandboxNetworkRule{"api.openai.com": {}}), + setupFF: ffDisabled, + wantCode: http.StatusBadRequest, + wantMsg: "not available for your team", + }, + // ── domain count ───────────────────────────────────────────────────────── + { + name: "exactly max domains is valid", + rules: func() *map[string][]api.SandboxNetworkRule { + m := make(map[string][]api.SandboxNetworkRule, maxNetworkRuleDomains) + for i := range maxNetworkRuleDomains { + m[fmt.Sprintf("domain%d.example.com", i)] = nil + } + + return &m + }(), + setupFF: ffEnabled, + }, + { + name: "one over max domains returns 400", + rules: func() *map[string][]api.SandboxNetworkRule { + m := make(map[string][]api.SandboxNetworkRule, maxNetworkRuleDomains+1) + for i := range maxNetworkRuleDomains + 1 { + m[fmt.Sprintf("domain%d.example.com", i)] = nil + } + + return &m + }(), + setupFF: ffEnabled, + wantCode: http.StatusBadRequest, + wantMsg: fmt.Sprintf("at most %d domains", maxNetworkRuleDomains), + }, + // ── domain key validation ───────────────────────────────────────────────── + { + name: "empty domain key returns 400", + rules: rulesMap(map[string][]api.SandboxNetworkRule{"": {}}), + setupFF: ffEnabled, + wantCode: http.StatusBadRequest, + wantMsg: "must not be empty", + }, + { + name: "domain exceeding max length returns 400", + rules: rulesMap(map[string][]api.SandboxNetworkRule{strings.Repeat("a", maxNetworkRuleDomainLen+1): {}}), + setupFF: ffEnabled, + wantCode: http.StatusBadRequest, + wantMsg: "maximum length", + }, + { + name: "invalid domain returns 400", + rules: rulesMap(map[string][]api.SandboxNetworkRule{"not a valid domain!": {}}), + setupFF: ffEnabled, + wantCode: http.StatusBadRequest, + wantMsg: "not a valid domain name", + }, + { + name: "valid plain domain is accepted", + rules: rulesMap(map[string][]api.SandboxNetworkRule{"api.openai.com": {}}), + setupFF: ffEnabled, + }, + { + name: "wildcard domain is rejected", + rules: rulesMap(map[string][]api.SandboxNetworkRule{"*.openai.com": {}}), + setupFF: ffEnabled, + wantCode: http.StatusBadRequest, + wantMsg: "not a valid domain name", + }, + { + name: "bare wildcard is rejected", + rules: rulesMap(map[string][]api.SandboxNetworkRule{"*.": {}}), + setupFF: ffEnabled, + wantCode: http.StatusBadRequest, + wantMsg: "not a valid domain name", + }, + // ── transform count ─────────────────────────────────────────────────────── + { + name: "one transform per domain is valid", + rules: rulesMap(map[string][]api.SandboxNetworkRule{ + "api.openai.com": {simpleRule(map[string]string{"Authorization": "Bearer token"})}, + }), + setupFF: ffEnabled, + }, + { + name: "two transforms for one domain returns 400", + rules: rulesMap(map[string][]api.SandboxNetworkRule{ + "api.openai.com": { + simpleRule(map[string]string{"Authorization": "Bearer token"}), + simpleRule(map[string]string{"X-Custom": "value"}), + }, + }), + setupFF: ffEnabled, + wantCode: http.StatusBadRequest, + wantMsg: fmt.Sprintf("at most %d transform rule", maxNetworkRuleTransformsPerDomain), + }, + // ── nil transform (no headers to check) ─────────────────────────────────── + { + name: "nil transform in rule is valid", + rules: rulesMap(map[string][]api.SandboxNetworkRule{ + "api.openai.com": {{Transform: nil}}, + }), + setupFF: ffEnabled, + }, + // ── header name ─────────────────────────────────────────────────────────── + { + name: "empty header name returns 400", + rules: rulesMap(map[string][]api.SandboxNetworkRule{ + "api.openai.com": {simpleRule(map[string]string{"": "value"})}, + }), + setupFF: ffEnabled, + wantCode: http.StatusBadRequest, + wantMsg: "must not be empty", + }, + { + name: "header name at max length is valid", + rules: rulesMap(map[string][]api.SandboxNetworkRule{ + "api.openai.com": {simpleRule(map[string]string{ + strings.Repeat("X", maxNetworkRuleHeaderNameLen): "value", + })}, + }), + setupFF: ffEnabled, + }, + { + name: "header name exceeding max length returns 400", + rules: rulesMap(map[string][]api.SandboxNetworkRule{ + "api.openai.com": {simpleRule(map[string]string{ + strings.Repeat("X", maxNetworkRuleHeaderNameLen+1): "value", + })}, + }), + setupFF: ffEnabled, + wantCode: http.StatusBadRequest, + wantMsg: "maximum length", + }, + // ── header value ────────────────────────────────────────────────────────── + { + name: "header value at max length is valid", + rules: rulesMap(map[string][]api.SandboxNetworkRule{ + "api.openai.com": {simpleRule(map[string]string{ + "Authorization": strings.Repeat("x", maxNetworkRuleHeaderValueLen), + })}, + }), + setupFF: ffEnabled, + }, + { + name: "header value exceeding max length returns 400", + rules: rulesMap(map[string][]api.SandboxNetworkRule{ + "api.openai.com": {simpleRule(map[string]string{ + "Authorization": strings.Repeat("x", maxNetworkRuleHeaderValueLen+1), + })}, + }), + setupFF: ffEnabled, + wantCode: http.StatusBadRequest, + wantMsg: "maximum length", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + ff := tt.setupFF(t) + apiErr := validateNetworkRules(context.Background(), ff, teamID, tt.rules) + + if tt.wantMsg == "" { + assert.Nil(t, apiErr) + + return + } + + if assert.NotNil(t, apiErr) { + assert.Equal(t, tt.wantCode, apiErr.Code) + assert.Contains(t, apiErr.ClientMsg, tt.wantMsg) + } + }) + } +} + func createTestTemplateAliasWithName(ctx context.Context, t *testing.T, db *testutils.Database, templateID, aliasName string, namespace *string) { t.Helper() From 084caa76c041b20de3670ce7018b8312c0069d41 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 28 Apr 2026 09:25:27 +0000 Subject: [PATCH 07/19] chore: auto-commit generated changes --- tests/integration/internal/api/generated.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/integration/internal/api/generated.go b/tests/integration/internal/api/generated.go index b43f96ac59..5d29c6aec6 100644 --- a/tests/integration/internal/api/generated.go +++ b/tests/integration/internal/api/generated.go @@ -894,6 +894,21 @@ type SandboxNetworkConfig struct { // MaskRequestHost Specify host mask which will be used for all sandbox requests MaskRequestHost *string `json:"maskRequestHost,omitempty"` + + // Rules Per-domain transform rules applied to matching egress HTTP/HTTPS requests. Keys are domain patterns (e.g. "api.openai.com", "*.openai.com"). A domain listed here is not automatically allowed — use allowOut to permit the traffic. + Rules *map[string][]SandboxNetworkRule `json:"rules,omitempty"` +} + +// SandboxNetworkRule Transform rule applied to egress requests matching a domain pattern. +type SandboxNetworkRule struct { + // Transform Transformations applied to matching egress requests before forwarding. + Transform *SandboxNetworkTransform `json:"transform,omitempty"` +} + +// SandboxNetworkTransform Transformations applied to matching egress requests before forwarding. +type SandboxNetworkTransform struct { + // Headers HTTP headers to inject or override in matching requests. An existing header with the same name is replaced. Values are plain strings; secret resolution happens client-side before sending to the API. + Headers *map[string]string `json:"headers,omitempty"` } // SandboxOnTimeout Action taken when the sandbox times out. @@ -1447,6 +1462,9 @@ type PutSandboxesSandboxIDNetworkJSONBody struct { // DenyOut List of denied CIDR blocks or IP addresses for egress traffic. Domain names are not supported for deny rules. DenyOut *[]string `json:"denyOut,omitempty"` + + // Rules Per-domain transform rules. Replaces all existing rules when provided. + Rules *map[string][]SandboxNetworkRule `json:"rules,omitempty"` } // PostSandboxesSandboxIDRefreshesJSONBody defines parameters for PostSandboxesSandboxIDRefreshes. From 66ce87e6d0bfe63df2a21751c3e3ec5b8ad8d486 Mon Sep 17 00:00:00 2001 From: Jiri Sveceny Date: Tue, 28 Apr 2026 11:42:50 +0200 Subject: [PATCH 08/19] Posthog events --- packages/api/internal/handlers/sandbox_create.go | 13 +++++++++++++ .../api/internal/handlers/sandbox_network_update.go | 13 +++++++++++++ 2 files changed, 26 insertions(+) diff --git a/packages/api/internal/handlers/sandbox_create.go b/packages/api/internal/handlers/sandbox_create.go index 6a4b09e9c9..f72a71a320 100644 --- a/packages/api/internal/handlers/sandbox_create.go +++ b/packages/api/internal/handlers/sandbox_create.go @@ -273,6 +273,19 @@ func (a *APIStore) PostSandboxes(c *gin.Context) { return } + if n := body.Network; n != nil && n.Rules != nil && len(*n.Rules) > 0 { + domains := make([]string, 0, len(*n.Rules)) + for domain := range *n.Rules { + domains = append(domains, domain) + } + + a.posthog.CreateAnalyticsTeamEvent(ctx, teamInfo.Team.ID.String(), "sandbox with network transform rules created", + a.posthog.GetPackageToPosthogProperties(&c.Request.Header). + Set("sandbox_id", sandboxID). + Set("domains", domains), + ) + } + c.JSON(http.StatusCreated, &sbx) } diff --git a/packages/api/internal/handlers/sandbox_network_update.go b/packages/api/internal/handlers/sandbox_network_update.go index 629ec9c55e..ac25e64ace 100644 --- a/packages/api/internal/handlers/sandbox_network_update.go +++ b/packages/api/internal/handlers/sandbox_network_update.go @@ -65,5 +65,18 @@ func (a *APIStore) PutSandboxesSandboxIDNetwork(c *gin.Context, sandboxID string return } + if len(rules) > 0 { + domains := make([]string, 0, len(rules)) + for domain := range rules { + domains = append(domains, domain) + } + + a.posthog.CreateAnalyticsTeamEvent(ctx, team.ID.String(), "sandbox with network transform rules created", + a.posthog.GetPackageToPosthogProperties(&c.Request.Header). + Set("sandbox_id", sandboxID). + Set("domains", domains), + ) + } + c.Status(http.StatusNoContent) } From 192fc67e02f568ba205ff9af25ecc9e692b7fd49 Mon Sep 17 00:00:00 2001 From: Jiri Sveceny Date: Tue, 28 Apr 2026 12:10:39 +0200 Subject: [PATCH 09/19] Update comment about domain patterns in openapi --- packages/api/internal/api/api.gen.go | 337 ++++++++++---------- spec/openapi.yml | 4 +- tests/integration/internal/api/generated.go | 2 +- 3 files changed, 171 insertions(+), 172 deletions(-) diff --git a/packages/api/internal/api/api.gen.go b/packages/api/internal/api/api.gen.go index c9ca343f7c..176bcd2983 100644 --- a/packages/api/internal/api/api.gen.go +++ b/packages/api/internal/api/api.gen.go @@ -900,7 +900,7 @@ type SandboxNetworkConfig struct { // MaskRequestHost Specify host mask which will be used for all sandbox requests MaskRequestHost *string `json:"maskRequestHost,omitempty"` - // Rules Per-domain transform rules applied to matching egress HTTP/HTTPS requests. Keys are domain patterns (e.g. "api.openai.com", "*.openai.com"). A domain listed here is not automatically allowed — use allowOut to permit the traffic. + // Rules Per-domain transform rules applied to matching egress HTTP/HTTPS requests. Keys are domains (e.g. "api.example.com", "example.com"). A domain listed here is not automatically allowed - use allowOut to permit the traffic. Rules *map[string][]SandboxNetworkRule `json:"rules,omitempty"` } @@ -12282,174 +12282,173 @@ func RegisterHandlersWithOptions(router gin.IRouter, si ServerInterface, options // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "AAD///0lcSkyLAEA", + "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/spec/openapi.yml b/spec/openapi.yml index accb1f4b24..c201c997ff 100644 --- a/spec/openapi.yml +++ b/spec/openapi.yml @@ -286,8 +286,8 @@ components: type: object description: > Per-domain transform rules applied to matching egress HTTP/HTTPS requests. - Keys are domain patterns (e.g. "api.openai.com", "*.openai.com"). - A domain listed here is not automatically allowed — use allowOut to permit the traffic. + Keys are domains (e.g. "api.example.com", "example.com"). + A domain listed here is not automatically allowed - use allowOut to permit the traffic. additionalProperties: type: array items: diff --git a/tests/integration/internal/api/generated.go b/tests/integration/internal/api/generated.go index 5d29c6aec6..a202f60802 100644 --- a/tests/integration/internal/api/generated.go +++ b/tests/integration/internal/api/generated.go @@ -895,7 +895,7 @@ type SandboxNetworkConfig struct { // MaskRequestHost Specify host mask which will be used for all sandbox requests MaskRequestHost *string `json:"maskRequestHost,omitempty"` - // Rules Per-domain transform rules applied to matching egress HTTP/HTTPS requests. Keys are domain patterns (e.g. "api.openai.com", "*.openai.com"). A domain listed here is not automatically allowed — use allowOut to permit the traffic. + // Rules Per-domain transform rules applied to matching egress HTTP/HTTPS requests. Keys are domains (e.g. "api.example.com", "example.com"). A domain listed here is not automatically allowed - use allowOut to permit the traffic. Rules *map[string][]SandboxNetworkRule `json:"rules,omitempty"` } From 202ebb6eaa4fda7b2e148703c9fb25475561e1e8 Mon Sep 17 00:00:00 2001 From: Jiri Sveceny Date: Tue, 28 Apr 2026 12:37:30 +0200 Subject: [PATCH 10/19] api: reject header names/values containing CR or LF characters Prevents HTTP header injection via network transform rules by checking for CR and LF bytes in both header names and values before storing or forwarding them. --- packages/api/internal/handlers/sandbox_create.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/packages/api/internal/handlers/sandbox_create.go b/packages/api/internal/handlers/sandbox_create.go index f72a71a320..d0607c0c97 100644 --- a/packages/api/internal/handlers/sandbox_create.go +++ b/packages/api/internal/handlers/sandbox_create.go @@ -714,6 +714,14 @@ func validateNetworkRules(ctx context.Context, featureFlags featureFlagsClient, } } + if strings.ContainsAny(name, "\r\n") { + return &api.APIError{ + Code: http.StatusBadRequest, + Err: fmt.Errorf("header name %q in rule for domain %q contains invalid characters", name, domain), + ClientMsg: fmt.Sprintf("Header name %q in rule for domain %q must not contain CR or LF characters.", name, domain), + } + } + if len(name) > maxNetworkRuleHeaderNameLen { return &api.APIError{ Code: http.StatusBadRequest, @@ -722,6 +730,14 @@ func validateNetworkRules(ctx context.Context, featureFlags featureFlagsClient, } } + if strings.ContainsAny(value, "\r\n") { + return &api.APIError{ + Code: http.StatusBadRequest, + Err: fmt.Errorf("value for header %q in rule for domain %q contains invalid characters", name, domain), + ClientMsg: fmt.Sprintf("Value for header %q in rule for domain %q must not contain CR or LF characters.", name, domain), + } + } + if len(value) > maxNetworkRuleHeaderValueLen { return &api.APIError{ Code: http.StatusBadRequest, From 8bb62c9e2d85ae0c121e4bd3651ee37a70a6972c Mon Sep 17 00:00:00 2001 From: Jiri Sveceny Date: Tue, 28 Apr 2026 12:37:34 +0200 Subject: [PATCH 11/19] api: clone headers map in dbNetworkConfigToAPI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Avoid aliasing the internal cache struct by cloning the headers map before exposing it in the API response. Also fixes a nil pointer to nil map serializing as "headers":null — when headers are nil the pointer is left nil so omitempty correctly omits the field. --- packages/api/internal/handlers/sandbox_get.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/api/internal/handlers/sandbox_get.go b/packages/api/internal/handlers/sandbox_get.go index 0e76441c1c..0509d8025c 100644 --- a/packages/api/internal/handlers/sandbox_get.go +++ b/packages/api/internal/handlers/sandbox_get.go @@ -3,6 +3,7 @@ package handlers import ( "errors" "fmt" + "maps" "net/http" "github.com/gin-gonic/gin" @@ -58,8 +59,13 @@ func dbNetworkConfigToAPI(network *dbtypes.SandboxNetworkConfig) *api.SandboxNet for _, r := range dbRules { apiRule := api.SandboxNetworkRule{} if r.Transform != nil { + var h *map[string]string + if r.Transform.Headers != nil { + clone := maps.Clone(r.Transform.Headers) + h = &clone + } apiRule.Transform = &api.SandboxNetworkTransform{ - Headers: &r.Transform.Headers, + Headers: h, } } apiDomainRules = append(apiDomainRules, apiRule) From 4c96456b908ebee73aa518eeaa01282344207119 Mon Sep 17 00:00:00 2001 From: Jiri Sveceny Date: Tue, 28 Apr 2026 12:37:38 +0200 Subject: [PATCH 12/19] api: fix posthog event label in network update handler PUT handler was emitting "sandbox with network transform rules created" instead of "updated", mislabeling update operations in analytics. --- packages/api/internal/handlers/sandbox_network_update.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/api/internal/handlers/sandbox_network_update.go b/packages/api/internal/handlers/sandbox_network_update.go index ac25e64ace..fde5ac77e6 100644 --- a/packages/api/internal/handlers/sandbox_network_update.go +++ b/packages/api/internal/handlers/sandbox_network_update.go @@ -71,7 +71,7 @@ func (a *APIStore) PutSandboxesSandboxIDNetwork(c *gin.Context, sandboxID string domains = append(domains, domain) } - a.posthog.CreateAnalyticsTeamEvent(ctx, team.ID.String(), "sandbox with network transform rules created", + a.posthog.CreateAnalyticsTeamEvent(ctx, team.ID.String(), "sandbox with network transform rules updated", a.posthog.GetPackageToPosthogProperties(&c.Request.Header). Set("sandbox_id", sandboxID). Set("domains", domains), From 5db6f5c02a757f83c15cd310a9419cdefa9c5534 Mon Sep 17 00:00:00 2001 From: Jiri Sveceny Date: Wed, 29 Apr 2026 14:44:52 +0200 Subject: [PATCH 13/19] api/handlers: tighten network rule header limits maxNetworkRuleHeaderValueLen was set to 256, which is too small for common base64-encoded values such as Bearer tokens or API keys that can easily exceed that length. Raise it to 2048. Add maxNetworkRuleHeadersPerRule (20) and enforce it in validateNetworkRules before iterating individual headers. Without a per-rule header count limit a single rule could carry an unbounded number of headers. --- packages/api/internal/handlers/sandbox_create.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/api/internal/handlers/sandbox_create.go b/packages/api/internal/handlers/sandbox_create.go index d0607c0c97..b6696ab8a1 100644 --- a/packages/api/internal/handlers/sandbox_create.go +++ b/packages/api/internal/handlers/sandbox_create.go @@ -53,7 +53,8 @@ const ( maxNetworkRuleTransformsPerDomain = 1 maxNetworkRuleDomainLen = 128 maxNetworkRuleHeaderNameLen = 64 - maxNetworkRuleHeaderValueLen = 256 + maxNetworkRuleHeaderValueLen = 2048 + maxNetworkRuleHeadersPerRule = 20 ) func (a *APIStore) PostSandboxes(c *gin.Context) { @@ -705,6 +706,14 @@ func validateNetworkRules(ctx context.Context, featureFlags featureFlagsClient, } headers := sharedUtils.DerefOrDefault(rule.Transform.Headers, nil) + if len(headers) > maxNetworkRuleHeadersPerRule { + return &api.APIError{ + Code: http.StatusBadRequest, + Err: fmt.Errorf("domain %q has %d headers (max %d)", domain, len(headers), maxNetworkRuleHeadersPerRule), + ClientMsg: fmt.Sprintf("Domain %q can have at most %d headers per rule.", domain, maxNetworkRuleHeadersPerRule), + } + } + for name, value := range headers { if len(name) == 0 { return &api.APIError{ From 64dc39ab6b9bce94825f3cc4ed2c8ceb0092d251 Mon Sep 17 00:00:00 2001 From: Jiri Sveceny Date: Wed, 29 Apr 2026 14:44:55 +0200 Subject: [PATCH 14/19] api/orchestrator: keep Rules nil in egress config when no rules given buildEgressConfig unconditionally allocated an empty map for orchRules even when rules was nil. Downstream consumers check `if egress.Rules != nil` to detect whether transform rules are configured; a non-nil empty map caused a false positive for every sandbox that was created without any transform rules. Guard the allocation behind `if rules != nil` so the field stays nil when no rules were provided. --- .../internal/orchestrator/create_instance.go | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/packages/api/internal/orchestrator/create_instance.go b/packages/api/internal/orchestrator/create_instance.go index bfd65311f1..46855aa912 100644 --- a/packages/api/internal/orchestrator/create_instance.go +++ b/packages/api/internal/orchestrator/create_instance.go @@ -62,19 +62,22 @@ func buildEgressConfig(allowedEntries, deniedEntries []string, rules map[string] allowedAddresses = append(allowedAddresses, sandbox_network.DefaultNameserver) } - orchRules := make(map[string]*orchestrator.SandboxNetworkDomainRules, len(rules)) - for domain, domainRules := range rules { - orchRuleList := make([]*orchestrator.SandboxNetworkRule, 0, len(domainRules)) - for _, r := range domainRules { - orchRule := &orchestrator.SandboxNetworkRule{} - if r.Transform != nil { - orchRule.Transform = &orchestrator.SandboxNetworkTransform{ - Headers: r.Transform.Headers, + var orchRules map[string]*orchestrator.SandboxNetworkDomainRules + if rules != nil { + orchRules = make(map[string]*orchestrator.SandboxNetworkDomainRules, len(rules)) + for domain, domainRules := range rules { + orchRuleList := make([]*orchestrator.SandboxNetworkRule, 0, len(domainRules)) + for _, r := range domainRules { + orchRule := &orchestrator.SandboxNetworkRule{} + if r.Transform != nil { + orchRule.Transform = &orchestrator.SandboxNetworkTransform{ + Headers: r.Transform.Headers, + } } + orchRuleList = append(orchRuleList, orchRule) } - orchRuleList = append(orchRuleList, orchRule) + orchRules[domain] = &orchestrator.SandboxNetworkDomainRules{Rules: orchRuleList} } - orchRules[domain] = &orchestrator.SandboxNetworkDomainRules{Rules: orchRuleList} } return &orchestrator.SandboxNetworkEgressConfig{ From 391715472f13b19803bc751ef7b3519c000b885e Mon Sep 17 00:00:00 2001 From: Jiri Sveceny Date: Wed, 29 Apr 2026 19:06:08 +0200 Subject: [PATCH 15/19] Use native tool to validate header name --- packages/api/internal/handlers/sandbox_create.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/api/internal/handlers/sandbox_create.go b/packages/api/internal/handlers/sandbox_create.go index b6696ab8a1..d2b527dd78 100644 --- a/packages/api/internal/handlers/sandbox_create.go +++ b/packages/api/internal/handlers/sandbox_create.go @@ -18,6 +18,7 @@ import ( "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" "go.uber.org/zap" + "golang.org/x/net/http/httpguts" "golang.org/x/net/idna" "github.com/e2b-dev/infra/packages/api/internal/api" @@ -723,7 +724,7 @@ func validateNetworkRules(ctx context.Context, featureFlags featureFlagsClient, } } - if strings.ContainsAny(name, "\r\n") { + if httpguts.ValidHeaderFieldName(name) { return &api.APIError{ Code: http.StatusBadRequest, Err: fmt.Errorf("header name %q in rule for domain %q contains invalid characters", name, domain), From 5830584a6c5f885a65c8afb10af869c4b438e8da Mon Sep 17 00:00:00 2001 From: Jiri Sveceny Date: Wed, 29 Apr 2026 19:11:56 +0200 Subject: [PATCH 16/19] fix inverted if --- packages/api/internal/handlers/sandbox_create.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/api/internal/handlers/sandbox_create.go b/packages/api/internal/handlers/sandbox_create.go index d2b527dd78..c9b99b3229 100644 --- a/packages/api/internal/handlers/sandbox_create.go +++ b/packages/api/internal/handlers/sandbox_create.go @@ -724,7 +724,7 @@ func validateNetworkRules(ctx context.Context, featureFlags featureFlagsClient, } } - if httpguts.ValidHeaderFieldName(name) { + if !httpguts.ValidHeaderFieldName(name) { return &api.APIError{ Code: http.StatusBadRequest, Err: fmt.Errorf("header name %q in rule for domain %q contains invalid characters", name, domain), From dc81689e34caef803048c39b245aaa4fe5111bf1 Mon Sep 17 00:00:00 2001 From: Jiri Sveceny Date: Thu, 30 Apr 2026 15:52:59 +0200 Subject: [PATCH 17/19] Fixed error message for invalid header name format --- packages/api/internal/handlers/sandbox_create.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/api/internal/handlers/sandbox_create.go b/packages/api/internal/handlers/sandbox_create.go index c9b99b3229..52084e5e26 100644 --- a/packages/api/internal/handlers/sandbox_create.go +++ b/packages/api/internal/handlers/sandbox_create.go @@ -728,7 +728,7 @@ func validateNetworkRules(ctx context.Context, featureFlags featureFlagsClient, return &api.APIError{ Code: http.StatusBadRequest, Err: fmt.Errorf("header name %q in rule for domain %q contains invalid characters", name, domain), - ClientMsg: fmt.Sprintf("Header name %q in rule for domain %q must not contain CR or LF characters.", name, domain), + ClientMsg: fmt.Sprintf("Header name %q in rule for domain %q must contain only valid HTTP token characters.", name, domain), } } From 78b77c68d98168b812f6cd70b925e1dbfb110da8 Mon Sep 17 00:00:00 2001 From: Jiri Sveceny Date: Thu, 30 Apr 2026 16:29:58 +0200 Subject: [PATCH 18/19] Transform rules can only apply to template builds with supported envd version Adds same envd version logic we already have for volumes. Re-worked logic a bit so we have helper function that handles all corner cases for us. --- .../api/internal/handlers/sandbox_create.go | 64 ++++++-- .../internal/handlers/sandbox_create_test.go | 140 +++++++++++------- .../handlers/sandbox_network_update.go | 16 +- 3 files changed, 153 insertions(+), 67 deletions(-) diff --git a/packages/api/internal/handlers/sandbox_create.go b/packages/api/internal/handlers/sandbox_create.go index 52084e5e26..c690ba6d9c 100644 --- a/packages/api/internal/handlers/sandbox_create.go +++ b/packages/api/internal/handlers/sandbox_create.go @@ -17,7 +17,6 @@ import ( "github.com/launchdarkly/go-sdk-common/v3/ldcontext" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" - "go.uber.org/zap" "golang.org/x/net/http/httpguts" "golang.org/x/net/idna" @@ -34,7 +33,6 @@ import ( "github.com/e2b-dev/infra/packages/shared/pkg/ginutils" "github.com/e2b-dev/infra/packages/shared/pkg/grpc/orchestrator" "github.com/e2b-dev/infra/packages/shared/pkg/id" - "github.com/e2b-dev/infra/packages/shared/pkg/logger" sbxlogger "github.com/e2b-dev/infra/packages/shared/pkg/logger/sandbox" "github.com/e2b-dev/infra/packages/shared/pkg/middleware/otel/metrics" sandbox_network "github.com/e2b-dev/infra/packages/shared/pkg/sandbox-network" @@ -183,7 +181,7 @@ func (a *APIStore) PostSandboxes(c *gin.Context) { var network *types.SandboxNetworkConfig if n := body.Network; n != nil { - if err := validateNetworkConfig(ctx, a.featureFlags, teamInfo.Team.ID, n); err != nil { + if err := validateNetworkConfig(ctx, a.featureFlags, teamInfo.Team.ID, sharedUtils.DerefOrDefault(build.EnvdVersion, ""), n); err != nil { telemetry.ReportError(ctx, "invalid network config", err.Err, telemetry.WithSandboxID(sandboxID)) a.sendAPIStoreError(c, err.Code, err.ClientMsg) @@ -347,10 +345,35 @@ func (im InvalidVolumeMountsError) Error() string { var errVolumesNotSupported = errors.New("volumes are not supported") +var errNetworkRulesNotSupported = errors.New("network transform rules are not supported") + var errNoEnvdVersion = errors.New("no envd version provided") +const minEnvdVersionForNetworkRules = "0.5.13" + const minEnvdVersionForVolumes = "0.5.14" +// checkEnvdVersionRequirement returns errNoEnvdVersion when buildVersion is empty, a parse +// error when the version string is invalid, or a wrapped featureErr when the build does not +// meet requiredMinVersion. The caller decides how to convert the returned error into an API +// response so each call-site can produce its own status code / message. +func checkEnvdVersionRequirement(buildVersion, requiredMinVersion string, featureErr error) error { + if buildVersion == "" { + return errNoEnvdVersion + } + + ok, err := sharedUtils.IsGTEVersion(buildVersion, requiredMinVersion) + if err != nil { + return fmt.Errorf("invalid envd version %q: %w", buildVersion, err) + } + + if !ok { + return fmt.Errorf("%w; template must be rebuilt. Template envd version is %s, must be at least %s", featureErr, buildVersion, requiredMinVersion) + } + + return nil +} + func convertAPIVolumesToOrchestratorVolumes(ctx context.Context, sqlClient *sqlcdb.Client, featureFlags featureFlagsClient, teamID uuid.UUID, volumeMounts []api.SandboxVolumeMount, env *queries.EnvBuild) ([]*orchestrator.SandboxVolumeMount, error) { // are any volumes configured? if len(volumeMounts) == 0 { @@ -363,16 +386,9 @@ func convertAPIVolumesToOrchestratorVolumes(ctx context.Context, sqlClient *sqlc } // does your envd version support volumes? - if envdVersion := sharedUtils.DerefOrDefault(env.EnvdVersion, ""); envdVersion == "" { - logger.L().Warn(ctx, "envd version is unset") - - return nil, errNoEnvdVersion - } else if ok, err := sharedUtils.IsGTEVersion(envdVersion, minEnvdVersionForVolumes); err != nil { - logger.L().Warn(ctx, "failed to check envd version", zap.Error(err), zap.String("envd_version", envdVersion)) - - return nil, fmt.Errorf("invalid envd version %q: %w", envdVersion, err) - } else if !ok { - return nil, fmt.Errorf("%w; template must be rebuilt. Template envd version is %s, must be at least %s to support volumes", errVolumesNotSupported, envdVersion, minEnvdVersionForVolumes) + envdVersion := sharedUtils.DerefOrDefault(env.EnvdVersion, "") + if err := checkEnvdVersionRequirement(envdVersion, minEnvdVersionForVolumes, errVolumesNotSupported); err != nil { + return nil, err } // get volumes from the database @@ -563,7 +579,7 @@ func apiRulesToDBRules(apiRules *map[string][]api.SandboxNetworkRule) map[string return dbRules } -func validateNetworkConfig(ctx context.Context, featureFlags featureFlagsClient, teamID uuid.UUID, network *api.SandboxNetworkConfig) *api.APIError { +func validateNetworkConfig(ctx context.Context, featureFlags featureFlagsClient, teamID uuid.UUID, envdVersion string, network *api.SandboxNetworkConfig) *api.APIError { if network == nil { return nil } @@ -603,7 +619,7 @@ func validateNetworkConfig(ctx context.Context, featureFlags featureFlagsClient, return err } - return validateNetworkRules(ctx, featureFlags, teamID, network.Rules) + return validateNetworkRules(ctx, featureFlags, teamID, envdVersion, network.Rules) } // validateEgressRules validates egress allow/deny rules: @@ -647,7 +663,7 @@ func validateEgressRules(allowOut, denyOut []string) *api.APIError { return nil } -func validateNetworkRules(ctx context.Context, featureFlags featureFlagsClient, teamID uuid.UUID, rules *map[string][]api.SandboxNetworkRule) *api.APIError { +func validateNetworkRules(ctx context.Context, featureFlags featureFlagsClient, teamID uuid.UUID, envdVersion string, rules *map[string][]api.SandboxNetworkRule) *api.APIError { if rules == nil { return nil } @@ -660,6 +676,22 @@ func validateNetworkRules(ctx context.Context, featureFlags featureFlagsClient, } } + if err := checkEnvdVersionRequirement(envdVersion, minEnvdVersionForNetworkRules, errNetworkRulesNotSupported); err != nil { + if errors.Is(err, errNetworkRulesNotSupported) { + return &api.APIError{ + Code: http.StatusBadRequest, + Err: err, + ClientMsg: err.Error(), + } + } + + return &api.APIError{ + Code: http.StatusInternalServerError, + Err: err, + ClientMsg: "internal error while validating network rules", + } + } + if len(*rules) > maxNetworkRuleDomains { return &api.APIError{ Code: http.StatusBadRequest, diff --git a/packages/api/internal/handlers/sandbox_create_test.go b/packages/api/internal/handlers/sandbox_create_test.go index 257ce4ac7e..5bba27d32b 100644 --- a/packages/api/internal/handlers/sandbox_create_test.go +++ b/packages/api/internal/handlers/sandbox_create_test.go @@ -280,7 +280,7 @@ func TestValidateNetworkConfig(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() mockFF := handlersmocks.NewMockFeatureFlagsClient(t) - err := validateNetworkConfig(context.Background(), mockFF, uuid.Nil, tt.network) + err := validateNetworkConfig(context.Background(), mockFF, uuid.Nil, "", tt.network) if tt.wantErr { if err == nil { @@ -819,11 +819,12 @@ func TestValidateNetworkRules(t *testing.T) { teamID := uuid.New() tests := []struct { - name string - rules *map[string][]api.SandboxNetworkRule - setupFF func(t *testing.T) *handlersmocks.MockFeatureFlagsClient - wantCode int - wantMsg string // substring of ClientMsg; empty means expect no error + name string + envdVersion string + rules *map[string][]api.SandboxNetworkRule + setupFF func(t *testing.T) *handlersmocks.MockFeatureFlagsClient + wantCode int + wantMsg string // substring of ClientMsg; empty means expect no error }{ // ── nil / empty ────────────────────────────────────────────────────────── { @@ -832,9 +833,10 @@ func TestValidateNetworkRules(t *testing.T) { setupFF: ffUnused, }, { - name: "empty rules map is valid", - rules: rulesMap(map[string][]api.SandboxNetworkRule{}), - setupFF: ffEnabled, + name: "empty rules map is valid", + envdVersion: minEnvdVersionForNetworkRules, + rules: rulesMap(map[string][]api.SandboxNetworkRule{}), + setupFF: ffEnabled, }, // ── feature flag ───────────────────────────────────────────────────────── { @@ -844,6 +846,28 @@ func TestValidateNetworkRules(t *testing.T) { wantCode: http.StatusBadRequest, wantMsg: "not available for your team", }, + // ── envd version ───────────────────────────────────────────────────────── + { + name: "missing envd version returns 500", + rules: rulesMap(map[string][]api.SandboxNetworkRule{"api.openai.com": {}}), + setupFF: ffEnabled, + wantCode: http.StatusInternalServerError, + wantMsg: "internal error while validating network rules", + }, + { + name: "envd version below minimum returns 400", + envdVersion: "0.5.12", + rules: rulesMap(map[string][]api.SandboxNetworkRule{"api.openai.com": {}}), + setupFF: ffEnabled, + wantCode: http.StatusBadRequest, + wantMsg: "template must be rebuilt", + }, + { + name: "envd version at minimum is valid", + envdVersion: minEnvdVersionForNetworkRules, + rules: rulesMap(map[string][]api.SandboxNetworkRule{"api.openai.com": {}}), + setupFF: ffEnabled, + }, // ── domain count ───────────────────────────────────────────────────────── { name: "exactly max domains is valid", @@ -855,7 +879,8 @@ func TestValidateNetworkRules(t *testing.T) { return &m }(), - setupFF: ffEnabled, + envdVersion: minEnvdVersionForNetworkRules, + setupFF: ffEnabled, }, { name: "one over max domains returns 400", @@ -867,61 +892,70 @@ func TestValidateNetworkRules(t *testing.T) { return &m }(), - setupFF: ffEnabled, - wantCode: http.StatusBadRequest, - wantMsg: fmt.Sprintf("at most %d domains", maxNetworkRuleDomains), + envdVersion: minEnvdVersionForNetworkRules, + setupFF: ffEnabled, + wantCode: http.StatusBadRequest, + wantMsg: fmt.Sprintf("at most %d domains", maxNetworkRuleDomains), }, // ── domain key validation ───────────────────────────────────────────────── { - name: "empty domain key returns 400", - rules: rulesMap(map[string][]api.SandboxNetworkRule{"": {}}), - setupFF: ffEnabled, - wantCode: http.StatusBadRequest, - wantMsg: "must not be empty", + name: "empty domain key returns 400", + envdVersion: minEnvdVersionForNetworkRules, + rules: rulesMap(map[string][]api.SandboxNetworkRule{"": {}}), + setupFF: ffEnabled, + wantCode: http.StatusBadRequest, + wantMsg: "must not be empty", }, { - name: "domain exceeding max length returns 400", - rules: rulesMap(map[string][]api.SandboxNetworkRule{strings.Repeat("a", maxNetworkRuleDomainLen+1): {}}), - setupFF: ffEnabled, - wantCode: http.StatusBadRequest, - wantMsg: "maximum length", + name: "domain exceeding max length returns 400", + envdVersion: minEnvdVersionForNetworkRules, + rules: rulesMap(map[string][]api.SandboxNetworkRule{strings.Repeat("a", maxNetworkRuleDomainLen+1): {}}), + setupFF: ffEnabled, + wantCode: http.StatusBadRequest, + wantMsg: "maximum length", }, { - name: "invalid domain returns 400", - rules: rulesMap(map[string][]api.SandboxNetworkRule{"not a valid domain!": {}}), - setupFF: ffEnabled, - wantCode: http.StatusBadRequest, - wantMsg: "not a valid domain name", + name: "invalid domain returns 400", + envdVersion: minEnvdVersionForNetworkRules, + rules: rulesMap(map[string][]api.SandboxNetworkRule{"not a valid domain!": {}}), + setupFF: ffEnabled, + wantCode: http.StatusBadRequest, + wantMsg: "not a valid domain name", }, { - name: "valid plain domain is accepted", - rules: rulesMap(map[string][]api.SandboxNetworkRule{"api.openai.com": {}}), - setupFF: ffEnabled, + name: "valid plain domain is accepted", + envdVersion: minEnvdVersionForNetworkRules, + rules: rulesMap(map[string][]api.SandboxNetworkRule{"api.openai.com": {}}), + setupFF: ffEnabled, }, { - name: "wildcard domain is rejected", - rules: rulesMap(map[string][]api.SandboxNetworkRule{"*.openai.com": {}}), - setupFF: ffEnabled, - wantCode: http.StatusBadRequest, - wantMsg: "not a valid domain name", + name: "wildcard domain is rejected", + envdVersion: minEnvdVersionForNetworkRules, + rules: rulesMap(map[string][]api.SandboxNetworkRule{"*.openai.com": {}}), + setupFF: ffEnabled, + wantCode: http.StatusBadRequest, + wantMsg: "not a valid domain name", }, { - name: "bare wildcard is rejected", - rules: rulesMap(map[string][]api.SandboxNetworkRule{"*.": {}}), - setupFF: ffEnabled, - wantCode: http.StatusBadRequest, - wantMsg: "not a valid domain name", + name: "bare wildcard is rejected", + envdVersion: minEnvdVersionForNetworkRules, + rules: rulesMap(map[string][]api.SandboxNetworkRule{"*.": {}}), + setupFF: ffEnabled, + wantCode: http.StatusBadRequest, + wantMsg: "not a valid domain name", }, // ── transform count ─────────────────────────────────────────────────────── { - name: "one transform per domain is valid", + name: "one transform per domain is valid", + envdVersion: minEnvdVersionForNetworkRules, rules: rulesMap(map[string][]api.SandboxNetworkRule{ "api.openai.com": {simpleRule(map[string]string{"Authorization": "Bearer token"})}, }), setupFF: ffEnabled, }, { - name: "two transforms for one domain returns 400", + name: "two transforms for one domain returns 400", + envdVersion: minEnvdVersionForNetworkRules, rules: rulesMap(map[string][]api.SandboxNetworkRule{ "api.openai.com": { simpleRule(map[string]string{"Authorization": "Bearer token"}), @@ -934,7 +968,8 @@ func TestValidateNetworkRules(t *testing.T) { }, // ── nil transform (no headers to check) ─────────────────────────────────── { - name: "nil transform in rule is valid", + name: "nil transform in rule is valid", + envdVersion: minEnvdVersionForNetworkRules, rules: rulesMap(map[string][]api.SandboxNetworkRule{ "api.openai.com": {{Transform: nil}}, }), @@ -942,7 +977,8 @@ func TestValidateNetworkRules(t *testing.T) { }, // ── header name ─────────────────────────────────────────────────────────── { - name: "empty header name returns 400", + name: "empty header name returns 400", + envdVersion: minEnvdVersionForNetworkRules, rules: rulesMap(map[string][]api.SandboxNetworkRule{ "api.openai.com": {simpleRule(map[string]string{"": "value"})}, }), @@ -951,7 +987,8 @@ func TestValidateNetworkRules(t *testing.T) { wantMsg: "must not be empty", }, { - name: "header name at max length is valid", + name: "header name at max length is valid", + envdVersion: minEnvdVersionForNetworkRules, rules: rulesMap(map[string][]api.SandboxNetworkRule{ "api.openai.com": {simpleRule(map[string]string{ strings.Repeat("X", maxNetworkRuleHeaderNameLen): "value", @@ -960,7 +997,8 @@ func TestValidateNetworkRules(t *testing.T) { setupFF: ffEnabled, }, { - name: "header name exceeding max length returns 400", + name: "header name exceeding max length returns 400", + envdVersion: minEnvdVersionForNetworkRules, rules: rulesMap(map[string][]api.SandboxNetworkRule{ "api.openai.com": {simpleRule(map[string]string{ strings.Repeat("X", maxNetworkRuleHeaderNameLen+1): "value", @@ -972,7 +1010,8 @@ func TestValidateNetworkRules(t *testing.T) { }, // ── header value ────────────────────────────────────────────────────────── { - name: "header value at max length is valid", + name: "header value at max length is valid", + envdVersion: minEnvdVersionForNetworkRules, rules: rulesMap(map[string][]api.SandboxNetworkRule{ "api.openai.com": {simpleRule(map[string]string{ "Authorization": strings.Repeat("x", maxNetworkRuleHeaderValueLen), @@ -981,7 +1020,8 @@ func TestValidateNetworkRules(t *testing.T) { setupFF: ffEnabled, }, { - name: "header value exceeding max length returns 400", + name: "header value exceeding max length returns 400", + envdVersion: minEnvdVersionForNetworkRules, rules: rulesMap(map[string][]api.SandboxNetworkRule{ "api.openai.com": {simpleRule(map[string]string{ "Authorization": strings.Repeat("x", maxNetworkRuleHeaderValueLen+1), @@ -998,7 +1038,7 @@ func TestValidateNetworkRules(t *testing.T) { t.Parallel() ff := tt.setupFF(t) - apiErr := validateNetworkRules(context.Background(), ff, teamID, tt.rules) + apiErr := validateNetworkRules(context.Background(), ff, teamID, tt.envdVersion, tt.rules) if tt.wantMsg == "" { assert.Nil(t, apiErr) diff --git a/packages/api/internal/handlers/sandbox_network_update.go b/packages/api/internal/handlers/sandbox_network_update.go index fde5ac77e6..f5d23d8e2f 100644 --- a/packages/api/internal/handlers/sandbox_network_update.go +++ b/packages/api/internal/handlers/sandbox_network_update.go @@ -1,12 +1,14 @@ package handlers import ( + "errors" "fmt" "net/http" "github.com/gin-gonic/gin" "github.com/e2b-dev/infra/packages/api/internal/api" + "github.com/e2b-dev/infra/packages/api/internal/sandbox" "github.com/e2b-dev/infra/packages/api/internal/utils" "github.com/e2b-dev/infra/packages/auth/pkg/auth" "github.com/e2b-dev/infra/packages/shared/pkg/ginutils" @@ -50,7 +52,19 @@ func (a *APIStore) PutSandboxesSandboxIDNetwork(c *gin.Context, sandboxID string return } - if apiErr := validateNetworkRules(ctx, a.featureFlags, team.ID, body.Rules); apiErr != nil { + sbxInfo, err := a.orchestrator.GetSandbox(ctx, team.ID, sandboxID) + if err != nil { + if errors.Is(err, sandbox.ErrNotFound) { + a.sendAPIStoreError(c, http.StatusNotFound, utils.SandboxNotFoundMsg(sandboxID)) + } else { + telemetry.ReportError(ctx, "error getting sandbox for network update", err) + a.sendAPIStoreError(c, http.StatusInternalServerError, "Failed to get sandbox") + } + + return + } + + if apiErr := validateNetworkRules(ctx, a.featureFlags, team.ID, sbxInfo.EnvdVersion, body.Rules); apiErr != nil { a.sendAPIStoreError(c, apiErr.Code, apiErr.ClientMsg) return From a0fe0c0a8c3c51d29c6109fab10c333ceb1cf3e7 Mon Sep 17 00:00:00 2001 From: Jiri Sveceny Date: Thu, 30 Apr 2026 17:32:25 +0200 Subject: [PATCH 19/19] Use `httpguts.ValidHeaderFieldValue` to validate transform header values --- packages/api/internal/handlers/sandbox_create.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/api/internal/handlers/sandbox_create.go b/packages/api/internal/handlers/sandbox_create.go index c690ba6d9c..88d5aa0d2f 100644 --- a/packages/api/internal/handlers/sandbox_create.go +++ b/packages/api/internal/handlers/sandbox_create.go @@ -772,11 +772,11 @@ func validateNetworkRules(ctx context.Context, featureFlags featureFlagsClient, } } - if strings.ContainsAny(value, "\r\n") { + if !httpguts.ValidHeaderFieldValue(value) { return &api.APIError{ Code: http.StatusBadRequest, Err: fmt.Errorf("value for header %q in rule for domain %q contains invalid characters", name, domain), - ClientMsg: fmt.Sprintf("Value for header %q in rule for domain %q must not contain CR or LF characters.", name, domain), + ClientMsg: fmt.Sprintf("Value for header %q in rule for domain %q contains invalid characters.", name, domain), } }