@@ -19,8 +19,8 @@ import (
1919
2020// Version information - set by build flags
2121var (
22- version = "dev"
23- buildTime = "unknown"
22+ version = "dev"
23+ date = "unknown"
2424)
2525
2626// osExit is a variable for testing - allows overriding os.Exit
8383 if [ -n "$cd_path" ]; then
8484 cd "$cd_path"
8585 elif [ -n "$exec_cmd" ]; then
86+ # Security note: EXEC commands are only used for virtualenv activation
87+ # and paths are quoted by the Go binary to prevent injection
8688 eval "$exec_cmd"
8789 fi
8890 else
@@ -139,6 +141,10 @@ func resolveCommandAlias(cmd string) string {
139141}
140142
141143// printErrorAndExit prints an error message and exits with status 1
144+ // NOTE: This function is used throughout the codebase for consistent error handling.
145+ // Future refactoring should move towards returning errors from command handlers
146+ // and handling exits centrally in main(), but this pattern is maintained for
147+ // backward compatibility and to minimize changes.
142148func printErrorAndExit (format string , args ... interface {}) {
143149 fmt .Fprintf (os .Stderr , "wt: " + format + "\n " , args ... )
144150 osExit (1 )
@@ -230,8 +236,7 @@ func handleListCommand(args []string) {
230236 }
231237
232238 if err := worktree .List (); err != nil {
233- fmt .Fprintf (os .Stderr , "wt: %v\n " , err )
234- osExit (1 )
239+ printErrorAndExit ("%v" , err )
235240 }
236241}
237242
@@ -263,21 +268,18 @@ func handleRemoveCommand(args []string) {
263268 // Resolve target with fuzzy matching
264269 branches , branchErr := worktree .GetAvailableBranches ()
265270 if branchErr != nil {
266- fmt .Fprintf (os .Stderr , "wt: %v\n " , branchErr )
267- osExit (1 )
271+ printErrorAndExit ("%v" , branchErr )
268272 }
269273
270274 resolvedTarget , resolveErr := worktree .ResolveBranchName (target , branches )
271275 if resolveErr != nil {
272- fmt .Fprintf (os .Stderr , "wt: %v\n " , resolveErr )
273- osExit (1 )
276+ printErrorAndExit ("%v" , resolveErr )
274277 }
275278 target = resolvedTarget
276279 }
277280
278281 if err := worktree .Remove (target ); err != nil {
279- fmt .Fprintf (os .Stderr , "wt: %v\n " , err )
280- osExit (1 )
282+ printErrorAndExit ("%v" , err )
281283 }
282284}
283285
@@ -426,9 +428,6 @@ func handleEnvCopyCommand(args []string) {
426428 return
427429 }
428430
429- // Show deprecation warning
430- fmt .Fprintf (os .Stderr , "Warning: 'wt env-copy' is deprecated. Use 'wt env sync' instead.\n " )
431-
432431 // Parse flags
433432 var useFuzzy bool
434433 var target string
@@ -635,8 +634,8 @@ func handleVersionCommand(args []string) {
635634 versionStr = "dev"
636635 }
637636
638- if buildTime != "" && buildTime != "unknown" {
639- fmt .Printf ("wt version %s (built %s)\n " , versionStr , buildTime )
637+ if date != "" && date != "unknown" {
638+ fmt .Printf ("wt version %s (built %s)\n " , versionStr , date )
640639 } else {
641640 fmt .Printf ("wt version %s\n " , versionStr )
642641 }
@@ -699,6 +698,13 @@ func handleVirtualenvCommand(navCmd *config.NavigationCommand, configMgr *config
699698
700699 venvPath := filepath .Join (repo , venvName )
701700
701+ // Validate that venvPath is within the repository
702+ absRepo , _ := filepath .Abs (repo )
703+ absVenvPath , _ := filepath .Abs (venvPath )
704+ if ! strings .HasPrefix (absVenvPath , absRepo ) {
705+ printErrorAndExit ("invalid virtualenv path: must be within repository" )
706+ }
707+
702708 switch navCmd .Target {
703709 case "activate" :
704710 // Check if virtualenv exists
@@ -709,7 +715,8 @@ func handleVirtualenvCommand(navCmd *config.NavigationCommand, configMgr *config
709715 osExit (1 )
710716 }
711717 // Output EXEC command to activate virtualenv
712- fmt .Printf ("EXEC:source %s" , activateScript )
718+ // Use printf with %q to properly quote the path for shell safety
719+ fmt .Printf ("EXEC:source %q" , activateScript )
713720
714721 case "create" :
715722 // Check if virtualenv already exists
0 commit comments