Skip to content

Commit 980b6cf

Browse files
committed
Reject an empty label name under UTF-8 validation
With legacy validation off, which is the default, an empty string passes _validate_labelname. A metric declared with labelnames=[''] then exposes g{="x"} 1.0, which both bundled parsers reject as invalid labels. Legacy validation already refuses it, and _validate_metric_name refuses an empty metric name the same way. Both parsers call the same function, so a quoted empty label name such as a{""="x"} 1 is now refused on input as well. Signed-off-by: Christopher Krah <48338417+chrikrah@users.noreply.github.com>
1 parent ff9a2d3 commit 980b6cf

4 files changed

Lines changed: 26 additions & 0 deletions

File tree

‎prometheus_client/validation.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ def _validate_labelname(l):
7878
7979
This check uses the global legacy validation setting to determine the validation scheme.
8080
"""
81+
if not l:
82+
raise ValueError('label name cannot be empty')
8183
if get_legacy_validation():
8284
if not METRIC_LABEL_NAME_RE.match(l):
8385
raise ValueError('Invalid label metric name: ' + l)

‎tests/openmetrics/test_parser.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -774,6 +774,7 @@ def test_invalid_input(self):
774774
('a{a=" # "}1\n# EOF\n'),
775775
('a{a=" # ",b=}1\n# EOF\n'),
776776
('a{a=" # "b}1\n# EOF\n'),
777+
('a{""="1"} 1\n# EOF\n'),
777778
# Missing value.
778779
('a\n# EOF\n'),
779780
('a \n# EOF\n'),

‎tests/test_core.py‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -818,6 +818,21 @@ def test_duplicate_labelnames_error_lists_only_duplicates(self):
818818
self.assertEqual(str(error.exception), f'Duplicate label names: {expected!r}')
819819
self.assertEqual(list(registry.collect()), [])
820820

821+
def test_empty_labelname_raises(self):
822+
# The exposition would contain {="value"}, which no parser accepts.
823+
for metric_type in (Counter, Gauge, Summary, Histogram, Info, Enum):
824+
with self.subTest(metric_type=metric_type):
825+
registry = CollectorRegistry()
826+
kwargs = {'states': ['ready']} if metric_type is Enum else {}
827+
with self.assertRaisesRegex(ValueError, '^label name cannot be empty$'):
828+
metric_type('empty_label', 'help', labelnames=[''], registry=registry, **kwargs)
829+
self.assertEqual(list(registry.collect()), [])
830+
831+
def test_empty_exemplar_labelname_raises(self):
832+
counter = Counter('empty_exemplar', 'help', registry=self.registry)
833+
with self.assertRaisesRegex(ValueError, '^label name cannot be empty$'):
834+
counter.inc(exemplar={'': 'abc'})
835+
821836
def test_unique_labelnames_iterator(self):
822837
# Valid one-shot label iterables still preserve both names.
823838
counter = Counter('iter_labels', 'help', iter(['a', 'b']), registry=self.registry)

‎tests/test_parser.py‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,14 @@ def test_multiple_trailing_commas(self):
217217
text = """# TYPE a counter
218218
# HELP a help
219219
a{foo="bar",, } 1
220+
"""
221+
self.assertRaises(ValueError,
222+
lambda: list(text_string_to_metric_families(text)))
223+
224+
def test_quoted_empty_label_name(self):
225+
text = """# TYPE a counter
226+
# HELP a help
227+
a{""="bar"} 1
220228
"""
221229
self.assertRaises(ValueError,
222230
lambda: list(text_string_to_metric_families(text)))

0 commit comments

Comments
 (0)