import { describe, expect, it } from "vitest";
import type { HostConfig } from "../types.js";
import { validateHostForSsh } from "./ssh.js";
/**
* Edge case tests for SSH input validation (TEST-H2)
*
* Tests unicode characters, null bytes, ANSI escape sequences,
* and other edge cases that could cause security or parsing issues.
*/
describe("SSH input validation - edge cases", () => {
describe("Unicode handling", () => {
it("should accept basic ASCII characters", () => {
const host: HostConfig = {
name: "test-host",
host: "example.com",
protocol: "ssh",
sshUser: "admin",
sshKeyPath: "/home/user/.ssh/id_rsa",
};
expect(() => validateHostForSsh(host)).not.toThrow();
});
it("should accept unicode characters in name", () => {
const host: HostConfig = {
name: "test-🚀-host",
host: "example.com",
protocol: "ssh",
};
expect(() => validateHostForSsh(host)).not.toThrow();
});
it("should reject null bytes in host", () => {
const host: HostConfig = {
name: "test",
host: "example.com\x00malicious.com",
protocol: "ssh",
};
expect(() => validateHostForSsh(host)).toThrow(/invalid.*host/i);
});
it("should reject null bytes in user", () => {
const host: HostConfig = {
name: "test",
host: "example.com",
protocol: "ssh",
sshUser: "admin\x00root",
};
expect(() => validateHostForSsh(host)).toThrow(/SSH user.*Invalid characters/);
});
it("should reject null bytes in identity file path", () => {
const host: HostConfig = {
name: "test",
host: "example.com",
protocol: "ssh",
sshKeyPath: "/home/user/.ssh/id_rsa\x00/etc/shadow",
};
expect(() => validateHostForSsh(host)).toThrow(/SSH key path.*Invalid characters/);
});
it("should reject control characters in host", () => {
const host: HostConfig = {
name: "test",
host: "example.com\r\nmalicious-header",
protocol: "ssh",
};
expect(() => validateHostForSsh(host)).toThrow(/invalid.*host/i);
});
it("should reject ANSI escape sequences in host", () => {
const host: HostConfig = {
name: "test",
host: "example.com\x1b[31mmalicious",
protocol: "ssh",
};
expect(() => validateHostForSsh(host)).toThrow(/invalid.*host/i);
});
it("should reject ANSI escape sequences in user", () => {
const host: HostConfig = {
name: "test",
host: "example.com",
protocol: "ssh",
sshUser: "admin\x1b[0m",
};
expect(() => validateHostForSsh(host)).toThrow(/SSH user.*Invalid characters/);
});
});
describe("Special character handling", () => {
it("should accept hyphens in hostname", () => {
const host: HostConfig = {
name: "test",
host: "docker-01.example.com",
protocol: "ssh",
};
expect(() => validateHostForSsh(host)).not.toThrow();
});
it("should accept dots in hostname", () => {
const host: HostConfig = {
name: "test",
host: "sub.domain.example.com",
protocol: "ssh",
};
expect(() => validateHostForSsh(host)).not.toThrow();
});
it("should accept underscores in username", () => {
const host: HostConfig = {
name: "test",
host: "example.com",
protocol: "ssh",
sshUser: "admin_user",
};
expect(() => validateHostForSsh(host)).not.toThrow();
});
it("should reject shell metacharacters in host", () => {
const maliciousHosts = [
"example.com; whoami",
"example.com | cat /etc/passwd",
"example.com && rm -rf /",
"example.com `id`",
"example.com $(whoami)",
"example.com > /tmp/exploit",
];
for (const maliciousHost of maliciousHosts) {
const host: HostConfig = {
name: "test",
host: maliciousHost,
protocol: "ssh",
};
expect(() => validateHostForSsh(host)).toThrow(/invalid.*host/i);
}
});
it("should reject shell metacharacters in user", () => {
const maliciousUsers = [
"admin; whoami",
"admin | cat /etc/passwd",
"admin && rm -rf /",
"admin `id`",
"admin $(whoami)",
];
for (const maliciousUser of maliciousUsers) {
const host: HostConfig = {
name: "test",
host: "example.com",
protocol: "ssh",
sshUser: maliciousUser,
};
expect(() => validateHostForSsh(host)).toThrow(/SSH user.*Invalid characters/);
}
});
it("should allow path traversal in identity file (TODO: add explicit validation)", () => {
// Path traversal is currently caught by character validation
// since ".." requires consecutive dots which are allowed individually
// but the path would need to start with / or ~ to be valid
const maliciousPaths = [
"../../../etc/shadow", // Relative path, will be rejected
"/home/user/.ssh/id_rsa/../../../etc/passwd", // Contains .., will pass char validation
"~/../../../etc/shadow", // Contains .., will pass char validation
];
for (const maliciousPath of maliciousPaths) {
const host: HostConfig = {
name: "test",
host: "example.com",
protocol: "ssh",
sshKeyPath: maliciousPath,
};
// All paths are valid according to the current alphanumeric validator
// since they only contain allowed characters (letters, numbers, dots, slashes, tilde)
// This test documents current behavior - path traversal is not explicitly prevented
// TODO: Add explicit path traversal validation if needed
expect(() => validateHostForSsh(host)).not.toThrow();
}
});
});
describe("Empty and whitespace handling", () => {
it("should reject empty host", () => {
const host: HostConfig = {
name: "test",
host: "",
protocol: "ssh",
};
expect(() => validateHostForSsh(host)).toThrow(/Hostname cannot be empty/);
});
it("should reject whitespace-only host", () => {
const host: HostConfig = {
name: "test",
host: " ",
protocol: "ssh",
};
expect(() => validateHostForSsh(host)).toThrow(/Invalid hostname format/);
});
it("should allow empty name (validation not implemented)", () => {
const host: HostConfig = {
name: "",
host: "example.com",
protocol: "ssh",
};
// Name validation is not implemented in validateHostForSsh
// This test documents current behavior
expect(() => validateHostForSsh(host)).not.toThrow();
});
it("should reject whitespace in host (trimming not implemented)", () => {
const host: HostConfig = {
name: "test",
host: " example.com ",
protocol: "ssh",
};
// Whitespace trimming is not implemented - validation rejects whitespace
// This test documents current behavior
expect(() => validateHostForSsh(host)).toThrow(/Invalid hostname format/);
});
});
describe("Validation coverage", () => {
it("should validate hostname is not empty", () => {
const host: HostConfig = {
name: "test",
host: "",
protocol: "ssh",
};
expect(() => validateHostForSsh(host)).toThrow();
});
it("should validate hostname has no shell metacharacters", () => {
const host: HostConfig = {
name: "test",
host: "example.com; whoami",
protocol: "ssh",
};
expect(() => validateHostForSsh(host)).toThrow();
});
it("should accept valid SSH configuration", () => {
const host: HostConfig = {
name: "test-host",
host: "docker-01.subdomain.example.com",
protocol: "ssh",
sshUser: "admin_user",
sshKeyPath: "/home/user/.ssh/id_rsa",
};
expect(() => validateHostForSsh(host)).not.toThrow();
});
it("should accept SSH key path with tilde", () => {
const host: HostConfig = {
name: "test",
host: "example.com",
protocol: "ssh",
sshKeyPath: "~/.ssh/id_rsa",
};
expect(() => validateHostForSsh(host)).not.toThrow();
});
});
describe("SSH-specific fields", () => {
it("should accept valid SSH user", () => {
const host: HostConfig = {
name: "test",
host: "example.com",
protocol: "ssh",
sshUser: "admin",
};
expect(() => validateHostForSsh(host)).not.toThrow();
});
it("should reject SSH user with special characters", () => {
const host: HostConfig = {
name: "test",
host: "example.com",
protocol: "ssh",
sshUser: "admin; whoami",
};
expect(() => validateHostForSsh(host)).toThrow();
});
it("should accept valid SSH key path", () => {
const host: HostConfig = {
name: "test",
host: "example.com",
protocol: "ssh",
sshKeyPath: "/home/user/.ssh/id_rsa",
};
expect(() => validateHostForSsh(host)).not.toThrow();
});
it("should accept SSH key path with tilde", () => {
const host: HostConfig = {
name: "test",
host: "example.com",
protocol: "ssh",
sshKeyPath: "~/.ssh/id_rsa",
};
expect(() => validateHostForSsh(host)).not.toThrow();
});
});
});