Home > Software engineering >  Limit what code can be executed when evaluating (eval()) user input for patchwork
Limit what code can be executed when evaluating (eval()) user input for patchwork

Time:10-20

I have implemented Patchwork into my Shiny Application, through which users can easily arrange multiple plots. Since the Syntax of Patchwork is that easy, I want to display a textInput to the user, through which they can issue patchwork commands as "A B" or "(A | B) / C". In my code, this looks like this: eval(parse(text = input$patchwork_text))

This however poses a massive security risk, as any code can be executed by the user. Is there a good way to ensure, that the textInput is only used as intended? I don't want to give up the flexibility that it gives. Would something work to limit the input to a few symbols ( , /, |, (, ) ) and single, uppercase characters? I'm looking forward to any feedback :)

CodePudding user response:

Of course it is never a good idea to let users run code in a shiny session, but this is what I would do to check that only operations on objects that you want to allow are performed.

Note that this is just a start, please expand this function.

I was not sure, whether the names of the plot objects are given or not. If they are created on the fly then you would need to grab them and pass the names as strings into the allowed_nms argument. Otherwise we can just default to LETTERS as @Mikko points out in the comments.

In general I would advise to not work with strings (and regexs), but turn the string into a call object with str2lang and then we can use all.vars() and other functions to inspect the call.

check_call <- function(inp_string, allowed_nms = LETTERS) {
  
  allowed_fns <- c(" ", "-", "/", "(", "|", "&")
  
  inp_call <- str2lang(inp_string)
  
  all_nms <- all.vars(inp_call)
  all_sym <- all.names(inp_call)
  all_fns <- setdiff(all_sym, all_nms)
  
  if (any(!all_fns %in% allowed_fns)) {
    stop("This operation is not allowed")
  }
  
  if (!is.null(allowed_nms) && any(!all_nms %in% allowed_nms)) {
    stop("Not allowed object name.")
  }
}

check_call("(A | B) / C")

check_call("(A | B) / z")
#> Error in check_call("(A | B) / z"): Not allowed object name.

check_call("system('rm -rf /')", allowed_nms = c("A", "B"))
#> Error in check_call("system('rm -rf /')", allowed_nms = c("A", "B")): This operation is not allowed

Created on 2022-10-20 by the reprex package (v2.0.1)

  • Related