{"record":{"id":"dcbe39e10b8215a0","repo":"infiniflow/ragflow","slug":"failed-to-load-configured-ssh-known-hosts-file","errorCode":null,"errorMessage":"Failed to load configured SSH known_hosts file.","messagePattern":"Failed to load configured SSH known_hosts file\\.","errorType":"exception","errorClass":"SandboxProviderConfigError","httpStatus":null,"severity":"error","filePath":"agent/sandbox/providers/ssh.py","lineNumber":464,"sourceCode":"        # RejectPolicy would reject every host on first connect,\n        # breaking the provider for normal setups. The order matters:\n        # load_system_host_keys() populates the store from\n        # ~/.ssh/known_hosts (and the legacy /etc/ssh/ssh_known_hosts);\n        # an optional explicit known_hosts file from `known_hosts`\n        # config is then merged on top.\n        client.load_system_host_keys()\n        if self.known_hosts:\n            try:\n                client.load_host_keys(self.known_hosts)\n            except OSError as exc:\n                # Fail closed when the operator-configured trust store\n                # is unreadable: continuing with system keys could let\n                # the connection succeed against an unintended anchor\n                # (e.g. an attacker who can write ~/.ssh/known_hosts).\n                # Match the Go provider's fail-closed posture (see\n                # internal/agent/sandbox/ssh.go::hostKeyCallback).\n                logging.warning(\"SSH: failed to load configured known_hosts file; refusing connection\")\n                raise SandboxProviderConfigError(\"Failed to load configured SSH known_hosts file.\") from exc\n        # Reject unknown hosts: this is the default fail-closed posture\n        # to prevent silent MITM. Operators must either ship a populated\n        # known_hosts file or accept the warning (paramiko will fail the\n        # connect) on first encounter.\n        client.set_missing_host_key_policy(paramiko.RejectPolicy())\n\n        connect_kwargs: dict[str, Any] = {\n            \"hostname\": self.host,\n            \"port\": self.port,\n            \"username\": self.username,\n            \"timeout\": self.timeout,\n            \"banner_timeout\": self.timeout,\n            \"auth_timeout\": self.timeout,\n            \"look_for_keys\": False,\n            \"allow_agent\": False,\n        }\n        if self.private_key:\n            connect_kwargs[\"pkey\"] = self._load_private_key()","sourceCodeStart":446,"sourceCodeEnd":482,"githubUrl":"https://github.com/infiniflow/ragflow/blob/554fb1133ac3861732235ad9c377eb5e0a770665/agent/sandbox/providers/ssh.py#L446-L482","documentation":"Raised as SandboxProviderConfigError when the operator configured a known_hosts file (self.known_hosts) but client.load_host_keys() raised OSError — file missing, unreadable, or a directory. The provider deliberately fails closed: continuing with only system keys could let the connection trust an unintended anchor (e.g. an attacker-writable ~/.ssh/known_hosts), matching the Go provider's posture. The OSError is chained and a warning is logged.","triggerScenarios":"initialize() with known_hosts pointing to a path that does not exist; file present but wrong ownership/permissions (OSError EACCES) especially when the app runs as a different user; path is a directory; container image built without copying the known_hosts file.","commonSituations":"Deploying with a config template referencing /etc/ssh/ssh_known_hosts that is absent in a slim container; file mounted read-only with restrictive mode; path typo or trailing whitespace in the config value (it is stripped, but case-sensitivity on paths still bites).","solutions":["Verify the file exists and is readable by the service user: ls -l <known_hosts path>","Create it if missing: ssh-keyscan -H <host> > <known_hosts> (run from a trusted network)","Fix permissions/ownership: chown <service-user> <file> && chmod 644 <file>","Or clear the known_hosts config option to rely on system host keys (still fail-closed via RejectPolicy)"],"exampleFix":"# before\nprovider.initialize({..., \"known_hosts\": \"/etc/ragflow/known_hosts\"})  # file absent -> fail closed\n\n# after\nssh-keyscan -H 10.0.0.5 > /etc/ragflow/known_hosts && chmod 644 /etc/ragflow/known_hosts\nprovider.initialize({..., \"known_hosts\": \"/etc/ragflow/known_hosts\"})","handlingStrategy":"validation","validationCode":"import os\nkh = config.get(\"known_hosts\", \"\")\nif kh and not (os.path.isfile(kh) and os.access(kh, os.R_OK)):\n    raise RuntimeError(f\"known_hosts path {kh!r} missing or unreadable; run ssh-keyscan first\")","typeGuard":null,"tryCatchPattern":"try:\n    provider.initialize(config)\nexcept SandboxProviderConfigError as e:\n    if \"known_hosts\" in str(e):\n        subprocess.run(f\"ssh-keyscan -H {config['host']} > {config['known_hosts']}\", shell=True, check=True)\n        provider.initialize(config)","preventionTips":["Generate the known_hosts file during deployment (ssh-keyscan) and verify readability by the service user","Mount it read-only into containers with mode 644","Fail CI/CD when the configured known_hosts path is absent instead of discovering it at runtime"],"tags":["ssh","known-hosts","security","configuration","filesystem"],"backgroundTag":null,"analyzedSha":"554fb1133ac3861732235ad9c377eb5e0a770665","analyzedAt":"2026-08-15T09:20:16.380Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}