cognium-dev 4.13.2 → 4.14.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/README.md +4 -10
- package/dist/cli.js +208 -35
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -307,9 +307,9 @@ Filter to security findings only: `cognium-dev scan ./src --category security`
|
|
|
307
307
|
| Rust | `.rs` | Actix-web, Rocket, Axum |
|
|
308
308
|
| Bash | `.sh`, `.bash` | Shell scripts |
|
|
309
309
|
| HTML | `.html`, `.htm` | Web extraction preprocessor |
|
|
310
|
-
| C#/.NET _(
|
|
310
|
+
| C#/.NET _(Preview)_ | `.cs` | ASP.NET Core, ADO.NET (EF Core not yet verified) |
|
|
311
311
|
|
|
312
|
-
> **C#/.NET is
|
|
312
|
+
> **C#/.NET is Preview** (since 4.0.0). Straight-line taint
|
|
313
313
|
> analysis across 10 CWE families (SQLi, command injection, path traversal,
|
|
314
314
|
> SSRF, code injection, XSS, deserialization, LDAP, XPath, XXE). Not yet
|
|
315
315
|
> benchmark-verified — expect gaps in branch/alias precision and detector breadth.
|
|
@@ -455,15 +455,9 @@ Cognium is built for speed:
|
|
|
455
455
|
- **Core Engine**: [circle-ir](https://github.com/cogniumhq/cognium-dev/tree/main/packages/circle-ir) - High-performance SAST library
|
|
456
456
|
- **Dependencies**: Only 1 runtime dependency (circle-ir)
|
|
457
457
|
|
|
458
|
-
##
|
|
458
|
+
## Data handling
|
|
459
459
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
- **Discovery Mode**: LLM reads source code to locate vulnerable methods from scratch
|
|
463
|
-
- **Verification Mode**: Confirms whether static findings are actually exploitable
|
|
464
|
-
- **Semantic Extraction**: Extracts design intent for automated gap analysis
|
|
465
|
-
|
|
466
|
-
For details on LLM integration and benchmark improvements (42.5% → 78.3% on CWE-Bench with Claude Opus), visit [cognium.net](https://cognium.net).
|
|
460
|
+
cognium-dev is fully deterministic and runs entirely on your machine: source code is not sent to Cognium or any third-party service. Optional AI-assisted analysis is a separate product, [cognium-ai](https://cognium.net), which can send code snippets to a configured model endpoint; use cognium-dev (or cognium-ai with AI-assisted mode off and no remote engine configured) for air-gapped scanning.
|
|
467
461
|
|
|
468
462
|
## Benchmark Results
|
|
469
463
|
|
package/dist/cli.js
CHANGED
|
@@ -5515,6 +5515,14 @@ function csharpBareName(node) {
|
|
|
5515
5515
|
}
|
|
5516
5516
|
return getNodeText(node);
|
|
5517
5517
|
}
|
|
5518
|
+
function csharpCreatedSimpleType(creation) {
|
|
5519
|
+
const typeNode = creation.childForFieldName("type");
|
|
5520
|
+
if (!typeNode)
|
|
5521
|
+
return null;
|
|
5522
|
+
const bare = csharpBareName(typeNode).replace(/<[^>]*>/g, "");
|
|
5523
|
+
const simple = bare.split(".").pop()?.trim() ?? "";
|
|
5524
|
+
return simple && simple !== "var" ? simple : null;
|
|
5525
|
+
}
|
|
5518
5526
|
function extractCSharpCalls(tree, cache) {
|
|
5519
5527
|
const calls = [];
|
|
5520
5528
|
const typeMap = buildCSharpReceiverTypeMap(tree, cache);
|
|
@@ -5523,11 +5531,15 @@ function extractCSharpCalls(tree, cache) {
|
|
|
5523
5531
|
const fn = inv.childForFieldName("function");
|
|
5524
5532
|
let methodName = "unknown";
|
|
5525
5533
|
let receiver = null;
|
|
5534
|
+
let receiverTypeFromCreation = null;
|
|
5526
5535
|
if (fn?.type === "member_access_expression") {
|
|
5527
5536
|
const nameNode = fn.childForFieldName("name");
|
|
5528
5537
|
const exprNode = fn.childForFieldName("expression");
|
|
5529
5538
|
methodName = nameNode ? csharpBareName(nameNode) : "unknown";
|
|
5530
5539
|
receiver = exprNode ? getNodeText(exprNode) : null;
|
|
5540
|
+
if (exprNode?.type === "object_creation_expression") {
|
|
5541
|
+
receiverTypeFromCreation = csharpCreatedSimpleType(exprNode);
|
|
5542
|
+
}
|
|
5531
5543
|
} else if (fn) {
|
|
5532
5544
|
methodName = csharpBareName(fn);
|
|
5533
5545
|
}
|
|
@@ -5545,7 +5557,7 @@ function extractCSharpCalls(tree, cache) {
|
|
|
5545
5557
|
calls.push({
|
|
5546
5558
|
method_name: methodName,
|
|
5547
5559
|
receiver,
|
|
5548
|
-
receiver_type: receiver ? typeMap.get(receiver) ?? null : null,
|
|
5560
|
+
receiver_type: receiverTypeFromCreation ?? (receiver ? typeMap.get(receiver) ?? null : null),
|
|
5549
5561
|
receiver_type_fqn: null,
|
|
5550
5562
|
arguments: args2,
|
|
5551
5563
|
location: { line: inv.startPosition.row + 1, column: inv.startPosition.column },
|
|
@@ -11410,6 +11422,7 @@ var DEFAULT_SOURCES = [
|
|
|
11410
11422
|
{ method: "getFileName", class: "MimeBodyPart", type: "file_input", severity: "high", return_tainted: true },
|
|
11411
11423
|
{ method: "getDisposition", class: "Part", type: "file_input", severity: "medium", return_tainted: true },
|
|
11412
11424
|
{ method: "getName", class: "ZipEntry", type: "file_input", severity: "high", return_tainted: true },
|
|
11425
|
+
{ method: "getName", class: "JarEntry", type: "file_input", severity: "high", return_tainted: true },
|
|
11413
11426
|
{ method: "getName", class: "ZipArchiveEntry", type: "file_input", severity: "high", return_tainted: true },
|
|
11414
11427
|
{ method: "getName", class: "TarArchiveEntry", type: "file_input", severity: "high", return_tainted: true },
|
|
11415
11428
|
{ method: "getName", class: "ArchiveEntry", type: "file_input", severity: "high", return_tainted: true },
|
|
@@ -11858,7 +11871,7 @@ var DEFAULT_SINKS = [
|
|
|
11858
11871
|
{ method: "checkout", class: "SCM", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
11859
11872
|
{ method: "retrieve", class: "LibraryAdder", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
11860
11873
|
{ method: "add", class: "LibraryAdder", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
11861
|
-
{ method: "CpsFlowExecution", class: "constructor", type: "
|
|
11874
|
+
{ method: "CpsFlowExecution", class: "constructor", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
|
|
11862
11875
|
{ method: "CpsFlowDefinition", class: "constructor", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
11863
11876
|
{ method: "FlowExecution", class: "constructor", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
11864
11877
|
{ method: "processControlCommand", class: "TransportConnection", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
@@ -14009,12 +14022,14 @@ function isInterproceduralTaintableType(typeName, language) {
|
|
|
14009
14022
|
function isParameterizedQueryCall(call, pattern) {
|
|
14010
14023
|
if (pattern.type !== "sql_injection")
|
|
14011
14024
|
return false;
|
|
14012
|
-
const
|
|
14025
|
+
const queryPos = pattern.arg_positions.length > 0 ? Math.min(...pattern.arg_positions) : 0;
|
|
14026
|
+
const queryArg = call.arguments.find((a) => a.position === queryPos);
|
|
14013
14027
|
if (queryArg) {
|
|
14014
14028
|
const queryText = queryArg.literal ?? queryArg.expression ?? "";
|
|
14015
14029
|
const hasPlaceholders = /(\?(?:\s|,|$|\))|\$\d+|:\w+|%s)/.test(queryText);
|
|
14016
14030
|
const hasConcatenation = /\+\s*[^+]/.test(queryText) || queryText.includes("${");
|
|
14017
|
-
|
|
14031
|
+
const hasBoundArg = queryPos === 0 ? call.arguments.length >= 2 : call.arguments.some((a) => a.position > queryPos);
|
|
14032
|
+
if (hasPlaceholders && !hasConcatenation && hasBoundArg) {
|
|
14018
14033
|
return true;
|
|
14019
14034
|
}
|
|
14020
14035
|
}
|
|
@@ -14757,6 +14772,17 @@ function isSafeJinjaRenderCall(call, pattern, language, sourceLines) {
|
|
|
14757
14772
|
}
|
|
14758
14773
|
return false;
|
|
14759
14774
|
}
|
|
14775
|
+
function isArgumentlessJsResponseWriter(call, pattern, language) {
|
|
14776
|
+
if (pattern.type !== "xss" || pattern.class !== "Response")
|
|
14777
|
+
return false;
|
|
14778
|
+
if (language !== "javascript" && language !== "typescript")
|
|
14779
|
+
return false;
|
|
14780
|
+
const positions = pattern.arg_positions;
|
|
14781
|
+
if (!positions || positions.length === 0)
|
|
14782
|
+
return false;
|
|
14783
|
+
const argc = call.arguments?.length ?? 0;
|
|
14784
|
+
return positions.every((pos) => pos >= argc);
|
|
14785
|
+
}
|
|
14760
14786
|
function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types) {
|
|
14761
14787
|
const patternsForLanguage = language === undefined ? patterns : patterns.filter((p) => sinkPatternAppliesTo(p, language));
|
|
14762
14788
|
const sinkMap = new Map;
|
|
@@ -14817,6 +14843,9 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
|
|
|
14817
14843
|
continue;
|
|
14818
14844
|
}
|
|
14819
14845
|
}
|
|
14846
|
+
if (isArgumentlessJsResponseWriter(call, pattern, language)) {
|
|
14847
|
+
continue;
|
|
14848
|
+
}
|
|
14820
14849
|
if ((pattern.type === "command_injection" || pattern.type === "sql_injection") && call.method_name === "exec" && (language === "javascript" || language === "typescript") && isRegexReceiver(call.receiver, sourceLines)) {
|
|
14821
14850
|
continue;
|
|
14822
14851
|
}
|
|
@@ -24259,6 +24288,17 @@ class VuePlugin extends BaseLanguagePlugin {
|
|
|
24259
24288
|
}
|
|
24260
24289
|
|
|
24261
24290
|
// ../circle-ir/dist/languages/plugins/go.js
|
|
24291
|
+
function sqlSinks(cls, methods) {
|
|
24292
|
+
return methods.map(([method, argPositions]) => ({
|
|
24293
|
+
method,
|
|
24294
|
+
class: cls,
|
|
24295
|
+
type: "sql_injection",
|
|
24296
|
+
cwe: "CWE-89",
|
|
24297
|
+
severity: "critical",
|
|
24298
|
+
argPositions
|
|
24299
|
+
}));
|
|
24300
|
+
}
|
|
24301
|
+
|
|
24262
24302
|
class GoPlugin extends BaseLanguagePlugin {
|
|
24263
24303
|
id = "go";
|
|
24264
24304
|
name = "Go";
|
|
@@ -24443,6 +24483,32 @@ class GoPlugin extends BaseLanguagePlugin {
|
|
|
24443
24483
|
severity: "critical",
|
|
24444
24484
|
argPositions: [0]
|
|
24445
24485
|
},
|
|
24486
|
+
...sqlSinks("DB", [
|
|
24487
|
+
["QueryContext", [1]],
|
|
24488
|
+
["QueryRowContext", [1]],
|
|
24489
|
+
["ExecContext", [1]],
|
|
24490
|
+
["Prepare", [0]],
|
|
24491
|
+
["PrepareContext", [1]]
|
|
24492
|
+
]),
|
|
24493
|
+
...sqlSinks("Tx", [
|
|
24494
|
+
["Exec", [0]],
|
|
24495
|
+
["QueryRow", [0]],
|
|
24496
|
+
["QueryContext", [1]],
|
|
24497
|
+
["QueryRowContext", [1]],
|
|
24498
|
+
["ExecContext", [1]],
|
|
24499
|
+
["Prepare", [0]],
|
|
24500
|
+
["PrepareContext", [1]]
|
|
24501
|
+
]),
|
|
24502
|
+
...sqlSinks("Conn", [
|
|
24503
|
+
["Query", [0]],
|
|
24504
|
+
["QueryRow", [0]],
|
|
24505
|
+
["Exec", [0]],
|
|
24506
|
+
["QueryContext", [1]],
|
|
24507
|
+
["QueryRowContext", [1]],
|
|
24508
|
+
["ExecContext", [1]],
|
|
24509
|
+
["Prepare", [0]],
|
|
24510
|
+
["PrepareContext", [1]]
|
|
24511
|
+
]),
|
|
24446
24512
|
{
|
|
24447
24513
|
method: "Command",
|
|
24448
24514
|
class: "exec",
|
|
@@ -39947,6 +40013,8 @@ function analyzeInterprocedural2(graphOrTypes, callsOrSources, dfgOrSinks, sourc
|
|
|
39947
40013
|
"QueryRowContext",
|
|
39948
40014
|
"Exec",
|
|
39949
40015
|
"ExecContext",
|
|
40016
|
+
"Prepare",
|
|
40017
|
+
"PrepareContext",
|
|
39950
40018
|
"EscapeString",
|
|
39951
40019
|
"HTMLEscapeString",
|
|
39952
40020
|
"JSEscapeString",
|
|
@@ -44880,7 +44948,7 @@ function isGeneratedFile(file) {
|
|
|
44880
44948
|
var PROVIDER_PATTERNS = [
|
|
44881
44949
|
{
|
|
44882
44950
|
name: "AWS access key",
|
|
44883
|
-
regex: /\
|
|
44951
|
+
regex: /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/,
|
|
44884
44952
|
severity: "critical",
|
|
44885
44953
|
level: "error",
|
|
44886
44954
|
fix: "Rotate the AWS access key immediately and move it to an environment variable or AWS Secrets Manager."
|
|
@@ -44948,6 +45016,20 @@ var PROVIDER_PATTERNS = [
|
|
|
44948
45016
|
level: "warning",
|
|
44949
45017
|
fix: "Publishable keys are not secret but should still not be checked in to back-end source files; verify front-end vs back-end context."
|
|
44950
45018
|
},
|
|
45019
|
+
{
|
|
45020
|
+
name: "Stripe test secret key",
|
|
45021
|
+
regex: /\bsk_test_[A-Za-z0-9]{24,}\b/,
|
|
45022
|
+
severity: "high",
|
|
45023
|
+
level: "warning",
|
|
45024
|
+
fix: "Rotate the Stripe test secret key in the Stripe Dashboard and load it from a secrets manager."
|
|
45025
|
+
},
|
|
45026
|
+
{
|
|
45027
|
+
name: "Stripe test restricted key",
|
|
45028
|
+
regex: /\brk_test_[A-Za-z0-9]{24,}\b/,
|
|
45029
|
+
severity: "high",
|
|
45030
|
+
level: "warning",
|
|
45031
|
+
fix: "Rotate the Stripe test restricted key in the Stripe Dashboard and load it from a secrets manager."
|
|
45032
|
+
},
|
|
44951
45033
|
{
|
|
44952
45034
|
name: "OpenAI API key",
|
|
44953
45035
|
regex: /\bsk-[A-Za-z0-9]{48}\b/,
|
|
@@ -45292,7 +45374,7 @@ class ScanSecretsPass {
|
|
|
45292
45374
|
const m = pattern.regex.exec(lineText);
|
|
45293
45375
|
if (!m)
|
|
45294
45376
|
continue;
|
|
45295
|
-
if ((pattern.name === "Stripe live restricted key" || pattern.name === "GitLab personal access token") && PLACEHOLDER_RE.test(m[0])) {
|
|
45377
|
+
if ((pattern.name === "Stripe live restricted key" || pattern.name === "Stripe test secret key" || pattern.name === "Stripe test restricted key" || pattern.name === "GitLab personal access token") && PLACEHOLDER_RE.test(m[0])) {
|
|
45296
45378
|
continue;
|
|
45297
45379
|
}
|
|
45298
45380
|
if (pattern.name === "PEM private key" && !pemHasInlineBody(lines, i2)) {
|
|
@@ -50396,6 +50478,27 @@ function longestLineLength(code) {
|
|
|
50396
50478
|
return max;
|
|
50397
50479
|
}
|
|
50398
50480
|
var MAX_ANALYZABLE_LINE_LENGTH = 1e4;
|
|
50481
|
+
function neutralizeOverlongLines(code) {
|
|
50482
|
+
if (longestLineLength(code) <= MAX_ANALYZABLE_LINE_LENGTH) {
|
|
50483
|
+
return { code, dropped: 0 };
|
|
50484
|
+
}
|
|
50485
|
+
let out2 = "";
|
|
50486
|
+
let start2 = 0;
|
|
50487
|
+
let dropped = 0;
|
|
50488
|
+
for (let i2 = 0;i2 <= code.length; i2++) {
|
|
50489
|
+
if (i2 !== code.length && code.charCodeAt(i2) !== 10)
|
|
50490
|
+
continue;
|
|
50491
|
+
if (i2 - start2 > MAX_ANALYZABLE_LINE_LENGTH)
|
|
50492
|
+
dropped++;
|
|
50493
|
+
else if (i2 > start2)
|
|
50494
|
+
out2 += code.slice(start2, i2);
|
|
50495
|
+
if (i2 < code.length)
|
|
50496
|
+
out2 += `
|
|
50497
|
+
`;
|
|
50498
|
+
start2 = i2 + 1;
|
|
50499
|
+
}
|
|
50500
|
+
return { code: out2, dropped };
|
|
50501
|
+
}
|
|
50399
50502
|
async function analyze(code, filePath, language, options = {}) {
|
|
50400
50503
|
if (!initialized) {
|
|
50401
50504
|
await initAnalyzer(options);
|
|
@@ -50414,6 +50517,15 @@ async function analyze(code, filePath, language, options = {}) {
|
|
|
50414
50517
|
parseGrammar = "tsx";
|
|
50415
50518
|
}
|
|
50416
50519
|
}
|
|
50520
|
+
const neutralized = neutralizeOverlongLines(code);
|
|
50521
|
+
if (neutralized.dropped > 0) {
|
|
50522
|
+
logger.warn("Neutralized over-long lines before analysis", {
|
|
50523
|
+
filePath,
|
|
50524
|
+
language,
|
|
50525
|
+
droppedLines: neutralized.dropped
|
|
50526
|
+
});
|
|
50527
|
+
code = neutralized.code;
|
|
50528
|
+
}
|
|
50417
50529
|
logger.debug("Analyzing file", { filePath, language, parseGrammar, codeLength: code.length });
|
|
50418
50530
|
const phaseTimingEnabled = globalThis.__circleIrPassTiming === true;
|
|
50419
50531
|
const phaseStart = () => phaseTimingEnabled ? Date.now() : 0;
|
|
@@ -51986,7 +52098,7 @@ var colors = {
|
|
|
51986
52098
|
};
|
|
51987
52099
|
|
|
51988
52100
|
// src/version.ts
|
|
51989
|
-
var version = "4.
|
|
52101
|
+
var version = "4.14.0";
|
|
51990
52102
|
|
|
51991
52103
|
// src/formatters.ts
|
|
51992
52104
|
var LIBRARY_API_SURFACE_TAG2 = "library-api-surface:caller-responsibility";
|
|
@@ -52447,6 +52559,21 @@ function formatSARIF(results, crossFileData, profileSummary) {
|
|
|
52447
52559
|
}
|
|
52448
52560
|
},
|
|
52449
52561
|
results: generateSarifResults(results, crossFileData),
|
|
52562
|
+
...crossFileData?.projectSizeBudgetExceeded ? {
|
|
52563
|
+
invocations: [
|
|
52564
|
+
{
|
|
52565
|
+
executionSuccessful: false,
|
|
52566
|
+
toolExecutionNotifications: [
|
|
52567
|
+
{
|
|
52568
|
+
level: "warning",
|
|
52569
|
+
message: {
|
|
52570
|
+
text: `Project size ceiling reached — analysed ${crossFileData.filesAnalysed ?? "?"} of ${crossFileData.filesTotal ?? "?"} files.`
|
|
52571
|
+
}
|
|
52572
|
+
}
|
|
52573
|
+
]
|
|
52574
|
+
}
|
|
52575
|
+
]
|
|
52576
|
+
} : {},
|
|
52450
52577
|
...profileSummary ? { properties: { projectProfile: profileSummary } } : {}
|
|
52451
52578
|
}
|
|
52452
52579
|
]
|
|
@@ -52538,8 +52665,8 @@ function generateSarifResults(results, crossFileData) {
|
|
|
52538
52665
|
|
|
52539
52666
|
// src/build-info.ts
|
|
52540
52667
|
var buildInfo = {
|
|
52541
|
-
gitSha: "
|
|
52542
|
-
builtAt: "2026-10-
|
|
52668
|
+
gitSha: "c44e104",
|
|
52669
|
+
builtAt: "2026-10-11T06:19:30.837Z"
|
|
52543
52670
|
};
|
|
52544
52671
|
|
|
52545
52672
|
// src/utils/args.ts
|
|
@@ -52781,6 +52908,38 @@ function spinner(text) {
|
|
|
52781
52908
|
}
|
|
52782
52909
|
|
|
52783
52910
|
// src/cli.ts
|
|
52911
|
+
function collapseWs(s) {
|
|
52912
|
+
return s.replace(/\s+/g, " ").trim();
|
|
52913
|
+
}
|
|
52914
|
+
function fileMatches(suppFile, findingFile, basePath) {
|
|
52915
|
+
const wanted = suppFile.replace(/^\.\//, "");
|
|
52916
|
+
const relativeFile = relative4(basePath, findingFile) || findingFile;
|
|
52917
|
+
return wanted === relativeFile || wanted === findingFile;
|
|
52918
|
+
}
|
|
52919
|
+
function findingMatchesSuppression(supp, finding, basePath, lineAt) {
|
|
52920
|
+
if (supp.pass !== finding.pass && supp.pass !== finding.alsoPass)
|
|
52921
|
+
return false;
|
|
52922
|
+
if (supp.file && !fileMatches(supp.file, finding.file, basePath))
|
|
52923
|
+
return false;
|
|
52924
|
+
const snippet = supp.snippet ? collapseWs(supp.snippet) : "";
|
|
52925
|
+
if (snippet) {
|
|
52926
|
+
const fromFile = lineAt?.(finding.file, finding.line);
|
|
52927
|
+
const text = fromFile !== undefined ? fromFile : finding.lineText;
|
|
52928
|
+
return text !== undefined && collapseWs(text).includes(snippet);
|
|
52929
|
+
}
|
|
52930
|
+
if (supp.line !== undefined && supp.line !== finding.line)
|
|
52931
|
+
return false;
|
|
52932
|
+
return true;
|
|
52933
|
+
}
|
|
52934
|
+
function staleSuppressions(suppressions, matched) {
|
|
52935
|
+
return suppressions.filter((_, i2) => !matched.has(i2));
|
|
52936
|
+
}
|
|
52937
|
+
function reportStaleSuppressions(suppressions, matched) {
|
|
52938
|
+
for (const supp of staleSuppressions(suppressions, matched)) {
|
|
52939
|
+
const where = [supp.file, supp.snippet ? "snippet" : "", supp.line !== undefined && !supp.snippet ? `line ${supp.line}` : ""].filter(Boolean).join(" ");
|
|
52940
|
+
console.error(colors.dim(`Suppression for ${supp.pass}${where ? ` (${where})` : ""} matched no finding`));
|
|
52941
|
+
}
|
|
52942
|
+
}
|
|
52784
52943
|
function loadConfig(profilePath) {
|
|
52785
52944
|
const configPath = profilePath || "cognium.config.json";
|
|
52786
52945
|
if (!existsSync(configPath)) {
|
|
@@ -52833,22 +52992,15 @@ function convertConfigToPassOptions(config) {
|
|
|
52833
52992
|
}
|
|
52834
52993
|
return { passOptions, disabledPasses };
|
|
52835
52994
|
}
|
|
52836
|
-
function applySuppressionsToResults(results, suppressions, basePath) {
|
|
52995
|
+
function applySuppressionsToResults(results, suppressions, basePath, match) {
|
|
52837
52996
|
if (suppressions.length === 0)
|
|
52838
52997
|
return results;
|
|
52839
52998
|
return results.map((result) => {
|
|
52840
|
-
const relativeFile = relative4(basePath, result.file) || result.file;
|
|
52841
52999
|
const filteredVulns = result.vulnerabilities.filter((vuln) => {
|
|
52842
|
-
for (
|
|
52843
|
-
if (
|
|
52844
|
-
continue;
|
|
52845
|
-
if (supp.file) {
|
|
52846
|
-
const suppFile = supp.file.replace(/^\.\//, "");
|
|
52847
|
-
if (suppFile !== relativeFile && suppFile !== result.file)
|
|
52848
|
-
continue;
|
|
52849
|
-
}
|
|
52850
|
-
if (supp.line !== undefined && supp.line !== vuln.line)
|
|
53000
|
+
for (let i2 = 0;i2 < suppressions.length; i2++) {
|
|
53001
|
+
if (!findingMatchesSuppression(suppressions[i2], { pass: vuln.type, file: result.file, line: vuln.line }, basePath, match?.lineAt))
|
|
52851
53002
|
continue;
|
|
53003
|
+
match?.matched?.add(i2);
|
|
52852
53004
|
return false;
|
|
52853
53005
|
}
|
|
52854
53006
|
return true;
|
|
@@ -52856,22 +53008,20 @@ function applySuppressionsToResults(results, suppressions, basePath) {
|
|
|
52856
53008
|
return { ...result, vulnerabilities: filteredVulns };
|
|
52857
53009
|
});
|
|
52858
53010
|
}
|
|
52859
|
-
function applySuppressionsToTaintPaths(paths, suppressions, basePath) {
|
|
53011
|
+
function applySuppressionsToTaintPaths(paths, suppressions, basePath, match) {
|
|
52860
53012
|
if (suppressions.length === 0)
|
|
52861
53013
|
return paths;
|
|
52862
53014
|
return paths.filter((path) => {
|
|
52863
|
-
for (
|
|
52864
|
-
|
|
52865
|
-
|
|
52866
|
-
|
|
52867
|
-
|
|
52868
|
-
|
|
52869
|
-
|
|
52870
|
-
|
|
52871
|
-
|
|
52872
|
-
|
|
52873
|
-
if (supp.line !== undefined && supp.line !== path.sink.line)
|
|
52874
|
-
continue;
|
|
53015
|
+
for (let i2 = 0;i2 < suppressions.length; i2++) {
|
|
53016
|
+
if (!findingMatchesSuppression(suppressions[i2], {
|
|
53017
|
+
pass: path.sink.type,
|
|
53018
|
+
alsoPass: `cross-file-${path.sink.type}`,
|
|
53019
|
+
file: path.sink.file,
|
|
53020
|
+
line: path.sink.line,
|
|
53021
|
+
lineText: path.sink.code
|
|
53022
|
+
}, basePath, match?.lineAt))
|
|
53023
|
+
continue;
|
|
53024
|
+
match?.matched?.add(i2);
|
|
52875
53025
|
return false;
|
|
52876
53026
|
}
|
|
52877
53027
|
return true;
|
|
@@ -53394,14 +53544,35 @@ async function runScan(targetPath, options) {
|
|
|
53394
53544
|
}
|
|
53395
53545
|
if (suppressions.length > 0) {
|
|
53396
53546
|
const beforeCount = results.reduce((sum, r) => sum + r.vulnerabilities.length, 0) + (crossFileData?.taintPaths.length ?? 0);
|
|
53397
|
-
|
|
53547
|
+
const matched = new Set;
|
|
53548
|
+
const needsLines = suppressions.some((s) => s.snippet && collapseWs(s.snippet).length > 0);
|
|
53549
|
+
const lineCache = new Map;
|
|
53550
|
+
const lineAt = needsLines ? (file, line) => {
|
|
53551
|
+
let lines = lineCache.get(file);
|
|
53552
|
+
if (lines === undefined) {
|
|
53553
|
+
try {
|
|
53554
|
+
lines = readFileSync(file, "utf-8").split(`
|
|
53555
|
+
`);
|
|
53556
|
+
} catch {
|
|
53557
|
+
lines = null;
|
|
53558
|
+
}
|
|
53559
|
+
lineCache.set(file, lines);
|
|
53560
|
+
}
|
|
53561
|
+
if (!lines || line < 1)
|
|
53562
|
+
return;
|
|
53563
|
+
return lines[line - 1];
|
|
53564
|
+
} : undefined;
|
|
53565
|
+
const match = { lineAt, matched };
|
|
53566
|
+
results = applySuppressionsToResults(results, suppressions, process.cwd(), match);
|
|
53398
53567
|
if (crossFileData) {
|
|
53399
|
-
crossFileData.taintPaths = applySuppressionsToTaintPaths(crossFileData.taintPaths, suppressions, process.cwd());
|
|
53568
|
+
crossFileData.taintPaths = applySuppressionsToTaintPaths(crossFileData.taintPaths, suppressions, process.cwd(), match);
|
|
53400
53569
|
}
|
|
53401
53570
|
const afterCount = results.reduce((sum, r) => sum + r.vulnerabilities.length, 0) + (crossFileData?.taintPaths.length ?? 0);
|
|
53402
53571
|
if (!options.quiet && beforeCount !== afterCount) {
|
|
53403
53572
|
console.error(colors.dim(`Suppressed ${beforeCount - afterCount} finding(s) via config`));
|
|
53404
53573
|
}
|
|
53574
|
+
if (!options.quiet)
|
|
53575
|
+
reportStaleSuppressions(suppressions, matched);
|
|
53405
53576
|
}
|
|
53406
53577
|
const severityOrder = ["low", "medium", "high", "critical"];
|
|
53407
53578
|
if (options.severity) {
|
|
@@ -54034,7 +54205,9 @@ main().catch((error) => {
|
|
|
54034
54205
|
process.exit(2);
|
|
54035
54206
|
});
|
|
54036
54207
|
export {
|
|
54208
|
+
staleSuppressions,
|
|
54037
54209
|
resolveScanLanguage,
|
|
54210
|
+
reportStaleSuppressions,
|
|
54038
54211
|
matchesGlob,
|
|
54039
54212
|
loadConfig,
|
|
54040
54213
|
isTestFile2 as isTestFile,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cognium-dev",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.14.0",
|
|
4
4
|
"description": "Static Application Security Testing CLI for detecting security vulnerabilities via taint tracking",
|
|
5
5
|
"bin": {
|
|
6
6
|
"cognium-dev": "dist/cli.js"
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
},
|
|
66
66
|
"dependencies": {
|
|
67
67
|
"@cognium/project-profile-detect": "1.1.2",
|
|
68
|
-
"circle-ir": "4.
|
|
68
|
+
"circle-ir": "4.14.0"
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
71
|
"@types/node": "^26.6.4",
|