Description
string.uuid is documented as requiring "a valid UUID as defined by RFC 4122", but the rule only checks the 8-4-4-4-12 hex shape:
this.matches('^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$')
RFC 4122, and RFC 9562 which obsoletes it, give meaning to two positions in that string:
- the version, the first character of the third group, is
1 to 8 (RFC 9562 section 4.2)
- the variant, the first character of the fourth group, is
10xx, i.e. 8, 9, a or b (section 4.1)
The Nil UUID (section 5.9) and the Max UUID (section 5.10) are the only valid values that follow neither rule. Because neither position is checked, strings with an undefined version or a non-RFC variant are reported as valid UUIDs. string.tuuid (^[0-9a-fA-F]{32}$) has the same gap.
Steps to Reproduce
syntax = "proto3";
package repro;
import "buf/validate/validate.proto";
message Request {
string id = 1 [(buf.validate.field).string.uuid = true];
}
import { create } from '@bufbuild/protobuf';
import { createValidator } from '@bufbuild/protovalidate';
import { RequestSchema } from './gen/repro_pb.js';
const validator = createValidator();
for (const id of [
'919108f7-52d1-4320-9bac-f847db4148a8', // v4
'919108f7-52d1-0320-9bac-f847db4148a8', // version 0
'919108f7-52d1-f320-9bac-f847db4148a8', // version f
'919108f7-52d1-4320-cbac-f847db4148a8', // variant c
'919108f7-52d1-4320-0bac-f847db4148a8', // variant 0
]) {
console.log(id, validator.validate(RequestSchema, create(RequestSchema, { id })).kind);
}
Expected Behavior
Only the v4 UUID is valid. The other four strings produce a string.uuid violation.
Actual Behavior
All five are valid:
919108f7-52d1-4320-9bac-f847db4148a8 valid
919108f7-52d1-0320-9bac-f847db4148a8 valid
919108f7-52d1-f320-9bac-f847db4148a8 valid
919108f7-52d1-4320-cbac-f847db4148a8 valid
919108f7-52d1-4320-0bac-f847db4148a8 valid
The rule is a CEL expression in validate.proto, so this is not specific to protovalidate-es. Every runtime evaluates the same expression.
Environment
- Operating System: Windows 11
- Version: 10.0.26200
- Compiler/Toolchain: Node.js 24.19.0
- Protobuf Compiler & Version: buf (npm
@bufbuild/buf), protoc-gen-es
- Protovalidate Version:
buf.build/bufbuild/protovalidate main (commit 511051f7f4374c3ca873b53ae68a9288), @bufbuild/protovalidate 1.2.0
Possible Solution
Check the two positions in both rules, and keep Nil and Max valid. In RE2 syntax:
string.uuid:
^(?:[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|0{8}-0{4}-0{4}-0{4}-0{12}|[fF]{8}-[fF]{4}-[fF]{4}-[fF]{4}-[fF]{12})$
string.tuuid:
^(?:[0-9a-fA-F]{12}[1-8][0-9a-fA-F]{3}[89abAB][0-9a-fA-F]{15}|0{32}|[fF]{32})$
The conformance cases today only cover v1 to v5 and Nil as valid, so nothing pins the current behavior. New cases would cover v6, v7, v8 and Max as valid, and an out-of-range version and variant as invalid.
This makes the rules stricter, so values that pass today would start failing. If you would rather keep uuid as a shape check, the alternative is to change the documentation so it no longer says "as defined by RFC 4122".
Which of these two directions would you prefer? I'm happy to open a PR for either one, including the conformance cases.
Additional Context
For comparison, uuid (validate), validator (isUUID) and zod (z.uuid()) check both positions and accept Nil and Max.
Description
string.uuidis documented as requiring "a valid UUID as defined by RFC 4122", but the rule only checks the8-4-4-4-12hex shape:RFC 4122, and RFC 9562 which obsoletes it, give meaning to two positions in that string:
1to8(RFC 9562 section 4.2)10xx, i.e.8,9,aorb(section 4.1)The Nil UUID (section 5.9) and the Max UUID (section 5.10) are the only valid values that follow neither rule. Because neither position is checked, strings with an undefined version or a non-RFC variant are reported as valid UUIDs.
string.tuuid(^[0-9a-fA-F]{32}$) has the same gap.Steps to Reproduce
Expected Behavior
Only the v4 UUID is valid. The other four strings produce a
string.uuidviolation.Actual Behavior
All five are
valid:The rule is a CEL expression in
validate.proto, so this is not specific to protovalidate-es. Every runtime evaluates the same expression.Environment
@bufbuild/buf),protoc-gen-esbuf.build/bufbuild/protovalidatemain(commit511051f7f4374c3ca873b53ae68a9288),@bufbuild/protovalidate1.2.0Possible Solution
Check the two positions in both rules, and keep Nil and Max valid. In RE2 syntax:
The conformance cases today only cover v1 to v5 and Nil as valid, so nothing pins the current behavior. New cases would cover v6, v7, v8 and Max as valid, and an out-of-range version and variant as invalid.
This makes the rules stricter, so values that pass today would start failing. If you would rather keep
uuidas a shape check, the alternative is to change the documentation so it no longer says "as defined by RFC 4122".Which of these two directions would you prefer? I'm happy to open a PR for either one, including the conformance cases.
Additional Context
For comparison,
uuid(validate),validator(isUUID) andzod(z.uuid()) check both positions and accept Nil and Max.