cognium-dev 3.67.0 → 3.69.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 +256 -1
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -6279,6 +6279,12 @@ function extractPythonLiteral(node) {
6279
6279
  if (literalTypes.includes(node.type)) {
6280
6280
  const text = getNodeText(node);
6281
6281
  if (node.type === "string") {
6282
+ for (let i2 = 0;i2 < node.childCount; i2++) {
6283
+ const child = node.child(i2);
6284
+ if (child && child.type === "interpolation") {
6285
+ return null;
6286
+ }
6287
+ }
6282
6288
  return text.replace(/^['"]|['"]$/g, "").replace(/^f['"]|['"]$/g, "");
6283
6289
  }
6284
6290
  return text;
@@ -16864,6 +16870,22 @@ class PythonPlugin extends BaseLanguagePlugin {
16864
16870
  severity: "high",
16865
16871
  argPositions: [0]
16866
16872
  },
16873
+ {
16874
+ method: "urlretrieve",
16875
+ class: "urllib.request",
16876
+ type: "ssrf",
16877
+ cwe: "CWE-918",
16878
+ severity: "high",
16879
+ argPositions: [0]
16880
+ },
16881
+ {
16882
+ method: "urlretrieve",
16883
+ class: "urllib.request",
16884
+ type: "path_traversal",
16885
+ cwe: "CWE-22",
16886
+ severity: "high",
16887
+ argPositions: [1]
16888
+ },
16867
16889
  {
16868
16890
  method: "loads",
16869
16891
  class: "pickle",
@@ -29561,6 +29583,237 @@ class TlsVerifyDisabledPass {
29561
29583
  }
29562
29584
  }
29563
29585
 
29586
+ // ../circle-ir/dist/analysis/passes/module-side-effect-pass.js
29587
+ var JS_EXEC_METHODS = new Set([
29588
+ "exec",
29589
+ "spawn",
29590
+ "execSync",
29591
+ "spawnSync",
29592
+ "execFile",
29593
+ "execFileSync"
29594
+ ]);
29595
+ var JS_EXEC_RECEIVERS = new Set([
29596
+ "child_process",
29597
+ "cp"
29598
+ ]);
29599
+ var JS_NETWORK_RECEIVER_METHOD = new Set([
29600
+ "https:request",
29601
+ "http:request",
29602
+ "https:get",
29603
+ "http:get"
29604
+ ]);
29605
+ var JS_NETWORK_MAYBE = new Set([
29606
+ "fetch"
29607
+ ]);
29608
+ var JS_ENV_SIGNAL_RE = /\bprocess\.env\b|\bos\.homedir\b|\/etc\/(passwd|shadow)\b|\.ssh\/id_(rsa|dsa|ed25519)\b|\bhomedir\b/;
29609
+ var PKG_JSON_BENIGN_INSTALL = new Set([
29610
+ "node-gyp rebuild",
29611
+ "prebuild-install",
29612
+ "prebuild-install || node-gyp rebuild",
29613
+ "husky install",
29614
+ "patch-package",
29615
+ "npm run build"
29616
+ ]);
29617
+ var PKG_JSON_INSTALL_SHELL_RE = /\b(curl|wget|nc|ncat|node\s+-e|node\s+-r|sh\s+-c|bash\s+-c|eval|base64\s+-d)\b/;
29618
+ var PY_NETWORK_RECEIVER_METHODS = [
29619
+ { receiver: "requests", method: "post" },
29620
+ { receiver: "requests", method: "put" },
29621
+ { receiver: "urllib.request", method: "urlopen" },
29622
+ { receiver: "socket", method: "create_connection" },
29623
+ { receiver: "socket", method: "connect" },
29624
+ { receiver: "subprocess", method: "run" },
29625
+ { receiver: "subprocess", method: "Popen" },
29626
+ { receiver: "os", method: "system" }
29627
+ ];
29628
+ var PY_ENV_SIGNAL_RE = /\bos\.environ\b|\bpwd\.getpw\b|\bid_(rsa|dsa|ed25519)\b|\bhome\b|\b\/etc\/(passwd|shadow)\b|\bPath\.home\b|\bglob\.glob\b/;
29629
+ var GO_INIT_DANGEROUS = [
29630
+ { receiver: "exec", method: "Command" },
29631
+ { receiver: "http", method: "Post" },
29632
+ { receiver: "http", method: "Get" },
29633
+ { receiver: "net", method: "LookupTXT" },
29634
+ { receiver: "os", method: "Setenv" }
29635
+ ];
29636
+ var RUST_DANGEROUS_METHODS = new Set([
29637
+ "Command::new",
29638
+ "process::Command::new",
29639
+ "std::process::Command::new",
29640
+ "new"
29641
+ ]);
29642
+ var RUST_DANGEROUS_RECEIVERS = new Set([
29643
+ "Command",
29644
+ "process::Command",
29645
+ "std::process::Command",
29646
+ "reqwest",
29647
+ "reqwest::blocking"
29648
+ ]);
29649
+
29650
+ class ModuleSideEffectPass {
29651
+ name = "module-side-effect";
29652
+ category = "security";
29653
+ run(ctx) {
29654
+ const { graph, language, code } = ctx;
29655
+ const file = graph.ir.meta.file;
29656
+ const findings = [];
29657
+ const emit = (line, pattern, api) => {
29658
+ if (findings.some((f) => f.line === line && f.pattern === pattern))
29659
+ return;
29660
+ findings.push({ line, language, pattern, api });
29661
+ ctx.addFinding({
29662
+ id: `${this.name}-${file}-${line}-${pattern.replace(/\W+/g, "-")}`,
29663
+ pass: this.name,
29664
+ category: this.category,
29665
+ rule_id: this.name,
29666
+ cwe: "CWE-829",
29667
+ severity: "high",
29668
+ level: "error",
29669
+ message: `Module-level / install-time side effect (${pattern}) in \`${api}\`. ` + `Code that runs at import / build / install time is invisible to ` + `runtime defenses and is the standard delivery vector for supply-` + `chain droppers (shai-hulud-style harvesters, malicious typosquats, ` + `build.rs exfil). If this side effect is intentional, move it into ` + `an explicit function invoked at runtime; if it is install-time ` + `configuration, restrict it to documented APIs (e.g. \`cargo:\` ` + `directives, \`node-gyp rebuild\`).`,
29670
+ file,
29671
+ line,
29672
+ fix: this.fixFor(language, pattern),
29673
+ evidence: { language, api, pattern }
29674
+ });
29675
+ };
29676
+ const isRustBuildScript = language === "rust" && /\bbuild\.rs$/.test(file);
29677
+ for (const call of graph.ir.calls) {
29678
+ if (language === "rust" && !isRustBuildScript)
29679
+ continue;
29680
+ const det = this.detectCall(call, language);
29681
+ if (!det)
29682
+ continue;
29683
+ emit(call.location.line, det.pattern, det.api);
29684
+ }
29685
+ if (language === "javascript" || language === "typescript") {
29686
+ if (/\bpackage\.json$/.test(file)) {
29687
+ for (const extra of this.scanPackageJson(code)) {
29688
+ emit(extra.line, extra.pattern, extra.api);
29689
+ }
29690
+ }
29691
+ }
29692
+ return { findings };
29693
+ }
29694
+ detectCall(call, language) {
29695
+ const method = call.method_name;
29696
+ const receiver = call.receiver ?? "";
29697
+ if (language === "javascript" || language === "typescript") {
29698
+ if (call.in_method != null)
29699
+ return null;
29700
+ if (JS_EXEC_RECEIVERS.has(receiver) && JS_EXEC_METHODS.has(method)) {
29701
+ return {
29702
+ pattern: "module-level child_process call",
29703
+ api: `${receiver}.${method}`
29704
+ };
29705
+ }
29706
+ const recvMethod = `${receiver}:${method}`;
29707
+ if (JS_NETWORK_RECEIVER_METHOD.has(recvMethod)) {
29708
+ return {
29709
+ pattern: "module-level network request",
29710
+ api: `${receiver}.${method}`
29711
+ };
29712
+ }
29713
+ if (JS_NETWORK_MAYBE.has(method) && receiver === "") {
29714
+ for (const arg of call.arguments) {
29715
+ if (JS_ENV_SIGNAL_RE.test(arg.expression ?? "")) {
29716
+ return {
29717
+ pattern: "module-level fetch of process.env",
29718
+ api: method
29719
+ };
29720
+ }
29721
+ }
29722
+ }
29723
+ return null;
29724
+ }
29725
+ if (language === "python") {
29726
+ if (call.in_method != null)
29727
+ return null;
29728
+ for (const tuple of PY_NETWORK_RECEIVER_METHODS) {
29729
+ if (receiver === tuple.receiver && method === tuple.method) {
29730
+ for (const arg of call.arguments) {
29731
+ if (PY_ENV_SIGNAL_RE.test(arg.expression ?? "")) {
29732
+ return {
29733
+ pattern: "import-time network call with env signal",
29734
+ api: `${receiver}.${method}`
29735
+ };
29736
+ }
29737
+ }
29738
+ }
29739
+ }
29740
+ return null;
29741
+ }
29742
+ if (language === "go") {
29743
+ if (call.in_method !== "init")
29744
+ return null;
29745
+ for (const tuple of GO_INIT_DANGEROUS) {
29746
+ if (receiver === tuple.receiver && method === tuple.method) {
29747
+ return {
29748
+ pattern: "init() install-time side effect",
29749
+ api: `${receiver}.${method}`
29750
+ };
29751
+ }
29752
+ }
29753
+ return null;
29754
+ }
29755
+ if (language === "rust") {
29756
+ const recv = receiver.trim();
29757
+ if (RUST_DANGEROUS_RECEIVERS.has(recv) || recv.startsWith("Command::")) {
29758
+ if (method === "new" || RUST_DANGEROUS_METHODS.has(method) || method === "get" || method === "post") {
29759
+ return {
29760
+ pattern: "build.rs side effect",
29761
+ api: `${recv || method}.${method}`
29762
+ };
29763
+ }
29764
+ }
29765
+ return null;
29766
+ }
29767
+ return null;
29768
+ }
29769
+ scanPackageJson(code) {
29770
+ const out2 = [];
29771
+ const lines = code.split(`
29772
+ `);
29773
+ const installRe = /"(pre|post)?install"\s*:\s*"([^"]+)"/i;
29774
+ for (let i2 = 0;i2 < lines.length; i2++) {
29775
+ const m = lines[i2].match(installRe);
29776
+ if (!m)
29777
+ continue;
29778
+ const value = m[2].trim();
29779
+ if (PKG_JSON_BENIGN_INSTALL.has(value))
29780
+ continue;
29781
+ if (!PKG_JSON_INSTALL_SHELL_RE.test(value))
29782
+ continue;
29783
+ out2.push({
29784
+ line: i2 + 1,
29785
+ pattern: "npm lifecycle hook executes shell",
29786
+ api: `scripts.${m[1] ?? ""}install`
29787
+ });
29788
+ }
29789
+ return out2;
29790
+ }
29791
+ fixFor(language, pattern) {
29792
+ if (pattern.includes("child_process")) {
29793
+ return "Remove the module-level child_process call. If an install-time " + "step is genuinely required, move it into an explicit function and " + "document why it must run at install time.";
29794
+ }
29795
+ if (pattern.includes("module-level network")) {
29796
+ return "Network requests should not run at module load. Move the call " + "inside an exported function called explicitly by the caller.";
29797
+ }
29798
+ if (pattern.includes("module-level fetch of process.env")) {
29799
+ return "Exfiltrating `process.env` at module load is the canonical " + "supply-chain dropper shape. Remove this code or, if intentional, " + "gate it behind an explicit opt-in.";
29800
+ }
29801
+ if (pattern.includes("npm lifecycle hook")) {
29802
+ return "Replace the install-script shell payload with a build tool " + "(e.g. `node-gyp rebuild`, `prebuild-install`). Lifecycle scripts " + "that invoke curl/wget/node -e/sh -c are how supply-chain droppers " + "are delivered.";
29803
+ }
29804
+ if (pattern.includes("import-time network call")) {
29805
+ return "Move the network call inside an explicit function. Sending " + "`os.environ` or filesystem secrets at module import is the canonical " + "credential-harvester shape.";
29806
+ }
29807
+ if (pattern.includes("init()")) {
29808
+ return "Move the side effect out of `init()`. Go `init` functions " + "run automatically on package import; network/exec calls there are " + "invisible to the caller and are how supply-chain droppers operate.";
29809
+ }
29810
+ if (pattern.includes("build.rs")) {
29811
+ return "`build.rs` should only emit `cargo:` directives. " + "Spawning subprocesses or making network requests at build time is " + "a documented supply-chain attack vector (see RUSTSEC).";
29812
+ }
29813
+ return "Remove the module-level side effect or move it inside an " + "explicit, runtime-invoked function.";
29814
+ }
29815
+ }
29816
+
29564
29817
  // ../circle-ir/dist/analysis/passes/jwt-verify-disabled-pass.js
29565
29818
  var PY_VERIFY_SIGNATURE_FALSE_RE = /["']verify_signature["']\s*:\s*False\b/;
29566
29819
  var PY_VERIFY_KW_FALSE_RE = /\bverify\s*=\s*False\b/;
@@ -31153,6 +31406,8 @@ async function analyze(code, filePath, language, options = {}) {
31153
31406
  pipeline.add(new WeakRandomPass);
31154
31407
  if (!disabledPasses.has("tls-verify-disabled"))
31155
31408
  pipeline.add(new TlsVerifyDisabledPass);
31409
+ if (!disabledPasses.has("module-side-effect"))
31410
+ pipeline.add(new ModuleSideEffectPass);
31156
31411
  if (!disabledPasses.has("jwt-verify-disabled"))
31157
31412
  pipeline.add(new JwtVerifyDisabledPass);
31158
31413
  if (!disabledPasses.has("csrf-protection-disabled"))
@@ -31354,7 +31609,7 @@ var colors = {
31354
31609
  };
31355
31610
 
31356
31611
  // src/version.ts
31357
- var version = "3.67.0";
31612
+ var version = "3.69.0";
31358
31613
 
31359
31614
  // src/formatters.ts
31360
31615
  var SINK_SEVERITY = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cognium-dev",
3
- "version": "3.67.0",
3
+ "version": "3.69.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.67.0"
68
+ "circle-ir": "^3.69.0"
69
69
  },
70
70
  "devDependencies": {
71
71
  "@types/node": "^25.5.0",