R glue with limited set of functions allowed in pattern?

234 Views Asked by At

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?

2

There are 2 best solutions below

0
On BEST ANSWER

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"
0
On

Building on this previous answer:

library(glue)
library(dplyr)

df <- tibble(a = c(70,80,90,4,5), conversionunit = c(60,60,60,1,1))

allowed <- c("/")

# Create new empty environment
safe_env <- new.env(parent = emptyenv())

# assign allowed functions & objects
lapply(allowed,function(f) assign(f,get(f),safe_env))
list2env(df,safe_env)

pattern <- "{a/conversionunit} minutes" # loaded from user's config file

df %>% mutate(output = glue(pattern,.envir=safe_env))
#> # A tibble: 5 x 3
#>       a conversionunit output                  
#>   <dbl>          <dbl> <glue>                  
#> 1    70             60 1.16666666666667 minutes
#> 2    80             60 1.33333333333333 minutes
#> 3    90             60 1.5 minutes             
#> 4     4              1 4 minutes               
#> 5     5              1 5 minutes