From e12ab432c6006ee7f3cea1341d74b4a03d1f712b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20Hu=C3=9F?= Date: Wed, 4 Nov 2026 16:18:08 +0100 Subject: [PATCH] fix(profiles): keep every endpoint field in provider profiles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Provider profiles and sandbox policies share the proto endpoint message, but the profile converter mapped only host, port, and protocol. Profiles with access, enforcement, rules, or credential settings could not be imported, and a Get followed by an Update stripped those fields. types.NetworkEndpoint is now an alias of PolicyNetworkEndpoint, and the profile converters reuse the policy endpoint conversion. Keyed literals such as NetworkEndpoint{Host: ..., Port: ...} keep compiling; the type is no longer comparable with ==, since it contains slices. Also re-exports NetworkCredentialBinding and McpOptions in openshell/v1, which PolicyNetworkEndpoint uses. Fixes #12 Signed-off-by: Roland Huß --- openshell/v1/internal/converter/profile.go | 21 +++----- .../v1/internal/converter/profile_test.go | 52 +++++++++++++++++++ openshell/v1/types/network_policy.go | 6 +-- openshell/v1/types/profile.go | 11 ++-- openshell/v1/types_reexport.go | 6 +++ 5 files changed, 74 insertions(+), 22 deletions(-) diff --git a/openshell/v1/internal/converter/profile.go b/openshell/v1/internal/converter/profile.go index e7583c9..8eaf845 100644 --- a/openshell/v1/internal/converter/profile.go +++ b/openshell/v1/internal/converter/profile.go @@ -82,29 +82,24 @@ func CredentialTokenGrantTypeToProto(t types.CredentialTokenGrantType) pb.Provid // --- NetworkEndpoint --- -// NetworkEndpointFromProto converts a proto NetworkEndpoint to an SDK NetworkEndpoint. -// Only Host, Port, and Protocol are mapped; additional proto fields are ignored. +// NetworkEndpointFromProto converts a proto NetworkEndpoint to an SDK +// NetworkEndpoint. Profile endpoints carry the same fields as policy +// endpoints, so every field is mapped. func NetworkEndpointFromProto(ep *sbv1.NetworkEndpoint) *types.NetworkEndpoint { if ep == nil { return nil } - return &types.NetworkEndpoint{ - Host: ep.GetHost(), - Port: ep.GetPort(), - Protocol: ep.GetProtocol(), - } + result := policyNetworkEndpointFromProto(ep) + return &result } -// NetworkEndpointToProto converts an SDK NetworkEndpoint to a proto NetworkEndpoint. +// NetworkEndpointToProto converts an SDK NetworkEndpoint to a proto +// NetworkEndpoint, mapping every field. func NetworkEndpointToProto(ep *types.NetworkEndpoint) *sbv1.NetworkEndpoint { if ep == nil { return nil } - return &sbv1.NetworkEndpoint{ - Host: ep.Host, - Port: ep.Port, - Protocol: ep.Protocol, - } + return policyNetworkEndpointToProto(ep) } // --- NetworkBinary --- diff --git a/openshell/v1/internal/converter/profile_test.go b/openshell/v1/internal/converter/profile_test.go index 1cdf3b9..7c4b884 100644 --- a/openshell/v1/internal/converter/profile_test.go +++ b/openshell/v1/internal/converter/profile_test.go @@ -11,6 +11,7 @@ import ( sbv1 "github.com/ai-openshell/sdk-go/proto/sandboxv1" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/durationpb" ) @@ -751,3 +752,54 @@ func TestProviderProfileRoundTrip(t *testing.T) { assert.Equal(t, original.Discovery.Credentials, back.Discovery.Credentials) } + +// Profile endpoints share the policy endpoint message; access, enforcement, +// credential settings, and every other field must survive both directions +// (ai-openshell/sdk-go#12). +func TestNetworkEndpointRoundTripKeepsAllFields(t *testing.T) { + in := &sbv1.NetworkEndpoint{ + Host: "api.anthropic.com", + Port: 443, + Ports: []uint32{443, 8443}, + Protocol: "rest", + Tls: sbv1.NetworkTlsMode_NETWORK_TLS_MODE_SKIP, + Enforcement: sbv1.NetworkEnforcementMode_NETWORK_ENFORCEMENT_MODE_ENFORCE, + Access: sbv1.NetworkAccessPreset_NETWORK_ACCESS_PRESET_FULL, + AllowedIps: []string{"10.0.0.0/8"}, + Path: "/v1", + AllowUninspectedCredentials: true, + ProviderCredentialed: true, + CredentialBinding: &sbv1.NetworkCredentialBinding{Provider: "anthropic"}, + } + + ep := NetworkEndpointFromProto(in) + require.NotNil(t, ep) + assert.Equal(t, v1.NetworkAccessPresetFull, ep.Access) + assert.True(t, ep.AllowUninspectedCredentials) + require.NotNil(t, ep.CredentialBinding) + assert.Equal(t, "anthropic", ep.CredentialBinding.Provider) + + out := NetworkEndpointToProto(ep) + assert.True(t, proto.Equal(in, out), "round trip changed the endpoint:\nin: %v\nout: %v", in, out) +} + +func TestProviderProfileRoundTripKeepsEndpointAccess(t *testing.T) { + in := &pb.ProviderProfile{ + Id: "claude-code", + Endpoints: []*sbv1.NetworkEndpoint{{ + Host: "api.anthropic.com", + Port: 443, + Protocol: "rest", + Access: sbv1.NetworkAccessPreset_NETWORK_ACCESS_PRESET_READ_WRITE, + Enforcement: sbv1.NetworkEnforcementMode_NETWORK_ENFORCEMENT_MODE_ENFORCE, + }}, + } + + profile := ProviderProfileFromProto(in) + require.Len(t, profile.Endpoints, 1) + assert.Equal(t, v1.NetworkAccessPresetReadWrite, profile.Endpoints[0].Access) + + out := ProviderProfileToProto(profile) + require.Len(t, out.GetEndpoints(), 1) + assert.True(t, proto.Equal(in.GetEndpoints()[0], out.GetEndpoints()[0])) +} diff --git a/openshell/v1/types/network_policy.go b/openshell/v1/types/network_policy.go index 0e41348..d26cf98 100644 --- a/openshell/v1/types/network_policy.go +++ b/openshell/v1/types/network_policy.go @@ -53,9 +53,9 @@ const ( NetworkAccessPresetFull NetworkAccessPreset = 3 ) -// PolicyNetworkEndpoint describes a full network endpoint with its access controls -// as used in sandbox network policy rules. This is distinct from [NetworkEndpoint] -// which is the simplified profile-level endpoint (Host, Port, Protocol only). +// PolicyNetworkEndpoint describes a network endpoint with its access controls, +// as used in sandbox network policy rules and, as [NetworkEndpoint], in +// provider profiles. type PolicyNetworkEndpoint struct { Host string Port uint32 diff --git a/openshell/v1/types/profile.go b/openshell/v1/types/profile.go index 1589eb2..b2886c8 100644 --- a/openshell/v1/types/profile.go +++ b/openshell/v1/types/profile.go @@ -139,12 +139,11 @@ type TokenGrantAudienceOverride struct { Scopes []string } -// NetworkEndpoint describes a network endpoint provided by a profile. -type NetworkEndpoint struct { - Host string - Port uint32 - Protocol string -} +// NetworkEndpoint describes a network endpoint provided by a profile. Profiles +// and sandbox policies share one endpoint message, so it is the same type as +// [PolicyNetworkEndpoint], with access, enforcement, rules, and credential +// settings. +type NetworkEndpoint = PolicyNetworkEndpoint // NetworkBinary describes a binary artifact provided by a profile. type NetworkBinary struct { diff --git a/openshell/v1/types_reexport.go b/openshell/v1/types_reexport.go index c982391..2c7725b 100644 --- a/openshell/v1/types_reexport.go +++ b/openshell/v1/types_reexport.go @@ -15,6 +15,12 @@ type NetworkPolicyRule = types.NetworkPolicyRule // PolicyNetworkEndpoint describes a full network endpoint in a sandbox network policy rule. type PolicyNetworkEndpoint = types.PolicyNetworkEndpoint +// NetworkCredentialBinding binds an endpoint to static credentials from an attached provider. +type NetworkCredentialBinding = types.NetworkCredentialBinding + +// McpOptions configures MCP inspection for a policy endpoint. +type McpOptions = types.McpOptions + // NetworkTLSMode controls TLS handling for a policy endpoint. type NetworkTLSMode = types.NetworkTLSMode