> I finally figured out it was because a stored procedure does exactly what the grandparent post says: It treats all inputs as data with no possibility to run as code.
This isn't well defined. Take this pseudocode stored procedure (OK, it's a python function):
def retrieve_relevant_data(user_input):
if user_input == 1:
return BACKING_STORE[5]
elif user_input == 2:
perform_side_effects()
return BACKING_STORE[1]
else:
return "Go away."
You can provide any input to that. You
could think of this as a function which "treats all input as data with no possibility to run as code" (it never calls eval!). But you could also usefully think of this as defining a tiny virtual machine with opcodes 1 and 2. If you think of it that way, you'll be forced to conclude that it does run user input as code, but the difference is in how you're labeling the function, not in what the function does.
The security gain from a stored procedure, on this analysis, is not that it won't run user input as code. It will! The security gain comes from replacing the full capability of the database ("run code on your local machine") with the smaller, whitelisted set of capabilities defined in the stored procedure.