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.
- package/configs/sinks/golang.json +61 -0
- package/configs/sinks/nodejs.json +11 -6
- package/configs/sinks/python.json +24 -0
- package/configs/sinks/rust.json +30 -0
- package/configs/sinks/sql.yaml +53 -0
- package/dist/analysis/config-loader.d.ts.map +1 -1
- package/dist/analysis/config-loader.js +57 -9
- package/dist/analysis/config-loader.js.map +1 -1
- package/dist/analysis/constant-propagation/patterns.d.ts.map +1 -1
- package/dist/analysis/constant-propagation/patterns.js +12 -0
- package/dist/analysis/constant-propagation/patterns.js.map +1 -1
- package/dist/analysis/constant-propagation/propagator.d.ts +62 -0
- package/dist/analysis/constant-propagation/propagator.d.ts.map +1 -1
- package/dist/analysis/constant-propagation/propagator.js +321 -7
- package/dist/analysis/constant-propagation/propagator.js.map +1 -1
- package/dist/analysis/passes/language-sources-pass.d.ts.map +1 -1
- package/dist/analysis/passes/language-sources-pass.js +55 -14
- package/dist/analysis/passes/language-sources-pass.js.map +1 -1
- package/dist/analysis/passes/security-headers-pass.d.ts.map +1 -1
- package/dist/analysis/passes/security-headers-pass.js +93 -0
- package/dist/analysis/passes/security-headers-pass.js.map +1 -1
- package/dist/analysis/passes/sink-filter-pass.d.ts.map +1 -1
- package/dist/analysis/passes/sink-filter-pass.js +16 -1
- package/dist/analysis/passes/sink-filter-pass.js.map +1 -1
- package/dist/analysis/passes/taint-propagation-pass.d.ts.map +1 -1
- package/dist/analysis/passes/taint-propagation-pass.js +153 -9
- package/dist/analysis/passes/taint-propagation-pass.js.map +1 -1
- package/dist/analysis/taint-matcher.d.ts.map +1 -1
- package/dist/analysis/taint-matcher.js +116 -2
- package/dist/analysis/taint-matcher.js.map +1 -1
- package/dist/analysis/taint-propagation.d.ts.map +1 -1
- package/dist/analysis/taint-propagation.js +25 -1
- package/dist/analysis/taint-propagation.js.map +1 -1
- package/dist/browser/circle-ir.js +533 -45
- package/dist/core/circle-ir-core.cjs +401 -21
- package/dist/core/circle-ir-core.js +401 -21
- package/dist/types/config.d.ts +7 -0
- package/dist/types/config.d.ts.map +1 -1
- 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,
|
|
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":"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"}
|
|
@@ -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;
|
|
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
|
-
|
|
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) {
|