spring-projects/spring-ai · error · IOException

Invalid filename for file '': absolute path ''

Error message

Invalid filename for file '': absolute path ''

What it means

resolveSafeChildPath rejects filenames that are absolute paths or carry a filesystem root. Since names originate from model-influenced API metadata, allowing '/etc/passwd' or 'C:\evil' would enable writing outside the target directory. The check name.isAbsolute() || name.getRoot() != null throws an IOException naming the offending raw value.

Source

Thrown at models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicSkillsResponseHelper.java:161

	 * Validate an API-provided filename and resolve it to a child of {@code targetDir}.
	 * Rejects null/blank names, absolute paths, names containing path separators or
	 * {@code .}/{@code ..} segments, and names that resolve outside {@code targetDir}.
	 * Filenames come from model-influenced API metadata and must not be trusted as safe
	 * path components.
	 */
	static Path resolveSafeChildPath(Path targetDir, @Nullable String rawName, String fileId) throws IOException {
		if (rawName == null || rawName.isBlank()) {
			throw new IOException("Invalid filename for file '" + fileId + "': null or blank");
		}
		Path name;
		try {
			name = Path.of(rawName);
		}
		catch (InvalidPathException ex) {
			throw new IOException("Invalid filename for file '" + fileId + "': " + rawName, ex);
		}
		if (name.isAbsolute() || name.getRoot() != null) {
			throw new IOException("Invalid filename for file '" + fileId + "': absolute path '" + rawName + "'");
		}
		if (name.getNameCount() != 1) {
			throw new IOException(
					"Invalid filename for file '" + fileId + "': must be a single path segment '" + rawName + "'");
		}
		String only = name.getName(0).toString();
		if (only.equals(".") || only.equals("..")) {
			throw new IOException("Invalid filename for file '" + fileId + "': '" + rawName + "'");
		}

		// One extra hardening check to make sure nothing fell through the cracks above
		// (future tweaks to the rules, odd platform path quirks, etc.).
		Path base = targetDir.toAbsolutePath().normalize();
		Path resolved = base.resolve(only).normalize();
		if (!resolved.startsWith(base)) {
			throw new IOException(
					"Invalid filename for file '" + fileId + "': resolves outside target directory '" + rawName + "'");
		}

View on GitHub (pinned to 98a7beda4f)

Solutions

  1. Strip any directory portion and keep only the base filename (e.g. Path.of(rawName).getFileName()) before resolving, after verifying it is a single safe segment.
  2. Reject/skip the file entry and log it — absolute paths from this API are never legitimate.
  3. Treat the occurrence as a data-quality or security signal: inspect the upstream response source for prompt injection.
  4. Keep using resolveSafeChildPath for every API-derived filename; do not bypass it with direct Path.of/Paths.get(targetDir, rawName).

Example fix

// before
Path p = resolveSafeChildPath(targetDir, "/etc/passwd", fileId); // throws
// after
String bare = Path.of(rawName).getFileName().toString();
if (!bare.equals(rawName)) {
    log.warn("Rejected absolute path from API for file {}", fileId);
    return null;
}
Path p = resolveSafeChildPath(targetDir, bare, fileId);
Defensive patterns

Strategy: validation

Validate before calling

Path n = Path.of(rawName);
if (n.isAbsolute() || n.getRoot() != null || n.getNameCount() != 1) {
    throw new SecurityException("Refusing non-simple filename from API: " + rawName);
}

Type guard

static boolean isSimpleRelativeName(String rawName) {
    try {
        Path n = Path.of(rawName);
        return !n.isAbsolute() && n.getRoot() == null && n.getNameCount() == 1;
    } catch (InvalidPathException e) { return false; }
}

Try / catch

try {
    Path p = AnthropicSkillsResponseHelper.resolveSafeChildPath(targetDir, rawName, fileId);
} catch (IOException e) {
    if (e.getMessage() != null && e.getMessage().contains("absolute path")) {
        securityLog.warn("Path-traversal attempt in file {}: {}", fileId, rawName);
    } else throw e;
}

Prevention

When it happens

Trigger: A file entry whose name field contains an absolute path like '/tmp/x.txt', '\\server\share\f' (UNC), or a Windows rooted path 'C:\file.txt' passed to resolveSafeChildPath.

Common situations: A compromised or hallucinating model returning full paths instead of bare filenames; proxying responses from another system that emits absolute paths; hostile API payloads probing for path traversal (this check is part of the traversal defense).

Understand the failure class

Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.

Related errors


AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11). Data as JSON: /api/errors/a8352b05cf39865b. Report an issue: GitHub.