cognium-dev 3.119.0 → 3.121.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.
Files changed (2) hide show
  1. package/dist/cli.js +178 -14
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -22082,6 +22082,126 @@ function truncate(s, maxLen) {
22082
22082
  return s.length > maxLen ? s.slice(0, maxLen) + "..." : s;
22083
22083
  }
22084
22084
 
22085
+ // ../circle-ir/dist/analysis/html/vue-template-xss-pass.js
22086
+ var DANGEROUS_BINDINGS = new Set([
22087
+ "v-html",
22088
+ "v-bind:innerhtml",
22089
+ ":innerhtml",
22090
+ "v-bind:outerhtml",
22091
+ ":outerhtml"
22092
+ ]);
22093
+ var ID_RE = /\b([A-Za-z_$][A-Za-z0-9_$]*)\b/g;
22094
+ var RESERVED = new Set([
22095
+ "true",
22096
+ "false",
22097
+ "null",
22098
+ "undefined",
22099
+ "this",
22100
+ "new",
22101
+ "typeof",
22102
+ "void",
22103
+ "delete",
22104
+ "instanceof",
22105
+ "in",
22106
+ "of",
22107
+ "Math",
22108
+ "Number",
22109
+ "String",
22110
+ "Boolean",
22111
+ "Array",
22112
+ "Object",
22113
+ "JSON"
22114
+ ]);
22115
+ function runVueTemplateXssChecks(rootNode, filePath, scriptResults) {
22116
+ const taintedNames = collectTaintedNames(scriptResults);
22117
+ if (taintedNames.size === 0)
22118
+ return [];
22119
+ const findings = [];
22120
+ const stack = [rootNode];
22121
+ while (stack.length > 0) {
22122
+ const node = stack.pop();
22123
+ if (node.type === "start_tag" || node.type === "self_closing_tag") {
22124
+ checkElementBindings(node, filePath, taintedNames, findings);
22125
+ }
22126
+ for (let i2 = node.childCount - 1;i2 >= 0; i2--) {
22127
+ const c = node.child(i2);
22128
+ if (c)
22129
+ stack.push(c);
22130
+ }
22131
+ }
22132
+ return findings;
22133
+ }
22134
+ function checkElementBindings(tag, filePath, tainted, out2) {
22135
+ for (let i2 = 0;i2 < tag.childCount; i2++) {
22136
+ const attr = tag.child(i2);
22137
+ if (!attr || attr.type !== "attribute")
22138
+ continue;
22139
+ const nameNode = findChildByType2(attr, "attribute_name");
22140
+ if (!nameNode)
22141
+ continue;
22142
+ const lcName = nameNode.text.toLowerCase();
22143
+ if (!DANGEROUS_BINDINGS.has(lcName))
22144
+ continue;
22145
+ const valueNode = findChildByType2(attr, "quoted_attribute_value") ?? findChildByType2(attr, "attribute_value");
22146
+ if (!valueNode)
22147
+ continue;
22148
+ const rhs = stripQuotes2(valueNode.text);
22149
+ if (!rhs.trim())
22150
+ continue;
22151
+ const matched = matchTaint(rhs, tainted);
22152
+ if (!matched)
22153
+ continue;
22154
+ const line = nameNode.startPosition.row + 1;
22155
+ out2.push({
22156
+ id: `vue-template-xss-${filePath}-${line}-${lcName}`,
22157
+ pass: "vue-template-xss",
22158
+ category: "security",
22159
+ rule_id: "vue-template-xss",
22160
+ cwe: "CWE-79",
22161
+ severity: "high",
22162
+ level: "error",
22163
+ message: `Vue template attribute "${nameNode.text}" binds tainted identifier "${matched}" — writes raw HTML (XSS risk).`,
22164
+ file: filePath,
22165
+ line,
22166
+ snippet: `${nameNode.text}="${rhs}"`
22167
+ });
22168
+ }
22169
+ }
22170
+ function matchTaint(rhs, tainted) {
22171
+ ID_RE.lastIndex = 0;
22172
+ let m;
22173
+ while ((m = ID_RE.exec(rhs)) !== null) {
22174
+ const id = m[1];
22175
+ if (RESERVED.has(id))
22176
+ continue;
22177
+ if (tainted.has(id))
22178
+ return id;
22179
+ }
22180
+ return;
22181
+ }
22182
+ function collectTaintedNames(blocks) {
22183
+ const names = new Set;
22184
+ for (const { ir } of blocks) {
22185
+ const sourceLines = new Set;
22186
+ for (const source of ir.taint.sources) {
22187
+ sourceLines.add(source.line);
22188
+ if (source.variable)
22189
+ names.add(source.variable);
22190
+ }
22191
+ for (const def of ir.dfg.defs) {
22192
+ if (sourceLines.has(def.line) && def.variable)
22193
+ names.add(def.variable);
22194
+ }
22195
+ for (const flow of ir.taint.flows ?? []) {
22196
+ for (const step of flow.path ?? []) {
22197
+ if (step.variable)
22198
+ names.add(step.variable);
22199
+ }
22200
+ }
22201
+ }
22202
+ return names;
22203
+ }
22204
+
22085
22205
  // ../circle-ir/dist/analysis/html/html-merge.js
22086
22206
  function mergeHtmlResults(htmlMeta, scriptResults, attributeFindings) {
22087
22207
  const allTypes = [];
@@ -23309,10 +23429,43 @@ function findBashTaintSources(sourceCode, dfg) {
23309
23429
  return sources;
23310
23430
  }
23311
23431
  var BASH_CREDENTIAL_PATTERN = /^(.*?)(password|passwd|secret|api_?key|token|auth_token|private_key|access_key)\s*=\s*["']?([^"'\s$][^"'\s]*)["']?\s*$/i;
23432
+ var BASH_CHECKSUM_VERIFY_PATTERN = /\b(?:sha(?:1|224|256|384|512)sum|md5sum|cksum|b2sum)\s+(?:-c\b|--check\b)/;
23433
+ function collectChecksumVerifiedTmpPaths(lines) {
23434
+ const verified = new Set;
23435
+ for (const line of lines) {
23436
+ if (!BASH_CHECKSUM_VERIFY_PATTERN.test(line))
23437
+ continue;
23438
+ const matches = line.match(/\/tmp\/[^\s"'$|`]+/g);
23439
+ if (!matches)
23440
+ continue;
23441
+ for (const p of matches)
23442
+ verified.add(p);
23443
+ }
23444
+ return verified;
23445
+ }
23446
+ var BASH_ARCHIVE_EXT_PATTERN = /\.(?:tgz|tar\.gz|tar\.bz2|tar\.xz|tar|tbz2|txz|zip|gz|bz2|xz|7z)$/i;
23447
+ function isArchiveOutputContext(line, tmpRel) {
23448
+ if (!BASH_ARCHIVE_EXT_PATTERN.test(tmpRel))
23449
+ return false;
23450
+ if (/\btar\b/.test(line) && /(?:^|\s)-?[A-Za-z]*c[A-Za-z]*\b/.test(line))
23451
+ return true;
23452
+ if (/\bzip\b/.test(line) && !/\bunzip\b/.test(line))
23453
+ return true;
23454
+ if (/\bgzip\b/.test(line) && /(?:-c\b|--stdout\b|>)/.test(line))
23455
+ return true;
23456
+ if (/\bbzip2\b/.test(line) && /(?:-c\b|--stdout\b|>)/.test(line))
23457
+ return true;
23458
+ if (/\bxz\b/.test(line) && /(?:-c\b|--stdout\b|>)/.test(line))
23459
+ return true;
23460
+ if (/\b7z\s+a\b/.test(line))
23461
+ return true;
23462
+ return false;
23463
+ }
23312
23464
  function findBashPatternFindings(sourceCode, file) {
23313
23465
  const findings = [];
23314
23466
  const lines = sourceCode.split(`
23315
23467
  `);
23468
+ const checksumVerifiedTmpPaths = collectChecksumVerifiedTmpPaths(lines);
23316
23469
  for (let i2 = 0;i2 < lines.length; i2++) {
23317
23470
  const line = lines[i2];
23318
23471
  const trimmed = line.trim();
@@ -23355,19 +23508,25 @@ function findBashPatternFindings(sourceCode, file) {
23355
23508
  }
23356
23509
  const tmpMatch = trimmed.match(/\/tmp\/([^\s"'$]+)/);
23357
23510
  if (tmpMatch && !/mktemp/.test(trimmed)) {
23358
- findings.push({
23359
- id: `predictable-temp-file-${file}-${lineNumber}`,
23360
- pass: "language-sources",
23361
- category: "security",
23362
- rule_id: "predictable-temp-file",
23363
- cwe: "CWE-377",
23364
- severity: "medium",
23365
- level: "warning",
23366
- message: `Predictable temp file: /tmp/${tmpMatch[1]}. Use mktemp instead`,
23367
- file,
23368
- line: lineNumber,
23369
- snippet: trimmed.substring(0, 80)
23370
- });
23511
+ const tmpRel = tmpMatch[1];
23512
+ const tmpPath = `/tmp/${tmpRel}`;
23513
+ const isChecksumVerified = checksumVerifiedTmpPaths.has(tmpPath);
23514
+ const isArchiveOutput = isArchiveOutputContext(trimmed, tmpRel);
23515
+ if (!isChecksumVerified && !isArchiveOutput) {
23516
+ findings.push({
23517
+ id: `predictable-temp-file-${file}-${lineNumber}`,
23518
+ pass: "language-sources",
23519
+ category: "security",
23520
+ rule_id: "predictable-temp-file",
23521
+ cwe: "CWE-377",
23522
+ severity: "medium",
23523
+ level: "warning",
23524
+ message: `Predictable temp file: /tmp/${tmpRel}. Use mktemp instead`,
23525
+ file,
23526
+ line: lineNumber,
23527
+ snippet: trimmed.substring(0, 80)
23528
+ });
23529
+ }
23371
23530
  }
23372
23531
  if (/\bchmod\b/.test(trimmed) && /\b(777|666)\b/.test(trimmed)) {
23373
23532
  const mode = trimmed.match(/\b(777|666)\b/)[1];
@@ -36208,6 +36367,11 @@ async function analyzeMarkupFile(code, filePath, options, language) {
36208
36367
  }
36209
36368
  }
36210
36369
  const attributeFindings = runHtmlAttributeSecurityChecks(tree.rootNode, filePath);
36370
+ if (language === "vue") {
36371
+ const vueXss = runVueTemplateXssChecks(tree.rootNode, filePath, scriptResults);
36372
+ if (vueXss.length > 0)
36373
+ attributeFindings.push(...vueXss);
36374
+ }
36211
36375
  const result = mergeHtmlResults(meta, scriptResults, attributeFindings);
36212
36376
  result.parse_status = htmlParseStatus;
36213
36377
  logger.debug("HTML analysis complete", {
@@ -36904,7 +37068,7 @@ var colors = {
36904
37068
  };
36905
37069
 
36906
37070
  // src/version.ts
36907
- var version = "3.119.0";
37071
+ var version = "3.121.0";
36908
37072
 
36909
37073
  // src/formatters.ts
36910
37074
  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.119.0",
3
+ "version": "3.121.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.119.0"
69
+ "circle-ir": "^3.121.0"
70
70
  },
71
71
  "devDependencies": {
72
72
  "@types/node": "^25.5.0",