This is an automated email from the ASF dual-hosted git repository.

tballison pushed a commit to branch branch_3x
in repository https://gitbox.apache.org/repos/asf/tika.git


The following commit(s) were added to refs/heads/branch_3x by this push:
     new a365b1d465 TIKA-4760 for 3x
a365b1d465 is described below

commit a365b1d465b6aa33b4bc6e428294ea2be04b28fc
Author: tallison <[email protected]>
AuthorDate: Wed Jun 24 20:59:11 2026 -0400

    TIKA-4760 for 3x
---
 CHANGES.txt                                        |  6 +++
 .../java/org/apache/tika/config/ConfigBase.java    | 40 ++++++++++++++++++-
 .../apache/tika/pipes/fetcher/FetcherManager.java  | 16 ++++++++
 .../apache/tika/server/core/TikaServerConfig.java  | 24 ++++++++++++
 .../apache/tika/server/core/TikaServerProcess.java |  4 +-
 .../tika/server/core/TikaServerConfigTest.java     | 45 ++++++++++++++++++++++
 .../resources/configs/tika-config-server-basic.xml |  1 +
 .../configs/tika-config-server-emitter.xml         |  2 +-
 ...ml => tika-config-server-pipes-no-unsecure.xml} | 10 +----
 9 files changed, 136 insertions(+), 12 deletions(-)

diff --git a/CHANGES.txt b/CHANGES.txt
index 0bdf66fa55..17c5276bab 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,3 +1,9 @@
+Release 3.3.2 - (unreleased)
+
+  * tika-server now requires enableUnsecureFeatures=true when the /pipes, 
/async, or
+    /status endpoints are selected; the server refuses to start otherwise. 
Previously
+    listing the endpoint was treated as sufficient consent (TIKA-4760).
+
 Release 3.3.1 - 5/20/2026
 
   * Dependency upgrades  (TIKA-4695).
diff --git a/tika-core/src/main/java/org/apache/tika/config/ConfigBase.java 
b/tika-core/src/main/java/org/apache/tika/config/ConfigBase.java
index 4afe30ac36..bee7fcd0a0 100644
--- a/tika-core/src/main/java/org/apache/tika/config/ConfigBase.java
+++ b/tika-core/src/main/java/org/apache/tika/config/ConfigBase.java
@@ -124,6 +124,24 @@ public abstract class ConfigBase {
     protected static <P, T> P buildComposite(String compositeElementName, 
Class<P> compositeClass,
                                              String itemName, Class<T> 
itemClass, InputStream is)
             throws TikaConfigException, IOException {
+        P composite = buildCompositeOrNull(compositeElementName, 
compositeClass, itemName,
+                itemClass, is);
+        if (composite == null) {
+            throw new TikaConfigException("could not find " + 
compositeElementName);
+        }
+        return composite;
+    }
+
+    /**
+     * As {@link #buildComposite(String, Class, String, Class, InputStream)}, 
but returns
+     * {@code null} instead of throwing when the composite element (e.g. 
{@code <fetchers/>})
+     * is absent from the config. If the element is present but a child item 
is misconfigured,
+     * this still throws so genuine configuration errors are not silently 
swallowed.
+     */
+    protected static <P, T> P buildCompositeOrNull(String compositeElementName,
+                                                   Class<P> compositeClass, 
String itemName,
+                                                   Class<T> itemClass, 
InputStream is)
+            throws TikaConfigException, IOException {
         Element properties = null;
         try {
             properties = XMLReaderUtils.buildDOM(is).getDocumentElement();
@@ -132,7 +150,7 @@ public abstract class ConfigBase {
         } catch (TikaException e) {
             throw new TikaConfigException("problem loading xml to dom", e);
         }
-        return buildComposite(compositeElementName, compositeClass, itemName, 
itemClass,
+        return buildCompositeOrNull(compositeElementName, compositeClass, 
itemName, itemClass,
                 properties);
     }
 
@@ -140,6 +158,24 @@ public abstract class ConfigBase {
                                              String itemName, Class<T> 
itemClass,
                                              Element properties)
             throws TikaConfigException, IOException {
+        P composite = buildCompositeOrNull(compositeElementName, 
compositeClass, itemName,
+                itemClass, properties);
+        if (composite == null) {
+            throw new TikaConfigException("could not find " + 
compositeElementName);
+        }
+        return composite;
+    }
+
+    /**
+     * As {@link #buildComposite(String, Class, String, Class, Element)}, but 
returns
+     * {@code null} instead of throwing when the composite element (e.g. 
{@code <fetchers/>})
+     * is absent from the config. If the element is present but a child item 
is misconfigured,
+     * this still throws so genuine configuration errors are not silently 
swallowed.
+     */
+    protected static <P, T> P buildCompositeOrNull(String compositeElementName,
+                                                   Class<P> compositeClass, 
String itemName,
+                                                   Class<T> itemClass, Element 
properties)
+            throws TikaConfigException, IOException {
 
         if (!("properties".equals(properties.getNodeName()) ||
             "properties".equals(properties.getLocalName()))) {
@@ -167,7 +203,7 @@ public abstract class ConfigBase {
                 }
             }
         }
-        throw new TikaConfigException("could not find " + 
compositeElementName);
+        return null;
     }
 
     private static <T> List<T> loadComposite(Node composite, String itemName,
diff --git 
a/tika-core/src/main/java/org/apache/tika/pipes/fetcher/FetcherManager.java 
b/tika-core/src/main/java/org/apache/tika/pipes/fetcher/FetcherManager.java
index f99806a90f..23fa876eee 100644
--- a/tika-core/src/main/java/org/apache/tika/pipes/fetcher/FetcherManager.java
+++ b/tika-core/src/main/java/org/apache/tika/pipes/fetcher/FetcherManager.java
@@ -43,6 +43,22 @@ public class FetcherManager extends ConfigBase {
                     "fetcher", Fetcher.class, is);
         }
     }
+
+    /**
+     * As {@link #load(Path)}, but returns an empty manager (no fetchers) when 
the config
+     * contains no {@code <fetchers/>} element, instead of throwing. This 
allows a server to
+     * enable unsecure features (e.g. for the status endpoint) without being 
forced to
+     * configure a fetcher. If the {@code <fetchers/>} element is present but 
a fetcher is
+     * misconfigured, this still throws.
+     */
+    public static FetcherManager loadOrEmpty(Path p) throws IOException, 
TikaConfigException {
+        try (InputStream is =
+                     Files.newInputStream(p)) {
+            FetcherManager fetcherManager = 
FetcherManager.buildCompositeOrNull("fetchers",
+                    FetcherManager.class, "fetcher", Fetcher.class, is);
+            return fetcherManager == null ? new FetcherManager(List.of()) : 
fetcherManager;
+        }
+    }
     private final Map<String, Fetcher> fetcherMap = new ConcurrentHashMap<>();
 
     public FetcherManager(List<Fetcher> fetchers) throws TikaConfigException {
diff --git 
a/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TikaServerConfig.java
 
b/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TikaServerConfig.java
index 663e95bebf..9c08dd3c4a 100644
--- 
a/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TikaServerConfig.java
+++ 
b/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TikaServerConfig.java
@@ -78,6 +78,13 @@ public class TikaServerConfig extends ConfigBase {
                     "as you've given your fetchers and the same write 
permissions " + "as your emitters.\n" + "Users could request and receive a 
sensitive file from your\n" +
                     "drive or a webpage from your intranet and/or send 
malicious content to\n" + " your emitter endpoints.  See CVE-2015-3271.\n" +
                     "Please make sure you know what you are doing.";
+    /**
+     * Endpoints that expose unsecure features (process-isolated pipes 
parsing, async
+     * batch processing, and server status). Selecting any of these requires
+     * {@code enableUnsecureFeatures=true} as an explicit, deliberate opt-in.
+     */
+    private static final Set<String> ENDPOINTS_REQUIRING_UNSECURE_FEATURES =
+            new HashSet<>(Arrays.asList("pipes", "async", "status"));
     private static final List<String> ONLY_IN_FORK_MODE = Arrays.asList(
             new String[]{"taskTimeoutMillis", "taskPulseMillis", "maxFiles", 
"javaPath", "maxRestarts", "numRestarts", "forkedStatusFile", 
"maxForkedStartupMillis",
                     "tmpFilePrefix"});
@@ -445,6 +452,23 @@ private long forkedProcessShutdownMillis = 
DEFAULT_FORKED_PROCESS_SHUTDOWN_MILLI
         if (host == null) {
             throw new TikaConfigException("Must specify 'host'");
         }
+        if (!enableUnsecureFeatures) {
+            List<String> requireUnsecure = new ArrayList<>();
+            for (String endpoint : endpoints) {
+                if (ENDPOINTS_REQUIRING_UNSECURE_FEATURES.contains(endpoint)
+                        && !requireUnsecure.contains(endpoint)) {
+                    requireUnsecure.add(endpoint);
+                }
+            }
+            if (!requireUnsecure.isEmpty()) {
+                throw new TikaConfigException(
+                        "The following selected endpoint(s) require unsecure 
features to be " +
+                        "enabled: " + requireUnsecure + ". Set 
'enableUnsecureFeatures' to true " +
+                        "in the 'server' section of your tika-config and 
confirm you understand " +
+                        "the security implications. These endpoints were 
previously enabled " +
+                        "simply by selecting them; the explicit opt-in is now 
required.");
+            }
+        }
 
         if (!StringUtils.isBlank(port)) {
             setPort(port);
diff --git 
a/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TikaServerProcess.java
 
b/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TikaServerProcess.java
index 0b1f7ab209..010348955e 100644
--- 
a/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TikaServerProcess.java
+++ 
b/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TikaServerProcess.java
@@ -205,7 +205,9 @@ public class TikaServerProcess {
         FetcherManager fetcherManager = null;
         InputStreamFactory inputStreamFactory = null;
         if (tikaServerConfig.isEnableUnsecureFeatures()) {
-            fetcherManager = 
FetcherManager.load(tikaServerConfig.getConfigPath());
+            //tolerate configs that enable unsecure features (e.g. for the 
status endpoint)
+            //without declaring any fetchers
+            fetcherManager = 
FetcherManager.loadOrEmpty(tikaServerConfig.getConfigPath());
             inputStreamFactory = new FetcherStreamFactory(fetcherManager);
         } else {
             inputStreamFactory = new DefaultInputStreamFactory();
diff --git 
a/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/TikaServerConfigTest.java
 
b/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/TikaServerConfigTest.java
index fb5c570c7c..ba7c2aafff 100644
--- 
a/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/TikaServerConfigTest.java
+++ 
b/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/TikaServerConfigTest.java
@@ -18,6 +18,7 @@ package org.apache.tika.server.core;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.nio.file.Path;
@@ -35,6 +36,7 @@ import org.apache.commons.cli.Options;
 import org.junit.jupiter.api.Test;
 
 import org.apache.tika.config.TikaConfigTest;
+import org.apache.tika.exception.TikaConfigException;
 import org.apache.tika.utils.ProcessUtils;
 
 public class TikaServerConfigTest {
@@ -108,6 +110,49 @@ public class TikaServerConfigTest {
         assertEquals(9999, ports[5]);
     }
 
+    @Test
+    public void testUnsecureEndpointRequiresEnableUnsecureFeatures() throws 
Exception {
+        // Selecting pipes (or async, status) without enableUnsecureFeatures 
must fail
+        // at config load (validateConsistency runs in load(CommandLine)), 
forcing an
+        // explicit opt-in.
+        CommandLineParser parser = new DefaultParser();
+        Path path = Paths.get(TikaConfigTest.class
+                
.getResource("/configs/tika-config-server-pipes-no-unsecure.xml")
+                .toURI());
+        CommandLine commandLine = parser.parse(new Options()
+                .addOption(Option
+                        .builder("c")
+                        .longOpt("config")
+                        .hasArg()
+                        .get()), new String[]{"-c", 
ProcessUtils.escapeCommandLine(path
+                .toAbsolutePath()
+                .toString())});
+        TikaConfigException ex = assertThrows(TikaConfigException.class,
+                () -> TikaServerConfig.load(commandLine));
+        assertTrue(ex.getMessage().contains("enableUnsecureFeatures"), 
ex.getMessage());
+        assertTrue(ex.getMessage().contains("pipes"), ex.getMessage());
+    }
+
+    @Test
+    public void testUnsecureEndpointAllowedWithEnableUnsecureFeatures() throws 
Exception {
+        // tika-config-server-basic.xml selects the 'status' endpoint together 
with
+        // enableUnsecureFeatures=true, so it must load without error.
+        CommandLineParser parser = new DefaultParser();
+        Path path = Paths.get(TikaConfigTest.class
+                .getResource("/configs/tika-config-server-basic.xml")
+                .toURI());
+        CommandLine commandLine = parser.parse(new Options()
+                .addOption(Option
+                        .builder("c")
+                        .longOpt("config")
+                        .hasArg()
+                        .get()), new String[]{"-c", 
ProcessUtils.escapeCommandLine(path
+                .toAbsolutePath()
+                .toString())});
+        TikaServerConfig config = TikaServerConfig.load(commandLine);
+        assertTrue(config.isEnableUnsecureFeatures());
+    }
+
     @Test
     public void testTlsConfig() throws Exception {
         Set<String> settings = new HashSet<>();
diff --git 
a/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-basic.xml
 
b/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-basic.xml
index 0825612781..3bbe269504 100644
--- 
a/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-basic.xml
+++ 
b/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-basic.xml
@@ -24,6 +24,7 @@
     <forkedJvmArgs>
       <arg>-Xmx512m</arg>
     </forkedJvmArgs>
+    <enableUnsecureFeatures>true</enableUnsecureFeatures>
     <endpoints>
       <endpoint>rmeta</endpoint>
       <endpoint>status</endpoint>
diff --git 
a/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-emitter.xml
 
b/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-emitter.xml
index 201f552f13..e158cc7a69 100644
--- 
a/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-emitter.xml
+++ 
b/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-emitter.xml
@@ -23,7 +23,7 @@
     <forkedJvmArgs>
       <arg>-Xmx512m</arg>
     </forkedJvmArgs>
-    <enableUnsecure>true</enableUnsecure>
+    <enableUnsecureFeatures>true</enableUnsecureFeatures>
     <endpoints>
       <endpoint>emit</endpoint>
       <endpoint>async</endpoint>
diff --git 
a/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-basic.xml
 
b/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-pipes-no-unsecure.xml
similarity index 77%
copy from 
tika-server/tika-server-core/src/test/resources/configs/tika-config-server-basic.xml
copy to 
tika-server/tika-server-core/src/test/resources/configs/tika-config-server-pipes-no-unsecure.xml
index 0825612781..631e78a92a 100644
--- 
a/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-basic.xml
+++ 
b/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-pipes-no-unsecure.xml
@@ -17,16 +17,10 @@
 -->
 <properties>
   <server>
-    <taskTimeoutMillis>120000</taskTimeoutMillis>
-    <minimumTimeoutMillis>10</minimumTimeoutMillis>
     <port>9999</port>
-    <maxFiles>1000</maxFiles>
-    <forkedJvmArgs>
-      <arg>-Xmx512m</arg>
-    </forkedJvmArgs>
     <endpoints>
-      <endpoint>rmeta</endpoint>
-      <endpoint>status</endpoint>
+      <endpoint>tika</endpoint>
+      <endpoint>pipes</endpoint>
     </endpoints>
   </server>
 </properties>

Reply via email to