Skip to content

Commit 14d653b

Browse files
authored
Merge pull request #1 from FTMahringer/alert-autofix-10
Potential fix for code scanning alert no. 10: Server-side request forgery
2 parents cee4f53 + 47f69a3 commit 14d653b

3 files changed

Lines changed: 51 additions & 24 deletions

File tree

packages/core/src/main/java/dev/synapse/plugins/loader/PluginLoaderController.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,21 @@ public PluginDTO updatePlugin(
179179
if (jarPath == null || jarPath.isBlank()) {
180180
throw new IllegalArgumentException("jarPath is required");
181181
}
182-
LoadedPlugin loaded = updateService.updatePlugin(id, Path.of(jarPath));
182+
String trimmed = jarPath.trim();
183+
if (
184+
trimmed.contains("..") ||
185+
trimmed.contains("/") ||
186+
trimmed.contains("\\") ||
187+
!trimmed.endsWith(".jar")
188+
) {
189+
throw new IllegalArgumentException(
190+
"jarPath must be a safe .jar filename"
191+
);
192+
}
193+
LoadedPlugin loaded = updateService.updatePlugin(
194+
id,
195+
Path.of(trimmed).getFileName()
196+
);
183197
Plugin dbPlugin = lifecycleService.findById(id);
184198
return DtoMapper.toDTO(dbPlugin);
185199
}

packages/core/src/main/java/dev/synapse/plugins/loader/PluginLoaderService.java

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
import java.net.MalformedURLException;
1616
import java.net.URL;
1717
import java.net.URLClassLoader;
18+
import java.nio.file.Files;
1819
import java.nio.file.Path;
20+
import java.nio.file.Paths;
1921
import java.time.Instant;
2022
import java.util.*;
2123
import java.util.concurrent.ConcurrentHashMap;
@@ -83,53 +85,57 @@ public LoadedPlugin loadPlugin(Path jarPath, Plugin dbPlugin)
8385
updateLoaderState(pluginId, Plugin.LoaderState.LOADING, null);
8486

8587
try {
86-
// Validate jarPath is a local file under the plugins directory
87-
// (prevent SSRF via external URLs and path traversal)
88-
Path normalized = jarPath.normalize();
89-
Path pluginsDir = Path.of(
88+
// Validate jarPath is a local canonical file under the plugins directory
89+
// (prevent SSRF/path confusion via traversal or symlink escape)
90+
Path pluginsDir = Paths.get(
9091
System.getenv().getOrDefault(
9192
"SYNAPSE_HOME",
9293
System.getProperty("user.home") + "/.synapse"
9394
),
9495
"plugins"
95-
).normalize();
96-
if (
97-
!normalized.isAbsolute() || !normalized.startsWith(pluginsDir)
98-
) {
96+
);
97+
Path normalized = jarPath.normalize();
98+
if (!normalized.isAbsolute()) {
9999
throw new PluginLoadException(
100100
pluginId,
101101
"Plugin JAR path must be an absolute local file under " +
102102
pluginsDir
103103
);
104104
}
105-
if (!java.nio.file.Files.exists(normalized)) {
105+
if (!Files.exists(normalized)) {
106106
throw new PluginLoadException(
107107
pluginId,
108108
"Plugin JAR not found: " + normalized
109109
);
110110
}
111111

112-
// Build file:// URL directly from validated local path.
113-
// CodeQL flags toUri().toURL() as SSRF-prone, so we construct
114-
// the URL string manually from the already-validated path.
115-
String absolutePath = normalized.toAbsolutePath().toString();
116-
String fileUrl = "file://" + absolutePath.replace('\\', '/');
112+
Path realPluginsDir;
113+
Path realJarPath;
114+
try {
115+
realPluginsDir = pluginsDir.toRealPath();
116+
realJarPath = normalized.toRealPath();
117+
} catch (java.io.IOException e) {
118+
throw new PluginLoadException(
119+
pluginId,
120+
"Failed to resolve canonical plugin path: " + e.getMessage(),
121+
e
122+
);
123+
}
117124

118-
// Validate the URL string starts with file:// before creating URL
119-
if (!fileUrl.startsWith("file://")) {
125+
if (!realJarPath.startsWith(realPluginsDir) || !Files.isRegularFile(realJarPath)) {
120126
throw new PluginLoadException(
121127
pluginId,
122-
"Plugin JAR URL must start with file://"
128+
"Plugin JAR path must be a regular file under " + realPluginsDir
123129
);
124130
}
125131

126132
URL jarUrl;
127133
try {
128-
jarUrl = new URL(fileUrl);
134+
jarUrl = realJarPath.toUri().toURL();
129135
} catch (MalformedURLException e) {
130136
throw new PluginLoadException(
131137
pluginId,
132-
"Invalid JAR URL: " + fileUrl
138+
"Invalid JAR URL: " + realJarPath
133139
);
134140
}
135141

@@ -140,7 +146,7 @@ public LoadedPlugin loadPlugin(Path jarPath, Plugin dbPlugin)
140146
);
141147

142148
// Layer 2: Create JPMS ModuleLayer
143-
ModuleFinder pluginFinder = ModuleFinder.of(normalized);
149+
ModuleFinder pluginFinder = ModuleFinder.of(realJarPath);
144150
Set<ModuleDescriptor> descriptors = pluginFinder
145151
.findAll()
146152
.stream()

packages/core/src/main/java/dev/synapse/plugins/loader/PluginUpdateService.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,13 @@ public PluginUpdateService(
6161
*/
6262
public LoadedPlugin updatePlugin(String pluginId, Path newJarPath)
6363
throws PluginLoadException {
64+
Path jarFileName = newJarPath.getFileName();
65+
if (jarFileName == null || !jarFileName.equals(newJarPath)) {
66+
throw new PluginLoadException(
67+
pluginId,
68+
"Invalid JAR path: only a file name is allowed"
69+
);
70+
}
6471
Plugin dbPlugin = pluginRepository
6572
.findById(pluginId)
6673
.orElseThrow(() ->
@@ -106,7 +113,7 @@ public LoadedPlugin updatePlugin(String pluginId, Path newJarPath)
106113

107114
// 3. Stage new JAR
108115
try {
109-
storageService.stageJar(newJarPath);
116+
storageService.stageJar(jarFileName);
110117
} catch (Exception e) {
111118
throw new PluginLoadException(
112119
pluginId,
@@ -118,15 +125,15 @@ public LoadedPlugin updatePlugin(String pluginId, Path newJarPath)
118125
// 4. Load new plugin
119126
Path stagedJar = storageService
120127
.getStagingDir()
121-
.resolve(newJarPath.getFileName().toString());
128+
.resolve(jarFileName.toString());
122129
LoadedPlugin loaded = loaderService.loadPlugin(stagedJar, dbPlugin);
123130

124131
// 5. Register in registry
125132
registerInRegistry(loaded, dbPlugin);
126133

127134
// 6. Promote to system
128135
try {
129-
storageService.promoteToSystem(newJarPath.getFileName().toString());
136+
storageService.promoteToSystem(jarFileName.toString());
130137
} catch (java.io.IOException e) {
131138
throw new PluginLoadException(
132139
pluginId,

0 commit comments

Comments
 (0)