{"record":{"id":"2ff0f6131db92cc5","repo":"usestrix/strix","slug":"spec-has-an-invalid-destination-path-candida","errorCode":null,"errorMessage":"'{spec}' has an invalid destination path: {candidate}","messagePattern":"'(.+?)' has an invalid destination path: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"strix/interface/utils.py","lineNumber":1707,"sourceCode":"# sources, so a large file makes session bring-up slower.\n\n\ndef _workspace_file_dest(spec: str, source: Path) -> str:\n    \"\"\"Return the workspace-relative destination declared by ``spec``.\"\"\"\n    _, sep, dest = spec.rpartition(\":\")\n    candidate = dest.strip() if sep and dest.strip() else source.name\n    if candidate.startswith(\"/\") or Path(candidate).is_absolute():\n        if not candidate.startswith(\"/workspace/\"):\n            raise ValueError(\n                f\"'{spec}' must land inside the workspace: use a relative \"\n                \"destination or a path under /workspace\"\n            )\n        candidate = candidate.removeprefix(\"/workspace/\")\n    candidate = candidate.strip(\"/\")\n    if not candidate:\n        raise ValueError(f\"'{spec}' has an empty destination path\")\n    if any(part in (\"\", \".\", \"..\") for part in candidate.split(\"/\")):\n        raise ValueError(f\"'{spec}' has an invalid destination path: {candidate}\")\n    # A control character would let the path span more than the one line it is\n    # rendered on in the agent task, so the whole spec is rejected.\n    if any(ord(char) < 0x20 or ord(char) == 0x7F for char in candidate):\n        raise ValueError(f\"'{spec}' has a control character in its destination path\")\n    return candidate\n\n\ndef resolve_workspace_files(specs: list[str] | None) -> list[dict[str, str]]:\n    \"\"\"Validate ``PATH[:DEST]`` specs into source/destination pairs.\n\n    Each spec names a readable host file. ``DEST`` is the path inside\n    ``/workspace``; it defaults to the file name. Raises ``ValueError`` with a\n    user-facing message when a spec is unusable.\n    \"\"\"\n    resolved: list[dict[str, str]] = []\n    seen: dict[str, str] = {}\n    for spec in specs or []:\n        raw, sep, dest = spec.rpartition(\":\")","sourceCodeStart":1689,"sourceCodeEnd":1725,"githubUrl":"https://github.com/usestrix/strix/blob/85513391305171ecc6faffe03da4a8bda5e3febb/strix/interface/utils.py#L1689-L1725","documentation":"Raised by _workspace_file_dest() when the destination path contains an empty, '.' or '..' path component after normalization. This blocks path-traversal: the injected file must stay inside /workspace and cannot climb out or reference the current directory.","triggerScenarios":"Specs like --workspace-file a.txt:../escape.txt, --workspace-file a.txt:foo/../bar, --workspace-file a.txt:./a.txt, or a.txt:foo//bar (empty part between slashes). The check runs on candidate.split('/'), so any traversal or duplicate-slash segment raises ValueError.","commonSituations":"User copies a relative path that was meaningful on the host (./configs/app.yaml) or relies on '..' to place the file next to another workspace dir. Also occurs when specs are generated by joining paths without normalizing (dir + '/' + name producing '//').","solutions":["Use a clean relative path with no '.', '..' or doubled slashes: --workspace-file a.txt:configs/app.yaml","Normalize generated specs before passing them: posixpath.normpath(dest).replace('./', '') and reject results starting with '..'","For paths that must sit 'up' a level, restructure so all injected files live flat or in subdirs under /workspace"],"exampleFix":"# before\nstrix --workspace-file app.ini:./config/../app.ini -t ./\n# after\nstrix --workspace-file app.ini:app.ini -t ./","handlingStrategy":"validation","validationCode":"import posixpath\n\ndef clean_dest(dest: str) -> str:\n    d = dest.strip().removeprefix(\"/workspace/\").strip(\"/\")\n    parts = d.split(\"/\")\n    if any(p in (\"\", \".\", \"..\") for p in parts):\n        raise ValueError(f\"unsafe destination: {dest!r}\")\n    return \"/\".join(parts)","typeGuard":null,"tryCatchPattern":"try:\n    resolve_workspace_files(specs)\nexcept ValueError as e:\n    # message names the offending spec and candidate path\n    show_error(str(e))","preventionTips":["Never pass host-relative convenience paths (./x, a/../b) as DEST","Normalize with posixpath.normpath then reject '..' components","Flatten injected layouts so traversal is unnecessary"],"tags":["strix","cli","path-traversal","validation","workspace-files"],"backgroundTag":null,"analyzedSha":"85513391305171ecc6faffe03da4a8bda5e3febb","analyzedAt":"2026-08-15T05:03:57.275Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}