Skip to content

Releases: GabrielBBaldez/spring-taint

v0.17.1

16 Jun 15:07

Choose a tag to compare

Patch release.

  • Fixed: shortened the GitHub Action description to under 125 characters so the Action can be published to the GitHub Marketplace.
  • Changed: the README header now uses the project logo.

No analysis-behavior change from v0.17.0.

v0.17.0 - @RabbitListener source + real-CVE docs

16 Jun 14:51

Choose a tag to compare

What's new

  • @RabbitListener as a taint source. A RabbitMQ message payload is external, untrusted input, modeled the same way as @KafkaListener. Closes the RabbitMQ item on the Phase 3 roadmap; adds a sqli-via-rabbit benchmark case.
  • Intentionally vulnerable demo app (examples/demo-app) - a standalone Spring Boot 3 app where each category (reflected XSS, command injection, cross-layer SQL injection) ships a vulnerable endpoint and a safe sibling, so one scan demonstrates both true positives and false-positive checks.
  • README gains a "Real CVEs of the classes it detects" section mapping each detector to public CVEs of the same bug class (Spring Cloud / Spring Data SQL injection, Spring Data Commons SpEL injection, a RuoYi SQLi).
  • The design scope doc is brought up to date with shipped work.

Benchmark

40 cases (37 vulnerable, 3 safe): 36 detected by the taint engine alone, 0 false positives; the near-miss layer (--src) catches the remaining wrong-context flow (37/37).

The taint scan runs on a JDK 17 runtime (Tai-e frontend limit); the build is JDK 17+. The pattern scanners (secrets / misconfig / config) have no JDK ceiling.

v0.16.0 — bean / DTO taint modeling

16 Jun 14:24

Choose a tag to compare

Driven by real-world testing (javaspringvulny): request data usually moves through a value object before reaching a sink, and pure source/sink matching misses that.

Value objects (DTOs, form/command beans, entities) are now modelled as taint containers: a tainted bean's String getter returns a tainted value, and a String setter taints the bean. So a flow like controller → form.setTerm(input)form.getTerm() → SQL, or a @RequestBody bean read via its getters, is now caught. Implemented as programmatic taint transfers for application-class String accessors; String-only to stay precise.

Precision verified: on spring-petclinic-rest this generates 1303 accessor transfers and still reports 0 false positives — it does not over-taint real, clean code.

Benchmark: 36 vulnerable cases (35 by the taint engine alone, 0 FP).

v0.15.0 — Spring Boot 2 (javax) support + Map-typed sources

16 Jun 13:48

Choose a tag to compare

Driven by, and verified against, a real vendor app — Contrast-Security-OSS/vulnerable-spring-boot-application (Spring Boot 2, javax.*, value read from a @RequestParam Map). Testing on real projects surfaced two gaps; both are now fixed:

  • Spring Boot 2 / Java EE (javax.*)javax.persistence createQuery/createNativeQuery and javax.servlet sendRedirect/getParameter/getHeader/getQueryString, alongside the jakarta.* signatures. The analyzer now works on Boot 2 apps (still widely deployed), not only Boot 3.
  • Map-typed sources — a Map.get / getOrDefault taint transfer, so a tainted @RequestParam Map<String,String> propagates to values read out of it.

The analyzer now finds that app's cross-layer SQL injection:

[CRITICAL] sql-injection (confidence: 99%)
  Source:  ProviderController.java:32 - search()           (@RequestParam Map)
  Sink:    ProviderSearchDAO.java:18  - createNativeQuery() (javax.persistence)

Benchmark grows to 35 vulnerable cases (34 by the taint engine alone, 0 false positives). docs/validation.md now documents three real apps across both Boot generations — precision (petclinic) and recall (two vulnerable apps).

v0.14.0 — XSS autofix + baseline mode

16 Jun 13:18

Choose a tag to compare

Autofix now covers XSS

In addition to SQL injection, the autofix wraps the interpolated values written to the response in HtmlUtils.htmlEscape(...) and adds the import:

  - response.getWriter().write("<p>" + out + "</p>");
  + response.getWriter().write("<p>" + HtmlUtils.htmlEscape(out) + "</p>");

Verified end-to-end: applying the SQL + XSS fixes compiles cleanly and drops the benchmark's xss findings from 12 to 4 (the rest are cross-method or non-concatenation sinks, out of scope).

Baseline mode

spring-taint scan target/classes --libs … --baseline spring-taint-baseline.txt

The first run records the current findings; later runs report — and gate CI on — only findings not in the baseline. This lets a team adopt the tool on a legacy codebase and fail the build only on new issues. Fingerprints are line-independent, so the baseline survives code moving around.

v0.13.0 — scanner unit tests + any-JDK bytecode (ASM 9.7)

16 Jun 13:10

Choose a tag to compare

Tests

A unit-test suite for the scanners — autofix, near-miss, config audit, config validator, secrets, misconfig — including regression tests for the v0.12.1 fixes (cross-method variable scoping, ambiguous file names, the sensitive-getter false positive, cross-method escaped variable). Test count went from 2 to 18. An in-memory Java compiler builds bytecode fixtures so the ASM-based scanners are tested against real .class files.

Fixed

Writing those tests surfaced a real defect: the bytecode scanners (secrets, misconfig) threw "Unsupported class file major version 65" on Java 21+ bytecode, because ASM was pinned at 9.4. Bumped to 9.7 (reads class files up to Java 23), so the "any JDK" claim for these scanners now actually holds.

The taint scan still needs a JDK 17 runtime — that is a separate Tai-e frontend limitation, not ASM.

v0.12.1 — correctness fixes from code review

16 Jun 04:45

Choose a tag to compare

A self-review of the engine surfaced edge cases the single-package benchmark did not exercise but real multi-package projects would. All fixed:

  • Inner/anonymous classes — flow locations used Foo$1.java (which does not exist), so suppression, near-miss and autofix silently skipped findings inside nested classes. The $… suffix is now stripped.
  • Autofix could rewrite the wrong file when two classes share a simple name across packages (now skipped), and could pick a same-named variable from another method (the lookup is now scoped to the sink's own method).
  • misconfig false positive — a sensitive getter used for a non-logging purpose (e.g. encoder.matches(input, user.getPassword())) no longer taints a later unrelated log call.
  • Near-miss false positive — an escaped variable no longer triggers a wrong-context finding on a same-named variable in another method.
  • Secrets@Value defaults that are themselves references/SpEL (${…}, #{…}) are no longer flagged as literal secrets.
  • validate-config closes its URLClassLoader (releases jar handles on Windows).
  • CI now asserts the benchmark detects exactly 33 findings, so a detection regression or new false positive fails the build.

Benchmark unchanged: 33/33 (taint) + 34 with near-miss, 0 false positives.

v0.12.0 — autofix for SQL injection

16 Jun 04:28

Choose a tag to compare

Turns the tool from a detector that generates work into one that resolves it. For SQL-injection findings, it generates the parameterized-query fix and can apply it.

[suggested fix] sql-injection - UserRepository.java:34 (low confidence)
  use a parameterized query (1 bound parameter)
  - return jdbc.query("SELECT * FROM users WHERE name = '" + name + "'", mapper);
  + return jdbc.query("SELECT * FROM users WHERE name = ?", mapper, name);
  • scan --src <dir> --suggest-fixes — show the fix as a diff, change nothing.
  • --fix — apply high-confidence fixes (short single-method flows) to the source; cross-layer ones are shown but left for review. --fix-confidence all applies every suggestion.

The concatenation becomes a ?-placeholder query and the interpolated values become bound parameters (surrounding quotes are dropped); execute(String) becomes update(String, Object...). The rewrite uses JavaParser and preserves the file's formatting, so the diff is minimal.

Verified end-to-end: applying the fixes drops the benchmark's SQL findings from 15 to 1 (the remaining one is R2DBC DatabaseClient, a different binding idiom and out of scope) and the patched code compiles.

Scope by design: JdbcTemplate query/update/execute whose argument is a string concatenation — anything else is left untouched rather than guessed at, because a wrong fix is worse than none.

v0.11.0 — near-miss sanitizer detection

16 Jun 04:18

Choose a tag to compare

The most dangerous vulnerabilities are not the unsanitized ones — they are the ones the developer believes are sanitized but are not. With scan --src <dir>, findings are now flagged when the path passes an attempted-but-incorrect sanitization:

  • Insufficientname.replaceAll("'", "") before a SQL sink does not prevent injection (backslash escaping, encodings, SQL functions).
  • Blacklistinput.replace("<script>", "") before an HTML sink is trivially bypassed (<scr<script>ipt>).
  • Discarded resulthtmlEscape(input) is called but its return value is ignored while the original input is written.
  • Wrong contexthtmlEscape(url) before response.sendRedirect(...). The taint engine alone treats this as sanitized, so without the near-miss layer it is a false negative — a flow other free tools miss entirely.

Shown in the console as (near-miss sanitizer) with the reason, and in SARIF under result.properties.nearMiss. The first three are real flows the engine already reports, so the annotation is advisory and adds no false positives; the wrong-context case is the one new detection the layer contributes.

Benchmark grows to 37 cases (34 vulnerable, 3 safe): 33/33 by the taint engine, 34 with the near-miss layer, 0 false positives.

Most free SAST tools stop tracking when they see any transformation on the path, assuming it sanitized. This does the opposite — it asks whether the sanitization is real.

v0.10.0 — cross-service, scheduled and transactional sources

16 Jun 03:38

Choose a tag to compare

The last block of the robustness roadmap: untrusted data that does not arrive on the immediate HTTP request. 30/30 vulnerable benchmark cases, 0 false positives.

@FeignClient results (cross-service)

A value returned by a Feign client comes from a downstream service and is untrusted at the caller — catching injection that crosses a service boundary, which tools treating Feign results as clean data miss.

@Scheduled entry points

Scheduled jobs take no request parameters, so handler-only analyzers never look at them. They are now analysis entry points, so external/persisted data they read internally (e.g. a @Repository read) can be followed to a sink.

@Transactional write-then-read

Input persisted and read back within one transaction is covered by the @Repository-read source model.

All three are String-only to stay precise (no false positives on the benchmark's safe cases). Propagating taint through entity/DTO getters would broaden recall at the cost of precision and is intentionally left out.

The taint analysis runs on JDK 17 (Tai-e 0.5.1 does not read JDK 21 bytecode).