summaryrefslogtreecommitdiff
path: root/internal/tui/eventstream/export.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/tui/eventstream/export.go')
-rw-r--r--internal/tui/eventstream/export.go21
1 files changed, 18 insertions, 3 deletions
diff --git a/internal/tui/eventstream/export.go b/internal/tui/eventstream/export.go
index 155a551..1aa4313 100644
--- a/internal/tui/eventstream/export.go
+++ b/internal/tui/eventstream/export.go
@@ -192,15 +192,30 @@ func exportRowsToCSV(rows []StreamEvent, exportDir, filename string) (string, er
return absPath, nil
}
+// ensureCSVFilename validates and normalises a user-supplied export filename.
+// It strips any directory components (preventing path traversal outside
+// exportDir) and rejects names that resolve to "." or "..". A ".csv"
+// extension is appended when the caller omits it.
func ensureCSVFilename(name string) (string, error) {
clean := strings.TrimSpace(name)
if clean == "" {
return "", errors.New("filename cannot be empty")
}
- if strings.HasSuffix(strings.ToLower(clean), ".csv") {
- return clean, nil
+
+ // Strip all directory components so that inputs such as
+ // "../../etc/passwd" or "/absolute/path.csv" cannot escape exportDir.
+ base := filepath.Base(clean)
+
+ // filepath.Base returns "." for empty/dot inputs and ".." for a raw ".."
+ // component — both are unusable as a plain filename.
+ if base == "." || base == ".." {
+ return "", errors.New("filename must not be a directory reference")
+ }
+
+ if strings.HasSuffix(strings.ToLower(base), ".csv") {
+ return base, nil
}
- return clean + ".csv", nil
+ return base + ".csv", nil
}
// ExportSnapshotToCSV exports a fresh filtered snapshot from the current source