Skip to content

Commit

Permalink
replaces jsonpath-plus module (#1547)
Browse files Browse the repository at this point in the history
OKTA-819401 fix: replaces jsonpath-plus module
  • Loading branch information
jaredperreault-okta authored Oct 17, 2024
1 parent a3efc35 commit 710288d
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 23 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

# 7.8.1

### Bug Fix
- [#1547](https://github.com/okta/okta-auth-js/pull/1547) fix: replaces `jsonpath-plus` module
- Address https://security.snyk.io/vuln/SNYK-JS-JSONPATHPLUS-7945884

# 7.8.0

### Features
Expand Down
2 changes: 1 addition & 1 deletion lib/idx/idxState/v1/idxResponseParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ const expandRelatesTo = (idxResponse, value) => {
if (k === 'relatesTo') {
const query = Array.isArray(value[k]) ? value[k][0] : value[k];
if (typeof query === 'string') {
const result = jsonpath({ path: query, json: idxResponse })[0];
const result = jsonpath({ path: query, json: idxResponse });
if (result) {
value[k] = result;
return;
Expand Down
37 changes: 31 additions & 6 deletions lib/util/jsonpath.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,33 @@
import { JSONPath, JSONPathOptions } from 'jsonpath-plus';
const jsonpathRegex = /\$?(?<step>\w+)|(?:\[(?<index>\d+)\])/g;

export function jsonpath(options: JSONPathOptions): any {
// eslint-disable-next-line new-cap
return JSONPath({
// Disable javascript evaluation by default
preventEval: true, ...options, });
/* eslint complexity:[0,8] */
export function jsonpath({ path, json }) {
const steps: string[] = [];
let match: RegExpExecArray | null;
while ((match = jsonpathRegex.exec(path)) !== null) {
const step = match?.groups?.step ?? match?.groups?.index;
if (step) {
steps.push(step);
}
}

if (steps.length < 1) {
return undefined;
}

// array length check above guarantees .pop() will return a value
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const lastStep = steps.pop()!;
let curr = json;
for (const step of steps) {
if (Object.prototype.hasOwnProperty.call(curr, step)) {
if (typeof curr[step] !== 'object') {
return undefined;
}

curr = curr[step];
}
}

return curr[lastStep];
}
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"private": true,
"name": "@okta/okta-auth-js",
"description": "The Okta Auth SDK",
"version": "7.8.0",
"version": "7.8.1",
"homepage": "https://github.com/okta/okta-auth-js",
"license": "Apache-2.0",
"main": "build/cjs/exports/default.js",
Expand Down Expand Up @@ -159,7 +159,6 @@
"cross-fetch": "^3.1.5",
"fast-text-encoding": "^1.0.6",
"js-cookie": "^3.0.1",
"jsonpath-plus": "^6.0.1",
"node-cache": "^5.1.2",
"p-cancelable": "^2.0.0",
"tiny-emitter": "1.1.0",
Expand Down
104 changes: 96 additions & 8 deletions test/spec/util/jsonpath.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,102 @@
import { jsonpath } from '../../../lib/util/jsonpath';

describe('jsonpath', () => {
it('should throw if vulnerable for RCE (remote code execution)', () => {
expect(() => {

it('should parse json paths from objects', () => {
expect(jsonpath({
path: '$.foo.bar',
json: { foo: { bar: 'pass' } }
})).toEqual('pass');

expect(jsonpath({
path: 'foo.bar',
json: { foo: { bar: { baz: 'pass' } } }
})).toEqual({ baz: 'pass' });

const arrValues1 = Array(30).fill('fail');
arrValues1[12] = 'pass';
expect(jsonpath({
path: '$.foo.bar[12]',
json: { foo: { bar: arrValues1 } }
})).toEqual('pass');

const arrValues2 = Array(30).fill({ bar: 'fail' });
arrValues2[25].bar = 'pass';
expect(jsonpath({
path: 'foo[22].bar',
json: { foo: arrValues2 }
})).toEqual('pass');

expect(jsonpath({
path: 'not.a.path',
json: { foo: 1 }
})).toEqual(undefined);

expect(jsonpath({
path: '$.foo.bar[12]',
json: { foo: { bar: [] } }
})).toEqual(undefined);

expect(jsonpath({
path: '$.foo.bar',
json: { foo: { bar: {} } }
})).toEqual({});

expect(jsonpath({
path: '$.foo.bar.baz',
json: { foo: { bar: [] } }
})).toEqual(undefined);

expect(jsonpath({
path: '',
json: { foo: { bar: [] } }
})).toEqual(undefined);

expect(jsonpath({
path: '[]',
json: { foo: { bar: [] } }
})).toEqual(undefined);

expect(jsonpath({
path: '{}',
json: { foo: { bar: [] } }
})).toEqual(undefined);
});

it('should gracefully handle RCE (remote code execution) attempts', () => {
const evalSpy = jest.spyOn(global, 'eval');
global.alert = jest.fn();
const alertSpy = jest.spyOn(global, 'alert');

expect(
jsonpath({
path: '$..[?(' + '(function a(arr){' + 'a([...arr, ...arr])' + '})([1]);)]',
json: {
nonEmpty: 'object',
},
})
).toBeUndefined();

expect(
jsonpath({
path: `$[(this.constructor.constructor("eval(alert('foo'))")())]`,
json: {
nonEmpty: 'object',
},
})
).toBeUndefined();

expect(
jsonpath({
path: '$..[?(' + '(function a(arr){' + 'a([...arr, ...arr])' + '})([1]);)]',
json: {
nonEmpty: 'object',
},
});
}).toThrow();
path: `$[(this.constructor.constructor("require("child_process").exec("echo 'foo'")")())]`,
json: {
nonEmpty: 'object',
},
})
).toBeUndefined();

expect(evalSpy).not.toHaveBeenCalled();
expect(alertSpy).not.toHaveBeenCalled();
});

});
12 changes: 6 additions & 6 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3077,16 +3077,16 @@ ansi-red@^0.1.1:
dependencies:
ansi-wrap "0.1.0"

ansi-regex@^2.0.0, ansi-regex@^4.1.0, ansi-regex@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.1.tgz#164daac87ab2d6f6db3a29875e2d1766582dabed"
integrity sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==

ansi-regex@^3.0.1, ansi-regex@^6.0.1:
ansi-regex@^2.0.0, ansi-regex@^3.0.1, ansi-regex@^6.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.1.tgz#123d6479e92ad45ad897d4054e3c7ca7db4944e1"
integrity sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==

ansi-regex@^4.1.0, ansi-regex@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.1.tgz#164daac87ab2d6f6db3a29875e2d1766582dabed"
integrity sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==

ansi-regex@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
Expand Down

0 comments on commit 710288d

Please sign in to comment.