conductor-oss/conductor · critical · IllegalArgumentException
Invalid skill package path: {name}
Error message
Invalid skill package path: {name} What it means
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.
Source
Thrown at agentspan/src/main/java/org/conductoross/conductor/ai/agentspan/runtime/service/SkillRegistryService.java:920
private void deletePackage(SkillDetail detail) {
String handle = detail.getPackageFileHandleId();
if (handle != null && !handle.isBlank()) {
try {
packageStore.delete(handle);
} catch (IllegalArgumentException ignored) {
// Legacy records used synthetic handles before the package store existed.
}
}
}
private String normalizeEntryName(String name) {
String normalized = name.replace('\\', '/');
while (normalized.startsWith("./")) {
normalized = normalized.substring(2);
}
if (normalized.isBlank() || normalized.startsWith("/") || normalized.contains("\0")) {
throw new IllegalArgumentException("Invalid skill package path: " + name);
}
for (String part : normalized.split("/")) {
if (part.isBlank() || ".".equals(part) || "..".equals(part)) {
throw new IllegalArgumentException("Invalid skill package path: " + name);
}
}
return normalized;
}
private void validateSkillName(String name) {
if (!SKILL_NAME_PATTERN.matcher(name).matches()) {
throw new IllegalArgumentException(
"Invalid skill name '"
+ name
+ "'. Use 1-128 characters: letters, numbers, '.', '_' or '-'.");
}
}
View on GitHub (pinned to cf7c3e4a8a)
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.
Example fix
// before: zip contains /etc/skill.md (absolute entry) unzip -l skill.zip -> /etc/skill.md // rebuild with relative names (cd skill-root && zip -r ../skill.zip .) // after: entries are skill.md, scripts/run.sh, ...
Defensive patterns
Strategy: validation
Validate before calling
// Reject absolute / NUL / blank entry names before zipping.
boolean isSafeEntryName(String raw) {
String n = raw.replace('\\', '/');
while (n.startsWith("./")) n = n.substring(2);
return !n.isBlank() && !n.startsWith("/") && !n.contains("\0");
} Type guard
boolean isPackageRootedEntry(String raw) {
String n = raw.replace('\\', '/');
while (n.startsWith("./")) n = n.substring(2);
return !n.isBlank() && !n.startsWith("/") && !n.contains("\0");
} Try / catch
try {
skillRegistry.ingest(zipBytes);
} catch (IllegalArgumentException e) {
if (e.getMessage().startsWith("Invalid skill package path"))
return badRequest(e.getMessage()); // reject the upload
throw e;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Skill package exceeds max uncompressed size of {maxUncompres
- Access denied: path traversal sequences are not allowed
- plannerContext header '${name}' contains CR/LF — rejected to
- Skill manifest name '{manifestName}' does not match package
- Skill {name} version {version} already exists with a differe
AI-assisted analysis of conductor-oss/conductor@cf7c3e4a8a (2026-08-14).
Data as JSON: /api/errors/d372316bc4d95f4d.
Report an issue: GitHub.