spring-projects/spring-boot · error · ReportableException
Entry '{entry.getName()}' would be written to '{canonicalEnt
Error message
Entry '{entry.getName()}' would be written to '{canonicalEntryPath}'. This is outside the output location of '{canonicalOutputPath}'. Verify your target server configuration. What it means
A Zip-Slip guard in ProjectGenerator.extractFromStream: for each ZipEntry it computes the canonical path of the target File and verifies it starts with the canonical output directory. If not, the entry would escape the output location (path traversal) and generation is aborted to prevent writing files outside the intended directory.
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 5b2dbdbb8b)
Solutions
- Do not disable this check. Verify the target Initializr service (--target) is trusted and its archive generation is correct.
- If you run a private Initializr, fix its archiving so entries are relative to the project root with no ../ components.
- If the entry name is legitimately unusual, inspect the zip locally (unzip -l) to confirm contents before trusting the server.
Example fix
// before: server emits entry '../../etc/foo' $ spring init --target https://untrusted-start/ --output myapp/ // after $ spring init --target https://start.spring.io --output myapp/ # use a trusted service
Defensive patterns
Strategy: validation
Validate before calling
// Before extracting, sanity-check archive entry names against the output dir
Path base = outputDirectory.getCanonicalFile().toPath();
try (ZipInputStream z = new ZipInputStream(new ByteArrayInputStream(content))) {
ZipEntry e; while ((e = z.getNextEntry()) != null) {
Path resolved = base.resolve(e.getName()).normalize();
if (!resolved.startsWith(base)) throw new IOException("Unsafe entry: " + e.getName());
}
} Type guard
// Zip-slip guard: entry is safe iff resolved path stays under base
boolean safe(Path base, String entryName) {
return base.resolve(entryName).normalize().startsWith(base);
} Try / catch
// This is a security check; do NOT catch-and-ignore. Treat as a hard failure
try { generator.generateProject(request, force); }
catch (ReportableException ex) {
if (ex.getMessage().contains("outside the output location")) {
// stop, audit the service, do not retry against the same untrusted server
throw ex;
}
throw ex;
} Prevention
- Only target trusted Initializr services (--target).
- If you host the service, generate archives with entries relative to a single root and reject ../ in entry names.
- Treat this error as a potential security incident, not a nuisance.
When it happens
Trigger: The downloaded zip from the Initializr service contains an entry whose name uses ../ sequences or an absolute path, so new File(outputDirectory, entry.getName()).getCanonicalPath() resolves outside outputDirectory.getCanonicalPath()+separator. The check at line 118 fails and the exception is thrown.
Common situations: A malicious or misconfigured Initializr service; a tampered proxy response; a custom service that packages entries with absolute or parent-relative names. This is a security control - the message directs you to verify the target server configuration.
Related errors
- No project type with id '${this.type}' - check the service c
- No type found with build '{this.build}' and format '{this.fo
- Multiple types found with build '{this.build}' and format '{
- No project type is set and no default is defined. Check the
- Could not save the project, the server did not set a preferr
AI-assisted analysis of spring-projects/spring-boot@5b2dbdbb8b (2026-08-04).
Data as JSON: /data/errors/5cd534b769ca14f5.json.
Report an issue: GitHub.