mirror of
https://github.com/n8n-io/n8n.git
synced 2026-06-01 01:07:04 +02:00
79 lines
2.6 KiB
TypeScript
79 lines
2.6 KiB
TypeScript
import * as jmespath from 'jmespath';
|
|
|
|
import { ExpressionError } from './safe-globals';
|
|
|
|
const unsafeJmespathProperties = [
|
|
'__proto__',
|
|
'prototype',
|
|
'constructor',
|
|
'getPrototypeOf',
|
|
'setPrototypeOf',
|
|
'getOwnPropertyDescriptor',
|
|
'getOwnPropertyDescriptors',
|
|
'defineProperty',
|
|
'defineProperties',
|
|
'mainModule',
|
|
'binding',
|
|
'_linkedBinding',
|
|
'_load',
|
|
'prepareStackTrace',
|
|
'__lookupGetter__',
|
|
'__lookupSetter__',
|
|
'__defineGetter__',
|
|
'__defineSetter__',
|
|
'caller',
|
|
'arguments',
|
|
'getBuiltinModule',
|
|
'dlopen',
|
|
'execve',
|
|
'loadEnvFile',
|
|
];
|
|
|
|
const unsafeJmespathPropertyPattern = new RegExp(
|
|
`\\b(?:${unsafeJmespathProperties.map((p) => p.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|')})\\b`,
|
|
);
|
|
|
|
/**
|
|
* In-isolate `$jmespath` / `$jmesPath` implementation.
|
|
*
|
|
* Mirrors the host-side wrapper in `packages/workflow/src/workflow-data-proxy.ts`
|
|
* so that expressions resolve `$jmespath` from the in-isolate runtime via
|
|
* Tournament's polyfill. `$jmespath` is a pure utility — it takes its
|
|
* data as an argument and runs a query on it, with no need for host
|
|
* context. Keeping it in-isolate means it does not appear as a
|
|
* host-callable on the bridge, which shrinks the host-callable surface
|
|
* the isolate can reach.
|
|
*
|
|
* Behavioural parity with the host wrapper:
|
|
* - Throws `ExpressionError` (same name) when args are wrong.
|
|
* - Rejects queries that contain unsafe property tokens.
|
|
* - Spreads non-array, non-null objects to drop proxies at the top level.
|
|
*
|
|
* Note on lazy proxies: when `data` is a lazy proxy (e.g. `$json`), each
|
|
* property access during `jmespath.search` triggers a synchronous host
|
|
* roundtrip via `getValueAtPath`. Functional but slow for deep traversals.
|
|
* Performance optimisation (e.g. bulk pre-fetch of the queried subtree) is
|
|
* a follow-up.
|
|
*/
|
|
export function jmesPath(data: unknown, query: string): unknown {
|
|
if (typeof data !== 'object' || typeof query !== 'string') {
|
|
throw new ExpressionError('expected two arguments (Object, string) for this function');
|
|
}
|
|
|
|
// jmespath decodes escape sequences inside quoted identifiers, so the
|
|
// token check must run against an unescaped query. Reject any backslash
|
|
// up front to keep the property-name match meaningful.
|
|
if (query.includes('\\') || unsafeJmespathPropertyPattern.test(query)) {
|
|
throw new ExpressionError(
|
|
'Cannot access this property in a jmespath query due to security concerns',
|
|
);
|
|
}
|
|
|
|
if (data !== null && !Array.isArray(data) && typeof data === 'object') {
|
|
return jmespath.search({ ...(data as Record<string, unknown>) }, query);
|
|
}
|
|
|
|
// Array or null — pass through to jmespath which accepts both
|
|
return jmespath.search(data as never, query);
|
|
}
|