From b194fd12f9b71354d21aa2e8aa24e8bb721cdf8f Mon Sep 17 00:00:00 2001 From: Ayoub Faouzi Date: Fri, 14 Aug 2026 16:20:04 +0100 Subject: [PATCH] feat: add --network and --country flags to rescan The rescan API accepts sandbox network settings, but the CLI never sent them. Add --network (default true) and --country (default US) to the rescan command and forward both to the /rescan endpoint, with test coverage for the new request body fields. --- cmd/rescan.go | 9 +++++++++ cmd/scanui.go | 12 ++++++++---- internal/webapi/files.go | 5 ++++- internal/webapi/files_test.go | 12 ++++++++++-- 4 files changed, 31 insertions(+), 7 deletions(-) diff --git a/cmd/rescan.go b/cmd/rescan.go index 8eaa952..8485590 100644 --- a/cmd/rescan.go +++ b/cmd/rescan.go @@ -17,6 +17,11 @@ import ( var sha256Re = regexp.MustCompile(`^[a-fA-F0-9]{64}$`) +var ( + rescanNetworkEnabled bool + rescanCountry string +) + func init() { reScanCmd.Flags().IntVar(¶llelFlag, "parallel", 1, "Number of files to rescan in parallel") @@ -26,6 +31,10 @@ func init() { "Detonation duration in seconds") reScanCmd.Flags().StringVarP(&osFlag, "os", "o", "windows-10-x64", "Preferred OS for detonation, choice(windows-7-x64 | windows-10-x64 | windows-11-x64)") + reScanCmd.Flags().BoolVar(&rescanNetworkEnabled, "network", true, + "Allow sandbox internet access") + reScanCmd.Flags().StringVar(&rescanCountry, "country", "US", + "Two-letter VPN exit country when network access is enabled") } // reScanFile re-scans a list of SHA256 with a TUI progress display. diff --git a/cmd/scanui.go b/cmd/scanui.go index 7abbf86..b713a31 100644 --- a/cmd/scanui.go +++ b/cmd/scanui.go @@ -123,7 +123,8 @@ func uploadFileCmd(index int, web webapi.Service, filename, token string) tea.Cm // Archive: rescan each unique child, not the container itself. children := uniqueDerivedFiles(file.DerivedFiles) for _, df := range children { - if err := web.Rescan(df.SHA256, token, osFlag, enableDetonationFlag, timeoutFlag); err != nil { + if err := web.Rescan(df.SHA256, token, osFlag, enableDetonationFlag, timeoutFlag, + rescanNetworkEnabled, rescanCountry); err != nil { return fileUploadedMsg{index: index, err: fmt.Errorf("rescan child %s: %w", df.SHA256[:12], err)} } } @@ -137,7 +138,8 @@ func uploadFileCmd(index int, web webapi.Service, filename, token string) tea.Cm } } - err = web.Rescan(sha256, token, osFlag, enableDetonationFlag, timeoutFlag) + err = web.Rescan(sha256, token, osFlag, enableDetonationFlag, timeoutFlag, + rescanNetworkEnabled, rescanCountry) if err != nil { return fileUploadedMsg{index: index, err: fmt.Errorf("rescan: %w", err)} } @@ -195,7 +197,8 @@ func rescanFileCmd(index int, web webapi.Service, sha256, token string) tea.Cmd if file.IsArchive && len(file.DerivedFiles) > 0 { children := uniqueDerivedFiles(file.DerivedFiles) for _, df := range children { - if err := web.Rescan(df.SHA256, token, osFlag, enableDetonationFlag, timeoutFlag); err != nil { + if err := web.Rescan(df.SHA256, token, osFlag, enableDetonationFlag, timeoutFlag, + rescanNetworkEnabled, rescanCountry); err != nil { return fileUploadedMsg{index: index, err: fmt.Errorf("rescan child %s: %w", df.SHA256[:12], err)} } } @@ -209,7 +212,8 @@ func rescanFileCmd(index int, web webapi.Service, sha256, token string) tea.Cmd } } - err := web.Rescan(sha256, token, osFlag, enableDetonationFlag, timeoutFlag) + err := web.Rescan(sha256, token, osFlag, enableDetonationFlag, timeoutFlag, + rescanNetworkEnabled, rescanCountry) if err != nil { return fileUploadedMsg{index: index, err: fmt.Errorf("rescan: %w", err)} } diff --git a/internal/webapi/files.go b/internal/webapi/files.go index 08fa665..cd6f0e0 100644 --- a/internal/webapi/files.go +++ b/internal/webapi/files.go @@ -166,13 +166,16 @@ func (s Service) Scan(filepath string, authToken, preferredOS string, enableDeto return &file, nil } -func (s Service) Rescan(sha256, authToken, preferredOS string, enableDetonation bool, timeout int) error { +func (s Service) Rescan(sha256, authToken, preferredOS string, enableDetonation bool, + timeout int, networkEnabled bool, country string) error { url := s.filesURL + sha256 + "/rescan" requestBody, err := json.Marshal(map[string]any{ "skip_detonation": !enableDetonation, "os": preferredOS, "timeout": timeout, + "network_enabled": networkEnabled, + "country": country, }) if err != nil { return err diff --git a/internal/webapi/files_test.go b/internal/webapi/files_test.go index 60e59f8..ac2ffcc 100644 --- a/internal/webapi/files_test.go +++ b/internal/webapi/files_test.go @@ -119,7 +119,8 @@ func TestRescan(t *testing.T) { defer srv.Close() svc := New(srv.URL) - if err := svc.Rescan(testSHA256, testAPIKey, "windows-7-x64", false, 60); err != nil { + if err := svc.Rescan(testSHA256, testAPIKey, "windows-7-x64", false, 60, + true, "US"); err != nil { t.Fatalf("Rescan() error = %v", err) } @@ -132,6 +133,12 @@ func TestRescan(t *testing.T) { if got := gotBody["timeout"]; got != float64(60) { t.Errorf("timeout = %v, want 60", got) } + if got := gotBody["network_enabled"]; got != true { + t.Errorf("network_enabled = %v, want true", got) + } + if got := gotBody["country"]; got != "US" { + t.Errorf("country = %v, want US", got) + } } func TestRescanError(t *testing.T) { @@ -142,7 +149,8 @@ func TestRescanError(t *testing.T) { defer srv.Close() svc := New(srv.URL) - err := svc.Rescan(testSHA256, testAPIKey, "windows-10-x64", false, 15) + err := svc.Rescan(testSHA256, testAPIKey, "windows-10-x64", false, 15, + false, "") if err == nil { t.Fatal("Rescan() expected error on HTTP 404, got nil") }