spring-projects/spring-ai · error · IOException

Invalid filename for file '': ''

Error message

Invalid filename for file '': ''

What it means

resolveSafeChildPath rejects filenames that resolve to '.' or '..' (or otherwise normalize to the target directory itself), throwing this IOException with the raw name. It prevents a download from overwriting the target directory or escaping it via relative-name tricks. The single-segment rule has already passed; this is the dot-segment guard.

Source

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

			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 + "'");
		}
		return resolved;
	}

	private static void extractFileIdsFromBashResult(BashCodeExecutionToolResultBlock resultBlock,
			List<String> fileIds) {
		BashCodeExecutionToolResultBlock.Content content = resultBlock.content();
		if (content.isBashCodeExecutionResultBlock()) {
			for (BashCodeExecutionOutputBlock outputBlock : content.asBashCodeExecutionResultBlock().content()) {

View on GitHub (pinned to 98a7beda4f)

Solutions

  1. Filter out directory entries ('.'/'..') from the API file list before calling filePath.
  2. Validate the filename client-side: skip files whose name is null, blank, or equals '.'/'..'.
  3. Sanitize by generating a fallback name (e.g. file-<fileId>.bin) when the reported name is not a real filename.
  4. Catch the IOException and log/skip the offending entry.

Example fix

// before
helper.filePath(fileId, name);
// after
if (name == null || name.isBlank() || name.equals(".") || name.equals("..")) {
    return; // skip non-file entry
}
helper.filePath(fileId, name);
Defensive patterns

Strategy: validation

Validate before calling

static boolean isRealFileName(String name) {
    return name != null && !name.isBlank()
        && !name.equals(".") && !name.equals("..");
}

Type guard

static String requireFileName(String name) {
    if (!isRealFileName(name)) throw new IllegalArgumentException("Not a filename: " + name);
    return name;
}

Try / catch

try {
    Path p = helper.filePath(fileId, name);
} catch (IOException e) {
    log.warn("Skipping non-file entry {}", name, e);
}

Prevention

When it happens

Trigger: The API-reported filename is '.', '..', './' or '.\' (e.g. empty-ish or self-referential names after normalization), so the extracted single segment equals '.' or '..'.

Common situations: Directory entries included in a skills listing and passed through as files; truncated or empty name fields that normalize to '.'; unusual zip/skill archives that contain directory markers.

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/e169f567eec657d8. Report an issue: GitHub.