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
16 changes: 12 additions & 4 deletions protocol/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -2307,10 +2307,14 @@ func (w WASMWorkload) Validate() error {
}

func validateProviderComponentRef(name, value string) error {
value = strings.TrimSpace(value)
if value == "" {
raw := value
trimmed := strings.TrimSpace(value)
if trimmed == "" {
return fmt.Errorf("%s is required", name)
}
if raw != trimmed {
return fmt.Errorf("%s must not contain surrounding whitespace", name)
}
if strings.ContainsAny(value, " \t\r\n\x00") {
return fmt.Errorf("%s must not contain whitespace or NUL", name)
}
Expand Down Expand Up @@ -4856,10 +4860,14 @@ func validateScopedRef(name, value, scheme string) error {
}

func validateComponentRef(name, value string) error {
value = strings.TrimSpace(value)
if value == "" {
raw := value
trimmed := strings.TrimSpace(value)
if trimmed == "" {
return fmt.Errorf("%s is required", name)
}
if raw != trimmed {
return fmt.Errorf("%s must not contain surrounding whitespace", name)
}
for _, prefix := range []string{"artifact://", "content://", "provider://"} {
if strings.HasPrefix(value, prefix) && !strings.ContainsAny(value, " \t\r\n\x00") {
return nil
Expand Down
35 changes: 35 additions & 0 deletions protocol/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,20 @@ func TestServiceWorkloadRejectsUnsafeShape(t *testing.T) {
}
}

func TestServiceWorkloadRejectsComponentRefWhitespace(t *testing.T) {
workload := protocol.ServiceWorkload{
ComponentRef: " provider://workflow-plugin-compute-service/service-runtime",
ComponentDigest: "sha256:" + strings.Repeat("a", 64),
Command: []string{"serve", "--port", "8080"},
Ports: []protocol.ServicePort{{Name: "http", Port: 8080, Protocol: "http"}},
HealthCheck: protocol.HealthCheck{Kind: "http", Path: "/healthz", IntervalSeconds: 5, TimeoutSeconds: 2},
Ingress: protocol.IngressPolicy{Mode: "private", AuthRequired: true},
}
if err := workload.Validate(); err == nil || !strings.Contains(err.Error(), "surrounding whitespace") {
t.Fatalf("service workload accepted whitespace-padded component_ref: %v", err)
}
}

func TestWorkloadSpecValidatesNodeServiceWorkload(t *testing.T) {
endpoint := "node.example.invalid:30303"
workload := protocol.WorkloadSpec{
Expand Down Expand Up @@ -184,6 +198,27 @@ func TestWorkloadSpecValidatesNodeServiceWorkload(t *testing.T) {
}
}

func TestNodeServiceWorkloadRejectsComponentRefWhitespace(t *testing.T) {
endpoint := "node.example.invalid:30303"
workload := protocol.NodeServiceWorkload{
ComponentRef: "provider://workflow-plugin-compute-service/node-runtime ",
ComponentDigest: "sha256:" + strings.Repeat("b", 64),
Chain: "ethereum",
Network: "sepolia",
DataDirRef: "volume://nodes/sepolia",
RPCSecretRef: "secret://nodes/sepolia/rpc",
PeerPolicy: protocol.PeerPolicy{
Mode: "allowlist",
AllowedPeers: []string{endpoint},
EgressAllowlist: []string{endpoint},
},
HealthCheck: protocol.HealthCheck{Kind: "command", Command: []string{"node", "status"}, IntervalSeconds: 10, TimeoutSeconds: 3},
}
if err := workload.Validate(); err == nil || !strings.Contains(err.Error(), "surrounding whitespace") {
t.Fatalf("node-service workload accepted whitespace-padded component_ref: %v", err)
}
}

func TestNodeServiceWorkloadRejectsMissingHealthAndUnsafePeers(t *testing.T) {
workload := protocol.NodeServiceWorkload{
ImageRef: "ghcr.io/gocodealone/node@sha256:" + strings.Repeat("c", 64),
Expand Down
Loading