cognium-dev 3.155.0 → 3.157.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 +376 -7
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -11671,7 +11671,10 @@ var DEFAULT_SANITIZERS = [
11671
11671
  { method: "MustParse", class: "uuid", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] },
11672
11672
  { method: "bool", removes: ["sql_injection", "command_injection", "xss", "code_injection"] },
11673
11673
  { method: "UUID", class: "uuid", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] },
11674
- { method: "Decimal", class: "decimal", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] }
11674
+ { method: "Decimal", class: "decimal", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] },
11675
+ { method: "update", class: "Cipher", removes: ["xss", "sql_injection", "command_injection", "path_traversal", "code_injection", "crlf", "log_injection", "ldap_injection", "xpath_injection"] },
11676
+ { method: "final", class: "Cipher", removes: ["xss", "sql_injection", "command_injection", "path_traversal", "code_injection", "crlf", "log_injection", "ldap_injection", "xpath_injection"] },
11677
+ { method: "doFinal", class: "Cipher", removes: ["xss", "sql_injection", "command_injection", "path_traversal", "code_injection", "crlf", "log_injection", "ldap_injection", "xpath_injection"] }
11675
11678
  ];
11676
11679
  var DEFAULT_SINK_SEMANTICS = [
11677
11680
  {
@@ -12978,7 +12981,8 @@ function matchesSinkPattern(call, pattern, typeHierarchy, language) {
12978
12981
  if (pattern.class === "constructor") {
12979
12982
  return true;
12980
12983
  }
12981
- if (call.receiver_type && call.receiver_type === pattern.class) {} else if (call.receiver_type_fqn && call.receiver_type_fqn.endsWith("." + pattern.class)) {} else if (call.receiver && !receiverMightBeClass(call.receiver, pattern.class)) {
12984
+ const subtypeArgArityOk = !pattern.arg_positions || pattern.arg_positions.length === 0 || pattern.arg_positions.every((pos) => pos < (call.arguments?.length ?? 0));
12985
+ if (call.receiver_type && call.receiver_type === pattern.class) {} else if (call.receiver_type && typeHierarchy && subtypeArgArityOk && typeHierarchy.isSubtypeOf(call.receiver_type, pattern.class)) {} else if (call.receiver_type_fqn && call.receiver_type_fqn.endsWith("." + pattern.class)) {} else if (call.receiver_type_fqn && typeHierarchy && subtypeArgArityOk && typeHierarchy.isSubtypeOf(call.receiver_type_fqn, pattern.class)) {} else if (call.receiver && !receiverMightBeClass(call.receiver, pattern.class)) {
12982
12986
  if (typeHierarchy && typeHierarchy.couldBeType(call.receiver, pattern.class)) {
12983
12987
  return true;
12984
12988
  }
@@ -14316,8 +14320,150 @@ function createWithJdkTypes() {
14316
14320
  for (const type of [...jdbcTypes, ...ioTypes, ...servletTypes]) {
14317
14321
  resolver.addType(type, "jdk", type.package);
14318
14322
  }
14323
+ registerCommonLibraries(resolver);
14319
14324
  return resolver;
14320
14325
  }
14326
+ function registerCommonLibraries(resolver) {
14327
+ const apacheHttpClient4x = [
14328
+ {
14329
+ name: "HttpClient",
14330
+ kind: "interface",
14331
+ package: "org.apache.http.client",
14332
+ extends: null,
14333
+ implements: [],
14334
+ annotations: [],
14335
+ methods: [],
14336
+ fields: [],
14337
+ start_line: 0,
14338
+ end_line: 0
14339
+ },
14340
+ {
14341
+ name: "AbstractHttpClient",
14342
+ kind: "class",
14343
+ package: "org.apache.http.impl.client",
14344
+ extends: null,
14345
+ implements: ["org.apache.http.client.HttpClient"],
14346
+ annotations: [],
14347
+ methods: [],
14348
+ fields: [],
14349
+ start_line: 0,
14350
+ end_line: 0
14351
+ },
14352
+ {
14353
+ name: "CloseableHttpClient",
14354
+ kind: "class",
14355
+ package: "org.apache.http.impl.client",
14356
+ extends: null,
14357
+ implements: ["org.apache.http.client.HttpClient"],
14358
+ annotations: [],
14359
+ methods: [],
14360
+ fields: [],
14361
+ start_line: 0,
14362
+ end_line: 0
14363
+ },
14364
+ {
14365
+ name: "InternalHttpClient",
14366
+ kind: "class",
14367
+ package: "org.apache.http.impl.client",
14368
+ extends: "org.apache.http.impl.client.CloseableHttpClient",
14369
+ implements: [],
14370
+ annotations: [],
14371
+ methods: [],
14372
+ fields: [],
14373
+ start_line: 0,
14374
+ end_line: 0
14375
+ },
14376
+ {
14377
+ name: "MinimalHttpClient",
14378
+ kind: "class",
14379
+ package: "org.apache.http.impl.client",
14380
+ extends: "org.apache.http.impl.client.CloseableHttpClient",
14381
+ implements: [],
14382
+ annotations: [],
14383
+ methods: [],
14384
+ fields: [],
14385
+ start_line: 0,
14386
+ end_line: 0
14387
+ },
14388
+ {
14389
+ name: "DefaultHttpClient",
14390
+ kind: "class",
14391
+ package: "org.apache.http.impl.client",
14392
+ extends: "org.apache.http.impl.client.AbstractHttpClient",
14393
+ implements: [],
14394
+ annotations: [],
14395
+ methods: [],
14396
+ fields: [],
14397
+ start_line: 0,
14398
+ end_line: 0
14399
+ },
14400
+ {
14401
+ name: "SystemDefaultHttpClient",
14402
+ kind: "class",
14403
+ package: "org.apache.http.impl.client",
14404
+ extends: "org.apache.http.impl.client.DefaultHttpClient",
14405
+ implements: [],
14406
+ annotations: [],
14407
+ methods: [],
14408
+ fields: [],
14409
+ start_line: 0,
14410
+ end_line: 0
14411
+ }
14412
+ ];
14413
+ const apacheHttpClient5x = [
14414
+ {
14415
+ name: "HttpClient",
14416
+ kind: "interface",
14417
+ package: "org.apache.hc.client5.http.classic",
14418
+ extends: null,
14419
+ implements: [],
14420
+ annotations: [],
14421
+ methods: [],
14422
+ fields: [],
14423
+ start_line: 0,
14424
+ end_line: 0
14425
+ },
14426
+ {
14427
+ name: "CloseableHttpClient",
14428
+ kind: "class",
14429
+ package: "org.apache.hc.client5.http.impl.classic",
14430
+ extends: null,
14431
+ implements: ["org.apache.hc.client5.http.classic.HttpClient"],
14432
+ annotations: [],
14433
+ methods: [],
14434
+ fields: [],
14435
+ start_line: 0,
14436
+ end_line: 0
14437
+ },
14438
+ {
14439
+ name: "InternalHttpClient",
14440
+ kind: "class",
14441
+ package: "org.apache.hc.client5.http.impl.classic",
14442
+ extends: "org.apache.hc.client5.http.impl.classic.CloseableHttpClient",
14443
+ implements: [],
14444
+ annotations: [],
14445
+ methods: [],
14446
+ fields: [],
14447
+ start_line: 0,
14448
+ end_line: 0
14449
+ },
14450
+ {
14451
+ name: "MinimalHttpClient",
14452
+ kind: "class",
14453
+ package: "org.apache.hc.client5.http.impl.classic",
14454
+ extends: "org.apache.hc.client5.http.impl.classic.CloseableHttpClient",
14455
+ implements: [],
14456
+ annotations: [],
14457
+ methods: [],
14458
+ fields: [],
14459
+ start_line: 0,
14460
+ end_line: 0
14461
+ }
14462
+ ];
14463
+ for (const type of [...apacheHttpClient4x, ...apacheHttpClient5x]) {
14464
+ resolver.addType(type, "common-libraries", type.package);
14465
+ }
14466
+ }
14321
14467
  // ../circle-ir/dist/resolution/symbol-table.js
14322
14468
  class SymbolTable {
14323
14469
  exports = new Map;
@@ -29491,6 +29637,202 @@ class LibraryProfileSourceGatePass {
29491
29637
  }
29492
29638
  }
29493
29639
 
29640
+ // ../circle-ir/dist/analysis/passes/mybatis-annotation-sql-sink-pass.js
29641
+ var MYBATIS_SQL_ANNOTATIONS = new Set([
29642
+ "Select",
29643
+ "Update",
29644
+ "Insert",
29645
+ "Delete",
29646
+ "SelectProvider",
29647
+ "UpdateProvider",
29648
+ "InsertProvider",
29649
+ "DeleteProvider"
29650
+ ]);
29651
+ var DOLLAR_BRACE_RE = /\$\{([A-Za-z_][A-Za-z0-9_]*)(?:\.[A-Za-z0-9_.]+)?\}/g;
29652
+ function parsePositionalRef(name2) {
29653
+ const paramMatch = /^param(\d+)$/.exec(name2);
29654
+ if (paramMatch) {
29655
+ const oneBased = Number.parseInt(paramMatch[1], 10);
29656
+ if (Number.isFinite(oneBased) && oneBased >= 1)
29657
+ return oneBased - 1;
29658
+ }
29659
+ const zeroMatch = /^(\d+)$/.exec(name2);
29660
+ if (zeroMatch) {
29661
+ const zeroBased = Number.parseInt(zeroMatch[1], 10);
29662
+ if (Number.isFinite(zeroBased) && zeroBased >= 0)
29663
+ return zeroBased;
29664
+ }
29665
+ return null;
29666
+ }
29667
+ function extractAnnotationBody(annotation) {
29668
+ const openIdx = annotation.indexOf("(");
29669
+ if (openIdx < 0)
29670
+ return null;
29671
+ const closeIdx = annotation.lastIndexOf(")");
29672
+ if (closeIdx <= openIdx)
29673
+ return null;
29674
+ return annotation.substring(openIdx + 1, closeIdx);
29675
+ }
29676
+ function annotationName(annotation) {
29677
+ const openIdx = annotation.indexOf("(");
29678
+ return openIdx < 0 ? annotation : annotation.substring(0, openIdx);
29679
+ }
29680
+ function extractParamName(annotation) {
29681
+ if (annotationName(annotation) !== "Param")
29682
+ return null;
29683
+ const body2 = extractAnnotationBody(annotation);
29684
+ if (body2 === null)
29685
+ return null;
29686
+ const strMatch = /^\s*["']([^"']*)["']/.exec(body2);
29687
+ return strMatch ? strMatch[1] : null;
29688
+ }
29689
+ function extractDollarBraceRefs(annotationBody) {
29690
+ const seen = new Set;
29691
+ const out2 = [];
29692
+ DOLLAR_BRACE_RE.lastIndex = 0;
29693
+ let m;
29694
+ while ((m = DOLLAR_BRACE_RE.exec(annotationBody)) !== null) {
29695
+ const name2 = m[1];
29696
+ if (!seen.has(name2)) {
29697
+ seen.add(name2);
29698
+ out2.push(name2);
29699
+ }
29700
+ }
29701
+ return out2;
29702
+ }
29703
+ function fileImportsMyBatis(imports) {
29704
+ for (const imp of imports) {
29705
+ const pkg = imp.from_package;
29706
+ if (!pkg)
29707
+ continue;
29708
+ if (pkg === "org.apache.ibatis.annotations" || pkg.startsWith("org.apache.ibatis.annotations.") || pkg === "org.apache.ibatis" || pkg.startsWith("org.apache.ibatis.")) {
29709
+ return true;
29710
+ }
29711
+ }
29712
+ return false;
29713
+ }
29714
+ function correlateDollarBraceToArgPositions(method, refs) {
29715
+ const paramNameToIndex = new Map;
29716
+ method.parameters.forEach((param, idx) => {
29717
+ for (const ann of param.annotations) {
29718
+ const paramName = extractParamName(ann);
29719
+ if (paramName !== null) {
29720
+ paramNameToIndex.set(paramName, idx);
29721
+ }
29722
+ }
29723
+ });
29724
+ const positions = new Set;
29725
+ for (const ref of refs) {
29726
+ const namedIdx = paramNameToIndex.get(ref);
29727
+ if (namedIdx !== undefined) {
29728
+ positions.add(namedIdx);
29729
+ continue;
29730
+ }
29731
+ const positionalIdx = parsePositionalRef(ref);
29732
+ if (positionalIdx !== null && positionalIdx < method.parameters.length) {
29733
+ positions.add(positionalIdx);
29734
+ }
29735
+ }
29736
+ return Array.from(positions).sort((a, b) => a - b);
29737
+ }
29738
+ function isMapperMethodCall(call, interfaceSimpleName, interfaceFqn, methodName) {
29739
+ const target = call.resolution?.target;
29740
+ if (target) {
29741
+ const suffix = `${interfaceSimpleName}.${methodName}`;
29742
+ const suffixFqn = `${interfaceFqn}.${methodName}`;
29743
+ if (target === suffix || target === suffixFqn || target.endsWith("." + suffix) || target.endsWith("." + suffixFqn)) {
29744
+ return true;
29745
+ }
29746
+ }
29747
+ if (call.method_name !== methodName)
29748
+ return false;
29749
+ if (call.receiver_type === interfaceSimpleName)
29750
+ return true;
29751
+ if (call.receiver_type_fqn === interfaceFqn)
29752
+ return true;
29753
+ return false;
29754
+ }
29755
+
29756
+ class MyBatisAnnotationSqlSinkPass {
29757
+ name = "mybatis-annotation-sql-sink";
29758
+ category = "security";
29759
+ run(ctx) {
29760
+ if (ctx.language !== "java") {
29761
+ return { annotatedMethodCount: 0, addedSinkCount: 0 };
29762
+ }
29763
+ const { types, imports, calls } = ctx.graph.ir;
29764
+ if (!fileImportsMyBatis(imports)) {
29765
+ return { annotatedMethodCount: 0, addedSinkCount: 0 };
29766
+ }
29767
+ const mapperMethods = [];
29768
+ for (const type of types) {
29769
+ if (type.kind !== "interface")
29770
+ continue;
29771
+ for (const method of type.methods) {
29772
+ let matchedRefs = [];
29773
+ for (const ann of method.annotations) {
29774
+ if (!MYBATIS_SQL_ANNOTATIONS.has(annotationName(ann)))
29775
+ continue;
29776
+ const body2 = extractAnnotationBody(ann);
29777
+ if (body2 === null)
29778
+ continue;
29779
+ const refs = extractDollarBraceRefs(body2);
29780
+ if (refs.length > 0) {
29781
+ matchedRefs = [...matchedRefs, ...refs];
29782
+ }
29783
+ }
29784
+ if (matchedRefs.length === 0)
29785
+ continue;
29786
+ const positions = correlateDollarBraceToArgPositions(method, matchedRefs);
29787
+ if (positions.length === 0)
29788
+ continue;
29789
+ const interfaceFqn = type.package ? `${type.package}.${type.name}` : type.name;
29790
+ mapperMethods.push({
29791
+ interfaceSimpleName: type.name,
29792
+ interfaceFqn,
29793
+ methodName: method.name,
29794
+ taintedArgPositions: positions
29795
+ });
29796
+ }
29797
+ }
29798
+ if (mapperMethods.length === 0) {
29799
+ return {
29800
+ annotatedMethodCount: 0,
29801
+ addedSinkCount: 0
29802
+ };
29803
+ }
29804
+ const sinks = ctx.hasResult("taint-matcher") ? ctx.getResult("taint-matcher").sinks : ctx.graph.ir.taint.sinks;
29805
+ let addedSinkCount = 0;
29806
+ for (const call of calls) {
29807
+ for (const rec of mapperMethods) {
29808
+ if (!isMapperMethodCall(call, rec.interfaceSimpleName, rec.interfaceFqn, rec.methodName)) {
29809
+ continue;
29810
+ }
29811
+ const line = call.location.line;
29812
+ if (sinks.some((s) => s.line === line && s.type === "sql_injection" && s.method === rec.methodName)) {
29813
+ continue;
29814
+ }
29815
+ const receiverLoc = call.receiver ? `${call.receiver}.${rec.methodName}()` : `${rec.methodName}()`;
29816
+ sinks.push({
29817
+ type: "sql_injection",
29818
+ cwe: "CWE-89",
29819
+ location: call.in_method ? `${receiverLoc} in ${call.in_method}` : receiverLoc,
29820
+ line,
29821
+ confidence: 0.95,
29822
+ method: rec.methodName,
29823
+ argPositions: rec.taintedArgPositions,
29824
+ class: rec.interfaceSimpleName
29825
+ });
29826
+ addedSinkCount++;
29827
+ }
29828
+ }
29829
+ return {
29830
+ annotatedMethodCount: mapperMethods.length,
29831
+ addedSinkCount
29832
+ };
29833
+ }
29834
+ }
29835
+
29494
29836
  // ../circle-ir/dist/analysis/passes/sink-filter-pass.js
29495
29837
  var JS_XSS_SANITIZERS = [
29496
29838
  /\bDOMPurify\.sanitize\s*\(/,
@@ -31497,11 +31839,36 @@ class TaintPropagationPass {
31497
31839
  return true;
31498
31840
  }
31499
31841
  const sansAtSink = sanitizersByLine.get(f.sink_line);
31500
- if (!sansAtSink || sansAtSink.length === 0)
31842
+ if (sansAtSink && sansAtSink.length > 0) {
31843
+ for (const san of sansAtSink) {
31844
+ if (san.sanitizes.includes(f.sink_type)) {
31845
+ return false;
31846
+ }
31847
+ }
31848
+ }
31849
+ const usesAtSink = graph.usesByLine.get(f.sink_line);
31850
+ if (!usesAtSink || usesAtSink.length === 0)
31501
31851
  return true;
31502
- for (const san of sansAtSink) {
31503
- if (san.sanitizes.includes(f.sink_type)) {
31504
- return false;
31852
+ const sinkVar = f.path.length > 0 ? f.path[f.path.length - 1].variable : undefined;
31853
+ if (!sinkVar)
31854
+ return true;
31855
+ for (const use of usesAtSink) {
31856
+ if (use.variable !== sinkVar)
31857
+ continue;
31858
+ if (use.def_id === null || use.def_id === undefined)
31859
+ continue;
31860
+ const walk = walkBackwardDefs(use.def_id, graph.chainsByToDef, graph.defById, { maxHops: 32 });
31861
+ for (const line of walk.lines) {
31862
+ if (line === f.sink_line)
31863
+ continue;
31864
+ const sansAtLine = sanitizersByLine.get(line);
31865
+ if (!sansAtLine || sansAtLine.length === 0)
31866
+ continue;
31867
+ for (const san of sansAtLine) {
31868
+ if (san.sanitizes.includes(f.sink_type)) {
31869
+ return false;
31870
+ }
31871
+ }
31505
31872
  }
31506
31873
  }
31507
31874
  return true;
@@ -42294,6 +42661,8 @@ async function analyze(code, filePath, language, options = {}) {
42294
42661
  pipeline.add(new SourceSemanticsPass);
42295
42662
  if (!disabledPasses.has("library-profile-source-gate"))
42296
42663
  pipeline.add(new LibraryProfileSourceGatePass);
42664
+ if (!disabledPasses.has("mybatis-annotation-sql-sink"))
42665
+ pipeline.add(new MyBatisAnnotationSqlSinkPass);
42297
42666
  pipeline.add(new SinkFilterPass);
42298
42667
  if (!disabledPasses.has("sink-semantics"))
42299
42668
  pipeline.add(new SinkSemanticsPass);
@@ -43239,7 +43608,7 @@ var colors = {
43239
43608
  };
43240
43609
 
43241
43610
  // src/version.ts
43242
- var version = "3.155.0";
43611
+ var version = "3.157.0";
43243
43612
 
43244
43613
  // src/formatters.ts
43245
43614
  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.155.0",
3
+ "version": "3.157.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.155.0"
69
+ "circle-ir": "^3.157.0"
70
70
  },
71
71
  "devDependencies": {
72
72
  "@types/node": "^25.5.0",