@carllee1983/dbcli 6.0.0 → 7.0.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.
@@ -51,7 +51,7 @@ var package_default;
51
51
  var init_package = __esm(() => {
52
52
  package_default = {
53
53
  name: "@carllee1983/dbcli",
54
- version: "6.0.0",
54
+ version: "7.0.0",
55
55
  description: "Database CLI for AI agents",
56
56
  type: "module",
57
57
  publishConfig: {
@@ -183,7 +183,9 @@ var init_package = __esm(() => {
183
183
  },
184
184
  overrides: {
185
185
  "brace-expansion": "^5.0.9",
186
- postcss: "^8.5.26"
186
+ browserslist: "^4.28.7",
187
+ postcss: "^8.5.26",
188
+ "postcss-selector-parser": "^6.1.3"
187
189
  }
188
190
  };
189
191
  });
@@ -9019,6 +9021,7 @@ var init_messages = __esm(() => {
9019
9021
  unsupported_lang: "Language '{lang}' not supported, using English",
9020
9022
  invalid_json: "Invalid JSON: {message}",
9021
9023
  table_blacklisted: "Error: Table '{table}' is blacklisted for {operation} operations",
9024
+ redis_target_blacklisted: "Redis command refused by blacklist",
9022
9025
  column_blacklisted_write: "Error: {operation} on table '{table}' touches blacklisted columns: {columns}",
9023
9026
  table_already_blacklisted: "Error: Table '{table}' is already blacklisted",
9024
9027
  table_not_in_blacklist: "Error: Table '{table}' is not in the blacklist",
@@ -9110,7 +9113,8 @@ Reference: {referencePath}`,
9110
9113
  columns_omitted: "Security: {count} column(s) were omitted based on your blacklist"
9111
9114
  },
9112
9115
  warnings: {
9113
- blacklist_override_used: "Warning: Blacklist override enabled (DBCLI_OVERRIDE_BLACKLIST=true). Executing {operation} on blacklisted table '{table}'"
9116
+ blacklist_override_used: "Warning: Blacklist override enabled (DBCLI_OVERRIDE_BLACKLIST=true). Executing {operation} on blacklisted table '{table}'",
9117
+ redis_blacklist_override_active: "Warning: Redis blacklist enforcement disabled by DBCLI_OVERRIDE_BLACKLIST=true"
9114
9118
  },
9115
9119
  version: {
9116
9120
  unsupported_warning: "{system} {version} is below minimum supported version {minVersion}. Some features may not work correctly.",
@@ -9337,6 +9341,7 @@ var init_messages2 = __esm(() => {
9337
9341
  unsupported_lang: "\u4E0D\u652F\u63F4\u7684\u8A9E\u8A00 '{lang}'\uFF0C\u4F7F\u7528\u82F1\u6587",
9338
9342
  invalid_json: "\u7121\u6548\u7684 JSON\uFF1A{message}",
9339
9343
  table_blacklisted: "\u932F\u8AA4: \u8868\u683C '{table}' \u7684 {operation} \u64CD\u4F5C\u5DF2\u88AB\u9ED1\u540D\u55AE",
9344
+ redis_target_blacklisted: "Redis \u6307\u4EE4\u5DF2\u88AB\u9ED1\u540D\u55AE\u62D2\u7D55",
9340
9345
  column_blacklisted_write: "\u932F\u8AA4: \u8868\u683C '{table}' \u7684 {operation} \u64CD\u4F5C\u5305\u542B\u9ED1\u540D\u55AE\u6B04\u4F4D: {columns}",
9341
9346
  table_already_blacklisted: "\u932F\u8AA4: \u8868\u683C '{table}' \u5DF2\u5728\u9ED1\u540D\u55AE\u4E2D",
9342
9347
  table_not_in_blacklist: "\u932F\u8AA4: \u8868\u683C '{table}' \u4E0D\u5728\u9ED1\u540D\u55AE\u4E2D",
@@ -9428,7 +9433,8 @@ var init_messages2 = __esm(() => {
9428
9433
  columns_omitted: "\u5B89\u5168: {count} \u500B\u6B04\u4F4D\u6839\u64DA\u4F60\u7684\u9ED1\u540D\u55AE\u5DF2\u88AB\u96B1\u85CF"
9429
9434
  },
9430
9435
  warnings: {
9431
- blacklist_override_used: "\u8B66\u544A: \u5DF2\u555F\u7528\u9ED1\u540D\u55AE\u8986\u84CB (DBCLI_OVERRIDE_BLACKLIST=true)\u3002\u57F7\u884C {operation} \u5728\u5DF2\u9ED1\u540D\u55AE\u7684\u8868\u683C '{table}' \u4E0A"
9436
+ blacklist_override_used: "\u8B66\u544A: \u5DF2\u555F\u7528\u9ED1\u540D\u55AE\u8986\u84CB (DBCLI_OVERRIDE_BLACKLIST=true)\u3002\u57F7\u884C {operation} \u5728\u5DF2\u9ED1\u540D\u55AE\u7684\u8868\u683C '{table}' \u4E0A",
9437
+ redis_blacklist_override_active: "\u8B66\u544A: DBCLI_OVERRIDE_BLACKLIST=true \u5DF2\u505C\u7528 Redis \u9ED1\u540D\u55AE\u5F37\u5236\u57F7\u884C"
9432
9438
  },
9433
9439
  version: {
9434
9440
  unsupported_warning: "{system} {version} \u4F4E\u65BC\u6700\u4F4E\u652F\u63F4\u7248\u672C {minVersion}\u3002\u90E8\u5206\u529F\u80FD\u53EF\u80FD\u7121\u6CD5\u6B63\u5E38\u904B\u4F5C\u3002",
@@ -11165,6 +11171,12 @@ var init_server_side_script = __esm(() => {
11165
11171
  ES_OPAQUE_BODY_KEYS = ["wrapper"];
11166
11172
  });
11167
11173
 
11174
+ // src/utils/case-fold.ts
11175
+ function foldCase(text2) {
11176
+ const lowered = (text2.includes("\u0130") ? text2.replaceAll("\u0130", "i") : text2).toLowerCase();
11177
+ return lowered.includes("\u03C2") ? lowered.replaceAll("\u03C2", "\u03C3") : lowered;
11178
+ }
11179
+
11168
11180
  // src/utils/glob.ts
11169
11181
  function findClassEnd(glob, open2) {
11170
11182
  for (let i = open2 + 1;i < glob.length; i++) {
@@ -11185,15 +11197,18 @@ function isValidCharacterClass(body) {
11185
11197
  return false;
11186
11198
  }
11187
11199
  }
11188
- function parseGlob(glob) {
11189
- const memo = parsedGlobs.get(glob);
11200
+ function parseGlob(glob, caseInsensitive) {
11201
+ const key = `${caseInsensitive ? "i" : "c"}\x00${glob}`;
11202
+ const memo = parsedGlobs.get(key);
11190
11203
  if (memo !== undefined)
11191
11204
  return memo;
11192
- const parsed = parseGlobUncached(glob);
11193
- parsedGlobs.set(glob, parsed);
11205
+ const parsed = parseGlobUncached(glob, caseInsensitive);
11206
+ if (parsedGlobs.size >= PARSED_GLOB_LIMIT)
11207
+ parsedGlobs.clear();
11208
+ parsedGlobs.set(key, parsed);
11194
11209
  return parsed;
11195
11210
  }
11196
- function parseGlobUncached(glob) {
11211
+ function parseGlobUncached(glob, caseInsensitive) {
11197
11212
  const runs = [];
11198
11213
  let current = [];
11199
11214
  let leadingStar = false;
@@ -11208,7 +11223,9 @@ function parseGlobUncached(glob) {
11208
11223
  const c = glob[i];
11209
11224
  if (c === "\\" && i + 1 < glob.length) {
11210
11225
  trailingStar = false;
11211
- current.push({ kind: "literal", char: glob[++i] });
11226
+ const escaped = String.fromCodePoint(glob.codePointAt(++i));
11227
+ i += escaped.length - 1;
11228
+ current.push(...literalToken(escaped, caseInsensitive));
11212
11229
  continue;
11213
11230
  }
11214
11231
  if (c === "*") {
@@ -11228,39 +11245,53 @@ function parseGlobUncached(glob) {
11228
11245
  const end = findClassEnd(glob, i);
11229
11246
  const body = end === -1 ? "" : glob.slice(i, end + 1);
11230
11247
  if (body !== "" && isValidCharacterClass(body)) {
11231
- current.push({ kind: "class", test: new RegExp(`^${body}$`, "s") });
11248
+ current.push({
11249
+ kind: "class",
11250
+ test: new RegExp(`^${body}$`, caseInsensitive ? "si" : "s")
11251
+ });
11232
11252
  i = end;
11233
11253
  continue;
11234
11254
  }
11235
- current.push({ kind: "literal", char: "[" });
11255
+ current.push(...literalToken("[", caseInsensitive));
11236
11256
  continue;
11237
11257
  }
11238
- current.push({ kind: "literal", char: c });
11258
+ const literal = String.fromCodePoint(glob.codePointAt(i));
11259
+ i += literal.length - 1;
11260
+ current.push(...literalToken(literal, caseInsensitive));
11239
11261
  }
11240
11262
  endRun();
11241
11263
  return { runs, leadingStar, trailingStar, anchored: !sawStar };
11242
11264
  }
11243
- function runMatchesAt(run, text2, at) {
11265
+ function literalToken(codePoint, caseInsensitive) {
11266
+ const folded = caseInsensitive ? foldCase(codePoint) : codePoint;
11267
+ return folded.split("").map((unit) => ({ kind: "literal", char: unit }));
11268
+ }
11269
+ function runMatchesAt(run, text2, raw, at) {
11244
11270
  if (at + run.length > text2.length)
11245
11271
  return false;
11246
11272
  for (let i = 0;i < run.length; i++) {
11247
11273
  const token = run[i];
11248
- const ch = text2[at + i];
11249
11274
  if (token.kind === "literal") {
11250
- if (token.char !== ch)
11275
+ if (token.char !== text2[at + i])
11251
11276
  return false;
11252
11277
  } else if (token.kind === "class") {
11253
- if (!token.test.test(ch))
11278
+ const rawChar = raw[at + i];
11279
+ if (rawChar === undefined) {
11280
+ throw new Error("globMatches: folded and unfolded subject lengths disagree");
11281
+ }
11282
+ if (!token.test.test(rawChar))
11254
11283
  return false;
11255
11284
  }
11256
11285
  }
11257
11286
  return true;
11258
11287
  }
11259
- function globMatches(glob, text2) {
11260
- const { runs, leadingStar, trailingStar, anchored } = parseGlob(glob);
11288
+ function globMatches(glob, subject, options) {
11289
+ const caseInsensitive = options?.caseInsensitive === true;
11290
+ const { runs, leadingStar, trailingStar, anchored } = parseGlob(glob, caseInsensitive);
11291
+ const text2 = caseInsensitive ? foldCase(subject) : subject;
11261
11292
  if (anchored) {
11262
11293
  const run = runs[0] ?? [];
11263
- return text2.length === run.length && runMatchesAt(run, text2, 0);
11294
+ return text2.length === run.length && runMatchesAt(run, text2, subject, 0);
11264
11295
  }
11265
11296
  if (runs.length === 0)
11266
11297
  return true;
@@ -11269,7 +11300,7 @@ function globMatches(glob, text2) {
11269
11300
  let last = runs.length;
11270
11301
  if (!leadingStar) {
11271
11302
  const head = runs[0];
11272
- if (!runMatchesAt(head, text2, 0))
11303
+ if (!runMatchesAt(head, text2, subject, 0))
11273
11304
  return false;
11274
11305
  cursor = head.length;
11275
11306
  first = 1;
@@ -11277,7 +11308,7 @@ function globMatches(glob, text2) {
11277
11308
  if (!trailingStar) {
11278
11309
  const tail = runs[last - 1];
11279
11310
  const at = text2.length - tail.length;
11280
- if (at < cursor || !runMatchesAt(tail, text2, at))
11311
+ if (at < cursor || !runMatchesAt(tail, text2, subject, at))
11281
11312
  return false;
11282
11313
  last -= 1;
11283
11314
  }
@@ -11286,7 +11317,7 @@ function globMatches(glob, text2) {
11286
11317
  const run = runs[r];
11287
11318
  let found = -1;
11288
11319
  for (let at = cursor;at + run.length <= limit; at++) {
11289
- if (runMatchesAt(run, text2, at)) {
11320
+ if (runMatchesAt(run, text2, subject, at)) {
11290
11321
  found = at;
11291
11322
  break;
11292
11323
  }
@@ -11297,6 +11328,9 @@ function globMatches(glob, text2) {
11297
11328
  }
11298
11329
  return true;
11299
11330
  }
11331
+ function isGlobPattern(value) {
11332
+ return /[*?[\\]/.test(value);
11333
+ }
11300
11334
  function escapeGlob(literal) {
11301
11335
  return literal.replace(/[*?[\]\\]/g, "\\$&");
11302
11336
  }
@@ -11322,11 +11356,17 @@ function globNeverMatches(glob) {
11322
11356
  }
11323
11357
  return null;
11324
11358
  }
11325
- var parsedGlobs;
11359
+ var parsedGlobs, PARSED_GLOB_LIMIT = 4096;
11326
11360
  var init_glob = __esm(() => {
11327
11361
  parsedGlobs = new Map;
11328
11362
  });
11329
11363
 
11364
+ // src/core/blacklist-fold.ts
11365
+ function foldFieldPath(path) {
11366
+ return foldCase(path);
11367
+ }
11368
+ var init_blacklist_fold = () => {};
11369
+
11330
11370
  // src/core/mongo/path-matcher.ts
11331
11371
  function compilePatterns(raw) {
11332
11372
  const patterns = [];
@@ -11364,6 +11404,9 @@ function compilePatterns(raw) {
11364
11404
  }
11365
11405
  return { patterns, rejected };
11366
11406
  }
11407
+ function matchSegment(pattern, segment) {
11408
+ return globMatches(pattern, segment, FOLD_CASE);
11409
+ }
11367
11410
  function matchAny(path, patterns) {
11368
11411
  if (patterns.length === 0)
11369
11412
  return false;
@@ -11378,7 +11421,7 @@ function matchAny(path, patterns) {
11378
11421
  }
11379
11422
  let ok = true;
11380
11423
  for (let i = 0;i < pat.segments.length; i++) {
11381
- if (!globMatches(pat.segments[i], pathSegments[i])) {
11424
+ if (!globMatches(pat.segments[i], pathSegments[i], FOLD_CASE)) {
11382
11425
  ok = false;
11383
11426
  break;
11384
11427
  }
@@ -11388,8 +11431,75 @@ function matchAny(path, patterns) {
11388
11431
  }
11389
11432
  return false;
11390
11433
  }
11434
+ function contiguousRules(literals, globs) {
11435
+ const widths = new Set;
11436
+ for (const rule of literals)
11437
+ widths.add(rule.split(".").length);
11438
+ return { literals, globs, literalWidths: [...widths].sort((a, b) => a - b) };
11439
+ }
11440
+ function reachesProtectedSegments(segments, rules) {
11441
+ const { literals, globs, literalWidths } = rules;
11442
+ const depth = segments.length;
11443
+ for (let start = 0;start < depth; start++) {
11444
+ for (const width of literalWidths) {
11445
+ const end = start + width;
11446
+ if (end > depth)
11447
+ break;
11448
+ const candidate = width === 1 ? segments[start] : segments.slice(start, end).join(".");
11449
+ if (literals.has(candidate))
11450
+ return true;
11451
+ }
11452
+ for (const pattern of globs) {
11453
+ const width = pattern.segments.length;
11454
+ if (start + width > depth)
11455
+ continue;
11456
+ let matched = true;
11457
+ for (let i = 0;i < width; i++) {
11458
+ if (!matchSegment(pattern.segments[i], segments[start + i])) {
11459
+ matched = false;
11460
+ break;
11461
+ }
11462
+ }
11463
+ if (matched)
11464
+ return true;
11465
+ }
11466
+ }
11467
+ return false;
11468
+ }
11469
+ function reachesProtectedPath(path, rules) {
11470
+ return reachesProtectedSegments(path.split("."), rules);
11471
+ }
11472
+ function contiguousRulesFor(protectedFields) {
11473
+ const memo = compiledRuleSets.get(protectedFields);
11474
+ if (memo !== undefined)
11475
+ return memo;
11476
+ const globbed = [];
11477
+ const literals = new Set;
11478
+ for (const rule of protectedFields) {
11479
+ if (isGlobPattern(rule))
11480
+ globbed.push(rule);
11481
+ else
11482
+ literals.add(foldFieldPath(rule));
11483
+ }
11484
+ let globs = [];
11485
+ if (globbed.length > 0) {
11486
+ const compiled = compilePatterns(globbed);
11487
+ if (compiled.rejected.length > 0) {
11488
+ const detail = compiled.rejected.map((r) => `'${r.raw}' (${r.reason})`).join(", ");
11489
+ throw new Error(`BlacklistRejection: blacklist entries this matcher cannot read: ${detail}`);
11490
+ }
11491
+ globs = compiled.patterns;
11492
+ }
11493
+ const rules = contiguousRules(literals, globs);
11494
+ compiledRuleSets.set(protectedFields, rules);
11495
+ return rules;
11496
+ }
11497
+ var FOLD_CASE, compiledRuleSets;
11391
11498
  var init_path_matcher = __esm(() => {
11392
11499
  init_glob();
11500
+ init_blacklist_fold();
11501
+ FOLD_CASE = { caseInsensitive: true };
11502
+ compiledRuleSets = new WeakMap;
11393
11503
  });
11394
11504
 
11395
11505
  // src/core/mongo/request-fields.ts
@@ -11400,40 +11510,10 @@ function asFieldPath(text2) {
11400
11510
  return text2.slice(1);
11401
11511
  return text2;
11402
11512
  }
11403
- function globRulesOf(protectedFields) {
11404
- const globbed = [...protectedFields].filter((rule) => /[*?[\\]/.test(rule));
11405
- if (globbed.length === 0)
11406
- return [];
11407
- const { patterns, rejected } = compilePatterns(globbed);
11408
- if (rejected.length > 0) {
11409
- const detail = rejected.map((r) => `'${r.raw}' (${r.reason})`).join(", ");
11410
- throw new Error(`BlacklistRejection: blacklist entries this matcher cannot read: ${detail}`);
11411
- }
11412
- return patterns;
11413
- }
11414
- function reachesProtectedField(path, protectedFields, globs) {
11415
- if (protectedFields.has(path))
11416
- return true;
11417
- if (globs.length > 0 && matchAny(path, globs))
11418
- return true;
11419
- const parts = path.split(".");
11420
- if (parts.length === 1)
11421
- return false;
11422
- for (let start = 0;start < parts.length; start += 1) {
11423
- for (let end = start + 1;end <= parts.length; end += 1) {
11424
- const candidate = parts.slice(start, end).join(".");
11425
- if (protectedFields.has(candidate))
11426
- return true;
11427
- if (globs.length > 0 && matchAny(candidate, globs))
11428
- return true;
11429
- }
11430
- }
11431
- return false;
11432
- }
11433
11513
  function findProtectedFieldReference(request, protectedFields) {
11434
11514
  if (protectedFields.size === 0)
11435
11515
  return;
11436
- const globs = globRulesOf(protectedFields);
11516
+ const rules = contiguousRulesFor(protectedFields);
11437
11517
  const candidates = [];
11438
11518
  let movesWholeDocument = false;
11439
11519
  const walk = (node) => {
@@ -11467,7 +11547,7 @@ function findProtectedFieldReference(request, protectedFields) {
11467
11547
  }
11468
11548
  };
11469
11549
  walk(request);
11470
- const named = candidates.find((path) => path.length > 0 && reachesProtectedField(path, protectedFields, globs));
11550
+ const named = candidates.find((path) => path.length > 0 && reachesProtectedPath(foldFieldPath(path), rules));
11471
11551
  if (named !== undefined)
11472
11552
  return named;
11473
11553
  return movesWholeDocument ? [...protectedFields][0] : undefined;
@@ -11490,10 +11570,12 @@ function protectedFieldsForRequest(request, collection, columns) {
11490
11570
  collectStrings(value);
11491
11571
  };
11492
11572
  collectStrings(request);
11493
- const namedLower = new Set([...named].map((value) => value.toLowerCase()));
11573
+ const namedFolded = new Set([...named].map(foldFieldPath));
11574
+ const collectionFolded = foldFieldPath(collection);
11494
11575
  const fields = new Set;
11495
11576
  for (const [name, rules] of Object.entries(columns)) {
11496
- const applies = name.toLowerCase() === collection.toLowerCase() || namedLower.has(name.toLowerCase());
11577
+ const folded = foldFieldPath(name);
11578
+ const applies = folded === collectionFolded || namedFolded.has(folded);
11497
11579
  if (!applies)
11498
11580
  continue;
11499
11581
  for (const rule of rules) {
@@ -11507,6 +11589,7 @@ function protectedFieldsForRequest(request, collection, columns) {
11507
11589
  var WHOLE_DOCUMENT_VARIABLES, RESHAPES_DOCUMENT, NAMES_A_FIELD_DYNAMICALLY = "$getField";
11508
11590
  var init_request_fields = __esm(() => {
11509
11591
  init_path_matcher();
11592
+ init_blacklist_fold();
11510
11593
  WHOLE_DOCUMENT_VARIABLES = new Set(["ROOT", "CURRENT"]);
11511
11594
  RESHAPES_DOCUMENT = new Set(["$objectToArray", "$replaceRoot", "$replaceWith"]);
11512
11595
  });
@@ -12493,6 +12576,15 @@ function sampleFromGlob(glob) {
12493
12576
  }
12494
12577
  return s;
12495
12578
  }
12579
+ function redisCommandTargets(command, args) {
12580
+ const spec = getCommandSpec(command);
12581
+ if (!spec)
12582
+ return [];
12583
+ return [
12584
+ ...expandKeyArity(spec.keyArity, args, args.length).map((index) => args[index]).filter((key) => key !== undefined),
12585
+ ...userPatterns(spec.keyArity, args)
12586
+ ];
12587
+ }
12496
12588
  function checkKeyArgs(command, args, rules) {
12497
12589
  if (rules.length === 0)
12498
12590
  return { ok: true };
@@ -13421,6 +13513,180 @@ var init_elasticsearch_adapter = __esm(() => {
13421
13513
  init_server_side_script();
13422
13514
  });
13423
13515
 
13516
+ // src/core/blacklist-manager.ts
13517
+ function isBlacklistOverrideEnabled(value) {
13518
+ return (value ?? Bun.env.DBCLI_OVERRIDE_BLACKLIST ?? "") === "true";
13519
+ }
13520
+ function normalizeBlacklistEntry(raw) {
13521
+ const trimmed = raw.trim();
13522
+ const quote = trimmed[0];
13523
+ if ((quote === '"' || quote === "`") && trimmed.length > 1 && trimmed.endsWith(quote)) {
13524
+ return trimmed.slice(1, -1).trim();
13525
+ }
13526
+ return trimmed;
13527
+ }
13528
+ function assertNotTableQualified(tableKey, column) {
13529
+ const dot = column.indexOf(".");
13530
+ if (dot <= 0)
13531
+ return;
13532
+ if (foldFieldPath(column.slice(0, dot)) !== foldFieldPath(tableKey))
13533
+ return;
13534
+ throw new Error(`[BlacklistManager] blacklist.columns["${tableKey}"] entry ${JSON.stringify(column)} is ` + `qualified with its own table and would never match. Write it as ` + `${JSON.stringify(column.slice(dot + 1))}.`);
13535
+ }
13536
+
13537
+ class BlacklistManager {
13538
+ config;
13539
+ state;
13540
+ overrideEnabled;
13541
+ rawTables = [];
13542
+ constructor(config, overrideEnvValue) {
13543
+ this.config = config;
13544
+ this.overrideEnabled = isBlacklistOverrideEnabled(overrideEnvValue);
13545
+ this.state = this.loadBlacklist();
13546
+ this.wildcardTables = this.rawTables.filter(isGlobPattern);
13547
+ }
13548
+ loadBlacklist() {
13549
+ this.rawTables.length = 0;
13550
+ const tables = new Set;
13551
+ const columns = new Map;
13552
+ const blacklistConfig = this.config.blacklist;
13553
+ if (!blacklistConfig) {
13554
+ return { tables, columns };
13555
+ }
13556
+ if (Array.isArray(blacklistConfig.tables)) {
13557
+ for (const tableName of blacklistConfig.tables) {
13558
+ if (typeof tableName === "string") {
13559
+ const entry = normalizeBlacklistEntry(tableName);
13560
+ this.rawTables.push(entry);
13561
+ tables.add(foldFieldPath(entry));
13562
+ } else {
13563
+ console.warn(`[BlacklistManager] Invalid table name in blacklist config: ${JSON.stringify(tableName)}`);
13564
+ }
13565
+ }
13566
+ } else if (blacklistConfig.tables !== undefined) {
13567
+ console.warn("[BlacklistManager] blacklist.tables must be an array, ignoring");
13568
+ }
13569
+ if (blacklistConfig.columns && typeof blacklistConfig.columns === "object" && !Array.isArray(blacklistConfig.columns)) {
13570
+ for (const [tableName, cols] of Object.entries(blacklistConfig.columns)) {
13571
+ if (typeof tableName !== "string") {
13572
+ console.warn(`[BlacklistManager] Invalid table name key in columns config: ${JSON.stringify(tableName)}`);
13573
+ continue;
13574
+ }
13575
+ if (!Array.isArray(cols)) {
13576
+ console.warn(`[BlacklistManager] blacklist.columns["${tableName}"] must be an array, ignoring`);
13577
+ continue;
13578
+ }
13579
+ const columnSet = new Set;
13580
+ for (const col of cols) {
13581
+ if (typeof col === "string") {
13582
+ const entry = normalizeBlacklistEntry(col);
13583
+ assertNotTableQualified(normalizeBlacklistEntry(tableName), entry);
13584
+ columnSet.add(entry);
13585
+ } else {
13586
+ console.warn(`[BlacklistManager] Invalid column name in blacklist.columns["${tableName}"]: ${JSON.stringify(col)}`);
13587
+ }
13588
+ }
13589
+ if (columnSet.size > 0) {
13590
+ columns.set(foldFieldPath(normalizeBlacklistEntry(tableName)), columnSet);
13591
+ }
13592
+ }
13593
+ } else if (blacklistConfig.columns !== undefined) {
13594
+ console.warn("[BlacklistManager] blacklist.columns must be an object, ignoring");
13595
+ }
13596
+ return { tables, columns };
13597
+ }
13598
+ isTableBlacklisted(tableName) {
13599
+ const name = foldFieldPath(tableName);
13600
+ if (this.state.tables.has(name))
13601
+ return true;
13602
+ for (const pattern of this.wildcardTables) {
13603
+ if (globMatches(pattern, tableName, { caseInsensitive: true }))
13604
+ return true;
13605
+ }
13606
+ return false;
13607
+ }
13608
+ wildcardTables;
13609
+ columnRulesFor(tableName) {
13610
+ const key = foldFieldPath(normalizeBlacklistEntry(tableName));
13611
+ const direct = this.state.columns.get(key);
13612
+ if (direct)
13613
+ return direct;
13614
+ const dot = key.lastIndexOf(".");
13615
+ if (dot > 0) {
13616
+ const bare = this.state.columns.get(key.slice(dot + 1));
13617
+ if (bare)
13618
+ return bare;
13619
+ }
13620
+ let merged;
13621
+ for (const [ruleKey, columns] of this.state.columns) {
13622
+ const ruleDot = ruleKey.lastIndexOf(".");
13623
+ if (ruleDot <= 0 || ruleKey.slice(ruleDot + 1) !== key)
13624
+ continue;
13625
+ merged ??= new Set;
13626
+ for (const column of columns)
13627
+ merged.add(column);
13628
+ }
13629
+ return merged;
13630
+ }
13631
+ isColumnBlacklisted(tableName, columnName) {
13632
+ const columnSet = this.columnRulesFor(tableName);
13633
+ if (!columnSet) {
13634
+ return false;
13635
+ }
13636
+ const target = foldFieldPath(normalizeBlacklistEntry(columnName));
13637
+ const compiled = this.foldedColumnsFor(columnSet);
13638
+ if (compiled.literals.has(target))
13639
+ return true;
13640
+ return compiled.globs.length > 0 && matchAny(target, compiled.globs);
13641
+ }
13642
+ foldedColumns = new WeakMap;
13643
+ foldedColumnsFor(columnSet) {
13644
+ const memo = this.foldedColumns.get(columnSet);
13645
+ if (memo !== undefined)
13646
+ return memo;
13647
+ const literals = new Set;
13648
+ const globbed = [];
13649
+ for (const rule of columnSet) {
13650
+ if (isGlobPattern(rule))
13651
+ globbed.push(rule);
13652
+ else
13653
+ literals.add(foldFieldPath(rule));
13654
+ }
13655
+ const folded = {
13656
+ literals,
13657
+ globs: globbed.length > 0 ? compilePatterns(globbed).patterns : []
13658
+ };
13659
+ this.foldedColumns.set(columnSet, folded);
13660
+ return folded;
13661
+ }
13662
+ getBlacklistedColumns(tableName) {
13663
+ const columnSet = this.columnRulesFor(tableName);
13664
+ if (!columnSet) {
13665
+ return [];
13666
+ }
13667
+ return Array.from(columnSet);
13668
+ }
13669
+ getAllBlacklistedColumns() {
13670
+ const all = new Set;
13671
+ for (const columnSet of this.state.columns.values()) {
13672
+ for (const column of columnSet)
13673
+ all.add(column);
13674
+ }
13675
+ return Array.from(all);
13676
+ }
13677
+ canOverrideBlacklist() {
13678
+ return this.overrideEnabled;
13679
+ }
13680
+ getState() {
13681
+ return this.state;
13682
+ }
13683
+ }
13684
+ var init_blacklist_manager = __esm(() => {
13685
+ init_glob();
13686
+ init_blacklist_fold();
13687
+ init_path_matcher();
13688
+ });
13689
+
13424
13690
  // src/adapters/factory.ts
13425
13691
  class AdapterFactory {
13426
13692
  static createSqlAdapter(rawOptions) {
@@ -13435,7 +13701,7 @@ class AdapterFactory {
13435
13701
  throw new Error(`createSqlAdapter requires a SQL system, got: ${options.system}`);
13436
13702
  }
13437
13703
  }
13438
- static createQueryableAdapter(rawOptions) {
13704
+ static createQueryableAdapterWithoutRules(rawOptions) {
13439
13705
  const options = withResolvedTimeout(rawOptions);
13440
13706
  switch (options.system) {
13441
13707
  case "mongodb":
@@ -13445,10 +13711,27 @@ class AdapterFactory {
13445
13711
  case "elasticsearch":
13446
13712
  return new ElasticsearchAdapter(options);
13447
13713
  default:
13448
- throw new Error(`createQueryableAdapter requires a non-SQL queryable system, got: ${options.system}`);
13714
+ throw new Error(`createQueryableAdapterWithoutRules requires a non-SQL queryable system, got: ${options.system}`);
13449
13715
  }
13450
13716
  }
13451
- static createAdapter(options) {
13717
+ static createAdapter(config) {
13718
+ const options = config.connection;
13719
+ switch (options.system) {
13720
+ case "postgresql":
13721
+ case "mysql":
13722
+ case "mariadb":
13723
+ return AdapterFactory.createSqlAdapter(options);
13724
+ case "redis":
13725
+ return AdapterFactory.createRedisAdapter(config);
13726
+ case "mongodb":
13727
+ return AdapterFactory.createMongoDBAdapter(config);
13728
+ case "elasticsearch":
13729
+ return AdapterFactory.createElasticsearchAdapter(options);
13730
+ default:
13731
+ throw new Error(`Unsupported database system: ${options.system}`);
13732
+ }
13733
+ }
13734
+ static createAdapterWithoutRules(options) {
13452
13735
  switch (options.system) {
13453
13736
  case "postgresql":
13454
13737
  case "mysql":
@@ -13457,7 +13740,7 @@ class AdapterFactory {
13457
13740
  case "mongodb":
13458
13741
  case "redis":
13459
13742
  case "elasticsearch":
13460
- return AdapterFactory.createQueryableAdapter(options);
13743
+ return AdapterFactory.createQueryableAdapterWithoutRules(options);
13461
13744
  default:
13462
13745
  throw new Error(`Unsupported database system: ${options.system}`);
13463
13746
  }
@@ -13467,7 +13750,7 @@ class AdapterFactory {
13467
13750
  if (options.system !== "mongodb") {
13468
13751
  throw new Error("createMongoDBAdapter requires system: mongodb");
13469
13752
  }
13470
- const adapter = AdapterFactory.createQueryableAdapter(options);
13753
+ const adapter = AdapterFactory.createQueryableAdapterWithoutRules(options);
13471
13754
  adapter.setBlacklistColumns(config.blacklist?.columns ?? {});
13472
13755
  return adapter;
13473
13756
  }
@@ -13478,7 +13761,12 @@ class AdapterFactory {
13478
13761
  }
13479
13762
  const options = withResolvedTimeout(rawOptions);
13480
13763
  const adapter = new RedisAdapter(options);
13481
- adapter.setBlacklistRules(config.blacklist?.tables ?? []);
13764
+ const blacklistRules = config.blacklist?.tables ?? [];
13765
+ const overrideEnabled = isBlacklistOverrideEnabled();
13766
+ if (overrideEnabled && blacklistRules.length > 0) {
13767
+ console.error(t("warnings.redis_blacklist_override_active"));
13768
+ }
13769
+ adapter.setBlacklistRules(overrideEnabled ? [] : blacklistRules);
13482
13770
  adapter.setMaskRules(config.redis?.mask ?? []);
13483
13771
  return adapter;
13484
13772
  }
@@ -13486,7 +13774,7 @@ class AdapterFactory {
13486
13774
  if (options.system !== "elasticsearch") {
13487
13775
  throw new Error("createElasticsearchAdapter requires system: elasticsearch");
13488
13776
  }
13489
- return AdapterFactory.createQueryableAdapter(options);
13777
+ return AdapterFactory.createQueryableAdapterWithoutRules(options);
13490
13778
  }
13491
13779
  }
13492
13780
  var init_factory = __esm(() => {
@@ -13495,6 +13783,8 @@ var init_factory = __esm(() => {
13495
13783
  init_mongodb_adapter();
13496
13784
  init_redis_adapter();
13497
13785
  init_elasticsearch_adapter();
13786
+ init_blacklist_manager();
13787
+ init_message_loader();
13498
13788
  });
13499
13789
 
13500
13790
  // src/adapters/index.ts
@@ -14109,7 +14399,7 @@ async function initCommandHandler(options, command) {
14109
14399
  password: String(resolveValue(newConfig.connection.password, "password")) || "",
14110
14400
  database: String(resolveValue(newConfig.connection.database, "database"))
14111
14401
  };
14112
- const adapter = AdapterFactory.createAdapter(testConnection);
14402
+ const adapter = AdapterFactory.createAdapterWithoutRules(testConnection);
14113
14403
  try {
14114
14404
  await adapter.connect();
14115
14405
  const isHealthy = await adapter.testConnection();
@@ -16651,147 +16941,6 @@ class ColumnIndexBuilder {
16651
16941
  };
16652
16942
  }
16653
16943
  }
16654
- // src/core/blacklist-manager.ts
16655
- function normalizeBlacklistEntry(raw) {
16656
- const trimmed = raw.trim();
16657
- const quote = trimmed[0];
16658
- if ((quote === '"' || quote === "`") && trimmed.length > 1 && trimmed.endsWith(quote)) {
16659
- return trimmed.slice(1, -1).trim();
16660
- }
16661
- return trimmed;
16662
- }
16663
- function assertNotTableQualified(tableKey, column) {
16664
- const dot = column.indexOf(".");
16665
- if (dot <= 0)
16666
- return;
16667
- if (column.slice(0, dot).toLowerCase() !== tableKey.toLowerCase())
16668
- return;
16669
- throw new Error(`[BlacklistManager] blacklist.columns["${tableKey}"] entry ${JSON.stringify(column)} is ` + `qualified with its own table and would never match. Write it as ` + `${JSON.stringify(column.slice(dot + 1))}.`);
16670
- }
16671
-
16672
- class BlacklistManager {
16673
- config;
16674
- state;
16675
- overrideEnabled;
16676
- constructor(config, overrideEnvValue) {
16677
- this.config = config;
16678
- this.overrideEnabled = (overrideEnvValue ?? Bun.env.DBCLI_OVERRIDE_BLACKLIST ?? "") === "true";
16679
- this.state = this.loadBlacklist();
16680
- this.wildcardTables = [...this.state.tables].filter((entry) => /[*?[\\]/.test(entry));
16681
- }
16682
- loadBlacklist() {
16683
- const tables = new Set;
16684
- const columns = new Map;
16685
- const blacklistConfig = this.config.blacklist;
16686
- if (!blacklistConfig) {
16687
- return { tables, columns };
16688
- }
16689
- if (Array.isArray(blacklistConfig.tables)) {
16690
- for (const tableName of blacklistConfig.tables) {
16691
- if (typeof tableName === "string") {
16692
- tables.add(normalizeBlacklistEntry(tableName).toLowerCase());
16693
- } else {
16694
- console.warn(`[BlacklistManager] Invalid table name in blacklist config: ${JSON.stringify(tableName)}`);
16695
- }
16696
- }
16697
- } else if (blacklistConfig.tables !== undefined) {
16698
- console.warn("[BlacklistManager] blacklist.tables must be an array, ignoring");
16699
- }
16700
- if (blacklistConfig.columns && typeof blacklistConfig.columns === "object" && !Array.isArray(blacklistConfig.columns)) {
16701
- for (const [tableName, cols] of Object.entries(blacklistConfig.columns)) {
16702
- if (typeof tableName !== "string") {
16703
- console.warn(`[BlacklistManager] Invalid table name key in columns config: ${JSON.stringify(tableName)}`);
16704
- continue;
16705
- }
16706
- if (!Array.isArray(cols)) {
16707
- console.warn(`[BlacklistManager] blacklist.columns["${tableName}"] must be an array, ignoring`);
16708
- continue;
16709
- }
16710
- const columnSet = new Set;
16711
- for (const col of cols) {
16712
- if (typeof col === "string") {
16713
- const entry = normalizeBlacklistEntry(col);
16714
- assertNotTableQualified(normalizeBlacklistEntry(tableName), entry);
16715
- columnSet.add(entry);
16716
- } else {
16717
- console.warn(`[BlacklistManager] Invalid column name in blacklist.columns["${tableName}"]: ${JSON.stringify(col)}`);
16718
- }
16719
- }
16720
- if (columnSet.size > 0) {
16721
- columns.set(normalizeBlacklistEntry(tableName).toLowerCase(), columnSet);
16722
- }
16723
- }
16724
- } else if (blacklistConfig.columns !== undefined) {
16725
- console.warn("[BlacklistManager] blacklist.columns must be an object, ignoring");
16726
- }
16727
- return { tables, columns };
16728
- }
16729
- isTableBlacklisted(tableName) {
16730
- const name = tableName.toLowerCase();
16731
- if (this.state.tables.has(name))
16732
- return true;
16733
- for (const pattern of this.wildcardTables) {
16734
- if (globMatches(pattern, name))
16735
- return true;
16736
- }
16737
- return false;
16738
- }
16739
- wildcardTables;
16740
- columnRulesFor(tableName) {
16741
- const key = normalizeBlacklistEntry(tableName).toLowerCase();
16742
- const direct = this.state.columns.get(key);
16743
- if (direct)
16744
- return direct;
16745
- const dot = key.lastIndexOf(".");
16746
- if (dot > 0) {
16747
- const bare = this.state.columns.get(key.slice(dot + 1));
16748
- if (bare)
16749
- return bare;
16750
- }
16751
- let merged;
16752
- for (const [ruleKey, columns] of this.state.columns) {
16753
- const ruleDot = ruleKey.lastIndexOf(".");
16754
- if (ruleDot <= 0 || ruleKey.slice(ruleDot + 1) !== key)
16755
- continue;
16756
- merged ??= new Set;
16757
- for (const column of columns)
16758
- merged.add(column);
16759
- }
16760
- return merged;
16761
- }
16762
- isColumnBlacklisted(tableName, columnName) {
16763
- const columnSet = this.columnRulesFor(tableName);
16764
- if (!columnSet) {
16765
- return false;
16766
- }
16767
- return columnSet.has(normalizeBlacklistEntry(columnName).toLowerCase());
16768
- }
16769
- getBlacklistedColumns(tableName) {
16770
- const columnSet = this.columnRulesFor(tableName);
16771
- if (!columnSet) {
16772
- return [];
16773
- }
16774
- return Array.from(columnSet);
16775
- }
16776
- getAllBlacklistedColumns() {
16777
- const all = new Set;
16778
- for (const columnSet of this.state.columns.values()) {
16779
- for (const column of columnSet)
16780
- all.add(column);
16781
- }
16782
- return Array.from(all);
16783
- }
16784
- canOverrideBlacklist() {
16785
- return this.overrideEnabled;
16786
- }
16787
- getState() {
16788
- return this.state;
16789
- }
16790
- }
16791
- var init_blacklist_manager = __esm(() => {
16792
- init_glob();
16793
- });
16794
-
16795
16944
  // src/types/blacklist.ts
16796
16945
  var BlacklistError;
16797
16946
  var init_blacklist = __esm(() => {
@@ -16855,18 +17004,138 @@ function projectRows(rows, selection) {
16855
17004
  const columnNames = collectColumnNames(projectedRows);
16856
17005
  return { rows: normalizeRows(projectedRows, columnNames), columnNames };
16857
17006
  }
16858
- function hasFieldPath(row, segments) {
16859
- return readPath(row, segments).found;
17007
+ function hasFieldPath(row, segments, options) {
17008
+ const caseInsensitive = options?.caseInsensitive === true;
17009
+ const folded = caseInsensitive && options?.alreadyFolded !== true ? segments.map(foldCase) : segments;
17010
+ return readPath(row, folded, caseInsensitive).found;
17011
+ }
17012
+ function narrow(rules, key) {
17013
+ const parts = key.includes(".") ? key.split(".") : [key];
17014
+ const live = [];
17015
+ for (const rule of rules) {
17016
+ const remaining = rule.segments.length;
17017
+ if (remaining < parts.length && !rule.wildcardTail)
17018
+ continue;
17019
+ let matches = true;
17020
+ for (let i = 0;i < Math.min(remaining, parts.length); i++) {
17021
+ if (!matchSegment(rule.segments[i], parts[i])) {
17022
+ matches = false;
17023
+ break;
17024
+ }
17025
+ }
17026
+ if (!matches)
17027
+ continue;
17028
+ if (remaining <= parts.length)
17029
+ return { drop: true, live: [] };
17030
+ live.push({
17031
+ root: rule.root,
17032
+ rootIndex: rule.rootIndex,
17033
+ offset: rule.offset + parts.length,
17034
+ segments: rule.segments.slice(parts.length),
17035
+ wildcardTail: rule.wildcardTail
17036
+ });
17037
+ }
17038
+ return { drop: false, live };
17039
+ }
17040
+ function liveRulesOf(patterns) {
17041
+ return patterns.map((pattern, index) => ({
17042
+ root: pattern,
17043
+ rootIndex: index,
17044
+ offset: 0,
17045
+ segments: pattern.segments,
17046
+ wildcardTail: pattern.wildcardTail
17047
+ }));
16860
17048
  }
16861
- function omitFieldPaths(rows, paths) {
16862
- const topLevel = new Set(paths);
16863
- const dotted = paths.filter((path) => path.includes(".")).map((path) => ({ head: path.slice(0, path.indexOf(".")), segments: path.split(".") }));
17049
+
17050
+ class NarrowStates {
17051
+ states = [];
17052
+ ids = new Map;
17053
+ transitions = new Map;
17054
+ constructor(rules) {
17055
+ this.intern(rules);
17056
+ }
17057
+ rules(state) {
17058
+ const rules = this.states[state];
17059
+ if (rules === undefined)
17060
+ throw new Error(`unknown nested-rule state ${state}`);
17061
+ return rules;
17062
+ }
17063
+ step(state, key) {
17064
+ const memoKey = `${state}\x00${key}`;
17065
+ const memo = this.transitions.get(memoKey);
17066
+ if (memo !== undefined)
17067
+ return memo;
17068
+ const { drop, live } = narrow(this.rules(state), key);
17069
+ const step = drop ? { drop: true } : { drop: false, next: this.intern(live) };
17070
+ if (this.transitions.size >= NARROW_STATE_LIMIT)
17071
+ this.transitions = new Map;
17072
+ this.transitions.set(memoKey, step);
17073
+ return step;
17074
+ }
17075
+ intern(rules) {
17076
+ const key = rules.map((rule) => `${rule.rootIndex}:${rule.offset}`).join(",");
17077
+ const existing = this.ids.get(key);
17078
+ if (existing !== undefined)
17079
+ return existing;
17080
+ const id = this.states.length;
17081
+ this.states.push(rules);
17082
+ this.ids.set(key, id);
17083
+ return id;
17084
+ }
17085
+ }
17086
+ function collectNestedMatches(row, states, pending, onMatch) {
17087
+ if (pending.size === 0)
17088
+ return;
17089
+ const walk = (value, state, depth) => {
17090
+ if (pending.size === 0 || states.rules(state).length === 0)
17091
+ return;
17092
+ if (Array.isArray(value)) {
17093
+ for (const item of value)
17094
+ walk(item, state, depth);
17095
+ return;
17096
+ }
17097
+ if (!isPlainRecord(value))
17098
+ return;
17099
+ for (const key of Object.getOwnPropertyNames(value)) {
17100
+ const step = states.step(state, key);
17101
+ if (step.drop) {
17102
+ if (depth > 0) {
17103
+ for (const rule of states.rules(state)) {
17104
+ if (!pending.has(rule.root))
17105
+ continue;
17106
+ if (!narrow([rule], key).drop)
17107
+ continue;
17108
+ pending.delete(rule.root);
17109
+ onMatch(rule.root);
17110
+ }
17111
+ }
17112
+ continue;
17113
+ }
17114
+ walk(value[key], step.next, depth + 1);
17115
+ }
17116
+ };
17117
+ walk(row, 0, 0);
17118
+ }
17119
+ function compileNestedRules(patterns) {
17120
+ return new NarrowStates(liveRulesOf(patterns));
17121
+ }
17122
+ function omitFieldPaths(rows, paths, options) {
17123
+ const caseInsensitive = options?.caseInsensitive === true;
17124
+ const nestedPatterns = options?.patterns ?? [];
17125
+ const nestedStates = nestedPatterns.length > 0 ? compileNestedRules(nestedPatterns) : undefined;
17126
+ const fold = (value) => caseInsensitive ? foldCase(value) : value;
17127
+ const topLevel = new Set(paths.map(fold));
17128
+ const dotted = paths.filter((path) => path.includes(".")).map((path) => ({ head: fold(path.slice(0, path.indexOf("."))), segments: path.split(".") }));
16864
17129
  const maskRecord = (row) => {
16865
- let projected = cloneRecord(row, topLevel);
17130
+ let projected = cloneRecord(row, topLevel, caseInsensitive);
16866
17131
  for (const { head, segments } of dotted) {
16867
- const value = row[head];
16868
- if (value !== null && typeof value === "object")
16869
- projected = omitPath(projected, segments);
17132
+ const value = headValue(row, head, caseInsensitive);
17133
+ if (value !== null && typeof value === "object") {
17134
+ projected = omitPath(projected, segments, caseInsensitive);
17135
+ }
17136
+ }
17137
+ if (nestedStates !== undefined && hasNestedRecord(row)) {
17138
+ projected = omitMatching(projected, nestedStates, 0);
16870
17139
  }
16871
17140
  return projected;
16872
17141
  };
@@ -16905,11 +17174,37 @@ function validatePath(path) {
16905
17174
  if (unsafe)
16906
17175
  throw new Error(`--fields contains an unsafe field segment: ${unsafe}`);
16907
17176
  }
16908
- function readPath(value, segments) {
17177
+ function headValue(row, head, caseInsensitive) {
17178
+ if (!caseInsensitive)
17179
+ return row[head];
17180
+ const key = foldedKeys(row).get(head);
17181
+ return key === undefined ? undefined : row[key];
17182
+ }
17183
+ function foldedKeys(value) {
17184
+ const memo = foldedKeyIndex.get(value);
17185
+ if (memo !== undefined)
17186
+ return memo;
17187
+ const index = new Map;
17188
+ for (const key of Object.getOwnPropertyNames(value)) {
17189
+ const folded = foldCase(key);
17190
+ if (!index.has(folded))
17191
+ index.set(folded, key);
17192
+ }
17193
+ foldedKeyIndex.set(value, index);
17194
+ return index;
17195
+ }
17196
+ function ownKey(value, name, caseInsensitive) {
17197
+ if (Object.prototype.hasOwnProperty.call(value, name))
17198
+ return name;
17199
+ if (!caseInsensitive)
17200
+ return;
17201
+ return foldedKeys(value).get(name);
17202
+ }
17203
+ function readPath(value, segments, caseInsensitive = false) {
16909
17204
  if (segments.length === 0)
16910
17205
  return { found: true, value };
16911
17206
  if (Array.isArray(value)) {
16912
- const results = value.map((item) => readPath(item, segments));
17207
+ const results = value.map((item) => readPath(item, segments, caseInsensitive));
16913
17208
  return {
16914
17209
  found: results.some((result) => result.found),
16915
17210
  value: results.map((result) => result.found ? result.value : undefined)
@@ -16917,41 +17212,68 @@ function readPath(value, segments) {
16917
17212
  }
16918
17213
  if (!isRecord(value))
16919
17214
  return { found: false };
16920
- const exactPath = segments.join(".");
16921
- if (Object.prototype.hasOwnProperty.call(value, exactPath)) {
16922
- return { found: true, value: value[exactPath] };
16923
- }
17215
+ const exact = ownKey(value, segments.join("."), caseInsensitive);
17216
+ if (exact !== undefined)
17217
+ return { found: true, value: value[exact] };
16924
17218
  const [head, ...tail] = segments;
16925
- if (!Object.prototype.hasOwnProperty.call(value, head))
17219
+ const headKey = ownKey(value, head, caseInsensitive);
17220
+ if (headKey === undefined)
16926
17221
  return { found: false };
16927
- return readPath(value[head], tail);
17222
+ return readPath(value[headKey], tail, caseInsensitive);
16928
17223
  }
16929
- function omitPath(value, segments) {
17224
+ function omitMatching(value, states, state) {
17225
+ if (states.rules(state).length === 0)
17226
+ return value;
17227
+ if (Array.isArray(value))
17228
+ return value.map((item) => omitMatching(item, states, state));
17229
+ if (!isPlainRecord(value))
17230
+ return value;
17231
+ const out = {};
17232
+ for (const [key, child] of Object.entries(value)) {
17233
+ const step = states.step(state, key);
17234
+ if (step.drop)
17235
+ continue;
17236
+ defineData(out, key, omitMatching(child, states, step.next));
17237
+ }
17238
+ return out;
17239
+ }
17240
+ function hasNestedRecord(row) {
17241
+ for (const key of Object.getOwnPropertyNames(row)) {
17242
+ const value = row[key];
17243
+ if (Array.isArray(value) || isPlainRecord(value))
17244
+ return true;
17245
+ }
17246
+ return false;
17247
+ }
17248
+ function omitPath(value, segments, caseInsensitive) {
16930
17249
  if (segments.length === 0)
16931
17250
  return value;
16932
17251
  if (Array.isArray(value))
16933
- return value.map((item) => omitPath(item, segments));
17252
+ return value.map((item) => omitPath(item, segments, caseInsensitive));
16934
17253
  if (!isPlainRecord(value))
16935
17254
  return value;
16936
- const exactPath = segments.join(".");
16937
- const [head, ...tail] = segments;
17255
+ const fold = (name) => caseInsensitive ? foldCase(name) : name;
17256
+ const exactPath = fold(segments.join("."));
17257
+ const head = fold(segments[0]);
17258
+ const tail = segments.slice(1);
16938
17259
  const out = {};
16939
17260
  for (const [key, child] of Object.entries(value)) {
16940
- if (key === exactPath)
17261
+ const folded = fold(key);
17262
+ if (folded === exactPath)
16941
17263
  continue;
16942
- if (key === head) {
17264
+ if (folded === head) {
16943
17265
  if (tail.length > 0)
16944
- defineData(out, key, omitPath(child, tail));
17266
+ defineData(out, key, omitPath(child, tail, caseInsensitive));
16945
17267
  continue;
16946
17268
  }
16947
17269
  defineData(out, key, child);
16948
17270
  }
16949
17271
  return out;
16950
17272
  }
16951
- function cloneRecord(value, omittedKeys) {
17273
+ function cloneRecord(value, omittedKeys, caseInsensitive = false) {
16952
17274
  const out = {};
16953
17275
  for (const [key, child] of Object.entries(value)) {
16954
- if (!omittedKeys.has(key))
17276
+ if (!omittedKeys.has(caseInsensitive ? foldCase(key) : key))
16955
17277
  defineData(out, key, child);
16956
17278
  }
16957
17279
  return out;
@@ -16996,9 +17318,11 @@ function defineData(target, key, value) {
16996
17318
  writable: true
16997
17319
  });
16998
17320
  }
16999
- var UNSAFE_SEGMENTS;
17321
+ var UNSAFE_SEGMENTS, NARROW_STATE_LIMIT = 4096, foldedKeyIndex;
17000
17322
  var init_field_projection = __esm(() => {
17323
+ init_path_matcher();
17001
17324
  UNSAFE_SEGMENTS = new Set(["__proto__", "prototype", "constructor"]);
17325
+ foldedKeyIndex = new WeakMap;
17002
17326
  });
17003
17327
 
17004
17328
  // src/utils/es-index-target.ts
@@ -17065,16 +17389,16 @@ function expandIndexTargets(target) {
17065
17389
  return { concrete, wildcards };
17066
17390
  }
17067
17391
  function matchesIndexGlob(pattern, name) {
17068
- const normalized = pattern.toLowerCase() === "_all" ? "*" : pattern.toLowerCase();
17392
+ const normalized = pattern.toLowerCase() === "_all" ? "*" : pattern;
17069
17393
  try {
17070
- return globMatches(normalized, name.toLowerCase());
17394
+ return globMatches(normalized, name, { caseInsensitive: true });
17071
17395
  } catch {
17072
17396
  return true;
17073
17397
  }
17074
17398
  }
17075
17399
  function reachesByConvention(name, entry) {
17076
- const lower = name.toLowerCase();
17077
- const target = entry.toLowerCase();
17400
+ const lower = foldFieldPath(name);
17401
+ const target = foldFieldPath(entry);
17078
17402
  return new RegExp(`^\\.ds-${escapeRegExp(target)}-`).test(lower) || new RegExp(`^${escapeRegExp(target)}-\\d+$`).test(lower);
17079
17403
  }
17080
17404
  function escapeRegExp(text2) {
@@ -17095,11 +17419,12 @@ function indexExpressionReaches(expression, blacklisted) {
17095
17419
  return false;
17096
17420
  const { concrete, wildcards } = expandIndexTargets(expression);
17097
17421
  const entries = expandBlacklistEntries(blacklisted);
17098
- const reachesName = (name) => entries.names.some((entry) => entry.toLowerCase() === name.toLowerCase() || reachesByConvention(name, entry)) || entries.patterns.some((pattern) => matchesIndexGlob(pattern, name));
17422
+ const reachesName = (name) => entries.names.some((entry) => foldFieldPath(entry) === foldFieldPath(name) || reachesByConvention(name, entry)) || entries.patterns.some((pattern) => matchesIndexGlob(pattern, name));
17099
17423
  return concrete.some(reachesName) || wildcards.some((pattern) => entries.names.some((entry) => matchesIndexGlob(pattern, entry)) || entries.patterns.length > 0);
17100
17424
  }
17101
17425
  var MAX_DECODE_PASSES = 4;
17102
17426
  var init_es_index_target = __esm(() => {
17427
+ init_blacklist_fold();
17103
17428
  init_glob();
17104
17429
  });
17105
17430
 
@@ -17118,7 +17443,7 @@ function dedupe2(values) {
17118
17443
  const seen = new Set;
17119
17444
  const result = [];
17120
17445
  for (const value of values) {
17121
- const key = value.toLowerCase();
17446
+ const key = foldFieldPath(value);
17122
17447
  if (value.length === 0 || seen.has(key))
17123
17448
  continue;
17124
17449
  seen.add(key);
@@ -17126,11 +17451,14 @@ function dedupe2(values) {
17126
17451
  }
17127
17452
  return result;
17128
17453
  }
17129
- function compileGlobRules(rules, fold, table, operation) {
17130
- const globbed = rules.filter((rule) => /[*?[\\]/.test(rule));
17454
+ function omittedByName(omittedColumns, path) {
17455
+ return omittedColumns.some((omitted) => path === omitted || path.startsWith(`${omitted}.`));
17456
+ }
17457
+ function compileGlobRules(rules, table, operation) {
17458
+ const globbed = rules.filter(isGlobPattern);
17131
17459
  if (globbed.length === 0)
17132
17460
  return [];
17133
- const { patterns, rejected } = compilePatterns(globbed.map(fold));
17461
+ const { patterns, rejected } = compilePatterns(globbed);
17134
17462
  if (rejected.length > 0) {
17135
17463
  const detail = rejected.map((r) => `'${r.raw}' (${r.reason})`).join(", ");
17136
17464
  throw new BlacklistError(`blacklist.columns for '${table}' has entries this matcher cannot read: ${detail}`, table, operation);
@@ -17194,10 +17522,10 @@ class BlacklistValidator {
17194
17522
  if (blacklisted.length === 0 || fields.length === 0) {
17195
17523
  return;
17196
17524
  }
17197
- const protectedPaths = new Set(blacklisted.map((name) => name.toLowerCase()));
17198
- const globs = compileGlobRules(blacklisted, (value) => value.toLowerCase(), tableName, operation);
17525
+ const protectedPaths = new Set(blacklisted.map(foldFieldPath));
17526
+ const globs = compileGlobRules(blacklisted, tableName, operation);
17199
17527
  const conflicts = fields.filter((field) => {
17200
- const name = field.toLowerCase();
17528
+ const name = foldFieldPath(field);
17201
17529
  if (protectedPaths.has(name))
17202
17530
  return true;
17203
17531
  if (matchAny(name, globs))
@@ -17240,7 +17568,7 @@ class BlacklistValidator {
17240
17568
  const tables = dedupe2(tableNames);
17241
17569
  const blacklistedColumns = tables.length === 0 ? this.manager.getAllBlacklistedColumns() : Array.from(new Set(tables.flatMap((table) => this.manager.getBlacklistedColumns(table))));
17242
17570
  if (blacklistedColumns.length === 0) {
17243
- return { filteredRows: rows, omittedColumns: [] };
17571
+ return { filteredRows: rows, omittedColumns: [], reachesOmitted: () => false };
17244
17572
  }
17245
17573
  const probeNested = blacklistedColumns.some((path) => path.includes("."));
17246
17574
  const presentColumns = new Set(columnList);
@@ -17252,7 +17580,7 @@ class BlacklistValidator {
17252
17580
  continue;
17253
17581
  const value = record[key];
17254
17582
  if (value !== null && typeof value === "object")
17255
- nestedHeads.add(key);
17583
+ nestedHeads.add(foldFieldPath(key));
17256
17584
  }
17257
17585
  };
17258
17586
  for (const row of rows) {
@@ -17265,35 +17593,54 @@ class BlacklistValidator {
17265
17593
  }
17266
17594
  collect(row);
17267
17595
  }
17268
- const foldHead = (path) => {
17269
- const dot = path.indexOf(".");
17270
- return dot < 0 ? path.toLowerCase() : path.slice(0, dot).toLowerCase() + path.slice(dot);
17271
- };
17272
- const protectedPaths = new Set(blacklistedColumns.map(foldHead));
17273
- const globRules = compileGlobRules(blacklistedColumns, foldHead, tables[0] ?? "unknown", "SELECT");
17596
+ const protectedPaths = new Set(blacklistedColumns.map(foldFieldPath));
17597
+ const globRules = compileGlobRules(blacklistedColumns, tables[0] ?? "unknown", "SELECT");
17274
17598
  const presentByFolded = new Map;
17275
- for (const column of presentColumns)
17276
- presentByFolded.set(foldHead(column), column);
17599
+ for (const column of presentColumns) {
17600
+ const folded = foldFieldPath(column);
17601
+ const names = presentByFolded.get(folded);
17602
+ if (names === undefined)
17603
+ presentByFolded.set(folded, [column]);
17604
+ else
17605
+ names.push(column);
17606
+ }
17277
17607
  const omitted = new Set;
17278
17608
  for (const path of blacklistedColumns) {
17279
- const present = presentByFolded.get(foldHead(path));
17609
+ const present = presentByFolded.get(foldFieldPath(path));
17280
17610
  if (present !== undefined) {
17281
- omitted.add(present);
17611
+ for (const name of present)
17612
+ omitted.add(name);
17282
17613
  continue;
17283
17614
  }
17284
17615
  const dot = path.indexOf(".");
17285
17616
  if (dot < 0)
17286
17617
  continue;
17287
- if (!nestedHeads.has(path.slice(0, dot)))
17618
+ if (!nestedHeads.has(foldFieldPath(path.slice(0, dot))))
17288
17619
  continue;
17289
- const segments = path.split(".");
17290
- if (rows.some((row) => hasFieldPath(row, segments)))
17620
+ const segments = path.split(".").map(foldFieldPath);
17621
+ if (rows.some((row) => hasFieldPath(row, segments, PRE_FOLDED)))
17291
17622
  omitted.add(path);
17292
17623
  }
17624
+ const nestedGlobs = globRules.filter((pattern) => pattern.segments.length > 1);
17625
+ const matchedNested = [];
17626
+ if (nestedGlobs.length > 0 && nestedHeads.size > 0) {
17627
+ const pending = new Set(nestedGlobs);
17628
+ const states = compileNestedRules(nestedGlobs);
17629
+ for (const row of rows) {
17630
+ if (pending.size === 0)
17631
+ break;
17632
+ if (row === null || typeof row !== "object")
17633
+ continue;
17634
+ collectNestedMatches(row, states, pending, (pattern) => {
17635
+ matchedNested.push(pattern);
17636
+ omitted.add(pattern.raw);
17637
+ });
17638
+ }
17639
+ }
17293
17640
  for (const column of presentColumns) {
17294
17641
  let dot = column.indexOf(".");
17295
17642
  while (dot >= 0) {
17296
- if (dot > 0 && protectedPaths.has(column.slice(0, dot).toLowerCase())) {
17643
+ if (dot > 0 && protectedPaths.has(foldFieldPath(column.slice(0, dot)))) {
17297
17644
  omitted.add(column);
17298
17645
  break;
17299
17646
  }
@@ -17302,7 +17649,7 @@ class BlacklistValidator {
17302
17649
  }
17303
17650
  if (globRules.length > 0) {
17304
17651
  for (const column of presentColumns) {
17305
- const folded = foldHead(column);
17652
+ const folded = foldFieldPath(column);
17306
17653
  if (matchAny(folded, globRules)) {
17307
17654
  omitted.add(column);
17308
17655
  continue;
@@ -17319,10 +17666,30 @@ class BlacklistValidator {
17319
17666
  }
17320
17667
  const omittedColumns = Array.from(omitted);
17321
17668
  if (omittedColumns.length === 0) {
17322
- return { filteredRows: rows, omittedColumns: [] };
17669
+ return { filteredRows: rows, omittedColumns: [], reachesOmitted: () => false };
17323
17670
  }
17324
- const filteredRows = omitFieldPaths(rows, omittedColumns);
17325
- return { filteredRows, omittedColumns };
17671
+ const removalPaths = matchedNested.length === 0 ? omittedColumns : omittedColumns.filter((path) => !matchedNested.some((p) => p.raw === path));
17672
+ const filteredRows = omitFieldPaths(rows, removalPaths, matchedNested.length > 0 ? { ...FOLD_CASE2, patterns: matchedNested } : FOLD_CASE2);
17673
+ return {
17674
+ filteredRows,
17675
+ omittedColumns,
17676
+ reachesOmitted: (path) => {
17677
+ if (omittedByName(removalPaths, path))
17678
+ return true;
17679
+ if (matchedNested.length === 0)
17680
+ return false;
17681
+ const folded = foldFieldPath(path);
17682
+ if (matchAny(folded, matchedNested))
17683
+ return true;
17684
+ let dot = folded.indexOf(".");
17685
+ while (dot > 0) {
17686
+ if (matchAny(folded.slice(0, dot), matchedNested))
17687
+ return true;
17688
+ dot = folded.indexOf(".", dot + 1);
17689
+ }
17690
+ return false;
17691
+ }
17692
+ };
17326
17693
  }
17327
17694
  buildSecurityNotification(_tableName, omittedColumns) {
17328
17695
  if (omittedColumns.length === 0) {
@@ -17333,12 +17700,17 @@ class BlacklistValidator {
17333
17700
  });
17334
17701
  }
17335
17702
  }
17703
+ var FOLD_CASE2, PRE_FOLDED;
17336
17704
  var init_blacklist_validator = __esm(() => {
17705
+ init_glob();
17337
17706
  init_blacklist();
17338
17707
  init_message_loader();
17339
17708
  init_field_projection();
17340
17709
  init_path_matcher();
17710
+ init_blacklist_fold();
17341
17711
  init_es_index_target();
17712
+ FOLD_CASE2 = { caseInsensitive: true };
17713
+ PRE_FOLDED = { caseInsensitive: true, alreadyFolded: true };
17342
17714
  });
17343
17715
 
17344
17716
  // src/core/verification/types.ts
@@ -22055,7 +22427,7 @@ Key: ${schema.name}`);
22055
22427
  await esAdapter.disconnect();
22056
22428
  }
22057
22429
  }
22058
- const adapter = AdapterFactory.createAdapter(config.connection);
22430
+ const adapter = AdapterFactory.createAdapter(config);
22059
22431
  await adapter.connect();
22060
22432
  const mongoMeta = config.connection.system === "mongodb" ? {
22061
22433
  blacklist: config.blacklist ?? { tables: [], columns: {} },
@@ -22544,12 +22916,12 @@ class QueryExecutor {
22544
22916
  let columnNames = rows.length > 0 && rows[0] ? Object.keys(rows[0]) : [];
22545
22917
  let filteredRows = rows;
22546
22918
  let securityNotification;
22547
- let omittedColumns = [];
22919
+ let reachesOmitted = () => false;
22548
22920
  if (this.blacklistValidator) {
22549
22921
  const filterResult = this.blacklistValidator.filterColumnsForTables(referencedTables, rows, columnNames);
22550
22922
  filteredRows = filterResult.filteredRows;
22551
22923
  if (filterResult.omittedColumns.length > 0) {
22552
- omittedColumns = filterResult.omittedColumns;
22924
+ reachesOmitted = filterResult.reachesOmitted;
22553
22925
  columnNames = columnNames.filter((col) => !filterResult.omittedColumns.includes(col));
22554
22926
  securityNotification = this.blacklistValidator.buildSecurityNotification(referencedTables[0] ?? "", filterResult.omittedColumns);
22555
22927
  }
@@ -22557,7 +22929,7 @@ class QueryExecutor {
22557
22929
  if (options?.fieldSelection) {
22558
22930
  const fieldSelection = options.fieldSelection.mode === "include" ? {
22559
22931
  mode: "include",
22560
- paths: options.fieldSelection.paths.filter((path2) => !omittedColumns.some((omitted) => path2 === omitted || path2.startsWith(`${omitted}.`)))
22932
+ paths: options.fieldSelection.paths.filter((path2) => !reachesOmitted(path2))
22561
22933
  } : options.fieldSelection;
22562
22934
  const projection = projectRows(filteredRows, fieldSelection);
22563
22935
  filteredRows = projection.rows;
@@ -22676,7 +23048,7 @@ function maskMongoRows(rows, collection, blacklist) {
22676
23048
  }
22677
23049
  if (patterns.length === 0)
22678
23050
  return rows;
22679
- const idAffected = patterns.some((p) => p.segments.length === 1 && !p.wildcardTail && globMatches(p.segments[0], "_id"));
23051
+ const idAffected = patterns.some((p) => p.segments.length === 1 && !p.wildcardTail && globMatches(p.segments[0], "_id", { caseInsensitive: true }));
22680
23052
  if (idAffected) {
22681
23053
  console.error(`[blacklist] collection '${collection}' blacklists '_id'; read paths still expose _id to preserve document references.`);
22682
23054
  }
@@ -22702,7 +23074,7 @@ function maskValue(value, path2, patterns) {
22702
23074
  if (value === null || value === undefined)
22703
23075
  return value;
22704
23076
  if (Array.isArray(value)) {
22705
- return value.map((item) => item && typeof item === "object" && !Array.isArray(item) ? maskRecord(item, path2, patterns) : item);
23077
+ return value.map((item) => maskValue(item, path2, patterns));
22706
23078
  }
22707
23079
  if (typeof value === "object") {
22708
23080
  return maskRecord(value, path2, patterns);
@@ -22710,9 +23082,9 @@ function maskValue(value, path2, patterns) {
22710
23082
  return value;
22711
23083
  }
22712
23084
  function findCaseInsensitive(columns, name) {
22713
- const target = name.toLowerCase();
23085
+ const target = foldFieldPath(name);
22714
23086
  for (const [k, v] of Object.entries(columns)) {
22715
- if (k.toLowerCase() === target)
23087
+ if (foldFieldPath(k) === target)
22716
23088
  return v;
22717
23089
  }
22718
23090
  return;
@@ -22721,6 +23093,7 @@ var REDACTED2 = "[REDACTED]";
22721
23093
  var init_field_masker = __esm(() => {
22722
23094
  init_blacklist();
22723
23095
  init_path_matcher();
23096
+ init_blacklist_fold();
22724
23097
  init_glob();
22725
23098
  init_glob();
22726
23099
  });
@@ -24838,9 +25211,9 @@ function applySelectPatternRules(facts, factors) {
24838
25211
  }
24839
25212
  }
24840
25213
  function applyBlacklistRules(facts, blacklist, factors) {
24841
- const blacklistedTables = new Set((blacklist.tables ?? []).map((table) => table.toLowerCase()));
25214
+ const blacklistedTables = new Set((blacklist.tables ?? []).map(foldFieldPath));
24842
25215
  for (const table of facts.targetTables) {
24843
- if (blacklistedTables.has(table.toLowerCase())) {
25216
+ if (blacklistedTables.has(foldFieldPath(table))) {
24844
25217
  pushFactor(factors, "table_blacklisted", "block", `Target table ${table} is blacklisted.`);
24845
25218
  }
24846
25219
  }
@@ -24856,7 +25229,7 @@ function applyBlacklistRules(facts, blacklist, factors) {
24856
25229
  function findBlacklistedColumnsForTable(blacklist, table) {
24857
25230
  const columns = blacklist.columns ?? {};
24858
25231
  for (const [tableName2, columnNames] of Object.entries(columns)) {
24859
- if (tableName2.toLowerCase() === table.toLowerCase()) {
25232
+ if (foldFieldPath(tableName2) === foldFieldPath(table)) {
24860
25233
  return new Set(columnNames);
24861
25234
  }
24862
25235
  }
@@ -24947,6 +25320,7 @@ function buildSuggestedCommands(facts, gap) {
24947
25320
  }
24948
25321
  var READ_LIKE_WORDS, WRITE_OR_DDL_LIKE_WORDS;
24949
25322
  var init_query_risk_analyzer = __esm(() => {
25323
+ init_blacklist_fold();
24950
25324
  init_permission_guard();
24951
25325
  init_size_category();
24952
25326
  READ_LIKE_WORDS = ["SELECT", "SHOW", "DESCRIBE", "EXPLAIN", "WITH"];
@@ -26503,6 +26877,19 @@ async function qCommand(name, options, command) {
26503
26877
  const family = engineFamily(engine);
26504
26878
  const sqlDialect = SQL_DIALECTS.find((dialect) => dialect === connectionSystem);
26505
26879
  const classification = family === "sql" ? enforcePermission(prepared.rewrittenSql, config.permission, sqlDialect) : undefined;
26880
+ const blacklistManager = new BlacklistManager(config);
26881
+ const blacklistValidator = new BlacklistValidator(blacklistManager);
26882
+ const redisTokens = family === "redis" ? parseRedisCommand(prepared.rewrittenSql) : [];
26883
+ const targets = family === "sql" ? extractTableReferences(prepared.rewrittenSql, {
26884
+ ...sqlDialect ? { dialect: sqlDialect } : {}
26885
+ }) : family === "es" ? [prepared.execHints?.index ?? ""] : family === "redis" && redisTokens.length > 0 ? redisCommandTargets(redisTokens[0], redisTokens.slice(1)) : [];
26886
+ const targetName = targets[0] ?? "";
26887
+ targetNameForAudit = family === "redis" ? name : targetName || name;
26888
+ if (family === "es") {
26889
+ blacklistValidator.checkIndexBlacklist("SELECT", targetName);
26890
+ } else if ((classification || family === "redis") && targets.length > 0) {
26891
+ blacklistValidator.checkTablesBlacklist(classification?.type ?? redisTokens[0] ?? "READ", targets);
26892
+ }
26506
26893
  if (options.dryRun) {
26507
26894
  console.log(formatDryRun({
26508
26895
  family,
@@ -26524,19 +26911,7 @@ async function qCommand(name, options, command) {
26524
26911
  await qMongoBranch2(snippet, prepared, options, config);
26525
26912
  return;
26526
26913
  }
26527
- const blacklistManager = new BlacklistManager(config);
26528
- const blacklistValidator = new BlacklistValidator(blacklistManager);
26529
- const targets = family === "sql" ? extractTableReferences(prepared.rewrittenSql, {
26530
- ...sqlDialect ? { dialect: sqlDialect } : {}
26531
- }) : family === "es" ? [prepared.execHints?.index ?? ""] : [];
26532
- const targetName = targets[0] ?? "";
26533
- targetNameForAudit = targetName || name;
26534
- if (family === "es") {
26535
- blacklistValidator.checkIndexBlacklist("SELECT", targetName);
26536
- } else if (classification && targets.length > 0) {
26537
- blacklistValidator.checkTablesBlacklist(classification.type, targets);
26538
- }
26539
- const adapter = AdapterFactory.createAdapter(config.connection);
26914
+ const adapter = AdapterFactory.createAdapter(config);
26540
26915
  await adapter.connect();
26541
26916
  try {
26542
26917
  if (!options.ui && options.format !== "html") {
@@ -26657,6 +27032,8 @@ async function readParamFile(path2) {
26657
27032
  return parsed;
26658
27033
  }
26659
27034
  async function handleQError(error, snippetName, options, config) {
27035
+ const redisBlacklistRejection = config?.connection?.system === "redis" && (error instanceof BlacklistError || error instanceof BlacklistRejection);
27036
+ const reportedError = redisBlacklistRejection ? new BlacklistError(t("errors.redis_target_blacklisted"), "[REDACTED]", error instanceof BlacklistError ? error.operation : error.command) : error;
26660
27037
  let auditId = null;
26661
27038
  let envelopeId;
26662
27039
  if (options.recovery === true) {
@@ -26666,21 +27043,22 @@ async function handleQError(error, snippetName, options, config) {
26666
27043
  auditId = await writeAuditEntry(config, "q", options, {
26667
27044
  success: false,
26668
27045
  target: snippetName,
26669
- error,
27046
+ error: reportedError,
26670
27047
  ...envelopeId && { recovery_ref: envelopeId }
26671
27048
  });
26672
27049
  }
26673
27050
  if (envelopeId !== undefined) {
26674
27051
  const { emitRecoveryEnvelope: emitRecoveryEnvelope2 } = await Promise.resolve().then(() => (init_recovery(), exports_recovery));
26675
- emitRecoveryEnvelope2(error, { operation: "q", snippet: snippetName }, { envelopeId, auditRef: auditId ?? undefined });
27052
+ emitRecoveryEnvelope2(reportedError, { operation: "q", snippet: snippetName }, { envelopeId, auditRef: auditId ?? undefined });
26676
27053
  }
26677
27054
  if (error instanceof SavedQueryError) {
26678
27055
  printLocalizedCliError(error.message, error);
26679
27056
  process.exit(1);
26680
27057
  }
26681
- if (error instanceof BlacklistError) {
26682
- printLocalizedCliError(error.message, error);
27058
+ if (error instanceof BlacklistError || error instanceof BlacklistRejection) {
27059
+ printLocalizedCliError(reportedError.message, reportedError);
26683
27060
  process.exit(1);
27061
+ return;
26684
27062
  }
26685
27063
  if (error instanceof PermissionError) {
26686
27064
  printLocalizedCliError(t_vars("errors.permission_denied", { required: error.requiredPermission }), error);
@@ -26791,6 +27169,9 @@ function evaluateExpectation(row, expects) {
26791
27169
  var init_q = __esm(() => {
26792
27170
  init_message_loader();
26793
27171
  init_adapters();
27172
+ init_redis_adapter();
27173
+ init_blacklist_enforcer();
27174
+ init_types3();
26794
27175
  init_config();
26795
27176
  init_config_path();
26796
27177
  init_blacklist_manager();
@@ -27245,17 +27626,17 @@ function applyPermission(operation, context, factors) {
27245
27626
  }
27246
27627
  }
27247
27628
  function applyTableBlacklist(target, context, factors) {
27248
- const blacklisted = (context.blacklist.tables ?? []).map((t2) => t2.toLowerCase());
27249
- if (blacklisted.includes(target.toLowerCase())) {
27629
+ const blacklisted = (context.blacklist.tables ?? []).map(foldFieldPath);
27630
+ if (blacklisted.includes(foldFieldPath(target))) {
27250
27631
  pushFactor2(factors, "table_blacklisted", "block", `Target collection ${target} is blacklisted.`);
27251
27632
  }
27252
27633
  }
27253
27634
  function applyColumnBlacklist(target, fields, context, factors) {
27254
27635
  const columns = context.blacklist.columns ?? {};
27255
- const lower = target.toLowerCase();
27636
+ const lower = foldFieldPath(target);
27256
27637
  let raw = [];
27257
27638
  for (const [t2, cols] of Object.entries(columns)) {
27258
- if (t2.toLowerCase() === lower) {
27639
+ if (foldFieldPath(t2) === lower) {
27259
27640
  raw = cols;
27260
27641
  break;
27261
27642
  }
@@ -27383,6 +27764,7 @@ var MONGO_OPERATOR_TIER, MONGO_BROAD_FILTER_OPERATORS, analyzeMongoDmlRisk = (in
27383
27764
  };
27384
27765
  };
27385
27766
  var init_dml_plan = __esm(() => {
27767
+ init_blacklist_fold();
27386
27768
  init_path_matcher();
27387
27769
  init_permission_guard();
27388
27770
  MONGO_OPERATOR_TIER = {
@@ -27521,17 +27903,17 @@ function applyPermission2(operation, context, factors) {
27521
27903
  }
27522
27904
  }
27523
27905
  function applyTableBlacklist2(target, context, factors) {
27524
- const blacklisted = (context.blacklist.tables ?? []).map((t2) => t2.toLowerCase());
27525
- if (blacklisted.includes(target.toLowerCase())) {
27906
+ const blacklisted = (context.blacklist.tables ?? []).map(foldFieldPath);
27907
+ if (blacklisted.includes(foldFieldPath(target))) {
27526
27908
  pushFactor3(factors, "table_blacklisted", "block", `Redis key ${target} is blacklisted.`);
27527
27909
  }
27528
27910
  }
27529
27911
  function applyColumnBlacklist2(target, fields, context, factors) {
27530
27912
  const columns = context.blacklist.columns ?? {};
27531
- const lower = target.toLowerCase();
27913
+ const lower = foldFieldPath(target);
27532
27914
  let blacklistedFields = [];
27533
27915
  for (const [t2, cols] of Object.entries(columns)) {
27534
- if (t2.toLowerCase() === lower) {
27916
+ if (foldFieldPath(t2) === lower) {
27535
27917
  blacklistedFields = cols;
27536
27918
  break;
27537
27919
  }
@@ -27629,6 +28011,7 @@ var analyzeRedisDmlRisk = (intent, context) => {
27629
28011
  };
27630
28012
  };
27631
28013
  var init_dml_plan2 = __esm(() => {
28014
+ init_blacklist_fold();
27632
28015
  init_permission_guard();
27633
28016
  });
27634
28017
 
@@ -27678,17 +28061,17 @@ function applyPermission3(operation, context, factors) {
27678
28061
  }
27679
28062
  }
27680
28063
  function applyTableBlacklist3(target, context, factors) {
27681
- const blacklisted = (context.blacklist.tables ?? []).map((t2) => t2.toLowerCase());
27682
- if (blacklisted.includes(target.toLowerCase())) {
28064
+ const blacklisted = (context.blacklist.tables ?? []).map(foldFieldPath);
28065
+ if (blacklisted.includes(foldFieldPath(target))) {
27683
28066
  pushFactor4(factors, "table_blacklisted", "block", `Elasticsearch index ${target} is blacklisted.`);
27684
28067
  }
27685
28068
  }
27686
28069
  function applyColumnBlacklist3(target, fields, context, factors) {
27687
28070
  const columns = context.blacklist.columns ?? {};
27688
- const lower = target.toLowerCase();
28071
+ const lower = foldFieldPath(target);
27689
28072
  let blacklistedFields = [];
27690
28073
  for (const [t2, cols] of Object.entries(columns)) {
27691
- if (t2.toLowerCase() === lower) {
28074
+ if (foldFieldPath(t2) === lower) {
27692
28075
  blacklistedFields = cols;
27693
28076
  break;
27694
28077
  }
@@ -27771,6 +28154,7 @@ var analyzeElasticsearchDmlRisk = (intent, context) => {
27771
28154
  };
27772
28155
  };
27773
28156
  var init_dml_plan3 = __esm(() => {
28157
+ init_blacklist_fold();
27774
28158
  init_permission_guard();
27775
28159
  });
27776
28160
 
@@ -31896,11 +32280,10 @@ async function checkAction(table, options) {
31896
32280
  const checker = new HealthChecker(adapter);
31897
32281
  const blacklistManager = new BlacklistManager(config);
31898
32282
  const blacklistedColumns = getBlacklistedColumnSet(blacklistManager);
31899
- const blacklistedTables = getBlacklistedTableSet(blacklistManager);
31900
32283
  const checkTypes = options.checks ? options.checks.split(",") : undefined;
31901
32284
  const sampleSize = parseInt(options.sample, 10) || 1e4;
31902
32285
  if (table) {
31903
- if (blacklistedTables.has(table.toLowerCase())) {
32286
+ if (blacklistManager.isTableBlacklisted(table)) {
31904
32287
  throw new Error(`Table "${table}" is blacklisted`);
31905
32288
  }
31906
32289
  const schema = await adapter.getTableSchema(table);
@@ -31915,7 +32298,7 @@ async function checkAction(table, options) {
31915
32298
  const reports = [];
31916
32299
  const skipped = [];
31917
32300
  for (const t2 of tables) {
31918
- if (blacklistedTables.has(t2.name.toLowerCase())) {
32301
+ if (blacklistManager.isTableBlacklisted(t2.name)) {
31919
32302
  skipped.push(`${t2.name} (blacklisted)`);
31920
32303
  continue;
31921
32304
  }
@@ -31991,10 +32374,6 @@ function getBlacklistedColumnSet(manager) {
31991
32374
  }
31992
32375
  return result;
31993
32376
  }
31994
- function getBlacklistedTableSet(manager) {
31995
- const state = manager.state;
31996
- return state?.tables || new Set;
31997
- }
31998
32377
  var ALLOWED_FORMATS6, checkCommand;
31999
32378
  var init_check = __esm(() => {
32000
32379
  init_esm();
@@ -35097,7 +35476,10 @@ async function collectInspect(opts) {
35097
35476
  let version = null;
35098
35477
  if (!opts.noConnect && config?.connection && system) {
35099
35478
  const probeTimeout = opts.probeTimeoutMs ?? DEFAULT_PROBE_MS;
35100
- const adapter = AdapterFactory.createAdapter(config.connection);
35479
+ const adapter = AdapterFactory.createAdapter({
35480
+ ...config,
35481
+ blacklist: config.blacklist ?? { tables: [], columns: {} }
35482
+ });
35101
35483
  let probeError = null;
35102
35484
  const probe = (async () => {
35103
35485
  await adapter.connect();
@@ -35491,8 +35873,12 @@ async function runDiagnostic(input) {
35491
35873
  description: meta.description ?? ""
35492
35874
  };
35493
35875
  let prepared;
35876
+ let redisTokens = [];
35494
35877
  try {
35495
35878
  prepared = prepareExecution(input.snippet, { engine: input.engine, noLimit: false }, {}, {});
35879
+ if (engineFamily(input.engine) === "redis") {
35880
+ redisTokens = parseRedisCommand(prepared.rewrittenSql);
35881
+ }
35496
35882
  } catch (err) {
35497
35883
  const reason = err instanceof SavedQueryError ? err.message : `prepare failed: ${err.message}`;
35498
35884
  return { ...base, rowCount: 0, rows: [], status: "skipped", reason, durationMs: 0 };
@@ -35500,7 +35886,7 @@ async function runDiagnostic(input) {
35500
35886
  const family = engineFamily(input.engine);
35501
35887
  const referencedTables = family === "sql" ? extractTableReferences(prepared.rewrittenSql, {
35502
35888
  ...ENGINE_DIALECT2[input.engine] ? { dialect: ENGINE_DIALECT2[input.engine] } : {}
35503
- }) : family === "es" && prepared.execHints?.index ? [prepared.execHints.index] : [];
35889
+ }) : family === "es" && prepared.execHints?.index ? [prepared.execHints.index] : family === "redis" && redisTokens.length > 0 ? redisCommandTargets(redisTokens[0], redisTokens.slice(1)) : [];
35504
35890
  if (input.blacklistValidator && referencedTables.length > 0) {
35505
35891
  try {
35506
35892
  input.blacklistValidator.checkTablesBlacklist("SELECT", referencedTables);
@@ -35512,7 +35898,7 @@ async function runDiagnostic(input) {
35512
35898
  rowCount: 0,
35513
35899
  rows: [],
35514
35900
  status: "skipped",
35515
- reason: err.message,
35901
+ reason: family === "redis" ? "Redis diagnostic skipped by blacklist" : err.message,
35516
35902
  durationMs: 0
35517
35903
  };
35518
35904
  }
@@ -35528,8 +35914,8 @@ async function runDiagnostic(input) {
35528
35914
  try {
35529
35915
  outcome = await Promise.race([exec, timer]);
35530
35916
  } catch (err) {
35531
- outcome = "error";
35532
- errorMessage2 = err.message;
35917
+ outcome = err instanceof BlacklistRejection ? "skipped" : "error";
35918
+ errorMessage2 = err instanceof BlacklistRejection ? "Redis diagnostic skipped by blacklist" : err.message;
35533
35919
  }
35534
35920
  const durationMs = Math.round(performance.now() - start);
35535
35921
  if (outcome === "timeout") {
@@ -35542,12 +35928,12 @@ async function runDiagnostic(input) {
35542
35928
  durationMs
35543
35929
  };
35544
35930
  }
35545
- if (outcome === "error") {
35931
+ if (outcome === "error" || outcome === "skipped") {
35546
35932
  return {
35547
35933
  ...base,
35548
35934
  rowCount: 0,
35549
35935
  rows: [],
35550
- status: "error",
35936
+ status: outcome,
35551
35937
  reason: errorMessage2 ?? "unknown error",
35552
35938
  durationMs
35553
35939
  };
@@ -35567,6 +35953,9 @@ async function runDiagnostic(input) {
35567
35953
  }
35568
35954
  var ENGINE_DIALECT2;
35569
35955
  var init_run_diagnostic = __esm(() => {
35956
+ init_redis_adapter();
35957
+ init_blacklist_enforcer();
35958
+ init_types3();
35570
35959
  init_saved_queries();
35571
35960
  init_strategies();
35572
35961
  init_sql_tables();
@@ -35630,7 +36019,7 @@ async function collectReport(opts) {
35630
36019
  warnings.push({ severity: "warn", message: "connection config missing after probe" });
35631
36020
  return finalize({ context, sections: [], warnings, generatedAt });
35632
36021
  }
35633
- const adapter = AdapterFactory.createAdapter(config.connection);
36022
+ const adapter = AdapterFactory.createAdapter(config);
35634
36023
  const blacklistValidator = new BlacklistValidator(new BlacklistManager(config));
35635
36024
  const sectionEvidence = new Map;
35636
36025
  for (const id of sections)
@@ -38519,6 +38908,7 @@ async function collectElasticsearchDoctorResults(config, runtimeOverrides = {})
38519
38908
  var ALLOWED_FORMATS15, SENSITIVE_PATTERNS, runDoctorChecks, defaultDoctorCollectorRuntime, doctorCommand;
38520
38909
  var init_doctor = __esm(() => {
38521
38910
  init_esm();
38911
+ init_blacklist_fold();
38522
38912
  init_colors();
38523
38913
  init_config();
38524
38914
  init_adapters();
@@ -38635,7 +39025,8 @@ var init_doctor = __esm(() => {
38635
39025
  for (const col of columns) {
38636
39026
  const colLower = col.toLowerCase();
38637
39027
  const isSensitive = SENSITIVE_PATTERNS.some((p) => colLower.includes(p));
38638
- if (isSensitive && !blacklisted.has(col)) {
39028
+ const foldedRules = new Set([...blacklisted].map(foldFieldPath));
39029
+ if (isSensitive && !foldedRules.has(foldFieldPath(col))) {
38639
39030
  unprotected.push(`${table}.${col}`);
38640
39031
  }
38641
39032
  }
@@ -46445,30 +46836,25 @@ function findStrings(node) {
46445
46836
  return found;
46446
46837
  }
46447
46838
  function namesProtectedField(term, protectedFields) {
46448
- if (protectedFields.has(term))
46449
- return true;
46450
- const parts = term.split(".");
46451
- if (parts.length === 1)
46452
- return false;
46453
- for (let start = 0;start < parts.length; start += 1) {
46454
- for (let end = start + 1;end <= parts.length; end += 1) {
46455
- if (protectedFields.has(parts.slice(start, end).join(".")))
46456
- return true;
46457
- }
46458
- }
46459
- return false;
46839
+ return reachesProtected(foldFieldPath(term), contiguousRulesFor(protectedFields));
46840
+ }
46841
+ function reachesProtected(folded, rules) {
46842
+ return reachesProtectedPath(folded, rules);
46460
46843
  }
46461
46844
  function redactFields(node, fields, trail = []) {
46845
+ return redactWithGlobs(node, contiguousRulesFor(fields), trail.flatMap((entry) => foldFieldPath(entry).split(".")));
46846
+ }
46847
+ function redactWithGlobs(node, rules, trail) {
46462
46848
  if (Array.isArray(node))
46463
- return node.map((item) => redactFields(item, fields, trail));
46849
+ return node.map((item) => redactWithGlobs(item, rules, trail));
46464
46850
  if (node === null || typeof node !== "object")
46465
46851
  return node;
46466
46852
  const out = {};
46467
46853
  for (const [key, value] of Object.entries(node)) {
46468
- const path4 = [...trail, key];
46469
- if (namesProtectedField(path4.join("."), fields))
46854
+ const path4 = [...trail, ...foldFieldPath(key).split(".")];
46855
+ if (reachesProtectedSegments(path4, rules))
46470
46856
  continue;
46471
- out[key] = redactFields(value, fields, path4);
46857
+ out[key] = redactWithGlobs(value, rules, path4);
46472
46858
  }
46473
46859
  return out;
46474
46860
  }
@@ -46521,7 +46907,9 @@ function assertNoBlacklistedIndexNamed(args) {
46521
46907
  }
46522
46908
  }
46523
46909
  function collectProtectedFields(blacklistColumns) {
46524
- return new Set(Object.values(blacklistColumns).flat().map((field) => field.trim()).filter((field) => field.length > 0));
46910
+ const fields = new Set(Object.values(blacklistColumns).flat().map(normalizeBlacklistEntry).filter((field) => field.length > 0));
46911
+ contiguousRulesFor(fields);
46912
+ return fields;
46525
46913
  }
46526
46914
  function queryStringFieldTerms(query) {
46527
46915
  const CONSERVATIVE = /[\s,:()"'[\]{}]+/;
@@ -46563,6 +46951,9 @@ var init_es_shell_guards = __esm(() => {
46563
46951
  init_message_loader();
46564
46952
  init_es_index_target();
46565
46953
  init_elasticsearch();
46954
+ init_blacklist_fold();
46955
+ init_blacklist_manager();
46956
+ init_path_matcher();
46566
46957
  UNSCOPED_METADATA_PREFIXES = ["_cat", "_cluster", "_nodes", "_license"];
46567
46958
  UNSCOPED_METADATA_WITHHELD = {
46568
46959
  _cluster: new Set(["state"]),
@@ -48348,7 +48739,7 @@ async function verifyNewPassword(configPath, target, connectionName, password) {
48348
48739
  process.env[target.varName] = password;
48349
48740
  try {
48350
48741
  const config = await configModule.read(storagePath, connectionName);
48351
- const adapter = AdapterFactory.createAdapter({
48742
+ const adapter = AdapterFactory.createAdapterWithoutRules({
48352
48743
  ...config.connection,
48353
48744
  password
48354
48745
  });