{"record":{"id":"5802c8702980f84f","repo":"github/spec-kit","slug":"refusing-to-use-symlinked-integration-manifest-dir","errorCode":null,"errorMessage":"Refusing to use symlinked integration manifest directory: {label}","messagePattern":"Refusing to use symlinked integration manifest directory: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/integrations/manifest.py","lineNumber":72,"sourceCode":"    except ValueError:\n        return path.as_posix()\n\n\ndef _ensure_safe_manifest_directory(root: Path, directory: Path) -> None:\n    \"\"\"Create a manifest directory without following symlinked parents.\"\"\"\n    root_resolved = root.resolve()\n    try:\n        rel = directory.relative_to(root)\n    except ValueError:\n        label = _manifest_path_label(root, directory)\n        raise ValueError(f\"Integration manifest directory escapes project root: {label}\") from None\n\n    current = root\n    for part in rel.parts:\n        current = current / part\n        label = _manifest_path_label(root, current)\n        if current.is_symlink():\n            raise ValueError(f\"Refusing to use symlinked integration manifest directory: {label}\")\n        if current.exists():\n            if not current.is_dir():\n                raise ValueError(f\"Integration manifest directory path is not a directory: {label}\")\n            try:\n                current.resolve().relative_to(root_resolved)\n            except (OSError, ValueError):\n                raise ValueError(f\"Integration manifest directory escapes project root: {label}\") from None\n            continue\n        current.mkdir()\n        try:\n            current.resolve().relative_to(root_resolved)\n        except (OSError, ValueError):\n            raise ValueError(f\"Integration manifest directory escapes project root: {label}\") from None\n\n\ndef _ensure_safe_manifest_destination(root: Path, path: Path) -> None:\n    \"\"\"Refuse manifest writes that would escape the project or follow symlinks.\"\"\"\n    root_resolved = root.resolve()","sourceCodeStart":54,"sourceCodeEnd":90,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/integrations/manifest.py#L54-L90","documentation":"Raised while walking each component of the manifest directory path when a component is a symlink. The CLI refuses to follow symlinked directories because mkdir/resolve could then write or mutate state outside the project root, defeating the manifest's containment guarantees.","triggerScenarios":"Any component of .specify, .specify/integrations, or deeper being a symlink when save() creates the manifest directory; typically .specify symlinked to a shared or external location.","commonSituations":"Developers symlinking .specify into a dotfiles repo or a shared config dir; CI caches that restore .specify as a symlink; containerized setups mounting config over symlinked paths.","solutions":["Remove the symlink and use a real directory (mkdir -p .specify/integrations)","Point the symlink target inside the project, or bind-mount instead of symlinking","If sharing manifests is the goal, copy them with a script instead of symlinking"],"exampleFix":"# before\nln -s ~/dotfiles/specify .specify\n# after\nmkdir -p .specify/integrations && cp ~/dotfiles/specify/*.json .specify/integrations/","handlingStrategy":"validation","validationCode":"from pathlib import Path\n\ndef manifest_dir_is_safe(root: Path, directory: Path) -> bool:\n    try:\n        rel = directory.relative_to(root)\n    except ValueError:\n        return False\n    cur = root\n    for part in rel.parts:\n        cur = cur / part\n        if cur.is_symlink():\n            return False\n    return True","typeGuard":null,"tryCatchPattern":"try:\n    manifest.save()\nexcept ValueError as exc:\n    if \"symlinked integration manifest directory\" in str(exc):\n        replace_symlink_with_real_dir(label_from(exc))\n    else:\n        raise","preventionTips":["Keep .specify as a real directory inside the repo","Add .specify to .gitignore rules that might link it","In CI, verify test fixtures create .specify with mkdir, not ln -s"],"tags":["manifest","symlink","security","integrations"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}