VYPR
Medium severity6.8NVD Advisory· Published Feb 27, 2025· Updated Apr 29, 2026

CVE-2025-1686

CVE-2025-1686

Description

Versions of the package io.pebbletemplates:pebble from 0 and before 4.1.0 are vulnerable to External Control of File Name or Path via the include tag. A high privileged attacker can access sensitive local files by crafting malicious notification templates that leverage this tag to include files like /etc/passwd or /proc/1/environ. Workaround This vulnerability can be mitigated by disabling the include macro in Pebble Templates: java new PebbleEngine.Builder() .registerExtensionCustomizer(new DisallowExtensionCustomizerBuilder() .disallowedTokenParserTags(List.of("include")) .build()) .build();

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
io.pebbletemplates:pebbleMaven
<= 3.2.3

Affected products

1

Patches

1
b3451c8f305a

CVE-2025-1686 (#715)

https://github.com/PebbleTemplates/pebbleEric BussieresDec 11, 2025via ghsa
18 files changed · +226 152
  • docs/pom.xml+1 1 modified
    @@ -5,7 +5,7 @@
       <parent>
         <groupId>io.pebbletemplates</groupId>
         <artifactId>pebble-project</artifactId>
    -    <version>4.0.1-SNAPSHOT</version>
    +    <version>4.1.0-SNAPSHOT</version>
       </parent>
     
       <artifactId>docs</artifactId>
    
  • docs/src/orchid/resources/changelog/v4_0_1.md+0 8 removed
    @@ -1,8 +0,0 @@
    ----
    -version: '4.0.1'
    ----
    -
    -- Use a default existing format of `yyyy-MM-dd'T'HH:mm:ssZ` when using date filter with a string (#677)
    -- NaN mus return false instead of throwing an exception (#695)
    -- Look for exact method / field match when doing reflection. Look for method get/is/has if none match
    -- Update some dependencies (#709)
    
  • docs/src/orchid/resources/changelog/v4_1_0.md+17 0 added
    @@ -0,0 +1,17 @@
    +---
    +version: '4.1.0'
    +---
    +
    +# BREAKING CHANGES
    +- Modify the `FileLoader` to use a mandatory sandboxed base directory parameter. (#715)
    +- If you do not provide a custom Loader, Pebble will now use only a `ClasspathLoader` by default, same as the spring autoconfiguration. (#715)
    +  Before that, it would have used an instance of the `DelegatingLoader` which consists of a `ClasspathLoader` and a `FileLoader` behind the scenes to find your templates.
    +
    +# New Features
    +- Use a default existing format of `yyyy-MM-dd'T'HH:mm:ssZ` when using date filter with a string (#677)
    +- Look for exact method / field match when doing reflection. Look for method get/is/has if none match
    +- Update some dependencies (#709)
    +
    +# Bug Fixes
    +- NaN must return false instead of throwing an exception (#695)
    +- [CVE-2025-1686](https://nvd.nist.gov/vuln/detail/CVE-2025-1686). 
    
  • docs/src/orchid/resources/wiki/guide/installation.md+5 6 modified
    @@ -61,18 +61,17 @@ finding your templates.
     
     Pebble ships with the following loader implementations:
     
    +- `DelegatingLoader`: Delegates responsibility to a collection of children loaders.
     - `ClasspathLoader`: Uses a classloader to search the current classpath.
    -- `FileLoader`:  Finds templates using a filesystem path.
    +- `FileLoader`:  Finds templates using a filesystem path. Must provide a mandatory absolute base path.
     - `ServletLoader`:  Uses a servlet context to find the template. This is the recommended loader for use within an
     application server but is not enabled by default.
     - `Servlet5Loader`:  Same as `ServletLoader`, but for Jakarta Servlet 5.0 or newer.
    -- `StringLoader`: Considers the name of the template to be the contents of the template.
    -- `DelegatingLoader`: Delegates responsibility to a collection of children loaders.
     - `MemoryLoader`: Loader that supports inheritance and doesn't require a filesystem. This is useful for applications
    +- `StringLoader`: Considers the name of the template to be the contents of the template. Should not be used in a production environment. It is primarily for testing and debugging. Many tags may not work when using this loader, such as "extends", "imports", etc.
       that retrieve templates from a database for example.
     
    -If you do not provide a custom Loader, Pebble will use an instance of the `DelegatingLoader` by default.
    -This delegating loader will use a `ClasspathLoader` and a `FileLoader` behind the scenes to find your templates.
    +If you do not provide a custom Loader, Pebble will use an instance of the `ClasspathLoader` by default.
     
     ## Pebble Engine Settings
     
    @@ -85,7 +84,7 @@ All the settings are set during the construction of the `PebbleEngine` object.
     | `tagCache` | An implementation of a ConcurrentMap cache that the Pebble engine will use for {{ anchor('cache tag', 'cache') }}. | Default implementation is `ConcurrentMapTagCache` and another implementation based on Caffeine is available (`CaffeineTagCache`) |
     | `defaultLocale` | The default locale which will be passed to each compiled template. The templates then use this locale for functions such as i18n, etc. A template can also be given a unique locale during evaluation.  | `Locale.getDefault()` |
     | `executorService` | An `ExecutorService` that allows the usage of some advanced multithreading features, such as the `parallel` tag. | `null` |
    -| `loader` | An implementation of the `Loader` interface which is used to find templates. | An implementation of the `DelegatingLoader` which uses a `ClasspathLoader` and a `FileLoader` behind the scenes. |
    +| `loader` | An implementation of the `Loader` interface which is used to find templates. | An implementation of the `ClasspathLoader` |
     | `strictVariables` | If set to true, Pebble will throw an exception if you try to access a variable or attribute that does not exist (or an attribute of a null variable). If set to false, your template will treat non-existing variables/attributes as null without ever skipping a beat. | `false` |
     | `methodAccessValidator` | Pebble provides two implementations. NoOpMethodAccessValidator which do nothing and BlacklistMethodAccessValidator which checks that the method being called is not blacklisted. | `BlacklistMethodAccessValidator` 
     | `literalDecimalTreatedAsInteger` | option for treating literal decimals as `int`. Otherwise it is `long`. | `false` |
    
  • pebble/pom.xml+1 1 modified
    @@ -3,7 +3,7 @@
       <parent>
         <groupId>io.pebbletemplates</groupId>
         <artifactId>pebble-project</artifactId>
    -    <version>4.0.1-SNAPSHOT</version>
    +    <version>4.1.0-SNAPSHOT</version>
       </parent>
     
       <artifactId>pebble</artifactId>
    
  • pebble-spring/pebble-legacy-spring-boot-starter/pom.xml+1 1 modified
    @@ -4,7 +4,7 @@
       <parent>
         <artifactId>pebble-spring</artifactId>
         <groupId>io.pebbletemplates</groupId>
    -    <version>4.0.1-SNAPSHOT</version>
    +    <version>4.1.0-SNAPSHOT</version>
       </parent>
     
       <artifactId>pebble-legacy-spring-boot-starter</artifactId>
    
  • pebble-spring/pebble-spring6/pom.xml+1 1 modified
    @@ -4,7 +4,7 @@
       <parent>
         <artifactId>pebble-spring</artifactId>
         <groupId>io.pebbletemplates</groupId>
    -    <version>4.0.1-SNAPSHOT</version>
    +    <version>4.1.0-SNAPSHOT</version>
       </parent>
     
       <artifactId>pebble-spring6</artifactId>
    
  • pebble-spring/pebble-spring7/pom.xml+1 1 modified
    @@ -4,7 +4,7 @@
       <parent>
         <artifactId>pebble-spring</artifactId>
         <groupId>io.pebbletemplates</groupId>
    -    <version>4.0.1-SNAPSHOT</version>
    +    <version>4.1.0-SNAPSHOT</version>
       </parent>
     
       <artifactId>pebble-spring7</artifactId>
    
  • pebble-spring/pebble-spring-boot-starter/pom.xml+1 1 modified
    @@ -4,7 +4,7 @@
       <parent>
         <artifactId>pebble-spring</artifactId>
         <groupId>io.pebbletemplates</groupId>
    -    <version>4.0.1-SNAPSHOT</version>
    +    <version>4.1.0-SNAPSHOT</version>
       </parent>
     
       <artifactId>pebble-spring-boot-starter</artifactId>
    
  • pebble-spring/pom.xml+1 1 modified
    @@ -4,7 +4,7 @@
       <parent>
         <groupId>io.pebbletemplates</groupId>
         <artifactId>pebble-project</artifactId>
    -    <version>4.0.1-SNAPSHOT</version>
    +    <version>4.1.0-SNAPSHOT</version>
       </parent>
     
       <artifactId>pebble-spring</artifactId>
    
  • pebble/src/main/java/io/pebbletemplates/pebble/loader/FileLoader.java+46 57 modified
    @@ -10,18 +10,12 @@
     
     import io.pebbletemplates.pebble.error.LoaderException;
     import io.pebbletemplates.pebble.utils.PathUtils;
    -
     import org.slf4j.Logger;
     import org.slf4j.LoggerFactory;
     
    -import java.io.BufferedReader;
    -import java.io.File;
    -import java.io.FileInputStream;
    -import java.io.FileNotFoundException;
    -import java.io.InputStream;
    -import java.io.InputStreamReader;
    -import java.io.Reader;
    -import java.io.UnsupportedEncodingException;
    +import java.io.*;
    +import java.nio.file.Path;
    +import java.nio.file.Paths;
     
     /**
      * This loader searches for a file located anywhere on the filesystem. It uses java.io.File to
    @@ -34,69 +28,35 @@ public class FileLoader implements Loader<String> {
       private static final Logger logger = LoggerFactory.getLogger(FileLoader.class);
     
       private String prefix;
    -
       private String suffix;
    -
       private String charset = "UTF-8";
     
    +  public FileLoader(String prefix) {
    +    this.setPrefix(prefix);
    +  }
    +
       @Override
       public Reader getReader(String templateName) {
    -    // try to load File
    -    InputStream is = null;
         File file = this.getFile(templateName);
    -    if (file.exists() && file.isFile()) {
    -      try {
    -        is = new FileInputStream(file);
    -      } catch (FileNotFoundException e) {
    -      }
    -    }
    -
    -    if (is == null) {
    -      throw new LoaderException(null,
    -          "Could not find template \"" + templateName + "\"");
    -    }
    -
         try {
    +      InputStream is = new FileInputStream(file);
           return new BufferedReader(new InputStreamReader(is, this.charset));
    +    } catch (FileNotFoundException e) {
    +      throw new LoaderException(e, String.format("Could not find template [prefix='%s', templateName='%s']", this.prefix, templateName));
         } catch (UnsupportedEncodingException e) {
    +      throw new LoaderException(e, String.format("Invalid charset '%s'", this.charset));
         }
    -
    -    return null;
       }
     
       private File getFile(String templateName) {
    -    // add the prefix and ensure the prefix ends with a separator character
    -    StringBuilder path = new StringBuilder();
    -    if (this.getPrefix() != null) {
    -
    -      path.append(this.getPrefix());
    -
    -      if (!this.getPrefix().endsWith(String.valueOf(File.separatorChar))) {
    -        path.append(File.separatorChar);
    -      }
    -    }
    -
         templateName = templateName + (this.getSuffix() == null ? "" : this.getSuffix());
    +    templateName = PathUtils.sanitize(templateName, File.separatorChar);
     
    -    logger.trace("Looking for template in {}{}.", path.toString(), templateName);
    +    Path path = Paths.get(this.getPrefix(), templateName);
    +    logger.trace("Looking for template in {}.", path);
     
    -    /*
    -     * if template name contains path segments, move those segments into the
    -     * path variable. The below technique needs to know the difference
    -     * between the path and file name.
    -     */
    -    String[] pathSegments = PathUtils.PATH_SEPARATOR_REGEX.split(templateName);
    -
    -    if (pathSegments.length > 1) {
    -      // file name is the last segment
    -      templateName = pathSegments[pathSegments.length - 1];
    -    }
    -    for (int i = 0; i < (pathSegments.length - 1); i++) {
    -      path.append(pathSegments[i]).append(File.separatorChar);
    -    }
    -
    -    // try to load File
    -    return new File(path.toString(), templateName);
    +    this.checkIfDirectoryTraversal(templateName);
    +    return path.toFile();
       }
     
       public String getSuffix() {
    @@ -114,7 +74,17 @@ public String getPrefix() {
     
       @Override
       public void setPrefix(String prefix) {
    -    this.prefix = prefix;
    +    if (prefix == null) {
    +      throw new LoaderException(null, "Prefix cannot be null");
    +    }
    +    String trimmedPrefix = prefix.trim();
    +    if (trimmedPrefix.isEmpty()) {
    +      throw new LoaderException(null, "Prefix cannot be empty");
    +    }
    +    if (!Paths.get(trimmedPrefix).isAbsolute()) {
    +      throw new LoaderException(null, "Prefix must be an absolute path");
    +    }
    +    this.prefix = trimmedPrefix;
       }
     
       public String getCharset() {
    @@ -140,4 +110,23 @@ public String createCacheKey(String templateName) {
       public boolean resourceExists(String templateName) {
         return this.getFile(templateName).exists();
       }
    +
    +  private void checkIfDirectoryTraversal(String templateName) {
    +    Path baseDirPath = Paths.get(prefix);
    +    Path userPath = Paths.get(templateName);
    +    if (userPath.isAbsolute()) {
    +      throw new LoaderException(null, String.format("templateName '%s' must be relative", templateName));
    +    }
    +
    +    // Join the two paths together, then normalize so that any ".." elements
    +    // in the userPath can remove parts of baseDirPath.
    +    // (e.g. "/foo/bar/baz" + "../attack" -> "/foo/bar/attack")
    +    Path resolvedPath = baseDirPath.resolve(userPath).normalize();
    +
    +    // Make sure the resulting path is still within the required directory.
    +    // (In the example above, "/foo/bar/attack" is not.)
    +    if (!resolvedPath.startsWith(baseDirPath)) {
    +      throw new LoaderException(null, String.format("template is not in the base directory path [baseDir='%s', templateName='%s']", this.prefix, templateName));
    +    }
    +  }
     }
    
  • pebble/src/main/java/io/pebbletemplates/pebble/PebbleEngine.java+11 19 modified
    @@ -9,45 +9,40 @@
     package io.pebbletemplates.pebble;
     
     
    +import io.pebbletemplates.pebble.attributes.methodaccess.BlacklistMethodAccessValidator;
    +import io.pebbletemplates.pebble.attributes.methodaccess.MethodAccessValidator;
     import io.pebbletemplates.pebble.cache.CacheKey;
     import io.pebbletemplates.pebble.cache.PebbleCache;
     import io.pebbletemplates.pebble.cache.tag.ConcurrentMapTagCache;
     import io.pebbletemplates.pebble.cache.tag.NoOpTagCache;
     import io.pebbletemplates.pebble.cache.template.ConcurrentMapTemplateCache;
     import io.pebbletemplates.pebble.cache.template.NoOpTemplateCache;
     import io.pebbletemplates.pebble.error.LoaderException;
    +import io.pebbletemplates.pebble.extension.*;
    +import io.pebbletemplates.pebble.extension.escaper.EscapingStrategy;
     import io.pebbletemplates.pebble.lexer.LexerImpl;
     import io.pebbletemplates.pebble.lexer.Syntax;
     import io.pebbletemplates.pebble.lexer.TokenStream;
    +import io.pebbletemplates.pebble.loader.ClasspathLoader;
    +import io.pebbletemplates.pebble.loader.Loader;
    +import io.pebbletemplates.pebble.loader.StringLoader;
     import io.pebbletemplates.pebble.node.RootNode;
     import io.pebbletemplates.pebble.parser.Parser;
     import io.pebbletemplates.pebble.parser.ParserImpl;
     import io.pebbletemplates.pebble.parser.ParserOptions;
    -import io.pebbletemplates.pebble.attributes.methodaccess.BlacklistMethodAccessValidator;
    -import io.pebbletemplates.pebble.attributes.methodaccess.MethodAccessValidator;
    -import io.pebbletemplates.pebble.extension.escaper.EscapingStrategy;
    -import io.pebbletemplates.pebble.loader.ClasspathLoader;
    -import io.pebbletemplates.pebble.loader.DelegatingLoader;
    -import io.pebbletemplates.pebble.loader.FileLoader;
    -import io.pebbletemplates.pebble.loader.Loader;
    -import io.pebbletemplates.pebble.loader.StringLoader;
    -import io.pebbletemplates.pebble.extension.*;
     import io.pebbletemplates.pebble.template.EvaluationOptions;
     import io.pebbletemplates.pebble.template.PebbleTemplate;
     import io.pebbletemplates.pebble.template.PebbleTemplateImpl;
    +import io.pebbletemplates.pebble.utils.TypeUtils;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
     
     import java.io.IOException;
     import java.io.Reader;
    -import java.util.ArrayList;
    -import java.util.List;
     import java.util.Locale;
     import java.util.concurrent.ExecutorService;
     import java.util.function.Function;
     
    -import io.pebbletemplates.pebble.utils.TypeUtils;
    -import org.slf4j.Logger;
    -import org.slf4j.LoggerFactory;
    -
     /**
      * The main class used for compiling templates. The PebbleEngine is responsible for delegating
      * responsibility to the lexer, parser, compiler, and template cache.
    @@ -584,10 +579,7 @@ public PebbleEngine build() {
     
           // default loader
           if (this.loader == null) {
    -        List<Loader<?>> defaultLoadingStrategies = new ArrayList<>();
    -        defaultLoadingStrategies.add(new ClasspathLoader());
    -        defaultLoadingStrategies.add(new FileLoader());
    -        this.loader = new DelegatingLoader(defaultLoadingStrategies);
    +        this.loader = new ClasspathLoader();
           }
     
           // default locale
    
  • pebble/src/main/java/io/pebbletemplates/pebble/utils/PathUtils.java+1 1 modified
    @@ -44,7 +44,7 @@ public static String resolveRelativePath(String relativePath, String anchorPath,
         return null;
       }
     
    -  private static String sanitize(String path, char expectedSeparator) {
    +  public static String sanitize(String path, char expectedSeparator) {
         return PATH_SEPARATOR_REGEX.matcher(path)
             .replaceAll(Matcher.quoteReplacement(String.valueOf(expectedSeparator)));
       }
    
  • pebble/src/test/java/io/pebbletemplates/pebble/FileLoaderTest.java+132 0 added
    @@ -0,0 +1,132 @@
    +package io.pebbletemplates.pebble;
    +
    +import io.pebbletemplates.pebble.error.LoaderException;
    +import io.pebbletemplates.pebble.error.PebbleException;
    +import io.pebbletemplates.pebble.loader.FileLoader;
    +import io.pebbletemplates.pebble.loader.Loader;
    +import io.pebbletemplates.pebble.template.PebbleTemplate;
    +import org.junit.jupiter.api.Test;
    +import org.junit.jupiter.params.ParameterizedTest;
    +import org.junit.jupiter.params.provider.ValueSource;
    +
    +import java.io.IOException;
    +import java.io.StringWriter;
    +import java.io.Writer;
    +import java.net.URISyntaxException;
    +import java.nio.file.Paths;
    +
    +import static org.junit.jupiter.api.Assertions.assertEquals;
    +import static org.junit.jupiter.api.Assertions.assertThrows;
    +
    +public class FileLoaderTest {
    +
    +  @Test
    +  void testFileLoaderPrefixNull() {
    +    assertThrows(LoaderException.class, () -> new FileLoader(null));
    +  }
    +
    +  @Test
    +  void testFileLoaderPrefixEmpty() {
    +    assertThrows(LoaderException.class, () -> new FileLoader(" "));
    +  }
    +
    +  @Test
    +  void testFileLoaderPrefixRelativePath() {
    +    assertThrows(LoaderException.class, () -> new FileLoader(" ../bar "));
    +  }
    +
    +  @Test
    +  void testFileLoader() throws PebbleException, IOException, URISyntaxException {
    +    String prefix = Paths.get(this.getClass().getClassLoader().getResource("templates").toURI()).toString();
    +    Loader<?> loader = new FileLoader(prefix);
    +    loader.setSuffix(".suffix");
    +    PebbleEngine engine = new PebbleEngine.Builder().loader(loader).strictVariables(false).build();
    +    PebbleTemplate template1 = engine.getTemplate("template.loaderTest.peb");
    +    Writer writer1 = new StringWriter();
    +    template1.evaluate(writer1);
    +    assertEquals("SUCCESS", writer1.toString());
    +  }
    +
    +  @Test
    +  void testFileLoaderAbsoluteTemplateName() throws PebbleException, URISyntaxException {
    +    String prefix = Paths.get(this.getClass().getClassLoader().getResource("templates").toURI()).toString();
    +    Loader<?> loader = new FileLoader(prefix);
    +    loader.setSuffix(".suffix");
    +    PebbleEngine engine = new PebbleEngine.Builder().loader(loader).strictVariables(false).build();
    +    assertThrows(LoaderException.class, () -> engine.getTemplate("/template.loaderTest.peb"));
    +  }
    +
    +  @Test
    +  void testFileLoaderTemplateNameIsADirectory() throws PebbleException, URISyntaxException {
    +    String prefix = Paths.get(this.getClass().getClassLoader().getResource("templates").toURI()).toString();
    +    Loader<?> loader = new FileLoader(prefix);
    +    PebbleEngine engine = new PebbleEngine.Builder().loader(loader).strictVariables(false).build();
    +    assertThrows(LoaderException.class, () -> engine.getTemplate("loader"));
    +  }
    +
    +  @Test
    +  void testFileLoaderRelativeTemplateName() throws PebbleException, IOException, URISyntaxException {
    +    String prefix = Paths.get(this.getClass().getClassLoader().getResource("templates").toURI()).getParent().toString();
    +    Loader<?> loader = new FileLoader(prefix);
    +    loader.setSuffix(".suffix");
    +    PebbleEngine engine = new PebbleEngine.Builder().loader(loader).strictVariables(false).build();
    +    PebbleTemplate template1 = engine.getTemplate("templates/template.loaderTest.peb");
    +    Writer writer1 = new StringWriter();
    +    template1.evaluate(writer1);
    +    assertEquals("SUCCESS", writer1.toString());
    +  }
    +
    +  @Test
    +  void testFileLoaderPathTraversal() throws PebbleException, URISyntaxException {
    +    String prefix = Paths.get(this.getClass().getClassLoader().getResource("templates").toURI()).toString();
    +    Loader<?> loader = new FileLoader(prefix);
    +    loader.setSuffix(".peb");
    +    PebbleEngine engine = new PebbleEngine.Builder().loader(loader).strictVariables(false).build();
    +    assertThrows(LoaderException.class, () -> engine.getTemplate("../template-tests/DoubleNestedIfStatement"));
    +  }
    +
    +  @ParameterizedTest
    +  @ValueSource(strings = {"%2e%2e%2f", "%2e%2e/", "..%2f", "%2e%2e%5c", "%2e%2e\\", "..%5c", "%252e%252e%255c", "..%255c"})
    +  void testFileLoaderPathTraversalEncoded(String relativePath) throws URISyntaxException {
    +    String prefix = Paths.get(this.getClass().getClassLoader().getResource("templates").toURI()).toString();
    +    Loader<?> loader = new FileLoader(prefix);
    +    loader.setSuffix(".peb");
    +    PebbleEngine engine = new PebbleEngine.Builder().loader(loader).strictVariables(false).build();
    +    assertThrows(LoaderException.class, () -> engine.getTemplate(relativePath + "template-tests/DoubleNestedIfStatement"));
    +  }
    +
    +  @Test
    +  void testFileLoaderUnsupportedCharset() throws PebbleException, URISyntaxException {
    +    String prefix = Paths.get(this.getClass().getClassLoader().getResource("templates").toURI()).toString();
    +    Loader<?> loader = new FileLoader(prefix);
    +    loader.setCharset("foobar");
    +    PebbleEngine engine = new PebbleEngine.Builder().loader(loader).strictVariables(false).build();
    +    assertThrows(LoaderException.class, () -> engine.getTemplate("template.loaderTest.peb"));
    +  }
    +
    +  /**
    +   * Tests if relative includes work. Issue #162.
    +   */
    +  @Test
    +  void testFileLoaderPathWithBackslash() throws IOException, URISyntaxException {
    +    String prefix = Paths.get(this.getClass().getClassLoader().getResource("templates").toURI()).toString();
    +    PebbleEngine pebble = new PebbleEngine.Builder().loader(new FileLoader(prefix)).build();
    +    PebbleTemplate template = pebble.getTemplate("relativepath/subdirectory1/template.forwardslashes.peb".replace("/", "\\")); // ensure backslashes in all environments
    +    Writer writer = new StringWriter();
    +    template.evaluate(writer);
    +    assertEquals("included", writer.toString());
    +  }
    +
    +  /**
    +   * Issue #162.
    +   */
    +  @Test
    +  void testFileLoaderPathWithForwardSlash() throws IOException, URISyntaxException {
    +    String prefix = Paths.get(this.getClass().getClassLoader().getResource("templates").toURI()).toString();
    +    PebbleEngine pebble = new PebbleEngine.Builder().loader(new FileLoader(prefix)).build();
    +    PebbleTemplate template = pebble.getTemplate("relativepath/subdirectory1/template.backwardslashes.peb");
    +    Writer writer = new StringWriter();
    +    template.evaluate(writer);
    +    assertEquals("included", writer.toString());
    +  }
    +}
    
  • pebble/src/test/java/io/pebbletemplates/pebble/LoaderTest.java+0 14 modified
    @@ -15,7 +15,6 @@
     import org.junit.jupiter.api.Test;
     
     import java.io.*;
    -import java.net.URISyntaxException;
     import java.net.URL;
     import java.net.URLClassLoader;
     import java.util.ArrayList;
    @@ -69,19 +68,6 @@ void testClassLoaderLoaderWithNestedTemplateInJar() throws PebbleException, IOEx
     
       }
     
    -  @Test
    -  void testFileLoader() throws PebbleException, IOException, URISyntaxException {
    -    Loader<?> loader = new FileLoader();
    -    loader.setSuffix(".suffix");
    -    PebbleEngine engine = new PebbleEngine.Builder().loader(loader).strictVariables(false).build();
    -    URL url = this.getClass().getResource("/templates/template.loaderTest.peb");
    -    PebbleTemplate template1 = engine.getTemplate(new File(url.toURI()).getPath());
    -    Writer writer1 = new StringWriter();
    -    template1.evaluate(writer1);
    -    assertEquals("SUCCESS", writer1.toString());
    -
    -  }
    -
       @Test
       void testDelegatingLoader() throws PebbleException, IOException {
         List<Loader<?>> loaders = new ArrayList<>();
    
  • pebble/src/test/java/io/pebbletemplates/pebble/TestRelativePath.java+0 39 modified
    @@ -1,17 +1,12 @@
     package io.pebbletemplates.pebble;
     
     import io.pebbletemplates.pebble.error.PebbleException;
    -import io.pebbletemplates.pebble.loader.FileLoader;
     import io.pebbletemplates.pebble.template.PebbleTemplate;
    -
     import org.junit.jupiter.api.Test;
     
    -import java.io.File;
     import java.io.IOException;
     import java.io.StringWriter;
     import java.io.Writer;
    -import java.net.URISyntaxException;
    -import java.net.URL;
     
     import static org.junit.jupiter.api.Assertions.assertEquals;
     
    @@ -65,38 +60,4 @@ void testRelativeImports() throws PebbleException, IOException {
         assertEquals("<input name=\"company\" value=\"forcorp\" type=\"text\" />",
             writer.toString().replaceAll("\\r?\\n", "").replace("\t", ""));
       }
    -
    -  /**
    -   * Tests if relative includes work. Issue #162.
    -   */
    -  @Test
    -  void testPathWithBackslashesWithRelativePathWithForwardSlashes()
    -      throws PebbleException, IOException, URISyntaxException {
    -    PebbleEngine pebble = new PebbleEngine.Builder().loader(new FileLoader()).build();
    -    URL url = this.getClass()
    -        .getResource("/templates/relativepath/subdirectory1/template.forwardslashes.peb");
    -    PebbleTemplate template = pebble
    -        .getTemplate(new File(url.toURI()).getPath()
    -            .replace("/", "\\")); // ensure backslashes in all environments
    -    Writer writer = new StringWriter();
    -    template.evaluate(writer);
    -    assertEquals("included", writer.toString());
    -  }
    -
    -  /**
    -   * Issue #162.
    -   */
    -  @Test
    -  void testPathWithForwardSlashesWithRelativePathWithBackwardSlashes()
    -      throws PebbleException, IOException, URISyntaxException {
    -    PebbleEngine pebble = new PebbleEngine.Builder().loader(new FileLoader()).build();
    -    URL url = this.getClass()
    -        .getResource("/templates/relativepath/subdirectory1/template.backwardslashes.peb");
    -    PebbleTemplate template = pebble
    -        .getTemplate(new File(url.toURI()).getPath()
    -            .replace("\\", "/")); // ensure forward slashes in all environments
    -    Writer writer = new StringWriter();
    -    template.evaluate(writer);
    -    assertEquals("included", writer.toString());
    -  }
     }
    
  • pom.xml+1 1 modified
    @@ -3,7 +3,7 @@
       <modelVersion>4.0.0</modelVersion>
       <groupId>io.pebbletemplates</groupId>
       <artifactId>pebble-project</artifactId>
    -  <version>4.0.1-SNAPSHOT</version>
    +  <version>4.1.0-SNAPSHOT</version>
     
       <packaging>pom</packaging>
     
    
  • README.md+6 0 modified
    @@ -6,6 +6,12 @@ includes integrated support for internationalization.
     
     For more information please visit the [website](https://pebbletemplates.io).
     
    +# Breaking changes in version 4.1.x
    +
    +- If you do not provide a custom Loader, Pebble will now use only a `ClasspathLoader` by default, same as the spring autoconfiguration.
    +  Before that, it would have used an instance of the `DelegatingLoader` which consists of a `ClasspathLoader` and a `FileLoader` behind the scenes to find your templates.
    +- Modify the `FileLoader` to use a mandatory sandboxed base directory parameter.
    +
     # Breaking changes in version 4.0.x
     
     - Use one of the following artifactId according to the spring boot version that you are using
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

9

News mentions

0

No linked articles in our index yet.