diff --git a/CHANGELOG.md b/CHANGELOG.md index 053f4b3..fa057e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,80 +1,76 @@ # Changelog -## [7.0.3](https://github.com/Flagsmith/flagsmith-nodejs-client/compare/v7.0.2...v7.0.3) (2026-01-21) +## [Unreleased] + +### Security +- replace jsonpath with jsonpath-plus to remediate eval()-based code injection vulnerability + +## [7.0.3](https://github.com/Flagsmith/flagsmith-nodejs-client/compare/v7.0.2...v7.0.3) (2026-01-21) ### Dependency Updates -* bump glob and npm ([#233](https://github.com/Flagsmith/flagsmith-nodejs-client/issues/233)) ([17802df](https://github.com/Flagsmith/flagsmith-nodejs-client/commit/17802dfafc10874a17dbb3804c0a4e91722864d8)) -* bump js-yaml from 4.1.0 to 4.1.1 ([#223](https://github.com/Flagsmith/flagsmith-nodejs-client/issues/223)) ([7235792](https://github.com/Flagsmith/flagsmith-nodejs-client/commit/7235792a10bb1a8ca3ffc1144697cd7654ec5c4e)) -* bump tar and npm ([#232](https://github.com/Flagsmith/flagsmith-nodejs-client/issues/232)) ([bb34fb5](https://github.com/Flagsmith/flagsmith-nodejs-client/commit/bb34fb5e53dff9003794df9d476674bf9fbb2e10)) +- bump glob and npm ([#233](https://github.com/Flagsmith/flagsmith-nodejs-client/issues/233)) ([17802df](https://github.com/Flagsmith/flagsmith-nodejs-client/commit/17802dfafc10874a17dbb3804c0a4e91722864d8)) +- bump js-yaml from 4.1.0 to 4.1.1 ([#223](https://github.com/Flagsmith/flagsmith-nodejs-client/issues/223)) ([7235792](https://github.com/Flagsmith/flagsmith-nodejs-client/commit/7235792a10bb1a8ca3ffc1144697cd7654ec5c4e)) +- bump tar and npm ([#232](https://github.com/Flagsmith/flagsmith-nodejs-client/issues/232)) ([bb34fb5](https://github.com/Flagsmith/flagsmith-nodejs-client/commit/bb34fb5e53dff9003794df9d476674bf9fbb2e10)) ## [7.0.2](https://github.com/Flagsmith/flagsmith-nodejs-client/compare/v7.0.1...v7.0.2) (2025-12-02) - ### CI -* use-nvmrc-for-version ([#236](https://github.com/Flagsmith/flagsmith-nodejs-client/issues/236)) ([a56f073](https://github.com/Flagsmith/flagsmith-nodejs-client/commit/a56f0736f6c31d3c273932ede8204710de7cf853)) +- use-nvmrc-for-version ([#236](https://github.com/Flagsmith/flagsmith-nodejs-client/issues/236)) ([a56f073](https://github.com/Flagsmith/flagsmith-nodejs-client/commit/a56f0736f6c31d3c273932ede8204710de7cf853)) ## [7.0.1](https://github.com/Flagsmith/flagsmith-nodejs-client/compare/v7.0.0...v7.0.1) (2025-12-02) - ### CI -* use-latest-npm ([#234](https://github.com/Flagsmith/flagsmith-nodejs-client/issues/234)) ([6a741f3](https://github.com/Flagsmith/flagsmith-nodejs-client/commit/6a741f3b12af3bea31150561dee1e6f7c7045e56)) +- use-latest-npm ([#234](https://github.com/Flagsmith/flagsmith-nodejs-client/issues/234)) ([6a741f3](https://github.com/Flagsmith/flagsmith-nodejs-client/commit/6a741f3b12af3bea31150561dee1e6f7c7045e56)) ## [7.0.0](https://github.com/Flagsmith/flagsmith-nodejs-client/compare/v6.2.0...v7.0.0) (2025-12-02) - ### ⚠ BREAKING CHANGES -* implement context values ([#203](https://github.com/Flagsmith/flagsmith-nodejs-client/issues/203)) +- implement context values ([#203](https://github.com/Flagsmith/flagsmith-nodejs-client/issues/203)) ### Features -* implement context values ([#203](https://github.com/Flagsmith/flagsmith-nodejs-client/issues/203)) ([41258f2](https://github.com/Flagsmith/flagsmith-nodejs-client/commit/41258f2e24ef7e89207a0f10116ffbd1229c0a30)) -* removed-feature-key-and-segment-key-from-schema ([#210](https://github.com/Flagsmith/flagsmith-nodejs-client/issues/210)) ([014f38b](https://github.com/Flagsmith/flagsmith-nodejs-client/commit/014f38bf33af77fb706e4e130e8a571914632408)) - +- implement context values ([#203](https://github.com/Flagsmith/flagsmith-nodejs-client/issues/203)) ([41258f2](https://github.com/Flagsmith/flagsmith-nodejs-client/commit/41258f2e24ef7e89207a0f10116ffbd1229c0a30)) +- removed-feature-key-and-segment-key-from-schema ([#210](https://github.com/Flagsmith/flagsmith-nodejs-client/issues/210)) ([014f38b](https://github.com/Flagsmith/flagsmith-nodejs-client/commit/014f38bf33af77fb706e4e130e8a571914632408)) ### Bug Fixes -* exclude-identities-when-traits-is-undefined ([#230](https://github.com/Flagsmith/flagsmith-nodejs-client/issues/230)) ([f7488e1](https://github.com/Flagsmith/flagsmith-nodejs-client/commit/f7488e17fe524111dd18c06a30be1c44ae15ec5d)) -* fix-mv-evaluation ([#222](https://github.com/Flagsmith/flagsmith-nodejs-client/issues/222)) ([ae1fb7e](https://github.com/Flagsmith/flagsmith-nodejs-client/commit/ae1fb7eb0551defd0823c94d37b860be7eb88a5d)) -* properly-map-environment-name ([#226](https://github.com/Flagsmith/flagsmith-nodejs-client/issues/226)) ([3c1d200](https://github.com/Flagsmith/flagsmith-nodejs-client/commit/3c1d200e656ec9926fdc6d4627bb259963d06a2e)) -* removed-dango-id-usage-in-mapper ([#229](https://github.com/Flagsmith/flagsmith-nodejs-client/issues/229)) ([29c7613](https://github.com/Flagsmith/flagsmith-nodejs-client/commit/29c761370a7e8d6d733a45293c60297843e7e1e7)) -* use-default-on-jsonpath-import ([#231](https://github.com/Flagsmith/flagsmith-nodejs-client/issues/231)) ([7a8d949](https://github.com/Flagsmith/flagsmith-nodejs-client/commit/7a8d9498fbc0297c881b3f32fc8d0d024fe8366f)) - +- exclude-identities-when-traits-is-undefined ([#230](https://github.com/Flagsmith/flagsmith-nodejs-client/issues/230)) ([f7488e1](https://github.com/Flagsmith/flagsmith-nodejs-client/commit/f7488e17fe524111dd18c06a30be1c44ae15ec5d)) +- fix-mv-evaluation ([#222](https://github.com/Flagsmith/flagsmith-nodejs-client/issues/222)) ([ae1fb7e](https://github.com/Flagsmith/flagsmith-nodejs-client/commit/ae1fb7eb0551defd0823c94d37b860be7eb88a5d)) +- properly-map-environment-name ([#226](https://github.com/Flagsmith/flagsmith-nodejs-client/issues/226)) ([3c1d200](https://github.com/Flagsmith/flagsmith-nodejs-client/commit/3c1d200e656ec9926fdc6d4627bb259963d06a2e)) +- removed-dango-id-usage-in-mapper ([#229](https://github.com/Flagsmith/flagsmith-nodejs-client/issues/229)) ([29c7613](https://github.com/Flagsmith/flagsmith-nodejs-client/commit/29c761370a7e8d6d733a45293c60297843e7e1e7)) +- use-default-on-jsonpath-import ([#231](https://github.com/Flagsmith/flagsmith-nodejs-client/issues/231)) ([7a8d949](https://github.com/Flagsmith/flagsmith-nodejs-client/commit/7a8d9498fbc0297c881b3f32fc8d0d024fe8366f)) ### CI -* use NPM trusted publishing ([#217](https://github.com/Flagsmith/flagsmith-nodejs-client/issues/217)) ([7d01563](https://github.com/Flagsmith/flagsmith-nodejs-client/commit/7d015635f4bc41246519799dddaea7ff8da2c50a)) +- use NPM trusted publishing ([#217](https://github.com/Flagsmith/flagsmith-nodejs-client/issues/217)) ([7d01563](https://github.com/Flagsmith/flagsmith-nodejs-client/commit/7d015635f4bc41246519799dddaea7ff8da2c50a)) ## [6.2.0](https://github.com/Flagsmith/flagsmith-nodejs-client/compare/v6.1.0...v6.2.0) (2025-11-04) - ### Features -* add user agent to requests ([#206](https://github.com/Flagsmith/flagsmith-nodejs-client/issues/206)) ([ef2b97a](https://github.com/Flagsmith/flagsmith-nodejs-client/commit/ef2b97a3022a5feeb96c3ccdb8009ae89b582d0b)) +- add user agent to requests ([#206](https://github.com/Flagsmith/flagsmith-nodejs-client/issues/206)) ([ef2b97a](https://github.com/Flagsmith/flagsmith-nodejs-client/commit/ef2b97a3022a5feeb96c3ccdb8009ae89b582d0b)) ### Bug Fixes -* handle environment documentation pagination ([#205](https://github.com/Flagsmith/flagsmith-nodejs-client/issues/205)) ([a83d3a5](https://github.com/Flagsmith/flagsmith-nodejs-client/commit/a83d3a5789abbc47abc2a95d07a19756ab7befbb)) - +- handle environment documentation pagination ([#205](https://github.com/Flagsmith/flagsmith-nodejs-client/issues/205)) ([a83d3a5](https://github.com/Flagsmith/flagsmith-nodejs-client/commit/a83d3a5789abbc47abc2a95d07a19756ab7befbb)) ### CI -* add release please configuration ([#190](https://github.com/Flagsmith/flagsmith-nodejs-client/issues/190)) ([946f911](https://github.com/Flagsmith/flagsmith-nodejs-client/commit/946f911e3c9d7df21bd7e5c6df5f9f92927e5e59)) - +- add release please configuration ([#190](https://github.com/Flagsmith/flagsmith-nodejs-client/issues/190)) ([946f911](https://github.com/Flagsmith/flagsmith-nodejs-client/commit/946f911e3c9d7df21bd7e5c6df5f9f92927e5e59)) ### Docs -* removing hero image from SDK readme ([#194](https://github.com/Flagsmith/flagsmith-nodejs-client/issues/194)) ([bc71d40](https://github.com/Flagsmith/flagsmith-nodejs-client/commit/bc71d40bdfa319b5333c18f4f9eacbe90b6fad0d)) - +- removing hero image from SDK readme ([#194](https://github.com/Flagsmith/flagsmith-nodejs-client/issues/194)) ([bc71d40](https://github.com/Flagsmith/flagsmith-nodejs-client/commit/bc71d40bdfa319b5333c18f4f9eacbe90b6fad0d)) ### Other -* add root CODEOWNERS ([#200](https://github.com/Flagsmith/flagsmith-nodejs-client/issues/200)) ([e81cc00](https://github.com/Flagsmith/flagsmith-nodejs-client/commit/e81cc00f1de35e0884b2cfc70c6cf54a75a3426c)) -* versioned test data ([#197](https://github.com/Flagsmith/flagsmith-nodejs-client/issues/197)) ([9fb5c12](https://github.com/Flagsmith/flagsmith-nodejs-client/commit/9fb5c127a2b56503ba876da2466c24e5ceff1d3f)) +- add root CODEOWNERS ([#200](https://github.com/Flagsmith/flagsmith-nodejs-client/issues/200)) ([e81cc00](https://github.com/Flagsmith/flagsmith-nodejs-client/commit/e81cc00f1de35e0884b2cfc70c6cf54a75a3426c)) +- versioned test data ([#197](https://github.com/Flagsmith/flagsmith-nodejs-client/issues/197)) ([9fb5c12](https://github.com/Flagsmith/flagsmith-nodejs-client/commit/9fb5c127a2b56503ba876da2466c24e5ceff1d3f)) diff --git a/flagsmith-engine/segments/evaluators.ts b/flagsmith-engine/segments/evaluators.ts index 3d2c1ad..d075bbe 100644 --- a/flagsmith-engine/segments/evaluators.ts +++ b/flagsmith-engine/segments/evaluators.ts @@ -1,4 +1,4 @@ -import * as jsonpathModule from 'jsonpath'; +import { JSONPath } from 'jsonpath-plus'; import { GenericEvaluationContext, InSegmentCondition, @@ -10,9 +10,6 @@ import { getHashedPercentageForObjIds } from '../utils/hashing/index.js'; import { SegmentConditionModel } from './models.js'; import { IS_NOT_SET, IS_SET, PERCENTAGE_SPLIT } from './constants.js'; -// Handle ESM/CJS interop - jsonpath exports default in ESM -const jsonpath = (jsonpathModule as any).default || jsonpathModule; - /** * Returns all segments that the identity belongs to based on segment rules evaluation. * @@ -140,8 +137,22 @@ function evaluateRuleConditions(ruleType: string, conditionResults: boolean[]): } } +const TRAITS_DOT_PATTERN = /^\$\.identity\.traits\.([^.]+)$/; +const TRAITS_BRACKET_PATTERN = /^\$\.identity\.traits\['(.+)'\]$/; + +function extractTraitNameFromPath(property: string): string | undefined { + return TRAITS_DOT_PATTERN.exec(property)?.[1] ?? TRAITS_BRACKET_PATTERN.exec(property)?.[1]; +} + function getTraitValue(property: string, context?: GenericEvaluationContext): any { if (property.startsWith('$.')) { + // Look up $.identity.traits.X and $.identity.traits['X'] paths directly + // to avoid jsonpath-plus mis-parsing special characters (e.g. $, [, ]) in + // trait names that appear inside bracket-notation strings. + const traitName = extractTraitNameFromPath(property); + if (traitName !== undefined) { + return context?.identity?.traits?.[traitName]; + } const contextValue = getContextValue(property, context); if (contextValue !== undefined && isPrimitive(contextValue)) { return contextValue; @@ -180,7 +191,7 @@ export function getContextValue(jsonPath: string, context?: GenericEvaluationCon try { const normalizedPath = normalizeJsonPath(jsonPath); - const results = jsonpath.query(context, normalizedPath); + const results = JSONPath({ path: normalizedPath, json: context }); return results.length > 0 ? results[0] : undefined; } catch (error) { return undefined; diff --git a/package-lock.json b/package-lock.json index 686ad1e..a7fa06f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "7.0.3", "license": "MIT", "dependencies": { - "jsonpath": "^1.1.1", + "jsonpath-plus": "^10.4.0", "pino": "^10", "semver": "^7.3.7", "undici-types": "^6.19.8" @@ -800,6 +800,30 @@ "dev": true, "license": "MIT" }, + "node_modules/@jsep-plugin/assignment": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@jsep-plugin/assignment/-/assignment-1.3.0.tgz", + "integrity": "sha512-VVgV+CXrhbMI3aSusQyclHkenWSAm95WaiKrMxRFam3JSUiIaQjoMIw2sEs/OX4XifnqeQUN4DYbJjlA8EfktQ==", + "license": "MIT", + "engines": { + "node": ">= 10.16.0" + }, + "peerDependencies": { + "jsep": "^0.4.0||^1.0.0" + } + }, + "node_modules/@jsep-plugin/regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@jsep-plugin/regex/-/regex-1.0.4.tgz", + "integrity": "sha512-q7qL4Mgjs1vByCaTnDFcBnV9HS7GVPJX5vyVoCgZHNSC9rjwIlmbXG5sUuorR5ndfHAIlJ8pVStxvjXHbNvtUg==", + "license": "MIT", + "engines": { + "node": ">= 10.16.0" + }, + "peerDependencies": { + "jsep": "^0.4.0||^1.0.0" + } + }, "node_modules/@pinojs/redact": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", @@ -1555,12 +1579,6 @@ "node": ">=6" } }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "license": "MIT" - }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -2005,62 +2023,6 @@ "node": ">=8" } }, - "node_modules/escodegen": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", - "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", - "license": "BSD-2-Clause", - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=4.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/escodegen/node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esprima": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.2.2.tgz", - "integrity": "sha512-+JpPZam9w5DuJ3Q67SqsMGtiHKENSMRVoxvArfJZK01/BfLEObtZ6orJa/MtoGNR/rfMgp5837T41PAmTwAv/A==", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, "node_modules/estree-walker": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", @@ -2071,15 +2033,6 @@ "@types/estree": "^1.0.0" } }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/expect": { "version": "30.1.2", "resolved": "https://registry.npmjs.org/expect/-/expect-30.1.2.tgz", @@ -2108,12 +2061,6 @@ "node": ">=12.0.0" } }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "license": "MIT" - }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -2485,6 +2432,16 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsep": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jsep/-/jsep-1.4.0.tgz", + "integrity": "sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 10.16.0" + } + }, "node_modules/json-schema-to-typescript": { "version": "15.0.4", "resolved": "https://registry.npmjs.org/json-schema-to-typescript/-/json-schema-to-typescript-15.0.4.tgz", @@ -2532,28 +2489,22 @@ "dev": true, "license": "MIT" }, - "node_modules/jsonpath": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/jsonpath/-/jsonpath-1.1.1.tgz", - "integrity": "sha512-l6Cg7jRpixfbgoWgkrl77dgEj8RPvND0wMH6TwQmi9Qs4TFfS9u5cUFnbeKTwj5ga5Y3BTGGNI28k117LJ009w==", - "license": "MIT", - "dependencies": { - "esprima": "1.2.2", - "static-eval": "2.0.2", - "underscore": "1.12.1" - } - }, - "node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "node_modules/jsonpath-plus": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-10.4.0.tgz", + "integrity": "sha512-T92WWatJXmhBbKsgH/0hl+jxjdXrifi5IKeMY02DWggRxX0UElcbVzPlmgLTbvsPeW1PasQ6xE2Q75stkhGbsA==", "license": "MIT", "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" + "@jsep-plugin/assignment": "^1.3.0", + "@jsep-plugin/regex": "^1.0.4", + "jsep": "^1.4.0" + }, + "bin": { + "jsonpath": "bin/jsonpath-cli.js", + "jsonpath-plus": "bin/jsonpath-cli.js" }, "engines": { - "node": ">= 0.8.0" + "node": ">=18.0.0" } }, "node_modules/lodash": { @@ -4840,23 +4791,6 @@ "node": ">=14.0.0" } }, - "node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "license": "MIT", - "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", @@ -4997,14 +4931,6 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/prettier": { "version": "2.8.8", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", @@ -5190,16 +5116,6 @@ "atomic-sleep": "^1.0.0" } }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "license": "BSD-3-Clause", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -5238,15 +5154,6 @@ "dev": true, "license": "MIT" }, - "node_modules/static-eval": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.0.2.tgz", - "integrity": "sha512-N/D219Hcr2bPjLxPiV+TQE++Tsmrady7TqAJugLy7Xk1EumfDWS/f5dtBbkRCGE7wKKXuYockQoj8Rm2/pVKyg==", - "license": "MIT", - "dependencies": { - "escodegen": "^1.8.1" - } - }, "node_modules/std-env": { "version": "3.8.0", "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.8.0.tgz", @@ -5426,18 +5333,6 @@ "node": ">=8.0" } }, - "node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", - "license": "MIT", - "dependencies": { - "prelude-ls": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/typescript": { "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", @@ -5451,12 +5346,6 @@ "node": ">=4.2.0" } }, - "node_modules/underscore": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz", - "integrity": "sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==", - "license": "MIT" - }, "node_modules/undici": { "version": "6.21.2", "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.2.tgz", @@ -5711,15 +5600,6 @@ "node": ">=8" } }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/wrap-ansi-cjs": { "name": "wrap-ansi", "version": "7.0.0", diff --git a/package.json b/package.json index 9305a9f..3c7650f 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "generate-engine-types": "npm run generate-evaluation-result-types && npm run generate-evaluation-context-types" }, "dependencies": { - "jsonpath": "^1.1.1", + "jsonpath-plus": "^10.4.0", "pino": "^10", "semver": "^7.3.7", "undici-types": "^6.19.8" diff --git a/tests/engine/unit/segments/segment_evaluators.test.ts b/tests/engine/unit/segments/segment_evaluators.test.ts index 74c63e9..87b8155 100644 --- a/tests/engine/unit/segments/segment_evaluators.test.ts +++ b/tests/engine/unit/segments/segment_evaluators.test.ts @@ -345,6 +345,71 @@ describe('getIdentitySegments single segment evaluation', () => { }); }); +describe('traitsMatchSegmentCondition with $.identity.traits.* properties', () => { + const mockContext: EvaluationContext = { + environment: { key: 'env', name: 'test' }, + identity: { + key: 'user', + identifier: 'user@example.com', + traits: { + age: 25, + tamaño: 'grande', + サイズ: 'medium', + '[$the.size$]': 'small' + } + }, + segments: {}, + features: {} + }; + + test.each([ + // dot notation – normal trait name + [{ property: '$.identity.traits.age', operator: 'EQUAL', value: '25' }, true], + [{ property: '$.identity.traits.age', operator: 'EQUAL', value: '30' }, false], + // dot notation – unicode trait name + [{ property: '$.identity.traits.tamaño', operator: 'EQUAL', value: 'grande' }, true], + [{ property: '$.identity.traits.サイズ', operator: 'EQUAL', value: 'medium' }, true], + // bracket notation – special characters in trait name that break jsonpath-plus + [ + { property: "$.identity.traits['[$the.size$]']", operator: 'EQUAL', value: 'small' }, + true + ], + [ + { property: "$.identity.traits['[$the.size$]']", operator: 'EQUAL', value: 'large' }, + false + ], + // non-existent trait + [{ property: '$.identity.traits.nonexistent', operator: 'EQUAL', value: 'any' }, false], + // IS_SET / IS_NOT_SET + [{ property: '$.identity.traits.age', operator: 'IS_SET', value: null }, true], + [{ property: '$.identity.traits.nonexistent', operator: 'IS_SET', value: null }, false], + [{ property: '$.identity.traits.nonexistent', operator: 'IS_NOT_SET', value: null }, true], + [{ property: '$.identity.traits.age', operator: 'IS_NOT_SET', value: null }, false], + // IN operator + [ + { + property: '$.identity.traits.tamaño', + operator: CONDITION_OPERATORS.IN, + value: ['grande', 'pequeño'] + }, + true + ], + [ + { + property: '$.identity.traits.tamaño', + operator: CONDITION_OPERATORS.IN, + value: ['pequeño'] + }, + false + ] + ] as Array<[SegmentCondition | InSegmentCondition, boolean]>)( + 'evaluates %j to %s', + (condition, expected) => { + expect(traitsMatchSegmentCondition(condition, 'seg', mockContext)).toBe(expected); + } + ); +}); + describe('getContextValue', () => { const mockContext: EvaluationContext = { environment: { @@ -354,6 +419,7 @@ describe('getContextValue', () => { identity: { key: 'user-123', identifier: 'user@example.com' + // intentionally no traits – tests below confirm paths that require traits return undefined }, segments: {}, features: {} @@ -371,7 +437,7 @@ describe('getContextValue', () => { // Undefined or invalid cases test.each([ - ['$.identity.traits.user_type', 'unsupported nested path'], + ['$.identity.traits.user_type', 'no traits in context'], ['identity.identifier', 'missing $ prefix'], ['$.invalid.path', 'completely invalid path'], ['$.identity.nonexistent', 'valid structure but missing property'],