infiniflow/ragflow · critical · SandboxProviderConfigError

Failed to load SSH private key. {errors}

Error message

Failed to load SSH private key. {errors}

What it means

Raised as SandboxProviderConfigError by the private-key loader after every paramiko key class (RSAKey, Ed25519Key, ECDSAKey, etc.) failed to parse the configured private_key material. The message joins the individual per-loader errors, which distinguish wrong passphrase ('Private key file is encrypted' / decrypt errors) from wrong format ('not a valid OPENSSH or PEM key'). Both a file path and an in-memory key string are attempted depending on how the config was supplied.

Source

Thrown at agent/sandbox/providers/ssh.py:514

        )
        errors: list[str] = []
        private_key_value = self.private_key.strip()
        passphrase = self.passphrase or None

        if os.path.exists(private_key_value):
            for key_cls in loaders:
                try:
                    return key_cls.from_private_key_file(private_key_value, password=passphrase)
                except Exception as exc:
                    errors.append(str(exc))
        else:
            for key_cls in loaders:
                try:
                    return key_cls.from_private_key(io.StringIO(private_key_value), password=passphrase)
                except Exception as exc:
                    errors.append(str(exc))

        raise SandboxProviderConfigError("Failed to load SSH private key. " + "; ".join(error for error in errors if error))

    def _create_remote_workspace(self, client: paramiko.SSHClient) -> str:
        base_dir = self.work_dir.rstrip("/") or "/tmp"
        template = posixpath.join(base_dir, "ragflow-codeexec.XXXXXX")
        stdout, stderr, exit_code = self._run_remote_command(
            client,
            f"mkdir -p {shlex.quote(base_dir)} && mktemp -d {shlex.quote(template)}",
            timeout=min(self.timeout, 10),
        )
        if exit_code != 0:
            raise RuntimeError(f"Failed to create remote workspace on {self.host}: {stderr or stdout or 'unknown error'}")

        remote_work_dir = stdout.strip().splitlines()[-1] if stdout.strip() else ""
        if not remote_work_dir:
            raise RuntimeError("Remote workspace creation did not return a path.")
        return remote_work_dir

    def _upload_script(

View on GitHub (pinned to 554fb1133a)

Solutions

  1. Read the joined per-key errors in the message: 'encrypted' means passphrase problem, 'invalid key' means format problem
  2. Verify the key locally: ssh-keygen -y -f <keyfile> (prompts for passphrase) or chmod 400 + ssh -i
  3. If newlines were flattened in config, re-supply the key as a file path instead of an inline string
  4. For unsupported formats: convert with ssh-keygen -p -m PEM -f <key> (or -t ed25519 to regenerate), or upgrade paramiko

Example fix

# before: inline key with escaped newlines from a secret
provider.initialize({..., "private_key": "-----BEGIN...-----BEGIN OPENSSH...\nAAA..."})

# after: pass a real file path
provider.initialize({..., "private_key": "/secrets/id_ed25519", "passphrase": "correct-horse"})
Defensive patterns

Strategy: validation

Validate before calling

import os
key = config.get("private_key", "")
if key and not os.path.isfile(key) and "PRIVATE KEY" not in key:
    raise RuntimeError("private_key must be a readable file path or a PEM/OpenSSH key body")

Try / catch

try:
    provider.initialize(config)
except SandboxProviderConfigError as e:
    if "Failed to load SSH private key" in str(e):
        # message joins per-loader errors: 'encrypted' => passphrase, else format
        log.error("key load failed: %s", e)

Prevention

When it happens

Trigger: initialize() with a private_key value that is neither a readable path nor a valid PEM/OpenSSH key body; correct key but wrong passphrase; key in a format paramiko's version does not support (e.g. very new OpenSSH format on an old paramiko); key body truncated or with escaped newlines mangled by config transport.

Common situations: Passing a Docker/K8s secret with literal '\n' instead of real newlines; PuTTYgen .ppk keys (unsupported by paramiko); passphrase mismatch after key rotation; ancient paramiko that cannot read openssh-key v1 format Ed25519 keys.

Related errors


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