Skip to content

Releases: cogniumhq/cognium-dev

cognium-dev 3.39.0 — cross-instance field-binding taint paths

12 Jun 07:08

Choose a tag to compare

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

12 Jun 06:21

Choose a tag to compare

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

12 Jun 05:46

Choose a tag to compare

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/.appendleft variants)

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

12 Jun 01:25

Choose a tag to compare

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_injection
  • command_injection (os.system, subprocess.call(..., shell=True))
  • path_traversal
  • code_injection (eval, exec)
  • deserialization (pickle.loads)
  • xxe (ET.fromstring)
  • ldap_injection
  • open_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)

12 Jun 00:29

Choose a tag to compare

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

Tests

  • circle-ir: 1904 / 1904 pass
  • cli: 125 / 125 pass

Install

npm install -g cognium-dev@3.35.0

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

12 Jun 07:07

Choose a tag to compare

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
```

circle-ir 3.38.0 — cross-file inter-procedural taint chains (#19)

12 Jun 06:21

Choose a tag to compare

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 BuildStepCommandRunner.runRuntime.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" +

  1. + "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.
  2. + "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" + .
  3. 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.
  4. + "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

  1. + "isMethodTaintSource" + + + "getSourceType" + skip + "interprocedural_param" + sources entirely.
  2. + "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" + .
  3. 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.
  4. 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
  • 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

12 Jun 05:46

Choose a tag to compare

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):

  1. detectExpressionScanFlows now accepts code + language and, for Python, expands sourcesWithVar with synthetic source records for every derived/aliased variable produced by buildPythonTaintedVars. Synthetic records inherit the earliest real source's line / type / confidence so emitted flows still anchor at the original request.form.get(...) site, not at the alias.
  2. buildPythonTaintedVars gained 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

12 Jun 01:25

Choose a tag to compare

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:

  1. 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 to buildJavaDFG(), which scans for method_declaration AST nodes; Python emits function_definition. Result: every Python file produced empty DFG.
  2. Python compound-expression args lose arg.variable. extractPythonArguments only sets arg.variable for bare identifier nodes. cur.execute("SELECT … " + uid) leaves arg.variable = undefined, defeating the DFG propagator's arg.variable === use.variable matching.

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 proper buildPythonDFG remains future work.

circle-ir@3.35.0 — Jenkins Groovy sandbox sink coverage (#17)

12 Jun 00:29

Choose a tag to compare

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, all code_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 fictional GroovySandbox.sandbox entry).
  • All entries mirrored in both src/analysis/config-loader.ts (DEFAULT_SINKS) and configs/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