🚨 Active Supply Chain Attack:node-ipc Package Compromised.Learn More
Socket
Book a DemoSign in
Socket

jsonpath

Package Overview
Dependencies
Maintainers
1
Versions
28
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

jsonpath - npm Package Compare versions

Comparing version
1.2.1
to
1.3.0
+151
-2
lib/handlers.js

@@ -6,2 +6,148 @@ var aesprim = require('./aesprim');

// Property names that must never be accessible in expressions.
// Mitigates prototype pollution and constructor escape attacks.
var UNSAFE_PROPERTY_NAMES = Object.create(null);
/* jshint -W069: true */
UNSAFE_PROPERTY_NAMES['constructor'] = true;
UNSAFE_PROPERTY_NAMES['__proto__'] = true;
UNSAFE_PROPERTY_NAMES['prototype'] = true;
/* jshint -W069: false */
function isUnsafePropertyName(name) {
return typeof name === 'string' && UNSAFE_PROPERTY_NAMES[name] === true;
}
function isSafeAst(ast) {
if (!ast || typeof ast !== 'object') return false;
function walk(node) {
if (!node || typeof node !== 'object' || !node.type) {
return false;
}
switch (node.type) {
// ===== SAFE TERMINALS =====
case 'Literal':
return true;
case 'Identifier':
// Only allow the special scope identifier
return node.name === '@';
// ===== PROPERTY ACCESS =====
case 'MemberExpression': {
if (!walk(node.object)) {
return false;
}
// Non-computed: obj.property
if (!node.computed && node.property.type === 'Identifier') {
if (isUnsafePropertyName(node.property.name)) {
return false;
}
return true;
}
// Computed: obj["property"]
if (node.computed) {
if (!walk(node.property)) {
return false;
}
if (
node.property.type === 'Literal' &&
isUnsafePropertyName(String(node.property.value))
) {
return false;
}
return true;
}
return false;
}
// ===== EXPRESSIONS =====
case 'UnaryExpression':
return walk(node.argument);
case 'BinaryExpression':
case 'LogicalExpression':
return walk(node.left) && walk(node.right);
case 'ConditionalExpression':
return (
walk(node.test) &&
walk(node.consequent) &&
walk(node.alternate)
);
case 'ArrayExpression':
for (var i = 0; i < node.elements.length; i++) {
if (!walk(node.elements[i])) {
return false;
}
}
return true;
case 'ObjectExpression':
for (var j = 0; j < node.properties.length; j++) {
var prop = node.properties[j];
// Reject unsafe keys
if (
prop.key &&
(
(prop.key.type === 'Identifier' &&
isUnsafePropertyName(prop.key.name)) ||
(prop.key.type === 'Literal' &&
isUnsafePropertyName(String(prop.key.value)))
)
) {
return false;
}
if (!walk(prop.value)) {
return false;
}
}
return true;
// ===== EXPLICITLY REJECT DANGEROUS TYPES =====
// Security: do not rely on default deny; list each code-execution / escape vector.
case 'CallExpression':
case 'NewExpression':
case 'FunctionExpression':
case 'ArrowFunctionExpression':
case 'ThisExpression':
case 'AssignmentExpression':
case 'UpdateExpression':
case 'SequenceExpression':
case 'TemplateLiteral':
case 'TemplateElement':
case 'TaggedTemplateExpression':
case 'ReturnStatement':
case 'ExpressionStatement':
return false;
// ===== DEFAULT DENY =====
default:
return false;
}
}
return walk(ast);
}
var Handlers = function() {

@@ -243,4 +389,7 @@ return this.initialize.apply(this, arguments);

function evaluate() {
try { return _evaluate.apply(this, arguments) }
function evaluate(ast, scope) {
if (!isSafeAst(ast)) {
throw new Error('Unsafe expression: script and filter expressions may only access the current node (@) with safe property names');
}
try { return _evaluate(ast, scope) }
catch (e) { }

@@ -247,0 +396,0 @@ }

+1
-1
{
"name": "jsonpath",
"description": "Query JavaScript objects with JSONPath expressions. Robust / safe JSONPath engine for Node.js.",
"version": "1.2.1",
"version": "1.3.0",
"author": "david@fmail.co.uk",

@@ -6,0 +6,0 @@ "scripts": {

@@ -51,2 +51,189 @@ var assert = require('assert');

});
suite('CVE-2026-1615: blocks code injection in filter/script expressions', function() {
var data = { a: {}, b: [1, 2, 3] };
test('rejects constructor access in filter expression', function() {
assert.throws(function() {
jp.query(data, '$[?(@.constructor)]');
}, /Unsafe expression/);
});
test('rejects constructor.constructor in filter expression', function() {
assert.throws(function() {
jp.query(data, '$[?(@.constructor.constructor)]');
}, /Unsafe expression/);
});
test('rejects chained constructor.constructor call: @.foo["constructor"]["constructor"](...)()', function() {
assert.throws(function() {
jp.query(data, '$[?(@.foo["constructor"]["constructor"]("return process")())]');
}, /Unsafe expression/);
});
test('rejects __proto__ access in filter expression', function() {
assert.throws(function() {
jp.query(data, '$[?(@.__proto__)]');
}, /Unsafe expression/);
});
test('rejects function call in filter expression', function() {
assert.throws(function() {
jp.query(data, '$[?(process.exit(1))]');
}, /Unsafe expression/);
});
test('rejects constructor access in script expression', function() {
var scriptData = { a: [1, 2, 3] };
assert.throws(function() {
jp.query(scriptData, '$[(@.constructor)]');
}, /Unsafe expression/);
});
test('allows safe filter expressions', function() {
var storeData = { store: { book: [ { price: 5 }, { price: 15 } ] } };
var results = jp.query(storeData, '$..book[?(@.price<10)]');
assert.deepEqual(results, [ { price: 5 } ]);
});
test('allows safe script expressions', function() {
var bookData = { book: [ { id: 1 }, { id: 2 }, { id: 3 } ] };
var results = jp.nodes(bookData, '$..book[(@.length-1)]');
assert.deepEqual(results[0].value, { id: 3 });
});
test('rejects bracket notation constructor: @["constructor"]', function() {
assert.throws(function() { jp.query(data, '$[?(@["constructor"])]'); }, /Unsafe expression/);
});
test('rejects bracket notation __proto__: @["__proto__"]', function() {
assert.throws(function() { jp.query(data, '$[?(@["__proto__"])]'); }, /Unsafe expression/);
});
test('rejects bracket notation prototype: @["prototype"]', function() {
assert.throws(function() { jp.query(data, '$[?(@["prototype"])]'); }, /Unsafe expression/);
});
test('rejects ObjectExpression with unsafe key: { "__proto__": @ }', function() {
assert.throws(function() { jp.query(data, '$[?({ "__proto__": @ })]'); }, /Unsafe expression|Unexpected token/);
});
test('rejects ObjectExpression with unsafe key: { "constructor": @ }', function() {
assert.throws(function() { jp.query(data, '$[?({ "constructor": @ })]'); }, /Unsafe expression|Unexpected token/);
});
test('rejects ObjectExpression with unsafe key: { "prototype": @ }', function() {
assert.throws(function() { jp.query(data, '$[?({ "prototype": @ })]'); }, /Unsafe expression|Unexpected token/);
});
test('rejects unicode escape constructor in bracket: @["\\u0063onstructor"]', function() {
assert.throws(function() { jp.query(data, '$[?(@["\\u0063onstructor"])]'); }, /Unsafe expression/);
});
test('rejects unicode escape __proto__ in bracket', function() {
var path = '$[?(@["\\u005f\\u005fproto\\u005f\\u005f"])]';
assert.throws(function() { jp.query(data, path); }, /Unsafe expression/);
});
test('rejects IIFE: (function(){return 1})()', function() {
assert.throws(function() { jp.query(data, '$[?((function(){return 1})())]'); }, /Unsafe expression/);
});
test('rejects direct function call: process.exit(1)', function() {
assert.throws(function() { jp.query(data, '$[?(process.exit(1))]'); }, /Unsafe expression/);
});
test('rejects require() call', function() {
assert.throws(function() { jp.query(data, '$[?(require("fs"))]'); }, /Unsafe expression/);
});
test('rejects eval() call', function() {
assert.throws(function() { jp.query(data, '$[?(eval("1"))]'); }, /Unsafe expression/);
});
test('rejects globalThis / global identifier', function() {
assert.throws(function() { jp.query(data, '$[?(globalThis)]'); }, /Unsafe expression/);
assert.throws(function() { jp.query(data, '$[?(global)]'); }, /Unsafe expression/);
});
test('rejects NewExpression: new Function("return 1")()', function() {
assert.throws(function() { jp.query(data, '$[?(new Function("return 1")())]'); }, /Unsafe expression/);
});
test('rejects JSFuck-style: [] ["filter"]["constructor"]', function() {
assert.throws(function() { jp.query(data, '$[?([]["filter"]["constructor"])]'); }, /Unsafe expression/);
});
test('rejects JSFuck-style constructor call (no @)', function() {
assert.throws(function() { jp.query(data, '$[?([]["filter"]["constructor"]("return 1")())]'); }, /Unsafe expression/);
});
test('rejects sequence expression: (1, process.exit)(1)', function() {
assert.throws(function() { jp.query(data, '$[?((1, process.exit)(1))]'); }, /Unsafe expression/);
});
test('rejects method call on @: @.valueOf()', function() {
assert.throws(function() { jp.query(data, '$[?(@.valueOf())]'); }, /Unsafe expression/);
});
test('rejects method call: @.toString()', function() {
assert.throws(function() { jp.query(data, '$[?(@.toString())]'); }, /Unsafe expression/);
});
test('rejects template literal in computed: @[`constructor`]', function() {
assert.throws(function() { jp.query(data, '$[?(@[`constructor`])]'); }, /Unsafe expression|Unexpected token|ILLEGAL/);
});
test('rejects tagged template (code execution vector)', function() {
assert.throws(function() { jp.query(data, '$[?(String.raw`x`)]'); }, /Unsafe expression|Unexpected token|ILLEGAL/);
});
test('rejects ArrowFunctionExpression', function() {
assert.throws(function() { jp.query(data, '$[?((()=>1)())]'); }, /Unsafe expression|Unexpected token/);
});
test('rejects ThisExpression (this)', function() {
assert.throws(function() { jp.query(data, '$[?(this)]'); }, /Unsafe expression/);
});
test('rejects script expression with constructor', function() {
assert.throws(function() { jp.query(data, '$[(@.constructor)]'); }, /Unsafe expression/);
});
test('rejects script expression with call', function() {
assert.throws(function() { jp.query(data, '$[((function(){return 0})())]'); }, /Unsafe expression/);
});
test('allows @.length (no call)', function() {
var r = jp.query(data, '$[?(@.length)]');
assert.ok(Array.isArray(r));
});
test('allows bracket with safe key: @["length"]', function() {
var r = jp.query(data, '$[?(@["length"])]');
assert.ok(Array.isArray(r));
});
test('allows @["@class"] (existing test pattern)', function() {
var d = { DIV: [{ '@class': 'value', val: 5 }] };
var r = jp.query(d, '$..DIV[?(@["@class"]=="value")]');
assert.deepEqual(r, d.DIV);
});
test('rejects prototype access in filter', function() {
assert.throws(function() { jp.query(data, '$[?(@.prototype)]'); }, /Unsafe expression/);
});
test('rejects comma/sequence that could hide call', function() {
assert.throws(function() { jp.query(data, '$[?((0, eval)("1"))]'); }, /Unsafe expression/);
});
test('rejects AssignmentExpression', function() {
assert.throws(function() { jp.query(data, '$[?((x=1)==1)]'); }, /Unsafe expression/);
});
test('rejects UpdateExpression (++, --)', function() {
assert.throws(function() { jp.query(data, '$[?(@.x++)]'); }, /Unsafe expression/);
});
});
});

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display