circle-ir 4.15.0 → 4.16.1

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 (135) hide show
  1. package/configs/sinks/golang.json +4 -4
  2. package/dist/analysis/config-loader.d.ts.map +1 -1
  3. package/dist/analysis/config-loader.js +11 -7
  4. package/dist/analysis/config-loader.js.map +1 -1
  5. package/dist/analysis/constant-caller-gate.d.ts +39 -0
  6. package/dist/analysis/constant-caller-gate.d.ts.map +1 -0
  7. package/dist/analysis/constant-caller-gate.js +298 -0
  8. package/dist/analysis/constant-caller-gate.js.map +1 -0
  9. package/dist/analysis/constant-propagation/evaluator.d.ts.map +1 -1
  10. package/dist/analysis/constant-propagation/evaluator.js +3 -1
  11. package/dist/analysis/constant-propagation/evaluator.js.map +1 -1
  12. package/dist/analysis/constant-propagation/patterns.d.ts +19 -0
  13. package/dist/analysis/constant-propagation/patterns.d.ts.map +1 -1
  14. package/dist/analysis/constant-propagation/patterns.js +33 -0
  15. package/dist/analysis/constant-propagation/patterns.js.map +1 -1
  16. package/dist/analysis/constant-propagation/propagator.d.ts +59 -0
  17. package/dist/analysis/constant-propagation/propagator.d.ts.map +1 -1
  18. package/dist/analysis/constant-propagation/propagator.js +396 -49
  19. package/dist/analysis/constant-propagation/propagator.js.map +1 -1
  20. package/dist/analysis/constant-propagation/types.d.ts +9 -0
  21. package/dist/analysis/constant-propagation/types.d.ts.map +1 -1
  22. package/dist/analysis/findings.d.ts.map +1 -1
  23. package/dist/analysis/findings.js +134 -0
  24. package/dist/analysis/findings.js.map +1 -1
  25. package/dist/analysis/interprocedural.d.ts.map +1 -1
  26. package/dist/analysis/interprocedural.js +127 -3
  27. package/dist/analysis/interprocedural.js.map +1 -1
  28. package/dist/analysis/java-array-element-keys.d.ts +14 -0
  29. package/dist/analysis/java-array-element-keys.d.ts.map +1 -0
  30. package/dist/analysis/java-array-element-keys.js +36 -0
  31. package/dist/analysis/java-array-element-keys.js.map +1 -0
  32. package/dist/analysis/js-crlf-strip.d.ts +26 -0
  33. package/dist/analysis/js-crlf-strip.d.ts.map +1 -0
  34. package/dist/analysis/js-crlf-strip.js +244 -0
  35. package/dist/analysis/js-crlf-strip.js.map +1 -0
  36. package/dist/analysis/passes/csharp-sanitizer-credit.d.ts +25 -0
  37. package/dist/analysis/passes/csharp-sanitizer-credit.d.ts.map +1 -1
  38. package/dist/analysis/passes/csharp-sanitizer-credit.js +157 -12
  39. package/dist/analysis/passes/csharp-sanitizer-credit.js.map +1 -1
  40. package/dist/analysis/passes/language-sources-pass.d.ts +38 -3
  41. package/dist/analysis/passes/language-sources-pass.d.ts.map +1 -1
  42. package/dist/analysis/passes/language-sources-pass.js +1234 -127
  43. package/dist/analysis/passes/language-sources-pass.js.map +1 -1
  44. package/dist/analysis/passes/param-source-flow-gate-pass.d.ts.map +1 -1
  45. package/dist/analysis/passes/param-source-flow-gate-pass.js +53 -1
  46. package/dist/analysis/passes/param-source-flow-gate-pass.js.map +1 -1
  47. package/dist/analysis/passes/plaintext-password-storage-pass.d.ts +6 -0
  48. package/dist/analysis/passes/plaintext-password-storage-pass.d.ts.map +1 -1
  49. package/dist/analysis/passes/plaintext-password-storage-pass.js +160 -0
  50. package/dist/analysis/passes/plaintext-password-storage-pass.js.map +1 -1
  51. package/dist/analysis/passes/scan-secrets-pass.d.ts +1 -1
  52. package/dist/analysis/passes/scan-secrets-pass.d.ts.map +1 -1
  53. package/dist/analysis/passes/scan-secrets-pass.js +7 -1
  54. package/dist/analysis/passes/scan-secrets-pass.js.map +1 -1
  55. package/dist/analysis/passes/sink-filter-pass.d.ts +1 -1
  56. package/dist/analysis/passes/sink-filter-pass.d.ts.map +1 -1
  57. package/dist/analysis/passes/sink-filter-pass.js +326 -34
  58. package/dist/analysis/passes/sink-filter-pass.js.map +1 -1
  59. package/dist/analysis/passes/taint-matcher-pass.d.ts.map +1 -1
  60. package/dist/analysis/passes/taint-matcher-pass.js +2 -2
  61. package/dist/analysis/passes/taint-matcher-pass.js.map +1 -1
  62. package/dist/analysis/passes/taint-propagation-pass.d.ts.map +1 -1
  63. package/dist/analysis/passes/taint-propagation-pass.js +1376 -58
  64. package/dist/analysis/passes/taint-propagation-pass.js.map +1 -1
  65. package/dist/analysis/passes/weak-hash-pass.d.ts +6 -0
  66. package/dist/analysis/passes/weak-hash-pass.d.ts.map +1 -1
  67. package/dist/analysis/passes/weak-hash-pass.js +191 -4
  68. package/dist/analysis/passes/weak-hash-pass.js.map +1 -1
  69. package/dist/analysis/passes/weak-random-pass.d.ts +5 -0
  70. package/dist/analysis/passes/weak-random-pass.d.ts.map +1 -1
  71. package/dist/analysis/passes/weak-random-pass.js +30 -0
  72. package/dist/analysis/passes/weak-random-pass.js.map +1 -1
  73. package/dist/analysis/project-callee-escape-gate.d.ts +40 -0
  74. package/dist/analysis/project-callee-escape-gate.d.ts.map +1 -0
  75. package/dist/analysis/project-callee-escape-gate.js +243 -0
  76. package/dist/analysis/project-callee-escape-gate.js.map +1 -0
  77. package/dist/analysis/rust-literal-guard-rebind.d.ts +34 -0
  78. package/dist/analysis/rust-literal-guard-rebind.d.ts.map +1 -0
  79. package/dist/analysis/rust-literal-guard-rebind.js +197 -0
  80. package/dist/analysis/rust-literal-guard-rebind.js.map +1 -0
  81. package/dist/analysis/rust-literal-guard.d.ts +33 -0
  82. package/dist/analysis/rust-literal-guard.d.ts.map +1 -0
  83. package/dist/analysis/rust-literal-guard.js +422 -0
  84. package/dist/analysis/rust-literal-guard.js.map +1 -0
  85. package/dist/analysis/scalar-derivation.d.ts +8 -0
  86. package/dist/analysis/scalar-derivation.d.ts.map +1 -1
  87. package/dist/analysis/scalar-derivation.js +172 -0
  88. package/dist/analysis/scalar-derivation.js.map +1 -1
  89. package/dist/analysis/taint-matcher.d.ts +2 -2
  90. package/dist/analysis/taint-matcher.d.ts.map +1 -1
  91. package/dist/analysis/taint-matcher.js +531 -31
  92. package/dist/analysis/taint-matcher.js.map +1 -1
  93. package/dist/analysis/taint-propagation.d.ts +26 -1
  94. package/dist/analysis/taint-propagation.d.ts.map +1 -1
  95. package/dist/analysis/taint-propagation.js +234 -13
  96. package/dist/analysis/taint-propagation.js.map +1 -1
  97. package/dist/analyzer.d.ts +16 -2
  98. package/dist/analyzer.d.ts.map +1 -1
  99. package/dist/analyzer.js +13 -3
  100. package/dist/analyzer.js.map +1 -1
  101. package/dist/browser/circle-ir.js +24432 -19928
  102. package/dist/core/circle-ir-core.cjs +4936 -3575
  103. package/dist/core/circle-ir-core.js +4936 -3575
  104. package/dist/core/extractors/calls.d.ts +12 -1
  105. package/dist/core/extractors/calls.d.ts.map +1 -1
  106. package/dist/core/extractors/calls.js +332 -22
  107. package/dist/core/extractors/calls.js.map +1 -1
  108. package/dist/core/extractors/dfg.js +102 -10
  109. package/dist/core/extractors/dfg.js.map +1 -1
  110. package/dist/core/extractors/types.d.ts +5 -1
  111. package/dist/core/extractors/types.d.ts.map +1 -1
  112. package/dist/core/extractors/types.js +62 -0
  113. package/dist/core/extractors/types.js.map +1 -1
  114. package/dist/graph/analysis-pass.d.ts +6 -1
  115. package/dist/graph/analysis-pass.d.ts.map +1 -1
  116. package/dist/graph/analysis-pass.js +2 -1
  117. package/dist/graph/analysis-pass.js.map +1 -1
  118. package/dist/navigation/navigation-index.d.ts +134 -14
  119. package/dist/navigation/navigation-index.d.ts.map +1 -1
  120. package/dist/navigation/navigation-index.js +1158 -104
  121. package/dist/navigation/navigation-index.js.map +1 -1
  122. package/dist/navigation/types.d.ts +59 -2
  123. package/dist/navigation/types.d.ts.map +1 -1
  124. package/dist/resolution/cross-file.d.ts +9 -0
  125. package/dist/resolution/cross-file.d.ts.map +1 -1
  126. package/dist/resolution/cross-file.js +102 -43
  127. package/dist/resolution/cross-file.js.map +1 -1
  128. package/dist/resolution/type-hierarchy.d.ts +3 -0
  129. package/dist/resolution/type-hierarchy.d.ts.map +1 -1
  130. package/dist/resolution/type-hierarchy.js +45 -1
  131. package/dist/resolution/type-hierarchy.js.map +1 -1
  132. package/dist/types/index.d.ts +33 -0
  133. package/dist/types/index.d.ts.map +1 -1
  134. package/dist/types/index.js.map +1 -1
  135. package/package.json +1 -1
@@ -0,0 +1,244 @@
1
+ /**
2
+ * Whether a JavaScript / TypeScript `String.replace` chain strips CR and LF
3
+ * (cognium-dev#562, the JS half of the Java rule in `java-crlf-strip.ts`).
4
+ *
5
+ * The decision is made by running the chain's own literal patterns against a
6
+ * short probe string, so a regex is judged by what it matches rather than by how
7
+ * it is spelled (`/[\r\n]/g`, `/\r|\n/g`, `/\s+/g` and `/[\x00-\x1f]/g` all
8
+ * strip both). Only literal arguments are understood: a pattern held in a
9
+ * variable, a `new RegExp(...)`, or a replacement that is a function or a
10
+ * non-literal makes the whole chain `undecided`, and the caller keeps the credit
11
+ * the class-less `replace` row gave before.
12
+ */
13
+ const MAX_PATTERN_LENGTH = 200;
14
+ /** String methods that may follow a replace in a judged chain without changing its CR / LF coverage. */
15
+ const NEUTRAL_AFTER_REPLACE = new Set([
16
+ 'replace', 'replaceAll', 'trim', 'trimStart', 'trimEnd', 'toString',
17
+ 'toLowerCase', 'toUpperCase', 'slice', 'substring',
18
+ ]);
19
+ /** Probe text: lone and doubled CR / LF, a CRLF pair and neutral characters. */
20
+ const PROBE = 'a\r\rb\n\nc\r\nd\n\re x\t';
21
+ function readQuoted(s, i) {
22
+ const q = s[i];
23
+ if (q !== '"' && q !== "'" && q !== '`')
24
+ return null;
25
+ let out = '';
26
+ for (let j = i + 1; j < s.length; j++) {
27
+ const c = s[j];
28
+ if (c === q)
29
+ return { value: out, end: j + 1 };
30
+ if (q === '`' && c === '$' && s[j + 1] === '{')
31
+ return null;
32
+ if (c === '\\') {
33
+ const n = s[j + 1];
34
+ if (n === undefined)
35
+ return null;
36
+ switch (n) {
37
+ case 'n':
38
+ out += '\n';
39
+ j++;
40
+ continue;
41
+ case 'r':
42
+ out += '\r';
43
+ j++;
44
+ continue;
45
+ case 't':
46
+ out += '\t';
47
+ j++;
48
+ continue;
49
+ case '0':
50
+ out += '\0';
51
+ j++;
52
+ continue;
53
+ case 'x': {
54
+ const h = /^[0-9a-fA-F]{2}/.exec(s.slice(j + 2, j + 4));
55
+ if (!h)
56
+ return null;
57
+ out += String.fromCharCode(parseInt(h[0], 16));
58
+ j += 3;
59
+ continue;
60
+ }
61
+ case 'u': {
62
+ const h = /^[0-9a-fA-F]{4}/.exec(s.slice(j + 2, j + 6));
63
+ if (!h)
64
+ return null;
65
+ out += String.fromCharCode(parseInt(h[0], 16));
66
+ j += 5;
67
+ continue;
68
+ }
69
+ default:
70
+ out += n;
71
+ j++;
72
+ continue;
73
+ }
74
+ }
75
+ if (c === '\n' && q !== '`')
76
+ return null;
77
+ out += c;
78
+ }
79
+ return null;
80
+ }
81
+ function readRegexLiteral(s, i) {
82
+ if (s[i] !== '/')
83
+ return null;
84
+ let inClass = false;
85
+ for (let j = i + 1; j < s.length; j++) {
86
+ const c = s[j];
87
+ if (c === '\n')
88
+ return null;
89
+ if (c === '\\') {
90
+ j++;
91
+ continue;
92
+ }
93
+ if (inClass) {
94
+ if (c === ']')
95
+ inClass = false;
96
+ continue;
97
+ }
98
+ if (c === '[') {
99
+ inClass = true;
100
+ continue;
101
+ }
102
+ if (c === '/') {
103
+ const flags = /^[dgimsuvy]*/.exec(s.slice(j + 1))[0];
104
+ return { source: s.slice(i + 1, j), flags, end: j + 1 + flags.length };
105
+ }
106
+ }
107
+ return null;
108
+ }
109
+ const skipWs = (s, i) => {
110
+ while (i < s.length && /\s/.test(s[i]))
111
+ i++;
112
+ return i;
113
+ };
114
+ /**
115
+ * Parse every `.replace(<literal>, <string literal>)` / `.replaceAll(...)` step
116
+ * of a chain, in source order. Returns null when any step is not literal.
117
+ */
118
+ function parseSteps(chainText) {
119
+ const steps = [];
120
+ const callRe = /\.\s*(replaceAll|replace)\s*\(/g;
121
+ let m;
122
+ while ((m = callRe.exec(chainText)) !== null) {
123
+ const kind = m[1];
124
+ let i = skipWs(chainText, m.index + m[0].length);
125
+ let pattern;
126
+ const rx = readRegexLiteral(chainText, i);
127
+ if (rx) {
128
+ if (rx.source.length > MAX_PATTERN_LENGTH)
129
+ return null;
130
+ try {
131
+ pattern = new RegExp(rx.source, rx.flags);
132
+ }
133
+ catch {
134
+ return null;
135
+ }
136
+ i = rx.end;
137
+ }
138
+ else {
139
+ const str = readQuoted(chainText, i);
140
+ if (!str)
141
+ return null;
142
+ pattern = str.value;
143
+ i = str.end;
144
+ }
145
+ i = skipWs(chainText, i);
146
+ if (chainText[i] !== ',')
147
+ return null;
148
+ i = skipWs(chainText, i + 1);
149
+ const rep = readQuoted(chainText, i);
150
+ if (!rep)
151
+ return null;
152
+ i = skipWs(chainText, rep.end);
153
+ if (chainText[i] !== ')')
154
+ return null;
155
+ steps.push({ kind, pattern, replacement: rep.value });
156
+ callRe.lastIndex = i + 1;
157
+ }
158
+ return steps;
159
+ }
160
+ function run(steps, input) {
161
+ let s = input;
162
+ try {
163
+ for (const st of steps) {
164
+ if (st.kind === 'replaceAll' && st.pattern instanceof RegExp && !st.pattern.global)
165
+ return null;
166
+ s = st.kind === 'replaceAll'
167
+ ? s.replaceAll(st.pattern, st.replacement)
168
+ : s.replace(st.pattern, st.replacement);
169
+ }
170
+ }
171
+ catch {
172
+ return null;
173
+ }
174
+ return s;
175
+ }
176
+ /**
177
+ * Verdict for the replace chain whose text is `chainText` (the receiver plus the
178
+ * `.method(...)` segments after it).
179
+ */
180
+ export function jsReplaceChainVerdict(chainText) {
181
+ const steps = parseSteps(chainText);
182
+ if (!steps || steps.length === 0)
183
+ return 'undecided';
184
+ // Another transform after the first replace may strip CR / LF on its own
185
+ // (`.split("\r").join("")`): the chain is only judged when every later
186
+ // segment is a replace or a harmless string method.
187
+ const first = chainText.search(/\.\s*(?:replaceAll|replace)\s*\(/);
188
+ for (const seg of chainText.slice(first + 1).matchAll(/\.\s*([A-Za-z_$][\w$]*)\s*\(/g)) {
189
+ if (!NEUTRAL_AFTER_REPLACE.has(seg[1]))
190
+ return 'undecided';
191
+ }
192
+ const out = run(steps, PROBE);
193
+ if (out === null)
194
+ return 'undecided';
195
+ return out.includes('\r') || out.includes('\n') ? 'leaves-crlf' : 'strips-crlf';
196
+ }
197
+ /**
198
+ * The text of the call chain that starts at (line, column): the receiver
199
+ * expression plus the `.method(...)` segments after it, up to the first
200
+ * character that cannot continue the chain at nesting depth 0. String and regex
201
+ * literals are skipped as units. The chain may continue over following lines.
202
+ */
203
+ export function jsChainTextAt(sourceLines, line, column) {
204
+ const text = [(sourceLines[line - 1] ?? '').slice(column), ...sourceLines.slice(line, line + 7)].join('\n');
205
+ let depth = 0;
206
+ let prevSig = '';
207
+ let i = 0;
208
+ for (; i < text.length; i++) {
209
+ const c = text[i];
210
+ if (c === '"' || c === "'" || c === '`') {
211
+ for (i++; i < text.length && text[i] !== c; i++)
212
+ if (text[i] === '\\')
213
+ i++;
214
+ prevSig = c;
215
+ continue;
216
+ }
217
+ if (c === '/' && depth > 0 && (prevSig === '(' || prevSig === ',')) {
218
+ const rx = readRegexLiteral(text, i);
219
+ if (rx) {
220
+ i = rx.end - 1;
221
+ prevSig = '/';
222
+ continue;
223
+ }
224
+ }
225
+ if (c === '(' || c === '[' || c === '{') {
226
+ depth++;
227
+ prevSig = c;
228
+ continue;
229
+ }
230
+ if (c === ')' || c === ']' || c === '}') {
231
+ if (depth === 0)
232
+ break;
233
+ depth--;
234
+ prevSig = c;
235
+ continue;
236
+ }
237
+ if (depth === 0 && !/[\w$.\s]/.test(c))
238
+ break;
239
+ if (!/\s/.test(c))
240
+ prevSig = c;
241
+ }
242
+ return text.slice(0, i);
243
+ }
244
+ //# sourceMappingURL=js-crlf-strip.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"js-crlf-strip.js","sourceRoot":"","sources":["../../src/analysis/js-crlf-strip.ts"],"names":[],"mappings":"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"}
@@ -43,6 +43,11 @@ export declare const CSHARP_ALLOWLIST_STRIP_RE: RegExp;
43
43
  export declare const CSHARP_SANITIZER_RES: Array<{
44
44
  re: RegExp;
45
45
  type: string;
46
+ unlessDeclared?: string;
47
+ }>;
48
+ export declare function csharpSanitizersFor(code: string): ReadonlyArray<{
49
+ re: RegExp;
50
+ type: string;
46
51
  }>;
47
52
  export interface CsMethodRange {
48
53
  name: string;
@@ -54,6 +59,7 @@ export declare class CSharpCredit {
54
59
  private readonly lines;
55
60
  private readonly helperCache;
56
61
  private readonly guardHelperCache;
62
+ private readonly sanitizers;
57
63
  constructor(code: string, methods: ReadonlyArray<CsMethodRange>);
58
64
  /** The innermost method containing `line`, or null. */
59
65
  private methodAt;
@@ -74,6 +80,12 @@ export declare class CSharpCredit {
74
80
  private returnExprs;
75
81
  /** Does `text` apply a sanitizer for `type`, built in or a same-file helper? */
76
82
  sanitizes(type: string, text: string): boolean;
83
+ /** Decode applied to a built-in sanitizer call undoes the encode (cognium-dev#736). */
84
+ private rhsDecodeUndoesSanitizer;
85
+ /** True when a value built from `rhs` is still safe for `type` given `sanitized` so far. */
86
+ private rhsKeepsSanitizerCredit;
87
+ /** Assignments and appends in one statement line, in source order. */
88
+ private creditOpsInStatement;
77
89
  private sanitizedVarsIn;
78
90
  /**
79
91
  * The variables sanitized for `type` at `sinkLine`, judged inside the method
@@ -84,6 +96,19 @@ export declare class CSharpCredit {
84
96
  * and read in another.
85
97
  */
86
98
  sanitizedVarsAt(type: string, sinkLine: number): Set<string>;
99
+ /**
100
+ * Class fields judged across every method that writes them (cognium-dev#808).
101
+ * A field is read in one method and written in others, so each write is
102
+ * judged in its own method with the same in-method credit as a local, and
103
+ * the field is safe for `type` only when every write is: `sanitized` holds
104
+ * the fields with at least one credited write and no uncredited one,
105
+ * `unsanitized` the fields with an uncredited write. A literal write
106
+ * (`_v = "";`, `_v = null;`) is neutral.
107
+ */
108
+ fieldWrites(type: string, fields: ReadonlySet<string>): {
109
+ sanitized: Set<string>;
110
+ unsanitized: Set<string>;
111
+ };
87
112
  /**
88
113
  * True when the sink on `sinkLine` also receives a tainted value that no
89
114
  * sanitizer covers. In `Write(HtmlEncode(a) + b)` the encoder covers `a`
@@ -1 +1 @@
1
- {"version":3,"file":"csharp-sanitizer-credit.d.ts","sourceRoot":"","sources":["../../../src/analysis/passes/csharp-sanitizer-credit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,yBAAyB,QACyE,CAAC;AA8BhH,eAAO,MAAM,oBAAoB,EAAE,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAsBpE,CAAC;AAYF,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAsDD,qBAAa,YAAY;IAKG,OAAO,CAAC,QAAQ,CAAC,OAAO;IAJlD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAW;IACjC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAkC;IAC9D,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAA6C;IAE9E,YAAY,IAAI,EAAE,MAAM,EAAmB,OAAO,EAAE,aAAa,CAAC,aAAa,CAAC,EAG/E;IAED,uDAAuD;IACvD,OAAO,CAAC,QAAQ;IAShB;;;OAGG;IACH,OAAO,CAAC,UAAU;IAiBlB,yDAAyD;IACzD,OAAO,CAAC,gBAAgB;IAkBxB;;;;OAIG;IACH,OAAO,CAAC,OAAO;IAkBf,gFAAgF;IAChF,OAAO,CAAC,WAAW;IAYnB,gFAAgF;IAChF,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAM7C;IAED,OAAO,CAAC,eAAe;IAevB;;;;;;;OAOG;IACH,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAE3D;IAED;;;;;;;;;;;OAWG;IACH,eAAe,CACb,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,EAC7B,KAAK,EAAE,aAAa,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,GACvD,OAAO,CAyBT;IAED;;;;;;;OAOG;IACH,OAAO,CAAC,gBAAgB;IAcxB,gFAAgF;IAChF,OAAO,CAAC,qBAAqB;IAuB7B;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAI7B;IAMD,8EAA8E;IAC9E,OAAO,CAAC,aAAa;IAqBrB,6EAA6E;IAC7E,OAAO,CAAC,WAAW;IAWnB,oFAAoF;IACpF,OAAO,CAAC,QAAQ;IAsBhB;;;;OAIG;IACH,OAAO,CAAC,YAAY;IAoBpB,OAAO,CAAC,QAAQ;IAuBhB;;;;;;;;;;;;;OAaG;IACH,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAuEzD;IAED;;;;;;;;OAQG;IACH,YAAY,CAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CA2BzE;IAED;;;;OAIG;IACH,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,GAAE,aAAa,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAM,GAAG,OAAO,CAYnG;IAMD;;;;;;;OAOG;IACH,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAkB1C;CACF"}
1
+ {"version":3,"file":"csharp-sanitizer-credit.d.ts","sourceRoot":"","sources":["../../../src/analysis/passes/csharp-sanitizer-credit.ts"],"names":[],"mappings":"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"}
@@ -69,6 +69,15 @@ export const CSHARP_SANITIZER_RES = [
69
69
  { re: /\b(?:HtmlEncode|JavaScriptStringEncode)\s*\(/, type: 'xss' },
70
70
  // `HtmlEncoder.Default.Encode(x)` is the ASP.NET Core spelling (#520).
71
71
  { re: /\bHtmlEncoder\s*\.\s*(?:Default\s*\.\s*)?Encode\s*\(/, type: 'xss' },
72
+ // `UrlEncoder.Default.Encode(x)` percent-encodes `<`, `"` and `'` (#564). A
73
+ // project type that declares its own `UrlEncoder` is not the framework's.
74
+ // Qualified `Evil.UrlEncoder` must not match (#736 case 6); only the framework
75
+ // type or an unqualified `UrlEncoder` after `using System.Text.Encodings.Web`.
76
+ {
77
+ re: /(?:System\.Text\.Encodings\.Web\.UrlEncoder|(?<![\w.])UrlEncoder)\s*\.\s*Default\s*\.\s*Encode\s*\(/,
78
+ type: 'xss',
79
+ unlessDeclared: 'UrlEncoder',
80
+ },
72
81
  { re: /\bPath\s*\.\s*GetFileName\s*\(/, type: 'path_traversal' },
73
82
  // XML-escapes `< > & " '`. A value escaped this way cannot close the quoted
74
83
  // literal it is placed in, which is what XPath injection needs.
@@ -88,6 +97,31 @@ export const CSHARP_SANITIZER_RES = [
88
97
  { re: CSHARP_ALLOWLIST_FILTER_RE, type: 'crlf' },
89
98
  ...CSHARP_ONE_WAY_HASH_RES.map(re => ({ re, type: 'external_taint_escape' })),
90
99
  ];
100
+ /**
101
+ * The built-in sanitizers that apply to `code`: those gated on a type name
102
+ * (`unlessDeclared`) are dropped when the file declares a type of that name.
103
+ */
104
+ const FRAMEWORK_URL_ENCODER = 'System.Text.Encodings.Web.UrlEncoder';
105
+ /** True when `UrlEncoder` in source text is not the framework encoder (#736 case 5). */
106
+ function csharpUrlEncoderShadowed(code) {
107
+ if (new RegExp(`\\b(?:class|struct|record|interface|enum)\\s+UrlEncoder\\b`).test(code)) {
108
+ return true;
109
+ }
110
+ const aliasRe = /\busing\s+UrlEncoder\s*=\s*([^;\n]+)\s*;/g;
111
+ for (const m of code.matchAll(aliasRe)) {
112
+ const target = m[1].trim();
113
+ if (target === FRAMEWORK_URL_ENCODER || target.endsWith('.' + FRAMEWORK_URL_ENCODER))
114
+ continue;
115
+ return true;
116
+ }
117
+ return false;
118
+ }
119
+ export function csharpSanitizersFor(code) {
120
+ const declared = (name) => name === 'UrlEncoder'
121
+ ? csharpUrlEncoderShadowed(code)
122
+ : new RegExp(`\\b(?:class|struct|record|interface|enum)\\s+${name}\\b`).test(code);
123
+ return CSHARP_SANITIZER_RES.filter(r => !r.unlessDeclared || !declared(r.unlessDeclared));
124
+ }
91
125
  /**
92
126
  * Sink types for which a value made only of letters, digits, `_` and `-` is
93
127
  * inert: each needs a metacharacter (quote, paren, slash, angle bracket, CR/LF)
@@ -99,7 +133,11 @@ const INERT_WHEN_ALPHANUMERIC = new Set([
99
133
  ]);
100
134
  const IDENT = '[A-Za-z_]\\w*';
101
135
  const ASSIGN_RE = new RegExp(`^\\s*(?:var\\s+|[A-Za-z_][\\w.<>\\[\\]?]*\\s+)?(${IDENT})\\s*=(?!=)\\s*(.+?);?\\s*$`);
136
+ const INNER_ASSIGN_RE = new RegExp(`\\b(${IDENT})\\s*=(?!=)\\s*([^;{}]+)`, 'g');
137
+ const APPEND_RE = new RegExp(`^\\s*(${IDENT})\\s*\\+=\\s*(.+?);?\\s*$`);
102
138
  const ENDS_STATEMENT_RE = /[;{}]\s*(?:\/\/.*)?$/;
139
+ /** Percent-decoding after encode restores raw bytes (cognium-dev#736 case 4). */
140
+ const CSHARP_DECODE_CALL_HEAD_RE = /\b(?:System\.Net\.)?WebUtility\s*\.\s*UrlDecode\s*\(|HttpUtility\s*\.\s*UrlDecode\s*\(|Uri\s*\.\s*UnescapeDataString\s*\(/g;
103
141
  function escapeRegex(s) {
104
142
  return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
105
143
  }
@@ -109,6 +147,30 @@ function withoutStringLiterals(text) {
109
147
  function mentions(text, name) {
110
148
  return new RegExp(`(?<![\\w])${escapeRegex(name)}(?![\\w])`).test(text);
111
149
  }
150
+ /** True when `text` still names a variable after literals are stripped (cognium-dev#736). */
151
+ function hasBareIdentifier(text) {
152
+ if (/\$"/.test(text))
153
+ return true;
154
+ return /\b[A-Za-z_]\w*\b/.test(withoutStringLiterals(text));
155
+ }
156
+ /** Identifier tokens in `text` after literals are stripped (cognium-dev#736 case 2). */
157
+ function bareIdentifiers(text) {
158
+ if (/\$"/.test(text))
159
+ return ['__interpolated__'];
160
+ const stripped = withoutStringLiterals(text);
161
+ const out = [];
162
+ for (const m of stripped.matchAll(/\b([A-Za-z_]\w*)\b/g))
163
+ out.push(m[1]);
164
+ return out;
165
+ }
166
+ /** `""`, `null`, `0`, `string.Empty`: a value that carries nothing from input. */
167
+ function isLiteralValue(rhs) {
168
+ if (/\$"/.test(rhs))
169
+ return false;
170
+ const ids = bareIdentifiers(rhs);
171
+ return ids.every(id => id === 'null' || id === 'true' || id === 'false' || id === 'default')
172
+ || /^\s*(?:string|String)\s*\.\s*Empty\s*$/.test(rhs);
173
+ }
112
174
  /** Text inside the parentheses opening at `open`, or null when unbalanced. */
113
175
  function insideParens(text, open) {
114
176
  let depth = 0;
@@ -151,8 +213,10 @@ export class CSharpCredit {
151
213
  lines;
152
214
  helperCache = new Map();
153
215
  guardHelperCache = new Map();
216
+ sanitizers;
154
217
  constructor(code, methods) {
155
218
  this.methods = methods;
219
+ this.sanitizers = csharpSanitizersFor(code);
156
220
  // CRLF files leave `\r` on every line, which `.` and `$` will not cross.
157
221
  this.lines = code.split(/\r?\n/);
158
222
  }
@@ -190,7 +254,7 @@ export class CSharpCredit {
190
254
  }
191
255
  /** Does `text` apply a built-in sanitizer for `type`? */
192
256
  builtinSanitizes(type, text) {
193
- for (const { re, type: t } of CSHARP_SANITIZER_RES) {
257
+ for (const { re, type: t } of this.sanitizers) {
194
258
  if (t === type && re.test(text))
195
259
  return true;
196
260
  }
@@ -259,19 +323,60 @@ export class CSharpCredit {
259
323
  }
260
324
  return false;
261
325
  }
326
+ /** Decode applied to a built-in sanitizer call undoes the encode (cognium-dev#736). */
327
+ rhsDecodeUndoesSanitizer(type, rhs) {
328
+ for (const m of rhs.matchAll(CSHARP_DECODE_CALL_HEAD_RE)) {
329
+ const open = m.index + m[0].length - 1;
330
+ const inner = insideParens(rhs, open);
331
+ if (inner !== null && this.builtinSanitizes(type, inner))
332
+ return true;
333
+ }
334
+ return false;
335
+ }
336
+ /** True when a value built from `rhs` is still safe for `type` given `sanitized` so far. */
337
+ rhsKeepsSanitizerCredit(type, rhs, sanitized, withHelpers, append) {
338
+ if (!append && this.rhsDecodeUndoesSanitizer(type, rhs))
339
+ return false;
340
+ if (withHelpers ? this.sanitizes(type, rhs) : this.builtinSanitizes(type, rhs))
341
+ return true;
342
+ if (append) {
343
+ if (!hasBareIdentifier(rhs))
344
+ return true;
345
+ return bareIdentifiers(rhs).every(id => sanitized.has(id));
346
+ }
347
+ const ids = bareIdentifiers(rhs);
348
+ return ids.length > 0 && ids.every(id => sanitized.has(id));
349
+ }
350
+ /** Assignments and appends in one statement line, in source order. */
351
+ creditOpsInStatement(text) {
352
+ const append = APPEND_RE.exec(text);
353
+ if (append)
354
+ return [{ kind: 'append', lhs: append[1], rhs: append[2] }];
355
+ const head = ASSIGN_RE.exec(text);
356
+ if (head)
357
+ return [{ kind: 'assign', lhs: head[1], rhs: head[2] }];
358
+ const out = [];
359
+ for (const m of text.matchAll(INNER_ASSIGN_RE)) {
360
+ out.push({ kind: 'assign', lhs: m[1], rhs: m[2].trim() });
361
+ }
362
+ return out;
363
+ }
262
364
  sanitizedVarsIn(type, from, to, withHelpers) {
263
365
  const sanitized = new Set();
264
366
  for (const st of this.statements(from, to)) {
265
- const m = ASSIGN_RE.exec(st.text);
266
- if (!m)
267
- continue;
268
- const [, lhs, rhs] = m;
269
- const credited = (withHelpers ? this.sanitizes(type, rhs) : this.builtinSanitizes(type, rhs)) ||
270
- [...sanitized].some(v => mentions(rhs, v));
271
- if (credited)
272
- sanitized.add(lhs);
273
- else
274
- sanitized.delete(lhs);
367
+ for (const op of this.creditOpsInStatement(st.text)) {
368
+ if (op.kind === 'append') {
369
+ if (!sanitized.has(op.lhs))
370
+ continue;
371
+ if (!this.rhsKeepsSanitizerCredit(type, op.rhs, sanitized, withHelpers, true))
372
+ sanitized.delete(op.lhs);
373
+ continue;
374
+ }
375
+ if (this.rhsKeepsSanitizerCredit(type, op.rhs, sanitized, withHelpers, false))
376
+ sanitized.add(op.lhs);
377
+ else
378
+ sanitized.delete(op.lhs);
379
+ }
275
380
  }
276
381
  return sanitized;
277
382
  }
@@ -286,6 +391,46 @@ export class CSharpCredit {
286
391
  sanitizedVarsAt(type, sinkLine) {
287
392
  return this.sanitizedVarsIn(type, this.methodAt(sinkLine)?.start_line ?? 1, sinkLine, true);
288
393
  }
394
+ /**
395
+ * Class fields judged across every method that writes them (cognium-dev#808).
396
+ * A field is read in one method and written in others, so each write is
397
+ * judged in its own method with the same in-method credit as a local, and
398
+ * the field is safe for `type` only when every write is: `sanitized` holds
399
+ * the fields with at least one credited write and no uncredited one,
400
+ * `unsanitized` the fields with an uncredited write. A literal write
401
+ * (`_v = "";`, `_v = null;`) is neutral.
402
+ */
403
+ fieldWrites(type, fields) {
404
+ const credited = new Set();
405
+ const unsanitized = new Set();
406
+ for (const m of this.methods) {
407
+ const sanitized = new Set();
408
+ for (const st of this.statements(m.start_line, m.end_line + 1)) {
409
+ // A nested method's statements belong to it, not to `m`.
410
+ if (this.methodAt(st.line) !== m)
411
+ continue;
412
+ for (const op of this.creditOpsInStatement(st.text)) {
413
+ const lhs = op.lhs;
414
+ const keeps = op.kind === 'append'
415
+ ? sanitized.has(lhs) && this.rhsKeepsSanitizerCredit(type, op.rhs, sanitized, true, true)
416
+ : this.rhsKeepsSanitizerCredit(type, op.rhs, sanitized, true, false);
417
+ if (keeps)
418
+ sanitized.add(lhs);
419
+ else
420
+ sanitized.delete(lhs);
421
+ if (!fields.has(lhs) || isLiteralValue(op.rhs))
422
+ continue;
423
+ if (keeps)
424
+ credited.add(lhs);
425
+ else
426
+ unsanitized.add(lhs);
427
+ }
428
+ }
429
+ }
430
+ for (const f of unsanitized)
431
+ credited.delete(f);
432
+ return { sanitized: credited, unsanitized };
433
+ }
289
434
  /**
290
435
  * True when the sink on `sinkLine` also receives a tainted value that no
291
436
  * sanitizer covers. In `Write(HtmlEncode(a) + b)` the encoder covers `a`
@@ -361,7 +506,7 @@ export class CSharpCredit {
361
506
  /** `text` with every sanitizer call for `type`, arguments included, removed. */
362
507
  withoutSanitizerCalls(type, text) {
363
508
  const res = [];
364
- for (const { re, type: t } of CSHARP_SANITIZER_RES)
509
+ for (const { re, type: t } of this.sanitizers)
365
510
  if (t === type)
366
511
  res.push(re);
367
512
  for (const name of this.helpers(type))