Repository: mitsuhiko/minijinja (language: rust)

Issue:
minijinja-cli: add ability to scope include template path
Apologies if this is already supported and I've overlooked it.

With include enabled, it seems you can include templates from any path, for example, `../secrets.txt`.

Is there interest in providing the ability to scope the include path? For instance, only allowing includes from a specified directory, or from a specified directory downwards?



Discussion / hints:

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6a5626ee..4fbbc83f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,9 @@ All notable changes to MiniJinja are documented here.
 - `minijinja-cli` now supports an `-o` or `--output` parameter to write
   into a target file.  #405
 
+- `minijinja-cli` now accepts the `--safe-path` parameter to disallow
+  includes or extends from paths not explicitly allowlisted.  #432
+
 - Added support for `Error::display_debug_info` which displays just the
   debug info, same way as alternative display on the error does.  #420
 
diff --git a/minijinja-cli/src/cli.rs b/minijinja-cli/src/cli.rs
index 6744c5b3..24ad5aed 100644
--- a/minijinja-cli/src/cli.rs
+++ b/minijinja-cli/src/cli.rs
@@ -27,6 +27,9 @@ pub(super) fn make_command() -> Command {
             arg!(--strict "disallow undefined variables in templates"),
             arg!(--"no-include" "Disallow includes and extending"),
             arg!(--"no-newline" "Do not output a newline"),
+            arg!(--"safe-path" <PATH>... "Only allow includes from this path. Can be used multiple times.")
+                .conflicts_with("no-include")
+                .value_parser(value_parser!(PathBuf)),
             arg!(--env "Pass environment variables as ENV to the template"),
             arg!(-E --expr <EXPR> "Evaluates an expression instead"),
             arg!(--"expr-out" <MODE> "Sets the expression output mode")
diff --git a/minijinja-cli/src/main.rs b/minijinja-cli/src/main.rs
index a84cc251..38a0e047 100644
--- a/minijinja-cli/src/main.rs
+++ b/minijinja-cli/src/main.rs
@@ -165,6 +165,7 @@ fn create_env(
     matches: &ArgMatches,
     cwd: PathBuf,
     allowed_template: Option<String>,
+    safe_paths: Vec<PathBuf>,
     stdin_used_for_data: bool,
 ) -> Environment<'static> {
     let mut env = Environment::new();
@@ -243,6 +244,14 @@ fn create_env(
             return Ok(stdin.clone());
         }
 
+        let fs_name = Path::new(name);
+        if !safe_paths.is_empty() && !safe_paths.iter().any(|x| fs_name.starts_with(x)) {
+            return Err(MError::new(
+                ErrorKind::InvalidOperation,
+                "Cannot include template from non-trusted path",
+            ));
+        }
+
         match fs::read_to_string(name) {
             Ok(contents) => Ok(Some(contents)),
             Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
@@ -326,11 +335,16 @@ fn execute() -> Result<i32, Error> {
     } else {
         None
     };
+    let safe_paths = matches
+        .get_many::<PathBuf>("safe-path")
+        .unwrap_or_default()
+        .map(|x| x.canonicalize().unwrap_or_else(|_| x.clone()))
+        .collect::<Vec<_>>();
     let mut output = Output::new(matches.get_one::<PathBuf>("output").unwrap())?;
 
     let no_newline = matches.get_flag("no-newline");
 
-    let env = create_env(&matches, cwd, allowed_template, stdin_used);
+    let env = create_env(&matches, cwd, allowed_template, safe_paths, stdin_used);
 
     if let Some(expr) = matches.get_one::<String>("expr") {
         let rv = env.compile_expression(expr)?.eval(ctx)?;


Test patch: