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.
- package/dist/cli.js +251 -20
- 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
|
-
|
|
23293
|
-
|
|
23294
|
-
|
|
23295
|
-
|
|
23296
|
-
|
|
23297
|
-
|
|
23298
|
-
|
|
23299
|
-
|
|
23300
|
-
|
|
23301
|
-
|
|
23302
|
-
|
|
23303
|
-
|
|
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
|
|
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
|
|
36093
|
-
logger.debug("Analyzing
|
|
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,
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
69
|
+
"circle-ir": "^3.121.0"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
72
|
"@types/node": "^25.5.0",
|