Skip to content

Commit 117a166

Browse files
committed
Merge branch 'main' into TIKA-4809-stage-7
2 parents c801786 + dbf709e commit 117a166

20 files changed

Lines changed: 298 additions & 73 deletions

File tree

.mvn/extensions.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@
33
<extension>
44
<groupId>eu.maveniverse.maven.nisse</groupId>
55
<artifactId>extension</artifactId>
6-
<version>0.9.3</version>
6+
<version>0.9.4</version>
77
</extension>
88
</extensions>

docs/modules/ROOT/pages/advanced/setting-limits.adoc

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ This is the same configuration tested in `AllLimitsTest.java`:
5252
"zipBombRatio": 100
5353
},
5454
"timeout-limits": {
55-
"taskTimeoutMillis": 60000
55+
"totalTaskTimeoutMillis": 3600000,
56+
"progressTimeoutMillis": 60000
5657
},
5758
"standard-metadata-limiter-factory": {
5859
"maxTotalBytes": 1048576,
@@ -243,27 +244,71 @@ See test: `tika-serialization/src/test/java/org/apache/tika/config/OutputLimitsT
243244

244245
== Timeout Limits
245246

246-
The `TimeoutLimits` class controls time-based limits for parsing operations.
247+
The `TimeoutLimits` class controls time-based limits for parsing operations. Tika 4.x
248+
uses a *two-tier* timeout: one bound on total wall-clock time, and one on time elapsed
249+
since the parser last reported progress.
247250

248251
=== Configuration Options
249252

250253
[cols="2,1,3"]
251254
|===
252255
|Setting |Default |Description
253256

254-
|`taskTimeoutMillis`
255-
|60000 (1 minute)
256-
|Maximum time in milliseconds for a parse operation to complete.
257+
|`totalTaskTimeoutMillis`
258+
|3600000 (1 hour)
259+
|Maximum wall-clock time in milliseconds for the entire parse task.
260+
261+
|`progressTimeoutMillis`
262+
|120000 (2 minutes)
263+
|Maximum time in milliseconds since the parser last reported progress. Catches
264+
infinite loops and hung processes.
257265
|===
258266

267+
[IMPORTANT]
268+
====
269+
Which bound actually applies depends on whether the parser reports progress.
270+
A parser that never calls `TikaProgressTracker.update()` never advances the
271+
timer, so it effectively gets `progressTimeoutMillis` as its total timeout —
272+
matching the single-timeout behavior of earlier versions.
273+
274+
In practice only long-running parsers report progress: `TesseractOCRParser`,
275+
`Tess4JParser`, `ExternalParser`, `GDALParser`, the VLM and image-embedding
276+
parsers, and `StringsParser`. Everything else — including container parsing and
277+
embedded-document recursion — does not.
278+
279+
Those parsers report progress *after* each external-process invocation
280+
completes, not while one is running. So a document that needs many OCR calls
281+
can extend well past `progressTimeoutMillis`, because each finished page resets
282+
the timer — but a *single* call that runs longer than `progressTimeoutMillis`
283+
is still cut short.
284+
285+
Because of this, `progressTimeoutMillis` also caps how long any single external
286+
process may run. Parsers that spawn processes size their own timeout via
287+
`TimeoutLimits.getProcessTimeoutMillis(context, ...)`, which never allows a
288+
value beyond `progressTimeoutMillis`, so the process is stopped just before the
289+
progress watchdog would fire.
290+
291+
The shipped defaults are aligned: `progressTimeoutMillis` is 120 seconds and
292+
the bundled process-spawning parsers (OCR, strings, inference) each default to
293+
a 120-second per-process timeout, so those defaults are reachable. **If you
294+
raise a per-process timeout above 120 seconds, raise `progressTimeoutMillis`
295+
to match** — raising the parser's own timeout alone has no effect.
296+
297+
For most documents — anything without one of the parsers above in the chain —
298+
the effective ceiling is `progressTimeoutMillis`, not `totalTaskTimeoutMillis`.
299+
Lower `totalTaskTimeoutMillis` if you need a hard ceiling on OCR-heavy or
300+
external-process work regardless of progress.
301+
====
302+
259303
=== JSON Configuration
260304

261305
[source,json]
262306
----
263307
{
264308
"parse-context": {
265309
"timeout-limits": {
266-
"taskTimeoutMillis": 120000
310+
"totalTaskTimeoutMillis": 7200000,
311+
"progressTimeoutMillis": 120000
267312
}
268313
}
269314
}
@@ -275,7 +320,7 @@ Configuration file: `tika-serialization/src/test/resources/configs/timeout-limit
275320

276321
[source,java]
277322
----
278-
TimeoutLimits limits = new TimeoutLimits(120000);
323+
TimeoutLimits limits = new TimeoutLimits(7200000, 120000);
279324
context.set(TimeoutLimits.class, limits);
280325
281326
// Helper method

docs/modules/ROOT/pages/developers/serialization.adoc

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,26 @@ The serialization system implements a security allowlist:
241241
This prevents attacks where malicious JSON specifies dangerous classes
242242
for instantiation.
243243

244+
[IMPORTANT]
245+
====
246+
The allowlist governs *which components may be instantiated* from JSON. It does
247+
not restrict *how an already-loaded component may be configured*.
248+
249+
Self-configuring components — which includes every `Parser`, since `Parser`
250+
extends `SelfConfiguring` — are skipped by the wire-block scan
251+
(`ParseContextDeserializer.assertNoBlockedComponents`): their config subtree is
252+
passed through to the component unexamined. So while a request cannot bind a new
253+
`Parser` from the wire, a request carrying
254+
`{"parse-context": {"pdf-parser": {"ocr": {"strategy": "OCR_AND_TEXT_EXTRACTION"}}}}`
255+
will reach `PDFParser` and take effect.
256+
257+
That is why per-request configuration is gated separately by
258+
`allowPerRequestConfig`, which is off by default. Treat "the caller may supply
259+
per-request config" as equivalent to "the caller may set any parser option,
260+
including options that spawn external processes such as OCR" — not as something
261+
the allowlist constrains.
262+
====
263+
244264
[source,java]
245265
----
246266
// This will FAIL - class not registered

docs/modules/ROOT/pages/migration-to-4x/design-notes-4x.adoc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,14 @@ may be bound from the wire; `Parser`, `Detector`, `Renderer`, and similar are
7474
blocked before anything is constructed. See
7575
xref:developers/serialization.adoc[Serialization and Configuration].
7676

77+
Note the boundary: the allowlist blocks *binding a component* from the wire, not
78+
*configuring one that is already loaded*. Self-configuring components — every
79+
`Parser` among them — have their config subtree passed through unscanned, so a
80+
per-request config can still set parser options (including ones that spawn
81+
external processes, such as OCR). This is why `allowPerRequestConfig` is a
82+
separate gate and is off by default; the allowlist alone does not make
83+
per-request configuration safe to expose.
84+
7785
=== Implementation Challenges
7886

7987
* Converted code to true Java beans with matching getters/setters

docs/modules/ROOT/pages/migration-to-4x/migrating-tika-server-4x.adoc

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,31 @@ for error text should check `tk:exception:container-exception` (full-object
135135
endpoints) or the `422` body (`/meta/\{field}`, populated only when
136136
`returnStackTrace=true`).
137137
138+
Two changes to the returned metadata come with this, neither of which produces an
139+
error:
140+
141+
* **`/meta` no longer returns a `language` field.** Language detection previously ran
142+
inline on this endpoint via a dedicated content handler that buffered text solely to
143+
detect the language, which meant holding the document text twice to populate one
144+
field. That handler was removed. `/meta` deliberately parses with the `ignore`
145+
content handler, so there is no text for a language detector to work from.
146+
+
147+
**Migration:** configure a language-detection metadata filter
148+
(`charsoup-metadata-filter`, `optimaize`, or `opennlp`) and use `/rmeta` or
149+
`/tika/json`, which capture content. The detected value arrives as
150+
`tk:detected-language`, with `tk:detected-language-confidence`. Note that these
151+
filters read `tk:content`, so they are no-ops on `/meta` and on any endpoint
152+
configured with the `ignore` handler.
153+
154+
* **`/meta` now sets `tk:exception:embedded-depth-limit-reached` on any document
155+
with embedded content.** `/meta` suppresses embedded parsing by setting an embedded
156+
depth limit of `0`, and reaching a limit is recorded. The previous implementation
157+
suppressed embedded documents by a different mechanism that recorded nothing. The
158+
flag is expected on this endpoint and does not indicate a truncated result.
159+
+
160+
**Migration:** clients that alert on the presence of any `tk:exception:*` key should
161+
exclude this one for `/meta`.
162+
138163
=== Accept Header Routing Removed
139164
140165
The `/tika` endpoint no longer routes based on `Accept` headers. Use explicit paths instead:
@@ -157,7 +182,24 @@ The following `TikaServerConfig` options have been removed:
157182
158183
=== `/pipes` and `/async` Require `allowPipes`; Per-Request Config Requires `allowPerRequestConfig`
159184
160-
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:
185+
This replaces the `enableUnsecureFeatures` flag that alpha-1 briefly used, and before
186+
that, enabling these capabilities simply by listing endpoints under `server.endpoints`.
187+
`enableUnsecureFeatures` no longer exists: a config that still carries it fails to start
188+
with an "Unrecognized field" error naming the key, rather than silently ignoring it.
189+
The single flag has been split into two, so that granting batch/fetcher access and
190+
granting per-request parser configuration are separate decisions:
191+
192+
|===
193+
|Was |Now
194+
195+
|`enableUnsecureFeatures: true` (to use `/pipes` or `/async`)
196+
|`allowPipes: true`
197+
198+
|`enableUnsecureFeatures: true` (to send per-request config)
199+
|`allowPerRequestConfig: true`
200+
|===
201+
202+
The capabilities are two default-`false` flags in the `server` section:
161203
162204
* `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.
163205
* `allowPerRequestConfig` gates per-request parser configuration: the `/config` family of endpoints and the multipart `config` part. When off, such requests are rejected with 403.
@@ -189,7 +231,7 @@ All tika-server configurations must now include a `pipes` section and a `file-sy
189231
"fetchers": {
190232
"file-system-fetcher": {
191233
"file-system-fetcher": {
192-
"allowAbsolutePaths": true
234+
"basePath": "/path/to/your/input"
193235
}
194236
}
195237
},
@@ -205,6 +247,23 @@ All tika-server configurations must now include a `pipes` section and a `file-sy
205247
}
206248
----
207249
250+
[IMPORTANT]
251+
====
252+
Set `basePath` to a directory that contains only the documents you intend the
253+
server to read. It is the filesystem sandbox: the fetcher rejects any fetch key
254+
that resolves outside it, including absolute paths and `../` traversal, and
255+
re-checks after resolving symlinks.
256+
257+
Setting `allowAbsolutePaths` instead of `basePath` turns that sandbox off
258+
entirely — fetch keys are then used as raw absolute paths, so any caller who can
259+
reach `/pipes` can read any file the server process can read. The matching
260+
emitter setting is worse: it grants arbitrary file *write*. `allowAbsolutePaths`
261+
is not a relaxation of `basePath`; it is what you get when there is no
262+
`basePath` at all, and it is a no-op when `basePath` is set. Use it only if you
263+
genuinely intend an unsandboxed fetcher and have restricted access to the server
264+
by other means.
265+
====
266+
208267
[IMPORTANT]
209268
====
210269
`numClients` is not boilerplate to copy unchanged from this example. In 3.x,

docs/modules/ROOT/pages/pipes/plugins/filesystem.adoc

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,16 +81,16 @@ The outer key (`fsf`) is the fetcher ID — referenced by `pipesIterator.fetcher
8181
|Field |Default |Description
8282

8383
|`basePath`
84-
|_required_
85-
|Base directory for fetch operations. Fetch keys are resolved relative to this path.
84+
|_none_
85+
|Base directory for fetch operations. Fetch keys are resolved relative to this path and must stay inside it. Not technically required, but omitting it disables containment entirely — see `allowAbsolutePaths` below and <<security-notes>>.
8686

8787
|`extractFileSystemMetadata`
8888
|`false`
8989
|When `true`, attach file size, created, and modified timestamps to the metadata of each fetched document.
9090

9191
|`allowAbsolutePaths`
9292
|`false`
93-
|When `true`, fetch keys may be absolute paths and `basePath` may be omitted. Use sparingly — see <<security-notes>>.
93+
|Permission to run *without* a `basePath`. It is not a relaxation of `basePath` — see <<security-notes>>.
9494
|===
9595

9696
[#file-system-emitter]
@@ -253,6 +253,7 @@ Tradeoffs:
253253
[#security-notes]
254254
== Security Notes
255255

256-
* **`basePath` is a sandbox boundary.** The fetcher and emitter reject fetch/emit keys that resolve outside `basePath`. Do not set `allowAbsolutePaths=true` unless the source of fetch keys is fully trusted — an attacker-controlled fetch key could otherwise read arbitrary files.
257-
* **Symlinks are followed.** A symlink under `basePath` pointing outside `basePath` may still be readable. If you need strict containment, do not allow symlinks in your input tree.
256+
* **`basePath` is the sandbox boundary, and it is the only one.** With `basePath` set, the fetcher and emitter reject any key that resolves outside it, including absolute paths and `../` traversal. `allowAbsolutePaths` has no effect in this state.
257+
* **Without `basePath` there is no containment at all.** The key is used as a raw absolute path, and the containment checks are skipped entirely. `allowAbsolutePaths=true` is how you assert that you intend this; it is a switch between two states, not a dial that loosens `basePath`. For the fetcher this means any file the process can read; for the emitter, any file it can write. Use it only when fetch/emit keys come from a fully trusted source and access to the service is restricted by other means.
258+
* **Symlink containment differs between fetcher and emitter.** The fetcher re-checks with `toRealPath()`, so a symlink under `basePath` pointing outside it is rejected. The emitter does not: it checks only the normalized path, so a symlink already present under its `basePath` can be written through. Do not rely on symlinks being contained on the emit side.
258259
* **Output directories are created automatically.** The emitter creates intermediate directories as needed. Make sure the process's umask is appropriate for the data being written.

docs/modules/ROOT/pages/pipes/plugins/http.adoc

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ include::example$pipes-http-fetcher.json[]
9292

9393
|`maxRedirects`
9494
|`0`
95-
|Maximum number of redirects to follow. `0` means follow none.
95+
|Maximum number of redirects to follow. `0` means follow none. Not applied to range requests — see <<security-notes>>.
9696

9797
|`maxSpoolSize`
9898
|`-1`
@@ -123,6 +123,38 @@ include::example$pipes-http-fetcher.json[]
123123
|Base64-encoded private key for asymmetric (RSA/ECDSA) JWT signing. Mutually exclusive with `jwtSecret`.
124124
|===
125125

126+
[#security-notes]
127+
== Security Notes
128+
129+
This fetcher makes the server issue HTTP requests to a URL supplied as the fetch key.
130+
That is a server-side request forgery primitive by design, and it is not constrained by
131+
this plugin. Treat the source of fetch keys as fully trusted, and restrict access to any
132+
endpoint that can reach it (`/pipes`, `/async`).
133+
134+
* **The fetch key is used as the URL with no validation.** It is passed straight to
135+
`new HttpGet(fetchKey)`. There is no scheme allowlist, no host denylist, and no check
136+
against loopback, link-local, or RFC1918 addresses. A fetch key of
137+
`http://169.254.169.254/...` reaches a cloud metadata endpoint like any other URL. The
138+
resolved address is recorded in metadata *after* the fetch, not consulted before it.
139+
Schemes other than `http`/`https` fail only because no other scheme is registered in
140+
the connection manager — that is a side effect of the transport setup, not a check.
141+
142+
* **TLS certificates are not verified, and this is not configurable here.** The
143+
underlying client defaults to `verifySsl=false`, which installs an accept-everything
144+
trust strategy and `NoopHostnameVerifier`. `HttpFetcherConfig` exposes no `verifySsl`
145+
setting, so an http-fetcher config cannot turn verification on. Do not use this fetcher
146+
to retrieve anything whose authenticity matters over an untrusted network.
147+
148+
* **`maxRedirects` does not apply to range requests.** The main `fetch` builds a
149+
`RequestConfig` from `maxRedirects`; the `startRange`/`endRange` overload sets no
150+
request config at all and therefore uses the client's defaults (redirects enabled).
151+
A `maxRedirects: 0` setting does not stop redirects on a range fetch.
152+
153+
* **The redirect host allowlist is currently inert.** `CustomRedirectStrategy` will
154+
refuse a redirect to a host outside `allowedHostsForRedirect`, but that set is never
155+
populated from any configuration path, and the check is skipped when the set is empty.
156+
Do not rely on it to contain redirects.
157+
126158
[#notes]
127159
== Notes
128160

docs/modules/ROOT/pages/pipes/timeouts.adoc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ Tika Pipes uses a two-tier timeout system to handle both long-running tasks and
2323

2424
* **`progressTimeoutMillis`** -- Maximum time between progress updates.
2525
If no progress is reported within this interval, the task is considered stalled and killed.
26-
Default: `60000` (1 minute).
26+
Default: `120000` (2 minutes).
27+
This also caps how long any single external process (OCR, `ExternalParser`, VLM)
28+
may run, since those parsers report progress only once a process completes.
2729

2830
* **`totalTaskTimeoutMillis`** -- Maximum wall-clock time for an entire task.
2931
Even if the parser is making progress, the task is killed after this time.

docs/modules/ROOT/pages/security.adoc

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,9 @@ and `allowComponentManagement` — the latter lets clients add, modify, and dele
5656
and read back stored configs, which can contain secrets — are off by default. Run it only behind
5757
network controls and, ideally, mutual TLS. See xref:using-tika/grpc/index.adoc[Tika gRPC].
5858

59-
For the upgrade from the former `enableUnsecureFeatures` flag, see
60-
xref:migration-to-4x/migrating-tika-server-4x.adoc[Migrating tika-server to 4.x].
59+
For the upgrade from the former `enableUnsecureFeatures` flag, which is now split into
60+
`allowPipes` and `allowPerRequestConfig`, see
61+
xref:migration-to-4x/migrating-tika-server-4x.adoc#_pipes_and_async_require_allowpipes_per_request_config_requires_allowperrequestconfig[Migrating tika-server to 4.x].
6162

6263
== Known Vulnerabilities
6364

0 commit comments

Comments
 (0)