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

tballison pushed a commit to branch TIKA-4764-more-granular-capability-selection
in repository https://gitbox.apache.org/repos/asf/tika.git

commit 63fb013efc4829adc7a65ef61da51430a31a9cd2
Author: tallison <[email protected]>
AuthorDate: Fri Jun 26 13:12:14 2026 -0400

    TIKA-4764 -- more granular selection of features in tika-server and 
tika-grpc
---
 CHANGES.txt                                        |  10 ++
 .../integration-testing/run-uat-script.adoc        |   6 +-
 .../advanced/integration-testing/tika-server.adoc  |  23 ++--
 docs/modules/ROOT/pages/advanced/robustness.adoc   |   2 +-
 docs/modules/ROOT/pages/configuration/index.adoc   |   2 +-
 .../migration-to-4x/migrating-tika-server-4x.adoc  |  14 ++-
 docs/modules/ROOT/pages/pipes/index.adoc           |   2 +-
 docs/modules/ROOT/pages/pipes/timeouts.adoc        |   2 +-
 docs/modules/ROOT/pages/using-tika/grpc/index.adoc | 130 +++++++++++++++++++++
 docs/modules/ROOT/pages/using-tika/index.adoc      |   7 ++
 .../ROOT/pages/using-tika/server/index.adoc        |  50 ++++----
 tika-grpc/README.md                                |   6 +
 .../org/apache/tika/pipes/grpc/TikaGrpcConfig.java | 102 ++++++++++++++++
 .../apache/tika/pipes/grpc/TikaGrpcServerImpl.java |  77 +++++++++++-
 .../apache/tika/pipes/grpc/TikaGrpcServerTest.java |  84 ++++++++++++-
 .../apache/tika/config/loader/TikaJsonConfig.java  |   1 +
 .../server/core/ConfigEndpointSecurityFilter.java  |  14 +--
 .../apache/tika/server/core/TikaServerConfig.java  |  72 +++++++-----
 .../apache/tika/server/core/TikaServerProcess.java |  10 +-
 .../tika/server/core/resource/TikaResource.java    |  26 ++---
 .../org/apache/tika/server/core/CXFTestBase.java   |   4 +-
 .../core/ConfigEndpointSecurityEnabledTest.java    |   4 +-
 .../server/core/ConfigEndpointSecurityTest.java    |   4 +-
 .../tika/server/core/TikaServerConfigTest.java     |  44 +++++--
 .../resources/config-examples/server-basic.json    |   3 +-
 .../resources/configs/cxf-test-base-template.json  |   3 +-
 .../configs/tika-config-server-basic.json          |   3 +-
 .../configs/tika-config-server-emitter.json        |   3 +-
 .../tika-config-server-fetcher-template.json       |   3 +-
 .../tika-config-server-fetchers-emitters.json      |   3 +-
 .../configs/tika-config-server-pipes-basic.json    |   3 +-
 ...json => tika-config-server-pipes-no-flags.json} |   0
 ...son => tika-config-server-status-no-flags.json} |   9 +-
 .../resources/configs/tika-config-server-tls.json  |   3 +-
 .../test/resources/configs/tika-config-server.json |   3 +-
 .../configs/tika-config-timeout-100ms.json         |   3 +-
 .../tika/server/standard/MetadataResourceTest.java |   2 +-
 .../standard/RecursiveMetadataResourceTest.java    |   2 +-
 .../tika/server/standard/TikaResourceTest.java     |   2 +-
 .../UnpackerResourceConfigDisabledTest.java        |   4 +-
 .../tika/server/standard/UnpackerResourceTest.java |   2 +-
 .../standard/UnpackerResourceWithConfigTest.java   |   2 +-
 .../resources/configs/cxf-test-base-template.json  |   3 +-
 43 files changed, 608 insertions(+), 144 deletions(-)

diff --git a/CHANGES.txt b/CHANGES.txt
index 4acdb12197..f0109c0a8c 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -18,6 +18,16 @@ Release 4.0.0-beta-1 - unreleased
      The extra jars are also forwarded onto forked pipes/server worker 
processes,
      so they are available where parsing actually happens (TIKA-4755).
 
+   * More granular, default-deny capability flags for tika-server and 
tika-grpc.
+     tika-server's enableUnsecureFeatures is split into allowPipes (gates the
+     /pipes and /async endpoints) and allowPerRequestConfig (gates the /config
+     endpoints and the multipart config part); the /status endpoint is no 
longer
+     gated and is enabled simply by listing it under endpoints. tika-grpc gains
+     the same allowPerRequestConfig flag plus allowComponentModifications 
(gates
+     runtime Save/Delete of fetchers and pipes iterators). All flags default to
+     false, so an out-of-the-box tika-grpc server no longer accepts per-request
+     configuration or runtime store mutations (TIKA-4764).
+
   OTHER CHANGES
 
    * Release artifacts are now channel-specific. Maven Central gets slim
diff --git 
a/docs/modules/ROOT/pages/advanced/integration-testing/run-uat-script.adoc 
b/docs/modules/ROOT/pages/advanced/integration-testing/run-uat-script.adoc
index 900cfedbc2..370480fc0e 100644
--- a/docs/modules/ROOT/pages/advanced/integration-testing/run-uat-script.adoc
+++ b/docs/modules/ROOT/pages/advanced/integration-testing/run-uat-script.adoc
@@ -51,9 +51,9 @@ Coverage includes:
 * `/unpack/all` (embedded extraction; verifies the response is a valid zip)
 * `/language/stream`
 * `/meta/form`, `/rmeta/form` (multipart variants)
-* `enableUnsecureFeatures=false` gating: `/meta/config`, `/rmeta/config`,
-  `/tika/config` all return 403; and selecting the `/pipes`, `/async`, or 
`/status`
-  endpoints without `enableUnsecureFeatures` makes the server refuse to start
+* `allowPerRequestConfig=false` gating: `/meta/config`, `/rmeta/config`,
+  `/tika/config` all return 403; and `allowPipes=false` gating: selecting the
+  `/pipes` or `/async` endpoints without `allowPipes` makes the server refuse 
to start
 * `X-Tika-OCRskipOcr` header, `Content-Disposition` filename
 * 404 / 405 error handling
 
diff --git 
a/docs/modules/ROOT/pages/advanced/integration-testing/tika-server.adoc 
b/docs/modules/ROOT/pages/advanced/integration-testing/tika-server.adoc
index d42d0a0644..69595447bd 100644
--- a/docs/modules/ROOT/pages/advanced/integration-testing/tika-server.adoc
+++ b/docs/modules/ROOT/pages/advanced/integration-testing/tika-server.adoc
@@ -212,13 +212,13 @@ curl -s -w "\nHTTP Status: %{http_code}\n" -X POST -F 
"[email protected]" http:/
 curl -s -w "\nHTTP Status: %{http_code}\n" -X POST -F "[email protected]" 
http://localhost:9998/unpack/config
 ----
 
-*Expected:* All return HTTP 403 with message: "Config endpoints are disabled. 
Set enableUnsecureFeatures=true in server config."
+*Expected:* All return HTTP 403 with message: "Config endpoints are disabled. 
Set allowPerRequestConfig=true in server config."
 
-=== Test 18b: `/pipes`, `/async`, `/status` Require enableUnsecureFeatures
+=== Test 18b: `/pipes` and `/async` Require allowPipes
 
 [source,bash]
 ----
-cat > tika-config-pipes-no-unsecure.json << 'EOF'
+cat > tika-config-pipes-no-flags.json << 'EOF'
 {
   "server": {"port": 9998, "endpoints": ["tika", "pipes"]},
   "pipes": {"numClients": 2},
@@ -226,12 +226,12 @@ cat > tika-config-pipes-no-unsecure.json << 'EOF'
 }
 EOF
 
-java -jar tika-server-standard-4.0.0-SNAPSHOT.jar -c 
tika-config-pipes-no-unsecure.json
+java -jar tika-server-standard-4.0.0-SNAPSHOT.jar -c 
tika-config-pipes-no-flags.json
 ----
 
-*Expected:* The server refuses to start, failing with a `TikaConfigException` 
stating that the `pipes` endpoint requires `enableUnsecureFeatures` to be 
`true`. The same applies to `async` and `status`.
+*Expected:* The server refuses to start, failing with a `TikaConfigException` 
stating that the `pipes` endpoint requires `allowPipes` to be `true`. The same 
applies to `async`. (`/status` is no longer gated — it can be enabled simply by 
listing `status` under `endpoints`.)
 
-== Part 2: Tests with enableUnsecureFeatures
+== Part 2: Tests with allowPipes / allowPerRequestConfig
 
 Stop the default server and create a config file:
 
@@ -239,12 +239,13 @@ Stop the default server and create a config file:
 ----
 pkill -f "tika-server-standard-4.0.0-SNAPSHOT.jar"
 
-cat > tika-config-unsecure.json << 'EOF'
+cat > tika-config-capabilities.json << 'EOF'
 {
   "server": {
     "port": 9998,
     "host": "localhost",
-    "enableUnsecureFeatures": true
+    "allowPipes": true,
+    "allowPerRequestConfig": true
   },
   "parsers": [
     {"default-parser": {}}
@@ -253,7 +254,7 @@ cat > tika-config-unsecure.json << 'EOF'
 }
 EOF
 
-java -jar tika-server-standard-4.0.0-SNAPSHOT.jar -c tika-config-unsecure.json 
&
+java -jar tika-server-standard-4.0.0-SNAPSHOT.jar -c 
tika-config-capabilities.json &
 sleep 10
 curl -s http://localhost:9998/version
 ----
@@ -382,7 +383,7 @@ rm -rf /tmp/tika-server-test
 
 The following endpoints were tested and verified working:
 
-=== Default Mode (enableUnsecureFeatures=false)
+=== Default Mode (allowPipes=false, allowPerRequestConfig=false)
 
 [cols="1,1,1", options="header"]
 |===
@@ -412,7 +413,7 @@ The following endpoints were tested and verified working:
 |`/unpack/config` |POST |BLOCKED (403) - Expected
 |===
 
-=== With enableUnsecureFeatures=true
+=== With allowPerRequestConfig=true
 
 [cols="1,1,1", options="header"]
 |===
diff --git a/docs/modules/ROOT/pages/advanced/robustness.adoc 
b/docs/modules/ROOT/pages/advanced/robustness.adoc
index 170db7cf27..c6ad52948c 100644
--- a/docs/modules/ROOT/pages/advanced/robustness.adoc
+++ b/docs/modules/ROOT/pages/advanced/robustness.adoc
@@ -97,7 +97,7 @@ tika-server restarts gracefully.
 
 tika-pipes::
 Available through programmatic use, tika-app `-a` option, or tika-server's 
`/async`
-and `/pipes` endpoints (the server endpoints require 
`enableUnsecureFeatures=true`).
+and `/pipes` endpoints (the server endpoints require `allowPipes=true`).
 
 == Security Testing and Prevention
 
diff --git a/docs/modules/ROOT/pages/configuration/index.adoc 
b/docs/modules/ROOT/pages/configuration/index.adoc
index 0605793967..19bcbe165f 100644
--- a/docs/modules/ROOT/pages/configuration/index.adoc
+++ b/docs/modules/ROOT/pages/configuration/index.adoc
@@ -74,7 +74,7 @@ used wherever a section is missing.
     "unpack-config": { /* embedded-byte extraction */ }
     /* other SelfConfiguring components by component name */
   },
-  "server": { /* tika-server options: enableUnsecureFeatures, cors, ... */ },
+  "server": { /* tika-server options: allowPipes, allowPerRequestConfig, cors, 
... */ },
   "pipes": { /* Pipes process management: numClients, parseMode, ... */ },
   "fetchers": { /* named fetcher instances */ },
   "emitters": { /* named emitter instances */ },
diff --git 
a/docs/modules/ROOT/pages/migration-to-4x/migrating-tika-server-4x.adoc 
b/docs/modules/ROOT/pages/migration-to-4x/migrating-tika-server-4x.adoc
index 59e8691e76..1321e0837d 100644
--- a/docs/modules/ROOT/pages/migration-to-4x/migrating-tika-server-4x.adoc
+++ b/docs/modules/ROOT/pages/migration-to-4x/migrating-tika-server-4x.adoc
@@ -132,17 +132,23 @@ The following `TikaServerConfig` options have been 
removed:
 
 * **Fetcher-based streaming** - The `InputStreamFactory` pattern for fetching 
documents via HTTP headers (`fetcherName`, `fetchKey`) has been removed. All 
documents are now processed via temp files through the pipes infrastructure.
 
-=== `/pipes`, `/async`, and `/status` Require `enableUnsecureFeatures`
+=== `/pipes` and `/async` Require `allowPipes`; Per-Request Config Requires 
`allowPerRequestConfig`
 
-Previously these endpoints were enabled simply by listing them under 
`server.endpoints`. They now *also* require `enableUnsecureFeatures` to be 
`true`; selecting any of `pipes`, `async`, or `status` without it causes the 
server to refuse to start with a clear error. `/pipes` and `/async` drive 
process-isolated batch parsing through your fetchers and emitters, and 
`/status` exposes server information, so this makes enabling them an explicit, 
deliberate opt-in.
+Previously these endpoints (and per-request parser configuration) were enabled 
simply by listing endpoints under `server.endpoints`. The capabilities are now 
split into two default-`false` flags in the `server` section:
 
-**Migration:** if your config selects `pipes`, `async`, or `status`, add 
`"enableUnsecureFeatures": true` to the `server` section:
+* `allowPipes` gates the `/pipes` and `/async` endpoints, which drive 
process-isolated batch parsing through your fetchers and emitters. Selecting 
either without `allowPipes` causes the server to refuse to start with a clear 
error.
+* `allowPerRequestConfig` gates per-request parser configuration: the 
`/config` family of endpoints and the multipart `config` part. When off, such 
requests are rejected with 403.
+
+`/status` is no longer gated: it exposes only aggregate counters, so it is 
enabled simply by listing `status` under `endpoints`.
+
+**Migration:** if your config selects `pipes` or `async`, add `"allowPipes": 
true`; if you rely on per-request config, add `"allowPerRequestConfig": true`:
 
 [source,json]
 ----
 {
   "server": {
-    "enableUnsecureFeatures": true,
+    "allowPipes": true,
+    "allowPerRequestConfig": true,
     "endpoints": ["tika", "rmeta", "pipes", "async", "status"]
   }
 }
diff --git a/docs/modules/ROOT/pages/pipes/index.adoc 
b/docs/modules/ROOT/pages/pipes/index.adoc
index 8037b8857f..9cb7c167e1 100644
--- a/docs/modules/ROOT/pages/pipes/index.adoc
+++ b/docs/modules/ROOT/pages/pipes/index.adoc
@@ -29,7 +29,7 @@ While Tika Pipes has a programmatic Java API, it is best used 
through:
 
 * xref:using-tika/cli/index.adoc[tika-app] — batch processing from the command 
line
 * xref:using-tika/server/index.adoc[tika-server] — REST API with pipes-based 
robustness built in
-* xref:using-tika/grpc/index.adoc[tika-grpc] — gRPC API with pipes-based 
robustness built in
+* xref:using-tika/grpc/index.adoc[tika-grpc] — gRPC API with pipes-based 
robustness built in. More exposed by default than tika-server; run only on a 
trusted network (see xref:using-tika/grpc/index.adoc#_security[Security]).
 
 See xref:advanced/robustness.adoc[Robustness] for details on how Tika Pipes 
protects
 against problematic files.
diff --git a/docs/modules/ROOT/pages/pipes/timeouts.adoc 
b/docs/modules/ROOT/pages/pipes/timeouts.adoc
index bbb94abfd5..1cdddd7746 100644
--- a/docs/modules/ROOT/pages/pipes/timeouts.adoc
+++ b/docs/modules/ROOT/pages/pipes/timeouts.adoc
@@ -68,7 +68,7 @@ This can be combined with other parse-context settings:
 
 == Per-Request Overrides
 
-When using Tika Server with `enableUnsecureFeatures: true`, timeouts can be 
overridden per-request
+When using Tika Server with `allowPerRequestConfig: true`, timeouts can be 
overridden per-request
 by including `TimeoutLimits` in the `ParseContext` of a `FetchEmitTuple`:
 
 [source,java]
diff --git a/docs/modules/ROOT/pages/using-tika/grpc/index.adoc 
b/docs/modules/ROOT/pages/using-tika/grpc/index.adoc
index 7782e1cd63..47ddb21e9f 100644
--- a/docs/modules/ROOT/pages/using-tika/grpc/index.adoc
+++ b/docs/modules/ROOT/pages/using-tika/grpc/index.adoc
@@ -29,6 +29,131 @@ register a fetcher (`SaveFetcher`) and then submit 
`FetchAndParseRequest`
 messages, each of which returns a `FetchAndParseReply` with extracted
 metadata and content.
 
+== Security
+
+[WARNING]
+====
+Treat tika-grpc as a privileged, trusted-network-only service — it is *more
+exposed by default than tika-server*. By default it has no transport security 
and
+no per-caller authorization, and its core `FetchAndParse` surface is always on:
+anyone who can reach the gRPC port can fetch and parse whatever the server's
+configured fetchers can reach.
+
+Its most dangerous capabilities — runtime mutation of the fetcher/iterator 
store
+(for example `SaveFetcher`, which can read files and load code on the server
+host) and per-request parse configuration — are *off by default*, behind the
+flags in <<_capability_flags>>, mirroring tika-server's default-deny capability
+flags (`allowPipes`, `allowPerRequestConfig`). Those flags are defense in 
depth,
+not a substitute for network controls and mutual TLS. Run tika-grpc only behind
+strict network controls.
+====
+
+=== Capability Flags
+
+Like tika-server, tika-grpc locks down its dangerous capabilities by default.
+The two flags below live in the `grpc` section of your tika-config and both
+default to `false`. Out of the box, the server only fetches and parses using 
the
+fetchers and pipes iterators declared in the config file, using the server's 
own
+parse configuration.
+
+[cols="1,3",options="header"]
+|===
+|Setting |Description
+
+|`allowPerRequestConfig`
+|When `true`, callers may attach per-request configuration to `FetchAndParse`
+requests (`parse_context_json` and `additional_fetch_config_json`), overriding
+the server's defaults for that request. Because this can reconfigure any 
pipeline
+component (fetcher, parser, timeouts, ...), it is off by default. When 
`false`, a
+request carrying either field is rejected with `PERMISSION_DENIED`.
+
+|`allowComponentModifications`
+|When `true`, callers may add, modify, or delete fetchers and pipes iterators 
at
+runtime (`SaveFetcher`, `DeleteFetcher`, `SavePipesIterator`,
+`DeletePipesIterator`). Because this changes what the server can reach for all
+subsequent requests (for example, adding a fetcher that escapes a configured 
base
+path), it is off by default. When `false`, those RPCs are rejected with
+`PERMISSION_DENIED`.
+|===
+
+Enable these only for trusted callers over a secured channel:
+
+[source,json]
+----
+{
+  "grpc": {
+    "allowPerRequestConfig": true,
+    "allowComponentModifications": true
+  }
+}
+----
+
+=== Transport Security (TLS)
+
+By default the gRPC server runs *without TLS*: connections are plaintext and
+unauthenticated. This includes the `apache/tika-grpc` Docker image, whose
+entrypoint does not pass `--secure`. Only run in this mode on a trusted 
network.
+
+Transport security is configured entirely through command-line flags (there is 
no
+JSON config for gRPC TLS), with three modes:
+
+*Insecure (default).* No `--secure` flag. Plaintext, no authentication.
+
+*Server (1-way) TLS.* Enable `-s`/`--secure` and supply the server certificate
+and key. The server authenticates to clients; clients are not authenticated.
+
+[source,bash]
+----
+java -jar tika-grpc-<version>.jar --secure \
+  --cert-chain server.pem --private-key server.key
+----
+
+Add `--private-key-password` if the private key is encrypted.
+
+*Mutual (2-way) TLS.* Additionally supply the trust collection (the CA used to
+verify client certificates) and require client authentication:
+
+[source,bash]
+----
+java -jar tika-grpc-<version>.jar --secure \
+  --cert-chain server.pem --private-key server.key \
+  --trust-cert-collection ca.pem --client-auth-required
+----
+
+Mutual TLS is opt-in: `--client-auth-required` is off by default, so it has no
+effect unless `--trust-cert-collection` is also given (a missing or 
non-existent
+trust-collection path is silently ignored). The default port is `50052`
+(`-p`/`--port`).
+
+When running the Docker image, append these flags to the container command — 
they
+are forwarded to the server — and mount the certificate files into the 
container.
+
+=== Kubernetes and Service Meshes
+
+Running tika-grpc in Kubernetes does not make it safe on its own. By default, 
pod
+networking is flat: any pod can reach any other pod's port, traffic is
+unencrypted, and there is no authentication or authorization between pods.
+Kubernetes gives you the tools to lock this down, but none of them are applied
+automatically. Two distinct controls are involved, and both matter:
+
+* *Transport security.* A service mesh (for example Istio or Linkerd) with 
sidecar
+  mTLS encrypts and authenticates pod-to-pod traffic. If the mesh provides 
this,
+  you can run tika-grpc without `--secure` and let the mesh handle transport
+  security in place of the TLS flags above.
+* *Reachability.* A `NetworkPolicy` that admits only your trusted client(s) to 
the
+  tika-grpc Service. This is the control that actually mitigates the exposure
+  described above: because tika-grpc has no per-caller authorization, the set 
of
+  pods that can reach the port is effectively the set of pods that can use its
+  enabled RPC surface.
+
+Mesh mTLS authenticates *who opened the connection*; it does not authorize 
*what
+that caller may do*, so an authenticated-but-untrusted pod can still invoke
+whatever RPC surface is enabled — at minimum `FetchAndParse` against your
+configured fetchers, and the runtime-mutation RPCs too if you have set
+`allowComponentModifications`. Restricting reachability with a `NetworkPolicy` 
is
+therefore required, not optional — running in Kubernetes without one leaves
+tika-grpc reachable by every pod in the cluster.
+
 == Per-Request `ParseContext`
 
 `FetchAndParseRequest.parse_context_json` lets the caller override the
@@ -46,6 +171,11 @@ parse-context component names; values are their JSON 
configs.
 See `META-INF/tika/parse-context.idx` (generated at build time from
 `@TikaComponent` annotations) for the available component names.
 
+NOTE: Per-request configuration is disabled by default. A request that sets
+`parse_context_json` (or `additional_fetch_config_json`) is rejected with
+`PERMISSION_DENIED` unless `allowPerRequestConfig` is enabled. See
+<<_capability_flags,Capability Flags>>.
+
 == Topics
 
 // Add links to specific topics as they are created
diff --git a/docs/modules/ROOT/pages/using-tika/index.adoc 
b/docs/modules/ROOT/pages/using-tika/index.adoc
index a81c5ee6c5..dbd049bba5 100644
--- a/docs/modules/ROOT/pages/using-tika/index.adoc
+++ b/docs/modules/ROOT/pages/using-tika/index.adoc
@@ -36,6 +36,13 @@ and microservice architectures.
 
 xref:using-tika/grpc/index.adoc[gRPC]::
 Use Tika via gRPC protocol. Best for high-performance, cross-language 
communication.
++
+NOTE: tika-grpc is more exposed by default than tika-server — by default it 
has no
+transport security or per-caller authorization, and its core fetch-and-parse 
surface
+is always on. (Its most dangerous capabilities — runtime fetcher/iterator 
changes and
+per-request parse configuration — are off by default.) Run it only on a 
trusted,
+access-controlled network. See the
+xref:using-tika/grpc/index.adoc#_security[Security] section.
 
 == Which Should I Use?
 
diff --git a/docs/modules/ROOT/pages/using-tika/server/index.adoc 
b/docs/modules/ROOT/pages/using-tika/server/index.adoc
index 4d2fc800b4..f7e15e9906 100644
--- a/docs/modules/ROOT/pages/using-tika/server/index.adoc
+++ b/docs/modules/ROOT/pages/using-tika/server/index.adoc
@@ -67,8 +67,8 @@ The server starts on `localhost:9998` by default.
 |Print the usage message.
 |===
 
-NOTE: Other behavior — `enableUnsecureFeatures`, CORS, TLS, timeouts — is 
configured
-in the JSON config file (see <<_configuration,Configuration>>), not via CLI 
flags.
+NOTE: Other behavior — `allowPipes`, `allowPerRequestConfig`, CORS, TLS, 
timeouts — is
+configured in the JSON config file (see <<_configuration,Configuration>>), not 
via CLI flags.
 
 == Endpoints
 
@@ -156,10 +156,10 @@ curl -T document.pdf 
http://localhost:9998/meta/Content-Type   # single field
 * `/translate/all/\{translator}/\{src}/\{dest}` — translation
 * `/pipes`, `/async` — Pipes-based bulk processing
 
-NOTE: `/pipes`, `/async`, and `/status` expose unsecure features and are only 
available
-when `enableUnsecureFeatures` is `true` (see <<_security_configuration,Security
-Configuration>>). Selecting any of them without that flag causes the server to 
refuse
-to start.
+NOTE: `/pipes` and `/async` require `allowPipes` (they drive process-isolated 
fetching
+and parsing); selecting either without it causes the server to refuse to 
start. `/status`
+is a plain opt-in endpoint — enable it simply by listing it under `endpoints`. 
See
+<<_security_configuration,Security Configuration>>.
 
 == Error Responses
 
@@ -210,9 +210,13 @@ Server behavior beyond host/port is controlled by a JSON 
config file passed via
 |===
 |Field |Default |Description
 
-|`enableUnsecureFeatures`
+|`allowPipes`
 |`false`
-|Required opt-in for unsecure features: the `/config` family of endpoints and 
the `/pipes`, `/async`, and `/status` endpoints. The server refuses to start if 
any of those endpoints are selected without this flag (see 
<<_security_configuration,Security Configuration>>).
+|Opt-in for the `/pipes` and `/async` endpoints, which drive process-isolated 
fetching and parsing. The server refuses to start if either is selected without 
this flag (see <<_security_configuration,Security Configuration>>).
+
+|`allowPerRequestConfig`
+|`false`
+|Opt-in for per-request parser configuration: the `/config` family of 
endpoints and the multipart `config` part. When off, such requests are rejected 
with 403 (see <<_security_configuration,Security Configuration>>).
 
 |`cors`
 |`""` (off)
@@ -265,49 +269,51 @@ Protected endpoints include:
 
 === Enabling Config Endpoints
 
-The setting is JSON-only — there is no CLI flag. Set `enableUnsecureFeatures` 
in
+The setting is JSON-only — there is no CLI flag. Set `allowPerRequestConfig` in
 your config file's `server` section:
 
 [source,json]
 ----
 {
   "server": {
-    "enableUnsecureFeatures": true
+    "allowPerRequestConfig": true
   }
 }
 ----
 
-WARNING: Only enable `enableUnsecureFeatures` if you have secured access to 
Tika
+WARNING: Only enable `allowPerRequestConfig` if you have secured access to Tika
 Server through network controls (firewalls, private subnets), a reverse proxy
 (nginx, Apache httpd), or
 xref:using-tika/server/tls.adoc[2-way TLS authentication]. Exposing config 
endpoints
 to untrusted networks can help attackers identify vulnerabilities and craft
 targeted attacks.
 
-=== Pipes, Async, and Status Endpoints
+=== Pipes and Async Endpoints
 
-The `/pipes`, `/async`, and `/status` endpoints also require 
`enableUnsecureFeatures`.
-`/pipes` and `/async` drive process-isolated batch parsing through your 
configured
-fetchers and emitters — whoever can reach them gains the read access of your 
fetchers
-and the write access of your emitters (see
-https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2015-3271[CVE-2015-3271]); 
`/status`
-exposes server information.
+The `/pipes` and `/async` endpoints require `allowPipes`. They drive 
process-isolated
+batch parsing through your configured fetchers and emitters — whoever can 
reach them
+gains the read access of your fetchers and the write access of your emitters 
(see
+https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2015-3271[CVE-2015-3271]).
 
 In earlier releases these endpoints were enabled simply by listing them under
-`server.endpoints`. You must now *also* set `enableUnsecureFeatures` to `true`;
-selecting any of them without it causes the server to refuse to start. This is
-deliberate — it makes enabling these powerful endpoints an explicit, 
considered choice.
+`server.endpoints`. You must now *also* set `allowPipes` to `true`; selecting 
either
+without it causes the server to refuse to start. This is deliberate — it makes 
enabling
+these powerful endpoints an explicit, considered choice.
 
 [source,json]
 ----
 {
   "server": {
-    "enableUnsecureFeatures": true,
+    "allowPipes": true,
     "endpoints": ["tika", "rmeta", "pipes", "async", "status"]
   }
 }
 ----
 
+NOTE: `/status` exposes only aggregate counters (active task count, files 
processed,
+time since last parse) and is *not* gated by `allowPipes` or 
`allowPerRequestConfig`.
+Enable it by listing `status` under `endpoints`.
+
 === Security Best Practices
 
 1. **Keep config endpoints disabled** in production (default behavior).
diff --git a/tika-grpc/README.md b/tika-grpc/README.md
index fdb2cf275a..986529ca30 100644
--- a/tika-grpc/README.md
+++ b/tika-grpc/README.md
@@ -11,6 +11,12 @@ This server will manage a pool of Tika Pipes clients.
     * Delete
 * Fetch + Parse a given Fetch Item
 
+> **Security note:** runtime fetcher/iterator mutations (Create/Update/Delete) 
and
+> per-request parse configuration are **disabled by default**. Enable them
+> explicitly via `allowComponentModifications` / `allowPerRequestConfig` in the
+> `grpc` section of your tika-config. See the
+> [Tika gRPC security configuration 
docs](../docs/modules/ROOT/pages/using-tika/grpc/index.adoc).
+
 ## Distribution and Maven Artifact
 
 **tika-grpc is designed to be run via Docker — it is not a standalone runnable 
artifact published to Maven Central.**
diff --git 
a/tika-grpc/src/main/java/org/apache/tika/pipes/grpc/TikaGrpcConfig.java 
b/tika-grpc/src/main/java/org/apache/tika/pipes/grpc/TikaGrpcConfig.java
new file mode 100644
index 0000000000..3ef88c19c9
--- /dev/null
+++ b/tika-grpc/src/main/java/org/apache/tika/pipes/grpc/TikaGrpcConfig.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tika.pipes.grpc;
+
+import java.io.IOException;
+
+import org.apache.tika.config.loader.TikaJsonConfig;
+
+/**
+ * Configuration for security-sensitive tika-grpc features, loaded from the
+ * {@code "grpc"} section of the tika-config JSON.
+ * <p>
+ * Both flags default to {@code false} so an out-of-the-box server is locked
+ * down: clients may only fetch-and-parse using the fetchers and pipes 
iterators
+ * the operator declared in the config file, using the server's own parse
+ * configuration. The capabilities below are dangerous and must be opted into
+ * explicitly:
+ * <ul>
+ *   <li>{@link #isAllowPerRequestConfig()} lets a client reconfigure any
+ *   pipeline component for a single request.</li>
+ *   <li>{@link #isAllowComponentModifications()} lets a client change which
+ *   fetchers and iterators the server has at all.</li>
+ * </ul>
+ */
+public class TikaGrpcConfig {
+
+    private boolean allowPerRequestConfig = false;
+
+    private boolean allowComponentModifications = false;
+
+    /**
+     * Loads {@link TikaGrpcConfig} from the {@code "grpc"} section of the JSON
+     * configuration, or returns a locked-down default instance (all flags
+     * {@code false}) if no {@code "grpc"} section is present.
+     *
+     * @param tikaJsonConfig the JSON configuration to load from
+     * @return the loaded config, or a default (locked-down) instance
+     * @throws IOException if deserialization fails
+     */
+    public static TikaGrpcConfig load(TikaJsonConfig tikaJsonConfig) throws 
IOException {
+        TikaGrpcConfig config = tikaJsonConfig.deserialize("grpc", 
TikaGrpcConfig.class);
+        if (config == null) {
+            config = new TikaGrpcConfig();
+        }
+        return config;
+    }
+
+    /**
+     * Whether clients may attach per-request configuration to FetchAndParse
+     * requests (the {@code additional_fetch_config_json} and
+     * {@code parse_context_json} fields), overriding the server's defaults for
+     * that single request.
+     * <p>
+     * This is dangerous because the supplied JSON can reconfigure <em>any</em>
+     * pipeline component (fetcher, parser/handler, timeout limits, ...), not
+     * just parse behavior. Defaults to {@code false}; when {@code false}, a
+     * request carrying either field is rejected.
+     *
+     * @return true if per-request configuration is permitted
+     */
+    public boolean isAllowPerRequestConfig() {
+        return allowPerRequestConfig;
+    }
+
+    public void setAllowPerRequestConfig(boolean allowPerRequestConfig) {
+        this.allowPerRequestConfig = allowPerRequestConfig;
+    }
+
+    /**
+     * Whether clients may add, modify, or delete fetchers and pipes iterators 
at
+     * runtime (the SaveFetcher, DeleteFetcher, SavePipesIterator and
+     * DeletePipesIterator RPCs).
+     * <p>
+     * This is dangerous because it changes what the server can reach for all
+     * subsequent requests and clients (for example, adding a fetcher that
+     * escapes a configured base path or points at an internal host). Defaults
+     * to {@code false}; when {@code false}, those RPCs are rejected.
+     *
+     * @return true if runtime component modifications are permitted
+     */
+    public boolean isAllowComponentModifications() {
+        return allowComponentModifications;
+    }
+
+    public void setAllowComponentModifications(boolean 
allowComponentModifications) {
+        this.allowComponentModifications = allowComponentModifications;
+    }
+}
diff --git 
a/tika-grpc/src/main/java/org/apache/tika/pipes/grpc/TikaGrpcServerImpl.java 
b/tika-grpc/src/main/java/org/apache/tika/pipes/grpc/TikaGrpcServerImpl.java
index 9f80afb352..5b3ee9e719 100644
--- a/tika-grpc/src/main/java/org/apache/tika/pipes/grpc/TikaGrpcServerImpl.java
+++ b/tika-grpc/src/main/java/org/apache/tika/pipes/grpc/TikaGrpcServerImpl.java
@@ -85,6 +85,7 @@ class TikaGrpcServerImpl extends TikaGrpc.TikaImplBase {
     private static final String PIPES_ITERATOR_PREFIX = "pipesIterator:";
 
     PipesConfig pipesConfig;
+    TikaGrpcConfig tikaGrpcConfig;
     PipesClient pipesClient;
     FetcherManager fetcherManager;
     ConfigStore configStore;
@@ -113,6 +114,10 @@ class TikaGrpcServerImpl extends TikaGrpc.TikaImplBase {
             pipesConfig = new PipesConfig();
         }
 
+        // Security-sensitive grpc features (per-request config, runtime 
component
+        // modifications) are off unless explicitly enabled in the "grpc" 
section.
+        tikaGrpcConfig = TikaGrpcConfig.load(tikaJsonConfig);
+
         pipesClient = new PipesClient(pipesConfig, configPath);
         
         try {
@@ -141,7 +146,8 @@ class TikaGrpcServerImpl extends TikaGrpc.TikaImplBase {
 
         this.configStore = createConfigStore();
 
-        fetcherManager = FetcherManager.load(pluginManager, tikaJsonConfig, 
true, this.configStore);
+        fetcherManager = FetcherManager.load(pluginManager, tikaJsonConfig,
+                tikaGrpcConfig.isAllowComponentModifications(), 
this.configStore);
     }
 
     private ConfigStore createConfigStore() throws TikaConfigException {
@@ -182,9 +188,60 @@ class TikaGrpcServerImpl extends TikaGrpc.TikaImplBase {
         }
     }
 
+    /**
+     * If the operator has not opted in to runtime component modifications, 
closes
+     * the call with {@code PERMISSION_DENIED} and returns {@code true}. 
Guards the
+     * Save/Delete fetcher and pipes-iterator RPCs. The caller must {@code 
return}
+     * immediately when this returns {@code true}.
+     * <p>
+     * We close the observer here rather than throwing, because a
+     * {@link io.grpc.StatusRuntimeException} thrown out of a service method is
+     * reported to the client as {@code UNKNOWN}; only {@code onError} 
transmits
+     * the intended status.
+     */
+    private boolean denyComponentModifications(StreamObserver<?> 
responseObserver) {
+        if (tikaGrpcConfig.isAllowComponentModifications()) {
+            return false;
+        }
+        responseObserver.onError(io.grpc.Status.PERMISSION_DENIED
+                .withDescription("Runtime component modifications are 
disabled. Set "
+                        + "'allowComponentModifications' to true in the 'grpc' 
section of your "
+                        + "tika-config to allow 
SaveFetcher/DeleteFetcher/SavePipesIterator/"
+                        + "DeletePipesIterator. Understand the security 
implications first.")
+                .asRuntimeException());
+        return true;
+    }
+
+    /**
+     * If the request carries per-request configuration
+     * ({@code additional_fetch_config_json} or {@code parse_context_json}) 
but the
+     * operator has not opted in, closes the call with {@code 
PERMISSION_DENIED} and
+     * returns {@code true}. A request with no per-request config is always 
allowed.
+     * The caller must {@code return} immediately when this returns {@code 
true}.
+     */
+    private boolean denyPerRequestConfig(FetchAndParseRequest request,
+                                         StreamObserver<?> responseObserver) {
+        boolean hasPerRequestConfig =
+                StringUtils.isNotBlank(request.getAdditionalFetchConfigJson())
+                        || 
StringUtils.isNotBlank(request.getParseContextJson());
+        if (!hasPerRequestConfig || tikaGrpcConfig.isAllowPerRequestConfig()) {
+            return false;
+        }
+        responseObserver.onError(io.grpc.Status.PERMISSION_DENIED
+                .withDescription("Per-request configuration is disabled. Set "
+                        + "'allowPerRequestConfig' to true in the 'grpc' 
section of your "
+                        + "tika-config to allow additional_fetch_config_json / 
"
+                        + "parse_context_json. Understand the security 
implications first.")
+                .asRuntimeException());
+        return true;
+    }
+
     @Override
     public void fetchAndParseServerSideStreaming(FetchAndParseRequest request,
                                                  
StreamObserver<FetchAndParseReply> responseObserver) {
+        if (denyPerRequestConfig(request, responseObserver)) {
+            return;
+        }
         fetchAndParseImpl(request, responseObserver);
     }
 
@@ -194,6 +251,9 @@ class TikaGrpcServerImpl extends TikaGrpc.TikaImplBase {
         return new StreamObserver<>() {
             @Override
             public void onNext(FetchAndParseRequest fetchAndParseRequest) {
+                if (denyPerRequestConfig(fetchAndParseRequest, 
responseObserver)) {
+                    return;
+                }
                 fetchAndParseImpl(fetchAndParseRequest, responseObserver);
             }
 
@@ -212,6 +272,9 @@ class TikaGrpcServerImpl extends TikaGrpc.TikaImplBase {
     @Override
     public void fetchAndParse(FetchAndParseRequest request,
                               StreamObserver<FetchAndParseReply> 
responseObserver) {
+        if (denyPerRequestConfig(request, responseObserver)) {
+            return;
+        }
         fetchAndParseImpl(request, responseObserver);
         responseObserver.onCompleted();
     }
@@ -269,6 +332,9 @@ class TikaGrpcServerImpl extends TikaGrpc.TikaImplBase {
     @Override
     public void saveFetcher(SaveFetcherRequest request,
                               StreamObserver<SaveFetcherReply> 
responseObserver) {
+        if (denyComponentModifications(responseObserver)) {
+            return;
+        }
         SaveFetcherReply reply =
                 
SaveFetcherReply.newBuilder().setFetcherId(request.getFetcherId()).build();
         try {
@@ -363,6 +429,9 @@ class TikaGrpcServerImpl extends TikaGrpc.TikaImplBase {
     @Override
     public void deleteFetcher(DeleteFetcherRequest request,
                               StreamObserver<DeleteFetcherReply> 
responseObserver) {
+        if (denyComponentModifications(responseObserver)) {
+            return;
+        }
         boolean successfulDelete = deleteFetcher(request.getFetcherId());
         
responseObserver.onNext(DeleteFetcherReply.newBuilder().setSuccess(successfulDelete).build());
         responseObserver.onCompleted();
@@ -398,6 +467,9 @@ class TikaGrpcServerImpl extends TikaGrpc.TikaImplBase {
     @Override
     public void savePipesIterator(SavePipesIteratorRequest request,
                                   StreamObserver<SavePipesIteratorReply> 
responseObserver) {
+        if (denyComponentModifications(responseObserver)) {
+            return;
+        }
         try {
             String iteratorId = request.getIteratorId();
             String iteratorClass = request.getIteratorClass();
@@ -466,6 +538,9 @@ class TikaGrpcServerImpl extends TikaGrpc.TikaImplBase {
     @Override
     public void deletePipesIterator(DeletePipesIteratorRequest request,
                                     StreamObserver<DeletePipesIteratorReply> 
responseObserver) {
+        if (denyComponentModifications(responseObserver)) {
+            return;
+        }
         try {
             String iteratorId = request.getIteratorId();
             LOG.info("Deleting pipes iterator: {}", iteratorId);
diff --git 
a/tika-grpc/src/test/java/org/apache/tika/pipes/grpc/TikaGrpcServerTest.java 
b/tika-grpc/src/test/java/org/apache/tika/pipes/grpc/TikaGrpcServerTest.java
index ee909098fc..04d57defce 100644
--- a/tika-grpc/src/test/java/org/apache/tika/pipes/grpc/TikaGrpcServerTest.java
+++ b/tika-grpc/src/test/java/org/apache/tika/pipes/grpc/TikaGrpcServerTest.java
@@ -42,9 +42,12 @@ import java.util.concurrent.atomic.AtomicBoolean;
 import com.asarkar.grpc.test.GrpcCleanupExtension;
 import com.asarkar.grpc.test.Resources;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
 import com.google.common.collect.ImmutableMap;
 import io.grpc.ManagedChannel;
 import io.grpc.Server;
+import io.grpc.Status;
+import io.grpc.StatusRuntimeException;
 import io.grpc.inprocess.InProcessChannelBuilder;
 import io.grpc.inprocess.InProcessServerBuilder;
 import io.grpc.stub.StreamObserver;
@@ -76,7 +79,13 @@ public class TikaGrpcServerTest {
     private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
     private static final Logger LOG = 
LoggerFactory.getLogger(TikaGrpcServerTest.class);
     public static final int NUM_TEST_DOCS = 2;
+    // Secure-default config: no "grpc" section, so per-request config and 
runtime
+    // component modifications are both disabled. Used by the default-deny 
tests.
     static Path tikaConfig = Paths.get("target", "tika-config-" + 
UUID.randomUUID() + ".json");
+    // Same config but with the dangerous grpc features explicitly enabled. 
Used by
+    // the CRUD/streaming tests, which save fetchers at runtime.
+    static Path tikaConfigUnlocked =
+            Paths.get("target", "tika-config-unlocked-" + UUID.randomUUID() + 
".json");
 
 
     @BeforeAll
@@ -102,12 +111,24 @@ public class TikaGrpcServerTest {
                 TikaGrpcServerTest.class, replacements, tikaConfig);
 
         LOG.debug("Written config to: {}", tikaConfig.toAbsolutePath());
+
+        // Derive an "unlocked" variant that opts in to the dangerous grpc 
features,
+        // for the tests that add/modify fetchers at runtime.
+        ObjectNode root = (ObjectNode) 
OBJECT_MAPPER.readTree(tikaConfig.toFile());
+        ObjectNode grpc = OBJECT_MAPPER.createObjectNode();
+        grpc.put("allowComponentModifications", true);
+        grpc.put("allowPerRequestConfig", true);
+        root.set("grpc", grpc);
+        FileUtils.write(tikaConfigUnlocked.toFile(),
+                
OBJECT_MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(root),
+                StandardCharsets.UTF_8);
     }
 
     @AfterAll
     static void clean() {
         try {
             Files.deleteIfExists(tikaConfig);
+            Files.deleteIfExists(tikaConfigUnlocked);
         } catch (Exception e) {
             LOG.warn("Failed to delete {}", tikaConfig, e);
         }
@@ -122,7 +143,7 @@ public class TikaGrpcServerTest {
         Server server = InProcessServerBuilder
                 .forName(serverName)
                 .directExecutor()
-                .addService(new 
TikaGrpcServerImpl(tikaConfig.toAbsolutePath().toString()))
+                .addService(new 
TikaGrpcServerImpl(tikaConfigUnlocked.toAbsolutePath().toString()))
                 .build()
                 .start();
         resources.register(server, Duration.ofSeconds(10));
@@ -201,11 +222,70 @@ public class TikaGrpcServerTest {
         return "nick" + i + ":is:cool:super/" + FileSystemFetcher.class;
     }
 
+    @Test
+    public void testComponentModificationsDeniedByDefault(Resources resources) 
throws Exception {
+        TikaGrpc.TikaBlockingStub blockingStub = startServer(resources, 
tikaConfig);
+
+        String targetFolder = new File("target").getAbsolutePath();
+        StatusRuntimeException saveEx = 
Assertions.assertThrows(StatusRuntimeException.class, () ->
+                blockingStub.saveFetcher(SaveFetcherRequest
+                        .newBuilder()
+                        .setFetcherId(createFetcherId(0))
+                        .setFetcherClass(FileSystemFetcher.class.getName())
+                        
.setFetcherConfigJson(OBJECT_MAPPER.writeValueAsString(ImmutableMap
+                                .builder()
+                                .put("basePath", targetFolder)
+                                .build()))
+                        .build()));
+        assertEquals(Status.Code.PERMISSION_DENIED, 
saveEx.getStatus().getCode());
+
+        StatusRuntimeException deleteEx = 
Assertions.assertThrows(StatusRuntimeException.class, () ->
+                blockingStub.deleteFetcher(DeleteFetcherRequest
+                        .newBuilder()
+                        .setFetcherId(createFetcherId(0))
+                        .build()));
+        assertEquals(Status.Code.PERMISSION_DENIED, 
deleteEx.getStatus().getCode());
+    }
+
+    @Test
+    public void testPerRequestConfigDeniedByDefault(Resources resources) 
throws Exception {
+        TikaGrpc.TikaBlockingStub blockingStub = startServer(resources, 
tikaConfig);
+
+        // The per-request config gate fires before any fetch, so this never 
reaches the network.
+        StatusRuntimeException ex = 
Assertions.assertThrows(StatusRuntimeException.class, () ->
+                blockingStub.fetchAndParse(FetchAndParseRequest
+                        .newBuilder()
+                        .setFetcherId("httpFetcherIdHere")
+                        .setFetchKey("https://example.com";)
+                        
.setParseContextJson("{\"basic-content-handler-factory\":{\"type\":\"HTML\"}}")
+                        .build()));
+        assertEquals(Status.Code.PERMISSION_DENIED, ex.getStatus().getCode());
+    }
+
+    private static TikaGrpc.TikaBlockingStub startServer(Resources resources, 
Path config)
+            throws Exception {
+        String serverName = InProcessServerBuilder.generateName();
+        Server server = InProcessServerBuilder
+                .forName(serverName)
+                .directExecutor()
+                .addService(new 
TikaGrpcServerImpl(config.toAbsolutePath().toString()))
+                .build()
+                .start();
+        resources.register(server, Duration.ofSeconds(10));
+
+        ManagedChannel channel = InProcessChannelBuilder
+                .forName(serverName)
+                .directExecutor()
+                .build();
+        resources.register(channel, Duration.ofSeconds(10));
+        return TikaGrpc.newBlockingStub(channel);
+    }
+
     @Test
     public void testBiStream(Resources resources) throws Exception {
         String serverName = InProcessServerBuilder.generateName();
 
-        TikaGrpcServerImpl tikaGrpcServerImpl = new 
TikaGrpcServerImpl(tikaConfig.toAbsolutePath().toString());
+        TikaGrpcServerImpl tikaGrpcServerImpl = new 
TikaGrpcServerImpl(tikaConfigUnlocked.toAbsolutePath().toString());
         Server server = InProcessServerBuilder
                 .forName(serverName)
                 .directExecutor()
diff --git 
a/tika-serialization/src/main/java/org/apache/tika/config/loader/TikaJsonConfig.java
 
b/tika-serialization/src/main/java/org/apache/tika/config/loader/TikaJsonConfig.java
index d645ef73be..d15815b4ac 100644
--- 
a/tika-serialization/src/main/java/org/apache/tika/config/loader/TikaJsonConfig.java
+++ 
b/tika-serialization/src/main/java/org/apache/tika/config/loader/TikaJsonConfig.java
@@ -117,6 +117,7 @@ public class TikaJsonConfig {
             "auto-detect-parser",
             "parse-context",
             "server",
+            "grpc",
 
             // Pipes/plugin keys
             "fetchers",
diff --git 
a/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/ConfigEndpointSecurityFilter.java
 
b/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/ConfigEndpointSecurityFilter.java
index 16787e441b..c047f737c3 100644
--- 
a/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/ConfigEndpointSecurityFilter.java
+++ 
b/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/ConfigEndpointSecurityFilter.java
@@ -23,24 +23,24 @@ import jakarta.ws.rs.core.Response;
 import jakarta.ws.rs.ext.Provider;
 
 /**
- * JAX-RS filter that gates /config endpoints behind the 
enableUnsecureFeatures flag.
- * When enableUnsecureFeatures is false, requests to paths containing 
"/config" will
+ * JAX-RS filter that gates /config endpoints behind the allowPerRequestConfig 
flag.
+ * When allowPerRequestConfig is false, requests to paths containing "/config" 
will
  * receive a 403 Forbidden response.
  */
 @Provider
 public class ConfigEndpointSecurityFilter implements ContainerRequestFilter {
 
-    private final boolean enableUnsecureFeatures;
+    private final boolean allowPerRequestConfig;
 
-    public ConfigEndpointSecurityFilter(boolean enableUnsecureFeatures) {
-        this.enableUnsecureFeatures = enableUnsecureFeatures;
+    public ConfigEndpointSecurityFilter(boolean allowPerRequestConfig) {
+        this.allowPerRequestConfig = allowPerRequestConfig;
     }
 
     @Override
     public void filter(ContainerRequestContext requestContext) {
-        if (!enableUnsecureFeatures && 
requestContext.getUriInfo().getPath().contains("/config")) {
+        if (!allowPerRequestConfig && 
requestContext.getUriInfo().getPath().contains("/config")) {
             requestContext.abortWith(Response.status(Response.Status.FORBIDDEN)
-                    .entity("Config endpoints are disabled. Set 
enableUnsecureFeatures=true in server config.")
+                    .entity("Config endpoints are disabled. Set 
allowPerRequestConfig=true in server config.")
                     .type(MediaType.TEXT_PLAIN)
                     .build());
         }
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 6eb1771af7..14fd249468 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
@@ -48,23 +48,16 @@ public class TikaServerConfig {
     //used in fork mode -- restart after processing this many files
     private static final long DEFAULT_MAX_FILES = 100000;
     private static final int DEFAULT_DIGEST_MARK_LIMIT = 20 * 1024 * 1024;
-    private static final String UNSECURE_WARNING =
-            "WARNING: You have chosen to run tika-server with unsecure 
features enabled.\n" +
-                    "Whoever has access to your service now has the same read 
permissions\n" +
-                    "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" +
-                    "Additionally, /config endpoints allow per-request parser 
configuration\n" +
-                    "which could enable dangerous operations.\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 now 
requires
-     * {@code enableUnsecureFeatures=true} as an explicit, deliberate opt-in.
+     * Endpoints that expose the pipes/fetch machinery (process-isolated pipes
+     * parsing and async batch processing). Selecting any of these requires
+     * {@code allowPipes=true} as an explicit, deliberate opt-in.
+     * <p>
+     * {@code status} is intentionally not in this set: it exposes only 
aggregate
+     * counters and is enabled simply by listing it under {@code endpoints}.
      */
-    private static final Set<String> ENDPOINTS_REQUIRING_UNSECURE_FEATURES =
-            new HashSet<>(Arrays.asList("pipes", "async", "status"));
+    private static final Set<String> ENDPOINTS_REQUIRING_PIPES =
+            new HashSet<>(Arrays.asList("pipes", "async"));
     private static final List<String> ONLY_IN_FORK_MODE = Arrays.asList(
             new String[]{"maxFiles", "javaPath", "maxRestarts", "numRestarts", 
"forkedStatusFile", "maxForkedStartupMillis",
                     "tmpFilePrefix"});
@@ -83,7 +76,8 @@ private long forkedProcessStartupMillis = 
DEFAULT_FORKED_PROCESS_STARTUP_MILLIS;
 private long forkedProcessShutdownMillis = 
DEFAULT_FORKED_PROCESS_SHUTDOWN_MILLIS;
 
  */
-    private boolean enableUnsecureFeatures = false;
+    private boolean allowPipes = false;
+    private boolean allowPerRequestConfig = false;
     private String cors = "";
     private boolean returnStackTrace = false;
     private String idBase = UUID
@@ -163,34 +157,52 @@ private long forkedProcessShutdownMillis = 
DEFAULT_FORKED_PROCESS_SHUTDOWN_MILLI
         return idBase;
     }
 
-    public boolean isEnableUnsecureFeatures() {
-        return enableUnsecureFeatures;
+    /**
+     * Whether the pipes/fetch endpoints ({@code pipes}, {@code async}) may be
+     * enabled. Off by default; selecting one of those endpoints without this 
set
+     * causes the server to refuse to start.
+     */
+    public boolean isAllowPipes() {
+        return allowPipes;
+    }
+
+    public void setAllowPipes(boolean allowPipes) {
+        this.allowPipes = allowPipes;
+    }
+
+    /**
+     * Whether callers may supply per-request parser configuration (the
+     * {@code /config} endpoints and the multipart {@code config} part). Off by
+     * default; when off, such requests are rejected with 403.
+     */
+    public boolean isAllowPerRequestConfig() {
+        return allowPerRequestConfig;
     }
 
-    public void setEnableUnsecureFeatures(boolean enableUnsecureFeatures) {
-        this.enableUnsecureFeatures = enableUnsecureFeatures;
+    public void setAllowPerRequestConfig(boolean allowPerRequestConfig) {
+        this.allowPerRequestConfig = allowPerRequestConfig;
     }
 
     private void validateConsistency(Set<String> settings) throws 
TikaConfigException {
         if (host == null) {
             throw new TikaConfigException("Must specify 'host'");
         }
-        if (!enableUnsecureFeatures) {
-            List<String> requireUnsecure = new ArrayList<>();
+        if (!allowPipes) {
+            List<String> requirePipes = new ArrayList<>();
             for (String endpoint : endpoints) {
-                if (ENDPOINTS_REQUIRING_UNSECURE_FEATURES.contains(endpoint)
-                        && !requireUnsecure.contains(endpoint)) {
-                    requireUnsecure.add(endpoint);
+                if (ENDPOINTS_REQUIRING_PIPES.contains(endpoint)
+                        && !requirePipes.contains(endpoint)) {
+                    requirePipes.add(endpoint);
                 }
             }
-            if (!requireUnsecure.isEmpty()) {
+            if (!requirePipes.isEmpty()) {
                 throw new TikaConfigException(
-                        "The following selected endpoint(s) require unsecure 
features to be " +
-                        "enabled: " + requireUnsecure + ". Set 
'enableUnsecureFeatures' to true " +
+                        "The following selected endpoint(s) require the pipes 
machinery to be " +
+                        "enabled: " + requirePipes + ". Set 'allowPipes' to 
true " +
                         "in the 'server' section of your tika-config and 
confirm you understand " +
                         "the security implications (see the tika-server 
documentation). These " +
-                        "endpoints were previously enabled simply by selecting 
them; the explicit " +
-                        "opt-in is now required.");
+                        "endpoints expose process-isolated fetching and 
parsing, which can read " +
+                        "files and reach network resources.");
             }
         }
     }
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 1d1cddfacb..241e96bee2 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
@@ -192,7 +192,7 @@ public class TikaServerProcess {
         }
 
         TikaResource.init(tikaLoader, serverStatus, pipesParsingHelper,
-                tikaServerConfig.isEnableUnsecureFeatures());
+                tikaServerConfig.isAllowPerRequestConfig());
         JAXRSServerFactoryBean sf = new JAXRSServerFactoryBean();
 
         List<ResourceProvider> resourceProviders = new ArrayList<>();
@@ -308,7 +308,7 @@ public class TikaServerProcess {
         writers.add(new JSONObjWriter());
 
         // Add ConfigEndpointSecurityFilter to gate /config endpoints
-        writers.add(new 
ConfigEndpointSecurityFilter(tikaServerConfig.isEnableUnsecureFeatures()));
+        writers.add(new 
ConfigEndpointSecurityFilter(tikaServerConfig.isAllowPerRequestConfig()));
 
         TikaLoggingFilter logFilter = null;
         if (!StringUtils.isBlank(tikaServerConfig.getLogLevel())) {
@@ -354,11 +354,13 @@ public class TikaServerProcess {
             resourceProviders.add(new SingletonResourceProvider(new 
TikaDetectors()));
             resourceProviders.add(new SingletonResourceProvider(new 
TikaParsers()));
             resourceProviders.add(new SingletonResourceProvider(new 
TikaVersion()));
-            if (tikaServerConfig.isEnableUnsecureFeatures()) {
+            if (tikaServerConfig.isAllowPipes()) {
                 addAsyncResource = true;
                 addPipesResource = true;
-                resourceProviders.add(new SingletonResourceProvider(new 
TikaServerStatus(serverStatus)));
             }
+            // status is a plain opt-in endpoint: it is only served when 
explicitly
+            // listed under "endpoints" (handled in the else branch below), 
not in
+            // this default set.
         } else {
             for (String endPoint : tikaServerConfig.getEndpoints()) {
                 if ("meta".equals(endPoint)) {
diff --git 
a/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/resource/TikaResource.java
 
b/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/resource/TikaResource.java
index a0ea80b80e..4abedede71 100644
--- 
a/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/resource/TikaResource.java
+++ 
b/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/resource/TikaResource.java
@@ -90,7 +90,7 @@ public class TikaResource {
     private static MetadataWriteLimiterFactory 
DEFAULT_METADATA_WRITE_LIMITER_FACTORY = null;
     // Whether per-request config injection (multipart "config" parts) is 
permitted.
     // Enforced in setupMultipartConfig so every config-consuming endpoint 
honors it.
-    private static boolean ENABLE_UNSECURE_FEATURES = false;
+    private static boolean ALLOW_PER_REQUEST_CONFIG = false;
 
     /**
      * Initialize TikaResource with pipes-based parsing for process isolation.
@@ -98,14 +98,14 @@ public class TikaResource {
      * @param tikaLoader the Tika loader
      * @param serverStatus server status tracker
      * @param pipesParsingHelper helper for pipes-based parsing, may be null 
if /tika endpoint is not enabled
-     * @param enableUnsecureFeatures whether per-request config injection is 
permitted
+     * @param allowPerRequestConfig whether per-request config injection is 
permitted
      */
     public static void init(TikaLoader tikaLoader, ServerStatus serverStatus,
-                            PipesParsingHelper pipesParsingHelper, boolean 
enableUnsecureFeatures) {
+                            PipesParsingHelper pipesParsingHelper, boolean 
allowPerRequestConfig) {
         TIKA_LOADER = tikaLoader;
         SERVER_STATUS = serverStatus;
         PIPES_PARSING_HELPER = pipesParsingHelper;
-        ENABLE_UNSECURE_FEATURES = enableUnsecureFeatures;
+        ALLOW_PER_REQUEST_CONFIG = allowPerRequestConfig;
         // MetadataWriteLimiterFactory is now loaded dynamically via 
loadParseContext()
     }
 
@@ -274,10 +274,10 @@ public class TikaResource {
 
         // Enforce the per-request config gate where the config part is 
actually
         // consumed, so every endpoint that accepts a config part honors
-        // enableUnsecureFeatures uniformly.
-        if (configAtt != null && !ENABLE_UNSECURE_FEATURES) {
+        // allowPerRequestConfig uniformly.
+        if (configAtt != null && !ALLOW_PER_REQUEST_CONFIG) {
             throw new 
WebApplicationException(Response.status(Response.Status.FORBIDDEN)
-                    .entity("Per-request configuration is disabled. Set 
enableUnsecureFeatures=true in server config.")
+                    .entity("Per-request configuration is disabled. Set 
allowPerRequestConfig=true in server config.")
                     .type(MediaType.TEXT_PLAIN)
                     .build());
         }
@@ -626,7 +626,7 @@ public class TikaResource {
      * <p>
      * Returns XHTML by default. Use /tika/config/text, /tika/config/html, or 
/tika/config/xml for other formats.
      * <p>
-     * This endpoint is gated behind enableUnsecureFeatures=true because 
per-request
+     * This endpoint is gated behind allowPerRequestConfig=true because 
per-request
      * configuration could enable dangerous operations.
      */
     @POST
@@ -649,7 +649,7 @@ public class TikaResource {
      * - "file" part (required): the document to parse
      * - "config" part (optional): JSON configuration for parser settings
      * <p>
-     * This endpoint is gated behind enableUnsecureFeatures=true because 
per-request
+     * This endpoint is gated behind allowPerRequestConfig=true because 
per-request
      * configuration could enable dangerous operations.
      */
     @POST
@@ -671,7 +671,7 @@ public class TikaResource {
      * - "file" part (required): the document to parse
      * - "config" part (optional): JSON configuration for parser settings
      * <p>
-     * This endpoint is gated behind enableUnsecureFeatures=true because 
per-request
+     * This endpoint is gated behind allowPerRequestConfig=true because 
per-request
      * configuration could enable dangerous operations.
      */
     @POST
@@ -693,7 +693,7 @@ public class TikaResource {
      * - "file" part (required): the document to parse
      * - "config" part (optional): JSON configuration for parser settings
      * <p>
-     * This endpoint is gated behind enableUnsecureFeatures=true because 
per-request
+     * This endpoint is gated behind allowPerRequestConfig=true because 
per-request
      * configuration could enable dangerous operations.
      */
     @POST
@@ -715,7 +715,7 @@ public class TikaResource {
      * - "file" part (required): the document to parse
      * - "config" part (optional): JSON configuration for parser settings
      * <p>
-     * This endpoint is gated behind enableUnsecureFeatures=true because 
per-request
+     * This endpoint is gated behind allowPerRequestConfig=true because 
per-request
      * configuration could enable dangerous operations.
      */
     @POST
@@ -739,7 +739,7 @@ public class TikaResource {
      * <p>
      * Default handler is text. Use config to specify different handler type.
      * <p>
-     * This endpoint is gated behind enableUnsecureFeatures=true because 
per-request
+     * This endpoint is gated behind allowPerRequestConfig=true because 
per-request
      * configuration could enable dangerous operations.
      */
     @POST
diff --git 
a/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/CXFTestBase.java
 
b/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/CXFTestBase.java
index 888cfe22e5..f081a5fc01 100644
--- 
a/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/CXFTestBase.java
+++ 
b/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/CXFTestBase.java
@@ -216,7 +216,7 @@ public abstract class CXFTestBase {
             PipesParsingHelper pipesParsingHelper = new 
PipesParsingHelper(this.pipesParser, pipesConfig,
                     inputTempDirectory, getUnpackEmitterBasePath(), false);
 
-            TikaResource.init(tika, new ServerStatus(), pipesParsingHelper, 
isEnableUnsecureFeatures());
+            TikaResource.init(tika, new ServerStatus(), pipesParsingHelper, 
isAllowPerRequestConfig());
         } finally {
             // Only delete tika config, keep pipes config for child processes
             Files.deleteIfExists(tmp);
@@ -372,7 +372,7 @@ public abstract class CXFTestBase {
      * the production default. Tests that POST a multipart "config" part must 
override
      * this to return true, otherwise the config part is rejected with 403.
      */
-    protected boolean isEnableUnsecureFeatures() {
+    protected boolean isAllowPerRequestConfig() {
         return false;
     }
 
diff --git 
a/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/ConfigEndpointSecurityEnabledTest.java
 
b/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/ConfigEndpointSecurityEnabledTest.java
index 0646f75ecd..e7335bf7ea 100644
--- 
a/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/ConfigEndpointSecurityEnabledTest.java
+++ 
b/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/ConfigEndpointSecurityEnabledTest.java
@@ -37,7 +37,7 @@ import org.apache.tika.server.core.resource.TikaResource;
 import org.apache.tika.server.core.writer.JSONMessageBodyWriter;
 
 /**
- * Tests that /config endpoints work when enableUnsecureFeatures=true.
+ * Tests that /config endpoints work when allowPerRequestConfig=true.
  */
 public class ConfigEndpointSecurityEnabledTest extends CXFTestBase {
 
@@ -55,7 +55,7 @@ public class ConfigEndpointSecurityEnabledTest extends 
CXFTestBase {
         List<Object> providers = new ArrayList<>();
         providers.add(new TikaServerParseExceptionMapper(false));
         providers.add(new JSONMessageBodyWriter());
-        // Add security filter with enableUnsecureFeatures=true
+        // Add security filter with allowPerRequestConfig=true
         providers.add(new ConfigEndpointSecurityFilter(true));
         sf.setProviders(providers);
     }
diff --git 
a/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/ConfigEndpointSecurityTest.java
 
b/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/ConfigEndpointSecurityTest.java
index cbb7b5aa86..314574899a 100644
--- 
a/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/ConfigEndpointSecurityTest.java
+++ 
b/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/ConfigEndpointSecurityTest.java
@@ -38,7 +38,7 @@ import 
org.apache.tika.server.core.writer.JSONMessageBodyWriter;
 
 /**
  * Tests for ConfigEndpointSecurityFilter.
- * Verifies that /config endpoints are gated behind enableUnsecureFeatures.
+ * Verifies that /config endpoints are gated behind allowPerRequestConfig.
  */
 public class ConfigEndpointSecurityTest extends CXFTestBase {
 
@@ -56,7 +56,7 @@ public class ConfigEndpointSecurityTest extends CXFTestBase {
         List<Object> providers = new ArrayList<>();
         providers.add(new TikaServerParseExceptionMapper(false));
         providers.add(new JSONMessageBodyWriter());
-        // Add security filter with enableUnsecureFeatures=false
+        // Add security filter with allowPerRequestConfig=false
         providers.add(new ConfigEndpointSecurityFilter(false));
         sf.setProviders(providers);
     }
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 d4920f04fe..56b5ffd0cb 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
@@ -45,7 +45,8 @@ public class TikaServerConfigTest extends TikaTest {
         CommandLine emptyCommandLine = parser.parse(new Options(), new 
String[]{});
         Path path = getConfigPath(getClass(), "tika-config-server.json");
         TikaServerConfig config = TikaServerConfig.load(path, 
emptyCommandLine, settings);
-        assertEquals(true, config.isEnableUnsecureFeatures());
+        assertTrue(config.isAllowPipes());
+        assertTrue(config.isAllowPerRequestConfig());
     }
 
     @Test
@@ -55,7 +56,8 @@ public class TikaServerConfigTest extends TikaTest {
         CommandLine emptyCommandLine = parser.parse(new Options(), new 
String[]{});
         Path path = getConfigPath(getClass(), 
"tika-config-server-fetchers-emitters.json");
         TikaServerConfig config = TikaServerConfig.load(path, 
emptyCommandLine, settings);
-        assertEquals(true, config.isEnableUnsecureFeatures());
+        assertTrue(config.isAllowPipes());
+        assertTrue(config.isAllowPerRequestConfig());
     }
 
     @Test
@@ -79,11 +81,11 @@ public class TikaServerConfigTest extends TikaTest {
     }
 
     @Test
-    public void testUnsecureEndpointRequiresEnableUnsecureFeatures() throws 
Exception {
-        // Selecting /pipes (or /async, /status) without 
enableUnsecureFeatures must fail
-        // at config load, forcing an explicit opt-in.
+    public void testPipesEndpointRequiresAllowPipes() throws Exception {
+        // Selecting /pipes (or /async) without allowPipes must fail at config 
load,
+        // forcing an explicit opt-in.
         CommandLineParser parser = new DefaultParser();
-        Path path = getConfigPath(getClass(), 
"tika-config-server-pipes-no-unsecure.json");
+        Path path = getConfigPath(getClass(), 
"tika-config-server-pipes-no-flags.json");
         CommandLine commandLine = parser.parse(new Options()
                 .addOption(Option
                         .builder("c")
@@ -94,14 +96,14 @@ public class TikaServerConfigTest extends TikaTest {
                 .toString())});
         TikaConfigException ex = assertThrows(TikaConfigException.class,
                 () -> TikaServerConfig.load(commandLine));
-        assertContains("enableUnsecureFeatures", ex.getMessage());
+        assertContains("allowPipes", ex.getMessage());
         assertContains("pipes", ex.getMessage());
     }
 
     @Test
-    public void testUnsecureEndpointAllowedWithEnableUnsecureFeatures() throws 
Exception {
-        // tika-config-server-basic.json selects the 'status' endpoint 
together with
-        // enableUnsecureFeatures=true, so it must load without error.
+    public void testEndpointsWithCapabilitiesLoad() throws Exception {
+        // tika-config-server-basic.json selects rmeta/status/tika together 
with
+        // allowPipes + allowPerRequestConfig, so it must load without error.
         CommandLineParser parser = new DefaultParser();
         Path path = getConfigPath(getClass(), "tika-config-server-basic.json");
         CommandLine commandLine = parser.parse(new Options()
@@ -113,7 +115,27 @@ public class TikaServerConfigTest extends TikaTest {
                 .toAbsolutePath()
                 .toString())});
         TikaServerConfig config = TikaServerConfig.load(commandLine);
-        assertTrue(config.isEnableUnsecureFeatures());
+        assertTrue(config.isAllowPipes());
+        assertTrue(config.isAllowPerRequestConfig());
+    }
+
+    @Test
+    public void testStatusEndpointDoesNotRequireAllowPipes() throws Exception {
+        // status is a plain opt-in endpoint: selecting it (without 
allowPipes) must
+        // load without error, unlike the pipes/async endpoints.
+        CommandLineParser parser = new DefaultParser();
+        Path path = getConfigPath(getClass(), 
"tika-config-server-status-no-flags.json");
+        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);
+        assertFalse(config.isAllowPipes());
+        assertFalse(config.isAllowPerRequestConfig());
     }
 
     @Test
diff --git 
a/tika-server/tika-server-core/src/test/resources/config-examples/server-basic.json
 
b/tika-server/tika-server-core/src/test/resources/config-examples/server-basic.json
index d133c0deeb..af5f016a34 100644
--- 
a/tika-server/tika-server-core/src/test/resources/config-examples/server-basic.json
+++ 
b/tika-server/tika-server-core/src/test/resources/config-examples/server-basic.json
@@ -3,7 +3,8 @@
     "port": 9998,
     "host": "localhost",
     "taskTimeoutMillis": 300000,
-    "enableUnsecureFeatures": false
+    "allowPipes": false,
+    "allowPerRequestConfig": false
   },
   "parsers": [
     {
diff --git 
a/tika-server/tika-server-core/src/test/resources/configs/cxf-test-base-template.json
 
b/tika-server/tika-server-core/src/test/resources/configs/cxf-test-base-template.json
index b2c0ef4118..664b2599e6 100644
--- 
a/tika-server/tika-server-core/src/test/resources/configs/cxf-test-base-template.json
+++ 
b/tika-server/tika-server-core/src/test/resources/configs/cxf-test-base-template.json
@@ -25,7 +25,8 @@
   },
   "server": {
     "port": 9999,
-    "enableUnsecureFeatures": true,
+    "allowPipes": true,
+    "allowPerRequestConfig": true,
     "endpoints": [
       "rmeta",
       "status",
diff --git 
a/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-basic.json
 
b/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-basic.json
index 41d8acb90b..ed88d33f02 100644
--- 
a/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-basic.json
+++ 
b/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-basic.json
@@ -8,7 +8,8 @@
   },
   "server": {
     "port": 9999,
-    "enableUnsecureFeatures": true,
+    "allowPipes": true,
+    "allowPerRequestConfig": true,
     "endpoints": [
       "rmeta",
       "status",
diff --git 
a/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-emitter.json
 
b/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-emitter.json
index 60a672a866..affc5f9a2f 100644
--- 
a/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-emitter.json
+++ 
b/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-emitter.json
@@ -8,7 +8,8 @@
   },
   "server": {
     "port": 9999,
-    "enableUnsecureFeatures": true,
+    "allowPipes": true,
+    "allowPerRequestConfig": true,
     "endpoints": [
       "emit",
       "async",
diff --git 
a/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-fetcher-template.json
 
b/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-fetcher-template.json
index 7348181239..dd42ae4f2c 100644
--- 
a/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-fetcher-template.json
+++ 
b/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-fetcher-template.json
@@ -8,7 +8,8 @@
   },
   "server": {
     "port": "PORT",
-    "enableUnsecureFeatures": true,
+    "allowPipes": true,
+    "allowPerRequestConfig": true,
     "endpoints": [
       "tika"
     ]
diff --git 
a/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-fetchers-emitters.json
 
b/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-fetchers-emitters.json
index b500cdd21b..040398522d 100644
--- 
a/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-fetchers-emitters.json
+++ 
b/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-fetchers-emitters.json
@@ -1,7 +1,8 @@
 {
   "server": {
     "port": 9999,
-    "enableUnsecureFeatures": true,
+    "allowPipes": true,
+    "allowPerRequestConfig": true,
     "endpoints": [
       "rmeta"
     ]
diff --git 
a/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-pipes-basic.json
 
b/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-pipes-basic.json
index 2b7ed5a22b..58a6a8e638 100644
--- 
a/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-pipes-basic.json
+++ 
b/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-pipes-basic.json
@@ -8,7 +8,8 @@
   },
   "server": {
     "port": 9999,
-    "enableUnsecureFeatures": true,
+    "allowPipes": true,
+    "allowPerRequestConfig": true,
     "endpoints": [
       "rmeta",
       "status",
diff --git 
a/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-pipes-no-unsecure.json
 
b/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-pipes-no-flags.json
similarity index 100%
copy from 
tika-server/tika-server-core/src/test/resources/configs/tika-config-server-pipes-no-unsecure.json
copy to 
tika-server/tika-server-core/src/test/resources/configs/tika-config-server-pipes-no-flags.json
diff --git 
a/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-pipes-no-unsecure.json
 
b/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-status-no-flags.json
similarity index 51%
rename from 
tika-server/tika-server-core/src/test/resources/configs/tika-config-server-pipes-no-unsecure.json
rename to 
tika-server/tika-server-core/src/test/resources/configs/tika-config-server-status-no-flags.json
index 262f29d7a8..0b94a296cc 100644
--- 
a/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-pipes-no-unsecure.json
+++ 
b/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-status-no-flags.json
@@ -1,16 +1,9 @@
 {
-  "fetchers": {
-    "file-system-fetcher": {
-      "file-system-fetcher": {
-        "allowAbsolutePaths": true
-      }
-    }
-  },
   "server": {
     "port": 9999,
     "endpoints": [
       "tika",
-      "pipes"
+      "status"
     ]
   },
   "pipes": {
diff --git 
a/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-tls.json
 
b/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-tls.json
index 7c526aa906..02e866bf7d 100644
--- 
a/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-tls.json
+++ 
b/tika-server/tika-server-core/src/test/resources/configs/tika-config-server-tls.json
@@ -1,7 +1,8 @@
 {
   "server": {
     "port": 9999,
-    "enableUnsecureFeatures": true,
+    "allowPipes": true,
+    "allowPerRequestConfig": true,
     "endpoints": [
       "rmeta"
     ],
diff --git 
a/tika-server/tika-server-core/src/test/resources/configs/tika-config-server.json
 
b/tika-server/tika-server-core/src/test/resources/configs/tika-config-server.json
index b500cdd21b..040398522d 100644
--- 
a/tika-server/tika-server-core/src/test/resources/configs/tika-config-server.json
+++ 
b/tika-server/tika-server-core/src/test/resources/configs/tika-config-server.json
@@ -1,7 +1,8 @@
 {
   "server": {
     "port": 9999,
-    "enableUnsecureFeatures": true,
+    "allowPipes": true,
+    "allowPerRequestConfig": true,
     "endpoints": [
       "rmeta"
     ]
diff --git 
a/tika-server/tika-server-core/src/test/resources/configs/tika-config-timeout-100ms.json
 
b/tika-server/tika-server-core/src/test/resources/configs/tika-config-timeout-100ms.json
index 8cf3bdc5e5..c0534fe6d6 100644
--- 
a/tika-server/tika-server-core/src/test/resources/configs/tika-config-timeout-100ms.json
+++ 
b/tika-server/tika-server-core/src/test/resources/configs/tika-config-timeout-100ms.json
@@ -1,6 +1,7 @@
 {
   "server": {
-    "enableUnsecureFeatures": true,
+    "allowPipes": true,
+    "allowPerRequestConfig": true,
     "enableLogFile": true
   },
   "parse-context": {
diff --git 
a/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/MetadataResourceTest.java
 
b/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/MetadataResourceTest.java
index 8ae59274c3..0a0a25b761 100644
--- 
a/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/MetadataResourceTest.java
+++ 
b/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/MetadataResourceTest.java
@@ -59,7 +59,7 @@ public class MetadataResourceTest extends CXFTestBase {
     private static final String META_PATH = "/meta";
 
     @Override
-    protected boolean isEnableUnsecureFeatures() {
+    protected boolean isAllowPerRequestConfig() {
         return true; // exercises per-request config injection
     }
 
diff --git 
a/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/RecursiveMetadataResourceTest.java
 
b/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/RecursiveMetadataResourceTest.java
index 936e202f1a..3203950a34 100644
--- 
a/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/RecursiveMetadataResourceTest.java
+++ 
b/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/RecursiveMetadataResourceTest.java
@@ -64,7 +64,7 @@ public class RecursiveMetadataResourceTest extends 
CXFTestBase {
     private static final String TEST_RECURSIVE_DOC = 
"test-documents/test_recursive_embedded.docx";
 
     @Override
-    protected boolean isEnableUnsecureFeatures() {
+    protected boolean isAllowPerRequestConfig() {
         return true; // exercises per-request config injection
     }
 
diff --git 
a/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/TikaResourceTest.java
 
b/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/TikaResourceTest.java
index 58ef99ecc6..b8ca2a7c3d 100644
--- 
a/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/TikaResourceTest.java
+++ 
b/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/TikaResourceTest.java
@@ -65,7 +65,7 @@ public class TikaResourceTest extends CXFTestBase {
     private static final int UNPROCESSEABLE = 422;
 
     @Override
-    protected boolean isEnableUnsecureFeatures() {
+    protected boolean isAllowPerRequestConfig() {
         return true; // exercises per-request config injection
     }
 
diff --git 
a/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/UnpackerResourceConfigDisabledTest.java
 
b/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/UnpackerResourceConfigDisabledTest.java
index 5e96ff7604..4f6f73175f 100644
--- 
a/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/UnpackerResourceConfigDisabledTest.java
+++ 
b/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/UnpackerResourceConfigDisabledTest.java
@@ -42,7 +42,7 @@ import org.apache.tika.server.core.writer.TarWriter;
 import org.apache.tika.server.core.writer.ZipWriter;
 
 /**
- * Verifies that /unpack and /unpack/all honor enableUnsecureFeatures: when 
per-request
+ * Verifies that /unpack and /unpack/all honor allowPerRequestConfig: when 
per-request
  * config injection is disabled (the default), a multipart "config" part is 
rejected
  * with 403. Counterpart to {@link UnpackerResourceWithConfigTest}, which 
covers the
  * enabled path.
@@ -52,7 +52,7 @@ public class UnpackerResourceConfigDisabledTest extends 
CXFTestBase {
     private static final String BASE_PATH = "/unpack";
     private static final String ALL_PATH = BASE_PATH + "/all";
 
-    // isEnableUnsecureFeatures() is intentionally NOT overridden; it defaults 
to false
+    // isAllowPerRequestConfig() is intentionally NOT overridden; it defaults 
to false
     // so a config part must be rejected.
 
     @Override
diff --git 
a/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/UnpackerResourceTest.java
 
b/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/UnpackerResourceTest.java
index 6d1c0149cc..ab458d0a9c 100644
--- 
a/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/UnpackerResourceTest.java
+++ 
b/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/UnpackerResourceTest.java
@@ -99,7 +99,7 @@ public class UnpackerResourceTest extends CXFTestBase {
     private Path unpackTempDir;
 
     @Override
-    protected boolean isEnableUnsecureFeatures() {
+    protected boolean isAllowPerRequestConfig() {
         return true; // exercises per-request config injection
     }
 
diff --git 
a/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/UnpackerResourceWithConfigTest.java
 
b/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/UnpackerResourceWithConfigTest.java
index 6f35a72eae..291cd80adb 100644
--- 
a/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/UnpackerResourceWithConfigTest.java
+++ 
b/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/UnpackerResourceWithConfigTest.java
@@ -73,7 +73,7 @@ public class UnpackerResourceWithConfigTest extends 
CXFTestBase {
     private Path unpackTempDir;
 
     @Override
-    protected boolean isEnableUnsecureFeatures() {
+    protected boolean isAllowPerRequestConfig() {
         return true; // exercises per-request config injection
     }
 
diff --git 
a/tika-server/tika-server-standard/src/test/resources/configs/cxf-test-base-template.json
 
b/tika-server/tika-server-standard/src/test/resources/configs/cxf-test-base-template.json
index afc750acab..f0bfba8eb0 100644
--- 
a/tika-server/tika-server-standard/src/test/resources/configs/cxf-test-base-template.json
+++ 
b/tika-server/tika-server-standard/src/test/resources/configs/cxf-test-base-template.json
@@ -27,7 +27,8 @@
     "port": 9999,
     "taskTimeoutMillis": "TIMEOUT_MILLIS",
     "taskPulseMillis": 100,
-    "enableUnsecureFeatures": true,
+    "allowPipes": true,
+    "allowPerRequestConfig": true,
     "endpoints": [
       "rmeta",
       "status",

Reply via email to