Severity: P2
Summary
The apc import command copies skill directories from an export archive using raw filesystem names (src.name) without calling sanitize_skill_name(). This creates an asymmetry with apc install, which enforces the regex ^[A-Za-z0-9][A-Za-z0-9_\-]{0,63}$.
A crafted export archive can install skills with names that would be rejected by apc install.
Affected Code
src/export_import.py:
for src in sorted(import_skills_dir.iterdir()):
if src.is_dir() and (src / "SKILL.md").exists():
dst = skills_dir / src.name # <-- no sanitize_skill_name() call
if dst.exists():
shutil.rmtree(dst)
shutil.copytree(src, dst)
Compare to apc install (src/install.py), which calls sanitize_skill_name(skill["name"]) before writing.
Problematic Names Importable via Crafted Archive
.hidden-skill (dotfile in ~/.apc/skills/)
- Skills with spaces or special characters in directory names
- Names longer than 64 characters
- Names starting with digits or underscores
Impact
- Inconsistent state in the skills directory with names that violate the expected invariant
- Dotfile skill directories could interfere with tool detection or glob patterns
- May bypass future enforcement that relies on the regex invariant being maintained
- While full path traversal via
../ is prevented by the OS (can't have / in filename), the weaker invariants allow edge cases
Recommended Fix
from skills import sanitize_skill_name
for src in sorted(import_skills_dir.iterdir()):
if src.is_dir() and (src / "SKILL.md").exists():
try:
safe_name = sanitize_skill_name(src.name)
except ValueError as e:
warning(f"Skipping skill with invalid name {src.name!r}: {e}")
continue
dst = skills_dir / safe_name
if dst.exists():
shutil.rmtree(dst)
shutil.copytree(src, dst)
count += 1
References
- CWE-20: Improper Input Validation
Severity: P2
Summary
The
apc importcommand copies skill directories from an export archive using raw filesystem names (src.name) without callingsanitize_skill_name(). This creates an asymmetry withapc install, which enforces the regex^[A-Za-z0-9][A-Za-z0-9_\-]{0,63}$.A crafted export archive can install skills with names that would be rejected by
apc install.Affected Code
src/export_import.py:Compare to
apc install(src/install.py), which callssanitize_skill_name(skill["name"])before writing.Problematic Names Importable via Crafted Archive
.hidden-skill(dotfile in~/.apc/skills/)Impact
../is prevented by the OS (can't have/in filename), the weaker invariants allow edge casesRecommended Fix
References