Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1e7c4e162e |
@@ -0,0 +1,331 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// validateWorkspaceID tests
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
func TestValidateWorkspaceID_Valid(t *testing.T) {
|
||||
valid := []string{
|
||||
"550e8400-e29b-41d4-a716-446655440000",
|
||||
"f47ac10b-58cc-4372-a567-0e02b2c3d479",
|
||||
"00000000-0000-0000-0000-000000000000",
|
||||
"123e4567-e89b-12d3-a456-426614174000",
|
||||
}
|
||||
for _, id := range valid {
|
||||
if err := validateWorkspaceID(id); err != nil {
|
||||
t.Errorf("valid UUID %q: unexpected error: %v", id, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateWorkspaceID_Invalid(t *testing.T) {
|
||||
invalid := []string{
|
||||
"",
|
||||
"not-a-uuid",
|
||||
"550e8400-e29b-41d4-a716", // too short
|
||||
"550e8400-e29b-41d4-a716-44665544000", // too short (missing digit)
|
||||
"550e8400-e29b-41d4-a716-4466554400000", // too long
|
||||
"550e8400e29b41d4a716446655440000", // no dashes
|
||||
"550e8400-e29b-41d4-a716-44665544000g", // invalid hex char
|
||||
"../../etc/passwd",
|
||||
"../../../secret",
|
||||
"-1",
|
||||
"0",
|
||||
"workspace-123",
|
||||
}
|
||||
for _, id := range invalid {
|
||||
if err := validateWorkspaceID(id); err == nil {
|
||||
t.Errorf("invalid ID %q: expected error, got nil", id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// validateWorkspaceFields tests
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
// yamlSpecialChars is the set banned by validateWorkspaceFields.
|
||||
const yamlSpecialChars = "{}[]|>*&!"
|
||||
|
||||
func TestValidateWorkspaceFields_Valid(t *testing.T) {
|
||||
err := validateWorkspaceFields("my-workspace", "backend", "claude-3-5-sonnet", "docker")
|
||||
if err != nil {
|
||||
t.Errorf("valid fields: unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateWorkspaceFields_Empty(t *testing.T) {
|
||||
// Empty strings are valid (all-zero-length passes the checks).
|
||||
err := validateWorkspaceFields("", "", "", "")
|
||||
if err != nil {
|
||||
t.Errorf("empty fields: unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateWorkspaceFields_NameNewline(t *testing.T) {
|
||||
for _, nl := range []string{"\n", "\r", "\r\n"} {
|
||||
err := validateWorkspaceFields("bad\nname", "", "", "")
|
||||
if err == nil {
|
||||
t.Errorf("name with CR: expected error, got nil")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateWorkspaceFields_RoleNewline(t *testing.T) {
|
||||
err := validateWorkspaceFields("", "bad\rrole", "", "")
|
||||
if err == nil {
|
||||
t.Errorf("role with CR: expected error, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateWorkspaceFields_NameYAMLSpecialChars(t *testing.T) {
|
||||
for _, ch := range strings.Split(yamlSpecialChars, "") {
|
||||
name := "name" + ch
|
||||
err := validateWorkspaceFields(name, "", "", "")
|
||||
if err == nil {
|
||||
t.Errorf("name with YAML special char %q: expected error, got nil", ch)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateWorkspaceFields_RoleYAMLSpecialChars(t *testing.T) {
|
||||
for _, ch := range strings.Split(yamlSpecialChars, "") {
|
||||
role := "role" + ch
|
||||
err := validateWorkspaceFields("", role, "", "")
|
||||
if err == nil {
|
||||
t.Errorf("role with YAML special char %q: expected error, got nil", ch)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Name: 256 chars should fail; 255 should pass.
|
||||
func TestValidateWorkspaceFields_NameLength(t *testing.T) {
|
||||
long := strings.Repeat("a", 256)
|
||||
err := validateWorkspaceFields(long, "", "", "")
|
||||
if err == nil {
|
||||
t.Errorf("name 256 chars: expected error, got nil")
|
||||
}
|
||||
err = validateWorkspaceFields(strings.Repeat("a", 255), "", "", "")
|
||||
if err != nil {
|
||||
t.Errorf("name 255 chars: unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Role: 1001 chars should fail; 1000 should pass.
|
||||
func TestValidateWorkspaceFields_RoleLength(t *testing.T) {
|
||||
long := strings.Repeat("a", 1001)
|
||||
err := validateWorkspaceFields("", long, "", "")
|
||||
if err == nil {
|
||||
t.Errorf("role 1001 chars: expected error, got nil")
|
||||
}
|
||||
err = validateWorkspaceFields("", strings.Repeat("a", 1000), "", "")
|
||||
if err != nil {
|
||||
t.Errorf("role 1000 chars: unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Model: 101 chars should fail; 100 should pass.
|
||||
func TestValidateWorkspaceFields_ModelLength(t *testing.T) {
|
||||
long := strings.Repeat("a", 101)
|
||||
err := validateWorkspaceFields("", "", long, "")
|
||||
if err == nil {
|
||||
t.Errorf("model 101 chars: expected error, got nil")
|
||||
}
|
||||
err = validateWorkspaceFields("", "", strings.Repeat("a", 100), "")
|
||||
if err != nil {
|
||||
t.Errorf("model 100 chars: unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Runtime: 101 chars should fail; 100 should pass.
|
||||
func TestValidateWorkspaceFields_RuntimeLength(t *testing.T) {
|
||||
long := strings.Repeat("a", 101)
|
||||
err := validateWorkspaceFields("", "", "", long)
|
||||
if err == nil {
|
||||
t.Errorf("runtime 101 chars: expected error, got nil")
|
||||
}
|
||||
err = validateWorkspaceFields("", "", "", strings.Repeat("a", 100))
|
||||
if err != nil {
|
||||
t.Errorf("runtime 100 chars: unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Newlines in model and runtime should also be rejected.
|
||||
func TestValidateWorkspaceFields_ModelNewline(t *testing.T) {
|
||||
err := validateWorkspaceFields("", "", "model\nname", "")
|
||||
if err == nil {
|
||||
t.Errorf("model with newline: expected error, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateWorkspaceFields_RuntimeNewline(t *testing.T) {
|
||||
err := validateWorkspaceFields("", "", "", "docker\n")
|
||||
if err == nil {
|
||||
t.Errorf("runtime with newline: expected error, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
// Valid punctuation does not trigger YAML special char rejection.
|
||||
func TestValidateWorkspaceFields_NameAllowedPunctuation(t *testing.T) {
|
||||
valid := []string{
|
||||
"my-workspace",
|
||||
"workspace.1",
|
||||
"workspace_1",
|
||||
"workspace@prod",
|
||||
"workspace (1)",
|
||||
"workspace:backend",
|
||||
"workspace+prod",
|
||||
"workspace=1",
|
||||
"workspace?yes",
|
||||
"workspace&test",
|
||||
}
|
||||
for _, name := range valid {
|
||||
err := validateWorkspaceFields(name, "", "", "")
|
||||
if err != nil {
|
||||
t.Errorf("name %q: unexpected error: %v", name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// validateWorkspaceDir tests
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
func TestValidateWorkspaceDir_ValidAbsolute(t *testing.T) {
|
||||
valid := []string{
|
||||
"/home/user/workspaces/my-workspace",
|
||||
"/opt/molecule/workspaces/ws1",
|
||||
"/var/data/workspaces",
|
||||
"/Users/test/workspace",
|
||||
"/workspace/123",
|
||||
"/a",
|
||||
}
|
||||
for _, dir := range valid {
|
||||
err := validateWorkspaceDir(dir)
|
||||
if err != nil {
|
||||
t.Errorf("valid path %q: unexpected error: %v", dir, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateWorkspaceDir_RelativePath(t *testing.T) {
|
||||
invalid := []string{
|
||||
"relative/path",
|
||||
"./workspace",
|
||||
"../workspace",
|
||||
"workspace",
|
||||
"",
|
||||
"./../etc/passwd",
|
||||
}
|
||||
for _, dir := range invalid {
|
||||
err := validateWorkspaceDir(dir)
|
||||
if err == nil {
|
||||
t.Errorf("relative path %q: expected error, got nil", dir)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateWorkspaceDir_DoubleDot(t *testing.T) {
|
||||
invalid := []string{
|
||||
"/home/user/../etc/passwd",
|
||||
"/workspace/../../root",
|
||||
"/opt/../opt/../etc",
|
||||
"/home/workspace/..",
|
||||
"/a/b/../../c",
|
||||
}
|
||||
for _, dir := range invalid {
|
||||
err := validateWorkspaceDir(dir)
|
||||
if err == nil {
|
||||
t.Errorf("path with .. %q: expected error, got nil", dir)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateWorkspaceDir_SystemPaths(t *testing.T) {
|
||||
systemPaths := []string{
|
||||
"/etc",
|
||||
"/etc/",
|
||||
"/etc/passwd",
|
||||
"/var",
|
||||
"/var/log",
|
||||
"/proc",
|
||||
"/proc/self",
|
||||
"/sys",
|
||||
"/sys/kernel",
|
||||
"/dev",
|
||||
"/dev/null",
|
||||
"/boot",
|
||||
"/boot/vmlinuz",
|
||||
"/sbin",
|
||||
"/sbin/init",
|
||||
"/bin",
|
||||
"/bin/sh",
|
||||
"/lib",
|
||||
"/lib64",
|
||||
"/usr",
|
||||
"/usr/bin",
|
||||
"/usr/local",
|
||||
"/usr/local/bin",
|
||||
}
|
||||
for _, dir := range systemPaths {
|
||||
err := validateWorkspaceDir(dir)
|
||||
if err == nil {
|
||||
t.Errorf("system path %q: expected error, got nil", dir)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateWorkspaceDir_NonSystemUnderSystem(t *testing.T) {
|
||||
// Paths that START WITH a system prefix are also rejected.
|
||||
underSystem := []string{
|
||||
"/etc/something",
|
||||
"/var/log/molecule",
|
||||
"/usr/local/share",
|
||||
"/usr/share",
|
||||
}
|
||||
for _, dir := range underSystem {
|
||||
err := validateWorkspaceDir(dir)
|
||||
if err == nil {
|
||||
t.Errorf("path under system root %q: expected error, got nil", dir)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateWorkspaceDir_NotUnderSystem(t *testing.T) {
|
||||
// Paths that merely contain a system prefix as a segment, but don't start with it, are fine.
|
||||
valid := []string{
|
||||
"/home/etc/something",
|
||||
"/opt/var/workspace",
|
||||
"/molecule/etc/passwd",
|
||||
"/varuser/local",
|
||||
"/usrenv/bin",
|
||||
}
|
||||
for _, dir := range valid {
|
||||
err := validateWorkspaceDir(dir)
|
||||
if err != nil {
|
||||
t.Errorf("path %q: unexpected error: %v", dir, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Paths that resolve (after Clean) to a system path are also rejected.
|
||||
func TestValidateWorkspaceDir_PathTraversalResolvesToSystem(t *testing.T) {
|
||||
// These are all technically relative paths that would be rejected as non-absolute,
|
||||
// but the ".." cases above cover the traversal semantics. Here we verify that
|
||||
// even paths that look absolute but have ".." segments are caught.
|
||||
invalid := []string{
|
||||
"/home/user/../../etc",
|
||||
"/a/b/../../../etc",
|
||||
}
|
||||
for _, dir := range invalid {
|
||||
err := validateWorkspaceDir(dir)
|
||||
if err == nil {
|
||||
t.Errorf("path %q: expected error, got nil", dir)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user