cognium-dev 3.50.0 → 3.52.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 +795 -49
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -10738,17 +10738,6 @@ var DEFAULT_SINKS = [
|
|
|
10738
10738
|
{ method: "exchange", class: "RestTemplate", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
10739
10739
|
{ method: "get", class: "WebClient", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [] },
|
|
10740
10740
|
{ method: "post", class: "WebClient", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [] },
|
|
10741
|
-
{ method: "Random", class: "constructor", type: "weak_random", cwe: "CWE-330", severity: "medium", arg_positions: [] },
|
|
10742
|
-
{ method: "nextInt", class: "Random", type: "weak_random", cwe: "CWE-330", severity: "medium", arg_positions: [] },
|
|
10743
|
-
{ method: "nextLong", class: "Random", type: "weak_random", cwe: "CWE-330", severity: "medium", arg_positions: [] },
|
|
10744
|
-
{ method: "nextFloat", class: "Random", type: "weak_random", cwe: "CWE-330", severity: "medium", arg_positions: [] },
|
|
10745
|
-
{ method: "nextDouble", class: "Random", type: "weak_random", cwe: "CWE-330", severity: "medium", arg_positions: [] },
|
|
10746
|
-
{ method: "nextBoolean", class: "Random", type: "weak_random", cwe: "CWE-330", severity: "medium", arg_positions: [] },
|
|
10747
|
-
{ method: "nextBytes", class: "Random", type: "weak_random", cwe: "CWE-330", severity: "medium", arg_positions: [] },
|
|
10748
|
-
{ method: "getInstance", class: "MessageDigest", type: "weak_hash", cwe: "CWE-328", severity: "medium", arg_positions: [0] },
|
|
10749
|
-
{ method: "getInstance", class: "Cipher", type: "weak_crypto", cwe: "CWE-327", severity: "high", arg_positions: [0] },
|
|
10750
|
-
{ method: "getInstance", class: "KeyGenerator", type: "weak_crypto", cwe: "CWE-327", severity: "high", arg_positions: [0] },
|
|
10751
|
-
{ method: "Cookie", class: "constructor", type: "insecure_cookie", cwe: "CWE-614", severity: "medium", arg_positions: [] },
|
|
10752
10741
|
{ method: "setAttribute", class: "HttpSession", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [0] },
|
|
10753
10742
|
{ method: "putValue", class: "HttpSession", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [0] },
|
|
10754
10743
|
{ method: "outputElementContent", class: "XMLOutputter", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
|
|
@@ -12063,6 +12052,12 @@ function receiverMightBeClass(receiver, className) {
|
|
|
12063
12052
|
return true;
|
|
12064
12053
|
}
|
|
12065
12054
|
}
|
|
12055
|
+
const goTemplateFactoryMatch = receiver.match(/\.(Must|New|Parse|ParseFiles|ParseGlob|ParseFS|Clone|Funcs|Option|Lookup|Delims)\(.+\)$/);
|
|
12056
|
+
if (goTemplateFactoryMatch && className === "Template") {
|
|
12057
|
+
if (/(?:^|\b)template\./.test(receiver) || /(?:^|\b)tmpl\./.test(receiver)) {
|
|
12058
|
+
return true;
|
|
12059
|
+
}
|
|
12060
|
+
}
|
|
12066
12061
|
}
|
|
12067
12062
|
if (receiver.includes("::") && receiver.endsWith(")")) {
|
|
12068
12063
|
const scopedMatch = receiver.match(/^(\w+)::(\w+)\(.*\)$/);
|
|
@@ -12119,7 +12114,8 @@ function receiverMightBeClass(receiver, className) {
|
|
|
12119
12114
|
em: ["EntityManager"],
|
|
12120
12115
|
ps: ["PreparedStatement"],
|
|
12121
12116
|
rs: ["ResultSet"],
|
|
12122
|
-
template: ["JdbcTemplate"],
|
|
12117
|
+
template: ["JdbcTemplate", "Template"],
|
|
12118
|
+
tmpl: ["Template"],
|
|
12123
12119
|
cur: ["Cursor"],
|
|
12124
12120
|
cursor: ["Cursor"],
|
|
12125
12121
|
writer: ["PrintWriter"],
|
|
@@ -17254,6 +17250,22 @@ class GoPlugin extends BaseLanguagePlugin {
|
|
|
17254
17250
|
severity: "high",
|
|
17255
17251
|
argPositions: [0]
|
|
17256
17252
|
},
|
|
17253
|
+
{
|
|
17254
|
+
method: "Execute",
|
|
17255
|
+
class: "Template",
|
|
17256
|
+
type: "xss",
|
|
17257
|
+
cwe: "CWE-79",
|
|
17258
|
+
severity: "high",
|
|
17259
|
+
argPositions: [1]
|
|
17260
|
+
},
|
|
17261
|
+
{
|
|
17262
|
+
method: "ExecuteTemplate",
|
|
17263
|
+
class: "Template",
|
|
17264
|
+
type: "xss",
|
|
17265
|
+
cwe: "CWE-79",
|
|
17266
|
+
severity: "high",
|
|
17267
|
+
argPositions: [2]
|
|
17268
|
+
},
|
|
17257
17269
|
{
|
|
17258
17270
|
method: "Get",
|
|
17259
17271
|
class: "http",
|
|
@@ -27114,66 +27126,790 @@ var COOKIE_RESPONSE_RECEIVERS = new Set([
|
|
|
27114
27126
|
]);
|
|
27115
27127
|
var SECURE_TRUE_RE = /\bsecure\s*:\s*true\b/;
|
|
27116
27128
|
var HTTPONLY_TRUE_RE = /\bhttpOnly\s*:\s*true\b/i;
|
|
27129
|
+
var PY_SET_COOKIE_RECEIVERS = new Set([
|
|
27130
|
+
"response",
|
|
27131
|
+
"resp",
|
|
27132
|
+
"res"
|
|
27133
|
+
]);
|
|
27134
|
+
var PY_SECURE_TRUE_RE = /\bsecure\s*=\s*True\b/;
|
|
27135
|
+
var PY_HTTPONLY_TRUE_RE = /\bhttponly\s*=\s*True\b/i;
|
|
27136
|
+
var JAVA_SET_SECURE_TRUE_RE = /\.setSecure\s*\(\s*true\s*\)/;
|
|
27137
|
+
var JAVA_SET_HTTPONLY_TRUE_RE = /\.setHttpOnly\s*\(\s*true\s*\)/;
|
|
27117
27138
|
|
|
27118
27139
|
class InsecureCookiePass {
|
|
27119
27140
|
name = "insecure-cookie";
|
|
27120
27141
|
category = "security";
|
|
27121
27142
|
run(ctx) {
|
|
27122
|
-
const { graph, language } = ctx;
|
|
27123
|
-
if (language !== "javascript" && language !== "typescript") {
|
|
27124
|
-
return { insecureCookies: [] };
|
|
27125
|
-
}
|
|
27143
|
+
const { graph, language, code } = ctx;
|
|
27126
27144
|
const file = graph.ir.meta.file;
|
|
27127
27145
|
const insecureCookies = [];
|
|
27146
|
+
if (language === "javascript" || language === "typescript") {
|
|
27147
|
+
for (const call of graph.ir.calls) {
|
|
27148
|
+
const det = this.detectJs(call);
|
|
27149
|
+
if (!det)
|
|
27150
|
+
continue;
|
|
27151
|
+
insecureCookies.push(det);
|
|
27152
|
+
this.emit(ctx, file, det, "js");
|
|
27153
|
+
}
|
|
27154
|
+
} else if (language === "python") {
|
|
27155
|
+
for (const call of graph.ir.calls) {
|
|
27156
|
+
const det = this.detectPython(call);
|
|
27157
|
+
if (!det)
|
|
27158
|
+
continue;
|
|
27159
|
+
insecureCookies.push(det);
|
|
27160
|
+
this.emit(ctx, file, det, "python");
|
|
27161
|
+
}
|
|
27162
|
+
} else if (language === "java") {
|
|
27163
|
+
const hasSetSecureTrue = JAVA_SET_SECURE_TRUE_RE.test(code);
|
|
27164
|
+
const hasSetHttpOnlyTrue = JAVA_SET_HTTPONLY_TRUE_RE.test(code);
|
|
27165
|
+
for (const call of graph.ir.calls) {
|
|
27166
|
+
const det = this.detectJavaCookieCtor(call, hasSetSecureTrue, hasSetHttpOnlyTrue);
|
|
27167
|
+
if (!det)
|
|
27168
|
+
continue;
|
|
27169
|
+
insecureCookies.push(det);
|
|
27170
|
+
this.emit(ctx, file, det, "java");
|
|
27171
|
+
}
|
|
27172
|
+
}
|
|
27173
|
+
return { insecureCookies };
|
|
27174
|
+
}
|
|
27175
|
+
detectJs(call) {
|
|
27176
|
+
if (call.method_name !== "cookie")
|
|
27177
|
+
return null;
|
|
27178
|
+
const receiver = call.receiver ?? "";
|
|
27179
|
+
if (!COOKIE_RESPONSE_RECEIVERS.has(receiver))
|
|
27180
|
+
return null;
|
|
27181
|
+
if (call.arguments.length < 2)
|
|
27182
|
+
return null;
|
|
27183
|
+
const opts = call.arguments.find((a) => a.position === 2);
|
|
27184
|
+
const optsExpr = (opts?.expression ?? "").trim();
|
|
27185
|
+
const optionsPresent = optsExpr.length > 0;
|
|
27186
|
+
const missingSecure = !SECURE_TRUE_RE.test(optsExpr);
|
|
27187
|
+
const missingHttpOnly = !HTTPONLY_TRUE_RE.test(optsExpr);
|
|
27188
|
+
if (!missingSecure && !missingHttpOnly)
|
|
27189
|
+
return null;
|
|
27190
|
+
return {
|
|
27191
|
+
line: call.location.line,
|
|
27192
|
+
receiver,
|
|
27193
|
+
missingSecure,
|
|
27194
|
+
missingHttpOnly,
|
|
27195
|
+
optionsPresent
|
|
27196
|
+
};
|
|
27197
|
+
}
|
|
27198
|
+
detectPython(call) {
|
|
27199
|
+
if (call.method_name !== "set_cookie")
|
|
27200
|
+
return null;
|
|
27201
|
+
const receiver = call.receiver ?? "";
|
|
27202
|
+
if (!PY_SET_COOKIE_RECEIVERS.has(receiver))
|
|
27203
|
+
return null;
|
|
27204
|
+
const argsBlob = call.arguments.map((a) => a.expression ?? "").join(", ");
|
|
27205
|
+
const missingSecure = !PY_SECURE_TRUE_RE.test(argsBlob);
|
|
27206
|
+
const missingHttpOnly = !PY_HTTPONLY_TRUE_RE.test(argsBlob);
|
|
27207
|
+
if (!missingSecure && !missingHttpOnly)
|
|
27208
|
+
return null;
|
|
27209
|
+
return {
|
|
27210
|
+
line: call.location.line,
|
|
27211
|
+
receiver,
|
|
27212
|
+
missingSecure,
|
|
27213
|
+
missingHttpOnly,
|
|
27214
|
+
optionsPresent: call.arguments.length >= 2
|
|
27215
|
+
};
|
|
27216
|
+
}
|
|
27217
|
+
detectJavaCookieCtor(call, hasSetSecureTrue, hasSetHttpOnlyTrue) {
|
|
27218
|
+
if (call.method_name !== "Cookie")
|
|
27219
|
+
return null;
|
|
27220
|
+
const looksLikeCtor = call.is_constructor || !call.receiver && call.receiver_type === "Cookie" || (call.resolution?.target ?? "").endsWith(".<init>");
|
|
27221
|
+
if (!looksLikeCtor)
|
|
27222
|
+
return null;
|
|
27223
|
+
if (call.arguments.length < 2)
|
|
27224
|
+
return null;
|
|
27225
|
+
const missingSecure = !hasSetSecureTrue;
|
|
27226
|
+
const missingHttpOnly = !hasSetHttpOnlyTrue;
|
|
27227
|
+
if (!missingSecure && !missingHttpOnly)
|
|
27228
|
+
return null;
|
|
27229
|
+
return {
|
|
27230
|
+
line: call.location.line,
|
|
27231
|
+
receiver: "new Cookie",
|
|
27232
|
+
missingSecure,
|
|
27233
|
+
missingHttpOnly,
|
|
27234
|
+
optionsPresent: false
|
|
27235
|
+
};
|
|
27236
|
+
}
|
|
27237
|
+
emit(ctx, file, det, flavor) {
|
|
27238
|
+
const missing = [];
|
|
27239
|
+
if (det.missingSecure)
|
|
27240
|
+
missing.push(flavor === "js" ? "`secure: true`" : flavor === "python" ? "`secure=True`" : "`setSecure(true)`");
|
|
27241
|
+
if (det.missingHttpOnly)
|
|
27242
|
+
missing.push(flavor === "js" ? "`httpOnly: true`" : flavor === "python" ? "`httponly=True`" : "`setHttpOnly(true)`");
|
|
27243
|
+
const fix = flavor === "js" ? 'Pass `{ secure: true, httpOnly: true, sameSite: "lax" }` as the third argument to `res.cookie()`.' : flavor === "python" ? 'Pass `secure=True, httponly=True, samesite="Lax"` to `response.set_cookie(...)`.' : "After constructing the cookie, call `cookie.setSecure(true)` and `cookie.setHttpOnly(true)` before adding it to the response.";
|
|
27244
|
+
ctx.addFinding({
|
|
27245
|
+
id: `${this.name}-${file}-${det.line}`,
|
|
27246
|
+
pass: this.name,
|
|
27247
|
+
category: this.category,
|
|
27248
|
+
rule_id: this.name,
|
|
27249
|
+
cwe: "CWE-614",
|
|
27250
|
+
severity: "medium",
|
|
27251
|
+
level: "warning",
|
|
27252
|
+
message: `Cookie set without ${missing.join(" and ")} — vulnerable to ` + `cleartext transmission (CWE-614) and client-side JS access ` + `(CWE-1004).`,
|
|
27253
|
+
file,
|
|
27254
|
+
line: det.line,
|
|
27255
|
+
fix,
|
|
27256
|
+
evidence: {
|
|
27257
|
+
receiver: det.receiver,
|
|
27258
|
+
options_present: det.optionsPresent,
|
|
27259
|
+
missing_secure: det.missingSecure,
|
|
27260
|
+
missing_http_only: det.missingHttpOnly
|
|
27261
|
+
}
|
|
27262
|
+
});
|
|
27263
|
+
}
|
|
27264
|
+
}
|
|
27265
|
+
|
|
27266
|
+
// ../circle-ir/dist/analysis/passes/weak-hash-pass.js
|
|
27267
|
+
var WEAK_HASH_NAMES = new Set([
|
|
27268
|
+
"md2",
|
|
27269
|
+
"md4",
|
|
27270
|
+
"md5",
|
|
27271
|
+
"sha-1",
|
|
27272
|
+
"sha1"
|
|
27273
|
+
]);
|
|
27274
|
+
var COMMONS_DIGEST_METHODS = new Set([
|
|
27275
|
+
"md2",
|
|
27276
|
+
"md2Hex",
|
|
27277
|
+
"md5",
|
|
27278
|
+
"md5Hex",
|
|
27279
|
+
"sha1",
|
|
27280
|
+
"sha1Hex",
|
|
27281
|
+
"sha",
|
|
27282
|
+
"shaHex"
|
|
27283
|
+
]);
|
|
27284
|
+
var PY_HASHLIB_WEAK = new Set(["md5", "sha1", "md4", "md2", "new"]);
|
|
27285
|
+
function stripQuotes4(s) {
|
|
27286
|
+
const trimmed = s.trim();
|
|
27287
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'") || trimmed.startsWith("`") && trimmed.endsWith("`")) {
|
|
27288
|
+
return trimmed.slice(1, -1);
|
|
27289
|
+
}
|
|
27290
|
+
return trimmed;
|
|
27291
|
+
}
|
|
27292
|
+
function literalAlgo(call, position) {
|
|
27293
|
+
const arg = call.arguments.find((a) => a.position === position);
|
|
27294
|
+
if (!arg)
|
|
27295
|
+
return null;
|
|
27296
|
+
const raw = arg.literal ?? arg.expression ?? "";
|
|
27297
|
+
const cleaned = stripQuotes4(raw).toLowerCase();
|
|
27298
|
+
return cleaned || null;
|
|
27299
|
+
}
|
|
27300
|
+
|
|
27301
|
+
class WeakHashPass {
|
|
27302
|
+
name = "weak-hash";
|
|
27303
|
+
category = "security";
|
|
27304
|
+
run(ctx) {
|
|
27305
|
+
const { graph, language } = ctx;
|
|
27306
|
+
const file = graph.ir.meta.file;
|
|
27307
|
+
const findings = [];
|
|
27128
27308
|
for (const call of graph.ir.calls) {
|
|
27129
|
-
|
|
27130
|
-
|
|
27131
|
-
const receiver = call.receiver ?? "";
|
|
27132
|
-
if (!COOKIE_RESPONSE_RECEIVERS.has(receiver))
|
|
27133
|
-
continue;
|
|
27134
|
-
if (call.arguments.length < 2)
|
|
27135
|
-
continue;
|
|
27136
|
-
const opts = call.arguments.find((a) => a.position === 2);
|
|
27137
|
-
const optsExpr = (opts?.expression ?? "").trim();
|
|
27138
|
-
const optionsPresent = optsExpr.length > 0;
|
|
27139
|
-
const missingSecure = !SECURE_TRUE_RE.test(optsExpr);
|
|
27140
|
-
const missingHttpOnly = !HTTPONLY_TRUE_RE.test(optsExpr);
|
|
27141
|
-
if (!missingSecure && !missingHttpOnly)
|
|
27309
|
+
const detection = this.detect(call, language);
|
|
27310
|
+
if (!detection)
|
|
27142
27311
|
continue;
|
|
27312
|
+
const { algorithm, api } = detection;
|
|
27143
27313
|
const line = call.location.line;
|
|
27144
|
-
|
|
27314
|
+
findings.push({ line, language, algorithm, api });
|
|
27315
|
+
ctx.addFinding({
|
|
27316
|
+
id: `${this.name}-${file}-${line}`,
|
|
27317
|
+
pass: this.name,
|
|
27318
|
+
category: this.category,
|
|
27319
|
+
rule_id: this.name,
|
|
27320
|
+
cwe: "CWE-328",
|
|
27321
|
+
severity: "medium",
|
|
27322
|
+
level: "warning",
|
|
27323
|
+
message: `Weak hash algorithm \`${algorithm.toUpperCase()}\` used via \`${api}\`. ` + "MD2/MD4/MD5/SHA-1 are cryptographically broken and must not be used " + "for passwords, signatures, integrity checks, or anywhere collision " + "resistance is required.",
|
|
27324
|
+
file,
|
|
27145
27325
|
line,
|
|
27146
|
-
|
|
27147
|
-
|
|
27148
|
-
missingHttpOnly,
|
|
27149
|
-
optionsPresent
|
|
27326
|
+
fix: "Use SHA-256 or stronger (SHA-384, SHA-512, SHA-3). For passwords, " + "use a password-hashing function: bcrypt, scrypt, Argon2, or PBKDF2.",
|
|
27327
|
+
evidence: { algorithm, api, language }
|
|
27150
27328
|
});
|
|
27151
|
-
|
|
27152
|
-
|
|
27153
|
-
|
|
27154
|
-
|
|
27155
|
-
|
|
27329
|
+
}
|
|
27330
|
+
return { findings };
|
|
27331
|
+
}
|
|
27332
|
+
detect(call, language) {
|
|
27333
|
+
const method = call.method_name;
|
|
27334
|
+
const receiver = call.receiver ?? "";
|
|
27335
|
+
if (language === "java") {
|
|
27336
|
+
if (method === "getInstance" && (receiver === "MessageDigest" || receiver.endsWith(".MessageDigest"))) {
|
|
27337
|
+
const algo = literalAlgo(call, 0);
|
|
27338
|
+
if (algo && WEAK_HASH_NAMES.has(algo)) {
|
|
27339
|
+
return { algorithm: algo, api: "MessageDigest.getInstance" };
|
|
27340
|
+
}
|
|
27341
|
+
}
|
|
27342
|
+
if (COMMONS_DIGEST_METHODS.has(method) && (receiver === "DigestUtils" || receiver.endsWith(".DigestUtils"))) {
|
|
27343
|
+
const algoFromMethod = method.toLowerCase().replace(/hex$/, "");
|
|
27344
|
+
const normalized = algoFromMethod === "sha" ? "sha1" : algoFromMethod;
|
|
27345
|
+
return { algorithm: normalized, api: `DigestUtils.${method}` };
|
|
27346
|
+
}
|
|
27347
|
+
return null;
|
|
27348
|
+
}
|
|
27349
|
+
if (language === "python") {
|
|
27350
|
+
if ((receiver === "hashlib" || receiver.endsWith(".hashlib")) && PY_HASHLIB_WEAK.has(method)) {
|
|
27351
|
+
if (method === "new") {
|
|
27352
|
+
const algo = literalAlgo(call, 0);
|
|
27353
|
+
if (algo && WEAK_HASH_NAMES.has(algo)) {
|
|
27354
|
+
return { algorithm: algo, api: "hashlib.new" };
|
|
27355
|
+
}
|
|
27356
|
+
return null;
|
|
27357
|
+
}
|
|
27358
|
+
return { algorithm: method, api: `hashlib.${method}` };
|
|
27359
|
+
}
|
|
27360
|
+
return null;
|
|
27361
|
+
}
|
|
27362
|
+
if (language === "javascript" || language === "typescript") {
|
|
27363
|
+
if ((method === "createHash" || method === "createHmac") && receiver === "crypto") {
|
|
27364
|
+
const algo = literalAlgo(call, 0);
|
|
27365
|
+
if (algo && WEAK_HASH_NAMES.has(algo)) {
|
|
27366
|
+
return { algorithm: algo, api: `crypto.${method}` };
|
|
27367
|
+
}
|
|
27368
|
+
}
|
|
27369
|
+
return null;
|
|
27370
|
+
}
|
|
27371
|
+
if (language === "go") {
|
|
27372
|
+
const isWeakPkg = receiver === "md5" || receiver === "sha1";
|
|
27373
|
+
if (isWeakPkg && (method === "New" || method === "Sum")) {
|
|
27374
|
+
return { algorithm: receiver, api: `${receiver}.${method}` };
|
|
27375
|
+
}
|
|
27376
|
+
return null;
|
|
27377
|
+
}
|
|
27378
|
+
return null;
|
|
27379
|
+
}
|
|
27380
|
+
}
|
|
27381
|
+
|
|
27382
|
+
// ../circle-ir/dist/analysis/passes/weak-crypto-pass.js
|
|
27383
|
+
var WEAK_CIPHER_BASES = new Set([
|
|
27384
|
+
"des",
|
|
27385
|
+
"3des",
|
|
27386
|
+
"desede",
|
|
27387
|
+
"tripledes",
|
|
27388
|
+
"rc2",
|
|
27389
|
+
"rc4",
|
|
27390
|
+
"arc4",
|
|
27391
|
+
"blowfish",
|
|
27392
|
+
"bf",
|
|
27393
|
+
"idea",
|
|
27394
|
+
"seed",
|
|
27395
|
+
"cast5"
|
|
27396
|
+
]);
|
|
27397
|
+
function classifyJavaCipherSpec(spec) {
|
|
27398
|
+
const parts2 = spec.split("/").map((p) => p.trim().toLowerCase());
|
|
27399
|
+
const base = parts2[0] ?? "";
|
|
27400
|
+
const mode = parts2[1] ?? "";
|
|
27401
|
+
const result = {};
|
|
27402
|
+
if (WEAK_CIPHER_BASES.has(base))
|
|
27403
|
+
result.weakBase = base;
|
|
27404
|
+
if (mode === "ecb")
|
|
27405
|
+
result.ecb = true;
|
|
27406
|
+
if (parts2.length === 1 && base === "aes")
|
|
27407
|
+
result.ecb = true;
|
|
27408
|
+
return result;
|
|
27409
|
+
}
|
|
27410
|
+
function stripQuotes5(s) {
|
|
27411
|
+
const t = s.trim();
|
|
27412
|
+
if (t.startsWith('"') && t.endsWith('"') || t.startsWith("'") && t.endsWith("'") || t.startsWith("`") && t.endsWith("`")) {
|
|
27413
|
+
return t.slice(1, -1);
|
|
27414
|
+
}
|
|
27415
|
+
return t;
|
|
27416
|
+
}
|
|
27417
|
+
function literalAlgo2(call, position) {
|
|
27418
|
+
const arg = call.arguments.find((a) => a.position === position);
|
|
27419
|
+
if (!arg)
|
|
27420
|
+
return null;
|
|
27421
|
+
const raw = arg.literal ?? arg.expression ?? "";
|
|
27422
|
+
const cleaned = stripQuotes5(raw);
|
|
27423
|
+
return cleaned || null;
|
|
27424
|
+
}
|
|
27425
|
+
|
|
27426
|
+
class WeakCryptoPass {
|
|
27427
|
+
name = "weak-crypto";
|
|
27428
|
+
category = "security";
|
|
27429
|
+
run(ctx) {
|
|
27430
|
+
const { graph, language } = ctx;
|
|
27431
|
+
const file = graph.ir.meta.file;
|
|
27432
|
+
const findings = [];
|
|
27433
|
+
for (const call of graph.ir.calls) {
|
|
27434
|
+
const detections = this.detect(call, language);
|
|
27435
|
+
for (const det of detections) {
|
|
27436
|
+
const line = call.location.line;
|
|
27437
|
+
findings.push({ line, language, ...det });
|
|
27438
|
+
const message = this.buildMessage(det);
|
|
27439
|
+
ctx.addFinding({
|
|
27440
|
+
id: `${this.name}-${file}-${line}-${det.issue}`,
|
|
27441
|
+
pass: this.name,
|
|
27442
|
+
category: this.category,
|
|
27443
|
+
rule_id: this.name,
|
|
27444
|
+
cwe: "CWE-327",
|
|
27445
|
+
severity: "high",
|
|
27446
|
+
level: "error",
|
|
27447
|
+
message,
|
|
27448
|
+
file,
|
|
27449
|
+
line,
|
|
27450
|
+
fix: "Use AES-GCM (authenticated) or ChaCha20-Poly1305. Avoid DES, " + "3DES, RC2, RC4, Blowfish, and ECB mode. For asymmetric encryption " + "use RSA-OAEP with ≥2048-bit keys or modern curve-based schemes.",
|
|
27451
|
+
evidence: { ...det, language }
|
|
27452
|
+
});
|
|
27453
|
+
}
|
|
27454
|
+
}
|
|
27455
|
+
return { findings };
|
|
27456
|
+
}
|
|
27457
|
+
buildMessage(det) {
|
|
27458
|
+
switch (det.issue) {
|
|
27459
|
+
case "weak-cipher":
|
|
27460
|
+
return `Weak symmetric cipher \`${det.detail.toUpperCase()}\` used via ` + `\`${det.api}\`. DES, 3DES, RC2, RC4, Blowfish, and IDEA/SEED/CAST5 ` + "are deprecated and broken at modern key sizes.";
|
|
27461
|
+
case "ecb-mode":
|
|
27462
|
+
return `ECB block-cipher mode used via \`${det.api}\` (\`${det.detail}\`). ` + "ECB leaks plaintext structure (identical blocks → identical ciphertext) " + "and is not semantically secure.";
|
|
27463
|
+
case "deprecated-api":
|
|
27464
|
+
return `Deprecated crypto API \`${det.api}\` used (no IV: \`${det.detail}\`). ` + "This API derives the key/IV from a password in an insecure way.";
|
|
27465
|
+
default:
|
|
27466
|
+
return `Weak cryptography: ${det.detail} (${det.api})`;
|
|
27467
|
+
}
|
|
27468
|
+
}
|
|
27469
|
+
detect(call, language) {
|
|
27470
|
+
const method = call.method_name;
|
|
27471
|
+
const receiver = call.receiver ?? "";
|
|
27472
|
+
const out2 = [];
|
|
27473
|
+
if (language === "java") {
|
|
27474
|
+
const isCipherFactory = method === "getInstance" && (receiver === "Cipher" || receiver.endsWith(".Cipher") || receiver === "KeyGenerator" || receiver.endsWith(".KeyGenerator"));
|
|
27475
|
+
if (isCipherFactory) {
|
|
27476
|
+
const spec = literalAlgo2(call, 0);
|
|
27477
|
+
if (spec) {
|
|
27478
|
+
const { weakBase, ecb } = classifyJavaCipherSpec(spec);
|
|
27479
|
+
const api = `${receiver}.getInstance`;
|
|
27480
|
+
if (weakBase)
|
|
27481
|
+
out2.push({ issue: "weak-cipher", detail: weakBase, api });
|
|
27482
|
+
if (ecb)
|
|
27483
|
+
out2.push({ issue: "ecb-mode", detail: spec, api });
|
|
27484
|
+
}
|
|
27485
|
+
}
|
|
27486
|
+
return out2;
|
|
27487
|
+
}
|
|
27488
|
+
if (language === "python") {
|
|
27489
|
+
if (method === "new") {
|
|
27490
|
+
const rcvLower = receiver.toLowerCase();
|
|
27491
|
+
const lastSeg = rcvLower.split(".").pop() ?? rcvLower;
|
|
27492
|
+
if (WEAK_CIPHER_BASES.has(lastSeg)) {
|
|
27493
|
+
out2.push({ issue: "weak-cipher", detail: lastSeg, api: `${receiver}.new` });
|
|
27494
|
+
}
|
|
27495
|
+
if (lastSeg === "aes" || lastSeg.endsWith(".aes")) {
|
|
27496
|
+
const mode = call.arguments.find((a) => a.position === 1);
|
|
27497
|
+
const modeExpr = (mode?.expression ?? "").trim();
|
|
27498
|
+
if (/\bMODE_ECB\b/.test(modeExpr)) {
|
|
27499
|
+
out2.push({ issue: "ecb-mode", detail: "AES.MODE_ECB", api: `${receiver}.new` });
|
|
27500
|
+
}
|
|
27501
|
+
}
|
|
27502
|
+
}
|
|
27503
|
+
const isHazmatAlgos = receiver === "algorithms" || receiver.endsWith(".algorithms");
|
|
27504
|
+
if (isHazmatAlgos) {
|
|
27505
|
+
const m = method.toLowerCase();
|
|
27506
|
+
const normalized = m === "tripledes" ? "3des" : m;
|
|
27507
|
+
if (WEAK_CIPHER_BASES.has(normalized)) {
|
|
27508
|
+
out2.push({ issue: "weak-cipher", detail: normalized, api: `algorithms.${method}` });
|
|
27509
|
+
}
|
|
27510
|
+
}
|
|
27511
|
+
return out2;
|
|
27512
|
+
}
|
|
27513
|
+
if (language === "javascript" || language === "typescript") {
|
|
27514
|
+
if (method === "createCipher" && receiver === "crypto") {
|
|
27515
|
+
const algo = literalAlgo2(call, 0) ?? "<unknown>";
|
|
27516
|
+
out2.push({ issue: "deprecated-api", detail: algo, api: "crypto.createCipher" });
|
|
27517
|
+
}
|
|
27518
|
+
if (method === "createCipheriv" && receiver === "crypto") {
|
|
27519
|
+
const algo = literalAlgo2(call, 0);
|
|
27520
|
+
if (algo) {
|
|
27521
|
+
const lower = algo.toLowerCase();
|
|
27522
|
+
const parts2 = lower.split("-");
|
|
27523
|
+
const base = parts2[0];
|
|
27524
|
+
const mode = parts2[parts2.length - 1];
|
|
27525
|
+
let normalizedBase = base;
|
|
27526
|
+
if (base === "bf")
|
|
27527
|
+
normalizedBase = "blowfish";
|
|
27528
|
+
if (base === "desede" || base === "des-ede3" || base === "des3")
|
|
27529
|
+
normalizedBase = "3des";
|
|
27530
|
+
if (WEAK_CIPHER_BASES.has(normalizedBase)) {
|
|
27531
|
+
out2.push({ issue: "weak-cipher", detail: normalizedBase, api: "crypto.createCipheriv" });
|
|
27532
|
+
}
|
|
27533
|
+
if (mode === "ecb") {
|
|
27534
|
+
out2.push({ issue: "ecb-mode", detail: lower, api: "crypto.createCipheriv" });
|
|
27535
|
+
}
|
|
27536
|
+
}
|
|
27537
|
+
}
|
|
27538
|
+
return out2;
|
|
27539
|
+
}
|
|
27540
|
+
if (language === "go") {
|
|
27541
|
+
if (receiver === "des" && (method === "NewCipher" || method === "NewTripleDESCipher")) {
|
|
27542
|
+
const base = method === "NewTripleDESCipher" ? "3des" : "des";
|
|
27543
|
+
out2.push({ issue: "weak-cipher", detail: base, api: `des.${method}` });
|
|
27544
|
+
}
|
|
27545
|
+
if (receiver === "rc4" && method === "NewCipher") {
|
|
27546
|
+
out2.push({ issue: "weak-cipher", detail: "rc4", api: "rc4.NewCipher" });
|
|
27547
|
+
}
|
|
27548
|
+
if ((method === "NewECBEncrypter" || method === "NewECBDecrypter") && receiver === "cipher") {
|
|
27549
|
+
out2.push({ issue: "ecb-mode", detail: method, api: `cipher.${method}` });
|
|
27550
|
+
}
|
|
27551
|
+
return out2;
|
|
27552
|
+
}
|
|
27553
|
+
return out2;
|
|
27554
|
+
}
|
|
27555
|
+
}
|
|
27556
|
+
|
|
27557
|
+
// ../circle-ir/dist/analysis/passes/weak-random-pass.js
|
|
27558
|
+
var JAVA_RANDOM_METHODS = new Set([
|
|
27559
|
+
"nextInt",
|
|
27560
|
+
"nextLong",
|
|
27561
|
+
"nextFloat",
|
|
27562
|
+
"nextDouble",
|
|
27563
|
+
"nextBoolean",
|
|
27564
|
+
"nextBytes",
|
|
27565
|
+
"nextGaussian",
|
|
27566
|
+
"ints",
|
|
27567
|
+
"longs",
|
|
27568
|
+
"doubles"
|
|
27569
|
+
]);
|
|
27570
|
+
var PY_RANDOM_FUNCS = new Set([
|
|
27571
|
+
"random",
|
|
27572
|
+
"randint",
|
|
27573
|
+
"choice",
|
|
27574
|
+
"uniform",
|
|
27575
|
+
"shuffle",
|
|
27576
|
+
"getrandbits",
|
|
27577
|
+
"sample",
|
|
27578
|
+
"choices",
|
|
27579
|
+
"randrange",
|
|
27580
|
+
"seed",
|
|
27581
|
+
"gauss",
|
|
27582
|
+
"normalvariate",
|
|
27583
|
+
"expovariate",
|
|
27584
|
+
"paretovariate",
|
|
27585
|
+
"weibullvariate",
|
|
27586
|
+
"triangular",
|
|
27587
|
+
"lognormvariate",
|
|
27588
|
+
"vonmisesvariate",
|
|
27589
|
+
"betavariate",
|
|
27590
|
+
"gammavariate"
|
|
27591
|
+
]);
|
|
27592
|
+
var GO_RAND_FUNCS = new Set([
|
|
27593
|
+
"Int",
|
|
27594
|
+
"Intn",
|
|
27595
|
+
"Int31",
|
|
27596
|
+
"Int31n",
|
|
27597
|
+
"Int63",
|
|
27598
|
+
"Int63n",
|
|
27599
|
+
"Float32",
|
|
27600
|
+
"Float64",
|
|
27601
|
+
"NormFloat64",
|
|
27602
|
+
"ExpFloat64",
|
|
27603
|
+
"Perm",
|
|
27604
|
+
"Shuffle",
|
|
27605
|
+
"Read",
|
|
27606
|
+
"Uint32",
|
|
27607
|
+
"Uint64",
|
|
27608
|
+
"Seed",
|
|
27609
|
+
"New",
|
|
27610
|
+
"NewSource"
|
|
27611
|
+
]);
|
|
27612
|
+
|
|
27613
|
+
class WeakRandomPass {
|
|
27614
|
+
name = "weak-random";
|
|
27615
|
+
category = "security";
|
|
27616
|
+
run(ctx) {
|
|
27617
|
+
const { graph, language } = ctx;
|
|
27618
|
+
const file = graph.ir.meta.file;
|
|
27619
|
+
const findings = [];
|
|
27620
|
+
for (const call of graph.ir.calls) {
|
|
27621
|
+
const api = this.detect(call, language, ctx);
|
|
27622
|
+
if (!api)
|
|
27623
|
+
continue;
|
|
27624
|
+
const line = call.location.line;
|
|
27625
|
+
findings.push({ line, language, api });
|
|
27156
27626
|
ctx.addFinding({
|
|
27157
27627
|
id: `${this.name}-${file}-${line}`,
|
|
27158
27628
|
pass: this.name,
|
|
27159
27629
|
category: this.category,
|
|
27160
27630
|
rule_id: this.name,
|
|
27161
|
-
cwe: "CWE-
|
|
27631
|
+
cwe: "CWE-330",
|
|
27162
27632
|
severity: "medium",
|
|
27163
27633
|
level: "warning",
|
|
27164
|
-
message: `
|
|
27634
|
+
message: `Non-cryptographic random generator \`${api}\` used. The output of ` + "this PRNG is predictable and must not be used for security-sensitive " + "values (tokens, session IDs, keys, salts, password reset codes, OTPs).",
|
|
27165
27635
|
file,
|
|
27166
27636
|
line,
|
|
27167
|
-
fix:
|
|
27168
|
-
evidence: {
|
|
27169
|
-
|
|
27170
|
-
|
|
27171
|
-
|
|
27172
|
-
|
|
27637
|
+
fix: this.fixFor(language),
|
|
27638
|
+
evidence: { api, language }
|
|
27639
|
+
});
|
|
27640
|
+
}
|
|
27641
|
+
return { findings };
|
|
27642
|
+
}
|
|
27643
|
+
fixFor(language) {
|
|
27644
|
+
switch (language) {
|
|
27645
|
+
case "java":
|
|
27646
|
+
return "Use `java.security.SecureRandom`. Example: " + "`SecureRandom sr = new SecureRandom(); byte[] b = new byte[32]; sr.nextBytes(b);`";
|
|
27647
|
+
case "python":
|
|
27648
|
+
return "Use the `secrets` module (`secrets.token_bytes`, " + "`secrets.token_hex`, `secrets.choice`, `secrets.randbelow`).";
|
|
27649
|
+
case "javascript":
|
|
27650
|
+
case "typescript":
|
|
27651
|
+
return "Use `crypto.randomBytes(n)` (Node.js) or " + "`crypto.getRandomValues(typedArray)` (browser).";
|
|
27652
|
+
case "go":
|
|
27653
|
+
return "Use `crypto/rand` instead of `math/rand`. Example: " + "`b := make([]byte, 32); _, _ = rand.Read(b)` (where `rand` is `crypto/rand`).";
|
|
27654
|
+
default:
|
|
27655
|
+
return "Use a cryptographically secure random generator from your standard library.";
|
|
27656
|
+
}
|
|
27657
|
+
}
|
|
27658
|
+
detect(call, language, ctx) {
|
|
27659
|
+
const method = call.method_name;
|
|
27660
|
+
const receiver = call.receiver ?? "";
|
|
27661
|
+
if (language === "java") {
|
|
27662
|
+
if (call.is_constructor) {
|
|
27663
|
+
const ctor = method;
|
|
27664
|
+
if (ctor === "Random")
|
|
27665
|
+
return "new Random";
|
|
27666
|
+
if (ctor === "SplittableRandom")
|
|
27667
|
+
return "new SplittableRandom";
|
|
27668
|
+
}
|
|
27669
|
+
if (method === "random" && (receiver === "Math" || receiver.endsWith(".Math"))) {
|
|
27670
|
+
return "Math.random";
|
|
27671
|
+
}
|
|
27672
|
+
if (JAVA_RANDOM_METHODS.has(method)) {
|
|
27673
|
+
const rt = call.receiver_type ?? "";
|
|
27674
|
+
if (rt === "Random" || rt === "SplittableRandom" || rt === "ThreadLocalRandom") {
|
|
27675
|
+
return `${rt}.${method}`;
|
|
27676
|
+
}
|
|
27677
|
+
}
|
|
27678
|
+
if (JAVA_RANDOM_METHODS.has(method) && /ThreadLocalRandom\.current\(\)/.test(receiver)) {
|
|
27679
|
+
return `ThreadLocalRandom.current.${method}`;
|
|
27680
|
+
}
|
|
27681
|
+
return null;
|
|
27682
|
+
}
|
|
27683
|
+
if (language === "python") {
|
|
27684
|
+
if ((receiver === "random" || receiver.endsWith(".random")) && PY_RANDOM_FUNCS.has(method)) {
|
|
27685
|
+
return `random.${method}`;
|
|
27686
|
+
}
|
|
27687
|
+
return null;
|
|
27688
|
+
}
|
|
27689
|
+
if (language === "javascript" || language === "typescript") {
|
|
27690
|
+
if (method === "random" && (receiver === "Math" || receiver.endsWith(".Math"))) {
|
|
27691
|
+
return "Math.random";
|
|
27692
|
+
}
|
|
27693
|
+
return null;
|
|
27694
|
+
}
|
|
27695
|
+
if (language === "go") {
|
|
27696
|
+
if (receiver === "rand" && GO_RAND_FUNCS.has(method)) {
|
|
27697
|
+
if (this.goMathRandIsActive(ctx)) {
|
|
27698
|
+
return `rand.${method}`;
|
|
27173
27699
|
}
|
|
27700
|
+
}
|
|
27701
|
+
return null;
|
|
27702
|
+
}
|
|
27703
|
+
return null;
|
|
27704
|
+
}
|
|
27705
|
+
goMathRandIsActive(ctx) {
|
|
27706
|
+
const imports = ctx.graph.ir.imports ?? [];
|
|
27707
|
+
let mathRandUnaliased = false;
|
|
27708
|
+
let cryptoRandUnaliased = false;
|
|
27709
|
+
for (const imp of imports) {
|
|
27710
|
+
const pkg = imp.from_package ?? imp.imported_name;
|
|
27711
|
+
const alias = imp.alias;
|
|
27712
|
+
if (pkg === "math/rand" && (!alias || alias === "rand")) {
|
|
27713
|
+
mathRandUnaliased = true;
|
|
27714
|
+
}
|
|
27715
|
+
if (pkg === "crypto/rand" && (!alias || alias === "rand")) {
|
|
27716
|
+
cryptoRandUnaliased = true;
|
|
27717
|
+
}
|
|
27718
|
+
}
|
|
27719
|
+
return mathRandUnaliased && !cryptoRandUnaliased;
|
|
27720
|
+
}
|
|
27721
|
+
}
|
|
27722
|
+
|
|
27723
|
+
// ../circle-ir/dist/analysis/passes/tls-verify-disabled-pass.js
|
|
27724
|
+
var PY_HTTP_METHODS = new Set([
|
|
27725
|
+
"get",
|
|
27726
|
+
"post",
|
|
27727
|
+
"put",
|
|
27728
|
+
"delete",
|
|
27729
|
+
"patch",
|
|
27730
|
+
"head",
|
|
27731
|
+
"options",
|
|
27732
|
+
"request",
|
|
27733
|
+
"send"
|
|
27734
|
+
]);
|
|
27735
|
+
var PY_HTTP_RECEIVERS = new Set([
|
|
27736
|
+
"requests",
|
|
27737
|
+
"httpx"
|
|
27738
|
+
]);
|
|
27739
|
+
var VERIFY_FALSE_RE = /\bverify\s*=\s*False\b/;
|
|
27740
|
+
var REJECT_UNAUTHORIZED_FALSE_RE = /\brejectUnauthorized\s*:\s*false\b/;
|
|
27741
|
+
var INSECURE_SKIP_VERIFY_TRUE_RE = /\bInsecureSkipVerify\s*:\s*true\b/;
|
|
27742
|
+
var HOSTNAME_LAMBDA_TRUE_RE = /\(\s*\w+\s*,\s*\w+\s*\)\s*->\s*true\b/;
|
|
27743
|
+
var ALLOW_ALL_HOSTNAME_VERIFIERS = new Set([
|
|
27744
|
+
"NoopHostnameVerifier.INSTANCE",
|
|
27745
|
+
"new AllowAllHostnameVerifier()",
|
|
27746
|
+
"new NoopHostnameVerifier()"
|
|
27747
|
+
]);
|
|
27748
|
+
|
|
27749
|
+
class TlsVerifyDisabledPass {
|
|
27750
|
+
name = "tls-verify-disabled";
|
|
27751
|
+
category = "security";
|
|
27752
|
+
run(ctx) {
|
|
27753
|
+
const { graph, language, code } = ctx;
|
|
27754
|
+
const file = graph.ir.meta.file;
|
|
27755
|
+
const findings = [];
|
|
27756
|
+
for (const call of graph.ir.calls) {
|
|
27757
|
+
const det = this.detectCall(call, language);
|
|
27758
|
+
if (!det)
|
|
27759
|
+
continue;
|
|
27760
|
+
const line = call.location.line;
|
|
27761
|
+
findings.push({ line, language, ...det });
|
|
27762
|
+
ctx.addFinding({
|
|
27763
|
+
id: `${this.name}-${file}-${line}-${det.pattern}`,
|
|
27764
|
+
pass: this.name,
|
|
27765
|
+
category: this.category,
|
|
27766
|
+
rule_id: this.name,
|
|
27767
|
+
cwe: "CWE-295",
|
|
27768
|
+
severity: "high",
|
|
27769
|
+
level: "error",
|
|
27770
|
+
message: `TLS certificate verification disabled via \`${det.pattern}\` in ` + `\`${det.api}\`. The connection becomes vulnerable to active ` + "man-in-the-middle attacks — any attacker on the network path can " + "present a forged certificate.",
|
|
27771
|
+
file,
|
|
27772
|
+
line,
|
|
27773
|
+
fix: this.fixFor(language, det.pattern),
|
|
27774
|
+
evidence: { ...det, language }
|
|
27174
27775
|
});
|
|
27175
27776
|
}
|
|
27176
|
-
|
|
27777
|
+
for (const extra of this.detectSourceText(code, language)) {
|
|
27778
|
+
const dupKey = `${extra.line}-${extra.pattern}`;
|
|
27779
|
+
if (findings.some((f) => `${f.line}-${f.pattern}` === dupKey))
|
|
27780
|
+
continue;
|
|
27781
|
+
findings.push({ ...extra, language });
|
|
27782
|
+
ctx.addFinding({
|
|
27783
|
+
id: `${this.name}-${file}-${extra.line}-${extra.pattern}`,
|
|
27784
|
+
pass: this.name,
|
|
27785
|
+
category: this.category,
|
|
27786
|
+
rule_id: this.name,
|
|
27787
|
+
cwe: "CWE-295",
|
|
27788
|
+
severity: "high",
|
|
27789
|
+
level: "error",
|
|
27790
|
+
message: `TLS certificate verification disabled via \`${extra.pattern}\` ` + `(${extra.api}). Vulnerable to active man-in-the-middle.`,
|
|
27791
|
+
file,
|
|
27792
|
+
line: extra.line,
|
|
27793
|
+
fix: this.fixFor(language, extra.pattern),
|
|
27794
|
+
evidence: { ...extra, language }
|
|
27795
|
+
});
|
|
27796
|
+
}
|
|
27797
|
+
return { findings };
|
|
27798
|
+
}
|
|
27799
|
+
detectCall(call, language) {
|
|
27800
|
+
const method = call.method_name;
|
|
27801
|
+
const receiver = call.receiver ?? "";
|
|
27802
|
+
if (language === "python") {
|
|
27803
|
+
if (PY_HTTP_RECEIVERS.has(receiver) && PY_HTTP_METHODS.has(method)) {
|
|
27804
|
+
for (const arg of call.arguments) {
|
|
27805
|
+
const expr = (arg.expression ?? "").trim();
|
|
27806
|
+
if (VERIFY_FALSE_RE.test(expr)) {
|
|
27807
|
+
return { pattern: "verify=False", api: `${receiver}.${method}` };
|
|
27808
|
+
}
|
|
27809
|
+
}
|
|
27810
|
+
}
|
|
27811
|
+
if (method === "_create_unverified_context" && receiver === "ssl") {
|
|
27812
|
+
return { pattern: "ssl._create_unverified_context", api: "ssl._create_unverified_context()" };
|
|
27813
|
+
}
|
|
27814
|
+
if (receiver === "httpx" && method === "Client") {
|
|
27815
|
+
for (const arg of call.arguments) {
|
|
27816
|
+
if (VERIFY_FALSE_RE.test(arg.expression ?? "")) {
|
|
27817
|
+
return { pattern: "verify=False", api: "httpx.Client" };
|
|
27818
|
+
}
|
|
27819
|
+
}
|
|
27820
|
+
}
|
|
27821
|
+
return null;
|
|
27822
|
+
}
|
|
27823
|
+
if (language === "javascript" || language === "typescript") {
|
|
27824
|
+
const lastSeg = method.includes(".") ? method.split(".").pop() ?? "" : method;
|
|
27825
|
+
const arglooks = method === "request" || method === "get" || method === "post" || method === "create" || method === "Agent" || method === "fetch" || lastSeg === "Agent" || lastSeg === "request" || lastSeg === "create";
|
|
27826
|
+
if (arglooks) {
|
|
27827
|
+
for (const arg of call.arguments) {
|
|
27828
|
+
if (REJECT_UNAUTHORIZED_FALSE_RE.test(arg.expression ?? "")) {
|
|
27829
|
+
return { pattern: "rejectUnauthorized: false", api: `${receiver || "(global)"}.${method}` };
|
|
27830
|
+
}
|
|
27831
|
+
}
|
|
27832
|
+
}
|
|
27833
|
+
return null;
|
|
27834
|
+
}
|
|
27835
|
+
if (language === "java") {
|
|
27836
|
+
if (method === "setHostnameVerifier") {
|
|
27837
|
+
const arg = call.arguments.find((a) => a.position === 0);
|
|
27838
|
+
const expr = (arg?.expression ?? "").trim();
|
|
27839
|
+
if (HOSTNAME_LAMBDA_TRUE_RE.test(expr)) {
|
|
27840
|
+
return { pattern: "(h,s) -> true", api: "setHostnameVerifier" };
|
|
27841
|
+
}
|
|
27842
|
+
for (const v of ALLOW_ALL_HOSTNAME_VERIFIERS) {
|
|
27843
|
+
if (expr === v || expr.replace(/\s+/g, "") === v.replace(/\s+/g, "")) {
|
|
27844
|
+
return { pattern: v, api: "setHostnameVerifier" };
|
|
27845
|
+
}
|
|
27846
|
+
}
|
|
27847
|
+
}
|
|
27848
|
+
return null;
|
|
27849
|
+
}
|
|
27850
|
+
return null;
|
|
27851
|
+
}
|
|
27852
|
+
detectSourceText(code, language) {
|
|
27853
|
+
const out2 = [];
|
|
27854
|
+
const lines = code.split(`
|
|
27855
|
+
`);
|
|
27856
|
+
if (language === "go") {
|
|
27857
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
27858
|
+
if (INSECURE_SKIP_VERIFY_TRUE_RE.test(lines[i2])) {
|
|
27859
|
+
out2.push({
|
|
27860
|
+
line: i2 + 1,
|
|
27861
|
+
pattern: "InsecureSkipVerify: true",
|
|
27862
|
+
api: "tls.Config"
|
|
27863
|
+
});
|
|
27864
|
+
}
|
|
27865
|
+
}
|
|
27866
|
+
}
|
|
27867
|
+
if (language === "python") {
|
|
27868
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
27869
|
+
const l = lines[i2];
|
|
27870
|
+
if (/ssl\._create_default_https_context\s*=\s*ssl\._create_unverified_context/.test(l)) {
|
|
27871
|
+
out2.push({
|
|
27872
|
+
line: i2 + 1,
|
|
27873
|
+
pattern: "ssl._create_default_https_context = _create_unverified_context",
|
|
27874
|
+
api: "ssl module override"
|
|
27875
|
+
});
|
|
27876
|
+
}
|
|
27877
|
+
}
|
|
27878
|
+
}
|
|
27879
|
+
if (language === "javascript" || language === "typescript") {
|
|
27880
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
27881
|
+
const l = lines[i2];
|
|
27882
|
+
if (/process\.env\.NODE_TLS_REJECT_UNAUTHORIZED\s*=\s*['"]0['"]/.test(l)) {
|
|
27883
|
+
out2.push({
|
|
27884
|
+
line: i2 + 1,
|
|
27885
|
+
pattern: "NODE_TLS_REJECT_UNAUTHORIZED=0",
|
|
27886
|
+
api: "process.env"
|
|
27887
|
+
});
|
|
27888
|
+
}
|
|
27889
|
+
}
|
|
27890
|
+
}
|
|
27891
|
+
return out2;
|
|
27892
|
+
}
|
|
27893
|
+
fixFor(language, pattern) {
|
|
27894
|
+
if (pattern.includes("InsecureSkipVerify")) {
|
|
27895
|
+
return "Remove `InsecureSkipVerify: true` — let Go verify the cert. If " + "you need to trust a private CA, set `RootCAs` to a `*x509.CertPool` " + "containing that CA.";
|
|
27896
|
+
}
|
|
27897
|
+
if (pattern.includes("verify=False")) {
|
|
27898
|
+
return "Remove `verify=False`. To trust a private CA, pass `verify='/path/to/ca.pem'`.";
|
|
27899
|
+
}
|
|
27900
|
+
if (pattern.includes("rejectUnauthorized")) {
|
|
27901
|
+
return "Remove `rejectUnauthorized: false`. To trust a private CA, set the " + "`ca` option to the CA cert(s). Never disable TLS verification globally.";
|
|
27902
|
+
}
|
|
27903
|
+
if (pattern.includes("NODE_TLS_REJECT_UNAUTHORIZED")) {
|
|
27904
|
+
return "Remove the `NODE_TLS_REJECT_UNAUTHORIZED=0` assignment — it globally " + "disables TLS verification for every outbound HTTPS request.";
|
|
27905
|
+
}
|
|
27906
|
+
if (language === "java") {
|
|
27907
|
+
return "Do not use an always-true HostnameVerifier or AllowAllHostnameVerifier. " + "Use the JVM's default verifier; for self-signed certs add the cert to a " + "custom TrustManager that validates the chain.";
|
|
27908
|
+
}
|
|
27909
|
+
if (pattern.includes("ssl._create_unverified_context")) {
|
|
27910
|
+
return "Do not use `_create_unverified_context()`. Use `ssl.create_default_context()`.";
|
|
27911
|
+
}
|
|
27912
|
+
return "Restore TLS certificate and hostname verification.";
|
|
27177
27913
|
}
|
|
27178
27914
|
}
|
|
27179
27915
|
|
|
@@ -28289,6 +29025,14 @@ async function analyze(code, filePath, language, options = {}) {
|
|
|
28289
29025
|
pipeline.add(new Spring4ShellPass);
|
|
28290
29026
|
if (!disabledPasses.has("insecure-cookie"))
|
|
28291
29027
|
pipeline.add(new InsecureCookiePass);
|
|
29028
|
+
if (!disabledPasses.has("weak-hash"))
|
|
29029
|
+
pipeline.add(new WeakHashPass);
|
|
29030
|
+
if (!disabledPasses.has("weak-crypto"))
|
|
29031
|
+
pipeline.add(new WeakCryptoPass);
|
|
29032
|
+
if (!disabledPasses.has("weak-random"))
|
|
29033
|
+
pipeline.add(new WeakRandomPass);
|
|
29034
|
+
if (!disabledPasses.has("tls-verify-disabled"))
|
|
29035
|
+
pipeline.add(new TlsVerifyDisabledPass);
|
|
28292
29036
|
const { results, findings } = pipeline.run(graph, code, language, config);
|
|
28293
29037
|
const sinkFilter = results.get("sink-filter");
|
|
28294
29038
|
const interProc = results.get("interprocedural");
|
|
@@ -28482,7 +29226,7 @@ var colors = {
|
|
|
28482
29226
|
};
|
|
28483
29227
|
|
|
28484
29228
|
// src/version.ts
|
|
28485
|
-
var version = "3.
|
|
29229
|
+
var version = "3.52.0";
|
|
28486
29230
|
|
|
28487
29231
|
// src/formatters.ts
|
|
28488
29232
|
var SINK_SEVERITY = {
|
|
@@ -29264,7 +30008,9 @@ function isTestFile2(filePath) {
|
|
|
29264
30008
|
var LANG_MAP = {
|
|
29265
30009
|
".java": "java",
|
|
29266
30010
|
".js": "javascript",
|
|
30011
|
+
".jsx": "javascript",
|
|
29267
30012
|
".mjs": "javascript",
|
|
30013
|
+
".cjs": "javascript",
|
|
29268
30014
|
".ts": "typescript",
|
|
29269
30015
|
".tsx": "typescript",
|
|
29270
30016
|
".py": "python",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cognium-dev",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.52.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.52.0"
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
71
|
"@types/node": "^25.5.0",
|