diff --git a/internal/config/guard_policy_validation.go b/internal/config/guard_policy_validation.go index 03dca6c91..c3d46fe2e 100644 --- a/internal/config/guard_policy_validation.go +++ b/internal/config/guard_policy_validation.go @@ -5,6 +5,8 @@ import ( "fmt" "sort" "strings" + + "github.com/github/gh-aw-mcpg/internal/strutil" ) const errMsgPolicyMissingKey = "policy must include allow-only or write-sink" @@ -186,11 +188,7 @@ func NormalizeGuardPolicy(policy *GuardPolicy) (*NormalizedGuardPolicy, error) { return normalized, nil case []string: - generic := make([]interface{}, len(scope)) - for i := range scope { - generic[i] = scope[i] - } - scopes, err := normalizeAndValidateScopeArray(generic) + scopes, err := normalizeAndValidateScopeArray(strutil.StringsToAny(scope)) if err != nil { return nil, err } diff --git a/internal/guard/wasm_payload.go b/internal/guard/wasm_payload.go index 960123df0..d34b6fc3a 100644 --- a/internal/guard/wasm_payload.go +++ b/internal/guard/wasm_payload.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/github/gh-aw-mcpg/internal/config" + "github.com/github/gh-aw-mcpg/internal/strutil" ) // normalizePolicyPayload coerces a policy value to a map[string]interface{}. @@ -250,24 +251,16 @@ func BuildLabelAgentPayload(policy interface{}, trustedBots []string, trustedUse if len(trustedBots) > 0 { // trusted-bots is a top-level key in the label_agent payload. - // Convert []string to []interface{} for JSON compatibility. - bots := make([]interface{}, len(trustedBots)) - for i, b := range trustedBots { - bots[i] = b - } + bots := strutil.StringsToAny(trustedBots) payload["trusted-bots"] = bots logWasm.Printf("BuildLabelAgentPayload: injected %d trusted-bots into payload", len(trustedBots)) } if len(trustedUsers) > 0 { // trusted-users is injected inside the allow-only object. - // Convert []string to []interface{} for JSON compatibility. // If allow-only is absent, the injection is skipped and buildStrictLabelAgentPayload // will reject the payload when called with the missing allow-only key. - users := make([]interface{}, len(trustedUsers)) - for i, u := range trustedUsers { - users[i] = u - } + users := strutil.StringsToAny(trustedUsers) // Inject into allow-only object if present if allowOnly, ok := payload["allow-only"].(map[string]interface{}); ok { allowOnly["trusted-users"] = users diff --git a/internal/strutil/strings_to_any_test.go b/internal/strutil/strings_to_any_test.go new file mode 100644 index 000000000..5121e6db5 --- /dev/null +++ b/internal/strutil/strings_to_any_test.go @@ -0,0 +1,26 @@ +package strutil + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestStringsToAny(t *testing.T) { + t.Parallel() + + t.Run("nil input returns empty (non-nil) slice", func(t *testing.T) { + t.Parallel() + assert.Equal(t, []interface{}{}, StringsToAny(nil)) + }) + + t.Run("empty input returns empty slice", func(t *testing.T) { + t.Parallel() + assert.Empty(t, StringsToAny([]string{})) + }) + + t.Run("converts all entries preserving order", func(t *testing.T) { + t.Parallel() + assert.Equal(t, []interface{}{"octo", "hub", "bot"}, StringsToAny([]string{"octo", "hub", "bot"})) + }) +} diff --git a/internal/strutil/strutil.go b/internal/strutil/strutil.go index 1d6429156..9dc59fa42 100644 --- a/internal/strutil/strutil.go +++ b/internal/strutil/strutil.go @@ -28,6 +28,15 @@ func DeduplicateStrings(input []string, sorted bool) []string { return out } +// StringsToAny converts a []string to []interface{}. +func StringsToAny(input []string) []interface{} { + out := make([]interface{}, len(input)) + for i, value := range input { + out[i] = value + } + return out +} + // GetStringFromMap returns the first non-empty string value found for any of // the given keys in m. For each key, the value must be present, typed as // string, and non-empty to be returned. Returns an empty string when no