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)
+ },
+ })
}