fix: tighten @org/name ref validation to prevent path traversal

Reject refs with multiple slashes (@org/a/b), dot segments (@../skill),
or leading dots in org/name. Applied to both CLI install() and SDK
parse_registry_ref() so the contract is enforced consistently.
This commit is contained in:
alex-clawd
2026-05-19 15:55:43 -07:00
parent a2e534821b
commit 74dff2da3d
2 changed files with 24 additions and 8 deletions

View File

@@ -92,16 +92,24 @@ class SkillCommand(BaseCommand, PlusAPIMixin):
raise SystemExit(1)
without_at = ref[1:]
if "/" not in without_at:
if without_at.count("/") != 1:
console.print(
"[red]Invalid skill reference. Use the format @org/name.[/red]"
)
raise SystemExit(1)
org, _, name = without_at.partition("/")
if not org or not name:
org, name = without_at.split("/", 1)
if (
not org
or not name
or org.startswith(".")
or name.startswith(".")
or len(Path(org).parts) != 1
or len(Path(name).parts) != 1
):
console.print(
"[red]Invalid skill reference: org and name must be non-empty.[/red]"
"[red]Invalid skill reference: org and name must be single, "
"non-empty path segments (no slashes, no '..').[/red]"
)
raise SystemExit(1)

View File

@@ -48,14 +48,22 @@ def parse_registry_ref(ref: str) -> tuple[str, str]:
if not ref.startswith("@"):
raise ValueError(f"Registry reference must start with '@', got: {ref!r}")
without_at = ref[1:]
if "/" not in without_at:
if without_at.count("/") != 1:
raise ValueError(
f"Registry reference must be in '@org/name' format, got: {ref!r}"
)
org, _, name = without_at.partition("/")
if not org or not name:
org, name = without_at.split("/", 1)
if (
not org
or not name
or org.startswith(".")
or name.startswith(".")
or "/" in org
or "/" in name
):
raise ValueError(
f"Registry reference must have non-empty org and name, got: {ref!r}"
f"Registry reference org and name must be single, non-empty path "
f"segments (no '..' or leading dots), got: {ref!r}"
)
return org, name