{"record":{"id":"28f2ce25fed0254a","repo":"github/spec-kit","slug":"refusing-to-scaffold-through-symlinked-path-labe","errorCode":null,"errorMessage":"Refusing to scaffold through symlinked path: {label}","messagePattern":"Refusing to scaffold through symlinked path: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/integration_scaffold.py","lineNumber":192,"sourceCode":"\n    Walks each component of *target* under *project_root* and rejects any\n    existing symlinked directory (or symlinked target), then confirms the\n    write destination still resolves inside the repository root. Mirrors the\n    symlink-aware guarding used for integration manifests.\n    \"\"\"\n    try:\n        rel = target.relative_to(project_root)\n    except ValueError:\n        raise ValueError(\n            f\"Refusing to scaffold outside the repository root: {target}\"\n        ) from None\n\n    current = project_root\n    for part in rel.parts:\n        current = current / part\n        if current.is_symlink():\n            label = current.relative_to(project_root).as_posix()\n            raise ValueError(f\"Refusing to scaffold through symlinked path: {label}\")\n\n    root_resolved = project_root.resolve()\n    try:\n        target.parent.resolve().relative_to(root_resolved)\n    except (OSError, ValueError):\n        raise ValueError(\n            f\"Refusing to scaffold outside the repository root: {target}\"\n        ) from None\n\n\ndef scaffold_integration(\n    project_root: Path,\n    key: str,\n    integration_type: str,\n) -> IntegrationScaffoldResult:\n    \"\"\"Create a minimal built-in integration package and test skeleton.\"\"\"\n    clean_key = _clean_key(key)\n    normalized_type = integration_type.strip().lower()","sourceCodeStart":174,"sourceCodeEnd":210,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/integration_scaffold.py#L174-L210","documentation":"Raised while _assert_safe_scaffold_target walks each component of the target path relative to project_root: if any existing component (e.g. src, specify_cli, integrations, or the new package dir) is a symlink, scaffolding aborts. This blocks a symlink-based escape where writing 'inside' the tree actually lands in an attacker- or user-controlled location outside it. It mirrors the symlink-aware guarding used for integration manifests.","triggerScenarios":"scaffold_integration target path traverses a directory that is a symlink, e.g. src/specify_cli/integrations -> /somewhere/else, or the target file itself already exists as a symlink; any component of rel.parts where current.is_symlink() is True.","commonSituations":"Developers symlinking the integrations directory from another checkout to share work; monorepo tooling that replaces source dirs with symlinks; a previously created package dir symlinked to a template dir.","solutions":["Replace the symlinked directory with a real directory (copy its contents in, remove the link): rm src/specify_cli/integrations && mv /somewhere/else integrations.","If the link exists for workflow reasons, scaffold into a clean tree and copy the generated files over manually.","Check with `find src tests -type l` from the repo root to locate the offending link named in the error message."],"exampleFix":"# before: integrations is a symlink\n$ ls -l src/specify_cli/integrations -> ../../shared/integrations\n# after: real directory\n$ rm src/specify_cli/integrations && cp -r ../../shared/integrations src/specify_cli/integrations","handlingStrategy":"validation","validationCode":"from pathlib import Path\n\ndef has_symlink_on_path(project_root: Path, target: Path) -> bool:\n    rel = target.relative_to(project_root)\n    cur = project_root\n    for part in rel.parts:\n        cur = cur / part\n        if cur.is_symlink():\n            return True\n    return False","typeGuard":null,"tryCatchPattern":"try:\n    scaffold_integration(root, key, itype)\nexcept ValueError as exc:\n    if \"symlinked path\" in str(exc):\n        print(f\"remove the symlink named in: {exc}\")  # then fix manually\n    raise","preventionTips":["Periodically run `find . -type l` in the repo to catch stray symlinks.","Avoid symlinking source directories between checkouts; copy or use git worktrees instead.","Treat any symlink under src/ or tests/ as a defect in a scaffold target tree."],"tags":["filesystem","symlink","security","scaffold"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}