cognium-dev 3.104.0 → 3.106.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/cli.js +1031 -51
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -4,8 +4,8 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
4
4
|
|
|
5
5
|
// src/cli.ts
|
|
6
6
|
import { readFileSync, existsSync } from "fs";
|
|
7
|
-
import { stat, readdir } from "fs/promises";
|
|
8
|
-
import { join, dirname as dirname2, extname, resolve, relative } from "path";
|
|
7
|
+
import { stat as stat2, readdir as readdir2 } from "fs/promises";
|
|
8
|
+
import { join as join2, dirname as dirname2, extname, resolve, relative as relative3 } from "path";
|
|
9
9
|
import { createRequire as createRequire2 } from "module";
|
|
10
10
|
|
|
11
11
|
// ../../node_modules/web-tree-sitter/web-tree-sitter.js
|
|
@@ -18060,6 +18060,85 @@ function isHighConfidence(finding) {
|
|
|
18060
18060
|
return finding.confidence === undefined || finding.confidence === "high";
|
|
18061
18061
|
}
|
|
18062
18062
|
|
|
18063
|
+
// ../circle-ir/dist/analysis/library-api-surface-downgrade.js
|
|
18064
|
+
var LIBRARY_API_SURFACE_TAG = "library-api-surface:caller-responsibility";
|
|
18065
|
+
function applyLibraryApiSurfaceDowngrade(findings) {
|
|
18066
|
+
return findings.map((f) => {
|
|
18067
|
+
if (!f.tags?.includes(LIBRARY_API_SURFACE_TAG))
|
|
18068
|
+
return f;
|
|
18069
|
+
if (f.severity === "medium" || f.severity === "low")
|
|
18070
|
+
return f;
|
|
18071
|
+
return {
|
|
18072
|
+
...f,
|
|
18073
|
+
original_severity: f.severity,
|
|
18074
|
+
severity: "medium",
|
|
18075
|
+
level: "warning"
|
|
18076
|
+
};
|
|
18077
|
+
});
|
|
18078
|
+
}
|
|
18079
|
+
|
|
18080
|
+
// ../circle-ir/dist/analysis/project-profile-transform.js
|
|
18081
|
+
var DOWNGRADE_ELIGIBLE_RULE_IDS = new Set([
|
|
18082
|
+
"code_injection",
|
|
18083
|
+
"template_injection",
|
|
18084
|
+
"xpath_injection",
|
|
18085
|
+
"sql_injection"
|
|
18086
|
+
]);
|
|
18087
|
+
function libraryDowngrade(severity) {
|
|
18088
|
+
switch (severity) {
|
|
18089
|
+
case "critical":
|
|
18090
|
+
return { severity: "medium", level: "warning" };
|
|
18091
|
+
case "high":
|
|
18092
|
+
return { severity: "low", level: "note" };
|
|
18093
|
+
case "medium":
|
|
18094
|
+
return { severity: "low", level: "note" };
|
|
18095
|
+
case "low":
|
|
18096
|
+
return { severity: "low", level: "note" };
|
|
18097
|
+
}
|
|
18098
|
+
}
|
|
18099
|
+
function levelForSeverity(severity) {
|
|
18100
|
+
switch (severity) {
|
|
18101
|
+
case "critical":
|
|
18102
|
+
return "error";
|
|
18103
|
+
case "high":
|
|
18104
|
+
return "error";
|
|
18105
|
+
case "medium":
|
|
18106
|
+
return "warning";
|
|
18107
|
+
case "low":
|
|
18108
|
+
return "note";
|
|
18109
|
+
}
|
|
18110
|
+
}
|
|
18111
|
+
function applyProjectProfileTransform(findings, resolveProfile) {
|
|
18112
|
+
return findings.map((f) => {
|
|
18113
|
+
if (!f.tags?.includes(LIBRARY_API_SURFACE_TAG))
|
|
18114
|
+
return f;
|
|
18115
|
+
const profile = resolveProfile(f.file);
|
|
18116
|
+
if (profile === "unknown")
|
|
18117
|
+
return f;
|
|
18118
|
+
const shape = profile.split("/")[0];
|
|
18119
|
+
if (shape === "library") {
|
|
18120
|
+
if (!DOWNGRADE_ELIGIBLE_RULE_IDS.has(f.rule_id))
|
|
18121
|
+
return f;
|
|
18122
|
+
const { severity, level } = libraryDowngrade(f.severity);
|
|
18123
|
+
if (severity === f.severity && level === f.level)
|
|
18124
|
+
return f;
|
|
18125
|
+
return { ...f, severity, level };
|
|
18126
|
+
}
|
|
18127
|
+
if (shape === "application") {
|
|
18128
|
+
if (!f.original_severity)
|
|
18129
|
+
return f;
|
|
18130
|
+
if (f.original_severity === f.severity)
|
|
18131
|
+
return f;
|
|
18132
|
+
return {
|
|
18133
|
+
...f,
|
|
18134
|
+
severity: f.original_severity,
|
|
18135
|
+
level: levelForSeverity(f.original_severity)
|
|
18136
|
+
};
|
|
18137
|
+
}
|
|
18138
|
+
return f;
|
|
18139
|
+
});
|
|
18140
|
+
}
|
|
18141
|
+
|
|
18063
18142
|
// ../circle-ir/dist/languages/registry.js
|
|
18064
18143
|
class DefaultLanguageRegistry {
|
|
18065
18144
|
plugins = new Map;
|
|
@@ -23545,6 +23624,39 @@ var PROTOCOL_CLIENT_PACKAGES = [
|
|
|
23545
23624
|
var OS_EXEC_RECEIVER_RE = /\b(?:Runtime|ProcessBuilder|DefaultExecutor|Executor|Exec|Launcher|ProcStarter|ProcessExecutor|RuntimeUtil)\s*[.(]/;
|
|
23546
23625
|
var PROCESS_BUILDER_ARGV_FORM_RE = /\bnew\s+ProcessBuilder\s*\(\s*(?:Arrays\.asList\b|List\.of\b|Collections\.singletonList\b|new\s+ArrayList\b|new\s+String\s*\[\s*\]\s*\{|"[^"]*"\s*,)/;
|
|
23547
23626
|
var JAVA_THROW_STATEMENT_RE = /^\s*throw\s+new\s+\w+(?:Exception|Error)\b/;
|
|
23627
|
+
var JEXL_ENGINE_TYPES = new Set(["JexlEngine", "Jexl", "JxltEngine"]);
|
|
23628
|
+
var JEXL_EXPRESSION_TYPE_RE = /(?:Jexl)?(?:Expression|Script|Template)(?:Script)?$/;
|
|
23629
|
+
var TEMPLATE_COMPILE_RECEIVER_TYPES = new Set([
|
|
23630
|
+
"Handlebars",
|
|
23631
|
+
"Mustache",
|
|
23632
|
+
"MustacheFactory",
|
|
23633
|
+
"DefaultMustacheFactory",
|
|
23634
|
+
"Pebble",
|
|
23635
|
+
"PebbleEngine",
|
|
23636
|
+
"PebbleEngineBuilder",
|
|
23637
|
+
"VelocityEngine",
|
|
23638
|
+
"Velocity",
|
|
23639
|
+
"Configuration",
|
|
23640
|
+
"Freemarker",
|
|
23641
|
+
"FreeMarker",
|
|
23642
|
+
"TemplateEngine",
|
|
23643
|
+
"SpringTemplateEngine",
|
|
23644
|
+
"Thymeleaf"
|
|
23645
|
+
]);
|
|
23646
|
+
var SPI_GET_RESOURCES_RE = /\.getResources\s*\(\s*["'][^"']*META-INF\/services\/[^"']*["']\s*\)/;
|
|
23647
|
+
var CLASSLOADER_PARENT_TYPES = new Set([
|
|
23648
|
+
"ClassLoader",
|
|
23649
|
+
"URLClassLoader",
|
|
23650
|
+
"SecureClassLoader"
|
|
23651
|
+
]);
|
|
23652
|
+
var CLASSLOADER_NAME_RE = /(?:CachingProvider|ClassLoader|SpiLoader)$/;
|
|
23653
|
+
var LOADCLASS_OVERRIDE_METHOD_RE = /\b(?:public|protected)\s+(?:[\w<>?,\s]+\s+)?(?:loadClass|findClass)\s*\(\s*String\b/;
|
|
23654
|
+
var JAVA_CLASS_DECL_RE = /\bclass\s+([A-Z]\w*)\b(?:\s+extends\s+([A-Z][\w.]*))?(?:\s+implements\s+([A-Z][\w.,<>\s?]*))?/;
|
|
23655
|
+
var SQL_BUILDER_CLASS_RE = /(?:Dialect|SqlBuilder|Quoter|Wrapper|SqlGenerator|QueryBuilder)$/;
|
|
23656
|
+
var SQL_BUILDER_WRAPPER_CALL_RE = /\.(?:wrap|quote|escape|identifier)\s*\(/;
|
|
23657
|
+
var SQL_EXTRACTION_RETURN_RE = /^(?:String|CharSequence|Optional\s*<\s*String\s*>)$/;
|
|
23658
|
+
var SQL_EXTRACTION_NAME_RE = /^(?:get|extract|build).*(?:[Ss]ql|[Qq]uery)|^toSql|.*Statement.*ToString$|.*Query.*String$/;
|
|
23659
|
+
var SQL_EXTRACTION_PRIMITIVE_INPUT_RE = /^(?:String|CharSequence)$/;
|
|
23548
23660
|
function resolveJavaReceiverType(receiver, sinkLine, sourceLines) {
|
|
23549
23661
|
if (!receiver || !/^[A-Za-z_]\w*$/.test(receiver))
|
|
23550
23662
|
return null;
|
|
@@ -23561,6 +23673,48 @@ function resolveJavaReceiverType(receiver, sinkLine, sourceLines) {
|
|
|
23561
23673
|
}
|
|
23562
23674
|
return null;
|
|
23563
23675
|
}
|
|
23676
|
+
function findEnclosingClassDecl(sinkLine, sourceLines) {
|
|
23677
|
+
const end = Math.min(sourceLines.length, sinkLine);
|
|
23678
|
+
for (let i2 = end - 1;i2 >= 0; i2--) {
|
|
23679
|
+
const ln = sourceLines[i2] ?? "";
|
|
23680
|
+
const m = ln.match(JAVA_CLASS_DECL_RE);
|
|
23681
|
+
if (!m)
|
|
23682
|
+
continue;
|
|
23683
|
+
const implementsTypes = m[3] ? m[3].split(",").map((s) => s.replace(/<[^>]*>/g, "").trim()).filter(Boolean).map((s) => {
|
|
23684
|
+
const parts2 = s.split(".");
|
|
23685
|
+
return parts2[parts2.length - 1] ?? s;
|
|
23686
|
+
}) : [];
|
|
23687
|
+
const extendsRaw = m[2] ?? null;
|
|
23688
|
+
const extendsType = extendsRaw ? (() => {
|
|
23689
|
+
const p = extendsRaw.split(".");
|
|
23690
|
+
return p[p.length - 1] ?? extendsRaw;
|
|
23691
|
+
})() : null;
|
|
23692
|
+
return { name: m[1], extendsType, implementsTypes };
|
|
23693
|
+
}
|
|
23694
|
+
return null;
|
|
23695
|
+
}
|
|
23696
|
+
function isInsideLoadClassOverride(sinkLine, sourceLines) {
|
|
23697
|
+
const limit = Math.max(0, sinkLine - 200);
|
|
23698
|
+
for (let i2 = sinkLine - 2;i2 >= limit; i2--) {
|
|
23699
|
+
const ln = sourceLines[i2] ?? "";
|
|
23700
|
+
if (LOADCLASS_OVERRIDE_METHOD_RE.test(ln))
|
|
23701
|
+
return true;
|
|
23702
|
+
if (/\bclass\s+[A-Z]\w*\b/.test(ln))
|
|
23703
|
+
return false;
|
|
23704
|
+
}
|
|
23705
|
+
return false;
|
|
23706
|
+
}
|
|
23707
|
+
function findEnclosingMethodFromIr(types, sinkLine) {
|
|
23708
|
+
for (const t of types) {
|
|
23709
|
+
if (sinkLine < t.start_line || sinkLine > t.end_line)
|
|
23710
|
+
continue;
|
|
23711
|
+
for (const m of t.methods) {
|
|
23712
|
+
if (sinkLine >= m.start_line && sinkLine <= m.end_line)
|
|
23713
|
+
return m;
|
|
23714
|
+
}
|
|
23715
|
+
}
|
|
23716
|
+
return null;
|
|
23717
|
+
}
|
|
23564
23718
|
function isJavaLiteralOrAnnotationAccessor(expr) {
|
|
23565
23719
|
const e = expr.trim();
|
|
23566
23720
|
if (e === "")
|
|
@@ -23842,6 +23996,168 @@ class SinkFilterPass {
|
|
|
23842
23996
|
return true;
|
|
23843
23997
|
});
|
|
23844
23998
|
}
|
|
23999
|
+
if (language === "java") {
|
|
24000
|
+
const sourceLines = ctx.code.split(`
|
|
24001
|
+
`);
|
|
24002
|
+
for (const sink of filtered) {
|
|
24003
|
+
if (sink.type !== "code_injection")
|
|
24004
|
+
continue;
|
|
24005
|
+
const sinkLineText = sourceLines[sink.line - 1] ?? "";
|
|
24006
|
+
const receiverMatch = sinkLineText.match(/\b(\w+)\s*\.\s*(\w+)\s*\(/);
|
|
24007
|
+
const receiver = receiverMatch?.[1];
|
|
24008
|
+
const method = sink.method ?? receiverMatch?.[2];
|
|
24009
|
+
let shouldTag = false;
|
|
24010
|
+
if (method === "createExpression" && receiver) {
|
|
24011
|
+
const recvType = resolveJavaReceiverType(receiver, sink.line, sourceLines);
|
|
24012
|
+
if (recvType && JEXL_ENGINE_TYPES.has(recvType))
|
|
24013
|
+
shouldTag = true;
|
|
24014
|
+
}
|
|
24015
|
+
if (!shouldTag && method === "evaluate" && receiver) {
|
|
24016
|
+
const recvType = resolveJavaReceiverType(receiver, sink.line, sourceLines);
|
|
24017
|
+
if (recvType && JEXL_EXPRESSION_TYPE_RE.test(recvType))
|
|
24018
|
+
shouldTag = true;
|
|
24019
|
+
}
|
|
24020
|
+
if (!shouldTag && method === "compile" && receiver) {
|
|
24021
|
+
const recvType = resolveJavaReceiverType(receiver, sink.line, sourceLines);
|
|
24022
|
+
if (recvType && TEMPLATE_COMPILE_RECEIVER_TYPES.has(recvType))
|
|
24023
|
+
shouldTag = true;
|
|
24024
|
+
}
|
|
24025
|
+
if (!shouldTag && method === "forName") {
|
|
24026
|
+
const start2 = Math.max(0, sink.line - 31);
|
|
24027
|
+
const end = Math.min(sourceLines.length, sink.line + 30);
|
|
24028
|
+
for (let i2 = start2;i2 < end; i2++) {
|
|
24029
|
+
if (SPI_GET_RESOURCES_RE.test(sourceLines[i2] ?? "")) {
|
|
24030
|
+
shouldTag = true;
|
|
24031
|
+
break;
|
|
24032
|
+
}
|
|
24033
|
+
}
|
|
24034
|
+
}
|
|
24035
|
+
if (!shouldTag && method && (method === "loadClass" || method === "findClass" || method === "forName")) {
|
|
24036
|
+
const enclosing = findEnclosingClassDecl(sink.line, sourceLines);
|
|
24037
|
+
if (enclosing) {
|
|
24038
|
+
const extendsClassLoader = enclosing.extendsType !== null && CLASSLOADER_PARENT_TYPES.has(enclosing.extendsType);
|
|
24039
|
+
const implementsSpi = enclosing.implementsTypes.some((t) => /CachingProvider$/.test(t));
|
|
24040
|
+
const nameLooksLikeLoader = CLASSLOADER_NAME_RE.test(enclosing.name);
|
|
24041
|
+
if (extendsClassLoader || implementsSpi || nameLooksLikeLoader)
|
|
24042
|
+
shouldTag = true;
|
|
24043
|
+
}
|
|
24044
|
+
if (!shouldTag && isInsideLoadClassOverride(sink.line, sourceLines)) {
|
|
24045
|
+
shouldTag = true;
|
|
24046
|
+
}
|
|
24047
|
+
}
|
|
24048
|
+
if (shouldTag) {
|
|
24049
|
+
const existing = sink.tags ?? [];
|
|
24050
|
+
if (!existing.includes(LIBRARY_API_SURFACE_TAG)) {
|
|
24051
|
+
sink.tags = [...existing, LIBRARY_API_SURFACE_TAG];
|
|
24052
|
+
}
|
|
24053
|
+
}
|
|
24054
|
+
}
|
|
24055
|
+
}
|
|
24056
|
+
if (language === "java") {
|
|
24057
|
+
const sourceLines = ctx.code.split(`
|
|
24058
|
+
`);
|
|
24059
|
+
filtered = filtered.filter((sink) => {
|
|
24060
|
+
if (sink.type !== "code_injection")
|
|
24061
|
+
return true;
|
|
24062
|
+
const sinkLineText = sourceLines[sink.line - 1] ?? "";
|
|
24063
|
+
const receiverMatch = sinkLineText.match(/\b(\w+)\s*\.\s*(\w+)\s*\(/);
|
|
24064
|
+
const receiver = receiverMatch?.[1];
|
|
24065
|
+
if (!receiver)
|
|
24066
|
+
return true;
|
|
24067
|
+
const start2 = Math.max(0, sink.line - 200);
|
|
24068
|
+
let arrayName = null;
|
|
24069
|
+
const foreachRe = new RegExp(`\\bfor\\s*\\(\\s*\\w[\\w<>?]*\\s+${receiver}\\s*:\\s*(\\w+)\\b`);
|
|
24070
|
+
for (let i2 = sink.line - 1;i2 >= start2; i2--) {
|
|
24071
|
+
const ln = sourceLines[i2] ?? "";
|
|
24072
|
+
const fm = ln.match(foreachRe);
|
|
24073
|
+
if (fm) {
|
|
24074
|
+
arrayName = fm[1];
|
|
24075
|
+
break;
|
|
24076
|
+
}
|
|
24077
|
+
}
|
|
24078
|
+
if (!arrayName) {
|
|
24079
|
+
const methodNameMatch = sinkLineText.match(/\.(\w+)\s*\(/);
|
|
24080
|
+
const methodNameRe = methodNameMatch ? methodNameMatch[1] : "\\w+";
|
|
24081
|
+
const idxRe = new RegExp(`\\b(\\w+)\\s*\\[[^\\]]+\\]\\s*\\.\\s*${methodNameRe}\\s*\\(`);
|
|
24082
|
+
const im = sinkLineText.match(idxRe);
|
|
24083
|
+
if (im)
|
|
24084
|
+
arrayName = im[1];
|
|
24085
|
+
}
|
|
24086
|
+
if (!arrayName)
|
|
24087
|
+
return true;
|
|
24088
|
+
const declRe = new RegExp(`\\bprivate\\s+static\\s+final\\s+(?:[\\w<>?,\\s]+)\\[\\s*\\]\\s+${arrayName}\\s*=`);
|
|
24089
|
+
let declStart = -1;
|
|
24090
|
+
for (let i2 = 0;i2 < sink.line; i2++) {
|
|
24091
|
+
if (declRe.test(sourceLines[i2] ?? "")) {
|
|
24092
|
+
declStart = i2;
|
|
24093
|
+
break;
|
|
24094
|
+
}
|
|
24095
|
+
}
|
|
24096
|
+
if (declStart < 0)
|
|
24097
|
+
return true;
|
|
24098
|
+
let body2 = "";
|
|
24099
|
+
for (let i2 = declStart;i2 < Math.min(sourceLines.length, declStart + 40); i2++) {
|
|
24100
|
+
body2 += (sourceLines[i2] ?? "") + `
|
|
24101
|
+
`;
|
|
24102
|
+
if (/\}\s*;/.test(sourceLines[i2] ?? ""))
|
|
24103
|
+
break;
|
|
24104
|
+
}
|
|
24105
|
+
const initMatch = body2.match(/=\s*(?:new\s+[\w<>?,\s]+\[\s*\]\s*)?\{([\s\S]*?)\}\s*;/);
|
|
24106
|
+
if (!initMatch)
|
|
24107
|
+
return true;
|
|
24108
|
+
const elements = initMatch[1].split(",").map((s) => s.trim()).filter(Boolean);
|
|
24109
|
+
if (elements.length === 0)
|
|
24110
|
+
return true;
|
|
24111
|
+
const allNewLiterals = elements.every((e) => /^new\s+[A-Z]\w*\s*\([^)]*\)$/.test(e));
|
|
24112
|
+
if (allNewLiterals)
|
|
24113
|
+
return false;
|
|
24114
|
+
return true;
|
|
24115
|
+
});
|
|
24116
|
+
}
|
|
24117
|
+
if (language === "java") {
|
|
24118
|
+
const sourceLines = ctx.code.split(`
|
|
24119
|
+
`);
|
|
24120
|
+
filtered = filtered.filter((sink) => {
|
|
24121
|
+
if (sink.type !== "sql_injection")
|
|
24122
|
+
return true;
|
|
24123
|
+
const enclosing = findEnclosingClassDecl(sink.line, sourceLines);
|
|
24124
|
+
if (!enclosing)
|
|
24125
|
+
return true;
|
|
24126
|
+
if (!SQL_BUILDER_CLASS_RE.test(enclosing.name))
|
|
24127
|
+
return true;
|
|
24128
|
+
const lo = Math.max(0, sink.line - 11);
|
|
24129
|
+
const hi = Math.min(sourceLines.length, sink.line + 10);
|
|
24130
|
+
for (let i2 = lo;i2 < hi; i2++) {
|
|
24131
|
+
if (SQL_BUILDER_WRAPPER_CALL_RE.test(sourceLines[i2] ?? ""))
|
|
24132
|
+
return false;
|
|
24133
|
+
}
|
|
24134
|
+
return true;
|
|
24135
|
+
});
|
|
24136
|
+
}
|
|
24137
|
+
if (language === "java") {
|
|
24138
|
+
const types = graph.ir.types ?? [];
|
|
24139
|
+
if (types.length > 0) {
|
|
24140
|
+
filtered = filtered.filter((sink) => {
|
|
24141
|
+
if (sink.type !== "sql_injection")
|
|
24142
|
+
return true;
|
|
24143
|
+
const enclosing = findEnclosingMethodFromIr(types, sink.line);
|
|
24144
|
+
if (!enclosing)
|
|
24145
|
+
return true;
|
|
24146
|
+
const ret = (enclosing.return_type ?? "").replace(/\s+/g, " ").trim();
|
|
24147
|
+
if (!SQL_EXTRACTION_RETURN_RE.test(ret))
|
|
24148
|
+
return true;
|
|
24149
|
+
if (!SQL_EXTRACTION_NAME_RE.test(enclosing.name))
|
|
24150
|
+
return true;
|
|
24151
|
+
const firstParamType = enclosing.parameters[0]?.type ?? null;
|
|
24152
|
+
if (firstParamType === null)
|
|
24153
|
+
return true;
|
|
24154
|
+
const simpleType = firstParamType.replace(/<[^>]*>/g, "").trim();
|
|
24155
|
+
if (SQL_EXTRACTION_PRIMITIVE_INPUT_RE.test(simpleType))
|
|
24156
|
+
return true;
|
|
24157
|
+
return false;
|
|
24158
|
+
});
|
|
24159
|
+
}
|
|
24160
|
+
}
|
|
23845
24161
|
return { sources, sinks: filtered, sanitizers };
|
|
23846
24162
|
}
|
|
23847
24163
|
}
|
|
@@ -34684,6 +35000,13 @@ function getNodeTypesForLanguage(language) {
|
|
|
34684
35000
|
]);
|
|
34685
35001
|
}
|
|
34686
35002
|
}
|
|
35003
|
+
function makeProfileResolver(p) {
|
|
35004
|
+
if (p === undefined)
|
|
35005
|
+
return () => "unknown";
|
|
35006
|
+
if (typeof p === "string")
|
|
35007
|
+
return () => p;
|
|
35008
|
+
return (file) => p.get(file) ?? "unknown";
|
|
35009
|
+
}
|
|
34687
35010
|
async function analyze(code, filePath, language, options = {}) {
|
|
34688
35011
|
if (!initialized) {
|
|
34689
35012
|
await initAnalyzer(options);
|
|
@@ -34862,6 +35185,21 @@ async function analyze(code, filePath, language, options = {}) {
|
|
|
34862
35185
|
flows: interProc.additionalFlows,
|
|
34863
35186
|
interprocedural: interProc.interprocedural
|
|
34864
35187
|
};
|
|
35188
|
+
if (taint.flows && taint.flows.length > 0 && taint.sinks.length > 0) {
|
|
35189
|
+
const sinkTagsByKey = new Map;
|
|
35190
|
+
for (const s of taint.sinks) {
|
|
35191
|
+
if (!s.tags || s.tags.length === 0)
|
|
35192
|
+
continue;
|
|
35193
|
+
sinkTagsByKey.set(`${s.line}:${s.type}`, s.tags);
|
|
35194
|
+
}
|
|
35195
|
+
if (sinkTagsByKey.size > 0) {
|
|
35196
|
+
for (const f of taint.flows) {
|
|
35197
|
+
const tags = sinkTagsByKey.get(`${f.sink_line}:${f.sink_type}`);
|
|
35198
|
+
if (tags && !f.tags)
|
|
35199
|
+
f.tags = [...tags];
|
|
35200
|
+
}
|
|
35201
|
+
}
|
|
35202
|
+
}
|
|
34865
35203
|
const unresolved = detectUnresolved(calls, types, dfg);
|
|
34866
35204
|
const enriched = buildEnriched(types, calls, taint.sources, taint.sinks);
|
|
34867
35205
|
const metricValues = new MetricRunner().run({ meta, types, calls, cfg, dfg, taint, imports, exports, unresolved, enriched }, code, language);
|
|
@@ -34874,7 +35212,9 @@ async function analyze(code, filePath, language, options = {}) {
|
|
|
34874
35212
|
});
|
|
34875
35213
|
emitFindingsInstrumentation(filePath, findings, taint);
|
|
34876
35214
|
const verifiedFindings = applyConfidenceFilter(findings, options.includeSpeculative === true);
|
|
34877
|
-
const
|
|
35215
|
+
const downgradedFindings = applyLibraryApiSurfaceDowngrade(verifiedFindings);
|
|
35216
|
+
const profiledFindings = applyProjectProfileTransform(downgradedFindings, makeProfileResolver(options.projectProfile));
|
|
35217
|
+
const cappedFindings = applyPerFileFindingCap(filePath, profiledFindings, options.perFileFindingCap ?? DEFAULT_PER_FILE_FINDING_CAP);
|
|
34878
35218
|
return {
|
|
34879
35219
|
meta,
|
|
34880
35220
|
types,
|
|
@@ -35032,6 +35372,491 @@ function deriveProjectRoot(paths) {
|
|
|
35032
35372
|
}
|
|
35033
35373
|
return common.join("/") || "/";
|
|
35034
35374
|
}
|
|
35375
|
+
// src/project-profile-detect/index.ts
|
|
35376
|
+
import { relative as relative2 } from "path";
|
|
35377
|
+
|
|
35378
|
+
// src/project-profile-detect/walk.ts
|
|
35379
|
+
import { readdir, readFile, stat } from "fs/promises";
|
|
35380
|
+
import { join, relative } from "path";
|
|
35381
|
+
|
|
35382
|
+
// src/project-profile-detect/maven-parse.ts
|
|
35383
|
+
var TAG = (name2) => new RegExp(`<${name2}\\b[^>]*>([\\s\\S]*?)<\\/${name2}>`, "i");
|
|
35384
|
+
var ALL_TAGS = (name2) => new RegExp(`<${name2}\\b[^>]*>([\\s\\S]*?)<\\/${name2}>`, "gi");
|
|
35385
|
+
function firstTag(xml, name2) {
|
|
35386
|
+
const m = TAG(name2).exec(xml);
|
|
35387
|
+
return m?.[1].trim();
|
|
35388
|
+
}
|
|
35389
|
+
function allTags(xml, name2) {
|
|
35390
|
+
const out2 = [];
|
|
35391
|
+
let m;
|
|
35392
|
+
const re = ALL_TAGS(name2);
|
|
35393
|
+
while ((m = re.exec(xml)) !== null)
|
|
35394
|
+
out2.push(m[1].trim());
|
|
35395
|
+
return out2;
|
|
35396
|
+
}
|
|
35397
|
+
var MAVEN_PLUGIN_MAP = {
|
|
35398
|
+
"spring-boot-maven-plugin": "spring-boot",
|
|
35399
|
+
"maven-plugin-plugin": "maven-plugin",
|
|
35400
|
+
"maven-war-plugin": "war",
|
|
35401
|
+
"maven-ear-plugin": "ear",
|
|
35402
|
+
"maven-shade-plugin": "application",
|
|
35403
|
+
"exec-maven-plugin": "application",
|
|
35404
|
+
"maven-assembly-plugin": "application"
|
|
35405
|
+
};
|
|
35406
|
+
function parseMavenPom(xml, moduleRoot, buildFile, directorySignals) {
|
|
35407
|
+
const stripped = xml.replace(/<parent\b[\s\S]*?<\/parent>/i, "");
|
|
35408
|
+
const groupId = firstTag(stripped, "groupId");
|
|
35409
|
+
const artifactId = firstTag(stripped, "artifactId");
|
|
35410
|
+
const version = firstTag(stripped, "version");
|
|
35411
|
+
const packaging = firstTag(stripped, "packaging");
|
|
35412
|
+
const buildBlock = firstTag(xml, "build") ?? "";
|
|
35413
|
+
const pluginBlocks = allTags(buildBlock, "plugin");
|
|
35414
|
+
const plugins = new Set;
|
|
35415
|
+
for (const p of pluginBlocks) {
|
|
35416
|
+
const aid = firstTag(p, "artifactId");
|
|
35417
|
+
if (aid && MAVEN_PLUGIN_MAP[aid])
|
|
35418
|
+
plugins.add(MAVEN_PLUGIN_MAP[aid]);
|
|
35419
|
+
}
|
|
35420
|
+
if (/<parent\b[\s\S]*?<artifactId>\s*spring-boot-starter-parent\s*<\/artifactId>/i.test(xml)) {
|
|
35421
|
+
plugins.add("spring-boot");
|
|
35422
|
+
}
|
|
35423
|
+
if (packaging === "war")
|
|
35424
|
+
plugins.add("war");
|
|
35425
|
+
if (packaging === "ear")
|
|
35426
|
+
plugins.add("ear");
|
|
35427
|
+
if (packaging === "maven-plugin")
|
|
35428
|
+
plugins.add("maven-plugin");
|
|
35429
|
+
const distBlock = firstTag(xml, "distributionManagement") ?? "";
|
|
35430
|
+
const urls = [
|
|
35431
|
+
...allTags(distBlock, "url")
|
|
35432
|
+
].map((u) => u.trim()).filter(Boolean);
|
|
35433
|
+
const signals = {
|
|
35434
|
+
...directorySignals,
|
|
35435
|
+
plugins: [...directorySignals.plugins, ...plugins],
|
|
35436
|
+
packaging,
|
|
35437
|
+
distributionUrls: [...directorySignals.distributionUrls, ...urls]
|
|
35438
|
+
};
|
|
35439
|
+
return {
|
|
35440
|
+
root: moduleRoot,
|
|
35441
|
+
buildSystem: "maven",
|
|
35442
|
+
buildFile,
|
|
35443
|
+
groupId,
|
|
35444
|
+
artifactId,
|
|
35445
|
+
version,
|
|
35446
|
+
signals
|
|
35447
|
+
};
|
|
35448
|
+
}
|
|
35449
|
+
|
|
35450
|
+
// src/project-profile-detect/gradle-parse.ts
|
|
35451
|
+
var GRADLE_PLUGIN_MAP = {
|
|
35452
|
+
"org.springframework.boot": "spring-boot",
|
|
35453
|
+
"io.spring.dependency-management": "spring-boot",
|
|
35454
|
+
"java-library": "java-library",
|
|
35455
|
+
application: "application",
|
|
35456
|
+
war: "war",
|
|
35457
|
+
ear: "ear",
|
|
35458
|
+
"maven-publish": "maven-publish",
|
|
35459
|
+
"java-gradle-plugin": "gradle-plugin",
|
|
35460
|
+
"com.gradle.plugin-publish": "gradle-plugin"
|
|
35461
|
+
};
|
|
35462
|
+
var PLUGIN_ID_RE = /\bid\s*[(\s]\s*['"]([^'"]+)['"]/g;
|
|
35463
|
+
var APPLY_PLUGIN_RE = /\bapply\s+plugin\s*:\s*['"]([^'"]+)['"]/g;
|
|
35464
|
+
var GROUP_RE = /\bgroup\s*[=(]\s*['"]([^'"]+)['"]/;
|
|
35465
|
+
var VERSION_RE = /\bversion\s*[=(]\s*['"]([^'"]+)['"]/;
|
|
35466
|
+
var URL_RE = /\burl\s*[=(]?\s*(?:uri\s*\(\s*)?['"]([^'"]+)['"]/g;
|
|
35467
|
+
function parsePlugins(text) {
|
|
35468
|
+
const ids = new Set;
|
|
35469
|
+
let m;
|
|
35470
|
+
PLUGIN_ID_RE.lastIndex = 0;
|
|
35471
|
+
while ((m = PLUGIN_ID_RE.exec(text)) !== null)
|
|
35472
|
+
ids.add(m[1]);
|
|
35473
|
+
APPLY_PLUGIN_RE.lastIndex = 0;
|
|
35474
|
+
while ((m = APPLY_PLUGIN_RE.exec(text)) !== null)
|
|
35475
|
+
ids.add(m[1]);
|
|
35476
|
+
const out2 = new Set;
|
|
35477
|
+
for (const id of ids) {
|
|
35478
|
+
const tag = GRADLE_PLUGIN_MAP[id];
|
|
35479
|
+
if (tag)
|
|
35480
|
+
out2.add(tag);
|
|
35481
|
+
}
|
|
35482
|
+
return out2;
|
|
35483
|
+
}
|
|
35484
|
+
function parseUrls(text) {
|
|
35485
|
+
const out2 = [];
|
|
35486
|
+
let m;
|
|
35487
|
+
URL_RE.lastIndex = 0;
|
|
35488
|
+
while ((m = URL_RE.exec(text)) !== null)
|
|
35489
|
+
out2.push(m[1]);
|
|
35490
|
+
return out2;
|
|
35491
|
+
}
|
|
35492
|
+
function parseGradleBuild(text, moduleRoot, buildFile, buildSystem, directorySignals) {
|
|
35493
|
+
const cleaned = text.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
35494
|
+
const pluginsSet = parsePlugins(cleaned);
|
|
35495
|
+
const urls = parseUrls(cleaned);
|
|
35496
|
+
const groupId = GROUP_RE.exec(cleaned)?.[1];
|
|
35497
|
+
const version = VERSION_RE.exec(cleaned)?.[1];
|
|
35498
|
+
const artifactId = moduleRoot.split(/[\\/]/).filter(Boolean).pop();
|
|
35499
|
+
const signals = {
|
|
35500
|
+
...directorySignals,
|
|
35501
|
+
plugins: [...directorySignals.plugins, ...pluginsSet],
|
|
35502
|
+
distributionUrls: [...directorySignals.distributionUrls, ...urls]
|
|
35503
|
+
};
|
|
35504
|
+
return {
|
|
35505
|
+
root: moduleRoot,
|
|
35506
|
+
buildSystem,
|
|
35507
|
+
buildFile,
|
|
35508
|
+
groupId,
|
|
35509
|
+
artifactId,
|
|
35510
|
+
version,
|
|
35511
|
+
signals
|
|
35512
|
+
};
|
|
35513
|
+
}
|
|
35514
|
+
|
|
35515
|
+
// src/project-profile-detect/walk.ts
|
|
35516
|
+
var BUILD_FILES = ["pom.xml", "build.gradle", "build.gradle.kts"];
|
|
35517
|
+
var SKIP_DIRS = new Set([
|
|
35518
|
+
"node_modules",
|
|
35519
|
+
".git",
|
|
35520
|
+
".svn",
|
|
35521
|
+
".hg",
|
|
35522
|
+
"target",
|
|
35523
|
+
"build",
|
|
35524
|
+
"out",
|
|
35525
|
+
"dist",
|
|
35526
|
+
".gradle",
|
|
35527
|
+
".idea",
|
|
35528
|
+
".vscode",
|
|
35529
|
+
"bin",
|
|
35530
|
+
"obj"
|
|
35531
|
+
]);
|
|
35532
|
+
async function discoverBuildModules(scanRoot) {
|
|
35533
|
+
const modules = [];
|
|
35534
|
+
await walk(scanRoot, modules);
|
|
35535
|
+
return modules;
|
|
35536
|
+
}
|
|
35537
|
+
async function walk(dir, out2) {
|
|
35538
|
+
let entries;
|
|
35539
|
+
try {
|
|
35540
|
+
entries = await readdir(dir);
|
|
35541
|
+
} catch {
|
|
35542
|
+
return;
|
|
35543
|
+
}
|
|
35544
|
+
const buildFile = entries.find((e) => BUILD_FILES.includes(e));
|
|
35545
|
+
if (buildFile) {
|
|
35546
|
+
const buildFilePath = join(dir, buildFile);
|
|
35547
|
+
const buildSystem = buildFile === "pom.xml" ? "maven" : buildFile === "build.gradle.kts" ? "gradle-kts" : "gradle";
|
|
35548
|
+
const signals = await collectDirectorySignals(dir);
|
|
35549
|
+
let mod;
|
|
35550
|
+
try {
|
|
35551
|
+
const raw = await readFile(buildFilePath, "utf-8");
|
|
35552
|
+
mod = buildSystem === "maven" ? parseMavenPom(raw, dir, buildFilePath, signals) : parseGradleBuild(raw, dir, buildFilePath, buildSystem, signals);
|
|
35553
|
+
} catch {
|
|
35554
|
+
mod = { root: dir, buildSystem, buildFile: buildFilePath, signals };
|
|
35555
|
+
}
|
|
35556
|
+
out2.push(mod);
|
|
35557
|
+
}
|
|
35558
|
+
for (const entry of entries) {
|
|
35559
|
+
if (SKIP_DIRS.has(entry) || entry.startsWith("."))
|
|
35560
|
+
continue;
|
|
35561
|
+
const full = join(dir, entry);
|
|
35562
|
+
let s;
|
|
35563
|
+
try {
|
|
35564
|
+
s = await stat(full);
|
|
35565
|
+
} catch {
|
|
35566
|
+
continue;
|
|
35567
|
+
}
|
|
35568
|
+
if (s.isDirectory()) {
|
|
35569
|
+
await walk(full, out2);
|
|
35570
|
+
}
|
|
35571
|
+
}
|
|
35572
|
+
}
|
|
35573
|
+
async function collectDirectorySignals(moduleRoot) {
|
|
35574
|
+
const signals = {
|
|
35575
|
+
plugins: [],
|
|
35576
|
+
distributionUrls: [],
|
|
35577
|
+
hasJpmsModuleInfo: false,
|
|
35578
|
+
hasSpiServices: false,
|
|
35579
|
+
hasMainMethod: false
|
|
35580
|
+
};
|
|
35581
|
+
const javaRoot = join(moduleRoot, "src", "main", "java");
|
|
35582
|
+
const resourcesRoot = join(moduleRoot, "src", "main", "resources");
|
|
35583
|
+
try {
|
|
35584
|
+
await stat(join(javaRoot, "module-info.java"));
|
|
35585
|
+
signals.hasJpmsModuleInfo = true;
|
|
35586
|
+
} catch {}
|
|
35587
|
+
try {
|
|
35588
|
+
const s = await stat(join(resourcesRoot, "META-INF", "services"));
|
|
35589
|
+
if (s.isDirectory())
|
|
35590
|
+
signals.hasSpiServices = true;
|
|
35591
|
+
} catch {}
|
|
35592
|
+
try {
|
|
35593
|
+
signals.hasMainMethod = await scanForMainMethod(javaRoot, 200);
|
|
35594
|
+
} catch {}
|
|
35595
|
+
return signals;
|
|
35596
|
+
}
|
|
35597
|
+
var MAIN_METHOD_RE = /\bpublic\s+static\s+void\s+main\s*\(\s*(?:final\s+)?String\s*(?:\[\s*\]|\.{3})/;
|
|
35598
|
+
async function scanForMainMethod(root, maxFiles) {
|
|
35599
|
+
let visited = 0;
|
|
35600
|
+
const stack = [root];
|
|
35601
|
+
while (stack.length > 0 && visited < maxFiles) {
|
|
35602
|
+
const dir = stack.pop();
|
|
35603
|
+
let entries;
|
|
35604
|
+
try {
|
|
35605
|
+
entries = await readdir(dir);
|
|
35606
|
+
} catch {
|
|
35607
|
+
continue;
|
|
35608
|
+
}
|
|
35609
|
+
for (const e of entries) {
|
|
35610
|
+
const full = join(dir, e);
|
|
35611
|
+
let s;
|
|
35612
|
+
try {
|
|
35613
|
+
s = await stat(full);
|
|
35614
|
+
} catch {
|
|
35615
|
+
continue;
|
|
35616
|
+
}
|
|
35617
|
+
if (s.isDirectory()) {
|
|
35618
|
+
stack.push(full);
|
|
35619
|
+
} else if (s.isFile() && e.endsWith(".java")) {
|
|
35620
|
+
visited++;
|
|
35621
|
+
try {
|
|
35622
|
+
const text = await readFile(full, "utf-8");
|
|
35623
|
+
if (MAIN_METHOD_RE.test(text))
|
|
35624
|
+
return true;
|
|
35625
|
+
} catch {}
|
|
35626
|
+
if (visited >= maxFiles)
|
|
35627
|
+
break;
|
|
35628
|
+
}
|
|
35629
|
+
}
|
|
35630
|
+
}
|
|
35631
|
+
return false;
|
|
35632
|
+
}
|
|
35633
|
+
async function enumerateScanFiles(scanRoot) {
|
|
35634
|
+
const out2 = [];
|
|
35635
|
+
await enumerate(scanRoot, out2);
|
|
35636
|
+
return out2;
|
|
35637
|
+
}
|
|
35638
|
+
async function enumerate(dir, out2) {
|
|
35639
|
+
let entries;
|
|
35640
|
+
try {
|
|
35641
|
+
entries = await readdir(dir);
|
|
35642
|
+
} catch {
|
|
35643
|
+
return;
|
|
35644
|
+
}
|
|
35645
|
+
for (const e of entries) {
|
|
35646
|
+
if (SKIP_DIRS.has(e) || e.startsWith("."))
|
|
35647
|
+
continue;
|
|
35648
|
+
const full = join(dir, e);
|
|
35649
|
+
let s;
|
|
35650
|
+
try {
|
|
35651
|
+
s = await stat(full);
|
|
35652
|
+
} catch {
|
|
35653
|
+
continue;
|
|
35654
|
+
}
|
|
35655
|
+
if (s.isDirectory()) {
|
|
35656
|
+
await enumerate(full, out2);
|
|
35657
|
+
} else if (s.isFile()) {
|
|
35658
|
+
out2.push(full);
|
|
35659
|
+
}
|
|
35660
|
+
}
|
|
35661
|
+
}
|
|
35662
|
+
function ownerOf(file, modules) {
|
|
35663
|
+
let best;
|
|
35664
|
+
let bestLen = -1;
|
|
35665
|
+
for (const m of modules) {
|
|
35666
|
+
if (file === m.root || file.startsWith(m.root + "/")) {
|
|
35667
|
+
if (m.root.length > bestLen) {
|
|
35668
|
+
best = m;
|
|
35669
|
+
bestLen = m.root.length;
|
|
35670
|
+
}
|
|
35671
|
+
}
|
|
35672
|
+
}
|
|
35673
|
+
return best;
|
|
35674
|
+
}
|
|
35675
|
+
|
|
35676
|
+
// src/project-profile-detect/publication-detect.ts
|
|
35677
|
+
var PUBLIC_REGISTRY_HOSTS = new Set([
|
|
35678
|
+
"repo.maven.apache.org",
|
|
35679
|
+
"repo1.maven.org",
|
|
35680
|
+
"oss.sonatype.org",
|
|
35681
|
+
"s01.oss.sonatype.org",
|
|
35682
|
+
"central.sonatype.com",
|
|
35683
|
+
"central.sonatype.org",
|
|
35684
|
+
"plugins.gradle.org",
|
|
35685
|
+
"jcenter.bintray.com"
|
|
35686
|
+
]);
|
|
35687
|
+
function isPubliclyPublished(urls) {
|
|
35688
|
+
for (const u of urls) {
|
|
35689
|
+
let host;
|
|
35690
|
+
try {
|
|
35691
|
+
host = new URL(u).hostname.toLowerCase();
|
|
35692
|
+
} catch {
|
|
35693
|
+
continue;
|
|
35694
|
+
}
|
|
35695
|
+
if (PUBLIC_REGISTRY_HOSTS.has(host))
|
|
35696
|
+
return true;
|
|
35697
|
+
}
|
|
35698
|
+
return false;
|
|
35699
|
+
}
|
|
35700
|
+
|
|
35701
|
+
// src/project-profile-detect/shape-resolve.ts
|
|
35702
|
+
function resolveShape(mod) {
|
|
35703
|
+
const sig = mod.signals;
|
|
35704
|
+
const has = (tag) => sig.plugins.includes(tag);
|
|
35705
|
+
const reasons = [];
|
|
35706
|
+
if (has("spring-boot")) {
|
|
35707
|
+
reasons.push("spring-boot plugin");
|
|
35708
|
+
return { shape: "server", reasons };
|
|
35709
|
+
}
|
|
35710
|
+
if (has("war") || has("ear")) {
|
|
35711
|
+
reasons.push(has("war") ? "war packaging" : "ear packaging");
|
|
35712
|
+
return { shape: "server", reasons };
|
|
35713
|
+
}
|
|
35714
|
+
if (has("maven-plugin") || has("gradle-plugin")) {
|
|
35715
|
+
reasons.push(has("maven-plugin") ? "maven-plugin packaging" : "gradle-plugin id");
|
|
35716
|
+
return { shape: "plugin", reasons };
|
|
35717
|
+
}
|
|
35718
|
+
if (has("application")) {
|
|
35719
|
+
reasons.push("application plugin");
|
|
35720
|
+
return { shape: "cli", reasons };
|
|
35721
|
+
}
|
|
35722
|
+
if (sig.hasMainMethod) {
|
|
35723
|
+
reasons.push("main(String[]) found");
|
|
35724
|
+
return { shape: "application", reasons };
|
|
35725
|
+
}
|
|
35726
|
+
const libSignals = [];
|
|
35727
|
+
if (has("java-library"))
|
|
35728
|
+
libSignals.push("java-library plugin");
|
|
35729
|
+
if (sig.hasJpmsModuleInfo)
|
|
35730
|
+
libSignals.push("JPMS module-info.java");
|
|
35731
|
+
if (sig.hasSpiServices)
|
|
35732
|
+
libSignals.push("META-INF/services SPI");
|
|
35733
|
+
if (libSignals.length > 0) {
|
|
35734
|
+
const published = isPubliclyPublished(sig.distributionUrls);
|
|
35735
|
+
if (published) {
|
|
35736
|
+
reasons.push(...libSignals, "public-registry distribution");
|
|
35737
|
+
return { shape: "library", reasons };
|
|
35738
|
+
}
|
|
35739
|
+
reasons.push(...libSignals, "no public-registry distribution (internal helper)");
|
|
35740
|
+
return { shape: "application", reasons };
|
|
35741
|
+
}
|
|
35742
|
+
reasons.push("no shape signals");
|
|
35743
|
+
return { shape: "unknown", reasons };
|
|
35744
|
+
}
|
|
35745
|
+
|
|
35746
|
+
// src/project-profile-detect/env-resolve.ts
|
|
35747
|
+
var TEST_RE = /(?:^|\/)tests?\//;
|
|
35748
|
+
var SAMPLE_RE = /(?:^|\/)(?:samples?|examples?|demos?|fixtures?)\//;
|
|
35749
|
+
var BENCHMARK_RE = /(?:^|\/)benchmarks?\//;
|
|
35750
|
+
var PROD_RE = /(?:^|\/)src\/main\//;
|
|
35751
|
+
function resolveEnv(absoluteFile) {
|
|
35752
|
+
const f = absoluteFile.replace(/\\/g, "/").toLowerCase();
|
|
35753
|
+
if (TEST_RE.test(f))
|
|
35754
|
+
return "test";
|
|
35755
|
+
if (BENCHMARK_RE.test(f))
|
|
35756
|
+
return "benchmark";
|
|
35757
|
+
if (SAMPLE_RE.test(f))
|
|
35758
|
+
return "sample";
|
|
35759
|
+
if (PROD_RE.test(f))
|
|
35760
|
+
return "production";
|
|
35761
|
+
return "dev";
|
|
35762
|
+
}
|
|
35763
|
+
|
|
35764
|
+
// src/project-profile-detect/overrides.ts
|
|
35765
|
+
function compileGlob(glob) {
|
|
35766
|
+
let re = "";
|
|
35767
|
+
let i2 = 0;
|
|
35768
|
+
while (i2 < glob.length) {
|
|
35769
|
+
const c = glob[i2];
|
|
35770
|
+
if (c === "*") {
|
|
35771
|
+
if (glob[i2 + 1] === "*") {
|
|
35772
|
+
re += ".*";
|
|
35773
|
+
i2 += 2;
|
|
35774
|
+
if (glob[i2] === "/")
|
|
35775
|
+
i2++;
|
|
35776
|
+
} else {
|
|
35777
|
+
re += "[^/]*";
|
|
35778
|
+
i2++;
|
|
35779
|
+
}
|
|
35780
|
+
} else if (c === "?") {
|
|
35781
|
+
re += "[^/]";
|
|
35782
|
+
i2++;
|
|
35783
|
+
} else if (/[.+^${}()|[\]\\]/.test(c)) {
|
|
35784
|
+
re += "\\" + c;
|
|
35785
|
+
i2++;
|
|
35786
|
+
} else {
|
|
35787
|
+
re += c;
|
|
35788
|
+
i2++;
|
|
35789
|
+
}
|
|
35790
|
+
}
|
|
35791
|
+
return new RegExp("^" + re + "$");
|
|
35792
|
+
}
|
|
35793
|
+
function compileOverrides(overrides) {
|
|
35794
|
+
if (!overrides)
|
|
35795
|
+
return [];
|
|
35796
|
+
return Object.entries(overrides).map(([glob, profile]) => ({
|
|
35797
|
+
re: compileGlob(glob),
|
|
35798
|
+
profile,
|
|
35799
|
+
glob
|
|
35800
|
+
}));
|
|
35801
|
+
}
|
|
35802
|
+
function applyOverrides(relativePath, compiled) {
|
|
35803
|
+
const p = relativePath.replace(/\\/g, "/");
|
|
35804
|
+
for (const { re, profile, glob } of compiled) {
|
|
35805
|
+
if (re.test(p))
|
|
35806
|
+
return { profile, glob };
|
|
35807
|
+
}
|
|
35808
|
+
return;
|
|
35809
|
+
}
|
|
35810
|
+
|
|
35811
|
+
// src/project-profile-detect/index.ts
|
|
35812
|
+
async function detectProjectProfiles(scanRoot, options = {}) {
|
|
35813
|
+
const modules = await discoverBuildModules(scanRoot);
|
|
35814
|
+
const files = await enumerateScanFiles(scanRoot);
|
|
35815
|
+
const compiledOverrides = compileOverrides(options.overrides);
|
|
35816
|
+
const resolvedByRoot = new Map;
|
|
35817
|
+
const resolvedModules = [];
|
|
35818
|
+
for (const m of modules) {
|
|
35819
|
+
const sr = resolveShape(m);
|
|
35820
|
+
const moduleEnv = resolveEnv(m.root);
|
|
35821
|
+
const profile = sr.shape === "unknown" ? "unknown" : `${sr.shape}/${moduleEnv}`;
|
|
35822
|
+
const r = { module: m, profile, reasons: sr.reasons };
|
|
35823
|
+
resolvedByRoot.set(m.root, r);
|
|
35824
|
+
resolvedModules.push(r);
|
|
35825
|
+
}
|
|
35826
|
+
const profileByFile = new Map;
|
|
35827
|
+
const unknownFiles = [];
|
|
35828
|
+
for (const file of files) {
|
|
35829
|
+
const rel = relative2(scanRoot, file);
|
|
35830
|
+
const ov = applyOverrides(rel, compiledOverrides);
|
|
35831
|
+
if (ov) {
|
|
35832
|
+
profileByFile.set(file, ov.profile);
|
|
35833
|
+
continue;
|
|
35834
|
+
}
|
|
35835
|
+
if (options.forcedProfile && options.forcedProfile !== "unknown") {
|
|
35836
|
+
const [shape2] = options.forcedProfile.split("/");
|
|
35837
|
+
const env2 = resolveEnv(file);
|
|
35838
|
+
profileByFile.set(file, `${shape2}/${env2}`);
|
|
35839
|
+
continue;
|
|
35840
|
+
}
|
|
35841
|
+
const owner = ownerOf(file, modules);
|
|
35842
|
+
if (!owner) {
|
|
35843
|
+
profileByFile.set(file, "unknown");
|
|
35844
|
+
unknownFiles.push(file);
|
|
35845
|
+
continue;
|
|
35846
|
+
}
|
|
35847
|
+
const ownerResolution = resolvedByRoot.get(owner.root);
|
|
35848
|
+
if (!ownerResolution || ownerResolution.profile === "unknown") {
|
|
35849
|
+
profileByFile.set(file, "unknown");
|
|
35850
|
+
unknownFiles.push(file);
|
|
35851
|
+
continue;
|
|
35852
|
+
}
|
|
35853
|
+
const [shape] = ownerResolution.profile.split("/");
|
|
35854
|
+
const env = resolveEnv(file);
|
|
35855
|
+
profileByFile.set(file, `${shape}/${env}`);
|
|
35856
|
+
}
|
|
35857
|
+
return { profileByFile, modules: resolvedModules, unknownFiles };
|
|
35858
|
+
}
|
|
35859
|
+
|
|
35035
35860
|
// src/utils/colors.ts
|
|
35036
35861
|
var RESET = "\x1B[0m";
|
|
35037
35862
|
var BOLD = "\x1B[1m";
|
|
@@ -35053,9 +35878,17 @@ var colors = {
|
|
|
35053
35878
|
};
|
|
35054
35879
|
|
|
35055
35880
|
// src/version.ts
|
|
35056
|
-
var version = "3.
|
|
35881
|
+
var version = "3.106.0";
|
|
35057
35882
|
|
|
35058
35883
|
// src/formatters.ts
|
|
35884
|
+
var LIBRARY_API_SURFACE_TAG2 = "library-api-surface:caller-responsibility";
|
|
35885
|
+
function applyTagSeverityDowngrade(severity, tags) {
|
|
35886
|
+
if (!tags || !tags.includes(LIBRARY_API_SURFACE_TAG2))
|
|
35887
|
+
return severity;
|
|
35888
|
+
if (severity === "critical" || severity === "high")
|
|
35889
|
+
return "medium";
|
|
35890
|
+
return severity;
|
|
35891
|
+
}
|
|
35059
35892
|
var SINK_SEVERITY = {
|
|
35060
35893
|
sql_injection: "critical",
|
|
35061
35894
|
nosql_injection: "high",
|
|
@@ -35377,8 +36210,22 @@ function formatCrossFilePaths(taintPaths) {
|
|
|
35377
36210
|
return lines.join(`
|
|
35378
36211
|
`);
|
|
35379
36212
|
}
|
|
35380
|
-
function formatResults(results, verbose, crossFileData) {
|
|
36213
|
+
function formatResults(results, verbose, crossFileData, profileSummary) {
|
|
35381
36214
|
const lines = [];
|
|
36215
|
+
if (profileSummary && profileSummary.modules.length > 0) {
|
|
36216
|
+
lines.push(colors.bold("Project profile"));
|
|
36217
|
+
for (const m of profileSummary.modules) {
|
|
36218
|
+
const tag = m.root === "" || m.root === "." ? "." : m.root;
|
|
36219
|
+
lines.push(` ${colors.cyan(tag)} → ${colors.bold(m.profile)}`);
|
|
36220
|
+
if (verbose) {
|
|
36221
|
+
lines.push(` reasons: ${m.reasons.join(" → ")}`);
|
|
36222
|
+
}
|
|
36223
|
+
}
|
|
36224
|
+
if (profileSummary.unknownFileCount > 0) {
|
|
36225
|
+
lines.push(colors.dim(` ${profileSummary.unknownFileCount} file(s) outside any module → unknown`));
|
|
36226
|
+
}
|
|
36227
|
+
lines.push("");
|
|
36228
|
+
}
|
|
35382
36229
|
for (const result of results) {
|
|
35383
36230
|
if (result.error) {
|
|
35384
36231
|
lines.push(colors.red(`[ERROR] ${result.file}: ${result.error}`));
|
|
@@ -35397,7 +36244,8 @@ function formatResults(results, verbose, crossFileData) {
|
|
|
35397
36244
|
const cweTag = vuln.cwe ? ` [${vuln.cwe}]` : "";
|
|
35398
36245
|
const severityUpper = vuln.severity.charAt(0).toUpperCase() + vuln.severity.slice(1);
|
|
35399
36246
|
const categoryTag = vuln.category && vuln.category !== "security" ? ` [${vuln.category}]` : "";
|
|
35400
|
-
|
|
36247
|
+
const libraryApiTag = vuln.tags?.includes(LIBRARY_API_SURFACE_TAG2) ? ` ${colors.cyan("[library-api-surface]")}` : "";
|
|
36248
|
+
lines.push(` ${colorFn(`[${icon}]`)} ${colorFn(vuln.type)} (${severityUpper})${cweTag}${categoryTag}${libraryApiTag}`);
|
|
35401
36249
|
lines.push(` Line ${vuln.line}: ${vuln.message}`);
|
|
35402
36250
|
const help = VULNERABILITY_HELP[vuln.type];
|
|
35403
36251
|
const fixText = vuln.fix ?? help?.fix;
|
|
@@ -35422,7 +36270,7 @@ function formatResults(results, verbose, crossFileData) {
|
|
|
35422
36270
|
return lines.join(`
|
|
35423
36271
|
`);
|
|
35424
36272
|
}
|
|
35425
|
-
function formatJSON(results, crossFileData) {
|
|
36273
|
+
function formatJSON(results, crossFileData, profileSummary) {
|
|
35426
36274
|
const output = {
|
|
35427
36275
|
version,
|
|
35428
36276
|
timestamp: new Date().toISOString(),
|
|
@@ -35434,6 +36282,7 @@ function formatJSON(results, crossFileData) {
|
|
|
35434
36282
|
cross_file_taint_paths: crossFileData?.taintPaths ?? [],
|
|
35435
36283
|
cross_file_calls: crossFileData?.crossFileCalls ?? [],
|
|
35436
36284
|
cross_file_budget_exceeded: crossFileData?.budgetExceeded ?? false,
|
|
36285
|
+
...profileSummary ? { project_profile: profileSummary } : {},
|
|
35437
36286
|
summary: {
|
|
35438
36287
|
filesScanned: results.length,
|
|
35439
36288
|
filesWithVulnerabilities: results.filter((r) => r.vulnerabilities.length > 0).length,
|
|
@@ -35445,7 +36294,7 @@ function formatJSON(results, crossFileData) {
|
|
|
35445
36294
|
};
|
|
35446
36295
|
return JSON.stringify(output, null, 2);
|
|
35447
36296
|
}
|
|
35448
|
-
function formatSARIF(results, crossFileData) {
|
|
36297
|
+
function formatSARIF(results, crossFileData, profileSummary) {
|
|
35449
36298
|
const sarif = {
|
|
35450
36299
|
$schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
|
|
35451
36300
|
version: "2.1.0",
|
|
@@ -35459,7 +36308,8 @@ function formatSARIF(results, crossFileData) {
|
|
|
35459
36308
|
rules: generateRules(results, crossFileData)
|
|
35460
36309
|
}
|
|
35461
36310
|
},
|
|
35462
|
-
results: generateSarifResults(results, crossFileData)
|
|
36311
|
+
results: generateSarifResults(results, crossFileData),
|
|
36312
|
+
...profileSummary ? { properties: { projectProfile: profileSummary } } : {}
|
|
35463
36313
|
}
|
|
35464
36314
|
]
|
|
35465
36315
|
};
|
|
@@ -35522,7 +36372,9 @@ function generateSarifResults(results, crossFileData) {
|
|
|
35522
36372
|
properties: {
|
|
35523
36373
|
cwe: vuln.cwe,
|
|
35524
36374
|
severity: vuln.severity,
|
|
35525
|
-
...vuln.fix ? { fix: vuln.fix } : {}
|
|
36375
|
+
...vuln.fix ? { fix: vuln.fix } : {},
|
|
36376
|
+
...vuln.tags && vuln.tags.length > 0 ? { tags: vuln.tags } : {},
|
|
36377
|
+
...vuln.profile ? { profile: vuln.profile } : {}
|
|
35526
36378
|
}
|
|
35527
36379
|
});
|
|
35528
36380
|
}
|
|
@@ -35632,6 +36484,13 @@ SCAN OPTIONS:
|
|
|
35632
36484
|
--disable-pass <passes> Disable specific passes (comma-separated, e.g., "naming-convention,todo-in-prod")
|
|
35633
36485
|
--exclude-tests Exclude test files and directories
|
|
35634
36486
|
--profile <file> Load config from file [default: cognium.config.json]
|
|
36487
|
+
--project-profile <p> Force project profile (shape/env). Disables auto-detection.
|
|
36488
|
+
- shape: library|application|cli|server|plugin
|
|
36489
|
+
- env: production|dev|sample|benchmark|test
|
|
36490
|
+
- Example: --project-profile library/production
|
|
36491
|
+
- Default: auto-detect from build files (pom.xml, build.gradle)
|
|
36492
|
+
--no-project-profile Disable project-profile auto-detection (every file → unknown).
|
|
36493
|
+
--project-profile-explain Print detected per-module profiles + reason chain, then exit.
|
|
35635
36494
|
-o, --output <file> Write results to file
|
|
35636
36495
|
-q, --quiet Suppress progress output
|
|
35637
36496
|
-v, --verbose Show detailed output
|
|
@@ -35820,7 +36679,7 @@ function applySuppressionsToResults(results, suppressions, basePath) {
|
|
|
35820
36679
|
if (suppressions.length === 0)
|
|
35821
36680
|
return results;
|
|
35822
36681
|
return results.map((result) => {
|
|
35823
|
-
const relativeFile =
|
|
36682
|
+
const relativeFile = relative3(basePath, result.file) || result.file;
|
|
35824
36683
|
const filteredVulns = result.vulnerabilities.filter((vuln) => {
|
|
35825
36684
|
for (const supp of suppressions) {
|
|
35826
36685
|
if (supp.pass !== vuln.type)
|
|
@@ -35903,13 +36762,13 @@ function matchesAnyPattern(filePath, patterns) {
|
|
|
35903
36762
|
async function collectFiles(targetPath, options = {}) {
|
|
35904
36763
|
const { language, excludeTests = false, includePatterns, excludePatterns, basePath } = options;
|
|
35905
36764
|
const files = [];
|
|
35906
|
-
const pathStat = await
|
|
36765
|
+
const pathStat = await stat2(targetPath);
|
|
35907
36766
|
if (pathStat.isFile()) {
|
|
35908
36767
|
if (excludeTests && isTestFile2(targetPath)) {
|
|
35909
36768
|
return files;
|
|
35910
36769
|
}
|
|
35911
36770
|
if (fileMatchesLanguage(targetPath, language)) {
|
|
35912
|
-
const relativePath = basePath ?
|
|
36771
|
+
const relativePath = basePath ? relative3(basePath, targetPath) : targetPath;
|
|
35913
36772
|
if (includePatterns && includePatterns.length > 0) {
|
|
35914
36773
|
if (!matchesAnyPattern(relativePath, includePatterns)) {
|
|
35915
36774
|
return files;
|
|
@@ -35921,14 +36780,14 @@ async function collectFiles(targetPath, options = {}) {
|
|
|
35921
36780
|
files.push(targetPath);
|
|
35922
36781
|
}
|
|
35923
36782
|
} else if (pathStat.isDirectory()) {
|
|
35924
|
-
const entries = await
|
|
36783
|
+
const entries = await readdir2(targetPath, { withFileTypes: true });
|
|
35925
36784
|
for (const entry of entries) {
|
|
35926
36785
|
if (entry.name.startsWith(".") || entry.name === "node_modules")
|
|
35927
36786
|
continue;
|
|
35928
36787
|
if (excludeTests && /^(test|tests|__tests__|spec|__mocks__)$/i.test(entry.name))
|
|
35929
36788
|
continue;
|
|
35930
|
-
const fullPath =
|
|
35931
|
-
const relativePath = basePath ?
|
|
36789
|
+
const fullPath = join2(targetPath, entry.name);
|
|
36790
|
+
const relativePath = basePath ? relative3(basePath, fullPath) : fullPath;
|
|
35932
36791
|
if (excludePatterns && entry.isDirectory()) {
|
|
35933
36792
|
const dirPattern = relativePath + "/";
|
|
35934
36793
|
if (excludePatterns.some((p) => matchesGlob(dirPattern, p) || matchesGlob(relativePath, p))) {
|
|
@@ -35945,15 +36804,17 @@ async function scanFile(filePath, language, analyzeOpts) {
|
|
|
35945
36804
|
const code = readFileSync(filePath, "utf-8");
|
|
35946
36805
|
const result = await analyze(code, filePath, language, {
|
|
35947
36806
|
passOptions: analyzeOpts?.passOptions,
|
|
35948
|
-
disabledPasses: analyzeOpts?.disabledPasses
|
|
36807
|
+
disabledPasses: analyzeOpts?.disabledPasses,
|
|
36808
|
+
...analyzeOpts?.projectProfile !== undefined ? { projectProfile: analyzeOpts.projectProfile } : {}
|
|
35949
36809
|
});
|
|
35950
36810
|
const vulnerabilities = (result.taint.flows || []).map((flow) => ({
|
|
35951
36811
|
type: flow.sink_type,
|
|
35952
|
-
severity: SINK_SEVERITY[flow.sink_type] ?? "high",
|
|
36812
|
+
severity: applyTagSeverityDowngrade(SINK_SEVERITY[flow.sink_type] ?? "high", flow.tags),
|
|
35953
36813
|
message: `${flow.sink_type} vulnerability: tainted data flows from line ${flow.source_line} to line ${flow.sink_line}`,
|
|
35954
36814
|
line: flow.sink_line,
|
|
35955
36815
|
cwe: SINK_CWE[flow.sink_type],
|
|
35956
|
-
category: "security"
|
|
36816
|
+
category: "security",
|
|
36817
|
+
...flow.tags && flow.tags.length > 0 ? { tags: [...flow.tags] } : {}
|
|
35957
36818
|
}));
|
|
35958
36819
|
for (const finding of result.findings ?? []) {
|
|
35959
36820
|
vulnerabilities.push({
|
|
@@ -35963,7 +36824,8 @@ async function scanFile(filePath, language, analyzeOpts) {
|
|
|
35963
36824
|
line: finding.line,
|
|
35964
36825
|
cwe: finding.cwe,
|
|
35965
36826
|
fix: finding.fix,
|
|
35966
|
-
category: finding.category ?? "reliability"
|
|
36827
|
+
category: finding.category ?? "reliability",
|
|
36828
|
+
...finding.tags && finding.tags.length > 0 ? { tags: [...finding.tags] } : {}
|
|
35967
36829
|
});
|
|
35968
36830
|
}
|
|
35969
36831
|
return { file: filePath, vulnerabilities };
|
|
@@ -35984,16 +36846,18 @@ async function scanProject(files, language, analyzeOpts, crossFileBudgetMs) {
|
|
|
35984
36846
|
const projectResult = await analyzeProject(filesWithCode, {
|
|
35985
36847
|
passOptions: analyzeOpts?.passOptions,
|
|
35986
36848
|
disabledPasses: analyzeOpts?.disabledPasses,
|
|
35987
|
-
...crossFileBudgetMs !== undefined ? { crossFileBudgetMs } : {}
|
|
36849
|
+
...crossFileBudgetMs !== undefined ? { crossFileBudgetMs } : {},
|
|
36850
|
+
...analyzeOpts?.projectProfile !== undefined ? { projectProfile: analyzeOpts.projectProfile } : {}
|
|
35988
36851
|
});
|
|
35989
36852
|
const results = projectResult.files.map(({ file, analysis }) => {
|
|
35990
36853
|
const vulnerabilities = (analysis.taint.flows || []).map((flow) => ({
|
|
35991
36854
|
type: flow.sink_type,
|
|
35992
|
-
severity: SINK_SEVERITY[flow.sink_type] ?? "high",
|
|
36855
|
+
severity: applyTagSeverityDowngrade(SINK_SEVERITY[flow.sink_type] ?? "high", flow.tags),
|
|
35993
36856
|
message: `${flow.sink_type} vulnerability: tainted data flows from line ${flow.source_line} to line ${flow.sink_line}`,
|
|
35994
36857
|
line: flow.sink_line,
|
|
35995
36858
|
cwe: SINK_CWE[flow.sink_type],
|
|
35996
|
-
category: "security"
|
|
36859
|
+
category: "security",
|
|
36860
|
+
...flow.tags && flow.tags.length > 0 ? { tags: [...flow.tags] } : {}
|
|
35997
36861
|
}));
|
|
35998
36862
|
for (const finding of analysis.findings ?? []) {
|
|
35999
36863
|
vulnerabilities.push({
|
|
@@ -36003,7 +36867,8 @@ async function scanProject(files, language, analyzeOpts, crossFileBudgetMs) {
|
|
|
36003
36867
|
line: finding.line,
|
|
36004
36868
|
cwe: finding.cwe,
|
|
36005
36869
|
fix: finding.fix,
|
|
36006
|
-
category: finding.category ?? "reliability"
|
|
36870
|
+
category: finding.category ?? "reliability",
|
|
36871
|
+
...finding.tags && finding.tags.length > 0 ? { tags: [...finding.tags] } : {}
|
|
36007
36872
|
});
|
|
36008
36873
|
}
|
|
36009
36874
|
return { file, vulnerabilities };
|
|
@@ -36020,7 +36885,7 @@ async function scanProject(files, language, analyzeOpts, crossFileBudgetMs) {
|
|
|
36020
36885
|
async function initWasm(spin) {
|
|
36021
36886
|
const isStandalone = import.meta.url.includes("/$bunfs/");
|
|
36022
36887
|
if (isStandalone) {
|
|
36023
|
-
const { dirname: dirname3, join:
|
|
36888
|
+
const { dirname: dirname3, join: join3 } = await import("path");
|
|
36024
36889
|
const binaryDir = dirname3(process.execPath);
|
|
36025
36890
|
const cwd = process.cwd();
|
|
36026
36891
|
let scriptDir = null;
|
|
@@ -36031,34 +36896,34 @@ async function initWasm(spin) {
|
|
|
36031
36896
|
} catch {}
|
|
36032
36897
|
}
|
|
36033
36898
|
const wasmLocations = [
|
|
36034
|
-
|
|
36035
|
-
|
|
36036
|
-
|
|
36899
|
+
join3(binaryDir, "wasm"),
|
|
36900
|
+
join3(cwd, "wasm"),
|
|
36901
|
+
join3(binaryDir, "..", "wasm"),
|
|
36037
36902
|
...scriptDir ? [
|
|
36038
|
-
|
|
36039
|
-
|
|
36040
|
-
|
|
36903
|
+
join3(scriptDir, "wasm"),
|
|
36904
|
+
join3(scriptDir, "..", "wasm"),
|
|
36905
|
+
join3(scriptDir, "..", "node_modules", "circle-ir", "dist", "wasm")
|
|
36041
36906
|
] : []
|
|
36042
36907
|
];
|
|
36043
36908
|
let wasmDir = null;
|
|
36044
36909
|
for (const location of wasmLocations) {
|
|
36045
|
-
if (existsSync(location) && existsSync(
|
|
36910
|
+
if (existsSync(location) && existsSync(join3(location, "web-tree-sitter.wasm"))) {
|
|
36046
36911
|
wasmDir = location;
|
|
36047
36912
|
break;
|
|
36048
36913
|
}
|
|
36049
36914
|
}
|
|
36050
36915
|
if (wasmDir) {
|
|
36051
36916
|
await initAnalyzer({
|
|
36052
|
-
wasmPath:
|
|
36917
|
+
wasmPath: join3(wasmDir, "web-tree-sitter.wasm"),
|
|
36053
36918
|
languagePaths: {
|
|
36054
|
-
bash:
|
|
36055
|
-
go:
|
|
36056
|
-
java:
|
|
36057
|
-
javascript:
|
|
36058
|
-
typescript:
|
|
36059
|
-
python:
|
|
36060
|
-
rust:
|
|
36061
|
-
html:
|
|
36919
|
+
bash: join3(wasmDir, "tree-sitter-bash.wasm"),
|
|
36920
|
+
go: join3(wasmDir, "tree-sitter-go.wasm"),
|
|
36921
|
+
java: join3(wasmDir, "tree-sitter-java.wasm"),
|
|
36922
|
+
javascript: join3(wasmDir, "tree-sitter-javascript.wasm"),
|
|
36923
|
+
typescript: join3(wasmDir, "tree-sitter-javascript.wasm"),
|
|
36924
|
+
python: join3(wasmDir, "tree-sitter-python.wasm"),
|
|
36925
|
+
rust: join3(wasmDir, "tree-sitter-rust.wasm"),
|
|
36926
|
+
html: join3(wasmDir, "tree-sitter-html.wasm")
|
|
36062
36927
|
}
|
|
36063
36928
|
});
|
|
36064
36929
|
} else {
|
|
@@ -36080,7 +36945,7 @@ Please ensure the wasm/ directory is located next to the binary or in your curre
|
|
|
36080
36945
|
} else {
|
|
36081
36946
|
const require2 = createRequire2(import.meta.url);
|
|
36082
36947
|
const circleIrPkg = require2.resolve("circle-ir/package.json");
|
|
36083
|
-
const wasmBasePath =
|
|
36948
|
+
const wasmBasePath = join2(dirname2(circleIrPkg), "dist", "wasm") + "/";
|
|
36084
36949
|
await initAnalyzer({
|
|
36085
36950
|
wasmPath: wasmBasePath + "web-tree-sitter.wasm",
|
|
36086
36951
|
languagePaths: {
|
|
@@ -36096,6 +36961,62 @@ Please ensure the wasm/ directory is located next to the binary or in your curre
|
|
|
36096
36961
|
});
|
|
36097
36962
|
}
|
|
36098
36963
|
}
|
|
36964
|
+
function buildProfileSummary(scanRoot, modules, resolvedProfiles) {
|
|
36965
|
+
if (!resolvedProfiles)
|
|
36966
|
+
return;
|
|
36967
|
+
if (modules.length === 0 && resolvedProfiles.size === 0)
|
|
36968
|
+
return;
|
|
36969
|
+
let unknownFileCount = 0;
|
|
36970
|
+
for (const p of resolvedProfiles.values()) {
|
|
36971
|
+
if (p === "unknown")
|
|
36972
|
+
unknownFileCount++;
|
|
36973
|
+
}
|
|
36974
|
+
return {
|
|
36975
|
+
scanRoot,
|
|
36976
|
+
modules: modules.map((m) => ({
|
|
36977
|
+
root: relative3(scanRoot, m.module.root) || ".",
|
|
36978
|
+
profile: m.profile,
|
|
36979
|
+
reasons: m.reasons,
|
|
36980
|
+
buildSystem: m.module.buildSystem
|
|
36981
|
+
})),
|
|
36982
|
+
unknownFileCount
|
|
36983
|
+
};
|
|
36984
|
+
}
|
|
36985
|
+
function summarizeModules(modules) {
|
|
36986
|
+
if (modules.length === 0)
|
|
36987
|
+
return "no build files detected";
|
|
36988
|
+
const counts = new Map;
|
|
36989
|
+
for (const m of modules) {
|
|
36990
|
+
counts.set(m.profile, (counts.get(m.profile) ?? 0) + 1);
|
|
36991
|
+
}
|
|
36992
|
+
const parts2 = [...counts.entries()].map(([profile, count]) => `${profile}×${count}`).sort();
|
|
36993
|
+
return `${modules.length} module(s): ${parts2.join(", ")}`;
|
|
36994
|
+
}
|
|
36995
|
+
function printProfileExplain(scanRoot, detection) {
|
|
36996
|
+
const out2 = [];
|
|
36997
|
+
out2.push(colors.bold("Project profile detection"));
|
|
36998
|
+
out2.push(` Scan root: ${scanRoot}`);
|
|
36999
|
+
out2.push(` Modules: ${detection.modules.length}`);
|
|
37000
|
+
out2.push(` Unknown files (no enclosing module): ${detection.unknownFiles.length}`);
|
|
37001
|
+
out2.push("");
|
|
37002
|
+
if (detection.modules.length === 0) {
|
|
37003
|
+
out2.push(" (no pom.xml, build.gradle, or build.gradle.kts found)");
|
|
37004
|
+
} else {
|
|
37005
|
+
for (const r of detection.modules) {
|
|
37006
|
+
const rel = relative3(scanRoot, r.module.root) || ".";
|
|
37007
|
+
out2.push(` ${colors.cyan(rel || ".")} → ${colors.bold(r.profile)}`);
|
|
37008
|
+
out2.push(` build: ${r.module.buildSystem} (${relative3(scanRoot, r.module.buildFile)})`);
|
|
37009
|
+
if (r.module.artifactId) {
|
|
37010
|
+
out2.push(` coords: ${r.module.groupId ?? "?"}:${r.module.artifactId}:${r.module.version ?? "?"}`);
|
|
37011
|
+
}
|
|
37012
|
+
out2.push(` reasons: ${r.reasons.join(" → ")}`);
|
|
37013
|
+
out2.push("");
|
|
37014
|
+
}
|
|
37015
|
+
}
|
|
37016
|
+
process.stderr.write(out2.join(`
|
|
37017
|
+
`) + `
|
|
37018
|
+
`);
|
|
37019
|
+
}
|
|
36099
37020
|
async function runScan(targetPath, options) {
|
|
36100
37021
|
const spin = options.quiet ? null : spinner("Initializing analyzer...").start();
|
|
36101
37022
|
const config = loadConfig(options.profile);
|
|
@@ -36149,8 +37070,39 @@ async function runScan(targetPath, options) {
|
|
|
36149
37070
|
}
|
|
36150
37071
|
let results;
|
|
36151
37072
|
let crossFileData;
|
|
36152
|
-
const
|
|
36153
|
-
|
|
37073
|
+
const forcedProfile = options.projectProfile ?? config?.profile;
|
|
37074
|
+
const profileOverrides = config?.profileOverrides;
|
|
37075
|
+
const detectionEnabled = !options.noProjectProfile && (await stat2(absPath)).isDirectory();
|
|
37076
|
+
let resolvedProfiles;
|
|
37077
|
+
let detectedModules = [];
|
|
37078
|
+
if (detectionEnabled) {
|
|
37079
|
+
if (spin)
|
|
37080
|
+
spin.text = "Detecting project profile...";
|
|
37081
|
+
const detection = await detectProjectProfiles(absPath, {
|
|
37082
|
+
forcedProfile,
|
|
37083
|
+
overrides: profileOverrides
|
|
37084
|
+
});
|
|
37085
|
+
resolvedProfiles = detection.profileByFile;
|
|
37086
|
+
detectedModules = detection.modules;
|
|
37087
|
+
if (options.projectProfileExplain) {
|
|
37088
|
+
if (spin)
|
|
37089
|
+
spin.stop();
|
|
37090
|
+
printProfileExplain(absPath, detection);
|
|
37091
|
+
return;
|
|
37092
|
+
}
|
|
37093
|
+
if (!options.quiet && detectedModules.length > 0) {
|
|
37094
|
+
const summary = summarizeModules(detectedModules);
|
|
37095
|
+
console.error(colors.dim(`Project profile: ${summary}`));
|
|
37096
|
+
}
|
|
37097
|
+
} else if (forcedProfile && !options.noProjectProfile) {
|
|
37098
|
+
resolvedProfiles = undefined;
|
|
37099
|
+
}
|
|
37100
|
+
const analyzeOpts = {
|
|
37101
|
+
passOptions,
|
|
37102
|
+
disabledPasses,
|
|
37103
|
+
...resolvedProfiles ? { projectProfile: resolvedProfiles } : forcedProfile && !options.noProjectProfile ? { projectProfile: forcedProfile } : {}
|
|
37104
|
+
};
|
|
37105
|
+
if ((await stat2(absPath)).isDirectory()) {
|
|
36154
37106
|
if (spin)
|
|
36155
37107
|
spin.text = `Running project analysis on ${files.length} file(s)...`;
|
|
36156
37108
|
const projectScan = await scanProject(files, options.language, analyzeOpts, options.crossFileBudgetMs);
|
|
@@ -36162,7 +37114,7 @@ async function runScan(targetPath, options) {
|
|
|
36162
37114
|
results = [];
|
|
36163
37115
|
let processed = 0;
|
|
36164
37116
|
const formatCurrentFile = (file) => {
|
|
36165
|
-
const rel =
|
|
37117
|
+
const rel = relative3(absPath, file) || file;
|
|
36166
37118
|
return rel.length > 80 ? `...${rel.slice(-77)}` : rel;
|
|
36167
37119
|
};
|
|
36168
37120
|
const concurrency = options.threads;
|
|
@@ -36260,17 +37212,27 @@ async function runScan(targetPath, options) {
|
|
|
36260
37212
|
const errors = results.filter((r) => r.error).length;
|
|
36261
37213
|
const crossFilePaths = crossFileData?.taintPaths.length ?? 0;
|
|
36262
37214
|
const shouldOutput = totalVulns > 0 || crossFilePaths > 0 || errors > 0 || options.verbose || options.output || options.format !== "text";
|
|
37215
|
+
const profileSummary = buildProfileSummary(absPath, detectedModules, resolvedProfiles);
|
|
37216
|
+
if (resolvedProfiles) {
|
|
37217
|
+
for (const r of results) {
|
|
37218
|
+
const p = resolvedProfiles.get(r.file);
|
|
37219
|
+
if (!p || p === "unknown")
|
|
37220
|
+
continue;
|
|
37221
|
+
for (const v of r.vulnerabilities)
|
|
37222
|
+
v.profile = p;
|
|
37223
|
+
}
|
|
37224
|
+
}
|
|
36263
37225
|
if (shouldOutput) {
|
|
36264
37226
|
let output;
|
|
36265
37227
|
switch (options.format) {
|
|
36266
37228
|
case "json":
|
|
36267
|
-
output = formatJSON(results, crossFileData);
|
|
37229
|
+
output = formatJSON(results, crossFileData, profileSummary);
|
|
36268
37230
|
break;
|
|
36269
37231
|
case "sarif":
|
|
36270
|
-
output = formatSARIF(results, crossFileData);
|
|
37232
|
+
output = formatSARIF(results, crossFileData, profileSummary);
|
|
36271
37233
|
break;
|
|
36272
37234
|
default:
|
|
36273
|
-
output = formatResults(results, options.verbose, crossFileData);
|
|
37235
|
+
output = formatResults(results, options.verbose, crossFileData, profileSummary);
|
|
36274
37236
|
}
|
|
36275
37237
|
if (options.output) {
|
|
36276
37238
|
const { writeFileSync } = await import("fs");
|
|
@@ -36356,7 +37318,7 @@ async function runMetrics(targetPath, options) {
|
|
|
36356
37318
|
continue;
|
|
36357
37319
|
}
|
|
36358
37320
|
if (spin) {
|
|
36359
|
-
const rel =
|
|
37321
|
+
const rel = relative3(absPath, file) || file;
|
|
36360
37322
|
const maxLen = 80;
|
|
36361
37323
|
const label = rel.length > maxLen ? `...${rel.slice(-(maxLen - 3))}` : rel;
|
|
36362
37324
|
spin.text = `Analyzing ${label}... (${processed}/${totalFiles})`;
|
|
@@ -36401,7 +37363,7 @@ async function runMetrics(targetPath, options) {
|
|
|
36401
37363
|
} else {
|
|
36402
37364
|
const lines = [];
|
|
36403
37365
|
for (const fm of filtered) {
|
|
36404
|
-
const rel =
|
|
37366
|
+
const rel = relative3(absPath, fm.file) || fm.file;
|
|
36405
37367
|
lines.push(rel);
|
|
36406
37368
|
const byCategory = new Map;
|
|
36407
37369
|
for (const m of fm.metrics) {
|
|
@@ -36557,6 +37519,21 @@ function applyFindingsInstrumentation() {
|
|
|
36557
37519
|
setFindingsInstrumentation(true);
|
|
36558
37520
|
}
|
|
36559
37521
|
}
|
|
37522
|
+
function parseProjectProfileArg(raw) {
|
|
37523
|
+
if (raw === undefined || raw === true || raw === "")
|
|
37524
|
+
return;
|
|
37525
|
+
const s = String(raw).toLowerCase();
|
|
37526
|
+
if (s === "unknown")
|
|
37527
|
+
return "unknown";
|
|
37528
|
+
const m = /^(library|application|cli|server|plugin)\/(production|dev|sample|benchmark|test)$/.exec(s);
|
|
37529
|
+
if (!m) {
|
|
37530
|
+
console.error(colors.yellow(`Warning: invalid --project-profile "${s}" — expected one of:
|
|
37531
|
+
` + ` {library,application,cli,server,plugin}/{production,dev,sample,benchmark,test}
|
|
37532
|
+
or "unknown". Falling back to auto-detection.`));
|
|
37533
|
+
return;
|
|
37534
|
+
}
|
|
37535
|
+
return s;
|
|
37536
|
+
}
|
|
36560
37537
|
function parseCrossFileBudgetMs(raw) {
|
|
36561
37538
|
if (raw === undefined || raw === true || raw === "")
|
|
36562
37539
|
return;
|
|
@@ -36629,7 +37606,10 @@ Usage: cognium-dev scan <path> [options]`);
|
|
|
36629
37606
|
excludeCwe: options["exclude-cwe"],
|
|
36630
37607
|
profile: options.profile || options.p,
|
|
36631
37608
|
disablePass: options["disable-pass"],
|
|
36632
|
-
crossFileBudgetMs: parseCrossFileBudgetMs(options["cross-file-budget-ms"])
|
|
37609
|
+
crossFileBudgetMs: parseCrossFileBudgetMs(options["cross-file-budget-ms"]),
|
|
37610
|
+
projectProfile: parseProjectProfileArg(options["project-profile"]),
|
|
37611
|
+
noProjectProfile: options["no-project-profile"] === true,
|
|
37612
|
+
projectProfileExplain: options["project-profile-explain"] === true
|
|
36633
37613
|
};
|
|
36634
37614
|
await runScan(targetPath, scanOptions);
|
|
36635
37615
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cognium-dev",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.106.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.
|
|
68
|
+
"circle-ir": "^3.106.0"
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
71
|
"@types/node": "^25.5.0",
|