Confine local file installation paths

This commit is contained in:
2026-08-10 17:59:29 +00:00
parent 59f3fe3d1d
commit 18ddf00d3d
20 changed files with 694 additions and 153 deletions

View File

@@ -3,7 +3,7 @@ package app
import (
"context"
"fmt"
"os"
"io"
"path/filepath"
"strings"
@@ -98,27 +98,35 @@ func executeRestoreDownloadAction(
return executeRestoreAudioAction(ctx, cfg, safeLocalPath, action, store)
}
tmpPath, err := downloadObjectToSiblingTemp(ctx, store, action.RemoteKey, safeLocalPath)
if err := fileops.EnsureWorkspaceDirectory(filepath.Dir(safeLocalPath)); err != nil {
return fmt.Errorf("create destination directory: %w", err)
}
temporary, err := fileops.DownloadToSiblingTemp(safeLocalPath, func(destination io.Writer) error {
return storage.DownloadTo(ctx, store, action.RemoteKey, destination)
})
if err != nil {
return fmt.Errorf("download to temp file: %w", err)
}
removeTmp := true
defer func() {
if removeTmp {
_ = os.Remove(tmpPath)
}
}()
defer func() { _ = temporary.Cleanup() }()
if action.LocalRelativePath == config.PathManifestFile {
if err := validateRestoredManifest(ctx, cfg, current, tmpPath); err != nil {
file, err := temporary.Open()
if err != nil {
return fmt.Errorf("open restored manifest: %w", err)
}
err = validateRestoredManifest(ctx, cfg, current, file)
closeErr := file.Close()
if err != nil {
return err
}
if closeErr != nil {
return fmt.Errorf("close restored manifest: %w", closeErr)
}
}
if err := fileops.InstallDownloadedTempFile(tmpPath, safeLocalPath, fileops.WorkspaceFileMode); err != nil {
if err := temporary.Install(filepath.Base(safeLocalPath), fileops.WorkspaceFileMode); err != nil {
return fmt.Errorf("install file atomically: %w", err)
}
removeTmp = false
return nil
}
@@ -155,36 +163,9 @@ func executeRestoreAudioAction(
return nil
}
func downloadObjectToSiblingTemp(ctx context.Context, store storage.ObjectStore, remoteKey, destPath string) (string, error) {
if strings.TrimSpace(destPath) == "" {
return "", fmt.Errorf("destination path is required")
}
dir := filepath.Dir(destPath)
if err := fileops.EnsureWorkspaceDirectory(dir); err != nil {
return "", fmt.Errorf("create destination directory: %w", err)
}
base := filepath.Base(destPath)
tmp, err := os.CreateTemp(dir, "."+base+".restore-*.tmp")
if err != nil {
return "", fmt.Errorf("create temp file: %w", err)
}
tmpPath := tmp.Name()
if err := tmp.Close(); err != nil {
_ = os.Remove(tmpPath)
return "", fmt.Errorf("close temp file: %w", err)
}
if err := store.Download(ctx, remoteKey, tmpPath); err != nil {
_ = os.Remove(tmpPath)
return "", err
}
return tmpPath, nil
}
func validateRestoredManifest(ctx context.Context, cfg *config.Config, current *RemoteCurrentState, path string) error {
func validateRestoredManifest(ctx context.Context, cfg *config.Config, current *RemoteCurrentState, source io.Reader) error {
manifestStore := &manifest.LocalStore{}
m, err := manifestStore.Load(ctx, path)
m, err := manifestStore.LoadReader(ctx, source)
if err != nil {
return fmt.Errorf("validate manifest decode: %w", err)
}

View File

@@ -5,6 +5,7 @@ import (
"context"
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
"strings"
@@ -581,6 +582,19 @@ func (s *stagedManifestDownloadStore) Download(ctx context.Context, key, localPa
return s.delegate.Download(ctx, key, localPath)
}
func (s *stagedManifestDownloadStore) DownloadTo(ctx context.Context, key string, destination io.Writer) error {
if strings.TrimSpace(key) == strings.TrimSpace(s.manifestKey) {
s.manifestReads++
payload := s.secondManifest
if s.manifestReads <= 1 {
payload = s.firstManifest
}
_, err := destination.Write(payload)
return err
}
return storage.DownloadTo(ctx, s.delegate, key, destination)
}
func (s *stagedManifestDownloadStore) Upload(ctx context.Context, localPath, key string, opts storage.UploadOptions) (storage.ObjectInfo, error) {
return s.delegate.Upload(ctx, localPath, key, opts)
}