{"record":{"id":"2344beb83d3286d6","repo":"affaan-m/ECC","slug":"secure-output-requires-o-nofollow-and-o-directory","errorCode":null,"errorMessage":"secure output requires O_NOFOLLOW and O_DIRECTORY","messagePattern":"secure output requires O_NOFOLLOW and O_DIRECTORY","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/workflow.py","lineNumber":174,"sourceCode":"    samples = measured.get(\"style_samples\", [])\n    if not isinstance(samples, list) or any(\n        not isinstance(sample, dict)\n        or not _finite_real(sample.get(\"time\"))\n        or float(cast(float, sample[\"time\"])) < 0\n        or float(cast(float, sample[\"time\"])) > float(duration)\n        for sample in samples\n    ):\n        raise ValueError(\"reference style evidence times must be finite and within duration\")\n    return float(duration)\n\n\nclass _SafeOutput:\n    \"\"\"Descriptor-bound output tree with no-follow traversal and atomic writes.\"\"\"\n\n    def __init__(self, root: Path) -> None:\n        self._root_fd = -1\n        if not hasattr(os, \"O_NOFOLLOW\") or not hasattr(os, \"O_DIRECTORY\"):\n            raise RuntimeError(\"secure output requires O_NOFOLLOW and O_DIRECTORY\")\n        if root.exists() or root.is_symlink():\n            metadata = root.lstat()\n            if stat.S_ISLNK(metadata.st_mode):\n                raise ValueError(\"output root must not be a symlink\")\n            if not stat.S_ISDIR(metadata.st_mode):\n                raise ValueError(\"output root must be a directory\")\n        else:\n            if not root.parent.is_dir():\n                raise ValueError(\"output parent directory must already exist\")\n            root.mkdir(mode=0o700)\n        self.root = root\n        self._root_fd = os.open(root, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW)\n        self._written: list[str] = []\n\n    def close(self) -> None:\n        if self._root_fd >= 0:\n            os.close(self._root_fd)\n            self._root_fd = -1","sourceCodeStart":156,"sourceCodeEnd":192,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/workflow.py#L156-L192","documentation":"_SafeOutput implements a secure output tree using dir_fd-relative, no-follow file operations, which require the platform's os.O_NOFOLLOW and os.O_DIRECTORY flags. If either attribute is missing (unsupported or emulated platform), __init__ raises immediately rather than silently falling back to symlink-vulnerable path handling. This is an intentional hard failure to preserve the symlink-attack guarantees of the writer.","triggerScenarios":"Instantiating _SafeOutput(root) on a Python build/platform lacking os.O_NOFOLLOW or os.O_DIRECTORY (e.g. Windows, some embedded or exotic platforms where os does not expose these flags).","commonSituations":"Running the tasteforge workflow on Windows or a minimal cross-compiled Python; very old Python versions; restricted sandboxes that strip os constants.","solutions":["Run the workflow on Linux/macOS where O_NOFOLLOW and O_DIRECTORY exist","Upgrade Python to a build that exposes these os flags on the target platform","If you control the code, gate _SafeOutput usage behind a platform check and provide an alternate (less strict) output writer for unsupported platforms","Avoid emulated Windows environments (MSYS/older WSL) for this workflow"],"exampleFix":"# before\nout = _SafeOutput(Path('out'))  # RuntimeError on Windows\n\n# after\nif not hasattr(os, 'O_NOFOLLOW') or not hasattr(os, 'O_DIRECTORY'):\n    raise SystemExit('tasteforge secure output requires a POSIX platform (Linux/macOS)')\nout = _SafeOutput(Path('out'))","handlingStrategy":"try-catch","validationCode":"import os\nplatform_ok = hasattr(os, 'O_NOFOLLOW') and hasattr(os, 'O_DIRECTORY')\nif not platform_ok:\n    raise SystemExit('This workflow requires a POSIX platform (Linux/macOS).')","typeGuard":"def supports_secure_output() -> bool:\n    return hasattr(os, 'O_NOFOLLOW') and hasattr(os, 'O_DIRECTORY')","tryCatchPattern":"try:\n    out = _SafeOutput(root)\nexcept RuntimeError as e:\n    if 'O_NOFOLLOW' in str(e):\n        sys.exit('Unsupported platform: secure output needs O_NOFOLLOW/O_DIRECTORY')\n    raise","preventionTips":["Run this workflow only on Linux/macOS","Check hasattr(os, 'O_NOFOLLOW') at startup, before long work","Avoid Windows/MSYS environments for security-sensitive file writes","Document the POSIX requirement in your pipeline setup"],"tags":["platform","security","filesystem"],"backgroundTag":"unsupported-platform","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"}