Skip to content

Commit 3482b22

Browse files
aksOpsclaude
andcommitted
fix(sonar): S5998 ReDoS regex bounds + S3077 AtomicReference for thread safety + S6856 bind path variable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 5fa6b17 commit 3482b22

5 files changed

Lines changed: 42 additions & 37 deletions

File tree

src/main/java/io/github/randomcodespace/iq/api/TopologyController.java

Lines changed: 34 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.nio.file.Path;
2121
import java.util.List;
2222
import java.util.Map;
23+
import java.util.concurrent.atomic.AtomicReference;
2324

2425
/**
2526
* REST API controller for service topology queries.
@@ -32,8 +33,8 @@ public class TopologyController {
3233
private final TopologyService topologyService;
3334
private final GraphStore graphStore;
3435
private final CodeIqConfig config;
35-
private volatile List<CodeNode> cachedNodes;
36-
private volatile List<CodeEdge> cachedEdges;
36+
private final AtomicReference<List<CodeNode>> cachedNodes = new AtomicReference<>();
37+
private final AtomicReference<List<CodeEdge>> cachedEdges = new AtomicReference<>();
3738

3839
public TopologyController(TopologyService topologyService,
3940
@Autowired(required = false) GraphStore graphStore,
@@ -65,15 +66,16 @@ private boolean hasNeo4jData() {
6566
* Load data from Neo4j if available, otherwise from H2 cache.
6667
*/
6768
private synchronized void ensureDataLoaded() {
68-
if (cachedNodes != null) return;
69+
if (cachedNodes.get() != null) return;
6970

7071
// Try Neo4j first (has enriched data with SERVICE nodes)
7172
if (hasNeo4jData()) {
72-
cachedNodes = graphStore.findAll();
73+
List<CodeNode> nodes = graphStore.findAll();
74+
cachedNodes.set(nodes);
7375
// Collect edges from all nodes' relationship lists
74-
cachedEdges = cachedNodes.stream()
76+
cachedEdges.set(nodes.stream()
7577
.flatMap(n -> n.getEdges().stream())
76-
.toList();
78+
.toList());
7779
return;
7880
}
7981

@@ -83,89 +85,91 @@ private synchronized void ensureDataLoaded() {
8385
Path h2File = root.resolve(config.getCacheDir()).resolve("analysis-cache.mv.db");
8486
if (!Files.exists(h2File)) return;
8587
try (AnalysisCache cache = new AnalysisCache(cachePath)) {
86-
cachedNodes = cache.loadAllNodes();
87-
cachedEdges = cache.loadAllEdges();
88+
cachedNodes.set(cache.loadAllNodes());
89+
cachedEdges.set(cache.loadAllEdges());
8890
}
8991
}
9092

9193
/**
9294
* Invalidate the in-memory cache (e.g. after re-analysis).
9395
*/
9496
public synchronized void invalidateCache() {
95-
cachedNodes = null;
96-
cachedEdges = null;
97+
cachedNodes.set(null);
98+
cachedEdges.set(null);
9799
neo4jHasData = null;
98100
}
99101

100102
@GetMapping
101103
public Map<String, Object> getTopology() {
102104
ensureDataLoaded();
103-
requireCache();
104-
return topologyService.getTopology(cachedNodes, cachedEdges);
105+
List<CodeNode> nodes = requireCache();
106+
return topologyService.getTopology(nodes, cachedEdges.get());
105107
}
106108

107109
@GetMapping("/services/{name}")
108110
public Map<String, Object> serviceDetail(@PathVariable String name) {
109111
ensureDataLoaded();
110-
requireCache();
111-
return topologyService.serviceDetail(name, cachedNodes, cachedEdges);
112+
List<CodeNode> nodes = requireCache();
113+
return topologyService.serviceDetail(name, nodes, cachedEdges.get());
112114
}
113115

114116
@GetMapping("/services/{name}/deps")
115117
public Map<String, Object> serviceDependencies(@PathVariable String name) {
116118
ensureDataLoaded();
117-
requireCache();
118-
return topologyService.serviceDependencies(name, cachedNodes, cachedEdges);
119+
List<CodeNode> nodes = requireCache();
120+
return topologyService.serviceDependencies(name, nodes, cachedEdges.get());
119121
}
120122

121123
@GetMapping("/services/{name}/dependents")
122124
public Map<String, Object> serviceDependents(@PathVariable String name) {
123125
ensureDataLoaded();
124-
requireCache();
125-
return topologyService.serviceDependents(name, cachedNodes, cachedEdges);
126+
List<CodeNode> nodes = requireCache();
127+
return topologyService.serviceDependents(name, nodes, cachedEdges.get());
126128
}
127129

128130
@GetMapping("/blast-radius/{nodeId}")
129131
public Map<String, Object> blastRadius(@PathVariable String nodeId) {
130132
ensureDataLoaded();
131-
requireCache();
132-
return topologyService.blastRadius(nodeId, cachedNodes, cachedEdges);
133+
List<CodeNode> nodes = requireCache();
134+
return topologyService.blastRadius(nodeId, nodes, cachedEdges.get());
133135
}
134136

135137
@GetMapping("/path")
136138
public List<Map<String, Object>> findPath(
137139
@RequestParam("from") String source,
138140
@RequestParam("to") String target) {
139141
ensureDataLoaded();
140-
requireCache();
141-
return topologyService.findPath(source, target, cachedNodes, cachedEdges);
142+
List<CodeNode> nodes = requireCache();
143+
return topologyService.findPath(source, target, nodes, cachedEdges.get());
142144
}
143145

144146
@GetMapping("/bottlenecks")
145147
public List<Map<String, Object>> findBottlenecks() {
146148
ensureDataLoaded();
147-
requireCache();
148-
return topologyService.findBottlenecks(cachedNodes, cachedEdges);
149+
List<CodeNode> nodes = requireCache();
150+
return topologyService.findBottlenecks(nodes, cachedEdges.get());
149151
}
150152

151153
@GetMapping("/circular")
152154
public List<List<String>> findCircularDeps() {
153155
ensureDataLoaded();
154-
requireCache();
155-
return topologyService.findCircularDeps(cachedNodes, cachedEdges);
156+
List<CodeNode> nodes = requireCache();
157+
return topologyService.findCircularDeps(nodes, cachedEdges.get());
156158
}
157159

158160
@GetMapping("/dead")
159161
public List<Map<String, Object>> findDeadServices() {
160162
ensureDataLoaded();
161-
requireCache();
162-
return topologyService.findDeadServices(cachedNodes, cachedEdges);
163+
List<CodeNode> nodes = requireCache();
164+
return topologyService.findDeadServices(nodes, cachedEdges.get());
163165
}
164166

165-
private void requireCache() {
166-
if (cachedNodes == null) {
167+
private List<CodeNode> requireCache() {
168+
List<CodeNode> nodes = cachedNodes.get();
169+
if (nodes == null) {
167170
throw new ResponseStatusException(HttpStatus.NOT_FOUND,
168171
"No analysis cache found. Run analyze first.");
169172
}
173+
return nodes;
170174
}
171175
}

src/main/java/io/github/randomcodespace/iq/detector/java/JdbcDetector.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public class JdbcDetector extends AbstractRegexDetector {
3333
private static final Pattern DRIVER_MANAGER_RE = Pattern.compile(
3434
"DriverManager\\s*\\.\\s*getConnection\\s*\\(\\s*\"(jdbc:[^\"]+)\"");
3535
private static final Pattern JDBC_TEMPLATE_RE = Pattern.compile(
36-
"(?:private|protected|public|final|\\s)+"
36+
"(?:private|protected|public|final)\\s+"
3737
+ "(?:final\\s+)?"
3838
+ "(JdbcTemplate|NamedParameterJdbcTemplate|JdbcClient)"
3939
+ "\\s+\\w+");

src/main/java/io/github/randomcodespace/iq/detector/java/RawSqlDetector.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,14 @@ public class RawSqlDetector extends AbstractRegexDetector {
2727

2828
private static final Pattern CLASS_RE = Pattern.compile("(?:public\\s+)?class\\s+(\\w+)");
2929
private static final Pattern QUERY_ANNO_RE = Pattern.compile(
30-
"@Query\\s*\\(\\s*(?:value\\s*=\\s*)?\"((?:[^\"\\\\]|\\\\.)*)\"", Pattern.DOTALL);
30+
"@Query\\s*\\(\\s*(?:value\\s*=\\s*)?\"([^\"\\\\]*(?:\\\\.[^\"\\\\]*)*)\"", Pattern.DOTALL);
3131
private static final Pattern NATIVE_QUERY_RE = Pattern.compile("nativeQuery\\s*=\\s*true");
3232
private static final Pattern JDBC_TEMPLATE_RE = Pattern.compile(
3333
"(?:jdbcTemplate|namedParameterJdbcTemplate|JdbcTemplate)\\s*\\."
3434
+ "(?:query|queryForObject|queryForList|queryForMap|update|execute|batchUpdate)"
35-
+ "\\s*\\(\\s*\"((?:[^\"\\\\]|\\\\.)*)\"", Pattern.DOTALL);
35+
+ "\\s*\\(\\s*\"([^\"\\\\]*(?:\\\\.[^\"\\\\]*)*)\"", Pattern.DOTALL);
3636
private static final Pattern EM_QUERY_RE = Pattern.compile(
37-
"(?:entityManager|em)\\s*\\.(?:createNativeQuery|createQuery)\\s*\\(\\s*\"((?:[^\"\\\\]|\\\\.)*)\"",
37+
"(?:entityManager|em)\\s*\\.(?:createNativeQuery|createQuery)\\s*\\(\\s*\"([^\"\\\\]*(?:\\\\.[^\"\\\\]*)*)\"",
3838
Pattern.DOTALL);
3939
private static final Pattern TABLE_REF_RE = Pattern.compile(
4040
"\\b(?:FROM|JOIN|INTO|UPDATE|TABLE)\\s+(\\w+)", Pattern.CASE_INSENSITIVE);

src/main/java/io/github/randomcodespace/iq/detector/typescript/NestJSControllerDetector.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,11 @@ public class NestJSControllerDetector extends AbstractAntlrDetector {
4141
private static final Pattern NESTJS_IMPORT = Pattern.compile("from\\s+['\"]@nestjs/");
4242

4343
private static final Pattern CONTROLLER_PATTERN = Pattern.compile(
44-
"@Controller\\(\\s*['\"`]?([^'\"`\\)\\s]*)['\"`]?\\s*\\)(?:\\s*@\\w+\\([^)]*\\))*\\s*\\n\\s*(?:export\\s+)?class\\s+(\\w+)"
44+
"@Controller\\(\\s*['\"`]?([^'\"`\\)\\s]*)['\"`]?\\s*\\)(?:\\s*@\\w+\\([^)]{0,200}\\))*\\s*\\n\\s*(?:export\\s+)?class\\s+(\\w+)"
4545
);
4646

4747
private static final Pattern ROUTE_PATTERN = Pattern.compile(
48-
"@(Get|Post|Put|Delete|Patch|Options|Head)\\(\\s*['\"`]?([^'\"`\\)\\s]*)['\"`]?\\s*\\)(?:\\s*@\\w+\\([^)]*\\))*\\s*\\n\\s*(?:async\\s+)?(\\w+)"
48+
"@(Get|Post|Put|Delete|Patch|Options|Head)\\(\\s*['\"`]?([^'\"`\\)\\s]*)['\"`]?\\s*\\)(?:\\s*@\\w+\\([^)]{0,200}\\))*\\s*\\n\\s*(?:async\\s+)?(\\w+)"
4949
);
5050

5151
@Override

src/main/java/io/github/randomcodespace/iq/web/SpaController.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import org.springframework.context.annotation.Profile;
55
import org.springframework.stereotype.Controller;
66
import org.springframework.web.bind.annotation.GetMapping;
7+
import org.springframework.web.bind.annotation.PathVariable;
78

89
/**
910
* Catch-all controller that forwards unmatched routes to index.html
@@ -40,7 +41,7 @@ public String forward() {
4041
* Does NOT match /api/**, /mcp/**, /actuator/**, or static assets.
4142
*/
4243
@GetMapping("/{path:[^\\.]*}")
43-
public String catchAll() {
44+
public String catchAll(@PathVariable String path) {
4445
return "forward:/index.html";
4546
}
4647
}

0 commit comments

Comments
 (0)