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

  1. Rebuild the zip with relative entry names only (`cd pkg && zip -r ../skill.zip .`).
  2. If you control the producer, sanitize entry names before zipping.
  3. 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

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


AI-assisted analysis of conductor-oss/conductor@cf7c3e4a8a (2026-08-14). Data as JSON: /api/errors/d372316bc4d95f4d. Report an issue: GitHub.