feat: auto-generate upload dest path, add upload confirmation check

- --upload-dest is now optional; defaults to C:\Windows\Temp\<UUID> (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 <carter-falconops@users.noreply.github.com>
This commit is contained in:
Cursor Agent
2026-03-03 17:57:15 +00:00
parent e16a230302
commit 870817161e
5 changed files with 43 additions and 5 deletions
+2 -4
View File
@@ -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)
+3
View File
@@ -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,
+7
View File
@@ -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
+7 -1
View File
@@ -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)
+24
View File
@@ -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
}