{"record":{"id":"c9c9d45782089285","repo":"infiniflow/ragflow","slug":"artifact-symlinks-are-not-allowed-relative-path","errorCode":null,"errorMessage":"Artifact symlinks are not allowed: {relative_path}","messagePattern":"Artifact symlinks are not allowed: (.+?)","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"agent/sandbox/providers/ssh.py","lineNumber":641,"sourceCode":"        artifacts: list[dict[str, Any]],\n    ) -> None:\n        try:\n            entries = sftp.listdir_attr(current_dir)\n        except FileNotFoundError:\n            return\n\n        for entry in sorted(entries, key=lambda item: item.filename):\n            name = entry.filename\n            remote_path = posixpath.join(current_dir, name)\n            relative_path = posixpath.join(relative_dir, name) if relative_dir else name\n            mode = entry.st_mode\n            if mode is None:\n                mode = sftp.lstat(remote_path).st_mode\n            if mode is None:\n                raise RuntimeError(f\"Unable to determine artifact entry type: {relative_path}\")\n\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:","sourceCodeStart":623,"sourceCodeEnd":659,"githubUrl":"https://github.com/infiniflow/ragflow/blob/554fb1133ac3861732235ad9c377eb5e0a770665/agent/sandbox/providers/ssh.py#L623-L659","documentation":"Raised as RuntimeError when artifact collection encounters a symbolic link anywhere under the artifacts directory. Symlinks are categorically rejected (via stat.S_ISLNK on lstat-derived mode) because following them would let sandboxed code exfiltrate arbitrary remote files (e.g. linking 'report.pdf' -> /etc/shadow) into the returned base64 artifacts. This is a deliberate security guard, not a size/format limitation.","triggerScenarios":"Sandboxed code creating os.symlink('/etc/passwd', 'out/data.json') or ln -s before finishing; artifacts dir containing links left by a prior run; language runtimes that symlink cache/output files into the working directory.","commonSituations":"Prompt-injected or malicious generated code trying to read remote secrets via the artifact channel; build tools (npm, pip) symlinking into workspace output; shared workspaces reused across executions.","solutions":["Remove symlinks from sandboxed code before returning: write real files, or copy link targets you legitimately own","Use a fresh instance/workspace per execution so stale links cannot accumulate","If it appeared unexpectedly, audit the executed code — this guard firing is a strong exfiltration signal"],"exampleFix":"# before (sandboxed code)\nos.symlink('/etc/passwd', 'artifacts/creds.json')\n\n# after (sandboxed code)\nwith open('artifacts/report.json', 'w') as f:\n    f.write(json.dumps({'status': 'ok'}))","handlingStrategy":"try-catch","validationCode":"# inside sandboxed code, before returning\nimport os\nfor root, _, files in os.walk(artifacts_dir):\n    for f in files:\n        p = os.path.join(root, f)\n        if os.path.islink(p):\n            os.remove(p)","typeGuard":null,"tryCatchPattern":"try:\n    artifacts = provider.collect_artifacts(instance_id, artifacts_dir)\nexcept RuntimeError as e:\n    if \"symlinks are not allowed\" in str(e):\n        log.security(\"sandboxed code attempted symlink exfiltration: %s\", e)\n        raise","preventionTips":["Treat this error as a security signal — audit the generated code, do not silence it","Generate artifacts directly; never link to files outside the workspace","Use a fresh workspace per execution so links cannot linger from prior runs"],"tags":["security","symlink","artifacts","exfiltration","ssh"],"backgroundTag":null,"analyzedSha":"554fb1133ac3861732235ad9c377eb5e0a770665","analyzedAt":"2026-08-15T09:20:16.380Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}