cognium-dev 3.119.0 → 3.123.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 +766 -17
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -11692,7 +11692,8 @@ var PYTHON_TAINTED_PATTERNS = [
|
|
|
11692
11692
|
{ pattern: /\brequest\.META\b/, sourceType: "http_header" },
|
|
11693
11693
|
{ pattern: /\brequest\.FILES\b/, sourceType: "file_input" },
|
|
11694
11694
|
{ pattern: /\brequest\.query_params\b/, sourceType: "http_param" },
|
|
11695
|
-
{ pattern: /\brequest\.path_params\b/, sourceType: "http_param" }
|
|
11695
|
+
{ pattern: /\brequest\.path_params\b/, sourceType: "http_param" },
|
|
11696
|
+
{ pattern: /\binput\s*\(/, sourceType: "io_input" }
|
|
11696
11697
|
];
|
|
11697
11698
|
function analyzeTaint(calls, types, config = getDefaultConfig(), typeHierarchy, language, code) {
|
|
11698
11699
|
const sourceLines = code !== undefined ? code.split(`
|
|
@@ -22082,6 +22083,126 @@ function truncate(s, maxLen) {
|
|
|
22082
22083
|
return s.length > maxLen ? s.slice(0, maxLen) + "..." : s;
|
|
22083
22084
|
}
|
|
22084
22085
|
|
|
22086
|
+
// ../circle-ir/dist/analysis/html/vue-template-xss-pass.js
|
|
22087
|
+
var DANGEROUS_BINDINGS = new Set([
|
|
22088
|
+
"v-html",
|
|
22089
|
+
"v-bind:innerhtml",
|
|
22090
|
+
":innerhtml",
|
|
22091
|
+
"v-bind:outerhtml",
|
|
22092
|
+
":outerhtml"
|
|
22093
|
+
]);
|
|
22094
|
+
var ID_RE = /\b([A-Za-z_$][A-Za-z0-9_$]*)\b/g;
|
|
22095
|
+
var RESERVED = new Set([
|
|
22096
|
+
"true",
|
|
22097
|
+
"false",
|
|
22098
|
+
"null",
|
|
22099
|
+
"undefined",
|
|
22100
|
+
"this",
|
|
22101
|
+
"new",
|
|
22102
|
+
"typeof",
|
|
22103
|
+
"void",
|
|
22104
|
+
"delete",
|
|
22105
|
+
"instanceof",
|
|
22106
|
+
"in",
|
|
22107
|
+
"of",
|
|
22108
|
+
"Math",
|
|
22109
|
+
"Number",
|
|
22110
|
+
"String",
|
|
22111
|
+
"Boolean",
|
|
22112
|
+
"Array",
|
|
22113
|
+
"Object",
|
|
22114
|
+
"JSON"
|
|
22115
|
+
]);
|
|
22116
|
+
function runVueTemplateXssChecks(rootNode, filePath, scriptResults) {
|
|
22117
|
+
const taintedNames = collectTaintedNames(scriptResults);
|
|
22118
|
+
if (taintedNames.size === 0)
|
|
22119
|
+
return [];
|
|
22120
|
+
const findings = [];
|
|
22121
|
+
const stack = [rootNode];
|
|
22122
|
+
while (stack.length > 0) {
|
|
22123
|
+
const node = stack.pop();
|
|
22124
|
+
if (node.type === "start_tag" || node.type === "self_closing_tag") {
|
|
22125
|
+
checkElementBindings(node, filePath, taintedNames, findings);
|
|
22126
|
+
}
|
|
22127
|
+
for (let i2 = node.childCount - 1;i2 >= 0; i2--) {
|
|
22128
|
+
const c = node.child(i2);
|
|
22129
|
+
if (c)
|
|
22130
|
+
stack.push(c);
|
|
22131
|
+
}
|
|
22132
|
+
}
|
|
22133
|
+
return findings;
|
|
22134
|
+
}
|
|
22135
|
+
function checkElementBindings(tag, filePath, tainted, out2) {
|
|
22136
|
+
for (let i2 = 0;i2 < tag.childCount; i2++) {
|
|
22137
|
+
const attr = tag.child(i2);
|
|
22138
|
+
if (!attr || attr.type !== "attribute")
|
|
22139
|
+
continue;
|
|
22140
|
+
const nameNode = findChildByType2(attr, "attribute_name");
|
|
22141
|
+
if (!nameNode)
|
|
22142
|
+
continue;
|
|
22143
|
+
const lcName = nameNode.text.toLowerCase();
|
|
22144
|
+
if (!DANGEROUS_BINDINGS.has(lcName))
|
|
22145
|
+
continue;
|
|
22146
|
+
const valueNode = findChildByType2(attr, "quoted_attribute_value") ?? findChildByType2(attr, "attribute_value");
|
|
22147
|
+
if (!valueNode)
|
|
22148
|
+
continue;
|
|
22149
|
+
const rhs = stripQuotes2(valueNode.text);
|
|
22150
|
+
if (!rhs.trim())
|
|
22151
|
+
continue;
|
|
22152
|
+
const matched = matchTaint(rhs, tainted);
|
|
22153
|
+
if (!matched)
|
|
22154
|
+
continue;
|
|
22155
|
+
const line = nameNode.startPosition.row + 1;
|
|
22156
|
+
out2.push({
|
|
22157
|
+
id: `vue-template-xss-${filePath}-${line}-${lcName}`,
|
|
22158
|
+
pass: "vue-template-xss",
|
|
22159
|
+
category: "security",
|
|
22160
|
+
rule_id: "vue-template-xss",
|
|
22161
|
+
cwe: "CWE-79",
|
|
22162
|
+
severity: "high",
|
|
22163
|
+
level: "error",
|
|
22164
|
+
message: `Vue template attribute "${nameNode.text}" binds tainted identifier "${matched}" — writes raw HTML (XSS risk).`,
|
|
22165
|
+
file: filePath,
|
|
22166
|
+
line,
|
|
22167
|
+
snippet: `${nameNode.text}="${rhs}"`
|
|
22168
|
+
});
|
|
22169
|
+
}
|
|
22170
|
+
}
|
|
22171
|
+
function matchTaint(rhs, tainted) {
|
|
22172
|
+
ID_RE.lastIndex = 0;
|
|
22173
|
+
let m;
|
|
22174
|
+
while ((m = ID_RE.exec(rhs)) !== null) {
|
|
22175
|
+
const id = m[1];
|
|
22176
|
+
if (RESERVED.has(id))
|
|
22177
|
+
continue;
|
|
22178
|
+
if (tainted.has(id))
|
|
22179
|
+
return id;
|
|
22180
|
+
}
|
|
22181
|
+
return;
|
|
22182
|
+
}
|
|
22183
|
+
function collectTaintedNames(blocks) {
|
|
22184
|
+
const names = new Set;
|
|
22185
|
+
for (const { ir } of blocks) {
|
|
22186
|
+
const sourceLines = new Set;
|
|
22187
|
+
for (const source of ir.taint.sources) {
|
|
22188
|
+
sourceLines.add(source.line);
|
|
22189
|
+
if (source.variable)
|
|
22190
|
+
names.add(source.variable);
|
|
22191
|
+
}
|
|
22192
|
+
for (const def of ir.dfg.defs) {
|
|
22193
|
+
if (sourceLines.has(def.line) && def.variable)
|
|
22194
|
+
names.add(def.variable);
|
|
22195
|
+
}
|
|
22196
|
+
for (const flow of ir.taint.flows ?? []) {
|
|
22197
|
+
for (const step of flow.path ?? []) {
|
|
22198
|
+
if (step.variable)
|
|
22199
|
+
names.add(step.variable);
|
|
22200
|
+
}
|
|
22201
|
+
}
|
|
22202
|
+
}
|
|
22203
|
+
return names;
|
|
22204
|
+
}
|
|
22205
|
+
|
|
22085
22206
|
// ../circle-ir/dist/analysis/html/html-merge.js
|
|
22086
22207
|
function mergeHtmlResults(htmlMeta, scriptResults, attributeFindings) {
|
|
22087
22208
|
const allTypes = [];
|
|
@@ -22391,7 +22512,8 @@ var PYTHON_TAINTED_PATTERNS2 = [
|
|
|
22391
22512
|
{ pattern: /\bget_form_parameter\s*\(/, type: "http_body" },
|
|
22392
22513
|
{ pattern: /\bget_query_parameter\s*\(/, type: "http_param" },
|
|
22393
22514
|
{ pattern: /\bget_header_value\s*\(/, type: "http_header" },
|
|
22394
|
-
{ pattern: /\bget_cookie_value\s*\(/, type: "http_cookie" }
|
|
22515
|
+
{ pattern: /\bget_cookie_value\s*\(/, type: "http_cookie" },
|
|
22516
|
+
{ pattern: /\binput\s*\(/, type: "io_input" }
|
|
22395
22517
|
];
|
|
22396
22518
|
|
|
22397
22519
|
class LanguageSourcesPass {
|
|
@@ -22451,6 +22573,19 @@ class LanguageSourcesPass {
|
|
|
22451
22573
|
});
|
|
22452
22574
|
}
|
|
22453
22575
|
}
|
|
22576
|
+
for (const r of findPythonReflectionInvocationSinks(code, pyTaintedVars)) {
|
|
22577
|
+
const alreadyExists = additionalSinks.some((s) => s.line === r.sinkLine && s.type === "code_injection" && s.method === r.method);
|
|
22578
|
+
if (!alreadyExists) {
|
|
22579
|
+
additionalSinks.push({
|
|
22580
|
+
type: "code_injection",
|
|
22581
|
+
cwe: "CWE-94",
|
|
22582
|
+
line: r.sinkLine,
|
|
22583
|
+
location: `reflection invocation (getattr result called) with tainted attribute name at line ${r.sinkLine}`,
|
|
22584
|
+
method: r.method,
|
|
22585
|
+
confidence: 0.85
|
|
22586
|
+
});
|
|
22587
|
+
}
|
|
22588
|
+
}
|
|
22454
22589
|
}
|
|
22455
22590
|
const jsTaintedVars = buildJavaScriptTaintedVars(code, language);
|
|
22456
22591
|
if (language === "bash") {
|
|
@@ -22469,10 +22604,24 @@ class LanguageSourcesPass {
|
|
|
22469
22604
|
if (language === "python") {
|
|
22470
22605
|
additionalSanitizers.push(...findPythonNetlocAllowlistGuardSanitizers(code));
|
|
22471
22606
|
additionalSanitizers.push(...findPythonRangeCheckGuardSanitizers(code));
|
|
22607
|
+
const pyMisconfigFindings = findPythonPatternFindings(code, graph.ir.meta.file);
|
|
22608
|
+
for (const finding of pyMisconfigFindings) {
|
|
22609
|
+
ctx.addFinding(finding);
|
|
22610
|
+
}
|
|
22472
22611
|
}
|
|
22473
22612
|
if (language === "rust") {
|
|
22474
22613
|
additionalSanitizers.push(...findRustSetAllowlistGuardSanitizers(code));
|
|
22475
22614
|
additionalSanitizers.push(...findRustCanonicalizeGuardSanitizers(code));
|
|
22615
|
+
const rustMisconfigFindings = findRustPatternFindings(code, graph.ir.meta.file);
|
|
22616
|
+
for (const finding of rustMisconfigFindings) {
|
|
22617
|
+
ctx.addFinding(finding);
|
|
22618
|
+
}
|
|
22619
|
+
}
|
|
22620
|
+
if (language === "python" || language === "javascript" || language === "typescript" || language === "go") {
|
|
22621
|
+
const exfilFindings = findExternalSecretExfiltrationFindings(code, graph.ir.meta.file, language);
|
|
22622
|
+
for (const finding of exfilFindings) {
|
|
22623
|
+
ctx.addFinding(finding);
|
|
22624
|
+
}
|
|
22476
22625
|
}
|
|
22477
22626
|
attachSourceLineCode(additionalSources, additionalSinks, code);
|
|
22478
22627
|
return { additionalSources, additionalSinks, additionalSanitizers, pyTaintedVars, pySanitizedVars, jsTaintedVars };
|
|
@@ -23099,6 +23248,51 @@ function findPythonReturnXSSSinks(sourceCode, taintedVars) {
|
|
|
23099
23248
|
}
|
|
23100
23249
|
return sinks;
|
|
23101
23250
|
}
|
|
23251
|
+
function findPythonReflectionInvocationSinks(sourceCode, taintedVars) {
|
|
23252
|
+
if (taintedVars.size === 0)
|
|
23253
|
+
return [];
|
|
23254
|
+
const sinks = [];
|
|
23255
|
+
const lines = sourceCode.split(`
|
|
23256
|
+
`);
|
|
23257
|
+
const directRe = /\bgetattr\s*\(\s*[^,()]+\s*,\s*([A-Za-z_][\w]*)\s*\)\s*\(/;
|
|
23258
|
+
const bindRe = /^\s*([A-Za-z_][\w]*)\s*=\s*getattr\s*\(\s*[^,()]+\s*,\s*([A-Za-z_][\w]*)\s*\)\s*$/;
|
|
23259
|
+
const aliases = [];
|
|
23260
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
23261
|
+
const line = lines[i2];
|
|
23262
|
+
if (line.trimStart().startsWith("#"))
|
|
23263
|
+
continue;
|
|
23264
|
+
const dm = line.match(directRe);
|
|
23265
|
+
if (dm && taintedVars.has(dm[1])) {
|
|
23266
|
+
sinks.push({ sinkLine: i2 + 1, method: "getattr" });
|
|
23267
|
+
continue;
|
|
23268
|
+
}
|
|
23269
|
+
const bm = line.match(bindRe);
|
|
23270
|
+
if (bm && taintedVars.has(bm[2])) {
|
|
23271
|
+
aliases.push({ name: bm[1], bindLine: i2 + 1 });
|
|
23272
|
+
}
|
|
23273
|
+
}
|
|
23274
|
+
if (aliases.length === 0)
|
|
23275
|
+
return sinks;
|
|
23276
|
+
const firedBindLines = new Set;
|
|
23277
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
23278
|
+
const line = lines[i2];
|
|
23279
|
+
if (line.trimStart().startsWith("#"))
|
|
23280
|
+
continue;
|
|
23281
|
+
const lineNum = i2 + 1;
|
|
23282
|
+
for (const a of aliases) {
|
|
23283
|
+
if (lineNum <= a.bindLine)
|
|
23284
|
+
continue;
|
|
23285
|
+
if (firedBindLines.has(a.bindLine))
|
|
23286
|
+
continue;
|
|
23287
|
+
const invokeRe = new RegExp(`(?<![\\w.])${a.name}\\s*\\(`);
|
|
23288
|
+
if (invokeRe.test(line)) {
|
|
23289
|
+
sinks.push({ sinkLine: a.bindLine, method: "getattr" });
|
|
23290
|
+
firedBindLines.add(a.bindLine);
|
|
23291
|
+
}
|
|
23292
|
+
}
|
|
23293
|
+
}
|
|
23294
|
+
return sinks;
|
|
23295
|
+
}
|
|
23102
23296
|
function findJavaScriptDOMSinks(sourceCode, language) {
|
|
23103
23297
|
if (!["javascript", "typescript"].includes(language))
|
|
23104
23298
|
return [];
|
|
@@ -23309,10 +23503,44 @@ function findBashTaintSources(sourceCode, dfg) {
|
|
|
23309
23503
|
return sources;
|
|
23310
23504
|
}
|
|
23311
23505
|
var BASH_CREDENTIAL_PATTERN = /^(.*?)(password|passwd|secret|api_?key|token|auth_token|private_key|access_key)\s*=\s*["']?([^"'\s$][^"'\s]*)["']?\s*$/i;
|
|
23506
|
+
var BASH_CHECKSUM_VERIFY_PATTERN = /\b(?:sha(?:1|224|256|384|512)sum|md5sum|cksum|b2sum)\s+(?:-c\b|--check\b)/;
|
|
23507
|
+
function collectChecksumVerifiedTmpPaths(lines) {
|
|
23508
|
+
const verified = new Set;
|
|
23509
|
+
for (const line of lines) {
|
|
23510
|
+
if (!BASH_CHECKSUM_VERIFY_PATTERN.test(line))
|
|
23511
|
+
continue;
|
|
23512
|
+
const matches = line.match(/\/tmp\/[^\s"'$|`]+/g);
|
|
23513
|
+
if (!matches)
|
|
23514
|
+
continue;
|
|
23515
|
+
for (const p of matches)
|
|
23516
|
+
verified.add(p);
|
|
23517
|
+
}
|
|
23518
|
+
return verified;
|
|
23519
|
+
}
|
|
23520
|
+
var BASH_ARCHIVE_EXT_PATTERN = /\.(?:tgz|tar\.gz|tar\.bz2|tar\.xz|tar|tbz2|txz|zip|gz|bz2|xz|7z)$/i;
|
|
23521
|
+
function isArchiveOutputContext(line, tmpRel) {
|
|
23522
|
+
if (!BASH_ARCHIVE_EXT_PATTERN.test(tmpRel))
|
|
23523
|
+
return false;
|
|
23524
|
+
if (/\btar\b/.test(line) && /(?:^|\s)-?[A-Za-z]*c[A-Za-z]*\b/.test(line))
|
|
23525
|
+
return true;
|
|
23526
|
+
if (/\bzip\b/.test(line) && !/\bunzip\b/.test(line))
|
|
23527
|
+
return true;
|
|
23528
|
+
if (/\bgzip\b/.test(line) && /(?:-c\b|--stdout\b|>)/.test(line))
|
|
23529
|
+
return true;
|
|
23530
|
+
if (/\bbzip2\b/.test(line) && /(?:-c\b|--stdout\b|>)/.test(line))
|
|
23531
|
+
return true;
|
|
23532
|
+
if (/\bxz\b/.test(line) && /(?:-c\b|--stdout\b|>)/.test(line))
|
|
23533
|
+
return true;
|
|
23534
|
+
if (/\b7z\s+a\b/.test(line))
|
|
23535
|
+
return true;
|
|
23536
|
+
return false;
|
|
23537
|
+
}
|
|
23312
23538
|
function findBashPatternFindings(sourceCode, file) {
|
|
23313
23539
|
const findings = [];
|
|
23314
23540
|
const lines = sourceCode.split(`
|
|
23315
23541
|
`);
|
|
23542
|
+
const checksumVerifiedTmpPaths = collectChecksumVerifiedTmpPaths(lines);
|
|
23543
|
+
const scriptHasVerifier = hasIntegrityVerifierAnywhere(lines);
|
|
23316
23544
|
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
23317
23545
|
const line = lines[i2];
|
|
23318
23546
|
const trimmed = line.trim();
|
|
@@ -23355,19 +23583,25 @@ function findBashPatternFindings(sourceCode, file) {
|
|
|
23355
23583
|
}
|
|
23356
23584
|
const tmpMatch = trimmed.match(/\/tmp\/([^\s"'$]+)/);
|
|
23357
23585
|
if (tmpMatch && !/mktemp/.test(trimmed)) {
|
|
23358
|
-
|
|
23359
|
-
|
|
23360
|
-
|
|
23361
|
-
|
|
23362
|
-
|
|
23363
|
-
|
|
23364
|
-
|
|
23365
|
-
|
|
23366
|
-
|
|
23367
|
-
|
|
23368
|
-
|
|
23369
|
-
|
|
23370
|
-
|
|
23586
|
+
const tmpRel = tmpMatch[1];
|
|
23587
|
+
const tmpPath = `/tmp/${tmpRel}`;
|
|
23588
|
+
const isChecksumVerified = checksumVerifiedTmpPaths.has(tmpPath);
|
|
23589
|
+
const isArchiveOutput = isArchiveOutputContext(trimmed, tmpRel);
|
|
23590
|
+
if (!isChecksumVerified && !isArchiveOutput) {
|
|
23591
|
+
findings.push({
|
|
23592
|
+
id: `predictable-temp-file-${file}-${lineNumber}`,
|
|
23593
|
+
pass: "language-sources",
|
|
23594
|
+
category: "security",
|
|
23595
|
+
rule_id: "predictable-temp-file",
|
|
23596
|
+
cwe: "CWE-377",
|
|
23597
|
+
severity: "medium",
|
|
23598
|
+
level: "warning",
|
|
23599
|
+
message: `Predictable temp file: /tmp/${tmpRel}. Use mktemp instead`,
|
|
23600
|
+
file,
|
|
23601
|
+
line: lineNumber,
|
|
23602
|
+
snippet: trimmed.substring(0, 80)
|
|
23603
|
+
});
|
|
23604
|
+
}
|
|
23371
23605
|
}
|
|
23372
23606
|
if (/\bchmod\b/.test(trimmed) && /\b(777|666)\b/.test(trimmed)) {
|
|
23373
23607
|
const mode = trimmed.match(/\b(777|666)\b/)[1];
|
|
@@ -23400,9 +23634,79 @@ function findBashPatternFindings(sourceCode, file) {
|
|
|
23400
23634
|
snippet: trimmed.substring(0, 80)
|
|
23401
23635
|
});
|
|
23402
23636
|
}
|
|
23637
|
+
if (!scriptHasVerifier) {
|
|
23638
|
+
const installer = matchUnverifiedPackageInstall(trimmed);
|
|
23639
|
+
if (installer) {
|
|
23640
|
+
findings.push({
|
|
23641
|
+
id: `unverified-package-install-${file}-${lineNumber}`,
|
|
23642
|
+
pass: "language-sources",
|
|
23643
|
+
category: "security",
|
|
23644
|
+
rule_id: "unverified-package-install",
|
|
23645
|
+
cwe: "CWE-494",
|
|
23646
|
+
severity: "high",
|
|
23647
|
+
level: "error",
|
|
23648
|
+
message: `Unverified package install via ${installer}: package contents are not integrity-checked (no gpg/sha256sum verify in script)`,
|
|
23649
|
+
file,
|
|
23650
|
+
line: lineNumber,
|
|
23651
|
+
snippet: trimmed.substring(0, 80)
|
|
23652
|
+
});
|
|
23653
|
+
}
|
|
23654
|
+
}
|
|
23655
|
+
const weakHashAlg = matchBashWeakHashCommand(trimmed);
|
|
23656
|
+
if (weakHashAlg) {
|
|
23657
|
+
findings.push({
|
|
23658
|
+
id: `weak-hash-${file}-${lineNumber}`,
|
|
23659
|
+
pass: "language-sources",
|
|
23660
|
+
category: "security",
|
|
23661
|
+
rule_id: "weak-hash",
|
|
23662
|
+
cwe: "CWE-328",
|
|
23663
|
+
severity: "medium",
|
|
23664
|
+
level: "warning",
|
|
23665
|
+
message: `Weak hash algorithm: ${weakHashAlg} is cryptographically broken. Use sha256sum or sha512sum`,
|
|
23666
|
+
file,
|
|
23667
|
+
line: lineNumber,
|
|
23668
|
+
snippet: trimmed.substring(0, 80)
|
|
23669
|
+
});
|
|
23670
|
+
}
|
|
23403
23671
|
}
|
|
23404
23672
|
return findings;
|
|
23405
23673
|
}
|
|
23674
|
+
function matchBashWeakHashCommand(line) {
|
|
23675
|
+
const re = /(?:^|[|;]|&&|\|\|)\s*(md5sum|sha1sum|md5|sha1)\b(?!\s*=)/;
|
|
23676
|
+
const m = line.match(re);
|
|
23677
|
+
if (!m)
|
|
23678
|
+
return null;
|
|
23679
|
+
return m[1];
|
|
23680
|
+
}
|
|
23681
|
+
function matchUnverifiedPackageInstall(line) {
|
|
23682
|
+
if (/\bdpkg\b/.test(line) && /(?:^|\s)(?:-[a-zA-Z]*[iIU][a-zA-Z]*|--install)\b/.test(line)) {
|
|
23683
|
+
return "dpkg";
|
|
23684
|
+
}
|
|
23685
|
+
if (/\brpm\b/.test(line) && /(?:^|\s)(?:-[a-zA-Z]*[iU][a-zA-Z]*|--install|--upgrade)\b/.test(line) && !/--(?:verify|checksig|erase|query)\b/.test(line)) {
|
|
23686
|
+
return "rpm";
|
|
23687
|
+
}
|
|
23688
|
+
const aptMatch = line.match(/\b(apt-get|apt|aptitude)\s+install\b/);
|
|
23689
|
+
if (aptMatch && /\.deb\b/.test(line)) {
|
|
23690
|
+
return aptMatch[1];
|
|
23691
|
+
}
|
|
23692
|
+
const yumMatch = line.match(/\b(yum|dnf|zypper)\s+install\b/);
|
|
23693
|
+
if (yumMatch && /\.rpm\b/.test(line)) {
|
|
23694
|
+
return yumMatch[1];
|
|
23695
|
+
}
|
|
23696
|
+
return null;
|
|
23697
|
+
}
|
|
23698
|
+
function hasIntegrityVerifierAnywhere(lines) {
|
|
23699
|
+
const sigRe = /\b(?:gpg(?:v|2)?|gpg)\s+(?:[^|]*\s)?--verify\b/;
|
|
23700
|
+
const rpmSigRe = /\brpm\s+(?:[^|]*\s)?--checksig\b/;
|
|
23701
|
+
const dpkgSigRe = /\bdpkg\s+(?:[^|]*\s)?--verify\b/;
|
|
23702
|
+
const sumRe = /\b(?:sha(?:1|224|256|384|512)sum|md5sum|cksum|b2sum)\s+(?:[^|]*\s)?(?:-c|--check)\b/;
|
|
23703
|
+
for (const line of lines) {
|
|
23704
|
+
if (sigRe.test(line) || rpmSigRe.test(line) || dpkgSigRe.test(line) || sumRe.test(line)) {
|
|
23705
|
+
return true;
|
|
23706
|
+
}
|
|
23707
|
+
}
|
|
23708
|
+
return false;
|
|
23709
|
+
}
|
|
23406
23710
|
function findBashRegexAllowlistSanitizers(code) {
|
|
23407
23711
|
const sanitizers = [];
|
|
23408
23712
|
const lines = code.split(`
|
|
@@ -23808,6 +24112,420 @@ function findRustCanonicalizeGuardSanitizers(code) {
|
|
|
23808
24112
|
}
|
|
23809
24113
|
return sanitizers;
|
|
23810
24114
|
}
|
|
24115
|
+
function collectEnvSecretVars(lines, language) {
|
|
24116
|
+
const out2 = new Map;
|
|
24117
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
24118
|
+
const line = lines[i2];
|
|
24119
|
+
if (language === "python") {
|
|
24120
|
+
const m = line.match(/^\s*(\w+)\s*=\s*os\.(?:environ\s*[\[.]|getenv\b)/);
|
|
24121
|
+
if (m)
|
|
24122
|
+
out2.set(m[1], i2 + 1);
|
|
24123
|
+
} else if (language === "javascript" || language === "typescript") {
|
|
24124
|
+
const m = line.match(/(?:^|\s|;)(?:const|let|var)\s+(\w+)\s*=\s*process\.env\b/);
|
|
24125
|
+
if (m)
|
|
24126
|
+
out2.set(m[1], i2 + 1);
|
|
24127
|
+
} else if (language === "go") {
|
|
24128
|
+
const m = line.match(/^\s*(\w+)\s*:?=\s*os\.Getenv\s*\(/);
|
|
24129
|
+
if (m)
|
|
24130
|
+
out2.set(m[1], i2 + 1);
|
|
24131
|
+
}
|
|
24132
|
+
}
|
|
24133
|
+
return out2;
|
|
24134
|
+
}
|
|
24135
|
+
function isExternalHostUrl(url) {
|
|
24136
|
+
const lower = url.toLowerCase();
|
|
24137
|
+
if (!/^https?:\/\//.test(lower))
|
|
24138
|
+
return false;
|
|
24139
|
+
const host = lower.replace(/^https?:\/\//, "").split(/[/?#:]/)[0];
|
|
24140
|
+
if (!host)
|
|
24141
|
+
return false;
|
|
24142
|
+
if (host === "localhost" || host === "127.0.0.1" || host === "0.0.0.0" || host === "::1")
|
|
24143
|
+
return false;
|
|
24144
|
+
if (/^10\./.test(host))
|
|
24145
|
+
return false;
|
|
24146
|
+
if (/^192\.168\./.test(host))
|
|
24147
|
+
return false;
|
|
24148
|
+
if (/^172\.(1[6-9]|2\d|3[01])\./.test(host))
|
|
24149
|
+
return false;
|
|
24150
|
+
if (host.includes(".internal.") || host.endsWith(".internal") || host.startsWith("internal."))
|
|
24151
|
+
return false;
|
|
24152
|
+
if (host.endsWith(".local") || host.endsWith(".lan") || host.endsWith(".corp"))
|
|
24153
|
+
return false;
|
|
24154
|
+
if (!host.includes("."))
|
|
24155
|
+
return false;
|
|
24156
|
+
return true;
|
|
24157
|
+
}
|
|
24158
|
+
function findBalancedCallEnd(code, start2) {
|
|
24159
|
+
let depth = 1;
|
|
24160
|
+
let i2 = start2;
|
|
24161
|
+
let inStr = null;
|
|
24162
|
+
while (i2 < code.length) {
|
|
24163
|
+
const ch = code[i2];
|
|
24164
|
+
if (inStr) {
|
|
24165
|
+
if (ch === "\\") {
|
|
24166
|
+
i2 += 2;
|
|
24167
|
+
continue;
|
|
24168
|
+
}
|
|
24169
|
+
if (ch === inStr)
|
|
24170
|
+
inStr = null;
|
|
24171
|
+
} else {
|
|
24172
|
+
if (ch === '"' || ch === "'" || ch === "`")
|
|
24173
|
+
inStr = ch;
|
|
24174
|
+
else if (ch === "(")
|
|
24175
|
+
depth++;
|
|
24176
|
+
else if (ch === ")") {
|
|
24177
|
+
depth--;
|
|
24178
|
+
if (depth === 0)
|
|
24179
|
+
return i2;
|
|
24180
|
+
}
|
|
24181
|
+
}
|
|
24182
|
+
i2++;
|
|
24183
|
+
}
|
|
24184
|
+
return -1;
|
|
24185
|
+
}
|
|
24186
|
+
function findKwargValueEnd(s, start2) {
|
|
24187
|
+
let depth = 0;
|
|
24188
|
+
let inStr = null;
|
|
24189
|
+
for (let i2 = start2;i2 < s.length; i2++) {
|
|
24190
|
+
const ch = s[i2];
|
|
24191
|
+
if (inStr) {
|
|
24192
|
+
if (ch === "\\") {
|
|
24193
|
+
i2++;
|
|
24194
|
+
continue;
|
|
24195
|
+
}
|
|
24196
|
+
if (ch === inStr)
|
|
24197
|
+
inStr = null;
|
|
24198
|
+
continue;
|
|
24199
|
+
}
|
|
24200
|
+
if (ch === '"' || ch === "'" || ch === "`") {
|
|
24201
|
+
inStr = ch;
|
|
24202
|
+
continue;
|
|
24203
|
+
}
|
|
24204
|
+
if (ch === "(" || ch === "[" || ch === "{")
|
|
24205
|
+
depth++;
|
|
24206
|
+
else if (ch === ")" || ch === "]" || ch === "}") {
|
|
24207
|
+
if (depth === 0)
|
|
24208
|
+
return i2;
|
|
24209
|
+
depth--;
|
|
24210
|
+
} else if (ch === "," && depth === 0) {
|
|
24211
|
+
return i2;
|
|
24212
|
+
}
|
|
24213
|
+
}
|
|
24214
|
+
return s.length;
|
|
24215
|
+
}
|
|
24216
|
+
function lineOfCharIndex(code, charIdx) {
|
|
24217
|
+
let line = 1;
|
|
24218
|
+
for (let i2 = 0;i2 < charIdx && i2 < code.length; i2++) {
|
|
24219
|
+
if (code[i2] === `
|
|
24220
|
+
`)
|
|
24221
|
+
line++;
|
|
24222
|
+
}
|
|
24223
|
+
return line;
|
|
24224
|
+
}
|
|
24225
|
+
function makeExfilFinding(file, line, snippet, fired, receiver) {
|
|
24226
|
+
return {
|
|
24227
|
+
id: `external-secret-exfiltration-${file}-${line}`,
|
|
24228
|
+
pass: "language-sources",
|
|
24229
|
+
category: "security",
|
|
24230
|
+
rule_id: "external-secret-exfiltration",
|
|
24231
|
+
cwe: "CWE-200",
|
|
24232
|
+
severity: "high",
|
|
24233
|
+
level: "error",
|
|
24234
|
+
message: `Environment secret(s) ${fired.join(", ")} transmitted in request body to external host via ${receiver}`,
|
|
24235
|
+
file,
|
|
24236
|
+
line,
|
|
24237
|
+
snippet: snippet.substring(0, 120)
|
|
24238
|
+
};
|
|
24239
|
+
}
|
|
24240
|
+
function findExternalSecretExfiltrationFindings(code, file, language) {
|
|
24241
|
+
const out2 = [];
|
|
24242
|
+
const lines = code.split(`
|
|
24243
|
+
`);
|
|
24244
|
+
const secretVars = collectEnvSecretVars(lines, language);
|
|
24245
|
+
if (secretVars.size === 0)
|
|
24246
|
+
return out2;
|
|
24247
|
+
if (language === "python") {
|
|
24248
|
+
out2.push(...findPythonExfilCalls(code, file, secretVars));
|
|
24249
|
+
} else if (language === "javascript" || language === "typescript") {
|
|
24250
|
+
out2.push(...findJavaScriptExfilCalls(code, file, secretVars));
|
|
24251
|
+
} else if (language === "go") {
|
|
24252
|
+
out2.push(...findGoExfilCalls(code, file, secretVars));
|
|
24253
|
+
}
|
|
24254
|
+
return out2;
|
|
24255
|
+
}
|
|
24256
|
+
function findPythonExfilCalls(code, file, secretVars) {
|
|
24257
|
+
const out2 = [];
|
|
24258
|
+
const callRe = /\b(requests|httpx)\.(post|put|patch|delete|request)\s*\(/g;
|
|
24259
|
+
let m;
|
|
24260
|
+
while (m = callRe.exec(code)) {
|
|
24261
|
+
const argStart = m.index + m[0].length;
|
|
24262
|
+
const argEnd = findBalancedCallEnd(code, argStart);
|
|
24263
|
+
if (argEnd < 0)
|
|
24264
|
+
continue;
|
|
24265
|
+
const args2 = code.slice(argStart, argEnd);
|
|
24266
|
+
const urlMatch = args2.match(/^\s*["']([^"']+)["']/);
|
|
24267
|
+
if (!urlMatch)
|
|
24268
|
+
continue;
|
|
24269
|
+
if (!isExternalHostUrl(urlMatch[1]))
|
|
24270
|
+
continue;
|
|
24271
|
+
const headersIdx = args2.search(/\bheaders\s*=/);
|
|
24272
|
+
let headersStr = "";
|
|
24273
|
+
let bodyStr = args2;
|
|
24274
|
+
if (headersIdx >= 0) {
|
|
24275
|
+
const eqIdx = args2.indexOf("=", headersIdx);
|
|
24276
|
+
const valueStart = eqIdx + 1;
|
|
24277
|
+
const headersEnd = findKwargValueEnd(args2, valueStart);
|
|
24278
|
+
headersStr = args2.slice(valueStart, headersEnd);
|
|
24279
|
+
bodyStr = args2.slice(0, headersIdx) + " " + args2.slice(headersEnd);
|
|
24280
|
+
}
|
|
24281
|
+
const fired = [];
|
|
24282
|
+
for (const v of secretVars.keys()) {
|
|
24283
|
+
const re = new RegExp(`\\b${v}\\b`);
|
|
24284
|
+
if (re.test(bodyStr) && !re.test(headersStr))
|
|
24285
|
+
fired.push(v);
|
|
24286
|
+
}
|
|
24287
|
+
if (fired.length > 0) {
|
|
24288
|
+
const line = lineOfCharIndex(code, m.index);
|
|
24289
|
+
out2.push(makeExfilFinding(file, line, code.slice(m.index, Math.min(argEnd + 1, m.index + 120)), fired, `${m[1]}.${m[2]}`));
|
|
24290
|
+
}
|
|
24291
|
+
}
|
|
24292
|
+
return out2;
|
|
24293
|
+
}
|
|
24294
|
+
function findJavaScriptExfilCalls(code, file, secretVars) {
|
|
24295
|
+
const out2 = [];
|
|
24296
|
+
const allLines = code.split(`
|
|
24297
|
+
`);
|
|
24298
|
+
const carriers = new Set;
|
|
24299
|
+
const carrierRe = /(?:^|[\s;{])(?:const|let|var)\s+(\w+)\s*=\s*([^;\n]+)/g;
|
|
24300
|
+
let cm;
|
|
24301
|
+
while (cm = carrierRe.exec(code)) {
|
|
24302
|
+
const name2 = cm[1];
|
|
24303
|
+
const rhs = cm[2];
|
|
24304
|
+
for (const v of secretVars.keys()) {
|
|
24305
|
+
if (new RegExp(`\\b${v}\\b`).test(rhs)) {
|
|
24306
|
+
carriers.add(name2);
|
|
24307
|
+
break;
|
|
24308
|
+
}
|
|
24309
|
+
}
|
|
24310
|
+
}
|
|
24311
|
+
const taintedRefs = new Set([...secretVars.keys(), ...carriers]);
|
|
24312
|
+
const networkRe = /\b(https?\.(?:request|get)|fetch|axios\.(?:post|put|patch|request|delete))\s*\(/g;
|
|
24313
|
+
let m;
|
|
24314
|
+
while (m = networkRe.exec(code)) {
|
|
24315
|
+
const argStart = m.index + m[0].length;
|
|
24316
|
+
const argEnd = findBalancedCallEnd(code, argStart);
|
|
24317
|
+
if (argEnd < 0)
|
|
24318
|
+
continue;
|
|
24319
|
+
const args2 = code.slice(argStart, argEnd);
|
|
24320
|
+
const urlMatch = args2.match(/^\s*(?:["']([^"']+)["']|`([^`$]+)`)/);
|
|
24321
|
+
if (!urlMatch)
|
|
24322
|
+
continue;
|
|
24323
|
+
const url = urlMatch[1] || urlMatch[2];
|
|
24324
|
+
if (!isExternalHostUrl(url))
|
|
24325
|
+
continue;
|
|
24326
|
+
const headersIdx = args2.search(/\bheaders\s*:/);
|
|
24327
|
+
let headersStr = "";
|
|
24328
|
+
let restStr = args2;
|
|
24329
|
+
if (headersIdx >= 0) {
|
|
24330
|
+
const valueStart = args2.indexOf(":", headersIdx) + 1;
|
|
24331
|
+
const headersEnd = findKwargValueEnd(args2, valueStart);
|
|
24332
|
+
headersStr = args2.slice(valueStart, headersEnd);
|
|
24333
|
+
restStr = args2.slice(0, headersIdx) + " " + args2.slice(headersEnd);
|
|
24334
|
+
}
|
|
24335
|
+
const fired = new Set;
|
|
24336
|
+
for (const v of taintedRefs) {
|
|
24337
|
+
const re = new RegExp(`\\b${v}\\b`);
|
|
24338
|
+
if (re.test(restStr) && !re.test(headersStr))
|
|
24339
|
+
fired.add(v);
|
|
24340
|
+
}
|
|
24341
|
+
const callLine = lineOfCharIndex(code, m.index);
|
|
24342
|
+
const windowEnd = Math.min(allLines.length, callLine + 20);
|
|
24343
|
+
const writeWindow = allLines.slice(callLine, windowEnd).join(`
|
|
24344
|
+
`);
|
|
24345
|
+
const writeRe = /\b\w+\.(?:write|end)\s*\(\s*(\w+)/g;
|
|
24346
|
+
let wm;
|
|
24347
|
+
while (wm = writeRe.exec(writeWindow)) {
|
|
24348
|
+
if (taintedRefs.has(wm[1]))
|
|
24349
|
+
fired.add(wm[1]);
|
|
24350
|
+
}
|
|
24351
|
+
if (fired.size > 0) {
|
|
24352
|
+
out2.push(makeExfilFinding(file, callLine, code.slice(m.index, Math.min(argEnd + 1, m.index + 120)), [...fired], m[1]));
|
|
24353
|
+
}
|
|
24354
|
+
}
|
|
24355
|
+
return out2;
|
|
24356
|
+
}
|
|
24357
|
+
function findGoExfilCalls(code, file, secretVars) {
|
|
24358
|
+
const out2 = [];
|
|
24359
|
+
const patterns = [
|
|
24360
|
+
{ re: /\bhttp\.PostForm\s*\(/g, urlArgIndex: 0, receiver: "http.PostForm" },
|
|
24361
|
+
{ re: /\bhttp\.Post\s*\(/g, urlArgIndex: 0, receiver: "http.Post" },
|
|
24362
|
+
{ re: /\bhttp\.NewRequest\s*\(/g, urlArgIndex: 1, receiver: "http.NewRequest" }
|
|
24363
|
+
];
|
|
24364
|
+
for (const { re, urlArgIndex, receiver } of patterns) {
|
|
24365
|
+
let m;
|
|
24366
|
+
while (m = re.exec(code)) {
|
|
24367
|
+
const argStart = m.index + m[0].length;
|
|
24368
|
+
const argEnd = findBalancedCallEnd(code, argStart);
|
|
24369
|
+
if (argEnd < 0)
|
|
24370
|
+
continue;
|
|
24371
|
+
const args2 = code.slice(argStart, argEnd);
|
|
24372
|
+
let urlSearchSlice = args2;
|
|
24373
|
+
if (urlArgIndex === 1) {
|
|
24374
|
+
const firstComma = findKwargValueEnd(args2, 0);
|
|
24375
|
+
if (firstComma >= args2.length)
|
|
24376
|
+
continue;
|
|
24377
|
+
urlSearchSlice = args2.slice(firstComma + 1);
|
|
24378
|
+
}
|
|
24379
|
+
const urlMatch = urlSearchSlice.match(/^\s*["`]([^"`]+)["`]/);
|
|
24380
|
+
if (!urlMatch)
|
|
24381
|
+
continue;
|
|
24382
|
+
if (!isExternalHostUrl(urlMatch[1]))
|
|
24383
|
+
continue;
|
|
24384
|
+
const fired = [];
|
|
24385
|
+
for (const v of secretVars.keys()) {
|
|
24386
|
+
if (new RegExp(`\\b${v}\\b`).test(args2))
|
|
24387
|
+
fired.push(v);
|
|
24388
|
+
}
|
|
24389
|
+
if (fired.length > 0) {
|
|
24390
|
+
const line = lineOfCharIndex(code, m.index);
|
|
24391
|
+
out2.push(makeExfilFinding(file, line, code.slice(m.index, Math.min(argEnd + 1, m.index + 120)), fired, receiver));
|
|
24392
|
+
}
|
|
24393
|
+
}
|
|
24394
|
+
}
|
|
24395
|
+
return out2;
|
|
24396
|
+
}
|
|
24397
|
+
function findPythonPatternFindings(code, file) {
|
|
24398
|
+
const out2 = [];
|
|
24399
|
+
const lines = code.split(`
|
|
24400
|
+
`);
|
|
24401
|
+
const subscriptHeaderRe = /\.headers\s*\[\s*['"]([^'"]+)['"]\s*\]\s*=\s*(.+)$/;
|
|
24402
|
+
const xfoHits = [];
|
|
24403
|
+
const cspHits = [];
|
|
24404
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
24405
|
+
const raw = lines[i2];
|
|
24406
|
+
const trimmed = raw.trim();
|
|
24407
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
24408
|
+
continue;
|
|
24409
|
+
const lineNumber = i2 + 1;
|
|
24410
|
+
const sm = trimmed.match(subscriptHeaderRe);
|
|
24411
|
+
if (sm) {
|
|
24412
|
+
const headerName = sm[1];
|
|
24413
|
+
const rhs = sm[2].trim();
|
|
24414
|
+
const headerLower = headerName.toLowerCase();
|
|
24415
|
+
if (headerLower === "access-control-allow-origin") {
|
|
24416
|
+
const valMatch = rhs.match(/^['"]\*['"]/);
|
|
24417
|
+
if (valMatch) {
|
|
24418
|
+
out2.push({
|
|
24419
|
+
id: `cors-wildcard-origin-${file}-${lineNumber}`,
|
|
24420
|
+
pass: "language-sources",
|
|
24421
|
+
category: "security",
|
|
24422
|
+
rule_id: "cors-wildcard-origin",
|
|
24423
|
+
cwe: "CWE-942",
|
|
24424
|
+
severity: "medium",
|
|
24425
|
+
level: "warning",
|
|
24426
|
+
message: "CORS Access-Control-Allow-Origin set to wildcard '*': any origin may read responses",
|
|
24427
|
+
file,
|
|
24428
|
+
line: lineNumber,
|
|
24429
|
+
snippet: trimmed.substring(0, 100)
|
|
24430
|
+
});
|
|
24431
|
+
}
|
|
24432
|
+
}
|
|
24433
|
+
if (headerLower === "x-frame-options") {
|
|
24434
|
+
xfoHits.push({ line: lineNumber, value: rhs });
|
|
24435
|
+
} else if (headerLower === "content-security-policy") {
|
|
24436
|
+
cspHits.push({ line: lineNumber, value: rhs });
|
|
24437
|
+
}
|
|
24438
|
+
}
|
|
24439
|
+
if (/\bverify_mode\s*=\s*ssl\.CERT_NONE\b/.test(trimmed)) {
|
|
24440
|
+
out2.push({
|
|
24441
|
+
id: `tls-verify-disabled-${file}-${lineNumber}`,
|
|
24442
|
+
pass: "language-sources",
|
|
24443
|
+
category: "security",
|
|
24444
|
+
rule_id: "tls-verify-disabled",
|
|
24445
|
+
cwe: "CWE-295",
|
|
24446
|
+
severity: "high",
|
|
24447
|
+
level: "error",
|
|
24448
|
+
message: "TLS certificate verification disabled: ssl context verify_mode set to CERT_NONE",
|
|
24449
|
+
file,
|
|
24450
|
+
line: lineNumber,
|
|
24451
|
+
snippet: trimmed.substring(0, 100)
|
|
24452
|
+
});
|
|
24453
|
+
} else if (/\bcheck_hostname\s*=\s*False\b/.test(trimmed)) {
|
|
24454
|
+
out2.push({
|
|
24455
|
+
id: `tls-verify-disabled-${file}-${lineNumber}`,
|
|
24456
|
+
pass: "language-sources",
|
|
24457
|
+
category: "security",
|
|
24458
|
+
rule_id: "tls-verify-disabled",
|
|
24459
|
+
cwe: "CWE-295",
|
|
24460
|
+
severity: "high",
|
|
24461
|
+
level: "error",
|
|
24462
|
+
message: "TLS hostname verification disabled: ssl context check_hostname set to False",
|
|
24463
|
+
file,
|
|
24464
|
+
line: lineNumber,
|
|
24465
|
+
snippet: trimmed.substring(0, 100)
|
|
24466
|
+
});
|
|
24467
|
+
}
|
|
24468
|
+
}
|
|
24469
|
+
const restrictiveXfo = xfoHits.find((h) => /['"](?:DENY|SAMEORIGIN)['"]/i.test(h.value));
|
|
24470
|
+
const permissiveCsp = cspHits.find((h) => {
|
|
24471
|
+
const pm = h.value.match(/['"]([^'"]+)['"]/);
|
|
24472
|
+
if (!pm)
|
|
24473
|
+
return false;
|
|
24474
|
+
const policy = pm[1];
|
|
24475
|
+
const faMatch = policy.match(/frame-ancestors\s+([^;]+)/i);
|
|
24476
|
+
if (!faMatch)
|
|
24477
|
+
return false;
|
|
24478
|
+
const directive = faMatch[1].trim();
|
|
24479
|
+
return /(^|\s)\*(\s|$)/.test(directive) || /\bhttps?:\/\//i.test(directive);
|
|
24480
|
+
});
|
|
24481
|
+
if (restrictiveXfo && permissiveCsp) {
|
|
24482
|
+
out2.push({
|
|
24483
|
+
id: `xfo-csp-mismatch-${file}-${restrictiveXfo.line}`,
|
|
24484
|
+
pass: "language-sources",
|
|
24485
|
+
category: "security",
|
|
24486
|
+
rule_id: "xfo-csp-mismatch",
|
|
24487
|
+
cwe: "CWE-1021",
|
|
24488
|
+
severity: "medium",
|
|
24489
|
+
level: "warning",
|
|
24490
|
+
message: "X-Frame-Options/CSP frame-ancestors mismatch: XFO restricts framing but CSP frame-ancestors is permissive (CSP overrides on modern browsers)",
|
|
24491
|
+
file,
|
|
24492
|
+
line: restrictiveXfo.line,
|
|
24493
|
+
snippet: lines[restrictiveXfo.line - 1].trim().substring(0, 100)
|
|
24494
|
+
});
|
|
24495
|
+
}
|
|
24496
|
+
return out2;
|
|
24497
|
+
}
|
|
24498
|
+
function findRustPatternFindings(code, file) {
|
|
24499
|
+
const out2 = [];
|
|
24500
|
+
const lines = code.split(`
|
|
24501
|
+
`);
|
|
24502
|
+
const re = /\.\s*(danger_accept_invalid_certs|danger_accept_invalid_hostnames)\s*\(\s*true\s*\)/;
|
|
24503
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
24504
|
+
const raw = lines[i2];
|
|
24505
|
+
const trimmed = raw.trim();
|
|
24506
|
+
if (!trimmed || trimmed.startsWith("//"))
|
|
24507
|
+
continue;
|
|
24508
|
+
const m = trimmed.match(re);
|
|
24509
|
+
if (!m)
|
|
24510
|
+
continue;
|
|
24511
|
+
const method = m[1];
|
|
24512
|
+
const what = method === "danger_accept_invalid_certs" ? "certificate" : "hostname";
|
|
24513
|
+
out2.push({
|
|
24514
|
+
id: `tls-verify-disabled-${file}-${i2 + 1}`,
|
|
24515
|
+
pass: "language-sources",
|
|
24516
|
+
category: "security",
|
|
24517
|
+
rule_id: "tls-verify-disabled",
|
|
24518
|
+
cwe: "CWE-295",
|
|
24519
|
+
severity: "high",
|
|
24520
|
+
level: "error",
|
|
24521
|
+
message: `TLS ${what} verification disabled: reqwest builder ${method}(true)`,
|
|
24522
|
+
file,
|
|
24523
|
+
line: i2 + 1,
|
|
24524
|
+
snippet: trimmed.substring(0, 100)
|
|
24525
|
+
});
|
|
24526
|
+
}
|
|
24527
|
+
return out2;
|
|
24528
|
+
}
|
|
23811
24529
|
|
|
23812
24530
|
// ../circle-ir/dist/analysis/passes/sink-filter-pass.js
|
|
23813
24531
|
var JS_XSS_SANITIZERS = [
|
|
@@ -26294,7 +27012,33 @@ function analyzeInterprocedural2(graphOrTypes, callsOrSources, dfgOrSinks, sourc
|
|
|
26294
27012
|
if (isBash && bashSafeBuiltins.has(call.method_name)) {
|
|
26295
27013
|
continue;
|
|
26296
27014
|
}
|
|
26297
|
-
|
|
27015
|
+
if (isBash && taintedArgPositions.length > 0) {
|
|
27016
|
+
const terminatorPos = call.arguments.filter((a) => a.expression === "--").map((a) => a.position).reduce((min, p) => min === null || p < min ? p : min, null);
|
|
27017
|
+
const earliestTainted = Math.min(...taintedArgPositions);
|
|
27018
|
+
if (terminatorPos !== null && terminatorPos < earliestTainted) {
|
|
27019
|
+
const allTaintedQuoted = taintedArgPositions.every((p) => {
|
|
27020
|
+
const a = call.arguments[p];
|
|
27021
|
+
if (!a)
|
|
27022
|
+
return false;
|
|
27023
|
+
const expr = a.expression.trim();
|
|
27024
|
+
return expr.startsWith('"') && expr.endsWith('"');
|
|
27025
|
+
});
|
|
27026
|
+
if (allTaintedQuoted) {
|
|
27027
|
+
continue;
|
|
27028
|
+
}
|
|
27029
|
+
}
|
|
27030
|
+
}
|
|
27031
|
+
const bashSqlCliTools = new Set(["sqlite3", "mysql", "psql", "mariadb"]);
|
|
27032
|
+
const isBashSqlCli = isBash && bashSqlCliTools.has(call.method_name);
|
|
27033
|
+
const sink = isBashSqlCli ? {
|
|
27034
|
+
type: "sql_injection",
|
|
27035
|
+
cwe: "CWE-89",
|
|
27036
|
+
location: `Tainted data (${taintedArgVars.join(", ")}) interpolated into SQL query passed to ${call.method_name}`,
|
|
27037
|
+
line: call.location.line,
|
|
27038
|
+
confidence: 0.7,
|
|
27039
|
+
method: call.method_name,
|
|
27040
|
+
argPositions: taintedArgPositions
|
|
27041
|
+
} : isBash ? {
|
|
26298
27042
|
type: "command_injection",
|
|
26299
27043
|
cwe: "CWE-78",
|
|
26300
27044
|
location: `Tainted data (${taintedArgVars.join(", ")}) passed unquoted to shell utility ${call.method_name}`,
|
|
@@ -36208,6 +36952,11 @@ async function analyzeMarkupFile(code, filePath, options, language) {
|
|
|
36208
36952
|
}
|
|
36209
36953
|
}
|
|
36210
36954
|
const attributeFindings = runHtmlAttributeSecurityChecks(tree.rootNode, filePath);
|
|
36955
|
+
if (language === "vue") {
|
|
36956
|
+
const vueXss = runVueTemplateXssChecks(tree.rootNode, filePath, scriptResults);
|
|
36957
|
+
if (vueXss.length > 0)
|
|
36958
|
+
attributeFindings.push(...vueXss);
|
|
36959
|
+
}
|
|
36211
36960
|
const result = mergeHtmlResults(meta, scriptResults, attributeFindings);
|
|
36212
36961
|
result.parse_status = htmlParseStatus;
|
|
36213
36962
|
logger.debug("HTML analysis complete", {
|
|
@@ -36904,7 +37653,7 @@ var colors = {
|
|
|
36904
37653
|
};
|
|
36905
37654
|
|
|
36906
37655
|
// src/version.ts
|
|
36907
|
-
var version = "3.
|
|
37656
|
+
var version = "3.123.0";
|
|
36908
37657
|
|
|
36909
37658
|
// src/formatters.ts
|
|
36910
37659
|
var LIBRARY_API_SURFACE_TAG2 = "library-api-surface:caller-responsibility";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cognium-dev",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.123.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",
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
68
|
"@cognium/project-profile-detect": "^1.1.0",
|
|
69
|
-
"circle-ir": "^3.
|
|
69
|
+
"circle-ir": "^3.123.0"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
72
|
"@types/node": "^25.5.0",
|