@carllee1983/dbcli 4.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 +305 -0
- package/dist/cli-runtime.mjs +1826 -1110
- package/dist/cli.mjs +4 -2
- package/dist/core.d.ts +43 -8
- package/dist/core.mjs +760 -191
- 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
|
});
|
|
@@ -4372,7 +4374,7 @@ function validateFormat(value, allowedFormats, commandName) {
|
|
|
4372
4374
|
throw new Error(`Invalid format "${value}" for ${commandName}. Allowed: ${allowed}`);
|
|
4373
4375
|
}
|
|
4374
4376
|
}
|
|
4375
|
-
var EnvRefSchema, StringOrEnvRef, NumberOrEnvRef, OptStringOrEnvRef, OptNumberOrEnvRef, MIN_CONNECTION_TIMEOUT_MS = 100, MAX_CONNECTION_TIMEOUT_MS = 600000, MIN_STATEMENT_TIMEOUT_MS = 0, MAX_STATEMENT_TIMEOUT_MS = 3600000, TimeoutField, MongoDBConnectionConfigSchema, SqlConnectionConfigSchema, RedisConnectionConfigSchema, ElasticsearchConnectionConfigSchema, ConnectionConfigSchema, PermissionSchema, EnvironmentLabelSchema, MetadataSchema, BLACKLIST_KEY_ALIASES, BlacklistConfigSchema, RedisMaskRuleSchema, RedisConfigSchema, AuditRotationConfigSchema, AuditConfigSchema, DbcliConfigSchema, SqlNamedConnectionSchema, MongoDBNamedConnectionSchema, RedisNamedConnectionSchema, ElasticsearchNamedConnectionSchema, NamedConnectionUnion, NamedConnectionSchema, DbcliConfigV2Schema;
|
|
4377
|
+
var EnvRefSchema, StringOrEnvRef, NumberOrEnvRef, OptStringOrEnvRef, OptNumberOrEnvRef, MIN_CONNECTION_TIMEOUT_MS = 100, MAX_CONNECTION_TIMEOUT_MS = 600000, MIN_STATEMENT_TIMEOUT_MS = 0, MAX_STATEMENT_TIMEOUT_MS = 3600000, TimeoutField, MongoDBConnectionConfigSchema, SqlConnectionConfigSchema, RedisConnectionConfigSchema, ElasticsearchConnectionConfigSchema, ConnectionConfigSchema, PermissionSchema, EnvironmentLabelSchema, MetadataSchema, BLACKLIST_KEY_ALIASES, BlacklistConfigSchema, RedisMaskRuleSchema, RedisConfigSchema, DEFAULT_AUDIT_ROTATION, AuditRotationConfigSchema, AuditConfigSchema, DbcliConfigSchema, SqlNamedConnectionSchema, MongoDBNamedConnectionSchema, RedisNamedConnectionSchema, ElasticsearchNamedConnectionSchema, NamedConnectionUnion, NamedConnectionSchema, DbcliConfigV2Schema;
|
|
4376
4378
|
var init_validation = __esm(() => {
|
|
4377
4379
|
init_zod();
|
|
4378
4380
|
EnvRefSchema = exports_external.object({
|
|
@@ -4474,10 +4476,11 @@ var init_validation = __esm(() => {
|
|
|
4474
4476
|
RedisConfigSchema = exports_external.object({
|
|
4475
4477
|
mask: exports_external.array(RedisMaskRuleSchema).default([])
|
|
4476
4478
|
}).optional();
|
|
4479
|
+
DEFAULT_AUDIT_ROTATION = { max_bytes: 10485760, max_entries: 1e4 };
|
|
4477
4480
|
AuditRotationConfigSchema = exports_external.object({
|
|
4478
|
-
max_bytes: exports_external.number().int().positive().default(
|
|
4479
|
-
max_entries: exports_external.number().int().positive().default(
|
|
4480
|
-
}).optional().default({
|
|
4481
|
+
max_bytes: exports_external.number().int().positive().default(DEFAULT_AUDIT_ROTATION.max_bytes),
|
|
4482
|
+
max_entries: exports_external.number().int().positive().default(DEFAULT_AUDIT_ROTATION.max_entries)
|
|
4483
|
+
}).optional().default({ ...DEFAULT_AUDIT_ROTATION });
|
|
4481
4484
|
AuditConfigSchema = exports_external.object({
|
|
4482
4485
|
enabled: exports_external.boolean().default(true),
|
|
4483
4486
|
strict: exports_external.boolean().default(false),
|
|
@@ -4488,7 +4491,7 @@ var init_validation = __esm(() => {
|
|
|
4488
4491
|
}).optional().default({
|
|
4489
4492
|
enabled: true,
|
|
4490
4493
|
strict: false,
|
|
4491
|
-
rotation: {
|
|
4494
|
+
rotation: { ...DEFAULT_AUDIT_ROTATION }
|
|
4492
4495
|
});
|
|
4493
4496
|
DbcliConfigSchema = exports_external.object({
|
|
4494
4497
|
connection: ConnectionConfigSchema,
|
|
@@ -6321,7 +6324,7 @@ var init_config = __esm(() => {
|
|
|
6321
6324
|
audit: {
|
|
6322
6325
|
enabled: true,
|
|
6323
6326
|
strict: false,
|
|
6324
|
-
rotation: {
|
|
6327
|
+
rotation: { ...DEFAULT_AUDIT_ROTATION }
|
|
6325
6328
|
}
|
|
6326
6329
|
};
|
|
6327
6330
|
configModule = {
|
|
@@ -9018,6 +9021,7 @@ var init_messages = __esm(() => {
|
|
|
9018
9021
|
unsupported_lang: "Language '{lang}' not supported, using English",
|
|
9019
9022
|
invalid_json: "Invalid JSON: {message}",
|
|
9020
9023
|
table_blacklisted: "Error: Table '{table}' is blacklisted for {operation} operations",
|
|
9024
|
+
redis_target_blacklisted: "Redis command refused by blacklist",
|
|
9021
9025
|
column_blacklisted_write: "Error: {operation} on table '{table}' touches blacklisted columns: {columns}",
|
|
9022
9026
|
table_already_blacklisted: "Error: Table '{table}' is already blacklisted",
|
|
9023
9027
|
table_not_in_blacklist: "Error: Table '{table}' is not in the blacklist",
|
|
@@ -9109,7 +9113,8 @@ Reference: {referencePath}`,
|
|
|
9109
9113
|
columns_omitted: "Security: {count} column(s) were omitted based on your blacklist"
|
|
9110
9114
|
},
|
|
9111
9115
|
warnings: {
|
|
9112
|
-
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"
|
|
9113
9118
|
},
|
|
9114
9119
|
version: {
|
|
9115
9120
|
unsupported_warning: "{system} {version} is below minimum supported version {minVersion}. Some features may not work correctly.",
|
|
@@ -9336,6 +9341,7 @@ var init_messages2 = __esm(() => {
|
|
|
9336
9341
|
unsupported_lang: "\u4E0D\u652F\u63F4\u7684\u8A9E\u8A00 '{lang}'\uFF0C\u4F7F\u7528\u82F1\u6587",
|
|
9337
9342
|
invalid_json: "\u7121\u6548\u7684 JSON\uFF1A{message}",
|
|
9338
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",
|
|
9339
9345
|
column_blacklisted_write: "\u932F\u8AA4: \u8868\u683C '{table}' \u7684 {operation} \u64CD\u4F5C\u5305\u542B\u9ED1\u540D\u55AE\u6B04\u4F4D: {columns}",
|
|
9340
9346
|
table_already_blacklisted: "\u932F\u8AA4: \u8868\u683C '{table}' \u5DF2\u5728\u9ED1\u540D\u55AE\u4E2D",
|
|
9341
9347
|
table_not_in_blacklist: "\u932F\u8AA4: \u8868\u683C '{table}' \u4E0D\u5728\u9ED1\u540D\u55AE\u4E2D",
|
|
@@ -9427,7 +9433,8 @@ var init_messages2 = __esm(() => {
|
|
|
9427
9433
|
columns_omitted: "\u5B89\u5168: {count} \u500B\u6B04\u4F4D\u6839\u64DA\u4F60\u7684\u9ED1\u540D\u55AE\u5DF2\u88AB\u96B1\u85CF"
|
|
9428
9434
|
},
|
|
9429
9435
|
warnings: {
|
|
9430
|
-
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"
|
|
9431
9438
|
},
|
|
9432
9439
|
version: {
|
|
9433
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",
|
|
@@ -11164,31 +11171,349 @@ var init_server_side_script = __esm(() => {
|
|
|
11164
11171
|
ES_OPAQUE_BODY_KEYS = ["wrapper"];
|
|
11165
11172
|
});
|
|
11166
11173
|
|
|
11167
|
-
// src/
|
|
11168
|
-
function
|
|
11169
|
-
|
|
11170
|
-
|
|
11171
|
-
|
|
11172
|
-
|
|
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
|
+
|
|
11180
|
+
// src/utils/glob.ts
|
|
11181
|
+
function findClassEnd(glob, open2) {
|
|
11182
|
+
for (let i = open2 + 1;i < glob.length; i++) {
|
|
11183
|
+
if (glob[i] === "\\") {
|
|
11184
|
+
i++;
|
|
11185
|
+
continue;
|
|
11186
|
+
}
|
|
11187
|
+
if (glob[i] === "]")
|
|
11188
|
+
return i;
|
|
11189
|
+
}
|
|
11190
|
+
return -1;
|
|
11174
11191
|
}
|
|
11175
|
-
function
|
|
11176
|
-
|
|
11192
|
+
function isValidCharacterClass(body) {
|
|
11193
|
+
try {
|
|
11194
|
+
new RegExp(body);
|
|
11177
11195
|
return true;
|
|
11178
|
-
|
|
11179
|
-
if (parts.length === 1)
|
|
11196
|
+
} catch {
|
|
11180
11197
|
return false;
|
|
11181
|
-
|
|
11182
|
-
|
|
11183
|
-
|
|
11198
|
+
}
|
|
11199
|
+
}
|
|
11200
|
+
function parseGlob(glob, caseInsensitive) {
|
|
11201
|
+
const key = `${caseInsensitive ? "i" : "c"}\x00${glob}`;
|
|
11202
|
+
const memo = parsedGlobs.get(key);
|
|
11203
|
+
if (memo !== undefined)
|
|
11204
|
+
return memo;
|
|
11205
|
+
const parsed = parseGlobUncached(glob, caseInsensitive);
|
|
11206
|
+
if (parsedGlobs.size >= PARSED_GLOB_LIMIT)
|
|
11207
|
+
parsedGlobs.clear();
|
|
11208
|
+
parsedGlobs.set(key, parsed);
|
|
11209
|
+
return parsed;
|
|
11210
|
+
}
|
|
11211
|
+
function parseGlobUncached(glob, caseInsensitive) {
|
|
11212
|
+
const runs = [];
|
|
11213
|
+
let current = [];
|
|
11214
|
+
let leadingStar = false;
|
|
11215
|
+
let trailingStar = false;
|
|
11216
|
+
let sawStar = false;
|
|
11217
|
+
const endRun = () => {
|
|
11218
|
+
if (current.length > 0)
|
|
11219
|
+
runs.push(current);
|
|
11220
|
+
current = [];
|
|
11221
|
+
};
|
|
11222
|
+
for (let i = 0;i < glob.length; i++) {
|
|
11223
|
+
const c = glob[i];
|
|
11224
|
+
if (c === "\\" && i + 1 < glob.length) {
|
|
11225
|
+
trailingStar = false;
|
|
11226
|
+
const escaped = String.fromCodePoint(glob.codePointAt(++i));
|
|
11227
|
+
i += escaped.length - 1;
|
|
11228
|
+
current.push(...literalToken(escaped, caseInsensitive));
|
|
11229
|
+
continue;
|
|
11230
|
+
}
|
|
11231
|
+
if (c === "*") {
|
|
11232
|
+
if (!sawStar && current.length === 0)
|
|
11233
|
+
leadingStar = true;
|
|
11234
|
+
sawStar = true;
|
|
11235
|
+
trailingStar = true;
|
|
11236
|
+
endRun();
|
|
11237
|
+
continue;
|
|
11238
|
+
}
|
|
11239
|
+
trailingStar = false;
|
|
11240
|
+
if (c === "?") {
|
|
11241
|
+
current.push({ kind: "any" });
|
|
11242
|
+
continue;
|
|
11243
|
+
}
|
|
11244
|
+
if (c === "[") {
|
|
11245
|
+
const end = findClassEnd(glob, i);
|
|
11246
|
+
const body = end === -1 ? "" : glob.slice(i, end + 1);
|
|
11247
|
+
if (body !== "" && isValidCharacterClass(body)) {
|
|
11248
|
+
current.push({
|
|
11249
|
+
kind: "class",
|
|
11250
|
+
test: new RegExp(`^${body}$`, caseInsensitive ? "si" : "s")
|
|
11251
|
+
});
|
|
11252
|
+
i = end;
|
|
11253
|
+
continue;
|
|
11254
|
+
}
|
|
11255
|
+
current.push(...literalToken("[", caseInsensitive));
|
|
11256
|
+
continue;
|
|
11257
|
+
}
|
|
11258
|
+
const literal = String.fromCodePoint(glob.codePointAt(i));
|
|
11259
|
+
i += literal.length - 1;
|
|
11260
|
+
current.push(...literalToken(literal, caseInsensitive));
|
|
11261
|
+
}
|
|
11262
|
+
endRun();
|
|
11263
|
+
return { runs, leadingStar, trailingStar, anchored: !sawStar };
|
|
11264
|
+
}
|
|
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) {
|
|
11270
|
+
if (at + run.length > text2.length)
|
|
11271
|
+
return false;
|
|
11272
|
+
for (let i = 0;i < run.length; i++) {
|
|
11273
|
+
const token = run[i];
|
|
11274
|
+
if (token.kind === "literal") {
|
|
11275
|
+
if (token.char !== text2[at + i])
|
|
11276
|
+
return false;
|
|
11277
|
+
} else if (token.kind === "class") {
|
|
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))
|
|
11283
|
+
return false;
|
|
11284
|
+
}
|
|
11285
|
+
}
|
|
11286
|
+
return true;
|
|
11287
|
+
}
|
|
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;
|
|
11292
|
+
if (anchored) {
|
|
11293
|
+
const run = runs[0] ?? [];
|
|
11294
|
+
return text2.length === run.length && runMatchesAt(run, text2, subject, 0);
|
|
11295
|
+
}
|
|
11296
|
+
if (runs.length === 0)
|
|
11297
|
+
return true;
|
|
11298
|
+
let cursor = 0;
|
|
11299
|
+
let first = 0;
|
|
11300
|
+
let last = runs.length;
|
|
11301
|
+
if (!leadingStar) {
|
|
11302
|
+
const head = runs[0];
|
|
11303
|
+
if (!runMatchesAt(head, text2, subject, 0))
|
|
11304
|
+
return false;
|
|
11305
|
+
cursor = head.length;
|
|
11306
|
+
first = 1;
|
|
11307
|
+
}
|
|
11308
|
+
if (!trailingStar) {
|
|
11309
|
+
const tail = runs[last - 1];
|
|
11310
|
+
const at = text2.length - tail.length;
|
|
11311
|
+
if (at < cursor || !runMatchesAt(tail, text2, subject, at))
|
|
11312
|
+
return false;
|
|
11313
|
+
last -= 1;
|
|
11314
|
+
}
|
|
11315
|
+
const limit = trailingStar ? text2.length : text2.length - runs[runs.length - 1].length;
|
|
11316
|
+
for (let r = first;r < last; r++) {
|
|
11317
|
+
const run = runs[r];
|
|
11318
|
+
let found = -1;
|
|
11319
|
+
for (let at = cursor;at + run.length <= limit; at++) {
|
|
11320
|
+
if (runMatchesAt(run, text2, subject, at)) {
|
|
11321
|
+
found = at;
|
|
11322
|
+
break;
|
|
11323
|
+
}
|
|
11324
|
+
}
|
|
11325
|
+
if (found === -1)
|
|
11326
|
+
return false;
|
|
11327
|
+
cursor = found + run.length;
|
|
11328
|
+
}
|
|
11329
|
+
return true;
|
|
11330
|
+
}
|
|
11331
|
+
function isGlobPattern(value) {
|
|
11332
|
+
return /[*?[\\]/.test(value);
|
|
11333
|
+
}
|
|
11334
|
+
function escapeGlob(literal) {
|
|
11335
|
+
return literal.replace(/[*?[\]\\]/g, "\\$&");
|
|
11336
|
+
}
|
|
11337
|
+
function globNeverMatches(glob) {
|
|
11338
|
+
for (let i = 0;i < glob.length; i++) {
|
|
11339
|
+
const c = glob[i];
|
|
11340
|
+
if (c === "\\") {
|
|
11341
|
+
i++;
|
|
11342
|
+
continue;
|
|
11343
|
+
}
|
|
11344
|
+
if (c !== "[")
|
|
11345
|
+
continue;
|
|
11346
|
+
const end = findClassEnd(glob, i);
|
|
11347
|
+
if (end === -1) {
|
|
11348
|
+
return "unclosed character class";
|
|
11349
|
+
}
|
|
11350
|
+
const body = glob.slice(i, end + 1);
|
|
11351
|
+
if (body === "[]" || body === "[^]")
|
|
11352
|
+
return "empty character class matches nothing";
|
|
11353
|
+
if (!isValidCharacterClass(body))
|
|
11354
|
+
return "invalid character class";
|
|
11355
|
+
i = end;
|
|
11356
|
+
}
|
|
11357
|
+
return null;
|
|
11358
|
+
}
|
|
11359
|
+
var parsedGlobs, PARSED_GLOB_LIMIT = 4096;
|
|
11360
|
+
var init_glob = __esm(() => {
|
|
11361
|
+
parsedGlobs = new Map;
|
|
11362
|
+
});
|
|
11363
|
+
|
|
11364
|
+
// src/core/blacklist-fold.ts
|
|
11365
|
+
function foldFieldPath(path) {
|
|
11366
|
+
return foldCase(path);
|
|
11367
|
+
}
|
|
11368
|
+
var init_blacklist_fold = () => {};
|
|
11369
|
+
|
|
11370
|
+
// src/core/mongo/path-matcher.ts
|
|
11371
|
+
function compilePatterns(raw) {
|
|
11372
|
+
const patterns = [];
|
|
11373
|
+
const rejected = [];
|
|
11374
|
+
for (const entry of raw) {
|
|
11375
|
+
if (typeof entry !== "string" || entry.length === 0) {
|
|
11376
|
+
rejected.push({ raw: String(entry ?? ""), reason: "must be a non-empty string" });
|
|
11377
|
+
continue;
|
|
11378
|
+
}
|
|
11379
|
+
const segments = entry.split(".");
|
|
11380
|
+
if (segments.some((s) => s.length === 0)) {
|
|
11381
|
+
rejected.push({ raw: entry, reason: "empty path segment" });
|
|
11382
|
+
continue;
|
|
11383
|
+
}
|
|
11384
|
+
const globstar = segments.find((seg) => seg.includes("**"));
|
|
11385
|
+
if (globstar !== undefined) {
|
|
11386
|
+
rejected.push({
|
|
11387
|
+
raw: entry,
|
|
11388
|
+
reason: "`**` matches one segment, not a subtree; write `*` or a longer path"
|
|
11389
|
+
});
|
|
11390
|
+
continue;
|
|
11391
|
+
}
|
|
11392
|
+
const dead = segments.map((seg) => globNeverMatches(seg)).find((r) => r !== null);
|
|
11393
|
+
if (dead !== undefined && dead !== null) {
|
|
11394
|
+
rejected.push({ raw: entry, reason: dead });
|
|
11395
|
+
continue;
|
|
11396
|
+
}
|
|
11397
|
+
const wildcardTail = segments.length > 1 && segments[segments.length - 1] === "*";
|
|
11398
|
+
const literal = wildcardTail ? segments.slice(0, -1) : segments;
|
|
11399
|
+
patterns.push({
|
|
11400
|
+
raw: entry,
|
|
11401
|
+
segments: literal,
|
|
11402
|
+
wildcardTail
|
|
11403
|
+
});
|
|
11404
|
+
}
|
|
11405
|
+
return { patterns, rejected };
|
|
11406
|
+
}
|
|
11407
|
+
function matchSegment(pattern, segment) {
|
|
11408
|
+
return globMatches(pattern, segment, FOLD_CASE);
|
|
11409
|
+
}
|
|
11410
|
+
function matchAny(path, patterns) {
|
|
11411
|
+
if (patterns.length === 0)
|
|
11412
|
+
return false;
|
|
11413
|
+
const pathSegments = path.split(".");
|
|
11414
|
+
for (const pat of patterns) {
|
|
11415
|
+
if (pat.wildcardTail) {
|
|
11416
|
+
if (pathSegments.length < pat.segments.length)
|
|
11417
|
+
continue;
|
|
11418
|
+
} else {
|
|
11419
|
+
if (pat.segments.length !== pathSegments.length)
|
|
11420
|
+
continue;
|
|
11421
|
+
}
|
|
11422
|
+
let ok = true;
|
|
11423
|
+
for (let i = 0;i < pat.segments.length; i++) {
|
|
11424
|
+
if (!globMatches(pat.segments[i], pathSegments[i], FOLD_CASE)) {
|
|
11425
|
+
ok = false;
|
|
11426
|
+
break;
|
|
11427
|
+
}
|
|
11428
|
+
}
|
|
11429
|
+
if (ok)
|
|
11430
|
+
return true;
|
|
11431
|
+
}
|
|
11432
|
+
return false;
|
|
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)
|
|
11184
11464
|
return true;
|
|
11185
11465
|
}
|
|
11186
11466
|
}
|
|
11187
11467
|
return false;
|
|
11188
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;
|
|
11498
|
+
var init_path_matcher = __esm(() => {
|
|
11499
|
+
init_glob();
|
|
11500
|
+
init_blacklist_fold();
|
|
11501
|
+
FOLD_CASE = { caseInsensitive: true };
|
|
11502
|
+
compiledRuleSets = new WeakMap;
|
|
11503
|
+
});
|
|
11504
|
+
|
|
11505
|
+
// src/core/mongo/request-fields.ts
|
|
11506
|
+
function asFieldPath(text2) {
|
|
11507
|
+
if (text2.startsWith("$$"))
|
|
11508
|
+
return text2.slice(2);
|
|
11509
|
+
if (text2.startsWith("$"))
|
|
11510
|
+
return text2.slice(1);
|
|
11511
|
+
return text2;
|
|
11512
|
+
}
|
|
11189
11513
|
function findProtectedFieldReference(request, protectedFields) {
|
|
11190
11514
|
if (protectedFields.size === 0)
|
|
11191
11515
|
return;
|
|
11516
|
+
const rules = contiguousRulesFor(protectedFields);
|
|
11192
11517
|
const candidates = [];
|
|
11193
11518
|
let movesWholeDocument = false;
|
|
11194
11519
|
const walk = (node) => {
|
|
@@ -11222,7 +11547,7 @@ function findProtectedFieldReference(request, protectedFields) {
|
|
|
11222
11547
|
}
|
|
11223
11548
|
};
|
|
11224
11549
|
walk(request);
|
|
11225
|
-
const named = candidates.find((path) => path.length > 0 &&
|
|
11550
|
+
const named = candidates.find((path) => path.length > 0 && reachesProtectedPath(foldFieldPath(path), rules));
|
|
11226
11551
|
if (named !== undefined)
|
|
11227
11552
|
return named;
|
|
11228
11553
|
return movesWholeDocument ? [...protectedFields][0] : undefined;
|
|
@@ -11245,10 +11570,12 @@ function protectedFieldsForRequest(request, collection, columns) {
|
|
|
11245
11570
|
collectStrings(value);
|
|
11246
11571
|
};
|
|
11247
11572
|
collectStrings(request);
|
|
11248
|
-
const
|
|
11573
|
+
const namedFolded = new Set([...named].map(foldFieldPath));
|
|
11574
|
+
const collectionFolded = foldFieldPath(collection);
|
|
11249
11575
|
const fields = new Set;
|
|
11250
11576
|
for (const [name, rules] of Object.entries(columns)) {
|
|
11251
|
-
const
|
|
11577
|
+
const folded = foldFieldPath(name);
|
|
11578
|
+
const applies = folded === collectionFolded || namedFolded.has(folded);
|
|
11252
11579
|
if (!applies)
|
|
11253
11580
|
continue;
|
|
11254
11581
|
for (const rule of rules) {
|
|
@@ -11261,6 +11588,8 @@ function protectedFieldsForRequest(request, collection, columns) {
|
|
|
11261
11588
|
}
|
|
11262
11589
|
var WHOLE_DOCUMENT_VARIABLES, RESHAPES_DOCUMENT, NAMES_A_FIELD_DYNAMICALLY = "$getField";
|
|
11263
11590
|
var init_request_fields = __esm(() => {
|
|
11591
|
+
init_path_matcher();
|
|
11592
|
+
init_blacklist_fold();
|
|
11264
11593
|
WHOLE_DOCUMENT_VARIABLES = new Set(["ROOT", "CURRENT"]);
|
|
11265
11594
|
RESHAPES_DOCUMENT = new Set(["$objectToArray", "$replaceRoot", "$replaceWith"]);
|
|
11266
11595
|
});
|
|
@@ -12215,66 +12544,13 @@ var init_size_guard = __esm(() => {
|
|
|
12215
12544
|
init_types3();
|
|
12216
12545
|
});
|
|
12217
12546
|
|
|
12218
|
-
// src/utils/glob.ts
|
|
12219
|
-
function globToRegex(glob) {
|
|
12220
|
-
let out = "^";
|
|
12221
|
-
for (let i = 0;i < glob.length; i++) {
|
|
12222
|
-
const c = glob[i];
|
|
12223
|
-
if (c === "\\" && i + 1 < glob.length) {
|
|
12224
|
-
out += glob[++i].replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
12225
|
-
continue;
|
|
12226
|
-
}
|
|
12227
|
-
if (c === "*")
|
|
12228
|
-
out += ".*";
|
|
12229
|
-
else if (c === "?")
|
|
12230
|
-
out += ".";
|
|
12231
|
-
else if (c === "[") {
|
|
12232
|
-
const end = findClassEnd(glob, i);
|
|
12233
|
-
const body = end === -1 ? "" : glob.slice(i, end + 1);
|
|
12234
|
-
if (body !== "" && isValidCharacterClass(body)) {
|
|
12235
|
-
out += body;
|
|
12236
|
-
i = end;
|
|
12237
|
-
} else {
|
|
12238
|
-
out += "\\[";
|
|
12239
|
-
}
|
|
12240
|
-
} else if (".^$+(){}|\\".includes(c)) {
|
|
12241
|
-
out += "\\" + c;
|
|
12242
|
-
} else {
|
|
12243
|
-
out += c;
|
|
12244
|
-
}
|
|
12245
|
-
}
|
|
12246
|
-
out += "$";
|
|
12247
|
-
return new RegExp(out);
|
|
12248
|
-
}
|
|
12249
|
-
function findClassEnd(glob, open2) {
|
|
12250
|
-
for (let i = open2 + 1;i < glob.length; i++) {
|
|
12251
|
-
if (glob[i] === "\\") {
|
|
12252
|
-
i++;
|
|
12253
|
-
continue;
|
|
12254
|
-
}
|
|
12255
|
-
if (glob[i] === "]")
|
|
12256
|
-
return i;
|
|
12257
|
-
}
|
|
12258
|
-
return -1;
|
|
12259
|
-
}
|
|
12260
|
-
function isValidCharacterClass(body) {
|
|
12261
|
-
try {
|
|
12262
|
-
new RegExp(body);
|
|
12263
|
-
return true;
|
|
12264
|
-
} catch {
|
|
12265
|
-
return false;
|
|
12266
|
-
}
|
|
12267
|
-
}
|
|
12268
|
-
|
|
12269
12547
|
// src/adapters/redis/blacklist-enforcer.ts
|
|
12270
12548
|
function patternsOverlap(a, b) {
|
|
12271
|
-
const ra = globToRegex(a);
|
|
12272
|
-
const rb = globToRegex(b);
|
|
12273
12549
|
const probeA = sampleFromGlob(a);
|
|
12274
12550
|
const probeB = sampleFromGlob(b);
|
|
12275
|
-
if (probeA !== null &&
|
|
12551
|
+
if (probeA !== null && globMatches(b, probeA))
|
|
12276
12552
|
return true;
|
|
12277
|
-
if (probeB !== null &&
|
|
12553
|
+
if (probeB !== null && globMatches(a, probeB))
|
|
12278
12554
|
return true;
|
|
12279
12555
|
return false;
|
|
12280
12556
|
}
|
|
@@ -12300,6 +12576,15 @@ function sampleFromGlob(glob) {
|
|
|
12300
12576
|
}
|
|
12301
12577
|
return s;
|
|
12302
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
|
+
}
|
|
12303
12588
|
function checkKeyArgs(command, args, rules) {
|
|
12304
12589
|
if (rules.length === 0)
|
|
12305
12590
|
return { ok: true };
|
|
@@ -12317,7 +12602,7 @@ function checkKeyArgs(command, args, rules) {
|
|
|
12317
12602
|
if (key === undefined)
|
|
12318
12603
|
continue;
|
|
12319
12604
|
for (const pat of rules) {
|
|
12320
|
-
if (
|
|
12605
|
+
if (globMatches(pat, key)) {
|
|
12321
12606
|
return { ok: false, matchedKey: key, matchedPattern: pat };
|
|
12322
12607
|
}
|
|
12323
12608
|
}
|
|
@@ -12374,6 +12659,7 @@ function expandKeyArity(arity, args, argCount) {
|
|
|
12374
12659
|
}
|
|
12375
12660
|
var init_blacklist_enforcer = __esm(() => {
|
|
12376
12661
|
init_command_metadata();
|
|
12662
|
+
init_glob();
|
|
12377
12663
|
});
|
|
12378
12664
|
|
|
12379
12665
|
// src/adapters/redis/returned-key-names.ts
|
|
@@ -12385,8 +12671,7 @@ function filterReturnedKeyNames(command, reply, rules) {
|
|
|
12385
12671
|
return reply;
|
|
12386
12672
|
if (!returnsKeyNames(command))
|
|
12387
12673
|
return reply;
|
|
12388
|
-
const
|
|
12389
|
-
const permitted = (key) => typeof key === "string" && regexes.every((regex) => !regex.test(key));
|
|
12674
|
+
const permitted = (key) => typeof key === "string" && rules.every((rule) => !globMatches(rule, key));
|
|
12390
12675
|
if (Array.isArray(reply) && reply.length === 2 && Array.isArray(reply[1])) {
|
|
12391
12676
|
return [reply[0], reply[1].filter(permitted)];
|
|
12392
12677
|
}
|
|
@@ -12396,12 +12681,13 @@ function filterReturnedKeyNames(command, reply, rules) {
|
|
|
12396
12681
|
}
|
|
12397
12682
|
var RETURNS_KEY_NAMES;
|
|
12398
12683
|
var init_returned_key_names = __esm(() => {
|
|
12684
|
+
init_glob();
|
|
12399
12685
|
RETURNS_KEY_NAMES = new Set(["SCAN", "KEYS"]);
|
|
12400
12686
|
});
|
|
12401
12687
|
|
|
12402
12688
|
// src/adapters/redis/value-masker.ts
|
|
12403
12689
|
function planFor(key, rules) {
|
|
12404
|
-
const matched = rules.filter((r) =>
|
|
12690
|
+
const matched = rules.filter((r) => globMatches(r.keyPattern, key));
|
|
12405
12691
|
if (matched.length === 0)
|
|
12406
12692
|
return null;
|
|
12407
12693
|
const wholeValue = matched.some((r) => !r.fields || r.fields.length === 0);
|
|
@@ -12456,7 +12742,7 @@ function maskRedisRows(command, args, rows, rules) {
|
|
|
12456
12742
|
}
|
|
12457
12743
|
var REDACTED = "[REDACTED]", MASKABLE;
|
|
12458
12744
|
var init_value_masker = __esm(() => {
|
|
12459
|
-
|
|
12745
|
+
init_glob();
|
|
12460
12746
|
MASKABLE = new Set(["GET", "GETRANGE", "HGETALL", "HGET", "HMGET", "HVALS"]);
|
|
12461
12747
|
});
|
|
12462
12748
|
|
|
@@ -12547,8 +12833,7 @@ class RedisAdapter {
|
|
|
12547
12833
|
async sampleKeyNames(limit) {
|
|
12548
12834
|
const client = this.requireClient();
|
|
12549
12835
|
const rules = this.blacklistRules;
|
|
12550
|
-
const
|
|
12551
|
-
const { keys, scanned } = await scanAllKeys(client, "*", limit, limit, (key) => regexes.every((r) => !r.test(key)));
|
|
12836
|
+
const { keys, scanned } = await scanAllKeys(client, "*", limit, limit, (key) => rules.every((rule) => !globMatches(rule, key)));
|
|
12552
12837
|
return { names: keys, truncated: scanned >= limit };
|
|
12553
12838
|
}
|
|
12554
12839
|
async getDbSize() {
|
|
@@ -12927,6 +13212,7 @@ var init_redis_adapter = __esm(() => {
|
|
|
12927
13212
|
init_types2();
|
|
12928
13213
|
init_size_guard();
|
|
12929
13214
|
init_blacklist_enforcer();
|
|
13215
|
+
init_glob();
|
|
12930
13216
|
init_returned_key_names();
|
|
12931
13217
|
init_types3();
|
|
12932
13218
|
init_command_metadata();
|
|
@@ -13227,6 +13513,180 @@ var init_elasticsearch_adapter = __esm(() => {
|
|
|
13227
13513
|
init_server_side_script();
|
|
13228
13514
|
});
|
|
13229
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
|
+
|
|
13230
13690
|
// src/adapters/factory.ts
|
|
13231
13691
|
class AdapterFactory {
|
|
13232
13692
|
static createSqlAdapter(rawOptions) {
|
|
@@ -13241,7 +13701,7 @@ class AdapterFactory {
|
|
|
13241
13701
|
throw new Error(`createSqlAdapter requires a SQL system, got: ${options.system}`);
|
|
13242
13702
|
}
|
|
13243
13703
|
}
|
|
13244
|
-
static
|
|
13704
|
+
static createQueryableAdapterWithoutRules(rawOptions) {
|
|
13245
13705
|
const options = withResolvedTimeout(rawOptions);
|
|
13246
13706
|
switch (options.system) {
|
|
13247
13707
|
case "mongodb":
|
|
@@ -13251,10 +13711,27 @@ class AdapterFactory {
|
|
|
13251
13711
|
case "elasticsearch":
|
|
13252
13712
|
return new ElasticsearchAdapter(options);
|
|
13253
13713
|
default:
|
|
13254
|
-
throw new Error(`
|
|
13714
|
+
throw new Error(`createQueryableAdapterWithoutRules requires a non-SQL queryable system, got: ${options.system}`);
|
|
13255
13715
|
}
|
|
13256
13716
|
}
|
|
13257
|
-
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) {
|
|
13258
13735
|
switch (options.system) {
|
|
13259
13736
|
case "postgresql":
|
|
13260
13737
|
case "mysql":
|
|
@@ -13263,7 +13740,7 @@ class AdapterFactory {
|
|
|
13263
13740
|
case "mongodb":
|
|
13264
13741
|
case "redis":
|
|
13265
13742
|
case "elasticsearch":
|
|
13266
|
-
return AdapterFactory.
|
|
13743
|
+
return AdapterFactory.createQueryableAdapterWithoutRules(options);
|
|
13267
13744
|
default:
|
|
13268
13745
|
throw new Error(`Unsupported database system: ${options.system}`);
|
|
13269
13746
|
}
|
|
@@ -13273,7 +13750,7 @@ class AdapterFactory {
|
|
|
13273
13750
|
if (options.system !== "mongodb") {
|
|
13274
13751
|
throw new Error("createMongoDBAdapter requires system: mongodb");
|
|
13275
13752
|
}
|
|
13276
|
-
const adapter = AdapterFactory.
|
|
13753
|
+
const adapter = AdapterFactory.createQueryableAdapterWithoutRules(options);
|
|
13277
13754
|
adapter.setBlacklistColumns(config.blacklist?.columns ?? {});
|
|
13278
13755
|
return adapter;
|
|
13279
13756
|
}
|
|
@@ -13284,7 +13761,12 @@ class AdapterFactory {
|
|
|
13284
13761
|
}
|
|
13285
13762
|
const options = withResolvedTimeout(rawOptions);
|
|
13286
13763
|
const adapter = new RedisAdapter(options);
|
|
13287
|
-
|
|
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);
|
|
13288
13770
|
adapter.setMaskRules(config.redis?.mask ?? []);
|
|
13289
13771
|
return adapter;
|
|
13290
13772
|
}
|
|
@@ -13292,7 +13774,7 @@ class AdapterFactory {
|
|
|
13292
13774
|
if (options.system !== "elasticsearch") {
|
|
13293
13775
|
throw new Error("createElasticsearchAdapter requires system: elasticsearch");
|
|
13294
13776
|
}
|
|
13295
|
-
return AdapterFactory.
|
|
13777
|
+
return AdapterFactory.createQueryableAdapterWithoutRules(options);
|
|
13296
13778
|
}
|
|
13297
13779
|
}
|
|
13298
13780
|
var init_factory = __esm(() => {
|
|
@@ -13301,6 +13783,8 @@ var init_factory = __esm(() => {
|
|
|
13301
13783
|
init_mongodb_adapter();
|
|
13302
13784
|
init_redis_adapter();
|
|
13303
13785
|
init_elasticsearch_adapter();
|
|
13786
|
+
init_blacklist_manager();
|
|
13787
|
+
init_message_loader();
|
|
13304
13788
|
});
|
|
13305
13789
|
|
|
13306
13790
|
// src/adapters/index.ts
|
|
@@ -13449,7 +13933,7 @@ async function writeV2InitConfig(configPath, connectionName, connection, permiss
|
|
|
13449
13933
|
audit: {
|
|
13450
13934
|
enabled: true,
|
|
13451
13935
|
strict: false,
|
|
13452
|
-
rotation: {
|
|
13936
|
+
rotation: { ...DEFAULT_AUDIT_ROTATION }
|
|
13453
13937
|
}
|
|
13454
13938
|
};
|
|
13455
13939
|
await writeV2Config(storagePath, v2Config);
|
|
@@ -13464,6 +13948,7 @@ var init_init_shared = __esm(() => {
|
|
|
13464
13948
|
init_config();
|
|
13465
13949
|
init_config_v2();
|
|
13466
13950
|
init_prompts();
|
|
13951
|
+
init_validation();
|
|
13467
13952
|
init_config_binding();
|
|
13468
13953
|
});
|
|
13469
13954
|
|
|
@@ -13914,7 +14399,7 @@ async function initCommandHandler(options, command) {
|
|
|
13914
14399
|
password: String(resolveValue(newConfig.connection.password, "password")) || "",
|
|
13915
14400
|
database: String(resolveValue(newConfig.connection.database, "database"))
|
|
13916
14401
|
};
|
|
13917
|
-
const adapter = AdapterFactory.
|
|
14402
|
+
const adapter = AdapterFactory.createAdapterWithoutRules(testConnection);
|
|
13918
14403
|
try {
|
|
13919
14404
|
await adapter.connect();
|
|
13920
14405
|
const isHealthy = await adapter.testConnection();
|
|
@@ -16456,94 +16941,6 @@ class ColumnIndexBuilder {
|
|
|
16456
16941
|
};
|
|
16457
16942
|
}
|
|
16458
16943
|
}
|
|
16459
|
-
// src/core/blacklist-manager.ts
|
|
16460
|
-
class BlacklistManager {
|
|
16461
|
-
config;
|
|
16462
|
-
state;
|
|
16463
|
-
overrideEnabled;
|
|
16464
|
-
constructor(config, overrideEnvValue) {
|
|
16465
|
-
this.config = config;
|
|
16466
|
-
this.overrideEnabled = (overrideEnvValue ?? Bun.env.DBCLI_OVERRIDE_BLACKLIST ?? "") === "true";
|
|
16467
|
-
this.state = this.loadBlacklist();
|
|
16468
|
-
}
|
|
16469
|
-
loadBlacklist() {
|
|
16470
|
-
const tables = new Set;
|
|
16471
|
-
const columns = new Map;
|
|
16472
|
-
const blacklistConfig = this.config.blacklist;
|
|
16473
|
-
if (!blacklistConfig) {
|
|
16474
|
-
return { tables, columns };
|
|
16475
|
-
}
|
|
16476
|
-
if (Array.isArray(blacklistConfig.tables)) {
|
|
16477
|
-
for (const tableName of blacklistConfig.tables) {
|
|
16478
|
-
if (typeof tableName === "string") {
|
|
16479
|
-
tables.add(tableName.toLowerCase());
|
|
16480
|
-
} else {
|
|
16481
|
-
console.warn(`[BlacklistManager] Invalid table name in blacklist config: ${JSON.stringify(tableName)}`);
|
|
16482
|
-
}
|
|
16483
|
-
}
|
|
16484
|
-
} else if (blacklistConfig.tables !== undefined) {
|
|
16485
|
-
console.warn("[BlacklistManager] blacklist.tables must be an array, ignoring");
|
|
16486
|
-
}
|
|
16487
|
-
if (blacklistConfig.columns && typeof blacklistConfig.columns === "object" && !Array.isArray(blacklistConfig.columns)) {
|
|
16488
|
-
for (const [tableName, cols] of Object.entries(blacklistConfig.columns)) {
|
|
16489
|
-
if (typeof tableName !== "string") {
|
|
16490
|
-
console.warn(`[BlacklistManager] Invalid table name key in columns config: ${JSON.stringify(tableName)}`);
|
|
16491
|
-
continue;
|
|
16492
|
-
}
|
|
16493
|
-
if (!Array.isArray(cols)) {
|
|
16494
|
-
console.warn(`[BlacklistManager] blacklist.columns["${tableName}"] must be an array, ignoring`);
|
|
16495
|
-
continue;
|
|
16496
|
-
}
|
|
16497
|
-
const columnSet = new Set;
|
|
16498
|
-
for (const col of cols) {
|
|
16499
|
-
if (typeof col === "string") {
|
|
16500
|
-
columnSet.add(col);
|
|
16501
|
-
} else {
|
|
16502
|
-
console.warn(`[BlacklistManager] Invalid column name in blacklist.columns["${tableName}"]: ${JSON.stringify(col)}`);
|
|
16503
|
-
}
|
|
16504
|
-
}
|
|
16505
|
-
if (columnSet.size > 0) {
|
|
16506
|
-
columns.set(tableName.toLowerCase(), columnSet);
|
|
16507
|
-
}
|
|
16508
|
-
}
|
|
16509
|
-
} else if (blacklistConfig.columns !== undefined) {
|
|
16510
|
-
console.warn("[BlacklistManager] blacklist.columns must be an object, ignoring");
|
|
16511
|
-
}
|
|
16512
|
-
return { tables, columns };
|
|
16513
|
-
}
|
|
16514
|
-
isTableBlacklisted(tableName) {
|
|
16515
|
-
return this.state.tables.has(tableName.toLowerCase());
|
|
16516
|
-
}
|
|
16517
|
-
isColumnBlacklisted(tableName, columnName) {
|
|
16518
|
-
const columnSet = this.state.columns.get(tableName.toLowerCase());
|
|
16519
|
-
if (!columnSet) {
|
|
16520
|
-
return false;
|
|
16521
|
-
}
|
|
16522
|
-
return columnSet.has(columnName);
|
|
16523
|
-
}
|
|
16524
|
-
getBlacklistedColumns(tableName) {
|
|
16525
|
-
const columnSet = this.state.columns.get(tableName.toLowerCase());
|
|
16526
|
-
if (!columnSet) {
|
|
16527
|
-
return [];
|
|
16528
|
-
}
|
|
16529
|
-
return Array.from(columnSet);
|
|
16530
|
-
}
|
|
16531
|
-
getAllBlacklistedColumns() {
|
|
16532
|
-
const all = new Set;
|
|
16533
|
-
for (const columnSet of this.state.columns.values()) {
|
|
16534
|
-
for (const column of columnSet)
|
|
16535
|
-
all.add(column);
|
|
16536
|
-
}
|
|
16537
|
-
return Array.from(all);
|
|
16538
|
-
}
|
|
16539
|
-
canOverrideBlacklist() {
|
|
16540
|
-
return this.overrideEnabled;
|
|
16541
|
-
}
|
|
16542
|
-
getState() {
|
|
16543
|
-
return this.state;
|
|
16544
|
-
}
|
|
16545
|
-
}
|
|
16546
|
-
|
|
16547
16944
|
// src/types/blacklist.ts
|
|
16548
16945
|
var BlacklistError;
|
|
16549
16946
|
var init_blacklist = __esm(() => {
|
|
@@ -16607,18 +17004,138 @@ function projectRows(rows, selection) {
|
|
|
16607
17004
|
const columnNames = collectColumnNames(projectedRows);
|
|
16608
17005
|
return { rows: normalizeRows(projectedRows, columnNames), columnNames };
|
|
16609
17006
|
}
|
|
16610
|
-
function hasFieldPath(row, segments) {
|
|
16611
|
-
|
|
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
|
+
}));
|
|
17048
|
+
}
|
|
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
|
+
}
|
|
16612
17085
|
}
|
|
16613
|
-
function
|
|
16614
|
-
|
|
16615
|
-
|
|
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(".") }));
|
|
16616
17129
|
const maskRecord = (row) => {
|
|
16617
|
-
let projected = cloneRecord(row, topLevel);
|
|
17130
|
+
let projected = cloneRecord(row, topLevel, caseInsensitive);
|
|
16618
17131
|
for (const { head, segments } of dotted) {
|
|
16619
|
-
const value = row
|
|
16620
|
-
if (value !== null && typeof value === "object")
|
|
16621
|
-
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);
|
|
16622
17139
|
}
|
|
16623
17140
|
return projected;
|
|
16624
17141
|
};
|
|
@@ -16657,11 +17174,37 @@ function validatePath(path) {
|
|
|
16657
17174
|
if (unsafe)
|
|
16658
17175
|
throw new Error(`--fields contains an unsafe field segment: ${unsafe}`);
|
|
16659
17176
|
}
|
|
16660
|
-
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) {
|
|
16661
17204
|
if (segments.length === 0)
|
|
16662
17205
|
return { found: true, value };
|
|
16663
17206
|
if (Array.isArray(value)) {
|
|
16664
|
-
const results = value.map((item) => readPath(item, segments));
|
|
17207
|
+
const results = value.map((item) => readPath(item, segments, caseInsensitive));
|
|
16665
17208
|
return {
|
|
16666
17209
|
found: results.some((result) => result.found),
|
|
16667
17210
|
value: results.map((result) => result.found ? result.value : undefined)
|
|
@@ -16669,41 +17212,68 @@ function readPath(value, segments) {
|
|
|
16669
17212
|
}
|
|
16670
17213
|
if (!isRecord(value))
|
|
16671
17214
|
return { found: false };
|
|
16672
|
-
const
|
|
16673
|
-
if (
|
|
16674
|
-
return { found: true, value: value[
|
|
16675
|
-
}
|
|
17215
|
+
const exact = ownKey(value, segments.join("."), caseInsensitive);
|
|
17216
|
+
if (exact !== undefined)
|
|
17217
|
+
return { found: true, value: value[exact] };
|
|
16676
17218
|
const [head, ...tail] = segments;
|
|
16677
|
-
|
|
17219
|
+
const headKey = ownKey(value, head, caseInsensitive);
|
|
17220
|
+
if (headKey === undefined)
|
|
16678
17221
|
return { found: false };
|
|
16679
|
-
return readPath(value[
|
|
17222
|
+
return readPath(value[headKey], tail, caseInsensitive);
|
|
17223
|
+
}
|
|
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;
|
|
16680
17247
|
}
|
|
16681
|
-
function omitPath(value, segments) {
|
|
17248
|
+
function omitPath(value, segments, caseInsensitive) {
|
|
16682
17249
|
if (segments.length === 0)
|
|
16683
17250
|
return value;
|
|
16684
17251
|
if (Array.isArray(value))
|
|
16685
|
-
return value.map((item) => omitPath(item, segments));
|
|
17252
|
+
return value.map((item) => omitPath(item, segments, caseInsensitive));
|
|
16686
17253
|
if (!isPlainRecord(value))
|
|
16687
17254
|
return value;
|
|
16688
|
-
const
|
|
16689
|
-
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);
|
|
16690
17259
|
const out = {};
|
|
16691
17260
|
for (const [key, child] of Object.entries(value)) {
|
|
16692
|
-
|
|
17261
|
+
const folded = fold(key);
|
|
17262
|
+
if (folded === exactPath)
|
|
16693
17263
|
continue;
|
|
16694
|
-
if (
|
|
17264
|
+
if (folded === head) {
|
|
16695
17265
|
if (tail.length > 0)
|
|
16696
|
-
defineData(out, key, omitPath(child, tail));
|
|
17266
|
+
defineData(out, key, omitPath(child, tail, caseInsensitive));
|
|
16697
17267
|
continue;
|
|
16698
17268
|
}
|
|
16699
17269
|
defineData(out, key, child);
|
|
16700
17270
|
}
|
|
16701
17271
|
return out;
|
|
16702
17272
|
}
|
|
16703
|
-
function cloneRecord(value, omittedKeys) {
|
|
17273
|
+
function cloneRecord(value, omittedKeys, caseInsensitive = false) {
|
|
16704
17274
|
const out = {};
|
|
16705
17275
|
for (const [key, child] of Object.entries(value)) {
|
|
16706
|
-
if (!omittedKeys.has(key))
|
|
17276
|
+
if (!omittedKeys.has(caseInsensitive ? foldCase(key) : key))
|
|
16707
17277
|
defineData(out, key, child);
|
|
16708
17278
|
}
|
|
16709
17279
|
return out;
|
|
@@ -16748,9 +17318,11 @@ function defineData(target, key, value) {
|
|
|
16748
17318
|
writable: true
|
|
16749
17319
|
});
|
|
16750
17320
|
}
|
|
16751
|
-
var UNSAFE_SEGMENTS;
|
|
17321
|
+
var UNSAFE_SEGMENTS, NARROW_STATE_LIMIT = 4096, foldedKeyIndex;
|
|
16752
17322
|
var init_field_projection = __esm(() => {
|
|
17323
|
+
init_path_matcher();
|
|
16753
17324
|
UNSAFE_SEGMENTS = new Set(["__proto__", "prototype", "constructor"]);
|
|
17325
|
+
foldedKeyIndex = new WeakMap;
|
|
16754
17326
|
});
|
|
16755
17327
|
|
|
16756
17328
|
// src/utils/es-index-target.ts
|
|
@@ -16817,16 +17389,16 @@ function expandIndexTargets(target) {
|
|
|
16817
17389
|
return { concrete, wildcards };
|
|
16818
17390
|
}
|
|
16819
17391
|
function matchesIndexGlob(pattern, name) {
|
|
16820
|
-
const normalized = pattern.toLowerCase() === "_all" ? "*" : pattern
|
|
17392
|
+
const normalized = pattern.toLowerCase() === "_all" ? "*" : pattern;
|
|
16821
17393
|
try {
|
|
16822
|
-
return
|
|
17394
|
+
return globMatches(normalized, name, { caseInsensitive: true });
|
|
16823
17395
|
} catch {
|
|
16824
17396
|
return true;
|
|
16825
17397
|
}
|
|
16826
17398
|
}
|
|
16827
17399
|
function reachesByConvention(name, entry) {
|
|
16828
|
-
const lower = name
|
|
16829
|
-
const target = entry
|
|
17400
|
+
const lower = foldFieldPath(name);
|
|
17401
|
+
const target = foldFieldPath(entry);
|
|
16830
17402
|
return new RegExp(`^\\.ds-${escapeRegExp(target)}-`).test(lower) || new RegExp(`^${escapeRegExp(target)}-\\d+$`).test(lower);
|
|
16831
17403
|
}
|
|
16832
17404
|
function escapeRegExp(text2) {
|
|
@@ -16847,11 +17419,14 @@ function indexExpressionReaches(expression, blacklisted) {
|
|
|
16847
17419
|
return false;
|
|
16848
17420
|
const { concrete, wildcards } = expandIndexTargets(expression);
|
|
16849
17421
|
const entries = expandBlacklistEntries(blacklisted);
|
|
16850
|
-
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));
|
|
16851
17423
|
return concrete.some(reachesName) || wildcards.some((pattern) => entries.names.some((entry) => matchesIndexGlob(pattern, entry)) || entries.patterns.length > 0);
|
|
16852
17424
|
}
|
|
16853
17425
|
var MAX_DECODE_PASSES = 4;
|
|
16854
|
-
var init_es_index_target = () => {
|
|
17426
|
+
var init_es_index_target = __esm(() => {
|
|
17427
|
+
init_blacklist_fold();
|
|
17428
|
+
init_glob();
|
|
17429
|
+
});
|
|
16855
17430
|
|
|
16856
17431
|
// src/core/blacklist-validator.ts
|
|
16857
17432
|
function flattenArrayRow(row) {
|
|
@@ -16868,7 +17443,7 @@ function dedupe2(values) {
|
|
|
16868
17443
|
const seen = new Set;
|
|
16869
17444
|
const result = [];
|
|
16870
17445
|
for (const value of values) {
|
|
16871
|
-
const key = value
|
|
17446
|
+
const key = foldFieldPath(value);
|
|
16872
17447
|
if (value.length === 0 || seen.has(key))
|
|
16873
17448
|
continue;
|
|
16874
17449
|
seen.add(key);
|
|
@@ -16876,6 +17451,20 @@ function dedupe2(values) {
|
|
|
16876
17451
|
}
|
|
16877
17452
|
return result;
|
|
16878
17453
|
}
|
|
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);
|
|
17459
|
+
if (globbed.length === 0)
|
|
17460
|
+
return [];
|
|
17461
|
+
const { patterns, rejected } = compilePatterns(globbed);
|
|
17462
|
+
if (rejected.length > 0) {
|
|
17463
|
+
const detail = rejected.map((r) => `'${r.raw}' (${r.reason})`).join(", ");
|
|
17464
|
+
throw new BlacklistError(`blacklist.columns for '${table}' has entries this matcher cannot read: ${detail}`, table, operation);
|
|
17465
|
+
}
|
|
17466
|
+
return patterns;
|
|
17467
|
+
}
|
|
16879
17468
|
|
|
16880
17469
|
class BlacklistValidator {
|
|
16881
17470
|
manager;
|
|
@@ -16933,7 +17522,27 @@ class BlacklistValidator {
|
|
|
16933
17522
|
if (blacklisted.length === 0 || fields.length === 0) {
|
|
16934
17523
|
return;
|
|
16935
17524
|
}
|
|
16936
|
-
const
|
|
17525
|
+
const protectedPaths = new Set(blacklisted.map(foldFieldPath));
|
|
17526
|
+
const globs = compileGlobRules(blacklisted, tableName, operation);
|
|
17527
|
+
const conflicts = fields.filter((field) => {
|
|
17528
|
+
const name = foldFieldPath(field);
|
|
17529
|
+
if (protectedPaths.has(name))
|
|
17530
|
+
return true;
|
|
17531
|
+
if (matchAny(name, globs))
|
|
17532
|
+
return true;
|
|
17533
|
+
let dot = name.indexOf(".");
|
|
17534
|
+
while (dot >= 0) {
|
|
17535
|
+
if (dot > 0) {
|
|
17536
|
+
const ancestor = name.slice(0, dot);
|
|
17537
|
+
if (protectedPaths.has(ancestor))
|
|
17538
|
+
return true;
|
|
17539
|
+
if (matchAny(ancestor, globs))
|
|
17540
|
+
return true;
|
|
17541
|
+
}
|
|
17542
|
+
dot = name.indexOf(".", dot + 1);
|
|
17543
|
+
}
|
|
17544
|
+
return false;
|
|
17545
|
+
});
|
|
16937
17546
|
if (conflicts.length === 0) {
|
|
16938
17547
|
return;
|
|
16939
17548
|
}
|
|
@@ -16959,7 +17568,7 @@ class BlacklistValidator {
|
|
|
16959
17568
|
const tables = dedupe2(tableNames);
|
|
16960
17569
|
const blacklistedColumns = tables.length === 0 ? this.manager.getAllBlacklistedColumns() : Array.from(new Set(tables.flatMap((table) => this.manager.getBlacklistedColumns(table))));
|
|
16961
17570
|
if (blacklistedColumns.length === 0) {
|
|
16962
|
-
return { filteredRows: rows, omittedColumns: [] };
|
|
17571
|
+
return { filteredRows: rows, omittedColumns: [], reachesOmitted: () => false };
|
|
16963
17572
|
}
|
|
16964
17573
|
const probeNested = blacklistedColumns.some((path) => path.includes("."));
|
|
16965
17574
|
const presentColumns = new Set(columnList);
|
|
@@ -16971,7 +17580,7 @@ class BlacklistValidator {
|
|
|
16971
17580
|
continue;
|
|
16972
17581
|
const value = record[key];
|
|
16973
17582
|
if (value !== null && typeof value === "object")
|
|
16974
|
-
nestedHeads.add(key);
|
|
17583
|
+
nestedHeads.add(foldFieldPath(key));
|
|
16975
17584
|
}
|
|
16976
17585
|
};
|
|
16977
17586
|
for (const row of rows) {
|
|
@@ -16984,38 +17593,103 @@ class BlacklistValidator {
|
|
|
16984
17593
|
}
|
|
16985
17594
|
collect(row);
|
|
16986
17595
|
}
|
|
16987
|
-
const protectedPaths = new Set(blacklistedColumns);
|
|
17596
|
+
const protectedPaths = new Set(blacklistedColumns.map(foldFieldPath));
|
|
17597
|
+
const globRules = compileGlobRules(blacklistedColumns, tables[0] ?? "unknown", "SELECT");
|
|
17598
|
+
const presentByFolded = new Map;
|
|
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
|
+
}
|
|
16988
17607
|
const omitted = new Set;
|
|
16989
17608
|
for (const path of blacklistedColumns) {
|
|
16990
|
-
|
|
16991
|
-
|
|
17609
|
+
const present = presentByFolded.get(foldFieldPath(path));
|
|
17610
|
+
if (present !== undefined) {
|
|
17611
|
+
for (const name of present)
|
|
17612
|
+
omitted.add(name);
|
|
16992
17613
|
continue;
|
|
16993
17614
|
}
|
|
16994
17615
|
const dot = path.indexOf(".");
|
|
16995
17616
|
if (dot < 0)
|
|
16996
17617
|
continue;
|
|
16997
|
-
if (!nestedHeads.has(path.slice(0, dot)))
|
|
17618
|
+
if (!nestedHeads.has(foldFieldPath(path.slice(0, dot))))
|
|
16998
17619
|
continue;
|
|
16999
|
-
const segments = path.split(".");
|
|
17000
|
-
if (rows.some((row) => hasFieldPath(row, segments)))
|
|
17620
|
+
const segments = path.split(".").map(foldFieldPath);
|
|
17621
|
+
if (rows.some((row) => hasFieldPath(row, segments, PRE_FOLDED)))
|
|
17001
17622
|
omitted.add(path);
|
|
17002
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
|
+
}
|
|
17003
17640
|
for (const column of presentColumns) {
|
|
17004
17641
|
let dot = column.indexOf(".");
|
|
17005
17642
|
while (dot >= 0) {
|
|
17006
|
-
if (dot > 0 && protectedPaths.has(column.slice(0, dot))) {
|
|
17643
|
+
if (dot > 0 && protectedPaths.has(foldFieldPath(column.slice(0, dot)))) {
|
|
17007
17644
|
omitted.add(column);
|
|
17008
17645
|
break;
|
|
17009
17646
|
}
|
|
17010
17647
|
dot = column.indexOf(".", dot + 1);
|
|
17011
17648
|
}
|
|
17012
17649
|
}
|
|
17650
|
+
if (globRules.length > 0) {
|
|
17651
|
+
for (const column of presentColumns) {
|
|
17652
|
+
const folded = foldFieldPath(column);
|
|
17653
|
+
if (matchAny(folded, globRules)) {
|
|
17654
|
+
omitted.add(column);
|
|
17655
|
+
continue;
|
|
17656
|
+
}
|
|
17657
|
+
let dot = folded.indexOf(".");
|
|
17658
|
+
while (dot >= 0) {
|
|
17659
|
+
if (dot > 0 && matchAny(folded.slice(0, dot), globRules)) {
|
|
17660
|
+
omitted.add(column);
|
|
17661
|
+
break;
|
|
17662
|
+
}
|
|
17663
|
+
dot = folded.indexOf(".", dot + 1);
|
|
17664
|
+
}
|
|
17665
|
+
}
|
|
17666
|
+
}
|
|
17013
17667
|
const omittedColumns = Array.from(omitted);
|
|
17014
17668
|
if (omittedColumns.length === 0) {
|
|
17015
|
-
return { filteredRows: rows, omittedColumns: [] };
|
|
17669
|
+
return { filteredRows: rows, omittedColumns: [], reachesOmitted: () => false };
|
|
17016
17670
|
}
|
|
17017
|
-
const
|
|
17018
|
-
|
|
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
|
+
};
|
|
17019
17693
|
}
|
|
17020
17694
|
buildSecurityNotification(_tableName, omittedColumns) {
|
|
17021
17695
|
if (omittedColumns.length === 0) {
|
|
@@ -17026,11 +17700,17 @@ class BlacklistValidator {
|
|
|
17026
17700
|
});
|
|
17027
17701
|
}
|
|
17028
17702
|
}
|
|
17703
|
+
var FOLD_CASE2, PRE_FOLDED;
|
|
17029
17704
|
var init_blacklist_validator = __esm(() => {
|
|
17705
|
+
init_glob();
|
|
17030
17706
|
init_blacklist();
|
|
17031
17707
|
init_message_loader();
|
|
17032
17708
|
init_field_projection();
|
|
17709
|
+
init_path_matcher();
|
|
17710
|
+
init_blacklist_fold();
|
|
17033
17711
|
init_es_index_target();
|
|
17712
|
+
FOLD_CASE2 = { caseInsensitive: true };
|
|
17713
|
+
PRE_FOLDED = { caseInsensitive: true, alreadyFolded: true };
|
|
17034
17714
|
});
|
|
17035
17715
|
|
|
17036
17716
|
// src/core/verification/types.ts
|
|
@@ -18040,6 +18720,7 @@ var init_core = __esm(() => {
|
|
|
18040
18720
|
init_schema_updater();
|
|
18041
18721
|
init_concurrent_lock();
|
|
18042
18722
|
init_error_recovery();
|
|
18723
|
+
init_blacklist_manager();
|
|
18043
18724
|
init_blacklist_validator();
|
|
18044
18725
|
init_size_category();
|
|
18045
18726
|
init_health_checker();
|
|
@@ -18418,6 +19099,355 @@ var init_sql_lexical = __esm(() => {
|
|
|
18418
19099
|
DOLLAR_QUOTE_DELIMITER = /^\$(?:(?:[A-Za-z_]|[\u0080-\uFFFF])(?:[A-Za-z0-9_]|[\u0080-\uFFFF])*)?\$/;
|
|
18419
19100
|
});
|
|
18420
19101
|
|
|
19102
|
+
// src/utils/sql-tables.ts
|
|
19103
|
+
var exports_sql_tables = {};
|
|
19104
|
+
__export(exports_sql_tables, {
|
|
19105
|
+
extractTableReferences: () => extractTableReferences
|
|
19106
|
+
});
|
|
19107
|
+
function tokenize(sql, dialect, backslashEscapes) {
|
|
19108
|
+
const tokens = [];
|
|
19109
|
+
const mysqlDialect = dialect === "mysql" || dialect === "mariadb";
|
|
19110
|
+
let i = 0;
|
|
19111
|
+
while (i < sql.length) {
|
|
19112
|
+
const char = sql[i];
|
|
19113
|
+
const dashFollowerCode = sql.charCodeAt(i + 2);
|
|
19114
|
+
if (char === "-" && sql[i + 1] === "-" && (!mysqlDialect || sql[i + 2] === undefined || dashFollowerCode <= 32 || dashFollowerCode === 127)) {
|
|
19115
|
+
while (i < sql.length && sql[i] !== `
|
|
19116
|
+
`)
|
|
19117
|
+
i++;
|
|
19118
|
+
continue;
|
|
19119
|
+
}
|
|
19120
|
+
if (mysqlDialect && char === "#") {
|
|
19121
|
+
while (i < sql.length && sql[i] !== `
|
|
19122
|
+
`)
|
|
19123
|
+
i++;
|
|
19124
|
+
continue;
|
|
19125
|
+
}
|
|
19126
|
+
if (char === "/" && sql[i + 1] === "*") {
|
|
19127
|
+
if (mysqlDialect && (sql.startsWith("/*!", i) || sql.startsWith("/*M!", i))) {
|
|
19128
|
+
const prefixLength = sql.startsWith("/*M!", i) ? 4 : 3;
|
|
19129
|
+
const closingIndex = sql.indexOf("*/", i + prefixLength);
|
|
19130
|
+
const bodyEnd = closingIndex === -1 ? sql.length : closingIndex;
|
|
19131
|
+
const body = sql.slice(i + prefixLength, bodyEnd).replace(/^\d+/, " ");
|
|
19132
|
+
tokens.push(...tokenize(body, dialect, backslashEscapes));
|
|
19133
|
+
i = closingIndex === -1 ? sql.length : closingIndex + 2;
|
|
19134
|
+
continue;
|
|
19135
|
+
}
|
|
19136
|
+
const nests = dialect === "postgresql";
|
|
19137
|
+
let depth = 1;
|
|
19138
|
+
i += 2;
|
|
19139
|
+
while (i < sql.length && depth > 0) {
|
|
19140
|
+
if (nests && sql[i] === "/" && sql[i + 1] === "*") {
|
|
19141
|
+
depth++;
|
|
19142
|
+
i += 2;
|
|
19143
|
+
continue;
|
|
19144
|
+
}
|
|
19145
|
+
if (sql[i] === "*" && sql[i + 1] === "/") {
|
|
19146
|
+
depth--;
|
|
19147
|
+
i += 2;
|
|
19148
|
+
continue;
|
|
19149
|
+
}
|
|
19150
|
+
i++;
|
|
19151
|
+
}
|
|
19152
|
+
continue;
|
|
19153
|
+
}
|
|
19154
|
+
if (dialect === "postgresql" && char === "$") {
|
|
19155
|
+
const delimiter = dollarQuoteDelimiterAt(sql, i);
|
|
19156
|
+
if (delimiter) {
|
|
19157
|
+
i += delimiter.length;
|
|
19158
|
+
const closingIndex = sql.indexOf(delimiter, i);
|
|
19159
|
+
i = closingIndex === -1 ? sql.length : closingIndex + delimiter.length;
|
|
19160
|
+
continue;
|
|
19161
|
+
}
|
|
19162
|
+
}
|
|
19163
|
+
if (char === "'") {
|
|
19164
|
+
i++;
|
|
19165
|
+
while (i < sql.length) {
|
|
19166
|
+
if (sql[i] === "'") {
|
|
19167
|
+
if (sql[i + 1] === "'") {
|
|
19168
|
+
i += 2;
|
|
19169
|
+
continue;
|
|
19170
|
+
}
|
|
19171
|
+
i++;
|
|
19172
|
+
break;
|
|
19173
|
+
}
|
|
19174
|
+
if (backslashEscapes && sql[i] === "\\") {
|
|
19175
|
+
i += 2;
|
|
19176
|
+
continue;
|
|
19177
|
+
}
|
|
19178
|
+
i++;
|
|
19179
|
+
}
|
|
19180
|
+
continue;
|
|
19181
|
+
}
|
|
19182
|
+
if (char === '"' || char === "`") {
|
|
19183
|
+
const quote = char;
|
|
19184
|
+
i++;
|
|
19185
|
+
let value = "";
|
|
19186
|
+
while (i < sql.length) {
|
|
19187
|
+
if (sql[i] === quote) {
|
|
19188
|
+
if (sql[i + 1] === quote) {
|
|
19189
|
+
value += quote;
|
|
19190
|
+
i += 2;
|
|
19191
|
+
continue;
|
|
19192
|
+
}
|
|
19193
|
+
i++;
|
|
19194
|
+
break;
|
|
19195
|
+
}
|
|
19196
|
+
if (backslashEscapes && sql[i] === "\\") {
|
|
19197
|
+
value += sql[i + 1] ?? "";
|
|
19198
|
+
i += 2;
|
|
19199
|
+
continue;
|
|
19200
|
+
}
|
|
19201
|
+
value += sql[i];
|
|
19202
|
+
i++;
|
|
19203
|
+
}
|
|
19204
|
+
tokens.push({ value, kind: "identifier", quoted: true });
|
|
19205
|
+
continue;
|
|
19206
|
+
}
|
|
19207
|
+
if (IDENTIFIER_START2.test(char)) {
|
|
19208
|
+
let value = "";
|
|
19209
|
+
while (i < sql.length && IDENTIFIER_PART.test(sql[i])) {
|
|
19210
|
+
value += sql[i];
|
|
19211
|
+
i++;
|
|
19212
|
+
}
|
|
19213
|
+
tokens.push({ value, kind: "identifier", quoted: false });
|
|
19214
|
+
continue;
|
|
19215
|
+
}
|
|
19216
|
+
if (char === "." || char === "," || char === "(" || char === ")" || char === ";") {
|
|
19217
|
+
tokens.push({ value: char, kind: "punctuation", quoted: false });
|
|
19218
|
+
i++;
|
|
19219
|
+
continue;
|
|
19220
|
+
}
|
|
19221
|
+
i++;
|
|
19222
|
+
}
|
|
19223
|
+
return tokens;
|
|
19224
|
+
}
|
|
19225
|
+
function isKeyword(token, keywords) {
|
|
19226
|
+
if (!token || token.kind !== "identifier" || token.quoted)
|
|
19227
|
+
return false;
|
|
19228
|
+
return keywords.has(token.value.toUpperCase());
|
|
19229
|
+
}
|
|
19230
|
+
function isPunctuation(token, value) {
|
|
19231
|
+
return token?.kind === "punctuation" && token.value === value;
|
|
19232
|
+
}
|
|
19233
|
+
function readQualifiedName(tokens, index) {
|
|
19234
|
+
const first = tokens[index];
|
|
19235
|
+
if (!first || first.kind !== "identifier")
|
|
19236
|
+
return null;
|
|
19237
|
+
const parts = [first.value];
|
|
19238
|
+
let cursor = index + 1;
|
|
19239
|
+
while (isPunctuation(tokens[cursor], ".") && tokens[cursor + 1]?.kind === "identifier") {
|
|
19240
|
+
parts.push(tokens[cursor + 1].value);
|
|
19241
|
+
cursor += 2;
|
|
19242
|
+
}
|
|
19243
|
+
return { parts, next: cursor };
|
|
19244
|
+
}
|
|
19245
|
+
function decodedVariants(value) {
|
|
19246
|
+
const decoded = value.replace(ANY_ESCAPE_SEQUENCE, (whole, _escape, long, short) => fromCodePointOrRaw(long ?? short, whole));
|
|
19247
|
+
return decoded === value ? [] : [decoded];
|
|
19248
|
+
}
|
|
19249
|
+
function fromCodePointOrRaw(hex, whole) {
|
|
19250
|
+
const codePoint = parseInt(hex, 16);
|
|
19251
|
+
return codePoint <= 1114111 ? String.fromCodePoint(codePoint) : whole;
|
|
19252
|
+
}
|
|
19253
|
+
function extractTableReferences(sql, options = {}) {
|
|
19254
|
+
const seen = new Set;
|
|
19255
|
+
const references = [];
|
|
19256
|
+
const record = (name) => {
|
|
19257
|
+
const key = name.toLowerCase();
|
|
19258
|
+
if (name.length === 0 || seen.has(key))
|
|
19259
|
+
return;
|
|
19260
|
+
seen.add(key);
|
|
19261
|
+
references.push(name);
|
|
19262
|
+
};
|
|
19263
|
+
const dialects = options.dialect ? [options.dialect] : ["postgresql", "mysql", undefined];
|
|
19264
|
+
for (const dialect of dialects) {
|
|
19265
|
+
for (const backslashEscapes of [false, true]) {
|
|
19266
|
+
collectReferences(tokenize(sql, dialect, backslashEscapes), record);
|
|
19267
|
+
}
|
|
19268
|
+
}
|
|
19269
|
+
return references;
|
|
19270
|
+
}
|
|
19271
|
+
function collectReferences(tokens, record) {
|
|
19272
|
+
const recordName = (parts) => {
|
|
19273
|
+
const bare = parts[parts.length - 1];
|
|
19274
|
+
record(bare);
|
|
19275
|
+
for (const variant of decodedVariants(bare))
|
|
19276
|
+
record(variant);
|
|
19277
|
+
if (parts.length > 1)
|
|
19278
|
+
record(parts.join("."));
|
|
19279
|
+
};
|
|
19280
|
+
let i = 0;
|
|
19281
|
+
while (i < tokens.length) {
|
|
19282
|
+
if (!isKeyword(tokens[i], TABLE_INTRODUCERS)) {
|
|
19283
|
+
i++;
|
|
19284
|
+
continue;
|
|
19285
|
+
}
|
|
19286
|
+
const introducer = tokens[i].value.toUpperCase();
|
|
19287
|
+
const parenMeansFunction = introducer === "FROM" || introducer === "JOIN";
|
|
19288
|
+
let cursor = i + 1;
|
|
19289
|
+
while (isKeyword(tokens[cursor], PRE_TABLE_NOISE))
|
|
19290
|
+
cursor++;
|
|
19291
|
+
while (parenMeansFunction && isPunctuation(tokens[cursor], "(") && !isKeyword(tokens[cursor + 1], SUBQUERY_OPENERS) && tokens[cursor + 1]?.kind === "identifier") {
|
|
19292
|
+
cursor++;
|
|
19293
|
+
}
|
|
19294
|
+
let expectTable = true;
|
|
19295
|
+
while (expectTable) {
|
|
19296
|
+
expectTable = false;
|
|
19297
|
+
const name = readQualifiedName(tokens, cursor);
|
|
19298
|
+
if (!name)
|
|
19299
|
+
break;
|
|
19300
|
+
const isFunctionCall = parenMeansFunction && isPunctuation(tokens[name.next], "(");
|
|
19301
|
+
if (!isFunctionCall)
|
|
19302
|
+
recordName(name.parts);
|
|
19303
|
+
cursor = name.next;
|
|
19304
|
+
if (isFunctionCall)
|
|
19305
|
+
break;
|
|
19306
|
+
if (isKeyword(tokens[cursor], AS_KEYWORD))
|
|
19307
|
+
cursor++;
|
|
19308
|
+
if (tokens[cursor]?.kind === "identifier" && !isKeyword(tokens[cursor], POST_TABLE_KEYWORDS)) {
|
|
19309
|
+
cursor++;
|
|
19310
|
+
}
|
|
19311
|
+
while (isPunctuation(tokens[cursor], "(")) {
|
|
19312
|
+
let depth = 0;
|
|
19313
|
+
do {
|
|
19314
|
+
if (isPunctuation(tokens[cursor], "("))
|
|
19315
|
+
depth++;
|
|
19316
|
+
else if (isPunctuation(tokens[cursor], ")"))
|
|
19317
|
+
depth--;
|
|
19318
|
+
cursor++;
|
|
19319
|
+
} while (depth > 0 && cursor < tokens.length);
|
|
19320
|
+
if (tokens[cursor]?.kind === "identifier" && !isKeyword(tokens[cursor], POST_TABLE_KEYWORDS))
|
|
19321
|
+
cursor++;
|
|
19322
|
+
}
|
|
19323
|
+
if (isPunctuation(tokens[cursor], ",")) {
|
|
19324
|
+
cursor++;
|
|
19325
|
+
expectTable = true;
|
|
19326
|
+
}
|
|
19327
|
+
}
|
|
19328
|
+
i = Math.max(cursor, i + 1);
|
|
19329
|
+
}
|
|
19330
|
+
let index = 0;
|
|
19331
|
+
while (index < tokens.length) {
|
|
19332
|
+
const token = tokens[index];
|
|
19333
|
+
if (!token || token.kind !== "identifier") {
|
|
19334
|
+
index++;
|
|
19335
|
+
continue;
|
|
19336
|
+
}
|
|
19337
|
+
const name = readQualifiedName(tokens, index);
|
|
19338
|
+
for (let part = 0;part < name.parts.length; part++) {
|
|
19339
|
+
const value = name.parts[part];
|
|
19340
|
+
const isQuoted = tokens[index + part * 2]?.quoted === true;
|
|
19341
|
+
if (isQuoted || !RESERVED_KEYWORDS.has(value.toUpperCase())) {
|
|
19342
|
+
record(value);
|
|
19343
|
+
for (const variant of decodedVariants(value))
|
|
19344
|
+
record(variant);
|
|
19345
|
+
}
|
|
19346
|
+
}
|
|
19347
|
+
if (name.parts.length > 1)
|
|
19348
|
+
record(name.parts.join("."));
|
|
19349
|
+
index = name.next;
|
|
19350
|
+
}
|
|
19351
|
+
}
|
|
19352
|
+
var TABLE_INTRODUCERS, PRE_TABLE_NOISE, SUBQUERY_OPENERS, AS_KEYWORD, POST_TABLE_KEYWORDS, RESERVED_KEYWORDS, IDENTIFIER_START2, IDENTIFIER_PART, ANY_ESCAPE_SEQUENCE;
|
|
19353
|
+
var init_sql_tables = __esm(() => {
|
|
19354
|
+
init_sql_lexical();
|
|
19355
|
+
TABLE_INTRODUCERS = new Set([
|
|
19356
|
+
"FROM",
|
|
19357
|
+
"JOIN",
|
|
19358
|
+
"INTO",
|
|
19359
|
+
"UPDATE",
|
|
19360
|
+
"TABLE",
|
|
19361
|
+
"TRUNCATE",
|
|
19362
|
+
"COPY",
|
|
19363
|
+
"USING",
|
|
19364
|
+
"STRAIGHT_JOIN"
|
|
19365
|
+
]);
|
|
19366
|
+
PRE_TABLE_NOISE = new Set(["ONLY", "LATERAL", "TABLE"]);
|
|
19367
|
+
SUBQUERY_OPENERS = new Set(["SELECT", "WITH", "VALUES", "TABLE"]);
|
|
19368
|
+
AS_KEYWORD = new Set(["AS"]);
|
|
19369
|
+
POST_TABLE_KEYWORDS = new Set([
|
|
19370
|
+
"AS",
|
|
19371
|
+
"ON",
|
|
19372
|
+
"USING",
|
|
19373
|
+
"WHERE",
|
|
19374
|
+
"GROUP",
|
|
19375
|
+
"ORDER",
|
|
19376
|
+
"HAVING",
|
|
19377
|
+
"LIMIT",
|
|
19378
|
+
"OFFSET",
|
|
19379
|
+
"FETCH",
|
|
19380
|
+
"WINDOW",
|
|
19381
|
+
"UNION",
|
|
19382
|
+
"INTERSECT",
|
|
19383
|
+
"EXCEPT",
|
|
19384
|
+
"JOIN",
|
|
19385
|
+
"INNER",
|
|
19386
|
+
"LEFT",
|
|
19387
|
+
"RIGHT",
|
|
19388
|
+
"FULL",
|
|
19389
|
+
"OUTER",
|
|
19390
|
+
"CROSS",
|
|
19391
|
+
"NATURAL",
|
|
19392
|
+
"STRAIGHT_JOIN",
|
|
19393
|
+
"SET",
|
|
19394
|
+
"VALUES",
|
|
19395
|
+
"SELECT",
|
|
19396
|
+
"RETURNING",
|
|
19397
|
+
"FOR",
|
|
19398
|
+
"INTO",
|
|
19399
|
+
"PARTITION",
|
|
19400
|
+
"WITH",
|
|
19401
|
+
"TABLESAMPLE",
|
|
19402
|
+
"FORCE",
|
|
19403
|
+
"IGNORE",
|
|
19404
|
+
"USE"
|
|
19405
|
+
]);
|
|
19406
|
+
RESERVED_KEYWORDS = new Set([
|
|
19407
|
+
"ALL",
|
|
19408
|
+
"AND",
|
|
19409
|
+
"AS",
|
|
19410
|
+
"ASC",
|
|
19411
|
+
"CASE",
|
|
19412
|
+
"CROSS",
|
|
19413
|
+
"DESC",
|
|
19414
|
+
"DISTINCT",
|
|
19415
|
+
"ELSE",
|
|
19416
|
+
"FALSE",
|
|
19417
|
+
"FOR",
|
|
19418
|
+
"FROM",
|
|
19419
|
+
"GROUP",
|
|
19420
|
+
"HAVING",
|
|
19421
|
+
"IN",
|
|
19422
|
+
"INNER",
|
|
19423
|
+
"INTO",
|
|
19424
|
+
"IS",
|
|
19425
|
+
"JOIN",
|
|
19426
|
+
"LEFT",
|
|
19427
|
+
"LIKE",
|
|
19428
|
+
"LIMIT",
|
|
19429
|
+
"NOT",
|
|
19430
|
+
"NULL",
|
|
19431
|
+
"ON",
|
|
19432
|
+
"OR",
|
|
19433
|
+
"ORDER",
|
|
19434
|
+
"OUTER",
|
|
19435
|
+
"RIGHT",
|
|
19436
|
+
"SELECT",
|
|
19437
|
+
"THEN",
|
|
19438
|
+
"TRUE",
|
|
19439
|
+
"UNION",
|
|
19440
|
+
"USING",
|
|
19441
|
+
"VALUES",
|
|
19442
|
+
"WHEN",
|
|
19443
|
+
"WHERE",
|
|
19444
|
+
"WITH"
|
|
19445
|
+
]);
|
|
19446
|
+
IDENTIFIER_START2 = /[A-Za-z_\u0080-\uFFFF]/;
|
|
19447
|
+
IDENTIFIER_PART = /[A-Za-z0-9_$\u0080-\uFFFF]/;
|
|
19448
|
+
ANY_ESCAPE_SEQUENCE = /([^0-9a-fA-F+'"\s])(?:\+([0-9a-fA-F]{6})|([0-9a-fA-F]{4}))/g;
|
|
19449
|
+
});
|
|
19450
|
+
|
|
18421
19451
|
// src/core/permission/sql-analysis.ts
|
|
18422
19452
|
function normalizeSQL(sql) {
|
|
18423
19453
|
return sql.replace(/--[^\n]*\n/g, `
|
|
@@ -19010,6 +20040,9 @@ var init_engine_hints = __esm(() => {
|
|
|
19010
20040
|
});
|
|
19011
20041
|
|
|
19012
20042
|
// src/core/audit/integration-helper.ts
|
|
20043
|
+
function sqlDialectFor(engine) {
|
|
20044
|
+
return SQL_AUDIT_DIALECTS.includes(engine) ? engine : null;
|
|
20045
|
+
}
|
|
19013
20046
|
async function getAuditLogger(config, configPath, connectionName) {
|
|
19014
20047
|
const storagePath = await resolveConfigStoragePath(configPath);
|
|
19015
20048
|
const connName = connectionName || config.effectiveConnectionName || getGlobalConnectionName() || "default";
|
|
@@ -19051,6 +20084,8 @@ async function writeAuditEntryResult(config, commandName, options, outcome) {
|
|
|
19051
20084
|
const logger = await getAuditLogger(config, options.config || ".dbcli", connectionName);
|
|
19052
20085
|
const engine = config.connection?.system || "postgresql";
|
|
19053
20086
|
const target = outcome.target || getOperationTarget(engine, commandName, options, outcome.sql);
|
|
20087
|
+
const auditDialect = sqlDialectFor(engine);
|
|
20088
|
+
const blacklistChecked = auditDialect ? extractTableReferences(outcome.sql ?? "", { dialect: auditDialect }) : [];
|
|
19054
20089
|
let tier = outcome.sideEffectTier ?? getEngineCapability(engine, commandName).tier;
|
|
19055
20090
|
if (options.dryRun || options.plan) {
|
|
19056
20091
|
tier = "dry-run";
|
|
@@ -19075,6 +20110,7 @@ async function writeAuditEntryResult(config, commandName, options, outcome) {
|
|
|
19075
20110
|
...outcome.recovery_ref && { recovery_ref: outcome.recovery_ref },
|
|
19076
20111
|
metadata: {
|
|
19077
20112
|
...outcome.metadata ?? {},
|
|
20113
|
+
...blacklistChecked.length > 0 && { blacklist_checked: blacklistChecked },
|
|
19078
20114
|
connection_name: connectionName,
|
|
19079
20115
|
environment: config.effectiveEnvironment ?? null
|
|
19080
20116
|
}
|
|
@@ -19087,15 +20123,17 @@ async function writeAuditEntryResult(config, commandName, options, outcome) {
|
|
|
19087
20123
|
};
|
|
19088
20124
|
}
|
|
19089
20125
|
}
|
|
19090
|
-
var _sessionIdService = null, _loggers, AuditRequiredError;
|
|
20126
|
+
var SQL_AUDIT_DIALECTS, _sessionIdService = null, _loggers, AuditRequiredError;
|
|
19091
20127
|
var init_integration_helper = __esm(() => {
|
|
19092
20128
|
init_logger2();
|
|
19093
20129
|
init_session_id();
|
|
19094
20130
|
init_config_binding();
|
|
19095
20131
|
init_config();
|
|
19096
20132
|
init_capabilities();
|
|
20133
|
+
init_sql_tables();
|
|
19097
20134
|
init_redaction();
|
|
19098
20135
|
init_engine_hints();
|
|
20136
|
+
SQL_AUDIT_DIALECTS = ["postgresql", "mysql", "mariadb"];
|
|
19099
20137
|
_loggers = new Map;
|
|
19100
20138
|
AuditRequiredError = class AuditRequiredError extends Error {
|
|
19101
20139
|
constructor(detail) {
|
|
@@ -19170,73 +20208,6 @@ async function getAllTablesFromAdapter(adapter) {
|
|
|
19170
20208
|
}
|
|
19171
20209
|
var init_error_suggester = () => {};
|
|
19172
20210
|
|
|
19173
|
-
// src/core/mongo/path-matcher.ts
|
|
19174
|
-
function compilePatterns(raw) {
|
|
19175
|
-
const patterns = [];
|
|
19176
|
-
const rejected = [];
|
|
19177
|
-
for (const entry of raw) {
|
|
19178
|
-
if (typeof entry !== "string" || entry.length === 0) {
|
|
19179
|
-
rejected.push({ raw: String(entry ?? ""), reason: "must be a non-empty string" });
|
|
19180
|
-
continue;
|
|
19181
|
-
}
|
|
19182
|
-
const segments = entry.split(".");
|
|
19183
|
-
if (segments.length === 0 || segments.some((s) => s.length === 0)) {
|
|
19184
|
-
rejected.push({ raw: entry, reason: "empty path segment" });
|
|
19185
|
-
continue;
|
|
19186
|
-
}
|
|
19187
|
-
const wildcardIndices = segments.map((s, i) => s.includes("*") ? i : -1).filter((i) => i >= 0);
|
|
19188
|
-
if (wildcardIndices.length === 0) {
|
|
19189
|
-
patterns.push({ raw: entry, segments, wildcardTail: false });
|
|
19190
|
-
continue;
|
|
19191
|
-
}
|
|
19192
|
-
const lastIndex = segments.length - 1;
|
|
19193
|
-
const onlyTail = wildcardIndices.length === 1 && wildcardIndices[0] === lastIndex && segments[lastIndex] === "*";
|
|
19194
|
-
if (!onlyTail) {
|
|
19195
|
-
rejected.push({ raw: entry, reason: "wildcard must be the final segment" });
|
|
19196
|
-
continue;
|
|
19197
|
-
}
|
|
19198
|
-
if (segments.length === 1) {
|
|
19199
|
-
rejected.push({ raw: entry, reason: "wildcard must have a parent path" });
|
|
19200
|
-
continue;
|
|
19201
|
-
}
|
|
19202
|
-
patterns.push({ raw: entry, segments: segments.slice(0, -1), wildcardTail: true });
|
|
19203
|
-
}
|
|
19204
|
-
return { patterns, rejected };
|
|
19205
|
-
}
|
|
19206
|
-
function matchAny(path, patterns) {
|
|
19207
|
-
if (patterns.length === 0)
|
|
19208
|
-
return false;
|
|
19209
|
-
const pathSegments = path.split(".");
|
|
19210
|
-
for (const pat of patterns) {
|
|
19211
|
-
if (pat.wildcardTail) {
|
|
19212
|
-
if (pathSegments.length < pat.segments.length)
|
|
19213
|
-
continue;
|
|
19214
|
-
let ok = true;
|
|
19215
|
-
for (let i = 0;i < pat.segments.length; i++) {
|
|
19216
|
-
if (pat.segments[i] !== pathSegments[i]) {
|
|
19217
|
-
ok = false;
|
|
19218
|
-
break;
|
|
19219
|
-
}
|
|
19220
|
-
}
|
|
19221
|
-
if (ok)
|
|
19222
|
-
return true;
|
|
19223
|
-
} else {
|
|
19224
|
-
if (pat.segments.length !== pathSegments.length)
|
|
19225
|
-
continue;
|
|
19226
|
-
let ok = true;
|
|
19227
|
-
for (let i = 0;i < pat.segments.length; i++) {
|
|
19228
|
-
if (pat.segments[i] !== pathSegments[i]) {
|
|
19229
|
-
ok = false;
|
|
19230
|
-
break;
|
|
19231
|
-
}
|
|
19232
|
-
}
|
|
19233
|
-
if (ok)
|
|
19234
|
-
return true;
|
|
19235
|
-
}
|
|
19236
|
-
}
|
|
19237
|
-
return false;
|
|
19238
|
-
}
|
|
19239
|
-
|
|
19240
20211
|
// src/utils/bounded-parallel.ts
|
|
19241
20212
|
async function mapWithConcurrency(items, limit, worker) {
|
|
19242
20213
|
if (items.length === 0)
|
|
@@ -21322,7 +22293,11 @@ function markRedactedColumns(cols, collection, blacklist) {
|
|
|
21322
22293
|
const raw = (blacklist.columns ?? {})[collection];
|
|
21323
22294
|
if (!raw || raw.length === 0)
|
|
21324
22295
|
return cols;
|
|
21325
|
-
const { patterns } = compilePatterns(raw);
|
|
22296
|
+
const { patterns, rejected } = compilePatterns(raw);
|
|
22297
|
+
if (rejected.length > 0) {
|
|
22298
|
+
const detail = rejected.map((r) => `'${r.raw}' (${r.reason})`).join(", ");
|
|
22299
|
+
throw new Error(`blacklist.columns for '${collection}' has entries this matcher cannot read: ${detail}`);
|
|
22300
|
+
}
|
|
21326
22301
|
if (patterns.length === 0)
|
|
21327
22302
|
return cols;
|
|
21328
22303
|
return cols.map((c) => matchAny(c.name, patterns) ? { ...c, redacted: true } : c);
|
|
@@ -21452,7 +22427,7 @@ Key: ${schema.name}`);
|
|
|
21452
22427
|
await esAdapter.disconnect();
|
|
21453
22428
|
}
|
|
21454
22429
|
}
|
|
21455
|
-
const adapter = AdapterFactory.createAdapter(config
|
|
22430
|
+
const adapter = AdapterFactory.createAdapter(config);
|
|
21456
22431
|
await adapter.connect();
|
|
21457
22432
|
const mongoMeta = config.connection.system === "mongodb" ? {
|
|
21458
22433
|
blacklist: config.blacklist ?? { tables: [], columns: {} },
|
|
@@ -21770,6 +22745,7 @@ var init_schema = __esm(() => {
|
|
|
21770
22745
|
init_validation();
|
|
21771
22746
|
init_connection_selector();
|
|
21772
22747
|
init_error_suggester();
|
|
22748
|
+
init_path_matcher();
|
|
21773
22749
|
init_config_path();
|
|
21774
22750
|
ALLOWED_FORMATS2 = ["table", "json"];
|
|
21775
22751
|
schemaCommand = new Command().name("schema").description("Display table schema, scan database schema, or refresh existing schema with detected changes").argument("[table]", "Optional: table name to inspect (if omitted, scans all tables)").option("--format <format>", "Output format: table (default) or json", "table").option("--config <path>", "Path to .dbcli config file", ".dbcli").addOption(createConnectionSelectorOption()).option("--refresh", "Refresh schema by detecting changes from database", false).option("--reset", "Clear all existing schema data and re-fetch from database", false).option("--force", "Skip confirmation when updating schema data", false).option("--sample-size <n>", "MongoDB only: number of documents to sample for schema inference (default 100, max 1000). Ignored on SQL connections.").option("--sample-method <method>", 'MongoDB only: "random" (default, uses $sample) or "natural" (uses find().limit()). Ignored on SQL connections.', "random").option("--recovery", "On failure, emit a structured recovery envelope to stdout (suppresses human stderr message)", false).action(schemaAction);
|
|
@@ -21874,355 +22850,6 @@ async function openInBrowser(target) {
|
|
|
21874
22850
|
}
|
|
21875
22851
|
var init_opener = () => {};
|
|
21876
22852
|
|
|
21877
|
-
// src/utils/sql-tables.ts
|
|
21878
|
-
var exports_sql_tables = {};
|
|
21879
|
-
__export(exports_sql_tables, {
|
|
21880
|
-
extractTableReferences: () => extractTableReferences
|
|
21881
|
-
});
|
|
21882
|
-
function tokenize(sql, dialect, backslashEscapes) {
|
|
21883
|
-
const tokens = [];
|
|
21884
|
-
const mysqlDialect = dialect === "mysql" || dialect === "mariadb";
|
|
21885
|
-
let i = 0;
|
|
21886
|
-
while (i < sql.length) {
|
|
21887
|
-
const char = sql[i];
|
|
21888
|
-
const dashFollowerCode = sql.charCodeAt(i + 2);
|
|
21889
|
-
if (char === "-" && sql[i + 1] === "-" && (!mysqlDialect || sql[i + 2] === undefined || dashFollowerCode <= 32 || dashFollowerCode === 127)) {
|
|
21890
|
-
while (i < sql.length && sql[i] !== `
|
|
21891
|
-
`)
|
|
21892
|
-
i++;
|
|
21893
|
-
continue;
|
|
21894
|
-
}
|
|
21895
|
-
if (mysqlDialect && char === "#") {
|
|
21896
|
-
while (i < sql.length && sql[i] !== `
|
|
21897
|
-
`)
|
|
21898
|
-
i++;
|
|
21899
|
-
continue;
|
|
21900
|
-
}
|
|
21901
|
-
if (char === "/" && sql[i + 1] === "*") {
|
|
21902
|
-
if (mysqlDialect && (sql.startsWith("/*!", i) || sql.startsWith("/*M!", i))) {
|
|
21903
|
-
const prefixLength = sql.startsWith("/*M!", i) ? 4 : 3;
|
|
21904
|
-
const closingIndex = sql.indexOf("*/", i + prefixLength);
|
|
21905
|
-
const bodyEnd = closingIndex === -1 ? sql.length : closingIndex;
|
|
21906
|
-
const body = sql.slice(i + prefixLength, bodyEnd).replace(/^\d+/, " ");
|
|
21907
|
-
tokens.push(...tokenize(body, dialect, backslashEscapes));
|
|
21908
|
-
i = closingIndex === -1 ? sql.length : closingIndex + 2;
|
|
21909
|
-
continue;
|
|
21910
|
-
}
|
|
21911
|
-
const nests = dialect === "postgresql";
|
|
21912
|
-
let depth = 1;
|
|
21913
|
-
i += 2;
|
|
21914
|
-
while (i < sql.length && depth > 0) {
|
|
21915
|
-
if (nests && sql[i] === "/" && sql[i + 1] === "*") {
|
|
21916
|
-
depth++;
|
|
21917
|
-
i += 2;
|
|
21918
|
-
continue;
|
|
21919
|
-
}
|
|
21920
|
-
if (sql[i] === "*" && sql[i + 1] === "/") {
|
|
21921
|
-
depth--;
|
|
21922
|
-
i += 2;
|
|
21923
|
-
continue;
|
|
21924
|
-
}
|
|
21925
|
-
i++;
|
|
21926
|
-
}
|
|
21927
|
-
continue;
|
|
21928
|
-
}
|
|
21929
|
-
if (dialect === "postgresql" && char === "$") {
|
|
21930
|
-
const delimiter = dollarQuoteDelimiterAt(sql, i);
|
|
21931
|
-
if (delimiter) {
|
|
21932
|
-
i += delimiter.length;
|
|
21933
|
-
const closingIndex = sql.indexOf(delimiter, i);
|
|
21934
|
-
i = closingIndex === -1 ? sql.length : closingIndex + delimiter.length;
|
|
21935
|
-
continue;
|
|
21936
|
-
}
|
|
21937
|
-
}
|
|
21938
|
-
if (char === "'") {
|
|
21939
|
-
i++;
|
|
21940
|
-
while (i < sql.length) {
|
|
21941
|
-
if (sql[i] === "'") {
|
|
21942
|
-
if (sql[i + 1] === "'") {
|
|
21943
|
-
i += 2;
|
|
21944
|
-
continue;
|
|
21945
|
-
}
|
|
21946
|
-
i++;
|
|
21947
|
-
break;
|
|
21948
|
-
}
|
|
21949
|
-
if (backslashEscapes && sql[i] === "\\") {
|
|
21950
|
-
i += 2;
|
|
21951
|
-
continue;
|
|
21952
|
-
}
|
|
21953
|
-
i++;
|
|
21954
|
-
}
|
|
21955
|
-
continue;
|
|
21956
|
-
}
|
|
21957
|
-
if (char === '"' || char === "`") {
|
|
21958
|
-
const quote = char;
|
|
21959
|
-
i++;
|
|
21960
|
-
let value = "";
|
|
21961
|
-
while (i < sql.length) {
|
|
21962
|
-
if (sql[i] === quote) {
|
|
21963
|
-
if (sql[i + 1] === quote) {
|
|
21964
|
-
value += quote;
|
|
21965
|
-
i += 2;
|
|
21966
|
-
continue;
|
|
21967
|
-
}
|
|
21968
|
-
i++;
|
|
21969
|
-
break;
|
|
21970
|
-
}
|
|
21971
|
-
if (backslashEscapes && sql[i] === "\\") {
|
|
21972
|
-
value += sql[i + 1] ?? "";
|
|
21973
|
-
i += 2;
|
|
21974
|
-
continue;
|
|
21975
|
-
}
|
|
21976
|
-
value += sql[i];
|
|
21977
|
-
i++;
|
|
21978
|
-
}
|
|
21979
|
-
tokens.push({ value, kind: "identifier", quoted: true });
|
|
21980
|
-
continue;
|
|
21981
|
-
}
|
|
21982
|
-
if (IDENTIFIER_START2.test(char)) {
|
|
21983
|
-
let value = "";
|
|
21984
|
-
while (i < sql.length && IDENTIFIER_PART.test(sql[i])) {
|
|
21985
|
-
value += sql[i];
|
|
21986
|
-
i++;
|
|
21987
|
-
}
|
|
21988
|
-
tokens.push({ value, kind: "identifier", quoted: false });
|
|
21989
|
-
continue;
|
|
21990
|
-
}
|
|
21991
|
-
if (char === "." || char === "," || char === "(" || char === ")" || char === ";") {
|
|
21992
|
-
tokens.push({ value: char, kind: "punctuation", quoted: false });
|
|
21993
|
-
i++;
|
|
21994
|
-
continue;
|
|
21995
|
-
}
|
|
21996
|
-
i++;
|
|
21997
|
-
}
|
|
21998
|
-
return tokens;
|
|
21999
|
-
}
|
|
22000
|
-
function isKeyword(token, keywords) {
|
|
22001
|
-
if (!token || token.kind !== "identifier" || token.quoted)
|
|
22002
|
-
return false;
|
|
22003
|
-
return keywords.has(token.value.toUpperCase());
|
|
22004
|
-
}
|
|
22005
|
-
function isPunctuation(token, value) {
|
|
22006
|
-
return token?.kind === "punctuation" && token.value === value;
|
|
22007
|
-
}
|
|
22008
|
-
function readQualifiedName(tokens, index) {
|
|
22009
|
-
const first = tokens[index];
|
|
22010
|
-
if (!first || first.kind !== "identifier")
|
|
22011
|
-
return null;
|
|
22012
|
-
const parts = [first.value];
|
|
22013
|
-
let cursor = index + 1;
|
|
22014
|
-
while (isPunctuation(tokens[cursor], ".") && tokens[cursor + 1]?.kind === "identifier") {
|
|
22015
|
-
parts.push(tokens[cursor + 1].value);
|
|
22016
|
-
cursor += 2;
|
|
22017
|
-
}
|
|
22018
|
-
return { parts, next: cursor };
|
|
22019
|
-
}
|
|
22020
|
-
function decodedVariants(value) {
|
|
22021
|
-
const decoded = value.replace(ANY_ESCAPE_SEQUENCE, (whole, _escape, long, short) => fromCodePointOrRaw(long ?? short, whole));
|
|
22022
|
-
return decoded === value ? [] : [decoded];
|
|
22023
|
-
}
|
|
22024
|
-
function fromCodePointOrRaw(hex, whole) {
|
|
22025
|
-
const codePoint = parseInt(hex, 16);
|
|
22026
|
-
return codePoint <= 1114111 ? String.fromCodePoint(codePoint) : whole;
|
|
22027
|
-
}
|
|
22028
|
-
function extractTableReferences(sql, options = {}) {
|
|
22029
|
-
const seen = new Set;
|
|
22030
|
-
const references = [];
|
|
22031
|
-
const record = (name) => {
|
|
22032
|
-
const key = name.toLowerCase();
|
|
22033
|
-
if (name.length === 0 || seen.has(key))
|
|
22034
|
-
return;
|
|
22035
|
-
seen.add(key);
|
|
22036
|
-
references.push(name);
|
|
22037
|
-
};
|
|
22038
|
-
const dialects = options.dialect ? [options.dialect] : ["postgresql", "mysql", undefined];
|
|
22039
|
-
for (const dialect of dialects) {
|
|
22040
|
-
for (const backslashEscapes of [false, true]) {
|
|
22041
|
-
collectReferences(tokenize(sql, dialect, backslashEscapes), record);
|
|
22042
|
-
}
|
|
22043
|
-
}
|
|
22044
|
-
return references;
|
|
22045
|
-
}
|
|
22046
|
-
function collectReferences(tokens, record) {
|
|
22047
|
-
const recordName = (parts) => {
|
|
22048
|
-
const bare = parts[parts.length - 1];
|
|
22049
|
-
record(bare);
|
|
22050
|
-
for (const variant of decodedVariants(bare))
|
|
22051
|
-
record(variant);
|
|
22052
|
-
if (parts.length > 1)
|
|
22053
|
-
record(parts.join("."));
|
|
22054
|
-
};
|
|
22055
|
-
let i = 0;
|
|
22056
|
-
while (i < tokens.length) {
|
|
22057
|
-
if (!isKeyword(tokens[i], TABLE_INTRODUCERS)) {
|
|
22058
|
-
i++;
|
|
22059
|
-
continue;
|
|
22060
|
-
}
|
|
22061
|
-
const introducer = tokens[i].value.toUpperCase();
|
|
22062
|
-
const parenMeansFunction = introducer === "FROM" || introducer === "JOIN";
|
|
22063
|
-
let cursor = i + 1;
|
|
22064
|
-
while (isKeyword(tokens[cursor], PRE_TABLE_NOISE))
|
|
22065
|
-
cursor++;
|
|
22066
|
-
while (parenMeansFunction && isPunctuation(tokens[cursor], "(") && !isKeyword(tokens[cursor + 1], SUBQUERY_OPENERS) && tokens[cursor + 1]?.kind === "identifier") {
|
|
22067
|
-
cursor++;
|
|
22068
|
-
}
|
|
22069
|
-
let expectTable = true;
|
|
22070
|
-
while (expectTable) {
|
|
22071
|
-
expectTable = false;
|
|
22072
|
-
const name = readQualifiedName(tokens, cursor);
|
|
22073
|
-
if (!name)
|
|
22074
|
-
break;
|
|
22075
|
-
const isFunctionCall = parenMeansFunction && isPunctuation(tokens[name.next], "(");
|
|
22076
|
-
if (!isFunctionCall)
|
|
22077
|
-
recordName(name.parts);
|
|
22078
|
-
cursor = name.next;
|
|
22079
|
-
if (isFunctionCall)
|
|
22080
|
-
break;
|
|
22081
|
-
if (isKeyword(tokens[cursor], AS_KEYWORD))
|
|
22082
|
-
cursor++;
|
|
22083
|
-
if (tokens[cursor]?.kind === "identifier" && !isKeyword(tokens[cursor], POST_TABLE_KEYWORDS)) {
|
|
22084
|
-
cursor++;
|
|
22085
|
-
}
|
|
22086
|
-
while (isPunctuation(tokens[cursor], "(")) {
|
|
22087
|
-
let depth = 0;
|
|
22088
|
-
do {
|
|
22089
|
-
if (isPunctuation(tokens[cursor], "("))
|
|
22090
|
-
depth++;
|
|
22091
|
-
else if (isPunctuation(tokens[cursor], ")"))
|
|
22092
|
-
depth--;
|
|
22093
|
-
cursor++;
|
|
22094
|
-
} while (depth > 0 && cursor < tokens.length);
|
|
22095
|
-
if (tokens[cursor]?.kind === "identifier" && !isKeyword(tokens[cursor], POST_TABLE_KEYWORDS))
|
|
22096
|
-
cursor++;
|
|
22097
|
-
}
|
|
22098
|
-
if (isPunctuation(tokens[cursor], ",")) {
|
|
22099
|
-
cursor++;
|
|
22100
|
-
expectTable = true;
|
|
22101
|
-
}
|
|
22102
|
-
}
|
|
22103
|
-
i = Math.max(cursor, i + 1);
|
|
22104
|
-
}
|
|
22105
|
-
let index = 0;
|
|
22106
|
-
while (index < tokens.length) {
|
|
22107
|
-
const token = tokens[index];
|
|
22108
|
-
if (!token || token.kind !== "identifier") {
|
|
22109
|
-
index++;
|
|
22110
|
-
continue;
|
|
22111
|
-
}
|
|
22112
|
-
const name = readQualifiedName(tokens, index);
|
|
22113
|
-
for (let part = 0;part < name.parts.length; part++) {
|
|
22114
|
-
const value = name.parts[part];
|
|
22115
|
-
const isQuoted = tokens[index + part * 2]?.quoted === true;
|
|
22116
|
-
if (isQuoted || !RESERVED_KEYWORDS.has(value.toUpperCase())) {
|
|
22117
|
-
record(value);
|
|
22118
|
-
for (const variant of decodedVariants(value))
|
|
22119
|
-
record(variant);
|
|
22120
|
-
}
|
|
22121
|
-
}
|
|
22122
|
-
if (name.parts.length > 1)
|
|
22123
|
-
record(name.parts.join("."));
|
|
22124
|
-
index = name.next;
|
|
22125
|
-
}
|
|
22126
|
-
}
|
|
22127
|
-
var TABLE_INTRODUCERS, PRE_TABLE_NOISE, SUBQUERY_OPENERS, AS_KEYWORD, POST_TABLE_KEYWORDS, RESERVED_KEYWORDS, IDENTIFIER_START2, IDENTIFIER_PART, ANY_ESCAPE_SEQUENCE;
|
|
22128
|
-
var init_sql_tables = __esm(() => {
|
|
22129
|
-
init_sql_lexical();
|
|
22130
|
-
TABLE_INTRODUCERS = new Set([
|
|
22131
|
-
"FROM",
|
|
22132
|
-
"JOIN",
|
|
22133
|
-
"INTO",
|
|
22134
|
-
"UPDATE",
|
|
22135
|
-
"TABLE",
|
|
22136
|
-
"TRUNCATE",
|
|
22137
|
-
"COPY",
|
|
22138
|
-
"USING",
|
|
22139
|
-
"STRAIGHT_JOIN"
|
|
22140
|
-
]);
|
|
22141
|
-
PRE_TABLE_NOISE = new Set(["ONLY", "LATERAL", "TABLE"]);
|
|
22142
|
-
SUBQUERY_OPENERS = new Set(["SELECT", "WITH", "VALUES", "TABLE"]);
|
|
22143
|
-
AS_KEYWORD = new Set(["AS"]);
|
|
22144
|
-
POST_TABLE_KEYWORDS = new Set([
|
|
22145
|
-
"AS",
|
|
22146
|
-
"ON",
|
|
22147
|
-
"USING",
|
|
22148
|
-
"WHERE",
|
|
22149
|
-
"GROUP",
|
|
22150
|
-
"ORDER",
|
|
22151
|
-
"HAVING",
|
|
22152
|
-
"LIMIT",
|
|
22153
|
-
"OFFSET",
|
|
22154
|
-
"FETCH",
|
|
22155
|
-
"WINDOW",
|
|
22156
|
-
"UNION",
|
|
22157
|
-
"INTERSECT",
|
|
22158
|
-
"EXCEPT",
|
|
22159
|
-
"JOIN",
|
|
22160
|
-
"INNER",
|
|
22161
|
-
"LEFT",
|
|
22162
|
-
"RIGHT",
|
|
22163
|
-
"FULL",
|
|
22164
|
-
"OUTER",
|
|
22165
|
-
"CROSS",
|
|
22166
|
-
"NATURAL",
|
|
22167
|
-
"STRAIGHT_JOIN",
|
|
22168
|
-
"SET",
|
|
22169
|
-
"VALUES",
|
|
22170
|
-
"SELECT",
|
|
22171
|
-
"RETURNING",
|
|
22172
|
-
"FOR",
|
|
22173
|
-
"INTO",
|
|
22174
|
-
"PARTITION",
|
|
22175
|
-
"WITH",
|
|
22176
|
-
"TABLESAMPLE",
|
|
22177
|
-
"FORCE",
|
|
22178
|
-
"IGNORE",
|
|
22179
|
-
"USE"
|
|
22180
|
-
]);
|
|
22181
|
-
RESERVED_KEYWORDS = new Set([
|
|
22182
|
-
"ALL",
|
|
22183
|
-
"AND",
|
|
22184
|
-
"AS",
|
|
22185
|
-
"ASC",
|
|
22186
|
-
"CASE",
|
|
22187
|
-
"CROSS",
|
|
22188
|
-
"DESC",
|
|
22189
|
-
"DISTINCT",
|
|
22190
|
-
"ELSE",
|
|
22191
|
-
"FALSE",
|
|
22192
|
-
"FOR",
|
|
22193
|
-
"FROM",
|
|
22194
|
-
"GROUP",
|
|
22195
|
-
"HAVING",
|
|
22196
|
-
"IN",
|
|
22197
|
-
"INNER",
|
|
22198
|
-
"INTO",
|
|
22199
|
-
"IS",
|
|
22200
|
-
"JOIN",
|
|
22201
|
-
"LEFT",
|
|
22202
|
-
"LIKE",
|
|
22203
|
-
"LIMIT",
|
|
22204
|
-
"NOT",
|
|
22205
|
-
"NULL",
|
|
22206
|
-
"ON",
|
|
22207
|
-
"OR",
|
|
22208
|
-
"ORDER",
|
|
22209
|
-
"OUTER",
|
|
22210
|
-
"RIGHT",
|
|
22211
|
-
"SELECT",
|
|
22212
|
-
"THEN",
|
|
22213
|
-
"TRUE",
|
|
22214
|
-
"UNION",
|
|
22215
|
-
"USING",
|
|
22216
|
-
"VALUES",
|
|
22217
|
-
"WHEN",
|
|
22218
|
-
"WHERE",
|
|
22219
|
-
"WITH"
|
|
22220
|
-
]);
|
|
22221
|
-
IDENTIFIER_START2 = /[A-Za-z_\u0080-\uFFFF]/;
|
|
22222
|
-
IDENTIFIER_PART = /[A-Za-z0-9_$\u0080-\uFFFF]/;
|
|
22223
|
-
ANY_ESCAPE_SEQUENCE = /([^0-9a-fA-F+'"\s])(?:\+([0-9a-fA-F]{6})|([0-9a-fA-F]{4}))/g;
|
|
22224
|
-
});
|
|
22225
|
-
|
|
22226
22853
|
// src/core/limits.ts
|
|
22227
22854
|
var DEFAULT_QUERY_ONLY_LIMIT = 1000;
|
|
22228
22855
|
|
|
@@ -22289,12 +22916,12 @@ class QueryExecutor {
|
|
|
22289
22916
|
let columnNames = rows.length > 0 && rows[0] ? Object.keys(rows[0]) : [];
|
|
22290
22917
|
let filteredRows = rows;
|
|
22291
22918
|
let securityNotification;
|
|
22292
|
-
let
|
|
22919
|
+
let reachesOmitted = () => false;
|
|
22293
22920
|
if (this.blacklistValidator) {
|
|
22294
22921
|
const filterResult = this.blacklistValidator.filterColumnsForTables(referencedTables, rows, columnNames);
|
|
22295
22922
|
filteredRows = filterResult.filteredRows;
|
|
22296
22923
|
if (filterResult.omittedColumns.length > 0) {
|
|
22297
|
-
|
|
22924
|
+
reachesOmitted = filterResult.reachesOmitted;
|
|
22298
22925
|
columnNames = columnNames.filter((col) => !filterResult.omittedColumns.includes(col));
|
|
22299
22926
|
securityNotification = this.blacklistValidator.buildSecurityNotification(referencedTables[0] ?? "", filterResult.omittedColumns);
|
|
22300
22927
|
}
|
|
@@ -22302,7 +22929,7 @@ class QueryExecutor {
|
|
|
22302
22929
|
if (options?.fieldSelection) {
|
|
22303
22930
|
const fieldSelection = options.fieldSelection.mode === "include" ? {
|
|
22304
22931
|
mode: "include",
|
|
22305
|
-
paths: options.fieldSelection.paths.filter((path2) => !
|
|
22932
|
+
paths: options.fieldSelection.paths.filter((path2) => !reachesOmitted(path2))
|
|
22306
22933
|
} : options.fieldSelection;
|
|
22307
22934
|
const projection = projectRows(filteredRows, fieldSelection);
|
|
22308
22935
|
filteredRows = projection.rows;
|
|
@@ -22402,7 +23029,10 @@ function maskMongoRowsForCollections(rows, collections, blacklist) {
|
|
|
22402
23029
|
const prefixKey = `\x00${scope.collection}@${scope.prefix}`;
|
|
22403
23030
|
return maskMongoRows(atTopLevel, prefixKey, {
|
|
22404
23031
|
...blacklist,
|
|
22405
|
-
columns: {
|
|
23032
|
+
columns: {
|
|
23033
|
+
...columns,
|
|
23034
|
+
[prefixKey]: rules.map((rule) => `${escapeGlob(scope.prefix)}.${rule}`)
|
|
23035
|
+
}
|
|
22406
23036
|
});
|
|
22407
23037
|
}, rows);
|
|
22408
23038
|
}
|
|
@@ -22411,10 +23041,14 @@ function maskMongoRows(rows, collection, blacklist) {
|
|
|
22411
23041
|
const raw = columns[collection] ?? findCaseInsensitive(columns, collection);
|
|
22412
23042
|
if (!raw || raw.length === 0)
|
|
22413
23043
|
return rows;
|
|
22414
|
-
const { patterns } = compilePatterns(raw);
|
|
23044
|
+
const { patterns, rejected } = compilePatterns(raw);
|
|
23045
|
+
if (rejected.length > 0) {
|
|
23046
|
+
const detail = rejected.map((r) => `'${r.raw}' (${r.reason})`).join(", ");
|
|
23047
|
+
throw new BlacklistError(`blacklist.columns for '${collection}' has entries this matcher cannot read: ${detail}`, collection, "READ");
|
|
23048
|
+
}
|
|
22415
23049
|
if (patterns.length === 0)
|
|
22416
23050
|
return rows;
|
|
22417
|
-
const idAffected = patterns.some((p) => p.segments.length === 1 && p.segments[0]
|
|
23051
|
+
const idAffected = patterns.some((p) => p.segments.length === 1 && !p.wildcardTail && globMatches(p.segments[0], "_id", { caseInsensitive: true }));
|
|
22418
23052
|
if (idAffected) {
|
|
22419
23053
|
console.error(`[blacklist] collection '${collection}' blacklists '_id'; read paths still expose _id to preserve document references.`);
|
|
22420
23054
|
}
|
|
@@ -22440,7 +23074,7 @@ function maskValue(value, path2, patterns) {
|
|
|
22440
23074
|
if (value === null || value === undefined)
|
|
22441
23075
|
return value;
|
|
22442
23076
|
if (Array.isArray(value)) {
|
|
22443
|
-
return value.map((item) =>
|
|
23077
|
+
return value.map((item) => maskValue(item, path2, patterns));
|
|
22444
23078
|
}
|
|
22445
23079
|
if (typeof value === "object") {
|
|
22446
23080
|
return maskRecord(value, path2, patterns);
|
|
@@ -22448,15 +23082,21 @@ function maskValue(value, path2, patterns) {
|
|
|
22448
23082
|
return value;
|
|
22449
23083
|
}
|
|
22450
23084
|
function findCaseInsensitive(columns, name) {
|
|
22451
|
-
const target = name
|
|
23085
|
+
const target = foldFieldPath(name);
|
|
22452
23086
|
for (const [k, v] of Object.entries(columns)) {
|
|
22453
|
-
if (k
|
|
23087
|
+
if (foldFieldPath(k) === target)
|
|
22454
23088
|
return v;
|
|
22455
23089
|
}
|
|
22456
23090
|
return;
|
|
22457
23091
|
}
|
|
22458
23092
|
var REDACTED2 = "[REDACTED]";
|
|
22459
|
-
var init_field_masker = () => {
|
|
23093
|
+
var init_field_masker = __esm(() => {
|
|
23094
|
+
init_blacklist();
|
|
23095
|
+
init_path_matcher();
|
|
23096
|
+
init_blacklist_fold();
|
|
23097
|
+
init_glob();
|
|
23098
|
+
init_glob();
|
|
23099
|
+
});
|
|
22460
23100
|
|
|
22461
23101
|
// src/core/mongo/collection-references.ts
|
|
22462
23102
|
function recordTarget(value, found) {
|
|
@@ -24224,6 +24864,7 @@ var init_query = __esm(() => {
|
|
|
24224
24864
|
init_opener();
|
|
24225
24865
|
init_query_executor();
|
|
24226
24866
|
init_config();
|
|
24867
|
+
init_blacklist_manager();
|
|
24227
24868
|
init_blacklist_validator();
|
|
24228
24869
|
init_config_path();
|
|
24229
24870
|
init_validation();
|
|
@@ -24570,9 +25211,9 @@ function applySelectPatternRules(facts, factors) {
|
|
|
24570
25211
|
}
|
|
24571
25212
|
}
|
|
24572
25213
|
function applyBlacklistRules(facts, blacklist, factors) {
|
|
24573
|
-
const blacklistedTables = new Set((blacklist.tables ?? []).map(
|
|
25214
|
+
const blacklistedTables = new Set((blacklist.tables ?? []).map(foldFieldPath));
|
|
24574
25215
|
for (const table of facts.targetTables) {
|
|
24575
|
-
if (blacklistedTables.has(table
|
|
25216
|
+
if (blacklistedTables.has(foldFieldPath(table))) {
|
|
24576
25217
|
pushFactor(factors, "table_blacklisted", "block", `Target table ${table} is blacklisted.`);
|
|
24577
25218
|
}
|
|
24578
25219
|
}
|
|
@@ -24588,7 +25229,7 @@ function applyBlacklistRules(facts, blacklist, factors) {
|
|
|
24588
25229
|
function findBlacklistedColumnsForTable(blacklist, table) {
|
|
24589
25230
|
const columns = blacklist.columns ?? {};
|
|
24590
25231
|
for (const [tableName2, columnNames] of Object.entries(columns)) {
|
|
24591
|
-
if (tableName2
|
|
25232
|
+
if (foldFieldPath(tableName2) === foldFieldPath(table)) {
|
|
24592
25233
|
return new Set(columnNames);
|
|
24593
25234
|
}
|
|
24594
25235
|
}
|
|
@@ -24679,6 +25320,7 @@ function buildSuggestedCommands(facts, gap) {
|
|
|
24679
25320
|
}
|
|
24680
25321
|
var READ_LIKE_WORDS, WRITE_OR_DDL_LIKE_WORDS;
|
|
24681
25322
|
var init_query_risk_analyzer = __esm(() => {
|
|
25323
|
+
init_blacklist_fold();
|
|
24682
25324
|
init_permission_guard();
|
|
24683
25325
|
init_size_category();
|
|
24684
25326
|
READ_LIKE_WORDS = ["SELECT", "SHOW", "DESCRIBE", "EXPLAIN", "WITH"];
|
|
@@ -26161,6 +26803,7 @@ async function qMongoBranch(snippet, prepared, options, config) {
|
|
|
26161
26803
|
}
|
|
26162
26804
|
var init_q_mongo = __esm(() => {
|
|
26163
26805
|
init_adapters();
|
|
26806
|
+
init_blacklist_manager();
|
|
26164
26807
|
init_blacklist_validator();
|
|
26165
26808
|
init_field_masker();
|
|
26166
26809
|
init_collection_references();
|
|
@@ -26234,6 +26877,19 @@ async function qCommand(name, options, command) {
|
|
|
26234
26877
|
const family = engineFamily(engine);
|
|
26235
26878
|
const sqlDialect = SQL_DIALECTS.find((dialect) => dialect === connectionSystem);
|
|
26236
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
|
+
}
|
|
26237
26893
|
if (options.dryRun) {
|
|
26238
26894
|
console.log(formatDryRun({
|
|
26239
26895
|
family,
|
|
@@ -26255,19 +26911,7 @@ async function qCommand(name, options, command) {
|
|
|
26255
26911
|
await qMongoBranch2(snippet, prepared, options, config);
|
|
26256
26912
|
return;
|
|
26257
26913
|
}
|
|
26258
|
-
const
|
|
26259
|
-
const blacklistValidator = new BlacklistValidator(blacklistManager);
|
|
26260
|
-
const targets = family === "sql" ? extractTableReferences(prepared.rewrittenSql, {
|
|
26261
|
-
...sqlDialect ? { dialect: sqlDialect } : {}
|
|
26262
|
-
}) : family === "es" ? [prepared.execHints?.index ?? ""] : [];
|
|
26263
|
-
const targetName = targets[0] ?? "";
|
|
26264
|
-
targetNameForAudit = targetName || name;
|
|
26265
|
-
if (family === "es") {
|
|
26266
|
-
blacklistValidator.checkIndexBlacklist("SELECT", targetName);
|
|
26267
|
-
} else if (classification && targets.length > 0) {
|
|
26268
|
-
blacklistValidator.checkTablesBlacklist(classification.type, targets);
|
|
26269
|
-
}
|
|
26270
|
-
const adapter = AdapterFactory.createAdapter(config.connection);
|
|
26914
|
+
const adapter = AdapterFactory.createAdapter(config);
|
|
26271
26915
|
await adapter.connect();
|
|
26272
26916
|
try {
|
|
26273
26917
|
if (!options.ui && options.format !== "html") {
|
|
@@ -26388,6 +27032,8 @@ async function readParamFile(path2) {
|
|
|
26388
27032
|
return parsed;
|
|
26389
27033
|
}
|
|
26390
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;
|
|
26391
27037
|
let auditId = null;
|
|
26392
27038
|
let envelopeId;
|
|
26393
27039
|
if (options.recovery === true) {
|
|
@@ -26397,21 +27043,22 @@ async function handleQError(error, snippetName, options, config) {
|
|
|
26397
27043
|
auditId = await writeAuditEntry(config, "q", options, {
|
|
26398
27044
|
success: false,
|
|
26399
27045
|
target: snippetName,
|
|
26400
|
-
error,
|
|
27046
|
+
error: reportedError,
|
|
26401
27047
|
...envelopeId && { recovery_ref: envelopeId }
|
|
26402
27048
|
});
|
|
26403
27049
|
}
|
|
26404
27050
|
if (envelopeId !== undefined) {
|
|
26405
27051
|
const { emitRecoveryEnvelope: emitRecoveryEnvelope2 } = await Promise.resolve().then(() => (init_recovery(), exports_recovery));
|
|
26406
|
-
emitRecoveryEnvelope2(
|
|
27052
|
+
emitRecoveryEnvelope2(reportedError, { operation: "q", snippet: snippetName }, { envelopeId, auditRef: auditId ?? undefined });
|
|
26407
27053
|
}
|
|
26408
27054
|
if (error instanceof SavedQueryError) {
|
|
26409
27055
|
printLocalizedCliError(error.message, error);
|
|
26410
27056
|
process.exit(1);
|
|
26411
27057
|
}
|
|
26412
|
-
if (error instanceof BlacklistError) {
|
|
26413
|
-
printLocalizedCliError(
|
|
27058
|
+
if (error instanceof BlacklistError || error instanceof BlacklistRejection) {
|
|
27059
|
+
printLocalizedCliError(reportedError.message, reportedError);
|
|
26414
27060
|
process.exit(1);
|
|
27061
|
+
return;
|
|
26415
27062
|
}
|
|
26416
27063
|
if (error instanceof PermissionError) {
|
|
26417
27064
|
printLocalizedCliError(t_vars("errors.permission_denied", { required: error.requiredPermission }), error);
|
|
@@ -26522,8 +27169,12 @@ function evaluateExpectation(row, expects) {
|
|
|
26522
27169
|
var init_q = __esm(() => {
|
|
26523
27170
|
init_message_loader();
|
|
26524
27171
|
init_adapters();
|
|
27172
|
+
init_redis_adapter();
|
|
27173
|
+
init_blacklist_enforcer();
|
|
27174
|
+
init_types3();
|
|
26525
27175
|
init_config();
|
|
26526
27176
|
init_config_path();
|
|
27177
|
+
init_blacklist_manager();
|
|
26527
27178
|
init_blacklist_validator();
|
|
26528
27179
|
init_blacklist();
|
|
26529
27180
|
init_permission_guard();
|
|
@@ -26792,6 +27443,366 @@ function previewDelete(collection, filter) {
|
|
|
26792
27443
|
}
|
|
26793
27444
|
var INDENT = 2;
|
|
26794
27445
|
|
|
27446
|
+
// src/utils/where-parser.ts
|
|
27447
|
+
function parseWhereClause(whereClause) {
|
|
27448
|
+
if (!whereClause || whereClause.trim() === "") {
|
|
27449
|
+
throw new Error("WHERE clause cannot be empty");
|
|
27450
|
+
}
|
|
27451
|
+
const conditions = {};
|
|
27452
|
+
const andParts = whereClause.split(/\s+AND\s+/i);
|
|
27453
|
+
for (const part of andParts) {
|
|
27454
|
+
const match = part.match(/^(\w+)\s*=\s*(.+)$/);
|
|
27455
|
+
if (!match) {
|
|
27456
|
+
throw new Error(`Cannot parse WHERE clause: "${part}". Use format "column=value" or "col1=val1 AND col2=val2"`);
|
|
27457
|
+
}
|
|
27458
|
+
const column = match[1];
|
|
27459
|
+
const valueStr = match[2];
|
|
27460
|
+
if (valueStr === undefined || column === undefined) {
|
|
27461
|
+
throw new Error(`Cannot parse WHERE clause: "${part}". Use format "column=value" or "col1=val1 AND col2=val2"`);
|
|
27462
|
+
}
|
|
27463
|
+
const trimmed = valueStr.trim();
|
|
27464
|
+
const isFullyQuoted = trimmed.length >= 2 && (trimmed.startsWith("'") && trimmed.endsWith("'") || trimmed.startsWith('"') && trimmed.endsWith('"'));
|
|
27465
|
+
if (!isFullyQuoted && /\s+AND\s*$/i.test(trimmed)) {
|
|
27466
|
+
throw new Error(`Cannot parse WHERE clause: "${part}". Use format "column=value" or "col1=val1 AND col2=val2"`);
|
|
27467
|
+
}
|
|
27468
|
+
const stripped = isFullyQuoted ? trimmed.slice(1, -1) : trimmed;
|
|
27469
|
+
let value = stripped;
|
|
27470
|
+
if (stripped !== "" && !isNaN(Number(stripped))) {
|
|
27471
|
+
value = Number(stripped);
|
|
27472
|
+
}
|
|
27473
|
+
if (stripped === "true")
|
|
27474
|
+
value = true;
|
|
27475
|
+
else if (stripped === "false")
|
|
27476
|
+
value = false;
|
|
27477
|
+
else if (stripped === "null")
|
|
27478
|
+
value = null;
|
|
27479
|
+
conditions[column] = value;
|
|
27480
|
+
}
|
|
27481
|
+
return conditions;
|
|
27482
|
+
}
|
|
27483
|
+
|
|
27484
|
+
// src/core/mongo/dml-plan.ts
|
|
27485
|
+
function permissionSql(operation) {
|
|
27486
|
+
if (operation === "insert")
|
|
27487
|
+
return "INSERT INTO dummy";
|
|
27488
|
+
if (operation === "update")
|
|
27489
|
+
return "UPDATE dummy";
|
|
27490
|
+
return "DELETE FROM dummy";
|
|
27491
|
+
}
|
|
27492
|
+
function broadOperation(operation) {
|
|
27493
|
+
if (operation === "insert")
|
|
27494
|
+
return "INSERT";
|
|
27495
|
+
if (operation === "update")
|
|
27496
|
+
return "UPDATE";
|
|
27497
|
+
return "DELETE";
|
|
27498
|
+
}
|
|
27499
|
+
function pushFactor2(factors, code, severity, message) {
|
|
27500
|
+
if (factors.some((f) => f.code === code && f.message === message))
|
|
27501
|
+
return;
|
|
27502
|
+
factors.push({ code, severity, message });
|
|
27503
|
+
}
|
|
27504
|
+
function decide2(factors) {
|
|
27505
|
+
if (factors.some((f) => f.severity === "block"))
|
|
27506
|
+
return "BLOCK";
|
|
27507
|
+
if (factors.some((f) => f.severity === "warn"))
|
|
27508
|
+
return "WARN";
|
|
27509
|
+
return "ALLOW";
|
|
27510
|
+
}
|
|
27511
|
+
function classifyMongoUpdate(setDoc) {
|
|
27512
|
+
const fields = new Set;
|
|
27513
|
+
const tierFactors = [];
|
|
27514
|
+
let hasBlock = false;
|
|
27515
|
+
const hasAnyOperator = Object.keys(setDoc).some((k) => k.startsWith("$"));
|
|
27516
|
+
if (!hasAnyOperator) {
|
|
27517
|
+
for (const k of Object.keys(setDoc))
|
|
27518
|
+
fields.add(k);
|
|
27519
|
+
return { fields: Array.from(fields), tierFactors, hasBlock };
|
|
27520
|
+
}
|
|
27521
|
+
const seenTiers = new Map;
|
|
27522
|
+
for (const [op, payload] of Object.entries(setDoc)) {
|
|
27523
|
+
if (!op.startsWith("$")) {
|
|
27524
|
+
fields.add(op);
|
|
27525
|
+
continue;
|
|
27526
|
+
}
|
|
27527
|
+
if (payload && typeof payload === "object" && !Array.isArray(payload)) {
|
|
27528
|
+
for (const k of Object.keys(payload))
|
|
27529
|
+
fields.add(k);
|
|
27530
|
+
}
|
|
27531
|
+
const tier = MONGO_OPERATOR_TIER[op];
|
|
27532
|
+
if (tier === undefined) {
|
|
27533
|
+
hasBlock = true;
|
|
27534
|
+
tierFactors.push({
|
|
27535
|
+
code: "mongo_unknown_operator",
|
|
27536
|
+
severity: "block",
|
|
27537
|
+
message: `Update uses unknown operator '${op}'. Reject by default; add to tier table if intentional.`
|
|
27538
|
+
});
|
|
27539
|
+
continue;
|
|
27540
|
+
}
|
|
27541
|
+
if (tier === "BLOCK") {
|
|
27542
|
+
hasBlock = true;
|
|
27543
|
+
tierFactors.push({
|
|
27544
|
+
code: "mongo_unknown_operator",
|
|
27545
|
+
severity: "block",
|
|
27546
|
+
message: `Update uses '${op}' which executes server-side code. Operation rejected.`
|
|
27547
|
+
});
|
|
27548
|
+
continue;
|
|
27549
|
+
}
|
|
27550
|
+
if (tier === "SAFE")
|
|
27551
|
+
continue;
|
|
27552
|
+
const bucket = seenTiers.get(tier) ?? [];
|
|
27553
|
+
bucket.push(op);
|
|
27554
|
+
seenTiers.set(tier, bucket);
|
|
27555
|
+
}
|
|
27556
|
+
for (const [tier, ops] of seenTiers) {
|
|
27557
|
+
if (tier === "RENAME") {
|
|
27558
|
+
tierFactors.push({
|
|
27559
|
+
code: "mongo_rename_operator",
|
|
27560
|
+
severity: "warn",
|
|
27561
|
+
message: `Update uses ${ops.join(", ")}; a renamed field keeps its value under a name the read mask does not know.`
|
|
27562
|
+
});
|
|
27563
|
+
} else if (tier === "ARITHMETIC") {
|
|
27564
|
+
tierFactors.push({
|
|
27565
|
+
code: "mongo_arithmetic_operator",
|
|
27566
|
+
severity: "warn",
|
|
27567
|
+
message: `Update uses ${ops.join(", ")}; numeric mutation may compound silently.`
|
|
27568
|
+
});
|
|
27569
|
+
} else if (tier === "ARRAY") {
|
|
27570
|
+
tierFactors.push({
|
|
27571
|
+
code: "mongo_array_operator",
|
|
27572
|
+
severity: "warn",
|
|
27573
|
+
message: `Update uses ${ops.join(", ")}; array mutation can grow unboundedly without a size guard.`
|
|
27574
|
+
});
|
|
27575
|
+
} else if (tier === "BITWISE") {
|
|
27576
|
+
tierFactors.push({
|
|
27577
|
+
code: "mongo_bitwise_operator",
|
|
27578
|
+
severity: "warn",
|
|
27579
|
+
message: `Update uses ${ops.join(", ")}; bitwise updates skip type promotion checks.`
|
|
27580
|
+
});
|
|
27581
|
+
}
|
|
27582
|
+
}
|
|
27583
|
+
return { fields: Array.from(fields), tierFactors, hasBlock };
|
|
27584
|
+
}
|
|
27585
|
+
function hasIdEquality(where) {
|
|
27586
|
+
if (!("_id" in where))
|
|
27587
|
+
return false;
|
|
27588
|
+
const value = where._id;
|
|
27589
|
+
if (value === null || value === undefined)
|
|
27590
|
+
return false;
|
|
27591
|
+
if (typeof value === "object")
|
|
27592
|
+
return false;
|
|
27593
|
+
return true;
|
|
27594
|
+
}
|
|
27595
|
+
function hasNonIdEquality(where) {
|
|
27596
|
+
for (const [key, value] of Object.entries(where)) {
|
|
27597
|
+
if (key === "_id")
|
|
27598
|
+
continue;
|
|
27599
|
+
if (key.startsWith("$"))
|
|
27600
|
+
continue;
|
|
27601
|
+
if (value === null)
|
|
27602
|
+
continue;
|
|
27603
|
+
if (typeof value === "object")
|
|
27604
|
+
continue;
|
|
27605
|
+
return true;
|
|
27606
|
+
}
|
|
27607
|
+
return false;
|
|
27608
|
+
}
|
|
27609
|
+
function hasBroadFilter(where) {
|
|
27610
|
+
for (const [key, value] of Object.entries(where)) {
|
|
27611
|
+
if (MONGO_BROAD_FILTER_OPERATORS.has(key))
|
|
27612
|
+
return true;
|
|
27613
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
27614
|
+
for (const inner of Object.keys(value)) {
|
|
27615
|
+
if (MONGO_BROAD_FILTER_OPERATORS.has(inner))
|
|
27616
|
+
return true;
|
|
27617
|
+
}
|
|
27618
|
+
}
|
|
27619
|
+
}
|
|
27620
|
+
return false;
|
|
27621
|
+
}
|
|
27622
|
+
function applyPermission(operation, context, factors) {
|
|
27623
|
+
const permResult = checkPermission(permissionSql(operation), context.permission);
|
|
27624
|
+
if (!permResult.allowed) {
|
|
27625
|
+
pushFactor2(factors, "permission_denied", "block", permResult.reason);
|
|
27626
|
+
}
|
|
27627
|
+
}
|
|
27628
|
+
function applyTableBlacklist(target, context, factors) {
|
|
27629
|
+
const blacklisted = (context.blacklist.tables ?? []).map(foldFieldPath);
|
|
27630
|
+
if (blacklisted.includes(foldFieldPath(target))) {
|
|
27631
|
+
pushFactor2(factors, "table_blacklisted", "block", `Target collection ${target} is blacklisted.`);
|
|
27632
|
+
}
|
|
27633
|
+
}
|
|
27634
|
+
function applyColumnBlacklist(target, fields, context, factors) {
|
|
27635
|
+
const columns = context.blacklist.columns ?? {};
|
|
27636
|
+
const lower = foldFieldPath(target);
|
|
27637
|
+
let raw = [];
|
|
27638
|
+
for (const [t2, cols] of Object.entries(columns)) {
|
|
27639
|
+
if (foldFieldPath(t2) === lower) {
|
|
27640
|
+
raw = cols;
|
|
27641
|
+
break;
|
|
27642
|
+
}
|
|
27643
|
+
}
|
|
27644
|
+
if (raw.length === 0)
|
|
27645
|
+
return;
|
|
27646
|
+
const { patterns, rejected } = compilePatterns(raw);
|
|
27647
|
+
if (rejected.length > 0) {
|
|
27648
|
+
const detail = rejected.map((r) => `'${r.raw}' (${r.reason})`).join(", ");
|
|
27649
|
+
pushFactor2(factors, "blacklist_unreadable", "block", `blacklist.columns for '${target}' has entries this matcher cannot read: ${detail}`);
|
|
27650
|
+
return;
|
|
27651
|
+
}
|
|
27652
|
+
if (patterns.length === 0)
|
|
27653
|
+
return;
|
|
27654
|
+
for (const field of fields) {
|
|
27655
|
+
if (matchAny(field, patterns)) {
|
|
27656
|
+
pushFactor2(factors, "blacklisted_column", "block", `MongoDB write would touch blacklisted path ${target}.${field}.`);
|
|
27657
|
+
}
|
|
27658
|
+
}
|
|
27659
|
+
}
|
|
27660
|
+
function applySchemaCoverage(target, context, factors) {
|
|
27661
|
+
const schemaTables = Object.keys(context.schema);
|
|
27662
|
+
if (schemaTables.length === 0) {
|
|
27663
|
+
pushFactor2(factors, "schema_cache_missing", "warn", "Schema cache is missing for the selected connection.");
|
|
27664
|
+
return;
|
|
27665
|
+
}
|
|
27666
|
+
const known = schemaTables.some((t2) => t2.toLowerCase() === target.toLowerCase());
|
|
27667
|
+
if (!known) {
|
|
27668
|
+
pushFactor2(factors, "schema_table_unknown", "warn", `Target collection ${target} is missing from schema cache.`);
|
|
27669
|
+
}
|
|
27670
|
+
}
|
|
27671
|
+
function buildRecommendations2(factors) {
|
|
27672
|
+
const out = new Set;
|
|
27673
|
+
const codes = new Set(factors.map((f) => f.code));
|
|
27674
|
+
if (codes.has("nonsql_filter_empty")) {
|
|
27675
|
+
out.add("Add an _id equality filter before executing this MongoDB write.");
|
|
27676
|
+
}
|
|
27677
|
+
if (codes.has("nonsql_missing_id")) {
|
|
27678
|
+
out.add("Prefer an _id equality filter before executing this MongoDB write.");
|
|
27679
|
+
}
|
|
27680
|
+
if (codes.has("nonsql_filter_broad")) {
|
|
27681
|
+
out.add("Narrow the filter to an _id equality or a tightly bounded condition.");
|
|
27682
|
+
}
|
|
27683
|
+
if (codes.has("nonsql_unsupported_operator")) {
|
|
27684
|
+
out.add("Restrict the update document to $set / $unset for planner-safe writes.");
|
|
27685
|
+
}
|
|
27686
|
+
if (codes.has("permission_denied")) {
|
|
27687
|
+
out.add("Switch to a connection with sufficient permission only if the operation is intended.");
|
|
27688
|
+
}
|
|
27689
|
+
if (codes.has("table_blacklisted") || codes.has("blacklisted_column")) {
|
|
27690
|
+
out.add("Review blacklist rules before accessing sensitive data.");
|
|
27691
|
+
}
|
|
27692
|
+
if (codes.has("schema_cache_missing") || codes.has("schema_table_unknown")) {
|
|
27693
|
+
out.add("Refresh schema cache for the target collection before executing.");
|
|
27694
|
+
}
|
|
27695
|
+
out.add("Use --dry-run on the actual write command.");
|
|
27696
|
+
return Array.from(out);
|
|
27697
|
+
}
|
|
27698
|
+
function buildSuggestedCommands2(target, factors) {
|
|
27699
|
+
const codes = new Set(factors.map((f) => f.code));
|
|
27700
|
+
if (codes.has("schema_cache_missing") || codes.has("schema_table_unknown")) {
|
|
27701
|
+
return [`dbcli schema ${target} --format json`];
|
|
27702
|
+
}
|
|
27703
|
+
return [];
|
|
27704
|
+
}
|
|
27705
|
+
function flattenInsertPaths(data, prefix = "") {
|
|
27706
|
+
const out = [];
|
|
27707
|
+
for (const [k, v] of Object.entries(data)) {
|
|
27708
|
+
const path2 = prefix === "" ? k : `${prefix}.${k}`;
|
|
27709
|
+
out.push(path2);
|
|
27710
|
+
if (v && typeof v === "object" && !Array.isArray(v) && !(v instanceof Date)) {
|
|
27711
|
+
const ctor = v.constructor?.name;
|
|
27712
|
+
if (ctor === "Object") {
|
|
27713
|
+
out.push(...flattenInsertPaths(v, path2));
|
|
27714
|
+
}
|
|
27715
|
+
}
|
|
27716
|
+
}
|
|
27717
|
+
return out;
|
|
27718
|
+
}
|
|
27719
|
+
var MONGO_OPERATOR_TIER, MONGO_BROAD_FILTER_OPERATORS, analyzeMongoDmlRisk = (intent, context) => {
|
|
27720
|
+
const factors = [];
|
|
27721
|
+
applyPermission(intent.operation, context, factors);
|
|
27722
|
+
applyTableBlacklist(intent.target, context, factors);
|
|
27723
|
+
if (intent.operation === "insert") {
|
|
27724
|
+
applyColumnBlacklist(intent.target, flattenInsertPaths(intent.data), context, factors);
|
|
27725
|
+
} else if (intent.operation === "update") {
|
|
27726
|
+
const cls = classifyMongoUpdate(intent.set);
|
|
27727
|
+
for (const f of cls.tierFactors)
|
|
27728
|
+
pushFactor2(factors, f.code, f.severity, f.message);
|
|
27729
|
+
applyColumnBlacklist(intent.target, cls.fields, context, factors);
|
|
27730
|
+
const where = intent.where ?? {};
|
|
27731
|
+
if (Object.keys(where).length === 0) {
|
|
27732
|
+
pushFactor2(factors, "nonsql_filter_empty", "block", "MongoDB update filter is empty and would match every document.");
|
|
27733
|
+
} else if (!hasIdEquality(where)) {
|
|
27734
|
+
if (hasBroadFilter(where)) {
|
|
27735
|
+
pushFactor2(factors, "nonsql_filter_broad", "warn", "MongoDB update filter uses a broad operator and may match multiple documents.");
|
|
27736
|
+
} else if (hasNonIdEquality(where)) {
|
|
27737
|
+
pushFactor2(factors, "nonsql_missing_id", "warn", "MongoDB update filter does not use _id and may match multiple documents.");
|
|
27738
|
+
} else {
|
|
27739
|
+
pushFactor2(factors, "nonsql_filter_broad", "warn", "MongoDB update filter is not an _id equality and may match multiple documents.");
|
|
27740
|
+
}
|
|
27741
|
+
}
|
|
27742
|
+
} else {
|
|
27743
|
+
const where = intent.where ?? {};
|
|
27744
|
+
if (Object.keys(where).length === 0) {
|
|
27745
|
+
pushFactor2(factors, "nonsql_filter_empty", "block", "MongoDB delete filter is empty and would match every document.");
|
|
27746
|
+
} else if (!hasIdEquality(where)) {
|
|
27747
|
+
if (hasBroadFilter(where)) {
|
|
27748
|
+
pushFactor2(factors, "nonsql_filter_broad", "warn", "MongoDB delete filter uses a broad operator and may match multiple documents.");
|
|
27749
|
+
} else if (hasNonIdEquality(where)) {
|
|
27750
|
+
pushFactor2(factors, "nonsql_missing_id", "warn", "MongoDB delete filter does not use _id and may match multiple documents.");
|
|
27751
|
+
} else {
|
|
27752
|
+
pushFactor2(factors, "nonsql_filter_broad", "warn", "MongoDB delete filter is not an _id equality and may match multiple documents.");
|
|
27753
|
+
}
|
|
27754
|
+
}
|
|
27755
|
+
}
|
|
27756
|
+
applySchemaCoverage(intent.target, context, factors);
|
|
27757
|
+
return {
|
|
27758
|
+
decision: decide2(factors),
|
|
27759
|
+
operation: broadOperation(intent.operation),
|
|
27760
|
+
targetTables: [intent.target],
|
|
27761
|
+
riskFactors: factors,
|
|
27762
|
+
recommendations: buildRecommendations2(factors),
|
|
27763
|
+
suggestedCommands: buildSuggestedCommands2(intent.target, factors)
|
|
27764
|
+
};
|
|
27765
|
+
};
|
|
27766
|
+
var init_dml_plan = __esm(() => {
|
|
27767
|
+
init_blacklist_fold();
|
|
27768
|
+
init_path_matcher();
|
|
27769
|
+
init_permission_guard();
|
|
27770
|
+
MONGO_OPERATOR_TIER = {
|
|
27771
|
+
$set: "SAFE",
|
|
27772
|
+
$unset: "SAFE",
|
|
27773
|
+
$rename: "RENAME",
|
|
27774
|
+
$inc: "ARITHMETIC",
|
|
27775
|
+
$mul: "ARITHMETIC",
|
|
27776
|
+
$min: "ARITHMETIC",
|
|
27777
|
+
$max: "ARITHMETIC",
|
|
27778
|
+
$currentDate: "ARITHMETIC",
|
|
27779
|
+
$push: "ARRAY",
|
|
27780
|
+
$pull: "ARRAY",
|
|
27781
|
+
$pullAll: "ARRAY",
|
|
27782
|
+
$pop: "ARRAY",
|
|
27783
|
+
$addToSet: "ARRAY",
|
|
27784
|
+
$bit: "BITWISE",
|
|
27785
|
+
$where: "BLOCK"
|
|
27786
|
+
};
|
|
27787
|
+
MONGO_BROAD_FILTER_OPERATORS = new Set([
|
|
27788
|
+
"$regex",
|
|
27789
|
+
"$in",
|
|
27790
|
+
"$nin",
|
|
27791
|
+
"$gt",
|
|
27792
|
+
"$gte",
|
|
27793
|
+
"$lt",
|
|
27794
|
+
"$lte",
|
|
27795
|
+
"$ne",
|
|
27796
|
+
"$exists",
|
|
27797
|
+
"$or",
|
|
27798
|
+
"$and",
|
|
27799
|
+
"$nor",
|
|
27800
|
+
"$not",
|
|
27801
|
+
"$expr",
|
|
27802
|
+
"$text"
|
|
27803
|
+
]);
|
|
27804
|
+
});
|
|
27805
|
+
|
|
26795
27806
|
// src/core/dml-plan-sql.ts
|
|
26796
27807
|
function assertIdentifier(value, role) {
|
|
26797
27808
|
if (!value || !IDENTIFIER_RE.test(value)) {
|
|
@@ -26851,359 +27862,6 @@ var init_dml_plan_sql = __esm(() => {
|
|
|
26851
27862
|
IDENTIFIER_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
26852
27863
|
});
|
|
26853
27864
|
|
|
26854
|
-
// src/utils/where-parser.ts
|
|
26855
|
-
function parseWhereClause(whereClause) {
|
|
26856
|
-
if (!whereClause || whereClause.trim() === "") {
|
|
26857
|
-
throw new Error("WHERE clause cannot be empty");
|
|
26858
|
-
}
|
|
26859
|
-
const conditions = {};
|
|
26860
|
-
const andParts = whereClause.split(/\s+AND\s+/i);
|
|
26861
|
-
for (const part of andParts) {
|
|
26862
|
-
const match = part.match(/^(\w+)\s*=\s*(.+)$/);
|
|
26863
|
-
if (!match) {
|
|
26864
|
-
throw new Error(`Cannot parse WHERE clause: "${part}". Use format "column=value" or "col1=val1 AND col2=val2"`);
|
|
26865
|
-
}
|
|
26866
|
-
const column = match[1];
|
|
26867
|
-
const valueStr = match[2];
|
|
26868
|
-
if (valueStr === undefined || column === undefined) {
|
|
26869
|
-
throw new Error(`Cannot parse WHERE clause: "${part}". Use format "column=value" or "col1=val1 AND col2=val2"`);
|
|
26870
|
-
}
|
|
26871
|
-
const trimmed = valueStr.trim();
|
|
26872
|
-
const isFullyQuoted = trimmed.length >= 2 && (trimmed.startsWith("'") && trimmed.endsWith("'") || trimmed.startsWith('"') && trimmed.endsWith('"'));
|
|
26873
|
-
if (!isFullyQuoted && /\s+AND\s*$/i.test(trimmed)) {
|
|
26874
|
-
throw new Error(`Cannot parse WHERE clause: "${part}". Use format "column=value" or "col1=val1 AND col2=val2"`);
|
|
26875
|
-
}
|
|
26876
|
-
const stripped = isFullyQuoted ? trimmed.slice(1, -1) : trimmed;
|
|
26877
|
-
let value = stripped;
|
|
26878
|
-
if (stripped !== "" && !isNaN(Number(stripped))) {
|
|
26879
|
-
value = Number(stripped);
|
|
26880
|
-
}
|
|
26881
|
-
if (stripped === "true")
|
|
26882
|
-
value = true;
|
|
26883
|
-
else if (stripped === "false")
|
|
26884
|
-
value = false;
|
|
26885
|
-
else if (stripped === "null")
|
|
26886
|
-
value = null;
|
|
26887
|
-
conditions[column] = value;
|
|
26888
|
-
}
|
|
26889
|
-
return conditions;
|
|
26890
|
-
}
|
|
26891
|
-
|
|
26892
|
-
// src/core/mongo/dml-plan.ts
|
|
26893
|
-
function permissionSql(operation) {
|
|
26894
|
-
if (operation === "insert")
|
|
26895
|
-
return "INSERT INTO dummy";
|
|
26896
|
-
if (operation === "update")
|
|
26897
|
-
return "UPDATE dummy";
|
|
26898
|
-
return "DELETE FROM dummy";
|
|
26899
|
-
}
|
|
26900
|
-
function broadOperation(operation) {
|
|
26901
|
-
if (operation === "insert")
|
|
26902
|
-
return "INSERT";
|
|
26903
|
-
if (operation === "update")
|
|
26904
|
-
return "UPDATE";
|
|
26905
|
-
return "DELETE";
|
|
26906
|
-
}
|
|
26907
|
-
function pushFactor2(factors, code, severity, message) {
|
|
26908
|
-
if (factors.some((f) => f.code === code && f.message === message))
|
|
26909
|
-
return;
|
|
26910
|
-
factors.push({ code, severity, message });
|
|
26911
|
-
}
|
|
26912
|
-
function decide2(factors) {
|
|
26913
|
-
if (factors.some((f) => f.severity === "block"))
|
|
26914
|
-
return "BLOCK";
|
|
26915
|
-
if (factors.some((f) => f.severity === "warn"))
|
|
26916
|
-
return "WARN";
|
|
26917
|
-
return "ALLOW";
|
|
26918
|
-
}
|
|
26919
|
-
function classifyMongoUpdate(setDoc) {
|
|
26920
|
-
const fields = new Set;
|
|
26921
|
-
const tierFactors = [];
|
|
26922
|
-
let hasBlock = false;
|
|
26923
|
-
const hasAnyOperator = Object.keys(setDoc).some((k) => k.startsWith("$"));
|
|
26924
|
-
if (!hasAnyOperator) {
|
|
26925
|
-
for (const k of Object.keys(setDoc))
|
|
26926
|
-
fields.add(k);
|
|
26927
|
-
return { fields: Array.from(fields), tierFactors, hasBlock };
|
|
26928
|
-
}
|
|
26929
|
-
const seenTiers = new Map;
|
|
26930
|
-
for (const [op, payload] of Object.entries(setDoc)) {
|
|
26931
|
-
if (!op.startsWith("$")) {
|
|
26932
|
-
fields.add(op);
|
|
26933
|
-
continue;
|
|
26934
|
-
}
|
|
26935
|
-
if (payload && typeof payload === "object" && !Array.isArray(payload)) {
|
|
26936
|
-
for (const k of Object.keys(payload))
|
|
26937
|
-
fields.add(k);
|
|
26938
|
-
}
|
|
26939
|
-
const tier = MONGO_OPERATOR_TIER[op];
|
|
26940
|
-
if (tier === undefined) {
|
|
26941
|
-
hasBlock = true;
|
|
26942
|
-
tierFactors.push({
|
|
26943
|
-
code: "mongo_unknown_operator",
|
|
26944
|
-
severity: "block",
|
|
26945
|
-
message: `Update uses unknown operator '${op}'. Reject by default; add to tier table if intentional.`
|
|
26946
|
-
});
|
|
26947
|
-
continue;
|
|
26948
|
-
}
|
|
26949
|
-
if (tier === "BLOCK") {
|
|
26950
|
-
hasBlock = true;
|
|
26951
|
-
tierFactors.push({
|
|
26952
|
-
code: "mongo_unknown_operator",
|
|
26953
|
-
severity: "block",
|
|
26954
|
-
message: `Update uses '${op}' which executes server-side code. Operation rejected.`
|
|
26955
|
-
});
|
|
26956
|
-
continue;
|
|
26957
|
-
}
|
|
26958
|
-
if (tier === "SAFE")
|
|
26959
|
-
continue;
|
|
26960
|
-
const bucket = seenTiers.get(tier) ?? [];
|
|
26961
|
-
bucket.push(op);
|
|
26962
|
-
seenTiers.set(tier, bucket);
|
|
26963
|
-
}
|
|
26964
|
-
for (const [tier, ops] of seenTiers) {
|
|
26965
|
-
if (tier === "RENAME") {
|
|
26966
|
-
tierFactors.push({
|
|
26967
|
-
code: "mongo_rename_operator",
|
|
26968
|
-
severity: "warn",
|
|
26969
|
-
message: `Update uses ${ops.join(", ")}; field rename does not exfiltrate data but can break readers.`
|
|
26970
|
-
});
|
|
26971
|
-
} else if (tier === "ARITHMETIC") {
|
|
26972
|
-
tierFactors.push({
|
|
26973
|
-
code: "mongo_arithmetic_operator",
|
|
26974
|
-
severity: "warn",
|
|
26975
|
-
message: `Update uses ${ops.join(", ")}; numeric mutation may compound silently.`
|
|
26976
|
-
});
|
|
26977
|
-
} else if (tier === "ARRAY") {
|
|
26978
|
-
tierFactors.push({
|
|
26979
|
-
code: "mongo_array_operator",
|
|
26980
|
-
severity: "warn",
|
|
26981
|
-
message: `Update uses ${ops.join(", ")}; array mutation can grow unboundedly without a size guard.`
|
|
26982
|
-
});
|
|
26983
|
-
} else if (tier === "BITWISE") {
|
|
26984
|
-
tierFactors.push({
|
|
26985
|
-
code: "mongo_bitwise_operator",
|
|
26986
|
-
severity: "warn",
|
|
26987
|
-
message: `Update uses ${ops.join(", ")}; bitwise updates skip type promotion checks.`
|
|
26988
|
-
});
|
|
26989
|
-
}
|
|
26990
|
-
}
|
|
26991
|
-
return { fields: Array.from(fields), tierFactors, hasBlock };
|
|
26992
|
-
}
|
|
26993
|
-
function hasIdEquality(where) {
|
|
26994
|
-
if (!("_id" in where))
|
|
26995
|
-
return false;
|
|
26996
|
-
const value = where._id;
|
|
26997
|
-
if (value === null || value === undefined)
|
|
26998
|
-
return false;
|
|
26999
|
-
if (typeof value === "object")
|
|
27000
|
-
return false;
|
|
27001
|
-
return true;
|
|
27002
|
-
}
|
|
27003
|
-
function hasNonIdEquality(where) {
|
|
27004
|
-
for (const [key, value] of Object.entries(where)) {
|
|
27005
|
-
if (key === "_id")
|
|
27006
|
-
continue;
|
|
27007
|
-
if (key.startsWith("$"))
|
|
27008
|
-
continue;
|
|
27009
|
-
if (value === null)
|
|
27010
|
-
continue;
|
|
27011
|
-
if (typeof value === "object")
|
|
27012
|
-
continue;
|
|
27013
|
-
return true;
|
|
27014
|
-
}
|
|
27015
|
-
return false;
|
|
27016
|
-
}
|
|
27017
|
-
function hasBroadFilter(where) {
|
|
27018
|
-
for (const [key, value] of Object.entries(where)) {
|
|
27019
|
-
if (MONGO_BROAD_FILTER_OPERATORS.has(key))
|
|
27020
|
-
return true;
|
|
27021
|
-
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
27022
|
-
for (const inner of Object.keys(value)) {
|
|
27023
|
-
if (MONGO_BROAD_FILTER_OPERATORS.has(inner))
|
|
27024
|
-
return true;
|
|
27025
|
-
}
|
|
27026
|
-
}
|
|
27027
|
-
}
|
|
27028
|
-
return false;
|
|
27029
|
-
}
|
|
27030
|
-
function applyPermission(operation, context, factors) {
|
|
27031
|
-
const permResult = checkPermission(permissionSql(operation), context.permission);
|
|
27032
|
-
if (!permResult.allowed) {
|
|
27033
|
-
pushFactor2(factors, "permission_denied", "block", permResult.reason);
|
|
27034
|
-
}
|
|
27035
|
-
}
|
|
27036
|
-
function applyTableBlacklist(target, context, factors) {
|
|
27037
|
-
const blacklisted = (context.blacklist.tables ?? []).map((t2) => t2.toLowerCase());
|
|
27038
|
-
if (blacklisted.includes(target.toLowerCase())) {
|
|
27039
|
-
pushFactor2(factors, "table_blacklisted", "block", `Target collection ${target} is blacklisted.`);
|
|
27040
|
-
}
|
|
27041
|
-
}
|
|
27042
|
-
function applyColumnBlacklist(target, fields, context, factors) {
|
|
27043
|
-
const columns = context.blacklist.columns ?? {};
|
|
27044
|
-
const lower = target.toLowerCase();
|
|
27045
|
-
let raw = [];
|
|
27046
|
-
for (const [t2, cols] of Object.entries(columns)) {
|
|
27047
|
-
if (t2.toLowerCase() === lower) {
|
|
27048
|
-
raw = cols;
|
|
27049
|
-
break;
|
|
27050
|
-
}
|
|
27051
|
-
}
|
|
27052
|
-
if (raw.length === 0)
|
|
27053
|
-
return;
|
|
27054
|
-
const { patterns } = compilePatterns(raw);
|
|
27055
|
-
if (patterns.length === 0)
|
|
27056
|
-
return;
|
|
27057
|
-
for (const field of fields) {
|
|
27058
|
-
if (matchAny(field, patterns)) {
|
|
27059
|
-
pushFactor2(factors, "blacklisted_column", "block", `MongoDB write would touch blacklisted path ${target}.${field}.`);
|
|
27060
|
-
}
|
|
27061
|
-
}
|
|
27062
|
-
}
|
|
27063
|
-
function applySchemaCoverage(target, context, factors) {
|
|
27064
|
-
const schemaTables = Object.keys(context.schema);
|
|
27065
|
-
if (schemaTables.length === 0) {
|
|
27066
|
-
pushFactor2(factors, "schema_cache_missing", "warn", "Schema cache is missing for the selected connection.");
|
|
27067
|
-
return;
|
|
27068
|
-
}
|
|
27069
|
-
const known = schemaTables.some((t2) => t2.toLowerCase() === target.toLowerCase());
|
|
27070
|
-
if (!known) {
|
|
27071
|
-
pushFactor2(factors, "schema_table_unknown", "warn", `Target collection ${target} is missing from schema cache.`);
|
|
27072
|
-
}
|
|
27073
|
-
}
|
|
27074
|
-
function buildRecommendations2(factors) {
|
|
27075
|
-
const out = new Set;
|
|
27076
|
-
const codes = new Set(factors.map((f) => f.code));
|
|
27077
|
-
if (codes.has("nonsql_filter_empty")) {
|
|
27078
|
-
out.add("Add an _id equality filter before executing this MongoDB write.");
|
|
27079
|
-
}
|
|
27080
|
-
if (codes.has("nonsql_missing_id")) {
|
|
27081
|
-
out.add("Prefer an _id equality filter before executing this MongoDB write.");
|
|
27082
|
-
}
|
|
27083
|
-
if (codes.has("nonsql_filter_broad")) {
|
|
27084
|
-
out.add("Narrow the filter to an _id equality or a tightly bounded condition.");
|
|
27085
|
-
}
|
|
27086
|
-
if (codes.has("nonsql_unsupported_operator")) {
|
|
27087
|
-
out.add("Restrict the update document to $set / $unset for planner-safe writes.");
|
|
27088
|
-
}
|
|
27089
|
-
if (codes.has("permission_denied")) {
|
|
27090
|
-
out.add("Switch to a connection with sufficient permission only if the operation is intended.");
|
|
27091
|
-
}
|
|
27092
|
-
if (codes.has("table_blacklisted") || codes.has("blacklisted_column")) {
|
|
27093
|
-
out.add("Review blacklist rules before accessing sensitive data.");
|
|
27094
|
-
}
|
|
27095
|
-
if (codes.has("schema_cache_missing") || codes.has("schema_table_unknown")) {
|
|
27096
|
-
out.add("Refresh schema cache for the target collection before executing.");
|
|
27097
|
-
}
|
|
27098
|
-
out.add("Use --dry-run on the actual write command.");
|
|
27099
|
-
return Array.from(out);
|
|
27100
|
-
}
|
|
27101
|
-
function buildSuggestedCommands2(target, factors) {
|
|
27102
|
-
const codes = new Set(factors.map((f) => f.code));
|
|
27103
|
-
if (codes.has("schema_cache_missing") || codes.has("schema_table_unknown")) {
|
|
27104
|
-
return [`dbcli schema ${target} --format json`];
|
|
27105
|
-
}
|
|
27106
|
-
return [];
|
|
27107
|
-
}
|
|
27108
|
-
function flattenInsertPaths(data, prefix = "") {
|
|
27109
|
-
const out = [];
|
|
27110
|
-
for (const [k, v] of Object.entries(data)) {
|
|
27111
|
-
const path2 = prefix === "" ? k : `${prefix}.${k}`;
|
|
27112
|
-
out.push(path2);
|
|
27113
|
-
if (v && typeof v === "object" && !Array.isArray(v) && !(v instanceof Date)) {
|
|
27114
|
-
const ctor = v.constructor?.name;
|
|
27115
|
-
if (ctor === "Object") {
|
|
27116
|
-
out.push(...flattenInsertPaths(v, path2));
|
|
27117
|
-
}
|
|
27118
|
-
}
|
|
27119
|
-
}
|
|
27120
|
-
return out;
|
|
27121
|
-
}
|
|
27122
|
-
var MONGO_OPERATOR_TIER, MONGO_BROAD_FILTER_OPERATORS, analyzeMongoDmlRisk = (intent, context) => {
|
|
27123
|
-
const factors = [];
|
|
27124
|
-
applyPermission(intent.operation, context, factors);
|
|
27125
|
-
applyTableBlacklist(intent.target, context, factors);
|
|
27126
|
-
if (intent.operation === "insert") {
|
|
27127
|
-
applyColumnBlacklist(intent.target, flattenInsertPaths(intent.data), context, factors);
|
|
27128
|
-
} else if (intent.operation === "update") {
|
|
27129
|
-
const cls = classifyMongoUpdate(intent.set);
|
|
27130
|
-
for (const f of cls.tierFactors)
|
|
27131
|
-
pushFactor2(factors, f.code, f.severity, f.message);
|
|
27132
|
-
applyColumnBlacklist(intent.target, cls.fields, context, factors);
|
|
27133
|
-
const where = intent.where ?? {};
|
|
27134
|
-
if (Object.keys(where).length === 0) {
|
|
27135
|
-
pushFactor2(factors, "nonsql_filter_empty", "block", "MongoDB update filter is empty and would match every document.");
|
|
27136
|
-
} else if (!hasIdEquality(where)) {
|
|
27137
|
-
if (hasBroadFilter(where)) {
|
|
27138
|
-
pushFactor2(factors, "nonsql_filter_broad", "warn", "MongoDB update filter uses a broad operator and may match multiple documents.");
|
|
27139
|
-
} else if (hasNonIdEquality(where)) {
|
|
27140
|
-
pushFactor2(factors, "nonsql_missing_id", "warn", "MongoDB update filter does not use _id and may match multiple documents.");
|
|
27141
|
-
} else {
|
|
27142
|
-
pushFactor2(factors, "nonsql_filter_broad", "warn", "MongoDB update filter is not an _id equality and may match multiple documents.");
|
|
27143
|
-
}
|
|
27144
|
-
}
|
|
27145
|
-
} else {
|
|
27146
|
-
const where = intent.where ?? {};
|
|
27147
|
-
if (Object.keys(where).length === 0) {
|
|
27148
|
-
pushFactor2(factors, "nonsql_filter_empty", "block", "MongoDB delete filter is empty and would match every document.");
|
|
27149
|
-
} else if (!hasIdEquality(where)) {
|
|
27150
|
-
if (hasBroadFilter(where)) {
|
|
27151
|
-
pushFactor2(factors, "nonsql_filter_broad", "warn", "MongoDB delete filter uses a broad operator and may match multiple documents.");
|
|
27152
|
-
} else if (hasNonIdEquality(where)) {
|
|
27153
|
-
pushFactor2(factors, "nonsql_missing_id", "warn", "MongoDB delete filter does not use _id and may match multiple documents.");
|
|
27154
|
-
} else {
|
|
27155
|
-
pushFactor2(factors, "nonsql_filter_broad", "warn", "MongoDB delete filter is not an _id equality and may match multiple documents.");
|
|
27156
|
-
}
|
|
27157
|
-
}
|
|
27158
|
-
}
|
|
27159
|
-
applySchemaCoverage(intent.target, context, factors);
|
|
27160
|
-
return {
|
|
27161
|
-
decision: decide2(factors),
|
|
27162
|
-
operation: broadOperation(intent.operation),
|
|
27163
|
-
targetTables: [intent.target],
|
|
27164
|
-
riskFactors: factors,
|
|
27165
|
-
recommendations: buildRecommendations2(factors),
|
|
27166
|
-
suggestedCommands: buildSuggestedCommands2(intent.target, factors)
|
|
27167
|
-
};
|
|
27168
|
-
};
|
|
27169
|
-
var init_dml_plan = __esm(() => {
|
|
27170
|
-
init_permission_guard();
|
|
27171
|
-
MONGO_OPERATOR_TIER = {
|
|
27172
|
-
$set: "SAFE",
|
|
27173
|
-
$unset: "SAFE",
|
|
27174
|
-
$rename: "RENAME",
|
|
27175
|
-
$inc: "ARITHMETIC",
|
|
27176
|
-
$mul: "ARITHMETIC",
|
|
27177
|
-
$min: "ARITHMETIC",
|
|
27178
|
-
$max: "ARITHMETIC",
|
|
27179
|
-
$currentDate: "ARITHMETIC",
|
|
27180
|
-
$push: "ARRAY",
|
|
27181
|
-
$pull: "ARRAY",
|
|
27182
|
-
$pullAll: "ARRAY",
|
|
27183
|
-
$pop: "ARRAY",
|
|
27184
|
-
$addToSet: "ARRAY",
|
|
27185
|
-
$bit: "BITWISE",
|
|
27186
|
-
$where: "BLOCK"
|
|
27187
|
-
};
|
|
27188
|
-
MONGO_BROAD_FILTER_OPERATORS = new Set([
|
|
27189
|
-
"$regex",
|
|
27190
|
-
"$in",
|
|
27191
|
-
"$nin",
|
|
27192
|
-
"$gt",
|
|
27193
|
-
"$gte",
|
|
27194
|
-
"$lt",
|
|
27195
|
-
"$lte",
|
|
27196
|
-
"$ne",
|
|
27197
|
-
"$exists",
|
|
27198
|
-
"$or",
|
|
27199
|
-
"$and",
|
|
27200
|
-
"$nor",
|
|
27201
|
-
"$not",
|
|
27202
|
-
"$expr",
|
|
27203
|
-
"$text"
|
|
27204
|
-
]);
|
|
27205
|
-
});
|
|
27206
|
-
|
|
27207
27865
|
// src/core/redis/dml-plan.ts
|
|
27208
27866
|
function permissionSql2(operation) {
|
|
27209
27867
|
if (operation === "insert")
|
|
@@ -27245,17 +27903,17 @@ function applyPermission2(operation, context, factors) {
|
|
|
27245
27903
|
}
|
|
27246
27904
|
}
|
|
27247
27905
|
function applyTableBlacklist2(target, context, factors) {
|
|
27248
|
-
const blacklisted = (context.blacklist.tables ?? []).map(
|
|
27249
|
-
if (blacklisted.includes(target
|
|
27906
|
+
const blacklisted = (context.blacklist.tables ?? []).map(foldFieldPath);
|
|
27907
|
+
if (blacklisted.includes(foldFieldPath(target))) {
|
|
27250
27908
|
pushFactor3(factors, "table_blacklisted", "block", `Redis key ${target} is blacklisted.`);
|
|
27251
27909
|
}
|
|
27252
27910
|
}
|
|
27253
27911
|
function applyColumnBlacklist2(target, fields, context, factors) {
|
|
27254
27912
|
const columns = context.blacklist.columns ?? {};
|
|
27255
|
-
const lower = target
|
|
27913
|
+
const lower = foldFieldPath(target);
|
|
27256
27914
|
let blacklistedFields = [];
|
|
27257
27915
|
for (const [t2, cols] of Object.entries(columns)) {
|
|
27258
|
-
if (t2
|
|
27916
|
+
if (foldFieldPath(t2) === lower) {
|
|
27259
27917
|
blacklistedFields = cols;
|
|
27260
27918
|
break;
|
|
27261
27919
|
}
|
|
@@ -27353,6 +28011,7 @@ var analyzeRedisDmlRisk = (intent, context) => {
|
|
|
27353
28011
|
};
|
|
27354
28012
|
};
|
|
27355
28013
|
var init_dml_plan2 = __esm(() => {
|
|
28014
|
+
init_blacklist_fold();
|
|
27356
28015
|
init_permission_guard();
|
|
27357
28016
|
});
|
|
27358
28017
|
|
|
@@ -27402,17 +28061,17 @@ function applyPermission3(operation, context, factors) {
|
|
|
27402
28061
|
}
|
|
27403
28062
|
}
|
|
27404
28063
|
function applyTableBlacklist3(target, context, factors) {
|
|
27405
|
-
const blacklisted = (context.blacklist.tables ?? []).map(
|
|
27406
|
-
if (blacklisted.includes(target
|
|
28064
|
+
const blacklisted = (context.blacklist.tables ?? []).map(foldFieldPath);
|
|
28065
|
+
if (blacklisted.includes(foldFieldPath(target))) {
|
|
27407
28066
|
pushFactor4(factors, "table_blacklisted", "block", `Elasticsearch index ${target} is blacklisted.`);
|
|
27408
28067
|
}
|
|
27409
28068
|
}
|
|
27410
28069
|
function applyColumnBlacklist3(target, fields, context, factors) {
|
|
27411
28070
|
const columns = context.blacklist.columns ?? {};
|
|
27412
|
-
const lower = target
|
|
28071
|
+
const lower = foldFieldPath(target);
|
|
27413
28072
|
let blacklistedFields = [];
|
|
27414
28073
|
for (const [t2, cols] of Object.entries(columns)) {
|
|
27415
|
-
if (t2
|
|
28074
|
+
if (foldFieldPath(t2) === lower) {
|
|
27416
28075
|
blacklistedFields = cols;
|
|
27417
28076
|
break;
|
|
27418
28077
|
}
|
|
@@ -27495,6 +28154,7 @@ var analyzeElasticsearchDmlRisk = (intent, context) => {
|
|
|
27495
28154
|
};
|
|
27496
28155
|
};
|
|
27497
28156
|
var init_dml_plan3 = __esm(() => {
|
|
28157
|
+
init_blacklist_fold();
|
|
27498
28158
|
init_permission_guard();
|
|
27499
28159
|
});
|
|
27500
28160
|
|
|
@@ -27665,7 +28325,7 @@ async function insertCommand(table, options, command) {
|
|
|
27665
28325
|
const blacklistManager = new BlacklistManager(config);
|
|
27666
28326
|
const blacklistValidator = new BlacklistValidator(blacklistManager);
|
|
27667
28327
|
blacklistValidator.checkTableBlacklist("INSERT", table, []);
|
|
27668
|
-
blacklistValidator.checkColumnBlacklistOnWrite(table,
|
|
28328
|
+
blacklistValidator.checkColumnBlacklistOnWrite(table, flattenInsertPaths(data), "INSERT");
|
|
27669
28329
|
const preview = `SET ${table} ... (Redis Insert)`;
|
|
27670
28330
|
if (options.dryRun) {
|
|
27671
28331
|
const output = {
|
|
@@ -27730,7 +28390,7 @@ async function insertCommand(table, options, command) {
|
|
|
27730
28390
|
const blacklistManager = new BlacklistManager(config);
|
|
27731
28391
|
const blacklistValidator = new BlacklistValidator(blacklistManager);
|
|
27732
28392
|
blacklistValidator.checkTableBlacklist("INSERT", table, []);
|
|
27733
|
-
blacklistValidator.checkColumnBlacklistOnWrite(table,
|
|
28393
|
+
blacklistValidator.checkColumnBlacklistOnWrite(table, flattenInsertPaths(data), "INSERT");
|
|
27734
28394
|
const preview = previewInsert(table, data);
|
|
27735
28395
|
if (options.dryRun) {
|
|
27736
28396
|
const output = {
|
|
@@ -27848,9 +28508,11 @@ var init_insert = __esm(() => {
|
|
|
27848
28508
|
init_mutation_outcome();
|
|
27849
28509
|
init_config();
|
|
27850
28510
|
init_permission_guard();
|
|
28511
|
+
init_blacklist_manager();
|
|
27851
28512
|
init_blacklist_validator();
|
|
27852
28513
|
init_blacklist();
|
|
27853
28514
|
init_config_path();
|
|
28515
|
+
init_dml_plan();
|
|
27854
28516
|
init_dml_plan4();
|
|
27855
28517
|
init_integration_helper();
|
|
27856
28518
|
});
|
|
@@ -28008,7 +28670,11 @@ async function updateCommand(table, options, command) {
|
|
|
28008
28670
|
if (hasOperator) {
|
|
28009
28671
|
const operators = updateDoc;
|
|
28010
28672
|
for (const [op, payload] of Object.entries(operators)) {
|
|
28011
|
-
if ((
|
|
28673
|
+
if (!op.startsWith("$")) {
|
|
28674
|
+
writtenFields.add(op);
|
|
28675
|
+
continue;
|
|
28676
|
+
}
|
|
28677
|
+
if (payload && typeof payload === "object" && !Array.isArray(payload)) {
|
|
28012
28678
|
for (const k of Object.keys(payload))
|
|
28013
28679
|
writtenFields.add(k);
|
|
28014
28680
|
}
|
|
@@ -28169,6 +28835,7 @@ var init_update = __esm(() => {
|
|
|
28169
28835
|
init_mutation_outcome();
|
|
28170
28836
|
init_config();
|
|
28171
28837
|
init_permission_guard();
|
|
28838
|
+
init_blacklist_manager();
|
|
28172
28839
|
init_blacklist_validator();
|
|
28173
28840
|
init_blacklist();
|
|
28174
28841
|
init_config_path();
|
|
@@ -28468,6 +29135,7 @@ var init_delete = __esm(() => {
|
|
|
28468
29135
|
init_mutation_outcome();
|
|
28469
29136
|
init_config();
|
|
28470
29137
|
init_permission_guard();
|
|
29138
|
+
init_blacklist_manager();
|
|
28471
29139
|
init_blacklist_validator();
|
|
28472
29140
|
init_blacklist();
|
|
28473
29141
|
init_config_path();
|
|
@@ -28911,6 +29579,7 @@ var init_export = __esm(() => {
|
|
|
28911
29579
|
init_config();
|
|
28912
29580
|
init_prompts();
|
|
28913
29581
|
init_config_path();
|
|
29582
|
+
init_blacklist_manager();
|
|
28914
29583
|
init_blacklist_validator();
|
|
28915
29584
|
init_applied_limit();
|
|
28916
29585
|
init_integration_helper();
|
|
@@ -30147,6 +30816,7 @@ async function gatherContext(workspaceRoot, configPath, options = {}) {
|
|
|
30147
30816
|
}
|
|
30148
30817
|
var init_context = __esm(() => {
|
|
30149
30818
|
init_config();
|
|
30819
|
+
init_blacklist_manager();
|
|
30150
30820
|
init_loader();
|
|
30151
30821
|
init_snippet_paths();
|
|
30152
30822
|
init_semantic();
|
|
@@ -31363,11 +32033,8 @@ function auditBlacklistPatterns(cfg) {
|
|
|
31363
32033
|
}
|
|
31364
32034
|
return { warnings };
|
|
31365
32035
|
}
|
|
31366
|
-
function isValidTableNameForSystem(name,
|
|
31367
|
-
|
|
31368
|
-
return false;
|
|
31369
|
-
const literalMatching = system === "postgresql" || system === "mysql" || system === "mariadb" || system === "mongodb";
|
|
31370
|
-
return !(literalMatching && GLOB_CHARS.test(name));
|
|
32036
|
+
function isValidTableNameForSystem(name, _system) {
|
|
32037
|
+
return isValidTableName(name);
|
|
31371
32038
|
}
|
|
31372
32039
|
function isValidTableName(name) {
|
|
31373
32040
|
return VALID_TABLE_NAME.test(name);
|
|
@@ -31533,16 +32200,16 @@ function resolveBlacklistConfigPath(options, command) {
|
|
|
31533
32200
|
}
|
|
31534
32201
|
return DEFAULT_CONFIG_PATH;
|
|
31535
32202
|
}
|
|
31536
|
-
var DEFAULT_CONFIG_PATH = ".dbcli", VALID_TABLE_NAME, VALID_COLUMN_NAME,
|
|
32203
|
+
var DEFAULT_CONFIG_PATH = ".dbcli", VALID_TABLE_NAME, VALID_COLUMN_NAME, blacklistCommand, tableCmd, columnCmd;
|
|
31537
32204
|
var init_blacklist2 = __esm(() => {
|
|
31538
32205
|
init_config_v2();
|
|
31539
32206
|
init_esm();
|
|
31540
32207
|
init_message_loader();
|
|
31541
32208
|
init_config();
|
|
32209
|
+
init_path_matcher();
|
|
31542
32210
|
init_validation();
|
|
31543
|
-
VALID_TABLE_NAME = /^[a-zA-Z0-9_.*?:@[\]
|
|
31544
|
-
VALID_COLUMN_NAME = /^[a-zA-
|
|
31545
|
-
GLOB_CHARS = /[*?[\]]/;
|
|
32211
|
+
VALID_TABLE_NAME = /^[a-zA-Z0-9_.*?:@[\]\\-]+$/;
|
|
32212
|
+
VALID_COLUMN_NAME = /^[a-zA-Z0-9_*?[\]\\-]+$/;
|
|
31546
32213
|
blacklistCommand = new Command("blacklist").description(t("blacklist.description"));
|
|
31547
32214
|
blacklistCommand.command("list").description(t("blacklist.list_title")).option("--config <path>", "Path to .dbcli config file").option("--format <type>", "Output format: text, json", "text").action(async (options, command) => {
|
|
31548
32215
|
try {
|
|
@@ -31613,11 +32280,10 @@ async function checkAction(table, options) {
|
|
|
31613
32280
|
const checker = new HealthChecker(adapter);
|
|
31614
32281
|
const blacklistManager = new BlacklistManager(config);
|
|
31615
32282
|
const blacklistedColumns = getBlacklistedColumnSet(blacklistManager);
|
|
31616
|
-
const blacklistedTables = getBlacklistedTableSet(blacklistManager);
|
|
31617
32283
|
const checkTypes = options.checks ? options.checks.split(",") : undefined;
|
|
31618
32284
|
const sampleSize = parseInt(options.sample, 10) || 1e4;
|
|
31619
32285
|
if (table) {
|
|
31620
|
-
if (
|
|
32286
|
+
if (blacklistManager.isTableBlacklisted(table)) {
|
|
31621
32287
|
throw new Error(`Table "${table}" is blacklisted`);
|
|
31622
32288
|
}
|
|
31623
32289
|
const schema = await adapter.getTableSchema(table);
|
|
@@ -31632,7 +32298,7 @@ async function checkAction(table, options) {
|
|
|
31632
32298
|
const reports = [];
|
|
31633
32299
|
const skipped = [];
|
|
31634
32300
|
for (const t2 of tables) {
|
|
31635
|
-
if (
|
|
32301
|
+
if (blacklistManager.isTableBlacklisted(t2.name)) {
|
|
31636
32302
|
skipped.push(`${t2.name} (blacklisted)`);
|
|
31637
32303
|
continue;
|
|
31638
32304
|
}
|
|
@@ -31708,16 +32374,13 @@ function getBlacklistedColumnSet(manager) {
|
|
|
31708
32374
|
}
|
|
31709
32375
|
return result;
|
|
31710
32376
|
}
|
|
31711
|
-
function getBlacklistedTableSet(manager) {
|
|
31712
|
-
const state = manager.state;
|
|
31713
|
-
return state?.tables || new Set;
|
|
31714
|
-
}
|
|
31715
32377
|
var ALLOWED_FORMATS6, checkCommand;
|
|
31716
32378
|
var init_check = __esm(() => {
|
|
31717
32379
|
init_esm();
|
|
31718
32380
|
init_adapters();
|
|
31719
32381
|
init_config();
|
|
31720
32382
|
init_health_checker();
|
|
32383
|
+
init_blacklist_manager();
|
|
31721
32384
|
init_size_category();
|
|
31722
32385
|
init_validation();
|
|
31723
32386
|
init_connection_selector();
|
|
@@ -33594,9 +34257,8 @@ var init_proposals = __esm(() => {
|
|
|
33594
34257
|
});
|
|
33595
34258
|
|
|
33596
34259
|
// src/core/orm-drift/compare.ts
|
|
33597
|
-
function
|
|
33598
|
-
|
|
33599
|
-
return new RegExp(`^${escaped}$`);
|
|
34260
|
+
function starOnlyGlob(glob) {
|
|
34261
|
+
return glob.replace(/[?[\]\\]/g, "\\$&");
|
|
33600
34262
|
}
|
|
33601
34263
|
function tableMap(schema, defaultSchema) {
|
|
33602
34264
|
const tables = new Map;
|
|
@@ -33630,7 +34292,7 @@ function compareNormalized(orm, db, opts) {
|
|
|
33630
34292
|
const tableKeys = new Set([...ormTables.keys(), ...dbTables.keys()]);
|
|
33631
34293
|
const extraDefaultIgnore = opts.extraDefaultIgnore ?? [];
|
|
33632
34294
|
const defaultIgnore = [...DEFAULT_IGNORE, ...extraDefaultIgnore];
|
|
33633
|
-
const ignorePatterns = opts.ignore
|
|
34295
|
+
const ignorePatterns = opts.ignore;
|
|
33634
34296
|
const targetLabel = opts.targetLabel ?? "database";
|
|
33635
34297
|
const entries = [];
|
|
33636
34298
|
for (const tableKey of tableKeys) {
|
|
@@ -33641,7 +34303,7 @@ function compareNormalized(orm, db, opts) {
|
|
|
33641
34303
|
continue;
|
|
33642
34304
|
const table = qualifiedTableName(normalizedTable.identity);
|
|
33643
34305
|
const isDefaultIgnored = defaultIgnore.includes(normalizedTable.identity.table);
|
|
33644
|
-
if (isDefaultIgnored || ignorePatterns.some((pattern) => pattern
|
|
34306
|
+
if (isDefaultIgnored || ignorePatterns.some((pattern) => globMatches(starOnlyGlob(pattern), table))) {
|
|
33645
34307
|
entries.push(entryWithProposals({
|
|
33646
34308
|
category: "unmanaged",
|
|
33647
34309
|
severity: "info",
|
|
@@ -33823,6 +34485,7 @@ function compareIndexes(ormIndexes, dbIndexes, tableIdentity, table, ormSource,
|
|
|
33823
34485
|
var DEFAULT_IGNORE, entryOrder = (left, right) => codePointOrder(left.table, right.table) || codePointOrder(left.object, right.object) || codePointOrder(left.category, right.category) || codePointOrder(left.detail, right.detail);
|
|
33824
34486
|
var init_compare = __esm(() => {
|
|
33825
34487
|
init_normalized_schema();
|
|
34488
|
+
init_glob();
|
|
33826
34489
|
init_proposals();
|
|
33827
34490
|
DEFAULT_IGNORE = ["_prisma_migrations"];
|
|
33828
34491
|
});
|
|
@@ -34719,7 +35382,7 @@ function shouldEmbedRecent(opts) {
|
|
|
34719
35382
|
return opts.forAgent === true || opts.format === "json";
|
|
34720
35383
|
}
|
|
34721
35384
|
function briefifyForRecent(entry) {
|
|
34722
|
-
const phase = entry.metadata?.
|
|
35385
|
+
const phase = entry.metadata?.shell_phase;
|
|
34723
35386
|
return {
|
|
34724
35387
|
id: entry.id,
|
|
34725
35388
|
ts: entry.ts,
|
|
@@ -34813,7 +35476,10 @@ async function collectInspect(opts) {
|
|
|
34813
35476
|
let version = null;
|
|
34814
35477
|
if (!opts.noConnect && config?.connection && system) {
|
|
34815
35478
|
const probeTimeout = opts.probeTimeoutMs ?? DEFAULT_PROBE_MS;
|
|
34816
|
-
const adapter = AdapterFactory.createAdapter(
|
|
35479
|
+
const adapter = AdapterFactory.createAdapter({
|
|
35480
|
+
...config,
|
|
35481
|
+
blacklist: config.blacklist ?? { tables: [], columns: {} }
|
|
35482
|
+
});
|
|
34817
35483
|
let probeError = null;
|
|
34818
35484
|
const probe = (async () => {
|
|
34819
35485
|
await adapter.connect();
|
|
@@ -35207,8 +35873,12 @@ async function runDiagnostic(input) {
|
|
|
35207
35873
|
description: meta.description ?? ""
|
|
35208
35874
|
};
|
|
35209
35875
|
let prepared;
|
|
35876
|
+
let redisTokens = [];
|
|
35210
35877
|
try {
|
|
35211
35878
|
prepared = prepareExecution(input.snippet, { engine: input.engine, noLimit: false }, {}, {});
|
|
35879
|
+
if (engineFamily(input.engine) === "redis") {
|
|
35880
|
+
redisTokens = parseRedisCommand(prepared.rewrittenSql);
|
|
35881
|
+
}
|
|
35212
35882
|
} catch (err) {
|
|
35213
35883
|
const reason = err instanceof SavedQueryError ? err.message : `prepare failed: ${err.message}`;
|
|
35214
35884
|
return { ...base, rowCount: 0, rows: [], status: "skipped", reason, durationMs: 0 };
|
|
@@ -35216,7 +35886,7 @@ async function runDiagnostic(input) {
|
|
|
35216
35886
|
const family = engineFamily(input.engine);
|
|
35217
35887
|
const referencedTables = family === "sql" ? extractTableReferences(prepared.rewrittenSql, {
|
|
35218
35888
|
...ENGINE_DIALECT2[input.engine] ? { dialect: ENGINE_DIALECT2[input.engine] } : {}
|
|
35219
|
-
}) : 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)) : [];
|
|
35220
35890
|
if (input.blacklistValidator && referencedTables.length > 0) {
|
|
35221
35891
|
try {
|
|
35222
35892
|
input.blacklistValidator.checkTablesBlacklist("SELECT", referencedTables);
|
|
@@ -35228,7 +35898,7 @@ async function runDiagnostic(input) {
|
|
|
35228
35898
|
rowCount: 0,
|
|
35229
35899
|
rows: [],
|
|
35230
35900
|
status: "skipped",
|
|
35231
|
-
reason: err.message,
|
|
35901
|
+
reason: family === "redis" ? "Redis diagnostic skipped by blacklist" : err.message,
|
|
35232
35902
|
durationMs: 0
|
|
35233
35903
|
};
|
|
35234
35904
|
}
|
|
@@ -35244,8 +35914,8 @@ async function runDiagnostic(input) {
|
|
|
35244
35914
|
try {
|
|
35245
35915
|
outcome = await Promise.race([exec, timer]);
|
|
35246
35916
|
} catch (err) {
|
|
35247
|
-
outcome = "error";
|
|
35248
|
-
errorMessage2 = err.message;
|
|
35917
|
+
outcome = err instanceof BlacklistRejection ? "skipped" : "error";
|
|
35918
|
+
errorMessage2 = err instanceof BlacklistRejection ? "Redis diagnostic skipped by blacklist" : err.message;
|
|
35249
35919
|
}
|
|
35250
35920
|
const durationMs = Math.round(performance.now() - start);
|
|
35251
35921
|
if (outcome === "timeout") {
|
|
@@ -35258,12 +35928,12 @@ async function runDiagnostic(input) {
|
|
|
35258
35928
|
durationMs
|
|
35259
35929
|
};
|
|
35260
35930
|
}
|
|
35261
|
-
if (outcome === "error") {
|
|
35931
|
+
if (outcome === "error" || outcome === "skipped") {
|
|
35262
35932
|
return {
|
|
35263
35933
|
...base,
|
|
35264
35934
|
rowCount: 0,
|
|
35265
35935
|
rows: [],
|
|
35266
|
-
status:
|
|
35936
|
+
status: outcome,
|
|
35267
35937
|
reason: errorMessage2 ?? "unknown error",
|
|
35268
35938
|
durationMs
|
|
35269
35939
|
};
|
|
@@ -35283,6 +35953,9 @@ async function runDiagnostic(input) {
|
|
|
35283
35953
|
}
|
|
35284
35954
|
var ENGINE_DIALECT2;
|
|
35285
35955
|
var init_run_diagnostic = __esm(() => {
|
|
35956
|
+
init_redis_adapter();
|
|
35957
|
+
init_blacklist_enforcer();
|
|
35958
|
+
init_types3();
|
|
35286
35959
|
init_saved_queries();
|
|
35287
35960
|
init_strategies();
|
|
35288
35961
|
init_sql_tables();
|
|
@@ -35346,7 +36019,7 @@ async function collectReport(opts) {
|
|
|
35346
36019
|
warnings.push({ severity: "warn", message: "connection config missing after probe" });
|
|
35347
36020
|
return finalize({ context, sections: [], warnings, generatedAt });
|
|
35348
36021
|
}
|
|
35349
|
-
const adapter = AdapterFactory.createAdapter(config
|
|
36022
|
+
const adapter = AdapterFactory.createAdapter(config);
|
|
35350
36023
|
const blacklistValidator = new BlacklistValidator(new BlacklistManager(config));
|
|
35351
36024
|
const sectionEvidence = new Map;
|
|
35352
36025
|
for (const id of sections)
|
|
@@ -35426,6 +36099,7 @@ var init_collector2 = __esm(() => {
|
|
|
35426
36099
|
init_saved_queries();
|
|
35427
36100
|
init_select_snippets();
|
|
35428
36101
|
init_run_diagnostic();
|
|
36102
|
+
init_blacklist_manager();
|
|
35429
36103
|
init_blacklist_validator();
|
|
35430
36104
|
init_section_map();
|
|
35431
36105
|
init_types7();
|
|
@@ -37431,7 +38105,7 @@ function parseTailN(raw) {
|
|
|
37431
38105
|
return requested;
|
|
37432
38106
|
}
|
|
37433
38107
|
function briefify(entry) {
|
|
37434
|
-
const phase = entry.metadata?.
|
|
38108
|
+
const phase = entry.metadata?.shell_phase;
|
|
37435
38109
|
return {
|
|
37436
38110
|
ts: entry.ts,
|
|
37437
38111
|
command: entry.command,
|
|
@@ -37486,8 +38160,8 @@ function renderTailAllTable(envelopes) {
|
|
|
37486
38160
|
}
|
|
37487
38161
|
function briefifyShow(entry) {
|
|
37488
38162
|
const { metadata, redacted_query, ...rest } = entry;
|
|
37489
|
-
const phase = metadata?.
|
|
37490
|
-
return phase === undefined ? rest : { ...rest, metadata: {
|
|
38163
|
+
const phase = metadata?.shell_phase;
|
|
38164
|
+
return phase === undefined ? rest : { ...rest, metadata: { shell_phase: phase } };
|
|
37491
38165
|
}
|
|
37492
38166
|
function renderEntryTable(entry) {
|
|
37493
38167
|
const lines = [];
|
|
@@ -38234,6 +38908,7 @@ async function collectElasticsearchDoctorResults(config, runtimeOverrides = {})
|
|
|
38234
38908
|
var ALLOWED_FORMATS15, SENSITIVE_PATTERNS, runDoctorChecks, defaultDoctorCollectorRuntime, doctorCommand;
|
|
38235
38909
|
var init_doctor = __esm(() => {
|
|
38236
38910
|
init_esm();
|
|
38911
|
+
init_blacklist_fold();
|
|
38237
38912
|
init_colors();
|
|
38238
38913
|
init_config();
|
|
38239
38914
|
init_adapters();
|
|
@@ -38350,7 +39025,8 @@ var init_doctor = __esm(() => {
|
|
|
38350
39025
|
for (const col of columns) {
|
|
38351
39026
|
const colLower = col.toLowerCase();
|
|
38352
39027
|
const isSensitive = SENSITIVE_PATTERNS.some((p) => colLower.includes(p));
|
|
38353
|
-
|
|
39028
|
+
const foldedRules = new Set([...blacklisted].map(foldFieldPath));
|
|
39029
|
+
if (isSensitive && !foldedRules.has(foldFieldPath(col))) {
|
|
38354
39030
|
unprotected.push(`${table}.${col}`);
|
|
38355
39031
|
}
|
|
38356
39032
|
}
|
|
@@ -42015,6 +42691,7 @@ var init_snapshot = __esm(() => {
|
|
|
42015
42691
|
init_config();
|
|
42016
42692
|
init_config_path();
|
|
42017
42693
|
init_validation();
|
|
42694
|
+
init_blacklist_manager();
|
|
42018
42695
|
init_blacklist_validator();
|
|
42019
42696
|
init_query_executor();
|
|
42020
42697
|
init_permission_guard();
|
|
@@ -42683,6 +43360,7 @@ var init_assert = __esm(() => {
|
|
|
42683
43360
|
init_config();
|
|
42684
43361
|
init_config_path();
|
|
42685
43362
|
init_validation();
|
|
43363
|
+
init_blacklist_manager();
|
|
42686
43364
|
init_blacklist_validator();
|
|
42687
43365
|
init_query_executor();
|
|
42688
43366
|
init_saved_queries();
|
|
@@ -44861,6 +45539,7 @@ var init_verify2 = __esm(() => {
|
|
|
44861
45539
|
init_adapters();
|
|
44862
45540
|
init_config();
|
|
44863
45541
|
init_config_path();
|
|
45542
|
+
init_blacklist_manager();
|
|
44864
45543
|
init_blacklist_validator();
|
|
44865
45544
|
init_query_executor();
|
|
44866
45545
|
init_query_risk_analyzer();
|
|
@@ -45612,15 +46291,17 @@ class ReplEngine {
|
|
|
45612
46291
|
adapter;
|
|
45613
46292
|
context;
|
|
45614
46293
|
writeGate;
|
|
46294
|
+
auditSink;
|
|
45615
46295
|
state;
|
|
45616
46296
|
buffer;
|
|
45617
46297
|
history;
|
|
45618
46298
|
formatter;
|
|
45619
46299
|
config;
|
|
45620
|
-
constructor(adapter, context, historyPath, config = null, writeGate = null) {
|
|
46300
|
+
constructor(adapter, context, historyPath, config = null, writeGate = null, auditSink = null) {
|
|
45621
46301
|
this.adapter = adapter;
|
|
45622
46302
|
this.context = context;
|
|
45623
46303
|
this.writeGate = writeGate;
|
|
46304
|
+
this.auditSink = auditSink;
|
|
45624
46305
|
this.state = { format: "table", timing: false, connected: true, noLimit: false };
|
|
45625
46306
|
this.buffer = new MultilineBuffer;
|
|
45626
46307
|
this.history = new HistoryManager(historyPath);
|
|
@@ -45752,10 +46433,11 @@ class ReplEngine {
|
|
|
45752
46433
|
} else {
|
|
45753
46434
|
const permResult = checkPermission(sql, this.context.permission, SQL_DIALECTS.find((dialect) => dialect === this.context.system));
|
|
45754
46435
|
if (!permResult.allowed) {
|
|
46436
|
+
await this.auditSink?.({ phase: "outcome", success: false, statement: sql });
|
|
45755
46437
|
return {
|
|
45756
46438
|
action: "continue",
|
|
45757
46439
|
output: import_picocolors3.default.red(t_vars("shell.error_permission", {
|
|
45758
|
-
required: permResult.
|
|
46440
|
+
required: permResult.requiredPermission ?? "admin",
|
|
45759
46441
|
current: this.context.permission
|
|
45760
46442
|
}))
|
|
45761
46443
|
};
|
|
@@ -45771,18 +46453,22 @@ class ReplEngine {
|
|
|
45771
46453
|
} catch (error) {
|
|
45772
46454
|
if (!(error instanceof BlacklistError))
|
|
45773
46455
|
throw error;
|
|
46456
|
+
await this.auditSink?.({ phase: "outcome", success: false, statement: sql });
|
|
45774
46457
|
return {
|
|
45775
46458
|
action: "continue",
|
|
45776
46459
|
output: import_picocolors3.default.red(t_vars("shell.error_blacklisted", { table: error.message }))
|
|
45777
46460
|
};
|
|
45778
46461
|
}
|
|
45779
46462
|
}
|
|
45780
|
-
if (this.writeGate && !await this.writeGate(sql))
|
|
46463
|
+
if (this.writeGate && !await this.writeGate(sql)) {
|
|
46464
|
+
await this.auditSink?.({ phase: "outcome", success: false, statement: sql });
|
|
45781
46465
|
return { action: "continue" };
|
|
46466
|
+
}
|
|
45782
46467
|
return this.runStatement(sql, blacklistValidator, referencedTables);
|
|
45783
46468
|
}
|
|
45784
46469
|
async runStatement(sql, blacklistValidator, referencedTables) {
|
|
45785
46470
|
const startTime = Date.now();
|
|
46471
|
+
await this.auditSink?.({ phase: "attempt", success: true, statement: sql });
|
|
45786
46472
|
try {
|
|
45787
46473
|
const result = await this.adapter.execute(sql, undefined, {
|
|
45788
46474
|
noLimit: this.state.noLimit
|
|
@@ -45808,6 +46494,7 @@ class ReplEngine {
|
|
|
45808
46494
|
` + import_picocolors3.default.dim(t_vars("shell.timing_display", { ms: String(elapsed) }));
|
|
45809
46495
|
}
|
|
45810
46496
|
this.state = { ...this.state, connected: true };
|
|
46497
|
+
await this.auditSink?.({ phase: "outcome", success: true, statement: sql });
|
|
45811
46498
|
return { action: "continue", output };
|
|
45812
46499
|
} catch (error) {
|
|
45813
46500
|
if (this.isConnectionError(error) && this.state.connected) {
|
|
@@ -45819,12 +46506,14 @@ class ReplEngine {
|
|
|
45819
46506
|
console.error(import_picocolors3.default.green(t("shell.error_reconnect_success")));
|
|
45820
46507
|
return this.runStatement(sql, blacklistValidator, referencedTables);
|
|
45821
46508
|
} catch (reconnectError) {
|
|
46509
|
+
await this.auditSink?.({ phase: "outcome", success: false, statement: sql });
|
|
45822
46510
|
return {
|
|
45823
46511
|
action: "continue",
|
|
45824
46512
|
output: import_picocolors3.default.red(t_vars("shell.error_reconnect_failed", { message: reconnectError.message }))
|
|
45825
46513
|
};
|
|
45826
46514
|
}
|
|
45827
46515
|
}
|
|
46516
|
+
await this.auditSink?.({ phase: "outcome", success: false, statement: sql });
|
|
45828
46517
|
return {
|
|
45829
46518
|
action: "continue",
|
|
45830
46519
|
output: import_picocolors3.default.red(t_vars("shell.error_sql_failed", { message: error.message }))
|
|
@@ -45859,6 +46548,7 @@ var init_repl_engine = __esm(() => {
|
|
|
45859
46548
|
init_query_result_formatter();
|
|
45860
46549
|
init_message_loader();
|
|
45861
46550
|
init_sql_tables();
|
|
46551
|
+
init_blacklist_manager();
|
|
45862
46552
|
init_blacklist_validator();
|
|
45863
46553
|
init_blacklist();
|
|
45864
46554
|
import_picocolors3 = __toESM(require_picocolors(), 1);
|
|
@@ -46146,30 +46836,25 @@ function findStrings(node) {
|
|
|
46146
46836
|
return found;
|
|
46147
46837
|
}
|
|
46148
46838
|
function namesProtectedField(term, protectedFields) {
|
|
46149
|
-
|
|
46150
|
-
|
|
46151
|
-
|
|
46152
|
-
|
|
46153
|
-
return false;
|
|
46154
|
-
for (let start = 0;start < parts.length; start += 1) {
|
|
46155
|
-
for (let end = start + 1;end <= parts.length; end += 1) {
|
|
46156
|
-
if (protectedFields.has(parts.slice(start, end).join(".")))
|
|
46157
|
-
return true;
|
|
46158
|
-
}
|
|
46159
|
-
}
|
|
46160
|
-
return false;
|
|
46839
|
+
return reachesProtected(foldFieldPath(term), contiguousRulesFor(protectedFields));
|
|
46840
|
+
}
|
|
46841
|
+
function reachesProtected(folded, rules) {
|
|
46842
|
+
return reachesProtectedPath(folded, rules);
|
|
46161
46843
|
}
|
|
46162
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) {
|
|
46163
46848
|
if (Array.isArray(node))
|
|
46164
|
-
return node.map((item) =>
|
|
46849
|
+
return node.map((item) => redactWithGlobs(item, rules, trail));
|
|
46165
46850
|
if (node === null || typeof node !== "object")
|
|
46166
46851
|
return node;
|
|
46167
46852
|
const out = {};
|
|
46168
46853
|
for (const [key, value] of Object.entries(node)) {
|
|
46169
|
-
const path4 = [...trail, key];
|
|
46170
|
-
if (
|
|
46854
|
+
const path4 = [...trail, ...foldFieldPath(key).split(".")];
|
|
46855
|
+
if (reachesProtectedSegments(path4, rules))
|
|
46171
46856
|
continue;
|
|
46172
|
-
out[key] =
|
|
46857
|
+
out[key] = redactWithGlobs(value, rules, path4);
|
|
46173
46858
|
}
|
|
46174
46859
|
return out;
|
|
46175
46860
|
}
|
|
@@ -46222,7 +46907,9 @@ function assertNoBlacklistedIndexNamed(args) {
|
|
|
46222
46907
|
}
|
|
46223
46908
|
}
|
|
46224
46909
|
function collectProtectedFields(blacklistColumns) {
|
|
46225
|
-
|
|
46910
|
+
const fields = new Set(Object.values(blacklistColumns).flat().map(normalizeBlacklistEntry).filter((field) => field.length > 0));
|
|
46911
|
+
contiguousRulesFor(fields);
|
|
46912
|
+
return fields;
|
|
46226
46913
|
}
|
|
46227
46914
|
function queryStringFieldTerms(query) {
|
|
46228
46915
|
const CONSERVATIVE = /[\s,:()"'[\]{}]+/;
|
|
@@ -46264,6 +46951,9 @@ var init_es_shell_guards = __esm(() => {
|
|
|
46264
46951
|
init_message_loader();
|
|
46265
46952
|
init_es_index_target();
|
|
46266
46953
|
init_elasticsearch();
|
|
46954
|
+
init_blacklist_fold();
|
|
46955
|
+
init_blacklist_manager();
|
|
46956
|
+
init_path_matcher();
|
|
46267
46957
|
UNSCOPED_METADATA_PREFIXES = ["_cat", "_cluster", "_nodes", "_license"];
|
|
46268
46958
|
UNSCOPED_METADATA_WITHHELD = {
|
|
46269
46959
|
_cluster: new Set(["state"]),
|
|
@@ -46400,7 +47090,7 @@ async function executeEsBlock(block2, session) {
|
|
|
46400
47090
|
target: record4.target,
|
|
46401
47091
|
sql: record4.statement,
|
|
46402
47092
|
sideEffectTier: record4.tierOverride,
|
|
46403
|
-
metadata: {
|
|
47093
|
+
metadata: { shell_phase: record4.phase }
|
|
46404
47094
|
})
|
|
46405
47095
|
});
|
|
46406
47096
|
console.log(JSON.stringify(res, null, 2));
|
|
@@ -46600,6 +47290,25 @@ var init_shell_write_gate = __esm(() => {
|
|
|
46600
47290
|
init_write_gate_guard();
|
|
46601
47291
|
});
|
|
46602
47292
|
|
|
47293
|
+
// src/commands/shell-audit-sink.ts
|
|
47294
|
+
var exports_shell_audit_sink = {};
|
|
47295
|
+
__export(exports_shell_audit_sink, {
|
|
47296
|
+
createShellAuditSink: () => createShellAuditSink
|
|
47297
|
+
});
|
|
47298
|
+
function createShellAuditSink(options) {
|
|
47299
|
+
const write = options.write ?? writeAuditEntryResult;
|
|
47300
|
+
return async (record4) => {
|
|
47301
|
+
return await write(options.config, "shell", { config: options.configPath }, {
|
|
47302
|
+
success: record4.success,
|
|
47303
|
+
sql: record4.statement,
|
|
47304
|
+
metadata: { shell_phase: record4.phase }
|
|
47305
|
+
});
|
|
47306
|
+
};
|
|
47307
|
+
}
|
|
47308
|
+
var init_shell_audit_sink = __esm(() => {
|
|
47309
|
+
init_integration_helper();
|
|
47310
|
+
});
|
|
47311
|
+
|
|
46603
47312
|
// src/commands/shell.ts
|
|
46604
47313
|
var exports_shell = {};
|
|
46605
47314
|
__export(exports_shell, {
|
|
@@ -46717,7 +47426,11 @@ async function runShell(options, configPath) {
|
|
|
46717
47426
|
dialect,
|
|
46718
47427
|
ask: asker.ask
|
|
46719
47428
|
}) : null;
|
|
46720
|
-
const
|
|
47429
|
+
const auditSink = (await Promise.resolve().then(() => (init_shell_audit_sink(), exports_shell_audit_sink))).createShellAuditSink({
|
|
47430
|
+
config,
|
|
47431
|
+
configPath
|
|
47432
|
+
});
|
|
47433
|
+
const engine = new ReplEngine(adapter, context, HISTORY_PATH, config, writeGate, auditSink);
|
|
46721
47434
|
const complete = createCompleter(context);
|
|
46722
47435
|
console.error(import_picocolors5.default.bold(t_vars("shell.welcome", {
|
|
46723
47436
|
system: config.connection.system,
|
|
@@ -47549,6 +48262,7 @@ var init_migrate = __esm(() => {
|
|
|
47549
48262
|
init_ddl2();
|
|
47550
48263
|
init_ddl_executor();
|
|
47551
48264
|
init_mutation_confirm();
|
|
48265
|
+
init_blacklist_manager();
|
|
47552
48266
|
init_config_path();
|
|
47553
48267
|
migrateCommand = new Command("migrate").description(t("migrate.description"));
|
|
47554
48268
|
addExecOpts(migrateCommand.command("create <table>").description(t("migrate.create_description")).option("--column <spec...>", 'Column definitions (e.g., "id:serial:pk" "name:varchar(50):not-null")')).action(async (table, opts, command) => {
|
|
@@ -47851,7 +48565,9 @@ function envVarNameFor(connName, field) {
|
|
|
47851
48565
|
const slug = connName.toUpperCase().replace(/[^A-Z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
47852
48566
|
return `DBCLI_${slug}_${field.toUpperCase()}`;
|
|
47853
48567
|
}
|
|
47854
|
-
var init_config_v2_mutations = () => {
|
|
48568
|
+
var init_config_v2_mutations = __esm(() => {
|
|
48569
|
+
init_validation();
|
|
48570
|
+
});
|
|
47855
48571
|
|
|
47856
48572
|
// src/core/env-file-writer.ts
|
|
47857
48573
|
import { chmod as chmod2, mkdir as mkdir18, readFile as readFile9, writeFile as writeFile9 } from "fs/promises";
|
|
@@ -48023,7 +48739,7 @@ async function verifyNewPassword(configPath, target, connectionName, password) {
|
|
|
48023
48739
|
process.env[target.varName] = password;
|
|
48024
48740
|
try {
|
|
48025
48741
|
const config = await configModule.read(storagePath, connectionName);
|
|
48026
|
-
const adapter = AdapterFactory.
|
|
48742
|
+
const adapter = AdapterFactory.createAdapterWithoutRules({
|
|
48027
48743
|
...config.connection,
|
|
48028
48744
|
password
|
|
48029
48745
|
});
|
|
@@ -52231,6 +52947,9 @@ var init_data_access = __esm(() => {
|
|
|
52231
52947
|
});
|
|
52232
52948
|
|
|
52233
52949
|
// src/core/orm-drift/change-set.ts
|
|
52950
|
+
function starOnlyGlob2(glob) {
|
|
52951
|
+
return glob.replace(/[?[\]\\]/g, "\\$&");
|
|
52952
|
+
}
|
|
52234
52953
|
function normalizeProposedChanges(input) {
|
|
52235
52954
|
validateInput(input);
|
|
52236
52955
|
const gaps = [
|
|
@@ -52288,7 +53007,7 @@ function tableMap2(schema, defaultSchema, side) {
|
|
|
52288
53007
|
return tables;
|
|
52289
53008
|
}
|
|
52290
53009
|
function ignoredTableKeys(input, declared, baseline, gaps) {
|
|
52291
|
-
const patterns = [...DEFAULT_IGNORE2, ...input.ignore ?? []]
|
|
53010
|
+
const patterns = [...DEFAULT_IGNORE2, ...input.ignore ?? []];
|
|
52292
53011
|
const ignored = new Set;
|
|
52293
53012
|
for (const [side, tables] of [
|
|
52294
53013
|
["declared", declared],
|
|
@@ -52296,7 +53015,7 @@ function ignoredTableKeys(input, declared, baseline, gaps) {
|
|
|
52296
53015
|
]) {
|
|
52297
53016
|
for (const [key, resolved] of tables) {
|
|
52298
53017
|
const name = qualifiedTableName(resolved.identity);
|
|
52299
|
-
if (!patterns.some((pattern) => pattern
|
|
53018
|
+
if (!patterns.some((pattern) => globMatches(starOnlyGlob2(pattern), name) || globMatches(starOnlyGlob2(pattern), resolved.identity.table)))
|
|
52300
53019
|
continue;
|
|
52301
53020
|
if (ignored.has(key))
|
|
52302
53021
|
continue;
|
|
@@ -52474,10 +53193,6 @@ function unparsedGaps(schema, side) {
|
|
|
52474
53193
|
reason: entry.reason
|
|
52475
53194
|
}));
|
|
52476
53195
|
}
|
|
52477
|
-
function globToRegex3(glob) {
|
|
52478
|
-
const escaped = glob.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
52479
|
-
return new RegExp(`^${escaped}$`);
|
|
52480
|
-
}
|
|
52481
53196
|
function gapOrder(left, right) {
|
|
52482
53197
|
return codePointOrder5(left.side, right.side) || codePointOrder5(left.kind, right.kind) || codePointOrder5(left.location, right.location) || codePointOrder5(left.reason, right.reason);
|
|
52483
53198
|
}
|
|
@@ -52493,6 +53208,7 @@ function codePointOrder5(left, right) {
|
|
|
52493
53208
|
}
|
|
52494
53209
|
var DEFAULT_IGNORE2, NormalizedChangeSetError;
|
|
52495
53210
|
var init_change_set = __esm(() => {
|
|
53211
|
+
init_glob();
|
|
52496
53212
|
DEFAULT_IGNORE2 = ["_prisma_migrations"];
|
|
52497
53213
|
NormalizedChangeSetError = class NormalizedChangeSetError extends Error {
|
|
52498
53214
|
constructor(message) {
|