Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import java.util.Scanner;
import java.util.regex.Pattern;

import io.swagger.codegen.utils.SecureFileUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -23,6 +24,7 @@ public abstract class AbstractGenerator {
@SuppressWarnings("static-method")
public File writeToFile(String filename, String contents) throws IOException {
LOGGER.info("writing file " + filename);
SecureFileUtils.validatePath(filename);
File output = new File(filename);

if (output.getParent() != null && !new File(output.getParent()).exists()) {
Expand Down Expand Up @@ -106,7 +108,7 @@ public String getFullTemplateFile(CodegenConfig config, String templateFile) {
return embeddedLibTemplateFile;
}
}

// Fall back to the template file embedded/packaged in the JAR file...
return config.embeddedTemplateDir() + File.separator + templateFile;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

import com.samskivert.mustache.Mustache;
import com.samskivert.mustache.Template;
import io.swagger.codegen.utils.SecureFileUtils;
import io.swagger.models.properties.UntypedProperty;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringEscapeUtils;
Expand Down Expand Up @@ -3562,6 +3563,7 @@ public String apiTestFilename(String templateName, String tag) {
}

public boolean shouldOverwrite(String filename) {
SecureFileUtils.validatePath(filename);
return !(skipOverwrite && new File(filename).exists());
}

Expand Down Expand Up @@ -3825,6 +3827,7 @@ public void writeOptional(String outputFolder, SupportingFile supportingFile) {
else {
folder = supportingFile.destinationFilename;
}
SecureFileUtils.validatePath(folder);
if(!new File(folder).exists()) {
supportingFiles.add(supportingFile);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import com.samskivert.mustache.Mustache;
import com.samskivert.mustache.Template;
import io.swagger.codegen.ignore.CodegenIgnoreProcessor;
import io.swagger.codegen.languages.AbstractJavaCodegen;
import io.swagger.codegen.utils.ImplementationVersion;
import io.swagger.codegen.utils.SecureFileUtils;
import io.swagger.models.*;
import io.swagger.models.auth.OAuth2Definition;
import io.swagger.models.auth.SecuritySchemeDefinition;
Expand Down Expand Up @@ -597,6 +597,7 @@ protected void generateSupportingFiles(List<File> files, Map<String, Object> bun
if (StringUtils.isNotEmpty(support.folder)) {
outputFolder += File.separator + support.folder;
}
SecureFileUtils.validatePath(outputFolder);
File of = new File(outputFolder);
if (!of.isDirectory()) {
of.mkdirs();
Expand Down Expand Up @@ -671,6 +672,7 @@ public Reader getTemplate(String name) {
// Output .swagger-codegen-ignore if it doesn't exist and wasn't explicitly created by a generator
final String swaggerCodegenIgnore = ".swagger-codegen-ignore";
String ignoreFileNameTarget = config.outputFolder() + File.separator + swaggerCodegenIgnore;
SecureFileUtils.validatePath(ignoreFileNameTarget);
File ignoreFile = new File(ignoreFileNameTarget);
if (isGenerateSwaggerMetadata && !ignoreFile.exists()) {
String ignoreFileNameSource = File.separator + config.getCommonTemplateDir() + File.separator + swaggerCodegenIgnore;
Expand Down Expand Up @@ -785,6 +787,7 @@ public List<File> generate() {

protected File processTemplateToFile(Map<String, Object> templateData, String templateName, String outputFilename) throws IOException {
String adjustedOutputFilename = outputFilename.replaceAll("//", "/").replace('/', File.separatorChar);
SecureFileUtils.validatePath(adjustedOutputFilename);
if (ignoreProcessor.allowsFile(new File(adjustedOutputFilename))) {
String templateFile = getFullTemplateFile(config, templateName);
String template = readTemplate(templateFile);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.google.common.collect.ImmutableList;
import io.swagger.codegen.ignore.rules.DirectoryRule;
import io.swagger.codegen.ignore.rules.Rule;
import io.swagger.codegen.utils.SecureFileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -40,7 +41,9 @@ public CodegenIgnoreProcessor(final String baseDirectory) {
*/
@SuppressWarnings("WeakerAccess")
public CodegenIgnoreProcessor(final String baseDirectory, final String ignoreFile) {
SecureFileUtils.validatePath(baseDirectory);
final File directory = new File(baseDirectory);
SecureFileUtils.validatePath(ignoreFile);
final File targetIgnoreFile = new File(directory, ignoreFile);
if (directory.exists() && directory.isDirectory()) {
loadFromFile(targetIgnoreFile);
Expand All @@ -55,6 +58,7 @@ public CodegenIgnoreProcessor(final String baseDirectory, final String ignoreFil
* @param targetIgnoreFile The ignore file location.
*/
public CodegenIgnoreProcessor(final File targetIgnoreFile) {
SecureFileUtils.validatePath(targetIgnoreFile);
loadFromFile(targetIgnoreFile);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import io.swagger.codegen.CodegenParameter;
import io.swagger.codegen.CodegenProperty;
import io.swagger.codegen.SupportingFile;
import io.swagger.codegen.utils.SecureFileUtils;
import io.swagger.models.Operation;
import io.swagger.models.Swagger;
import io.swagger.models.parameters.Parameter;
Expand Down Expand Up @@ -197,6 +198,7 @@ public void preprocessSwagger(Swagger swagger) {
//copy input swagger to output folder
try {
String swaggerJson = Json.pretty(swagger);
SecureFileUtils.validatePath(outputFolder);
FileUtils.writeStringToFile(new File(outputFolder + File.separator + "swagger.json"), swaggerJson, StandardCharsets.UTF_8);
} catch (IOException e) {
throw new RuntimeException(e.getMessage(), e.getCause());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import io.swagger.codegen.CodegenType;
import io.swagger.codegen.DefaultCodegen;
import io.swagger.codegen.SupportingFile;
import io.swagger.codegen.utils.SecureFileUtils;
import io.swagger.models.Model;
import io.swagger.models.Operation;
import io.swagger.models.Swagger;
Expand Down Expand Up @@ -326,7 +327,7 @@ public String generateGemName(String moduleName) {
}

@Override
public String escapeReservedWord(String name) {
public String escapeReservedWord(String name) {
if(this.reservedWordsMappings().containsKey(name)) {
return this.reservedWordsMappings().get(name);
}
Expand Down Expand Up @@ -741,6 +742,7 @@ public void setGemAuthorEmail(String gemAuthorEmail) {
@Override
public boolean shouldOverwrite(String filename) {
// skip spec file as the file might have been updated with new test cases
SecureFileUtils.validatePath(filename);
return !(skipOverwrite && new File(filename).exists());
//
//return super.shouldOverwrite(filename) && !filename.endsWith("_spec.rb");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@
import java.util.Map;

import io.swagger.codegen.CliOption;
import io.swagger.codegen.CodegenConstants;
import io.swagger.codegen.utils.SecureFileUtils;
import io.swagger.models.Model;
import io.swagger.models.properties.Property;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
Expand Down Expand Up @@ -63,6 +62,7 @@ public void processSwagger(Swagger swagger) {

try {
String outputFile = outputFolder + File.separator + this.outputFile;
SecureFileUtils.validatePath(outputFile);
FileUtils.writeStringToFile(new File(outputFile), swaggerString, StandardCharsets.UTF_8);
LOGGER.debug("wrote file to " + outputFile);
} catch (Exception e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator;
import io.swagger.codegen.*;
import io.swagger.codegen.utils.SecureFileUtils;
import io.swagger.jackson.mixin.OperationResponseMixin;
import io.swagger.jackson.mixin.ResponseSchemaMixin;
import io.swagger.models.Model;
Expand Down Expand Up @@ -104,6 +105,7 @@ public void processSwagger(Swagger swagger) {
configureMapper(mapper);
String swaggerString = mapper.writeValueAsString(swagger);
String outputFile = outputFolder + File.separator + this.outputFile;
SecureFileUtils.validatePath(outputFile);
FileUtils.writeStringToFile(new File(outputFile), swaggerString, StandardCharsets.UTF_8);
LOGGER.debug("wrote file to " + outputFile);
} catch (Exception e) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package io.swagger.codegen.utils;

import java.io.File;
import java.io.IOException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
* Utility class for secure file operations that prevent path traversal attacks.
* Uses a simplified approach focusing on canonical path validation and allowlist-based security.
*/
public class SecureFileUtils {
private static final Logger LOGGER = LoggerFactory.getLogger(SecureFileUtils.class);

private SecureFileUtils() {
// Utility class
}

public static void validatePath(File file) {
if (file == null) {
LOGGER.error("File cannot be null");
throw new IllegalArgumentException("File cannot be null");
}

try {
String absolutePath = file.getAbsolutePath();
String canonicalPath = file.getCanonicalPath();

if (absolutePath.contains("..") || absolutePath.contains("\0")) {
LOGGER.error("Path contains suspicious characters: {}", absolutePath);
throw new SecurityException("Path contains suspicious characters: " + absolutePath);
}

if (canonicalPath.contains("..") || canonicalPath.contains("\0")) {
LOGGER.error("Path contains suspicious characters: {}", canonicalPath);
throw new SecurityException("Path contains suspicious characters: " + canonicalPath);
}

} catch (IOException e) {
LOGGER.error("Unable to resolve canonical path for: {}, error: {}", file.getAbsolutePath(), e.getMessage());
throw new SecurityException("Unable to resolve canonical path for: " + file.getAbsolutePath(), e);
}
}

public static void validatePath(String path) {
if (path == null || path.trim().isEmpty()) {
LOGGER.error("Path cannot be null or empty");
throw new IllegalArgumentException("Path cannot be null or empty");
}

if (path.contains("..") || path.contains("\0")) {
LOGGER.error("Path contains suspicious characters: {}", path);
throw new SecurityException("Path contains suspicious characters: " + path);
}

validatePath(new File(path));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package io.swagger.codegen;

import org.testng.annotations.Test;

import java.io.IOException;

public class AbstractGeneratorTest {


private static class TestableAbstractGenerator extends AbstractGenerator {
}

@Test(expectedExceptions = SecurityException.class)
public void testWriteToFileWithPathTraversal() throws IOException {
TestableAbstractGenerator generator = new TestableAbstractGenerator();
generator.writeToFile("../../../etc/passwd", "malicious content");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,14 @@ public void testAdditionalPropertiesPutForConfigValues() throws Exception {
Assert.assertEquals(codegen.additionalProperties().get(CodegenConstants.HIDE_GENERATION_TIMESTAMP), Boolean.FALSE);
Assert.assertEquals(codegen.isHideGenerationTimestamp(), false);
}

@Test
public void testShouldOverwriteWithPathTraversal() {
DefaultCodegen codegen = new DefaultCodegen();
Assert.assertThrows(
"shouldOverwrite should throw SecurityException for suspicious path",
SecurityException.class,
() -> codegen.shouldOverwrite("../../../etc/passwd")
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.junit.rules.TemporaryFolder;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
Expand Down Expand Up @@ -45,6 +44,8 @@ public class DefaultGeneratorTest {

public TemporaryFolder folder = new TemporaryFolder();

private final DefaultGenerator generator = new DefaultGenerator();;

@BeforeMethod
public void setUp() throws Exception {
folder.create();
Expand Down Expand Up @@ -1134,4 +1135,9 @@ private static CodegenOperation findCodegenOperationByOperationId(Map<String, Li
}
return null;
}

@Test(expectedExceptions = SecurityException.class)
public void testProcessTemplateToFileWithPathTraversal() throws IOException {
generator.processTemplateToFile(new HashMap<>(), "template.mustache", "../../../etc/passwd");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package io.swagger.codegen.ignore;

import org.testng.Assert;
import org.testng.annotations.Test;

import java.io.File;

public class CodegenIgnoreProcessorSecurityTest {

@Test
public void testConstructorWithPathTraversal() {
Assert.assertThrows(
"shouldOverwrite should throw SecurityException for suspicious path",
SecurityException.class,
() -> new CodegenIgnoreProcessor("../../../etc")
);
}

@Test
public void testFileConstructorWithPathTraversal() {
File maliciousFile = new File("../../../etc/passwd");

Assert.assertThrows(
"shouldOverwrite should throw SecurityException for suspicious path",
SecurityException.class,
() -> new CodegenIgnoreProcessor(maliciousFile)
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.swagger.codegen.languages;

import org.testng.Assert;
import org.testng.annotations.Test;

public class RubyClientCodegenTest {

@Test
public void testShouldOverwriteWithPathTraversal() {
RubyClientCodegen codegen = new RubyClientCodegen();
Assert.assertThrows(
"shouldOverwrite should throw SecurityException for suspicious path",
SecurityException.class,
() -> codegen.shouldOverwrite("../../../etc/passwd")
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package io.swagger.codegen.languages;

import io.swagger.models.Swagger;
import io.swagger.models.Info;
import org.testng.annotations.Test;

public class SwaggerGeneratorTest {

@Test
public void testProcessSwaggerWithPathTraversal() {
SwaggerGenerator generator = new SwaggerGenerator();
generator.setOutputFile("../../../etc/passwd");

Swagger swagger = new Swagger();
swagger.setInfo(new Info().title("Test API").version("1.0.0"));

generator.processSwagger(swagger);
}
}
Loading
Loading