Skip to content

[BUG] string.uuid and string.tuuid accept values with an undefined UUID version or variant #530

Description

@otnc

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions