@memberjunction/global 5.51.3 → 5.51.4

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.
Files changed (38) hide show
  1. package/README.md +2 -2
  2. package/dist/SQLExpressionValidator.d.ts +37 -31
  3. package/dist/SQLExpressionValidator.d.ts.map +1 -1
  4. package/dist/SQLExpressionValidator.js +129 -117
  5. package/dist/SQLExpressionValidator.js.map +1 -1
  6. package/dist/SQLScreeningScanner.d.ts +91 -0
  7. package/dist/SQLScreeningScanner.d.ts.map +1 -0
  8. package/dist/SQLScreeningScanner.js +332 -0
  9. package/dist/SQLScreeningScanner.js.map +1 -0
  10. package/dist/SafeExpressionEvaluator.d.ts +131 -6
  11. package/dist/SafeExpressionEvaluator.d.ts.map +1 -1
  12. package/dist/SafeExpressionEvaluator.js +344 -41
  13. package/dist/SafeExpressionEvaluator.js.map +1 -1
  14. package/dist/__tests__/SQLExpressionValidator.security.test.js +176 -2
  15. package/dist/__tests__/SQLExpressionValidator.security.test.js.map +1 -1
  16. package/dist/__tests__/SQLScreeningScanner.test.d.ts +2 -0
  17. package/dist/__tests__/SQLScreeningScanner.test.d.ts.map +1 -0
  18. package/dist/__tests__/SQLScreeningScanner.test.js +172 -0
  19. package/dist/__tests__/SQLScreeningScanner.test.js.map +1 -0
  20. package/dist/__tests__/SafeExpressionEvaluator.security.test.d.ts +2 -0
  21. package/dist/__tests__/SafeExpressionEvaluator.security.test.d.ts.map +1 -0
  22. package/dist/__tests__/SafeExpressionEvaluator.security.test.js +284 -0
  23. package/dist/__tests__/SafeExpressionEvaluator.security.test.js.map +1 -0
  24. package/dist/__tests__/SafeExpressionEvaluator.test.d.ts +2 -0
  25. package/dist/__tests__/SafeExpressionEvaluator.test.d.ts.map +1 -0
  26. package/dist/__tests__/SafeExpressionEvaluator.test.js +355 -0
  27. package/dist/__tests__/SafeExpressionEvaluator.test.js.map +1 -0
  28. package/dist/__tests__/util.test.js +19 -1
  29. package/dist/__tests__/util.test.js.map +1 -1
  30. package/dist/index.d.ts +1 -0
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +1 -0
  33. package/dist/index.js.map +1 -1
  34. package/dist/util.d.ts +19 -0
  35. package/dist/util.d.ts.map +1 -1
  36. package/dist/util.js +23 -0
  37. package/dist/util.js.map +1 -1
  38. package/package.json +2 -1
@@ -0,0 +1,91 @@
1
+ /**
2
+ * @fileoverview Dialect-aware SQL scanner for MemberJunction's SQL denylist screens.
3
+ *
4
+ * A denylist screen is only as good as its idea of which text is code. This scanner reads SQL the
5
+ * way SQL Server or PostgreSQL splits it into tokens and marks every string literal, quoted
6
+ * identifier and comment, so a screen checks exactly the text the database runs as code. When it
7
+ * cannot read the text the way the database would, it reports that, so the screen rejects the text
8
+ * instead of guessing.
9
+ *
10
+ * Used by `SQLExpressionValidator` and `DatabaseProviderBase.ValidateUserProvidedSQLClause`.
11
+ *
12
+ * @module @memberjunction/global/SQLScreeningScanner
13
+ */
14
+ /** SQL dialects whose lexical rules {@link ScanSQLForScreening} knows. */
15
+ export type SQLScreeningDialect = 'sqlserver' | 'postgresql';
16
+ /**
17
+ * Every dialect {@link ScanSQLForScreening} knows. A screen that does not know which database will
18
+ * run the SQL must accept it under each one.
19
+ */
20
+ export declare const SQL_SCREENING_DIALECTS: readonly SQLScreeningDialect[];
21
+ /** Kinds of text that the database never runs as code. */
22
+ export type SQLScreeningSpanKind = 'string' | 'identifier' | 'comment';
23
+ /** One string literal, quoted identifier or comment found by {@link ScanSQLForScreening}. */
24
+ export interface SQLScreeningSpan {
25
+ Kind: SQLScreeningSpanKind;
26
+ /** Offset of the first character, including a prefix such as `N` or `E`. */
27
+ Start: number;
28
+ /** Offset just past the last character. */
29
+ End: number;
30
+ }
31
+ /** Result of {@link ScanSQLForScreening}. */
32
+ export interface SQLScreeningScan {
33
+ /** The dialect whose rules were applied. */
34
+ Dialect: SQLScreeningDialect;
35
+ /** True when every character was read the way the database reads it. When false, reject the text. */
36
+ Classified: boolean;
37
+ /** Why the text could not be classified; null when {@link Classified} is true. */
38
+ Error: string | null;
39
+ /** The string literals, quoted identifiers and comments found, in order. */
40
+ Spans: SQLScreeningSpan[];
41
+ /** Number of comments found. */
42
+ CommentCount: number;
43
+ /**
44
+ * The input with each string literal, quoted identifier and comment replaced by one space, and a
45
+ * space between a number and a word written against it. Only code remains, split where the
46
+ * database splits it, so `N'x'UNION` reads as ` UNION` and `1UNION` as `1 UNION`. Keyword,
47
+ * statement-separator and comment-marker checks read this. When {@link Classified} is false,
48
+ * the text from the point where the scan stopped is left as written.
49
+ */
50
+ Code: string;
51
+ /**
52
+ * {@link Code}, except each quoted identifier is written as its bare name between spaces, so
53
+ * `[sys].[objects]` reads as ` sys . objects `. Checks for object and function names read this,
54
+ * so quoting a name does not hide it.
55
+ */
56
+ CodeWithIdentifiers: string;
57
+ /**
58
+ * The first token that is not whitespace or a comment, when that token is an unquoted word and
59
+ * the character after it is ASCII, so every server ends the word in the same place. Null when a
60
+ * literal, quoted identifier, number or symbol comes first, or there is no token. Checks for the
61
+ * statement's opening keyword read this.
62
+ */
63
+ LeadingWord: string | null;
64
+ }
65
+ /**
66
+ * Reads `sql` the way `dialect` splits it into tokens. Returns the string literals, quoted
67
+ * identifiers and comments it contains, and the code that remains.
68
+ *
69
+ * Both dialects: `'…'` and `N'…'` strings, in which `''` is an escaped quote and a backslash is an
70
+ * ordinary character; `"…"` identifiers with `""` escapes; `--` comments to the end of the line; and
71
+ * nested block comments. SQL Server adds `[…]` identifiers with `]]` escapes. PostgreSQL adds `E'…'`
72
+ * strings, in which a backslash escapes the next character, and dollar-quoted strings (`$$…$$`,
73
+ * `$tag$…$tag$`); a `[` is code there.
74
+ *
75
+ * 🚨 Do not add backslash escapes to plain `'…'` strings. Neither server honors them (PostgreSQL is
76
+ * assumed to run with `standard_conforming_strings = on`, its default since 9.1), and a scanner that
77
+ * does reads `x = 'a\') ; DROP TABLE t; --'` as one literal while the database runs the DROP.
78
+ *
79
+ * The scan stops and reports an error, instead of guessing, when:
80
+ * - a string, quoted identifier, dollar-quoted string or block comment is not closed;
81
+ * - the text contains a NUL character;
82
+ * - a `--` comment contains a line break other than `\n` or `\r\n`;
83
+ * - a PostgreSQL `E'…'` string is followed by another quoted segment, which PostgreSQL can join to
84
+ * it and read with backslash escapes;
85
+ * - on SQL Server, a number is followed by `e` and a word character (`1EXEC` splits two ways).
86
+ *
87
+ * @param sql The clause, expression or query to read
88
+ * @param dialect The dialect whose lexical rules apply
89
+ */
90
+ export declare function ScanSQLForScreening(sql: string, dialect: SQLScreeningDialect): SQLScreeningScan;
91
+ //# sourceMappingURL=SQLScreeningScanner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SQLScreeningScanner.d.ts","sourceRoot":"","sources":["../src/SQLScreeningScanner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,0EAA0E;AAC1E,MAAM,MAAM,mBAAmB,GAAG,WAAW,GAAG,YAAY,CAAC;AAE7D;;;GAGG;AACH,eAAO,MAAM,sBAAsB,EAAE,SAAS,mBAAmB,EAAgC,CAAC;AAElG,0DAA0D;AAC1D,MAAM,MAAM,oBAAoB,GAAG,QAAQ,GAAG,YAAY,GAAG,SAAS,CAAC;AAEvE,6FAA6F;AAC7F,MAAM,WAAW,gBAAgB;IAC7B,IAAI,EAAE,oBAAoB,CAAC;IAC3B,4EAA4E;IAC5E,KAAK,EAAE,MAAM,CAAC;IACd,2CAA2C;IAC3C,GAAG,EAAE,MAAM,CAAC;CACf;AAED,6CAA6C;AAC7C,MAAM,WAAW,gBAAgB;IAC7B,4CAA4C;IAC5C,OAAO,EAAE,mBAAmB,CAAC;IAC7B,qGAAqG;IACrG,UAAU,EAAE,OAAO,CAAC;IACpB,kFAAkF;IAClF,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,4EAA4E;IAC5E,KAAK,EAAE,gBAAgB,EAAE,CAAC;IAC1B,gCAAgC;IAChC,YAAY,EAAE,MAAM,CAAC;IACrB;;;;;;OAMG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,mBAAmB,EAAE,MAAM,CAAC;IAC5B;;;;;OAKG;IACH,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AA2DD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,mBAAmB,GAAG,gBAAgB,CAE/F"}
@@ -0,0 +1,332 @@
1
+ /**
2
+ * @fileoverview Dialect-aware SQL scanner for MemberJunction's SQL denylist screens.
3
+ *
4
+ * A denylist screen is only as good as its idea of which text is code. This scanner reads SQL the
5
+ * way SQL Server or PostgreSQL splits it into tokens and marks every string literal, quoted
6
+ * identifier and comment, so a screen checks exactly the text the database runs as code. When it
7
+ * cannot read the text the way the database would, it reports that, so the screen rejects the text
8
+ * instead of guessing.
9
+ *
10
+ * Used by `SQLExpressionValidator` and `DatabaseProviderBase.ValidateUserProvidedSQLClause`.
11
+ *
12
+ * @module @memberjunction/global/SQLScreeningScanner
13
+ */
14
+ /**
15
+ * Every dialect {@link ScanSQLForScreening} knows. A screen that does not know which database will
16
+ * run the SQL must accept it under each one.
17
+ */
18
+ export const SQL_SCREENING_DIALECTS = ['sqlserver', 'postgresql'];
19
+ const LEXICAL_RULES = {
20
+ sqlserver: {
21
+ BracketIdentifiers: true,
22
+ PostgresStrings: false,
23
+ // Letters and digits of any script. Other non-ASCII characters are not part of a word: SQL
24
+ // Server reads a currency symbol such as ¥ as the start of a money literal, so in `¥1SELECT`
25
+ // the keyword is a separate token.
26
+ WordStart: /[\p{L}_@#]/u,
27
+ WordPart: /[\p{L}\p{N}_@#$]/u,
28
+ BareExponent: true,
29
+ },
30
+ postgresql: {
31
+ BracketIdentifiers: false,
32
+ PostgresStrings: true,
33
+ WordStart: /[A-Za-z_\u0080-￿]/,
34
+ WordPart: /[A-Za-z0-9_$\u0080-￿]/,
35
+ BareExponent: false,
36
+ },
37
+ };
38
+ const DIGIT = /[0-9]/;
39
+ const WHITESPACE = /\s/;
40
+ /** Whitespace every server skips. Other Unicode spaces count as a token when finding the leading word. */
41
+ const ASCII_WHITESPACE = /[ \t\n\r\f\v]/;
42
+ const NON_ASCII = /[^\x00-\x7f]/;
43
+ /**
44
+ * A numeric literal at `lastIndex` (sticky). An exponent needs digits, so in PostgreSQL's `1e'…'`
45
+ * the `e` is left to open an E'…' string.
46
+ */
47
+ const NUMBER = /0[xX][0-9A-Fa-f]*|(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)(?:[eE][+-]?[0-9]+)?/y;
48
+ /** A PostgreSQL `$$` or `$tag$` delimiter at `lastIndex` (sticky). */
49
+ const DOLLAR_DELIMITER = /\$(?:[A-Za-z_\u0080-￿][A-Za-z0-9_\u0080-￿]*)?\$/y;
50
+ /** Line breaks other than `\n` and `\r\n`. Servers disagree on whether they end a `--` comment. */
51
+ const AMBIGUOUS_LINE_BREAKS = new Set(['\r', '\u000b', '\u000c', '\u0085', '
', '
']);
52
+ /**
53
+ * Reads `sql` the way `dialect` splits it into tokens. Returns the string literals, quoted
54
+ * identifiers and comments it contains, and the code that remains.
55
+ *
56
+ * Both dialects: `'…'` and `N'…'` strings, in which `''` is an escaped quote and a backslash is an
57
+ * ordinary character; `"…"` identifiers with `""` escapes; `--` comments to the end of the line; and
58
+ * nested block comments. SQL Server adds `[…]` identifiers with `]]` escapes. PostgreSQL adds `E'…'`
59
+ * strings, in which a backslash escapes the next character, and dollar-quoted strings (`$$…$$`,
60
+ * `$tag$…$tag$`); a `[` is code there.
61
+ *
62
+ * 🚨 Do not add backslash escapes to plain `'…'` strings. Neither server honors them (PostgreSQL is
63
+ * assumed to run with `standard_conforming_strings = on`, its default since 9.1), and a scanner that
64
+ * does reads `x = 'a\') ; DROP TABLE t; --'` as one literal while the database runs the DROP.
65
+ *
66
+ * The scan stops and reports an error, instead of guessing, when:
67
+ * - a string, quoted identifier, dollar-quoted string or block comment is not closed;
68
+ * - the text contains a NUL character;
69
+ * - a `--` comment contains a line break other than `\n` or `\r\n`;
70
+ * - a PostgreSQL `E'…'` string is followed by another quoted segment, which PostgreSQL can join to
71
+ * it and read with backslash escapes;
72
+ * - on SQL Server, a number is followed by `e` and a word character (`1EXEC` splits two ways).
73
+ *
74
+ * @param sql The clause, expression or query to read
75
+ * @param dialect The dialect whose lexical rules apply
76
+ */
77
+ export function ScanSQLForScreening(sql, dialect) {
78
+ return new SQLScreeningScanner(sql, dialect).Scan();
79
+ }
80
+ /** One left-to-right pass over the input, with the dialect's rules. */
81
+ class SQLScreeningScanner {
82
+ constructor(sql, dialect) {
83
+ this.sql = sql;
84
+ this.dialect = dialect;
85
+ this.spans = [];
86
+ this.code = [];
87
+ this.codeWithIdentifiers = [];
88
+ this.commentCount = 0;
89
+ this.pos = 0;
90
+ /** Offset of the first token that is not whitespace or a comment, once one is reached. */
91
+ this.leadingTokenStart = null;
92
+ this.leadingWord = null;
93
+ this.rules = LEXICAL_RULES[dialect];
94
+ }
95
+ Scan() {
96
+ if (!SQL_SCREENING_DIALECTS.includes(this.dialect))
97
+ return this.stop(`"${this.dialect}" is not a known SQL dialect`);
98
+ if (this.sql.includes('\0'))
99
+ return this.stop('the text contains a NUL character');
100
+ while (this.pos < this.sql.length) {
101
+ const error = this.readToken();
102
+ if (error !== null)
103
+ return this.stop(error);
104
+ }
105
+ return this.result(null);
106
+ }
107
+ /** Reads the token at `pos`. Returns why the text cannot be classified, or null. */
108
+ readToken() {
109
+ const c = this.sql[this.pos];
110
+ const next = this.sql[this.pos + 1] ?? '';
111
+ if (c === '-' && next === '-')
112
+ return this.readLineComment();
113
+ if (c === '/' && next === '*')
114
+ return this.readBlockComment();
115
+ if (this.leadingTokenStart === null && !ASCII_WHITESPACE.test(c))
116
+ this.leadingTokenStart = this.pos;
117
+ if (c === "'")
118
+ return this.readString(this.pos + 1);
119
+ if ((c === 'N' || c === 'n') && next === "'")
120
+ return this.readString(this.pos + 2);
121
+ if (this.rules.PostgresStrings && (c === 'E' || c === 'e') && next === "'")
122
+ return this.readEscapeString();
123
+ if (this.rules.PostgresStrings && c === '$') {
124
+ const delimiter = this.dollarDelimiterAt(this.pos);
125
+ if (delimiter !== null)
126
+ return this.readDollarString(delimiter);
127
+ }
128
+ if (c === '"')
129
+ return this.readQuotedIdentifier('"');
130
+ if (this.rules.BracketIdentifiers && c === '[')
131
+ return this.readQuotedIdentifier(']');
132
+ if (DIGIT.test(c) || (c === '.' && DIGIT.test(next)))
133
+ return this.readNumber();
134
+ if (this.rules.WordStart.test(c))
135
+ return this.readWord();
136
+ this.emitCode(this.pos + 1);
137
+ return null;
138
+ }
139
+ /** Reads a `'…'` string whose content starts at `contentStart`. */
140
+ readString(contentStart) {
141
+ const end = this.findQuoteEnd(contentStart, "'", false);
142
+ if (end === -1)
143
+ return 'a string literal is not closed';
144
+ this.emitSpan('string', end);
145
+ return null;
146
+ }
147
+ /** Reads a PostgreSQL `E'…'` string, in which a backslash escapes the next character. */
148
+ readEscapeString() {
149
+ const end = this.findQuoteEnd(this.pos + 2, "'", true);
150
+ if (end === -1)
151
+ return "an E'…' string is not closed";
152
+ if (this.quoteFollows(end)) {
153
+ return "an E'…' string is followed by another quoted segment, which PostgreSQL can read as part of it";
154
+ }
155
+ this.emitSpan('string', end);
156
+ return null;
157
+ }
158
+ /** Reads a PostgreSQL dollar-quoted string that opens with `delimiter`. */
159
+ readDollarString(delimiter) {
160
+ const close = this.sql.indexOf(delimiter, this.pos + delimiter.length);
161
+ if (close === -1)
162
+ return 'a dollar-quoted string is not closed';
163
+ this.emitSpan('string', close + delimiter.length);
164
+ return null;
165
+ }
166
+ /** Reads a `"…"` or `[…]` identifier; `close` is its closing character. */
167
+ readQuotedIdentifier(close) {
168
+ const end = this.findQuoteEnd(this.pos + 1, close, false);
169
+ if (end === -1)
170
+ return 'a quoted identifier is not closed';
171
+ const name = this.sql.slice(this.pos + 1, end - 1).split(close + close).join(close);
172
+ this.emitSpan('identifier', end, name);
173
+ return null;
174
+ }
175
+ /** Reads a `--` comment, which ends at the end of the line. */
176
+ readLineComment() {
177
+ let j = this.pos + 2;
178
+ while (j < this.sql.length && this.sql[j] !== '\n') {
179
+ if (this.sql[j] === '\r' && this.sql[j + 1] === '\n')
180
+ break;
181
+ if (AMBIGUOUS_LINE_BREAKS.has(this.sql[j])) {
182
+ return 'a -- comment contains a line break that database servers read differently';
183
+ }
184
+ j++;
185
+ }
186
+ this.commentCount++;
187
+ this.emitSpan('comment', j);
188
+ return null;
189
+ }
190
+ /** Reads a block comment. Block comments nest on both SQL Server and PostgreSQL. */
191
+ readBlockComment() {
192
+ let depth = 0;
193
+ let j = this.pos;
194
+ while (j < this.sql.length) {
195
+ if (this.sql[j] === '/' && this.sql[j + 1] === '*') {
196
+ depth++;
197
+ j += 2;
198
+ }
199
+ else if (this.sql[j] === '*' && this.sql[j + 1] === '/') {
200
+ depth--;
201
+ j += 2;
202
+ if (depth === 0) {
203
+ this.commentCount++;
204
+ this.emitSpan('comment', j);
205
+ return null;
206
+ }
207
+ }
208
+ else {
209
+ j++;
210
+ }
211
+ }
212
+ return 'a block comment is not closed';
213
+ }
214
+ /** Reads a number. A word written against it is a separate token, so a space is added between. */
215
+ readNumber() {
216
+ NUMBER.lastIndex = this.pos;
217
+ const match = NUMBER.exec(this.sql);
218
+ const end = this.pos + (match ? match[0].length : 1);
219
+ const following = this.sql[end] ?? '';
220
+ const afterThat = this.sql[end + 1] ?? '';
221
+ if (this.rules.BareExponent && (following === 'e' || following === 'E') && this.rules.WordStart.test(afterThat)) {
222
+ return 'a number is followed by a word that starts with E, which the server can read as part of the number';
223
+ }
224
+ this.emitCode(end);
225
+ if (following !== '' && this.rules.WordStart.test(following))
226
+ this.emitBoundary();
227
+ return null;
228
+ }
229
+ /** Reads an unquoted keyword or identifier. */
230
+ readWord() {
231
+ let j = this.pos + 1;
232
+ while (j < this.sql.length && this.rules.WordPart.test(this.sql[j]))
233
+ j++;
234
+ if (this.pos === this.leadingTokenStart && !NON_ASCII.test(this.sql[j] ?? '')) {
235
+ this.leadingWord = this.sql.slice(this.pos, j);
236
+ }
237
+ this.emitCode(j);
238
+ return null;
239
+ }
240
+ /**
241
+ * Offset just past the `close` character that ends a quoted run whose content starts at `from`,
242
+ * or -1 when none does. A doubled `close` is an escaped one; with `backslashEscapes`, so is any
243
+ * character after a backslash.
244
+ */
245
+ findQuoteEnd(from, close, backslashEscapes) {
246
+ let j = from;
247
+ while (j < this.sql.length) {
248
+ const ch = this.sql[j];
249
+ if (backslashEscapes && ch === '\\') {
250
+ j += 2;
251
+ }
252
+ else if (ch !== close) {
253
+ j++;
254
+ }
255
+ else if (this.sql[j + 1] === close) {
256
+ j += 2;
257
+ }
258
+ else {
259
+ return j + 1;
260
+ }
261
+ }
262
+ return -1;
263
+ }
264
+ /** Whether only whitespace and `--` comments come between `from` and the next `'`. */
265
+ quoteFollows(from) {
266
+ let j = from;
267
+ while (j < this.sql.length) {
268
+ const ch = this.sql[j];
269
+ if (ch === "'")
270
+ return true;
271
+ if (ch === '-' && this.sql[j + 1] === '-') {
272
+ j = this.lineEnd(j);
273
+ }
274
+ else if (WHITESPACE.test(ch)) {
275
+ j++;
276
+ }
277
+ else {
278
+ return false;
279
+ }
280
+ }
281
+ return false;
282
+ }
283
+ /** Offset of the first `\n` or `\r` at or after `from`, or the input length. */
284
+ lineEnd(from) {
285
+ let j = from;
286
+ while (j < this.sql.length && this.sql[j] !== '\n' && this.sql[j] !== '\r')
287
+ j++;
288
+ return j;
289
+ }
290
+ /** The `$$` or `$tag$` delimiter at `at`, or null when the `$` does not open a dollar-quoted string. */
291
+ dollarDelimiterAt(at) {
292
+ DOLLAR_DELIMITER.lastIndex = at;
293
+ return DOLLAR_DELIMITER.exec(this.sql)?.[0] ?? null;
294
+ }
295
+ /** Copies code up to `end` into both projections. */
296
+ emitCode(end) {
297
+ const text = this.sql.slice(this.pos, end);
298
+ this.code.push(text);
299
+ this.codeWithIdentifiers.push(text);
300
+ this.pos = end;
301
+ }
302
+ /** Records a span up to `end` and writes a space for it, or an identifier's bare name. */
303
+ emitSpan(kind, end, identifierName) {
304
+ this.spans.push({ Kind: kind, Start: this.pos, End: end });
305
+ this.code.push(' ');
306
+ this.codeWithIdentifiers.push(identifierName === undefined ? ' ' : ` ${identifierName} `);
307
+ this.pos = end;
308
+ }
309
+ /** Writes a token boundary that the input leaves implicit. */
310
+ emitBoundary() {
311
+ this.code.push(' ');
312
+ this.codeWithIdentifiers.push(' ');
313
+ }
314
+ /** Ends the scan at `pos`, leaving the unread text as written. */
315
+ stop(error) {
316
+ this.emitCode(this.sql.length);
317
+ return this.result(error);
318
+ }
319
+ result(error) {
320
+ return {
321
+ Dialect: this.dialect,
322
+ Classified: error === null,
323
+ Error: error,
324
+ Spans: this.spans,
325
+ CommentCount: this.commentCount,
326
+ Code: this.code.join(''),
327
+ CodeWithIdentifiers: this.codeWithIdentifiers.join(''),
328
+ LeadingWord: this.leadingWord,
329
+ };
330
+ }
331
+ }
332
+ //# sourceMappingURL=SQLScreeningScanner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SQLScreeningScanner.js","sourceRoot":"","sources":["../src/SQLScreeningScanner.ts"],"names":[],"mappings":"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"}
@@ -9,6 +9,33 @@
9
9
  * @author MemberJunction.com
10
10
  * @since 2.76.0
11
11
  */
12
+ /**
13
+ * Namespace globals whose listed methods a condition may CALL, keyed by namespace name.
14
+ *
15
+ * Both halves of `Math.abs(x)` are fixed identifiers in the AST, so admitting them adds no dynamic
16
+ * lookup: the four invariants that close the sandbox escape — literal-only computed keys, no
17
+ * `constructor`/`__proto__`/`prototype`, a closed non-computed call surface with no
18
+ * `call`/`apply`/`bind`, and screened free identifiers — are all untouched by this list.
19
+ *
20
+ * Every entry is a pure value function. `Math.random` is deliberately absent (a condition that
21
+ * evaluates differently on each run is not a guard), as are `Object.assign`,
22
+ * `Object.defineProperty` and `Object.setPrototypeOf`, which mutate.
23
+ */
24
+ export declare const SAFE_GLOBAL_NAMESPACE_METHODS: ReadonlyMap<string, ReadonlySet<string>>;
25
+ /**
26
+ * Global functions a condition may call by bare name — the coercions and numeric predicates that
27
+ * authored specs use (`Number(payload.count) > 3`, `isNaN(payload.count)`).
28
+ */
29
+ export declare const SAFE_GLOBAL_CALLABLES: ReadonlySet<string>;
30
+ /**
31
+ * Every global name a condition may reference, as a namespace or as a bare callable.
32
+ *
33
+ * **This is the single source of truth for the ambient globals of the condition dialect.** It lives
34
+ * beside the policy screen that enforces it so a caller validating conditions ahead of evaluation
35
+ * (`ai-core-plus`'s task-graph door) cannot bless a name the runtime would refuse — the failure mode
36
+ * that shipped once already, where the door's own list and the evaluator disagreed silently.
37
+ */
38
+ export declare const SAFE_EXPRESSION_GLOBALS: ReadonlySet<string>;
12
39
  /**
13
40
  * Result of expression evaluation including success status and diagnostics
14
41
  */
@@ -29,11 +56,22 @@ export interface ExpressionEvaluationResult {
29
56
  * Supported operations:
30
57
  * - Comparison: ==, ===, !=, !==, <, >, <=, >=
31
58
  * - Logical: &&, ||, !
32
- * - Property access: dot notation (e.g., payload.customer.name)
33
- * - Array access: bracket notation (e.g., items[0])
59
+ * - Property access: dot notation (e.g., payload.customer.name), including optional chaining (`?.`)
60
+ * - Array access: bracket notation with a literal index/key (e.g., items[0])
34
61
  * - Safe methods: .length, .includes(), .startsWith(), .endsWith()
35
62
  * - Array methods: .some(), .every(), .find(), .filter()
36
- * - Type checking: typeof, instanceof (limited to safe types)
63
+ * - Safe globals: the namespaces and bare callables in {@link SAFE_EXPRESSION_GLOBALS}
64
+ * (e.g. `Math.abs(output.delta) < 5`, `Number(payload.count) > 3`, `Object.keys(payload).length`)
65
+ * - Type checking: typeof
66
+ *
67
+ * Safety is enforced by parsing the expression to an AST and walking it against
68
+ * an ALLOWLIST of node types before it is compiled — an unlisted construct
69
+ * (computed member access with a non-literal key, `.constructor`/`__proto__`
70
+ * access, any call outside the safe-method and safe-global lists, host-global
71
+ * identifiers, etc.) is rejected at validation time and never reaches the compiler. A structural
72
+ * allowlist cannot be defeated by string concatenation the way a textual
73
+ * denylist can, and it does not over-reject data that merely mentions a reserved
74
+ * word (e.g. `name == 'constructor'` is a legal comparison).
37
75
  *
38
76
  * @class SafeExpressionEvaluator
39
77
  *
@@ -62,10 +100,24 @@ export interface ExpressionEvaluationResult {
62
100
  */
63
101
  export declare class SafeExpressionEvaluator {
64
102
  /**
65
- * Patterns that indicate potentially dangerous code
103
+ * Identifiers that must never resolve to a host global. Because the compiled
104
+ * function runs in global scope, a bare `process`/`Function`/`globalThis`/…
105
+ * would reach the real global object; identifiers are atomic AST tokens, so a
106
+ * name that is split with string concatenation parses as separate identifiers
107
+ * or an operator and is caught by the structural rules instead.
66
108
  * @private
67
109
  */
68
- private static readonly DANGEROUS_PATTERNS;
110
+ private static readonly DANGEROUS_IDENTIFIERS;
111
+ /**
112
+ * Property names that open the prototype chain / constructor climb. Denied on
113
+ * both dotted (`x.constructor`) and literal-bracket (`x["constructor"]`) access.
114
+ * @private
115
+ */
116
+ private static readonly DANGEROUS_PROPERTY_NAMES;
117
+ /** Binary operators the evaluator permits. `instanceof`/`in`/bitwise are excluded. @private */
118
+ private static readonly ALLOWED_BINARY_OPERATORS;
119
+ /** Unary operators the evaluator permits. `delete`/`void` are excluded. @private */
120
+ private static readonly ALLOWED_UNARY_OPERATORS;
69
121
  /**
70
122
  * Cache for compiled expressions to improve performance on repeated evaluations
71
123
  * @private
@@ -87,7 +139,8 @@ export declare class SafeExpressionEvaluator {
87
139
  */
88
140
  evaluate(expression: string, context: Record<string, any>, enableDiagnostics?: boolean): ExpressionEvaluationResult;
89
141
  /**
90
- * Validates an expression for safety
142
+ * Validates an expression for safety by parsing it and walking the AST against
143
+ * an allowlist. Returns an error message if any construct is not permitted.
91
144
  *
92
145
  * @param {string} expression - The expression to validate
93
146
  *
@@ -96,6 +149,44 @@ export declare class SafeExpressionEvaluator {
96
149
  * @private
97
150
  */
98
151
  private validateExpression;
152
+ /** Builds the standard rejection message. @private */
153
+ private forbidden;
154
+ /**
155
+ * Walks an AST node against the allowlist. Returns an error message on the first
156
+ * disallowed construct, or null if the entire subtree is permitted.
157
+ * @private
158
+ */
159
+ private checkNode;
160
+ /**
161
+ * Member access is allowed for dotted property reads and literal-key bracket
162
+ * reads, but never for a computed key built at runtime (the string-concat
163
+ * escape route) or a prototype-chain property.
164
+ * @private
165
+ */
166
+ private checkMemberExpression;
167
+ /**
168
+ * A call is admitted only when its callee is a FIXED name on one of two allowlists: a bare
169
+ * safe global (`parseInt(...)`) or a dotted method that is either a safe instance method or a
170
+ * method of a safe global namespace (`Math.abs(...)`). Everything else — a computed callee, an
171
+ * unlisted bare identifier (`eval(...)`, `Function(...)`), a call on a call's result that is
172
+ * not itself a safe method — is rejected.
173
+ * @private
174
+ */
175
+ private checkCallExpression;
176
+ /**
177
+ * Whether `<receiver>.<method>()` names a permitted call: a safe instance method on any value,
178
+ * or a method of a safe global namespace when the receiver is that namespace by bare name.
179
+ * @private
180
+ */
181
+ private isCallableMethod;
182
+ /** Walks a call's arguments; spreads are rejected outright. @private */
183
+ private checkCallArguments;
184
+ /**
185
+ * Arrow functions are permitted only as callback arguments to the safe array
186
+ * methods; parameters must be simple identifiers and the body an expression.
187
+ * @private
188
+ */
189
+ private checkArrowFunction;
99
190
  /**
100
191
  * Creates a safe context object with only allowed properties
101
192
  *
@@ -126,6 +217,40 @@ export declare class SafeExpressionEvaluator {
126
217
  * @private
127
218
  */
128
219
  private cloneValue;
220
+ /**
221
+ * Checks whether an expression *could* be evaluated, without evaluating it.
222
+ *
223
+ * The difference matters because `evaluate` reports two unrelated problems the same way. Given
224
+ * a condition authored against a runtime envelope, an empty context makes `payload.x > 1` fail
225
+ * with `payload is not defined` — indistinguishable, by result shape, from `payload.x >` failing
226
+ * with `Unexpected token`. One is a typo the author should be told about at the door; the other
227
+ * is a perfectly good condition that simply has no data yet. A submit-time check built on
228
+ * `evaluate` therefore refuses every legitimate condition.
229
+ *
230
+ * So this compiles and never runs. The expression goes through the same policy screen
231
+ * `evaluate` applies (the AST allowlist), then the same function body is BUILT and discarded —
232
+ * never invoked. Values are never consulted, which is precisely the property wanted: unknown
233
+ * identifiers, absent properties and undefined chains all PASS, because none of them is a syntax
234
+ * error and whether they resolve is a question about a run that has not happened yet.
235
+ *
236
+ * **Compilation is not evaluation**, and the distinction is load-bearing for safety: `Function`
237
+ * parses the body and returns; nothing in the expression executes. The policy screen still runs
238
+ * first, so the constructs `evaluate` refuses are refused here too — those produce a permanent
239
+ * runtime refusal, and an author is better told now.
240
+ *
241
+ * `Undecidable` is the honest third answer. A host that forbids dynamic compilation (a strict
242
+ * CSP without `unsafe-eval`) cannot answer the question at all, and a validator that read that
243
+ * as "invalid" would refuse every condition in the browser. Callers should treat it as a pass.
244
+ *
245
+ * @param expression the expression to check
246
+ * @returns `Valid: true` when it parses; `Valid: false` with `Error` when it definitely does
247
+ * not; `Valid: true` with `Undecidable: true` when this environment cannot compile
248
+ */
249
+ validateSyntax(expression: string): {
250
+ Valid: boolean;
251
+ Error?: string;
252
+ Undecidable?: boolean;
253
+ };
129
254
  /**
130
255
  * Evaluates multiple expressions and returns all results
131
256
  *
@@ -1 +1 @@
1
- {"version":3,"file":"SafeExpressionEvaluator.d.ts","sourceRoot":"","sources":["../src/SafeExpressionEvaluator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH;;GAEG;AACH,MAAM,WAAW,0BAA0B;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE;QACV,UAAU,EAAE,MAAM,CAAC;QACnB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAC7B,cAAc,EAAE,MAAM,CAAC;KAC1B,CAAC;CACL;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,qBAAa,uBAAuB;IAChC;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CA4BxC;IAEF;;;OAGG;IACH,OAAO,CAAC,QAAQ,CAAC,wBAAwB,CAAuD;IAEhG;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,YAAY,CAoBlC;IAEF;;;;;;;;OAQG;IACI,QAAQ,CACX,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC5B,iBAAiB,GAAE,OAAe,GACnC,0BAA0B;IAwE7B;;;;;;;;OAQG;IACH,OAAO,CAAC,kBAAkB;IAgC1B;;;;;;;;OAQG;IACH,OAAO,CAAC,iBAAiB;IAiBzB;;;;;;;;OAQG;IACH,OAAO,CAAC,uBAAuB;IAW/B;;;;;;;;OAQG;IACH,OAAO,CAAC,UAAU;IAwDlB;;;;;;;OAOG;IACI,gBAAgB,CACnB,WAAW,EAAE,KAAK,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAC,CAAC,EACvD,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC7B,MAAM,CAAC,MAAM,EAAE,0BAA0B,CAAC;CAUhD;AAED;;GAEG;AACH,eAAO,MAAM,0BAA0B,yBAAgC,CAAC"}
1
+ {"version":3,"file":"SafeExpressionEvaluator.d.ts","sourceRoot":"","sources":["../src/SafeExpressionEvaluator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAuBH;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,6BAA6B,EAAE,WAAW,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,CAUjF,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,qBAAqB,EAAE,WAAW,CAAC,MAAM,CAEpD,CAAC;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,uBAAuB,EAAE,WAAW,CAAC,MAAM,CAGtD,CAAC;AAEH;;GAEG;AACH,MAAM,WAAW,0BAA0B;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE;QACV,UAAU,EAAE,MAAM,CAAC;QACnB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAC7B,cAAc,EAAE,MAAM,CAAC;KAC1B,CAAC;CACL;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,qBAAa,uBAAuB;IAChC;;;;;;;OAOG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,qBAAqB,CAK1C;IAEH;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,wBAAwB,CAE7C;IAEH,+FAA+F;IAC/F,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,wBAAwB,CAE7C;IAEH,oFAAoF;IACpF,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,uBAAuB,CAA8C;IAE7F;;;OAGG;IACH,OAAO,CAAC,QAAQ,CAAC,wBAAwB,CAAuD;IAEhG;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,YAAY,CAoBlC;IAEF;;;;;;;;OAQG;IACI,QAAQ,CACX,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC5B,iBAAiB,GAAE,OAAe,GACnC,0BAA0B;IAwE7B;;;;;;;;;OASG;IACH,OAAO,CAAC,kBAAkB;IA4C1B,sDAAsD;IACtD,OAAO,CAAC,SAAS;IAIjB;;;;OAIG;IACH,OAAO,CAAC,SAAS;IAmEjB;;;;;OAKG;IACH,OAAO,CAAC,qBAAqB;IA4B7B;;;;;;;OAOG;IACH,OAAO,CAAC,mBAAmB;IA2B3B;;;;OAIG;IACH,OAAO,CAAC,gBAAgB;IAUxB,wEAAwE;IACxE,OAAO,CAAC,kBAAkB;IAW1B;;;;OAIG;IACH,OAAO,CAAC,kBAAkB;IAkB1B;;;;;;;;OAQG;IACH,OAAO,CAAC,iBAAiB;IAiBzB;;;;;;;;OAQG;IACH,OAAO,CAAC,uBAAuB;IAW/B;;;;;;;;OAQG;IACH,OAAO,CAAC,UAAU;IAwDlB;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACI,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,OAAO,CAAA;KAAE;IAqBpG;;;;;;;OAOG;IACI,gBAAgB,CACnB,WAAW,EAAE,KAAK,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAC,CAAC,EACvD,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC7B,MAAM,CAAC,MAAM,EAAE,0BAA0B,CAAC;CAUhD;AAED;;GAEG;AACH,eAAO,MAAM,0BAA0B,yBAAgC,CAAC"}