{"record":{"id":"ad0e2d29da6d13fd","repo":"spring-projects/spring-ai","slug":"invalid-filename-for-file","errorCode":null,"errorMessage":"Invalid filename for file '': ","messagePattern":"Invalid filename for file '': ","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicSkillsResponseHelper.java","lineNumber":158,"sourceCode":"\t}\n\n\t/**\n\t * Validate an API-provided filename and resolve it to a child of {@code targetDir}.\n\t * Rejects null/blank names, absolute paths, names containing path separators or\n\t * {@code .}/{@code ..} segments, and names that resolve outside {@code targetDir}.\n\t * Filenames come from model-influenced API metadata and must not be trusted as safe\n\t * path components.\n\t */\n\tstatic Path resolveSafeChildPath(Path targetDir, @Nullable String rawName, String fileId) throws IOException {\n\t\tif (rawName == null || rawName.isBlank()) {\n\t\t\tthrow new IOException(\"Invalid filename for file '\" + fileId + \"': null or blank\");\n\t\t}\n\t\tPath name;\n\t\ttry {\n\t\t\tname = Path.of(rawName);\n\t\t}\n\t\tcatch (InvalidPathException ex) {\n\t\t\tthrow new IOException(\"Invalid filename for file '\" + fileId + \"': \" + rawName, ex);\n\t\t}\n\t\tif (name.isAbsolute() || name.getRoot() != null) {\n\t\t\tthrow new IOException(\"Invalid filename for file '\" + fileId + \"': absolute path '\" + rawName + \"'\");\n\t\t}\n\t\tif (name.getNameCount() != 1) {\n\t\t\tthrow new IOException(\n\t\t\t\t\t\"Invalid filename for file '\" + fileId + \"': must be a single path segment '\" + rawName + \"'\");\n\t\t}\n\t\tString only = name.getName(0).toString();\n\t\tif (only.equals(\".\") || only.equals(\"..\")) {\n\t\t\tthrow new IOException(\"Invalid filename for file '\" + fileId + \"': '\" + rawName + \"'\");\n\t\t}\n\n\t\t// One extra hardening check to make sure nothing fell through the cracks above\n\t\t// (future tweaks to the rules, odd platform path quirks, etc.).\n\t\tPath base = targetDir.toAbsolutePath().normalize();\n\t\tPath resolved = base.resolve(only).normalize();\n\t\tif (!resolved.startsWith(base)) {","sourceCodeStart":140,"sourceCodeEnd":176,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicSkillsResponseHelper.java#L140-L176","documentation":"resolveSafeChildPath converts the raw filename with Path.of(); if the name is not a valid path on the current filesystem (e.g. contains NUL bytes, illegal characters on Windows, or platform-invalid sequences) Path.of throws InvalidPathException, which is rethrown as an IOException. Because filenames come from model-influenced API metadata, they are treated as untrusted and validated strictly.","triggerScenarios":"A file entry whose name contains characters illegal for the OS filesystem — NUL (\\0), on Windows characters like : * ? \" < > | or reserved names (CON, NUL), or invalid UTF-8-derived sequences — passed into resolveSafeChildPath.","commonSituations":"Model-generated filenames containing special characters; Windows hosts receiving names with colons or wildcards; responses from a different OS convention (Unix-style names with characters Windows rejects); corrupted/malicious API payloads.","solutions":["Sanitize the filename before calling resolveSafeChildPath: strip or replace illegal characters with a safe substitute (e.g. '_').","Skip the offending file entry and log the fileId and raw name for investigation.","Generate your own deterministic safe filename (e.g. fileId + detected extension) instead of trusting the API name.","If targeting multiple OSes, test filename handling on Windows where the illegal-character set is larger."],"exampleFix":"// before\nPath p = resolveSafeChildPath(targetDir, rawNameFromApi, fileId);\n// after\nString safe = rawNameFromApi.replaceAll(\"[\\\\u0000:*?\\\"<>|]\", \"_\");\nPath p = resolveSafeChildPath(targetDir, safe, fileId);","handlingStrategy":"try-catch","validationCode":"try { Path.of(rawName); } catch (InvalidPathException e) {\n    log.warn(\"Filename not valid on this filesystem for file {}: {}\", fileId, rawName);\n    return;\n}","typeGuard":"static boolean isConstructablePath(String rawName) {\n    try { Path.of(rawName); return true; } catch (InvalidPathException e) { return false; }\n}","tryCatchPattern":"try {\n    Path p = AnthropicSkillsResponseHelper.resolveSafeChildPath(targetDir, rawName, fileId);\n} catch (IOException e) {\n    if (e.getCause() instanceof InvalidPathException) {\n        log.warn(\"Skipping file {} with OS-invalid name '{}'\", fileId, rawName);\n    } else throw e;\n}","preventionTips":["Sanitize model/API-supplied filenames (replace illegal chars) before path resolution.","Test on Windows, whose illegal-character set is larger than Linux's.","Prefer deriving filenames from fileId rather than trusting model-generated names."],"tags":["java","spring-ai","anthropic","skills","invalid-path","path-safety"],"backgroundTag":"invalid-identifier-format","analyzedSha":"98a7beda4f29d80a71c5837eb4053b03a93a46f7","analyzedAt":"2026-09-11T14:15:49.441Z","contentChangedAt":"2026-09-11T14:15:49.441Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}