cognium-dev 3.118.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 +251 -20
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -20668,6 +20668,71 @@ class HtmlPlugin extends BaseLanguagePlugin {
20668
20668
  }
20669
20669
  }
20670
20670
 
20671
+ // ../circle-ir/dist/languages/plugins/vue.js
20672
+ class VuePlugin extends BaseLanguagePlugin {
20673
+ id = "vue";
20674
+ name = "Vue";
20675
+ extensions = [".vue"];
20676
+ wasmPath = "tree-sitter-html.wasm";
20677
+ nodeTypes = {
20678
+ classDeclaration: [],
20679
+ interfaceDeclaration: [],
20680
+ enumDeclaration: [],
20681
+ functionDeclaration: [],
20682
+ methodDeclaration: [],
20683
+ methodCall: [],
20684
+ functionCall: [],
20685
+ assignment: [],
20686
+ variableDeclaration: [],
20687
+ parameter: [],
20688
+ argument: [],
20689
+ annotation: [],
20690
+ decorator: [],
20691
+ importStatement: [],
20692
+ ifStatement: [],
20693
+ forStatement: [],
20694
+ whileStatement: [],
20695
+ tryStatement: [],
20696
+ returnStatement: []
20697
+ };
20698
+ detectFramework(_context) {
20699
+ return;
20700
+ }
20701
+ getBuiltinSources() {
20702
+ return [];
20703
+ }
20704
+ getBuiltinSinks() {
20705
+ return [];
20706
+ }
20707
+ getReceiverType(_node, _context) {
20708
+ return;
20709
+ }
20710
+ isStringLiteral(node) {
20711
+ return node.type === "attribute_value" || node.type === "quoted_attribute_value";
20712
+ }
20713
+ getStringValue(node) {
20714
+ if (!this.isStringLiteral(node))
20715
+ return;
20716
+ const text = node.text;
20717
+ if (text.startsWith('"') && text.endsWith('"') || text.startsWith("'") && text.endsWith("'")) {
20718
+ return text.slice(1, -1);
20719
+ }
20720
+ return text;
20721
+ }
20722
+ extractTypes(_context) {
20723
+ return [];
20724
+ }
20725
+ extractCalls(_context) {
20726
+ return [];
20727
+ }
20728
+ extractImports(_context) {
20729
+ return [];
20730
+ }
20731
+ extractPackage(_context) {
20732
+ return;
20733
+ }
20734
+ }
20735
+
20671
20736
  // ../circle-ir/dist/languages/plugins/go.js
20672
20737
  class GoPlugin extends BaseLanguagePlugin {
20673
20738
  id = "go";
@@ -21333,6 +21398,7 @@ function registerBuiltinPlugins() {
21333
21398
  registerLanguage(new RustPlugin);
21334
21399
  registerLanguage(new BashPlugin);
21335
21400
  registerLanguage(new HtmlPlugin);
21401
+ registerLanguage(new VuePlugin);
21336
21402
  registerLanguage(new GoPlugin);
21337
21403
  }
21338
21404
  // ../circle-ir/dist/analysis/passes/cross-file-pass.js
@@ -22016,6 +22082,126 @@ function truncate(s, maxLen) {
22016
22082
  return s.length > maxLen ? s.slice(0, maxLen) + "..." : s;
22017
22083
  }
22018
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
+
22019
22205
  // ../circle-ir/dist/analysis/html/html-merge.js
22020
22206
  function mergeHtmlResults(htmlMeta, scriptResults, attributeFindings) {
22021
22207
  const allTypes = [];
@@ -23243,10 +23429,43 @@ function findBashTaintSources(sourceCode, dfg) {
23243
23429
  return sources;
23244
23430
  }
23245
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
+ }
23246
23464
  function findBashPatternFindings(sourceCode, file) {
23247
23465
  const findings = [];
23248
23466
  const lines = sourceCode.split(`
23249
23467
  `);
23468
+ const checksumVerifiedTmpPaths = collectChecksumVerifiedTmpPaths(lines);
23250
23469
  for (let i2 = 0;i2 < lines.length; i2++) {
23251
23470
  const line = lines[i2];
23252
23471
  const trimmed = line.trim();
@@ -23289,19 +23508,25 @@ function findBashPatternFindings(sourceCode, file) {
23289
23508
  }
23290
23509
  const tmpMatch = trimmed.match(/\/tmp\/([^\s"'$]+)/);
23291
23510
  if (tmpMatch && !/mktemp/.test(trimmed)) {
23292
- findings.push({
23293
- id: `predictable-temp-file-${file}-${lineNumber}`,
23294
- pass: "language-sources",
23295
- category: "security",
23296
- rule_id: "predictable-temp-file",
23297
- cwe: "CWE-377",
23298
- severity: "medium",
23299
- level: "warning",
23300
- message: `Predictable temp file: /tmp/${tmpMatch[1]}. Use mktemp instead`,
23301
- file,
23302
- line: lineNumber,
23303
- snippet: trimmed.substring(0, 80)
23304
- });
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
+ }
23305
23530
  }
23306
23531
  if (/\bchmod\b/.test(trimmed) && /\b(777|666)\b/.test(trimmed)) {
23307
23532
  const mode = trimmed.match(/\b(777|666)\b/)[1];
@@ -35811,6 +36036,7 @@ function getNodeTypesForLanguage(language) {
35811
36036
  "while_statement"
35812
36037
  ]);
35813
36038
  case "html":
36039
+ case "vue":
35814
36040
  return new Set([
35815
36041
  "element",
35816
36042
  "script_element",
@@ -35865,8 +36091,8 @@ async function analyze(code, filePath, language, options = {}) {
35865
36091
  if (!initialized) {
35866
36092
  await initAnalyzer(options);
35867
36093
  }
35868
- if (language === "html") {
35869
- return analyzeHtmlFile(code, filePath, options);
36094
+ if (language === "html" || language === "vue") {
36095
+ return analyzeMarkupFile(code, filePath, options, language);
35870
36096
  }
35871
36097
  let parseGrammar = language;
35872
36098
  if (language === "javascript" || language === "typescript") {
@@ -36089,16 +36315,16 @@ async function analyze(code, filePath, language, options = {}) {
36089
36315
  disposeTree(tree);
36090
36316
  }
36091
36317
  }
36092
- async function analyzeHtmlFile(code, filePath, options) {
36093
- logger.debug("Analyzing HTML file", { filePath, codeLength: code.length });
36318
+ async function analyzeMarkupFile(code, filePath, options, language) {
36319
+ logger.debug("Analyzing markup file", { filePath, language, codeLength: code.length });
36094
36320
  const tree = await parse(code, "html");
36095
36321
  try {
36096
- const meta = extractMeta(code, tree, filePath, "html");
36322
+ const meta = extractMeta(code, tree, filePath, language);
36097
36323
  const htmlParseStatus = extractParseStatus(tree);
36098
36324
  if (htmlParseStatus.has_errors) {
36099
36325
  logger.warn("Partial parse — IR may be incomplete", {
36100
36326
  filePath,
36101
- language: "html",
36327
+ language,
36102
36328
  errorCount: htmlParseStatus.error_count,
36103
36329
  firstErrorLine: htmlParseStatus.error_locations[0]?.line
36104
36330
  });
@@ -36141,6 +36367,11 @@ async function analyzeHtmlFile(code, filePath, options) {
36141
36367
  }
36142
36368
  }
36143
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
+ }
36144
36375
  const result = mergeHtmlResults(meta, scriptResults, attributeFindings);
36145
36376
  result.parse_status = htmlParseStatus;
36146
36377
  logger.debug("HTML analysis complete", {
@@ -36837,7 +37068,7 @@ var colors = {
36837
37068
  };
36838
37069
 
36839
37070
  // src/version.ts
36840
- var version = "3.118.0";
37071
+ var version = "3.121.0";
36841
37072
 
36842
37073
  // src/formatters.ts
36843
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.118.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.118.0"
69
+ "circle-ir": "^3.121.0"
70
70
  },
71
71
  "devDependencies": {
72
72
  "@types/node": "^25.5.0",