{"record":{"id":"c24343a1face7e07","repo":"zylon-ai/private-gpt","slug":"path-canonical-path-does-not-match-any-session","errorCode":null,"errorMessage":"Path '{canonical_path}' does not match any session mount.","messagePattern":"Path '(.+?)' does not match any session mount\\.","errorType":"exception","errorClass":"ValueError","httpStatus":400,"severity":"error","filePath":"private_gpt/components/code_execution/path_translator.py","lineNumber":66,"sourceCode":"    def unregister(self, canonical: str) -> None:\n        \"\"\"Remove a mount mapping and rebuild the internal regex.\"\"\"\n        self._mounts = [(c, r, w) for c, r, w in self._mounts if c != canonical]\n        self._rebuild_regex()\n\n    # ------------------------------------------------------------------\n    # Path translation helpers\n    # ------------------------------------------------------------------\n\n    def to_real(self, canonical_path: str) -> Path:\n        \"\"\"Translate a canonical path to its real filesystem Path.\n\n        Raises ValueError if the path does not start with any known mount prefix.\n        \"\"\"\n        for canonical, real, _ in self._mounts:\n            if canonical_path.startswith(canonical):\n                relative = canonical_path[len(canonical) :]\n                return real / relative\n        raise ValueError(f\"Path '{canonical_path}' does not match any session mount.\")\n\n    def to_canonical(self, real: Path | str) -> str:\n        \"\"\"Reverse-translate a real path to its canonical form.\n\n        Raises ValueError if the real path is outside all mount points.\n        \"\"\"\n        real_str = str(real)\n        for canonical, mount_real, _ in self._mounts:\n            mount_str = str(mount_real)\n            if real_str == mount_str or real_str.startswith(mount_str + \"/\"):\n                relative = real_str[len(mount_str) :]\n                return canonical + relative.lstrip(\"/\")\n        raise ValueError(f\"Real path '{real}' is not inside any session mount.\")\n\n    # ------------------------------------------------------------------\n    # String rewriting (commands and output)\n    # ------------------------------------------------------------------\n","sourceCodeStart":48,"sourceCodeEnd":84,"githubUrl":"https://github.com/zylon-ai/private-gpt/blob/4a030776a31a901ad80b1bf4d7faa2c1a367efbb/private_gpt/components/code_execution/path_translator.py#L48-L84","documentation":"PathTranslator.to_real() maps an LLM-visible canonical path back to a real filesystem path by longest-prefix match over the session's mount table. This ValueError means the canonical_path does not start with any registered mount prefix (or the mount table is empty), so translation is impossible and — importantly for security — the path would escape the sandbox if guessed.","triggerScenarios":"Calling to_real() with a path invented by the LLM outside mounted roots (e.g. '/etc/passwd' when only '/home/agent/' is mounted); using a real absolute path instead of its canonical form; calling before any mounts were registered.","commonSituations":"Model hallucinating paths outside its sandbox; forgetting to call to_canonical() on real paths before echoing them back; mounts unregistered mid-session while old paths linger in conversation.","solutions":["Only pass paths previously produced by to_canonical() back into to_real()","Register a mount covering the needed prefix via translator.register(canonical, real, writable)","Treat the ValueError as expected LLM behavior: catch it and re-prompt the model with the list of valid mounted roots"],"exampleFix":"# before\nreal = translator.to_real(\"/etc/passwd\")  # not mounted\n\n# after\ntry:\n    real = translator.to_real(canonical_path)\nexcept ValueError:\n    real = None  # tell the model which mounts exist","handlingStrategy":"try-catch","validationCode":"canonical_prefixes = [c for c, _, _ in translator._mounts]  # or expose a public mounts property\nif not any(canonical_path.startswith(c) for c in canonical_prefixes):\n    return error_to_model(f\"path not accessible; mounted roots: {canonical_prefixes}\")","typeGuard":"def is_mounted_canonical(translator: PathTranslator, path: str) -> bool:\n    return any(path.startswith(c) for c, _, _ in translator._mounts)","tryCatchPattern":"try:\n    real = translator.to_real(canonical_path)\nexcept ValueError:\n    # expected when the LLM invents paths; re-prompt with valid roots\n    mounts = [c for c, _, _ in translator._mounts]\n    return f\"Path is outside the sandbox. Accessible roots: {mounts}\"","preventionTips":["Always round-trip: only feed to_real() strings produced by to_canonical()","Include the list of mounted roots in the system prompt so the model stays inside them","Treat unmatched paths as a normal LLM outcome (re-prompt), not an exceptional bug"],"tags":["code-execution","path-translation","sandbox","validation"],"backgroundTag":null,"analyzedSha":"4a030776a31a901ad80b1bf4d7faa2c1a367efbb","analyzedAt":"2026-08-15T03:51:26.951Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}