circle-ir 3.52.0 → 3.53.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/configs/sinks/path.yaml +0 -16
- package/configs/sources/file_sources.yaml +32 -0
- package/dist/analysis/config-loader.d.ts.map +1 -1
- package/dist/analysis/config-loader.js +8 -1
- package/dist/analysis/config-loader.js.map +1 -1
- package/dist/analysis/passes/weak-crypto-pass.d.ts +17 -7
- package/dist/analysis/passes/weak-crypto-pass.d.ts.map +1 -1
- package/dist/analysis/passes/weak-crypto-pass.js +179 -10
- package/dist/analysis/passes/weak-crypto-pass.js.map +1 -1
- package/dist/analysis/taint-matcher.d.ts.map +1 -1
- package/dist/analysis/taint-matcher.js +28 -13
- package/dist/analysis/taint-matcher.js.map +1 -1
- package/dist/browser/circle-ir.js +107 -9
- package/dist/core/circle-ir-core.cjs +16 -7
- package/dist/core/circle-ir-core.js +16 -7
- package/package.json +1 -1
|
@@ -10621,6 +10621,13 @@ var DEFAULT_SOURCES = [
|
|
|
10621
10621
|
{ method: "getFileName", class: "BodyPart", type: "file_input", severity: "high", return_tainted: true },
|
|
10622
10622
|
{ method: "getFileName", class: "MimeBodyPart", type: "file_input", severity: "high", return_tainted: true },
|
|
10623
10623
|
{ method: "getDisposition", class: "Part", type: "file_input", severity: "medium", return_tainted: true },
|
|
10624
|
+
// Archive entry names (Zip-Slip / Tar-Slip CWE-22, issue #52)
|
|
10625
|
+
// entry.getName() returns a path that may contain ../ — flowing into File()/FileOutputStream()
|
|
10626
|
+
// is a classic Zip-Slip vulnerability.
|
|
10627
|
+
{ method: "getName", class: "ZipEntry", type: "file_input", severity: "high", return_tainted: true },
|
|
10628
|
+
{ method: "getName", class: "ZipArchiveEntry", type: "file_input", severity: "high", return_tainted: true },
|
|
10629
|
+
{ method: "getName", class: "TarArchiveEntry", type: "file_input", severity: "high", return_tainted: true },
|
|
10630
|
+
{ method: "getName", class: "ArchiveEntry", type: "file_input", severity: "high", return_tainted: true },
|
|
10624
10631
|
// Command line arguments
|
|
10625
10632
|
{ method: "getArgs", type: "io_input", severity: "high", return_tainted: true },
|
|
10626
10633
|
{ method: "getOptionValue", class: "CommandLine", type: "io_input", severity: "high", return_tainted: true },
|
|
@@ -11055,7 +11062,7 @@ var DEFAULT_SINKS = [
|
|
|
11055
11062
|
{ method: "staticFileLocation", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
11056
11063
|
// Zip/archive handling
|
|
11057
11064
|
{ method: "getEntry", class: "ZipFile", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
11058
|
-
|
|
11065
|
+
// ZipEntry.getName moved to file_sources.yaml as a taint SOURCE (type=archive_entry, issue #52)
|
|
11059
11066
|
// Resource loading classes (various frameworks)
|
|
11060
11067
|
{ method: "ClassPathResource", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
11061
11068
|
{ method: "FileSystemResource", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
@@ -12738,10 +12745,11 @@ function matchesSourcePattern(call, pattern) {
|
|
|
12738
12745
|
return false;
|
|
12739
12746
|
}
|
|
12740
12747
|
if (pattern.class && pattern.class !== "constructor") {
|
|
12741
|
-
if (
|
|
12748
|
+
if (call.receiver_type && call.receiver_type === pattern.class) {
|
|
12749
|
+
} else if (call.receiver_type_fqn && call.receiver_type_fqn.endsWith("." + pattern.class)) {
|
|
12750
|
+
} else if (!call.receiver) {
|
|
12742
12751
|
return false;
|
|
12743
|
-
}
|
|
12744
|
-
if (!receiverMightBeClass(call.receiver, pattern.class)) {
|
|
12752
|
+
} else if (!receiverMightBeClass(call.receiver, pattern.class)) {
|
|
12745
12753
|
return false;
|
|
12746
12754
|
}
|
|
12747
12755
|
}
|
|
@@ -12999,13 +13007,14 @@ function matchesSinkPattern(call, pattern, typeHierarchy, language) {
|
|
|
12999
13007
|
if (pattern.class === "constructor") {
|
|
13000
13008
|
return true;
|
|
13001
13009
|
}
|
|
13002
|
-
if (call.
|
|
13010
|
+
if (call.receiver_type && call.receiver_type === pattern.class) {
|
|
13011
|
+
} else if (call.receiver_type_fqn && call.receiver_type_fqn.endsWith("." + pattern.class)) {
|
|
13012
|
+
} else if (call.receiver && !receiverMightBeClass(call.receiver, pattern.class)) {
|
|
13003
13013
|
if (typeHierarchy && typeHierarchy.couldBeType(call.receiver, pattern.class)) {
|
|
13004
13014
|
return true;
|
|
13005
13015
|
}
|
|
13006
13016
|
return false;
|
|
13007
|
-
}
|
|
13008
|
-
if (!call.receiver) {
|
|
13017
|
+
} else if (!call.receiver && !call.receiver_type) {
|
|
13009
13018
|
return false;
|
|
13010
13019
|
}
|
|
13011
13020
|
}
|
|
@@ -27347,6 +27356,50 @@ function literalAlgo2(call, position) {
|
|
|
27347
27356
|
const cleaned = stripQuotes5(raw);
|
|
27348
27357
|
return cleaned || null;
|
|
27349
27358
|
}
|
|
27359
|
+
function detectStaticIvJava(call) {
|
|
27360
|
+
const arg = call.arguments.find((a) => a.position === 0);
|
|
27361
|
+
if (!arg) return null;
|
|
27362
|
+
const expr = (arg.literal ?? arg.expression ?? "").trim();
|
|
27363
|
+
if (!expr) return null;
|
|
27364
|
+
if (/^new\s+byte\s*\[[^\]]*\]\s*$/.test(expr)) {
|
|
27365
|
+
return `zero-filled ${expr}`;
|
|
27366
|
+
}
|
|
27367
|
+
if (/^new\s+byte\s*\[\s*\]\s*\{[^}]*\}\s*$/.test(expr)) {
|
|
27368
|
+
return `literal byte[] initializer`;
|
|
27369
|
+
}
|
|
27370
|
+
if (/^"[^"]*"\.getBytes\s*\(/.test(expr)) {
|
|
27371
|
+
return `literal string .getBytes()`;
|
|
27372
|
+
}
|
|
27373
|
+
if (/^"[^"]*"$/.test(expr)) {
|
|
27374
|
+
return `literal string`;
|
|
27375
|
+
}
|
|
27376
|
+
return null;
|
|
27377
|
+
}
|
|
27378
|
+
function isJavaCtor(call, className) {
|
|
27379
|
+
if (call.is_constructor === true) return true;
|
|
27380
|
+
if (call.receiver) return false;
|
|
27381
|
+
if (call.receiver_type === className) return true;
|
|
27382
|
+
if ((call.receiver_type_fqn ?? "").endsWith("." + className)) return true;
|
|
27383
|
+
return false;
|
|
27384
|
+
}
|
|
27385
|
+
function detectHardcodedKeyJava(call) {
|
|
27386
|
+
const arg = call.arguments.find((a) => a.position === 0);
|
|
27387
|
+
if (!arg) return null;
|
|
27388
|
+
const expr = (arg.literal ?? arg.expression ?? "").trim();
|
|
27389
|
+
if (!expr) return null;
|
|
27390
|
+
if (/^"[^"]*"\.getBytes\s*\(/.test(expr)) return `literal string .getBytes()`;
|
|
27391
|
+
if (/^new\s+byte\s*\[\s*\]\s*\{[^}]*\}\s*$/.test(expr)) return `literal byte[] initializer`;
|
|
27392
|
+
if (/^"[^"]*"$/.test(expr)) return `literal string`;
|
|
27393
|
+
return null;
|
|
27394
|
+
}
|
|
27395
|
+
var ISSUE_CWE = {
|
|
27396
|
+
"weak-cipher": "CWE-327",
|
|
27397
|
+
"ecb-mode": "CWE-327",
|
|
27398
|
+
"deprecated-api": "CWE-327",
|
|
27399
|
+
"static-iv": "CWE-329",
|
|
27400
|
+
"hardcoded-key": "CWE-321",
|
|
27401
|
+
"weak-rsa-key": "CWE-326"
|
|
27402
|
+
};
|
|
27350
27403
|
var WeakCryptoPass = class {
|
|
27351
27404
|
name = "weak-crypto";
|
|
27352
27405
|
category = "security";
|
|
@@ -27365,13 +27418,13 @@ var WeakCryptoPass = class {
|
|
|
27365
27418
|
pass: this.name,
|
|
27366
27419
|
category: this.category,
|
|
27367
27420
|
rule_id: this.name,
|
|
27368
|
-
cwe:
|
|
27421
|
+
cwe: ISSUE_CWE[det.issue],
|
|
27369
27422
|
severity: "high",
|
|
27370
27423
|
level: "error",
|
|
27371
27424
|
message,
|
|
27372
27425
|
file,
|
|
27373
27426
|
line,
|
|
27374
|
-
fix:
|
|
27427
|
+
fix: this.buildFix(det.issue),
|
|
27375
27428
|
evidence: { ...det, language }
|
|
27376
27429
|
});
|
|
27377
27430
|
}
|
|
@@ -27386,10 +27439,28 @@ var WeakCryptoPass = class {
|
|
|
27386
27439
|
return `ECB block-cipher mode used via \`${det.api}\` (\`${det.detail}\`). ECB leaks plaintext structure (identical blocks \u2192 identical ciphertext) and is not semantically secure.`;
|
|
27387
27440
|
case "deprecated-api":
|
|
27388
27441
|
return `Deprecated crypto API \`${det.api}\` used (no IV: \`${det.detail}\`). This API derives the key/IV from a password in an insecure way.`;
|
|
27442
|
+
case "static-iv":
|
|
27443
|
+
return `Static or zero-valued IV passed to \`${det.api}\` (\`${det.detail}\`). Reusing a fixed IV with CBC/CTR/GCM breaks confidentiality and, for GCM, can leak the authentication key.`;
|
|
27444
|
+
case "hardcoded-key":
|
|
27445
|
+
return `Hardcoded symmetric key material passed to \`${det.api}\` (\`${det.detail}\`). Keys embedded in source code are trivially recoverable from binaries and shared across deployments \u2014 they provide no confidentiality.`;
|
|
27446
|
+
case "weak-rsa-key":
|
|
27447
|
+
return `Weak RSA key size \`${det.detail}\` requested via \`${det.api}\`. RSA keys below 2048 bits are factorable and not compliant with NIST SP 800-57 / FIPS 186-5.`;
|
|
27389
27448
|
default:
|
|
27390
27449
|
return `Weak cryptography: ${det.detail} (${det.api})`;
|
|
27391
27450
|
}
|
|
27392
27451
|
}
|
|
27452
|
+
buildFix(issue) {
|
|
27453
|
+
switch (issue) {
|
|
27454
|
+
case "static-iv":
|
|
27455
|
+
return "Generate a fresh random IV per message using SecureRandom: `byte[] iv = new byte[12]; SecureRandom.getInstanceStrong().nextBytes(iv); new IvParameterSpec(iv);` and prepend it to the ciphertext.";
|
|
27456
|
+
case "hardcoded-key":
|
|
27457
|
+
return "Load the key from a secure key management system (HSM, KMS, Vault) or platform keystore. Never embed key material in source code.";
|
|
27458
|
+
case "weak-rsa-key":
|
|
27459
|
+
return "Initialize KeyPairGenerator with at least 2048 bits (preferably 3072 or 4096) for RSA, or switch to EC keys (P-256+).";
|
|
27460
|
+
default:
|
|
27461
|
+
return "Use AES-GCM (authenticated) or ChaCha20-Poly1305. Avoid DES, 3DES, RC2, RC4, Blowfish, and ECB mode. For asymmetric encryption use RSA-OAEP with \u22652048-bit keys or modern curve-based schemes.";
|
|
27462
|
+
}
|
|
27463
|
+
}
|
|
27393
27464
|
detect(call, language) {
|
|
27394
27465
|
const method = call.method_name;
|
|
27395
27466
|
const receiver = call.receiver ?? "";
|
|
@@ -27405,6 +27476,33 @@ var WeakCryptoPass = class {
|
|
|
27405
27476
|
if (ecb) out2.push({ issue: "ecb-mode", detail: spec, api });
|
|
27406
27477
|
}
|
|
27407
27478
|
}
|
|
27479
|
+
if (method === "IvParameterSpec" && isJavaCtor(call, "IvParameterSpec")) {
|
|
27480
|
+
const ivDetail = detectStaticIvJava(call);
|
|
27481
|
+
if (ivDetail) {
|
|
27482
|
+
out2.push({ issue: "static-iv", detail: ivDetail, api: "new IvParameterSpec" });
|
|
27483
|
+
}
|
|
27484
|
+
}
|
|
27485
|
+
if (method === "SecretKeySpec" && isJavaCtor(call, "SecretKeySpec")) {
|
|
27486
|
+
const keyDetail = detectHardcodedKeyJava(call);
|
|
27487
|
+
if (keyDetail) {
|
|
27488
|
+
out2.push({ issue: "hardcoded-key", detail: keyDetail, api: "new SecretKeySpec" });
|
|
27489
|
+
}
|
|
27490
|
+
}
|
|
27491
|
+
if (method === "initialize") {
|
|
27492
|
+
const isKpg = call.receiver_type === "KeyPairGenerator" || (call.receiver_type_fqn ?? "").endsWith(".KeyPairGenerator");
|
|
27493
|
+
if (isKpg) {
|
|
27494
|
+
const sizeArg = call.arguments.find((a) => a.position === 0);
|
|
27495
|
+
const expr = (sizeArg?.literal ?? sizeArg?.expression ?? "").trim();
|
|
27496
|
+
const n = parseInt(expr, 10);
|
|
27497
|
+
if (Number.isFinite(n) && n > 0 && n < 2048) {
|
|
27498
|
+
out2.push({
|
|
27499
|
+
issue: "weak-rsa-key",
|
|
27500
|
+
detail: String(n),
|
|
27501
|
+
api: "KeyPairGenerator.initialize"
|
|
27502
|
+
});
|
|
27503
|
+
}
|
|
27504
|
+
}
|
|
27505
|
+
}
|
|
27408
27506
|
return out2;
|
|
27409
27507
|
}
|
|
27410
27508
|
if (language === "python") {
|
|
@@ -10003,6 +10003,13 @@ var DEFAULT_SOURCES = [
|
|
|
10003
10003
|
{ method: "getFileName", class: "BodyPart", type: "file_input", severity: "high", return_tainted: true },
|
|
10004
10004
|
{ method: "getFileName", class: "MimeBodyPart", type: "file_input", severity: "high", return_tainted: true },
|
|
10005
10005
|
{ method: "getDisposition", class: "Part", type: "file_input", severity: "medium", return_tainted: true },
|
|
10006
|
+
// Archive entry names (Zip-Slip / Tar-Slip CWE-22, issue #52)
|
|
10007
|
+
// entry.getName() returns a path that may contain ../ — flowing into File()/FileOutputStream()
|
|
10008
|
+
// is a classic Zip-Slip vulnerability.
|
|
10009
|
+
{ method: "getName", class: "ZipEntry", type: "file_input", severity: "high", return_tainted: true },
|
|
10010
|
+
{ method: "getName", class: "ZipArchiveEntry", type: "file_input", severity: "high", return_tainted: true },
|
|
10011
|
+
{ method: "getName", class: "TarArchiveEntry", type: "file_input", severity: "high", return_tainted: true },
|
|
10012
|
+
{ method: "getName", class: "ArchiveEntry", type: "file_input", severity: "high", return_tainted: true },
|
|
10006
10013
|
// Command line arguments
|
|
10007
10014
|
{ method: "getArgs", type: "io_input", severity: "high", return_tainted: true },
|
|
10008
10015
|
{ method: "getOptionValue", class: "CommandLine", type: "io_input", severity: "high", return_tainted: true },
|
|
@@ -10437,7 +10444,7 @@ var DEFAULT_SINKS = [
|
|
|
10437
10444
|
{ method: "staticFileLocation", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
10438
10445
|
// Zip/archive handling
|
|
10439
10446
|
{ method: "getEntry", class: "ZipFile", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
10440
|
-
|
|
10447
|
+
// ZipEntry.getName moved to file_sources.yaml as a taint SOURCE (type=archive_entry, issue #52)
|
|
10441
10448
|
// Resource loading classes (various frameworks)
|
|
10442
10449
|
{ method: "ClassPathResource", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
10443
10450
|
{ method: "FileSystemResource", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
@@ -12033,10 +12040,11 @@ function matchesSourcePattern(call, pattern) {
|
|
|
12033
12040
|
return false;
|
|
12034
12041
|
}
|
|
12035
12042
|
if (pattern.class && pattern.class !== "constructor") {
|
|
12036
|
-
if (
|
|
12043
|
+
if (call.receiver_type && call.receiver_type === pattern.class) {
|
|
12044
|
+
} else if (call.receiver_type_fqn && call.receiver_type_fqn.endsWith("." + pattern.class)) {
|
|
12045
|
+
} else if (!call.receiver) {
|
|
12037
12046
|
return false;
|
|
12038
|
-
}
|
|
12039
|
-
if (!receiverMightBeClass(call.receiver, pattern.class)) {
|
|
12047
|
+
} else if (!receiverMightBeClass(call.receiver, pattern.class)) {
|
|
12040
12048
|
return false;
|
|
12041
12049
|
}
|
|
12042
12050
|
}
|
|
@@ -12294,13 +12302,14 @@ function matchesSinkPattern(call, pattern, typeHierarchy, language) {
|
|
|
12294
12302
|
if (pattern.class === "constructor") {
|
|
12295
12303
|
return true;
|
|
12296
12304
|
}
|
|
12297
|
-
if (call.
|
|
12305
|
+
if (call.receiver_type && call.receiver_type === pattern.class) {
|
|
12306
|
+
} else if (call.receiver_type_fqn && call.receiver_type_fqn.endsWith("." + pattern.class)) {
|
|
12307
|
+
} else if (call.receiver && !receiverMightBeClass(call.receiver, pattern.class)) {
|
|
12298
12308
|
if (typeHierarchy && typeHierarchy.couldBeType(call.receiver, pattern.class)) {
|
|
12299
12309
|
return true;
|
|
12300
12310
|
}
|
|
12301
12311
|
return false;
|
|
12302
|
-
}
|
|
12303
|
-
if (!call.receiver) {
|
|
12312
|
+
} else if (!call.receiver && !call.receiver_type) {
|
|
12304
12313
|
return false;
|
|
12305
12314
|
}
|
|
12306
12315
|
}
|
|
@@ -9937,6 +9937,13 @@ var DEFAULT_SOURCES = [
|
|
|
9937
9937
|
{ method: "getFileName", class: "BodyPart", type: "file_input", severity: "high", return_tainted: true },
|
|
9938
9938
|
{ method: "getFileName", class: "MimeBodyPart", type: "file_input", severity: "high", return_tainted: true },
|
|
9939
9939
|
{ method: "getDisposition", class: "Part", type: "file_input", severity: "medium", return_tainted: true },
|
|
9940
|
+
// Archive entry names (Zip-Slip / Tar-Slip CWE-22, issue #52)
|
|
9941
|
+
// entry.getName() returns a path that may contain ../ — flowing into File()/FileOutputStream()
|
|
9942
|
+
// is a classic Zip-Slip vulnerability.
|
|
9943
|
+
{ method: "getName", class: "ZipEntry", type: "file_input", severity: "high", return_tainted: true },
|
|
9944
|
+
{ method: "getName", class: "ZipArchiveEntry", type: "file_input", severity: "high", return_tainted: true },
|
|
9945
|
+
{ method: "getName", class: "TarArchiveEntry", type: "file_input", severity: "high", return_tainted: true },
|
|
9946
|
+
{ method: "getName", class: "ArchiveEntry", type: "file_input", severity: "high", return_tainted: true },
|
|
9940
9947
|
// Command line arguments
|
|
9941
9948
|
{ method: "getArgs", type: "io_input", severity: "high", return_tainted: true },
|
|
9942
9949
|
{ method: "getOptionValue", class: "CommandLine", type: "io_input", severity: "high", return_tainted: true },
|
|
@@ -10371,7 +10378,7 @@ var DEFAULT_SINKS = [
|
|
|
10371
10378
|
{ method: "staticFileLocation", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
10372
10379
|
// Zip/archive handling
|
|
10373
10380
|
{ method: "getEntry", class: "ZipFile", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
10374
|
-
|
|
10381
|
+
// ZipEntry.getName moved to file_sources.yaml as a taint SOURCE (type=archive_entry, issue #52)
|
|
10375
10382
|
// Resource loading classes (various frameworks)
|
|
10376
10383
|
{ method: "ClassPathResource", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
10377
10384
|
{ method: "FileSystemResource", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
@@ -11967,10 +11974,11 @@ function matchesSourcePattern(call, pattern) {
|
|
|
11967
11974
|
return false;
|
|
11968
11975
|
}
|
|
11969
11976
|
if (pattern.class && pattern.class !== "constructor") {
|
|
11970
|
-
if (
|
|
11977
|
+
if (call.receiver_type && call.receiver_type === pattern.class) {
|
|
11978
|
+
} else if (call.receiver_type_fqn && call.receiver_type_fqn.endsWith("." + pattern.class)) {
|
|
11979
|
+
} else if (!call.receiver) {
|
|
11971
11980
|
return false;
|
|
11972
|
-
}
|
|
11973
|
-
if (!receiverMightBeClass(call.receiver, pattern.class)) {
|
|
11981
|
+
} else if (!receiverMightBeClass(call.receiver, pattern.class)) {
|
|
11974
11982
|
return false;
|
|
11975
11983
|
}
|
|
11976
11984
|
}
|
|
@@ -12228,13 +12236,14 @@ function matchesSinkPattern(call, pattern, typeHierarchy, language) {
|
|
|
12228
12236
|
if (pattern.class === "constructor") {
|
|
12229
12237
|
return true;
|
|
12230
12238
|
}
|
|
12231
|
-
if (call.
|
|
12239
|
+
if (call.receiver_type && call.receiver_type === pattern.class) {
|
|
12240
|
+
} else if (call.receiver_type_fqn && call.receiver_type_fqn.endsWith("." + pattern.class)) {
|
|
12241
|
+
} else if (call.receiver && !receiverMightBeClass(call.receiver, pattern.class)) {
|
|
12232
12242
|
if (typeHierarchy && typeHierarchy.couldBeType(call.receiver, pattern.class)) {
|
|
12233
12243
|
return true;
|
|
12234
12244
|
}
|
|
12235
12245
|
return false;
|
|
12236
|
-
}
|
|
12237
|
-
if (!call.receiver) {
|
|
12246
|
+
} else if (!call.receiver && !call.receiver_type) {
|
|
12238
12247
|
return false;
|
|
12239
12248
|
}
|
|
12240
12249
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "circle-ir",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.53.0",
|
|
4
4
|
"description": "High-performance Static Application Security Testing (SAST) library for detecting security vulnerabilities through taint analysis",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|