circle-ir 3.57.0 → 3.59.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 +275 -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 +226 -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 +610 -45
  35. package/dist/core/circle-ir-core.cjs +368 -21
  36. package/dist/core/circle-ir-core.js +368 -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":"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"}
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;
@@ -1202,6 +1430,20 @@ export class ConstantPropagator {
1202
1430
  }
1203
1431
  this.inConditionalBranch = wasInConditional;
1204
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
+ }
1205
1447
  }
1206
1448
  }
1207
1449
  /**
@@ -1405,18 +1647,44 @@ export class ConstantPropagator {
1405
1647
  /**
1406
1648
  * Check if an expression is a call to a sanitizer method.
1407
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.
1408
1652
  */
1409
1653
  isSanitizerMethodCall(node) {
1410
- if (node.type !== 'method_invocation') {
1411
- return false;
1412
- }
1413
- const nameNode = node.childForFieldName('name');
1414
- if (!nameNode) {
1654
+ const methodName = this.extractCallName(node);
1655
+ if (!methodName)
1415
1656
  return false;
1416
- }
1417
- const methodName = getNodeText(nameNode, this.source);
1418
1657
  return SANITIZER_METHODS.has(methodName) || this.methodReturnsSanitized.has(methodName);
1419
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
+ }
1420
1688
  /**
1421
1689
  * Check if an expression is a call to an anti-sanitizer method.
1422
1690
  * Anti-sanitizers reverse the effect of sanitization (e.g., URLDecoder.decode reverses URLEncoder.encode).