can1357/oh-my-pi · error

SSH key permissions must be 600 or stricter: ${keyPath}

Error message

SSH key permissions must be 600 or stricter: ${keyPath}

What it means

On non-Windows platforms, validateKeyPermissions enforces that the private key has no group/other permission bits ((mode & 0o077) === 0), i.e. 600 or stricter. SSH itself refuses world/group-readable private keys; this library fails fast with a clear message before spawning ssh.

Source

Thrown at packages/coding-agent/src/ssh/connection-manager.ts:260

async function validateKeyPermissions(keyPath?: string, platform: SshPlatform = process.platform): Promise<void> {
	if (!keyPath) return;
	let stats: fs.Stats;
	try {
		stats = await fs.promises.stat(keyPath);
	} catch (err) {
		if (isEnoent(err)) {
			throw new Error(`SSH key not found: ${keyPath}`);
		}
		throw err;
	}
	if (!stats.isFile()) {
		throw new Error(`SSH key is not a file: ${keyPath}`);
	}
	if (platform === "win32") return;
	const mode = stats.mode & 0o777;
	if ((mode & 0o077) !== 0) {
		throw new Error(`SSH key permissions must be 600 or stricter: ${keyPath}`);
	}
}

function buildCommonArgs(host: SSHConnectionTarget, options?: SSHArgsOptions): string[] {
	const args = options?.allowStdin ? [] : ["-n"];

	if (supportsSshControlMaster(options?.platform)) {
		args.push("-o", "ControlMaster=auto", "-o", `ControlPath=${CONTROL_PATH}`, "-o", "ControlPersist=3600");
	}

	args.push("-o", "BatchMode=yes", "-o", "StrictHostKeyChecking=accept-new");

	if (host.port) {
		args.push("-p", String(host.port));
	}
	if (host.keyPath) {
		args.push("-i", host.keyPath);
	}

View on GitHub (pinned to 9690622007)

Solutions

  1. chmod 600 <keyPath>
  2. If under WSL /mnt/c, move the key into the Linux filesystem (~/) and chmod 600, or remount with correct metadata
  3. Avoid storing keys in git; if you must, fix the mode after checkout

Example fix

// before
$ ls -l ~/.ssh/id_ed25519
-rw-r--r-- 1 me me ... id_ed25519
// after
$ chmod 600 ~/.ssh/id_ed25519
Defensive patterns

Strategy: validation

Validate before calling

import * as fs from "node:fs/promises";
if (process.platform !== "win32") {
  const st = await fs.stat(keyPath);
  if ((st.mode & 0o077) !== 0) {
    throw new Error(`chmod 600 ${keyPath} before connecting (mode ${(st.mode & 0o777).toString(8)})`);
  }
}

Try / catch

try {
  await connect(target);
} catch (err) {
  if (err instanceof Error && err.message.includes("permissions must be 600")) {
    const p = err.message.split(": ").pop()!;
    await $`chmod 600 ${p}`.quiet().nothrow();
    return connect(target); // retry once after fixing
  }
  throw err;
}

Prevention

When it happens

Trigger: Connecting with a key whose file mode is e.g. 644, 664, or 755 on Linux/macOS; commonly after copying a key with scp -r, downloading it via a browser, or cloning it from git (git preserves only the executable bit, yielding 644).

Common situations: Fresh key copied from Windows/macOS to Linux, key checked into a repo and checked out with 644, WSL accessing a key under /mnt/c (drvfs mounts are 777), backup-restore that reset modes.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/a8ac9aec86f9666b. Report an issue: GitHub.