@carllee1983/dbcli 1.47.1 → 1.48.0
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/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/CHANGELOG.md +67 -2
- package/dist/cli.mjs +949 -99
- package/dist/core.d.ts +69 -3
- package/dist/core.mjs +544 -52
- package/gemini-extension.json +1 -1
- package/package.json +3 -2
- package/plugins/dbcli-agent/.codex-plugin/plugin.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -52,7 +52,7 @@ var package_default;
|
|
|
52
52
|
var init_package = __esm(() => {
|
|
53
53
|
package_default = {
|
|
54
54
|
name: "@carllee1983/dbcli",
|
|
55
|
-
version: "1.
|
|
55
|
+
version: "1.48.0",
|
|
56
56
|
description: "Database CLI for AI agents",
|
|
57
57
|
type: "module",
|
|
58
58
|
publishConfig: {
|
|
@@ -99,6 +99,7 @@ var init_package = __esm(() => {
|
|
|
99
99
|
},
|
|
100
100
|
files: [
|
|
101
101
|
"dist/",
|
|
102
|
+
"!dist/.build-stamp",
|
|
102
103
|
"assets/",
|
|
103
104
|
"plugins/",
|
|
104
105
|
"skills/",
|
|
@@ -120,7 +121,7 @@ var init_package = __esm(() => {
|
|
|
120
121
|
"release:check": "bash scripts/release-check.sh",
|
|
121
122
|
"plugin:sync": "bun run scripts/sync-plugin-assets.ts --write",
|
|
122
123
|
"plugin:check": "bun run scripts/sync-plugin-assets.ts",
|
|
123
|
-
test: "bun test",
|
|
124
|
+
test: "bun test --timeout 30000",
|
|
124
125
|
"test:unit": "bun test tests/unit tests/core",
|
|
125
126
|
"test:integration": "bun test tests/integration",
|
|
126
127
|
"test:docker": "docker compose -f docker-compose.test.yml up -d --wait && bun test tests/integration/adapters; docker compose -f docker-compose.test.yml down",
|
|
@@ -9316,6 +9317,14 @@ class BlacklistManager {
|
|
|
9316
9317
|
}
|
|
9317
9318
|
return Array.from(columnSet);
|
|
9318
9319
|
}
|
|
9320
|
+
getAllBlacklistedColumns() {
|
|
9321
|
+
const all = new Set;
|
|
9322
|
+
for (const columnSet of this.state.columns.values()) {
|
|
9323
|
+
for (const column of columnSet)
|
|
9324
|
+
all.add(column);
|
|
9325
|
+
}
|
|
9326
|
+
return Array.from(all);
|
|
9327
|
+
}
|
|
9319
9328
|
canOverrideBlacklist() {
|
|
9320
9329
|
return this.overrideEnabled;
|
|
9321
9330
|
}
|
|
@@ -9780,6 +9789,35 @@ var init_redis = __esm(() => {
|
|
|
9780
9789
|
};
|
|
9781
9790
|
});
|
|
9782
9791
|
|
|
9792
|
+
// src/utils/sql-lexical.ts
|
|
9793
|
+
function dollarQuoteDelimiterAt(sql, index) {
|
|
9794
|
+
if (sql[index] !== "$")
|
|
9795
|
+
return;
|
|
9796
|
+
if (continuesIdentifier(sql, index))
|
|
9797
|
+
return;
|
|
9798
|
+
return sql.slice(index).match(DOLLAR_QUOTE_DELIMITER)?.[0];
|
|
9799
|
+
}
|
|
9800
|
+
function continuesIdentifier(sql, index) {
|
|
9801
|
+
let start = index;
|
|
9802
|
+
while (start > 0 && IDENTIFIER_CONTINUATION.test(sql[start - 1] ?? ""))
|
|
9803
|
+
start--;
|
|
9804
|
+
if (start === index)
|
|
9805
|
+
return false;
|
|
9806
|
+
const run = sql.slice(start, index);
|
|
9807
|
+
const first = run[0];
|
|
9808
|
+
if (IDENTIFIER_START.test(first))
|
|
9809
|
+
return true;
|
|
9810
|
+
if (first === "$")
|
|
9811
|
+
return false;
|
|
9812
|
+
return run.replace(/^[0-9]+(?:[eE][0-9]+)?/, "").length > 0;
|
|
9813
|
+
}
|
|
9814
|
+
var IDENTIFIER_CONTINUATION, IDENTIFIER_START, DOLLAR_QUOTE_DELIMITER;
|
|
9815
|
+
var init_sql_lexical = __esm(() => {
|
|
9816
|
+
IDENTIFIER_CONTINUATION = /[A-Za-z0-9_$]|[\u0080-\uFFFF]/;
|
|
9817
|
+
IDENTIFIER_START = /[A-Za-z_]|[\u0080-\uFFFF]/;
|
|
9818
|
+
DOLLAR_QUOTE_DELIMITER = /^\$(?:(?:[A-Za-z_]|[\u0080-\uFFFF])(?:[A-Za-z0-9_]|[\u0080-\uFFFF])*)?\$/;
|
|
9819
|
+
});
|
|
9820
|
+
|
|
9783
9821
|
// src/core/permission-guard.ts
|
|
9784
9822
|
var exports_permission_guard = {};
|
|
9785
9823
|
__export(exports_permission_guard, {
|
|
@@ -9873,8 +9911,7 @@ function stripCommentsAndStrings2(sql, options = {}) {
|
|
|
9873
9911
|
continue;
|
|
9874
9912
|
}
|
|
9875
9913
|
if (options.dialect === "postgresql" && char === "$") {
|
|
9876
|
-
const
|
|
9877
|
-
const delimiter = opensToken ? sql.slice(i).match(/^\$(?:[A-Za-z_][A-Za-z0-9_]*)?\$/)?.[0] : undefined;
|
|
9914
|
+
const delimiter = dollarQuoteDelimiterAt(sql, i);
|
|
9878
9915
|
if (delimiter) {
|
|
9879
9916
|
i += delimiter.length;
|
|
9880
9917
|
const closingIndex = sql.indexOf(delimiter, i);
|
|
@@ -10348,8 +10385,9 @@ function checkElasticsearchPermission(classification, permission) {
|
|
|
10348
10385
|
reason: `Elasticsearch ${classification.type} operation requires higher permission tier`
|
|
10349
10386
|
};
|
|
10350
10387
|
}
|
|
10351
|
-
var PermissionError,
|
|
10388
|
+
var PermissionError, SQL_DIALECTS, SQL_WRITE_OR_DDL_KEYWORDS, SQL_LOCK_CLAUSE, ESCALATABLE_READ_TYPES, REDIS_COMMAND_PERMISSION, PERMISSION_RANK;
|
|
10352
10389
|
var init_permission_guard = __esm(() => {
|
|
10390
|
+
init_sql_lexical();
|
|
10353
10391
|
PermissionError = class PermissionError extends Error {
|
|
10354
10392
|
classification;
|
|
10355
10393
|
requiredPermission;
|
|
@@ -10361,7 +10399,6 @@ var init_permission_guard = __esm(() => {
|
|
|
10361
10399
|
Object.setPrototypeOf(this, PermissionError.prototype);
|
|
10362
10400
|
}
|
|
10363
10401
|
};
|
|
10364
|
-
IDENTIFIER_CONTINUATION = /[A-Za-z0-9_$]|[\u0080-\uFFFF]/;
|
|
10365
10402
|
SQL_DIALECTS = ["postgresql", "mysql", "mariadb"];
|
|
10366
10403
|
SQL_WRITE_OR_DDL_KEYWORDS = /(?<![.\w])(INSERT|UPDATE|DELETE|MERGE|UPSERT|REPLACE|TRUNCATE|DROP|ALTER|CREATE|GRANT|REVOKE|RENAME|INTO)\b(?!\s*\()/i;
|
|
10367
10404
|
SQL_LOCK_CLAUSE = /\bFOR\s+(?:NO\s+KEY\s+)?UPDATE\b|\bFOR\s+(?:KEY\s+)?SHARE\b/gi;
|
|
@@ -26063,22 +26100,27 @@ var init_size_guard2 = __esm(() => {
|
|
|
26063
26100
|
init_types4();
|
|
26064
26101
|
});
|
|
26065
26102
|
|
|
26066
|
-
// src/
|
|
26103
|
+
// src/utils/glob.ts
|
|
26067
26104
|
function globToRegex(glob) {
|
|
26068
26105
|
let out = "^";
|
|
26069
26106
|
for (let i = 0;i < glob.length; i++) {
|
|
26070
26107
|
const c2 = glob[i];
|
|
26108
|
+
if (c2 === "\\" && i + 1 < glob.length) {
|
|
26109
|
+
out += glob[++i].replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
26110
|
+
continue;
|
|
26111
|
+
}
|
|
26071
26112
|
if (c2 === "*")
|
|
26072
26113
|
out += ".*";
|
|
26073
26114
|
else if (c2 === "?")
|
|
26074
26115
|
out += ".";
|
|
26075
26116
|
else if (c2 === "[") {
|
|
26076
|
-
const end = glob
|
|
26077
|
-
|
|
26078
|
-
|
|
26079
|
-
|
|
26080
|
-
out += glob.slice(i, end + 1);
|
|
26117
|
+
const end = findClassEnd(glob, i);
|
|
26118
|
+
const body = end === -1 ? "" : glob.slice(i, end + 1);
|
|
26119
|
+
if (body !== "" && isValidCharacterClass(body)) {
|
|
26120
|
+
out += body;
|
|
26081
26121
|
i = end;
|
|
26122
|
+
} else {
|
|
26123
|
+
out += "\\[";
|
|
26082
26124
|
}
|
|
26083
26125
|
} else if (".^$+(){}|\\".includes(c2)) {
|
|
26084
26126
|
out += "\\" + c2;
|
|
@@ -26089,6 +26131,27 @@ function globToRegex(glob) {
|
|
|
26089
26131
|
out += "$";
|
|
26090
26132
|
return new RegExp(out);
|
|
26091
26133
|
}
|
|
26134
|
+
function findClassEnd(glob, open2) {
|
|
26135
|
+
for (let i = open2 + 1;i < glob.length; i++) {
|
|
26136
|
+
if (glob[i] === "\\") {
|
|
26137
|
+
i++;
|
|
26138
|
+
continue;
|
|
26139
|
+
}
|
|
26140
|
+
if (glob[i] === "]")
|
|
26141
|
+
return i;
|
|
26142
|
+
}
|
|
26143
|
+
return -1;
|
|
26144
|
+
}
|
|
26145
|
+
function isValidCharacterClass(body) {
|
|
26146
|
+
try {
|
|
26147
|
+
new RegExp(body);
|
|
26148
|
+
return true;
|
|
26149
|
+
} catch {
|
|
26150
|
+
return false;
|
|
26151
|
+
}
|
|
26152
|
+
}
|
|
26153
|
+
|
|
26154
|
+
// src/adapters/redis/blacklist-enforcer.ts
|
|
26092
26155
|
function patternsOverlap(a, b) {
|
|
26093
26156
|
const ra = globToRegex(a);
|
|
26094
26157
|
const rb = globToRegex(b);
|
|
@@ -30374,28 +30437,161 @@ var init_field_projection = __esm(() => {
|
|
|
30374
30437
|
UNSAFE_SEGMENTS = new Set(["__proto__", "prototype", "constructor"]);
|
|
30375
30438
|
});
|
|
30376
30439
|
|
|
30440
|
+
// src/utils/es-index-target.ts
|
|
30441
|
+
function normalizeEsPath(path5) {
|
|
30442
|
+
let current = path5;
|
|
30443
|
+
for (let pass = 0;pass < MAX_DECODE_PASSES; pass++) {
|
|
30444
|
+
let decoded = current;
|
|
30445
|
+
try {
|
|
30446
|
+
decoded = decodeURIComponent(current);
|
|
30447
|
+
} catch {}
|
|
30448
|
+
const segments = [];
|
|
30449
|
+
for (const segment of decoded.split("/")) {
|
|
30450
|
+
if (segment === "." || segment === "")
|
|
30451
|
+
continue;
|
|
30452
|
+
if (segment === "..") {
|
|
30453
|
+
segments.pop();
|
|
30454
|
+
continue;
|
|
30455
|
+
}
|
|
30456
|
+
segments.push(segment);
|
|
30457
|
+
}
|
|
30458
|
+
const resolved = `/${segments.join("/")}`;
|
|
30459
|
+
if (resolved === current)
|
|
30460
|
+
return resolved;
|
|
30461
|
+
current = resolved;
|
|
30462
|
+
}
|
|
30463
|
+
return current;
|
|
30464
|
+
}
|
|
30465
|
+
function unwrap(target) {
|
|
30466
|
+
let current = target;
|
|
30467
|
+
for (let pass = 0;pass < MAX_DECODE_PASSES; pass++) {
|
|
30468
|
+
let next = current;
|
|
30469
|
+
try {
|
|
30470
|
+
next = decodeURIComponent(next);
|
|
30471
|
+
} catch {}
|
|
30472
|
+
if (next.startsWith("<") && next.endsWith(">")) {
|
|
30473
|
+
next = next.slice(1, -1).replace(/\{[^}]*\}/g, "*");
|
|
30474
|
+
}
|
|
30475
|
+
if (next === current)
|
|
30476
|
+
return current;
|
|
30477
|
+
current = next;
|
|
30478
|
+
}
|
|
30479
|
+
return current;
|
|
30480
|
+
}
|
|
30481
|
+
function expandIndexTargets(target) {
|
|
30482
|
+
const concrete = [];
|
|
30483
|
+
const wildcards = [];
|
|
30484
|
+
const add = (candidate) => {
|
|
30485
|
+
if (candidate.length === 0)
|
|
30486
|
+
return;
|
|
30487
|
+
if (/[*?]/.test(candidate) || candidate.toLowerCase() === "_all")
|
|
30488
|
+
wildcards.push(candidate);
|
|
30489
|
+
else
|
|
30490
|
+
concrete.push(candidate);
|
|
30491
|
+
};
|
|
30492
|
+
for (const rawPart of unwrap(target).split(",")) {
|
|
30493
|
+
const part = unwrap(rawPart.trim()).trim().replace(/^[-+]/, "");
|
|
30494
|
+
if (part.length === 0)
|
|
30495
|
+
continue;
|
|
30496
|
+
add(part);
|
|
30497
|
+
if (part.includes(":"))
|
|
30498
|
+
for (const section of part.split(":"))
|
|
30499
|
+
add(section);
|
|
30500
|
+
}
|
|
30501
|
+
return { concrete, wildcards };
|
|
30502
|
+
}
|
|
30503
|
+
function matchesIndexGlob(pattern, name2) {
|
|
30504
|
+
const normalized = pattern.toLowerCase() === "_all" ? "*" : pattern.toLowerCase();
|
|
30505
|
+
try {
|
|
30506
|
+
return globToRegex(normalized).test(name2.toLowerCase());
|
|
30507
|
+
} catch {
|
|
30508
|
+
return true;
|
|
30509
|
+
}
|
|
30510
|
+
}
|
|
30511
|
+
function reachesByConvention(name2, entry) {
|
|
30512
|
+
const lower = name2.toLowerCase();
|
|
30513
|
+
const target = entry.toLowerCase();
|
|
30514
|
+
return new RegExp(`^\\.ds-${escapeRegExp(target)}-`).test(lower) || new RegExp(`^${escapeRegExp(target)}-\\d+$`).test(lower);
|
|
30515
|
+
}
|
|
30516
|
+
function escapeRegExp(text2) {
|
|
30517
|
+
return text2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
30518
|
+
}
|
|
30519
|
+
function indexExpressionReaches(expression, blacklisted) {
|
|
30520
|
+
if (blacklisted.length === 0)
|
|
30521
|
+
return false;
|
|
30522
|
+
const { concrete, wildcards } = expandIndexTargets(expression);
|
|
30523
|
+
return concrete.some((name2) => blacklisted.some((entry) => entry.toLowerCase() === name2.toLowerCase() || reachesByConvention(name2, entry))) || wildcards.some((pattern) => blacklisted.some((entry) => matchesIndexGlob(pattern, entry)));
|
|
30524
|
+
}
|
|
30525
|
+
var MAX_DECODE_PASSES = 4;
|
|
30526
|
+
var init_es_index_target = () => {};
|
|
30527
|
+
|
|
30377
30528
|
// src/core/blacklist-validator.ts
|
|
30529
|
+
function dedupe2(values) {
|
|
30530
|
+
const seen = new Set;
|
|
30531
|
+
const result = [];
|
|
30532
|
+
for (const value of values) {
|
|
30533
|
+
const key = value.toLowerCase();
|
|
30534
|
+
if (value.length === 0 || seen.has(key))
|
|
30535
|
+
continue;
|
|
30536
|
+
seen.add(key);
|
|
30537
|
+
result.push(value);
|
|
30538
|
+
}
|
|
30539
|
+
return result;
|
|
30540
|
+
}
|
|
30541
|
+
|
|
30378
30542
|
class BlacklistValidator {
|
|
30379
30543
|
manager;
|
|
30380
30544
|
constructor(manager) {
|
|
30381
30545
|
this.manager = manager;
|
|
30382
30546
|
}
|
|
30383
|
-
checkTableBlacklist(operation, tableName,
|
|
30547
|
+
checkTableBlacklist(operation, tableName, tableList = []) {
|
|
30548
|
+
this.checkTablesBlacklist(operation, [tableName, ...tableList]);
|
|
30549
|
+
}
|
|
30550
|
+
checkTablesBlacklist(operation, tableNames) {
|
|
30551
|
+
const tables = dedupe2(tableNames);
|
|
30552
|
+
if (tables.length === 0) {
|
|
30553
|
+
return;
|
|
30554
|
+
}
|
|
30384
30555
|
if (this.manager.canOverrideBlacklist()) {
|
|
30385
|
-
const
|
|
30556
|
+
const message2 = t_vars("warnings.blacklist_override_used", {
|
|
30386
30557
|
operation,
|
|
30387
|
-
table:
|
|
30558
|
+
table: tables.join(", ")
|
|
30388
30559
|
});
|
|
30389
|
-
console.error(
|
|
30560
|
+
console.error(message2);
|
|
30390
30561
|
return;
|
|
30391
30562
|
}
|
|
30392
|
-
|
|
30393
|
-
|
|
30394
|
-
|
|
30395
|
-
operation
|
|
30396
|
-
});
|
|
30397
|
-
throw new BlacklistError(message, tableName, operation);
|
|
30563
|
+
const blocked = tables.filter((table) => this.manager.isTableBlacklisted(table));
|
|
30564
|
+
if (blocked.length === 0) {
|
|
30565
|
+
return;
|
|
30398
30566
|
}
|
|
30567
|
+
const message = t_vars("errors.table_blacklisted", {
|
|
30568
|
+
table: blocked.join(", "),
|
|
30569
|
+
operation
|
|
30570
|
+
});
|
|
30571
|
+
throw new BlacklistError(message, blocked[0], operation);
|
|
30572
|
+
}
|
|
30573
|
+
checkIndexBlacklist(operation, target) {
|
|
30574
|
+
const { concrete, wildcards } = expandIndexTargets(target);
|
|
30575
|
+
this.checkTablesBlacklist(operation, concrete);
|
|
30576
|
+
if (wildcards.length === 0 || this.manager.canOverrideBlacklist())
|
|
30577
|
+
return;
|
|
30578
|
+
const blacklisted = Array.from(this.manager.getState().tables);
|
|
30579
|
+
if (blacklisted.length === 0)
|
|
30580
|
+
return;
|
|
30581
|
+
const reachable = wildcards.filter((pattern) => blacklisted.some((entry) => matchesIndexGlob(pattern, entry)));
|
|
30582
|
+
if (reachable.length === 0)
|
|
30583
|
+
return;
|
|
30584
|
+
const message = t_vars("errors.table_blacklisted", {
|
|
30585
|
+
table: reachable.join(", "),
|
|
30586
|
+
operation
|
|
30587
|
+
});
|
|
30588
|
+
throw new BlacklistError(message, reachable[0], operation);
|
|
30589
|
+
}
|
|
30590
|
+
filterColumnsForIndexExpression(target, rows, columnList) {
|
|
30591
|
+
const { concrete, wildcards } = expandIndexTargets(target);
|
|
30592
|
+
const ruleKeys = Array.from(this.manager.getState().columns.keys());
|
|
30593
|
+
const reachable = ruleKeys.filter((key) => wildcards.some((pattern) => matchesIndexGlob(pattern, key)));
|
|
30594
|
+
return this.filterColumnsForTables([...concrete, ...reachable], rows, columnList);
|
|
30399
30595
|
}
|
|
30400
30596
|
checkColumnBlacklistOnWrite(tableName, fields, operation = "WRITE") {
|
|
30401
30597
|
const blacklisted = this.manager.getBlacklistedColumns(tableName);
|
|
@@ -30422,7 +30618,11 @@ class BlacklistValidator {
|
|
|
30422
30618
|
throw new BlacklistError(message, tableName, operation);
|
|
30423
30619
|
}
|
|
30424
30620
|
filterColumns(tableName, rows, columnList) {
|
|
30425
|
-
|
|
30621
|
+
return this.filterColumnsForTables([tableName], rows, columnList);
|
|
30622
|
+
}
|
|
30623
|
+
filterColumnsForTables(tableNames, rows, columnList) {
|
|
30624
|
+
const tables = dedupe2(tableNames);
|
|
30625
|
+
const blacklistedColumns = tables.length === 0 ? this.manager.getAllBlacklistedColumns() : Array.from(new Set(tables.flatMap((table) => this.manager.getBlacklistedColumns(table))));
|
|
30426
30626
|
if (blacklistedColumns.length === 0) {
|
|
30427
30627
|
return { filteredRows: rows, omittedColumns: [] };
|
|
30428
30628
|
}
|
|
@@ -30446,6 +30646,7 @@ var init_blacklist_validator = __esm(() => {
|
|
|
30446
30646
|
init_blacklist();
|
|
30447
30647
|
init_message_loader();
|
|
30448
30648
|
init_field_projection();
|
|
30649
|
+
init_es_index_target();
|
|
30449
30650
|
});
|
|
30450
30651
|
|
|
30451
30652
|
// src/core/verification/types.ts
|
|
@@ -34433,6 +34634,355 @@ async function openInBrowser(target) {
|
|
|
34433
34634
|
}
|
|
34434
34635
|
var init_opener = () => {};
|
|
34435
34636
|
|
|
34637
|
+
// src/utils/sql-tables.ts
|
|
34638
|
+
var exports_sql_tables = {};
|
|
34639
|
+
__export(exports_sql_tables, {
|
|
34640
|
+
extractTableReferences: () => extractTableReferences
|
|
34641
|
+
});
|
|
34642
|
+
function tokenize(sql, dialect, backslashEscapes) {
|
|
34643
|
+
const tokens = [];
|
|
34644
|
+
const mysqlDialect = dialect === "mysql" || dialect === "mariadb";
|
|
34645
|
+
let i = 0;
|
|
34646
|
+
while (i < sql.length) {
|
|
34647
|
+
const char = sql[i];
|
|
34648
|
+
const dashFollowerCode = sql.charCodeAt(i + 2);
|
|
34649
|
+
if (char === "-" && sql[i + 1] === "-" && (!mysqlDialect || sql[i + 2] === undefined || dashFollowerCode <= 32 || dashFollowerCode === 127)) {
|
|
34650
|
+
while (i < sql.length && sql[i] !== `
|
|
34651
|
+
`)
|
|
34652
|
+
i++;
|
|
34653
|
+
continue;
|
|
34654
|
+
}
|
|
34655
|
+
if (mysqlDialect && char === "#") {
|
|
34656
|
+
while (i < sql.length && sql[i] !== `
|
|
34657
|
+
`)
|
|
34658
|
+
i++;
|
|
34659
|
+
continue;
|
|
34660
|
+
}
|
|
34661
|
+
if (char === "/" && sql[i + 1] === "*") {
|
|
34662
|
+
if (mysqlDialect && (sql.startsWith("/*!", i) || sql.startsWith("/*M!", i))) {
|
|
34663
|
+
const prefixLength = sql.startsWith("/*M!", i) ? 4 : 3;
|
|
34664
|
+
const closingIndex = sql.indexOf("*/", i + prefixLength);
|
|
34665
|
+
const bodyEnd = closingIndex === -1 ? sql.length : closingIndex;
|
|
34666
|
+
const body = sql.slice(i + prefixLength, bodyEnd).replace(/^\d+/, " ");
|
|
34667
|
+
tokens.push(...tokenize(body, dialect, backslashEscapes));
|
|
34668
|
+
i = closingIndex === -1 ? sql.length : closingIndex + 2;
|
|
34669
|
+
continue;
|
|
34670
|
+
}
|
|
34671
|
+
const nests = dialect === "postgresql";
|
|
34672
|
+
let depth = 1;
|
|
34673
|
+
i += 2;
|
|
34674
|
+
while (i < sql.length && depth > 0) {
|
|
34675
|
+
if (nests && sql[i] === "/" && sql[i + 1] === "*") {
|
|
34676
|
+
depth++;
|
|
34677
|
+
i += 2;
|
|
34678
|
+
continue;
|
|
34679
|
+
}
|
|
34680
|
+
if (sql[i] === "*" && sql[i + 1] === "/") {
|
|
34681
|
+
depth--;
|
|
34682
|
+
i += 2;
|
|
34683
|
+
continue;
|
|
34684
|
+
}
|
|
34685
|
+
i++;
|
|
34686
|
+
}
|
|
34687
|
+
continue;
|
|
34688
|
+
}
|
|
34689
|
+
if (dialect === "postgresql" && char === "$") {
|
|
34690
|
+
const delimiter = dollarQuoteDelimiterAt(sql, i);
|
|
34691
|
+
if (delimiter) {
|
|
34692
|
+
i += delimiter.length;
|
|
34693
|
+
const closingIndex = sql.indexOf(delimiter, i);
|
|
34694
|
+
i = closingIndex === -1 ? sql.length : closingIndex + delimiter.length;
|
|
34695
|
+
continue;
|
|
34696
|
+
}
|
|
34697
|
+
}
|
|
34698
|
+
if (char === "'") {
|
|
34699
|
+
i++;
|
|
34700
|
+
while (i < sql.length) {
|
|
34701
|
+
if (sql[i] === "'") {
|
|
34702
|
+
if (sql[i + 1] === "'") {
|
|
34703
|
+
i += 2;
|
|
34704
|
+
continue;
|
|
34705
|
+
}
|
|
34706
|
+
i++;
|
|
34707
|
+
break;
|
|
34708
|
+
}
|
|
34709
|
+
if (backslashEscapes && sql[i] === "\\") {
|
|
34710
|
+
i += 2;
|
|
34711
|
+
continue;
|
|
34712
|
+
}
|
|
34713
|
+
i++;
|
|
34714
|
+
}
|
|
34715
|
+
continue;
|
|
34716
|
+
}
|
|
34717
|
+
if (char === '"' || char === "`") {
|
|
34718
|
+
const quote = char;
|
|
34719
|
+
i++;
|
|
34720
|
+
let value = "";
|
|
34721
|
+
while (i < sql.length) {
|
|
34722
|
+
if (sql[i] === quote) {
|
|
34723
|
+
if (sql[i + 1] === quote) {
|
|
34724
|
+
value += quote;
|
|
34725
|
+
i += 2;
|
|
34726
|
+
continue;
|
|
34727
|
+
}
|
|
34728
|
+
i++;
|
|
34729
|
+
break;
|
|
34730
|
+
}
|
|
34731
|
+
if (backslashEscapes && sql[i] === "\\") {
|
|
34732
|
+
value += sql[i + 1] ?? "";
|
|
34733
|
+
i += 2;
|
|
34734
|
+
continue;
|
|
34735
|
+
}
|
|
34736
|
+
value += sql[i];
|
|
34737
|
+
i++;
|
|
34738
|
+
}
|
|
34739
|
+
tokens.push({ value, kind: "identifier", quoted: true });
|
|
34740
|
+
continue;
|
|
34741
|
+
}
|
|
34742
|
+
if (IDENTIFIER_START2.test(char)) {
|
|
34743
|
+
let value = "";
|
|
34744
|
+
while (i < sql.length && IDENTIFIER_PART.test(sql[i])) {
|
|
34745
|
+
value += sql[i];
|
|
34746
|
+
i++;
|
|
34747
|
+
}
|
|
34748
|
+
tokens.push({ value, kind: "identifier", quoted: false });
|
|
34749
|
+
continue;
|
|
34750
|
+
}
|
|
34751
|
+
if (char === "." || char === "," || char === "(" || char === ")" || char === ";") {
|
|
34752
|
+
tokens.push({ value: char, kind: "punctuation", quoted: false });
|
|
34753
|
+
i++;
|
|
34754
|
+
continue;
|
|
34755
|
+
}
|
|
34756
|
+
i++;
|
|
34757
|
+
}
|
|
34758
|
+
return tokens;
|
|
34759
|
+
}
|
|
34760
|
+
function isKeyword(token, keywords) {
|
|
34761
|
+
if (!token || token.kind !== "identifier" || token.quoted)
|
|
34762
|
+
return false;
|
|
34763
|
+
return keywords.has(token.value.toUpperCase());
|
|
34764
|
+
}
|
|
34765
|
+
function isPunctuation(token, value) {
|
|
34766
|
+
return token?.kind === "punctuation" && token.value === value;
|
|
34767
|
+
}
|
|
34768
|
+
function readQualifiedName(tokens, index) {
|
|
34769
|
+
const first = tokens[index];
|
|
34770
|
+
if (!first || first.kind !== "identifier")
|
|
34771
|
+
return null;
|
|
34772
|
+
const parts = [first.value];
|
|
34773
|
+
let cursor = index + 1;
|
|
34774
|
+
while (isPunctuation(tokens[cursor], ".") && tokens[cursor + 1]?.kind === "identifier") {
|
|
34775
|
+
parts.push(tokens[cursor + 1].value);
|
|
34776
|
+
cursor += 2;
|
|
34777
|
+
}
|
|
34778
|
+
return { parts, next: cursor };
|
|
34779
|
+
}
|
|
34780
|
+
function decodedVariants(value) {
|
|
34781
|
+
const decoded = value.replace(ANY_ESCAPE_SEQUENCE, (whole, _escape, long, short) => fromCodePointOrRaw(long ?? short, whole));
|
|
34782
|
+
return decoded === value ? [] : [decoded];
|
|
34783
|
+
}
|
|
34784
|
+
function fromCodePointOrRaw(hex, whole) {
|
|
34785
|
+
const codePoint = parseInt(hex, 16);
|
|
34786
|
+
return codePoint <= 1114111 ? String.fromCodePoint(codePoint) : whole;
|
|
34787
|
+
}
|
|
34788
|
+
function extractTableReferences(sql, options = {}) {
|
|
34789
|
+
const seen = new Set;
|
|
34790
|
+
const references = [];
|
|
34791
|
+
const record = (name2) => {
|
|
34792
|
+
const key = name2.toLowerCase();
|
|
34793
|
+
if (name2.length === 0 || seen.has(key))
|
|
34794
|
+
return;
|
|
34795
|
+
seen.add(key);
|
|
34796
|
+
references.push(name2);
|
|
34797
|
+
};
|
|
34798
|
+
const dialects = options.dialect ? [options.dialect] : ["postgresql", "mysql", undefined];
|
|
34799
|
+
for (const dialect of dialects) {
|
|
34800
|
+
for (const backslashEscapes of [false, true]) {
|
|
34801
|
+
collectReferences(tokenize(sql, dialect, backslashEscapes), record);
|
|
34802
|
+
}
|
|
34803
|
+
}
|
|
34804
|
+
return references;
|
|
34805
|
+
}
|
|
34806
|
+
function collectReferences(tokens, record) {
|
|
34807
|
+
const recordName = (parts) => {
|
|
34808
|
+
const bare = parts[parts.length - 1];
|
|
34809
|
+
record(bare);
|
|
34810
|
+
for (const variant of decodedVariants(bare))
|
|
34811
|
+
record(variant);
|
|
34812
|
+
if (parts.length > 1)
|
|
34813
|
+
record(parts.join("."));
|
|
34814
|
+
};
|
|
34815
|
+
let i = 0;
|
|
34816
|
+
while (i < tokens.length) {
|
|
34817
|
+
if (!isKeyword(tokens[i], TABLE_INTRODUCERS)) {
|
|
34818
|
+
i++;
|
|
34819
|
+
continue;
|
|
34820
|
+
}
|
|
34821
|
+
const introducer = tokens[i].value.toUpperCase();
|
|
34822
|
+
const parenMeansFunction = introducer === "FROM" || introducer === "JOIN";
|
|
34823
|
+
let cursor = i + 1;
|
|
34824
|
+
while (isKeyword(tokens[cursor], PRE_TABLE_NOISE))
|
|
34825
|
+
cursor++;
|
|
34826
|
+
while (parenMeansFunction && isPunctuation(tokens[cursor], "(") && !isKeyword(tokens[cursor + 1], SUBQUERY_OPENERS) && tokens[cursor + 1]?.kind === "identifier") {
|
|
34827
|
+
cursor++;
|
|
34828
|
+
}
|
|
34829
|
+
let expectTable = true;
|
|
34830
|
+
while (expectTable) {
|
|
34831
|
+
expectTable = false;
|
|
34832
|
+
const name2 = readQualifiedName(tokens, cursor);
|
|
34833
|
+
if (!name2)
|
|
34834
|
+
break;
|
|
34835
|
+
const isFunctionCall = parenMeansFunction && isPunctuation(tokens[name2.next], "(");
|
|
34836
|
+
if (!isFunctionCall)
|
|
34837
|
+
recordName(name2.parts);
|
|
34838
|
+
cursor = name2.next;
|
|
34839
|
+
if (isFunctionCall)
|
|
34840
|
+
break;
|
|
34841
|
+
if (isKeyword(tokens[cursor], AS_KEYWORD))
|
|
34842
|
+
cursor++;
|
|
34843
|
+
if (tokens[cursor]?.kind === "identifier" && !isKeyword(tokens[cursor], POST_TABLE_KEYWORDS)) {
|
|
34844
|
+
cursor++;
|
|
34845
|
+
}
|
|
34846
|
+
while (isPunctuation(tokens[cursor], "(")) {
|
|
34847
|
+
let depth = 0;
|
|
34848
|
+
do {
|
|
34849
|
+
if (isPunctuation(tokens[cursor], "("))
|
|
34850
|
+
depth++;
|
|
34851
|
+
else if (isPunctuation(tokens[cursor], ")"))
|
|
34852
|
+
depth--;
|
|
34853
|
+
cursor++;
|
|
34854
|
+
} while (depth > 0 && cursor < tokens.length);
|
|
34855
|
+
if (tokens[cursor]?.kind === "identifier" && !isKeyword(tokens[cursor], POST_TABLE_KEYWORDS))
|
|
34856
|
+
cursor++;
|
|
34857
|
+
}
|
|
34858
|
+
if (isPunctuation(tokens[cursor], ",")) {
|
|
34859
|
+
cursor++;
|
|
34860
|
+
expectTable = true;
|
|
34861
|
+
}
|
|
34862
|
+
}
|
|
34863
|
+
i = Math.max(cursor, i + 1);
|
|
34864
|
+
}
|
|
34865
|
+
let index = 0;
|
|
34866
|
+
while (index < tokens.length) {
|
|
34867
|
+
const token = tokens[index];
|
|
34868
|
+
if (!token || token.kind !== "identifier") {
|
|
34869
|
+
index++;
|
|
34870
|
+
continue;
|
|
34871
|
+
}
|
|
34872
|
+
const name2 = readQualifiedName(tokens, index);
|
|
34873
|
+
for (let part = 0;part < name2.parts.length; part++) {
|
|
34874
|
+
const value = name2.parts[part];
|
|
34875
|
+
const isQuoted = tokens[index + part * 2]?.quoted === true;
|
|
34876
|
+
if (isQuoted || !RESERVED_KEYWORDS.has(value.toUpperCase())) {
|
|
34877
|
+
record(value);
|
|
34878
|
+
for (const variant of decodedVariants(value))
|
|
34879
|
+
record(variant);
|
|
34880
|
+
}
|
|
34881
|
+
}
|
|
34882
|
+
if (name2.parts.length > 1)
|
|
34883
|
+
record(name2.parts.join("."));
|
|
34884
|
+
index = name2.next;
|
|
34885
|
+
}
|
|
34886
|
+
}
|
|
34887
|
+
var TABLE_INTRODUCERS, PRE_TABLE_NOISE, SUBQUERY_OPENERS, AS_KEYWORD, POST_TABLE_KEYWORDS, RESERVED_KEYWORDS, IDENTIFIER_START2, IDENTIFIER_PART, ANY_ESCAPE_SEQUENCE;
|
|
34888
|
+
var init_sql_tables = __esm(() => {
|
|
34889
|
+
init_sql_lexical();
|
|
34890
|
+
TABLE_INTRODUCERS = new Set([
|
|
34891
|
+
"FROM",
|
|
34892
|
+
"JOIN",
|
|
34893
|
+
"INTO",
|
|
34894
|
+
"UPDATE",
|
|
34895
|
+
"TABLE",
|
|
34896
|
+
"TRUNCATE",
|
|
34897
|
+
"COPY",
|
|
34898
|
+
"USING",
|
|
34899
|
+
"STRAIGHT_JOIN"
|
|
34900
|
+
]);
|
|
34901
|
+
PRE_TABLE_NOISE = new Set(["ONLY", "LATERAL", "TABLE"]);
|
|
34902
|
+
SUBQUERY_OPENERS = new Set(["SELECT", "WITH", "VALUES", "TABLE"]);
|
|
34903
|
+
AS_KEYWORD = new Set(["AS"]);
|
|
34904
|
+
POST_TABLE_KEYWORDS = new Set([
|
|
34905
|
+
"AS",
|
|
34906
|
+
"ON",
|
|
34907
|
+
"USING",
|
|
34908
|
+
"WHERE",
|
|
34909
|
+
"GROUP",
|
|
34910
|
+
"ORDER",
|
|
34911
|
+
"HAVING",
|
|
34912
|
+
"LIMIT",
|
|
34913
|
+
"OFFSET",
|
|
34914
|
+
"FETCH",
|
|
34915
|
+
"WINDOW",
|
|
34916
|
+
"UNION",
|
|
34917
|
+
"INTERSECT",
|
|
34918
|
+
"EXCEPT",
|
|
34919
|
+
"JOIN",
|
|
34920
|
+
"INNER",
|
|
34921
|
+
"LEFT",
|
|
34922
|
+
"RIGHT",
|
|
34923
|
+
"FULL",
|
|
34924
|
+
"OUTER",
|
|
34925
|
+
"CROSS",
|
|
34926
|
+
"NATURAL",
|
|
34927
|
+
"STRAIGHT_JOIN",
|
|
34928
|
+
"SET",
|
|
34929
|
+
"VALUES",
|
|
34930
|
+
"SELECT",
|
|
34931
|
+
"RETURNING",
|
|
34932
|
+
"FOR",
|
|
34933
|
+
"INTO",
|
|
34934
|
+
"PARTITION",
|
|
34935
|
+
"WITH",
|
|
34936
|
+
"TABLESAMPLE",
|
|
34937
|
+
"FORCE",
|
|
34938
|
+
"IGNORE",
|
|
34939
|
+
"USE"
|
|
34940
|
+
]);
|
|
34941
|
+
RESERVED_KEYWORDS = new Set([
|
|
34942
|
+
"ALL",
|
|
34943
|
+
"AND",
|
|
34944
|
+
"AS",
|
|
34945
|
+
"ASC",
|
|
34946
|
+
"CASE",
|
|
34947
|
+
"CROSS",
|
|
34948
|
+
"DESC",
|
|
34949
|
+
"DISTINCT",
|
|
34950
|
+
"ELSE",
|
|
34951
|
+
"FALSE",
|
|
34952
|
+
"FOR",
|
|
34953
|
+
"FROM",
|
|
34954
|
+
"GROUP",
|
|
34955
|
+
"HAVING",
|
|
34956
|
+
"IN",
|
|
34957
|
+
"INNER",
|
|
34958
|
+
"INTO",
|
|
34959
|
+
"IS",
|
|
34960
|
+
"JOIN",
|
|
34961
|
+
"LEFT",
|
|
34962
|
+
"LIKE",
|
|
34963
|
+
"LIMIT",
|
|
34964
|
+
"NOT",
|
|
34965
|
+
"NULL",
|
|
34966
|
+
"ON",
|
|
34967
|
+
"OR",
|
|
34968
|
+
"ORDER",
|
|
34969
|
+
"OUTER",
|
|
34970
|
+
"RIGHT",
|
|
34971
|
+
"SELECT",
|
|
34972
|
+
"THEN",
|
|
34973
|
+
"TRUE",
|
|
34974
|
+
"UNION",
|
|
34975
|
+
"USING",
|
|
34976
|
+
"VALUES",
|
|
34977
|
+
"WHEN",
|
|
34978
|
+
"WHERE",
|
|
34979
|
+
"WITH"
|
|
34980
|
+
]);
|
|
34981
|
+
IDENTIFIER_START2 = /[A-Za-z_\u0080-\uFFFF]/;
|
|
34982
|
+
IDENTIFIER_PART = /[A-Za-z0-9_$\u0080-\uFFFF]/;
|
|
34983
|
+
ANY_ESCAPE_SEQUENCE = /([^0-9a-fA-F+'"\s])(?:\+([0-9a-fA-F]{6})|([0-9a-fA-F]{4}))/g;
|
|
34984
|
+
});
|
|
34985
|
+
|
|
34436
34986
|
// src/core/limits.ts
|
|
34437
34987
|
var DEFAULT_QUERY_ONLY_LIMIT = 1000;
|
|
34438
34988
|
|
|
@@ -34486,11 +35036,9 @@ class QueryExecutor {
|
|
|
34486
35036
|
}
|
|
34487
35037
|
}
|
|
34488
35038
|
}
|
|
34489
|
-
|
|
34490
|
-
|
|
34491
|
-
|
|
34492
|
-
this.blacklistValidator.checkTableBlacklist(classification.type, tableName, []);
|
|
34493
|
-
}
|
|
35039
|
+
const referencedTables = this.blacklistValidator ? extractTableReferences(sql, { dialect: this.resolveDialect() }) : [];
|
|
35040
|
+
if (this.blacklistValidator && referencedTables.length > 0) {
|
|
35041
|
+
this.blacklistValidator.checkTablesBlacklist(classification.type, referencedTables);
|
|
34494
35042
|
}
|
|
34495
35043
|
const resultData = await this.adapter.execute(executeSql);
|
|
34496
35044
|
const executionTimeMs = Math.round(performance.now() - start);
|
|
@@ -34503,15 +35051,12 @@ class QueryExecutor {
|
|
|
34503
35051
|
let securityNotification;
|
|
34504
35052
|
let omittedColumns = [];
|
|
34505
35053
|
if (this.blacklistValidator) {
|
|
34506
|
-
const
|
|
34507
|
-
|
|
34508
|
-
|
|
34509
|
-
|
|
34510
|
-
|
|
34511
|
-
|
|
34512
|
-
columnNames = columnNames.filter((col) => !filterResult.omittedColumns.includes(col));
|
|
34513
|
-
securityNotification = this.blacklistValidator.buildSecurityNotification(tableName, filterResult.omittedColumns);
|
|
34514
|
-
}
|
|
35054
|
+
const filterResult = this.blacklistValidator.filterColumnsForTables(referencedTables, rows, columnNames);
|
|
35055
|
+
filteredRows = filterResult.filteredRows;
|
|
35056
|
+
if (filterResult.omittedColumns.length > 0) {
|
|
35057
|
+
omittedColumns = filterResult.omittedColumns;
|
|
35058
|
+
columnNames = columnNames.filter((col) => !filterResult.omittedColumns.includes(col));
|
|
35059
|
+
securityNotification = this.blacklistValidator.buildSecurityNotification(referencedTables[0] ?? "", filterResult.omittedColumns);
|
|
34515
35060
|
}
|
|
34516
35061
|
}
|
|
34517
35062
|
if (options?.fieldSelection) {
|
|
@@ -34617,13 +35162,30 @@ function inferColumnType(value) {
|
|
|
34617
35162
|
var init_query_executor = __esm(() => {
|
|
34618
35163
|
init_permission_guard();
|
|
34619
35164
|
init_error_suggester();
|
|
34620
|
-
|
|
35165
|
+
init_sql_tables();
|
|
34621
35166
|
init_applied_limit();
|
|
34622
35167
|
init_integration_helper();
|
|
34623
35168
|
init_field_projection();
|
|
34624
35169
|
});
|
|
34625
35170
|
|
|
34626
35171
|
// src/core/mongo/field-masker.ts
|
|
35172
|
+
function maskMongoRowsForCollections(rows, collections, blacklist) {
|
|
35173
|
+
const columns = blacklist.columns ?? {};
|
|
35174
|
+
return collections.reduce((masked, entry) => {
|
|
35175
|
+
const scope = typeof entry === "string" ? { collection: entry } : entry;
|
|
35176
|
+
const atTopLevel = maskMongoRows(masked, scope.collection, blacklist);
|
|
35177
|
+
if (!scope.prefix)
|
|
35178
|
+
return atTopLevel;
|
|
35179
|
+
const rules = columns[scope.collection] ?? findCaseInsensitive(columns, scope.collection);
|
|
35180
|
+
if (!rules || rules.length === 0)
|
|
35181
|
+
return atTopLevel;
|
|
35182
|
+
const prefixKey = `\x00${scope.collection}@${scope.prefix}`;
|
|
35183
|
+
return maskMongoRows(atTopLevel, prefixKey, {
|
|
35184
|
+
...blacklist,
|
|
35185
|
+
columns: { ...columns, [prefixKey]: rules.map((rule) => `${scope.prefix}.${rule}`) }
|
|
35186
|
+
});
|
|
35187
|
+
}, rows);
|
|
35188
|
+
}
|
|
34627
35189
|
function maskMongoRows(rows, collection, blacklist) {
|
|
34628
35190
|
const columns = blacklist.columns ?? {};
|
|
34629
35191
|
const raw = columns[collection] ?? findCaseInsensitive(columns, collection);
|
|
@@ -34676,6 +35238,86 @@ function findCaseInsensitive(columns, name2) {
|
|
|
34676
35238
|
var REDACTED2 = "[REDACTED]";
|
|
34677
35239
|
var init_field_masker = () => {};
|
|
34678
35240
|
|
|
35241
|
+
// src/core/mongo/collection-references.ts
|
|
35242
|
+
function recordTarget(value, found) {
|
|
35243
|
+
if (typeof value === "string") {
|
|
35244
|
+
if (value.length > 0)
|
|
35245
|
+
found.add(value);
|
|
35246
|
+
return;
|
|
35247
|
+
}
|
|
35248
|
+
if (value === null || typeof value !== "object")
|
|
35249
|
+
return;
|
|
35250
|
+
const record = value;
|
|
35251
|
+
for (const field of COLLECTION_FIELDS) {
|
|
35252
|
+
const target = record[field];
|
|
35253
|
+
if (typeof target === "string" && target.length > 0)
|
|
35254
|
+
found.add(target);
|
|
35255
|
+
else if (target !== null && typeof target === "object")
|
|
35256
|
+
recordTarget(target, found);
|
|
35257
|
+
}
|
|
35258
|
+
}
|
|
35259
|
+
function joinPrefix(parent, child) {
|
|
35260
|
+
if (!parent)
|
|
35261
|
+
return child;
|
|
35262
|
+
if (!child)
|
|
35263
|
+
return parent;
|
|
35264
|
+
return `${parent}.${child}`;
|
|
35265
|
+
}
|
|
35266
|
+
function collect2(pipeline, found, scopes, parentPrefix) {
|
|
35267
|
+
if (!Array.isArray(pipeline))
|
|
35268
|
+
return;
|
|
35269
|
+
for (const stage of pipeline) {
|
|
35270
|
+
if (stage === null || typeof stage !== "object")
|
|
35271
|
+
continue;
|
|
35272
|
+
const record = stage;
|
|
35273
|
+
for (const name2 of COLLECTION_STAGES) {
|
|
35274
|
+
if (!Object.prototype.hasOwnProperty.call(record, name2))
|
|
35275
|
+
continue;
|
|
35276
|
+
const stageTargets = new Set;
|
|
35277
|
+
recordTarget(record[name2], stageTargets);
|
|
35278
|
+
for (const collection of stageTargets)
|
|
35279
|
+
found.add(collection);
|
|
35280
|
+
const value = record[name2];
|
|
35281
|
+
const as = value !== null && typeof value === "object" ? value["as"] : undefined;
|
|
35282
|
+
const prefix = joinPrefix(parentPrefix, typeof as === "string" && as ? as : undefined);
|
|
35283
|
+
for (const collection of stageTargets) {
|
|
35284
|
+
scopes.push({ collection, ...prefix ? { prefix } : {} });
|
|
35285
|
+
}
|
|
35286
|
+
}
|
|
35287
|
+
for (const holder of SUB_PIPELINE_HOLDERS2) {
|
|
35288
|
+
const value = record[holder];
|
|
35289
|
+
if (value === undefined || value === null)
|
|
35290
|
+
continue;
|
|
35291
|
+
if (Array.isArray(value)) {
|
|
35292
|
+
collect2(value, found, scopes, parentPrefix);
|
|
35293
|
+
continue;
|
|
35294
|
+
}
|
|
35295
|
+
if (typeof value !== "object")
|
|
35296
|
+
continue;
|
|
35297
|
+
for (const [key, nested] of Object.entries(value)) {
|
|
35298
|
+
const holderPrefix = holder === "$facet" ? joinPrefix(parentPrefix, key) : joinPrefix(parentPrefix, typeof value["as"] === "string" ? value["as"] : undefined);
|
|
35299
|
+
collect2(nested, found, scopes, holderPrefix);
|
|
35300
|
+
}
|
|
35301
|
+
}
|
|
35302
|
+
}
|
|
35303
|
+
}
|
|
35304
|
+
function findMongoCollectionReferences(pipeline) {
|
|
35305
|
+
const found = new Set;
|
|
35306
|
+
collect2(pipeline, found, []);
|
|
35307
|
+
return Array.from(found);
|
|
35308
|
+
}
|
|
35309
|
+
function findMongoCollectionScopes(pipeline) {
|
|
35310
|
+
const scopes = [];
|
|
35311
|
+
collect2(pipeline, new Set, scopes);
|
|
35312
|
+
return scopes;
|
|
35313
|
+
}
|
|
35314
|
+
var COLLECTION_FIELDS, SUB_PIPELINE_HOLDERS2, COLLECTION_STAGES;
|
|
35315
|
+
var init_collection_references = __esm(() => {
|
|
35316
|
+
COLLECTION_FIELDS = ["from", "coll", "into"];
|
|
35317
|
+
SUB_PIPELINE_HOLDERS2 = ["$facet", "$lookup", "$unionWith", "$graphLookup"];
|
|
35318
|
+
COLLECTION_STAGES = ["$lookup", "$unionWith", "$graphLookup", "$out", "$merge"];
|
|
35319
|
+
});
|
|
35320
|
+
|
|
34679
35321
|
// src/core/query-input.ts
|
|
34680
35322
|
function trimOuterWhitespace(value) {
|
|
34681
35323
|
let start = 0;
|
|
@@ -34857,6 +35499,13 @@ var init_query_size_guard = __esm(() => {
|
|
|
34857
35499
|
import crypto3 from "crypto";
|
|
34858
35500
|
import { tmpdir } from "os";
|
|
34859
35501
|
import { join as join23 } from "path";
|
|
35502
|
+
function mongoCollectionRefs(query) {
|
|
35503
|
+
try {
|
|
35504
|
+
return findMongoCollectionScopes(JSON.parse(query));
|
|
35505
|
+
} catch {
|
|
35506
|
+
return [];
|
|
35507
|
+
}
|
|
35508
|
+
}
|
|
34860
35509
|
function requireSqlConnection2(connection) {
|
|
34861
35510
|
if (!["postgresql", "mysql", "mariadb"].includes(connection.system)) {
|
|
34862
35511
|
throw new Error(`This command requires a SQL connection, got: ${connection.system}`);
|
|
@@ -35008,17 +35657,22 @@ async function preflightQuery(query, options, context, fieldSelection, multiConn
|
|
|
35008
35657
|
await preflightSqlSizeGuard(query, options, config);
|
|
35009
35658
|
}
|
|
35010
35659
|
async function preflightSqlSizeGuard(query, options, config) {
|
|
35011
|
-
|
|
35012
|
-
const mainTable = extractTableName2(query);
|
|
35013
|
-
if (!mainTable || !config.schema || options.noLimit)
|
|
35014
|
-
return;
|
|
35015
|
-
const tableSchema = config.schema[mainTable];
|
|
35016
|
-
if (!tableSchema)
|
|
35660
|
+
if (!config.schema || options.noLimit)
|
|
35017
35661
|
return;
|
|
35662
|
+
const { extractTableReferences: extractTableReferences2 } = await Promise.resolve().then(() => (init_sql_tables(), exports_sql_tables));
|
|
35663
|
+
const { SQL_DIALECTS: SQL_DIALECTS2 } = await Promise.resolve().then(() => (init_permission_guard(), exports_permission_guard));
|
|
35664
|
+
const dialect = SQL_DIALECTS2.find((candidate) => candidate === config.connection?.system);
|
|
35665
|
+
const tables = extractTableReferences2(query, { ...dialect ? { dialect } : {} });
|
|
35666
|
+
const schema = config.schema;
|
|
35018
35667
|
const { shouldBlockQuery: shouldBlockQuery2 } = await Promise.resolve().then(() => (init_query_size_guard(), exports_query_size_guard));
|
|
35019
|
-
const
|
|
35020
|
-
|
|
35021
|
-
|
|
35668
|
+
for (const table of tables) {
|
|
35669
|
+
const tableSchema = schema[table];
|
|
35670
|
+
if (!tableSchema)
|
|
35671
|
+
continue;
|
|
35672
|
+
const guard = shouldBlockQuery2(query, tableSchema);
|
|
35673
|
+
if (guard.blocked)
|
|
35674
|
+
throw new Error(`\u26A0 ${guard.reason}`);
|
|
35675
|
+
}
|
|
35022
35676
|
}
|
|
35023
35677
|
async function preflightMongoQuery(query, options, config, multiConnection) {
|
|
35024
35678
|
const collection = options.collection;
|
|
@@ -35044,7 +35698,10 @@ async function preflightMongoQuery(query, options, config, multiConnection) {
|
|
|
35044
35698
|
context: "MongoDB multi-connection pipelines"
|
|
35045
35699
|
});
|
|
35046
35700
|
const blacklistValidator = new BlacklistValidator(new BlacklistManager(config));
|
|
35047
|
-
blacklistValidator.
|
|
35701
|
+
blacklistValidator.checkTablesBlacklist("SELECT", [
|
|
35702
|
+
collection,
|
|
35703
|
+
...findMongoCollectionReferences(parsedQuery)
|
|
35704
|
+
]);
|
|
35048
35705
|
if (!config.schema || options.noLimit)
|
|
35049
35706
|
return;
|
|
35050
35707
|
const tableSchema = config.schema[collection];
|
|
@@ -35068,7 +35725,7 @@ function preflightElasticsearchQuery(query, options, config, multiConnection) {
|
|
|
35068
35725
|
JSON.parse(body);
|
|
35069
35726
|
enforceElasticsearchPermission({ method: "POST", apiPath: `/${indexName}/_search`, body }, multiConnection ? "query-only" : config.permission);
|
|
35070
35727
|
const blacklistValidator = new BlacklistValidator(new BlacklistManager(config));
|
|
35071
|
-
blacklistValidator.
|
|
35728
|
+
blacklistValidator.checkIndexBlacklist("SELECT", indexName);
|
|
35072
35729
|
}
|
|
35073
35730
|
async function executeConnectionQuery(query, options, context, fieldSelection) {
|
|
35074
35731
|
const { config, configPath, connectionName } = context;
|
|
@@ -35221,7 +35878,7 @@ async function mongoQueryBranch(queryStr, options, context, fieldSelection) {
|
|
|
35221
35878
|
const limitedResult = appliedLimit === undefined ? undefined : trimAppliedLimit(result.rows, appliedLimit);
|
|
35222
35879
|
const visibleRows = limitedResult?.rows ?? result.rows;
|
|
35223
35880
|
const blacklistCfg = config.blacklist ?? { tables: [], columns: {} };
|
|
35224
|
-
const maskedRows =
|
|
35881
|
+
const maskedRows = maskMongoRowsForCollections(visibleRows, [collection, ...mongoCollectionRefs(queryStr)], blacklistCfg);
|
|
35225
35882
|
const projected = fieldSelection ? projectRows(maskedRows, fieldSelection) : undefined;
|
|
35226
35883
|
const outputRows = projected?.rows ?? maskedRows;
|
|
35227
35884
|
const columnNames = projected?.columnNames ?? (outputRows[0] ? Object.keys(outputRows[0]) : []);
|
|
@@ -35325,7 +35982,7 @@ async function elasticsearchQueryBranch(queryStr, options, context) {
|
|
|
35325
35982
|
const limitedResult = appliedLimit === undefined ? undefined : trimAppliedLimit(result.rows, appliedLimit);
|
|
35326
35983
|
const visibleRows = limitedResult?.rows ?? result.rows;
|
|
35327
35984
|
const columnNames = visibleRows[0] ? Object.keys(visibleRows[0]) : [];
|
|
35328
|
-
const filterResult = blacklistValidator.
|
|
35985
|
+
const filterResult = blacklistValidator.filterColumnsForIndexExpression(indexName, visibleRows, columnNames);
|
|
35329
35986
|
const queryResult = {
|
|
35330
35987
|
rows: filterResult.filteredRows,
|
|
35331
35988
|
rowCount: filterResult.filteredRows.length,
|
|
@@ -35387,6 +36044,7 @@ var init_query = __esm(() => {
|
|
|
35387
36044
|
init_applied_limit();
|
|
35388
36045
|
init_integration_helper();
|
|
35389
36046
|
init_field_masker();
|
|
36047
|
+
init_collection_references();
|
|
35390
36048
|
init_types4();
|
|
35391
36049
|
init_query_input();
|
|
35392
36050
|
init_field_projection();
|
|
@@ -35950,7 +36608,8 @@ async function qMongoBranch(snippet, prepared, options, config) {
|
|
|
35950
36608
|
});
|
|
35951
36609
|
const blacklistManager = new BlacklistManager(config);
|
|
35952
36610
|
const blacklistValidator = new BlacklistValidator(blacklistManager);
|
|
35953
|
-
|
|
36611
|
+
const collections = [collection, ...findMongoCollectionReferences(parsedBody)];
|
|
36612
|
+
blacklistValidator.checkTablesBlacklist("SELECT", collections);
|
|
35954
36613
|
if (options.dryRun) {
|
|
35955
36614
|
console.log(`Dry-run preview (no execution):`);
|
|
35956
36615
|
console.log(`Collection: ${collection}`);
|
|
@@ -35965,7 +36624,7 @@ async function qMongoBranch(snippet, prepared, options, config) {
|
|
|
35965
36624
|
const result = await adapter.execute(prepared.driver.sql, [collection]);
|
|
35966
36625
|
const executionTimeMs = Math.round(performance.now() - start);
|
|
35967
36626
|
const blacklistCfg = config.blacklist ?? { tables: [], columns: {} };
|
|
35968
|
-
const masked =
|
|
36627
|
+
const masked = maskMongoRowsForCollections(result.rows, [collection, ...findMongoCollectionScopes(parsedBody)], blacklistCfg);
|
|
35969
36628
|
const securityNotification = (blacklistCfg.columns[collection] ?? []).length > 0 ? "Some fields may have been redacted as [REDACTED] per .dbcli blacklist." : undefined;
|
|
35970
36629
|
if (options.ui || options.format === "html") {
|
|
35971
36630
|
const html = await generateHtmlReport({
|
|
@@ -36006,6 +36665,7 @@ var init_q_mongo = __esm(() => {
|
|
|
36006
36665
|
init_adapters();
|
|
36007
36666
|
init_blacklist_validator();
|
|
36008
36667
|
init_field_masker();
|
|
36668
|
+
init_collection_references();
|
|
36009
36669
|
init_formatters();
|
|
36010
36670
|
init_html_formatter();
|
|
36011
36671
|
init_opener();
|
|
@@ -36053,7 +36713,8 @@ async function qCommand(name2, options, command) {
|
|
|
36053
36713
|
config = await configModule.read(configPath);
|
|
36054
36714
|
if (!config.connection)
|
|
36055
36715
|
throw new Error('Run "dbcli init" first');
|
|
36056
|
-
const
|
|
36716
|
+
const connectionSystem = config.connection.system;
|
|
36717
|
+
const engine = mapSystemToEngine(connectionSystem);
|
|
36057
36718
|
const dirs = resolveSnippetDirs(process.cwd());
|
|
36058
36719
|
const map = await loadSnippets(dirs);
|
|
36059
36720
|
const snippet = resolveByName(map, name2, engine);
|
|
@@ -36088,10 +36749,16 @@ async function qCommand(name2, options, command) {
|
|
|
36088
36749
|
const blacklistManager = new BlacklistManager(config);
|
|
36089
36750
|
const blacklistValidator = new BlacklistValidator(blacklistManager);
|
|
36090
36751
|
const family = engineFamily(engine);
|
|
36091
|
-
const
|
|
36752
|
+
const sqlDialect = SQL_DIALECTS.find((dialect) => dialect === connectionSystem);
|
|
36753
|
+
const targets = family === "sql" ? extractTableReferences(prepared.rewrittenSql, {
|
|
36754
|
+
...sqlDialect ? { dialect: sqlDialect } : {}
|
|
36755
|
+
}) : family === "es" ? [prepared.execHints?.index ?? ""] : [];
|
|
36756
|
+
const targetName = targets[0] ?? "";
|
|
36092
36757
|
targetNameForAudit = targetName || name2;
|
|
36093
|
-
if (family
|
|
36094
|
-
blacklistValidator.
|
|
36758
|
+
if (family === "es") {
|
|
36759
|
+
blacklistValidator.checkIndexBlacklist("SELECT", targetName);
|
|
36760
|
+
} else if (family !== "redis" && targets.length > 0) {
|
|
36761
|
+
blacklistValidator.checkTablesBlacklist("SELECT", targets);
|
|
36095
36762
|
}
|
|
36096
36763
|
const adapter = AdapterFactory.createAdapter(config.connection);
|
|
36097
36764
|
await adapter.connect();
|
|
@@ -36106,7 +36773,7 @@ async function qCommand(name2, options, command) {
|
|
|
36106
36773
|
const limitedResult = prepared.guardLimit === undefined ? undefined : trimAppliedLimit(result.rows, prepared.guardLimit);
|
|
36107
36774
|
const resultRows = limitedResult?.rows ?? result.rows;
|
|
36108
36775
|
const columnNames = resultRows[0] ? Object.keys(resultRows[0]) : [];
|
|
36109
|
-
const filtered = family === "redis" ? { filteredRows: resultRows, omittedColumns: [] } : blacklistValidator.
|
|
36776
|
+
const filtered = family === "redis" ? { filteredRows: resultRows, omittedColumns: [] } : family === "es" ? blacklistValidator.filterColumnsForIndexExpression(targetName, resultRows, columnNames) : blacklistValidator.filterColumnsForTables(targets, resultRows, columnNames);
|
|
36110
36777
|
const securityNotification = family === "redis" || filtered.omittedColumns.length === 0 ? undefined : blacklistValidator.buildSecurityNotification(targetName, filtered.omittedColumns);
|
|
36111
36778
|
if (options.ui || options.format === "html") {
|
|
36112
36779
|
const html = await generateHtmlReport({
|
|
@@ -36166,6 +36833,11 @@ async function qCommand(name2, options, command) {
|
|
|
36166
36833
|
} else {
|
|
36167
36834
|
try {
|
|
36168
36835
|
console.error(colors.dim(`Executing verification query: ${verifySpec.query}`));
|
|
36836
|
+
if (family === "sql") {
|
|
36837
|
+
blacklistValidator.checkTablesBlacklist("SELECT", extractTableReferences(verifySpec.query, {
|
|
36838
|
+
...sqlDialect ? { dialect: sqlDialect } : {}
|
|
36839
|
+
}));
|
|
36840
|
+
}
|
|
36169
36841
|
const verifyResult = await adapter.execute(verifySpec.query);
|
|
36170
36842
|
const firstRow = verifyResult.rows[0];
|
|
36171
36843
|
const evalResult = evaluateExpectation(firstRow, verifySpec.expects);
|
|
@@ -36348,7 +37020,7 @@ var init_q = __esm(() => {
|
|
|
36348
37020
|
init_blacklist_validator();
|
|
36349
37021
|
init_blacklist();
|
|
36350
37022
|
init_permission_guard();
|
|
36351
|
-
|
|
37023
|
+
init_sql_tables();
|
|
36352
37024
|
init_formatters();
|
|
36353
37025
|
init_html_formatter();
|
|
36354
37026
|
init_opener();
|
|
@@ -39186,7 +39858,8 @@ async function exportCommand(sql, options, command) {
|
|
|
39186
39858
|
}
|
|
39187
39859
|
const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection6(config.connection));
|
|
39188
39860
|
await adapter.connect();
|
|
39189
|
-
const
|
|
39861
|
+
const blacklistValidator = new BlacklistValidator(new BlacklistManager(config));
|
|
39862
|
+
const executor3 = new QueryExecutor(adapter, config.permission, blacklistValidator, config, {
|
|
39190
39863
|
recovery: options.recovery,
|
|
39191
39864
|
deferDiagnostics: true
|
|
39192
39865
|
});
|
|
@@ -39329,15 +40002,19 @@ async function esExportBranch(query, options, config) {
|
|
|
39329
40002
|
let rowCount;
|
|
39330
40003
|
const diagnostics = [];
|
|
39331
40004
|
try {
|
|
40005
|
+
const declaredTarget = query.trim().startsWith("{") ? options.index ?? options.collection : query.trim();
|
|
40006
|
+
if (declaredTarget)
|
|
40007
|
+
blacklistValidator.checkIndexBlacklist("SELECT", declaredTarget);
|
|
39332
40008
|
const {
|
|
39333
40009
|
rows: fetched,
|
|
39334
40010
|
target,
|
|
39335
40011
|
cap: cap2
|
|
39336
40012
|
} = await buildEsExportRows(query, options, adapter);
|
|
39337
|
-
blacklistValidator.
|
|
40013
|
+
blacklistValidator.checkIndexBlacklist("SELECT", target);
|
|
39338
40014
|
const limitedResult = cap2 === undefined ? undefined : trimAppliedLimit(fetched, cap2);
|
|
39339
40015
|
assertExportNotSilentlyTruncated(limitedResult?.metadata, options);
|
|
39340
|
-
const
|
|
40016
|
+
const visibleRows = limitedResult?.rows ?? fetched;
|
|
40017
|
+
const rows = blacklistValidator.filterColumnsForIndexExpression(target, visibleRows, collectColumnUnion(visibleRows)).filteredRows;
|
|
39341
40018
|
rowCount = rows.length;
|
|
39342
40019
|
const columns = collectColumnUnion(rows);
|
|
39343
40020
|
if (options.format === "html") {
|
|
@@ -39393,7 +40070,9 @@ async function mongoExportBranch(query, options, config) {
|
|
|
39393
40070
|
});
|
|
39394
40071
|
const blacklistManager = new BlacklistManager(config);
|
|
39395
40072
|
const blacklistValidator = new BlacklistValidator(blacklistManager);
|
|
39396
|
-
|
|
40073
|
+
const mongoScopes = findMongoCollectionScopes(JSON.parse(query));
|
|
40074
|
+
const mongoCollections = [collection, ...findMongoCollectionReferences(JSON.parse(query))];
|
|
40075
|
+
blacklistValidator.checkTablesBlacklist("SELECT", mongoCollections);
|
|
39397
40076
|
let effectiveLimit;
|
|
39398
40077
|
if (options.noLimit) {
|
|
39399
40078
|
effectiveLimit = undefined;
|
|
@@ -39413,7 +40092,7 @@ async function mongoExportBranch(query, options, config) {
|
|
|
39413
40092
|
const limitedResult = effectiveLimit === undefined ? undefined : trimAppliedLimit(result.rows, effectiveLimit);
|
|
39414
40093
|
assertExportNotSilentlyTruncated(limitedResult?.metadata, options);
|
|
39415
40094
|
const blacklistCfg = config.blacklist ?? { tables: [], columns: {} };
|
|
39416
|
-
const maskedRows =
|
|
40095
|
+
const maskedRows = maskMongoRowsForCollections(limitedResult?.rows ?? result.rows, [collection, ...mongoScopes], blacklistCfg);
|
|
39417
40096
|
rowCount = maskedRows.length;
|
|
39418
40097
|
hasBlacklistedColumns = (blacklistCfg.columns[collection] ?? []).length > 0;
|
|
39419
40098
|
const visibleColumns = collectColumnUnion(maskedRows);
|
|
@@ -39542,6 +40221,7 @@ var init_export = __esm(() => {
|
|
|
39542
40221
|
init_integration_helper();
|
|
39543
40222
|
init_engine_hints();
|
|
39544
40223
|
init_field_masker();
|
|
40224
|
+
init_collection_references();
|
|
39545
40225
|
SQL_PATTERN = /^\s*(SELECT|INSERT|UPDATE|DELETE|CREATE|DROP|ALTER|SHOW|DESCRIBE)\b/i;
|
|
39546
40226
|
});
|
|
39547
40227
|
|
|
@@ -93845,6 +94525,25 @@ async function runDiagnostic(input) {
|
|
|
93845
94525
|
return { ...base, rowCount: 0, rows: [], status: "skipped", reason, durationMs: 0 };
|
|
93846
94526
|
}
|
|
93847
94527
|
const family = engineFamily(input.engine);
|
|
94528
|
+
const referencedTables = family === "sql" ? extractTableReferences(prepared.rewrittenSql, {
|
|
94529
|
+
...ENGINE_DIALECT2[input.engine] ? { dialect: ENGINE_DIALECT2[input.engine] } : {}
|
|
94530
|
+
}) : family === "es" && prepared.execHints?.index ? [prepared.execHints.index] : [];
|
|
94531
|
+
if (input.blacklistValidator && referencedTables.length > 0) {
|
|
94532
|
+
try {
|
|
94533
|
+
input.blacklistValidator.checkTablesBlacklist("SELECT", referencedTables);
|
|
94534
|
+
} catch (err) {
|
|
94535
|
+
if (!(err instanceof BlacklistError))
|
|
94536
|
+
throw err;
|
|
94537
|
+
return {
|
|
94538
|
+
...base,
|
|
94539
|
+
rowCount: 0,
|
|
94540
|
+
rows: [],
|
|
94541
|
+
status: "skipped",
|
|
94542
|
+
reason: err.message,
|
|
94543
|
+
durationMs: 0
|
|
94544
|
+
};
|
|
94545
|
+
}
|
|
94546
|
+
}
|
|
93848
94547
|
const start = performance.now();
|
|
93849
94548
|
const exec2 = (async () => {
|
|
93850
94549
|
const indexParams = family === "es" && prepared.execHints?.index ? [prepared.execHints.index] : [];
|
|
@@ -93880,7 +94579,8 @@ async function runDiagnostic(input) {
|
|
|
93880
94579
|
durationMs
|
|
93881
94580
|
};
|
|
93882
94581
|
}
|
|
93883
|
-
const
|
|
94582
|
+
const fetched = outcome.rows ?? [];
|
|
94583
|
+
const rows = input.blacklistValidator ? input.blacklistValidator.filterColumnsForTables(referencedTables, fetched, fetched[0] ? Object.keys(fetched[0]) : []).filteredRows : fetched;
|
|
93884
94584
|
if (rows.length === 0) {
|
|
93885
94585
|
return { ...base, rowCount: 0, rows: [], status: "no-data", durationMs };
|
|
93886
94586
|
}
|
|
@@ -93892,9 +94592,17 @@ async function runDiagnostic(input) {
|
|
|
93892
94592
|
durationMs
|
|
93893
94593
|
};
|
|
93894
94594
|
}
|
|
94595
|
+
var ENGINE_DIALECT2;
|
|
93895
94596
|
var init_run_diagnostic = __esm(() => {
|
|
93896
94597
|
init_saved_queries();
|
|
93897
94598
|
init_strategies();
|
|
94599
|
+
init_sql_tables();
|
|
94600
|
+
init_blacklist();
|
|
94601
|
+
ENGINE_DIALECT2 = {
|
|
94602
|
+
postgres: "postgresql",
|
|
94603
|
+
mysql: "mysql",
|
|
94604
|
+
mariadb: "mariadb"
|
|
94605
|
+
};
|
|
93898
94606
|
});
|
|
93899
94607
|
|
|
93900
94608
|
// src/core/report/collector.ts
|
|
@@ -93950,6 +94658,7 @@ async function collectReport(opts) {
|
|
|
93950
94658
|
return finalize({ context, sections: [], warnings, generatedAt });
|
|
93951
94659
|
}
|
|
93952
94660
|
const adapter = AdapterFactory.createAdapter(config.connection);
|
|
94661
|
+
const blacklistValidator = new BlacklistValidator(new BlacklistManager(config));
|
|
93953
94662
|
const sectionEvidence = new Map;
|
|
93954
94663
|
for (const id of sections)
|
|
93955
94664
|
sectionEvidence.set(id, []);
|
|
@@ -93963,7 +94672,8 @@ async function collectReport(opts) {
|
|
|
93963
94672
|
adapter,
|
|
93964
94673
|
engine,
|
|
93965
94674
|
timeoutMs: timeout,
|
|
93966
|
-
maxRows
|
|
94675
|
+
maxRows,
|
|
94676
|
+
blacklistValidator
|
|
93967
94677
|
});
|
|
93968
94678
|
const sectionId = sectionForIntent(ev.intent);
|
|
93969
94679
|
if (sectionId && sectionEvidence.has(sectionId)) {
|
|
@@ -94027,6 +94737,7 @@ var init_collector2 = __esm(() => {
|
|
|
94027
94737
|
init_saved_queries();
|
|
94028
94738
|
init_select_snippets();
|
|
94029
94739
|
init_run_diagnostic();
|
|
94740
|
+
init_blacklist_validator();
|
|
94030
94741
|
init_section_map();
|
|
94031
94742
|
init_types7();
|
|
94032
94743
|
});
|
|
@@ -97934,7 +98645,8 @@ var init_snapshot = __esm(() => {
|
|
|
97934
98645
|
init_validation();
|
|
97935
98646
|
init_blacklist_validator();
|
|
97936
98647
|
init_query_executor();
|
|
97937
|
-
|
|
98648
|
+
init_permission_guard();
|
|
98649
|
+
init_sql_tables();
|
|
97938
98650
|
init_fingerprint();
|
|
97939
98651
|
init_serializer();
|
|
97940
98652
|
init_saved_queries();
|
|
@@ -97962,10 +98674,12 @@ var init_snapshot = __esm(() => {
|
|
|
97962
98674
|
try {
|
|
97963
98675
|
const blacklistManager = new BlacklistManager(config);
|
|
97964
98676
|
const blacklistValidator = new BlacklistValidator(blacklistManager);
|
|
98677
|
+
const sqlDialect = SQL_DIALECTS.find((dialect) => dialect === config.connection?.system);
|
|
97965
98678
|
const executor3 = new QueryExecutor(adapter, config.permission, blacklistValidator, config, options);
|
|
97966
98679
|
const result = await executor3.execute(sql, { autoLimit: options.limit !== false });
|
|
97967
|
-
const
|
|
97968
|
-
|
|
98680
|
+
const redactedColumns = Array.from(new Set(extractTableReferences(sql, {
|
|
98681
|
+
...sqlDialect ? { dialect: sqlDialect } : {}
|
|
98682
|
+
}).flatMap((table) => blacklistManager.getBlacklistedColumns(table))));
|
|
97969
98683
|
const snap = buildFingerprint(result, {
|
|
97970
98684
|
includeRows: options.rows === true,
|
|
97971
98685
|
redactedColumns,
|
|
@@ -99531,8 +100245,20 @@ function buildRealRunners(ctx) {
|
|
|
99531
100245
|
const blacklist = config.blacklist ?? { tables: [], columns: {} };
|
|
99532
100246
|
const schema = config.schema ?? {};
|
|
99533
100247
|
const schemaLookup = { tables: schema, cacheAvailable: Object.keys(schema).length > 0 };
|
|
99534
|
-
const analyze = (sql) => analyzeQueryRisk({
|
|
99535
|
-
|
|
100248
|
+
const analyze = (sql) => analyzeQueryRisk({
|
|
100249
|
+
sql: sql.trim(),
|
|
100250
|
+
permission: config.permission,
|
|
100251
|
+
blacklist,
|
|
100252
|
+
schemaLookup,
|
|
100253
|
+
dialect: toSqlDialect(config.connection?.system)
|
|
100254
|
+
});
|
|
100255
|
+
const analyzePlan = (sql) => analyzeQueryRisk({
|
|
100256
|
+
sql: sql.trim(),
|
|
100257
|
+
permission: "read-write",
|
|
100258
|
+
blacklist,
|
|
100259
|
+
schemaLookup,
|
|
100260
|
+
dialect: toSqlDialect(config.connection?.system)
|
|
100261
|
+
});
|
|
99536
100262
|
return {
|
|
99537
100263
|
blacklistGuard: async (table) => {
|
|
99538
100264
|
const bm = new BlacklistManager(config);
|
|
@@ -99605,7 +100331,13 @@ function buildMigrationRunners(ctx) {
|
|
|
99605
100331
|
const blacklist = config.blacklist ?? { tables: [], columns: {} };
|
|
99606
100332
|
const schema = config.schema ?? {};
|
|
99607
100333
|
const schemaLookup = { tables: schema, cacheAvailable: Object.keys(schema).length > 0 };
|
|
99608
|
-
const analyze = (sql) => analyzeQueryRisk({
|
|
100334
|
+
const analyze = (sql) => analyzeQueryRisk({
|
|
100335
|
+
sql: sql.trim(),
|
|
100336
|
+
permission: config.permission,
|
|
100337
|
+
blacklist,
|
|
100338
|
+
schemaLookup,
|
|
100339
|
+
dialect: toSqlDialect(config.connection?.system)
|
|
100340
|
+
});
|
|
99609
100341
|
return {
|
|
99610
100342
|
blacklistGuard: async (table) => {
|
|
99611
100343
|
const bm = new BlacklistManager(config);
|
|
@@ -99673,8 +100405,20 @@ function buildRollbackRunners(ctx, input) {
|
|
|
99673
100405
|
const blacklist = config.blacklist ?? { tables: [], columns: {} };
|
|
99674
100406
|
const schema = config.schema ?? {};
|
|
99675
100407
|
const schemaLookup = { tables: schema, cacheAvailable: Object.keys(schema).length > 0 };
|
|
99676
|
-
const analyze = (sql) => analyzeQueryRisk({
|
|
99677
|
-
|
|
100408
|
+
const analyze = (sql) => analyzeQueryRisk({
|
|
100409
|
+
sql: sql.trim(),
|
|
100410
|
+
permission: config.permission,
|
|
100411
|
+
blacklist,
|
|
100412
|
+
schemaLookup,
|
|
100413
|
+
dialect: toSqlDialect(config.connection?.system)
|
|
100414
|
+
});
|
|
100415
|
+
const analyzePlan = (sql) => analyzeQueryRisk({
|
|
100416
|
+
sql: sql.trim(),
|
|
100417
|
+
permission: "read-write",
|
|
100418
|
+
blacklist,
|
|
100419
|
+
schemaLookup,
|
|
100420
|
+
dialect: toSqlDialect(config.connection?.system)
|
|
100421
|
+
});
|
|
99678
100422
|
const ddlStatementGuard = async (statement, table) => {
|
|
99679
100423
|
if (!isSingleStatement(statement)) {
|
|
99680
100424
|
return {
|
|
@@ -99786,7 +100530,13 @@ function buildConstraintRunners(ctx, input) {
|
|
|
99786
100530
|
const blacklist = config.blacklist ?? { tables: [], columns: {} };
|
|
99787
100531
|
const schema = config.schema ?? {};
|
|
99788
100532
|
const schemaLookup = { tables: schema, cacheAvailable: Object.keys(schema).length > 0 };
|
|
99789
|
-
const analyze = (sql) => analyzeQueryRisk({
|
|
100533
|
+
const analyze = (sql) => analyzeQueryRisk({
|
|
100534
|
+
sql: sql.trim(),
|
|
100535
|
+
permission: config.permission,
|
|
100536
|
+
blacklist,
|
|
100537
|
+
schemaLookup,
|
|
100538
|
+
dialect: toSqlDialect(config.connection?.system)
|
|
100539
|
+
});
|
|
99790
100540
|
const engine = constraintEngineOf(config.connection.system);
|
|
99791
100541
|
const violationSql = buildViolationQuery(input, engine);
|
|
99792
100542
|
const columnsExist = async (table, cols) => {
|
|
@@ -103041,17 +103791,20 @@ class ReplEngine {
|
|
|
103041
103791
|
};
|
|
103042
103792
|
}
|
|
103043
103793
|
}
|
|
103044
|
-
|
|
103045
|
-
|
|
103046
|
-
|
|
103047
|
-
|
|
103048
|
-
|
|
103049
|
-
|
|
103050
|
-
|
|
103051
|
-
|
|
103052
|
-
|
|
103053
|
-
|
|
103054
|
-
|
|
103794
|
+
const referencedTables = extractTableReferences(sql, {
|
|
103795
|
+
dialect: SQL_DIALECTS.find((dialect) => dialect === this.context.system)
|
|
103796
|
+
});
|
|
103797
|
+
const blacklistValidator = this.config?.blacklist ? new BlacklistValidator(new BlacklistManager(this.config)) : undefined;
|
|
103798
|
+
if (blacklistValidator && referencedTables.length > 0) {
|
|
103799
|
+
try {
|
|
103800
|
+
blacklistValidator.checkTablesBlacklist("SELECT", referencedTables);
|
|
103801
|
+
} catch (error) {
|
|
103802
|
+
if (!(error instanceof BlacklistError))
|
|
103803
|
+
throw error;
|
|
103804
|
+
return {
|
|
103805
|
+
action: "continue",
|
|
103806
|
+
output: import_picocolors3.default.red(t_vars("shell.error_blacklisted", { table: error.message }))
|
|
103807
|
+
};
|
|
103055
103808
|
}
|
|
103056
103809
|
}
|
|
103057
103810
|
const startTime = Date.now();
|
|
@@ -103060,8 +103813,11 @@ class ReplEngine {
|
|
|
103060
103813
|
noLimit: this.state.noLimit
|
|
103061
103814
|
});
|
|
103062
103815
|
const elapsed = Date.now() - startTime;
|
|
103063
|
-
const
|
|
103064
|
-
const
|
|
103816
|
+
const fetched = result.rows;
|
|
103817
|
+
const fetchedColumns = fetched.length > 0 && fetched[0] ? Object.keys(fetched[0]) : [];
|
|
103818
|
+
const filtered = blacklistValidator ? blacklistValidator.filterColumnsForTables(referencedTables, fetched, fetchedColumns) : { filteredRows: fetched, omittedColumns: [] };
|
|
103819
|
+
const rows = filtered.filteredRows;
|
|
103820
|
+
const columnNames = fetchedColumns.filter((col) => !filtered.omittedColumns.includes(col));
|
|
103065
103821
|
const queryResult = {
|
|
103066
103822
|
rows,
|
|
103067
103823
|
rowCount: rows.length,
|
|
@@ -103113,10 +103869,6 @@ class ReplEngine {
|
|
|
103113
103869
|
}
|
|
103114
103870
|
return null;
|
|
103115
103871
|
}
|
|
103116
|
-
extractTableName(sql) {
|
|
103117
|
-
const match = sql.match(/\b(?:FROM|INTO|UPDATE)\s+["'`]?(\w+)["'`]?/i);
|
|
103118
|
-
return match?.[1];
|
|
103119
|
-
}
|
|
103120
103872
|
isConnectionError(error) {
|
|
103121
103873
|
const e = error;
|
|
103122
103874
|
const msg = (e.message ?? "").toLowerCase();
|
|
@@ -103131,6 +103883,9 @@ var init_repl_engine = __esm(() => {
|
|
|
103131
103883
|
init_permission_guard();
|
|
103132
103884
|
init_query_result_formatter();
|
|
103133
103885
|
init_message_loader();
|
|
103886
|
+
init_sql_tables();
|
|
103887
|
+
init_blacklist_validator();
|
|
103888
|
+
init_blacklist();
|
|
103134
103889
|
import_picocolors3 = __toESM(require_picocolors(), 1);
|
|
103135
103890
|
});
|
|
103136
103891
|
|
|
@@ -103340,22 +104095,115 @@ function extractIndexFromPath(path6) {
|
|
|
103340
104095
|
return;
|
|
103341
104096
|
return seg.split("?")[0];
|
|
103342
104097
|
}
|
|
103343
|
-
|
|
103344
|
-
const
|
|
103345
|
-
|
|
103346
|
-
|
|
104098
|
+
function isUnscopedMetadataPath(path6) {
|
|
104099
|
+
const first = path6.replace(/^\//, "").split("/")[0]?.split("?")[0] ?? "";
|
|
104100
|
+
return UNSCOPED_METADATA_PREFIXES.includes(first);
|
|
104101
|
+
}
|
|
104102
|
+
function findIndexNamesInBody(body) {
|
|
104103
|
+
const found = [];
|
|
104104
|
+
const walk = (node) => {
|
|
104105
|
+
if (Array.isArray(node)) {
|
|
104106
|
+
for (const item of node)
|
|
104107
|
+
walk(item);
|
|
104108
|
+
return;
|
|
104109
|
+
}
|
|
104110
|
+
if (node === null || typeof node !== "object")
|
|
104111
|
+
return;
|
|
104112
|
+
for (const [key, value] of Object.entries(node)) {
|
|
104113
|
+
if (key === "_index" || key === "index") {
|
|
104114
|
+
for (const candidate of Array.isArray(value) ? value : [value]) {
|
|
104115
|
+
if (typeof candidate === "string" && candidate.length > 0)
|
|
104116
|
+
found.push(candidate);
|
|
104117
|
+
}
|
|
104118
|
+
}
|
|
104119
|
+
walk(value);
|
|
104120
|
+
}
|
|
104121
|
+
};
|
|
104122
|
+
walk(body);
|
|
104123
|
+
return found;
|
|
104124
|
+
}
|
|
104125
|
+
async function runEsRequest(req, adapter, blacklistTables, blacklistColumns = {}) {
|
|
104126
|
+
const rawPath = req.path.split("?")[0] ?? req.path;
|
|
104127
|
+
const routedPath = normalizeEsPath(rawPath);
|
|
104128
|
+
const index = extractIndexFromPath(routedPath);
|
|
104129
|
+
if (blacklistTables.length > 0) {
|
|
104130
|
+
const literalSegments = `/${rawPath.split("/").filter(Boolean).join("/")}`;
|
|
104131
|
+
if (routedPath !== literalSegments) {
|
|
104132
|
+
throw new Error(`BlacklistRejection: '${req.path}' routes to '${routedPath}', which is not what it ` + `spells. Write the path the server will receive.`);
|
|
104133
|
+
}
|
|
104134
|
+
const blacklistedSegment = routedPath.split("/").find((segment) => segment.length > 0 && indexExpressionReaches(segment, blacklistTables));
|
|
104135
|
+
if (blacklistedSegment !== undefined) {
|
|
104136
|
+
throw new Error(`BlacklistRejection: index '${blacklistedSegment}' is blacklist-protected`);
|
|
104137
|
+
}
|
|
104138
|
+
if (index === undefined) {
|
|
104139
|
+
if (!isUnscopedMetadataPath(routedPath)) {
|
|
104140
|
+
throw new Error(`BlacklistRejection: '${req.path}' names no index, so it cannot be checked against ` + `the blacklist. Scope the request to an index, e.g. GET /<index>/_search.`);
|
|
104141
|
+
}
|
|
104142
|
+
}
|
|
104143
|
+
const inBody = findIndexNamesInBody(req.body).find((name2) => indexExpressionReaches(name2, blacklistTables));
|
|
104144
|
+
if (inBody !== undefined) {
|
|
104145
|
+
throw new Error(`BlacklistRejection: index '${inBody}' is blacklist-protected`);
|
|
104146
|
+
}
|
|
104147
|
+
}
|
|
104148
|
+
const protectedFields = new Set(Object.values(blacklistColumns).flat());
|
|
104149
|
+
if (protectedFields.size > 0) {
|
|
104150
|
+
const named = findStrings(req.body).find((text2) => protectedFields.has(text2));
|
|
104151
|
+
if (named !== undefined) {
|
|
104152
|
+
throw new Error(`BlacklistRejection: field '${named}' is blacklist-protected and cannot be named in a ` + `request \u2014 sorting, aggregating or scripting on it would return its values.`);
|
|
104153
|
+
}
|
|
103347
104154
|
}
|
|
103348
104155
|
let body = req.body;
|
|
103349
104156
|
if (req.path.includes("_search") && body !== null && typeof body === "object" && !Array.isArray(body) && body.size === undefined) {
|
|
103350
104157
|
body = { ...body, size: ES_SHELL_SIZE_CAP };
|
|
103351
104158
|
}
|
|
103352
|
-
|
|
104159
|
+
const response = await adapter.request(req.method, req.path, body);
|
|
104160
|
+
return protectedFields.size === 0 ? response : redactFields(response, protectedFields);
|
|
104161
|
+
}
|
|
104162
|
+
function findStrings(node) {
|
|
104163
|
+
const found = [];
|
|
104164
|
+
const walk = (value) => {
|
|
104165
|
+
if (typeof value === "string") {
|
|
104166
|
+
found.push(value);
|
|
104167
|
+
for (const piece of value.split(/[^A-Za-z0-9_.]+/)) {
|
|
104168
|
+
if (piece.length > 0 && piece !== value)
|
|
104169
|
+
found.push(piece);
|
|
104170
|
+
}
|
|
104171
|
+
return;
|
|
104172
|
+
}
|
|
104173
|
+
if (Array.isArray(value)) {
|
|
104174
|
+
for (const item of value)
|
|
104175
|
+
walk(item);
|
|
104176
|
+
return;
|
|
104177
|
+
}
|
|
104178
|
+
if (value === null || typeof value !== "object")
|
|
104179
|
+
return;
|
|
104180
|
+
for (const [key, nested] of Object.entries(value)) {
|
|
104181
|
+
found.push(key);
|
|
104182
|
+
walk(nested);
|
|
104183
|
+
}
|
|
104184
|
+
};
|
|
104185
|
+
walk(node);
|
|
104186
|
+
return found;
|
|
104187
|
+
}
|
|
104188
|
+
function redactFields(node, fields) {
|
|
104189
|
+
if (Array.isArray(node))
|
|
104190
|
+
return node.map((item) => redactFields(item, fields));
|
|
104191
|
+
if (node === null || typeof node !== "object")
|
|
104192
|
+
return node;
|
|
104193
|
+
const out = {};
|
|
104194
|
+
for (const [key, value] of Object.entries(node)) {
|
|
104195
|
+
if (fields.has(key))
|
|
104196
|
+
continue;
|
|
104197
|
+
out[key] = redactFields(value, fields);
|
|
104198
|
+
}
|
|
104199
|
+
return out;
|
|
103353
104200
|
}
|
|
103354
104201
|
async function runEsShell(configPath) {
|
|
103355
104202
|
const config = await configModule.read(configPath);
|
|
103356
104203
|
const adapter = AdapterFactory.createElasticsearchAdapter(config.connection);
|
|
103357
104204
|
await adapter.connect();
|
|
103358
104205
|
const blacklistTables = config.blacklist?.tables ?? [];
|
|
104206
|
+
const blacklistColumns = config.blacklist?.columns ?? {};
|
|
103359
104207
|
console.error(import_picocolors4.default.bold("Elasticsearch shell \u2014 Kibana Dev Tools syntax"));
|
|
103360
104208
|
console.error(import_picocolors4.default.dim('Enter "<METHOD> /<path>" then an optional JSON body; submit with a blank line. Try: GET /_cat/indices'));
|
|
103361
104209
|
console.error(import_picocolors4.default.dim('Ctrl+C cancels the current block; Ctrl+D or "exit" quits.'));
|
|
@@ -103380,7 +104228,7 @@ async function runEsShell(configPath) {
|
|
|
103380
104228
|
}
|
|
103381
104229
|
try {
|
|
103382
104230
|
const req = parseEsRequest(block2);
|
|
103383
|
-
const res = await runEsRequest(req, adapter, blacklistTables);
|
|
104231
|
+
const res = await runEsRequest(req, adapter, blacklistTables, blacklistColumns);
|
|
103384
104232
|
console.log(JSON.stringify(res, null, 2));
|
|
103385
104233
|
} catch (error) {
|
|
103386
104234
|
console.error(import_picocolors4.default.red(error.message));
|
|
@@ -103408,11 +104256,13 @@ async function runEsShell(configPath) {
|
|
|
103408
104256
|
process.exit(0);
|
|
103409
104257
|
});
|
|
103410
104258
|
}
|
|
103411
|
-
var import_picocolors4, ES_SHELL_SIZE_CAP = 1000;
|
|
104259
|
+
var import_picocolors4, UNSCOPED_METADATA_PREFIXES, ES_SHELL_SIZE_CAP = 1000;
|
|
103412
104260
|
var init_es_shell = __esm(() => {
|
|
103413
104261
|
init_config();
|
|
103414
104262
|
init_adapters();
|
|
104263
|
+
init_es_index_target();
|
|
103415
104264
|
import_picocolors4 = __toESM(require_picocolors(), 1);
|
|
104265
|
+
UNSCOPED_METADATA_PREFIXES = ["_cat", "_cluster", "_nodes", "_tasks", "_ingest", "_license"];
|
|
103416
104266
|
});
|
|
103417
104267
|
|
|
103418
104268
|
// src/core/repl/command-registry.ts
|