cognium-dev 3.132.0 → 3.133.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +479 -1
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -22610,6 +22610,9 @@ class LanguageSourcesPass {
|
|
|
22610
22610
|
for (const finding of findGoLocationHeaderOpenRedirectFindings(code, graph.ir.meta.file)) {
|
|
22611
22611
|
ctx.addFinding(finding);
|
|
22612
22612
|
}
|
|
22613
|
+
for (const finding of findGoPluginOpenCodeInjectionFindings(code, graph.ir.meta.file)) {
|
|
22614
|
+
ctx.addFinding(finding);
|
|
22615
|
+
}
|
|
22613
22616
|
}
|
|
22614
22617
|
if (language === "python") {
|
|
22615
22618
|
additionalSanitizers.push(...findPythonNetlocAllowlistGuardSanitizers(code));
|
|
@@ -22631,6 +22634,9 @@ class LanguageSourcesPass {
|
|
|
22631
22634
|
for (const finding of findPythonHeadersSubscriptOpenRedirectFindings(code, graph.ir.meta.file)) {
|
|
22632
22635
|
ctx.addFinding(finding);
|
|
22633
22636
|
}
|
|
22637
|
+
for (const finding of findPythonInteractiveInterpreterCodeInjectionFindings(code, graph.ir.meta.file)) {
|
|
22638
|
+
ctx.addFinding(finding);
|
|
22639
|
+
}
|
|
22634
22640
|
}
|
|
22635
22641
|
if (language === "rust") {
|
|
22636
22642
|
additionalSanitizers.push(...findRustSetAllowlistGuardSanitizers(code));
|
|
@@ -22655,6 +22661,9 @@ class LanguageSourcesPass {
|
|
|
22655
22661
|
for (const finding of findRustAppendHeaderTupleOpenRedirectFindings(code, graph.ir.meta.file)) {
|
|
22656
22662
|
ctx.addFinding(finding);
|
|
22657
22663
|
}
|
|
22664
|
+
for (const finding of findRustEvalCrateCodeInjectionFindings(code, graph.ir.meta.file)) {
|
|
22665
|
+
ctx.addFinding(finding);
|
|
22666
|
+
}
|
|
22658
22667
|
}
|
|
22659
22668
|
if (language === "javascript" || language === "typescript" || language === "htmljs") {
|
|
22660
22669
|
additionalSanitizers.push(...findJsSafeJsonParseSanitizers(code));
|
|
@@ -22676,6 +22685,9 @@ class LanguageSourcesPass {
|
|
|
22676
22685
|
for (const finding of findJsDomOpenRedirectFindings(code, graph.ir.meta.file)) {
|
|
22677
22686
|
ctx.addFinding(finding);
|
|
22678
22687
|
}
|
|
22688
|
+
for (const finding of findJsIndirectEvalCodeInjectionFindings(code, graph.ir.meta.file)) {
|
|
22689
|
+
ctx.addFinding(finding);
|
|
22690
|
+
}
|
|
22679
22691
|
}
|
|
22680
22692
|
if (language === "java") {
|
|
22681
22693
|
additionalSanitizers.push(...findJavaSafeJsonParseSanitizers(code));
|
|
@@ -26202,6 +26214,472 @@ function findJsDomOpenRedirectFindings(code, file) {
|
|
|
26202
26214
|
}
|
|
26203
26215
|
return out2;
|
|
26204
26216
|
}
|
|
26217
|
+
function findGoPluginOpenCodeInjectionFindings(code, file) {
|
|
26218
|
+
const findings = [];
|
|
26219
|
+
if (typeof code !== "string" || code.length === 0)
|
|
26220
|
+
return findings;
|
|
26221
|
+
if (!/\bplugin\s*\.\s*(?:Open|Lookup)\s*\(/.test(code))
|
|
26222
|
+
return findings;
|
|
26223
|
+
const lines = code.split(`
|
|
26224
|
+
`);
|
|
26225
|
+
const reqExtractRe = /\b\w+\s*\.\s*(?:FormValue|PostFormValue|URL\.Query\(\)\.Get|Header\.Get|Cookie)\s*\(/;
|
|
26226
|
+
const httpReqParamRe = /\*\s*http\.Request\b/;
|
|
26227
|
+
const callRe = /\bplugin\s*\.\s*(?:Open|Lookup)\s*\(\s*([^)]*)\s*\)/;
|
|
26228
|
+
const sinkLabel = (op) => op === "Lookup" ? "Go plugin.Lookup" : "Go plugin.Open";
|
|
26229
|
+
const funcs = [];
|
|
26230
|
+
let cur = null;
|
|
26231
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
26232
|
+
const t = lines[i2].trim();
|
|
26233
|
+
if (/^func\b/.test(t)) {
|
|
26234
|
+
if (cur) {
|
|
26235
|
+
cur.end = i2 - 1;
|
|
26236
|
+
funcs.push(cur);
|
|
26237
|
+
}
|
|
26238
|
+
cur = { start: i2, end: lines.length - 1 };
|
|
26239
|
+
}
|
|
26240
|
+
}
|
|
26241
|
+
if (cur)
|
|
26242
|
+
funcs.push(cur);
|
|
26243
|
+
for (const fn of funcs) {
|
|
26244
|
+
const header = lines[fn.start];
|
|
26245
|
+
if (!httpReqParamRe.test(header))
|
|
26246
|
+
continue;
|
|
26247
|
+
const taintedVars = new Set;
|
|
26248
|
+
for (let pass = 0;pass < 3; pass++) {
|
|
26249
|
+
const before = taintedVars.size;
|
|
26250
|
+
for (let i2 = fn.start;i2 <= fn.end; i2++) {
|
|
26251
|
+
const line = lines[i2];
|
|
26252
|
+
const trimmed = line.trim();
|
|
26253
|
+
const assignMatch = trimmed.match(/^(\w+)\s*(?::=|=)\s*(.+?)(?:\s*\/\/.*)?$/);
|
|
26254
|
+
if (!assignMatch)
|
|
26255
|
+
continue;
|
|
26256
|
+
const lhs = assignMatch[1];
|
|
26257
|
+
const rhs = assignMatch[2];
|
|
26258
|
+
if (taintedVars.has(lhs))
|
|
26259
|
+
continue;
|
|
26260
|
+
if (reqExtractRe.test(rhs)) {
|
|
26261
|
+
taintedVars.add(lhs);
|
|
26262
|
+
continue;
|
|
26263
|
+
}
|
|
26264
|
+
for (const v of taintedVars) {
|
|
26265
|
+
const re = new RegExp(`\\b${v}\\b`);
|
|
26266
|
+
if (re.test(rhs)) {
|
|
26267
|
+
taintedVars.add(lhs);
|
|
26268
|
+
break;
|
|
26269
|
+
}
|
|
26270
|
+
}
|
|
26271
|
+
}
|
|
26272
|
+
if (taintedVars.size === before)
|
|
26273
|
+
break;
|
|
26274
|
+
}
|
|
26275
|
+
if (taintedVars.size === 0)
|
|
26276
|
+
continue;
|
|
26277
|
+
for (let i2 = fn.start;i2 <= fn.end; i2++) {
|
|
26278
|
+
const line = lines[i2];
|
|
26279
|
+
const m = line.match(callRe);
|
|
26280
|
+
if (!m)
|
|
26281
|
+
continue;
|
|
26282
|
+
const arg = m[1].trim();
|
|
26283
|
+
if (arg.length === 0)
|
|
26284
|
+
continue;
|
|
26285
|
+
if (/^"[^"]*"$/.test(arg))
|
|
26286
|
+
continue;
|
|
26287
|
+
let tainted = false;
|
|
26288
|
+
if (reqExtractRe.test(arg))
|
|
26289
|
+
tainted = true;
|
|
26290
|
+
else {
|
|
26291
|
+
for (const v of taintedVars) {
|
|
26292
|
+
const re = new RegExp(`\\b${v}\\b`);
|
|
26293
|
+
if (re.test(arg)) {
|
|
26294
|
+
tainted = true;
|
|
26295
|
+
break;
|
|
26296
|
+
}
|
|
26297
|
+
}
|
|
26298
|
+
}
|
|
26299
|
+
if (!tainted)
|
|
26300
|
+
continue;
|
|
26301
|
+
const op = /\bplugin\s*\.\s*Lookup\b/.test(line) ? "Lookup" : "Open";
|
|
26302
|
+
findings.push({
|
|
26303
|
+
id: `code_injection-${file}-${i2 + 1}-go-plugin-${op.toLowerCase()}`,
|
|
26304
|
+
pass: "language-sources",
|
|
26305
|
+
category: "security",
|
|
26306
|
+
rule_id: "code_injection",
|
|
26307
|
+
cwe: "CWE-94",
|
|
26308
|
+
severity: "critical",
|
|
26309
|
+
level: "error",
|
|
26310
|
+
message: `Code injection: ${sinkLabel(op)} called with a path/symbol derived ` + "from an *http.Request without an allow-list. Loading a plugin " + "runs its init() and exposes arbitrary exported symbols. Restrict " + "the path to a trusted directory or use a fixed allow-list.",
|
|
26311
|
+
file,
|
|
26312
|
+
line: i2 + 1,
|
|
26313
|
+
snippet: line.trim()
|
|
26314
|
+
});
|
|
26315
|
+
}
|
|
26316
|
+
}
|
|
26317
|
+
return findings;
|
|
26318
|
+
}
|
|
26319
|
+
function findJsIndirectEvalCodeInjectionFindings(code, file) {
|
|
26320
|
+
const findings = [];
|
|
26321
|
+
if (typeof code !== "string" || code.length === 0)
|
|
26322
|
+
return findings;
|
|
26323
|
+
if (!/\beval\b/.test(code))
|
|
26324
|
+
return findings;
|
|
26325
|
+
const lines = code.split(`
|
|
26326
|
+
`);
|
|
26327
|
+
const reqExtractRe = /\breq(?:uest)?\s*\.\s*(?:body|query|params|headers|cookies)\b/;
|
|
26328
|
+
const aliasRe = /^\s*(?:const|let|var)\s+(\w+)\s*=\s*(?:globalThis\s*\.\s*eval|global\s*\.\s*eval|window\s*\.\s*eval|self\s*\.\s*eval|eval)\s*;?\s*$/;
|
|
26329
|
+
const aliases = new Set;
|
|
26330
|
+
for (const line of lines) {
|
|
26331
|
+
const m = line.match(aliasRe);
|
|
26332
|
+
if (m)
|
|
26333
|
+
aliases.add(m[1]);
|
|
26334
|
+
}
|
|
26335
|
+
const taintedVars = new Set;
|
|
26336
|
+
const assignRe = /^\s*(?:const|let|var)\s+(\w+)\s*=\s*(.+?);?\s*$/;
|
|
26337
|
+
const reassignRe = /^\s*(\w+)\s*=\s*(.+?);?\s*$/;
|
|
26338
|
+
for (let pass = 0;pass < 3; pass++) {
|
|
26339
|
+
const before = taintedVars.size;
|
|
26340
|
+
for (const line of lines) {
|
|
26341
|
+
const m = line.match(assignRe) || line.match(reassignRe);
|
|
26342
|
+
if (!m)
|
|
26343
|
+
continue;
|
|
26344
|
+
const lhs = m[1];
|
|
26345
|
+
const rhs = m[2];
|
|
26346
|
+
if (taintedVars.has(lhs))
|
|
26347
|
+
continue;
|
|
26348
|
+
if (lhs === "const" || lhs === "let" || lhs === "var")
|
|
26349
|
+
continue;
|
|
26350
|
+
if (reqExtractRe.test(rhs)) {
|
|
26351
|
+
taintedVars.add(lhs);
|
|
26352
|
+
continue;
|
|
26353
|
+
}
|
|
26354
|
+
for (const v of taintedVars) {
|
|
26355
|
+
const re = new RegExp(`\\b${v}\\b`);
|
|
26356
|
+
if (re.test(rhs)) {
|
|
26357
|
+
taintedVars.add(lhs);
|
|
26358
|
+
break;
|
|
26359
|
+
}
|
|
26360
|
+
}
|
|
26361
|
+
}
|
|
26362
|
+
if (taintedVars.size === before)
|
|
26363
|
+
break;
|
|
26364
|
+
}
|
|
26365
|
+
const indirectCommaRe = /\(\s*0\s*,\s*eval\s*\)\s*\(\s*([^)]*)\s*\)/;
|
|
26366
|
+
const indirectMemberRe = /\b(?:globalThis|global|window|self)\s*\.\s*eval\s*\(\s*([^)]*)\s*\)/;
|
|
26367
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
26368
|
+
const line = lines[i2];
|
|
26369
|
+
const trimmed = line.trim();
|
|
26370
|
+
if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("*"))
|
|
26371
|
+
continue;
|
|
26372
|
+
if (aliasRe.test(line))
|
|
26373
|
+
continue;
|
|
26374
|
+
let arg = null;
|
|
26375
|
+
let formLabel = "";
|
|
26376
|
+
let m = trimmed.match(indirectCommaRe);
|
|
26377
|
+
if (m) {
|
|
26378
|
+
arg = m[1].trim();
|
|
26379
|
+
formLabel = "(0, eval)(...) indirect eval";
|
|
26380
|
+
}
|
|
26381
|
+
if (!arg) {
|
|
26382
|
+
m = trimmed.match(indirectMemberRe);
|
|
26383
|
+
if (m) {
|
|
26384
|
+
arg = m[1].trim();
|
|
26385
|
+
formLabel = "globalThis.eval / window.eval / self.eval indirect eval";
|
|
26386
|
+
}
|
|
26387
|
+
}
|
|
26388
|
+
if (!arg && aliases.size > 0) {
|
|
26389
|
+
for (const a of aliases) {
|
|
26390
|
+
const aliasCallRe = new RegExp(`\\b${a}\\s*\\(\\s*([^)]*)\\s*\\)`);
|
|
26391
|
+
const mm = trimmed.match(aliasCallRe);
|
|
26392
|
+
if (mm) {
|
|
26393
|
+
arg = mm[1].trim();
|
|
26394
|
+
formLabel = `aliased eval reference \`${a}(...)\``;
|
|
26395
|
+
break;
|
|
26396
|
+
}
|
|
26397
|
+
}
|
|
26398
|
+
}
|
|
26399
|
+
if (arg === null)
|
|
26400
|
+
continue;
|
|
26401
|
+
if (arg.length === 0)
|
|
26402
|
+
continue;
|
|
26403
|
+
if (/^['"`][^'"`]*['"`]$/.test(arg))
|
|
26404
|
+
continue;
|
|
26405
|
+
let tainted = false;
|
|
26406
|
+
if (reqExtractRe.test(arg))
|
|
26407
|
+
tainted = true;
|
|
26408
|
+
else {
|
|
26409
|
+
for (const v of taintedVars) {
|
|
26410
|
+
const re = new RegExp(`\\b${v}\\b`);
|
|
26411
|
+
if (re.test(arg)) {
|
|
26412
|
+
tainted = true;
|
|
26413
|
+
break;
|
|
26414
|
+
}
|
|
26415
|
+
}
|
|
26416
|
+
}
|
|
26417
|
+
if (!tainted)
|
|
26418
|
+
continue;
|
|
26419
|
+
findings.push({
|
|
26420
|
+
id: `code_injection-${file}-${i2 + 1}-js-indirect-eval`,
|
|
26421
|
+
pass: "language-sources",
|
|
26422
|
+
category: "security",
|
|
26423
|
+
rule_id: "code_injection",
|
|
26424
|
+
cwe: "CWE-94",
|
|
26425
|
+
severity: "critical",
|
|
26426
|
+
level: "error",
|
|
26427
|
+
message: `Code injection: ${formLabel} called with a value derived from ` + "an HTTP request body/query/headers. Indirect eval forms still " + "execute arbitrary code in the global scope. Remove the eval and " + "parse the input with a safe data parser instead.",
|
|
26428
|
+
file,
|
|
26429
|
+
line: i2 + 1,
|
|
26430
|
+
snippet: trimmed
|
|
26431
|
+
});
|
|
26432
|
+
}
|
|
26433
|
+
return findings;
|
|
26434
|
+
}
|
|
26435
|
+
function findPythonInteractiveInterpreterCodeInjectionFindings(code, file) {
|
|
26436
|
+
const findings = [];
|
|
26437
|
+
if (typeof code !== "string" || code.length === 0)
|
|
26438
|
+
return findings;
|
|
26439
|
+
if (!/^\s*import\s+code\b/m.test(code))
|
|
26440
|
+
return findings;
|
|
26441
|
+
if (!/\bcode\s*\.\s*(?:InteractiveInterpreter|InteractiveConsole|compile_command)\b/.test(code)) {
|
|
26442
|
+
return findings;
|
|
26443
|
+
}
|
|
26444
|
+
const lines = code.split(`
|
|
26445
|
+
`);
|
|
26446
|
+
const reqExtractRe = /\brequest\s*\.\s*(?:args|form|values|files|json|cookies|headers|get_data|get_json)\b/;
|
|
26447
|
+
const taintedVars = new Set;
|
|
26448
|
+
const assignRe = /^\s*(\w+)\s*=\s*(.+?)\s*(?:#.*)?$/;
|
|
26449
|
+
for (let pass = 0;pass < 3; pass++) {
|
|
26450
|
+
const before = taintedVars.size;
|
|
26451
|
+
for (const line of lines) {
|
|
26452
|
+
const m = line.match(assignRe);
|
|
26453
|
+
if (!m)
|
|
26454
|
+
continue;
|
|
26455
|
+
const lhs = m[1];
|
|
26456
|
+
const rhs = m[2];
|
|
26457
|
+
if (taintedVars.has(lhs))
|
|
26458
|
+
continue;
|
|
26459
|
+
if (reqExtractRe.test(rhs)) {
|
|
26460
|
+
taintedVars.add(lhs);
|
|
26461
|
+
continue;
|
|
26462
|
+
}
|
|
26463
|
+
for (const v of taintedVars) {
|
|
26464
|
+
const re = new RegExp(`\\b${v}\\b`);
|
|
26465
|
+
if (re.test(rhs)) {
|
|
26466
|
+
taintedVars.add(lhs);
|
|
26467
|
+
break;
|
|
26468
|
+
}
|
|
26469
|
+
}
|
|
26470
|
+
}
|
|
26471
|
+
if (taintedVars.size === before)
|
|
26472
|
+
break;
|
|
26473
|
+
}
|
|
26474
|
+
const callRe = /\bcode\s*\.\s*(?:InteractiveInterpreter|InteractiveConsole)\s*\([^)]*\)\s*\.\s*(runsource|runcode|push|interact)\s*\(\s*([^),]+)/;
|
|
26475
|
+
const compileRe = /\bcode\s*\.\s*compile_command\s*\(\s*([^),]+)/;
|
|
26476
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
26477
|
+
const line = lines[i2];
|
|
26478
|
+
const trimmed = line.trim();
|
|
26479
|
+
let arg = null;
|
|
26480
|
+
let formLabel = "";
|
|
26481
|
+
const m1 = trimmed.match(callRe);
|
|
26482
|
+
if (m1) {
|
|
26483
|
+
arg = m1[2].trim();
|
|
26484
|
+
formLabel = `code.${/Interpreter/.test(trimmed) ? "InteractiveInterpreter" : "InteractiveConsole"}().${m1[1]}`;
|
|
26485
|
+
}
|
|
26486
|
+
if (!arg) {
|
|
26487
|
+
const m2 = trimmed.match(compileRe);
|
|
26488
|
+
if (m2) {
|
|
26489
|
+
arg = m2[1].trim();
|
|
26490
|
+
formLabel = "code.compile_command";
|
|
26491
|
+
}
|
|
26492
|
+
}
|
|
26493
|
+
if (arg === null)
|
|
26494
|
+
continue;
|
|
26495
|
+
if (arg.length === 0)
|
|
26496
|
+
continue;
|
|
26497
|
+
if (/^['"][^'"]*['"]$/.test(arg))
|
|
26498
|
+
continue;
|
|
26499
|
+
let tainted = false;
|
|
26500
|
+
if (reqExtractRe.test(arg))
|
|
26501
|
+
tainted = true;
|
|
26502
|
+
else {
|
|
26503
|
+
for (const v of taintedVars) {
|
|
26504
|
+
const re = new RegExp(`\\b${v}\\b`);
|
|
26505
|
+
if (re.test(arg)) {
|
|
26506
|
+
tainted = true;
|
|
26507
|
+
break;
|
|
26508
|
+
}
|
|
26509
|
+
}
|
|
26510
|
+
}
|
|
26511
|
+
if (!tainted)
|
|
26512
|
+
continue;
|
|
26513
|
+
findings.push({
|
|
26514
|
+
id: `code_injection-${file}-${i2 + 1}-py-interactive-interpreter`,
|
|
26515
|
+
pass: "language-sources",
|
|
26516
|
+
category: "security",
|
|
26517
|
+
rule_id: "code_injection",
|
|
26518
|
+
cwe: "CWE-94",
|
|
26519
|
+
severity: "critical",
|
|
26520
|
+
level: "error",
|
|
26521
|
+
message: `Code injection: ${formLabel}(...) called with a value derived from ` + "a Flask request extractor. The Python `code` module compiles and " + "executes arbitrary source. Remove the call and validate input " + "against a fixed allow-list instead.",
|
|
26522
|
+
file,
|
|
26523
|
+
line: i2 + 1,
|
|
26524
|
+
snippet: trimmed
|
|
26525
|
+
});
|
|
26526
|
+
}
|
|
26527
|
+
return findings;
|
|
26528
|
+
}
|
|
26529
|
+
function findRustEvalCrateCodeInjectionFindings(code, file) {
|
|
26530
|
+
const findings = [];
|
|
26531
|
+
if (typeof code !== "string" || code.length === 0)
|
|
26532
|
+
return findings;
|
|
26533
|
+
if (!/\b(?:evalexpr\s*::\s*eval|libloading\s*::\s*Library\s*::\s*new|\.\s*load\s*\([^)]*\)\s*\.\s*(?:exec|eval|call))/.test(code)) {
|
|
26534
|
+
return findings;
|
|
26535
|
+
}
|
|
26536
|
+
const lines = code.split(`
|
|
26537
|
+
`);
|
|
26538
|
+
const extractorTypeRe = /:\s*(?:String|Bytes|bytes::Bytes|axum::body::Bytes|web::Query\b|web::Path\b|web::Form\b|web::Json\b|HttpRequest\b|actix_web::HttpRequest\b)/;
|
|
26539
|
+
const fns = [];
|
|
26540
|
+
let cur = null;
|
|
26541
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
26542
|
+
const t = lines[i2];
|
|
26543
|
+
if (/^\s*(?:pub\s+)?(?:async\s+)?fn\s+\w+\s*\(/.test(t)) {
|
|
26544
|
+
if (cur) {
|
|
26545
|
+
cur.end = i2 - 1;
|
|
26546
|
+
fns.push(cur);
|
|
26547
|
+
}
|
|
26548
|
+
cur = { start: i2, end: lines.length - 1, tainted: new Set };
|
|
26549
|
+
const headerJoined = (() => {
|
|
26550
|
+
let j = i2;
|
|
26551
|
+
let s = "";
|
|
26552
|
+
while (j < lines.length && !/\{\s*$/.test(s)) {
|
|
26553
|
+
s += lines[j];
|
|
26554
|
+
if (/\{\s*$/.test(lines[j]))
|
|
26555
|
+
break;
|
|
26556
|
+
j++;
|
|
26557
|
+
if (j - i2 > 12)
|
|
26558
|
+
break;
|
|
26559
|
+
}
|
|
26560
|
+
return s;
|
|
26561
|
+
})();
|
|
26562
|
+
const open = headerJoined.indexOf("(");
|
|
26563
|
+
const close = headerJoined.lastIndexOf(")");
|
|
26564
|
+
if (open !== -1 && close > open) {
|
|
26565
|
+
const params = headerJoined.substring(open + 1, close);
|
|
26566
|
+
let depth = 0;
|
|
26567
|
+
let buf = "";
|
|
26568
|
+
const parts2 = [];
|
|
26569
|
+
for (const ch of params) {
|
|
26570
|
+
if (ch === "<" || ch === "(")
|
|
26571
|
+
depth++;
|
|
26572
|
+
else if (ch === ">" || ch === ")")
|
|
26573
|
+
depth--;
|
|
26574
|
+
if (ch === "," && depth === 0) {
|
|
26575
|
+
parts2.push(buf);
|
|
26576
|
+
buf = "";
|
|
26577
|
+
continue;
|
|
26578
|
+
}
|
|
26579
|
+
buf += ch;
|
|
26580
|
+
}
|
|
26581
|
+
if (buf.trim().length > 0)
|
|
26582
|
+
parts2.push(buf);
|
|
26583
|
+
for (const p of parts2) {
|
|
26584
|
+
const pm = p.match(/(?:mut\s+)?(\w+)\s*:/);
|
|
26585
|
+
if (!pm)
|
|
26586
|
+
continue;
|
|
26587
|
+
if (extractorTypeRe.test(p))
|
|
26588
|
+
cur.tainted.add(pm[1]);
|
|
26589
|
+
}
|
|
26590
|
+
}
|
|
26591
|
+
}
|
|
26592
|
+
}
|
|
26593
|
+
if (cur)
|
|
26594
|
+
fns.push(cur);
|
|
26595
|
+
for (const fn of fns) {
|
|
26596
|
+
for (let pass = 0;pass < 3; pass++) {
|
|
26597
|
+
const before = fn.tainted.size;
|
|
26598
|
+
for (let i2 = fn.start;i2 <= fn.end; i2++) {
|
|
26599
|
+
const t = lines[i2].trim();
|
|
26600
|
+
const m = t.match(/^let\s+(?:mut\s+)?(\w+)\s*(?::\s*[^=]+)?=\s*(.+?);?$/);
|
|
26601
|
+
if (!m)
|
|
26602
|
+
continue;
|
|
26603
|
+
const lhs = m[1];
|
|
26604
|
+
const rhs = m[2];
|
|
26605
|
+
if (fn.tainted.has(lhs))
|
|
26606
|
+
continue;
|
|
26607
|
+
for (const v of fn.tainted) {
|
|
26608
|
+
const re = new RegExp(`\\b${v}\\b`);
|
|
26609
|
+
if (re.test(rhs)) {
|
|
26610
|
+
fn.tainted.add(lhs);
|
|
26611
|
+
break;
|
|
26612
|
+
}
|
|
26613
|
+
}
|
|
26614
|
+
}
|
|
26615
|
+
if (fn.tainted.size === before)
|
|
26616
|
+
break;
|
|
26617
|
+
}
|
|
26618
|
+
}
|
|
26619
|
+
const evalExprRe = /\bevalexpr\s*::\s*eval(?:_with_context|_boolean|_int|_float|_string|_tuple|_empty)?\s*\(\s*([^)]+)\s*\)/;
|
|
26620
|
+
const libloadingRe = /\blibloading\s*::\s*Library\s*::\s*new\s*\(\s*([^)]+)\s*\)/;
|
|
26621
|
+
const luaLoadRe = /\.\s*load\s*\(\s*([^)]+)\s*\)\s*\.\s*(?:exec|eval|call)\b/;
|
|
26622
|
+
for (const fn of fns) {
|
|
26623
|
+
if (fn.tainted.size === 0)
|
|
26624
|
+
continue;
|
|
26625
|
+
for (let i2 = fn.start;i2 <= fn.end; i2++) {
|
|
26626
|
+
const line = lines[i2];
|
|
26627
|
+
const trimmed = line.trim();
|
|
26628
|
+
let arg = null;
|
|
26629
|
+
let formLabel = "";
|
|
26630
|
+
let m = trimmed.match(evalExprRe);
|
|
26631
|
+
if (m) {
|
|
26632
|
+
arg = m[1].trim();
|
|
26633
|
+
formLabel = "evalexpr::eval";
|
|
26634
|
+
}
|
|
26635
|
+
if (!arg) {
|
|
26636
|
+
m = trimmed.match(libloadingRe);
|
|
26637
|
+
if (m) {
|
|
26638
|
+
arg = m[1].trim();
|
|
26639
|
+
formLabel = "libloading::Library::new";
|
|
26640
|
+
}
|
|
26641
|
+
}
|
|
26642
|
+
if (!arg) {
|
|
26643
|
+
m = trimmed.match(luaLoadRe);
|
|
26644
|
+
if (m) {
|
|
26645
|
+
arg = m[1].trim();
|
|
26646
|
+
formLabel = "mlua/rlua Lua::load().{exec|eval|call}";
|
|
26647
|
+
}
|
|
26648
|
+
}
|
|
26649
|
+
if (arg === null)
|
|
26650
|
+
continue;
|
|
26651
|
+
if (arg.length === 0)
|
|
26652
|
+
continue;
|
|
26653
|
+
let unwrapped = arg.replace(/^&\s*/, "").trim();
|
|
26654
|
+
if (/^"[^"]*"$/.test(unwrapped))
|
|
26655
|
+
continue;
|
|
26656
|
+
let tainted = false;
|
|
26657
|
+
for (const v of fn.tainted) {
|
|
26658
|
+
const re = new RegExp(`\\b${v}\\b`);
|
|
26659
|
+
if (re.test(unwrapped)) {
|
|
26660
|
+
tainted = true;
|
|
26661
|
+
break;
|
|
26662
|
+
}
|
|
26663
|
+
}
|
|
26664
|
+
if (!tainted)
|
|
26665
|
+
continue;
|
|
26666
|
+
findings.push({
|
|
26667
|
+
id: `code_injection-${file}-${i2 + 1}-rust-eval-crate`,
|
|
26668
|
+
pass: "language-sources",
|
|
26669
|
+
category: "security",
|
|
26670
|
+
rule_id: "code_injection",
|
|
26671
|
+
cwe: "CWE-94",
|
|
26672
|
+
severity: "critical",
|
|
26673
|
+
level: "error",
|
|
26674
|
+
message: `Code injection: ${formLabel}(...) called with a value derived ` + "from an HTTP request extractor (body / Query / Path / Form / " + "Json / HttpRequest). The expression / library / Lua chunk is " + "executed as code. Remove the dynamic-eval path or restrict " + "input to a fixed allow-list.",
|
|
26675
|
+
file,
|
|
26676
|
+
line: i2 + 1,
|
|
26677
|
+
snippet: trimmed
|
|
26678
|
+
});
|
|
26679
|
+
}
|
|
26680
|
+
}
|
|
26681
|
+
return findings;
|
|
26682
|
+
}
|
|
26205
26683
|
|
|
26206
26684
|
// ../circle-ir/dist/analysis/passes/sink-filter-pass.js
|
|
26207
26685
|
var JS_XSS_SANITIZERS = [
|
|
@@ -39329,7 +39807,7 @@ var colors = {
|
|
|
39329
39807
|
};
|
|
39330
39808
|
|
|
39331
39809
|
// src/version.ts
|
|
39332
|
-
var version = "3.
|
|
39810
|
+
var version = "3.133.0";
|
|
39333
39811
|
|
|
39334
39812
|
// src/formatters.ts
|
|
39335
39813
|
var LIBRARY_API_SURFACE_TAG2 = "library-api-surface:caller-responsibility";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cognium-dev",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.133.0",
|
|
4
4
|
"description": "Static Application Security Testing CLI for detecting security vulnerabilities via taint tracking",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
68
|
"@cognium/project-profile-detect": "^1.1.0",
|
|
69
|
-
"circle-ir": "^3.
|
|
69
|
+
"circle-ir": "^3.133.0"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
72
|
"@types/node": "^25.5.0",
|