{"record":{"id":"592088c0c906dcd5","repo":"github/spec-kit","slug":"invalid-command-alias-alias-r-alias-reason","errorCode":null,"errorMessage":"Invalid command alias {alias!r}: {alias_reason}","messagePattern":"Invalid command alias (.+?): (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/agents.py","lineNumber":708,"sourceCode":"        for cmd_info in commands:\n            cmd_name = cmd_info[\"name\"]\n            aliases = cmd_info.get(\"aliases\", [])\n            cmd_file = cmd_info[\"file\"]\n            name_reason = relative_extension_path_violation(cmd_name)\n            if name_reason:\n                raise ValueError(\n                    f\"Invalid command name {cmd_name!r}: {name_reason}\"\n                )\n            if aliases is None:\n                aliases = []\n            if not isinstance(aliases, list):\n                raise ValueError(\n                    f\"Aliases for command {cmd_name!r} must be a list\"\n                )\n            for alias in aliases:\n                alias_reason = relative_extension_path_violation(alias)\n                if alias_reason:\n                    raise ValueError(\n                        f\"Invalid command alias {alias!r}: {alias_reason}\"\n                    )\n\n            # Guard against path traversal using the single shared policy in\n            # relative_extension_path_violation(), so the runtime guard stays\n            # aligned with ExtensionManifest._validate() and the skill/preset\n            # readers. Skip a malformed/unsafe ``file`` (non-string, empty,\n            # whitespace, absolute/anchored, or ``..`` traversal); the\n            # resolve()/relative_to() check below is the final containment\n            # backstop.\n            if relative_extension_path_violation(cmd_file):\n                continue\n            try:\n                source_file = (source_root / cmd_file).resolve()\n                source_file.relative_to(source_root)  # raises ValueError if outside\n            except (OSError, ValueError):\n                continue\n","sourceCodeStart":690,"sourceCodeEnd":726,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/agents.py#L690-L726","documentation":"Each entry in a command's aliases list is checked with the same relative_extension_path_violation() policy as command names: aliases must be safe relative path components (no separators, no '..', not absolute/anchored/empty). A violating alias raises ValueError with the alias and reason before any files are written.","triggerScenarios":"aliases: [\"../root\", \"/abs\", \"a/b\", \"\"] — an alias intended as an alternate invocation name but containing path syntax; aliases copied from full command file paths rather than short names.","commonSituations":"Confusing the alias (a short alternate name) with the file path; extension manifests reusing a relative file path as an alias; refactors that moved names into subpaths without updating aliases to flat names.","solutions":["Make every alias a flat, relative name with no path separators or traversal (e.g. 'p', 'spec-plan').","Keep file locations in the 'file' field only — aliases are pure invocation names.","Run the shared validator on aliases in your generator before calling register_commands()."],"exampleFix":"# before\n{\"name\": \"plan\", \"aliases\": [\"commands/../p\"], \"file\": \"plan.md\"}\n# after\n{\"name\": \"plan\", \"aliases\": [\"p\"], \"file\": \"plan.md\"}","handlingStrategy":"validation","validationCode":"from specify_cli import relative_extension_path_violation\n\nunsafe = [\n    a for c in commands for a in (c.get(\"aliases\") or [])\n    if relative_extension_path_violation(a)\n]\nif unsafe:\n    raise SystemExit(f\"unsafe aliases: {unsafe}\")","typeGuard":"from specify_cli import relative_extension_path_violation\n\ndef is_safe_alias(alias: str) -> bool:\n    \"\"\"True when the alias passes the shared path-safety policy.\"\"\"\n    return relative_extension_path_violation(alias) is None","tryCatchPattern":"try:\n    registrar.register_commands(...)\nexcept ValueError as exc:\n    if \"Invalid command alias\" in str(exc):\n        raise SystemExit(f\"rejected unsafe alias: {exc}\") from exc\n    raise","preventionTips":["Aliases are invocation names, not file paths — keep them flat and separator-free.","Apply the same naming lint to aliases as to command names.","Audit extension manifests for aliases containing '/', '\\', or '..'."],"tags":["security","path-traversal","aliases","validation"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}