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.
@@ -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;AA8KD,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,WAAW,EAAE,CA0GvF"}
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
- findings.push({
1294
- id: `predictable-temp-file-${file}-${lineNumber}`,
1295
- pass: 'language-sources',
1296
- category: 'security',
1297
- rule_id: 'predictable-temp-file',
1298
- cwe: 'CWE-377',
1299
- severity: 'medium',
1300
- level: 'warning',
1301
- message: `Predictable temp file: /tmp/${tmpMatch[1]}. Use mktemp instead`,
1302
- file,
1303
- line: lineNumber,
1304
- snippet: trimmed.substring(0, 80),
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)) {