feat: refactor export file writing to avoid Windows Defender false positives
- Replace direct file creation with atomic temp file + rename pattern - Add path validation to prevent writing to sensitive system directories - Set proper file permissions (0644) on exported files - Maintain backward compatibility and JSONL export functionality - Reduce ransomware heuristic triggers through safer file operations
This commit is contained in:
@@ -5,12 +5,46 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/steveyegge/beads/internal/types"
|
||||
)
|
||||
|
||||
// validateExportPath checks if the output path is safe to write to
|
||||
func validateExportPath(path string) error {
|
||||
// Get absolute path to normalize it
|
||||
absPath, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid path: %v", err)
|
||||
}
|
||||
|
||||
// Convert to lowercase for case-insensitive comparison on Windows
|
||||
absPathLower := strings.ToLower(absPath)
|
||||
|
||||
// List of sensitive system directories to avoid
|
||||
sensitiveDirs := []string{
|
||||
"c:\\windows",
|
||||
"c:\\program files",
|
||||
"c:\\program files (x86)",
|
||||
"c:\\programdata",
|
||||
"c:\\system volume information",
|
||||
"c:\\$recycle.bin",
|
||||
"c:\\boot",
|
||||
"c:\\recovery",
|
||||
}
|
||||
|
||||
for _, dir := range sensitiveDirs {
|
||||
if strings.HasPrefix(absPathLower, strings.ToLower(dir)) {
|
||||
return fmt.Errorf("cannot write to sensitive system directory: %s", dir)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var exportCmd = &cobra.Command{
|
||||
Use: "export",
|
||||
Short: "Export issues to JSONL format",
|
||||
@@ -60,18 +94,37 @@ Output to stdout by default, or use -o flag for file output.`,
|
||||
|
||||
// Open output
|
||||
out := os.Stdout
|
||||
var tempFile *os.File
|
||||
var tempPath string
|
||||
var finalPath string
|
||||
if output != "" {
|
||||
f, err := os.Create(output)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error creating output file: %v\n", err)
|
||||
// Validate output path before creating files
|
||||
if err := validateExportPath(output); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Create temporary file in same directory for atomic rename
|
||||
dir := filepath.Dir(output)
|
||||
base := filepath.Base(output)
|
||||
var err error
|
||||
tempFile, err = os.CreateTemp(dir, base+".tmp.*")
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error creating temporary file: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
tempPath = tempFile.Name()
|
||||
finalPath = output
|
||||
|
||||
// Ensure cleanup on failure
|
||||
defer func() {
|
||||
if err := f.Close(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Warning: failed to close output file: %v\n", err)
|
||||
if tempFile != nil {
|
||||
tempFile.Close()
|
||||
os.Remove(tempPath) // Clean up temp file if we haven't renamed it
|
||||
}
|
||||
}()
|
||||
out = f
|
||||
|
||||
out = tempFile
|
||||
}
|
||||
|
||||
// Write JSONL
|
||||
@@ -82,6 +135,27 @@ Output to stdout by default, or use -o flag for file output.`,
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// If writing to file, atomically replace the target file
|
||||
if tempFile != nil {
|
||||
// Close the temp file before renaming
|
||||
if err := tempFile.Close(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Warning: failed to close temporary file: %v\n", err)
|
||||
}
|
||||
tempFile = nil // Prevent cleanup
|
||||
|
||||
// Atomically replace the target file
|
||||
if err := os.Rename(tempPath, finalPath); err != nil {
|
||||
os.Remove(tempPath) // Clean up on failure
|
||||
fmt.Fprintf(os.Stderr, "Error replacing output file: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Set appropriate file permissions (0644: rw-r--r--)
|
||||
if err := os.Chmod(finalPath, 0644); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Warning: failed to set file permissions: %v\n", err)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user