circle-ir 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/analysis/html/index.d.ts +1 -0
- package/dist/analysis/html/index.d.ts.map +1 -1
- package/dist/analysis/html/index.js +1 -0
- package/dist/analysis/html/index.js.map +1 -1
- package/dist/analysis/html/vue-template-xss-pass.d.ts +36 -0
- package/dist/analysis/html/vue-template-xss-pass.d.ts.map +1 -0
- package/dist/analysis/html/vue-template-xss-pass.js +163 -0
- package/dist/analysis/html/vue-template-xss-pass.js.map +1 -0
- package/dist/analysis/passes/language-sources-pass.d.ts.map +1 -1
- package/dist/analysis/passes/language-sources-pass.js +61 -13
- package/dist/analysis/passes/language-sources-pass.js.map +1 -1
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +29 -11
- package/dist/analyzer.js.map +1 -1
- package/dist/browser/circle-ir.js +229 -19
- package/dist/core/parser.d.ts +1 -1
- package/dist/core/parser.d.ts.map +1 -1
- package/dist/languages/plugins/index.d.ts +1 -0
- package/dist/languages/plugins/index.d.ts.map +1 -1
- package/dist/languages/plugins/index.js +3 -0
- package/dist/languages/plugins/index.js.map +1 -1
- package/dist/languages/plugins/vue.d.ts +43 -0
- package/dist/languages/plugins/vue.d.ts.map +1 -0
- package/dist/languages/plugins/vue.js +91 -0
- package/dist/languages/plugins/vue.js.map +1 -0
- package/dist/languages/types.d.ts +1 -1
- package/dist/languages/types.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -8,5 +8,6 @@
|
|
|
8
8
|
*/
|
|
9
9
|
export { extractHtmlContent, type HtmlScriptBlock, type HtmlEventHandler, type HtmlExtractionResult, } from './html-extractor.js';
|
|
10
10
|
export { runHtmlAttributeSecurityChecks } from './html-attribute-security-pass.js';
|
|
11
|
+
export { runVueTemplateXssChecks } from './vue-template-xss-pass.js';
|
|
11
12
|
export { mergeHtmlResults, type ScriptBlockResult, } from './html-merge.js';
|
|
12
13
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/analysis/html/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EACL,kBAAkB,EAClB,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,GAC1B,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,8BAA8B,EAAE,MAAM,mCAAmC,CAAC;AAEnF,OAAO,EACL,gBAAgB,EAChB,KAAK,iBAAiB,GACvB,MAAM,iBAAiB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/analysis/html/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EACL,kBAAkB,EAClB,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,GAC1B,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,8BAA8B,EAAE,MAAM,mCAAmC,CAAC;AAEnF,OAAO,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AAErE,OAAO,EACL,gBAAgB,EAChB,KAAK,iBAAiB,GACvB,MAAM,iBAAiB,CAAC"}
|
|
@@ -8,5 +8,6 @@
|
|
|
8
8
|
*/
|
|
9
9
|
export { extractHtmlContent, } from './html-extractor.js';
|
|
10
10
|
export { runHtmlAttributeSecurityChecks } from './html-attribute-security-pass.js';
|
|
11
|
+
export { runVueTemplateXssChecks } from './vue-template-xss-pass.js';
|
|
11
12
|
export { mergeHtmlResults, } from './html-merge.js';
|
|
12
13
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/analysis/html/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EACL,kBAAkB,GAInB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,8BAA8B,EAAE,MAAM,mCAAmC,CAAC;AAEnF,OAAO,EACL,gBAAgB,GAEjB,MAAM,iBAAiB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/analysis/html/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EACL,kBAAkB,GAInB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,8BAA8B,EAAE,MAAM,mCAAmC,CAAC;AAEnF,OAAO,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AAErE,OAAO,EACL,gBAAgB,GAEjB,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vue Template XSS Pass
|
|
3
|
+
*
|
|
4
|
+
* Sprint 64 (cognium-dev #184, sprint 2 of 2). Synthetic-emission pass
|
|
5
|
+
* that walks the `<template>` subtree of a Vue Single-File Component
|
|
6
|
+
* looking for attribute bindings that write raw HTML
|
|
7
|
+
* (`v-html`, `v-bind:innerHTML`, `:innerHTML`, `v-bind:outerHTML`,
|
|
8
|
+
* `:outerHTML`). For each match it parses the RHS expression for JS
|
|
9
|
+
* identifiers and resolves them against the set of tainted variable
|
|
10
|
+
* names extracted from the file's `<script>` blocks. A match emits a
|
|
11
|
+
* `vue-template-xss` finding (CWE-79).
|
|
12
|
+
*
|
|
13
|
+
* The pass is invoked from `analyzeMarkupFile()` only when
|
|
14
|
+
* `language === 'vue'`; plain `.html` files skip it because raw-HTML
|
|
15
|
+
* sinks there land in `javascript_dom_xss.yaml` via the JS pipeline.
|
|
16
|
+
*
|
|
17
|
+
* Pattern mirrors `html-attribute-security-pass.ts`: walk the tree
|
|
18
|
+
* iteratively, emit `SastFinding` objects directly, no taint matcher,
|
|
19
|
+
* no sink-config YAML.
|
|
20
|
+
*/
|
|
21
|
+
import type { Node as SyntaxNode } from 'web-tree-sitter';
|
|
22
|
+
import type { SastFinding } from '../../types/index.js';
|
|
23
|
+
import type { ScriptBlockResult } from './html-merge.js';
|
|
24
|
+
/**
|
|
25
|
+
* Walk the parse tree for a Vue SFC and emit `vue-template-xss`
|
|
26
|
+
* findings for dangerous template-attribute bindings that reference
|
|
27
|
+
* tainted identifiers from the file's script blocks.
|
|
28
|
+
*
|
|
29
|
+
* @param rootNode - tree-sitter-html root node of the .vue file
|
|
30
|
+
* @param filePath - path to the .vue file (for finding `file` field)
|
|
31
|
+
* @param scriptResults - per-script-block IRs produced earlier in
|
|
32
|
+
* `analyzeMarkupFile()`. Each block's `ir.taint` + `ir.dfg` is the
|
|
33
|
+
* source of truth for which identifiers are tainted.
|
|
34
|
+
*/
|
|
35
|
+
export declare function runVueTemplateXssChecks(rootNode: SyntaxNode, filePath: string, scriptResults: ScriptBlockResult[]): SastFinding[];
|
|
36
|
+
//# sourceMappingURL=vue-template-xss-pass.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vue-template-xss-pass.d.ts","sourceRoot":"","sources":["../../../src/analysis/html/vue-template-xss-pass.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,IAAI,IAAI,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAiCzD;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,UAAU,EACpB,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,iBAAiB,EAAE,GACjC,WAAW,EAAE,CAqBf"}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vue Template XSS Pass
|
|
3
|
+
*
|
|
4
|
+
* Sprint 64 (cognium-dev #184, sprint 2 of 2). Synthetic-emission pass
|
|
5
|
+
* that walks the `<template>` subtree of a Vue Single-File Component
|
|
6
|
+
* looking for attribute bindings that write raw HTML
|
|
7
|
+
* (`v-html`, `v-bind:innerHTML`, `:innerHTML`, `v-bind:outerHTML`,
|
|
8
|
+
* `:outerHTML`). For each match it parses the RHS expression for JS
|
|
9
|
+
* identifiers and resolves them against the set of tainted variable
|
|
10
|
+
* names extracted from the file's `<script>` blocks. A match emits a
|
|
11
|
+
* `vue-template-xss` finding (CWE-79).
|
|
12
|
+
*
|
|
13
|
+
* The pass is invoked from `analyzeMarkupFile()` only when
|
|
14
|
+
* `language === 'vue'`; plain `.html` files skip it because raw-HTML
|
|
15
|
+
* sinks there land in `javascript_dom_xss.yaml` via the JS pipeline.
|
|
16
|
+
*
|
|
17
|
+
* Pattern mirrors `html-attribute-security-pass.ts`: walk the tree
|
|
18
|
+
* iteratively, emit `SastFinding` objects directly, no taint matcher,
|
|
19
|
+
* no sink-config YAML.
|
|
20
|
+
*/
|
|
21
|
+
import { findChildByType, stripQuotes } from './html-extractor.js';
|
|
22
|
+
/**
|
|
23
|
+
* Vue template attribute bindings that write raw HTML.
|
|
24
|
+
* v-text is intentionally excluded — it writes via textContent which
|
|
25
|
+
* the browser escapes, so it's safe.
|
|
26
|
+
*/
|
|
27
|
+
const DANGEROUS_BINDINGS = new Set([
|
|
28
|
+
'v-html',
|
|
29
|
+
'v-bind:innerhtml',
|
|
30
|
+
':innerhtml',
|
|
31
|
+
'v-bind:outerhtml',
|
|
32
|
+
':outerhtml',
|
|
33
|
+
]);
|
|
34
|
+
/**
|
|
35
|
+
* JS identifier matcher used to extract referenced names from the RHS
|
|
36
|
+
* expression of a binding. Chained access like `state.user.name`
|
|
37
|
+
* yields each identifier in turn (`state`, `user`, `name`); the first
|
|
38
|
+
* one that resolves to a tainted variable wins.
|
|
39
|
+
*/
|
|
40
|
+
const ID_RE = /\b([A-Za-z_$][A-Za-z0-9_$]*)\b/g;
|
|
41
|
+
/**
|
|
42
|
+
* JS keywords / safe globals that must never count as taint candidates.
|
|
43
|
+
*/
|
|
44
|
+
const RESERVED = new Set([
|
|
45
|
+
'true', 'false', 'null', 'undefined', 'this', 'new', 'typeof',
|
|
46
|
+
'void', 'delete', 'instanceof', 'in', 'of',
|
|
47
|
+
'Math', 'Number', 'String', 'Boolean', 'Array', 'Object', 'JSON',
|
|
48
|
+
]);
|
|
49
|
+
/**
|
|
50
|
+
* Walk the parse tree for a Vue SFC and emit `vue-template-xss`
|
|
51
|
+
* findings for dangerous template-attribute bindings that reference
|
|
52
|
+
* tainted identifiers from the file's script blocks.
|
|
53
|
+
*
|
|
54
|
+
* @param rootNode - tree-sitter-html root node of the .vue file
|
|
55
|
+
* @param filePath - path to the .vue file (for finding `file` field)
|
|
56
|
+
* @param scriptResults - per-script-block IRs produced earlier in
|
|
57
|
+
* `analyzeMarkupFile()`. Each block's `ir.taint` + `ir.dfg` is the
|
|
58
|
+
* source of truth for which identifiers are tainted.
|
|
59
|
+
*/
|
|
60
|
+
export function runVueTemplateXssChecks(rootNode, filePath, scriptResults) {
|
|
61
|
+
const taintedNames = collectTaintedNames(scriptResults);
|
|
62
|
+
if (taintedNames.size === 0)
|
|
63
|
+
return [];
|
|
64
|
+
const findings = [];
|
|
65
|
+
// Iterative DFS — guard against stack overflow on deep templates,
|
|
66
|
+
// matches the pattern used in html-attribute-security-pass.ts.
|
|
67
|
+
const stack = [rootNode];
|
|
68
|
+
while (stack.length > 0) {
|
|
69
|
+
const node = stack.pop();
|
|
70
|
+
if (node.type === 'start_tag' || node.type === 'self_closing_tag') {
|
|
71
|
+
checkElementBindings(node, filePath, taintedNames, findings);
|
|
72
|
+
}
|
|
73
|
+
for (let i = node.childCount - 1; i >= 0; i--) {
|
|
74
|
+
const c = node.child(i);
|
|
75
|
+
if (c)
|
|
76
|
+
stack.push(c);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return findings;
|
|
80
|
+
}
|
|
81
|
+
function checkElementBindings(tag, filePath, tainted, out) {
|
|
82
|
+
for (let i = 0; i < tag.childCount; i++) {
|
|
83
|
+
const attr = tag.child(i);
|
|
84
|
+
if (!attr || attr.type !== 'attribute')
|
|
85
|
+
continue;
|
|
86
|
+
const nameNode = findChildByType(attr, 'attribute_name');
|
|
87
|
+
if (!nameNode)
|
|
88
|
+
continue;
|
|
89
|
+
const lcName = nameNode.text.toLowerCase();
|
|
90
|
+
if (!DANGEROUS_BINDINGS.has(lcName))
|
|
91
|
+
continue;
|
|
92
|
+
const valueNode = findChildByType(attr, 'quoted_attribute_value') ??
|
|
93
|
+
findChildByType(attr, 'attribute_value');
|
|
94
|
+
if (!valueNode)
|
|
95
|
+
continue;
|
|
96
|
+
const rhs = stripQuotes(valueNode.text);
|
|
97
|
+
if (!rhs.trim())
|
|
98
|
+
continue;
|
|
99
|
+
const matched = matchTaint(rhs, tainted);
|
|
100
|
+
if (!matched)
|
|
101
|
+
continue;
|
|
102
|
+
const line = nameNode.startPosition.row + 1;
|
|
103
|
+
out.push({
|
|
104
|
+
id: `vue-template-xss-${filePath}-${line}-${lcName}`,
|
|
105
|
+
pass: 'vue-template-xss',
|
|
106
|
+
category: 'security',
|
|
107
|
+
rule_id: 'vue-template-xss',
|
|
108
|
+
cwe: 'CWE-79',
|
|
109
|
+
severity: 'high',
|
|
110
|
+
level: 'error',
|
|
111
|
+
message: `Vue template attribute "${nameNode.text}" binds tainted identifier "${matched}" — writes raw HTML (XSS risk).`,
|
|
112
|
+
file: filePath,
|
|
113
|
+
line,
|
|
114
|
+
snippet: `${nameNode.text}="${rhs}"`,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function matchTaint(rhs, tainted) {
|
|
119
|
+
ID_RE.lastIndex = 0;
|
|
120
|
+
let m;
|
|
121
|
+
while ((m = ID_RE.exec(rhs)) !== null) {
|
|
122
|
+
const id = m[1];
|
|
123
|
+
if (RESERVED.has(id))
|
|
124
|
+
continue;
|
|
125
|
+
if (tainted.has(id))
|
|
126
|
+
return id;
|
|
127
|
+
}
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Build the set of identifier names that should be treated as tainted
|
|
132
|
+
* when they appear on the RHS of a template binding. A name qualifies
|
|
133
|
+
* if either:
|
|
134
|
+
* 1. A `DFGDef` at the same line as a `TaintSource` carries it (the
|
|
135
|
+
* variable directly captures a source expression), or
|
|
136
|
+
* 2. It appears in the `path` of any `TaintFlowInfo` (so
|
|
137
|
+
* re-assignments like `const y = x;` where `x` is tainted carry
|
|
138
|
+
* the taint through to `y`), or
|
|
139
|
+
* 3. The `TaintSource.variable` field is populated directly.
|
|
140
|
+
*/
|
|
141
|
+
function collectTaintedNames(blocks) {
|
|
142
|
+
const names = new Set();
|
|
143
|
+
for (const { ir } of blocks) {
|
|
144
|
+
const sourceLines = new Set();
|
|
145
|
+
for (const source of ir.taint.sources) {
|
|
146
|
+
sourceLines.add(source.line);
|
|
147
|
+
if (source.variable)
|
|
148
|
+
names.add(source.variable);
|
|
149
|
+
}
|
|
150
|
+
for (const def of ir.dfg.defs) {
|
|
151
|
+
if (sourceLines.has(def.line) && def.variable)
|
|
152
|
+
names.add(def.variable);
|
|
153
|
+
}
|
|
154
|
+
for (const flow of ir.taint.flows ?? []) {
|
|
155
|
+
for (const step of flow.path ?? []) {
|
|
156
|
+
if (step.variable)
|
|
157
|
+
names.add(step.variable);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return names;
|
|
162
|
+
}
|
|
163
|
+
//# sourceMappingURL=vue-template-xss-pass.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vue-template-xss-pass.js","sourceRoot":"","sources":["../../../src/analysis/html/vue-template-xss-pass.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"language-sources-pass.d.ts","sourceRoot":"","sources":["../../../src/analysis/passes/language-sources-pass.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,cAAc,EAAwB,WAAW,EAAO,MAAM,sBAAsB,CAAC;AAC3H,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAqB9E,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;IA0C/B,CAAC;AA4BF,MAAM,WAAW,qBAAqB;IACpC,iBAAiB,EAAE,WAAW,EAAE,CAAC;IACjC,eAAe,EAAE,SAAS,EAAE,CAAC;IAC7B;;;;OAIG;IACH,oBAAoB,EAAE,cAAc,EAAE,CAAC;IACvC;;;OAGG;IACH,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC;;;OAGG;IACH,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC7B;;;OAGG;IACH,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAMD,qBAAa,mBAAoB,YAAW,YAAY,CAAC,qBAAqB,CAAC;IAC7E,QAAQ,CAAC,IAAI,sBAAsB;IACnC,QAAQ,CAAC,QAAQ,EAAG,UAAU,CAAU;IAExC,GAAG,CAAC,GAAG,EAAE,WAAW,GAAG,qBAAqB;CAuH7C;AAgkBD,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAkG9E;AAED,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,MAAM,EAAE,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,CAwC5G;AAED,wBAAgB,iCAAiC,CAC/C,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAC/B,KAAK,CAAC;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAoBjD;AAoFD,wBAAgB,0BAA0B,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAmBpG;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,GACpB,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAmCrB;
|
|
1
|
+
{"version":3,"file":"language-sources-pass.d.ts","sourceRoot":"","sources":["../../../src/analysis/passes/language-sources-pass.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,cAAc,EAAwB,WAAW,EAAO,MAAM,sBAAsB,CAAC;AAC3H,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAqB9E,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;IA0C/B,CAAC;AA4BF,MAAM,WAAW,qBAAqB;IACpC,iBAAiB,EAAE,WAAW,EAAE,CAAC;IACjC,eAAe,EAAE,SAAS,EAAE,CAAC;IAC7B;;;;OAIG;IACH,oBAAoB,EAAE,cAAc,EAAE,CAAC;IACvC;;;OAGG;IACH,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC;;;OAGG;IACH,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC7B;;;OAGG;IACH,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAMD,qBAAa,mBAAoB,YAAW,YAAY,CAAC,qBAAqB,CAAC;IAC7E,QAAQ,CAAC,IAAI,sBAAsB;IACnC,QAAQ,CAAC,QAAQ,EAAG,UAAU,CAAU;IAExC,GAAG,CAAC,GAAG,EAAE,WAAW,GAAG,qBAAqB;CAuH7C;AAgkBD,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAkG9E;AAED,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,MAAM,EAAE,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,CAwC5G;AAED,wBAAgB,iCAAiC,CAC/C,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAC/B,KAAK,CAAC;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAoBjD;AAoFD,wBAAgB,0BAA0B,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAmBpG;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,GACpB,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAmCrB;AA+MD,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,WAAW,EAAE,CAmHvF"}
|
|
@@ -1240,9 +1240,49 @@ function findBashTaintSources(sourceCode, dfg) {
|
|
|
1240
1240
|
// Bash/Shell pattern-based findings
|
|
1241
1241
|
// ---------------------------------------------------------------------------
|
|
1242
1242
|
const BASH_CREDENTIAL_PATTERN = /^(.*?)(password|passwd|secret|api_?key|token|auth_token|private_key|access_key)\s*=\s*["']?([^"'\s$][^"'\s]*)["']?\s*$/i;
|
|
1243
|
+
// Sprint 65 (#216): suppress predictable-temp-file when the same /tmp/X path
|
|
1244
|
+
// is verified by a checksum tool later in the script. This breaks the TOCTOU
|
|
1245
|
+
// substitution risk that justifies the warning.
|
|
1246
|
+
const BASH_CHECKSUM_VERIFY_PATTERN = /\b(?:sha(?:1|224|256|384|512)sum|md5sum|cksum|b2sum)\s+(?:-c\b|--check\b)/;
|
|
1247
|
+
function collectChecksumVerifiedTmpPaths(lines) {
|
|
1248
|
+
const verified = new Set();
|
|
1249
|
+
for (const line of lines) {
|
|
1250
|
+
if (!BASH_CHECKSUM_VERIFY_PATTERN.test(line))
|
|
1251
|
+
continue;
|
|
1252
|
+
const matches = line.match(/\/tmp\/[^\s"'$|`]+/g);
|
|
1253
|
+
if (!matches)
|
|
1254
|
+
continue;
|
|
1255
|
+
for (const p of matches)
|
|
1256
|
+
verified.add(p);
|
|
1257
|
+
}
|
|
1258
|
+
return verified;
|
|
1259
|
+
}
|
|
1260
|
+
// Sprint 65 (#216): suppress predictable-temp-file when the /tmp file is the
|
|
1261
|
+
// WRITE TARGET of an archive command (tar/zip/gzip/7z/bzip2/xz). The file is
|
|
1262
|
+
// being produced, not consumed, so there is no TOCTOU read race.
|
|
1263
|
+
const BASH_ARCHIVE_EXT_PATTERN = /\.(?:tgz|tar\.gz|tar\.bz2|tar\.xz|tar|tbz2|txz|zip|gz|bz2|xz|7z)$/i;
|
|
1264
|
+
function isArchiveOutputContext(line, tmpRel) {
|
|
1265
|
+
if (!BASH_ARCHIVE_EXT_PATTERN.test(tmpRel))
|
|
1266
|
+
return false;
|
|
1267
|
+
// tar with a create flag (c) anywhere in the flag cluster: tar czf, tar -cf, tar cvjf, etc.
|
|
1268
|
+
if (/\btar\b/.test(line) && /(?:^|\s)-?[A-Za-z]*c[A-Za-z]*\b/.test(line))
|
|
1269
|
+
return true;
|
|
1270
|
+
if (/\bzip\b/.test(line) && !/\bunzip\b/.test(line))
|
|
1271
|
+
return true;
|
|
1272
|
+
if (/\bgzip\b/.test(line) && /(?:-c\b|--stdout\b|>)/.test(line))
|
|
1273
|
+
return true;
|
|
1274
|
+
if (/\bbzip2\b/.test(line) && /(?:-c\b|--stdout\b|>)/.test(line))
|
|
1275
|
+
return true;
|
|
1276
|
+
if (/\bxz\b/.test(line) && /(?:-c\b|--stdout\b|>)/.test(line))
|
|
1277
|
+
return true;
|
|
1278
|
+
if (/\b7z\s+a\b/.test(line))
|
|
1279
|
+
return true;
|
|
1280
|
+
return false;
|
|
1281
|
+
}
|
|
1243
1282
|
export function findBashPatternFindings(sourceCode, file) {
|
|
1244
1283
|
const findings = [];
|
|
1245
1284
|
const lines = sourceCode.split('\n');
|
|
1285
|
+
const checksumVerifiedTmpPaths = collectChecksumVerifiedTmpPaths(lines);
|
|
1246
1286
|
for (let i = 0; i < lines.length; i++) {
|
|
1247
1287
|
const line = lines[i];
|
|
1248
1288
|
const trimmed = line.trim();
|
|
@@ -1290,19 +1330,27 @@ export function findBashPatternFindings(sourceCode, file) {
|
|
|
1290
1330
|
// 3. Predictable /tmp file (no variable in path)
|
|
1291
1331
|
const tmpMatch = trimmed.match(/\/tmp\/([^\s"'$]+)/);
|
|
1292
1332
|
if (tmpMatch && !/mktemp/.test(trimmed)) {
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1333
|
+
const tmpRel = tmpMatch[1];
|
|
1334
|
+
const tmpPath = `/tmp/${tmpRel}`;
|
|
1335
|
+
// Sprint 65 (#216): suppress when path is checksum-verified or is an
|
|
1336
|
+
// archive WRITE target. Both shapes are benign-by-construction.
|
|
1337
|
+
const isChecksumVerified = checksumVerifiedTmpPaths.has(tmpPath);
|
|
1338
|
+
const isArchiveOutput = isArchiveOutputContext(trimmed, tmpRel);
|
|
1339
|
+
if (!isChecksumVerified && !isArchiveOutput) {
|
|
1340
|
+
findings.push({
|
|
1341
|
+
id: `predictable-temp-file-${file}-${lineNumber}`,
|
|
1342
|
+
pass: 'language-sources',
|
|
1343
|
+
category: 'security',
|
|
1344
|
+
rule_id: 'predictable-temp-file',
|
|
1345
|
+
cwe: 'CWE-377',
|
|
1346
|
+
severity: 'medium',
|
|
1347
|
+
level: 'warning',
|
|
1348
|
+
message: `Predictable temp file: /tmp/${tmpRel}. Use mktemp instead`,
|
|
1349
|
+
file,
|
|
1350
|
+
line: lineNumber,
|
|
1351
|
+
snippet: trimmed.substring(0, 80),
|
|
1352
|
+
});
|
|
1353
|
+
}
|
|
1306
1354
|
}
|
|
1307
1355
|
// 4. Insecure file permissions: chmod 777 or chmod 666
|
|
1308
1356
|
if (/\bchmod\b/.test(trimmed) && /\b(777|666)\b/.test(trimmed)) {
|