(t *testing.T)
| 440 | } |
| 441 | |
| 442 | func TestValidatePathWithinBase(t *testing.T) { |
| 443 | base := t.TempDir() |
| 444 | |
| 445 | tests := []struct { |
| 446 | name string |
| 447 | candidate string |
| 448 | shouldErr bool |
| 449 | }{ |
| 450 | { |
| 451 | name: "file directly inside base", |
| 452 | candidate: filepath.Join(base, "file.txt"), |
| 453 | shouldErr: false, |
| 454 | }, |
| 455 | { |
| 456 | name: "file in subdirectory", |
| 457 | candidate: filepath.Join(base, "sub", "file.txt"), |
| 458 | shouldErr: false, |
| 459 | }, |
| 460 | { |
| 461 | name: "base directory itself", |
| 462 | candidate: base, |
| 463 | shouldErr: false, |
| 464 | }, |
| 465 | { |
| 466 | name: "path traversal with ..", |
| 467 | candidate: filepath.Join(base, "..", "escape.txt"), |
| 468 | shouldErr: true, |
| 469 | }, |
| 470 | { |
| 471 | name: "deeply nested traversal", |
| 472 | candidate: filepath.Join(base, "a", "b", "..", "..", "..", "escape.txt"), |
| 473 | shouldErr: true, |
| 474 | }, |
| 475 | { |
| 476 | name: "absolute path outside base", |
| 477 | candidate: "/etc/passwd", |
| 478 | shouldErr: true, |
| 479 | }, |
| 480 | } |
| 481 | |
| 482 | for _, tt := range tests { |
| 483 | t.Run(tt.name, func(t *testing.T) { |
| 484 | err := ValidatePathWithinBase(base, tt.candidate) |
| 485 | if tt.shouldErr { |
| 486 | require.Error(t, err, "ValidatePathWithinBase should reject path %q relative to %q", tt.candidate, base) |
| 487 | assert.Contains(t, err.Error(), "escapes base directory", "Error should describe the escape") |
| 488 | } else { |
| 489 | require.NoError(t, err, "ValidatePathWithinBase should accept path %q within %q", tt.candidate, base) |
| 490 | } |
| 491 | }) |
| 492 | } |
| 493 | |
| 494 | t.Run("symlink escape", func(t *testing.T) { |
| 495 | // Create a real file outside the base directory. |
| 496 | outsideFile, err := os.CreateTemp("", "validatepathwithinbase-outside-*") |
| 497 | require.NoError(t, err, "failed to create outside file") |
| 498 | t.Cleanup(func() { _ = os.Remove(outsideFile.Name()) }) |
| 499 | outsidePath := outsideFile.Name() |
nothing calls this directly
no test coverage detected