Releases: cogniumhq/cognium-dev
cognium-dev 3.39.0 — cross-instance field-binding taint paths
What's New
circle-ir upgraded 3.38.0 → 3.39.0 — adds cross-instance field-binding taint propagation.
cognium-dev scan against multi-file Java projects now emits taint_paths for the canonical CWE-Bench-Java Jenkins shape and adjacent framework-DI patterns where 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.
What This Closes
Previously-hidden field-binding chains now surface with constructor_field or autowired_field source types and confidence-decayed multi-hop paths. The canonical Jenkins ReadTrustedStep shape (CWE-Bench-Java tail) now closes end-to-end.
Output Formats
Text, JSON, and SARIF output formats are unchanged — previously-hidden paths simply appear in all three.
Compatibility
No regressions: full circle-ir suite at 1939 passing tests (1935 baseline + 4 new fixtures).
Install
```bash
npm
npm install -g cognium-dev@3.39.0
scan a project
cognium-dev scan ./my-java-project
```
See the full circle-ir 3.39.0 release notes for technical details.
cognium-dev 3.38.0 — cross-file inter-procedural taint chains
Changed
circle-ir upgraded 3.37.0 → 3.38.0 — closes the Java cross-file inter-procedural taint gap (#19) that hid CVE-2018-1260 (Spring SpEL injection) and CVE-2011-2732 (Spring open redirect) shapes.
+ "cognium-dev scan" + against multi-file Java projects now emits + "taint_paths" + for the canonical pattern:
+ "```" +
source-in-callee-A → wrapper-return-in-caller → sink-call-in-caller → sink-in-callee-B
+ "```" +
…where neither file in isolation contains both a source and the sink.
+ "cross_file_calls[].args_mapping[].taint_propagates" + is now populated from the callee's analyzed + "taintedParams" + summary (previously hard-coded + "false" + ), giving downstream consumers an at-a-glance view of which arguments carry tainted data across a resolved inter-file call.
Output formats (text, JSON, SARIF) are unchanged; previously-hidden multi-hop chains now surface with confidence-decayed paths (0.85 per hop, floor 0.30).
The fix also tightens single-hop cross-file flow detection with a variable-connectivity gate that eliminates false positives when a sanitized wrapper sits between the controller-side source and the callee-side sink.
Java/JS/Python flows for in-file and pre-existing cross-file shapes are unaffected (verified by full OWASP Benchmark Java + Juliet + SecuriBench Micro suites).
Install
+ "```bash" +
npm install -g cognium-dev@3.38.0
+ "```" +
Full diff: cognium-dev-v3.37.0...cognium-dev-v3.38.0
cognium-dev 3.37.0
circle-ir upgraded 3.36.0 → 3.37.0
Closes the remaining Python false-negative tail uncovered after #18 (#20). cognium-dev scan against Python projects now emits flows for multi-hop indirection shapes that 3.36.0 still missed:
- Simple alias chains:
bar = uid; sql = "..." + bar; cur.execute(sql) - configparser round-trips:
conf.set('s','k', tainted); bar = conf.get('s','k'); cur.execute(f'... {bar}') - List/dict round-trips:
lst.append(tainted); bar = lst[0]; subprocess.run([..., bar])(and.add/.extend/.insert/.push/.put/.appendleftvariants)
These were the dominant remaining drivers of OWASP BenchmarkPython false negatives. Output formats (text, JSON, SARIF) are unchanged; previously-hidden flows now surface in all three.
Java/JS/Bash flows are unaffected — the alias expansion is gated to Python only and verified by an explicit Java sqli non-regression test plus the full 156-case Juliet suite.
Cross-module helper indirection (helpers.db_sqlite.results(cur, sql)) is not addressed and requires inter-procedural taint summaries, filed as future work.
🤖 Generated with Claude Code
cognium-dev v3.36.0
Changed
circle-ir upgraded 3.35.0 → 3.36.0 — fixes a long-standing structural defect that left result.taint.flows empty for every Python sink category (#18).
cognium-dev scan against Python projects now emits cross-source/sink flows for:
sql_injectioncommand_injection(os.system,subprocess.call(..., shell=True))path_traversalcode_injection(eval,exec)deserialization(pickle.loads)xxe(ET.fromstring)ldap_injectionopen_redirect
Output formats (text, JSON, SARIF) are unchanged; previously-hidden flows now surface in all three. Java/JS/Bash flows are unaffected (verified by 156-case Juliet suite + targeted non-regression test).
See circle-ir v3.36.0 release notes for full technical detail.
cognium-dev@3.35.0 — Jenkins Groovy sandbox sink coverage (#17)
Highlights
Bumps circle-ir to 3.35.0. For Java projects, cognium-dev scan now flags taint reaching any org.kohsuke.groovy.sandbox.SandboxInterceptor / GroovyInterceptor dispatch hook (onMethodCall, onStaticCall, onGetProperty, onSetProperty, onGetAttribute, onSetAttribute, onMethodPointer, onSuperCall, onSuperConstructor, plus parent-class entries), SandboxTransformer.call, and GroovySandbox.runInSandbox.
Prior releases only flagged SandboxInterceptor.onNewInstance, leaving method/static dispatch (the most common CVE-2023-24422 bypass shape) silently uncovered.
Changed
- circle-ir 3.34.0 → 3.35.0 — see circle-ir@3.35.0 release notes.
Tests
- circle-ir: 1904 / 1904 pass
- cli: 125 / 125 pass
Install
npm install -g cognium-dev@3.35.0circle-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
```
circle-ir 3.38.0 — cross-file inter-procedural taint chains (#19)
Fixed
Cross-file inter-procedural taint chains now resolve through wrapper return values and sink-param summaries (#19).
Closes the Java Spring-shape gap reported for CVE-2011-2732 (sendRedirect open redirect via UrlHandler.determineTargetUrl wrapper) — and by virtue of the same fix, the Jenkins #1 shape (@DataBoundConstructor field bound to user input flowing through BuildStep → CommandRunner.run → Runtime.exec) and CVE-2018-1260 (Spring SpEL injection through SpelHelper.parseExpression + getValue wrapper).
After diagnostic review the issue was reframed: it is not Spring-specific. The engine already had every intermediate signal — sources per file, sinks per file, the intra-file interprocedural_param → sink flow in the sink wrapper, and cross-file call resolution with args_mapping. Only the chaining between them was missing.
Root cause — three independent gaps in + "CrossFileResolver" +
+ "isMethodTaintSource" +treated+ "interprocedural_param" +sources as "real", so every internal helper with typed parameters was marked+ "returnsSource = true" +. Cross-file+ "wrapper(...)" +calls would then ghost-taint their callers.+ "findTaintedParams" +only looked at annotations (+ "@RequestParam" +/+ "@RequestBody" +/+ "@PathVariable" +) — so a sink-wrapper like+ "RedirectStrategy.sendRedirect(req, res, String url) { res.sendRedirect(url); }" +carried+ "taintedParams = []" +, and the+ "args_mapping[].taint_propagates" +summary on every cross-file call was permanently stuck at+ "false" +.- No chaining method existed.
+ "findCrossFileTaintFlows()" +only emits+ "source-in-caller → sink-in-callee" +flows; it cannot see the canonical 2-wrapper chain+ "source-in-callee-A → wrapper-return-in-caller → sink-call-in-caller → sink-in-callee-B" +, even though+ "callee-A.returnsSource=true" +++ "callee-B.taintedParams=[2]" +is the exact summary needed to link them. + "findCrossFileTaintFlows()" +overapproximated when the caller had its own real source: it emitted a path to any downstream cross-file sink regardless of whether the call's arguments actually carried the source.
Fix — four minimal changes
+ "isMethodTaintSource" +++ "getSourceType" +skip+ "interprocedural_param" +sources entirely.+ "findTaintedParams" +adds a sink-arg-matching heuristic: for every known sink inside the method body, scan the call expression's argument variables and whole-word-match them against the method's parameter names. Hits →+ "taintedParams" +.- New
+ "findInterproceduralTaintPaths()" +walks each caller method, seeds tainted map from real sources, marks DFG+ "local" +defs tainted on+ "returnsSource" +callee, emits multi-hop+ "TaintPath" +when a tainted arg reaches a callee's+ "taintedParam" +. Confidence: 0.85 per hop, floor 0.30. - Variable-connectivity gate on
+ "findCrossFileTaintFlows()" +: require the cross-file call's args to mention the source's owning variable (eliminates sanitized-wrapper FPs).
CrossFilePass
- Emits new section "1b. Inter-procedural multi-hop taint chains" with dedup against direct cross-file flows.
- Populates
+ "cross_file_calls[].args_mapping[].taint_propagates" +from the callee's analyzed+ "taintedParams" +summary (was hard-coded+ "false" +).
Verification
- 4 new fixtures in
+ "tests/analysis/project-graph.test.ts" +:- CVE-2011-2732
+ "sendRedirect" +open-redirect shape (positive) - Sanitized-wrapper negative control (must not emit)
- CVE-2018-1260 SpEL
+ "parseExpression" +++ "getValue" +shape - Jenkins #1
+ "@DataBoundConstructor" +→+ "Runtime.exec" +shape
- CVE-2011-2732
- All 1935 tests pass (1931 baseline + 4 new). Typecheck clean.
- OWASP Benchmark Java + Juliet (156/156) + SecuriBench Micro: no regression.
Why this is not a redesign
Every primitive used by the new chaining logic — + "resolveCall" + , + "methodTaintInfo" + , + "ir.dfg.defs" + , + "ir.taint.sources/sinks" + — already existed. No new IR types, no new pipeline pass, no new config files. The fix is purely in how CrossFileResolver consumes existing per-file analysis results.
Full diff: circle-ir-v3.37.0...circle-ir-v3.38.0
circle-ir 3.37.0
Python multi-hop taint propagation (#20)
Closes the indirection-pattern false-negative tail uncovered after #18. The supplement that fixed one-hop direct flows still emitted taint.flows = [] for every aliased / container / round-trip shape — the dominant remaining driver of OWASP BenchmarkPython misses and the blocker for circle-ir-ai#75.
Shapes now detected
- Shape A — configparser round-trip:
conf.set('s','k', tainted); bar = conf.get('s','k'); cur.execute(f'... {bar}') - Shape B — list/dict round-trip:
lst.append(tainted); bar = lst[0]; argList = ['sh','-c', f'echo {bar}']; subprocess.run(argList) - Shape C — simple alias chain (not in the original bug report):
bar = uid; sql = "..." + bar; cur.execute(sql). Even one rename of a tainted variable broke the flow.
Fix
Two surgical changes (~50 LOC total):
detectExpressionScanFlowsnow acceptscode+languageand, for Python, expandssourcesWithVarwith synthetic source records for every derived/aliased variable produced bybuildPythonTaintedVars. Synthetic records inherit the earliest real source'sline/type/confidenceso emitted flows still anchor at the originalrequest.form.get(...)site, not at the alias.buildPythonTaintedVarsgained one rule:(\w+)\.(append|extend|insert|add|push|put|appendleft)\(taintedExpr)taints the receiver. This composes with the existing dict-access propagation so list-append-then-subscript-read round-trips correctly.
Why not a full Python DFG
A proper buildPythonDFG mirroring buildJavaDFG is ~990 LOC plus a separate AST pass for compound-expression arg decomposition. The supplement + rule are deterministic, regex-based, and unblock the entire BenchmarkPython false-negative tail today. Full DFG remains future work and will subsume this supplement.
Java non-regression
Alias expansion is gated on language === 'python'. Java sources rarely set .variable (matched on annotations/types), so sourcesWithVar is empty for Java and the supplement is a no-op. Verified by explicit Java sqli non-regression test + the full 156-case Juliet suite.
Tests
6 new end-to-end regression cases (shapes A, B, B-variant, C, #18 one-hop control, Java non-regression). 1931 passing tests (1925 baseline + 6 new).
Not addressed (future work)
- Cross-module / cross-file helper indirection (
helpers.db_sqlite.results(cur, sql)) — requires inter-procedural taint summaries. - Full Python DFG builder.
🤖 Generated with Claude Code
circle-ir v3.36.0
Fixed
Python taint flows emit for every sink category — systematic fix (#18).
result.taint.flows was empty for every Python case (sqli, command_injection, path_traversal, code_injection, deserialization, xxe, ldap_injection, open_redirect) — including the XSS case the reporter believed was working.
Root causes
Two structural defects, not category-specific:
- No per-language DFG builder for Python.
core/extractors/dfg.ts:buildDFG()dispatches on language with explicit branches for JS, Rust, Bash, Go. Python falls through tobuildJavaDFG(), which scans formethod_declarationAST nodes; Python emitsfunction_definition. Result: every Python file produced empty DFG. - Python compound-expression args lose
arg.variable.extractPythonArgumentsonly setsarg.variablefor bareidentifiernodes.cur.execute("SELECT … " + uid)leavesarg.variable = undefined, defeating the DFG propagator'sarg.variable === use.variablematching.
Fix
Language-agnostic detectExpressionScanFlows() supplement in TaintPropagationPass. Word-boundary matches each source's explicit .variable field against sink call argument expressions. Reuses existing FP filters; respects sink.argPositions. ~40 LOC vs ~990 LOC for a full Python DFG.
Why systematic
Python's findPythonAssignmentSources already sets source.variable for assignment-style sources — a single variable-tracking primitive covers every sink category at once. Not a per-category patch.
Tests
- 10 unit tests (
taint-propagation-pass.test.ts) — positive cases, multi-sink-same-line dedup, argPositions filter, word-boundary, dead-code, Java non-emission, source-after-sink, propagator dedup. - 11 end-to-end tests (
taint-propagation.test.ts) — every previously-broken Python category + XSS positive control + Java sqli non-regression.
Total suite: 1925 passing tests (1904 baseline + 21 new).
Notes
- Reporter's premise that "XSS works, others don't" was falsified by direct probe.
- Python DFG fall-through is a latent bug affecting other consumers (
DFGVerifier,PathFinder). A properbuildPythonDFGremains future work.
circle-ir@3.35.0 — Jenkins Groovy sandbox sink coverage (#17)
Highlights
Closes #17 (CVE-2023-24422). Broadens default code_injection sink coverage for the Jenkins Groovy sandbox dispatch surface from a single method (SandboxInterceptor.onNewInstance) to the full dispatch API — 16 new sink entries.
Added
SandboxInterceptor(9 methods, allcode_injection/ CWE-94 / critical):onMethodCall,onStaticCall,onGetProperty,onSetProperty,onGetAttribute,onSetAttribute,onMethodPointer,onSuperCall,onSuperConstructor.GroovyInterceptor(parent class — 5 methods):onMethodCall,onNewInstance,onStaticCall,onGetProperty,onSetProperty. Plugins extending the parent class directly were previously uncovered.SandboxTransformer.call— AST transformer (CVE bypasses typically target this rewriting step).GroovySandbox.runInSandbox— Jenkins outer wrapper (replaces a fictionalGroovySandbox.sandboxentry).- All entries mirrored in both
src/analysis/config-loader.ts(DEFAULT_SINKS) andconfigs/sinks/code_injection.yaml. - 9 regression tests covering each new dispatch hook, parent-class entries, AST transformer, outer wrapper, property/attribute batch, a negative control, and an end-to-end CVE-2023-24422 shape with HTTP param + header sources.
Notes
The reporter's original "modeled as sanitizer" premise was incorrect on verification — SANITIZER_METHODS contains zero interceptor entries. The real defect was a registry split: getDefaultConfig() only reads the embedded DEFAULT_SINKS array, so YAML-only entries for onMethodCall/onStaticCall were dead-letter. This release closes that split for the Jenkins Groovy surface rather than landing a one-off CVE patch.
Tests
- 1904 / 1904 pass (1895 baseline + 9 new)
Install
npm install circle-ir@3.35.0