From b142c8115ab2d79259511e4f33afef3cae786982 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Fri, 10 Jul 2026 14:00:59 -0400 Subject: [PATCH] Disable Mustache partial template resolution in CustomMustacheFactory (#22438) Override getReader() in CustomMustacheFactory to throw MustacheException when a partial template ({{>name}}) is encountered at compile time. Search templates in OpenSearch are self-contained inline strings, so partial resolution has no legitimate use. Signed-off-by: Craig Perkins (cherry picked from commit e1fa8aab47c1f7f8781157dc22cf057f431a1043) Signed-off-by: opensearch-ci-bot --- .../mustache/CustomMustacheFactory.java | 7 +++++ .../mustache/CustomMustacheFactoryTests.java | 27 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/modules/lang-mustache/src/main/java/org/opensearch/script/mustache/CustomMustacheFactory.java b/modules/lang-mustache/src/main/java/org/opensearch/script/mustache/CustomMustacheFactory.java index c8d28575fef47..773eed9fcb140 100644 --- a/modules/lang-mustache/src/main/java/org/opensearch/script/mustache/CustomMustacheFactory.java +++ b/modules/lang-mustache/src/main/java/org/opensearch/script/mustache/CustomMustacheFactory.java @@ -49,6 +49,7 @@ import org.opensearch.core.xcontent.XContentBuilder; import java.io.IOException; +import java.io.Reader; import java.io.StringWriter; import java.io.Writer; import java.net.URLEncoder; @@ -104,6 +105,12 @@ public void encode(String value, Writer writer) { } } + /** Partial templates ({{>name}}) are not supported. */ + @Override + public Reader getReader(String resourceName) { + throw new MustacheException("Partial templates are not supported in OpenSearch Mustache scripts: [" + resourceName + "]"); + } + static Encoder createEncoder(String mimeType) { Supplier supplier = ENCODERS.get(mimeType); if (supplier == null) { diff --git a/modules/lang-mustache/src/test/java/org/opensearch/script/mustache/CustomMustacheFactoryTests.java b/modules/lang-mustache/src/test/java/org/opensearch/script/mustache/CustomMustacheFactoryTests.java index 422795ec4b171..95137c74ae437 100644 --- a/modules/lang-mustache/src/test/java/org/opensearch/script/mustache/CustomMustacheFactoryTests.java +++ b/modules/lang-mustache/src/test/java/org/opensearch/script/mustache/CustomMustacheFactoryTests.java @@ -34,6 +34,7 @@ import org.opensearch.script.Script; import org.opensearch.script.ScriptEngine; +import org.opensearch.script.ScriptException; import org.opensearch.script.TemplateScript; import org.opensearch.test.OpenSearchTestCase; @@ -44,6 +45,7 @@ import static org.opensearch.script.mustache.CustomMustacheFactory.JSON_MIME_TYPE; import static org.opensearch.script.mustache.CustomMustacheFactory.PLAIN_TEXT_MIME_TYPE; import static org.opensearch.script.mustache.CustomMustacheFactory.X_WWW_FORM_URLENCODED_MIME_TYPE; +import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.instanceOf; @@ -106,4 +108,29 @@ public void testUrlEncoder() { TemplateScript executable = compiled.newInstance(singletonMap("value", "tilde~ AND date:[2016 FROM*]")); assertThat(executable.execute(), equalTo("{\"field\": \"tilde%7E+AND+date%3A%5B2016+FROM*%5D\"}")); } + + public void testPartialTemplateWithFileUrlIsBlocked() { + final ScriptEngine engine = new MustacheScriptEngine(); + ScriptException e = expectThrows( + ScriptException.class, + () -> engine.compile(null, "{{>file:///tmp/secret.txt}}", TemplateScript.CONTEXT, emptyMap()) + ); + assertThat(e.getMessage(), containsString("Partial templates are not supported")); + } + + public void testPartialTemplateWithRelativePathIsBlocked() { + final ScriptEngine engine = new MustacheScriptEngine(); + ScriptException e = expectThrows( + ScriptException.class, + () -> engine.compile(null, "{{>../../../tmp/data}}", TemplateScript.CONTEXT, emptyMap()) + ); + assertThat(e.getMessage(), containsString("Partial templates are not supported")); + } + + public void testInlineTemplateWithoutPartialsStillRenders() { + final ScriptEngine engine = new MustacheScriptEngine(); + TemplateScript.Factory compiled = engine.compile(null, "hello {{name}}", TemplateScript.CONTEXT, emptyMap()); + TemplateScript executable = compiled.newInstance(singletonMap("name", "world")); + assertThat(executable.execute(), equalTo("hello world")); + } }