Skip to content

Commit f046489

Browse files
committed
Merge branch 'fix/snakeyaml-safeconstructor-cors'
2 parents 88bf14c + b78db62 commit f046489

7 files changed

Lines changed: 366 additions & 8 deletions

File tree

src/main/java/io/github/randomcodespace/iq/analyzer/ConfigScanner.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ private void scanSpringConfig(Path root, InfrastructureRegistry registry) {
9494
private void parseSpringYaml(Path file, InfrastructureRegistry registry) {
9595
try {
9696
String content = Files.readString(file, StandardCharsets.UTF_8);
97-
Yaml yaml = new Yaml();
97+
Yaml yaml = new Yaml(new org.yaml.snakeyaml.constructor.SafeConstructor(new org.yaml.snakeyaml.LoaderOptions()));
9898
Object loaded = yaml.load(content);
9999
if (!(loaded instanceof Map<?, ?> raw)) return;
100100

@@ -224,7 +224,7 @@ private void scanDockerCompose(Path root, InfrastructureRegistry registry) {
224224
private void parseDockerCompose(Path file, InfrastructureRegistry registry) {
225225
try {
226226
String content = Files.readString(file, StandardCharsets.UTF_8);
227-
Yaml yaml = new Yaml();
227+
Yaml yaml = new Yaml(new org.yaml.snakeyaml.constructor.SafeConstructor(new org.yaml.snakeyaml.LoaderOptions()));
228228
Object loaded = yaml.load(content);
229229
if (!(loaded instanceof Map<?, ?> data)) return;
230230

src/main/java/io/github/randomcodespace/iq/analyzer/StructuredParser.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public Object parse(String language, String content, String filePath) {
6262

6363
@SuppressWarnings("unchecked")
6464
private Object parseYaml(String content) {
65-
var yaml = new Yaml();
65+
var yaml = new Yaml(new org.yaml.snakeyaml.constructor.SafeConstructor(new org.yaml.snakeyaml.LoaderOptions()));
6666
var docs = new java.util.ArrayList<>();
6767
for (Object doc : yaml.loadAll(content)) {
6868
docs.add(doc);

src/main/java/io/github/randomcodespace/iq/cli/GraphCommand.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ private String renderYaml(List<CodeNode> nodes) {
122122
graphData.put("nodes", nodeList);
123123
graphData.put("count", nodes.size());
124124

125-
Yaml yaml = new Yaml();
125+
Yaml yaml = new Yaml(new org.yaml.snakeyaml.constructor.SafeConstructor(new org.yaml.snakeyaml.LoaderOptions()));
126126
return yaml.dump(graphData);
127127
}
128128

src/main/java/io/github/randomcodespace/iq/config/CorsConfig.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.github.randomcodespace.iq.config;
22

3+
import org.springframework.beans.factory.annotation.Value;
34
import org.springframework.context.annotation.Bean;
45
import org.springframework.context.annotation.Configuration;
56
import org.springframework.context.annotation.Profile;
@@ -9,17 +10,22 @@
910
@Configuration
1011
@Profile("serving")
1112
public class CorsConfig {
13+
14+
@Value("${codeiq.cors.allowed-origin-patterns:http://localhost:[*],http://127.0.0.1:[*]}")
15+
private String allowedOriginPatterns = "http://localhost:[*],http://127.0.0.1:[*]";
16+
1217
@Bean
1318
public WebMvcConfigurer corsConfigurer() {
19+
String[] patterns = allowedOriginPatterns.split(",");
1420
return new WebMvcConfigurer() {
1521
@Override
1622
public void addCorsMappings(CorsRegistry registry) {
1723
registry.addMapping("/api/**")
18-
.allowedOrigins("*")
24+
.allowedOriginPatterns(patterns)
1925
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
2026
.allowedHeaders("*");
2127
registry.addMapping("/mcp/**")
22-
.allowedOrigins("*")
28+
.allowedOriginPatterns(patterns)
2329
.allowedMethods("GET", "POST", "OPTIONS")
2430
.allowedHeaders("*");
2531
}

src/main/java/io/github/randomcodespace/iq/config/ProjectConfigLoader.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public static boolean loadIfPresent(Path directory, CodeIqConfig config) {
4444
if (Files.isRegularFile(configFile)) {
4545
try {
4646
String content = Files.readString(configFile, StandardCharsets.UTF_8);
47-
Yaml yaml = new Yaml();
47+
Yaml yaml = new Yaml(new org.yaml.snakeyaml.constructor.SafeConstructor(new org.yaml.snakeyaml.LoaderOptions()));
4848
Map<String, Object> data = yaml.load(content);
4949
if (data != null) {
5050
applyOverrides(data, config);
@@ -74,7 +74,7 @@ public static ProjectConfig loadProjectConfig(Path directory) {
7474
if (Files.isRegularFile(configFile)) {
7575
try {
7676
String content = Files.readString(configFile, StandardCharsets.UTF_8);
77-
Yaml yaml = new Yaml();
77+
Yaml yaml = new Yaml(new org.yaml.snakeyaml.constructor.SafeConstructor(new org.yaml.snakeyaml.LoaderOptions()));
7878
Map<String, Object> data = yaml.load(content);
7979
if (data != null) {
8080
log.info("Loaded project config from {}", configFile);
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package io.github.randomcodespace.iq.config;
2+
3+
import org.junit.jupiter.api.Test;
4+
import org.springframework.web.cors.CorsConfiguration;
5+
import org.springframework.web.servlet.config.annotation.CorsRegistry;
6+
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
7+
8+
import java.util.Map;
9+
10+
import static org.junit.jupiter.api.Assertions.*;
11+
12+
class CorsConfigTest {
13+
14+
static class TestableCorsRegistry extends CorsRegistry {
15+
@Override
16+
public Map<String, CorsConfiguration> getCorsConfigurations() {
17+
return super.getCorsConfigurations();
18+
}
19+
}
20+
21+
private CorsConfig createCorsConfig() {
22+
return new CorsConfig();
23+
}
24+
25+
@Test
26+
void corsConfigurerReturnsWebMvcConfigurer() {
27+
WebMvcConfigurer configurer = createCorsConfig().corsConfigurer();
28+
assertNotNull(configurer);
29+
}
30+
31+
@Test
32+
void corsConfigurerDoesNotThrowWhenAddingMappings() {
33+
WebMvcConfigurer configurer = createCorsConfig().corsConfigurer();
34+
TestableCorsRegistry registry = new TestableCorsRegistry();
35+
assertDoesNotThrow(() -> configurer.addCorsMappings(registry));
36+
}
37+
38+
@Test
39+
void corsRegistryContainsApiAndMcpMappings() {
40+
WebMvcConfigurer configurer = createCorsConfig().corsConfigurer();
41+
TestableCorsRegistry registry = new TestableCorsRegistry();
42+
configurer.addCorsMappings(registry);
43+
44+
var configurations = registry.getCorsConfigurations();
45+
assertTrue(configurations.containsKey("/api/**"),
46+
"Should register CORS mapping for /api/**");
47+
assertTrue(configurations.containsKey("/mcp/**"),
48+
"Should register CORS mapping for /mcp/**");
49+
}
50+
51+
@Test
52+
void apiMappingAllowsExpectedMethods() {
53+
WebMvcConfigurer configurer = createCorsConfig().corsConfigurer();
54+
TestableCorsRegistry registry = new TestableCorsRegistry();
55+
configurer.addCorsMappings(registry);
56+
57+
var configurations = registry.getCorsConfigurations();
58+
var apiCors = configurations.get("/api/**");
59+
assertNotNull(apiCors);
60+
var methods = apiCors.getAllowedMethods();
61+
assertNotNull(methods);
62+
assertTrue(methods.contains("GET"));
63+
assertTrue(methods.contains("OPTIONS"));
64+
}
65+
66+
@Test
67+
void mcpMappingAllowsGetPostOptions() {
68+
WebMvcConfigurer configurer = createCorsConfig().corsConfigurer();
69+
TestableCorsRegistry registry = new TestableCorsRegistry();
70+
configurer.addCorsMappings(registry);
71+
72+
var configurations = registry.getCorsConfigurations();
73+
var mcpCors = configurations.get("/mcp/**");
74+
assertNotNull(mcpCors);
75+
var methods = mcpCors.getAllowedMethods();
76+
assertNotNull(methods);
77+
assertTrue(methods.contains("GET"));
78+
assertTrue(methods.contains("POST"));
79+
assertTrue(methods.contains("OPTIONS"));
80+
}
81+
82+
@Test
83+
void apiMappingRestrictsToLocalhostOrigins() {
84+
WebMvcConfigurer configurer = createCorsConfig().corsConfigurer();
85+
TestableCorsRegistry registry = new TestableCorsRegistry();
86+
configurer.addCorsMappings(registry);
87+
88+
var configurations = registry.getCorsConfigurations();
89+
var apiCors = configurations.get("/api/**");
90+
assertNotNull(apiCors);
91+
var patterns = apiCors.getAllowedOriginPatterns();
92+
assertNotNull(patterns);
93+
assertTrue(patterns.stream().anyMatch(p -> p.contains("localhost")),
94+
"CORS should restrict to localhost origins");
95+
}
96+
97+
@Test
98+
void apiMappingAllowsAllHeaders() {
99+
WebMvcConfigurer configurer = createCorsConfig().corsConfigurer();
100+
TestableCorsRegistry registry = new TestableCorsRegistry();
101+
configurer.addCorsMappings(registry);
102+
103+
var configurations = registry.getCorsConfigurations();
104+
var apiCors = configurations.get("/api/**");
105+
assertNotNull(apiCors);
106+
var headers = apiCors.getAllowedHeaders();
107+
assertNotNull(headers);
108+
assertTrue(headers.contains("*"));
109+
}
110+
}

0 commit comments

Comments
 (0)