From ebc025fb3c61f42211c9ad02f21a8c122b844a86 Mon Sep 17 00:00:00 2001 From: Tolyan Korniltsev Date: Tue, 30 Jun 2026 11:56:03 +0700 Subject: [PATCH 1/2] Validate label characters in Pyroscope config Reject appName and tag keys/values containing Prometheus label syntax characters ({, }, ,, =) during config validation. --- src/utils/check-pyroscope-config.ts | 65 +++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/src/utils/check-pyroscope-config.ts b/src/utils/check-pyroscope-config.ts index 7945f5c2..9e689c2e 100644 --- a/src/utils/check-pyroscope-config.ts +++ b/src/utils/check-pyroscope-config.ts @@ -4,6 +4,9 @@ import { PyroscopeWallConfig, } from '../pyroscope-config.js'; +const INVALID_LABEL_CHARACTERS = ['{', '}', ',', '='] as const; +const INVALID_LABEL_CHARACTERS_MESSAGE = "'{', '}', ',', or '=' characters"; + export function checkPyroscopeConfig( config: unknown ): asserts config is PyroscopeConfig { @@ -15,8 +18,12 @@ export function checkPyroscopeConfig( if (!hasValidApplicationName(config)) { errors.push('Expecting a config with string appName'); + } else { + validateApplicationName(config, errors); } + validateTags(config, errors); + if (!hasValidAuthToken(config)) { errors.push('Expecting a config with string auth token'); } @@ -51,6 +58,64 @@ function hasValidApplicationName( ); } +function validateApplicationName( + config: Record, + errors: string[] +): void { + const appName: string | undefined = (config as Partial) + .appName; + + if (appName === undefined) { + return; + } + + if (hasInvalidLabelCharacters(appName)) { + errors.push(`appName must not contain ${INVALID_LABEL_CHARACTERS_MESSAGE}`); + } +} + +function validateTags( + config: Record, + errors: string[] +): void { + const tags: unknown = (config as Partial).tags; + + if (tags === undefined) { + return; + } + + if (!isObject(tags)) { + errors.push('Expecting a config with object tags'); + return; + } + + for (const [tagKey, tagValue] of Object.entries(tags)) { + if (hasInvalidLabelCharacters(tagKey)) { + errors.push( + `tag key "${tagKey}" must not contain ${INVALID_LABEL_CHARACTERS_MESSAGE}` + ); + } + + if (typeof tagValue === 'string' && hasInvalidLabelCharacters(tagValue)) { + errors.push( + `tag value for "${tagKey}" must not contain ${INVALID_LABEL_CHARACTERS_MESSAGE}` + ); + } else if (typeof tagValue !== 'number' && typeof tagValue !== 'string') { + errors.push(`tag value for "${tagKey}" must be a string or number`); + } + } +} + +function hasInvalidLabelCharacters(value: string): boolean { + for (const character of INVALID_LABEL_CHARACTERS) { + if (value.includes(character)) { + return true; + } + } + + return false; +} + function hasValidAuthToken(config: Record): boolean { return ( (config as Partial).authToken === undefined || From ad5d612e3d408699a12ccacb134fa834fbad9ddf Mon Sep 17 00:00:00 2001 From: Tolyan Korniltsev Date: Fri, 3 Jul 2026 18:05:16 +0700 Subject: [PATCH 2/2] Address review: include tag value in error messages, add tests Co-Authored-By: Claude Fable 5 --- src/utils/check-pyroscope-config.ts | 6 ++- test/check-pyroscope-config.test.ts | 59 +++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 test/check-pyroscope-config.test.ts diff --git a/src/utils/check-pyroscope-config.ts b/src/utils/check-pyroscope-config.ts index 9e689c2e..7f6ae590 100644 --- a/src/utils/check-pyroscope-config.ts +++ b/src/utils/check-pyroscope-config.ts @@ -98,10 +98,12 @@ function validateTags( if (typeof tagValue === 'string' && hasInvalidLabelCharacters(tagValue)) { errors.push( - `tag value for "${tagKey}" must not contain ${INVALID_LABEL_CHARACTERS_MESSAGE}` + `tag value "${tagValue}" for key "${tagKey}" must not contain ${INVALID_LABEL_CHARACTERS_MESSAGE}` ); } else if (typeof tagValue !== 'number' && typeof tagValue !== 'string') { - errors.push(`tag value for "${tagKey}" must be a string or number`); + errors.push( + `tag value "${String(tagValue)}" for key "${tagKey}" must be a string or number` + ); } } } diff --git a/test/check-pyroscope-config.test.ts b/test/check-pyroscope-config.test.ts new file mode 100644 index 00000000..a880f0fe --- /dev/null +++ b/test/check-pyroscope-config.test.ts @@ -0,0 +1,59 @@ +import { describe, it } from 'node:test'; +import { strict as assert } from 'node:assert'; + +import { checkPyroscopeConfig } from '../src/utils/check-pyroscope-config.js'; + +const INVALID_CHARACTERS = ['{', '}', ',', '=']; + +describe('checkPyroscopeConfig', () => { + it('accepts a valid config', () => { + assert.doesNotThrow(() => + checkPyroscopeConfig({ + appName: 'my-app', + tags: { region: 'us-east-1', replica: 3 }, + }) + ); + }); + + for (const character of INVALID_CHARACTERS) { + it(`rejects appName containing "${character}"`, () => { + assert.throws( + () => checkPyroscopeConfig({ appName: `my${character}app` }), + /appName must not contain/ + ); + }); + + it(`rejects tag key containing "${character}"`, () => { + assert.throws( + () => + checkPyroscopeConfig({ + appName: 'my-app', + tags: { [`bad${character}key`]: 'value' }, + }), + new RegExp(`tag key "bad\\${character}key" must not contain`) + ); + }); + + it(`rejects tag value containing "${character}"`, () => { + assert.throws( + () => + checkPyroscopeConfig({ + appName: 'my-app', + tags: { key: `bad${character}value` }, + }), + new RegExp(`tag value "bad\\${character}value" for key "key"`) + ); + }); + } + + it('rejects a tag value that is not a string or number', () => { + assert.throws( + () => + checkPyroscopeConfig({ + appName: 'my-app', + tags: { key: true }, + }), + /tag value "true" for key "key" must be a string or number/ + ); + }); +});