{"record":{"id":"f94465e7a0e2d3e9","repo":"can1357/oh-my-pi","slug":"sftp-password-injection-is-unsupported-by-the-shar","errorCode":null,"errorMessage":"SFTP password injection is unsupported by the shared SSH transport; configure a private-key path or SSH agent","messagePattern":"SFTP password injection is unsupported by the shared SSH transport; configure a private-key path or SSH agent","errorType":"validation","errorClass":"DestinationUnavailableError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/blob-broker/uploaders-self-hosted.ts","lineNumber":191,"sourceCode":"}\n\nfunction createFtpUploader(config: DestinationRuntimeConfig): BlobUploader {\n\tconst protocol = optionString(config, \"protocol\", \"sftp\");\n\tif (protocol !== \"ftp\" && protocol !== \"ftps\" && protocol !== \"sftp\") {\n\t\tthrow new Error(\"Destination option protocol must be ftp, ftps, or sftp\");\n\t}\n\tconst host = requiredStringOption(config, \"host\");\n\tconst username = requireCredential(config, \"username\");\n\tconst directory = optionString(config, \"path\");\n\tconst publicBase = requiredStringOption(config, \"publicBaseUrl\");\n\thttpBase(publicBase, \"publicBaseUrl\");\n\n\tif (protocol === \"sftp\") {\n\t\tconst port = optionNumber(config, \"port\", 22) ?? 22;\n\t\tconst keyPath = credentialString(config, \"privateKey\");\n\t\tconst password = credentialString(config, \"password\");\n\t\tif (password && !keyPath) {\n\t\t\tthrow new DestinationUnavailableError(\n\t\t\t\t\"ftp\",\n\t\t\t\t\"SFTP password injection is unsupported by the shared SSH transport; configure a private-key path or SSH agent\",\n\t\t\t);\n\t\t}\n\t\tif (keyPath?.includes(\"-----BEGIN\")) {\n\t\t\tthrow new DestinationUnavailableError(\n\t\t\t\t\"ftp\",\n\t\t\t\t\"the SFTP privateKey credential must be a filesystem path, not key contents\",\n\t\t\t);\n\t\t}\n\t\tconst connectionName = `blob-${username}-${host}-${port}`.replace(/[^A-Za-z0-9._-]/g, \"-\");\n\t\treturn {\n\t\t\tdestination: \"ftp\",\n\t\t\tasync upload(request) {\n\t\t\t\tconst filename = safeFileName(request);\n\t\t\t\tawait writeRemoteFile(\n\t\t\t\t\t{ name: connectionName, host, username, port, ...(keyPath ? { keyPath } : {}) },\n\t\t\t\t\tremotePath(directory, filename),","sourceCodeStart":173,"sourceCodeEnd":209,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/blob-broker/uploaders-self-hosted.ts#L173-L209","documentation":"A DestinationUnavailableError thrown when an sftp destination defines a password credential but no privateKey path. The ftp uploader routes SFTP through the shared SSH transport (writeRemoteFile), which only supports key files or SSH agent auth; interactive password injection is not implemented, so the destination is declared unavailable at creation time.","triggerScenarios":"options.protocol = \"sftp\" with credentials.password set and credentials.privateKey absent (or falsy), when createFtpUploader builds the uploader for destination \"ftp\".","commonSituations":"Reusing an FTP (curl-based) config's password credential on an SFTP destination; assuming password auth works because plain ftp/ftps supports it; missing that privateKey must be set even when ssh-agent is available (the check only requires a key path to be configured).","solutions":["Set credentials.privateKey to the filesystem path of an SSH private key (e.g. ~/.ssh/id_ed25519).","Or rely on SSH agent auth: still set a privateKey path if your agent holds the key, or use a key loaded in the agent without a password credential.","If you only have password access to the server, switch options.protocol to \"ftp\" or \"ftps\" (curl path supports user:password).","Add the key to ssh-agent (ssh-add) if you want agent-based auth alongside the configured key path."],"exampleFix":"// before\n{ \"destination\": \"ftp\", \"options\": { \"protocol\": \"sftp\", \"host\": \"box.example.com\" }, \"credentials\": { \"username\": \"u\", \"password\": \"hunter2\" } }\n// after\n{ \"destination\": \"ftp\", \"options\": { \"protocol\": \"sftp\", \"host\": \"box.example.com\" }, \"credentials\": { \"username\": \"u\", \"privateKey\": \"/home/me/.ssh/id_ed25519\" } }","handlingStrategy":"validation","validationCode":"if (dest.protocol === 'sftp' && dest.credentials?.password && !dest.credentials?.privateKey) {\n  throw new Error('sftp destinations need credentials.privateKey (path) or SSH agent; password-only is unsupported');\n}","typeGuard":null,"tryCatchPattern":"try {\n  const uploader = createSelfHostedUploader('ftp', config);\n} catch (err) {\n  if (err?.name === 'DestinationUnavailableError' && /password injection is unsupported/.test(err.message)) {\n    // reconfigure with a privateKey path or switch protocol to ftp/ftps\n  } else throw err;\n}","preventionTips":["Provision SSH key-based auth on servers and never rely on passwords for SFTP automation.","Keep credentials.privateKey as a path convention; load key material into ssh-agent where possible.","Run a pre-flight validation of sftp destinations (protocol + credentials combination) at config load.","Use password auth only on ftp/ftps (curl) destinations, where it is supported."],"tags":["sftp","authentication","ssh","unsupported-feature"],"backgroundTag":"unsupported-auth-method","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}