Skip to content

Commit 56a285c

Browse files
fix(CVE-2026-1615): Replace jsonpath with jsonpath-plus to fix security vulnerability (#247)
1 parent 980728a commit 56a285c

File tree

4 files changed

+135
-179
lines changed

4 files changed

+135
-179
lines changed

flagsmith-engine/segments/evaluators.ts

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import * as jsonpathModule from 'jsonpath';
1+
import { JSONPath } from 'jsonpath-plus';
22
import {
33
GenericEvaluationContext,
44
InSegmentCondition,
@@ -10,9 +10,6 @@ import { getHashedPercentageForObjIds } from '../utils/hashing/index.js';
1010
import { SegmentConditionModel } from './models.js';
1111
import { IS_NOT_SET, IS_SET, PERCENTAGE_SPLIT } from './constants.js';
1212

13-
// Handle ESM/CJS interop - jsonpath exports default in ESM
14-
const jsonpath = (jsonpathModule as any).default || jsonpathModule;
15-
1613
/**
1714
* Returns all segments that the identity belongs to based on segment rules evaluation.
1815
*
@@ -140,8 +137,22 @@ function evaluateRuleConditions(ruleType: string, conditionResults: boolean[]):
140137
}
141138
}
142139

140+
const TRAITS_DOT_PATTERN = /^\$\.identity\.traits\.(.+)$/;
141+
const TRAITS_BRACKET_PATTERN = /^\$\.identity\.traits\['(.+)'\]$/;
142+
143+
function extractTraitNameFromPath(property: string): string | undefined {
144+
return TRAITS_DOT_PATTERN.exec(property)?.[1] ?? TRAITS_BRACKET_PATTERN.exec(property)?.[1];
145+
}
146+
143147
function getTraitValue(property: string, context?: GenericEvaluationContext): any {
144148
if (property.startsWith('$.')) {
149+
// Look up $.identity.traits.X and $.identity.traits['X'] paths directly
150+
// to avoid jsonpath-plus mis-parsing special characters (e.g. $, [, ]) in
151+
// trait names that appear inside bracket-notation strings.
152+
const traitName = extractTraitNameFromPath(property);
153+
if (traitName !== undefined) {
154+
return context?.identity?.traits?.[traitName];
155+
}
145156
const contextValue = getContextValue(property, context);
146157
if (contextValue !== undefined && isPrimitive(contextValue)) {
147158
return contextValue;
@@ -179,14 +190,9 @@ export function getContextValue(jsonPath: string, context?: GenericEvaluationCon
179190
if (!context || !jsonPath?.startsWith('$.')) return undefined;
180191

181192
try {
182-
const normalizedPath = normalizeJsonPath(jsonPath);
183-
const results = jsonpath.query(context, normalizedPath);
193+
const results = JSONPath({ path: jsonPath, json: context });
184194
return results.length > 0 ? results[0] : undefined;
185195
} catch (error) {
186196
return undefined;
187197
}
188198
}
189-
190-
function normalizeJsonPath(jsonPath: string): string {
191-
return jsonPath.replace(/\.([^.\[\]]+)$/, "['$1']");
192-
}

package-lock.json

Lines changed: 47 additions & 167 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
"generate-engine-types": "npm run generate-evaluation-result-types && npm run generate-evaluation-context-types"
6565
},
6666
"dependencies": {
67-
"jsonpath": "^1.1.1",
67+
"jsonpath-plus": "^10.4.0",
6868
"pino": "^10",
6969
"semver": "^7.3.7",
7070
"undici-types": "^6.19.8"

0 commit comments

Comments
 (0)