From 870817161eb9eb635ccf569223e12a8d55880776 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 3 Mar 2026 17:57:15 +0000 Subject: [PATCH] feat: auto-generate upload dest path, add upload confirmation check - --upload-dest is now optional; defaults to C:\Windows\Temp\ (same pattern as --out remote path generation) - After upload, confirm file exists via SMB stat and log path + size - Add --no-upload-confirm flag to skip the confirmation check - Add UploadConfirmer optional interface in io.go - Add ConfirmUpload method on FileStager (stats remote file, logs details) Co-authored-by: Carter --- cmd/args.go | 6 ++---- cmd/root.go | 3 +++ pkg/goexec/io.go | 7 +++++++ pkg/goexec/method.go | 8 +++++++- pkg/goexec/smb/input.go | 24 ++++++++++++++++++++++++ 5 files changed, 43 insertions(+), 5 deletions(-) diff --git a/cmd/args.go b/cmd/args.go index ce7c046..20508ee 100644 --- a/cmd/args.go +++ b/cmd/args.go @@ -51,7 +51,8 @@ func registerExecutionFlags(fs *pflag.FlagSet) { func registerExecutionUploadFlags(fs *pflag.FlagSet) { fs.StringVar(&uploadSource, "upload", "", "Upload local `file` to remote filesystem") - fs.StringVar(&uploadDest, "upload-dest", "", "Remote destination `path` for uploaded file") + fs.StringVar(&uploadDest, "upload-dest", "", "Remote destination `path` for uploaded file (default: random temp path)") + fs.BoolVar(&exec.Upload.NoConfirm, "no-upload-confirm", false, "Skip upload confirmation check") } func registerExecutionOutputFlags(fs *pflag.FlagSet) { @@ -185,9 +186,6 @@ func argsUpload(methods ...string) func(cmd *cobra.Command, args []string) error return args(append(as, func(*cobra.Command, []string) (err error) { if uploadSource != "" { - if uploadDest == "" { - return fmt.Errorf("--upload-dest is required when --upload is set") - } exec.Upload.Reader, err = os.Open(uploadSource) if err != nil { return fmt.Errorf("open upload file: %w", err) diff --git a/cmd/root.go b/cmd/root.go index 6aaa178..6315736 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -185,6 +185,9 @@ Authors: FalconOps LLC (@FalconOpsLLC), } if uploadSource != "" { + if uploadDest == "" { + uploadDest = `C:\Windows\Temp\` + uuid.NewString() + } exec.Upload.RemotePath = uploadDest exec.Upload.Provider = &smb.FileStager{ Client: &smbClient, diff --git a/pkg/goexec/io.go b/pkg/goexec/io.go index 604c8fb..15c998f 100644 --- a/pkg/goexec/io.go +++ b/pkg/goexec/io.go @@ -35,11 +35,18 @@ type ExecutionOutput struct { } type ExecutionUpload struct { + NoConfirm bool RemotePath string Provider InputProvider Reader io.ReadCloser } +// UploadConfirmer is an optional interface that InputProvider implementations +// can satisfy to confirm a file was successfully uploaded. +type UploadConfirmer interface { + ConfirmUpload(ctx context.Context) error +} + type ExecutionInput struct { StageFile io.ReadCloser Executable string diff --git a/pkg/goexec/method.go b/pkg/goexec/method.go index 2d9eb0c..437fd17 100644 --- a/pkg/goexec/method.go +++ b/pkg/goexec/method.go @@ -124,7 +124,13 @@ func ExecuteCleanMethod(ctx context.Context, module CleanExecutionMethod, execIO log.Error().Err(err).Msg("Upload failed") return fmt.Errorf("upload: %w", err) } - log.Info().Msg("Upload succeeded") + if !execIO.Upload.NoConfirm { + if confirmer, ok := execIO.Upload.Provider.(UploadConfirmer); ok { + if err = confirmer.ConfirmUpload(ctx); err != nil { + log.Warn().Err(err).Msg("Upload confirmation failed") + } + } + } } // Execute (only if a command/executable was provided) diff --git a/pkg/goexec/smb/input.go b/pkg/goexec/smb/input.go index e9f866e..c02bed7 100644 --- a/pkg/goexec/smb/input.go +++ b/pkg/goexec/smb/input.go @@ -9,6 +9,7 @@ import ( "strings" "github.com/FalconOpsLLC/goexec/pkg/goexec" + "github.com/rs/zerolog" ) type FileStager struct { @@ -61,3 +62,26 @@ func (o *FileStager) Upload(ctx context.Context, reader io.Reader) (err error) { return } + +// ConfirmUpload checks that the uploaded file exists on the remote filesystem +// and logs the file path and size. The share must already be mounted from a +// prior Upload call. +func (o *FileStager) ConfirmUpload(ctx context.Context) error { + log := zerolog.Ctx(ctx) + + if o.Client.mount == nil { + return fmt.Errorf("share not mounted") + } + + info, err := o.Client.mount.Stat(o.relativePath) + if err != nil { + return fmt.Errorf("stat remote file: %w", err) + } + + log.Info(). + Str("path", o.File). + Int64("size", info.Size()). + Msg("Upload confirmed") + + return nil +}