diff --git a/backend/apps/skill_registry/parse_install_command.py b/backend/apps/skill_registry/parse_install_command.py index 988f25bf..ee5410fa 100644 --- a/backend/apps/skill_registry/parse_install_command.py +++ b/backend/apps/skill_registry/parse_install_command.py @@ -41,11 +41,17 @@ def parse_install_command(raw: str) -> Optional[str]: rest = [t for t in tokens[1:] if not t.startswith("-")] # The verb and the package name arrive in either order ("npx skills add x", "npm i skills x"), # so strip both, in whichever order they appear. + named_registry = False for _ in range(2): if rest and rest[0].lower() in ("skills", "skill", "@skills/cli", "openswarm"): rest = rest[1:] + named_registry = True elif rest and rest[0].lower() in P_VERBS: rest = rest[1:] + # Without the registry name this is just some other npx command, and `npx create-react-app foo` + # must never read as "install the create-react-app skill". + if not named_registry: + return None candidate = rest[0] if rest else "" return candidate if candidate and P_SKILL_ID.match(candidate) else None diff --git a/backend/tests/test_parse_install_command_refusals.py b/backend/tests/test_parse_install_command_refusals.py new file mode 100644 index 00000000..3a54da82 --- /dev/null +++ b/backend/tests/test_parse_install_command_refusals.py @@ -0,0 +1,40 @@ +"""A paste that is not a skills install must return None, not a guess. + +`npx create-react-app foo` parsed as the skill id "create-react-app" until 2026-08-07: any unrelated +npx command a user pasted would have installed a skill by that name. The module's own docstring says +None means "I could not read this", never a guess, so the registry name is now required.""" + +import pytest + +from backend.apps.skill_registry.parse_install_command import parse_install_command + +REAL_INSTALLS = [ + ("npx skills add pdf-filler", "pdf-filler"), + ("$ npx skills add pdf-filler", "pdf-filler"), + ("npm i skills pdf-filler", "pdf-filler"), + ("bunx skills install pdf-filler", "pdf-filler"), + ("pnpm skills add @acme/pdf-filler", "@acme/pdf-filler"), + ("npx skills add pdf-filler --force", "pdf-filler"), + ("https://skills.sh/s/pdf-filler", "pdf-filler"), + ("pdf-filler", "pdf-filler"), +] + +NOT_INSTALLS = [ + "npx create-react-app foo", + "npm i lodash", + "npx vite build", + "yarn add react", + "rm -rf /", + "", + " ", +] + + +@pytest.mark.parametrize("raw,expected", REAL_INSTALLS) +def test_the_forms_people_actually_paste_still_parse(raw, expected): + assert parse_install_command(raw) == expected + + +@pytest.mark.parametrize("raw", NOT_INSTALLS) +def test_anything_that_is_not_a_skills_install_is_refused(raw): + assert parse_install_command(raw) is None, f"{raw!r} must not be read as a skill id"