@profullstack/threatcrush 0.7.0 → 0.7.1

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/index.js CHANGED
@@ -9580,111 +9580,7 @@ function workerId() {
9580
9580
  return `${import_node_os3.default.hostname()}/${process.pid}`;
9581
9581
  }
9582
9582
 
9583
- // src/scan/dependencies.ts
9584
- var import_node_fs5 = require("fs");
9585
- var import_node_path2 = require("path");
9586
- var LOCKFILES = [
9587
- { file: "package-lock.json", ecosystem: "npm" },
9588
- { file: "pnpm-lock.yaml", ecosystem: "npm" },
9589
- { file: "yarn.lock", ecosystem: "npm" },
9590
- { file: "requirements.txt", ecosystem: "PyPI" },
9591
- { file: "Pipfile.lock", ecosystem: "PyPI" }
9592
- ];
9593
- var MAX_DEPS_PER_LOCKFILE = 50;
9594
- async function scanDependencies(targetPath) {
9595
- const findings = [];
9596
- for (const { file, ecosystem } of LOCKFILES) {
9597
- const lockPath = (0, import_node_path2.join)(targetPath, file);
9598
- if (!(0, import_node_fs5.existsSync)(lockPath)) continue;
9599
- let deps;
9600
- try {
9601
- deps = parseDependencies(lockPath, file);
9602
- } catch {
9603
- continue;
9604
- }
9605
- for (const dep of deps.slice(0, MAX_DEPS_PER_LOCKFILE)) {
9606
- let vulns;
9607
- try {
9608
- vulns = await queryOsv(dep.name, dep.version, ecosystem);
9609
- } catch {
9610
- continue;
9611
- }
9612
- for (const vuln of vulns) {
9613
- const cvss = vuln.severity?.find((entry) => entry.type === "CVSS_V3")?.score;
9614
- findings.push({
9615
- ruleId: "dependency-known-vulnerability",
9616
- title: "Dependency CVE",
9617
- file,
9618
- line: 1,
9619
- severity: severityFromCvss(cvss),
9620
- confidence: "evidence",
9621
- message: `${dep.name}@${dep.version}: ${vuln.summary ?? vuln.id}`,
9622
- consequence: "A published advisory exists for the exact version resolved in this lockfile.",
9623
- excerpt: `${vuln.id}${cvss ? ` (CVSS: ${cvss})` : ""}`,
9624
- category: "dependency"
9625
- });
9626
- }
9627
- }
9628
- }
9629
- return findings;
9630
- }
9631
- function severityFromCvss(score) {
9632
- if (!score) return "medium";
9633
- const value = Number.parseFloat(score);
9634
- if (Number.isNaN(value)) return "medium";
9635
- if (value >= 9) return "critical";
9636
- if (value >= 7) return "high";
9637
- if (value >= 4) return "medium";
9638
- return "low";
9639
- }
9640
- function parseDependencies(lockPath, filename) {
9641
- const deps = [];
9642
- if (filename === "package-lock.json") {
9643
- const lock = JSON.parse((0, import_node_fs5.readFileSync)(lockPath, "utf-8"));
9644
- const packages = lock.packages ?? lock.dependencies ?? {};
9645
- for (const [key, value] of Object.entries(packages)) {
9646
- const name = key.replace(/^node_modules\//, "");
9647
- const version = value?.version;
9648
- if (name && version && !name.startsWith(".")) deps.push({ name, version });
9649
- }
9650
- return deps;
9651
- }
9652
- if (filename === "requirements.txt") {
9653
- for (const line of (0, import_node_fs5.readFileSync)(lockPath, "utf-8").split("\n")) {
9654
- const match = /^([a-zA-Z0-9_.-]+)==([0-9.]+)/.exec(line);
9655
- if (match?.[1] && match[2]) deps.push({ name: match[1], version: match[2] });
9656
- }
9657
- }
9658
- return deps;
9659
- }
9660
- function isValidPackageName(name) {
9661
- return /^[@a-zA-Z0-9_.\-/]{1,214}$/.test(name);
9662
- }
9663
- function isValidVersion(version) {
9664
- return /^[0-9a-zA-Z._\-+]{1,50}$/.test(version);
9665
- }
9666
- async function queryOsv(name, version, ecosystem) {
9667
- if (!isValidPackageName(name) || !isValidVersion(version)) return [];
9668
- try {
9669
- const response = await fetch("https://api.osv.dev/v1/query", {
9670
- method: "POST",
9671
- headers: { "Content-Type": "application/json" },
9672
- body: JSON.stringify({ package: { name, ecosystem }, version }),
9673
- signal: AbortSignal.timeout(5e3)
9674
- });
9675
- if (!response.ok) return [];
9676
- const data = await response.json();
9677
- return data.vulns ?? [];
9678
- } catch {
9679
- return [];
9680
- }
9681
- }
9682
-
9683
- // src/scan/engine.ts
9684
- var import_node_fs6 = require("fs");
9685
- var import_node_path3 = require("path");
9686
-
9687
- // src/scan/types.ts
9583
+ // ../../packages/scan/src/types.ts
9688
9584
  var SEVERITY_ORDER = ["info", "low", "medium", "high", "critical"];
9689
9585
  function severityRank(severity) {
9690
9586
  const index = SEVERITY_ORDER.indexOf(severity);
@@ -9695,7 +9591,7 @@ function severityFor(declared, confidence) {
9695
9591
  return severityRank(declared) > severityRank("medium") ? "medium" : declared;
9696
9592
  }
9697
9593
 
9698
- // src/scan/code-rules.ts
9594
+ // ../../packages/scan/src/code-rules.ts
9699
9595
  var UNTRUSTED_JS = /\b(?:req|request|ctx|context)\s*\.\s*(?:body|query|params|param|headers|cookies|url|files)\b|\bprocess\.argv\b|\bwindow\.location\b|\bdocument\.location\b|\blocation\.(?:search|hash|href)\b|\bsearchParams\s*\.\s*(?:get|getAll|has|entries|keys|values|forEach)\b|\bgetParameter\s*\(|\bgetQueryString\s*\(|\bgetInputStream\s*\(/;
9700
9596
  var UNTRUSTED_PY = /\brequest\b|\bparams\b|\bflask\b|\bsys\.argv\b|\bos\.environ\b\s*\[/;
9701
9597
  var UNTRUSTED_RB = /\bparams\s*\[|\brequest\b|\bcookies\s*\[/;
@@ -10079,6 +9975,7 @@ var CODE_RULES = [
10079
9975
  },
10080
9976
  {
10081
9977
  id: "tls-verification-disabled",
9978
+ inherent: true,
10082
9979
  title: "TLS certificate verification disabled",
10083
9980
  consequence: "Every connection made this way is trivially interceptable; the encryption is decorative.",
10084
9981
  cwe: "CWE-295",
@@ -10172,6 +10069,7 @@ var CODE_RULES = [
10172
10069
  // of privileged work actually happens, and they run as whoever invoked them.
10173
10070
  {
10174
10071
  id: "sh-remote-script-execution",
10072
+ inherent: true,
10175
10073
  title: "network output piped into a shell",
10176
10074
  consequence: "Whatever that URL serves at the moment this runs is executed as the invoking user. There is no version, no signature, and no review \u2014 a compromise of the host, or anyone able to answer for it, is a compromise of every machine that runs the script.",
10177
10075
  cwe: "CWE-494",
@@ -10227,6 +10125,7 @@ var CODE_RULES = [
10227
10125
  },
10228
10126
  {
10229
10127
  id: "sh-insecure-transport-flag",
10128
+ inherent: true,
10230
10129
  title: "certificate verification disabled",
10231
10130
  consequence: "Anyone positioned between this host and the server can substitute the response. When the response is a package, a key or a script, that is remote code execution with the transport doing nothing to stop it.",
10232
10131
  cwe: "CWE-295",
@@ -10241,6 +10140,7 @@ var CODE_RULES = [
10241
10140
  },
10242
10141
  {
10243
10142
  id: "sh-plaintext-download",
10143
+ inherent: true,
10244
10144
  title: "download over plain HTTP",
10245
10145
  consequence: "The response arrives unauthenticated over a channel any intermediary can rewrite. Where the payload is an archive, a package list or a key, substituting it is straightforward and leaves nothing for the script to notice.",
10246
10146
  cwe: "CWE-319",
@@ -10252,6 +10152,7 @@ var CODE_RULES = [
10252
10152
  },
10253
10153
  {
10254
10154
  id: "sh-world-writable-permissions",
10155
+ inherent: true,
10255
10156
  title: "world-writable permissions",
10256
10157
  consequence: "Any local account can rewrite the file. If it is a script, a config or anything on a privileged path, the next process to read it runs someone else\u2019s content.",
10257
10158
  cwe: "CWE-732",
@@ -10417,6 +10318,7 @@ var CODE_RULES = [
10417
10318
  },
10418
10319
  {
10419
10320
  id: "java-broken-cipher",
10321
+ inherent: true,
10420
10322
  title: "broken cipher or ECB mode",
10421
10323
  consequence: "DES, RC2, RC4 and Blowfish are broken or too small to rely on. ECB encrypts identical plaintext blocks to identical ciphertext blocks, so structure in the data survives encryption and is readable straight off the ciphertext.",
10422
10324
  cwe: "CWE-327",
@@ -10503,6 +10405,9 @@ function fileTextOf(lines) {
10503
10405
  }
10504
10406
  return text;
10505
10407
  }
10408
+ function withoutSingleQuoted(text) {
10409
+ return text.replace(/'[^'\n]*'/g, "''");
10410
+ }
10506
10411
  function evaluateRule(rule, ctx) {
10507
10412
  if (rule.languages && !rule.languages.includes(ctx.language)) return null;
10508
10413
  const line = ctx.lines[ctx.index] ?? "";
@@ -10517,13 +10422,15 @@ function evaluateRule(rule, ctx) {
10517
10422
  const guard = rule.guard === void 0 ? GENERIC_GUARD : rule.guard;
10518
10423
  if (guard && (guard.test(line) || guard.test(context))) return null;
10519
10424
  const untrusted = untrustedPatternFor(ctx.language);
10520
- const contextual = untrusted.test(line) || untrusted.test(context);
10425
+ const probeLine = ctx.language === "shell" ? withoutSingleQuoted(line) : line;
10426
+ const probeContext = ctx.language === "shell" ? withoutSingleQuoted(context) : context;
10427
+ const contextual = untrusted.test(probeLine) || untrusted.test(probeContext);
10521
10428
  if (rule.needsContext && !contextual) return null;
10522
- const confidence = contextual ? "contextual" : "pattern";
10429
+ const confidence = rule.inherent ? "evidence" : contextual ? "contextual" : "pattern";
10523
10430
  return { rule, confidence, severity: severityFor(rule.severity, confidence) };
10524
10431
  }
10525
10432
 
10526
- // src/scan/manifest-rules.ts
10433
+ // ../../packages/scan/src/manifest-rules.ts
10527
10434
  var POPULAR_NPM = [
10528
10435
  "react",
10529
10436
  "react-dom",
@@ -10798,7 +10705,7 @@ function scanRequirementsTxt(text) {
10798
10705
  return findings;
10799
10706
  }
10800
10707
 
10801
- // src/scan/secret-rules.ts
10708
+ // ../../packages/scan/src/secret-rules.ts
10802
10709
  var SECRET_RULES = [
10803
10710
  {
10804
10711
  id: "secret-aws-access-key",
@@ -10981,7 +10888,15 @@ var SENSITIVE_FILES = [
10981
10888
  // match. Reporting the file itself trades a real finding for a chore.
10982
10889
  ];
10983
10890
 
10984
- // src/scan/engine.ts
10891
+ // ../../packages/scan/src/text.ts
10892
+ function baseNameOf(path) {
10893
+ return path.slice(path.lastIndexOf("/") + 1);
10894
+ }
10895
+ function extensionOf(path) {
10896
+ const base = baseNameOf(path);
10897
+ const dot = base.lastIndexOf(".");
10898
+ return dot <= 0 ? "" : base.slice(dot);
10899
+ }
10985
10900
  var SKIP_DIRS = /* @__PURE__ */ new Set([
10986
10901
  "node_modules",
10987
10902
  ".git",
@@ -11090,7 +11005,7 @@ var LANGUAGE_BY_EXTENSION = {
11090
11005
  };
11091
11006
  function languageOf(filename) {
11092
11007
  if (filename.startsWith(".env") || filename.endsWith(".env")) return "config";
11093
- return LANGUAGE_BY_EXTENSION[(0, import_node_path3.extname)(filename).toLowerCase()] ?? "other";
11008
+ return LANGUAGE_BY_EXTENSION[extensionOf(filename).toLowerCase()] ?? "other";
11094
11009
  }
11095
11010
  var LANGUAGE_BY_INTERPRETER = {
11096
11011
  sh: "shell",
@@ -11110,10 +11025,12 @@ var LANGUAGE_BY_INTERPRETER = {
11110
11025
  php: "php"
11111
11026
  };
11112
11027
  function languageOfShebang(firstLine) {
11113
- const match = /^#!\s*(\S+)(?:\s+(\S+))?/.exec(firstLine);
11028
+ const match = /^#!\s*(\S+)(?:\s+(.+?))?\s*$/.exec(firstLine);
11114
11029
  if (!match) return null;
11115
- const command = (0, import_node_path3.basename)(match[1]);
11116
- const name = command === "env" && match[2] ? (0, import_node_path3.basename)(match[2]) : command;
11030
+ const command = baseNameOf(match[1]);
11031
+ const args = match[2]?.trim().split(/\s+/) ?? [];
11032
+ const splitString = args[0] === "-S" || args[0] === "--split-string";
11033
+ const name = command === "env" ? baseNameOf(args[splitString ? 1 : 0] ?? "") : command;
11117
11034
  const exact = LANGUAGE_BY_INTERPRETER[name];
11118
11035
  if (exact) return exact;
11119
11036
  const stripped = name.replace(/[\d.]+$/, "");
@@ -11215,6 +11132,15 @@ function scanManifest(relativePath, filename, text) {
11215
11132
  category: "manifest"
11216
11133
  }));
11217
11134
  }
11135
+ function meetsFailThreshold(findings, threshold) {
11136
+ if (threshold.length === 0) return false;
11137
+ const floor = Math.min(...threshold.map(severityRank));
11138
+ return findings.some((finding) => severityRank(finding.severity) >= floor);
11139
+ }
11140
+
11141
+ // ../../packages/scan/src/node/walk.ts
11142
+ var import_node_fs5 = require("fs");
11143
+ var import_node_path2 = require("path");
11218
11144
  function scanPath(targetPath, options = {}) {
11219
11145
  const maxFileBytes = options.maxFileBytes ?? 1024 * 1024;
11220
11146
  const allowed = options.categories ? new Set(options.categories) : null;
@@ -11224,15 +11150,15 @@ function scanPath(targetPath, options = {}) {
11224
11150
  let suppressed = 0;
11225
11151
  const rootIsDirectory = (() => {
11226
11152
  try {
11227
- return (0, import_node_fs6.statSync)(targetPath).isDirectory();
11153
+ return (0, import_node_fs5.statSync)(targetPath).isDirectory();
11228
11154
  } catch {
11229
11155
  return true;
11230
11156
  }
11231
11157
  })();
11232
- const walkRoot = rootIsDirectory ? targetPath : (0, import_node_path3.dirname)(targetPath);
11158
+ const walkRoot = rootIsDirectory ? targetPath : (0, import_node_path2.dirname)(targetPath);
11233
11159
  const scanFile = (fullPath, filename) => {
11234
11160
  const relativePath = toRelative(walkRoot, fullPath);
11235
- const extension = (0, import_node_path3.extname)(filename).toLowerCase();
11161
+ const extension = (0, import_node_path2.extname)(filename).toLowerCase();
11236
11162
  const isManifest = filename === "package.json" || filename === "requirements.txt";
11237
11163
  const scannable = SCAN_EXTENSIONS.has(extension) || filename.startsWith(".env");
11238
11164
  const mayDeclareInterpreter = !scannable && !isManifest && extension === "";
@@ -11244,26 +11170,26 @@ function scanPath(targetPath, options = {}) {
11244
11170
  let handle;
11245
11171
  let declared = null;
11246
11172
  try {
11247
- handle = (0, import_node_fs6.openSync)(fullPath, "r");
11173
+ handle = (0, import_node_fs5.openSync)(fullPath, "r");
11248
11174
  } catch {
11249
11175
  unreadable.push(relativePath);
11250
11176
  return;
11251
11177
  }
11252
11178
  try {
11253
- if ((0, import_node_fs6.fstatSync)(handle).size > maxFileBytes) return;
11179
+ if ((0, import_node_fs5.fstatSync)(handle).size > maxFileBytes) return;
11254
11180
  if (mayDeclareInterpreter) {
11255
11181
  const prefix = Buffer.alloc(128);
11256
- const read = (0, import_node_fs6.readSync)(handle, prefix, 0, prefix.length, 0);
11182
+ const read = (0, import_node_fs5.readSync)(handle, prefix, 0, prefix.length, 0);
11257
11183
  declared = languageOfShebang(prefix.subarray(0, read).toString("utf-8").split("\n", 1)[0] ?? "");
11258
11184
  if (!declared) return;
11259
11185
  }
11260
- text = (0, import_node_fs6.readFileSync)(handle, "utf-8");
11186
+ text = (0, import_node_fs5.readFileSync)(handle, "utf-8");
11261
11187
  } catch {
11262
11188
  unreadable.push(relativePath);
11263
11189
  return;
11264
11190
  } finally {
11265
11191
  try {
11266
- (0, import_node_fs6.closeSync)(handle);
11192
+ (0, import_node_fs5.closeSync)(handle);
11267
11193
  } catch {
11268
11194
  }
11269
11195
  }
@@ -11280,13 +11206,13 @@ function scanPath(targetPath, options = {}) {
11280
11206
  const walk = (currentPath) => {
11281
11207
  let entries;
11282
11208
  try {
11283
- entries = (0, import_node_fs6.readdirSync)(currentPath, { withFileTypes: true });
11209
+ entries = (0, import_node_fs5.readdirSync)(currentPath, { withFileTypes: true });
11284
11210
  } catch {
11285
11211
  unreadable.push(toRelative(walkRoot, currentPath));
11286
11212
  return;
11287
11213
  }
11288
11214
  for (const entry of entries) {
11289
- const fullPath = (0, import_node_path3.join)(currentPath, entry.name);
11215
+ const fullPath = (0, import_node_path2.join)(currentPath, entry.name);
11290
11216
  if (entry.isDirectory()) {
11291
11217
  if (SKIP_DIRS.has(entry.name)) continue;
11292
11218
  walk(fullPath);
@@ -11299,7 +11225,7 @@ function scanPath(targetPath, options = {}) {
11299
11225
  if (rootIsDirectory) {
11300
11226
  walk(targetPath);
11301
11227
  } else {
11302
- scanFile(targetPath, (0, import_node_path3.basename)(targetPath));
11228
+ scanFile(targetPath, (0, import_node_path2.basename)(targetPath));
11303
11229
  }
11304
11230
  const filtered = allowed ? findings.filter((f) => allowed.has(f.category)) : findings;
11305
11231
  filtered.sort(
@@ -11330,16 +11256,111 @@ function recordSensitiveFile(filename, relativePath, sink, fileFindings) {
11330
11256
  }
11331
11257
  }
11332
11258
  function toRelative(base, target) {
11333
- const rel = (0, import_node_path3.relative)(base, target);
11334
- return (rel === "" ? target : rel).split(import_node_path3.sep).join("/");
11259
+ const rel = (0, import_node_path2.relative)(base, target);
11260
+ return (rel === "" ? target : rel).split(import_node_path2.sep).join("/");
11335
11261
  }
11336
- function meetsFailThreshold(findings, threshold) {
11337
- if (threshold.length === 0) return false;
11338
- const floor = Math.min(...threshold.map(severityRank));
11339
- return findings.some((finding) => severityRank(finding.severity) >= floor);
11262
+
11263
+ // ../../packages/scan/src/node/dependencies.ts
11264
+ var import_node_fs6 = require("fs");
11265
+ var import_node_path3 = require("path");
11266
+ var LOCKFILES = [
11267
+ { file: "package-lock.json", ecosystem: "npm" },
11268
+ { file: "pnpm-lock.yaml", ecosystem: "npm" },
11269
+ { file: "yarn.lock", ecosystem: "npm" },
11270
+ { file: "requirements.txt", ecosystem: "PyPI" },
11271
+ { file: "Pipfile.lock", ecosystem: "PyPI" }
11272
+ ];
11273
+ var MAX_DEPS_PER_LOCKFILE = 50;
11274
+ async function scanDependencies(targetPath) {
11275
+ const findings = [];
11276
+ for (const { file, ecosystem } of LOCKFILES) {
11277
+ const lockPath = (0, import_node_path3.join)(targetPath, file);
11278
+ if (!(0, import_node_fs6.existsSync)(lockPath)) continue;
11279
+ let deps;
11280
+ try {
11281
+ deps = parseDependencies(lockPath, file);
11282
+ } catch {
11283
+ continue;
11284
+ }
11285
+ for (const dep of deps.slice(0, MAX_DEPS_PER_LOCKFILE)) {
11286
+ let vulns;
11287
+ try {
11288
+ vulns = await queryOsv(dep.name, dep.version, ecosystem);
11289
+ } catch {
11290
+ continue;
11291
+ }
11292
+ for (const vuln of vulns) {
11293
+ const cvss = vuln.severity?.find((entry) => entry.type === "CVSS_V3")?.score;
11294
+ findings.push({
11295
+ ruleId: "dependency-known-vulnerability",
11296
+ title: "Dependency CVE",
11297
+ file,
11298
+ line: 1,
11299
+ severity: severityFromCvss(cvss),
11300
+ confidence: "evidence",
11301
+ message: `${dep.name}@${dep.version}: ${vuln.summary ?? vuln.id}`,
11302
+ consequence: "A published advisory exists for the exact version resolved in this lockfile.",
11303
+ excerpt: `${vuln.id}${cvss ? ` (CVSS: ${cvss})` : ""}`,
11304
+ category: "dependency"
11305
+ });
11306
+ }
11307
+ }
11308
+ }
11309
+ return findings;
11310
+ }
11311
+ function severityFromCvss(score) {
11312
+ if (!score) return "medium";
11313
+ const value = Number.parseFloat(score);
11314
+ if (Number.isNaN(value)) return "medium";
11315
+ if (value >= 9) return "critical";
11316
+ if (value >= 7) return "high";
11317
+ if (value >= 4) return "medium";
11318
+ return "low";
11319
+ }
11320
+ function parseDependencies(lockPath, filename) {
11321
+ const deps = [];
11322
+ if (filename === "package-lock.json") {
11323
+ const lock = JSON.parse((0, import_node_fs6.readFileSync)(lockPath, "utf-8"));
11324
+ const packages = lock.packages ?? lock.dependencies ?? {};
11325
+ for (const [key, value] of Object.entries(packages)) {
11326
+ const name = key.replace(/^node_modules\//, "");
11327
+ const version = value?.version;
11328
+ if (name && version && !name.startsWith(".")) deps.push({ name, version });
11329
+ }
11330
+ return deps;
11331
+ }
11332
+ if (filename === "requirements.txt") {
11333
+ for (const line of (0, import_node_fs6.readFileSync)(lockPath, "utf-8").split("\n")) {
11334
+ const match = /^([a-zA-Z0-9_.-]+)==([0-9.]+)/.exec(line);
11335
+ if (match?.[1] && match[2]) deps.push({ name: match[1], version: match[2] });
11336
+ }
11337
+ }
11338
+ return deps;
11339
+ }
11340
+ function isValidPackageName(name) {
11341
+ return /^[@a-zA-Z0-9_.\-/]{1,214}$/.test(name);
11342
+ }
11343
+ function isValidVersion(version) {
11344
+ return /^[0-9a-zA-Z._\-+]{1,50}$/.test(version);
11345
+ }
11346
+ async function queryOsv(name, version, ecosystem) {
11347
+ if (!isValidPackageName(name) || !isValidVersion(version)) return [];
11348
+ try {
11349
+ const response = await fetch("https://api.osv.dev/v1/query", {
11350
+ method: "POST",
11351
+ headers: { "Content-Type": "application/json" },
11352
+ body: JSON.stringify({ package: { name, ecosystem }, version }),
11353
+ signal: AbortSignal.timeout(5e3)
11354
+ });
11355
+ if (!response.ok) return [];
11356
+ const data = await response.json();
11357
+ return data.vulns ?? [];
11358
+ } catch {
11359
+ return [];
11360
+ }
11340
11361
  }
11341
11362
 
11342
- // src/scan/sarif.ts
11363
+ // ../../packages/scan/src/node/sarif.ts
11343
11364
  var import_node_crypto = require("crypto");
11344
11365
  var import_node_path4 = require("path");
11345
11366
  var FINGERPRINT_KEY = "threatcrush/contentHash/v1";