{"record":{"id":"2f6e03b8e9c612ca","repo":"github/spec-kit","slug":"absolute-paths-are-not-allowed-in-manifests-rel","errorCode":null,"errorMessage":"Absolute paths are not allowed in manifests: {rel}","messagePattern":"Absolute paths are not allowed in manifests: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/integrations/manifest.py","lineNumber":36,"sourceCode":"\n\ndef _sha256(path: Path) -> str:\n    \"\"\"Return the hex SHA-256 digest of *path*.\"\"\"\n    h = hashlib.sha256()\n    with open(path, \"rb\") as fh:\n        for chunk in iter(lambda: fh.read(8192), b\"\"):\n            h.update(chunk)\n    return h.hexdigest()\n\n\ndef _validate_rel_path(rel: Path, root: Path) -> Path:\n    \"\"\"Resolve *rel* against *root* and verify it stays within *root*.\n\n    Raises ``ValueError`` if *rel* is absolute, contains ``..`` segments\n    that escape *root*, or otherwise resolves outside the project root.\n    \"\"\"\n    if rel.is_absolute():\n        raise ValueError(\n            f\"Absolute paths are not allowed in manifests: {rel}\"\n        )\n    resolved = (root / rel).resolve()\n    root_resolved = root.resolve()\n    try:\n        resolved.relative_to(root_resolved)\n    except ValueError:\n        raise ValueError(\n            f\"Path {rel} resolves to {resolved} which is outside \"\n            f\"the project root {root_resolved}\"\n        ) from None\n    return resolved\n\n\ndef _manifest_path_label(root: Path, path: Path) -> str:\n    try:\n        return path.relative_to(root).as_posix()\n    except ValueError:","sourceCodeStart":18,"sourceCodeEnd":54,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/integrations/manifest.py#L18-L54","documentation":"Raised by _validate_rel_path() in manifest.py (src/specify_cli/integrations/manifest.py:36) when a manifest operation is given a relative path that is actually absolute. Manifests record every installed file as a path relative to the project root (hashed and removed on uninstall), so absolute paths — which could target anything on disk — are rejected before any filesystem write.","triggerScenarios":"Calling manifest.record_file()/record_existing() (or any code path reaching _validate_rel_path) with a Path like Path('/etc/passwd') or an absolute Windows path 'C:\\\\x\\\\y' — rel.is_absolute() is true and ValueError is raised immediately.","commonSituations":"Custom integration setup() code building file paths from absolute sources (resolved template paths) and passing them straight to manifest.record_file(); passing dest-resolved paths instead of project-relative ones; Windows callers using rooted paths.","solutions":["Convert the path to be relative to project_root before recording: path.relative_to(project_root).","In custom setup() implementations, record the destination relative path ('folder/commands/plan.md'), never the resolved absolute one.","Check for accidental leading '/' or drive letters in path construction."],"exampleFix":"# before\ncreated = dest / \"plan.md\"          # absolute\nmanifest.record_file(str(created), content)\n\n# after\nrel = created.relative_to(project_root)\nmanifest.record_file(rel.as_posix(), content)","handlingStrategy":"validation","validationCode":"from pathlib import Path\n\ndef to_manifest_rel(path: Path, root: Path) -> Path:\n    if path.is_absolute():\n        return path.relative_to(root)  # raises if outside root\n    return path\n\nrel = to_manifest_rel(created_file, project_root)\nmanifest.record_file(rel.as_posix(), content)","typeGuard":"def is_relative_path(p) -> bool:\n    return not Path(p).is_absolute() and \"..\" not in Path(p).parts","tryCatchPattern":"try:\n    manifest.record_file(rel_str, content)\nexcept ValueError as e:\n    if \"Absolute paths\" in str(e):\n        rel_str = Path(rel_str).relative_to(project_root).as_posix()\n        manifest.record_file(rel_str, content)\n    else:\n        raise","preventionTips":["Always pass project-relative POSIX paths to manifest.record_file/record_existing.","In custom setup() code, derive the relative path via created.relative_to(project_root).","Never feed resolved/absolute destination paths into the manifest."],"tags":["filesystem","path-traversal","security","manifest","spec-kit"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}