Blob Blame History Raw
diff --git a/kittens/ask/choices.go b/kittens/ask/choices.go
index 0aa7d52997..370d4aae91 100644
--- a/kittens/ask/choices.go
+++ b/kittens/ask/choices.go
@@ -60,7 +60,7 @@ func extra_for(width, screen_width int) int {
 	return utils.Max(0, screen_width-width)/2 + 1
 }
 
-func choices(o *Options) (response string, err error) {
+func GetChoices(o *Options) (response string, err error) {
 	response = ""
 	lp, err := loop.New()
 	if err != nil {
diff --git a/kittens/ask/main.go b/kittens/ask/main.go
index 087c9b4747..c41d71c5cf 100644
--- a/kittens/ask/main.go
+++ b/kittens/ask/main.go
@@ -33,7 +33,7 @@ func main(_ *cli.Command, o *Options, args []string) (rc int, err error) {
 	}
 	switch o.Type {
 	case "yesno", "choices":
-		result.Response, err = choices(o)
+		result.Response, err = GetChoices(o)
 		if err != nil {
 			return 1, err
 		}
diff --git a/kitty/entry_points.py b/kitty/entry_points.py
index b2d3a71310..b712ce9590 100644
--- a/kitty/entry_points.py
+++ b/kitty/entry_points.py
@@ -94,6 +94,7 @@ def edit(args: List[str]) -> None:
 
 
 def shebang(args: List[str]) -> None:
+    from kitty.constants import kitten_exe
     script_path = args[1]
     cmd = args[2:]
     if cmd == ['__ext__']:
@@ -111,7 +112,7 @@ def shebang(args: List[str]) -> None:
                 cmd = line.split(' ')
             else:
                 cmd = line.split(' ', maxsplit=1)
-    os.execvp(cmd[0], cmd + [script_path])
+    os.execvp(kitten_exe(), ['kitten', '__confirm_and_run_shebang__'] + cmd + [script_path])
 
 
 def run_kitten(args: List[str]) -> None:
diff --git a/tools/cmd/tool/confirm_and_run_shebang.go b/tools/cmd/tool/confirm_and_run_shebang.go
new file mode 100644
index 0000000000..d938c2b074
--- /dev/null
+++ b/tools/cmd/tool/confirm_and_run_shebang.go
@@ -0,0 +1,48 @@
+// License: GPLv3 Copyright: 2023, Kovid Goyal, <kovid at kovidgoyal.net>
+
+package tool
+
+import (
+	"fmt"
+	"os"
+
+	"golang.org/x/sys/unix"
+
+	"kitty/kittens/ask"
+	"kitty/tools/cli/markup"
+	"kitty/tools/utils"
+)
+
+var _ = fmt.Print
+
+func ask_for_permission(script_path string) (allowed bool, err error) {
+	opts := &ask.Options{Type: "yesno", Default: "n"}
+
+	ctx := markup.New(true)
+	opts.Message = ctx.Prettify(fmt.Sprintf(
+		"Attempting to execute the script: :yellow:`%s`\nExecuting untrusted scripts can be dangerous. Proceed anyway?", script_path))
+	response, err := ask.GetChoices(opts)
+	return response == "y", err
+}
+
+func confirm_and_run_shebang(args []string) (rc int, err error) {
+	script_path := args[len(args)-1]
+	if unix.Access(script_path, unix.X_OK) != nil {
+		allowed, err := ask_for_permission(script_path)
+		if err != nil {
+			return 1, err
+		}
+		if !allowed {
+			return 1, fmt.Errorf("Execution permission refused by user")
+		}
+	}
+	exe := utils.FindExe(args[0])
+	if exe == "" {
+		return 1, fmt.Errorf("Failed to find the script interpreter: %s", args[0])
+	}
+	err = unix.Exec(exe, args, os.Environ())
+	if err != nil {
+		rc = 1
+	}
+	return
+}
diff --git a/tools/cmd/tool/main.go b/tools/cmd/tool/main.go
index 1b51d4ea01..c20d8cbe8a 100644
--- a/tools/cmd/tool/main.go
+++ b/tools/cmd/tool/main.go
@@ -67,4 +67,13 @@ func KittyToolEntryPoints(root *cli.Command) {
 			return
 		},
 	})
+	// __confirm_and_run_shebang__
+	root.AddSubCommand(&cli.Command{
+		Name:            "__confirm_and_run_shebang__",
+		Hidden:          true,
+		OnlyArgsAllowed: true,
+		Run: func(cmd *cli.Command, args []string) (rc int, err error) {
+			return confirm_and_run_shebang(args)
+		},
+	})
 }