infiniflow/ragflow · error · RuntimeError
Artifact symlinks are not allowed: {relative_path}
Error message
Artifact symlinks are not allowed: {relative_path} What it means
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.
Source
Thrown at agent/sandbox/providers/ssh.py:641
artifacts: list[dict[str, Any]],
) -> None:
try:
entries = sftp.listdir_attr(current_dir)
except FileNotFoundError:
return
for entry in sorted(entries, key=lambda item: item.filename):
name = entry.filename
remote_path = posixpath.join(current_dir, name)
relative_path = posixpath.join(relative_dir, name) if relative_dir else name
mode = entry.st_mode
if mode is None:
mode = sftp.lstat(remote_path).st_mode
if mode is None:
raise RuntimeError(f"Unable to determine artifact entry type: {relative_path}")
if stat.S_ISLNK(mode):
raise RuntimeError(f"Artifact symlinks are not allowed: {relative_path}")
if stat.S_ISDIR(mode):
self._collect_artifacts_recursive(sftp, remote_path, relative_path, artifacts)
continue
if not stat.S_ISREG(mode):
raise RuntimeError(f"Unsupported artifact entry: {relative_path}")
if len(artifacts) >= self.max_artifacts:
raise RuntimeError(f"SSH execution produced more than {self.max_artifacts} artifacts.")
size = int(entry.st_size or 0)
if size > self.max_artifact_bytes:
raise RuntimeError(f"Artifact exceeds {self.max_artifact_bytes} bytes: {relative_path}")
ext = os.path.splitext(name)[1].lower()
if ext not in ALLOWED_ARTIFACT_EXTENSIONS:
raise RuntimeError(f"Unsupported artifact type: {relative_path}")
with sftp.file(remote_path, "rb") as artifact_file:View on GitHub (pinned to 554fb1133a)
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
Example fix
# before (sandboxed code)
os.symlink('/etc/passwd', 'artifacts/creds.json')
# after (sandboxed code)
with open('artifacts/report.json', 'w') as f:
f.write(json.dumps({'status': 'ok'})) Defensive patterns
Strategy: try-catch
Validate before calling
# inside sandboxed code, before returning
import os
for root, _, files in os.walk(artifacts_dir):
for f in files:
p = os.path.join(root, f)
if os.path.islink(p):
os.remove(p) Try / catch
try:
artifacts = provider.collect_artifacts(instance_id, artifacts_dir)
except RuntimeError as e:
if "symlinks are not allowed" in str(e):
log.security("sandboxed code attempted symlink exfiltration: %s", e)
raise Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Artifact symlinks are not allowed: {path.name}
- Artifact symlinks are not allowed: {relative_path}
- Artifact symlinks are not allowed: {relative_path}
- Failed to load configured SSH known_hosts file.
- Unable to determine artifact entry type: {relative_path}
AI-assisted analysis of infiniflow/ragflow@554fb1133a (2026-08-15).
Data as JSON: /api/errors/c9c9d45782089285.
Report an issue: GitHub.