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
2 changes: 1 addition & 1 deletion proto/protovalidate/buf/validate/validate.proto
Original file line number Diff line number Diff line change
Expand Up @@ -3828,7 +3828,7 @@ message StringRules {
message: "must be a valid HTTP header name"
expression:
"rules.well_known_regex != 1 || this == '' || this.matches(!has(rules.strict) || rules.strict ?"
"'^:?[0-9a-zA-Z!#$%&\\'*+-.^_|~\\x60]+$' :"
"'^:?[0-9a-zA-Z!#$%&\\'*+.^_|~\\x60-]+$' :"

@timostamm timostamm Sep 3, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The cause of the bug was +-. in the character class. It's a range that expands to +,-..

The most direct fix is to quote the hyphen, but it would need to be \\\\- in validate.proto because of the two layers of escaping (prototext and CEL).

Moving the hyphen to the end has the same effect (no longer a range) but doesn't require escaping.

Reproduction:

header/v1/header.proto:

syntax = "proto3";
package header.v1;
import "buf/validate/validate.proto";

message Header {
  // The regular expression in `string.well_known_regex.header_name`
  string name = 1 [(buf.validate.field).cel = {
    // original, broken:
    //expression: "this.matches('^:?[0-9a-zA-Z!#$%&\\'*+-.^_|~\\x60]+$')"

    // fixed:
    expression: "this.matches('^:?[0-9a-zA-Z!#$%&\\'*+.^_|~\\x60-]+$')"
  }];
}

main.go:

package main

import (
	headerv1 "example/internal/gen/header/v1"
	"fmt"

	"buf.build/go/protovalidate"
)

func main() {
	h := &headerv1.Header{Name: ","}
	err := protovalidate.Validate(h)
	fmt.Printf("%q %t\n", h.GetName(), err == nil)
}

The output is "," true with the original, "," false with the fixed expression.

"'^[^\\u0000\\u000A\\u000D]+$')"
},
(predefined).cel = {
Expand Down
2 changes: 1 addition & 1 deletion tools/internal/gen/buf/validate/validate.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions tools/protovalidate-conformance/internal/cases/cases_strings.go
Original file line number Diff line number Diff line change
Expand Up @@ -1528,6 +1528,10 @@ func stringSuite() suites.Suite {
Message: &cases.StringHttpHeaderName{Val: ":authority"},
Expected: results.Success(true),
},
"well_known_regex/header_name/strict/valid/all_chars": {
Message: &cases.StringHttpHeaderName{Val: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!#$%&'*+-.^_|~`"},
Expected: results.Success(true),
},
"well_known_regex/header_name/strict/valid/numbers": {
Message: &cases.StringHttpHeaderName{Val: "abc-123"},
Expected: results.Success(true),
Expand Down Expand Up @@ -1570,6 +1574,16 @@ func stringSuite() suites.Suite {
},
),
},
"well_known_regex/header_name/strict/invalid/comma": {
Message: &cases.StringHttpHeaderName{Val: "foo,bar"},
Expected: results.Violations(
&validate.Violation{
Field: results.FieldPath("val"),
Rule: results.FieldPath("string.well_known_regex"),
RuleId: proto.String("string.well_known_regex.header_name"),
},
),
},
"well_known_regex/header_name/strict/invalid/space": {
Message: &cases.StringHttpHeaderName{Val: "foo bar"},
Expected: results.Violations(
Expand Down
Loading