{"record":{"id":"d372316bc4d95f4d","repo":"conductor-oss/conductor","slug":"invalid-skill-package-path-name","errorCode":null,"errorMessage":"Invalid skill package path: {name}","messagePattern":"Invalid skill package path: (.+?)","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"critical","filePath":"agentspan/src/main/java/org/conductoross/conductor/ai/agentspan/runtime/service/SkillRegistryService.java","lineNumber":920,"sourceCode":"\n    private void deletePackage(SkillDetail detail) {\n        String handle = detail.getPackageFileHandleId();\n        if (handle != null && !handle.isBlank()) {\n            try {\n                packageStore.delete(handle);\n            } catch (IllegalArgumentException ignored) {\n                // Legacy records used synthetic handles before the package store existed.\n            }\n        }\n    }\n\n    private String normalizeEntryName(String name) {\n        String normalized = name.replace('\\\\', '/');\n        while (normalized.startsWith(\"./\")) {\n            normalized = normalized.substring(2);\n        }\n        if (normalized.isBlank() || normalized.startsWith(\"/\") || normalized.contains(\"\\0\")) {\n            throw new IllegalArgumentException(\"Invalid skill package path: \" + name);\n        }\n        for (String part : normalized.split(\"/\")) {\n            if (part.isBlank() || \".\".equals(part) || \"..\".equals(part)) {\n                throw new IllegalArgumentException(\"Invalid skill package path: \" + name);\n            }\n        }\n        return normalized;\n    }\n\n    private void validateSkillName(String name) {\n        if (!SKILL_NAME_PATTERN.matcher(name).matches()) {\n            throw new IllegalArgumentException(\n                    \"Invalid skill name '\"\n                            + name\n                            + \"'. Use 1-128 characters: letters, numbers, '.', '_' or '-'.\");\n        }\n    }\n","sourceCodeStart":902,"sourceCodeEnd":938,"githubUrl":"https://github.com/conductor-oss/conductor/blob/cf7c3e4a8adfb158be778ab1ec525323c363cd3a/agentspan/src/main/java/org/conductoross/conductor/ai/agentspan/runtime/service/SkillRegistryService.java#L902-L938","documentation":"First guard in normalizeEntryName: rejects a zip entry name when, after backslash->slash conversion and stripping leading `./`, it is blank, starts with `/` (absolute path), or contains a NUL byte. These shapes would let an entry escape the package root or smuggle control characters, so they are blocked outright. This is the first line of the zip-slip / path-traversal defense for skill packages.","triggerScenarios":"A packaged file entry whose name is empty, begins with `/` (absolute Unix path), or contains `\\0`. Triggered while iterating zip entries during skill package ingestion.","commonSituations":"Malicious or malformed zip crafted to write outside the extraction root; an archive tool that emitted an absolute path; a corrupt zip with a NUL in the entry name.","solutions":["Rebuild the zip with relative entry names only (`cd pkg && zip -r ../skill.zip .`).","If you control the producer, sanitize entry names before zipping.","Reject the upload and ask the author to repackage."],"exampleFix":"// before: zip contains /etc/skill.md (absolute entry)\nunzip -l skill.zip  ->  /etc/skill.md\n// rebuild with relative names\n(cd skill-root && zip -r ../skill.zip .)\n// after: entries are skill.md, scripts/run.sh, ...","handlingStrategy":"validation","validationCode":"// Reject absolute / NUL / blank entry names before zipping.\nboolean isSafeEntryName(String raw) {\n    String n = raw.replace('\\\\', '/');\n    while (n.startsWith(\"./\")) n = n.substring(2);\n    return !n.isBlank() && !n.startsWith(\"/\") && !n.contains(\"\\0\");\n}","typeGuard":"boolean isPackageRootedEntry(String raw) {\n    String n = raw.replace('\\\\', '/');\n    while (n.startsWith(\"./\")) n = n.substring(2);\n    return !n.isBlank() && !n.startsWith(\"/\") && !n.contains(\"\\0\");\n}","tryCatchPattern":"try {\n    skillRegistry.ingest(zipBytes);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().startsWith(\"Invalid skill package path\"))\n        return badRequest(e.getMessage()); // reject the upload\n    throw e;\n}","preventionTips":["Build zips with relative entry names from the package root (`zip -r pkg.zip .`).","Scan uploaded zips for absolute entries with `unzip -l` in CI.","Reject any entry containing a NUL byte at the upload boundary."],"tags":["skill-registry","zip-slip","path-traversal","security"],"backgroundTag":null,"analyzedSha":"cf7c3e4a8adfb158be778ab1ec525323c363cd3a","analyzedAt":"2026-08-14T03:33:19.897Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}