Skip to content

Commit 488327a

Browse files
committed
add test coverage for path traversal security functions
1 parent a76a219 commit 488327a

File tree

3 files changed

+401
-129
lines changed

3 files changed

+401
-129
lines changed

TESTING.md

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,27 +7,29 @@ Complete testing for `kubectl-rexec`.
77
### Running Tests
88

99
```bash
10-
cd plugin
11-
go test -v
12-
13-
cd rexec/server
14-
go test -v
10+
go test ./rexec/server
11+
go test ./plugin
1512

1613
go test -v -run TestParseFileSpec
1714
```
1815

19-
#### Plugin Tests (`plugin/`)
16+
## Plugin Tests (`plugin/`)
2017

2118
| Test | Description |
2219
|------|-------------|
2320
| `TestParseFileSpec` | Parses `pod:/path` and `ns/pod:/path` |
2421
| `TestValidateLocalDestination` | Validates local path exists |
2522
| `TestExtractTarSingleFile` | Extracts single file from tar |
2623
| `TestExtractTarDirectory` | Extracts directory from tar |
27-
| `TestExtractTarSymlinkSkipped` | Security: symlinks are skipped with warning |
24+
| `TestExtractTarLinkTypesSkipped` | Security: symlinks and hard links are skipped with warning |
25+
| `TestExtractTarPathTraversal` | Security: path traversal attempts are blocked |
2826
| `TestExtractTarValidDoubleDotFilename` | Valid filenames like `file..txt` are allowed |
27+
| `TestExtractTarRenameDirectory` | Extracts directory with different name |
2928
| `TestRunWithArgsValidation` | Rejects upload, pod-to-pod |
3029
| `TestValidateCopySpecs` | Validates copy specs |
30+
| `TestComputeSafeTarget` | Security: validates tar entry paths and targets |
31+
| `TestProcessTarEntry` | Tests individual tar entry processing |
32+
| `TestProcessTarEntryUnsupportedTypes` | Security: unsupported tar types are skipped with warning |
3133

3234
#### Server Tests (`rexec/server/`)
3335

plugin/cp.go

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -154,12 +154,9 @@ func validateCopySpecs(src, dest *fileSpec) error {
154154
func validateLocalDestination(destPath string) error {
155155
destPath = filepath.Clean(destPath)
156156

157-
info, err := os.Stat(destPath)
157+
_, err := os.Stat(destPath)
158158
if err == nil {
159-
if info.IsDir() {
160-
return nil
161-
}
162-
return nil // existing file, will overwrite
159+
return nil // existing file or directory
163160
}
164161

165162
parentDir := filepath.Dir(destPath)
@@ -293,12 +290,6 @@ func (o *CopyOptions) extractTar(reader io.Reader, destPath, srcBase string) err
293290
destInfo, statErr := os.Stat(destPath)
294291
destIsDir := statErr == nil && destInfo.IsDir()
295292

296-
// Compute absolute paths for security validation
297-
destPathAbs, err := filepath.Abs(destPath)
298-
if err != nil {
299-
return fmt.Errorf("invalid destination path: %v", err)
300-
}
301-
302293
var baseDir string
303294
if destIsDir {
304295
baseDir = destPath
@@ -321,7 +312,7 @@ func (o *CopyOptions) extractTar(reader io.Reader, destPath, srcBase string) err
321312
}
322313

323314
// Security: validate and compute safe target path
324-
targetAbs, err := computeSafeTarget(header.Name, destPath, destPathAbs, baseAbs, srcBase, destIsDir)
315+
targetAbs, err := computeSafeTarget(header.Name, destPath, baseAbs, srcBase, destIsDir)
325316
if err != nil {
326317
return err
327318
}
@@ -359,13 +350,18 @@ func (o *CopyOptions) processTarEntry(header *tar.Header, tarReader *tar.Reader,
359350
case tar.TypeSymlink:
360351
//nolint:errcheck
361352
_, _ = fmt.Fprintf(o.IOStreams.ErrOut, "Warning: skipping symlink %s -> %s (symlinks not supported for security)\n", header.Name, header.Linkname)
353+
case tar.TypeLink:
354+
//nolint:errcheck
355+
_, _ = fmt.Fprintf(o.IOStreams.ErrOut, "Warning: skipping hard link %s -> %s (hard links not supported for security)\n", header.Name, header.Linkname)
356+
default:
357+
//nolint:errcheck
358+
_, _ = fmt.Fprintf(o.IOStreams.ErrOut, "Warning: skipping unsupported tar entry %s (type %d)\n", header.Name, header.Typeflag)
362359
}
363360
return nil
364361
}
365362

366363
// computeSafeTarget validates the tar entry name and computes a safe absolute target path.
367-
func computeSafeTarget(name, destPath, destPathAbs, baseAbs, srcBase string, destIsDir bool) (string, error) {
368-
364+
func computeSafeTarget(name, destPath, baseAbs, srcBase string, destIsDir bool) (string, error) {
369365
cleanName := path.Clean(name)
370366

371367
if cleanName == ".." || strings.HasPrefix(cleanName, "../") || path.IsAbs(cleanName) {
@@ -393,7 +389,7 @@ func computeSafeTarget(name, destPath, destPathAbs, baseAbs, srcBase string, des
393389
return "", fmt.Errorf(errPathTraversal, name)
394390
}
395391

396-
if strings.HasPrefix(rel, "..") {
392+
if rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) {
397393
return "", fmt.Errorf(errPathTraversal, name)
398394
}
399395

0 commit comments

Comments
 (0)