cognium-dev 3.85.0 → 3.86.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 +98 -44
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -12112,6 +12112,19 @@ function argIsClassLiteral(call, position) {
|
|
|
12112
12112
|
return false;
|
|
12113
12113
|
return CLASS_LITERAL_RE.test(expr);
|
|
12114
12114
|
}
|
|
12115
|
+
var CWE_78_RECEIVER_ALLOWLIST = new Set([
|
|
12116
|
+
"Runtime",
|
|
12117
|
+
"ProcessBuilder",
|
|
12118
|
+
"Process",
|
|
12119
|
+
"CommandLine",
|
|
12120
|
+
"DefaultExecutor",
|
|
12121
|
+
"Executor",
|
|
12122
|
+
"Exec",
|
|
12123
|
+
"Launcher",
|
|
12124
|
+
"ProcStarter",
|
|
12125
|
+
"ProcessExecutor",
|
|
12126
|
+
"RuntimeUtil"
|
|
12127
|
+
]);
|
|
12115
12128
|
function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
|
|
12116
12129
|
const sinkMap = new Map;
|
|
12117
12130
|
for (const call of calls) {
|
|
@@ -12132,6 +12145,18 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
|
|
|
12132
12145
|
if (pattern.safe_if_class_literal_at !== undefined && argIsClassLiteral(call, pattern.safe_if_class_literal_at)) {
|
|
12133
12146
|
continue;
|
|
12134
12147
|
}
|
|
12148
|
+
if (pattern.type === "command_injection") {
|
|
12149
|
+
if (call.is_constructor) {
|
|
12150
|
+
if (!CWE_78_RECEIVER_ALLOWLIST.has(call.method_name)) {
|
|
12151
|
+
continue;
|
|
12152
|
+
}
|
|
12153
|
+
} else {
|
|
12154
|
+
const receiverClass = call.receiver_type;
|
|
12155
|
+
if (receiverClass && !CWE_78_RECEIVER_ALLOWLIST.has(receiverClass)) {
|
|
12156
|
+
continue;
|
|
12157
|
+
}
|
|
12158
|
+
}
|
|
12159
|
+
}
|
|
12135
12160
|
const location = formatCallLocation(call);
|
|
12136
12161
|
const key = `${location}:${call.location.line}:${pattern.cwe}`;
|
|
12137
12162
|
const confidence = calculateSinkConfidence(call, pattern);
|
|
@@ -29073,6 +29098,20 @@ var CRED_KEYWORD_RE = /\b([A-Za-z_$][\w$]*?(?:password|passwd|secret|api[_-]?key
|
|
|
29073
29098
|
var CRED_DYNAMIC_VALUE_RE = /\$\{|process\.env|os\.environ|os\.Getenv|System\.getenv/;
|
|
29074
29099
|
var CRED_FUNCTION_DECL_RE = /\b(?:function|func|def|fn)\s+\w+\s*\(/;
|
|
29075
29100
|
var CRED_COMPARISON_RE = /(?:===?|!==?|>=|<=|<>)\s*["'`]/;
|
|
29101
|
+
var PROPERTY_KEY_RE = /^[a-z][a-zA-Z0-9_-]*\.[a-zA-Z][a-zA-Z0-9_.-]*$/;
|
|
29102
|
+
var PLAIN_IDENTIFIER_RE = /^[a-z][a-zA-Z_]*$/;
|
|
29103
|
+
function charClassDiversity(s) {
|
|
29104
|
+
let n = 0;
|
|
29105
|
+
if (/[a-z]/.test(s))
|
|
29106
|
+
n++;
|
|
29107
|
+
if (/[A-Z]/.test(s))
|
|
29108
|
+
n++;
|
|
29109
|
+
if (/[0-9]/.test(s))
|
|
29110
|
+
n++;
|
|
29111
|
+
if (/[^a-zA-Z0-9]/.test(s))
|
|
29112
|
+
n++;
|
|
29113
|
+
return n;
|
|
29114
|
+
}
|
|
29076
29115
|
function isLikelyCredentialAssignment(line) {
|
|
29077
29116
|
if (CRED_FUNCTION_DECL_RE.test(line))
|
|
29078
29117
|
return null;
|
|
@@ -29091,6 +29130,18 @@ function isLikelyCredentialAssignment(line) {
|
|
|
29091
29130
|
return null;
|
|
29092
29131
|
if (isAllSameChar(value))
|
|
29093
29132
|
return null;
|
|
29133
|
+
if (value.length < 12)
|
|
29134
|
+
return null;
|
|
29135
|
+
if (shannonEntropy(value) < 3.5)
|
|
29136
|
+
return null;
|
|
29137
|
+
if (charClassDiversity(value) < 2)
|
|
29138
|
+
return null;
|
|
29139
|
+
if (PROPERTY_KEY_RE.test(value))
|
|
29140
|
+
return null;
|
|
29141
|
+
if (PLAIN_IDENTIFIER_RE.test(value))
|
|
29142
|
+
return null;
|
|
29143
|
+
if (/^[0-9]+$/.test(value) && value.length < 16)
|
|
29144
|
+
return null;
|
|
29094
29145
|
return { name: name2, value };
|
|
29095
29146
|
}
|
|
29096
29147
|
var STRING_LITERAL_RE = /(["'`])((?:\\.|(?!\1).){8,200})\1/g;
|
|
@@ -29207,7 +29258,7 @@ function findStringArrayLineRanges(code) {
|
|
|
29207
29258
|
let depth = 1;
|
|
29208
29259
|
let li = i2;
|
|
29209
29260
|
let col = m.index + m[0].length;
|
|
29210
|
-
let lineBudget =
|
|
29261
|
+
let lineBudget = 100;
|
|
29211
29262
|
const spanLines = [li + 1];
|
|
29212
29263
|
let spanText = "";
|
|
29213
29264
|
while (depth > 0 && li < lines.length && lineBudget > 0) {
|
|
@@ -29264,6 +29315,7 @@ function extractEnclosingFieldName(lineText) {
|
|
|
29264
29315
|
}
|
|
29265
29316
|
var TEST_CALL_RE = /\b(?:expect|assert|describe|it|test)\s*\(/;
|
|
29266
29317
|
var COMMENT_EXAMPLE_RE = /(?:\/\/|#)\s*(?:example|sample|test|fixture)/i;
|
|
29318
|
+
var FAST_CANDIDATE_PROBE_RE = /["'`][A-Za-z0-9+/=_-]{32,}["'`]/;
|
|
29267
29319
|
|
|
29268
29320
|
class ScanSecretsPass {
|
|
29269
29321
|
name = "scan-secrets";
|
|
@@ -29284,8 +29336,9 @@ class ScanSecretsPass {
|
|
|
29284
29336
|
seen.add(`${f.line}:${f.rule_id}`);
|
|
29285
29337
|
}
|
|
29286
29338
|
}
|
|
29287
|
-
const
|
|
29288
|
-
const
|
|
29339
|
+
const hasEntropyCandidate = FAST_CANDIDATE_PROBE_RE.test(ctx.code);
|
|
29340
|
+
const annotationLines = hasEntropyCandidate ? findAnnotationLineRanges(ctx.code) : new Set;
|
|
29341
|
+
const arrayLines = hasEntropyCandidate ? findStringArrayLineRanges(ctx.code) : new Set;
|
|
29289
29342
|
let providerFindings = 0;
|
|
29290
29343
|
let entropyFindings = 0;
|
|
29291
29344
|
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
@@ -29345,51 +29398,52 @@ class ScanSecretsPass {
|
|
|
29345
29398
|
});
|
|
29346
29399
|
providerFindings += 1;
|
|
29347
29400
|
}
|
|
29348
|
-
|
|
29349
|
-
|
|
29350
|
-
|
|
29351
|
-
|
|
29352
|
-
|
|
29353
|
-
if (COMMENT_EXAMPLE_RE.test(lineText))
|
|
29354
|
-
continue;
|
|
29355
|
-
if (annotationLines.has(lineNum))
|
|
29356
|
-
continue;
|
|
29357
|
-
if (arrayLines.has(lineNum))
|
|
29358
|
-
continue;
|
|
29359
|
-
STRING_LITERAL_RE.lastIndex = 0;
|
|
29360
|
-
let match;
|
|
29361
|
-
while ((match = STRING_LITERAL_RE.exec(lineText)) !== null) {
|
|
29362
|
-
const value = match[2];
|
|
29363
|
-
if (!this.isCandidate(value))
|
|
29401
|
+
if (hasEntropyCandidate)
|
|
29402
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
29403
|
+
const lineText = lines[i2];
|
|
29404
|
+
const lineNum = i2 + 1;
|
|
29405
|
+
if (TEST_CALL_RE.test(lineText))
|
|
29364
29406
|
continue;
|
|
29365
|
-
if (
|
|
29407
|
+
if (COMMENT_EXAMPLE_RE.test(lineText))
|
|
29366
29408
|
continue;
|
|
29367
|
-
if (
|
|
29409
|
+
if (annotationLines.has(lineNum))
|
|
29368
29410
|
continue;
|
|
29369
|
-
|
|
29370
|
-
if (seen.has(key))
|
|
29411
|
+
if (arrayLines.has(lineNum))
|
|
29371
29412
|
continue;
|
|
29372
|
-
|
|
29373
|
-
|
|
29374
|
-
|
|
29375
|
-
|
|
29376
|
-
|
|
29377
|
-
|
|
29378
|
-
|
|
29379
|
-
|
|
29380
|
-
|
|
29381
|
-
|
|
29382
|
-
|
|
29383
|
-
|
|
29384
|
-
|
|
29385
|
-
|
|
29386
|
-
|
|
29387
|
-
|
|
29388
|
-
|
|
29389
|
-
|
|
29390
|
-
|
|
29413
|
+
STRING_LITERAL_RE.lastIndex = 0;
|
|
29414
|
+
let match;
|
|
29415
|
+
while ((match = STRING_LITERAL_RE.exec(lineText)) !== null) {
|
|
29416
|
+
const value = match[2];
|
|
29417
|
+
if (!this.isCandidate(value))
|
|
29418
|
+
continue;
|
|
29419
|
+
if (value.length < 32)
|
|
29420
|
+
continue;
|
|
29421
|
+
if (!this.passesEntropyGate(value, lineText))
|
|
29422
|
+
continue;
|
|
29423
|
+
const key = `${lineNum}:hardcoded-credential-entropy`;
|
|
29424
|
+
if (seen.has(key))
|
|
29425
|
+
continue;
|
|
29426
|
+
if (seen.has(`${lineNum}:hardcoded-credential`))
|
|
29427
|
+
continue;
|
|
29428
|
+
seen.add(key);
|
|
29429
|
+
ctx.addFinding({
|
|
29430
|
+
id: `hardcoded-credential-entropy-${file}-${lineNum}`,
|
|
29431
|
+
pass: this.name,
|
|
29432
|
+
category: this.category,
|
|
29433
|
+
rule_id: "hardcoded-credential-entropy",
|
|
29434
|
+
cwe: "CWE-798",
|
|
29435
|
+
severity: "high",
|
|
29436
|
+
level: "warning",
|
|
29437
|
+
message: `Possible hardcoded secret: high-entropy string literal (${value.length} chars)`,
|
|
29438
|
+
file,
|
|
29439
|
+
line: lineNum,
|
|
29440
|
+
snippet: lineText.trim().substring(0, 120),
|
|
29441
|
+
fix: "If this is a credential, move it to environment / secrets manager. If it is sample data, add an `example` / `test` marker or disable this pass via `disabledPasses: ['scan-secrets']`.",
|
|
29442
|
+
evidence: { kind: "entropy", length: value.length }
|
|
29443
|
+
});
|
|
29444
|
+
entropyFindings += 1;
|
|
29445
|
+
}
|
|
29391
29446
|
}
|
|
29392
|
-
}
|
|
29393
29447
|
return { providerFindings, entropyFindings };
|
|
29394
29448
|
}
|
|
29395
29449
|
isCandidate(s) {
|
|
@@ -33990,7 +34044,7 @@ var colors = {
|
|
|
33990
34044
|
};
|
|
33991
34045
|
|
|
33992
34046
|
// src/version.ts
|
|
33993
|
-
var version = "3.
|
|
34047
|
+
var version = "3.86.0";
|
|
33994
34048
|
|
|
33995
34049
|
// src/formatters.ts
|
|
33996
34050
|
var SINK_SEVERITY = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cognium-dev",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.86.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",
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"registry": "https://registry.npmjs.org/"
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
|
-
"circle-ir": "^3.
|
|
68
|
+
"circle-ir": "^3.86.0"
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
71
|
"@types/node": "^25.5.0",
|