Repository: gohugoio/hugo (language: go)

Issue:
common/hexec: Remove github.com/cli/safeexec
<https://pkg.go.dev/os/exec#hdr-Executables_in_the_current_directory>

> To avoid those security problems, as of Go 1.19, this package will not resolve a program using an implicit or explicit path entry relative to the current directory. 

Our go.mod requires Go 1.23 or later, so it seems like it would be safe to remove the `github.com/cli/safeexec` package.


Discussion / hints:

Fix patch:
diff --git a/README.md b/README.md
index a453c7320a3..e0b3f1567c8 100644
--- a/README.md
+++ b/README.md
@@ -189,7 +189,6 @@ github.com/bep/simplecobra="v0.5.0"
 github.com/bep/tmc="v0.5.1"
 github.com/cespare/xxhash/v2="v2.3.0"
 github.com/clbanning/mxj/v2="v2.7.0"
-github.com/cli/safeexec="v1.0.1"
 github.com/cpuguy83/go-md2man/v2="v2.0.4"
 github.com/disintegration/gift="v1.2.1"
 github.com/dlclark/regexp2="v1.11.5"
diff --git a/common/hexec/exec.go b/common/hexec/exec.go
index 1369e847cde..c3a6ebf571e 100644
--- a/common/hexec/exec.go
+++ b/common/hexec/exec.go
@@ -27,7 +27,6 @@ import (
 	"sync"
 
 	"github.com/bep/logg"
-	"github.com/cli/safeexec"
 	"github.com/gohugoio/hugo/common/loggers"
 	"github.com/gohugoio/hugo/common/maps"
 	"github.com/gohugoio/hugo/config"
@@ -113,18 +112,6 @@ func IsNotFound(err error) bool {
 	return errors.As(err, &notFoundErr)
 }
 
-// SafeCommand is a wrapper around os/exec Command which uses a LookPath
-// implementation that does not search in current directory before looking in PATH.
-// See https://github.com/cli/safeexec and the linked issues.
-func SafeCommand(name string, arg ...string) (*exec.Cmd, error) {
-	bin, err := safeexec.LookPath(name)
-	if err != nil {
-		return nil, err
-	}
-
-	return exec.Command(bin, arg...), nil
-}
-
 // Exec enforces a security policy for commands run via os/exec.
 type Exec struct {
 	sc         security.Config
@@ -197,7 +184,7 @@ func (e *Exec) Npx(name string, arg ...any) (Runner, error) {
 		tryFuncs := map[binaryLocation]tryFunc{
 			binaryLocationNodeModules: func() func(...any) (Runner, error) {
 				nodeBinFilename := filepath.Join(e.workingDir, nodeModulesBinPath, name)
-				_, err := safeexec.LookPath(nodeBinFilename)
+				_, err := exec.LookPath(nodeBinFilename)
 				if err != nil {
 					return nil
 				}
@@ -215,7 +202,7 @@ func (e *Exec) Npx(name string, arg ...any) (Runner, error) {
 				}
 			},
 			binaryLocationPath: func() func(...any) (Runner, error) {
-				if _, err := safeexec.LookPath(name); err != nil {
+				if _, err := exec.LookPath(name); err != nil {
 					return nil
 				}
 				return func(arg2 ...any) (Runner, error) {
@@ -346,7 +333,7 @@ func (c *commandeer) command(arg ...any) (*cmdWrapper, error) {
 		bin = c.fullyQualifiedName
 	} else {
 		var err error
-		bin, err = safeexec.LookPath(c.name)
+		bin, err = exec.LookPath(c.name)
 		if err != nil {
 			return nil, &NotFoundError{
 				name:   c.name,
@@ -384,7 +371,7 @@ func InPath(binaryName string) bool {
 	if strings.Contains(binaryName, "/") {
 		panic("binary name should not contain any slash")
 	}
-	_, err := safeexec.LookPath(binaryName)
+	_, err := exec.LookPath(binaryName)
 	return err == nil
 }
 
@@ -394,7 +381,7 @@ func LookPath(binaryName string) string {
 	if strings.Contains(binaryName, "/") {
 		panic("binary name should not contain any slash")
 	}
-	s, err := safeexec.LookPath(binaryName)
+	s, err := exec.LookPath(binaryName)
 	if err != nil {
 		return ""
 	}
diff --git a/go.mod b/go.mod
index 86a30f381a9..9e639d1d5cd 100644
--- a/go.mod
+++ b/go.mod
@@ -24,7 +24,6 @@ require (
 	github.com/bep/tmc v0.5.1
 	github.com/cespare/xxhash/v2 v2.3.0
 	github.com/clbanning/mxj/v2 v2.7.0
-	github.com/cli/safeexec v1.0.1
 	github.com/disintegration/gift v1.2.1
 	github.com/dustin/go-humanize v1.0.1
 	github.com/evanw/esbuild v0.24.2
@@ -120,6 +119,7 @@ require (
 	github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 // indirect
 	github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 // indirect
 	github.com/aws/smithy-go v1.22.2 // indirect
+	github.com/cli/safeexec v1.0.1 // indirect
 	github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
 	github.com/dlclark/regexp2 v1.11.4 // indirect
 	github.com/felixge/httpsnoop v1.0.4 // indirect
diff --git a/releaser/releaser.go b/releaser/releaser.go
index 4c3db2c1489..46cee1b1355 100644
--- a/releaser/releaser.go
+++ b/releaser/releaser.go
@@ -19,11 +19,11 @@ import (
 	"fmt"
 	"log"
 	"os"
+	"os/exec"
 	"path/filepath"
 	"regexp"
 	"strings"
 
-	"github.com/gohugoio/hugo/common/hexec"
 	"github.com/gohugoio/hugo/common/hugo"
 )
 
@@ -222,7 +222,7 @@ func (r *ReleaseHandler) replaceInFile(filename string, oldNew ...string) error
 }
 
 func git(args ...string) (string, error) {
-	cmd, _ := hexec.SafeCommand("git", args...)
+	cmd := exec.Command("git", args...)
 	out, err := cmd.CombinedOutput()
 	if err != nil {
 		return "", fmt.Errorf("git failed: %q: %q (%q)", err, out, args)
diff --git a/scripts/fork_go_templates/main.go b/scripts/fork_go_templates/main.go
index cd461043c1a..e4895c87ac5 100644
--- a/scripts/fork_go_templates/main.go
+++ b/scripts/fork_go_templates/main.go
@@ -4,12 +4,11 @@ import (
 	"fmt"
 	"log"
 	"os"
+	"os/exec"
 	"path/filepath"
 	"regexp"
 	"strings"
 
-	"github.com/gohugoio/hugo/common/hexec"
-
 	"github.com/gohugoio/hugo/common/hugio"
 
 	"github.com/spf13/afero"
@@ -208,7 +207,7 @@ func removeAll(expression, content string) string {
 }
 
 func rewrite(filename, rule string) {
-	cmf, _ := hexec.SafeCommand("gofmt", "-w", "-r", rule, filename)
+	cmf := exec.Command("gofmt", "-w", "-r", rule, filename)
 	out, err := cmf.CombinedOutput()
 	if err != nil {
 		log.Fatal("gofmt failed:", string(out))
@@ -217,7 +216,7 @@ func rewrite(filename, rule string) {
 
 func goimports(dir string) {
 	// Needs go install golang.org/x/tools/cmd/goimports@latest
-	cmf, _ := hexec.SafeCommand("goimports", "-w", dir)
+	cmf := exec.Command("goimports", "-w", dir)
 	out, err := cmf.CombinedOutput()
 	if err != nil {
 		log.Fatal("goimports failed:", string(out))
@@ -225,7 +224,7 @@ func goimports(dir string) {
 }
 
 func gofmt(dir string) {
-	cmf, _ := hexec.SafeCommand("gofmt", "-w", dir)
+	cmf := exec.Command("gofmt", "-w", dir)
 	out, err := cmf.CombinedOutput()
 	if err != nil {
 		log.Fatal("gofmt failed:", string(out))


Test patch: