cognium-dev 3.68.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.
- package/dist/cli.js +234 -1
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -29583,6 +29583,237 @@ class TlsVerifyDisabledPass {
|
|
|
29583
29583
|
}
|
|
29584
29584
|
}
|
|
29585
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
|
+
|
|
29586
29817
|
// ../circle-ir/dist/analysis/passes/jwt-verify-disabled-pass.js
|
|
29587
29818
|
var PY_VERIFY_SIGNATURE_FALSE_RE = /["']verify_signature["']\s*:\s*False\b/;
|
|
29588
29819
|
var PY_VERIFY_KW_FALSE_RE = /\bverify\s*=\s*False\b/;
|
|
@@ -31175,6 +31406,8 @@ async function analyze(code, filePath, language, options = {}) {
|
|
|
31175
31406
|
pipeline.add(new WeakRandomPass);
|
|
31176
31407
|
if (!disabledPasses.has("tls-verify-disabled"))
|
|
31177
31408
|
pipeline.add(new TlsVerifyDisabledPass);
|
|
31409
|
+
if (!disabledPasses.has("module-side-effect"))
|
|
31410
|
+
pipeline.add(new ModuleSideEffectPass);
|
|
31178
31411
|
if (!disabledPasses.has("jwt-verify-disabled"))
|
|
31179
31412
|
pipeline.add(new JwtVerifyDisabledPass);
|
|
31180
31413
|
if (!disabledPasses.has("csrf-protection-disabled"))
|
|
@@ -31376,7 +31609,7 @@ var colors = {
|
|
|
31376
31609
|
};
|
|
31377
31610
|
|
|
31378
31611
|
// src/version.ts
|
|
31379
|
-
var version = "3.
|
|
31612
|
+
var version = "3.69.0";
|
|
31380
31613
|
|
|
31381
31614
|
// src/formatters.ts
|
|
31382
31615
|
var SINK_SEVERITY = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cognium-dev",
|
|
3
|
-
"version": "3.
|
|
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.
|
|
68
|
+
"circle-ir": "^3.69.0"
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
71
|
"@types/node": "^25.5.0",
|