circle-ir 3.39.0 — cross-instance field-binding taint propagation
What's New
Closes the canonical CWE-Bench-Java Jenkins shape and adjacent framework-DI patterns that 3.38.0 still could not surface: the source is bound onto a field by one class (@DataBoundConstructor, @Autowired / @Inject / @Resource, or setter chain) and consumed by another class reading that field on an aliased instance.
Both direct field reads (String p = step.path) and getter-mediated reads (String p = step.getPath()) are now closed, and the sink may live either in the caller's own method body (Files.newInputStream(Paths.get(p))) or in a downstream cross-file callee.
Changes
Two surgical changes in CrossFileResolver + the project-level pass:
1. findInterproceduralTaintPaths — caller-body sink emission (step 2c)
After marking caller-side locals tainted via a wrapper return, also check whether any sink in the caller's own method body consumes a tainted variable. Closes shapes where the final sink (Paths.get(p), Runtime.exec(cmd)) lives in the caller's file rather than in a cross-file callee.
2. New FieldTaintInfo summary + findFieldBindingTaintPaths()
analyzeFieldTaint(ir) runs per file, recording:
- Constructor-bound fields via existing
constructor_fieldsources - Setter writers (
set<Field>(<param>)with one param) @Autowired/@Inject/@Resource-annotated fields
findFieldBindingTaintPaths() per caller method scans local DFG defs and co-located uses to detect local = receiver.field field-reads:
- Handles expression-bearing defs
- Falls back to co-located use-pair matching
(receiver, field)against the receiver's declared type's field list when theexpressionfield is absent
When the receiver's declared type owns a tainted field, the local is marked tainted with origin anchored to the writer, and paths are emitted via both caller-body-sink and cross-file-callee forwarding paths.
Hop kind union extended to include field_write and field_read.
3. CrossFilePass integration
Field-binding paths merged into the existing ipPaths flow with the same TaintPath conversion logic (dedup against direct cross-file flows + IP paths).
Verification
4 new fixtures in tests/analysis/project-graph.test.ts:
| Fixture | Source | Sink | Pattern |
|---|---|---|---|
| Jenkins ReadTrustedStep (direct) | constructor_field |
path_traversal (CWE-22) |
step.path read + Paths.get |
| Jenkins ReadTrustedStep (getter) | constructor_field |
path_traversal (CWE-22) |
step.getPath() + Paths.get |
@Autowired field |
autowired_field |
path_traversal (CWE-22) |
repo.userInput + Paths.get |
| Ctor + setter mix | constructor_field |
path_traversal (CWE-22) |
Co-existing setter does not regress ctor detection |
Total suite: 1939 passing tests (1935 baseline from 3.38.0 + 4 new).
Why This Is Not a Redesign
Both changes reuse every existing primitive: methodTaintInfo, resolveCall, taint.sources/sinks, ir.dfg.defs/uses, and the existing matchTaintedArg heuristic. The walk is two linear passes per caller method, with the second activated only when fieldTaintInfo is non-empty.
Install
```bash
npm install circle-ir@3.39.0
```