Skip to content

Commit 62f2eb9

Browse files
committed
feat: prevent path traversal attacks
1 parent 6948b50 commit 62f2eb9

23 files changed

+490
-21
lines changed

modules/swagger-codegen/src/main/java/io/swagger/codegen/AbstractGenerator.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import java.util.Scanner;
1414
import java.util.regex.Pattern;
1515

16+
import io.swagger.codegen.utils.SecureFileUtils;
1617
import org.apache.commons.lang3.StringUtils;
1718
import org.slf4j.Logger;
1819
import org.slf4j.LoggerFactory;
@@ -21,11 +22,13 @@ public abstract class AbstractGenerator {
2122
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractGenerator.class);
2223

2324
@SuppressWarnings("static-method")
24-
public File writeToFile(String filename, String contents) throws IOException {
25+
public File writeToFile(String filename, String contents) throws IOException, SecurityException {
2526
LOGGER.info("writing file " + filename);
27+
SecureFileUtils.validatePath(filename);
2628
File output = new File(filename);
2729

2830
if (output.getParent() != null && !new File(output.getParent()).exists()) {
31+
SecureFileUtils.validatePath(output.getParent());
2932
File parent = new File(output.getParent());
3033
parent.mkdirs();
3134
}

modules/swagger-codegen/src/main/java/io/swagger/codegen/DefaultCodegen.java

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import com.samskivert.mustache.Mustache;
1616
import com.samskivert.mustache.Template;
17+
import io.swagger.codegen.utils.SecureFileUtils;
1718
import io.swagger.models.properties.UntypedProperty;
1819
import org.apache.commons.lang3.ObjectUtils;
1920
import org.apache.commons.lang3.StringEscapeUtils;
@@ -3562,6 +3563,11 @@ public String apiTestFilename(String templateName, String tag) {
35623563
}
35633564

35643565
public boolean shouldOverwrite(String filename) {
3566+
try {
3567+
SecureFileUtils.validatePath(filename);
3568+
} catch (SecurityException e) {
3569+
return true;
3570+
}
35653571
return !(skipOverwrite && new File(filename).exists());
35663572
}
35673573

@@ -3825,11 +3831,17 @@ public void writeOptional(String outputFolder, SupportingFile supportingFile) {
38253831
else {
38263832
folder = supportingFile.destinationFilename;
38273833
}
3828-
if(!new File(folder).exists()) {
3829-
supportingFiles.add(supportingFile);
3830-
} else {
3831-
LOGGER.info("Skipped overwriting " + supportingFile.destinationFilename + " as the file already exists in " + folder);
3834+
try {
3835+
SecureFileUtils.validatePath(folder);
3836+
if(!new File(folder).exists()) {
3837+
supportingFiles.add(supportingFile);
3838+
} else {
3839+
LOGGER.info("Skipped overwriting " + supportingFile.destinationFilename + " as the file already exists in " + folder);
3840+
}
3841+
} catch (SecurityException e) {
3842+
LOGGER.error("Error while validating path" + folder, e);
38323843
}
3844+
38333845
}
38343846

38353847
/**

modules/swagger-codegen/src/main/java/io/swagger/codegen/DefaultGenerator.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
import com.samskivert.mustache.Mustache;
44
import com.samskivert.mustache.Template;
55
import io.swagger.codegen.ignore.CodegenIgnoreProcessor;
6-
import io.swagger.codegen.languages.AbstractJavaCodegen;
76
import io.swagger.codegen.utils.ImplementationVersion;
7+
import io.swagger.codegen.utils.SecureFileUtils;
88
import io.swagger.models.*;
99
import io.swagger.models.auth.OAuth2Definition;
1010
import io.swagger.models.auth.SecuritySchemeDefinition;
@@ -597,6 +597,7 @@ protected void generateSupportingFiles(List<File> files, Map<String, Object> bun
597597
if (StringUtils.isNotEmpty(support.folder)) {
598598
outputFolder += File.separator + support.folder;
599599
}
600+
SecureFileUtils.validatePath(outputFolder);
600601
File of = new File(outputFolder);
601602
if (!of.isDirectory()) {
602603
of.mkdirs();
@@ -671,6 +672,7 @@ public Reader getTemplate(String name) {
671672
// Output .swagger-codegen-ignore if it doesn't exist and wasn't explicitly created by a generator
672673
final String swaggerCodegenIgnore = ".swagger-codegen-ignore";
673674
String ignoreFileNameTarget = config.outputFolder() + File.separator + swaggerCodegenIgnore;
675+
SecureFileUtils.validatePath(ignoreFileNameTarget);
674676
File ignoreFile = new File(ignoreFileNameTarget);
675677
if (isGenerateSwaggerMetadata && !ignoreFile.exists()) {
676678
String ignoreFileNameSource = File.separator + config.getCommonTemplateDir() + File.separator + swaggerCodegenIgnore;
@@ -785,6 +787,7 @@ public List<File> generate() {
785787

786788
protected File processTemplateToFile(Map<String, Object> templateData, String templateName, String outputFilename) throws IOException {
787789
String adjustedOutputFilename = outputFilename.replaceAll("//", "/").replace('/', File.separatorChar);
790+
SecureFileUtils.validatePath(adjustedOutputFilename);
788791
if (ignoreProcessor.allowsFile(new File(adjustedOutputFilename))) {
789792
String templateFile = getFullTemplateFile(config, templateName);
790793
String template = readTemplate(templateFile);
@@ -801,6 +804,7 @@ public Reader getTemplate(String name) {
801804
.compile(template);
802805

803806
writeToFile(adjustedOutputFilename, tmpl.execute(templateData));
807+
SecureFileUtils.validatePath(adjustedOutputFilename);
804808
return new File(adjustedOutputFilename);
805809
}
806810

modules/swagger-codegen/src/main/java/io/swagger/codegen/ignore/CodegenIgnoreProcessor.java

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.google.common.collect.ImmutableList;
44
import io.swagger.codegen.ignore.rules.DirectoryRule;
55
import io.swagger.codegen.ignore.rules.Rule;
6+
import io.swagger.codegen.utils.SecureFileUtils;
67
import org.slf4j.Logger;
78
import org.slf4j.LoggerFactory;
89

@@ -40,12 +41,19 @@ public CodegenIgnoreProcessor(final String baseDirectory) {
4041
*/
4142
@SuppressWarnings("WeakerAccess")
4243
public CodegenIgnoreProcessor(final String baseDirectory, final String ignoreFile) {
43-
final File directory = new File(baseDirectory);
44-
final File targetIgnoreFile = new File(directory, ignoreFile);
45-
if (directory.exists() && directory.isDirectory()) {
46-
loadFromFile(targetIgnoreFile);
47-
} else {
48-
LOGGER.warn("Output directory does not exist, or is inaccessible. No file (.swagger-codegen-ignore) will be evaluated.");
44+
try {
45+
SecureFileUtils.validatePath(baseDirectory);
46+
final File directory = new File(baseDirectory);
47+
SecureFileUtils.validatePath(directory);
48+
SecureFileUtils.validatePath(ignoreFile);
49+
final File targetIgnoreFile = new File(directory, ignoreFile);
50+
if (directory.exists() && directory.isDirectory()) {
51+
loadFromFile(targetIgnoreFile);
52+
} else {
53+
LOGGER.warn("Output directory does not exist, or is inaccessible. No file (.swagger-codegen-ignore) will be evaluated.");
54+
}
55+
} catch (SecurityException e) {
56+
LOGGER.error("Security violation: attempted to access unsafe ignore file path ", e);
4957
}
5058
}
5159

@@ -55,7 +63,12 @@ public CodegenIgnoreProcessor(final String baseDirectory, final String ignoreFil
5563
* @param targetIgnoreFile The ignore file location.
5664
*/
5765
public CodegenIgnoreProcessor(final File targetIgnoreFile) {
58-
loadFromFile(targetIgnoreFile);
66+
try {
67+
SecureFileUtils.validatePath(targetIgnoreFile);
68+
loadFromFile(targetIgnoreFile);
69+
} catch (SecurityException e) {
70+
LOGGER.error("Security violation: attempted to access unsafe ignore file path: " + targetIgnoreFile.getAbsolutePath(), e);
71+
}
5972
}
6073

6174
private void loadFromFile(File targetIgnoreFile) {

modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/JavaJAXRSSpecServerCodegen.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import io.swagger.codegen.CodegenParameter;
88
import io.swagger.codegen.CodegenProperty;
99
import io.swagger.codegen.SupportingFile;
10+
import io.swagger.codegen.utils.SecureFileUtils;
1011
import io.swagger.models.Operation;
1112
import io.swagger.models.Swagger;
1213
import io.swagger.models.parameters.Parameter;
@@ -197,8 +198,9 @@ public void preprocessSwagger(Swagger swagger) {
197198
//copy input swagger to output folder
198199
try {
199200
String swaggerJson = Json.pretty(swagger);
201+
SecureFileUtils.validatePath(outputFolder);
200202
FileUtils.writeStringToFile(new File(outputFolder + File.separator + "swagger.json"), swaggerJson, StandardCharsets.UTF_8);
201-
} catch (IOException e) {
203+
} catch (IOException | SecurityException e) {
202204
throw new RuntimeException(e.getMessage(), e.getCause());
203205
}
204206
super.preprocessSwagger(swagger);

modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/RubyClientCodegen.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import io.swagger.codegen.CodegenType;
1010
import io.swagger.codegen.DefaultCodegen;
1111
import io.swagger.codegen.SupportingFile;
12+
import io.swagger.codegen.utils.SecureFileUtils;
1213
import io.swagger.models.Model;
1314
import io.swagger.models.Operation;
1415
import io.swagger.models.Swagger;
@@ -741,7 +742,13 @@ public void setGemAuthorEmail(String gemAuthorEmail) {
741742
@Override
742743
public boolean shouldOverwrite(String filename) {
743744
// skip spec file as the file might have been updated with new test cases
744-
return !(skipOverwrite && new File(filename).exists());
745+
try {
746+
SecureFileUtils.validatePath(filename);
747+
return !(skipOverwrite && new File(filename).exists());
748+
} catch (SecurityException e) {
749+
LOGGER.warn("Security violation: attempted to check unsafe file path: " + filename, e);
750+
return false;
751+
}
745752
//
746753
//return super.shouldOverwrite(filename) && !filename.endsWith("_spec.rb");
747754
}

modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/SwaggerGenerator.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,8 @@
66
import java.util.Map;
77

88
import io.swagger.codegen.CliOption;
9-
import io.swagger.codegen.CodegenConstants;
9+
import io.swagger.codegen.utils.SecureFileUtils;
1010
import io.swagger.models.Model;
11-
import io.swagger.models.properties.Property;
1211
import org.apache.commons.io.FileUtils;
1312
import org.apache.commons.lang3.StringUtils;
1413
import org.slf4j.Logger;
@@ -63,6 +62,7 @@ public void processSwagger(Swagger swagger) {
6362

6463
try {
6564
String outputFile = outputFolder + File.separator + this.outputFile;
65+
SecureFileUtils.validatePath(outputFile);
6666
FileUtils.writeStringToFile(new File(outputFile), swaggerString, StandardCharsets.UTF_8);
6767
LOGGER.debug("wrote file to " + outputFile);
6868
} catch (Exception e) {

modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/SwaggerYamlGenerator.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
99
import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator;
1010
import io.swagger.codegen.*;
11+
import io.swagger.codegen.utils.SecureFileUtils;
1112
import io.swagger.jackson.mixin.OperationResponseMixin;
1213
import io.swagger.jackson.mixin.ResponseSchemaMixin;
1314
import io.swagger.models.Model;
@@ -104,6 +105,7 @@ public void processSwagger(Swagger swagger) {
104105
configureMapper(mapper);
105106
String swaggerString = mapper.writeValueAsString(swagger);
106107
String outputFile = outputFolder + File.separator + this.outputFile;
108+
SecureFileUtils.validatePath(outputFile);
107109
FileUtils.writeStringToFile(new File(outputFile), swaggerString, StandardCharsets.UTF_8);
108110
LOGGER.debug("wrote file to " + outputFile);
109111
} catch (Exception e) {
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package io.swagger.codegen.utils;
2+
3+
import java.io.File;
4+
import java.io.IOException;
5+
6+
7+
/**
8+
* Utility class for secure file operations that prevent path traversal attacks.
9+
* Uses a simplified approach focusing on canonical path validation and allowlist-based security.
10+
*/
11+
public class SecureFileUtils {
12+
13+
public static void validatePath(File file) throws SecurityException {
14+
if (file == null) {
15+
throw new IllegalArgumentException("File cannot be null");
16+
}
17+
18+
try {
19+
String absolutePath = file.getAbsolutePath();
20+
String canonicalPath = file.getCanonicalPath();
21+
22+
if (absolutePath.contains("..") || absolutePath.contains("\0")) {
23+
throw new SecurityException("Path contains suspicious characters: " + absolutePath);
24+
}
25+
26+
if (canonicalPath.contains("..") || canonicalPath.contains("\0")) {
27+
throw new SecurityException("Path contains suspicious characters: " + canonicalPath);
28+
}
29+
30+
} catch (IOException e) {
31+
throw new SecurityException("Unable to resolve canonical path for: " + file.getAbsolutePath(), e);
32+
}
33+
}
34+
35+
public static void validatePath(String path) throws SecurityException {
36+
if (path == null || path.trim().isEmpty()) {
37+
throw new IllegalArgumentException("Path cannot be null or empty");
38+
}
39+
40+
if (path.contains("..") || path.contains("\0")) {
41+
throw new SecurityException("Path contains suspicious characters: " + path);
42+
}
43+
44+
validatePath(new File(path));
45+
}
46+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package io.swagger.codegen;
2+
3+
import org.testng.annotations.Test;
4+
5+
import java.io.IOException;
6+
7+
public class AbstractGeneratorTest {
8+
9+
10+
private static class TestableAbstractGenerator extends AbstractGenerator {
11+
}
12+
13+
@Test(expectedExceptions = SecurityException.class)
14+
public void testWriteToFileWithPathTraversal() throws IOException {
15+
TestableAbstractGenerator generator = new TestableAbstractGenerator();
16+
generator.writeToFile("../../../etc/passwd", "malicious content");
17+
}
18+
}

0 commit comments

Comments
 (0)