{"record":{"id":"a8275a963afd54c1","repo":"infiniflow/ragflow","slug":"unsupported-artifact-type-relative-path","errorCode":null,"errorMessage":"Unsupported artifact type: {relative_path}","messagePattern":"Unsupported artifact type: (.+?)","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"warning","filePath":"agent/sandbox/providers/ssh.py","lineNumber":657,"sourceCode":"\n            if stat.S_ISLNK(mode):\n                raise RuntimeError(f\"Artifact symlinks are not allowed: {relative_path}\")\n            if stat.S_ISDIR(mode):\n                self._collect_artifacts_recursive(sftp, remote_path, relative_path, artifacts)\n                continue\n            if not stat.S_ISREG(mode):\n                raise RuntimeError(f\"Unsupported artifact entry: {relative_path}\")\n\n            if len(artifacts) >= self.max_artifacts:\n                raise RuntimeError(f\"SSH execution produced more than {self.max_artifacts} artifacts.\")\n\n            size = int(entry.st_size or 0)\n            if size > self.max_artifact_bytes:\n                raise RuntimeError(f\"Artifact exceeds {self.max_artifact_bytes} bytes: {relative_path}\")\n\n            ext = os.path.splitext(name)[1].lower()\n            if ext not in ALLOWED_ARTIFACT_EXTENSIONS:\n                raise RuntimeError(f\"Unsupported artifact type: {relative_path}\")\n\n            with sftp.file(remote_path, \"rb\") as artifact_file:\n                content = artifact_file.read()\n\n            artifacts.append(\n                {\n                    \"name\": relative_path,\n                    \"content_b64\": base64.b64encode(content).decode(\"ascii\"),\n                    \"mime_type\": mimetypes.guess_type(name)[0] or \"application/octet-stream\",\n                    \"size\": size,\n                }\n            )\n\n    @staticmethod\n    def _normalize_language(language: str) -> str:\n        lang_lower = (language or \"python\").lower()\n        if lang_lower in {\"python\", \"python3\"}:\n            return \"python\"","sourceCodeStart":639,"sourceCodeEnd":675,"githubUrl":"https://github.com/infiniflow/ragflow/blob/554fb1133ac3861732235ad9c377eb5e0a770665/agent/sandbox/providers/ssh.py#L639-L675","documentation":"Raised as RuntimeError when an artifact's file extension is not in ALLOWED_ARTIFACT_EXTENSIONS ({.csv,.html,.jpeg,.jpg,.json,.pdf,.png,.svg}). The whitelist bounds what content types get base64-embedded and returned to the agent, blocking arbitrary binary types. It fires after the regular-file, symlink, count, and size checks pass, so a file failing here was otherwise collectable.","triggerScenarios":"Sandboxed code writing .txt, .xlsx, .xml, .parquet, .zip, or extensionless files into the artifacts dir; uppercase extensions are fine (lowercased before check) but e.g. '.JSON5' or '.htm' are not; generated temp files like 'plot.png.tmp' left behind.","commonSituations":"Excel exports (.xlsx) from data code; plain-text logs; intermediate files not cleaned up; users expecting .txt to be allowed.","solutions":["Emit only whitelisted types: wrap text in .html/.json/.csv, re-encode spreadsheets as CSV, export charts as .png/.svg","Delete non-whitelisted intermediates before the sandboxed code returns","Rename to a truthful allowed extension only when content genuinely matches (e.g. JSON data -> .json)"],"exampleFix":"# before (sandboxed code)\nopen('artifacts/notes.txt', 'w').write('ok')\n\n# after (sandboxed code)\nimport json\njson.dump({'note': 'ok'}, open('artifacts/notes.json', 'w'))","handlingStrategy":"validation","validationCode":"ALLOWED = {\".csv\", \".html\", \".jpeg\", \".jpg\", \".json\", \".pdf\", \".png\", \".svg\"}\nimport os\nbad = [f for f in os.listdir(artifacts_dir) if os.path.splitext(f)[1].lower() not in ALLOWED]\nif bad:\n    raise RuntimeError(f\"non-whitelisted artifact extensions: {bad}\")","typeGuard":"ALLOWED = {\".csv\", \".html\", \".jpeg\", \".jpg\", \".json\", \".pdf\", \".png\", \".svg\"}\ndef is_allowed_artifact_name(name: str) -> bool:\n    return os.path.splitext(name)[1].lower() in ALLOWED","tryCatchPattern":"try:\n    artifacts = provider.collect_artifacts(instance_id, artifacts_dir)\nexcept RuntimeError as e:\n    if \"Unsupported artifact type\" in str(e):\n        raise RuntimeError(\"rename/re-encode outputs to a whitelisted type: csv/html/jpg/json/pdf/png/svg\") from e","preventionTips":["Standardize generated outputs on the whitelisted extensions","Delete temp/partial files (e.g. '.png.tmp') before returning","Encode spreadsheets as CSV and text as JSON/HTML, not .xlsx/.txt"],"tags":["artifacts","file-type","whitelist","validation","ssh"],"backgroundTag":null,"analyzedSha":"554fb1133ac3861732235ad9c377eb5e0a770665","analyzedAt":"2026-08-15T09:20:16.380Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}