fix(skills): validate skill names and quote provisioning paths
Skill names become host/guest path segments interpolated into the
`bottle.exec` shell strings in each contrib provider's provision_skills.
They were validated only as strings, so a name with shell metacharacters
or path traversal could reach the command.
Layer two defenses:
- Primary: reject any skill name that isn't kebab-case
([a-z][a-z0-9-]*) at manifest load, reusing the convention already
enforced on bottle/agent filenames (new is_valid_entity_name helper
in manifest_schema). Fails loud and early, protecting every consumer
of the name — not just the exec call sites.
- Failsafe: shlex.quote the interpolated skills_dir / dst paths in the
claude, codex, and pi providers, so a future unvalidated field can't
inject shell metacharacters even if it bypasses the load-time check.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NkwFXLFff9PYPy4wgVBJp9
This commit is contained in:
@@ -33,13 +33,20 @@ AGENT_KEYS = (
|
||||
AGENT_MODEL_KEYS = AGENT_KEYS | frozenset({"prompt"})
|
||||
|
||||
|
||||
def is_valid_entity_name(name: str) -> bool:
|
||||
"""True if `name` fits the kebab-case `[a-z][a-z0-9-]*` convention
|
||||
shared by bottle/agent filenames and skill names. Names that satisfy
|
||||
this are also safe to interpolate into a host/guest path segment."""
|
||||
return bool(_FILENAME_RX.match(name))
|
||||
|
||||
|
||||
def entity_name_from_path(path: Path) -> str | None:
|
||||
"""Return the entity name implied by the filename, or None if the
|
||||
filename does not fit the [a-z][a-z0-9-]* convention."""
|
||||
if path.suffix != ".md":
|
||||
return None
|
||||
stem = path.stem
|
||||
if not _FILENAME_RX.match(stem):
|
||||
if not is_valid_entity_name(stem):
|
||||
return None
|
||||
return stem
|
||||
|
||||
|
||||
Reference in New Issue
Block a user