spring-projects/spring-boot · critical · ReportableException

Entry '{}' would be written to '{}'. This is outside the out

Error message

Entry '{}' would be written to '{}'. This is outside the output location of '{}'. Verify your target server configuration.

What it means

Thrown by ProjectGenerator.extractFromStream as a Zip-Slip guard: a zip entry's resolved canonical path does not start with the output directory's canonical path, meaning the entry would escape the target directory. This is a security check against path traversal; the CLI refuses to write the entry.

Source

Thrown at cli/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/ProjectGenerator.java:119

		byte[] content = entity.getContent();
		Assert.state(content != null, "'content' must not be null");
		try (ZipInputStream zipStream = new ZipInputStream(new ByteArrayInputStream(content))) {
			extractFromStream(zipStream, overwrite, outputDirectory);
			fixExecutableFlag(outputDirectory, "mvnw");
			fixExecutableFlag(outputDirectory, "gradlew");
			Log.info("Project extracted to '" + outputDirectory.getAbsolutePath() + "'");
		}
	}

	private void extractFromStream(ZipInputStream zipStream, boolean overwrite, File outputDirectory)
			throws IOException {
		ZipEntry entry = zipStream.getNextEntry();
		String canonicalOutputPath = outputDirectory.getCanonicalPath() + File.separator;
		while (entry != null) {
			File file = new File(outputDirectory, entry.getName());
			String canonicalEntryPath = file.getCanonicalPath();
			if (!canonicalEntryPath.startsWith(canonicalOutputPath)) {
				throw new ReportableException("Entry '" + entry.getName() + "' would be written to '"
						+ canonicalEntryPath + "'. This is outside the output location of '" + canonicalOutputPath
						+ "'. Verify your target server configuration.");
			}
			if (file.exists() && !overwrite) {
				throw new ReportableException((file.isDirectory() ? "Directory" : "File") + " '" + file.getName()
						+ "' already exists. Use --force if you want to overwrite or "
						+ "specify an alternate location.");
			}
			if (!entry.isDirectory()) {
				FileCopyUtils.copy(StreamUtils.nonClosing(zipStream), new FileOutputStream(file));
			}
			else {
				file.mkdir();
			}
			zipStream.closeEntry();
			entry = zipStream.getNextEntry();
		}
	}

View on GitHub (pinned to 270dfe353f)

Solutions

  1. Do NOT bypass this check - it protects against path traversal
  2. Verify the integrity and trustworthiness of the target Initializr service
  3. Report the issue to the service operator
  4. Use a different, trusted Initializr endpoint (e.g. https://start.spring.io)
Defensive patterns

Strategy: try-catch

Validate before calling

// The guard is internal; callers cannot cheaply pre-check archive contents.
// Mitigate by trusting the source: pin --target to a known-good Initializr.
// If you handle the archive yourself, pre-scan entries:
java.nio.file.Path out = outputDirectory.toPath().normalize();
for (java.util.zip.ZipEntry e : Collections.list(zip.entries())) {
    java.nio.file.Path resolved = out.resolve(e.getName()).normalize();
    if (!resolved.startsWith(out)) {
        throw new SecurityException("Refusing path-traversal entry: " + e.getName());
    }
}

Try / catch

try {
    generator.generateProject(request, force);
} catch (ReportableException ex) {
    if (ex.getMessage().startsWith("Entry '") && ex.getMessage().contains("outside the output location")) {
        // Do NOT retry with --force; the archive is unsafe.
        Log.error("Server returned an unsafe archive (zip slip). Use a trusted Initializr.");
    }
    throw ex;
}

Prevention

When it happens

Trigger: The downloaded project archive contains an entry name with path-traversal sequences (e.g. ../../etc/foo) that resolves outside the output directory.

Common situations: A compromised or misconfigured Initializr server serving a malicious archive; a server bug producing bad entry paths; an archive that uses absolute paths the guard correctly rejects.

Related errors


AI-assisted analysis of spring-projects/spring-boot@270dfe353f (2026-08-11). Data as JSON: /api/errors/fa02e024b98767fe. Report an issue: GitHub.