{"id":50904169,"url":"https://github.com/gabrielbbaldez/spring-taint","last_synced_at":"2026-06-16T06:00:25.828Z","repository":{"id":365125410,"uuid":"1270648422","full_name":"GabrielBBaldez/spring-taint","owner":"GabrielBBaldez","description":"Interprocedural taint analysis for Spring Boot, built on Tai-e — detects multi-layer data-flow vulnerabilities that SonarQube can't reach.","archived":false,"fork":false,"pushed_at":"2026-06-16T02:17:50.000Z","size":108,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-16T02:20:38.860Z","etag":null,"topics":["java","sarif","sast","security","spring-boot","static-analysis","tai-e","taint-analysis"],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/GabrielBBaldez.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-15T23:23:28.000Z","updated_at":"2026-06-16T02:17:52.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/GabrielBBaldez/spring-taint","commit_stats":null,"previous_names":["gabrielbbaldez/spring-taint"],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/GabrielBBaldez/spring-taint","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GabrielBBaldez%2Fspring-taint","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GabrielBBaldez%2Fspring-taint/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GabrielBBaldez%2Fspring-taint/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GabrielBBaldez%2Fspring-taint/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/GabrielBBaldez","download_url":"https://codeload.github.com/GabrielBBaldez/spring-taint/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GabrielBBaldez%2Fspring-taint/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34393302,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-16T02:00:06.860Z","response_time":126,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["java","sarif","sast","security","spring-boot","static-analysis","tai-e","taint-analysis"],"created_at":"2026-06-16T06:00:15.932Z","updated_at":"2026-06-16T06:00:25.820Z","avatar_url":"https://github.com/GabrielBBaldez.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"docs/assets/logo.svg\" alt=\"Spring Taint Analyzer\" width=\"104\" height=\"104\" /\u003e\n\u003c/p\u003e\n\n# Spring Taint Analyzer\n\n[![CI](https://github.com/GabrielBBaldez/spring-taint/actions/workflows/ci.yml/badge.svg)](https://github.com/GabrielBBaldez/spring-taint/actions/workflows/ci.yml)\n[![Release](https://img.shields.io/github/v/release/GabrielBBaldez/spring-taint?sort=semver)](https://github.com/GabrielBBaldez/spring-taint/releases)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)\n[![Java](https://img.shields.io/badge/Java-17%2B-orange.svg)](#building)\n\n\u003e Interprocedural taint analysis for Spring Boot applications, built on [Tai-e](https://github.com/pascal-lab/Tai-e).\n\u003e Detects multi-layer data-flow vulnerabilities that conventional tools such as SonarQube cannot reach.\n\nDetects **12 vulnerability classes** across **6 frameworks**, including cross-layer,\nreactive, cross-service, and cross-request stored injection — **33/33 vulnerable\nbenchmark cases with 0 false positives**, plus a near-miss layer that flags\n*attempted-but-incorrect* sanitization. Ships as a CLI, a self-contained jar, a\nDocker image, and a GitHub Action with SARIF 2.1 output.\n\n---\n\n## The problem\n\nConsider this seemingly harmless Spring Boot code:\n\n```java\n// Controller\n@GetMapping(\"/users\")\npublic List\u003cUser\u003e search(@RequestParam String name) {\n    return userService.search(name);\n}\n\n// Service\npublic List\u003cUser\u003e search(String name) {\n    String filtered = nameFilter(name); // looks like sanitization, but isn't\n    return userRepo.findByName(filtered);\n}\n\n// Repository\npublic List\u003cUser\u003e findByName(String name) {\n    return jdbc.query(\n        \"SELECT * FROM users WHERE name = '\" + name + \"'\", // 🚨 SQL Injection\n        mapper\n    );\n}\n```\n\nThe value comes from `@RequestParam`, crosses the service and repository layers, and reaches a SQL query without sanitization. A trivial payload like `name = ' OR '1'='1` exposes the whole table.\n\n**SonarQube does not detect this path.** It only flags the case where the sink is in the same method as the source. The real vulnerability lives in flows that cross multiple layers — and that is exactly where this project operates.\n\n---\n\n## What is taint analysis\n\nTaint analysis tracks the flow of untrusted data through a system using three concepts:\n\n```\n[SOURCE] ──► data flow ──► [SANITIZER?] ──► [SINK]\n                                │\n                        if absent → alert\n```\n\n- **Source** — where external data enters: `@RequestParam`, `@RequestBody`, `@KafkaListener`\n- **Sanitizer** — what cleans the data: `HtmlUtils.htmlEscape()`, parameterized queries, `@Valid`\n- **Sink** — where dangerous data is consumed: `JdbcTemplate.execute()`, `Runtime.exec()`, `response.write()`\n\nIf data flows from a source to a sink **without passing through a sanitizer** → potential vulnerability.\n\nThe analysis is **interprocedural**: it tracks data across methods, classes, and abstraction layers — not just within a single function.\n\n---\n\n## Positioning: complementary to SonarQube\n\nThis project does **not** replace SonarQube. They serve different purposes:\n\n| Tool | Purpose | Interprocedural taint |\n|---|---|---|\n| SonarQube | General quality + bugs + simple vulnerabilities | ❌ |\n| Semgrep OSS | Static code patterns | ❌ |\n| Semgrep Pro | Interprocedural taint | ✅ — but **paid** |\n| Checkmarx / Veracode | Full enterprise SAST | ✅ — but **expensive** |\n| **Spring Taint Analyzer** | Interprocedural taint for Spring Boot | ✅ — **free** |\n\nWhere it differs in practice — the Spring-specific capabilities that depend on real\ninterprocedural taint:\n\n| Capability | Spring Taint | SonarQube (free) | Semgrep OSS |\n|---|:---:|:---:|:---:|\n| Interprocedural taint (across methods/layers) | ✅ | ❌ | ❌ |\n| `@KafkaListener` / `@FeignClient` as sources | ✅ | ❌ | ❌ |\n| `MultipartFile` / `@MatrixVariable` as sources | ✅ | ❌ | ❌ |\n| Conditional sanitizers | ✅ | ❌ | ❌ |\n| Cross-request stored injection | ✅ | ❌ | ❌ |\n| WebFlux / Reactor (`Mono` / `Flux`) | ✅ | ❌ | ❌ |\n| JPQL / template / JNDI / XXE injection | ✅ | ❌ | ❌ |\n| Spring Security \u0026 `application.yml` misconfig | ✅ | ❌ | ❌ |\n| Near-miss sanitizer detection (wrong/insufficient sanitization) | ✅ | ❌ | ❌ |\n| Autofix — applies a parameterized-query patch | ✅ | ❌ | ❌ |\n| Per-finding confidence score | ✅ | ❌ | ❌ |\n| Diff mode for pull requests | ✅ | ❌ | ✅ |\n| SARIF 2.1 output | ✅ | ✅ | ✅ |\n| Free / open source | ✅ | ✅ | ✅ |\n\n\u003e **Semgrep Pro** has interprocedural taint but is a paid product (~$35/dev/month).\n\u003e Spring Taint Analyzer delivers the Spring Boot–focused equivalent, free.\n\nExpected use in a CI pipeline:\n\n```yaml\n- sonarqube scan     # general quality, code smells, coverage\n- spring-taint scan  # deep data-flow vulnerabilities\n```\n\n**Value proposition in one sentence:** you already use SonarQube — this project detects what it cannot see.\n\n---\n\n## Architecture\n\nBuilt on **[Tai-e](https://github.com/pascal-lab/Tai-e)** (Nanjing University, ISSTA 2023), a modern static-analysis framework for Java. Tai-e solves the hard parts — call-graph construction, context-sensitive pointer analysis, and interprocedural **IFDS** taint propagation. Our work is the Spring layer on top of it.\n\n```\nSpring Boot project\n       │  compile (Maven / Gradle)\n       ▼\nBytecode (.class / JAR)            ← analysis runs here, not on source\n       │\n       ▼\nTai-e: Call Graph + Pointer Analysis\n       │\n       ▼\nIFDS Taint Propagation\n       │\n       ▼\nSpring Source/Sink Config          ← our differentiator\n(@RequestParam, @KafkaListener,\n JdbcTemplate, Runtime.exec…)\n       │\n       ▼\nSARIF 2.1 report\n(terminal / GitHub / GitLab / VS Code)\n```\n\nOperating on bytecode (not source) gives precise inheritance/generics resolution, analysis of third-party dependencies without source, and independence from any IDE or build system.\n\n---\n\n## Project layout\n\n```\nspring-taint/\n├── config/\n│   └── spring-taint.yml          # default Spring sources/sinks/sanitizers (Tai-e format)\n├── docs/\n│   └── design/                   # technical scope \u0026 design notes\n├── spring-taint-engine/          # analyzer: CLI, config loader, Tai-e adapter, SARIF reporter\n└── spring-taint-benchmark/       # intentionally vulnerable Spring Boot cases + ground truth\n```\n\n---\n\n## Benchmark\n\nLike FlowDroid's DroidBench, this repo ships a benchmark of intentionally vulnerable (and intentionally safe) Spring Boot cases. Every advertised detection is validated against it before release.\n\nThe benchmark has **37 cases (34 vulnerable, 3 safe)** across SQL and JPQL injection\n(direct, through-service, four-layer, via-Kafka, reactive R2DBC), reflected,\nconditional-sanitizer and **cross-request stored** XSS, SSRF, SpEL, JNDI, XXE,\ntemplate injection (SSTI), log injection, path traversal, command injection, and\nopen redirect — with sources from Spring (`@RequestParam`, `@PathVariable`,\n`@RequestBody`, `@RequestHeader`, `@MatrixVariable`, `MultipartFile`),\n`@KafkaListener`, JAX-RS (`@QueryParam`), `@Repository` reads, **`@FeignClient`\nresults, `@Scheduled` jobs and `@Transactional` write-then-read**, plus taint\nflowing through `Optional` / `CompletableFuture` wrappers. Ground truth is\nin [`expected.yml`](spring-taint-benchmark/expected.yml).\n\nCurrent engine result: **33/33 vulnerable cases detected, 0 false positives** on the\n3 safe cases; the near-miss layer (`--src`) catches one further wrong-context flow\n(34) and explains the rest. Full table: [benchmark README](spring-taint-benchmark/README.md).\nPer-rule reference: [docs/rules.md](docs/rules.md).\n\nPositive cases measure **recall**; safe cases measure **precision**.\n\n---\n\n## Scope by phases\n\n- **Phase 1 — Spring MVC (MVP):** SQL injection, XSS, path traversal, command injection, SSRF, SpEL injection, open redirect. Sources: `@RequestParam`, `@PathVariable`, `@RequestBody`, `@RequestHeader`, `@CookieValue`, `@ModelAttribute`, servlet API. Exit criterion: detect every benchmark case with zero false negatives and precision \u003e 80%.\n- **Phase 2 — gaps left open by existing OSS tools (done):** `@KafkaListener` as a source, conditional sanitizers, custom method sanitizers, cross-request stored injection, WebFlux / async (`Mono`/`Flux` as transparent taint wrappers).\n- **Phase 3 — multi-framework \u0026 robustness (done):** JAX-RS / Quarkus and Micronaut sources; JNDI / XXE / template / JPQL / log-injection sinks; `@FeignClient`, `@Scheduled` and `@Transactional` sources; configuration and misconfiguration audits.\n- **Phase 4 — roadmap:** gRPC and RabbitMQ sources, an IntelliJ plugin, and publishing the image to GHCR.\n\nThe full technical scope lives in [`docs/design/spring-taint-scope.md`](docs/design/spring-taint-scope.md).\n\n---\n\n## Usage\n\nThe commands (the runnable `java -jar …/spring-taint-all.jar` form is in\n[Building](#building); `scan` needs the target's dependency classpath via `--libs`):\n\n```bash\n# Basic scan\nspring-taint scan target/classes --libs \"\u003cdependency classpath\u003e\"\n\n# Custom configuration (merged onto the built-in rules)\nspring-taint scan target/classes --libs \"…\" --config spring-taint.yml\n\n# SARIF output (GitHub Advanced Security, GitLab SAST, VS Code)\nspring-taint scan target/classes --libs \"…\" --output results.sarif\n\n# Filter by severity, show the full trace\nspring-taint scan target/classes --libs \"…\" --severity critical,high --verbose\n\n# Only findings touching files changed vs a base ref (fast PR scans)\nspring-taint scan target/classes --libs \"…\" --diff origin/main\n\n# Near-miss notes + suggested parameterized-query fixes (needs the sources)\nspring-taint scan target/classes --libs \"…\" --src src/main/java --suggest-fixes\n\n# Apply the high-confidence fixes to the source\nspring-taint scan target/classes --libs \"…\" --src src/main/java --fix\n```\n\nExample output (every taint finding carries a confidence score):\n\n```\n[CRITICAL] sql-injection (confidence: 95%)\n  Source:  UserController.java:28 - search() - tainted parameter\n  Flow:    UserController.search() -\u003e UserService.search() -\u003e UserRepository.query()\n  Sink:    UserRepository.java:27 - sink: query()\n  Sanitizer: none detected\n```\n\n---\n\n## Extensibility\n\nTeams can add their own rules in Tai-e's YAML format:\n\n```yaml\nsources:\n  - { kind: call,  method: \"\u003ccom.myapp.LegacyInput: java.lang.String readUserData()\u003e\", index: result }\n  - { kind: param, method: \"\u003ccom.myapp.EventHandler: void onEvent(java.lang.String)\u003e\", index: 0 }\n\nsinks:\n  - { method: \"\u003ccom.myapp.LegacyDao: void rawExecute(java.lang.String)\u003e\", index: 0 }\n\nsanitizers:\n  - { method: \"\u003ccom.myapp.Validator: java.lang.String sanitize(java.lang.String)\u003e\", index: 0 }\n```\n\n---\n\n## Building\n\nRequires JDK 17+ and Maven.\n\n```bash\nmvn -q clean package          # build engine + benchmark\nmvn -q -pl spring-taint-benchmark package   # compile the benchmark cases only\n```\n\n### Running a scan\n\nBuild the self-contained jar and scan compiled classes. Pass the target's\ndependency classpath with `--libs` so framework types like `JdbcTemplate` resolve\n(the taint config is bundled, so `--config` is optional):\n\n```bash\njava -jar spring-taint-engine/target/spring-taint-all.jar \\\n  scan target/classes --libs \"$(... your dependency classpath ...)\" \\\n  --output results.sarif\n```\n\n\u003e **The analysis currently runs on JDK 17.** Tai-e 0.5.1's frontend cannot read\n\u003e JDK 21 bytecode, so run the analyzer with a JDK 17 runtime (the project still\n\u003e compiles to Java 17 on any JDK 17+).\n\nA custom `--config` is **merged** onto the built-in rules (use `--no-default-config`\nto replace them instead).\n\n### Secrets, configuration and misconfiguration scans\n\nThree pattern-based scans (any JDK, no taint engine) complement the taint analysis:\n\n```bash\n# Hardcoded secrets in bytecode — secret-named constants, known key formats\n# (AWS, GitHub, …), and @Value defaults\njava -jar …/spring-taint-all.jar secrets target/classes\n\n# Insecure settings in application*.yml / .properties — hardcoded secrets,\n# disabled TLS, Security auto-config excluded, Actuator \"*\", H2 console\njava -jar …/spring-taint-all.jar config src/main/resources\n\n# Insecure Spring code in bytecode — csrf()/frameOptions().disable(),\n# @CrossOrigin(\"*\"), insecure cookies, sensitive data logged\njava -jar …/spring-taint-all.jar misconfig target/classes\n```\n\n---\n\n## GitHub Action\n\nRun the analyzer in CI and upload findings to GitHub code scanning. The action\nruns as a Docker container on JDK 17; give it the compiled classes and the\ndependency classpath:\n\n```yaml\n- uses: actions/checkout@v4\n- uses: actions/setup-java@v4\n  with: { distribution: temurin, java-version: '17' }\n\n- run: mvn -B -ntp package -DskipTests\n- id: cp\n  run: echo \"value=$(mvn -q dependency:build-classpath -Dmdep.outputFilterFile=/dev/stdout)\" \u003e\u003e \"$GITHUB_OUTPUT\"\n\n- name: Spring Taint Analysis\n  uses: GabrielBBaldez/spring-taint@main\n  with:\n    path: target/classes\n    libs: ${{ steps.cp.outputs.value }}\n    output: results.sarif\n    severity: critical,high\n\n- uses: github/codeql-action/upload-sarif@v3\n  if: always()\n  with:\n    sarif_file: results.sarif\n```\n\nSee [`action.yml`](action.yml) for all inputs. This repo also scans its own\nbenchmark on every push — see [`.github/workflows/ci.yml`](.github/workflows/ci.yml).\n\n---\n\n## Dashboard\n\nA web console (React + Vite + TypeScript) visualizes the SARIF output: severity\nbreakdown, findings by rule, and the full **source → sink taint flow** for each\nfinding. Drop a `.sarif` file to load your own report. See\n[`dashboard/`](dashboard/).\n\n```bash\ncd dashboard \u0026\u0026 npm install \u0026\u0026 npm run dev   # → http://localhost:4321\n```\n\n---\n\n## Status\n\n- [x] Scope and positioning\n- [x] Engine choice (Tai-e)\n- [x] Gap mapping vs. competitors\n- [x] Project scaffold (Maven multi-module, CLI skeleton, config loader, SARIF model)\n- [x] Initial benchmark: SQL injection (direct / through-service / via-Kafka / safe), reflected XSS, path traversal, command injection\n- [x] Engine: Tai-e IFDS wired end-to-end on the benchmark\n- [x] Spring source layer: annotation → Tai-e param-source generation\n- [x] Functional CLI with SARIF output\n- [x] precision/recall on the current benchmark — **30/30 vulnerable cases detected, 0 false positives** across SQL / JPQL injection (direct / through-service / four-layer / via-Kafka / reactive R2DBC), reflected / conditional / **cross-request stored** XSS, SSRF, SpEL, JNDI, XXE, template injection (SSTI), log injection, path traversal, command injection, open redirect; multi-framework sources (Spring MVC/WebFlux, Kafka, JAX-RS/Quarkus, Micronaut, `@Repository` reads, `@FeignClient`, `@Scheduled`)\n- [x] Phase 2 differentiators: `@KafkaListener` source, conditional sanitizers, stored / second-order injection\n- [x] GitHub Action (Docker) + self-contained jar + CI workflow\n- [x] Web dashboard (React + Vite) for SARIF reports\n- [x] Hardcoded-secrets scanner (`secrets` command); mergeable `--config`\n- [x] Robustness pass: JNDI / XXE / log / template / JPQL sinks, file-upload and `@MatrixVariable` sources, `Optional` / `CompletableFuture` taint transfers, framework-internal sink filtering\n- [x] Configuration \u0026 misconfiguration audits: `config` (insecure `application.yml`/`.properties`) and `misconfig` (CSRF/clickjacking disabled, CORS `*`, insecure cookies, sensitive data logged)\n- [x] Adoption: per-finding confidence score (console + SARIF), `scan --diff \u003cref\u003e` for fast pull-request scans, inline `// spring-taint: suppress` comments (`--src` / `suppressions`), and `validate-config` to catch typo'd custom rules\n- [x] Advanced sources: `@FeignClient` results (cross-service), `@Scheduled` jobs as entry points, and `@Transactional` write-then-read stored injection\n- [x] Near-miss sanitizers (`--src`): flags insufficient (quote-stripping), blacklist, discarded-result, and wrong-context sanitization — the \"I'm sure this is safe\" class of bug\n- [x] Autofix (`--suggest-fixes` / `--fix`): rewrites a concatenated SQL query into a parameterized one; verified end-to-end (applying the fixes drops the benchmark's SQL findings 15 → 1 and the patched code compiles)\n\n---\n\n## Known limitations\n\nStatic analysis has inherent limits. For this project:\n\n- **Java reflection** (`Class.forName()`, `Method.invoke()`) can break the flow\n- **Spring dynamic proxies** (AOP / CGLib) introduce indirection that may break the call graph\n- **Entity / DTO field tracking** — sources are `String`-only (a `@Repository`/`@FeignClient` returning a DTO whose getter is later read is not followed), a deliberate precision-over-recall choice\n- **Complex lambdas / method references** — partial coverage via Tai-e\n- **The taint analysis runs on JDK 17** — Tai-e 0.5.1 cannot read JDK 21 bytecode\n\nEach release documents its limitations explicitly, alongside the test cases that exercise them.\n\n---\n\n## Contributing\n\nContributions are welcome — see [CONTRIBUTING.md](CONTRIBUTING.md) for the dev\nsetup, how to add a benchmark case, and the PR checklist. Please also read the\n[Code of Conduct](CODE_OF_CONDUCT.md). To report a security issue, see\n[SECURITY.md](SECURITY.md).\n\n## Acknowledgements\n\nBuilt on [Tai-e](https://github.com/pascal-lab/Tai-e) (Nanjing University), which\nprovides the call-graph construction, pointer analysis, and IFDS taint propagation.\nTai-e is licensed under LGPL-3.0; this project depends on it as a library.\n\n## License\n\n[MIT](LICENSE) © Gabriel Baldez.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgabrielbbaldez%2Fspring-taint","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgabrielbbaldez%2Fspring-taint","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgabrielbbaldez%2Fspring-taint/lists"}