@profullstack/threatcrush 0.7.2 → 0.8.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/index.js CHANGED
@@ -11141,6 +11141,81 @@ function meetsFailThreshold(findings, threshold) {
11141
11141
  // ../../packages/scan/src/node/walk.ts
11142
11142
  var import_node_fs5 = require("fs");
11143
11143
  var import_node_path2 = require("path");
11144
+ function compileExcludes(patterns) {
11145
+ const matchers = patterns.map((p) => p.trim()).filter((p) => p.length > 0 && !p.startsWith("#")).map(compilePattern);
11146
+ if (matchers.length === 0) return () => false;
11147
+ return (relPath) => {
11148
+ const segs = relPath.split("/");
11149
+ return matchers.some((m) => m(segs));
11150
+ };
11151
+ }
11152
+ var GLOBSTAR = /* @__PURE__ */ Symbol("globstar");
11153
+ function compilePattern(pattern) {
11154
+ const withoutLead = pattern.replace(/^\.?\//, "");
11155
+ let end = withoutLead.length;
11156
+ while (end > 0 && withoutLead[end - 1] === "/") end -= 1;
11157
+ const p = withoutLead.slice(0, end);
11158
+ if (!p.includes("/")) {
11159
+ if (p === "**") return () => true;
11160
+ const rx = segToRegExp(p);
11161
+ return (segs) => segs.some((s) => rx.test(s));
11162
+ }
11163
+ const raw = p.split("/");
11164
+ const trailingGlobstar = raw[raw.length - 1] === "**";
11165
+ const core = trailingGlobstar ? raw.slice(0, -1) : raw;
11166
+ const parts = core.map((s) => s === "**" ? GLOBSTAR : segToRegExp(s));
11167
+ return (segs) => {
11168
+ const end2 = matchPrefix(parts, segs);
11169
+ if (end2 < 0) return false;
11170
+ return trailingGlobstar ? end2 < segs.length : true;
11171
+ };
11172
+ }
11173
+ function segToRegExp(seg) {
11174
+ let body = "";
11175
+ for (let i = 0; i < seg.length; i += 1) {
11176
+ const c = seg[i];
11177
+ if (c === "*") {
11178
+ while (seg[i + 1] === "*") i += 1;
11179
+ body += "[^/]*";
11180
+ } else if (c === "?") {
11181
+ body += "[^/]";
11182
+ } else {
11183
+ body += c.replace(/[.+^${}()|[\]\\]/g, "\\$&");
11184
+ }
11185
+ }
11186
+ return new RegExp(`^${body}$`);
11187
+ }
11188
+ function matchPrefix(parts, segs) {
11189
+ let pi = 0;
11190
+ let si = 0;
11191
+ let star = -1;
11192
+ let starSi = -1;
11193
+ while (pi < parts.length) {
11194
+ const part = parts[pi];
11195
+ if (part === GLOBSTAR) {
11196
+ star = pi;
11197
+ starSi = si;
11198
+ pi += 1;
11199
+ } else if (si < segs.length && part.test(segs[si])) {
11200
+ pi += 1;
11201
+ si += 1;
11202
+ } else if (star !== -1 && starSi < segs.length) {
11203
+ starSi += 1;
11204
+ si = starSi;
11205
+ pi = star + 1;
11206
+ } else {
11207
+ return -1;
11208
+ }
11209
+ }
11210
+ return si;
11211
+ }
11212
+ function readIgnoreFile(root) {
11213
+ try {
11214
+ return (0, import_node_fs5.readFileSync)((0, import_node_path2.join)(root, ".threatcrushignore"), "utf-8").split("\n");
11215
+ } catch {
11216
+ return [];
11217
+ }
11218
+ }
11144
11219
  function scanPath(targetPath, options = {}) {
11145
11220
  const maxFileBytes = options.maxFileBytes ?? 1024 * 1024;
11146
11221
  const allowed = options.categories ? new Set(options.categories) : null;
@@ -11148,6 +11223,7 @@ function scanPath(targetPath, options = {}) {
11148
11223
  const unreadable = [];
11149
11224
  let filesScanned = 0;
11150
11225
  let suppressed = 0;
11226
+ let excluded = 0;
11151
11227
  const rootIsDirectory = (() => {
11152
11228
  try {
11153
11229
  return (0, import_node_fs5.statSync)(targetPath).isDirectory();
@@ -11156,6 +11232,7 @@ function scanPath(targetPath, options = {}) {
11156
11232
  }
11157
11233
  })();
11158
11234
  const walkRoot = rootIsDirectory ? targetPath : (0, import_node_path2.dirname)(targetPath);
11235
+ const isExcluded = compileExcludes([...options.exclude ?? [], ...readIgnoreFile(walkRoot)]);
11159
11236
  const scanFile = (fullPath, filename) => {
11160
11237
  const relativePath = toRelative(walkRoot, fullPath);
11161
11238
  const extension = (0, import_node_path2.extname)(filename).toLowerCase();
@@ -11213,17 +11290,28 @@ function scanPath(targetPath, options = {}) {
11213
11290
  }
11214
11291
  for (const entry of entries) {
11215
11292
  const fullPath = (0, import_node_path2.join)(currentPath, entry.name);
11293
+ const relativePath = toRelative(walkRoot, fullPath);
11216
11294
  if (entry.isDirectory()) {
11217
11295
  if (SKIP_DIRS.has(entry.name)) continue;
11296
+ if (isExcluded(relativePath)) {
11297
+ excluded += 1;
11298
+ continue;
11299
+ }
11218
11300
  walk(fullPath);
11219
11301
  continue;
11220
11302
  }
11221
11303
  if (!entry.isFile()) continue;
11304
+ if (isExcluded(relativePath)) {
11305
+ excluded += 1;
11306
+ continue;
11307
+ }
11222
11308
  scanFile(fullPath, entry.name);
11223
11309
  }
11224
11310
  };
11225
11311
  if (rootIsDirectory) {
11226
11312
  walk(targetPath);
11313
+ } else if (isExcluded(toRelative(walkRoot, targetPath))) {
11314
+ excluded += 1;
11227
11315
  } else {
11228
11316
  scanFile(targetPath, (0, import_node_path2.basename)(targetPath));
11229
11317
  }
@@ -11231,7 +11319,7 @@ function scanPath(targetPath, options = {}) {
11231
11319
  filtered.sort(
11232
11320
  (a, b) => severityRank(b.severity) - severityRank(a.severity) || a.file.localeCompare(b.file) || a.line - b.line
11233
11321
  );
11234
- return { findings: filtered, filesScanned, unreadable, suppressed, root: walkRoot };
11322
+ return { findings: filtered, filesScanned, unreadable, suppressed, excluded, root: walkRoot };
11235
11323
  }
11236
11324
  function recordSensitiveFile(filename, relativePath, sink, fileFindings) {
11237
11325
  if (fileFindings.length > 0) return;
@@ -11580,6 +11668,7 @@ async function scanCommand(targetPath, options = {}) {
11580
11668
  try {
11581
11669
  let seen = 0;
11582
11670
  const report = scanPath(targetPath, {
11671
+ exclude: options.exclude,
11583
11672
  onFile: () => {
11584
11673
  seen += 1;
11585
11674
  if (spinner) spinner.text = `Scanning files... (${seen} files)`;
@@ -11595,6 +11684,7 @@ async function scanCommand(targetPath, options = {}) {
11595
11684
  filesScanned: report.filesScanned,
11596
11685
  unreadable: report.unreadable,
11597
11686
  suppressed: report.suppressed,
11687
+ excluded: report.excluded,
11598
11688
  root: report.root
11599
11689
  };
11600
11690
  } catch (err) {
@@ -11614,6 +11704,13 @@ async function scanCommand(targetPath, options = {}) {
11614
11704
  for (const path of outcome.unreadable) say(source_default.gray(` ${path}`));
11615
11705
  }
11616
11706
  }
11707
+ if (outcome.excluded > 0) {
11708
+ say(
11709
+ source_default.gray(
11710
+ ` \xB7 ${outcome.excluded} path(s) excluded by --exclude or .threatcrushignore`
11711
+ )
11712
+ );
11713
+ }
11617
11714
  if (outcome.suppressed > 0) {
11618
11715
  say(
11619
11716
  source_default.gray(
@@ -17245,7 +17342,12 @@ program2.command("scan").description("Scan codebase for vulnerabilities and secr
17245
17342
  ).option(
17246
17343
  "--path-prefix <prefix>",
17247
17344
  "prepend this to SARIF file URIs \u2014 use when the scan root is not the repository root"
17248
- ).option("--deps", "also query OSV.dev for advisories against lockfile versions (network)").option("-v, --verbose", "list the paths that could not be read").action(async (targetPath, opts) => {
17345
+ ).option("--deps", "also query OSV.dev for advisories against lockfile versions (network)").option(
17346
+ "--exclude <glob>",
17347
+ "skip paths matching this glob (repeatable); merged with a .threatcrushignore at the scan root",
17348
+ (value, previous) => [...previous, value],
17349
+ []
17350
+ ).option("-v, --verbose", "list the paths that could not be read").action(async (targetPath, opts) => {
17249
17351
  const format = (opts.format ?? "text").toLowerCase();
17250
17352
  if (!["text", "json", "sarif"].includes(format)) {
17251
17353
  console.error(source_default.red(`Unknown --format "${opts.format}" (expected text, json, or sarif)`));
@@ -17264,6 +17366,7 @@ program2.command("scan").description("Scan codebase for vulnerabilities and secr
17264
17366
  failOn,
17265
17367
  pathPrefix: opts.pathPrefix,
17266
17368
  dependencies: opts.deps,
17369
+ exclude: opts.exclude,
17267
17370
  verbose: opts.verbose
17268
17371
  });
17269
17372
  });