{"record":{"id":"42bdcdbf1ba396e3","repo":"affaan-m/ECC","slug":"receipt-source-is-not-a-safe-regular-file-source-path","errorCode":null,"errorMessage":"receipt source is not a safe regular file: {source_path}","messagePattern":"receipt source is not a safe regular file: (.+?)","errorType":"validation","errorClass":"ContractError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/contract.py","lineNumber":354,"sourceCode":"                if float(source_duration) <= 0:\n                    raise ContractError(\"receipt reference has an invalid finite source duration\")\n                source_durations[(source_path, expected_digest)] = float(source_duration)\n                _validate_probe_evidence(\n                    source.get(\"probe\"), float(source_duration), label=\"receipt reference\"\n                )\n    source_policy = receipt.get(\"source_availability_policy\")\n    if known_sources and source_policy not in {\"allow_unavailable\", \"require_available\"}:\n        raise ContractError(\"receipt must declare an explicit source availability policy\")\n    for source_path, expected_digest in sorted(known_sources):\n        path = Path(source_path)\n        try:\n            metadata = path.lstat()\n        except FileNotFoundError:\n            if source_policy == \"require_available\":\n                raise ContractError(f\"receipt source is unavailable: {source_path}\") from None\n            continue\n        if stat.S_ISLNK(metadata.st_mode) or not stat.S_ISREG(metadata.st_mode):\n            raise ContractError(f\"receipt source is not a safe regular file: {source_path}\")\n        try:\n            actual_digest = _sha256(path)\n        except FileNotFoundError:\n            if source_policy == \"require_available\":\n                raise ContractError(f\"receipt source is unavailable: {source_path}\") from None\n            continue\n        except OSError:\n            raise ContractError(f\"receipt source cannot be securely read: {source_path}\") from None\n        if actual_digest != expected_digest:\n            raise ContractError(f\"receipt source SHA-256 changed after generation: {source_path}\")\n\n    emitted = {\n        path.relative_to(out_dir).as_posix()\n        for path in out_dir.rglob(\"*\")\n        if path.is_file() and path.name != \"receipt.json\"\n    }\n    bound_paths: list[str] = []\n    for entry in entries:","sourceCodeStart":336,"sourceCodeEnd":372,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/contract.py#L336-L372","documentation":"Receipt sources must be safe, regular files: after lstat, the validator rejects anything that is a symbolic link or not a regular file (directories, fifos, sockets, devices) with ContractError('receipt source is not a safe regular file: <path>'). This blocks symlink-based tampering and TOCTOU tricks where a declared source is swapped for a link or special file.","triggerScenarios":"A declared source path is a symlink (even pointing to a valid file), a directory, or a special file (fifo/socket/device) at validation time.","commonSituations":"macOS/Windows checkouts where evidence files became symlinks; users linking sources into a project to avoid duplicating data; a source path pointing at a directory after refactoring; tmpfs/named-pipe intermediates used during generation.","solutions":["Replace the symlink/special file with a real regular file copy at the declared path","Update the receipt's path to point at the actual regular file and regenerate its sha256","Re-run generation after fixing the layout so the receipt matches reality"],"exampleFix":"# before\nln -s /shared/data.csv src/data.csv\n# after\ncp /shared/data.csv src/data.csv  # real regular file","handlingStrategy":"validation","validationCode":"import stat\nfrom pathlib import Path\n\ndef safe_regular_files(receipt):\n    bad = []\n    for s in receipt.get('references', []) + receipt.get('evidence_files', []):\n        m = Path(s['path']).lstat()\n        if stat.S_ISLNK(m.st_mode) or not stat.S_ISREG(m.st_mode):\n            bad.append(s['path'])\n    return bad  # must be empty","typeGuard":"def is_safe_regular(path: str) -> bool:\n    try:\n        m = Path(path).lstat()\n    except OSError:\n        return False\n    return not stat.S_ISLNK(m.st_mode) and stat.S_ISREG(m.st_mode)","tryCatchPattern":"try:\n    validate_artifact_receipt(receipt, out_dir)\nexcept ContractError as e:\n    if 'not a safe regular file' in str(e):\n        bad = str(e).rsplit(': ', 1)[1]\n        # replace symlink/dir with a real regular file copy, then retry\n    raise","preventionTips":["Never symlink declared sources into a project; copy them","Point receipt paths at regular files, never directories or sockets","Check lstat mode of each source before generating a receipt","Enable core.symlinks=false or equivalent in environments that convert files to links"],"tags":["filesystem","security","symlink","receipt"],"backgroundTag":"path-traversal-blocked","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}