@devilsdev/rag-pipeline-utils 2.1.12 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.ragrc.schema.json +218 -0
- package/README.md +178 -550
- package/bin/cli.js +8 -14
- package/contracts/reranker-contract.json +69 -0
- package/dist/index.d.ts +237 -0
- package/dist/index.mjs +22 -7
- package/package.json +18 -13
- package/src/ai/adaptive-retrieval.js +191 -0
- package/src/ai/federated-learning.js +214 -0
- package/src/ai/federation/federated-learning-coordinator.js +345 -0
- package/src/ai/federation.js +76 -0
- package/src/ai/index-fixed.js +509 -0
- package/src/ai/index.js +33 -0
- package/src/ai/model-trainer.js +217 -0
- package/src/ai/model-training.js +205 -0
- package/src/ai/module-template.js +37 -0
- package/src/ai/multimodal/multi-modal-processor.js +345 -0
- package/src/ai/multimodal-processing.js +268 -0
- package/src/ai/multimodal.js +71 -0
- package/src/ai/orchestrator.js +70 -0
- package/src/ai/retrieval/adaptive-retrieval-engine.js +398 -0
- package/src/ai/retrieval-engine.js +69 -0
- package/src/ai/training/model-training-orchestrator.js +228 -0
- package/src/ci/workflow-analyzer.js +71 -0
- package/src/cli/commands/ai-ml.js +967 -0
- package/src/cli/commands/docs.js +228 -0
- package/src/cli/commands/dx.js +538 -0
- package/src/cli/commands/plugin-hub.js +875 -0
- package/src/cli/doctor-command.js +639 -0
- package/src/cli/enhanced-cli-commands.js +945 -0
- package/src/cli/interactive-wizard.js +997 -0
- package/src/cli/plugin-marketplace-commands.js +869 -0
- package/src/cli.js +17 -0
- package/src/config/enhanced-ragrc-schema.js +632 -0
- package/src/config/load-config.js +54 -0
- package/src/config/load-plugin-config.js +69 -0
- package/src/config/normalize-config.js +105 -0
- package/src/config/schema-validator.js +315 -0
- package/src/config/validate-plugin-schema.js +35 -0
- package/src/config/validate-schema.js +113 -0
- package/src/contracts/plugin-contracts.js +33 -0
- package/src/core/create-pipeline.js +99 -0
- package/src/core/dependency-injection.js +242 -0
- package/src/core/observability/event-logger.js +241 -0
- package/src/core/observability/instrumented-pipeline.js +609 -0
- package/src/core/observability/metrics.js +497 -0
- package/src/core/observability/tracing.js +781 -0
- package/src/core/performance/benchmark.js +516 -0
- package/src/core/performance/parallel-processor.js +240 -0
- package/src/core/performance/streaming-safeguards.js +620 -0
- package/src/core/pipeline-factory.js +60 -0
- package/src/core/plugin-contracts.js +89 -0
- package/src/core/plugin-marketplace/plugin-metadata.js +485 -0
- package/src/core/plugin-marketplace/plugin-publisher.js +574 -0
- package/src/core/plugin-marketplace/plugin-registry-format.js +419 -0
- package/src/core/plugin-marketplace/version-resolver.js +61 -0
- package/src/core/plugin-registry.js +294 -0
- package/src/dag/dag-engine.js +954 -0
- package/src/dag/dag-node.js +76 -0
- package/src/dag/enhanced-dag-engine.js +822 -0
- package/src/dx/index.js +40 -0
- package/src/dx/integration-templates.js +665 -0
- package/src/dx/performance-profiler.js +968 -0
- package/src/dx/realtime-debugger.js +755 -0
- package/src/dx/visual-pipeline-builder.js +578 -0
- package/src/ecosystem/plugin-analytics-dashboard.js +778 -0
- package/src/ecosystem/plugin-certification.js +580 -0
- package/src/ecosystem/plugin-hub.js +821 -0
- package/src/enterprise/audit-logging.js +967 -0
- package/src/enterprise/data-governance.js +408 -0
- package/src/enterprise/multi-tenancy.js +496 -0
- package/src/enterprise/sso-integration.js +1030 -0
- package/src/evaluate/evaluator.js +52 -0
- package/src/evaluate/scoring.js +91 -0
- package/src/index.js +67 -0
- package/src/ingest.js +59 -0
- package/src/loader/csv-loader.js +38 -0
- package/src/loader/directory-loader.js +48 -0
- package/src/loader/html-loader.js +62 -0
- package/src/loader/markdown-loader.js +67 -0
- package/src/mocks/openai-embedder.js +119 -0
- package/src/mocks/openai-llm.js +77 -0
- package/src/mocks/pdf-loader.js +21 -0
- package/src/mocks/pinecone-retriever.js +106 -0
- package/src/observability/audit-logger.js +758 -0
- package/src/observability/dashboard-config.json +675 -0
- package/src/observability/event-logger.js +310 -0
- package/src/observability/metrics.js +691 -0
- package/src/observability/security-monitor.js +830 -0
- package/src/observability/slo-monitor.js +319 -0
- package/src/observability/telemetry.js +686 -0
- package/src/pipeline/pipeline.js +18 -0
- package/src/plugins/sample-embedder.js +53 -0
- package/src/plugins/sample-llm.js +30 -0
- package/src/plugins/sample-loader.js +24 -0
- package/src/plugins/sample-retriever.js +84 -0
- package/src/query.js +117 -0
- package/src/reranker/llm-reranker.js +111 -0
- package/src/scripts/script-utilities.js +36 -0
- package/src/security/plugin-sandbox.js +586 -0
- package/src/security/plugin-signature-verifier.js +414 -0
- package/src/security/validator.js +36 -0
- package/src/utils/ci/diagnostic-reporter.js +57 -0
- package/src/utils/logger.js +211 -0
- package/src/utils/performance-monitor.js +561 -0
- package/src/utils/plugin-scaffolder.js +447 -0
- package/src/utils/rate-limiter.js +244 -0
- package/src/utils/retry-policy.js +443 -0
- package/src/utils/retry.js +107 -0
- package/src/utils/safe-expression-evaluator.js +579 -0
- package/src/utils/secure-logging.js +338 -0
- package/src/utils/structured-logger.js +388 -0
- package/src/utils/validate-plugin-contract.js +42 -0
|
@@ -0,0 +1,579 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Safe Expression Evaluator
|
|
3
|
+
* Safely evaluates simple expressions without allowing arbitrary code execution
|
|
4
|
+
* Replaces dangerous eval() and Function() usage with a restricted parser
|
|
5
|
+
*
|
|
6
|
+
* @author DevilsDev Team
|
|
7
|
+
* @since 2.1.8
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Safe expression evaluator for debugging conditions
|
|
12
|
+
* Only allows simple property access, comparisons, and basic operations
|
|
13
|
+
*/
|
|
14
|
+
class SafeExpressionEvaluator {
|
|
15
|
+
constructor(options = {}) {
|
|
16
|
+
// Dynamic production check
|
|
17
|
+
const productionEnabled = process.env.NODE_ENV !== 'production';
|
|
18
|
+
this.enabled = options.enabled !== false && productionEnabled;
|
|
19
|
+
this.maxDepth = options.maxDepth || 5;
|
|
20
|
+
this.maxExpression = options.maxExpression || 200;
|
|
21
|
+
|
|
22
|
+
// Allowed operators for expressions
|
|
23
|
+
this.allowedOperators = new Set([
|
|
24
|
+
'==',
|
|
25
|
+
'===',
|
|
26
|
+
'!=',
|
|
27
|
+
'!==',
|
|
28
|
+
'<',
|
|
29
|
+
'>',
|
|
30
|
+
'<=',
|
|
31
|
+
'>=',
|
|
32
|
+
'&&',
|
|
33
|
+
'||',
|
|
34
|
+
'!',
|
|
35
|
+
'+',
|
|
36
|
+
'-',
|
|
37
|
+
'*',
|
|
38
|
+
'/',
|
|
39
|
+
'%',
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
// Allowed property access patterns
|
|
43
|
+
this.allowedPropertyPattern = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
|
|
44
|
+
|
|
45
|
+
// Dangerous identifiers that should never be accessible
|
|
46
|
+
this.blockedIdentifiers = new Set([
|
|
47
|
+
'globalThis',
|
|
48
|
+
'global',
|
|
49
|
+
'process',
|
|
50
|
+
'require',
|
|
51
|
+
'module',
|
|
52
|
+
'console',
|
|
53
|
+
'eval',
|
|
54
|
+
'Function',
|
|
55
|
+
'window',
|
|
56
|
+
'document',
|
|
57
|
+
]);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Evaluate a safe expression
|
|
62
|
+
* @param {string} expression - Expression to evaluate
|
|
63
|
+
* @param {object} context - Safe context object
|
|
64
|
+
* @returns {any} Evaluation result
|
|
65
|
+
*/
|
|
66
|
+
evaluate(expression, context = {}) {
|
|
67
|
+
if (!this.enabled) {
|
|
68
|
+
throw new Error('Safe evaluator is disabled in production for security');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (typeof expression !== 'string') {
|
|
72
|
+
throw new Error('Expression must be a string');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (expression.length > this.maxExpression) {
|
|
76
|
+
throw new Error('Expression too long');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Parse and validate the expression
|
|
80
|
+
const tokens = this.tokenize(expression);
|
|
81
|
+
const ast = this.parse(tokens);
|
|
82
|
+
|
|
83
|
+
return this.evaluateNode(ast, context);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Tokenize expression into safe tokens
|
|
88
|
+
* @param {string} expression - Expression to tokenize
|
|
89
|
+
* @returns {Array} Array of tokens
|
|
90
|
+
*/
|
|
91
|
+
tokenize(expression) {
|
|
92
|
+
const tokens = [];
|
|
93
|
+
let i = 0;
|
|
94
|
+
|
|
95
|
+
while (i < expression.length) {
|
|
96
|
+
const char = expression[i];
|
|
97
|
+
|
|
98
|
+
// Skip whitespace
|
|
99
|
+
if (/\s/.test(char)) {
|
|
100
|
+
i++;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Numbers
|
|
105
|
+
if (/\d/.test(char)) {
|
|
106
|
+
let number = '';
|
|
107
|
+
while (i < expression.length && /[\d.]/.test(expression[i])) {
|
|
108
|
+
number += expression[i];
|
|
109
|
+
i++;
|
|
110
|
+
}
|
|
111
|
+
tokens.push({ type: 'NUMBER', value: parseFloat(number) });
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Identifiers and property access
|
|
116
|
+
if (/[a-zA-Z_$]/.test(char)) {
|
|
117
|
+
let identifier = '';
|
|
118
|
+
while (i < expression.length && /[a-zA-Z0-9_$]/.test(expression[i])) {
|
|
119
|
+
identifier += expression[i];
|
|
120
|
+
i++;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Check for boolean literals
|
|
124
|
+
if (identifier === 'true') {
|
|
125
|
+
tokens.push({ type: 'BOOLEAN', value: true });
|
|
126
|
+
} else if (identifier === 'false') {
|
|
127
|
+
tokens.push({ type: 'BOOLEAN', value: false });
|
|
128
|
+
} else if (identifier === 'null') {
|
|
129
|
+
tokens.push({ type: 'NULL', value: null });
|
|
130
|
+
} else if (identifier === 'undefined') {
|
|
131
|
+
tokens.push({ type: 'UNDEFINED', value: undefined });
|
|
132
|
+
} else {
|
|
133
|
+
tokens.push({ type: 'IDENTIFIER', value: identifier });
|
|
134
|
+
}
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// String literals
|
|
139
|
+
if (char === '"' || char === '\'') {
|
|
140
|
+
const quote = char;
|
|
141
|
+
let string = '';
|
|
142
|
+
i++; // Skip opening quote
|
|
143
|
+
|
|
144
|
+
while (i < expression.length && expression[i] !== quote) {
|
|
145
|
+
if (expression[i] === '\\' && i + 1 < expression.length) {
|
|
146
|
+
// Handle escape sequences
|
|
147
|
+
const nextChar = expression[i + 1];
|
|
148
|
+
if (nextChar === quote || nextChar === '\\') {
|
|
149
|
+
string += nextChar;
|
|
150
|
+
i += 2;
|
|
151
|
+
} else {
|
|
152
|
+
string += expression[i];
|
|
153
|
+
i++;
|
|
154
|
+
}
|
|
155
|
+
} else {
|
|
156
|
+
string += expression[i];
|
|
157
|
+
i++;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (i >= expression.length) {
|
|
162
|
+
throw new Error('Unterminated string literal');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
i++; // Skip closing quote
|
|
166
|
+
tokens.push({ type: 'STRING', value: string });
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Operators
|
|
171
|
+
if (char === '=' && expression[i + 1] === '=') {
|
|
172
|
+
if (expression[i + 2] === '=') {
|
|
173
|
+
tokens.push({ type: 'OPERATOR', value: '===' });
|
|
174
|
+
i += 3;
|
|
175
|
+
} else {
|
|
176
|
+
tokens.push({ type: 'OPERATOR', value: '==' });
|
|
177
|
+
i += 2;
|
|
178
|
+
}
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (char === '!' && expression[i + 1] === '=') {
|
|
183
|
+
if (expression[i + 2] === '=') {
|
|
184
|
+
tokens.push({ type: 'OPERATOR', value: '!==' });
|
|
185
|
+
i += 3;
|
|
186
|
+
} else {
|
|
187
|
+
tokens.push({ type: 'OPERATOR', value: '!=' });
|
|
188
|
+
i += 2;
|
|
189
|
+
}
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (char === '<' || char === '>') {
|
|
194
|
+
if (expression[i + 1] === '=') {
|
|
195
|
+
tokens.push({ type: 'OPERATOR', value: char + '=' });
|
|
196
|
+
i += 2;
|
|
197
|
+
} else {
|
|
198
|
+
tokens.push({ type: 'OPERATOR', value: char });
|
|
199
|
+
i++;
|
|
200
|
+
}
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (char === '&' && expression[i + 1] === '&') {
|
|
205
|
+
tokens.push({ type: 'OPERATOR', value: '&&' });
|
|
206
|
+
i += 2;
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (char === '|' && expression[i + 1] === '|') {
|
|
211
|
+
tokens.push({ type: 'OPERATOR', value: '||' });
|
|
212
|
+
i += 2;
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Single character operators and punctuation
|
|
217
|
+
if ('!+-*/%().'.includes(char)) {
|
|
218
|
+
if (char === '.') {
|
|
219
|
+
tokens.push({ type: 'DOT', value: '.' });
|
|
220
|
+
} else if (char === '(' || char === ')') {
|
|
221
|
+
tokens.push({ type: 'PAREN', value: char });
|
|
222
|
+
} else {
|
|
223
|
+
tokens.push({ type: 'OPERATOR', value: char });
|
|
224
|
+
}
|
|
225
|
+
i++;
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
throw new Error(`Unexpected character: ${char}`);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return tokens;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Parse tokens into AST
|
|
237
|
+
* @param {Array} tokens - Tokens to parse
|
|
238
|
+
* @returns {object} AST node
|
|
239
|
+
*/
|
|
240
|
+
parse(tokens) {
|
|
241
|
+
let index = 0;
|
|
242
|
+
|
|
243
|
+
const parseExpression = () => {
|
|
244
|
+
return parseLogicalOr();
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
const parseLogicalOr = () => {
|
|
248
|
+
let left = parseLogicalAnd();
|
|
249
|
+
|
|
250
|
+
while (index < tokens.length && tokens[index].value === '||') {
|
|
251
|
+
const operator = tokens[index].value;
|
|
252
|
+
index++;
|
|
253
|
+
const right = parseLogicalAnd();
|
|
254
|
+
left = { type: 'BinaryExpression', operator, left, right };
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return left;
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
const parseLogicalAnd = () => {
|
|
261
|
+
let left = parseEquality();
|
|
262
|
+
|
|
263
|
+
while (index < tokens.length && tokens[index].value === '&&') {
|
|
264
|
+
const operator = tokens[index].value;
|
|
265
|
+
index++;
|
|
266
|
+
const right = parseEquality();
|
|
267
|
+
left = { type: 'BinaryExpression', operator, left, right };
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return left;
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
const parseEquality = () => {
|
|
274
|
+
let left = parseComparison();
|
|
275
|
+
|
|
276
|
+
while (
|
|
277
|
+
index < tokens.length &&
|
|
278
|
+
['==', '===', '!=', '!=='].includes(tokens[index].value)
|
|
279
|
+
) {
|
|
280
|
+
const operator = tokens[index].value;
|
|
281
|
+
index++;
|
|
282
|
+
const right = parseComparison();
|
|
283
|
+
left = { type: 'BinaryExpression', operator, left, right };
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return left;
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
const parseComparison = () => {
|
|
290
|
+
let left = parseAdditive();
|
|
291
|
+
|
|
292
|
+
while (
|
|
293
|
+
index < tokens.length &&
|
|
294
|
+
['<', '>', '<=', '>='].includes(tokens[index].value)
|
|
295
|
+
) {
|
|
296
|
+
const operator = tokens[index].value;
|
|
297
|
+
index++;
|
|
298
|
+
const right = parseAdditive();
|
|
299
|
+
left = { type: 'BinaryExpression', operator, left, right };
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return left;
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
const parseAdditive = () => {
|
|
306
|
+
let left = parseMultiplicative();
|
|
307
|
+
|
|
308
|
+
while (
|
|
309
|
+
index < tokens.length &&
|
|
310
|
+
['+', '-'].includes(tokens[index].value)
|
|
311
|
+
) {
|
|
312
|
+
const operator = tokens[index].value;
|
|
313
|
+
index++;
|
|
314
|
+
const right = parseMultiplicative();
|
|
315
|
+
left = { type: 'BinaryExpression', operator, left, right };
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return left;
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
const parseMultiplicative = () => {
|
|
322
|
+
let left = parseUnary();
|
|
323
|
+
|
|
324
|
+
while (
|
|
325
|
+
index < tokens.length &&
|
|
326
|
+
['*', '/', '%'].includes(tokens[index].value)
|
|
327
|
+
) {
|
|
328
|
+
const operator = tokens[index].value;
|
|
329
|
+
index++;
|
|
330
|
+
const right = parseUnary();
|
|
331
|
+
left = { type: 'BinaryExpression', operator, left, right };
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return left;
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
const parseUnary = () => {
|
|
338
|
+
if (index < tokens.length && tokens[index].value === '!') {
|
|
339
|
+
const operator = tokens[index].value;
|
|
340
|
+
index++;
|
|
341
|
+
const argument = parseUnary();
|
|
342
|
+
return { type: 'UnaryExpression', operator, argument };
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return parseMemberAccess();
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
const parseMemberAccess = () => {
|
|
349
|
+
let object = parsePrimary();
|
|
350
|
+
|
|
351
|
+
while (index < tokens.length && tokens[index].type === 'DOT') {
|
|
352
|
+
index++; // Skip dot
|
|
353
|
+
if (index >= tokens.length || tokens[index].type !== 'IDENTIFIER') {
|
|
354
|
+
throw new Error('Expected property name after dot');
|
|
355
|
+
}
|
|
356
|
+
const property = tokens[index].value;
|
|
357
|
+
index++;
|
|
358
|
+
object = { type: 'MemberExpression', object, property };
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return object;
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
const parsePrimary = () => {
|
|
365
|
+
if (index >= tokens.length) {
|
|
366
|
+
throw new Error('Unexpected end of expression');
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const token = tokens[index];
|
|
370
|
+
|
|
371
|
+
if (token.type === 'NUMBER') {
|
|
372
|
+
index++;
|
|
373
|
+
return { type: 'Literal', value: token.value };
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (token.type === 'STRING') {
|
|
377
|
+
index++;
|
|
378
|
+
return { type: 'Literal', value: token.value };
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (token.type === 'BOOLEAN') {
|
|
382
|
+
index++;
|
|
383
|
+
return { type: 'Literal', value: token.value };
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (token.type === 'NULL') {
|
|
387
|
+
index++;
|
|
388
|
+
return { type: 'Literal', value: null };
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (token.type === 'UNDEFINED') {
|
|
392
|
+
index++;
|
|
393
|
+
return { type: 'Literal', value: undefined };
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (token.type === 'IDENTIFIER') {
|
|
397
|
+
index++;
|
|
398
|
+
return { type: 'Identifier', name: token.value };
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (token.type === 'PAREN' && token.value === '(') {
|
|
402
|
+
index++; // Skip opening paren
|
|
403
|
+
const expr = parseExpression();
|
|
404
|
+
if (index >= tokens.length || tokens[index].value !== ')') {
|
|
405
|
+
throw new Error('Expected closing parenthesis');
|
|
406
|
+
}
|
|
407
|
+
index++; // Skip closing paren
|
|
408
|
+
return expr;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
throw new Error(`Unexpected token: ${token.value}`);
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
const ast = parseExpression();
|
|
415
|
+
|
|
416
|
+
if (index < tokens.length) {
|
|
417
|
+
throw new Error(
|
|
418
|
+
`Unexpected token after expression: ${tokens[index].value}`,
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
return ast;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Evaluate AST node
|
|
427
|
+
* @param {object} node - AST node
|
|
428
|
+
* @param {object} context - Evaluation context
|
|
429
|
+
* @param {number} depth - Current recursion depth
|
|
430
|
+
* @returns {any} Evaluation result
|
|
431
|
+
*/
|
|
432
|
+
evaluateNode(node, context, depth = 0) {
|
|
433
|
+
if (depth > this.maxDepth) {
|
|
434
|
+
throw new Error('Expression too complex');
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
switch (node.type) {
|
|
438
|
+
case 'Literal':
|
|
439
|
+
return node.value;
|
|
440
|
+
|
|
441
|
+
case 'Identifier':
|
|
442
|
+
// Block dangerous identifiers
|
|
443
|
+
if (this.blockedIdentifiers.has(node.name)) {
|
|
444
|
+
return undefined;
|
|
445
|
+
}
|
|
446
|
+
if (!Object.prototype.hasOwnProperty.call(context, node.name)) {
|
|
447
|
+
return undefined;
|
|
448
|
+
}
|
|
449
|
+
return context[node.name];
|
|
450
|
+
|
|
451
|
+
case 'MemberExpression': {
|
|
452
|
+
const object = this.evaluateNode(node.object, context, depth + 1);
|
|
453
|
+
if (object == null) {
|
|
454
|
+
return undefined;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Allow specific properties on arrays
|
|
458
|
+
if (Array.isArray(object)) {
|
|
459
|
+
if (node.property === 'length') {
|
|
460
|
+
return object.length;
|
|
461
|
+
}
|
|
462
|
+
if (node.property === 'first') {
|
|
463
|
+
return object[0];
|
|
464
|
+
}
|
|
465
|
+
if (node.property === 'last') {
|
|
466
|
+
return object[object.length - 1];
|
|
467
|
+
}
|
|
468
|
+
// Block other array access
|
|
469
|
+
return undefined;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Only allow simple property access on plain objects
|
|
473
|
+
if (typeof object !== 'object') {
|
|
474
|
+
return undefined;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// Validate property name - block dangerous properties
|
|
478
|
+
if (
|
|
479
|
+
!this.allowedPropertyPattern.test(node.property) ||
|
|
480
|
+
node.property.startsWith('__') ||
|
|
481
|
+
['constructor', 'prototype', '__proto__'].includes(node.property)
|
|
482
|
+
) {
|
|
483
|
+
throw new Error('Invalid property name');
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// Only access own properties
|
|
487
|
+
if (!Object.prototype.hasOwnProperty.call(object, node.property)) {
|
|
488
|
+
return undefined;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
return object[node.property];
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
case 'BinaryExpression': {
|
|
495
|
+
const left = this.evaluateNode(node.left, context, depth + 1);
|
|
496
|
+
const right = this.evaluateNode(node.right, context, depth + 1);
|
|
497
|
+
|
|
498
|
+
switch (node.operator) {
|
|
499
|
+
case '==':
|
|
500
|
+
return left == right; // eslint-disable-line eqeqeq
|
|
501
|
+
case '===':
|
|
502
|
+
return left === right;
|
|
503
|
+
case '!=':
|
|
504
|
+
return left != right; // eslint-disable-line eqeqeq
|
|
505
|
+
case '!==':
|
|
506
|
+
return left !== right;
|
|
507
|
+
case '<':
|
|
508
|
+
return left < right;
|
|
509
|
+
case '>':
|
|
510
|
+
return left > right;
|
|
511
|
+
case '<=':
|
|
512
|
+
return left <= right;
|
|
513
|
+
case '>=':
|
|
514
|
+
return left >= right;
|
|
515
|
+
case '&&':
|
|
516
|
+
return left && right;
|
|
517
|
+
case '||':
|
|
518
|
+
return left || right;
|
|
519
|
+
case '+':
|
|
520
|
+
return left + right;
|
|
521
|
+
case '-':
|
|
522
|
+
return left - right;
|
|
523
|
+
case '*':
|
|
524
|
+
return left * right;
|
|
525
|
+
case '/':
|
|
526
|
+
return left / right;
|
|
527
|
+
case '%':
|
|
528
|
+
return left % right;
|
|
529
|
+
default:
|
|
530
|
+
throw new Error(`Unknown operator: ${node.operator}`);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
case 'UnaryExpression': {
|
|
535
|
+
const argument = this.evaluateNode(node.argument, context, depth + 1);
|
|
536
|
+
|
|
537
|
+
switch (node.operator) {
|
|
538
|
+
case '!':
|
|
539
|
+
return !argument;
|
|
540
|
+
default:
|
|
541
|
+
throw new Error(`Unknown unary operator: ${node.operator}`);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
default:
|
|
546
|
+
throw new Error(`Unknown node type: ${node.type}`);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Create a safe evaluator instance
|
|
553
|
+
* @param {object} options - Configuration options
|
|
554
|
+
* @returns {SafeExpressionEvaluator} Evaluator instance
|
|
555
|
+
*/
|
|
556
|
+
function createSafeEvaluator(options = {}) {
|
|
557
|
+
return new SafeExpressionEvaluator(options);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Evaluate expression safely
|
|
562
|
+
* @param {string} expression - Expression to evaluate
|
|
563
|
+
* @param {object} context - Context object
|
|
564
|
+
* @param {object} options - Options
|
|
565
|
+
* @returns {any} Evaluation result
|
|
566
|
+
*/
|
|
567
|
+
function evaluateExpression(expression, context = {}, options = {}) {
|
|
568
|
+
const evaluator = createSafeEvaluator(options);
|
|
569
|
+
return evaluator.evaluate(expression, context);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
module.exports = {
|
|
573
|
+
SafeExpressionEvaluator,
|
|
574
|
+
createSafeEvaluator,
|
|
575
|
+
evaluateExpression,
|
|
576
|
+
};
|
|
577
|
+
|
|
578
|
+
// CommonJS/ESM interop
|
|
579
|
+
module.exports.default = module.exports;
|