{"record":{"id":"69510d5b4c347cca","repo":"OpenAPITools/openapi-generator","slug":"target-files-must-be-generated-within-the-output-d","errorCode":null,"errorMessage":"Target files must be generated within the output directory; absoluteTarget=%s outDir=%s","messagePattern":"Target files must be generated within the output directory; absoluteTarget=(.+?) outDir=(.+?)","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"critical","filePath":"modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java","lineNumber":1470,"sourceCode":"\n    protected File processTemplateToFile(Map<String, Object> templateData, String templateName, String outputFilename, boolean shouldGenerate, String skippedByOption) throws IOException {\n        return processTemplateToFile(templateData, templateName, outputFilename, shouldGenerate, skippedByOption, this.config.getOutputDir());\n    }\n\n    /**\n     * Stores lowercased absolute paths for O(1) case-insensitive duplicate detection.\n     */\n    private final Set<String> seenFilesLower = new HashSet<>();\n\n    private File processTemplateToFile(Map<String, Object> templateData, String templateName, String outputFilename, boolean shouldGenerate, String skippedByOption, String intendedOutputDir) throws IOException {\n        String adjustedOutputFilename = outputFilename.replaceAll(\"//\", \"/\").replace('/', File.separatorChar);\n        File target = new File(adjustedOutputFilename);\n        if (ignoreProcessor.allowsFile(target)) {\n            if (shouldGenerate) {\n                Path outDir = java.nio.file.Paths.get(intendedOutputDir).toAbsolutePath();\n                Path absoluteTarget = target.toPath().toAbsolutePath();\n                if (!absoluteTarget.startsWith(outDir)) {\n                    throw new RuntimeException(String.format(Locale.ROOT, \"Target files must be generated within the output directory; absoluteTarget=%s outDir=%s\", absoluteTarget, outDir));\n                }\n\n                // O(1) case-insensitive duplicate check via a pre-lowercased shadow set\n                if (!seenFilesLower.add(absoluteTarget.toString().toLowerCase(Locale.ROOT))) {\n                    LOGGER.warn(\"Duplicate file path detected. Not all operating systems can handle case sensitive file paths. path={}\", absoluteTarget);\n                }\n                return this.templateProcessor.write(templateData, templateName, target);\n            } else {\n                this.templateProcessor.skip(target.toPath(), String.format(Locale.ROOT, \"Skipped by %s options supplied by user.\", skippedByOption));\n                return null;\n            }\n        } else {\n            this.templateProcessor.ignore(target.toPath(), \"Ignored by rule in ignore file.\");\n            return null;\n        }\n    }\n\n    public Map<String, List<CodegenOperation>> processPaths(Paths paths) {","sourceCodeStart":1452,"sourceCodeEnd":1488,"githubUrl":"https://github.com/OpenAPITools/openapi-generator/blob/fcec517be3cf5b7964296bcba25fbc97541484e7/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java#L1452-L1488","documentation":"processTemplateToFile now takes an explicit intendedOutputDir and enforces containment: the resolved absolute target path must start with the intended output directory before anything is written. If a supporting file, model/api file, or doc file computes to a path escaping the output folder (via '..' segments or an absolute path), generation aborts with both paths printed. This is a deliberate path-traversal containment check (added as hardening), so it fires on generator/template configurations - or spec-sourced names - that produce out-of-tree output paths.","triggerScenarios":"A SupportingFile destination containing '..' ('new SupportingFile(\"index.html\", \"../docs\", ...)'); a custom generator computing output filenames from spec content (schema/operation names) that embed '/' or '..'; a template whose filename expression yields an absolute path; outputFolder configured relative while the computed target is absolute (e.g. '/tmp/x' or 'C:\\\\x'), so toAbsolutePath() diverges from the output dir.","commonSituations":"Custom generators written before the containment check that deliberately wrote outside the output folder (e.g. placing docs next to sources); specs with names containing slashes injected into file paths; Windows absolute paths (C:\\\\) interacting with a mismatched outputFolder; template filename lambdas using un-sanitized spec strings.","solutions":["Compare the two printed paths: absoluteTarget shows exactly which file escaped; trace it back to the SupportingFile destination or template filename that produced it.","Remove '..' segments and leading separators from supporting-file destinations and template filename expressions; keep every destination relative to outputFolder.","If spec strings (schema ids, operation ids, tag names) feed filenames, sanitize them in the generator (strip '/', '\\\\', '..') or fix the offending names in the spec.","If you genuinely need files elsewhere, generate into a parent output folder that contains the desired location, then relocate with a build step - do not fight the containment check.","Set outputFolder as an absolute path to avoid relative-vs-absolute divergence between the two compared paths."],"exampleFix":"// before (custom generator)\n supportingFiles().add(new SupportingFile(\"index.html\", \"../../docs\", \"index.html\"));\n// after - relative destination inside the output folder, relocate later if needed\n supportingFiles().add(new SupportingFile(\"index.html\", \"docs\", \"index.html\"));","handlingStrategy":"validation","validationCode":"// Contain every computed output path before generation (mirrors the library check)\nPath outDir = Path.of(config.outputFolder()).toAbsolutePath().normalize();\nList<Path> targets = new ArrayList<>();\nfor (SupportingFile sf : config.supportingFiles()) {\n    String f = (sf.destinationFolder == null ? \"\" : sf.destinationFolder + \"/\") + sf.destinationFilename;\n    Path t = outDir.resolve(f).normalize();\n    if (!t.startsWith(outDir) || f.contains(\"..\")) {\n        throw new IllegalStateException(\"Supporting file escapes output dir: \" + f);\n    }\n    targets.add(t);\n}","typeGuard":"private static boolean isContainedPath(Path outDir, String relativeName) {\n    if (relativeName.startsWith(\"/\") || relativeName.contains(\"..\")) return false;\n    return outDir.resolve(relativeName).normalize().startsWith(outDir.normalize());\n}","tryCatchPattern":"try {\n    generator.opts(input).generate();\n} catch (RuntimeException e) {\n    if (e.getMessage() != null && e.getMessage().startsWith(\"Target files must be generated within the output directory\")) {\n        // parse absoluteTarget=... and outDir=... from the message;\n        // fix the supporting-file destination / template filename, never whitelist the escape\n        throw new GenerationFailure(\"Output path containment violated\", e);\n    }\n    throw e;\n}","preventionTips":["Never use '..' or absolute paths in SupportingFile destinations or template filename expressions.","Sanitize any spec-derived string (schema/operation/tag names) before it enters a filename.","Configure outputFolder as an absolute path so generator and check compare the same base."],"tags":["openapi-generator","path-traversal","security","filesystem","custom-generator"],"backgroundTag":"path-traversal-blocked","analyzedSha":"fcec517be3cf5b7964296bcba25fbc97541484e7","analyzedAt":"2026-08-22T11:13:11.613Z","schemaVersion":2},"datasetVersion":"2026-08-22T14:17:55.899Z"}