{"record":{"id":"b0e401232d1297d3","repo":"paperclipai/paperclip","slug":"railway-ssh-host-key-invalid","errorCode":"railway_ssh_host_key_invalid","errorMessage":"The Railway host key is too long.","messagePattern":"The Railway host key is too long\\.","errorType":"error_code","errorClass":"RailwayError","httpStatus":400,"severity":"error","filePath":"server/src/services/railway-ssh.ts","lineNumber":13,"sourceCode":"import { execFile, spawn } from \"node:child_process\";\nimport { mkdtemp, readFile, rm, writeFile } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport path from \"node:path\";\nimport { promisify } from \"node:util\";\nimport { randomBytes } from \"node:crypto\";\nimport { RailwayError, type RailwaySshInput } from \"./railway.js\";\nimport { redactSensitiveText } from \"../redaction.js\";\n\nexport const RAILWAY_SSH_SECRET_PATH = \"railway.ssh_private_key\";\n\nexport function validateRailwayKnownHosts(value: string): string {\n  if (value.length > 8192) throw new RailwayError(\"railway_ssh_host_key_invalid\", \"The Railway host key is too long.\", 400);\n  const lines = value.trim().split(/\\r?\\n/);\n  if (lines.length === 0 || lines.length > 5 || lines.some((line) => !/^ssh\\.railway\\.com (ssh-ed25519|ssh-rsa|ecdsa-sha2-nistp256) [A-Za-z0-9+/]+={0,2}$/.test(line))) {\n    throw new RailwayError(\"railway_ssh_host_key_invalid\", \"Paste verified known_hosts lines for ssh.railway.com only, without aliases, wildcards or comments.\", 400);\n  }\n  return lines.join(\"\\n\") + \"\\n\";\n}\n\nexport async function generateRailwaySshKey(): Promise<{ publicKey: string; privateKey: string }> {\n  const directory = await mkdtemp(path.join(tmpdir(), \"paperclip-railway-key-\"));\n  try {\n    const keyPath = path.join(directory, \"identity\");\n    await promisify(execFile)(\"/usr/bin/ssh-keygen\", [\"-q\", \"-t\", \"ed25519\", \"-N\", \"\", \"-C\", \"paperclip-railway\", \"-f\", keyPath], { timeout: 10_000, env: { PATH: \"/usr/bin:/bin\" } });\n    return { publicKey: (await readFile(`${keyPath}.pub`, \"utf8\")).trim(), privateKey: await readFile(keyPath, \"utf8\") };\n  } catch {\n    throw new RailwayError(\"railway_ssh_unavailable\", \"Generating a Railway key requires system OpenSSH (ssh-keygen) on the Paperclip runtime.\", 422);\n  } finally { await rm(directory, { recursive: true, force: true }); }\n}\n","sourceCodeStart":1,"sourceCodeEnd":31,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/server/src/services/railway-ssh.ts#L1-L31","documentation":"RailwayError (code railway_ssh_host_key_invalid, HTTP 400) thrown by validateRailwayKnownHosts when the provided known_hosts value exceeds 8192 characters. The validator caps host key input to a bounded size before storing it as a Railway connection secret. Oversized input is rejected up-front as a client error.","triggerScenarios":"Saving a Railway connection whose known_hosts field is longer than 8192 characters — e.g. pasting an entire ~/.ssh/known_hosts file, multiple full keys with comments, or base64 blobs with wrappers.","commonSituations":"User pastes their whole known_hosts file instead of only the ssh.railway.com lines; includes commented entries, hashed @-entries, or certificate lines; clipboard picks up extra content; automated import dumps all hosts.","solutions":["Trim input to only 1-5 verified ssh.railway.com lines (max 8192 chars total)","Remove comments, aliases, @cert-authority/@revoked markers, and non-Railway host lines","Regenerate with the correct line: 'ssh.railway.com ssh-ed25519 <base64>' (fetch via ssh-keyscan ssh.railway.com and filter)","Validate length client-side: value.length <= 8192 before submitting"],"exampleFix":"// before\nconst knownHosts = fs.readFileSync(\"~/.ssh/known_hosts\", \"utf8\"); // too large\n// after\nconst knownHosts = fs.readFileSync(\"~/.ssh/known_hosts\", \"utf8\")\n  .split(\"\\n\")\n  .filter((l) => l.startsWith(\"ssh.railway.com \"))\n  .slice(0, 5)\n  .join(\"\\n\");","handlingStrategy":"validation","validationCode":"function validateHostKeyLength(value) {\n  if (typeof value !== \"string\" || value.length > 8192) {\n    throw new Error(\"Known hosts input must be a string of at most 8192 characters\");\n  }\n  return value;\n}","typeGuard":"null","tryCatchPattern":"try {\n  const normalized = await api.saveRailwayKnownHosts(value);\n} catch (e) {\n  if (e.code === \"railway_ssh_host_key_invalid\" && value.length > 8192) {\n    showUserError(\"Paste only the ssh.railway.com lines (max 5), not your whole known_hosts file.\");\n    return;\n  }\n  throw e;\n}","preventionTips":["Paste only verified ssh.railway.com lines (1-5), never the entire known_hosts file","Client-side check value.length <= 8192 before submit","Strip comments, hashed entries, and non-Railway hosts before saving","Document the 8192-char limit next to the connection form"],"tags":["validation","ssh","railway"],"backgroundTag":"value-out-of-range","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-22T11:17:16.035Z"}