paperclipai/paperclip · error · RailwayError

railway_ssh_host_key_invalid

railway_ssh_host_key_invalid

Error message

The Railway host key is too long.

What it means

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.

Solutions

  1. Trim input to only 1-5 verified ssh.railway.com lines (max 8192 chars total)
  2. Remove comments, aliases, @cert-authority/@revoked markers, and non-Railway host lines
  3. Regenerate with the correct line: 'ssh.railway.com ssh-ed25519 <base64>' (fetch via ssh-keyscan ssh.railway.com and filter)
  4. Validate length client-side: value.length <= 8192 before submitting

Example fix

// before
const knownHosts = fs.readFileSync("~/.ssh/known_hosts", "utf8"); // too large
// after
const knownHosts = fs.readFileSync("~/.ssh/known_hosts", "utf8")
  .split("\n")
  .filter((l) => l.startsWith("ssh.railway.com "))
  .slice(0, 5)
  .join("\n");
Defensive patterns

Strategy: validation

Validate before calling

function validateHostKeyLength(value) {
  if (typeof value !== "string" || value.length > 8192) {
    throw new Error("Known hosts input must be a string of at most 8192 characters");
  }
  return value;
}

Type guard

null

Try / catch

try {
  const normalized = await api.saveRailwayKnownHosts(value);
} catch (e) {
  if (e.code === "railway_ssh_host_key_invalid" && value.length > 8192) {
    showUserError("Paste only the ssh.railway.com lines (max 5), not your whole known_hosts file.");
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18). Data as JSON: /api/errors/b0e401232d1297d3. Report an issue: GitHub.

Appendix: source

Thrown at server/src/services/railway-ssh.ts:13

import { execFile, spawn } from "node:child_process";
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
import { promisify } from "node:util";
import { randomBytes } from "node:crypto";
import { RailwayError, type RailwaySshInput } from "./railway.js";
import { redactSensitiveText } from "../redaction.js";

export const RAILWAY_SSH_SECRET_PATH = "railway.ssh_private_key";

export function validateRailwayKnownHosts(value: string): string {
  if (value.length > 8192) throw new RailwayError("railway_ssh_host_key_invalid", "The Railway host key is too long.", 400);
  const lines = value.trim().split(/\r?\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))) {
    throw new RailwayError("railway_ssh_host_key_invalid", "Paste verified known_hosts lines for ssh.railway.com only, without aliases, wildcards or comments.", 400);
  }
  return lines.join("\n") + "\n";
}

export async function generateRailwaySshKey(): Promise<{ publicKey: string; privateKey: string }> {
  const directory = await mkdtemp(path.join(tmpdir(), "paperclip-railway-key-"));
  try {
    const keyPath = path.join(directory, "identity");
    await promisify(execFile)("/usr/bin/ssh-keygen", ["-q", "-t", "ed25519", "-N", "", "-C", "paperclip-railway", "-f", keyPath], { timeout: 10_000, env: { PATH: "/usr/bin:/bin" } });
    return { publicKey: (await readFile(`${keyPath}.pub`, "utf8")).trim(), privateKey: await readFile(keyPath, "utf8") };
  } catch {
    throw new RailwayError("railway_ssh_unavailable", "Generating a Railway key requires system OpenSSH (ssh-keygen) on the Paperclip runtime.", 422);
  } finally { await rm(directory, { recursive: true, force: true }); }
}

View on GitHub (pinned to 3f1d897a7c)