infiniflow/ragflow · error · RuntimeError

Artifact symlinks are not allowed: {relative_path}

Error message

Artifact symlinks are not allowed: {relative_path}

What it means

Raised during artifact collection when a listed file entry has a non-None `symlink_target`. Symlinks are categorically rejected to prevent exfiltrating files outside the artifacts directory (a link pointing at /etc/passwd or another tenant's data) — a security guard, not a size limit.

Source

Thrown at agent/sandbox/providers/ucloud_agent_sandbox.py:426

        """Collect allowed files from the execution artifact directory."""
        artifacts: list[dict[str, Any]] = []
        self._collect_artifacts_recursive(sandbox, artifacts_dir, "", artifacts, depth=0)
        return artifacts

    def _collect_artifacts_recursive(self, sandbox, current_dir: str, relative_dir: str, artifacts: list[dict[str, Any]], depth: int) -> None:
        """Traverse artifact directories while enforcing type, size, and depth limits."""
        if depth > MAX_ARTIFACT_DEPTH:
            raise RuntimeError(f"Artifact directory nesting exceeds {MAX_ARTIFACT_DEPTH} levels: {relative_dir}")
        sdk = _get_ucloud_sandbox_module()
        try:
            entries = sandbox.files.list(current_dir, depth=1, request_timeout=self.timeout)
        except sdk.FileNotFoundException:
            return
        for entry in sorted(entries, key=lambda item: item.path):
            name = posixpath.basename(entry.path)
            relative_path = posixpath.join(relative_dir, name) if relative_dir else name
            if entry.symlink_target is not None:
                raise RuntimeError(f"Artifact symlinks are not allowed: {relative_path}")
            if entry.type == sdk.FileType.DIR:
                self._collect_artifacts_recursive(sandbox, entry.path, relative_path, artifacts, depth + 1)
                continue
            if len(artifacts) >= self.max_artifacts:
                raise RuntimeError(f"UCloud Agent Sandbox execution produced more than {self.max_artifacts} artifacts.")
            if entry.size > self.max_artifact_bytes:
                raise RuntimeError(f"Artifact exceeds {self.max_artifact_bytes} bytes: {relative_path}")
            extension = os.path.splitext(name)[1].lower()
            if extension not in ALLOWED_ARTIFACT_EXTENSIONS:
                raise RuntimeError(f"Unsupported artifact type: {relative_path}")
            content = bytes(sandbox.files.read(entry.path, format="bytes", request_timeout=self.timeout))
            artifacts.append(
                {
                    "name": relative_path,
                    "content_b64": base64.b64encode(content).decode("ascii"),
                    "mime_type": mimetypes.guess_type(name)[0] or "application/octet-stream",
                    "size": entry.size,
                }

View on GitHub (pinned to 554fb1133a)

Solutions

  1. Copy files into the artifacts directory instead of symlinking (`shutil.copy`, not os.symlink).
  2. Configure build tools to emit plain files or place their symlinked dirs outside artifacts.
  3. Treat this error as a security signal — audit user code that tried to symlink absolute paths.
  4. Do not attempt to bypass by raising MAX depth/size; the check is unconditional by design.

Example fix

# before
os.symlink("/etc/passwd", "artifacts/passwd")  # collection -> RuntimeError

# after
shutil.copy("input.txt", "artifacts/input.txt")
Defensive patterns

Strategy: try-catch

Validate before calling

# reject symlink creation in code you send to the sandbox
if "os.symlink" in code or "ln -s" in code:
    raise ValueError("symlinks are not allowed in artifacts; copy files instead")

Type guard

def is_symlink_artifact_error(exc: RuntimeError) -> bool:
    return "symlinks are not allowed" in str(e if (e := exc) else "")

Try / catch

try:
    result = provider.execute(inst, code)
except RuntimeError as e:
    if "symlinks are not allowed" in str(e):
        # security signal: inspect what the code tried to link before retrying
        audit_and_reject(code)
    raise

Prevention

When it happens

Trigger: Executed code creating `os.symlink('/etc/passwd', 'artifacts/secret')` or symlinking any external path into the artifacts directory; collection then aborts the whole execution result.

Common situations: Convenient-looking LLM code that symlinks inputs into the output dir; build scripts whose tooling creates relative symlinks (e.g. node .bin links) inside the artifacts tree; prompt-injection attempts trying to read host files.

Related errors


AI-assisted analysis of infiniflow/ragflow@554fb1133a (2026-08-15). Data as JSON: /api/errors/d1b3969221022a15. Report an issue: GitHub.