diff --git a/docs/changelog.rst b/docs/changelog.rst index 51bc001b0f5..24b594a0085 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -54,6 +54,9 @@ Detailed list of changes - Fix re-using an image id for an animated image for a still image causing a crash (:iss:`6244`) +- kitty +open: Ask for permission before executing script files that are not marked as executable. This prevents accidental execution + of script files via MIME type association from programs that unconditionally "open" attachments/downloaded files via MIME type associations. + 0.28.1 [2023-04-21] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/kittens/ask/choices.go b/kittens/ask/choices.go index 0aa7d529977..370d4aae91b 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 087c9b47471..c41d71c5cf6 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 b2d3a713106..b712ce95903 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 00000000000..d938c2b0746 --- /dev/null +++ b/tools/cmd/tool/confirm_and_run_shebang.go @@ -0,0 +1,48 @@ +// License: GPLv3 Copyright: 2023, Kovid Goyal, + +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 1b51d4ea018..c20d8cbe8ad 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) + }, + }) }