OpenAPITools/openapi-generator · critical · RuntimeException

Target files must be generated within the output directory;

Error message

Target files must be generated within the output directory; absoluteTarget=%s outDir=%s

What it means

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.

Source

Thrown at modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java:1470

    protected File processTemplateToFile(Map<String, Object> templateData, String templateName, String outputFilename, boolean shouldGenerate, String skippedByOption) throws IOException {
        return processTemplateToFile(templateData, templateName, outputFilename, shouldGenerate, skippedByOption, this.config.getOutputDir());
    }

    /**
     * Stores lowercased absolute paths for O(1) case-insensitive duplicate detection.
     */
    private final Set<String> seenFilesLower = new HashSet<>();

    private File processTemplateToFile(Map<String, Object> templateData, String templateName, String outputFilename, boolean shouldGenerate, String skippedByOption, String intendedOutputDir) throws IOException {
        String adjustedOutputFilename = outputFilename.replaceAll("//", "/").replace('/', File.separatorChar);
        File target = new File(adjustedOutputFilename);
        if (ignoreProcessor.allowsFile(target)) {
            if (shouldGenerate) {
                Path outDir = java.nio.file.Paths.get(intendedOutputDir).toAbsolutePath();
                Path absoluteTarget = target.toPath().toAbsolutePath();
                if (!absoluteTarget.startsWith(outDir)) {
                    throw new RuntimeException(String.format(Locale.ROOT, "Target files must be generated within the output directory; absoluteTarget=%s outDir=%s", absoluteTarget, outDir));
                }

                // O(1) case-insensitive duplicate check via a pre-lowercased shadow set
                if (!seenFilesLower.add(absoluteTarget.toString().toLowerCase(Locale.ROOT))) {
                    LOGGER.warn("Duplicate file path detected. Not all operating systems can handle case sensitive file paths. path={}", absoluteTarget);
                }
                return this.templateProcessor.write(templateData, templateName, target);
            } else {
                this.templateProcessor.skip(target.toPath(), String.format(Locale.ROOT, "Skipped by %s options supplied by user.", skippedByOption));
                return null;
            }
        } else {
            this.templateProcessor.ignore(target.toPath(), "Ignored by rule in ignore file.");
            return null;
        }
    }

    public Map<String, List<CodegenOperation>> processPaths(Paths paths) {

View on GitHub (pinned to fcec517be3)

Solutions

  1. Compare the two printed paths: absoluteTarget shows exactly which file escaped; trace it back to the SupportingFile destination or template filename that produced it.
  2. Remove '..' segments and leading separators from supporting-file destinations and template filename expressions; keep every destination relative to outputFolder.
  3. 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.
  4. 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.
  5. Set outputFolder as an absolute path to avoid relative-vs-absolute divergence between the two compared paths.

Example fix

// before (custom generator)
 supportingFiles().add(new SupportingFile("index.html", "../../docs", "index.html"));
// after - relative destination inside the output folder, relocate later if needed
 supportingFiles().add(new SupportingFile("index.html", "docs", "index.html"));
Defensive patterns

Strategy: validation

Validate before calling

// Contain every computed output path before generation (mirrors the library check)
Path outDir = Path.of(config.outputFolder()).toAbsolutePath().normalize();
List<Path> targets = new ArrayList<>();
for (SupportingFile sf : config.supportingFiles()) {
    String f = (sf.destinationFolder == null ? "" : sf.destinationFolder + "/") + sf.destinationFilename;
    Path t = outDir.resolve(f).normalize();
    if (!t.startsWith(outDir) || f.contains("..")) {
        throw new IllegalStateException("Supporting file escapes output dir: " + f);
    }
    targets.add(t);
}

Type guard

private static boolean isContainedPath(Path outDir, String relativeName) {
    if (relativeName.startsWith("/") || relativeName.contains("..")) return false;
    return outDir.resolve(relativeName).normalize().startsWith(outDir.normalize());
}

Try / catch

try {
    generator.opts(input).generate();
} catch (RuntimeException e) {
    if (e.getMessage() != null && e.getMessage().startsWith("Target files must be generated within the output directory")) {
        // parse absoluteTarget=... and outDir=... from the message;
        // fix the supporting-file destination / template filename, never whitelist the escape
        throw new GenerationFailure("Output path containment violated", e);
    }
    throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of OpenAPITools/openapi-generator@fcec517be3 (2026-08-22). Data as JSON: /api/errors/69510d5b4c347cca. Report an issue: GitHub.