@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.
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/CHANGELOG.md +227 -0
- package/dist/cli-runtime.mjs +732 -341
- package/dist/cli.mjs +4 -2
- package/dist/core.d.ts +21 -3
- package/dist/core.mjs +514 -220
- package/gemini-extension.json +1 -1
- package/package.json +4 -2
- package/plugins/dbcli-agent/.codex-plugin/plugin.json +1 -1
package/dist/cli-runtime.mjs
CHANGED
|
@@ -51,7 +51,7 @@ var package_default;
|
|
|
51
51
|
var init_package = __esm(() => {
|
|
52
52
|
package_default = {
|
|
53
53
|
name: "@carllee1983/dbcli",
|
|
54
|
-
version: "
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
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({
|
|
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(
|
|
11255
|
+
current.push(...literalToken("[", caseInsensitive));
|
|
11236
11256
|
continue;
|
|
11237
11257
|
}
|
|
11238
|
-
|
|
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
|
|
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 !==
|
|
11275
|
+
if (token.char !== text2[at + i])
|
|
11251
11276
|
return false;
|
|
11252
11277
|
} else if (token.kind === "class") {
|
|
11253
|
-
|
|
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,
|
|
11260
|
-
const
|
|
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
|
|
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 &&
|
|
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
|
|
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
|
|
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
|
|
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(`
|
|
13714
|
+
throw new Error(`createQueryableAdapterWithoutRules requires a non-SQL queryable system, got: ${options.system}`);
|
|
13449
13715
|
}
|
|
13450
13716
|
}
|
|
13451
|
-
static createAdapter(
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
16862
|
-
|
|
16863
|
-
|
|
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
|
|
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
|
|
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
|
|
16921
|
-
if (
|
|
16922
|
-
return { found: true, value: value[
|
|
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
|
-
|
|
17219
|
+
const headKey = ownKey(value, head, caseInsensitive);
|
|
17220
|
+
if (headKey === undefined)
|
|
16926
17221
|
return { found: false };
|
|
16927
|
-
return readPath(value[
|
|
17222
|
+
return readPath(value[headKey], tail, caseInsensitive);
|
|
16928
17223
|
}
|
|
16929
|
-
function
|
|
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
|
|
16937
|
-
const
|
|
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
|
-
|
|
17261
|
+
const folded = fold(key);
|
|
17262
|
+
if (folded === exactPath)
|
|
16941
17263
|
continue;
|
|
16942
|
-
if (
|
|
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
|
|
17392
|
+
const normalized = pattern.toLowerCase() === "_all" ? "*" : pattern;
|
|
17069
17393
|
try {
|
|
17070
|
-
return globMatches(normalized, name
|
|
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
|
|
17077
|
-
const target = entry
|
|
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
|
|
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
|
|
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
|
|
17130
|
-
|
|
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
|
|
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(
|
|
17198
|
-
const globs = compileGlobRules(blacklisted,
|
|
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
|
|
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
|
|
17269
|
-
|
|
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
|
-
|
|
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(
|
|
17609
|
+
const present = presentByFolded.get(foldFieldPath(path));
|
|
17280
17610
|
if (present !== undefined) {
|
|
17281
|
-
|
|
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)
|
|
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 =
|
|
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
|
|
17325
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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) => !
|
|
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) =>
|
|
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
|
|
23085
|
+
const target = foldFieldPath(name);
|
|
22714
23086
|
for (const [k, v] of Object.entries(columns)) {
|
|
22715
|
-
if (k
|
|
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(
|
|
25214
|
+
const blacklistedTables = new Set((blacklist.tables ?? []).map(foldFieldPath));
|
|
24842
25215
|
for (const table of facts.targetTables) {
|
|
24843
|
-
if (blacklistedTables.has(table
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
27249
|
-
if (blacklisted.includes(target
|
|
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
|
|
27636
|
+
const lower = foldFieldPath(target);
|
|
27256
27637
|
let raw = [];
|
|
27257
27638
|
for (const [t2, cols] of Object.entries(columns)) {
|
|
27258
|
-
if (t2
|
|
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(
|
|
27525
|
-
if (blacklisted.includes(target
|
|
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
|
|
27913
|
+
const lower = foldFieldPath(target);
|
|
27532
27914
|
let blacklistedFields = [];
|
|
27533
27915
|
for (const [t2, cols] of Object.entries(columns)) {
|
|
27534
|
-
if (t2
|
|
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(
|
|
27682
|
-
if (blacklisted.includes(target
|
|
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
|
|
28071
|
+
const lower = foldFieldPath(target);
|
|
27689
28072
|
let blacklistedFields = [];
|
|
27690
28073
|
for (const [t2, cols] of Object.entries(columns)) {
|
|
27691
|
-
if (t2
|
|
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 (
|
|
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 (
|
|
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(
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
46449
|
-
|
|
46450
|
-
|
|
46451
|
-
|
|
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) =>
|
|
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 (
|
|
46854
|
+
const path4 = [...trail, ...foldFieldPath(key).split(".")];
|
|
46855
|
+
if (reachesProtectedSegments(path4, rules))
|
|
46470
46856
|
continue;
|
|
46471
|
-
out[key] =
|
|
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
|
-
|
|
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.
|
|
48742
|
+
const adapter = AdapterFactory.createAdapterWithoutRules({
|
|
48352
48743
|
...config.connection,
|
|
48353
48744
|
password
|
|
48354
48745
|
});
|