{"record":{"id":"bced5e795d40f0b6","repo":"zylon-ai/private-gpt","slug":"invalid-system-specification-dict-system","errorCode":null,"errorMessage":"Invalid system specification (dict): {system}","messagePattern":"Invalid system specification \\(dict\\): (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"warning","filePath":"private_gpt/chat/input_models.py","lineNumber":191,"sourceCode":"\n    # If already a System instance, return as-is\n    if isinstance(system, System):\n        return system\n\n    # TextBlock -> System(text=...)\n    if isinstance(system, TextBlock):\n        return System(text=system.text)\n\n    # String -> System(text=...)\n    if isinstance(system, str):\n        return System(text=system)\n\n    # Dict -> try to convert to System via pydantic\n    if isinstance(system, dict):\n        try:\n            return System.model_validate(system)\n        except Exception as e:\n            raise ValueError(f\"Invalid system specification (dict): {system}\") from e\n\n    # List: allow list of System / str / dict and convert+merge\n    if isinstance(system, list):\n        # Convert each item to System\n        converted: list[System] = []\n        for item in system:\n            if isinstance(item, System):\n                converted.append(item)\n            elif isinstance(item, TextBlock):\n                converted.append(System(text=item.text))\n            elif isinstance(item, str):\n                converted.append(System(text=item))\n            elif isinstance(item, dict):\n                try:\n                    converted.append(System.model_validate(item))\n                except Exception as e:\n                    raise ValueError(\n                        f\"Invalid system item in list (dict): {item}\"","sourceCodeStart":173,"sourceCodeEnd":209,"githubUrl":"https://github.com/zylon-ai/private-gpt/blob/4a030776a31a901ad80b1bf4d7faa2c1a367efbb/private_gpt/chat/input_models.py#L173-L209","documentation":"Raised by FileService._validate_file_id when any path segment of the storage path equals '..'. It is a path-traversal guard applied after decoding the file_id into a canonical storage path, rejecting ids that would escape their intended folder. FastAPI surfaces it as a 400 Bad Request.","triggerScenarios":"Supplying a file_id that decodes to a path like 'uploads/../../etc/passwd'; fuzzing or manually crafting ids; a client bug that concatenates user input containing '..' into the id field.","commonSituations":"Security scanners and penetration tests probing the files API; client code building file ids from filenames without sanitization; server-side tests asserting traversal is blocked.","solutions":["Remove any '..' segments from user-supplied path components before encoding them into a file_id.","Use only ids returned by the upload/list APIs rather than constructing ids from raw filenames.","If you are intentionally testing traversal protection, expect a 400 and move on — the guard is working as designed."],"exampleFix":"// before\nconst id = encodeFileId(`uploads/${fileName}`); // fileName may contain '..'\n\n// after\nconst safe = fileName.split('/').filter((s) => s && s !== '.').join('/');\nif (safe.includes('..')) throw new Error('invalid filename');\nconst id = encodeFileId(`uploads/${safe}`);","handlingStrategy":"validation","validationCode":"function safeSegments(p) {\n  const segs = p.split('/');\n  if (segs.includes('..')) throw new Error('path traversal rejected');\n  return segs.filter(Boolean).join('/');\n}\nconst fileId = encodeId(`uploads/${safeSegments(userInput)}`);","typeGuard":"const hasNoTraversal = (s) => !s.split('/').includes('..');","tryCatchPattern":"try { await api.fileContent(scopeId, fileId); }\ncatch (e) { if (e.status === 400) { /* invalid id: log and reject input */ } else throw e; }","preventionTips":["Sanitize every path segment that feeds into a file id","Treat 400 from this endpoint as a client bug, not a transient error","Add fuzz tests asserting traversal ids are rejected client-side before shipping"],"tags":["fastapi","http-400","path-traversal","security","validation"],"backgroundTag":null,"analyzedSha":"4a030776a31a901ad80b1bf4d7faa2c1a367efbb","analyzedAt":"2026-08-15T03:51:26.951Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}