circle-ir 3.56.0 → 3.58.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 (39) hide show
  1. package/configs/sinks/golang.json +61 -0
  2. package/configs/sinks/nodejs.json +11 -6
  3. package/configs/sinks/python.json +24 -0
  4. package/configs/sinks/rust.json +30 -0
  5. package/configs/sinks/sql.yaml +53 -0
  6. package/dist/analysis/config-loader.d.ts.map +1 -1
  7. package/dist/analysis/config-loader.js +57 -9
  8. package/dist/analysis/config-loader.js.map +1 -1
  9. package/dist/analysis/constant-propagation/patterns.d.ts.map +1 -1
  10. package/dist/analysis/constant-propagation/patterns.js +12 -0
  11. package/dist/analysis/constant-propagation/patterns.js.map +1 -1
  12. package/dist/analysis/constant-propagation/propagator.d.ts +62 -0
  13. package/dist/analysis/constant-propagation/propagator.d.ts.map +1 -1
  14. package/dist/analysis/constant-propagation/propagator.js +321 -7
  15. package/dist/analysis/constant-propagation/propagator.js.map +1 -1
  16. package/dist/analysis/passes/language-sources-pass.d.ts.map +1 -1
  17. package/dist/analysis/passes/language-sources-pass.js +55 -14
  18. package/dist/analysis/passes/language-sources-pass.js.map +1 -1
  19. package/dist/analysis/passes/security-headers-pass.d.ts.map +1 -1
  20. package/dist/analysis/passes/security-headers-pass.js +93 -0
  21. package/dist/analysis/passes/security-headers-pass.js.map +1 -1
  22. package/dist/analysis/passes/sink-filter-pass.d.ts.map +1 -1
  23. package/dist/analysis/passes/sink-filter-pass.js +16 -1
  24. package/dist/analysis/passes/sink-filter-pass.js.map +1 -1
  25. package/dist/analysis/passes/taint-propagation-pass.d.ts.map +1 -1
  26. package/dist/analysis/passes/taint-propagation-pass.js +153 -9
  27. package/dist/analysis/passes/taint-propagation-pass.js.map +1 -1
  28. package/dist/analysis/taint-matcher.d.ts.map +1 -1
  29. package/dist/analysis/taint-matcher.js +116 -2
  30. package/dist/analysis/taint-matcher.js.map +1 -1
  31. package/dist/analysis/taint-propagation.d.ts.map +1 -1
  32. package/dist/analysis/taint-propagation.js +25 -1
  33. package/dist/analysis/taint-propagation.js.map +1 -1
  34. package/dist/browser/circle-ir.js +533 -45
  35. package/dist/core/circle-ir-core.cjs +401 -21
  36. package/dist/core/circle-ir-core.js +401 -21
  37. package/dist/types/config.d.ts +7 -0
  38. package/dist/types/config.d.ts.map +1 -1
  39. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"patterns.d.ts","sourceRoot":"","sources":["../../../src/analysis/constant-propagation/patterns.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,eAAO,MAAM,cAAc,UAgE1B,CAAC;AAGF,eAAO,MAAM,mBAAmB,QAE/B,CAAC;AAMF,eAAO,MAAM,iBAAiB,aAwC5B,CAAC;AAOH,eAAO,MAAM,sBAAsB,aA4BjC,CAAC;AAOH,eAAO,MAAM,kBAAkB,aA8B7B,CAAC"}
1
+ {"version":3,"file":"patterns.d.ts","sourceRoot":"","sources":["../../../src/analysis/constant-propagation/patterns.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,eAAO,MAAM,cAAc,UAgE1B,CAAC;AAGF,eAAO,MAAM,mBAAmB,QAE/B,CAAC;AAMF,eAAO,MAAM,iBAAiB,aAqD5B,CAAC;AAOH,eAAO,MAAM,sBAAsB,aA4BjC,CAAC;AAOH,eAAO,MAAM,kBAAkB,aA8B7B,CAAC"}
@@ -99,6 +99,18 @@ export const SANITIZER_METHODS = new Set([
99
99
  // General
100
100
  'sanitize', 'encode', 'escape', 'clean', 'filter', 'validate', 'validatePath',
101
101
  'validateCityName', 'validateInput', 'sanitizeInput',
102
+ // Type-cast barriers (#57) — numeric/boolean casts cannot carry a string
103
+ // injection payload. Conservative whitelist; ambiguous names like `valueOf`,
104
+ // `Parse`, `fromString` are intentionally excluded.
105
+ // Java
106
+ 'parseInt', 'parseLong', 'parseFloat', 'parseDouble', 'parseShort', 'parseByte',
107
+ 'fromString', // UUID.fromString — parses strict UUID format, rejects injection
108
+ // JS/TS (parseInt/parseFloat covered above)
109
+ 'Number', 'BigInt',
110
+ // Go
111
+ 'Atoi', 'ParseInt', 'ParseFloat', 'ParseUint', 'ParseBool',
112
+ // Python
113
+ 'int', 'float', 'bool',
102
114
  ]);
103
115
  // =============================================================================
104
116
  // Anti-Sanitizer Methods
@@ -1 +1 @@
1
- {"version":3,"file":"patterns.js","sourceRoot":"","sources":["../../../src/analysis/constant-propagation/patterns.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"patterns.js","sourceRoot":"","sources":["../../../src/analysis/constant-propagation/patterns.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,gFAAgF;AAChF,wBAAwB;AACxB,gFAAgF;AAEhF,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,6BAA6B;IAC7B,sBAAsB;IACtB,mBAAmB;IACnB,oBAAoB;IACpB,oBAAoB;IACpB,wBAAwB;IACxB,wBAAwB;IACxB,qBAAqB;IACrB,uBAAuB;IACvB,yBAAyB;IACzB,mBAAmB;IACnB,gBAAgB;IAChB,sBAAsB;IACtB,mBAAmB;IACnB,aAAa;IACb,cAAc;IACd,YAAY,EAAG,oBAAoB;IACnC,cAAc;IACd,aAAa;IAEb,0DAA0D;IAC1D,sBAAsB;IACtB,oBAAoB;IACpB,eAAe;IAEf,cAAc;IACd,YAAY;IACZ,WAAW;IACX,YAAY;IACZ,QAAQ;IACR,gBAAgB;IAChB,gBAAgB;IAChB,qBAAqB;IACrB,eAAe;IAEf,kBAAkB;IAClB,wBAAwB;IACxB,cAAc;IAEd,mBAAmB;IACnB,aAAa;IACb,aAAa;IAEb,eAAe;IACf,qBAAqB;IACrB,mBAAmB;IACnB,qBAAqB;IAErB,mCAAmC;IACnC,eAAe;IACf,iBAAiB;IACjB,eAAe;IACf,mBAAmB;IACnB,iBAAiB;IACjB,mBAAmB;IACnB,cAAc;IACd,sBAAsB;IACtB,aAAa;IACb,eAAe;IACf,gBAAgB;IAChB,eAAe;IACf,uBAAuB;IACvB,yBAAyB;CAC1B,CAAC;AAEF,2DAA2D;AAC3D,MAAM,CAAC,MAAM,mBAAmB,GAAG,IAAI,MAAM,CAC3C,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAC5E,CAAC;AAEF,gFAAgF;AAChF,oBAAoB;AACpB,gFAAgF;AAEhF,MAAM,CAAC,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC;IACvC,QAAQ;IACR,eAAe,EAAE,wBAAwB,EAAE,qBAAqB;IAChE,cAAc,EAAE,cAAc,EAAE,cAAc,EAAE,uBAAuB;IACvE,eAAe,EAAE,aAAa,EAAE,gBAAgB,EAAE,cAAc;IAChE,aAAa,EAAE,iBAAiB,EAAE,cAAc;IAEhD,qBAAqB;IACrB,SAAS,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,0BAA0B;IAC3E,eAAe,EAAE,oBAAoB,EAAE,wBAAwB;IAC/D,qBAAqB,EAAE,cAAc,EAAE,WAAW,EAAE,QAAQ,EAAE,iBAAiB;IAC/E,QAAQ,EAAE,iBAAiB,EAAE,eAAe,EAAE,eAAe,EAAE,UAAU;IAEzE,iBAAiB;IACjB,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,WAAW,EAAE,aAAa,EAAE,aAAa;IACrF,kBAAkB,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW;IAExE,mBAAmB;IACnB,YAAY,EAAE,mBAAmB,EAAE,eAAe;IAElD,oBAAoB;IACpB,WAAW,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY;IACvE,SAAS,EAAE,cAAc,EAAE,WAAW,EAAE,UAAU,EAAE,eAAe;IAEnE,4BAA4B;IAC5B,kBAAkB,EAAE,WAAW,EAAE,YAAY;IAE7C,qCAAqC;IACrC,oBAAoB,EAAE,WAAW;IAEjC,0CAA0C;IAC1C,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,UAAU;IAEzC,oCAAoC;IACpC,eAAe,EAAE,sBAAsB,EAAE,cAAc,EAAE,gCAAgC;IACzF,eAAe,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM;IAE9C,UAAU;IACV,UAAU,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,cAAc;IAC7E,kBAAkB,EAAE,eAAe,EAAE,eAAe;IAEpD,yEAAyE;IACzE,6EAA6E;IAC7E,oDAAoD;IACpD,OAAO;IACP,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,WAAW;IAC/E,YAAY,EAAE,iEAAiE;IAC/E,4CAA4C;IAC5C,QAAQ,EAAE,QAAQ;IAClB,KAAK;IACL,MAAM,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,WAAW;IAC1D,SAAS;IACT,KAAK,EAAE,OAAO,EAAE,MAAM;CACvB,CAAC,CAAC;AAEH,gFAAgF;AAChF,yBAAyB;AACzB,+FAA+F;AAC/F,gFAAgF;AAEhF,MAAM,CAAC,MAAM,sBAAsB,GAAG,IAAI,GAAG,CAAC;IAC5C,uCAAuC;IACvC,QAAQ,EAAY,sBAAsB;IAC1C,oBAAoB;IACpB,WAAW;IAEX,6CAA6C;IAC7C,cAAc;IACd,QAAQ,EAAY,+BAA+B;IAEnD,2CAA2C;IAC3C,cAAc,EAAE,eAAe,EAAE,eAAe;IAChD,aAAa;IACb,oBAAoB;IACpB,cAAc;IACd,cAAc;IAEd,6EAA6E;IAC7E,oEAAoE;IACpE,2EAA2E;IAC3E,wCAAwC;IACxC,0BAA0B;IAC1B,eAAe;IACf,qBAAqB;IAErB,mBAAmB;IACnB,UAAU;IACV,YAAY;CACb,CAAC,CAAC;AAEH,gFAAgF;AAChF,qBAAqB;AACrB,iFAAiF;AACjF,gFAAgF;AAEhF,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAC;IACxC,yBAAyB;IACzB,KAAK,EAAa,qCAAqC;IACvD,IAAI,EAAc,wBAAwB;IAC1C,SAAS,EAAS,sBAAsB;IACxC,gBAAgB,EAAE,6BAA6B;IAC/C,YAAY,EAAM,yBAAyB;IAE3C,UAAU;IACV,QAAQ,EAAU,qBAAqB;IACvC,OAAO,EAAW,eAAe;IACjC,OAAO,EAAW,cAAc;IAChC,QAAQ,EAAU,8BAA8B;IAEhD,mBAAmB;IACnB,SAAS,EAAS,oBAAoB;IACtC,QAAQ,EAAU,qBAAqB;IACvC,MAAM,EAAY,mBAAmB;IACrC,QAAQ,EAAU,uBAAuB;IAEzC,mBAAmB;IACnB,gBAAgB,EAAE,8BAA8B;IAEhD,8EAA8E;IAC9E,kFAAkF;IAClF,8EAA8E;IAC9E,+EAA+E;IAC/E,0BAA0B;IAC1B,eAAe;IACf,qBAAqB;CACtB,CAAC,CAAC"}
@@ -46,6 +46,7 @@ export declare class ConstantPropagator {
46
46
  private currentClassName;
47
47
  private inConstructor;
48
48
  private constructorParamPositions;
49
+ private safePatternFieldsCache;
49
50
  /**
50
51
  * Analyze source code and build constant propagation state.
51
52
  */
@@ -80,6 +81,58 @@ export declare class ConstantPropagator {
80
81
  * These are variables declared directly in the class body, not inside methods.
81
82
  */
82
83
  private collectClassFields;
84
+ /**
85
+ * Sprint 9 #55 — seed the symbol table with Python module-level constant
86
+ * assignments. Walks only direct children of the `module` root and adds
87
+ * `IDENT = <primitive literal>` to `symbols` so `if IDENT:` guards inside
88
+ * downstream functions can be folded to dead code.
89
+ *
90
+ * Recognized literal RHS kinds: `true`/`false` (booleans), integer/float
91
+ * literals, string literals. The ExpressionEvaluator already understands
92
+ * each via the same lookup callback; we just need the symbol present.
93
+ */
94
+ /**
95
+ * Sprint 9 #55 — gate `field_declaration` folding to primitive literals.
96
+ *
97
+ * The deep-nesting regression (cognium-ai#88) constructs a Java
98
+ * `static final String hyphenData = "a" + "b" + ... (10k segments)` at the
99
+ * class level. `handleVariableDeclaration` would otherwise dispatch
100
+ * `evaluateExpression` on the deeply nested binary AST and blow the V8
101
+ * stack. The dead-code-by-const-guard pattern (`if (DEBUG)`) only requires
102
+ * `boolean`/`integer`/`string` (single-literal) RHS folding, so restrict
103
+ * to those node types.
104
+ */
105
+ private fieldDeclHasPrimitiveLiteralValue;
106
+ /**
107
+ * Sprint 9 #58.1 — collect the set of class-level `Pattern` field names
108
+ * whose compiled regex is strict-anchored, i.e. provably matches a
109
+ * bounded character set with no wildcard escape. A subsequent
110
+ * `if (!FIELD.matcher(var).matches()) throw ...;` guard then proves
111
+ * `var` is sanitized after the if.
112
+ *
113
+ * Recognized initializer shapes (scanned via source-text regex to avoid
114
+ * threading another AST walk):
115
+ * `static final Pattern FIELD = Pattern.compile("regex");`
116
+ *
117
+ * Strict-anchored regex criteria:
118
+ * - starts with `^` and ends with `$`
119
+ * - after stripping `[...]` character classes, must not contain `.` or
120
+ * `|` (a `.` could match anything; `|` admits an arbitrary alternative)
121
+ */
122
+ private getSafePatternFields;
123
+ private isStrictAnchoredRegex;
124
+ /**
125
+ * Sprint 9 #58.1 — detect the regex-allowlist guard pattern.
126
+ *
127
+ * if (!SAFE_NAME.matcher(var).matches()) { throw ...; }
128
+ *
129
+ * Returns the guarded variable name if the pattern matches AND
130
+ * `SAFE_NAME` is a recognized strict-anchored Pattern field, otherwise
131
+ * null. Caller drops the variable from `tainted` after the if-block.
132
+ */
133
+ private detectRegexAllowlistGuard;
134
+ private consequenceContainsThrow;
135
+ private seedPythonModuleConstants;
83
136
  private findAllMethods;
84
137
  private getMethodName;
85
138
  private refineTaintFromConstants;
@@ -149,8 +202,17 @@ export declare class ConstantPropagator {
149
202
  /**
150
203
  * Check if an expression is a call to a sanitizer method.
151
204
  * This includes both built-in sanitizers and @sanitizer annotated methods.
205
+ * Handles Java (`method_invocation`), Go/JS/TS (`call_expression`), and
206
+ * Python (`call`) AST shapes.
152
207
  */
153
208
  isSanitizerMethodCall(node: Node): boolean;
209
+ /**
210
+ * Extract the trailing method/function name from any call node shape:
211
+ * Java `method_invocation` — name field
212
+ * Go/JS `call_expression` — function field (identifier or selector/member)
213
+ * Python `call` — function field (identifier or attribute)
214
+ */
215
+ private extractCallName;
154
216
  /**
155
217
  * Check if an expression is a call to an anti-sanitizer method.
156
218
  * Anti-sanitizers reverse the effect of sanitization (e.g., URLDecoder.decode reverses URLEncoder.encode).
@@ -1 +1 @@
1
- {"version":3,"file":"propagator.d.ts","sourceRoot":"","sources":["../../../src/analysis/constant-propagation/propagator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,KAAK,EAAE,aAAa,EAAE,wBAAwB,EAAE,gBAAgB,EAAkB,MAAM,YAAY,CAAC;AAK5G;;;;;;;;GAQG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,OAAO,CAAyC;IACxD,OAAO,CAAC,OAAO,CAA0B;IACzC,OAAO,CAAC,gBAAgB,CAA0B;IAClD,OAAO,CAAC,kBAAkB,CAAuC;IAEjE,OAAO,CAAC,aAAa,CAA0B;IAC/C,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,SAAS,CAAuB;IAGxC,OAAO,CAAC,eAAe,CAAgC;IAEvD,OAAO,CAAC,mBAAmB,CAAkB;IAE7C,OAAO,CAAC,qBAAqB,CAA0B;IAEvD,OAAO,CAAC,sBAAsB,CAA0B;IAExD,OAAO,CAAC,sBAAsB,CAAkC;IAEhE,OAAO,CAAC,sBAAsB,CAA0B;IAExD,OAAO,CAAC,uBAAuB,CAAgB;IAE/C,OAAO,CAAC,YAAY,CAA6C;IAEjE,OAAO,CAAC,aAAa,CAA0B;IAE/C,OAAO,CAAC,oBAAoB,CAAuC;IAEnE,OAAO,CAAC,aAAa,CAAuB;IAG5C,OAAO,CAAC,iBAAiB,CAAuC;IAEhE,OAAO,CAAC,cAAc,CAAgB;IAEtC,OAAO,CAAC,cAAc,CAAkC;IAExD,OAAO,CAAC,iBAAiB,CAA0B;IAEnD,OAAO,CAAC,mBAAmB,CAAkB;IAE7C,OAAO,CAAC,eAAe,CAAkC;IAEzD,OAAO,CAAC,WAAW,CAA0B;IAE7C,OAAO,CAAC,qBAAqB,CAA0B;IAEvD,OAAO,CAAC,kBAAkB,CAA0C;IAEpE,OAAO,CAAC,gBAAgB,CAAuB;IAE/C,OAAO,CAAC,aAAa,CAAkB;IAEvC,OAAO,CAAC,yBAAyB,CAAkC;IAEnE;;OAEG;IACH,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,uBAAuB,GAAE,MAAM,EAAO,EAAE,gBAAgB,GAAE,MAAM,EAAO,EAAE,iBAAiB,GAAE,gBAAgB,EAAO,GAAG,wBAAwB;IAmGtL;;OAEG;IACH,kBAAkB,CAAC,IAAI,EAAE,IAAI,GAAG,aAAa;IAI7C;;OAEG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS;IAIpD;;OAEG;IACH,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAInC;;OAEG;IACH,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAQtC;;OAEG;IACH,OAAO,CAAC,oBAAoB;IA8H5B,OAAO,CAAC,mBAAmB;IAsD3B,OAAO,CAAC,qBAAqB;IAuD7B,OAAO,CAAC,mBAAmB;IAgB3B,OAAO,CAAC,eAAe;IAqBvB,OAAO,CAAC,+BAA+B;IAuCvC;;;OAGG;IACH,OAAO,CAAC,kBAAkB;IAiC1B,OAAO,CAAC,cAAc;IAkBtB,OAAO,CAAC,aAAa;IAYrB,OAAO,CAAC,wBAAwB;IAkDhC,OAAO,CAAC,KAAK;IAmBb;;;;OAIG;IACH,OAAO,CAAC,QAAQ;IAgEhB;;;OAGG;IACH,OAAO,CAAC,uBAAuB;IA8E/B;;;OAGG;IACH,OAAO,CAAC,aAAa;IAYrB;;;;OAIG;IACH,OAAO,CAAC,YAAY;IAapB,OAAO,CAAC,mBAAmB;IAmD3B,OAAO,CAAC,wBAAwB;IAwBhC;;;OAGG;IACH,OAAO,CAAC,2BAA2B;IAanC;;OAEG;IACH,OAAO,CAAC,wBAAwB;IAYhC,OAAO,CAAC,iBAAiB;IA8BzB,OAAO,CAAC,yBAAyB;IAwDjC,OAAO,CAAC,gBAAgB;IA4FxB,OAAO,CAAC,4BAA4B;IAqCpC,OAAO,CAAC,sBAAsB;IA8C9B,OAAO,CAAC,iBAAiB;IAsGzB;;;OAGG;IACH,OAAO,CAAC,kBAAkB;IAwB1B;;;;OAIG;IACH,OAAO,CAAC,mBAAmB;IAW3B;;;;OAIG;IACH,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAoB/C,OAAO,CAAC,YAAY;IAwEpB,OAAO,CAAC,aAAa;IAwBrB,OAAO,CAAC,yBAAyB;IAYjC,OAAO,CAAC,eAAe;IAQvB,OAAO,CAAC,iBAAiB;IAYzB,OAAO,CAAC,gBAAgB;IAqBxB;;;OAGG;IACH,qBAAqB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO;IAc1C;;;;OAIG;IACH,mBAAmB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO;IAcxC;;;OAGG;IACH,8BAA8B,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO;IAiCnD;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAoB5B;;;;OAIG;IACH,OAAO,CAAC,uBAAuB;IAiB/B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IA8B3B;;;OAGG;IACH,OAAO,CAAC,uBAAuB;IAe/B,mBAAmB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO;IAqBxC,OAAO,CAAC,uBAAuB;IAoW/B,OAAO,CAAC,oBAAoB;CA4L7B"}
1
+ {"version":3,"file":"propagator.d.ts","sourceRoot":"","sources":["../../../src/analysis/constant-propagation/propagator.ts"],"names":[],"mappings":"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"}
@@ -72,6 +72,10 @@ export class ConstantPropagator {
72
72
  inConstructor = false;
73
73
  // Map constructor parameter names to their positions (0-indexed)
74
74
  constructorParamPositions = new Map();
75
+ // Sprint 9 #58.1 — names of `static final Pattern` fields whose compiled
76
+ // regex is strict-anchored (provably matches a bounded character set).
77
+ // Populated lazily on first access via `getSafePatternFields()`.
78
+ safePatternFieldsCache = null;
75
79
  /**
76
80
  * Analyze source code and build constant propagation state.
77
81
  */
@@ -105,6 +109,7 @@ export class ConstantPropagator {
105
109
  this.currentClassName = null;
106
110
  this.inConstructor = false;
107
111
  this.constructorParamPositions.clear();
112
+ this.safePatternFieldsCache = null;
108
113
  // Pre-pass: identify class fields
109
114
  this.collectClassFields(tree.rootNode);
110
115
  // Pre-populate methodReturnsSanitized with methods marked with @sanitizer annotation
@@ -115,6 +120,14 @@ export class ConstantPropagator {
115
120
  this.evaluator = new ExpressionEvaluator(this.source, (name) => this.lookupSymbol(name));
116
121
  // Pre-pass: identify methods that always return constants or sanitized values
117
122
  this.analyzeMethodReturns(tree.rootNode);
123
+ // Sprint 9 #55 — Pre-pass: fold Python module-level constant assignments
124
+ // (`DEBUG = False`, `FOO = "bar"`) into the symbol table so dead-code
125
+ // detection of `if DEBUG:` guards inside functions can mark the body
126
+ // unreachable. We do this only for top-level `assignment` nodes whose
127
+ // RHS is a primitive literal — keeps the change narrow and avoids the
128
+ // recursion hazards of dispatching the full `handleAssignment` path
129
+ // on Python ASTs.
130
+ this.seedPythonModuleConstants(tree.rootNode);
118
131
  // First pass: collect symbols, taint, and unreachable lines
119
132
  this.visit(tree.rootNode);
120
133
  // Second pass: refine taint for variables derived from constants
@@ -505,6 +518,203 @@ export class ConstantPropagator {
505
518
  }
506
519
  }
507
520
  }
521
+ /**
522
+ * Sprint 9 #55 — seed the symbol table with Python module-level constant
523
+ * assignments. Walks only direct children of the `module` root and adds
524
+ * `IDENT = <primitive literal>` to `symbols` so `if IDENT:` guards inside
525
+ * downstream functions can be folded to dead code.
526
+ *
527
+ * Recognized literal RHS kinds: `true`/`false` (booleans), integer/float
528
+ * literals, string literals. The ExpressionEvaluator already understands
529
+ * each via the same lookup callback; we just need the symbol present.
530
+ */
531
+ /**
532
+ * Sprint 9 #55 — gate `field_declaration` folding to primitive literals.
533
+ *
534
+ * The deep-nesting regression (cognium-ai#88) constructs a Java
535
+ * `static final String hyphenData = "a" + "b" + ... (10k segments)` at the
536
+ * class level. `handleVariableDeclaration` would otherwise dispatch
537
+ * `evaluateExpression` on the deeply nested binary AST and blow the V8
538
+ * stack. The dead-code-by-const-guard pattern (`if (DEBUG)`) only requires
539
+ * `boolean`/`integer`/`string` (single-literal) RHS folding, so restrict
540
+ * to those node types.
541
+ */
542
+ fieldDeclHasPrimitiveLiteralValue(node) {
543
+ const primitive = new Set([
544
+ // Java literal node types
545
+ 'true', 'false', 'null_literal',
546
+ 'decimal_integer_literal', 'hex_integer_literal',
547
+ 'octal_integer_literal', 'binary_integer_literal',
548
+ 'decimal_floating_point_literal',
549
+ 'hex_floating_point_literal',
550
+ 'character_literal', 'string_literal',
551
+ // JS/TS literal node types (defensive, in case other langs reuse it)
552
+ 'number', 'string',
553
+ ]);
554
+ for (const child of node.children) {
555
+ if (child.type !== 'variable_declarator')
556
+ continue;
557
+ const value = child.childForFieldName('value');
558
+ if (!value)
559
+ continue;
560
+ if (!primitive.has(value.type))
561
+ return false;
562
+ }
563
+ return true;
564
+ }
565
+ /**
566
+ * Sprint 9 #58.1 — collect the set of class-level `Pattern` field names
567
+ * whose compiled regex is strict-anchored, i.e. provably matches a
568
+ * bounded character set with no wildcard escape. A subsequent
569
+ * `if (!FIELD.matcher(var).matches()) throw ...;` guard then proves
570
+ * `var` is sanitized after the if.
571
+ *
572
+ * Recognized initializer shapes (scanned via source-text regex to avoid
573
+ * threading another AST walk):
574
+ * `static final Pattern FIELD = Pattern.compile("regex");`
575
+ *
576
+ * Strict-anchored regex criteria:
577
+ * - starts with `^` and ends with `$`
578
+ * - after stripping `[...]` character classes, must not contain `.` or
579
+ * `|` (a `.` could match anything; `|` admits an arbitrary alternative)
580
+ */
581
+ getSafePatternFields() {
582
+ if (this.safePatternFieldsCache !== null)
583
+ return this.safePatternFieldsCache;
584
+ const set = new Set();
585
+ // Match `static final Pattern NAME = Pattern.compile("REGEX")` allowing
586
+ // arbitrary modifier order and optional `public/private/protected`.
587
+ const re = /\b(?:public\s+|private\s+|protected\s+)?(?:static\s+final|final\s+static)\s+(?:java\.util\.regex\.)?Pattern\s+(\w+)\s*=\s*(?:java\.util\.regex\.)?Pattern\s*\.\s*compile\s*\(\s*"((?:[^"\\]|\\.)*)"/g;
588
+ let m;
589
+ while ((m = re.exec(this.source)) !== null) {
590
+ const name = m[1];
591
+ const regex = m[2];
592
+ if (this.isStrictAnchoredRegex(regex))
593
+ set.add(name);
594
+ }
595
+ this.safePatternFieldsCache = set;
596
+ return set;
597
+ }
598
+ isStrictAnchoredRegex(re) {
599
+ if (!re.startsWith('^') || !re.endsWith('$'))
600
+ return false;
601
+ // Strip [..] character classes (and escaped brackets) — wildcards inside
602
+ // a class are fine because they only match the listed characters.
603
+ const stripped = re.replace(/\[(?:[^\]\\]|\\.)*\]/g, '');
604
+ // Forbid bare `.` (any char) and `|` (alternation) outside classes.
605
+ // `\.` (escaped dot) is fine; same for `\|`.
606
+ const cleaned = stripped.replace(/\\./g, '');
607
+ if (cleaned.includes('.'))
608
+ return false;
609
+ if (cleaned.includes('|'))
610
+ return false;
611
+ return true;
612
+ }
613
+ /**
614
+ * Sprint 9 #58.1 — detect the regex-allowlist guard pattern.
615
+ *
616
+ * if (!SAFE_NAME.matcher(var).matches()) { throw ...; }
617
+ *
618
+ * Returns the guarded variable name if the pattern matches AND
619
+ * `SAFE_NAME` is a recognized strict-anchored Pattern field, otherwise
620
+ * null. Caller drops the variable from `tainted` after the if-block.
621
+ */
622
+ detectRegexAllowlistGuard(condition, consequence) {
623
+ if (!consequence)
624
+ return null;
625
+ let condText = getNodeText(condition, this.source).replace(/\s+/g, '');
626
+ // Strip balanced outer parens (Java's `if_statement` condition is a
627
+ // `parenthesized_expression`, so `(!SAFE.matcher(x).matches())` arrives
628
+ // wrapped). Repeat in case of redundant parens.
629
+ while (condText.startsWith('(') && condText.endsWith(')')) {
630
+ const inner = condText.slice(1, -1);
631
+ let depth = 0;
632
+ let balanced = true;
633
+ for (let i = 0; i < inner.length; i++) {
634
+ if (inner[i] === '(')
635
+ depth++;
636
+ else if (inner[i] === ')')
637
+ depth--;
638
+ if (depth < 0) {
639
+ balanced = false;
640
+ break;
641
+ }
642
+ }
643
+ if (!balanced || depth !== 0)
644
+ break;
645
+ condText = inner;
646
+ }
647
+ const m = condText.match(/^!(\w+)\.matcher\((\w+)\)\.matches\(\)$/);
648
+ if (!m)
649
+ return null;
650
+ const patternName = m[1];
651
+ const varName = m[2];
652
+ if (!this.getSafePatternFields().has(patternName))
653
+ return null;
654
+ if (!this.consequenceContainsThrow(consequence))
655
+ return null;
656
+ return varName;
657
+ }
658
+ consequenceContainsThrow(node) {
659
+ if (node.type === 'throw_statement')
660
+ return true;
661
+ // For block bodies, look for any throw_statement child.
662
+ const stack = [node];
663
+ while (stack.length > 0) {
664
+ const n = stack.pop();
665
+ if (!n)
666
+ continue;
667
+ if (n.type === 'throw_statement')
668
+ return true;
669
+ // Don't descend into nested control-flow that may not actually throw.
670
+ if (n.type === 'if_statement' || n.type === 'switch_statement')
671
+ continue;
672
+ for (const c of n.children)
673
+ stack.push(c);
674
+ }
675
+ return false;
676
+ }
677
+ seedPythonModuleConstants(root) {
678
+ if (root.type !== 'module')
679
+ return; // Python-only — Java/JS roots differ.
680
+ for (const child of root.children) {
681
+ // Top-level Python statements are wrapped in an `expression_statement`
682
+ // whose first child is the actual expression. Module-level `x = y`
683
+ // produces `expression_statement → assignment`.
684
+ const target = child.type === 'assignment'
685
+ ? child
686
+ : child.type === 'expression_statement' && child.children.length > 0
687
+ ? child.children[0]
688
+ : null;
689
+ if (!target || target.type !== 'assignment')
690
+ continue;
691
+ const left = target.childForFieldName('left');
692
+ const right = target.childForFieldName('right');
693
+ if (!left || !right)
694
+ continue;
695
+ if (left.type !== 'identifier')
696
+ continue;
697
+ // Constrain RHS to PRIMITIVE literal nodes only. Evaluating compound
698
+ // expressions (e.g. `"a" + "b" + ...`) via the full evaluator can
699
+ // recurse arbitrarily deep on pathological generated sources — the
700
+ // deep-nesting regression (cognium-ai#88) constructs 10k-segment
701
+ // string concatenations at module level. We don't need that here.
702
+ const allowed = new Set([
703
+ 'true', 'false', 'none',
704
+ 'integer', 'float',
705
+ 'string',
706
+ ]);
707
+ if (!allowed.has(right.type))
708
+ continue;
709
+ const name = getNodeText(left, this.source);
710
+ if (!name)
711
+ continue;
712
+ const value = this.evaluateExpression(right);
713
+ if (!isKnown(value))
714
+ continue;
715
+ this.symbols.set(name, value);
716
+ }
717
+ }
508
718
  findAllMethods(node) {
509
719
  // Iterative DFS — guards against stack overflow on deeply nested AST
510
720
  // shapes (cognium-ai#88).
@@ -616,6 +826,24 @@ export class ConstantPropagator {
616
826
  case 'local_variable_declaration':
617
827
  this.handleVariableDeclaration(node);
618
828
  return false;
829
+ case 'field_declaration':
830
+ // Sprint 9 #55 — fold class-level `static final` constants into the
831
+ // symbols table so `if (DEBUG) { ... }` style guards can be evaluated
832
+ // and the unreachable branch marked as dead code. Structurally
833
+ // identical to `local_variable_declaration` (both have
834
+ // `variable_declarator` children with `name`/`value`), so the same
835
+ // handler applies.
836
+ //
837
+ // Constrain to primitive-literal initializers only. Java
838
+ // `public static final String hyphenData = "a" + "b" + ...` at the
839
+ // class level can be arbitrarily deep (cognium-ai#88), and the
840
+ // evaluator's binary recursion blows the stack at ~5k segments. The
841
+ // dead-code-by-const-guard pattern (`if (DEBUG)`) only needs
842
+ // booleans/numbers/single strings folded; nothing else.
843
+ if (this.fieldDeclHasPrimitiveLiteralValue(node)) {
844
+ this.handleVariableDeclaration(node);
845
+ }
846
+ return false;
619
847
  case 'assignment_expression':
620
848
  this.handleAssignment(node);
621
849
  return false;
@@ -777,6 +1005,33 @@ export class ConstantPropagator {
777
1005
  if (nameNode) {
778
1006
  const varName = getNodeText(nameNode, this.source);
779
1007
  loopVarNames.add(varName);
1008
+ // Sprint 8 #84: propagate taint from iterated collection to the loop variable.
1009
+ // If the collection is tainted (whole-collection taint), or contains tainted
1010
+ // elements/keys, mark the loop variable as tainted so downstream uses at
1011
+ // sinks fire as expected.
1012
+ const collectionNode = node.childForFieldName('value');
1013
+ if (collectionNode) {
1014
+ const collectionName = getNodeText(collectionNode, this.source);
1015
+ const scopedCollection = this.currentMethod
1016
+ ? `${this.currentMethod}:${collectionName}`
1017
+ : collectionName;
1018
+ let elementIsTainted = this.tainted.has(collectionName) || this.tainted.has(scopedCollection);
1019
+ if (!elementIsTainted) {
1020
+ const taintedIndices = this.taintedArrayElements.get(collectionName);
1021
+ if (taintedIndices && taintedIndices.size > 0)
1022
+ elementIsTainted = true;
1023
+ }
1024
+ if (!elementIsTainted) {
1025
+ const taintedKeys = this.taintedCollections.get(collectionName);
1026
+ if (taintedKeys && taintedKeys.size > 0)
1027
+ elementIsTainted = true;
1028
+ }
1029
+ if (elementIsTainted) {
1030
+ const scopedVar = this.currentMethod ? `${this.currentMethod}:${varName}` : varName;
1031
+ this.tainted.add(varName);
1032
+ this.tainted.add(scopedVar);
1033
+ }
1034
+ }
780
1035
  }
781
1036
  }
782
1037
  // Mark all loop variables as unknown BEFORE visiting children
@@ -1175,6 +1430,20 @@ export class ConstantPropagator {
1175
1430
  }
1176
1431
  this.inConditionalBranch = wasInConditional;
1177
1432
  this.tainted = new Set([...taintedBefore, ...taintedAfterThen, ...taintedAfterElse]);
1433
+ // Sprint 9 #58.1 — regex-allowlist sanitizer.
1434
+ // if (!SAFE_NAME.matcher(var).matches()) throw ...;
1435
+ // After this if, `var` is provably one of the strict-anchored regex's
1436
+ // matches. Drop it from `tainted` (and from any scoped variant).
1437
+ const guardedVar = this.detectRegexAllowlistGuard(condition, consequence);
1438
+ if (guardedVar) {
1439
+ this.tainted.delete(guardedVar);
1440
+ this.sanitizedVars.add(guardedVar);
1441
+ const scoped = this.getScopedName(guardedVar);
1442
+ if (scoped !== guardedVar) {
1443
+ this.tainted.delete(scoped);
1444
+ this.sanitizedVars.add(scoped);
1445
+ }
1446
+ }
1178
1447
  }
1179
1448
  }
1180
1449
  /**
@@ -1378,18 +1647,44 @@ export class ConstantPropagator {
1378
1647
  /**
1379
1648
  * Check if an expression is a call to a sanitizer method.
1380
1649
  * This includes both built-in sanitizers and @sanitizer annotated methods.
1650
+ * Handles Java (`method_invocation`), Go/JS/TS (`call_expression`), and
1651
+ * Python (`call`) AST shapes.
1381
1652
  */
1382
1653
  isSanitizerMethodCall(node) {
1383
- if (node.type !== 'method_invocation') {
1654
+ const methodName = this.extractCallName(node);
1655
+ if (!methodName)
1384
1656
  return false;
1385
- }
1386
- const nameNode = node.childForFieldName('name');
1387
- if (!nameNode) {
1388
- return false;
1389
- }
1390
- const methodName = getNodeText(nameNode, this.source);
1391
1657
  return SANITIZER_METHODS.has(methodName) || this.methodReturnsSanitized.has(methodName);
1392
1658
  }
1659
+ /**
1660
+ * Extract the trailing method/function name from any call node shape:
1661
+ * Java `method_invocation` — name field
1662
+ * Go/JS `call_expression` — function field (identifier or selector/member)
1663
+ * Python `call` — function field (identifier or attribute)
1664
+ */
1665
+ extractCallName(node) {
1666
+ let fnNode = null;
1667
+ if (node.type === 'method_invocation') {
1668
+ fnNode = node.childForFieldName('name');
1669
+ }
1670
+ else if (node.type === 'call_expression' || node.type === 'call') {
1671
+ fnNode = node.childForFieldName('function');
1672
+ }
1673
+ if (!fnNode)
1674
+ return null;
1675
+ // For selector_expression (Go), member_expression (JS), attribute (Python),
1676
+ // take the trailing identifier.
1677
+ if (fnNode.type === 'selector_expression' ||
1678
+ fnNode.type === 'member_expression' ||
1679
+ fnNode.type === 'attribute') {
1680
+ const tail = fnNode.childForFieldName('field') ||
1681
+ fnNode.childForFieldName('property') ||
1682
+ fnNode.childForFieldName('attribute');
1683
+ if (tail)
1684
+ return getNodeText(tail, this.source);
1685
+ }
1686
+ return getNodeText(fnNode, this.source);
1687
+ }
1393
1688
  /**
1394
1689
  * Check if an expression is a call to an anti-sanitizer method.
1395
1690
  * Anti-sanitizers reverse the effect of sanitization (e.g., URLDecoder.decode reverses URLEncoder.encode).
@@ -1881,9 +2176,28 @@ export class ConstantPropagator {
1881
2176
  this.taintedCollections.set(collectionName, new Set());
1882
2177
  }
1883
2178
  this.taintedCollections.get(collectionName).add(keyStr);
2179
+ // Sprint 8 #62: also mark the map variable itself as tainted so that
2180
+ // downstream `map.get(k)` reads at sinks are detected by
2181
+ // detectCollectionFlows (which checks taintedVars by container name).
2182
+ const scopedCollection = this.currentMethod ? `${this.currentMethod}:${collectionName}` : collectionName;
2183
+ this.tainted.add(scopedCollection);
2184
+ this.tainted.add(collectionName);
1884
2185
  }
1885
2186
  }
1886
2187
  }
2188
+ // Sprint 8 #62: StringBuilder / StringBuffer append/insert taint propagation.
2189
+ // When tainted data is appended to a builder, mark the builder variable as
2190
+ // tainted so that `sb.toString()` at sinks is detected by detectCollectionFlows.
2191
+ if (methodName === 'append' || methodName === 'insert') {
2192
+ const args = argsNode.children.filter((c) => c.type !== '(' && c.type !== ')' && c.type !== ',');
2193
+ // For append the value is arg[0]; for insert(int offset, value) the value is arg[1].
2194
+ const valueArg = methodName === 'insert' && args.length >= 2 ? args[1] : args[0];
2195
+ if (valueArg && this.isTaintedExpression(valueArg)) {
2196
+ const scopedCollection = this.currentMethod ? `${this.currentMethod}:${collectionName}` : collectionName;
2197
+ this.tainted.add(scopedCollection);
2198
+ this.tainted.add(collectionName);
2199
+ }
2200
+ }
1887
2201
  if (methodName === 'add' || methodName === 'addLast') {
1888
2202
  const args = argsNode.children.filter((c) => c.type !== '(' && c.type !== ')' && c.type !== ',');
1889
2203
  if (args.length >= 1) {