{"record":{"id":"7ddc23a70043bd56","repo":"usestrix/strix","slug":"spec-must-land-inside-the-workspace-use-a-rel","errorCode":null,"errorMessage":"'{spec}' must land inside the workspace: use a relative destination or a path under /workspace","messagePattern":"'(.+?)' must land inside the workspace: use a relative destination or a path under /workspace","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"strix/interface/utils.py","lineNumber":1698,"sourceCode":"        sys.exit(1)\n\n    return path\n\n\n# --- Workspace files -------------------------------------------------------\n#\n# ``--workspace-file`` places a single host file into the sandbox workspace,\n# outside every target tree. Content rides the same upload as the target\n# 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.","sourceCodeStart":1680,"sourceCodeEnd":1716,"githubUrl":"https://github.com/usestrix/strix/blob/85513391305171ecc6faffe03da4a8bda5e3febb/strix/interface/utils.py#L1680-L1716","documentation":"Raised by _workspace_file_dest() in strix/interface/utils.py when a --workspace-file spec's destination is an absolute path that does not start with /workspace/. The tool requires every injected file to land inside the sandbox's /workspace directory; absolute destinations elsewhere (e.g. /etc/passwd) are rejected to prevent writing outside the workspace.","triggerScenarios":"Calling strix with --workspace-file /host/secrets.txt:/etc/creds or any spec whose DEST part (after the last ':') is an absolute path not prefixed with /workspace/. Example: resolve_workspace_files(['/tmp/a.txt:/opt/a.txt']) raises ValueError.","commonSituations":"User copies a host absolute path into the DEST slot thinking it refers to the host, or tries to place the file next to the target tree (e.g. /app/config.yaml) instead of /workspace. Also triggered by Windows-style paths like C:\\\\conf\\\\a.yaml parsed as absolute.","solutions":["Use a relative destination: --workspace-file /host/secrets.txt:secrets.txt (file lands at /workspace/secrets.txt)","If you need an explicit absolute form, prefix it with /workspace/: --workspace-file /host/f.txt:/workspace/f.txt","Drop the destination entirely and let it default to the source file name: --workspace-file /host/f.txt"],"exampleFix":"# before\nstrix --workspace-file /etc/app/settings.ini:/etc/settings.ini -t ./\n# after\nstrix --workspace-file /etc/app/settings.ini:settings.ini -t ./\n# or explicitly: --workspace-file /etc/app/settings.ini:/workspace/settings.ini","handlingStrategy":"validation","validationCode":"def safe_workspace_spec(source: str, dest: str) -> str:\n    from pathlib import PurePosixPath\n    d = dest.strip()\n    if d.startswith(\"/\") and not d.startswith(\"/workspace/\"):\n        raise SystemExit(f\"destination must be relative or under /workspace: {d}\")\n    d = d.removeprefix(\"/workspace/\").strip(\"/\")\n    return f\"{source}:{d}\" if d else source","typeGuard":null,"tryCatchPattern":"try:\n    resolve_workspace_files([spec])\nexcept ValueError as e:\n    # user-facing message; re-prompt for a corrected spec\n    print(e)","preventionTips":["Always write destinations as plain relative paths (configs/app.yaml), never absolute host paths","Treat /workspace/ as the only legal absolute prefix","Default to omitting DEST so the basename is used"],"tags":["strix","cli","path-validation","workspace-files"],"backgroundTag":null,"analyzedSha":"85513391305171ecc6faffe03da4a8bda5e3febb","analyzedAt":"2026-08-15T05:03:57.275Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}