cognium-dev 3.80.0 → 3.82.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 +927 -5
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -10660,7 +10660,6 @@ var DEFAULT_SINKS = [
10660
10660
  { method: "addObject", class: "ModelAndView", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [1] },
10661
10661
  { method: "println", type: "xss", cwe: "CWE-79", severity: "medium", arg_positions: [0] },
10662
10662
  { method: "print", type: "xss", cwe: "CWE-79", severity: "medium", arg_positions: [0] },
10663
- { method: "write", type: "xss", cwe: "CWE-79", severity: "medium", arg_positions: [0] },
10664
10663
  { method: "append", class: "StringBuilder", type: "xss", cwe: "CWE-79", severity: "medium", arg_positions: [0] },
10665
10664
  { method: "append", class: "StringBuffer", type: "xss", cwe: "CWE-79", severity: "medium", arg_positions: [0] },
10666
10665
  { method: "handleHyperlinks", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
@@ -11479,9 +11478,15 @@ var DEFAULT_SANITIZERS = [
11479
11478
  { method: "sanitize", class: "DOMPurify", removes: ["xss"] },
11480
11479
  { method: "escape", class: "validator", removes: ["xss"] },
11481
11480
  { method: "parse", class: "JSON", removes: ["xss", "code_injection"] },
11482
- { method: "parseInt", removes: ["sql_injection", "nosql_injection", "command_injection", "xss"] },
11483
- { method: "parseFloat", removes: ["sql_injection", "nosql_injection", "command_injection"] },
11484
- { method: "Number", removes: ["sql_injection", "nosql_injection", "command_injection"] },
11481
+ { method: "parseInt", removes: ["sql_injection", "nosql_injection", "command_injection", "xss", "external_taint_escape", "path_traversal", "code_injection"] },
11482
+ { method: "parseFloat", removes: ["sql_injection", "nosql_injection", "command_injection", "external_taint_escape", "path_traversal", "code_injection"] },
11483
+ { method: "Number", removes: ["sql_injection", "nosql_injection", "command_injection", "external_taint_escape", "path_traversal", "code_injection"] },
11484
+ { method: "min", class: "Math", removes: ["external_taint_escape"] },
11485
+ { method: "max", class: "Math", removes: ["external_taint_escape"] },
11486
+ { method: "includes", removes: ["external_taint_escape"] },
11487
+ { method: "has", removes: ["external_taint_escape"] },
11488
+ { method: "contains", removes: ["external_taint_escape"] },
11489
+ { method: "indexOf", removes: ["external_taint_escape"] },
11485
11490
  { method: "basename", class: "path", removes: ["path_traversal"] },
11486
11491
  { method: "normalize", class: "path", removes: ["path_traversal"] },
11487
11492
  { method: "resolve", class: "path", removes: ["path_traversal"] },
@@ -30162,6 +30167,911 @@ class WeakRandomPass {
30162
30167
  }
30163
30168
  }
30164
30169
 
30170
+ // ../circle-ir/dist/analysis/passes/_credential-helpers.js
30171
+ var CRED_KEYWORD_RE2 = /(?:password|passwd|pwd|secret|api[_-]?key|auth[_-]?token|private[_-]?key|access[_-]?key|credential)/i;
30172
+ function isCredentialIdentifier(name2) {
30173
+ if (!name2)
30174
+ return false;
30175
+ if (name2.length < 3)
30176
+ return false;
30177
+ return CRED_KEYWORD_RE2.test(name2);
30178
+ }
30179
+ function argLooksLikeCredential(arg) {
30180
+ if (!arg)
30181
+ return false;
30182
+ if (arg.variable && isCredentialIdentifier(arg.variable))
30183
+ return true;
30184
+ const expr = (arg.expression ?? "").trim();
30185
+ if (!expr)
30186
+ return false;
30187
+ const head = expr.split(/[.\s(]/, 1)[0] ?? "";
30188
+ return isCredentialIdentifier(head);
30189
+ }
30190
+ function stripQuotes6(s) {
30191
+ const t = s.trim();
30192
+ if (t.startsWith('"') && t.endsWith('"') || t.startsWith("'") && t.endsWith("'") || t.startsWith("`") && t.endsWith("`")) {
30193
+ return t.slice(1, -1);
30194
+ }
30195
+ return t;
30196
+ }
30197
+ function literalAt(call, position) {
30198
+ const arg = call.arguments.find((a) => a.position === position);
30199
+ if (!arg)
30200
+ return null;
30201
+ const raw = arg.literal ?? arg.expression ?? "";
30202
+ const trimmed = raw.trim();
30203
+ if (trimmed.startsWith('"') || trimmed.startsWith("'") || trimmed.startsWith("`")) {
30204
+ return stripQuotes6(trimmed);
30205
+ }
30206
+ if (arg.literal)
30207
+ return stripQuotes6(arg.literal);
30208
+ return null;
30209
+ }
30210
+ function isHashFunctionCall(call) {
30211
+ const method = call.method_name ?? "";
30212
+ const receiver = call.receiver ?? "";
30213
+ const recvLower = receiver.toLowerCase();
30214
+ if (recvLower === "bcrypt" || recvLower.endsWith(".bcrypt")) {
30215
+ return method === "hashpw" || method === "hash" || method === "hashSync" || method === "GenerateFromPassword" || method === "generate_password_hash";
30216
+ }
30217
+ if (recvLower === "argon2" || recvLower.endsWith(".argon2")) {
30218
+ return method === "hash" || method === "Hash" || method === "PasswordHash";
30219
+ }
30220
+ if (recvLower === "hashlib")
30221
+ return true;
30222
+ if (recvLower === "passlib" || recvLower.includes("passlib.hash"))
30223
+ return true;
30224
+ if (method === "PBKDF2HMAC" || method === "derive")
30225
+ return true;
30226
+ if (recvLower === "crypto") {
30227
+ return method === "createHash" || method === "createHmac" || method === "pbkdf2" || method === "pbkdf2Sync" || method === "scrypt" || method === "scryptSync";
30228
+ }
30229
+ if (receiver === "MessageDigest" || receiver.endsWith(".MessageDigest")) {
30230
+ return method === "getInstance" || method === "update" || method === "digest";
30231
+ }
30232
+ if (receiver === "DigestUtils" || receiver.endsWith(".DigestUtils")) {
30233
+ return true;
30234
+ }
30235
+ if (receiver === "SecretKeyFactory" || receiver.endsWith(".SecretKeyFactory")) {
30236
+ return method === "getInstance" || method === "generateSecret";
30237
+ }
30238
+ if (method === "PBEKeySpec")
30239
+ return true;
30240
+ if (receiver === "md5" || receiver === "sha1" || receiver === "sha256" || receiver === "sha512" || receiver === "sha3" || receiver.endsWith("/md5") || receiver.endsWith("/sha1") || receiver.endsWith("/sha256") || receiver.endsWith("/sha512")) {
30241
+ return method === "New" || method === "Sum" || method === "New224" || method === "New384";
30242
+ }
30243
+ const m = method.toLowerCase();
30244
+ if (m === "hash" || m === "hashpw" || m === "hashsync" || m === "pbkdf2" || m === "pbkdf2sync" || m === "scrypt" || m === "scryptsync")
30245
+ return true;
30246
+ return false;
30247
+ }
30248
+ function priorHashOf(varName, priorCalls) {
30249
+ for (const c of priorCalls) {
30250
+ if (!isHashFunctionCall(c))
30251
+ continue;
30252
+ for (const a of c.arguments) {
30253
+ if (a.variable === varName)
30254
+ return true;
30255
+ const head = (a.expression ?? "").trim().split(/[.\s(]/, 1)[0];
30256
+ if (head === varName)
30257
+ return true;
30258
+ }
30259
+ }
30260
+ return false;
30261
+ }
30262
+
30263
+ // ../circle-ir/dist/analysis/passes/weak-password-hash-pass.js
30264
+ var FAST_HASH_NAMES = new Set([
30265
+ "sha224",
30266
+ "sha-224",
30267
+ "sha256",
30268
+ "sha-256",
30269
+ "sha384",
30270
+ "sha-384",
30271
+ "sha512",
30272
+ "sha-512",
30273
+ "sha3",
30274
+ "sha-3",
30275
+ "sha3-256",
30276
+ "sha3-512"
30277
+ ]);
30278
+ var BCRYPT_MIN_COST = 10;
30279
+ var PBKDF2_MIN_ITERATIONS = 1e5;
30280
+ function intLiteral(s) {
30281
+ if (s == null)
30282
+ return null;
30283
+ const t = s.trim();
30284
+ if (!/^-?\d+$/.test(t))
30285
+ return null;
30286
+ const n = parseInt(t, 10);
30287
+ return Number.isFinite(n) ? n : null;
30288
+ }
30289
+ function pyKwargInt(call, name2) {
30290
+ for (const a of call.arguments) {
30291
+ const expr = (a.expression ?? "").trim();
30292
+ const m = expr.match(new RegExp(`^${name2}\\s*=\\s*(-?\\d+)$`));
30293
+ if (m)
30294
+ return parseInt(m[1], 10);
30295
+ }
30296
+ return null;
30297
+ }
30298
+
30299
+ class WeakPasswordHashPass {
30300
+ name = "weak-password-hash";
30301
+ category = "security";
30302
+ run(ctx) {
30303
+ const { graph, language } = ctx;
30304
+ const file = graph.ir.meta.file;
30305
+ const findings = [];
30306
+ for (const call of graph.ir.calls) {
30307
+ const detection = this.detect(call, language);
30308
+ if (!detection)
30309
+ continue;
30310
+ const { kind, api } = detection;
30311
+ const line = call.location.line;
30312
+ findings.push({ line, language, kind, api });
30313
+ const message = kind === "fast-unsalted-hash" ? `Fast/unsalted hash \`${api}\` applied to a password. ` + "General-purpose hashes (SHA-256/512) are unsuitable for password storage." : kind === "low-bcrypt-cost" ? `bcrypt called with insufficient cost factor (< ${BCRYPT_MIN_COST}).` : `PBKDF2 called with insufficient iteration count (< ${PBKDF2_MIN_ITERATIONS}).`;
30314
+ ctx.addFinding({
30315
+ id: `${this.name}-${file}-${line}`,
30316
+ pass: this.name,
30317
+ category: this.category,
30318
+ rule_id: this.name,
30319
+ cwe: "CWE-916",
30320
+ severity: "high",
30321
+ level: "warning",
30322
+ message,
30323
+ file,
30324
+ line,
30325
+ fix: "Use a memory-hard password-hashing function with appropriate cost: " + "Argon2id (recommended), bcrypt (cost ≥ 12), scrypt, or PBKDF2 with ≥ 600k iterations.",
30326
+ evidence: { kind, api, language }
30327
+ });
30328
+ }
30329
+ return { findings };
30330
+ }
30331
+ detect(call, language) {
30332
+ const method = call.method_name ?? "";
30333
+ const receiver = call.receiver ?? "";
30334
+ const recvLower = receiver.toLowerCase();
30335
+ if (recvLower === "bcrypt" || recvLower.endsWith(".bcrypt")) {
30336
+ if (method === "gensalt") {
30337
+ const rounds = pyKwargInt(call, "rounds");
30338
+ if (rounds !== null && rounds < BCRYPT_MIN_COST) {
30339
+ return { kind: "low-bcrypt-cost", api: "bcrypt.gensalt" };
30340
+ }
30341
+ }
30342
+ if (method === "hash" || method === "hashSync") {
30343
+ const cost = intLiteral(literalAt(call, 1));
30344
+ if (cost !== null && cost < BCRYPT_MIN_COST) {
30345
+ return { kind: "low-bcrypt-cost", api: `bcrypt.${method}` };
30346
+ }
30347
+ }
30348
+ if (method === "GenerateFromPassword") {
30349
+ const arg1 = call.arguments.find((a) => a.position === 1);
30350
+ const expr = (arg1?.expression ?? "").trim();
30351
+ const n = intLiteral(expr);
30352
+ if (n !== null && n < BCRYPT_MIN_COST) {
30353
+ return { kind: "low-bcrypt-cost", api: "bcrypt.GenerateFromPassword" };
30354
+ }
30355
+ if (expr === "bcrypt.MinCost") {
30356
+ return { kind: "low-bcrypt-cost", api: "bcrypt.GenerateFromPassword" };
30357
+ }
30358
+ }
30359
+ }
30360
+ if (method === "PBKDF2HMAC") {
30361
+ const iters = pyKwargInt(call, "iterations");
30362
+ if (iters !== null && iters < PBKDF2_MIN_ITERATIONS) {
30363
+ return { kind: "low-pbkdf2-iterations", api: "PBKDF2HMAC" };
30364
+ }
30365
+ }
30366
+ if ((method === "pbkdf2" || method === "pbkdf2Sync") && (recvLower === "crypto" || recvLower.endsWith(".crypto"))) {
30367
+ const iters = intLiteral(literalAt(call, 2));
30368
+ if (iters !== null && iters < PBKDF2_MIN_ITERATIONS) {
30369
+ return { kind: "low-pbkdf2-iterations", api: `crypto.${method}` };
30370
+ }
30371
+ }
30372
+ if (method === "PBEKeySpec" && language === "java") {
30373
+ const iters = intLiteral(literalAt(call, 2));
30374
+ if (iters !== null && iters < PBKDF2_MIN_ITERATIONS) {
30375
+ return { kind: "low-pbkdf2-iterations", api: "PBEKeySpec" };
30376
+ }
30377
+ }
30378
+ if (language === "python" && (recvLower === "hashlib" || recvLower.endsWith(".hashlib"))) {
30379
+ if (FAST_HASH_NAMES.has(method.toLowerCase())) {
30380
+ if (argLooksLikeCredential(call.arguments.find((a) => a.position === 0))) {
30381
+ return { kind: "fast-unsalted-hash", api: `hashlib.${method}` };
30382
+ }
30383
+ }
30384
+ if (method === "new") {
30385
+ const algo = literalAt(call, 0)?.toLowerCase() ?? "";
30386
+ if (FAST_HASH_NAMES.has(algo) && argLooksLikeCredential(call.arguments.find((a) => a.position === 1))) {
30387
+ return { kind: "fast-unsalted-hash", api: `hashlib.new(${algo})` };
30388
+ }
30389
+ }
30390
+ }
30391
+ if ((language === "javascript" || language === "typescript") && method === "update") {
30392
+ const recvExpr = (receiver ?? "").toLowerCase();
30393
+ const hashLike = recvExpr.includes("hash") || recvExpr.includes("createhash") || recvExpr.includes("sha") || recvExpr.includes("md");
30394
+ if (hashLike && argLooksLikeCredential(call.arguments.find((a) => a.position === 0))) {
30395
+ return { kind: "fast-unsalted-hash", api: "crypto.createHash().update" };
30396
+ }
30397
+ }
30398
+ if (language === "java" && method === "update") {
30399
+ const recvName = (receiver ?? "").toLowerCase();
30400
+ const looksLikeDigest = recvName.includes("digest") || recvName.includes("md") || recvName.includes("hash");
30401
+ if (looksLikeDigest && argLooksLikeCredential(call.arguments.find((a) => a.position === 0))) {
30402
+ return { kind: "fast-unsalted-hash", api: "MessageDigest.update" };
30403
+ }
30404
+ }
30405
+ if (language === "go") {
30406
+ const isFastPkg = receiver === "sha256" || receiver === "sha512" || receiver === "sha3" || receiver === "sha224";
30407
+ if (isFastPkg && (method === "Sum256" || method === "Sum512" || method === "Sum224" || method === "Sum384" || method === "Sum")) {
30408
+ const expr = (call.arguments.find((a) => a.position === 0)?.expression ?? "").trim();
30409
+ const inner = expr.replace(/^\[\]byte\s*\(\s*/, "").replace(/\s*\)\s*$/, "");
30410
+ if (argLooksLikeCredential({ position: 0, expression: inner, variable: inner })) {
30411
+ return { kind: "fast-unsalted-hash", api: `${receiver}.${method}` };
30412
+ }
30413
+ }
30414
+ }
30415
+ return null;
30416
+ }
30417
+ }
30418
+
30419
+ // ../circle-ir/dist/analysis/passes/weak-password-encoding-pass.js
30420
+ function isBasicAuthContext(call, code) {
30421
+ const line = call.location.line;
30422
+ if (line < 1)
30423
+ return false;
30424
+ const lines = code.split(`
30425
+ `);
30426
+ const start2 = Math.max(0, line - 2);
30427
+ const end = Math.min(lines.length, line + 1);
30428
+ const window2 = lines.slice(start2, end).join(`
30429
+ `);
30430
+ return /["'`]Basic\s/i.test(window2);
30431
+ }
30432
+
30433
+ class WeakPasswordEncodingPass {
30434
+ name = "weak-password-encoding";
30435
+ category = "security";
30436
+ run(ctx) {
30437
+ const { graph, language, code } = ctx;
30438
+ const file = graph.ir.meta.file;
30439
+ const findings = [];
30440
+ for (const call of graph.ir.calls) {
30441
+ const api = this.detect(call, language);
30442
+ if (!api)
30443
+ continue;
30444
+ if (isBasicAuthContext(call, code))
30445
+ continue;
30446
+ const line = call.location.line;
30447
+ findings.push({ line, language, api });
30448
+ ctx.addFinding({
30449
+ id: `${this.name}-${file}-${line}`,
30450
+ pass: this.name,
30451
+ category: this.category,
30452
+ rule_id: this.name,
30453
+ cwe: "CWE-261",
30454
+ severity: "medium",
30455
+ level: "warning",
30456
+ message: `Credential encoded via \`${api}\` — encoding is NOT encryption. ` + "Base64/hex provide no confidentiality; anyone with the encoded value can decode it.",
30457
+ file,
30458
+ line,
30459
+ fix: "For storage, use a password hash (Argon2id / bcrypt). " + "For transport, use TLS. For symmetric secrecy, use authenticated encryption (AES-GCM).",
30460
+ evidence: { api, language }
30461
+ });
30462
+ }
30463
+ return { findings };
30464
+ }
30465
+ detect(call, language) {
30466
+ const method = call.method_name ?? "";
30467
+ const receiver = call.receiver ?? "";
30468
+ const recvLower = receiver.toLowerCase();
30469
+ const arg0 = call.arguments.find((a) => a.position === 0);
30470
+ if (language === "python") {
30471
+ if (recvLower === "base64" && (method === "b64encode" || method === "urlsafe_b64encode" || method === "standard_b64encode")) {
30472
+ if (argLooksLikeCredential(arg0))
30473
+ return `base64.${method}`;
30474
+ }
30475
+ if (recvLower === "binascii" && method === "hexlify") {
30476
+ if (argLooksLikeCredential(arg0))
30477
+ return "binascii.hexlify";
30478
+ }
30479
+ }
30480
+ if (language === "javascript" || language === "typescript") {
30481
+ if (method === "toString") {
30482
+ const encoding = literalAt(call, 0);
30483
+ if (encoding === "base64" || encoding === "hex" || encoding === "base64url") {
30484
+ const recv = (receiver ?? "").toLowerCase();
30485
+ if (recv.includes("buffer.from") && /(?:password|passwd|pwd|secret|api[_-]?key|auth[_-]?token|private[_-]?key|access[_-]?key|credential)/i.test(receiver ?? "")) {
30486
+ return `Buffer.from().toString('${encoding}')`;
30487
+ }
30488
+ }
30489
+ }
30490
+ if (method === "btoa" && receiver === "") {
30491
+ if (argLooksLikeCredential(arg0))
30492
+ return "btoa";
30493
+ }
30494
+ }
30495
+ if (language === "java") {
30496
+ if (method === "encodeToString") {
30497
+ const recv = (receiver ?? "").toLowerCase();
30498
+ if (recv.includes("encoder") || recv.includes("base64")) {
30499
+ const expr = (arg0?.expression ?? "").trim();
30500
+ const head = expr.split(/[.\s(]/, 1)[0] ?? "";
30501
+ if (argLooksLikeCredential({ position: 0, expression: head, variable: head })) {
30502
+ return "Base64.encodeToString";
30503
+ }
30504
+ }
30505
+ }
30506
+ if (method === "encodeHexString" && (receiver === "Hex" || receiver.endsWith(".Hex"))) {
30507
+ const expr = (arg0?.expression ?? "").trim();
30508
+ const head = expr.split(/[.\s(]/, 1)[0] ?? "";
30509
+ if (argLooksLikeCredential({ position: 0, expression: head, variable: head })) {
30510
+ return "Hex.encodeHexString";
30511
+ }
30512
+ }
30513
+ }
30514
+ if (language === "go") {
30515
+ if (method === "EncodeToString") {
30516
+ const recv = (receiver ?? "").toLowerCase();
30517
+ if (recv.includes("base64") || recv.includes("hex") || recv.includes("encoding")) {
30518
+ const expr = (arg0?.expression ?? "").trim();
30519
+ const inner = expr.replace(/^\[\]byte\s*\(\s*/, "").replace(/\s*\)\s*$/, "");
30520
+ const head = inner.split(/[.\s(]/, 1)[0] ?? "";
30521
+ if (argLooksLikeCredential({ position: 0, expression: head, variable: head })) {
30522
+ return recv.includes("hex") ? "hex.EncodeToString" : "base64.EncodeToString";
30523
+ }
30524
+ }
30525
+ }
30526
+ }
30527
+ return null;
30528
+ }
30529
+ }
30530
+
30531
+ // ../circle-ir/dist/analysis/passes/info-disclosure-stacktrace-pass.js
30532
+ var RESPONSE_RECEIVER_RE = /^(res|response|w|writer|ctx|c)$/i;
30533
+ var LOGGER_RECEIVER_RE = /^(log|logger|slog|console|pino|winston|sentry)$/i;
30534
+ var RESPONSE_SEND_METHODS = new Set([
30535
+ "send",
30536
+ "json",
30537
+ "write",
30538
+ "writeHead",
30539
+ "end",
30540
+ "sendFile",
30541
+ "println",
30542
+ "print",
30543
+ "getWriter",
30544
+ "Fprintln",
30545
+ "Fprintf",
30546
+ "Fprint"
30547
+ ]);
30548
+ function isExceptionExpression(expr) {
30549
+ if (!expr)
30550
+ return false;
30551
+ const e = expr.trim();
30552
+ return /\b(err|error|exc|exception|e|t|throwable)\.(stack|message|toString\(|getMessage\(|getStackTrace\(|getLocalizedMessage\(|getCause\()/i.test(e) || /\btraceback\.(format_exc|format_exception|print_exc)\b/i.test(e) || /\bdebug\.Stack\(\)/.test(e) || /\bstr\(\s*(err|error|exc|exception|e)\s*\)/i.test(e) || /\bString\(\s*(err|error|exc|exception|e)\s*\)/i.test(e);
30553
+ }
30554
+ function argIsException(arg) {
30555
+ if (!arg)
30556
+ return false;
30557
+ if (arg.variable && /^(err|error|exc|exception|e|t|throwable)$/i.test(arg.variable)) {
30558
+ return true;
30559
+ }
30560
+ return isExceptionExpression(arg.expression);
30561
+ }
30562
+ function detectJavaPrintStackTrace(call) {
30563
+ if (call.method_name !== "printStackTrace")
30564
+ return null;
30565
+ const rec = call.receiver ?? "";
30566
+ if (!/^(e|ex|exc|exception|err|error|t|throwable)$/i.test(rec))
30567
+ return null;
30568
+ const arg0 = call.arguments.find((a) => a.position === 0);
30569
+ if (!arg0)
30570
+ return null;
30571
+ const expr = (arg0.expression ?? arg0.variable ?? "").trim();
30572
+ if (/\bresponse\.getWriter\(\)/.test(expr) || /\bresp\.getWriter\(\)/.test(expr) || /\bout\b/.test(expr) || /\bgetWriter\(\)/.test(expr)) {
30573
+ return "e.printStackTrace(response.getWriter())";
30574
+ }
30575
+ return null;
30576
+ }
30577
+ function detectResponseLeakCall(call) {
30578
+ const method = call.method_name ?? "";
30579
+ const receiver = call.receiver ?? "";
30580
+ if (!RESPONSE_SEND_METHODS.has(method))
30581
+ return null;
30582
+ if (LOGGER_RECEIVER_RE.test(receiver))
30583
+ return null;
30584
+ const recTail = receiver.split(".").pop() ?? receiver;
30585
+ const recHead = receiver.split(".")[0] ?? receiver;
30586
+ if (!RESPONSE_RECEIVER_RE.test(recTail) && !RESPONSE_RECEIVER_RE.test(recHead)) {
30587
+ if (!/(?:^|[.\s])(res|response)\.(?:status|set|header|cookie)\b/i.test(receiver)) {
30588
+ return null;
30589
+ }
30590
+ }
30591
+ for (const a of call.arguments) {
30592
+ if (argIsException(a)) {
30593
+ return `${receiver || ""}${receiver ? "." : ""}${method}(${(a.expression ?? a.variable ?? "").trim()})`;
30594
+ }
30595
+ }
30596
+ return null;
30597
+ }
30598
+ function detectPythonTracebackReturn(ctx) {
30599
+ const out2 = [];
30600
+ const lines = ctx.code.split(`
30601
+ `);
30602
+ for (let i2 = 0;i2 < lines.length; i2++) {
30603
+ const ln = lines[i2] ?? "";
30604
+ if (/\breturn\s+traceback\.format_exc\s*\(\s*\)/.test(ln) || /\breturn\s+\{[^}]*traceback\.format_exc\s*\(\s*\)[^}]*\}/.test(ln) || /\bjsonify\s*\([^)]*traceback\.format_exc\s*\(\s*\)/.test(ln)) {
30605
+ out2.push({ line: i2 + 1, api: "return traceback.format_exc()" });
30606
+ continue;
30607
+ }
30608
+ if (/\breturn\s+(?:str|repr)\s*\(\s*(?:e|err|error|exc|exception)\s*\)/.test(ln)) {
30609
+ const start2 = Math.max(0, i2 - 8);
30610
+ const end = Math.min(lines.length, i2 + 2);
30611
+ const window2 = lines.slice(start2, end).join(`
30612
+ `);
30613
+ if (/@(?:app|router|blueprint)\.(?:route|get|post|put|delete|patch)\b/.test(window2)) {
30614
+ out2.push({ line: i2 + 1, api: "return str(e) in handler" });
30615
+ }
30616
+ }
30617
+ }
30618
+ return out2;
30619
+ }
30620
+
30621
+ class InfoDisclosureStacktracePass {
30622
+ name = "info-disclosure-stacktrace";
30623
+ category = "security";
30624
+ run(ctx) {
30625
+ const { graph, language } = ctx;
30626
+ const file = graph.ir.meta.file;
30627
+ const findings = [];
30628
+ if (language === "python") {
30629
+ for (const f of detectPythonTracebackReturn(ctx)) {
30630
+ findings.push({ line: f.line, api: f.api, language });
30631
+ ctx.addFinding(this.makeFinding(file, f.line, f.api));
30632
+ }
30633
+ }
30634
+ for (const call of graph.ir.calls) {
30635
+ let api = null;
30636
+ if (language === "java") {
30637
+ api = detectJavaPrintStackTrace(call);
30638
+ if (!api)
30639
+ api = detectResponseLeakCall(call);
30640
+ } else if (language === "javascript" || language === "typescript") {
30641
+ api = detectResponseLeakCall(call);
30642
+ } else if (language === "go") {
30643
+ const method = call.method_name ?? "";
30644
+ const rec = call.receiver ?? "";
30645
+ if (rec === "http" && method === "Error") {
30646
+ const arg1 = call.arguments.find((a) => a.position === 1);
30647
+ if (argIsException(arg1))
30648
+ api = "http.Error(w, err.Error())";
30649
+ } else if (rec === "fmt" && (method === "Fprintln" || method === "Fprintf" || method === "Fprint")) {
30650
+ const arg0 = call.arguments.find((a) => a.position === 0);
30651
+ if (arg0 && /^(w|writer|resp|response)$/i.test((arg0.variable ?? arg0.expression ?? "").trim())) {
30652
+ for (const a of call.arguments) {
30653
+ if (a.position === 0)
30654
+ continue;
30655
+ if (argIsException(a)) {
30656
+ api = `fmt.${method}(w, err)`;
30657
+ break;
30658
+ }
30659
+ }
30660
+ }
30661
+ } else {
30662
+ api = detectResponseLeakCall(call);
30663
+ }
30664
+ } else if (language === "python") {
30665
+ api = detectResponseLeakCall(call);
30666
+ }
30667
+ if (!api)
30668
+ continue;
30669
+ const line = call.location.line;
30670
+ findings.push({ line, api, language });
30671
+ ctx.addFinding(this.makeFinding(file, line, api));
30672
+ }
30673
+ return { findings };
30674
+ }
30675
+ makeFinding(file, line, api) {
30676
+ return {
30677
+ id: `${this.name}-${file}-${line}`,
30678
+ pass: this.name,
30679
+ category: this.category,
30680
+ rule_id: this.name,
30681
+ cwe: "CWE-209",
30682
+ severity: "medium",
30683
+ level: "warning",
30684
+ message: `Exception detail returned to client via \`${api}\`. ` + "Leaking stack traces / exception messages reveals framework internals, " + "file paths, and class names — useful reconnaissance for an attacker.",
30685
+ file,
30686
+ line,
30687
+ fix: "Return a generic error response to the client (e.g. status 500 + a " + "request id) and log the full exception server-side via your logger " + '(e.g. `logger.error("…", e)` or `console.error(err)`).',
30688
+ evidence: { api }
30689
+ };
30690
+ }
30691
+ }
30692
+
30693
+ // ../circle-ir/dist/analysis/passes/unrestricted-file-upload-pass.js
30694
+ var UPLOAD_NAME_RE = /(?:getOriginalFilename|getSubmittedFileName|originalname|originalName|\.filename|\.Filename|FileHeader\.Filename|UploadFile)/;
30695
+ var FILE_SAFE_CALL_RE = /(?:secure_filename|FilenameUtils\.getExtension|\.lastIndexOf\(['"]\.['"]\)|ALLOWED_EXT|ALLOWED_EXTENSIONS|allowedExtensions|\bfileFilter\b|filepath\.Ext|path\.extname)/;
30696
+ function lineWindow(code, startLine, endLine) {
30697
+ const lines = code.split(`
30698
+ `);
30699
+ const s = Math.max(0, startLine - 1);
30700
+ const e = Math.min(lines.length, endLine);
30701
+ return lines.slice(s, e).join(`
30702
+ `);
30703
+ }
30704
+ function callHasUploadName(call) {
30705
+ for (const a of call.arguments) {
30706
+ const expr = (a.expression ?? a.variable ?? "").trim();
30707
+ if (UPLOAD_NAME_RE.test(expr))
30708
+ return true;
30709
+ }
30710
+ if (UPLOAD_NAME_RE.test(call.receiver ?? ""))
30711
+ return true;
30712
+ return false;
30713
+ }
30714
+
30715
+ class UnrestrictedFileUploadPass {
30716
+ name = "unrestricted-file-upload";
30717
+ category = "security";
30718
+ run(ctx) {
30719
+ const { graph, language, code } = ctx;
30720
+ const file = graph.ir.meta.file;
30721
+ const findings = [];
30722
+ const safeFunctionRanges = [];
30723
+ for (const t of graph.ir.types) {
30724
+ for (const m of t.methods) {
30725
+ const body2 = lineWindow(code, m.start_line, m.end_line);
30726
+ if (FILE_SAFE_CALL_RE.test(body2)) {
30727
+ safeFunctionRanges.push({ start: m.start_line, end: m.end_line });
30728
+ }
30729
+ }
30730
+ }
30731
+ const inSafeRange = (line) => {
30732
+ for (const r of safeFunctionRanges) {
30733
+ if (line >= r.start && line <= r.end)
30734
+ return true;
30735
+ }
30736
+ const win = lineWindow(code, Math.max(1, line - 20), line + 5);
30737
+ return FILE_SAFE_CALL_RE.test(win);
30738
+ };
30739
+ if (language === "java") {
30740
+ for (const call of graph.ir.calls) {
30741
+ const m = call.method_name ?? "";
30742
+ if (m === "transferTo" && callHasUploadName(call)) {
30743
+ if (inSafeRange(call.location.line))
30744
+ continue;
30745
+ this.emit(ctx, findings, file, call.location.line, language, "MultipartFile.transferTo(<original filename>)");
30746
+ continue;
30747
+ }
30748
+ if (m === "copy" && (call.receiver === "Files" || (call.receiver ?? "").endsWith(".Files"))) {
30749
+ if (callHasUploadName(call)) {
30750
+ if (inSafeRange(call.location.line))
30751
+ continue;
30752
+ this.emit(ctx, findings, file, call.location.line, language, "Files.copy(input, Path.of(dir, <original filename>))");
30753
+ }
30754
+ }
30755
+ }
30756
+ }
30757
+ if (language === "javascript" || language === "typescript") {
30758
+ for (const call of graph.ir.calls) {
30759
+ const m = call.method_name ?? "";
30760
+ const rec = call.receiver ?? "";
30761
+ if (m === "multer" || rec === "" && m === "multer") {
30762
+ const arg0 = call.arguments.find((a) => a.position === 0);
30763
+ const expr = (arg0?.expression ?? "").trim();
30764
+ if (/\bdest\s*:/.test(expr) && !/\bfileFilter\s*:/.test(expr)) {
30765
+ if (inSafeRange(call.location.line))
30766
+ continue;
30767
+ this.emit(ctx, findings, file, call.location.line, language, "multer({ dest }) without fileFilter");
30768
+ continue;
30769
+ }
30770
+ }
30771
+ if (rec === "fs" && (m === "writeFile" || m === "writeFileSync" || m === "appendFile")) {
30772
+ if (callHasUploadName(call) || call.arguments.some((a) => /\breq\.file(?:s)?\b/.test(a.expression ?? a.variable ?? ""))) {
30773
+ if (inSafeRange(call.location.line))
30774
+ continue;
30775
+ this.emit(ctx, findings, file, call.location.line, language, `fs.${m}(<path>, req.file.buffer)`);
30776
+ }
30777
+ }
30778
+ }
30779
+ }
30780
+ if (language === "python") {
30781
+ for (const call of graph.ir.calls) {
30782
+ const m = call.method_name ?? "";
30783
+ if (m === "save") {
30784
+ const rec = call.receiver ?? "";
30785
+ if (!/^(f|file|upload|attachment)$/i.test(rec) && rec !== "")
30786
+ continue;
30787
+ if (!callHasUploadName(call))
30788
+ continue;
30789
+ if (inSafeRange(call.location.line))
30790
+ continue;
30791
+ this.emit(ctx, findings, file, call.location.line, language, "f.save(<dir>, f.filename) without secure_filename");
30792
+ }
30793
+ }
30794
+ }
30795
+ if (language === "go") {
30796
+ for (const call of graph.ir.calls) {
30797
+ const m = call.method_name ?? "";
30798
+ const rec = call.receiver ?? "";
30799
+ if (rec === "os" && (m === "Create" || m === "OpenFile")) {
30800
+ if (callHasUploadName(call)) {
30801
+ if (inSafeRange(call.location.line))
30802
+ continue;
30803
+ this.emit(ctx, findings, file, call.location.line, language, `os.${m}(<uploaded filename>)`);
30804
+ }
30805
+ }
30806
+ if ((rec === "os" || rec === "ioutil") && m === "WriteFile") {
30807
+ if (callHasUploadName(call)) {
30808
+ if (inSafeRange(call.location.line))
30809
+ continue;
30810
+ this.emit(ctx, findings, file, call.location.line, language, `${rec}.WriteFile(<uploaded filename>, …)`);
30811
+ }
30812
+ }
30813
+ }
30814
+ }
30815
+ return { findings };
30816
+ }
30817
+ emit(ctx, findings, file, line, language, api) {
30818
+ findings.push({ line, api, language });
30819
+ ctx.addFinding({
30820
+ id: `${this.name}-${file}-${line}`,
30821
+ pass: this.name,
30822
+ category: this.category,
30823
+ rule_id: this.name,
30824
+ cwe: "CWE-434",
30825
+ severity: "high",
30826
+ level: "error",
30827
+ message: `File upload saved using untrusted name (${api}) — no extension allow-list or ` + "filename canonicalization detected. An attacker can upload a `.jsp`/`.php`/`.html` " + "file and request it back, achieving RCE or stored XSS.",
30828
+ file,
30829
+ line,
30830
+ fix: "Validate the uploaded extension against an allow-list (e.g. " + '`Set.of("png","jpg")`), then save with a sanitized filename. In Python use ' + "`werkzeug.utils.secure_filename`. In multer pass a `fileFilter`. Never " + "concatenate the upload's original filename into a save path without " + "validation.",
30831
+ evidence: { api, language }
30832
+ });
30833
+ }
30834
+ }
30835
+
30836
+ // ../circle-ir/dist/analysis/passes/plaintext-password-storage-pass.js
30837
+ function isWriteStorageCall(call, language) {
30838
+ const method = call.method_name ?? "";
30839
+ const receiver = call.receiver ?? "";
30840
+ const recvLower = receiver.toLowerCase();
30841
+ if (language === "python") {
30842
+ if (method === "write" || method === "writelines") {
30843
+ return { credPos: 0, api: `<file>.${method}` };
30844
+ }
30845
+ if ((recvLower === "pickle" || recvLower === "json" || recvLower === "yaml") && (method === "dump" || method === "dumps")) {
30846
+ return { credPos: 0, api: `${receiver}.${method}` };
30847
+ }
30848
+ if (recvLower === "redis" && (method === "set" || method === "setex" || method === "hset")) {
30849
+ return { credPos: 1, api: `redis.${method}` };
30850
+ }
30851
+ }
30852
+ if (language === "javascript" || language === "typescript") {
30853
+ if ((recvLower === "fs" || recvLower.endsWith(".fs")) && (method === "writeFile" || method === "writeFileSync" || method === "appendFile" || method === "appendFileSync")) {
30854
+ return { credPos: 1, api: `fs.${method}` };
30855
+ }
30856
+ if ((recvLower === "localstorage" || recvLower === "sessionstorage") && method === "setItem") {
30857
+ return { credPos: 1, api: `${receiver}.setItem` };
30858
+ }
30859
+ if (recvLower === "redis" && (method === "set" || method === "setex" || method === "hset")) {
30860
+ return { credPos: 1, api: `redis.${method}` };
30861
+ }
30862
+ }
30863
+ if (language === "java") {
30864
+ if ((receiver === "Files" || receiver.endsWith(".Files")) && (method === "write" || method === "writeString")) {
30865
+ return { credPos: 1, api: `Files.${method}` };
30866
+ }
30867
+ if (method === "write") {
30868
+ const lc = (receiver ?? "").toLowerCase();
30869
+ if (lc.includes("writer") || lc.includes("file") || lc.includes("stream")) {
30870
+ return { credPos: 0, api: `${receiver}.write` };
30871
+ }
30872
+ }
30873
+ }
30874
+ if (language === "go") {
30875
+ if (receiver === "os" || receiver.endsWith("/os")) {
30876
+ if (method === "WriteFile")
30877
+ return { credPos: 1, api: "os.WriteFile" };
30878
+ }
30879
+ if (receiver === "ioutil" || receiver.endsWith("/ioutil")) {
30880
+ if (method === "WriteFile")
30881
+ return { credPos: 1, api: "ioutil.WriteFile" };
30882
+ }
30883
+ if (method === "WriteString" || method === "Write") {
30884
+ return { credPos: 0, api: `<file>.${method}` };
30885
+ }
30886
+ }
30887
+ return null;
30888
+ }
30889
+
30890
+ class PlaintextPasswordStoragePass {
30891
+ name = "plaintext-password-storage";
30892
+ category = "security";
30893
+ run(ctx) {
30894
+ const { graph, language } = ctx;
30895
+ const file = graph.ir.meta.file;
30896
+ const findings = [];
30897
+ const callsByScope = new Map;
30898
+ for (const call of graph.ir.calls) {
30899
+ const scope = call.in_method ?? "<top>";
30900
+ const arr = callsByScope.get(scope) ?? [];
30901
+ arr.push(call);
30902
+ callsByScope.set(scope, arr);
30903
+ }
30904
+ for (const call of graph.ir.calls) {
30905
+ const spec = isWriteStorageCall(call, language);
30906
+ if (!spec)
30907
+ continue;
30908
+ const credArg = call.arguments.find((a) => a.position === spec.credPos);
30909
+ if (!credArg)
30910
+ continue;
30911
+ if (!argLooksLikeCredential(credArg))
30912
+ continue;
30913
+ const identExpr = (credArg.expression ?? "").trim();
30914
+ const head = identExpr.split(/[.\s(]/, 1)[0] ?? "";
30915
+ const identifier = credArg.variable ?? head;
30916
+ if (!identifier)
30917
+ continue;
30918
+ const scope = call.in_method ?? "<top>";
30919
+ const scopeCalls = callsByScope.get(scope) ?? [];
30920
+ const prior = scopeCalls.filter((c) => c.location.line < call.location.line);
30921
+ if (priorHashOf(identifier, prior))
30922
+ continue;
30923
+ if (/\b(?:hashpw|hash|sha\d+|md5|bcrypt|argon2|pbkdf2|digest)\b/i.test(credArg.expression ?? "")) {
30924
+ continue;
30925
+ }
30926
+ const line = call.location.line;
30927
+ findings.push({ line, language, api: spec.api, identifier });
30928
+ ctx.addFinding({
30929
+ id: `${this.name}-${file}-${line}`,
30930
+ pass: this.name,
30931
+ category: this.category,
30932
+ rule_id: this.name,
30933
+ cwe: "CWE-256",
30934
+ severity: "high",
30935
+ level: "warning",
30936
+ message: `Credential \`${identifier}\` written in plaintext via \`${spec.api}\`. ` + "Passwords / secrets must be hashed (Argon2id, bcrypt) before storage.",
30937
+ file,
30938
+ line,
30939
+ fix: "Hash the credential with Argon2id / bcrypt before writing it to " + "disk, cookie, KV store, or database.",
30940
+ evidence: { identifier, api: spec.api, language }
30941
+ });
30942
+ }
30943
+ return { findings };
30944
+ }
30945
+ }
30946
+
30947
+ // ../circle-ir/dist/analysis/passes/cleartext-credential-transport-pass.js
30948
+ var LOCALHOST_RE = /^(?:localhost|127\.0\.0\.1|0\.0\.0\.0)(?::\d+)?$/i;
30949
+ function isInsecureHttpUrl(urlLiteral) {
30950
+ if (!urlLiteral)
30951
+ return false;
30952
+ if (!/^http:\/\//i.test(urlLiteral))
30953
+ return false;
30954
+ const rest = urlLiteral.slice("http://".length);
30955
+ const host = rest.split("/", 1)[0] ?? "";
30956
+ if (LOCALHOST_RE.test(host))
30957
+ return false;
30958
+ return true;
30959
+ }
30960
+ function anyArgCarriesCredential(call, startPos) {
30961
+ for (const a of call.arguments) {
30962
+ if (a.position < startPos)
30963
+ continue;
30964
+ if (argLooksLikeCredential(a))
30965
+ return true;
30966
+ const expr = (a.expression ?? "").trim();
30967
+ if (!expr)
30968
+ continue;
30969
+ if (/(?:["'`]?(?:password|passwd|pwd|secret|api[_-]?key|auth[_-]?token|credential)["'`]?\s*[:=])/i.test(expr)) {
30970
+ return true;
30971
+ }
30972
+ if (/\b(?:password|passwd|pwd|secret|api_key|api-key|apiKey|auth_token|authToken|credential)\w*\b/i.test(expr)) {
30973
+ return true;
30974
+ }
30975
+ }
30976
+ return false;
30977
+ }
30978
+
30979
+ class CleartextCredentialTransportPass {
30980
+ name = "cleartext-credential-transport";
30981
+ category = "security";
30982
+ run(ctx) {
30983
+ const { graph, language } = ctx;
30984
+ const file = graph.ir.meta.file;
30985
+ const findings = [];
30986
+ for (const call of graph.ir.calls) {
30987
+ const detection = this.detect(call, language);
30988
+ if (!detection)
30989
+ continue;
30990
+ const { api, url } = detection;
30991
+ const line = call.location.line;
30992
+ findings.push({ line, language, api, url });
30993
+ ctx.addFinding({
30994
+ id: `${this.name}-${file}-${line}`,
30995
+ pass: this.name,
30996
+ category: this.category,
30997
+ rule_id: this.name,
30998
+ cwe: "CWE-523",
30999
+ severity: "high",
31000
+ level: "error",
31001
+ message: `Credentials transmitted to \`${url}\` over HTTP via \`${api}\`. ` + "Cleartext transport exposes credentials to network observers.",
31002
+ file,
31003
+ line,
31004
+ fix: "Use HTTPS (https://) for all endpoints that receive credentials. " + "For internal traffic, terminate TLS at the service boundary.",
31005
+ evidence: { api, url, language }
31006
+ });
31007
+ }
31008
+ return { findings };
31009
+ }
31010
+ detect(call, language) {
31011
+ const method = call.method_name ?? "";
31012
+ const receiver = call.receiver ?? "";
31013
+ const recvLower = receiver.toLowerCase();
31014
+ if (language === "python") {
31015
+ if ((recvLower === "requests" || recvLower === "httpx" || recvLower.endsWith(".requests") || recvLower.endsWith(".httpx")) && (method === "post" || method === "put" || method === "patch" || method === "request")) {
31016
+ const url = literalAt(call, method === "request" ? 1 : 0);
31017
+ if (isInsecureHttpUrl(url) && anyArgCarriesCredential(call, 1)) {
31018
+ return { api: `${receiver}.${method}`, url };
31019
+ }
31020
+ }
31021
+ if (method === "urlopen" && recvLower.includes("urllib")) {
31022
+ const url = literalAt(call, 0);
31023
+ if (isInsecureHttpUrl(url) && anyArgCarriesCredential(call, 1)) {
31024
+ return { api: "urllib.request.urlopen", url };
31025
+ }
31026
+ }
31027
+ }
31028
+ if (language === "javascript" || language === "typescript") {
31029
+ if ((recvLower === "axios" || recvLower.endsWith(".axios")) && (method === "post" || method === "put" || method === "patch" || method === "request")) {
31030
+ const url = literalAt(call, 0);
31031
+ if (isInsecureHttpUrl(url) && anyArgCarriesCredential(call, 1)) {
31032
+ return { api: `axios.${method}`, url };
31033
+ }
31034
+ }
31035
+ if (method === "fetch" && receiver === "") {
31036
+ const url = literalAt(call, 0);
31037
+ if (isInsecureHttpUrl(url) && anyArgCarriesCredential(call, 1)) {
31038
+ return { api: "fetch", url };
31039
+ }
31040
+ }
31041
+ if (method === "request" && (recvLower === "http" || recvLower.endsWith(".http"))) {
31042
+ const url = literalAt(call, 0);
31043
+ if (isInsecureHttpUrl(url) && anyArgCarriesCredential(call, 1)) {
31044
+ return { api: "http.request", url };
31045
+ }
31046
+ }
31047
+ }
31048
+ if (language === "java" && method === "URL" && receiver === "") {
31049
+ const url = literalAt(call, 0);
31050
+ if (!isInsecureHttpUrl(url))
31051
+ return null;
31052
+ const scope = call.in_method ?? null;
31053
+ if (!scope)
31054
+ return null;
31055
+ return null;
31056
+ }
31057
+ if (language === "go") {
31058
+ if (method === "Post" && (receiver === "http" || receiver.endsWith("/http"))) {
31059
+ const url = literalAt(call, 0);
31060
+ if (isInsecureHttpUrl(url) && anyArgCarriesCredential(call, 1)) {
31061
+ return { api: "http.Post", url };
31062
+ }
31063
+ }
31064
+ if (method === "NewRequest" && (receiver === "http" || receiver.endsWith("/http"))) {
31065
+ const url = literalAt(call, 1);
31066
+ if (isInsecureHttpUrl(url) && anyArgCarriesCredential(call, 2)) {
31067
+ return { api: "http.NewRequest", url };
31068
+ }
31069
+ }
31070
+ }
31071
+ return null;
31072
+ }
31073
+ }
31074
+
30165
31075
  // ../circle-ir/dist/analysis/passes/tls-verify-disabled-pass.js
30166
31076
  var PY_HTTP_METHODS = new Set([
30167
31077
  "get",
@@ -32463,6 +33373,14 @@ async function analyze(code, filePath, language, options = {}) {
32463
33373
  pipeline.add(new WeakCryptoPass);
32464
33374
  if (!disabledPasses.has("weak-random"))
32465
33375
  pipeline.add(new WeakRandomPass);
33376
+ if (!disabledPasses.has("weak-password-hash"))
33377
+ pipeline.add(new WeakPasswordHashPass);
33378
+ if (!disabledPasses.has("weak-password-encoding"))
33379
+ pipeline.add(new WeakPasswordEncodingPass);
33380
+ if (!disabledPasses.has("plaintext-password-storage"))
33381
+ pipeline.add(new PlaintextPasswordStoragePass);
33382
+ if (!disabledPasses.has("cleartext-credential-transport"))
33383
+ pipeline.add(new CleartextCredentialTransportPass);
32466
33384
  if (!disabledPasses.has("tls-verify-disabled"))
32467
33385
  pipeline.add(new TlsVerifyDisabledPass);
32468
33386
  if (!disabledPasses.has("module-side-effect"))
@@ -32477,6 +33395,10 @@ async function analyze(code, filePath, language, options = {}) {
32477
33395
  pipeline.add(new XmlEntityExpansionPass);
32478
33396
  if (!disabledPasses.has("mass-assignment"))
32479
33397
  pipeline.add(new MassAssignmentPass);
33398
+ if (!disabledPasses.has("info-disclosure-stacktrace"))
33399
+ pipeline.add(new InfoDisclosureStacktracePass);
33400
+ if (!disabledPasses.has("unrestricted-file-upload"))
33401
+ pipeline.add(new UnrestrictedFileUploadPass);
32480
33402
  const { results, findings } = pipeline.run(graph, code, language, config);
32481
33403
  const sinkFilter = results.get("sink-filter");
32482
33404
  const interProc = results.get("interprocedural");
@@ -32670,7 +33592,7 @@ var colors = {
32670
33592
  };
32671
33593
 
32672
33594
  // src/version.ts
32673
- var version = "3.80.0";
33595
+ var version = "3.82.0";
32674
33596
 
32675
33597
  // src/formatters.ts
32676
33598
  var SINK_SEVERITY = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cognium-dev",
3
- "version": "3.80.0",
3
+ "version": "3.82.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.80.0"
68
+ "circle-ir": "^3.82.0"
69
69
  },
70
70
  "devDependencies": {
71
71
  "@types/node": "^25.5.0",