@askdb/core 1.0.0-beta.51 → 1.0.0-beta.52
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/README.md +6 -6
- package/dist/errors.d.ts +13 -8
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +9 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -9
- package/dist/index.js.map +1 -1
- package/dist/logging/pino-supported-levels.d.ts +1 -1
- package/dist/logging/pino-supported-levels.js +1 -1
- package/dist/schema/v2/loader.js +1 -1
- package/dist/schema/v2/loader.js.map +1 -1
- package/dist/schema/v2/tenant-policy.d.ts +0 -9
- package/dist/schema/v2/tenant-policy.d.ts.map +1 -1
- package/dist/schema/v2/tenant-policy.js +0 -11
- package/dist/schema/v2/tenant-policy.js.map +1 -1
- package/dist/sql/bind.d.ts.map +1 -1
- package/dist/sql/bind.js +3 -2
- package/dist/sql/bind.js.map +1 -1
- package/dist/sql/guardrails.d.ts.map +1 -1
- package/dist/sql/guardrails.js +2 -2
- package/dist/sql/guardrails.js.map +1 -1
- package/dist/sql/lexer.d.ts +12 -2
- package/dist/sql/lexer.d.ts.map +1 -1
- package/dist/sql/lexer.js +17 -4
- package/dist/sql/lexer.js.map +1 -1
- package/dist/sql/prompt.js +1 -1
- package/dist/sql/prompt.js.map +1 -1
- package/dist/sql/readings.d.ts +109 -0
- package/dist/sql/readings.d.ts.map +1 -0
- package/dist/sql/readings.js +287 -0
- package/dist/sql/readings.js.map +1 -0
- package/dist/sql/schema-question-precheck.d.ts.map +1 -1
- package/dist/sql/schema-question-precheck.js +2 -2
- package/dist/sql/schema-question-precheck.js.map +1 -1
- package/dist/sql/sensitive-guardrail.d.ts +5 -0
- package/dist/sql/sensitive-guardrail.d.ts.map +1 -1
- package/dist/sql/sensitive-guardrail.js +49 -24
- package/dist/sql/sensitive-guardrail.js.map +1 -1
- package/dist/sql/tenant-guardrail.d.ts +3 -1
- package/dist/sql/tenant-guardrail.d.ts.map +1 -1
- package/dist/sql/tenant-guardrail.js +337 -239
- package/dist/sql/tenant-guardrail.js.map +1 -1
- package/dist/sql/tenant-placeholders.d.ts +2 -2
- package/dist/sql/tenant-placeholders.d.ts.map +1 -1
- package/dist/sql/tenant-placeholders.js +2 -2
- package/dist/sql/tenant-placeholders.js.map +1 -1
- package/dist/sql/validate.d.ts +8 -5
- package/dist/sql/validate.d.ts.map +1 -1
- package/dist/sql/validate.js +33 -21
- package/dist/sql/validate.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How each SQL check reads a statement, per dialect: the lexer readings it must pass under. This
|
|
3
|
+
* module is the one place that decides them (#815, ADR 0010's stage (c), "one shared analysis"),
|
|
4
|
+
* and {@link readSql} lexes each reading once per statement, so a reading changes in one place.
|
|
5
|
+
* The checks stay heuristics (ADR 0012).
|
|
6
|
+
*
|
|
7
|
+
* The sets differ by check. #815 moved them here unchanged; merging them would tighten some checks
|
|
8
|
+
* and loosen others, so that is its own decision.
|
|
9
|
+
*
|
|
10
|
+
* | Check | A built-in engine | Any other id, or no dialect |
|
|
11
|
+
* |---|---|---|
|
|
12
|
+
* | read-only ({@link readOnlyReadings}) | the engine's profile | every engine's profile |
|
|
13
|
+
* | sensitive ({@link sensitiveReadings}) | the engine's (Postgres family: plus CockroachDB's comment end) | the generic profile, plus each engine's that lexes cleanly |
|
|
14
|
+
* | tenant: statements and calls ({@link tenantStatementReadings}) | the engine's (Postgres family: both comment ends) | every engine's, Postgres and MySQL with both string settings |
|
|
15
|
+
* | tenant: predicates ({@link tenantCodeReadings}) | the engine's view | the standard, Postgres, MySQL and SQL Server views, the first two with both comment ends; the Postgres and SQL Server views with block comments nested and not |
|
|
16
|
+
* | placeholders ({@link placeholderReading}) | the engine's | the generic profile |
|
|
17
|
+
* | names outside printable ASCII, tenant and sensitive ({@link unmatchedNonAsciiName}) | `sqlserver`: the engine's; any other engine: none | as for tenant statements |
|
|
18
|
+
* | `'…'` read as a name or an alias, tenant and sensitive ({@link stringsSqliteReadsAsNames}) | `sqlite`: its readings; any other engine: none | the generic and standard readings, and SQLite's |
|
|
19
|
+
* | Postgres-only forms (`postgresReadingProfiles` in `lexer.ts`) | the engine's, when it is on the Postgres lexer | Postgres's, with backslash escapes too when set |
|
|
20
|
+
*
|
|
21
|
+
* Every MySQL reading is taken twice by the tenant check, with `/*! … *\/` bodies as code and as
|
|
22
|
+
* comments, since a server older than the comment's version skips the body. Its Postgres and SQL
|
|
23
|
+
* Server views, which nest block comments as those engines do, are taken twice too, with the
|
|
24
|
+
* comments nested and not, so a predicate counts only where the two agree on what is code. A `postgres` dialect
|
|
25
|
+
* may point at CockroachDB, which is wire-compatible but ends a `--` comment only at `\n`, so the
|
|
26
|
+
* Postgres family is read with both comment ends where that decides what is code.
|
|
27
|
+
*/
|
|
28
|
+
import { isBuiltInDialectId } from "./dialect-spec.js";
|
|
29
|
+
import { ENGINE_LEXER_PROFILES, GENERIC_LEXER, lexerProfileFor, lexSql } from "./lexer.js";
|
|
30
|
+
/**
|
|
31
|
+
* Whether `dialect` names a built-in engine family, which the checks read its own way. Any other
|
|
32
|
+
* id is read every engine's way, and checked against every built-in spec's denylist.
|
|
33
|
+
*/
|
|
34
|
+
export function isKnownEngine(dialect) {
|
|
35
|
+
return lexerProfileFor(dialect) !== undefined;
|
|
36
|
+
}
|
|
37
|
+
/** A Postgres-family profile, ended at `\n` only the way CockroachDB ends a `--` comment. */
|
|
38
|
+
function asCockroachDb(profile) {
|
|
39
|
+
return { ...profile, lineCommentEndsAtLfOnly: true };
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The read-only check (`validateSelectSql`): the engine's profile, or every engine's. It refuses
|
|
43
|
+
* every comment, so where a comment ends doesn't change its verdict.
|
|
44
|
+
*/
|
|
45
|
+
export function readOnlyReadings(dialect) {
|
|
46
|
+
const profile = lexerProfileFor(dialect);
|
|
47
|
+
return profile ? [profile] : ENGINE_LEXER_PROFILES;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The sensitive check. With a built-in engine, its profile (for the Postgres family, the references
|
|
51
|
+
* CockroachDB's comment end shows are added too). Otherwise the generic profile decides scope issues
|
|
52
|
+
* (`primary`), and the references found under each engine's profile that lexes cleanly are added to
|
|
53
|
+
* it (`union`), so a column one engine's quoting rules hide is still reported.
|
|
54
|
+
*/
|
|
55
|
+
export function sensitiveReadings(dialect) {
|
|
56
|
+
const profile = dialect ? lexerProfileFor(dialect) : undefined;
|
|
57
|
+
if (!profile)
|
|
58
|
+
return { primary: GENERIC_LEXER, union: ENGINE_LEXER_PROFILES };
|
|
59
|
+
return { primary: profile, union: profile.id === "postgres" ? [asCockroachDb(profile)] : [] };
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Every way a server may lex `sql` into statements, for the tenant check's statement and call
|
|
63
|
+
* checks: the dialect's profile when it names a built-in engine, otherwise every engine's, and for
|
|
64
|
+
* Postgres and MySQL both string settings (a backslash ends a literal under one and escapes the
|
|
65
|
+
* quote under the other). A MySQL profile is read with its `/*!NNNNN … *\/` bodies as code and as
|
|
66
|
+
* comments, since a server older than NNNNN skips them, and a Postgres-family one with both
|
|
67
|
+
* comment ends.
|
|
68
|
+
*/
|
|
69
|
+
export function tenantStatementReadings(dialect) {
|
|
70
|
+
const known = dialect?.id !== undefined ? lexerProfileFor(dialect) : undefined;
|
|
71
|
+
const profiles = known
|
|
72
|
+
? known.id === "postgres" ? [known, asCockroachDb(known)] : [known]
|
|
73
|
+
: ENGINE_LEXER_PROFILES.flatMap((p) => p.id === "postgres" || p.id === "mysql" ? [p, { ...p, backslashEscapes: !p.backslashEscapes }] : [p]);
|
|
74
|
+
return bothExecutableCommentReadings(profiles);
|
|
75
|
+
}
|
|
76
|
+
/** Printable ASCII, the only characters a name can hold for the checks to match it by spelling everywhere. */
|
|
77
|
+
const PRINTABLE_ASCII = /^[\x20-\x7e]*$/;
|
|
78
|
+
/**
|
|
79
|
+
* The first name in `read`, quoted or not, that holds a character outside printable ASCII and isn't
|
|
80
|
+
* one of `known` exactly, as written; undefined when there is none. SQL Server resolves a name
|
|
81
|
+
* through the database collation, which ignores some characters (U+200D, U+FEFF, any emoji) and
|
|
82
|
+
* reads others as ASCII letters (fullwidth letters, small capitals), so such a name can reach a
|
|
83
|
+
* table or column that the tenant and sensitive checks, which match names by spelling, never see.
|
|
84
|
+
* Read under `sqlserver`, and under any id that is no built-in engine or no dialect, each reading
|
|
85
|
+
* from {@link tenantStatementReadings}; the other built-in engines compare such a name as written.
|
|
86
|
+
*/
|
|
87
|
+
export function unmatchedNonAsciiName(read, dialect, known) {
|
|
88
|
+
if (dialect !== undefined && isBuiltInDialectId(dialect.id) && dialect.id !== "sqlserver")
|
|
89
|
+
return undefined;
|
|
90
|
+
for (const profile of tenantStatementReadings(dialect)) {
|
|
91
|
+
for (const token of read.tokens(profile)) {
|
|
92
|
+
const name = token.kind === "word" ? token.text : token.kind === "quoted_identifier" ? token.value : undefined;
|
|
93
|
+
if (name !== undefined && !PRINTABLE_ASCII.test(name) && !known.has(name))
|
|
94
|
+
return token.text;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
/** Whether `profile` may be SQLite's reading: SQLite's own, or the generic and standard ones a dialect that may be SQLite gets. */
|
|
100
|
+
export function mayBeSqlite(profile) {
|
|
101
|
+
return profile.id === "sqlite" || profile.id === "generic";
|
|
102
|
+
}
|
|
103
|
+
/** Words that start a clause other than `FROM`, so a `,` after one is in no `FROM` list. */
|
|
104
|
+
const NOT_A_FROM_LIST = new Set(["select", "where", "group", "having", "order", "limit", "offset", "window", "values", "returning", "union", "intersect", "except", "set", "with"]);
|
|
105
|
+
/**
|
|
106
|
+
* The indices in `tokens` of the `'…'` strings that SQLite reads as names. SQLite takes a
|
|
107
|
+
* single-quoted literal where only a name may stand as that name, so `FROM 'orders'` reads the
|
|
108
|
+
* table `orders`. A string counts right after `FROM`, `JOIN` or `IN` (`IN 'orders'` reads a table),
|
|
109
|
+
* after a `,` in a `FROM` list, or beside a `.` (`main.'orders'`, `'main'.orders`). With
|
|
110
|
+
* `withAliases`, it also counts right after a table or CTE reference, or after `AS` following one,
|
|
111
|
+
* where it is that read's alias (`FROM orders 'o'`). The tenant and sensitive checks read such a
|
|
112
|
+
* string as a quoted name under a reading that {@link mayBeSqlite may be SQLite's}; only the tenant
|
|
113
|
+
* check reads aliases, since the sensitive check already fails closed on a qualifier it can't bind.
|
|
114
|
+
*/
|
|
115
|
+
export function stringsSqliteReadsAsNames(tokens, withAliases) {
|
|
116
|
+
const code = [...tokens.keys()].filter((i) => tokens[i].kind !== "comment");
|
|
117
|
+
const at = (j) => (j >= 0 && j < code.length ? tokens[code[j]] : undefined);
|
|
118
|
+
const is = (t, kind, text) => t?.kind === kind && t.lower === text;
|
|
119
|
+
const inFromList = (comma) => {
|
|
120
|
+
let depth = 0;
|
|
121
|
+
for (let j = comma - 1; j >= 0; j--) {
|
|
122
|
+
const t = at(j);
|
|
123
|
+
if (is(t, "punct", ")"))
|
|
124
|
+
depth++;
|
|
125
|
+
else if (is(t, "punct", "(")) {
|
|
126
|
+
if (depth === 0)
|
|
127
|
+
return false;
|
|
128
|
+
depth--;
|
|
129
|
+
}
|
|
130
|
+
else if (depth === 0 && is(t, "punct", ";"))
|
|
131
|
+
return false;
|
|
132
|
+
else if (depth === 0 && t.kind === "word") {
|
|
133
|
+
if (t.lower === "from")
|
|
134
|
+
return true;
|
|
135
|
+
if (NOT_A_FROM_LIST.has(t.lower))
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return false;
|
|
140
|
+
};
|
|
141
|
+
const names = new Set();
|
|
142
|
+
/** Positions in `code` of the names that stand in a table's place: `FROM orders`, `JOIN main.orders`. */
|
|
143
|
+
const sources = new Set();
|
|
144
|
+
for (let j = 0; j < code.length; j++) {
|
|
145
|
+
const t = at(j);
|
|
146
|
+
const prev = at(j - 1);
|
|
147
|
+
const plainString = t.kind === "string" && t.text.startsWith("'");
|
|
148
|
+
const startsSource = is(prev, "word", "from") || is(prev, "word", "join") ||
|
|
149
|
+
(is(prev, "punct", ",") && inFromList(j - 1)) || (is(prev, "punct", ".") && sources.has(j - 2));
|
|
150
|
+
if (startsSource && (t.kind === "word" || t.kind === "quoted_identifier" || plainString))
|
|
151
|
+
sources.add(j);
|
|
152
|
+
if (!plainString)
|
|
153
|
+
continue;
|
|
154
|
+
const alias = withAliases && (sources.has(j - 1) || (is(prev, "word", "as") && sources.has(j - 2)));
|
|
155
|
+
const named = alias || is(prev, "punct", ".") || is(at(j + 1), "punct", ".") ||
|
|
156
|
+
is(prev, "word", "from") || is(prev, "word", "join") || is(prev, "word", "in") ||
|
|
157
|
+
(is(prev, "punct", ",") && inFromList(j - 1));
|
|
158
|
+
if (named)
|
|
159
|
+
names.add(code[j]);
|
|
160
|
+
}
|
|
161
|
+
return names;
|
|
162
|
+
}
|
|
163
|
+
/** A view that nests block comments, also read with them ending at the first `*\/`. */
|
|
164
|
+
function bothBlockCommentReadings(profiles) {
|
|
165
|
+
return profiles.flatMap((p) => (p.nestedBlockComments ? [p, { ...p, nestedBlockComments: false }] : [p]));
|
|
166
|
+
}
|
|
167
|
+
/** A profile that reads `/*!NNNNN … *\/` bodies as code, also read with them as comments. */
|
|
168
|
+
function bothExecutableCommentReadings(profiles) {
|
|
169
|
+
return profiles.flatMap((p) => (p.executableComments ? [p, { ...p, executableComments: false }] : [p]));
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* The tenant check's standard view: how its own scanner split code from strings and comments, kept
|
|
173
|
+
* as a lexer profile (#815). `'…'` strings with `''` escapes; `"…"`, `` `…` `` and `[…]` quoted
|
|
174
|
+
* names under every engine, where the first `]` ends a bracketed name; `--` and `/* *\/` comments,
|
|
175
|
+
* never nested, where a line comment ends only at `\n`.
|
|
176
|
+
*/
|
|
177
|
+
const STANDARD_VIEW = {
|
|
178
|
+
id: "generic",
|
|
179
|
+
backslashEscapes: false,
|
|
180
|
+
doubleQuote: "identifier",
|
|
181
|
+
backtickIdentifiers: true,
|
|
182
|
+
bracketIdentifiers: "simple",
|
|
183
|
+
dollarQuotes: false,
|
|
184
|
+
escapeStrings: false,
|
|
185
|
+
hashComments: false,
|
|
186
|
+
dashCommentNeedsSpace: false,
|
|
187
|
+
nestedBlockComments: false,
|
|
188
|
+
executableComments: false,
|
|
189
|
+
atVariables: false,
|
|
190
|
+
hashIdentifiers: false,
|
|
191
|
+
tsqlLiterals: false,
|
|
192
|
+
lineCommentEndsAtLfOnly: true,
|
|
193
|
+
strictStringPrefixes: true,
|
|
194
|
+
};
|
|
195
|
+
/**
|
|
196
|
+
* The standard view, with Postgres `E'…'` escape strings, `$tag$…$tag$` strings, and block comments
|
|
197
|
+
* nested the way Postgres and CockroachDB nest them.
|
|
198
|
+
*/
|
|
199
|
+
const POSTGRES_VIEW = {
|
|
200
|
+
...STANDARD_VIEW,
|
|
201
|
+
id: "postgres",
|
|
202
|
+
escapeStrings: true,
|
|
203
|
+
dollarQuotes: true,
|
|
204
|
+
nestedBlockComments: true,
|
|
205
|
+
};
|
|
206
|
+
/**
|
|
207
|
+
* The standard view, read the MySQL way: `"…"` is a string, backslash escapes, `#` comments, `--`
|
|
208
|
+
* only before whitespace, and `/*! … *\/` bodies as code.
|
|
209
|
+
*/
|
|
210
|
+
const MYSQL_VIEW = {
|
|
211
|
+
...STANDARD_VIEW,
|
|
212
|
+
id: "mysql",
|
|
213
|
+
doubleQuote: "string",
|
|
214
|
+
backslashEscapes: true,
|
|
215
|
+
hashComments: true,
|
|
216
|
+
dashCommentNeedsSpace: true,
|
|
217
|
+
executableComments: true,
|
|
218
|
+
};
|
|
219
|
+
/** A view whose line comments end at `\r` too, as on Postgres and SQL Server. */
|
|
220
|
+
const crEnds = (view) => ({ ...view, lineCommentEndsAtLfOnly: false });
|
|
221
|
+
/**
|
|
222
|
+
* The standard view, read the SQL Server way (#623): a line comment ends at `\r` too, a name or
|
|
223
|
+
* number ends where SQL Server ends it (`tsqlLiterals`: after `0x` or `1E`, and at whitespace such
|
|
224
|
+
* as U+00A0, U+200B and U+0085), so a keyword written right after one is a word of its own, `]]`
|
|
225
|
+
* inside a bracketed name is an escaped `]`, and block comments nest.
|
|
226
|
+
*/
|
|
227
|
+
const SQLSERVER_VIEW = {
|
|
228
|
+
...crEnds(STANDARD_VIEW),
|
|
229
|
+
id: "sqlserver",
|
|
230
|
+
tsqlLiterals: true,
|
|
231
|
+
bracketIdentifiers: "doubled",
|
|
232
|
+
nestedBlockComments: true,
|
|
233
|
+
};
|
|
234
|
+
/**
|
|
235
|
+
* The tenant check's predicate readings. A built-in engine gets its own view: the MySQL one twice,
|
|
236
|
+
* with executable comments as code and as comments; the Postgres one with both comment ends; the
|
|
237
|
+
* SQL Server one. The Postgres and SQL Server views nest block comments, and each is read again with
|
|
238
|
+
* them not nested. Backslash escapes follow the spec as the lexer reads it: an unset value on MySQL
|
|
239
|
+
* or MariaDB is the server default, on. Any other id, or none, gets the standard and Postgres views
|
|
240
|
+
* with both comment ends, the MySQL view and the SQL Server view: a table counts as referenced when
|
|
241
|
+
* any of them sees it, a predicate only when every one does.
|
|
242
|
+
*/
|
|
243
|
+
export function tenantCodeReadings(dialect) {
|
|
244
|
+
const views = (profiles) => bothExecutableCommentReadings(bothBlockCommentReadings(profiles)).map((profile) => ({ profile, pipesAreOr: profile.id === "mysql" }));
|
|
245
|
+
if (dialect === undefined || !isBuiltInDialectId(dialect.id)) {
|
|
246
|
+
return views([STANDARD_VIEW, crEnds(STANDARD_VIEW), POSTGRES_VIEW, crEnds(POSTGRES_VIEW), MYSQL_VIEW, SQLSERVER_VIEW]);
|
|
247
|
+
}
|
|
248
|
+
const mysqlFamily = dialect.id === "mysql" || dialect.id === "mariadb";
|
|
249
|
+
const backslashEscapes = mysqlFamily ? dialect.backslashEscapes !== false : dialect.backslashEscapes === true;
|
|
250
|
+
const engine = mysqlFamily
|
|
251
|
+
? [MYSQL_VIEW]
|
|
252
|
+
: dialect.id === "postgres" || dialect.id === "cockroachdb"
|
|
253
|
+
? [POSTGRES_VIEW, crEnds(POSTGRES_VIEW)]
|
|
254
|
+
: dialect.id === "sqlserver"
|
|
255
|
+
? [SQLSERVER_VIEW]
|
|
256
|
+
: [STANDARD_VIEW];
|
|
257
|
+
return views(engine.map((view) => ({ ...view, backslashEscapes })));
|
|
258
|
+
}
|
|
259
|
+
/** Placeholder scans (`tokenizeSqlSpans`): the engine's profile, or the generic one. */
|
|
260
|
+
export function placeholderReading(dialect) {
|
|
261
|
+
return (dialect && lexerProfileFor(dialect)) ?? GENERIC_LEXER;
|
|
262
|
+
}
|
|
263
|
+
/** Read `sql`: {@link SqlRead.tokens} lexes it under a profile the first time it is asked for one. */
|
|
264
|
+
export function readSql(sql) {
|
|
265
|
+
const lexed = new Map();
|
|
266
|
+
return {
|
|
267
|
+
sql,
|
|
268
|
+
tokens(profile) {
|
|
269
|
+
const key = profileKey(profile);
|
|
270
|
+
let tokens = lexed.get(key);
|
|
271
|
+
if (tokens === undefined) {
|
|
272
|
+
tokens = lexSql(sql, profile);
|
|
273
|
+
lexed.set(key, tokens);
|
|
274
|
+
}
|
|
275
|
+
return tokens;
|
|
276
|
+
},
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
/** Two profiles with the same rules lex the same tokens. */
|
|
280
|
+
function profileKey(p) {
|
|
281
|
+
return [
|
|
282
|
+
p.id, p.backslashEscapes, p.doubleQuote, p.backtickIdentifiers, p.bracketIdentifiers, p.dollarQuotes,
|
|
283
|
+
p.escapeStrings, p.hashComments, p.dashCommentNeedsSpace, p.nestedBlockComments, p.executableComments,
|
|
284
|
+
p.atVariables, p.hashIdentifiers, p.tsqlLiterals, p.lineCommentEndsAtLfOnly === true, p.strictStringPrefixes === true,
|
|
285
|
+
].join("|");
|
|
286
|
+
}
|
|
287
|
+
//# sourceMappingURL=readings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"readings.js","sourceRoot":"","sources":["../../src/sql/readings.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema-question-precheck.d.ts","sourceRoot":"","sources":["../../src/sql/schema-question-precheck.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAS9D,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,mBAAmB,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"schema-question-precheck.d.ts","sourceRoot":"","sources":["../../src/sql/schema-question-precheck.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAS9D,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,mBAAmB,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAOvF;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,mBAAmB,GAAG,MAAM,EAAE,CAwB7F"}
|
|
@@ -5,10 +5,10 @@ const JOIN_RELATION_HINT = /\b(join|joined|joins|inner\s+join|left\s+join|right\
|
|
|
5
5
|
const FROM_JOIN_TABLE = /\b(?:from|join)\s+([a-zA-Z_][\w]*)\b/gi;
|
|
6
6
|
export function assertNlToSqlInputs(schema, question) {
|
|
7
7
|
if (schema.tables.length === 0) {
|
|
8
|
-
throw new AskDbError("Schema has no tables.
|
|
8
|
+
throw new AskDbError("Schema has no tables. Pass a `schema` with at least one table.");
|
|
9
9
|
}
|
|
10
10
|
if (!question.trim()) {
|
|
11
|
-
throw new AskDbError("Question is empty. Pass a non-blank natural-language question
|
|
11
|
+
throw new AskDbError("Question is empty. Pass a non-blank natural-language `question`.");
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema-question-precheck.js","sourceRoot":"","sources":["../../src/sql/schema-question-precheck.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAEzD,MAAM,kBAAkB,GACtB,oMAAoM,CAAC;AAEvM,oGAAoG;AACpG,MAAM,eAAe,GAAG,wCAAwC,CAAC;AAEjE,MAAM,UAAU,mBAAmB,CAAC,MAA2B,EAAE,QAAgB;IAC/E,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,UAAU,
|
|
1
|
+
{"version":3,"file":"schema-question-precheck.js","sourceRoot":"","sources":["../../src/sql/schema-question-precheck.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAEzD,MAAM,kBAAkB,GACtB,oMAAoM,CAAC;AAEvM,oGAAoG;AACpG,MAAM,eAAe,GAAG,wCAAwC,CAAC;AAEjE,MAAM,UAAU,mBAAmB,CAAC,MAA2B,EAAE,QAAgB;IAC/E,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,UAAU,CAAC,gEAAgE,CAAC,CAAC;IACzF,CAAC;IACD,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC;QACrB,MAAM,IAAI,UAAU,CAAC,kEAAkE,CAAC,CAAC;IAC3F,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,qBAAqB,CAAC,QAAgB,EAAE,MAA2B;IACjF,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,wEAAwE;IACxE,MAAM,OAAO,GAAG,UAAU,IAAI,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC;IAC5F,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;IAErE,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,kBAAkB,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC9D,KAAK,CAAC,IAAI,CACR,kIAAkI,CACnI,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,CAAC,IAAI,QAAQ,CAAC,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC;QACnD,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC;QAClB,MAAM,GAAG,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC;QAC9B,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,SAAS;QAC5B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACd,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YACzB,KAAK,CAAC,IAAI,CAAC,gCAAgC,GAAG,+BAA+B,CAAC,CAAC;QACjF,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -64,4 +64,9 @@ export declare function scanSensitiveReferences(sql: string, schema: AnyNormaliz
|
|
|
64
64
|
*/
|
|
65
65
|
export declare function schemaHasSensitiveIdentifiers(schema: AnyNormalizedSchema): boolean;
|
|
66
66
|
export { formatSensitiveReference };
|
|
67
|
+
/**
|
|
68
|
+
* Every table, namespace and column name in `schema`, exactly as it spells them. Internal to
|
|
69
|
+
* `@askdb/core`: the tenant check counts a name outside printable ASCII only when it is one of them.
|
|
70
|
+
*/
|
|
71
|
+
export declare function schemaNames(schema: AnyNormalizedSchema): ReadonlySet<string>;
|
|
67
72
|
//# sourceMappingURL=sensitive-guardrail.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sensitive-guardrail.d.ts","sourceRoot":"","sources":["../../src/sql/sensitive-guardrail.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,kBAAkB,EAEvB,KAAK,oBAAoB,EAC1B,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAU,wBAAwB,EAAoC,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"sensitive-guardrail.d.ts","sourceRoot":"","sources":["../../src/sql/sensitive-guardrail.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,kBAAkB,EAEvB,KAAK,oBAAoB,EAC1B,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAU,wBAAwB,EAAoC,MAAM,uBAAuB,CAAC;AAI3G,MAAM,MAAM,sBAAsB,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEvD,MAAM,MAAM,kCAAkC,GAAG;IAC/C;;;;OAIG;IACH,IAAI,CAAC,EAAE,sBAAsB,CAAC;IAC9B;;;;;OAKG;IACH,OAAO,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,GAAG,kBAAkB,CAAC,CAAC;CACxD,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACrC,8FAA8F;IAC9F,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,kBAAkB,EAAE,CAAC;IACjC,8FAA8F;IAC9F,eAAe,CAAC,EAAE,oBAAoB,CAAC;CACxC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,2BAA2B,CACzC,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,mBAAmB,EAC3B,OAAO,CAAC,EAAE,kCAAkC,GAC3C,wBAAwB,CAM1B;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,mBAAmB,EAC3B,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,GAAG,kBAAkB,CAAC,GAAG,SAAS,GAChE,wBAAwB,CAG1B;AAED;;;GAGG;AACH,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAElF;AAED,OAAO,EAAE,wBAAwB,EAAE,CAAC;AAoFpC;;;GAGG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,mBAAmB,GAAG,WAAW,CAAC,MAAM,CAAC,CAE5E"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { decide, formatSensitiveReference, sensitiveFindings, throwIfDenied } from "./guardrail-decide.js";
|
|
2
|
-
import {
|
|
2
|
+
import { findPostgresOnlyForms, hasUnterminatedToken } from "./lexer.js";
|
|
3
|
+
import { isKnownEngine, mayBeSqlite, readSql, sensitiveReadings, stringsSqliteReadsAsNames, unmatchedNonAsciiName } from "./readings.js";
|
|
3
4
|
/**
|
|
4
5
|
* Check whether a SQL statement references tables/columns marked `sensitive`
|
|
5
6
|
* in the schema artifact.
|
|
@@ -88,17 +89,38 @@ function indexSchema(schema) {
|
|
|
88
89
|
else
|
|
89
90
|
byName.set(entry.lower, [entry]);
|
|
90
91
|
});
|
|
91
|
-
const
|
|
92
|
+
const names = new Set();
|
|
93
|
+
for (const t of tables) {
|
|
94
|
+
names.add(t.name);
|
|
95
|
+
if (t.schema !== undefined)
|
|
96
|
+
names.add(t.schema);
|
|
97
|
+
for (const c of t.columns)
|
|
98
|
+
names.add(c.name);
|
|
99
|
+
}
|
|
100
|
+
const index = { tables, byName, hasSensitive, names };
|
|
92
101
|
indexCache.set(schema, index);
|
|
93
102
|
return index;
|
|
94
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Every table, namespace and column name in `schema`, exactly as it spells them. Internal to
|
|
106
|
+
* `@askdb/core`: the tenant check counts a name outside printable ASCII only when it is one of them.
|
|
107
|
+
*/
|
|
108
|
+
export function schemaNames(schema) {
|
|
109
|
+
return indexSchema(schema).names;
|
|
110
|
+
}
|
|
95
111
|
/**
|
|
96
112
|
* Map lexer tokens onto the scanner's simpler vocabulary. Comments are dropped (MySQL
|
|
97
|
-
* `/*! … *\/` bodies are already lexed as code).
|
|
113
|
+
* `/*! … *\/` bodies are already lexed as code). A `'…'` at an index in `names` is a quoted name,
|
|
114
|
+
* as SQLite reads `FROM 'users'`.
|
|
98
115
|
*/
|
|
99
|
-
function toToks(tokens) {
|
|
116
|
+
function toToks(tokens, names) {
|
|
100
117
|
const out = [];
|
|
101
|
-
for (const t of tokens) {
|
|
118
|
+
for (const [index, t] of tokens.entries()) {
|
|
119
|
+
if (names.has(index)) {
|
|
120
|
+
const inner = t.text.slice(1, t.unterminated ? undefined : -1).replaceAll("''", "'");
|
|
121
|
+
out.push({ kind: "word", value: inner, lower: inner.toLowerCase(), quoted: true });
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
102
124
|
switch (t.kind) {
|
|
103
125
|
case "comment":
|
|
104
126
|
break;
|
|
@@ -219,28 +241,25 @@ function selectModifiersFor(dialect) {
|
|
|
219
241
|
}
|
|
220
242
|
}
|
|
221
243
|
/**
|
|
222
|
-
* Lex and scan. With a known dialect the statement is
|
|
223
|
-
* (a Postgres-family one also the way CockroachDB ends a line
|
|
224
|
-
* found there added). Without one (or for an unrecognized id) the
|
|
225
|
-
* issues, and references are unioned across every engine's reading
|
|
244
|
+
* Lex and scan, under the readings `sensitiveReadings` gives. With a known dialect the statement is
|
|
245
|
+
* lexed the way that engine reads it (a Postgres-family one also the way CockroachDB ends a line
|
|
246
|
+
* comment, with the references found there added). Without one (or for an unrecognized id) the
|
|
247
|
+
* generic reading decides scope issues, and references are unioned across every engine's reading
|
|
248
|
+
* that lexes cleanly —
|
|
226
249
|
* so a column hidden from one reading by an engine-specific quote or comment rule (for
|
|
227
250
|
* example MySQL `'\''` or Postgres `E'\''`) is still reported. Readings with an
|
|
228
251
|
* unterminated token are skipped: the engine would reject that SQL outright.
|
|
229
252
|
*/
|
|
230
253
|
function scanSql(sql, index, dialect) {
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
? ENGINE_LEXER_PROFILES
|
|
238
|
-
: profile.id === "postgres" ? [{ ...profile, lineCommentEndsAtLfOnly: true }] : [];
|
|
239
|
-
for (const p of others) {
|
|
240
|
-
const tokens = lexSql(sql, p);
|
|
254
|
+
const read = readSql(sql);
|
|
255
|
+
const readings = sensitiveReadings(dialect);
|
|
256
|
+
const modifiers = dialect && isKnownEngine(dialect) ? selectModifiersFor(dialect) : SELECT_MODIFIERS.any;
|
|
257
|
+
const primary = scanTokens(read.tokens(readings.primary), index, modifiers, mayBeSqlite(readings.primary));
|
|
258
|
+
for (const p of readings.union) {
|
|
259
|
+
const tokens = read.tokens(p);
|
|
241
260
|
if (hasUnterminatedToken(tokens))
|
|
242
261
|
continue;
|
|
243
|
-
for (const [key, ref] of scanTokens(tokens, index, modifiers).found) {
|
|
262
|
+
for (const [key, ref] of scanTokens(tokens, index, modifiers, mayBeSqlite(p)).found) {
|
|
244
263
|
const existing = primary.found.get(key);
|
|
245
264
|
if (!existing)
|
|
246
265
|
primary.found.set(key, ref);
|
|
@@ -248,7 +267,12 @@ function scanSql(sql, index, dialect) {
|
|
|
248
267
|
existing.matchKind = "qualified";
|
|
249
268
|
}
|
|
250
269
|
}
|
|
251
|
-
|
|
270
|
+
const outcome = withPostgresOnlyIssues(primary, sql, dialect);
|
|
271
|
+
// SQL Server's collation may read a name outside printable ASCII as a sensitive column's, which
|
|
272
|
+
// the scan above matches by spelling: unless the schema spells it exactly, fail closed.
|
|
273
|
+
if (unmatchedNonAsciiName(read, dialect, index.names) !== undefined)
|
|
274
|
+
outcome.issues.add("UNICODE_ESCAPED_IDENTIFIER");
|
|
275
|
+
return finalize(outcome);
|
|
252
276
|
}
|
|
253
277
|
/**
|
|
254
278
|
* `outcome` with a scope issue for each form only the Postgres reading can hold: a `U&"…"` name,
|
|
@@ -278,8 +302,9 @@ function finalize(outcome) {
|
|
|
278
302
|
...(unresolvedScope ? { unresolvedScope } : {}),
|
|
279
303
|
};
|
|
280
304
|
}
|
|
281
|
-
function scanTokens(lexed, index, modifiers) {
|
|
282
|
-
|
|
305
|
+
function scanTokens(lexed, index, modifiers, sqlite) {
|
|
306
|
+
// A reading that may be SQLite's reads a '…' in a table's place as the table's name.
|
|
307
|
+
const tokens = toToks(lexed, sqlite ? stringsSqliteReadsAsNames(lexed, false) : new Set());
|
|
283
308
|
const isWord = (t) => t?.kind === "word";
|
|
284
309
|
const isPunct = (t, v) => t?.kind === "punct" && t.value === v;
|
|
285
310
|
const isKeywordTok = (t, v) => t?.kind === "word" && !t.quoted && t.lower === v;
|
|
@@ -864,7 +889,7 @@ const SCOPE_ISSUE_TEXT = {
|
|
|
864
889
|
UNKNOWN_QUALIFIER: "a qualified reference used a qualifier that is not a known table, alias, or CTE",
|
|
865
890
|
OPAQUE_TABLE_SOURCE: "a table source is not a relation name (table function or table-valued expression)",
|
|
866
891
|
UNTERMINATED_TOKEN: "a string, quoted identifier, or comment never closes, so the rest of the statement could not be scanned",
|
|
867
|
-
UNICODE_ESCAPED_IDENTIFIER: "a Unicode-escaped name (U&\"…\")
|
|
892
|
+
UNICODE_ESCAPED_IDENTIFIER: "a name can't be matched against sensitive columns as written: a Unicode-escaped name (U&\"…\"), which Postgres decodes, or a name outside printable ASCII that the schema doesn't spell, which SQL Server's collation may read as another",
|
|
868
893
|
STRING_CONTINUATION: "an E'…' string continues on a later line, which Postgres reads with backslash escapes, so where the string ends is unclear",
|
|
869
894
|
ESCAPED_BYTE_STRING: "a b'…' byte string holds a backslash or continues on a later line, which CockroachDB reads with backslash escapes, so where the string ends is unclear",
|
|
870
895
|
};
|