Any time you send commands and data down a single channel, user input that's intended to be data can be misinterpreted as a command. For example, if your program wants to:
run("program --option '{user_input}' > file")
to save some input to a file, and the user's input is: '; bad_command #
then when run() sends that string to the shell, the shell will run: program --option '';
bad_command #' > file
Most languages have something like a safe_exec() that separates the shape of the command from the values of the options, executing "program" with the options and the user_input in the arguments array as data. Skipping the shell step, which would just be building an exec call anyway, removes the opportunity for users to confuse it into doing something else.The list-based API alternative they recommend might look like this:
safe_exec(["program", "--option", user_input], stdout="file")
and it would always exec "program" with argv[1] == "--option" && argv[2] == user_input. If the user_input happens to be: '; bad_command #
...well, then, the user can enjoy the contents of their file.