{"record":{"id":"9a56bf5ff0a3cb2b","repo":"github/spec-kit","slug":"integration-destination-dest-escapes-project-roo","errorCode":null,"errorMessage":"Integration destination {dest} escapes project root {project_root_resolved}","messagePattern":"Integration destination (.+?) escapes project root (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/integrations/base.py","lineNumber":904,"sourceCode":"        and call ``process_template()`` in their own loop — see\n        ``CopilotIntegration`` for an example.\n        \"\"\"\n        templates = self.list_command_templates()\n        if not templates:\n            return []\n\n        project_root_resolved = project_root.resolve()\n        if manifest.project_root != project_root_resolved:\n            raise ValueError(\n                f\"manifest.project_root ({manifest.project_root}) does not match \"\n                f\"project_root ({project_root_resolved})\"\n            )\n\n        dest = self.commands_dest(project_root).resolve()\n        try:\n            dest.relative_to(project_root_resolved)\n        except ValueError as exc:\n            raise ValueError(\n                f\"Integration destination {dest} escapes \"\n                f\"project root {project_root_resolved}\"\n            ) from exc\n\n        created: list[Path] = []\n\n        for src_file in templates:\n            dst_name = self.command_filename(src_file.stem)\n            dst_file = self.copy_command_to_directory(src_file, dest, dst_name)\n            self.record_file_in_manifest(dst_file, project_root, manifest)\n            created.append(dst_file)\n\n\n        return created\n\n    def teardown(\n        self,\n        project_root: Path,","sourceCodeStart":886,"sourceCodeEnd":922,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/integrations/base.py#L886-L922","documentation":"During command installation, the base-class setup() resolves the commands destination (config folder + commands_subdir) and requires it to sit inside the resolved project root. This guard blocks a registrar_config['dir'] (or a symlinked destination) that would write command files outside the user's project — for example an absolute folder like '/etc' or a '../' escape. This instance is the guard in the plain copy path used by non-template integrations.","triggerScenarios":"An integration subclass whose config['folder'] is an absolute path or contains '..' so commands_dest(project_root).resolve() falls outside project_root.resolve(); or the destination directory being a symlink pointing outside the project.","commonSituations":"Hand-written custom integrations with folder values like '~/.agent' or '/opt/commands'; symlinked dot-directories (e.g. .kilo -> elsewhere) in the user's project; copy-pasting an integration class and forgetting to change folder to a project-relative path.","solutions":["Set config['folder'] to a project-relative directory name such as '.myagent/' — never absolute, never '..'-containing.","Remove or replace destination symlinks that point outside the project with real directories.","Verify with: `python -c \"from pathlib import Path; print((Path('.myagent').resolve()))\"` from the project root and confirm it stays inside."],"exampleFix":"# before\nclass MyIntegration(IntegrationBase):\n    config = {\"folder\": \"/etc/my-agent\", ...}\n# after\nclass MyIntegration(IntegrationBase):\n    config = {\"folder\": \".my-agent\", ...}","handlingStrategy":"validation","validationCode":"from pathlib import Path\n\ndef dest_inside_project(project_root: Path, integration) -> bool:\n    root = project_root.resolve()\n    try:\n        integration.commands_dest(project_root).resolve().relative_to(root)\n    except ValueError:\n        return False\n    return True","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep config['folder'] a bare relative directory name like '.myagent/'.","Never hard-code absolute paths or '..' in integration folder/dir config.","Scan the project for out-of-tree symlinks before running integration setup."],"tags":["configuration","filesystem","security","path-safety","integrations"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}