Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 8 additions & 25 deletions internal/guard/wasm_payload.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,10 @@ func buildStrictLabelAgentPayload(policy interface{}) (map[string]interface{}, e

reposRaw, hasRepos := allowOnly["repos"]
integrityRaw, hasIntegrity := allowOnly["min-integrity"]
integrityFieldName := "min-integrity"
if !hasIntegrity {
integrityRaw, hasIntegrity = allowOnly["integrity"]
integrityFieldName = "integrity"
}
if !hasRepos || !hasIntegrity {
return nil, fmt.Errorf("invalid guard policy transport shape: missing required fields repos and/or min-integrity in allow-only object")
Expand All @@ -114,15 +116,8 @@ func buildStrictLabelAgentPayload(policy interface{}) (map[string]interface{}, e
return nil, fmt.Errorf("invalid repos value: expected all, public, or non-empty array of scoped strings")
}

integrity, ok := integrityRaw.(string)
if !ok {
return nil, fmt.Errorf("invalid integrity value: expected one of none|unapproved|approved|merged")
}

switch strings.ToLower(strings.TrimSpace(integrity)) {
case "none", "unapproved", "approved", "merged":
default:
return nil, fmt.Errorf("invalid integrity value: expected one of none|unapproved|approved|merged")
if err := validateIntegrityField(integrityFieldName, integrityRaw); err != nil {
return nil, err
}

// Validate blocked-users if present: must be a non-empty array of non-empty strings.
Expand Down Expand Up @@ -199,27 +194,15 @@ func buildStrictLabelAgentPayload(policy interface{}) (map[string]interface{}, e

// Validate disapproval-integrity if present.
if disIntRaw, ok := allowOnly["disapproval-integrity"]; ok {
disInt, ok := disIntRaw.(string)
if !ok {
return nil, fmt.Errorf("invalid disapproval-integrity value: expected one of none|unapproved|approved|merged")
}
switch strings.ToLower(strings.TrimSpace(disInt)) {
case "none", "unapproved", "approved", "merged":
default:
return nil, fmt.Errorf("invalid disapproval-integrity value: expected one of none|unapproved|approved|merged")
if err := validateIntegrityField("disapproval-integrity", disIntRaw); err != nil {
return nil, err
}
}

// Validate endorser-min-integrity if present.
if endMinRaw, ok := allowOnly["endorser-min-integrity"]; ok {
endMin, ok := endMinRaw.(string)
if !ok {
return nil, fmt.Errorf("invalid endorser-min-integrity value: expected one of none|unapproved|approved|merged")
}
switch strings.ToLower(strings.TrimSpace(endMin)) {
case "none", "unapproved", "approved", "merged":
default:
return nil, fmt.Errorf("invalid endorser-min-integrity value: expected one of none|unapproved|approved|merged")
if err := validateIntegrityField("endorser-min-integrity", endMinRaw); err != nil {
return nil, err
}
}

Expand Down
2 changes: 1 addition & 1 deletion internal/guard/wasm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,7 @@ func TestBuildStrictLabelAgentPayloadExtended(t *testing.T) {

_, err := buildStrictLabelAgentPayload(policy)
require.Error(t, err)
assert.Contains(t, err.Error(), "invalid integrity value")
assert.Contains(t, err.Error(), "invalid min-integrity value")
})

t.Run("valid allow-only policy succeeds", func(t *testing.T) {
Expand Down
38 changes: 38 additions & 0 deletions internal/guard/wasm_validate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package guard

import (
"fmt"
"strings"
)

// allowedIntegrityLevels is the single source of truth for valid integrity-level values.
var allowedIntegrityLevels = []string{"none", "unapproved", "approved", "merged"}

var allowedIntegrityLevelSet = map[string]struct{}{
"none": {},
"unapproved": {},
"approved": {},
"merged": {},
}

func invalidIntegrityFieldError(fieldName string) error {
return fmt.Errorf(
"invalid %s value: expected one of %s",
fieldName,
strings.Join(allowedIntegrityLevels, "|"),
)
}

// validateIntegrityField returns an error if raw is not a valid integrity-level
// string. fieldName is used in the error message (e.g. "disapproval-integrity").
func validateIntegrityField(fieldName string, raw interface{}) error {
s, ok := raw.(string)
if !ok {
return invalidIntegrityFieldError(fieldName)
}
normalized := strings.ToLower(strings.TrimSpace(s))
if _, ok := allowedIntegrityLevelSet[normalized]; ok {
return nil
}
Comment on lines +26 to +36
Copy link

Copilot AI May 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

validateIntegrityField still duplicates the allowed-values string in multiple places (the error message is repeated in both the type-assertion failure and default case, and the allowed values are also duplicated in the switch). To fully achieve the goal of “single source of truth” for integrity levels, consider centralizing the allowed values (e.g., a const/slice + helper to format the error) so adding a new integrity level requires changing it in only one place.

Suggested change
// validateIntegrityField returns an error if raw is not a valid integrity-level
// string. fieldName is used in the error message (e.g. "disapproval-integrity").
func validateIntegrityField(fieldName string, raw interface{}) error {
s, ok := raw.(string)
if !ok {
return fmt.Errorf("invalid %s value: expected one of none|unapproved|approved|merged", fieldName)
}
switch strings.ToLower(strings.TrimSpace(s)) {
case "none", "unapproved", "approved", "merged":
return nil
default:
return fmt.Errorf("invalid %s value: expected one of none|unapproved|approved|merged", fieldName)
}
var allowedIntegrityLevels = []string{"none", "unapproved", "approved", "merged"}
var allowedIntegrityLevelSet = map[string]struct{}{
"none": {},
"unapproved": {},
"approved": {},
"merged": {},
}
func invalidIntegrityFieldError(fieldName string) error {
return fmt.Errorf(
"invalid %s value: expected one of %s",
fieldName,
strings.Join(allowedIntegrityLevels, "|"),
)
}
// validateIntegrityField returns an error if raw is not a valid integrity-level
// string. fieldName is used in the error message (e.g. "disapproval-integrity").
func validateIntegrityField(fieldName string, raw interface{}) error {
s, ok := raw.(string)
if !ok {
return invalidIntegrityFieldError(fieldName)
}
normalized := strings.ToLower(strings.TrimSpace(s))
if _, ok := allowedIntegrityLevelSet[normalized]; ok {
return nil
}
return invalidIntegrityFieldError(fieldName)

Copilot uses AI. Check for mistakes.
return invalidIntegrityFieldError(fieldName)
}
Loading