@profullstack/threatcrush 0.7.1 → 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/daemon.js +90 -2
- package/dist/daemon.js.map +1 -1
- package/dist/index.js +106 -3
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -9632,7 +9632,7 @@ var XXE_GUARD = /FEATURE_SECURE_PROCESSING|setExpandEntityReferences|disallow-do
|
|
|
9632
9632
|
var XML_PARSING_FILE = /\b(?:javax\.xml|org\.xml\.sax|org\.w3c\.dom|org\.jdom2?|org\.dom4j|XmlPullParser|DocumentBuilderFactory|DocumentBuilder|SAXParserFactory|SAXParser|XMLInputFactory|XMLReaderFactory|XMLReader|SAXBuilder|SAXReader)\b/;
|
|
9633
9633
|
var CODE_SINK = /\bglobalThis\s*\[|\bconstructor\b|\beval\b|\bFunction\b|\brun\s*\(|\bvm\s*\.\s*run/;
|
|
9634
9634
|
var EXFIL_SINK = /\bconsole\s*\.\s*(?:log|debug|info|warn|error)\s*\(|\bfetch\s*\(|\baxios\b|\brequest\s*\(|\.\s*send\s*\(/;
|
|
9635
|
-
var SQL_KEYWORDS = "SELECT|INSERT\\s+INTO|
|
|
9635
|
+
var SQL_KEYWORDS = "SELECT\\b(?=[^`'\"\\n]*\\bFROM\\b)|INSERT\\s+INTO|UPDATE\\b(?=[^`'\"\\n]*\\bSET\\b)|DELETE\\s+FROM|DROP\\s+(?:TABLE|DATABASE|INDEX|VIEW|SCHEMA)|TRUNCATE\\s+TABLE|UNION\\s+SELECT";
|
|
9636
9636
|
var SQL_IN_DOUBLE = `"[^"\\n]*(?:${SQL_KEYWORDS})\\b[^"\\n]*"`;
|
|
9637
9637
|
var SQL_IN_SINGLE = `'[^'\\n]*(?:${SQL_KEYWORDS})\\b[^'\\n]*'`;
|
|
9638
9638
|
var SQL_STRING = `(?:${SQL_IN_DOUBLE}|${SQL_IN_SINGLE})`;
|
|
@@ -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(
|
|
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
|
});
|