Refactor installation and runner scripts for enhanced usability and security
- Updated the installation script to clarify that production wrappers do not include fixed test secrets, improving user guidance for local testing. - Modified the jiggablend-manager and jiggablend-runner scripts to remove fixed test configurations, emphasizing the use of local test credentials via `make init-test`. - Enhanced error handling in the runner script to ensure that an API key is provided, improving security and user feedback. - Added new flags and options for the runner, including sandboxing capabilities and GPU ray tracing control, enhancing flexibility for users. - Improved README documentation to reflect changes in usage and configuration, ensuring users have clear instructions for setup and execution.
This commit is contained in:
@@ -80,10 +80,54 @@ func (s *Storage) JobPath(jobID int64) string {
|
||||
return filepath.Join(s.basePath, "jobs", fmt.Sprintf("%d", jobID))
|
||||
}
|
||||
|
||||
// SanitizeFilename returns a safe basename for client-supplied names.
|
||||
// Rejects empty, ".", "..", and root-like basenames.
|
||||
func SanitizeFilename(name string) (string, error) {
|
||||
cleaned := filepath.Clean(strings.ReplaceAll(name, "\\", "/"))
|
||||
base := filepath.Base(cleaned)
|
||||
// filepath.Base("..") is ".." ; Clean("/../..") style inputs can yield "/" or "."
|
||||
if base == "" || base == "." || base == ".." || base == "/" || base == string(os.PathSeparator) {
|
||||
return "", fmt.Errorf("invalid filename: %q", name)
|
||||
}
|
||||
// Reject if the cleaned path still has parent references after basenaming was skipped
|
||||
if strings.Contains(cleaned, "..") && base == cleaned {
|
||||
return "", fmt.Errorf("invalid filename: %q", name)
|
||||
}
|
||||
return base, nil
|
||||
}
|
||||
|
||||
// SafePathUnderRoot joins root and a relative path and ensures the result stays under root.
|
||||
func SafePathUnderRoot(root, rel string) (string, error) {
|
||||
if rel == "" {
|
||||
return "", fmt.Errorf("empty path")
|
||||
}
|
||||
// Normalize to forward slashes then clean
|
||||
rel = filepath.ToSlash(rel)
|
||||
if filepath.IsAbs(rel) || strings.HasPrefix(rel, "/") {
|
||||
return "", fmt.Errorf("absolute path not allowed: %q", rel)
|
||||
}
|
||||
cleanRel := filepath.Clean(rel)
|
||||
if cleanRel == ".." || strings.HasPrefix(cleanRel, ".."+string(os.PathSeparator)) {
|
||||
return "", fmt.Errorf("path escapes root: %q", rel)
|
||||
}
|
||||
cleanRoot := filepath.Clean(root)
|
||||
full := filepath.Join(cleanRoot, cleanRel)
|
||||
cleanFull := filepath.Clean(full)
|
||||
sep := string(os.PathSeparator)
|
||||
if cleanFull != cleanRoot && !strings.HasPrefix(cleanFull, cleanRoot+sep) {
|
||||
return "", fmt.Errorf("path escapes root: %q", rel)
|
||||
}
|
||||
return cleanFull, nil
|
||||
}
|
||||
|
||||
// SaveUpload saves an uploaded file
|
||||
func (s *Storage) SaveUpload(jobID int64, filename string, reader io.Reader) (string, error) {
|
||||
// Sanitize filename to prevent path traversal
|
||||
filename = filepath.Base(filename)
|
||||
var err error
|
||||
filename, err = SanitizeFilename(filename)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
jobPath := s.JobPath(jobID)
|
||||
if err := os.MkdirAll(jobPath, 0755); err != nil {
|
||||
@@ -106,12 +150,26 @@ func (s *Storage) SaveUpload(jobID int64, filename string, reader io.Reader) (st
|
||||
|
||||
// SaveOutput saves an output file
|
||||
func (s *Storage) SaveOutput(jobID int64, filename string, reader io.Reader) (string, error) {
|
||||
// Sanitize filename to prevent path traversal (parity with SaveUpload)
|
||||
var err error
|
||||
filename, err = SanitizeFilename(filename)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
outputPath := filepath.Join(s.outputsPath(), fmt.Sprintf("%d", jobID))
|
||||
if err := os.MkdirAll(outputPath, 0755); err != nil {
|
||||
return "", fmt.Errorf("failed to create output directory: %w", err)
|
||||
}
|
||||
|
||||
filePath := filepath.Join(outputPath, filename)
|
||||
// Defense in depth: ensure resolved path stays under output dir
|
||||
if resolved, err := SafePathUnderRoot(outputPath, filename); err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
filePath = resolved
|
||||
}
|
||||
|
||||
file, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create file: %w", err)
|
||||
|
||||
@@ -66,6 +66,51 @@ func TestSaveOutput(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSaveOutput_PathTraversal(t *testing.T) {
|
||||
s := setupStorage(t)
|
||||
path, err := s.SaveOutput(42, "../../etc/passwd", strings.NewReader("evil"))
|
||||
if err != nil {
|
||||
t.Fatalf("SaveOutput: %v", err)
|
||||
}
|
||||
outRoot := filepath.Join(s.BasePath(), "outputs", "42")
|
||||
if !strings.HasPrefix(path, outRoot) {
|
||||
t.Errorf("saved file %q escaped output directory %q", path, outRoot)
|
||||
}
|
||||
if filepath.Base(path) != "passwd" {
|
||||
t.Errorf("expected basename passwd, got %q", filepath.Base(path))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSanitizeFilename(t *testing.T) {
|
||||
got, err := SanitizeFilename("../../etc/passwd")
|
||||
if err != nil {
|
||||
t.Fatalf("SanitizeFilename: %v", err)
|
||||
}
|
||||
if got != "passwd" {
|
||||
t.Fatalf("got %q want passwd", got)
|
||||
}
|
||||
if _, err := SanitizeFilename(".."); err == nil {
|
||||
t.Fatal("expected error for ..")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSafePathUnderRoot(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
ok, err := SafePathUnderRoot(root, "subdir/file.blend")
|
||||
if err != nil {
|
||||
t.Fatalf("SafePathUnderRoot: %v", err)
|
||||
}
|
||||
if !strings.HasPrefix(ok, root) {
|
||||
t.Fatalf("path %q not under root %q", ok, root)
|
||||
}
|
||||
if _, err := SafePathUnderRoot(root, "../escape.blend"); err == nil {
|
||||
t.Fatal("expected escape to fail")
|
||||
}
|
||||
if _, err := SafePathUnderRoot(root, "/abs.blend"); err == nil {
|
||||
t.Fatal("expected absolute path to fail")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetFile(t *testing.T) {
|
||||
s := setupStorage(t)
|
||||
savedPath, err := s.SaveUpload(1, "readme.txt", strings.NewReader("hello"))
|
||||
|
||||
Reference in New Issue
Block a user