Skip to main content

Java and Spring

Vulkro does full source analysis for Java, with first-class Spring Boot support. It extracts your endpoint map, reads the access-control model off the annotations and the security filter chain, runs 30 dedicated Java detectors, tracks request data through same-file call chains to dangerous sinks, and matches your Maven dependencies against the local CVE bundle.

Java is part of the Pro tier. Because your first vulkro login starts a 14-day trial of the full product, you can scan Java from day one. The Free tier covers Python, JavaScript, TypeScript, and Go; Java, Kotlin, C#, PHP, Ruby, and Apex are in Pro.

Project detection

Vulkro recognises a Java project from its build files:

  • pom.xml (Maven)
  • build.gradle (Gradle, Groovy DSL)
  • build.gradle.kts (Gradle, Kotlin DSL)

Spring Boot is recognised from the dependency coordinates in those files: org.springframework.boot, spring-boot-starter, spring-web, and spring-webmvc. When those markers are present, endpoint and access-control extraction turn on automatically.

Run a scan with no extra flags:

vulkro scan .

To see the endpoint map first, without running the security passes:

vulkro discover .
vulkro discover . --format json

Endpoint extraction

Vulkro reads the route table straight from the annotations. For Spring MVC and Spring WebFlux it understands the six mapping annotations:

  • @GetMapping
  • @PostMapping
  • @PutMapping
  • @DeleteMapping
  • @PatchMapping
  • @RequestMapping (including the method = RequestMethod.* form)

Class-level @RequestMapping base paths are combined with the method-level mapping, so the recorded path matches what the server actually serves. A controller annotated @RequestMapping("/api/users") with a method @GetMapping("/{id}") is recorded as GET /api/users/{id}.

For JAX-RS (Jakarta / Java EE REST) it reads @Path together with the method annotations @GET, @POST, @PUT, @DELETE, @PATCH, @HEAD, and @OPTIONS.

Access-control extraction

For each endpoint, Vulkro determines whether an access-control gate is present and what it requires. It reads:

  • Method-security annotations: @PreAuthorize, @PostAuthorize, @Secured, @RolesAllowed, @PermitAll, @DenyAll.
  • SpEL role expressions inside those annotations: hasRole(...), hasAnyRole(...), hasAuthority(...), hasAnyAuthority(...).
  • The HttpSecurity filter-chain DSL: requestMatchers(...), antMatchers(...), mvcMatchers(...), and anyRequest() chained to permitAll(), authenticated(), hasRole(...), and the rest of the authorization terminals.

The result is a per-endpoint protected / unprotected status, the same shape Vulkro builds for every other language. Java endpoints then flow into the language-agnostic OWASP API Top 10 endpoint checks:

  • API1 (BOLA / IDOR) - object-reference handlers without an ownership or tenant gate.
  • API2 (broken authentication) - endpoints reachable with no access-control gate.
  • API4 (unrestricted resource consumption) - list endpoints without pagination.
  • API9 (improper inventory) - the discovered surface as an inventory input.

See OWASP API Top 10.

Detector families

Vulkro ships 30 dedicated Java detectors, each with its own finding ID. Every one carries a finding-level message and remediation you can read with vulkro explain <ID>. The descriptions below are of detector behaviour; the detection logic itself is not published.

Injection

IDWhat it flags
JAVA-SQLI-001Request data concatenated into a JDBC Statement / string-built query instead of a PreparedStatement bind.
JAVA-CMDI-001Untrusted data reaching Runtime.exec / ProcessBuilder command construction.
JAVA-LDAP-001Untrusted data in an LDAP search filter without escaping.
JAVA-XPATH-001Untrusted data concatenated into an XPath expression.
JAVA-SPEL-001Untrusted data evaluated as a Spring Expression Language (SpEL) expression.
JAVA-SCRIPT-001Untrusted data passed to a ScriptEngine (eval) invocation.
JAVA-JNDI-001Untrusted data in a JNDI lookup (the log4shell-shaped sink).
JAVA-REFLECT-001Untrusted data driving reflective class / method resolution.
JAVA-NOSQL-001Untrusted data in a NoSQL query (MongoDB and similar).
JAVA-XSS-001Untrusted data written into an HTML response or template without encoding.
JAVA-CRLF-001Untrusted data in a response header or redirect target (header / response splitting).
JAVA-REDIRECT-001Untrusted data driving a redirect destination (open redirect).
JAVA-SSRF-001Untrusted data driving an outbound HTTP request URL.
JAVA-REDOS-001A user-influenced regular expression prone to catastrophic backtracking.

Deserialization and XML

IDWhat it flags
JAVA-DESER-001Native Java deserialization of untrusted bytes (ObjectInputStream.readObject and the well-known gadget-chain sinks).
JAVA-XXE-001XML parser configured without disabling external entities (XXE).

Cryptography and TLS

IDWhat it flags
JAVA-CRYPTO-001Weak or misused cryptographic primitives (broken ciphers / hashes, ECB mode, static IV).
JAVA-TLS-001Disabled certificate or hostname verification (trust-all TrustManager / HostnameVerifier).
JAVA-RANDOM-001java.util.Random used for security-sensitive values instead of SecureRandom.
JAVA-JWT-001JWT parsing without signature verification, or an insecure signing configuration.
JAVA-TIMING-001Non-constant-time comparison of secrets / tokens.

Session and cookies

IDWhat it flags
JAVA-COOKIE-001Cookies set without the Secure / HttpOnly flags.

Spring and framework configuration

IDWhat it flags
JAVA-SECCONF-001Insecure Spring Security configuration (for example CSRF disabled, or a permissive filter chain).
JAVA-ACTUATOR-001Spring Boot Actuator endpoints exposed without access control.
JAVA-CORS-001Over-permissive CORS configuration (wildcard origin with credentials).

Information exposure

IDWhat it flags
JAVA-ERROR-001Stack traces or internal error detail returned to the client.

File handling

IDWhat it flags
JAVA-PATH-001Untrusted data in a filesystem path (path traversal).
JAVA-FILEPERM-001Files created with overly broad permissions.
JAVA-TOCTOU-001A check-then-use gap on a filesystem resource (time-of-check to time-of-use).
JAVA-MASSASSIGN-001Request bodies bound directly onto persistence entities (mass assignment / over-posting).

Java code is also covered by the language-agnostic trust-boundary check (TRUST-BOUNDARY-001).

Cross-method taint tracking

Beyond the single-statement detectors, Vulkro runs an interprocedural taint pass for Java. It follows request-bound data from a source, through method calls within the same file, to a dangerous sink.

Sources it treats as user-controlled:

  • Spring binding annotations on handler parameters: @RequestParam, @PathVariable, @RequestBody, @RequestHeader, @CookieValue.
  • Servlet request objects typed HttpServletRequest.
  • Servlet entry methods: doGet, doPost, doPut, doDelete, service.

When tainted data reaches a sink through a chain of same-file calls, Vulkro emits one of these rules, and the finding carries the source-to-sink hop chain (rendered as SARIF codeFlows):

IDSink class
JAVA-TAINT-SQL-001SQL query construction
JAVA-TAINT-CMDI-001Command execution
JAVA-TAINT-PATH-001Filesystem path
JAVA-TAINT-SSRF-001Outbound request URL
JAVA-TAINT-DESER-001Deserialization
JAVA-TAINT-REFLECT-001Reflective resolution
JAVA-TAINT-MISC-001Other tracked sinks

See Taint analysis for the cross-language view.

Honest limits

Stated plainly, so you know what the Java taint pass does and does not do:

  • No cross-file taint. A flow is followed only within a single file. A controller that hands a request value to a service class in another file is still scanned in its own right, but the flow spanning both files is not stitched together.
  • Spring bean wiring is not modelled as a flow. Constructor injection and @Autowired dependencies are not followed as taint edges, so a controller-to-service call across files does not propagate taint.
  • Instance-field taint is not propagated. Tainting a field in one method and reading it in another is not tracked.
  • Sanitiser detection is coarse. Recognition of encoders, validators, and parameter binding is pattern-based, not type-resolved.

These limits mean the taint pass under-reports rather than over-reports across file and object boundaries. The single-statement detectors above still fire independently of the taint pass.

Dependencies

Vulkro parses Maven pom.xml <dependencies> and matches them against the local CVE bundle (OSV Maven ecosystem). Coordinates are keyed as groupId:artifactId, and ${property} placeholders are resolved against <properties>. Direct declared dependencies are covered; parent-POM inheritance, <dependencyManagement> version pins, and transitive resolution are out of scope, so a version managed by a parent POM or BOM (for example a Spring Boot starter) is reported without a version and is not matched against CVEs.

Gradle projects (build.gradle, build.gradle.kts) are detected as Java and scanned for code, but their dependency declarations are not yet parsed for CVE matching. If your CVE coverage depends on Gradle manifests, track that as a gap. See Dependencies and CVEs.

Fully offline (VULKRO_OFFLINE=1) Maven matching additionally needs the Maven ecosystem in the local CVE bundle; confirm your installed bundle includes it before relying on offline Maven CVE matching.

Suppressing findings

Inline suppression works in Java comments:

// vulkro:disable next-line JAVA-SQLI-001
String q = "SELECT * FROM users WHERE id = " + id;

You can also suppress in vulkro.toml with an optional expiry. See the suppressions guide for the full syntax.

Not yet in the benchmark

Java and Spring support is newer than Vulkro's published benchmark corpus, so it is not yet reflected in those precision / recall numbers - the corpus's Java repositories stay excluded from the roll-up pending a re-score. See the Benchmark page for what is and is not measured today.

Useful commands

vulkro scan . # full scan
vulkro scan ci . # CI preset (gate + fail-on critical/high)
vulkro discover . --format json # endpoint map only
vulkro dataflow . # input-to-sink data-flow document
vulkro explain JAVA-SQLI-001 # remediation guidance for a finding ID