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.
Files changed (2) hide show
  1. package/dist/cli.js +98 -44
  2. 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 = 500;
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 annotationLines = findAnnotationLineRanges(ctx.code);
29288
- const arrayLines = findStringArrayLineRanges(ctx.code);
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
- for (let i2 = 0;i2 < lines.length; i2++) {
29349
- const lineText = lines[i2];
29350
- const lineNum = i2 + 1;
29351
- if (TEST_CALL_RE.test(lineText))
29352
- continue;
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 (value.length < 32)
29407
+ if (COMMENT_EXAMPLE_RE.test(lineText))
29366
29408
  continue;
29367
- if (!this.passesEntropyGate(value, lineText))
29409
+ if (annotationLines.has(lineNum))
29368
29410
  continue;
29369
- const key = `${lineNum}:hardcoded-credential-entropy`;
29370
- if (seen.has(key))
29411
+ if (arrayLines.has(lineNum))
29371
29412
  continue;
29372
- if (seen.has(`${lineNum}:hardcoded-credential`))
29373
- continue;
29374
- seen.add(key);
29375
- ctx.addFinding({
29376
- id: `hardcoded-credential-entropy-${file}-${lineNum}`,
29377
- pass: this.name,
29378
- category: this.category,
29379
- rule_id: "hardcoded-credential-entropy",
29380
- cwe: "CWE-798",
29381
- severity: "high",
29382
- level: "warning",
29383
- message: `Possible hardcoded secret: high-entropy string literal (${value.length} chars)`,
29384
- file,
29385
- line: lineNum,
29386
- snippet: lineText.trim().substring(0, 120),
29387
- 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']`.",
29388
- evidence: { kind: "entropy", length: value.length }
29389
- });
29390
- entropyFindings += 1;
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.85.0";
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.85.0",
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.85.0"
68
+ "circle-ir": "^3.86.0"
69
69
  },
70
70
  "devDependencies": {
71
71
  "@types/node": "^25.5.0",