Hello ...
I am using R shiny to develop mathematics APPS where I use the function eval() to convert string numbers from user to numeric inputs in R. For example, to accept the vector: v = (3, 4/11, pi/2, sqrt(13)), I use the textInput function then convert it to numeric values using .the following code.
_v = as.character(input$v)_
_v = strsplit(v,",")_
_v = v[[1]]_
_vv = v_
_vi = rep(0,times = length(v))_
_for (i in 1:length(vv)){_
_ vi[i] = eval(parse(text = vv[i])) # convert to numeric ***_
_}_
_v = vi_
My problem is that I read on Stackflow that eval() function should never be used if an R shiny app is to be hosted online. Is there a secure way to capture this input.
I also use the same to accept mathematical functions. For example, a user enters a function like exp(x) * sin(x) in the input field, then I convert it to an R function using the line below.
_f = function(x) {eval(parse(text = input$func))}_
Thank you for any help or suggestion.
1 Like
One idea that comes to mind is to interpret the R expression manually and impose some of your own restrictions. Here's an example:
library(rlang)
interpret <- function(expr_str,
max_length = 32,
whitelist = c("/", "*", "sqrt", "c")) {
safer_eval <- function(expr) {
if (rlang::is_call(expr)) {
fn_name <- rlang::call_name(expr)
if (!fn_name %in% whitelist) stop("Disallowed function: ", fn_name)
do.call(get(fn_name, baseenv()), Map(safer_eval, rlang::call_args(expr)))
} else if (rlang::is_syntactic_literal(expr)) {
expr
} else {
stop("Unknown expression: ", expr)
}
}
stopifnot(length(expr_str) < max_length)
safer_eval(rlang::parse_expr(expr_str))
}
# > interpret("c(2*3, 12)")
# [1] 6 12
# > interpret("c(2*3, 12, system('rm /tmp/foo'))")
# Error in (function (expr, env) : Disallowed function: system
- If the input string is longer than
max_length
, it throws an exception. This prevents people from submitting very large strings that could tie up your R process with parsing.
- Recognize only a subset of R functions. In particular, those named by
whitelist
that exist in base
. This prevents people from calling functions like base::system
. It's important that these functions don't accept quoted arguments that are evaluated separately, since that could defeat the whitelist approach.
- Anything other than a function call or a syntactic literal throws the
Unknown expression
exception, so variables are not resolved and can't be created.
Since functions can't be defined and loops can't be created, I don't think it would be possible to tie up R with a busy loop or infinite recursion, and so we don't need to impose a limit on running time. We wouldn't be able to do that anyway without forking a new R process, or without writing a more involved interpreter or VM that maintained its own code vector and argument stack.
One extension point you might consider is, instead of throwing Unknown expression
, interpret
could accept the input
object as a parameter, and you could resolve symbols to members of the input object. This way, your interpreted programs could participate in reactivity.
In the course of researching this I was directed to https://github.com/jeroen/RAppArmor by a co-worker, but it looks much more involved to set up, but is probably the "complete" answer, particularly if you have control over your hosting environment and are on Linux.
4 Likes
Thanks very much alandipert for the detailed and informative response. That solves my concern.
1 Like