August 13, 2026
We Found Authorization Vulnerabilities in Two of GitHub’s Most-Starred Java Repositories — Semgrep…
110,000 combined stars. Zero authorization findings from the industry-standard scanners. Here’s what a purpose-built tool found instead.
By Suman Lamichhane
3 min read
We built a static analysis tool called Chanakya. Last week we pointed it at some of the most popular Java repositories on GitHub. What it found surprised us.
The repositories
macrozheng/mall — 84,000 stars. One of the most referenced Spring Boot e-commerce implementations in existence. Thousands of developers use it as a learning reference, and plenty use it as the starting point for production applications.
YunaiV/ruoyi-vue-pro — 26,000 stars. A widely used enterprise Java framework built on Spring Boot, powering internal management systems at companies in China and beyond.
We ran Semgrep on both. Zero security findings related to authorization. We ran CodeQL. Same result.
Then we ran Chanakya.
What we found in mall
In OmsPortalOrderController.java, there's an endpoint that retrieves order details by ID:
@ApiOperation("获取用户订单详情")
@GetMapping(value = "/{id}")
public CommonResult<OmsOrderDetail> detail(@PathVariable Long id) {
OmsOrderDetail orderDetail = portalOrderService.detail(id);
return CommonResult.success(orderDetail);
}@ApiOperation("获取用户订单详情")
@GetMapping(value = "/{id}")
public CommonResult<OmsOrderDetail> detail(@PathVariable Long id) {
OmsOrderDetail orderDetail = portalOrderService.detail(id);
return CommonResult.success(orderDetail);
}The ID comes straight from the URL. Any authenticated customer can change it to any value and retrieve another customer's complete order record — name, shipping address, phone number, and everything they bought.
The fix is one method call away. The deleteOrder endpoint directly below it does this correctly:
if (!order.getMemberId().equals(currentMember.getId())) {
return CommonResult.failed("不是该用户的订单");
}if (!order.getMemberId().equals(currentMember.getId())) {
return CommonResult.failed("不是该用户的订单");
}The safe pattern already exists in the same file, a few lines down. The vulnerable pattern was never caught because no static analysis tool was looking for it.
What we found in ruoyi-vue-pro
GoViewDataController contains an endpoint that executes SQL supplied directly in the request body:
@PostMapping("/getDataBySQL")
@PreAuthorize("@ss.hasPermission('report:go-view-data:get-by-sql')")
public CommonResult<List<Map<String, Object>>> getDataBySQL(
@RequestBody GoViewDataGetReqVO reqVO) {
return success(goViewDataService.getDataBySQL(reqVO));
}@PostMapping("/getDataBySQL")
@PreAuthorize("@ss.hasPermission('report:go-view-data:get-by-sql')")
public CommonResult<List<Map<String, Object>>> getDataBySQL(
@RequestBody GoViewDataGetReqVO reqVO) {
return success(goViewDataService.getDataBySQL(reqVO));
}Inside getDataBySQL, the service executes the SQL string against the database with no parameterization or sanitization. The permission check is real, but it's coarse: any principal holding that one permission gets arbitrary read access to the entire database.
We also found two ERP endpoints where sibling methods carry authorization checks and the endpoints themselves don't — a missing-permission gap in the access control model.
Why these were missed
Broken Access Control is A01 in the OWASP Top 10 2025 — the number one risk category, as it has been since 2021. IDOR is one of its most common variants, and it's also among the hardest things to detect with static analysis, because the vulnerability is the absence of an ownership check rather than the presence of a dangerous function call.
Semgrep detects patterns. CodeQL tracks taint flows into dangerous sinks. Neither is designed to ask the question IDOR detection actually requires:
Does any guard in this codebase verify that the requesting user owns the object they are accessing?
How Chanakya works
Chanakya uses a technique we call dominance-based guard binding. Instead of flagging every database call that receives user input, it asks two questions at once.
First: does a guard dominate this database call in the control flow graph? Dominance means the guard must execute before the database call can be reached — not merely that it appears somewhere nearby in the file.
Second: does that guard bind to the same identifier the user controls? A role check that says "must be logged in" doesn't bind to the specific object being accessed. An ownership check that says "this object must belong to the requesting user" does.
Only when both conditions fail does Chanakya report a finding. That's what keeps the noise manageable.
The numbers
On the SQL injection cases in OWASP BenchmarkJava — 272 verified vulnerable cases — Chanakya achieves 88.60% recall with zero false positives across 232 safe cases.
For access control detection, we hand-labeled 65 cases by reading actual source code across 8 production repositories. No automated labeling. Every true positive and false positive has a written rationale. On that corpus: precision 0.750, recall 0.828.
Worth being direct about what 0.750 precision means: roughly one in four findings needs a human to dismiss it. For a vulnerability class that existing tools report at a rate of zero, we think that's a trade worth making — but it's a triage queue, not an oracle.
Scan time is under 5 seconds on most repositories. mall, at 524 Java files, completed in 32.7 seconds.
The tool runs entirely offline. No code leaves your machine.
Responsible disclosure
We reported the mall finding to the maintainers at github.com/macrozheng/mall/issues/992, and reported the ruoyi findings to those maintainers as well.
We're sharing this publicly to show what static analysis purpose-built for authorization checking finds on real production code.
What this means if you write Java
If you're building a Spring Boot application and you have an endpoint that takes a path variable or request parameter and feeds it into a database lookup, go check whether that code verifies ownership before returning data. Not authentication — ownership.
The pattern that protects you is simple:
if (!object.getOwnerId().equals(currentUser.getId())) {
throw new AccessDeniedException("No permission");
}if (!object.getOwnerId().equals(currentUser.getId())) {
throw new AccessDeniedException("No permission");
}Or with Spring Security:
@PreAuthorize("#id == authentication.principal.id")@PreAuthorize("#id == authentication.principal.id")Chanakya checks for both patterns and their variations across Spring Boot, Quarkus, and Jakarta EE.
Try it
If you build Java applications and want to know what Chanakya finds in your codebase, reach out and we'll run a free scan. Nothing leaves your machine.
Contact: sumanlamichhane45@gmail.com