{"record":{"id":"7b25c89d549b0751","repo":"github/spec-kit","slug":"invalid-copilot-prompt-name-cmd-name-r-name-re","errorCode":null,"errorMessage":"Invalid Copilot prompt name {cmd_name!r}: {name_reason}","messagePattern":"Invalid Copilot prompt name (.+?): (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/agents.py","lineNumber":995,"sourceCode":"        except (OSError, ValueError):\n            # Windows often requires Developer Mode or admin privileges for\n            # symlinks, and relpath can fail across drives. Keep dev installs\n            # functional by falling back to a copy.\n            if dest_file.is_symlink():\n                dest_file.unlink()\n            dest_file.write_text(content, encoding=\"utf-8\")\n\n    @staticmethod\n    def write_copilot_prompt(project_root: Path, cmd_name: str) -> None:\n        \"\"\"Generate a companion .prompt.md file for a Copilot agent command.\n\n        Args:\n            project_root: Path to project root\n            cmd_name: Command name (e.g. 'speckit.my-ext.example')\n        \"\"\"\n        name_reason = relative_extension_path_violation(cmd_name)\n        if name_reason:\n            raise ValueError(\n                f\"Invalid Copilot prompt name {cmd_name!r}: {name_reason}\"\n            )\n        prompts_dir = project_root / \".github\" / \"prompts\"\n        prompts_dir.mkdir(parents=True, exist_ok=True)\n        prompt_file = prompts_dir / f\"{cmd_name}.prompt.md\"\n        CommandRegistrar._ensure_inside(prompt_file, prompts_dir)\n        prompt_file.parent.mkdir(parents=True, exist_ok=True)\n        prompt_file.write_text(f\"---\\nagent: {cmd_name}\\n---\\n\", encoding=\"utf-8\")\n\n    @staticmethod\n    def _resolve_agent_dir(\n        agent_name: str,\n        agent_config: dict[str, Any],\n        project_root: Path,\n    ) -> Path:\n        \"\"\"Return the agent command directory, falling back to legacy_dir.\n\n        Supports project-relative paths (e.g. ``.claude/skills/``),","sourceCodeStart":977,"sourceCodeEnd":1013,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/agents.py#L977-L1013","documentation":"write_copilot_prompt() creates the companion .prompt.md file under .github/prompts/ for a Copilot agent command. Because the file path is built as prompts_dir / f\"{cmd_name}.prompt.md\", the cmd_name is first checked with relative_extension_path_violation(); an unsafe name (separators, '..', absolute/anchored, empty/whitespace) raises ValueError before any write, and _ensure_inside() then backstops containment.","triggerScenarios":"Calling CommandRegistrar.write_copilot_prompt(root, \"../evil\") or a name containing '/' like \"team/plan\"; extension command names with traversal segments reaching the Copilot prompt-generation path.","commonSituations":"Copilot commands namespaced with dots or slashes ('speckit.my-ext.example' is fine; 'my/ext/../x' is not); malformed extension manifests targeting Copilot; names produced by string concatenation that accidentally include '..' or a leading '/'.","solutions":["Pass a flat command name with no path components: write_copilot_prompt(root, \"speckit.my-ext.example\").","Sanitize extension-provided names before Copilot registration (reject '/', '\\', '..').","Prefer dot-namespacing over slash-namespacing for Copilot command identifiers."],"exampleFix":"# before\nCommandRegistrar.write_copilot_prompt(root, \"../../evil\")\n# after\nCommandRegistrar.write_copilot_prompt(root, \"speckit.example\")","handlingStrategy":"validation","validationCode":"from specify_cli import relative_extension_path_violation\n\nif relative_extension_path_violation(cmd_name):\n    raise SystemExit(f\"unsafe Copilot prompt name: {cmd_name!r}\")","typeGuard":"from specify_cli import relative_extension_path_violation\n\ndef is_safe_prompt_name(name: str) -> bool:\n    \"\"\"True when the Copilot prompt name passes the path-safety policy.\"\"\"\n    return relative_extension_path_violation(name) is None","tryCatchPattern":"try:\n    CommandRegistrar.write_copilot_prompt(project_root, cmd_name)\nexcept ValueError as exc:\n    if \"Invalid Copilot prompt name\" in str(exc):\n        raise SystemExit(f\"rejected unsafe prompt name: {exc}\") from exc\n    raise","preventionTips":["Use dot-namespacing (speckit.ext.name) for Copilot identifiers, never slashes.","Sanitize extension-provided names before Copilot registration.","Remember the name becomes .github/prompts/<name>.prompt.md — treat it as a filename component."],"tags":["security","path-traversal","copilot","validation"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}