Search code examples
rdplyrtidyverser-environmentr-glue

R glue with limited set of functions allowed in pattern?


I am using glue() to format strings. Ideally I would like to give the user the option to provide his own, potentially complex formatting patterns. These will usually be distributed as part of a yaml config file, which also contains many other settings.

library(glue)
df <- tibble(a = c(70,80,90,4,5), conversionunit = c(60,60,60,1,1))
pattern <- "{a/conversionunit} minutes" # loaded from user's config file
df <- df %>% mutate(output = glue(pattern))

# more complex alternative
pattern <- "{round(a/conversionunit, digits=2)} minutes" # loaded from user's config file
df <- df %>% mutate(output = glue(pattern))

However, there is a security risk since glue statements may execute arbitrary code. The example here below is benign of course. The risk is developed because it will be likely that users download and use complex config files without studying them in detail, and a bad actor may distribute evil config files.

pattern <- "{shell('echo 1', intern = TRUE)} {a}"
df <- df %>% mutate(output = glue(pattern))

I am aware of glue_safe however this is more restrictive than I want. Ideally I want to provide a list of allowed functions

safe_fun <- list(`*` = `*`, `/` = `/`, "round" = round) %>% as.environment() # etc

and allow only the use of those specified ones. Is there any way to do this?


Solution

  • Define the environment to hold the data and functions and set its parent to emptyenv().

    library(glue)
    library(tibble) # lst
    
    safe_fun <- lst(`*`, `/`, round)
    safe_env <- list2env(c(df, safe_fun), parent = emptyenv())
    
    # test 1
    glue("{a/conversionunit} minutes", .envir = safe_env)
    ## 1.16666666666667 minutes
    ## 1.33333333333333 minutes
    ## 1.5 minutes
    ## 4 minutes
    ## 5 minutes
    
    # test 2
    glue("{sqrt(a)/conversionunit} minutes", .envir = safe_env)
    ## Error in sqrt(a) : could not find function "sqrt"