Skip to content

circle-ir 3.39.0 — cross-instance field-binding taint propagation

Choose a tag to compare

@openmason openmason released this 12 Jun 07:07
· 49 commits to main since this release

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_field sources
  • 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 the expression field 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
```