@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/dist/core.mjs CHANGED
@@ -15819,22 +15819,27 @@ function truncateResult(command, reply, opts) {
15819
15819
  return { value: reply };
15820
15820
  }
15821
15821
 
15822
- // src/adapters/redis/blacklist-enforcer.ts
15822
+ // src/utils/glob.ts
15823
15823
  function globToRegex(glob) {
15824
15824
  let out = "^";
15825
15825
  for (let i = 0;i < glob.length; i++) {
15826
15826
  const c = glob[i];
15827
+ if (c === "\\" && i + 1 < glob.length) {
15828
+ out += glob[++i].replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
15829
+ continue;
15830
+ }
15827
15831
  if (c === "*")
15828
15832
  out += ".*";
15829
15833
  else if (c === "?")
15830
15834
  out += ".";
15831
15835
  else if (c === "[") {
15832
- const end = glob.indexOf("]", i);
15833
- if (end === -1)
15834
- out += "\\[";
15835
- else {
15836
- out += glob.slice(i, end + 1);
15836
+ const end = findClassEnd(glob, i);
15837
+ const body = end === -1 ? "" : glob.slice(i, end + 1);
15838
+ if (body !== "" && isValidCharacterClass(body)) {
15839
+ out += body;
15837
15840
  i = end;
15841
+ } else {
15842
+ out += "\\[";
15838
15843
  }
15839
15844
  } else if (".^$+(){}|\\".includes(c)) {
15840
15845
  out += "\\" + c;
@@ -15845,6 +15850,27 @@ function globToRegex(glob) {
15845
15850
  out += "$";
15846
15851
  return new RegExp(out);
15847
15852
  }
15853
+ function findClassEnd(glob, open) {
15854
+ for (let i = open + 1;i < glob.length; i++) {
15855
+ if (glob[i] === "\\") {
15856
+ i++;
15857
+ continue;
15858
+ }
15859
+ if (glob[i] === "]")
15860
+ return i;
15861
+ }
15862
+ return -1;
15863
+ }
15864
+ function isValidCharacterClass(body) {
15865
+ try {
15866
+ new RegExp(body);
15867
+ return true;
15868
+ } catch {
15869
+ return false;
15870
+ }
15871
+ }
15872
+
15873
+ // src/adapters/redis/blacklist-enforcer.ts
15848
15874
  function patternsOverlap(a, b) {
15849
15875
  const ra = globToRegex(a);
15850
15876
  const rb = globToRegex(b);
@@ -16801,6 +16827,32 @@ class AdapterFactory {
16801
16827
  return AdapterFactory.createQueryableAdapter(options);
16802
16828
  }
16803
16829
  }
16830
+ // src/utils/sql-lexical.ts
16831
+ var IDENTIFIER_CONTINUATION = /[A-Za-z0-9_$]|[\u0080-\uFFFF]/;
16832
+ var IDENTIFIER_START = /[A-Za-z_]|[\u0080-\uFFFF]/;
16833
+ var DOLLAR_QUOTE_DELIMITER = /^\$(?:(?:[A-Za-z_]|[\u0080-\uFFFF])(?:[A-Za-z0-9_]|[\u0080-\uFFFF])*)?\$/;
16834
+ function dollarQuoteDelimiterAt(sql, index) {
16835
+ if (sql[index] !== "$")
16836
+ return;
16837
+ if (continuesIdentifier(sql, index))
16838
+ return;
16839
+ return sql.slice(index).match(DOLLAR_QUOTE_DELIMITER)?.[0];
16840
+ }
16841
+ function continuesIdentifier(sql, index) {
16842
+ let start = index;
16843
+ while (start > 0 && IDENTIFIER_CONTINUATION.test(sql[start - 1] ?? ""))
16844
+ start--;
16845
+ if (start === index)
16846
+ return false;
16847
+ const run = sql.slice(start, index);
16848
+ const first = run[0];
16849
+ if (IDENTIFIER_START.test(first))
16850
+ return true;
16851
+ if (first === "$")
16852
+ return false;
16853
+ return run.replace(/^[0-9]+(?:[eE][0-9]+)?/, "").length > 0;
16854
+ }
16855
+
16804
16856
  // src/core/permission-guard.ts
16805
16857
  class PermissionError extends Error {
16806
16858
  classification;
@@ -16817,7 +16869,6 @@ function normalizeSQL(sql) {
16817
16869
  return sql.replace(/--[^\n]*\n/g, `
16818
16870
  `).replace(/\/\*[\s\S]*?\*\//g, " ").trim().replace(/\s+/g, " ");
16819
16871
  }
16820
- var IDENTIFIER_CONTINUATION = /[A-Za-z0-9_$]|[\u0080-\uFFFF]/;
16821
16872
  function stripCommentsAndStrings(sql, options = {}) {
16822
16873
  let result = "";
16823
16874
  let i = 0;
@@ -16880,8 +16931,7 @@ function stripCommentsAndStrings(sql, options = {}) {
16880
16931
  continue;
16881
16932
  }
16882
16933
  if (options.dialect === "postgresql" && char === "$") {
16883
- const opensToken = !IDENTIFIER_CONTINUATION.test(sql[i - 1] ?? "");
16884
- const delimiter = opensToken ? sql.slice(i).match(/^\$(?:[A-Za-z_][A-Za-z0-9_]*)?\$/)?.[0] : undefined;
16934
+ const delimiter = dollarQuoteDelimiterAt(sql, i);
16885
16935
  if (delimiter) {
16886
16936
  i += delimiter.length;
16887
16937
  const closingIndex = sql.indexOf(delimiter, i);
@@ -17311,28 +17361,345 @@ async function getAllTablesFromAdapter(adapter) {
17311
17361
  }
17312
17362
  }
17313
17363
 
17314
- // src/utils/engine-hints.ts
17315
- var TABLE_NAME_RE = /\b(?:FROM|INTO|UPDATE)\s+["'`]?([a-zA-Z_][a-zA-Z0-9_]*)["'`]?/i;
17316
- function extractTableName(sql) {
17317
- const match = sql.match(TABLE_NAME_RE);
17318
- return match ? match[1] ?? null : null;
17319
- }
17320
- function getOperationTarget(system, command, options, sql) {
17321
- if (system === "mongodb") {
17322
- return options.collection || "<unknown-collection>";
17364
+ // src/utils/sql-tables.ts
17365
+ var TABLE_INTRODUCERS = new Set([
17366
+ "FROM",
17367
+ "JOIN",
17368
+ "INTO",
17369
+ "UPDATE",
17370
+ "TABLE",
17371
+ "TRUNCATE",
17372
+ "COPY",
17373
+ "USING",
17374
+ "STRAIGHT_JOIN"
17375
+ ]);
17376
+ var PRE_TABLE_NOISE = new Set(["ONLY", "LATERAL", "TABLE"]);
17377
+ var SUBQUERY_OPENERS = new Set(["SELECT", "WITH", "VALUES", "TABLE"]);
17378
+ var AS_KEYWORD = new Set(["AS"]);
17379
+ var POST_TABLE_KEYWORDS = new Set([
17380
+ "AS",
17381
+ "ON",
17382
+ "USING",
17383
+ "WHERE",
17384
+ "GROUP",
17385
+ "ORDER",
17386
+ "HAVING",
17387
+ "LIMIT",
17388
+ "OFFSET",
17389
+ "FETCH",
17390
+ "WINDOW",
17391
+ "UNION",
17392
+ "INTERSECT",
17393
+ "EXCEPT",
17394
+ "JOIN",
17395
+ "INNER",
17396
+ "LEFT",
17397
+ "RIGHT",
17398
+ "FULL",
17399
+ "OUTER",
17400
+ "CROSS",
17401
+ "NATURAL",
17402
+ "STRAIGHT_JOIN",
17403
+ "SET",
17404
+ "VALUES",
17405
+ "SELECT",
17406
+ "RETURNING",
17407
+ "FOR",
17408
+ "INTO",
17409
+ "PARTITION",
17410
+ "WITH",
17411
+ "TABLESAMPLE",
17412
+ "FORCE",
17413
+ "IGNORE",
17414
+ "USE"
17415
+ ]);
17416
+ var RESERVED_KEYWORDS = new Set([
17417
+ "ALL",
17418
+ "AND",
17419
+ "AS",
17420
+ "ASC",
17421
+ "CASE",
17422
+ "CROSS",
17423
+ "DESC",
17424
+ "DISTINCT",
17425
+ "ELSE",
17426
+ "FALSE",
17427
+ "FOR",
17428
+ "FROM",
17429
+ "GROUP",
17430
+ "HAVING",
17431
+ "IN",
17432
+ "INNER",
17433
+ "INTO",
17434
+ "IS",
17435
+ "JOIN",
17436
+ "LEFT",
17437
+ "LIKE",
17438
+ "LIMIT",
17439
+ "NOT",
17440
+ "NULL",
17441
+ "ON",
17442
+ "OR",
17443
+ "ORDER",
17444
+ "OUTER",
17445
+ "RIGHT",
17446
+ "SELECT",
17447
+ "THEN",
17448
+ "TRUE",
17449
+ "UNION",
17450
+ "USING",
17451
+ "VALUES",
17452
+ "WHEN",
17453
+ "WHERE",
17454
+ "WITH"
17455
+ ]);
17456
+ var IDENTIFIER_START2 = /[A-Za-z_\u0080-\uFFFF]/;
17457
+ var IDENTIFIER_PART = /[A-Za-z0-9_$\u0080-\uFFFF]/;
17458
+ function tokenize(sql, dialect, backslashEscapes) {
17459
+ const tokens = [];
17460
+ const mysqlDialect = dialect === "mysql" || dialect === "mariadb";
17461
+ let i = 0;
17462
+ while (i < sql.length) {
17463
+ const char = sql[i];
17464
+ const dashFollowerCode = sql.charCodeAt(i + 2);
17465
+ if (char === "-" && sql[i + 1] === "-" && (!mysqlDialect || sql[i + 2] === undefined || dashFollowerCode <= 32 || dashFollowerCode === 127)) {
17466
+ while (i < sql.length && sql[i] !== `
17467
+ `)
17468
+ i++;
17469
+ continue;
17470
+ }
17471
+ if (mysqlDialect && char === "#") {
17472
+ while (i < sql.length && sql[i] !== `
17473
+ `)
17474
+ i++;
17475
+ continue;
17476
+ }
17477
+ if (char === "/" && sql[i + 1] === "*") {
17478
+ if (mysqlDialect && (sql.startsWith("/*!", i) || sql.startsWith("/*M!", i))) {
17479
+ const prefixLength = sql.startsWith("/*M!", i) ? 4 : 3;
17480
+ const closingIndex = sql.indexOf("*/", i + prefixLength);
17481
+ const bodyEnd = closingIndex === -1 ? sql.length : closingIndex;
17482
+ const body = sql.slice(i + prefixLength, bodyEnd).replace(/^\d+/, " ");
17483
+ tokens.push(...tokenize(body, dialect, backslashEscapes));
17484
+ i = closingIndex === -1 ? sql.length : closingIndex + 2;
17485
+ continue;
17486
+ }
17487
+ const nests = dialect === "postgresql";
17488
+ let depth = 1;
17489
+ i += 2;
17490
+ while (i < sql.length && depth > 0) {
17491
+ if (nests && sql[i] === "/" && sql[i + 1] === "*") {
17492
+ depth++;
17493
+ i += 2;
17494
+ continue;
17495
+ }
17496
+ if (sql[i] === "*" && sql[i + 1] === "/") {
17497
+ depth--;
17498
+ i += 2;
17499
+ continue;
17500
+ }
17501
+ i++;
17502
+ }
17503
+ continue;
17504
+ }
17505
+ if (dialect === "postgresql" && char === "$") {
17506
+ const delimiter = dollarQuoteDelimiterAt(sql, i);
17507
+ if (delimiter) {
17508
+ i += delimiter.length;
17509
+ const closingIndex = sql.indexOf(delimiter, i);
17510
+ i = closingIndex === -1 ? sql.length : closingIndex + delimiter.length;
17511
+ continue;
17512
+ }
17513
+ }
17514
+ if (char === "'") {
17515
+ i++;
17516
+ while (i < sql.length) {
17517
+ if (sql[i] === "'") {
17518
+ if (sql[i + 1] === "'") {
17519
+ i += 2;
17520
+ continue;
17521
+ }
17522
+ i++;
17523
+ break;
17524
+ }
17525
+ if (backslashEscapes && sql[i] === "\\") {
17526
+ i += 2;
17527
+ continue;
17528
+ }
17529
+ i++;
17530
+ }
17531
+ continue;
17532
+ }
17533
+ if (char === '"' || char === "`") {
17534
+ const quote = char;
17535
+ i++;
17536
+ let value = "";
17537
+ while (i < sql.length) {
17538
+ if (sql[i] === quote) {
17539
+ if (sql[i + 1] === quote) {
17540
+ value += quote;
17541
+ i += 2;
17542
+ continue;
17543
+ }
17544
+ i++;
17545
+ break;
17546
+ }
17547
+ if (backslashEscapes && sql[i] === "\\") {
17548
+ value += sql[i + 1] ?? "";
17549
+ i += 2;
17550
+ continue;
17551
+ }
17552
+ value += sql[i];
17553
+ i++;
17554
+ }
17555
+ tokens.push({ value, kind: "identifier", quoted: true });
17556
+ continue;
17557
+ }
17558
+ if (IDENTIFIER_START2.test(char)) {
17559
+ let value = "";
17560
+ while (i < sql.length && IDENTIFIER_PART.test(sql[i])) {
17561
+ value += sql[i];
17562
+ i++;
17563
+ }
17564
+ tokens.push({ value, kind: "identifier", quoted: false });
17565
+ continue;
17566
+ }
17567
+ if (char === "." || char === "," || char === "(" || char === ")" || char === ";") {
17568
+ tokens.push({ value: char, kind: "punctuation", quoted: false });
17569
+ i++;
17570
+ continue;
17571
+ }
17572
+ i++;
17323
17573
  }
17324
- if (system === "elasticsearch") {
17325
- return options.index || options.collection || "<unknown-index>";
17574
+ return tokens;
17575
+ }
17576
+ function isKeyword(token, keywords) {
17577
+ if (!token || token.kind !== "identifier" || token.quoted)
17578
+ return false;
17579
+ return keywords.has(token.value.toUpperCase());
17580
+ }
17581
+ function isPunctuation(token, value) {
17582
+ return token?.kind === "punctuation" && token.value === value;
17583
+ }
17584
+ function readQualifiedName(tokens, index) {
17585
+ const first = tokens[index];
17586
+ if (!first || first.kind !== "identifier")
17587
+ return null;
17588
+ const parts = [first.value];
17589
+ let cursor = index + 1;
17590
+ while (isPunctuation(tokens[cursor], ".") && tokens[cursor + 1]?.kind === "identifier") {
17591
+ parts.push(tokens[cursor + 1].value);
17592
+ cursor += 2;
17326
17593
  }
17327
- if (system === "redis") {
17328
- return options.table || options.key || "<unknown-key>";
17594
+ return { parts, next: cursor };
17595
+ }
17596
+ var ANY_ESCAPE_SEQUENCE = /([^0-9a-fA-F+'"\s])(?:\+([0-9a-fA-F]{6})|([0-9a-fA-F]{4}))/g;
17597
+ function decodedVariants(value) {
17598
+ const decoded = value.replace(ANY_ESCAPE_SEQUENCE, (whole, _escape, long, short) => fromCodePointOrRaw(long ?? short, whole));
17599
+ return decoded === value ? [] : [decoded];
17600
+ }
17601
+ function fromCodePointOrRaw(hex, whole) {
17602
+ const codePoint = parseInt(hex, 16);
17603
+ return codePoint <= 1114111 ? String.fromCodePoint(codePoint) : whole;
17604
+ }
17605
+ function extractTableReferences(sql, options = {}) {
17606
+ const seen = new Set;
17607
+ const references = [];
17608
+ const record = (name) => {
17609
+ const key = name.toLowerCase();
17610
+ if (name.length === 0 || seen.has(key))
17611
+ return;
17612
+ seen.add(key);
17613
+ references.push(name);
17614
+ };
17615
+ const dialects = options.dialect ? [options.dialect] : ["postgresql", "mysql", undefined];
17616
+ for (const dialect of dialects) {
17617
+ for (const backslashEscapes of [false, true]) {
17618
+ collectReferences(tokenize(sql, dialect, backslashEscapes), record);
17619
+ }
17620
+ }
17621
+ return references;
17622
+ }
17623
+ function collectReferences(tokens, record) {
17624
+ const recordName = (parts) => {
17625
+ const bare = parts[parts.length - 1];
17626
+ record(bare);
17627
+ for (const variant of decodedVariants(bare))
17628
+ record(variant);
17629
+ if (parts.length > 1)
17630
+ record(parts.join("."));
17631
+ };
17632
+ let i = 0;
17633
+ while (i < tokens.length) {
17634
+ if (!isKeyword(tokens[i], TABLE_INTRODUCERS)) {
17635
+ i++;
17636
+ continue;
17637
+ }
17638
+ const introducer = tokens[i].value.toUpperCase();
17639
+ const parenMeansFunction = introducer === "FROM" || introducer === "JOIN";
17640
+ let cursor = i + 1;
17641
+ while (isKeyword(tokens[cursor], PRE_TABLE_NOISE))
17642
+ cursor++;
17643
+ while (parenMeansFunction && isPunctuation(tokens[cursor], "(") && !isKeyword(tokens[cursor + 1], SUBQUERY_OPENERS) && tokens[cursor + 1]?.kind === "identifier") {
17644
+ cursor++;
17645
+ }
17646
+ let expectTable = true;
17647
+ while (expectTable) {
17648
+ expectTable = false;
17649
+ const name = readQualifiedName(tokens, cursor);
17650
+ if (!name)
17651
+ break;
17652
+ const isFunctionCall = parenMeansFunction && isPunctuation(tokens[name.next], "(");
17653
+ if (!isFunctionCall)
17654
+ recordName(name.parts);
17655
+ cursor = name.next;
17656
+ if (isFunctionCall)
17657
+ break;
17658
+ if (isKeyword(tokens[cursor], AS_KEYWORD))
17659
+ cursor++;
17660
+ if (tokens[cursor]?.kind === "identifier" && !isKeyword(tokens[cursor], POST_TABLE_KEYWORDS)) {
17661
+ cursor++;
17662
+ }
17663
+ while (isPunctuation(tokens[cursor], "(")) {
17664
+ let depth = 0;
17665
+ do {
17666
+ if (isPunctuation(tokens[cursor], "("))
17667
+ depth++;
17668
+ else if (isPunctuation(tokens[cursor], ")"))
17669
+ depth--;
17670
+ cursor++;
17671
+ } while (depth > 0 && cursor < tokens.length);
17672
+ if (tokens[cursor]?.kind === "identifier" && !isKeyword(tokens[cursor], POST_TABLE_KEYWORDS))
17673
+ cursor++;
17674
+ }
17675
+ if (isPunctuation(tokens[cursor], ",")) {
17676
+ cursor++;
17677
+ expectTable = true;
17678
+ }
17679
+ }
17680
+ i = Math.max(cursor, i + 1);
17329
17681
  }
17330
- if (sql) {
17331
- const table = extractTableName(sql);
17332
- if (table)
17333
- return table;
17682
+ let index = 0;
17683
+ while (index < tokens.length) {
17684
+ const token = tokens[index];
17685
+ if (!token || token.kind !== "identifier") {
17686
+ index++;
17687
+ continue;
17688
+ }
17689
+ const name = readQualifiedName(tokens, index);
17690
+ for (let part = 0;part < name.parts.length; part++) {
17691
+ const value = name.parts[part];
17692
+ const isQuoted = tokens[index + part * 2]?.quoted === true;
17693
+ if (isQuoted || !RESERVED_KEYWORDS.has(value.toUpperCase())) {
17694
+ record(value);
17695
+ for (const variant of decodedVariants(value))
17696
+ record(variant);
17697
+ }
17698
+ }
17699
+ if (name.parts.length > 1)
17700
+ record(name.parts.join("."));
17701
+ index = name.next;
17334
17702
  }
17335
- return options.table || "<unknown-target>";
17336
17703
  }
17337
17704
 
17338
17705
  // src/core/limits.ts
@@ -22745,6 +23112,30 @@ function redactSql(sql) {
22745
23112
  return redactSensitive(redacted);
22746
23113
  }
22747
23114
 
23115
+ // src/utils/engine-hints.ts
23116
+ var TABLE_NAME_RE = /\b(?:FROM|INTO|UPDATE)\s+["'`]?([a-zA-Z_][a-zA-Z0-9_]*)["'`]?/i;
23117
+ function extractTableName(sql) {
23118
+ const match = sql.match(TABLE_NAME_RE);
23119
+ return match ? match[1] ?? null : null;
23120
+ }
23121
+ function getOperationTarget(system, command, options, sql) {
23122
+ if (system === "mongodb") {
23123
+ return options.collection || "<unknown-collection>";
23124
+ }
23125
+ if (system === "elasticsearch") {
23126
+ return options.index || options.collection || "<unknown-index>";
23127
+ }
23128
+ if (system === "redis") {
23129
+ return options.table || options.key || "<unknown-key>";
23130
+ }
23131
+ if (sql) {
23132
+ const table = extractTableName(sql);
23133
+ if (table)
23134
+ return table;
23135
+ }
23136
+ return options.table || "<unknown-target>";
23137
+ }
23138
+
22748
23139
  // src/core/audit/integration-helper.ts
22749
23140
  var _sessionIdService = null;
22750
23141
  var _loggers = new Map;
@@ -22978,11 +23369,9 @@ class QueryExecutor {
22978
23369
  }
22979
23370
  }
22980
23371
  }
22981
- if (this.blacklistValidator) {
22982
- const tableName = extractTableName(sql);
22983
- if (tableName) {
22984
- this.blacklistValidator.checkTableBlacklist(classification.type, tableName, []);
22985
- }
23372
+ const referencedTables = this.blacklistValidator ? extractTableReferences(sql, { dialect: this.resolveDialect() }) : [];
23373
+ if (this.blacklistValidator && referencedTables.length > 0) {
23374
+ this.blacklistValidator.checkTablesBlacklist(classification.type, referencedTables);
22986
23375
  }
22987
23376
  const resultData = await this.adapter.execute(executeSql);
22988
23377
  const executionTimeMs = Math.round(performance.now() - start);
@@ -22995,15 +23384,12 @@ class QueryExecutor {
22995
23384
  let securityNotification;
22996
23385
  let omittedColumns = [];
22997
23386
  if (this.blacklistValidator) {
22998
- const tableName = extractTableName(sql);
22999
- if (tableName) {
23000
- const filterResult = this.blacklistValidator.filterColumns(tableName, rows, columnNames);
23001
- filteredRows = filterResult.filteredRows;
23002
- if (filterResult.omittedColumns.length > 0) {
23003
- omittedColumns = filterResult.omittedColumns;
23004
- columnNames = columnNames.filter((col) => !filterResult.omittedColumns.includes(col));
23005
- securityNotification = this.blacklistValidator.buildSecurityNotification(tableName, filterResult.omittedColumns);
23006
- }
23387
+ const filterResult = this.blacklistValidator.filterColumnsForTables(referencedTables, rows, columnNames);
23388
+ filteredRows = filterResult.filteredRows;
23389
+ if (filterResult.omittedColumns.length > 0) {
23390
+ omittedColumns = filterResult.omittedColumns;
23391
+ columnNames = columnNames.filter((col) => !filterResult.omittedColumns.includes(col));
23392
+ securityNotification = this.blacklistValidator.buildSecurityNotification(referencedTables[0] ?? "", filterResult.omittedColumns);
23007
23393
  }
23008
23394
  }
23009
23395
  if (options?.fieldSelection) {
@@ -23665,6 +24051,14 @@ class BlacklistManager {
23665
24051
  }
23666
24052
  return Array.from(columnSet);
23667
24053
  }
24054
+ getAllBlacklistedColumns() {
24055
+ const all = new Set;
24056
+ for (const columnSet of this.state.columns.values()) {
24057
+ for (const column of columnSet)
24058
+ all.add(column);
24059
+ }
24060
+ return Array.from(all);
24061
+ }
23668
24062
  canOverrideBlacklist() {
23669
24063
  return this.overrideEnabled;
23670
24064
  }
@@ -23672,28 +24066,122 @@ class BlacklistManager {
23672
24066
  return this.state;
23673
24067
  }
23674
24068
  }
24069
+ // src/utils/es-index-target.ts
24070
+ var MAX_DECODE_PASSES = 4;
24071
+ function unwrap(target) {
24072
+ let current = target;
24073
+ for (let pass = 0;pass < MAX_DECODE_PASSES; pass++) {
24074
+ let next = current;
24075
+ try {
24076
+ next = decodeURIComponent(next);
24077
+ } catch {}
24078
+ if (next.startsWith("<") && next.endsWith(">")) {
24079
+ next = next.slice(1, -1).replace(/\{[^}]*\}/g, "*");
24080
+ }
24081
+ if (next === current)
24082
+ return current;
24083
+ current = next;
24084
+ }
24085
+ return current;
24086
+ }
24087
+ function expandIndexTargets(target) {
24088
+ const concrete = [];
24089
+ const wildcards = [];
24090
+ const add = (candidate) => {
24091
+ if (candidate.length === 0)
24092
+ return;
24093
+ if (/[*?]/.test(candidate) || candidate.toLowerCase() === "_all")
24094
+ wildcards.push(candidate);
24095
+ else
24096
+ concrete.push(candidate);
24097
+ };
24098
+ for (const rawPart of unwrap(target).split(",")) {
24099
+ const part = unwrap(rawPart.trim()).trim().replace(/^[-+]/, "");
24100
+ if (part.length === 0)
24101
+ continue;
24102
+ add(part);
24103
+ if (part.includes(":"))
24104
+ for (const section of part.split(":"))
24105
+ add(section);
24106
+ }
24107
+ return { concrete, wildcards };
24108
+ }
24109
+ function matchesIndexGlob(pattern, name) {
24110
+ const normalized = pattern.toLowerCase() === "_all" ? "*" : pattern.toLowerCase();
24111
+ try {
24112
+ return globToRegex(normalized).test(name.toLowerCase());
24113
+ } catch {
24114
+ return true;
24115
+ }
24116
+ }
24117
+
23675
24118
  // src/core/blacklist-validator.ts
24119
+ function dedupe2(values) {
24120
+ const seen = new Set;
24121
+ const result = [];
24122
+ for (const value of values) {
24123
+ const key = value.toLowerCase();
24124
+ if (value.length === 0 || seen.has(key))
24125
+ continue;
24126
+ seen.add(key);
24127
+ result.push(value);
24128
+ }
24129
+ return result;
24130
+ }
24131
+
23676
24132
  class BlacklistValidator {
23677
24133
  manager;
23678
24134
  constructor(manager) {
23679
24135
  this.manager = manager;
23680
24136
  }
23681
- checkTableBlacklist(operation, tableName, _tableList = []) {
24137
+ checkTableBlacklist(operation, tableName, tableList = []) {
24138
+ this.checkTablesBlacklist(operation, [tableName, ...tableList]);
24139
+ }
24140
+ checkTablesBlacklist(operation, tableNames) {
24141
+ const tables = dedupe2(tableNames);
24142
+ if (tables.length === 0) {
24143
+ return;
24144
+ }
23682
24145
  if (this.manager.canOverrideBlacklist()) {
23683
- const message = t_vars("warnings.blacklist_override_used", {
24146
+ const message2 = t_vars("warnings.blacklist_override_used", {
23684
24147
  operation,
23685
- table: tableName
24148
+ table: tables.join(", ")
23686
24149
  });
23687
- console.error(message);
24150
+ console.error(message2);
23688
24151
  return;
23689
24152
  }
23690
- if (this.manager.isTableBlacklisted(tableName)) {
23691
- const message = t_vars("errors.table_blacklisted", {
23692
- table: tableName,
23693
- operation
23694
- });
23695
- throw new BlacklistError(message, tableName, operation);
24153
+ const blocked = tables.filter((table) => this.manager.isTableBlacklisted(table));
24154
+ if (blocked.length === 0) {
24155
+ return;
23696
24156
  }
24157
+ const message = t_vars("errors.table_blacklisted", {
24158
+ table: blocked.join(", "),
24159
+ operation
24160
+ });
24161
+ throw new BlacklistError(message, blocked[0], operation);
24162
+ }
24163
+ checkIndexBlacklist(operation, target) {
24164
+ const { concrete, wildcards } = expandIndexTargets(target);
24165
+ this.checkTablesBlacklist(operation, concrete);
24166
+ if (wildcards.length === 0 || this.manager.canOverrideBlacklist())
24167
+ return;
24168
+ const blacklisted = Array.from(this.manager.getState().tables);
24169
+ if (blacklisted.length === 0)
24170
+ return;
24171
+ const reachable = wildcards.filter((pattern) => blacklisted.some((entry) => matchesIndexGlob(pattern, entry)));
24172
+ if (reachable.length === 0)
24173
+ return;
24174
+ const message = t_vars("errors.table_blacklisted", {
24175
+ table: reachable.join(", "),
24176
+ operation
24177
+ });
24178
+ throw new BlacklistError(message, reachable[0], operation);
24179
+ }
24180
+ filterColumnsForIndexExpression(target, rows, columnList) {
24181
+ const { concrete, wildcards } = expandIndexTargets(target);
24182
+ const ruleKeys = Array.from(this.manager.getState().columns.keys());
24183
+ const reachable = ruleKeys.filter((key) => wildcards.some((pattern) => matchesIndexGlob(pattern, key)));
24184
+ return this.filterColumnsForTables([...concrete, ...reachable], rows, columnList);
23697
24185
  }
23698
24186
  checkColumnBlacklistOnWrite(tableName, fields, operation = "WRITE") {
23699
24187
  const blacklisted = this.manager.getBlacklistedColumns(tableName);
@@ -23720,7 +24208,11 @@ class BlacklistValidator {
23720
24208
  throw new BlacklistError(message, tableName, operation);
23721
24209
  }
23722
24210
  filterColumns(tableName, rows, columnList) {
23723
- const blacklistedColumns = this.manager.getBlacklistedColumns(tableName);
24211
+ return this.filterColumnsForTables([tableName], rows, columnList);
24212
+ }
24213
+ filterColumnsForTables(tableNames, rows, columnList) {
24214
+ const tables = dedupe2(tableNames);
24215
+ const blacklistedColumns = tables.length === 0 ? this.manager.getAllBlacklistedColumns() : Array.from(new Set(tables.flatMap((table) => this.manager.getBlacklistedColumns(table))));
23724
24216
  if (blacklistedColumns.length === 0) {
23725
24217
  return { filteredRows: rows, omittedColumns: [] };
23726
24218
  }