Security

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

Baseline date: 2026-10-12. This page was written against Apache Tika 4.1.0 (the latest release on Maven Central on that date) with the 3.3.x differences noted, Java 21 and later for the examples (Tika 4.x needs Java 17), Spring Boot 4.x on Spring Framework 7 and the Docker image apache/tika:4.1.0. Tika exists to open files that somebody else made. In the Bookshelf intake service those files come from authors, publishers, scanners and e-mail, and any of them can be broken by accident or crafted on purpose. This page explains what the Tika project promises and what it leaves to you: the official security model, the trust boundaries of a deployment, why tika-server and tika-grpc must never face untrusted callers, the capability flags that limit what a caller can reach, external commands, XML external entities (XXE) and the XFA vulnerability, credentials, upload validation, the CVE history and how to stay updated.

Related pages: Robustness, Limits & Timeouts (the limits that bound a parse), Tika Pipes (the process isolation this page relies on), Tika Server (REST API), Tika gRPC, tika-app Command Line, Docker & Kubernetes Deployment, Configuration (tika-config.json and its environment-variable secrets), Extending Tika (writing an ExternalParser) and PDF Parsing (XFA in detail). For the limits of Apache POI itself (zip bombs, record sizes, DOCTYPE handling in Office files) see Office Files: Security & Untrusted Files, which this page does not repeat.

What was run for this page: a tika-server 4.1.0 from the official tika-server-standard-4.1.0.zip on Java 25 (default binding, the 403 for per-request configuration, the start-up refusal without allowPipes, the 413 request-size limit, TLS with an environment-variable keystore password, exception redaction and the refusal of a per-request redaction override), and three Java programs compiled against tika-core, tika-parsers-standard-package, tika-pipes-fork-parser and tika-serialization 4.1.0 (an upload gate, a gate in front of PipesForkParser, and an external parser plus FileCommandDetector configured with absolute paths). The CVE list was checked against the Tika security page and the CVE records on 2026-10-12. The reverse-proxy and Kubernetes examples were not run. The section What was verified and what was not has the details.

The security model

The Tika project publishes a short security model that decides what counts as a vulnerability. Reading it first saves a lot of wrong assumptions, because it is stricter than most people expect. It separates two risks and one assumption:

Risk Applies when Who is responsible

Untrusted data

Always: whenever Tika parses a file you did not create, however you run Tika.

The project fixes bugs and treats some failures as vulnerabilities (below). Containing a hostile file is the caller’s job.

Untrusted callers

Only when you expose tika-server or tika-grpc over a network.

The operator. The project says plainly that securing access is not its responsibility.

Trusted configuration

Always: the tika-config.json file and the command line come from the operator.

The operator. A setting that enables a dangerous capability does what it was asked.

Untrusted data: Tika is not a security boundary

The model states that Tika is used on untrusted data every day and that the project invests in handling it safely, but that Tika itself is not a security boundary. It depends on many third-party format libraries (PDFBox, POI, Commons Compress, metadata-extractor, and many more), any of which can contain an undiscovered flaw. The working assumption is that a hostile file may crash the parsing process, hang it or take it over. The defences are yours: sandboxing, network isolation, resource limits and least privilege, sized to your threat model.

Two further statements matter for an intake service:

  • Detection and extraction can be fooled. A file can be crafted so that type detection or text extraction gives a wrong answer. Do not use either as the only check in a high-risk decision (cross-domain filtering, malware gates, search that controls access).

  • Tika does not recognise "weird" files. Polyglots (a file that is valid as two formats), chimeras and other parser differentials are not flagged. Tika will happily extract one reading of such a file.

Denial of service: in-process versus Pipes

This is the rule most people get wrong. When you call AutoDetectParser (or the Tika facade, or any parser) directly, the parse runs on your thread and in your heap. The model says that denial of service is then not a security issue: an OutOfMemoryError, a StackOverflowError, an endless loop or a crash caused by a malformed file is a bug to report in JIRA, and containing it is the caller’s responsibility. The 4.1.x security page adds that the limits (output-limits.writeLimit, embedded-limits.maxCount, the metadata limiter) bound what a parse produces, not the work it does to produce it.

Process isolation changes this, and it is a mechanism rather than advice. Tika Pipes, PipesForkParser, tika-server and tika-grpc parse each document in a forked JVM. A parse that runs out of memory, hangs or crashes kills the fork, not your process: the fork is restarted and the caller gets a status (OOM, TIMEOUT or UNSPECIFIED_CRASH, HTTP 503 from tika-server) instead of a dead JVM.

The boundary is conditional. A document that defeats the limits of Pipes itself (escaping the heap ceiling, the timeout or the process boundary) is a bug in Tika that the project treats as a security issue.

What happened Security issue? What to do

An in-process AutoDetectParser.parse throws OutOfMemoryError on a crafted file

No: a bug

Report it in JIRA; move the parse out of process.

An in-process parse loops forever

No: a bug

Same. A thread cannot be stopped safely, a process can.

A forked parse runs out of memory or hangs and is killed

No: the sandbox worked

Handle the 503 / OOM / TIMEOUT status; record the file and move on.

A file makes a forked parse escape its heap ceiling or timeout, or affect the parent process

Yes

Report it privately to security@apache.org, not in a public issue.

A file makes Tika read a local file or reach a URL it should not (XXE, path traversal)

Yes, from a default configuration

Report it privately; see the CVE history below.

Tika’s output contains a javascript: link, a spreadsheet formula or HTML

No: output is untrusted by design

Sanitise before rendering, exporting or prompting.

A finding needs allowPerRequestConfig or allowPipes to have been switched on

No: a hardening suggestion

The operator enabled a powerful capability; the same access from a default configuration would be a vulnerability.

A setting is accepted but silently has no effect (a limit parsed and never enforced)

Yes

Report it: the operator believes they are protected and is not.

Extracted output is untrusted

Tika’s output is an extraction, not a sanitised rendering. The XHTML, Markdown, text and metadata carry whatever the file contained: hyperlinks with any URI scheme (including javascript: and data:), cell values that look like formulas, control characters and fragments of markup. The security model makes hostile content in the output the consumer’s responsibility. For Bookshelf that means:

  • escape the text before putting it in an HTML page (a review snippet, a search result highlight);

  • neutralise leading =, +, - and @ before writing extracted values to a CSV or spreadsheet (the Office Files security page has a compiled sanitiser);

  • treat extracted text sent to an LLM as untrusted input that may carry instructions (prompt injection): keep it in a delimited data section and never let it choose tools or recipients;

  • never use extracted metadata (a dc:creator, a file name inside a zip) as a path, a query or a command.

Configuration is trusted input

The tika-config.json file and the command line that start the process come from the operator. Tika rejects unknown keys at load time, so a typo fails loudly, and per-request configuration is off by default. The consequences:

  • Whoever can write tika-config.json can make Tika run programs (external-parser), read any directory a fetcher can reach and write wherever an emitter points. Treat the file as code: under version control, reviewed, mounted read-only.

  • Never build the file from request data, and never let a user choose which preset, parser or fetcher configuration is loaded beyond a fixed allow-list.

  • The directory given as tika.extras.dir (or mounted at /tika-extras in the Docker image) and the plugins/ directory are code locations: whatever jar is there runs with the full privileges of the Tika process.

Reporting a vulnerability

Report a suspected vulnerability privately to security@apache.org, following the Apache Software Foundation security process. Do not open a public JIRA issue or a GitHub pull request for it. Ordinary robustness bugs (an in-process OOM, a parser exception) go to the Tika JIRA as usual.

Trust boundaries of a deployment

The figure follows one Bookshelf upload from the internet to the forked parser. Every dashed line is a trust boundary where something must be checked, and none of those checks is done by Tika.

Trust boundaries: untrusted upload, gateway with authentication, intake service with upload validation, tika-server on a trusted network, forked JVMs and external tools, and untrusted output
Boundary Crossing Controls that belong here

1

The internet to the edge

TLS, authentication and authorisation of the user, request size limits, rate limits, a path allow-list. Tika has none of these.

2

The edge to the application

The application knows who the user is and what they may upload. It validates the upload (size, extension allow-list, type detected from bytes, a random storage name) before any parser runs.

3

The application to Tika

Only the application can reach tika-server or tika-grpc: a loopback or private address, a firewall rule or a Kubernetes NetworkPolicy, optionally mTLS. The capability flags are off.

inside Tika

The server to its forks and tools

Process isolation with a heap ceiling and timeouts, external tools on absolute paths, an unprivileged user, no network unless a fetcher needs it.

out

Tika back to the application

The extracted text and metadata are untrusted again: sanitise before rendering, indexing or prompting, and redact exception detail for callers.

The same picture applies to the library path: replace tika-server with PipesForkParser inside the intake service, and boundary 3 becomes the process boundary of the fork.

tika-server and tika-grpc are not security boundaries

The 4.1.x documentation repeats this on the security page, the server page and the gRPC page: neither tika-server nor tika-grpc is a security boundary. They do not authenticate or authorise callers, and parsing untrusted documents is risky in itself. Expose them only to trusted callers on a trusted network, never directly to untrusted users or the public internet, and put your own authentication, authorisation and network controls in front of them.

The practical rules:

  1. Only your services call Tika. Users upload to your application; your application calls Tika. There is no "public Tika endpoint" design that is safe.

  2. Bind narrowly. Loopback when the caller is on the same host, a private interface or a cluster-internal Service otherwise.

  3. Authenticate in front. A reverse proxy or API gateway, two-way TLS, or a service mesh with mTLS plus a network policy.

  4. Leave the capability flags off unless a trusted caller needs them, and then expose that capability on a separate, more restricted instance.

  5. Run with least privilege: not as root, read-only configuration, no write access beyond a temporary directory, no outbound network unless a fetcher or VLM needs it.

Binding: 127.0.0.1 by default, 0.0.0.0 in Docker

tika-server binds to localhost unless told otherwise. -h (or --host, or server.host in the JSON) changes it, and -h * is translated to 0.0.0.0, all interfaces. Started from the zip with only a port:

java -jar tika-server-standard-4.1.0.jar -c default.json   # {"server": {"port": 19824}}
lsof -nP -iTCP:19824 -sTCP:LISTEN
curl -s http://localhost:19824/version

Output:

COMMAND  PID      USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
java    6048 bookshelf  157u  IPv6 0x9e248419a33f0651      0t0  TCP 127.0.0.1:19824 (LISTEN)
Apache Tika 4.1.0

The official Docker image is different: its entry point already passes -h 0.0.0.0, so inside the container the server listens on every interface, and the documentation says not to pass -h again. What reaches it is then decided by how you publish the port. Publish it on the host’s loopback only, or not at all when the caller runs in the same Docker network or Kubernetes namespace:

# reachable from this host only
docker run -d --name tika -p 127.0.0.1:9998:9998 apache/tika:4.1.0-1

# reachable only from containers on the private "bookshelf" network: no -p at all
docker network create --internal bookshelf
docker run -d --name tika --network bookshelf apache/tika:4.1.0-1

A plain -p 9998:9998 publishes the port on every host interface, and Docker’s own firewall rules can bypass a host firewall such as ufw. That is the most common way a Tika instance ends up on the internet.

Source: Apache Tika 4.1.x, Tika Server (Command Line Options) and Apache Tika 4.1.x, Running Tika in Docker. The Docker commands were not run for this page; the image facts come from the 4.1.x Docker page.

The image also runs as UID/GID 35002, not root, and keeps the distribution in a root-owned /opt/tika-server. Keep it that way: do not add --user root, and mount configuration read-only (:ro). The Docker & Kubernetes Deployment page covers resource limits and health checks; Docker: Security covers container hardening in general.

Authentication in front: a gateway

tika-server has no built-in token, API-key, JWT or OAuth2 support. The TLS page of the 4.1.x documentation recommends the common shape: a reverse proxy (nginx, Apache httpd, Traefik) or an API gateway terminates TLS and authenticates callers, and Tika runs on plain HTTP on loopback or a private network behind it. The proxy is also the natural place for a path allow-list (so /config, /pipes and /async are unreachable even if a flag is turned on by mistake), a body size limit, timeouts and rate limits.

An nginx sketch for the Bookshelf intake service: only PUT to /tika, /rmeta and /detect, after an authentication sub-request to the application’s own auth service. The names auth.internal and tika are placeholders for your services.

upstream tika_backend { server tika:9998; }

server {
    listen 443 ssl;
    server_name tika-gw.bookshelf.internal;
    ssl_certificate     /etc/nginx/tls/gw.crt;
    ssl_certificate_key /etc/nginx/tls/gw.key;

    client_max_body_size 50m;          # below tika-server's maxRequestSizeBytes
    proxy_read_timeout   180s;         # above the parse timeout, so Tika answers first

    # every request is authenticated by the application's auth service
    location = /_auth {
        internal;
        proxy_pass http://auth.internal/verify;
        proxy_pass_request_body off;
        proxy_set_header Content-Length "";
        proxy_set_header X-Original-URI $request_uri;
    }

    # the only paths and the only method the intake service needs
    location ~ ^/(tika|rmeta|detect)(/(text|md|json|html|xml))?$ {
        auth_request /_auth;
        limit_except PUT { deny all; }
        proxy_request_buffering off;
        proxy_pass http://tika_backend;
    }

    # everything else, including /config, /pipes, /async, /unpack and /status
    location / { return 404; }
}

Source: Apache Tika 4.1.x, TLS/SSL Configuration (Should you configure TLS here at all?), nginx, ngx_http_auth_request_module and nginx, client_max_body_size. This configuration was not run for this page; validate it with nginx -t and a test request for every blocked path.

Two details matter. First, the proxy’s body limit should be at or below tika-server’s maxRequestSizeBytes, so oversize uploads are refused at the edge. Second, a path allow-list must match the 4.x endpoint shapes: 4.x names the output format in the path (/tika/text, /rmeta/md) instead of the 3.x Accept header, and per-request configuration lives under /config path segments, which the regular expression above never matches.

The capability flags: defence in depth

The flags below limit what a caller who reaches Tika can do. The documentation is explicit that they are defence in depth, not security boundaries: they do not make it safe to expose the service to untrusted callers. All default to off.

Setting Default Where What it gates

allowPipes

false

server section (tika-server)

The /pipes and /async endpoints, which fetch and emit through your configured fetchers and emitters. Whoever reaches them gets the read access of your fetchers and the write access of your emitters. Listing pipes or async under endpoints without this flag makes the server refuse to start.

allowPerRequestConfig

false

server section (tika-server)

The /config endpoints (/tika/config, /rmeta/config, /meta/config and their format variants) and the multipart config part. A caller who reaches it chooses how documents parse, including options that start external processes such as OCR, and can defeat memory and timeout limits. When off, such requests get 403.

endpoints

all default endpoints

server section

Which endpoints exist at all. Omitting one also removes SPI resources bound to its path. /language runs in the server JVM, not in a fork, so omit it if you do not need it.

maxRequestSizeBytes

1 GiB

server section

The largest request body, enforced on bytes read (chunked uploads too); larger requests get 413. Bodies above maxInlineBytes are spooled to disk, so this also bounds disk use per request. A negative value disables it: do not.

cors

empty string (off)

server section

Browser cross-origin access. Keep it off: browsers should never call Tika.

allowPerRequestConfig

false

grpc section (tika-grpc)

parse_context_json and additional_fetch_config_json on FetchAndParse; when off, PERMISSION_DENIED.

allowComponentManagement

false

grpc section (tika-grpc)

SaveFetcher, DeleteFetcher, SavePipesIterator, DeletePipesIterator and reading stored configurations back (GetFetcher, ListFetchers, GetPipesIterator). Stored configurations can contain passwords, access keys and tokens; when off, the read RPCs return only the id and type.

There is no command-line flag for any of these: -h, -p, -i and --metricsPort are the only options that override the JSON, and everything else (the flags, CORS, TLS, timeouts) is JSON-only. That is deliberate: enabling a dangerous capability is a visible line in a reviewed file.

The behaviour, observed on the tika-server 4.1.0 started above with the default configuration:

curl -s -w '\nHTTP %{http_code}\n' -X POST -F "file=@catalogue.txt" \
  -F 'config={"output-limits":{"writeLimit":10}};type=application/json' \
  http://localhost:19824/tika/config/text
Config endpoints are disabled. Set allowPerRequestConfig=true in server config.
HTTP 403

And with { "server": { "port": 19825, "endpoints": ["tika", "rmeta", "pipes"] } }, the server stops at start-up (the message as logged, stack trace omitted):

ERROR [main] org.apache.tika.server.core.TikaServerProcess Can't start:
org.apache.tika.exception.TikaConfigException: The following selected endpoint(s) require the pipes machinery to be
enabled: [pipes]. 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 expose process-isolated fetching and
parsing, which can read files and reach network resources.

Presets instead of per-request configuration

When callers really need different parse settings (OCR for scans, no OCR for born-digital PDFs), prefer presets to allowPerRequestConfig. A preset is a named parse-context fragment that the operator defines in the server configuration and the caller only selects by name (/tika/preset/<name>, /rmeta/preset/<name>, /unpack/preset/<name>). Presets do not need allowPerRequestConfig, never combine with request-supplied configuration, are logged at start-up, and give the proxy an addressable path to allow or block (allow /rmeta/preset/ while blocking /rmeta/config). They run with the trust of the configuration file, so review a preset as carefully as the rest of the file. The Tika Server (REST API) page shows how to define them.

A hardened tika-server configuration

The configuration the Bookshelf intake service runs with: loopback only, the four endpoints it uses, a 50 MiB request limit, TLS with the keystore password from the environment, redacted exception messages, and both capability flags at their default (off), which is why they are not in the file.

{
  "server": {
    "host": "127.0.0.1",
    "port": 19826,
    "endpoints": ["tika", "rmeta", "detect", "version"],
    "maxRequestSizeBytes": 52428800,
    "tlsConfig": {
      "active": true,
      "keyStoreType": "PKCS12",
      "keyStoreFile": "/etc/tika/tls/server-keystore.p12",
      "keyStorePassword": "${env:KEYSTORE_PASSWORD}"
    }
  },
  "parse-context": {
    "exception-reporting": { "level": "MESSAGE_REDACTED", "maxLength": 10000 }
  }
}

Started with a self-signed PKCS12 keystore made by keytool and the password exported from the shell (never written to a file), then probed:

export KEYSTORE_PASSWORD="$(cat /run/secrets/tika-keystore-password)"
java -jar tika-server-standard-4.1.0.jar -c hardened.json &

curl -sk https://127.0.0.1:19826/version                       # TLS works
curl -sS http://127.0.0.1:19826/version                        # plain HTTP is refused
curl -sk -o /dev/null -w 'HTTP %{http_code}\n' -T catalogue.txt https://127.0.0.1:19826/unpack
head -c 62914560 /dev/zero > big.bin                           # 60 MiB
curl -sk -w '\nHTTP %{http_code}\n' -T big.bin https://127.0.0.1:19826/tika/text

Output (log lines abridged):

WARN  TikaServerProcess The TLS configuration is in BETA and might change dramatically in future releases.
INFO  TikaServerProcess Started Apache Tika server 5f30bcc4-... at https://127.0.0.1:19826/
Apache Tika 4.1.0
curl: (1) Received HTTP/0.9 when not allowed
HTTP 404
Request body exceeds maxRequestSizeBytes
HTTP 413

The plain-HTTP request gets a TLS record back, which curl refuses to read as HTTP. /unpack is not listed under endpoints, so it does not exist (404), and the 60 MiB body is refused before any parse starts. The keystore used for this page was a throwaway self-signed file.

Environment variables in tlsConfig. The 4.1.x TLS page uses "${env:KEYSTORE_PASSWORD}" in its own examples, but its "Keystore passwords" section says that Tika reads the passwords literally and does not expand ${...} placeholders. In 4.1.0 the examples are right: TikaServerConfig reads the server section through TikaLoader, whose TikaJsonConfig.load runs EnvInterpolator on every string value, and the server above started on HTTPS with the password supplied only through KEYSTORE_PASSWORD. Two more runs confirm that the value really comes from the environment: with a wrong value the start fails with java.io.IOException: keystore password was incorrect, and with the variable unset it fails with TikaConfigException: environment variable KEYSTORE_PASSWORD is not set; referenced at /server/tlsConfig/keyStorePassword. The interpolation is new in 4.1.0; on 4.0.x write the file from a secret at start-up instead, as the TLS page suggests.

TLS and mTLS

tika-server’s TLS lives in server.tlsConfig (active, keyStoreType, keyStoreFile, keyStorePassword, the trustStore* trio, clientAuthenticationWanted, clientAuthenticationRequired, includedProtocols defaulting to TLS 1.2 and 1.3, cipher-suite lists and certExpirationWarningDays). With clientAuthenticationRequired and a truststore it becomes two-way TLS, which is real caller authentication: only holders of a client certificate the truststore trusts can connect. That is the one form of authentication Tika can do by itself, and the server logs that its TLS configuration is still beta.

For most deployments a proxy or mesh terminating TLS is simpler (certificate rotation, metrics, rate limiting). Use tika-server’s own TLS when there is no proxy, when you want client certificates end to end, or as a second layer behind the proxy. The Tika Server (REST API) page has the full key table.

tika-grpc configures TLS only on the command line (--secure, --cert-chain, --private-key, --private-key-password, --trust-cert-collection, --client-auth-required); there is no JSON for it, and its default is plaintext and unauthenticated, including the apache/tika-grpc Docker image, whose entry point does not pass --secure. The gRPC page states that it is more exposed than tika-server by default, because its FetchAndParse RPC is always on: anyone who reaches the port can fetch and parse whatever the configured fetchers reach.

java -jar tika-grpc-4.1.0.jar -c tika-config.json --secure \
  --cert-chain server.pem --private-key server.key \
  --trust-cert-collection ca.pem --client-auth-required

Source: Apache Tika 4.1.x, Tika gRPC (Transport security). Not run for this page; the Tika gRPC page covers the service in full.

Kubernetes: mTLS is not authorisation

Pod networking in Kubernetes is flat by default: any pod can reach any other pod’s port, unencrypted and unauthenticated. The gRPC page makes the distinction that also applies to tika-server: a service mesh with mTLS authenticates who opened the connection, it does not authorise what that caller may do. Because Tika has no per-caller authorisation, the set of pods that can reach the port is the set of pods that can use it. A NetworkPolicy that admits only the intake service is therefore required, not optional:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: tika-only-from-intake
  namespace: bookshelf
spec:
  podSelector:
    matchLabels:
      app: tika-server
  policyTypes: ["Ingress", "Egress"]
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: bookshelf-intake
      ports:
        - protocol: TCP
          port: 9998
  egress: []          # no outbound traffic: add rules only for fetchers, emitters or a VLM that need them

Source: Apache Tika 4.1.x, Tika gRPC (Kubernetes and service meshes) and Kubernetes, Network Policies. Not applied to a cluster for this page. A policy only takes effect with a CNI plugin that enforces it; see Kubernetes: Network Policies. An empty egress list also blocks DNS, which is what you want for a Tika that only receives bytes.

Exception detail: redact it for callers

By default Tika reports exceptions in full: stack traces, and messages that can contain the spooled file’s path, the original file name, host names and fragments of the document. They appear in the tk:exception:* metadata of successful parses, in tika-server error bodies (422, 500), in the *.metadata.json entries of an /unpack zip and in /pipes and /async result messages. The exception-reporting block of the parse-context section controls all of them at once:

Key Default Values

level

FULL

FULL (complete stack trace), MESSAGE_REDACTED (stack trace and cause chain without any exception message), REDACTED (exception class names only).

maxLength

-1 (unlimited)

Characters after which the formatted exception is cut, with a truncation marker.

The same broken manuscript.docx (a zip whose [Content_Types].xml does not cover _rels/.rels), sent to /rmeta/text on the default server and on the hardened one, gives this tk:exception:container-exception (frames omitted):

# level FULL (default)
org.apache.tika.exception.TikaException: Error creating OOXML extractor
Caused by: org.apache.poi.openxml4j.exceptions.InvalidFormatException: The part /_rels/.rels does not have any content
type ! Rule: Package require content types when retrieving a part from a package. [M.1.14]

# level MESSAGE_REDACTED
org.apache.tika.exception.TikaException
	at org.apache.tika.parser.microsoft.ooxml.OOXMLExtractorFactory.parse(OOXMLExtractorFactory.java:210)
	at org.apache.tika.parser.microsoft.ooxml.OOXMLParser.parse(OOXMLParser.java:145)
	...

The setting is operator-only. With allowPerRequestConfig switched on, a request that tries to turn it back to FULL is refused:

Could not resolve the request config: Component 'exception-reporting' (context key
org.apache.tika.config.ExceptionReporting) may not be supplied via a request parseContext. Components of this type
must be configured server-side in the Tika config, not at request time.
HTTP 400

Set MESSAGE_REDACTED (or REDACTED) whenever the callers are less trusted than the operator, and prefer it to a metadata filter that drops tk:exception:*: a filter supplied in a per-request configuration replaces the operator’s filter instead of adding to it. A third-party parser still calling the deprecated two-argument EmbeddedDocumentUtil.recordException reports FULL whatever the policy says.

From 3.x to 4.x on the server side

3.x 4.x

tika-server’s auto-restart (and ForkParser for embedders) as the isolation mechanisms

Every parse request is served by a forked JVM through Pipes; not optional.

enableUnsecureFeatures enabled the risky features; from 3.3.2 it also gated /pipes, /async and /status

Split into allowPipes and allowPerRequestConfig. A configuration that still contains enableUnsecureFeatures fails to start with an "Unrecognized field" error. /status is no longer gated (aggregate counters only); list it under endpoints to enable it.

Per-request configuration through X-Tika-* headers (X-Tika-OCR..., X-Tika-PDF...)

Headers removed and silently ignored; per-request configuration only through /config (behind allowPerRequestConfig) or operator-defined presets.

Fetching through request headers (fetcherName, fetchKey)

Removed; fetching only through /pipes and /async behind allowPipes.

Client-supplied meta_ headers copied into the output

Removed.

The history explains the caution. CVE-2015-3271 (Tika 1.9) let a remote caller read arbitrary files through the fileUrl header, and CVE-2018-1335 (1.7 to 1.17) let crafted headers inject commands into the command line of the server; both only affected servers open to untrusted clients, which is exactly the deployment this page forbids. In 2026, CVE-2026-66756 showed that /unpack on 4.0.0-alpha-1 could be configured past the secure defaults; it was fixed in 4.0.0-beta-1, before the first stable 4.x release.

Source: Apache Tika 4.1.x, Migrating tika-server to 4.x and the CVE records linked in the CVE history.

The library path: parse in a fork

Embedding Tika does not avoid the security model, it moves boundary 3 into your process. The 4.1.x Java API guide recommends running Tika as a separate service first and, if you embed it, using PipesForkParser from tika-pipes-fork-parser: it parses in a separate JVM with automatic restart and timeouts, and one instance is thread-safe. Calling AutoDetectParser directly on untrusted content means your application owns the isolation, because a thread that loops inside a parser cannot be stopped safely and an OutOfMemoryError can leave the JVM in an undefined state.

The Bookshelf intake path combines the upload gate of the next section with a forked parser that has a heap ceiling, both timeouts, an output limit and an attachment limit:

<dependency>
    <groupId>org.apache.tika</groupId>
    <artifactId>tika-pipes-fork-parser</artifactId>
    <version>4.1.0</version>
</dependency>
implementation("org.apache.tika:tika-pipes-fork-parser:4.1.0")
import java.nio.file.Path;

import org.apache.tika.config.TimeoutLimits;
import org.apache.tika.pipes.fork.PipesForkParser;
import org.apache.tika.pipes.fork.PipesForkParserConfig;
import org.apache.tika.pipes.fork.PipesForkResult;

/** Gate first, then parse in a forked JVM with a heap ceiling and timeouts. */
public final class SandboxedIntake {

    public static void main(String[] args) throws Exception {
        UploadGate gate = new UploadGate();
        PipesForkParserConfig config = new PipesForkParserConfig()
                .addJvmArg("-Xmx512m")                                // heap ceiling of the fork
                .setTimeoutLimits(new TimeoutLimits(120_000, 30_000)) // total, progress (ms)
                .setWriteLimit(2_000_000)                             // characters of output
                .setMaxEmbeddedCount(500)                             // attachments, zip entries
                .setMaxFilesPerProcess(1_000);                        // recycle the fork

        try (PipesForkParser parser = new PipesForkParser(config)) {
            for (String name : args) {
                Path upload = Path.of("files", name);
                UploadGate.Verdict verdict = gate.check(upload, name);
                if (!verdict.accepted()) {
                    System.out.println(name + " -> rejected: " + verdict.reason());
                    continue;
                }
                PipesForkResult result = parser.parse(upload);
                if (result.isSuccess()) {
                    // PARSE_SUCCESS or PARSE_SUCCESS_WITH_EXCEPTION (partial content)
                    String text = result.getContent();
                    int chars = text == null ? 0 : text.strip().length();
                    System.out.println(name + " -> " + result.getStatus() + ", " + chars + " chars");
                } else if (result.isProcessCrash()) {
                    // OOM, TIMEOUT or UNSPECIFIED_CRASH: the fork died, this JVM did not
                    System.out.println(name + " -> fork crashed: " + result.getStatus());
                } else {
                    System.out.println(name + " -> failed: " + result.getStatus());
                }
            }
        }
    }
}

Run with java SandboxedIntake manuscript.docx catalogue.pdf renamed-invoice.png broken-manuscript.docx (the fork’s log lines go to stderr and are omitted):

manuscript.docx -> PARSE_SUCCESS, 72 chars
catalogue.pdf -> PARSE_SUCCESS, 87 chars
renamed-invoice.png -> rejected: content is application/pdf, not .png
broken-manuscript.docx -> PARSE_SUCCESS_WITH_EXCEPTION, 0 chars

The broken manuscript passes the gate (its bytes really are a Word package) and fails inside the fork, which reports the exception and stays usable. A crafted file that exhausted the 512 MiB heap would come back as OOM with the intake service unaffected. The Robustness, Limits & Timeouts page covers every limit and timeout, and how to test the failure modes with MockParser.

Upload validation per OWASP

The OWASP File Upload Cheat Sheet lists the controls for any upload feature. Tika helps with one of them (detecting the real type) and is the reason for several others (the parser is the attack surface). The mapping for a Tika-backed intake service:

OWASP control With Tika

Allow-list the extensions the business needs

Your code. Bookshelf accepts pdf, docx, epub, jpg, png, txt and eml; everything Tika can parse is far more than you need to accept. Reject macro-enabled Office types (.docm, .xlsm) unless required.

Validate the file type, do not trust the Content-Type header

Detect from the bytes with Detector.detect and compare with the allow-list for the extension. Pass no file name and no client Content-Type to detection, or the hint can win for ambiguous files.

Change the file name to one generated by the application

Your code: store under a UUID, keep the original name only as escaped metadata. Never reuse a name from inside an archive (the 2018 tika-app Zip Slip, CVE-2018-11762, came from an embedded absolute path).

Set a file name length limit and restrict characters

Your code, before anything touches the file system.

Set a file size limit

At the gateway, in the application, and maxRequestSizeBytes in tika-server. Expansion inside the file (zip bombs, embedded documents) is bounded separately by output-limits and embedded-limits.

Only allow authorised users to upload

At the gateway and in the application; tika-server cannot.

Store the files on a different server or outside the web root, with restricted permissions

Your storage; Tika never needs write access to it. Give Tika the bytes (a PUT) or a read-only fetcher.

Run the files through an antivirus or a sandbox

Your pipeline. Tika is not a malware scanner and does not flag polyglots.

Protect against CSRF

Your web layer.

Keep libraries updated and configured securely

Tika, its parser dependencies and any external tools: see Staying updated.

The upload gate the Bookshelf service runs before any parser (it only uses tika-core detection, so it is cheap and runs in-process):

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.regex.Pattern;

import org.apache.tika.config.loader.TikaLoader;
import org.apache.tika.detect.Detector;
import org.apache.tika.io.TikaInputStream;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.mime.MediaType;
import org.apache.tika.parser.ParseContext;

/** Validates an upload before any parser sees it (OWASP File Upload Cheat Sheet order). */
public final class UploadGate {

    public record Verdict(boolean accepted, String storedAs, String detected, String reason) { }

    private static final long MAX_BYTES = 20L * 1024 * 1024;
    private static final Pattern SAFE_NAME = Pattern.compile("[\\p{L}\\p{N} ._-]{1,120}");

    /** Extension allow-list, each mapped to the media types the bytes may have. */
    private static final Map<String, Set<String>> ALLOWED = Map.of(
            "pdf", Set.of("application/pdf"),
            "docx", Set.of("application/vnd.openxmlformats-officedocument.wordprocessingml.document"),
            "epub", Set.of("application/epub+zip"),
            "jpg", Set.of("image/jpeg"),
            "png", Set.of("image/png"),
            "txt", Set.of("text/plain"),
            "eml", Set.of("message/rfc822"));

    private final Detector detector;

    public UploadGate() throws Exception {
        this.detector = TikaLoader.loadDefault().loadDetectors();
    }

    public Verdict check(Path upload, String clientName) throws IOException {
        long size = Files.size(upload);
        if (size == 0 || size > MAX_BYTES) {
            return new Verdict(false, null, null, "size " + size + " outside 1.." + MAX_BYTES);
        }
        if (!SAFE_NAME.matcher(clientName).matches() || clientName.startsWith(".")) {
            return new Verdict(false, null, null, "file name rejected");
        }
        int dot = clientName.lastIndexOf('.');
        String ext = dot < 0 ? "" : clientName.substring(dot + 1).toLowerCase(Locale.ROOT);
        Set<String> expected = ALLOWED.get(ext);
        if (expected == null) {
            return new Verdict(false, null, null, "extension '" + ext + "' not allowed");
        }
        // Detect from the bytes only: no file name, no client Content-Type.
        MediaType detected;
        try (TikaInputStream tis = TikaInputStream.get(upload)) {
            detected = detector.detect(tis, new Metadata(), new ParseContext());
        }
        String base = detected.getBaseType().toString();
        if (!expected.contains(base)) {
            return new Verdict(false, null, base, "content is " + base + ", not ." + ext);
        }
        // Never reuse the client's name on disk.
        String storedAs = UUID.randomUUID() + "." + ext;
        return new Verdict(true, storedAs, base, "ok");
    }

    public static void main(String[] args) throws Exception {
        UploadGate gate = new UploadGate();
        for (String name : args) {
            Path p = Path.of("files", name);
            System.out.println(name + " -> " + gate.check(p, name));
        }
        // A client name that tries to climb out of the upload directory
        System.out.println("../../etc/cron.d/x.pdf -> "
                + gate.check(Path.of("files", "catalogue.pdf"), "../../etc/cron.d/x.pdf"));
    }
}

Run on five Bookshelf samples (the UUIDs differ on every run):

manuscript.docx -> Verdict[accepted=true, storedAs=09143161-bddc-4c3d-96c7-2a2907c5d199.docx, detected=application/vnd.openxmlformats-officedocument.wordprocessingml.document, reason=ok]
catalogue.pdf -> Verdict[accepted=true, storedAs=c70853ca-825b-40f2-be43-899bc90a2e17.pdf, detected=application/pdf, reason=ok]
renamed-invoice.png -> Verdict[accepted=false, storedAs=null, detected=application/pdf, reason=content is application/pdf, not .png]
publisher-email.eml -> Verdict[accepted=true, storedAs=7acc6f71-2e5a-4876-8dda-da1671c86e50.eml, detected=message/rfc822, reason=ok]
legacy-catalogue.txt -> Verdict[accepted=true, storedAs=e466301d-6532-40fd-90b2-83c5f61db713.txt, detected=text/plain, reason=ok]
../../etc/cron.d/x.pdf -> Verdict[accepted=false, storedAs=null, detected=null, reason=file name rejected]

Source: OWASP File Upload Cheat Sheet and Detector, Javadoc 4.1.0. The detection behaviour itself is explained in Media Types & Detection.

What the gate does not do, and why:

  • It does not prove the file is safe. The broken manuscript of the previous section passed it. Detection reads the first bytes and, for containers, the entry names; a file can match a type and still be malformed or hostile further in. The gate removes the cheap attacks (a renamed executable, a PDF posing as an image, an absurd size); the fork contains the rest.

  • It does not look inside archives. publisher-email.eml with samples.zip attached is accepted, and the zip is parsed later as an embedded document under embedded-limits and output-limits (depth, count and the zip-bomb ratio), inside the fork.

  • It does not stream. The uploaded file is already on disk here; in a Spring Boot controller, bound the multipart size in the framework first (spring.servlet.multipart.max-file-size) so the bytes never reach the gate.

For POI-level protections when Office files are read with POI directly (zip-bomb ratio, record limits, macro detection with FileMagic), see the upload validation section of Office Files: Security & Untrusted Files. When Tika parses an Office file it uses POI under the hood, and the same POI limits apply inside the fork.

External commands

Three Tika 4.1.0 components can run programs on the host. The 4.1.x security page names them and gives one rule: external command configuration must be done only by trusted administrators, and untrusted users must never configure command paths or arguments.

Component Class (in tika-core) What runs

FileCommandDetector

org.apache.tika.detect.FileCommandDetector

The system file command, for type detection. Not loaded by default (spi = false); you add it to detectors.

ExternalParser

org.apache.tika.parser.external.ExternalParser

Any configured program (exiftool, ffmpeg, sox) whose stdout, stderr or output file becomes text or metadata.

ExternalEmbedder

org.apache.tika.embedder.ExternalEmbedder

An external tool that writes metadata into a file.

Tesseract OCR and the ImageMagick preprocessing step are also external programs, started by TesseractOCRParser; see OCR with Tesseract for its own security notes.

The best practices from the 4.1.x security page, with what each means in a configuration:

  • Restrict configuration access. Only administrators can change the file that names commands. Mount it read-only.

  • Use absolute paths. /usr/bin/file, not file, so a changed PATH or a planted binary in the working directory cannot substitute the program.

  • Sandbox execution. A container with CPU and memory limits, an unprivileged user and no network.

  • Audit the command configuration at every change and upgrade.

And the risks specific to ExternalParser:

  • checkCommandLine runs at type-query time. The check command runs the first time getSupportedTypes() is called, not at parse time, so merely asking which parsers exist (for example /parsers on tika-server) runs a process.

  • stderr can leak information. Tools write paths, user names, versions and internal errors to stderr. returnStderr is false by default for that reason; turning it on stores raw stderr in the document’s metadata, where end users may see it.

  • Buffer limits. maxStdOut and maxStdErr (10,000 bytes each by default) bound how much output is held in memory. Keep them small. timeoutMillis (60,000 by default) bounds the process.

  • Configuration cannot change at request time. The 4.1.0 ExternalParserConfig source documents that it is immutable and that ExternalParser does not accept runtime changes through ParseContext, for security reasons.

A configuration for the Bookshelf covers, following those rules: absolute paths for the parser, its check and the detector, short limits, no raw stderr, and metadata only.

{
  "detectors": [
    { "default-detector": {} },
    { "file-command-detector": { "filePath": "/usr/bin/file", "timeoutMillis": 5000 } }
  ],
  "parsers": [
    { "default-parser": {} },
    {
      "external-parser": {
        "supportedTypes": ["image/jpeg"],
        "commandLine": ["/usr/bin/file", "--brief", "${INPUT_FILE}"],
        "checkCommandLine": ["/usr/bin/file", "--version"],
        "checkErrorCodes": [126, 127],
        "timeoutMillis": 10000,
        "maxStdOut": 4096,
        "maxStdErr": 4096,
        "returnStderr": false,
        "contentSource": "none",
        "stdoutHandler": {
          "regex-capture-parser": {
            "captureMap": { "file:description": "^([^,\\r\\n]+)" }
          }
        }
      }
    }
  ]
}

Loaded with TikaLoader.load(path).loadAutoDetectParser() and run on book-cover.jpg (the keys printed by a short loop over Metadata.names()):

external-process:stderr-length = 0
external-process:stderr-truncated = false
file:description = JPEG image data
file:mime = image/jpeg
tk:parsed-by = org.apache.tika.parser.CompositeParser, org.apache.tika.parser.external.ExternalParser
external-process:stdout-truncated = false
external-process:timeout = false
external-process:stdout-length = 128
external-process:exit-value = 0
Content-Type = image/jpeg

file:mime comes from FileCommandDetector, which records the file command’s answer but does not use it to choose the parser unless useMime is true. Two details found while running it:

  • The JSON key for the detector’s program is filePath. A first attempt with fileCommandPath (the name of the Java field) failed at load time with Unrecognized field "fileCommandPath" ... (4 known properties: "filePath", "useMime", "maxBytes", "timeoutMillis"), which is the strict validation working as intended. The setter’s source comment says it opens a potential command vulnerability and must never be set by an untrusted user.

  • The parser is scoped with supportedTypes, not _mime-include, so the lazy check can disable it when the tool is missing; Extending Tika explains the difference.

An external tool brings its own vulnerabilities: ExifTool, FFmpeg, ImageMagick and libmagic have all had CVEs for crafted files. Pin and patch them like a dependency, track their advisories as well as Tika’s, and do not install tools the service does not use (the apache/tika:4.1.0-full image includes Tesseract and other tools; the plain image does not).

XXE and XFA

An XML External Entity (XXE) attack uses an XML document that declares an entity pointing at a local file or a URL. An XML parser that resolves the entity copies the file (or the response) into its output, which turns a parser into a file reader or a request forger inside your network (server-side request forgery). Many formats Tika reads are XML or contain XML: OOXML, ODF, EPUB, SVG, XMP metadata in images and PDFs, and XFA forms in PDFs.

Tika creates its XML parsers through org.apache.tika.utils.XMLReaderUtils. In the 4.1.0 source its SAX factories turn off external-general-entities and the loading of external DTDs, its readers and DOM builders get an entity resolver that ignores every external reference, its StAX factory disables SUPPORT_DTD and IS_SUPPORTING_EXTERNAL_ENTITIES, and entity expansion is capped (DEFAULT_MAX_ENTITY_EXPANSIONS is 20, overridable with the JDK’s jdk.xml.entityExpansionLimit property). That central hardening is why the historic XXE fixes in Tika were mostly about a code path that did not go through it. For this page a test XML file was parsed with the Tika facade 4.1.0; it declared an external entity that pointed at a local canary file. The canary text did not appear in the output (the entity was dropped and the surrounding text kept). The test file is not reproduced here.

If your own code parses XML that came out of Tika or alongside it (a sidecar file, an XMP packet you extract yourself), it needs the same hardening; the OWASP XXE Prevention Cheat Sheet lists the settings per Java parser, and XMLReaderUtils.getSAXParser() gives you Tika’s preconfigured one.

CVE-2025-54988 and CVE-2025-66516: XXE through XFA

The most important Tika vulnerability of recent years is an XXE in the handling of XFA (XML Forms Architecture) forms inside PDF files:

CVE From the CVE record

CVE-2025-54988 (published 2025-08-20, CVSS 3.1 8.4, the record’s title calls it critical)

XXE in tika-parser-pdf-module 1.13 through 3.2.1 through a crafted XFA file inside a PDF. An attacker may read sensitive data or trigger requests to internal resources or third-party servers. The module is used by tika-parsers-standard-modules, tika-parsers-standard-package, tika-app, tika-grpc and tika-server-standard. Fixed in 3.2.2.

CVE-2025-66516 (published 2025-12-04, CVSS 3.1 8.4)

The same vulnerability with a wider scope: the entry point was the PDF module, but the flaw and its fix were in tika-core (1.13 to 3.2.1), so upgrading only the PDF module left you vulnerable. It also names tika-pdf-module 2.0.0 to 3.2.1 and the 1.x tika-parsers artifact (1.13 to 1.28.5), where PDFParser lived in 1.x.

At the baseline date Tika’s own security page lists CVE-2025-54988 but not CVE-2025-66516.

What to do:

  1. Run 3.3.x or 4.1.x; every 4.x release includes the fix. Upgrade tika-core and every parser module together: import tika-bom so all org.apache.tika artifacts share one version, then check what was resolved with mvn dependency:tree -Dincludes=org.apache.tika or gradle dependencies. A stale tika-core pulled in by another library is exactly the case CVE-2025-66516 describes.

  2. If you are on 1.x, there is no fixed release: 1.x reached end of life in 2022. Migrate.

  3. Treat XFA as a signal: pdf:has-xfa = true files are rare in a book catalogue and can be routed to a stricter queue.

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.apache.tika</groupId>
      <artifactId>tika-bom</artifactId>
      <version>4.1.0</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>
dependencies {
    implementation(platform("org.apache.tika:tika-bom:4.1.0"))
    implementation("org.apache.tika:tika-core")
    implementation("org.apache.tika:tika-parsers-standard-package")
}

Source: the CVE records above and Apache Tika, Security. The XFA parsing itself, with the ifXFAExtractOnlyXFA option and a real XFA sample, is in PDF Parsing.

Credential handling

Tika needs credentials as soon as Pipes fetches from or emits to something: S3 or Azure keys, an Elasticsearch API key, a Solr password, an HTTP fetcher’s basic auth, a VLM provider’s API key, a keystore password. The 4.1.x security page notes that Tika holds some of them as Java String objects, which stay in memory until garbage collected, and gives four recommendations; here they are with the 4.1.0 mechanisms behind them.

Recommendation How, in Tika 4.1.0

Use environment variables rather than configuration files

${env:NAME} in any string value of tika-config.json (new in 4.1.0). An unset variable fails the load with its name and JSON path, never a value. Per-request configuration is never interpolated, so a caller cannot read the server’s environment. The forks inherit the environment and resolve the same references.

Use a secret manager (HashiCorp Vault, AWS Secrets Manager, …​) in production

Have the platform inject the secret as an environment variable (a Kubernetes Secret with secretKeyRef, a Vault agent, a systemd EnvironmentFile) or write the configuration file from the secret at start-up. Tika has no built-in secret-manager client.

Encrypt credentials

HttpClientFactory (in tika-httpclient-commons, used by the HTTP fetcher and the Elasticsearch, OpenSearch and Solr plugins) can hold its user name and password AES-encrypted (credentialsAESEncrypted), with the key in the AES_KEY environment variable. See the warning below.

Minimise scope and rotate

A read-only bucket policy for the fetcher, an index-scoped API key for the emitter, a VLM key with a spending limit.

{
  "fetchers": {
    "covers": { "file-system-fetcher": { "basePath": "${env:BOOKSHELF_INBOX}/covers" } }
  },
  "emitters": {
    "es": { "es-emitter": { "esUrl": "https://search.bookshelf.internal:9200/bookshelf",
                            "apiKey": "${env:ES_API_KEY}" } }
  }
}

Source: Apache Tika 4.1.x, Security (Credential Handling) and Apache Tika 4.1.x, Configuration. The interpolation rules were verified for the Configuration page, which has the details; the fetchers / emitters shape here is taken from that page.

Where credentials still leak, even with ${env:NAME}:

  • The resolved configuration. TikaLoader.getConfig().getRootNode() returns the JSON after interpolation. Never log it. A persistent pipes config store (configStoreType file or Ignite) writes the resolved fetcher and emitter configurations to that store; the default in-memory store writes nothing.

  • tika-grpc’s read RPCs. With allowComponentManagement on, GetFetcher and ListFetchers return stored configurations, which can contain passwords and keys. Keep the flag off.

  • Exception messages. A failed fetch can put a URL with an embedded token into a tk:exception:* value or a pipes result message; exception-reporting with MESSAGE_REDACTED removes the messages.

  • Process listings. Never pass secrets as -D system properties or command arguments of the forks (forkedJvmArgs), which other users on the host can read in ps.

credentialsAESEncrypted did not work as written in the test environment. In the 4.1.0 source, HttpClientFactory derives a 128-bit key from AES_KEY with SHA-1 and calls Cipher.getInstance("AES/GCM/PKCS5Padding") without an IV parameter. On the JDK used for this page (Oracle Java 25 with its default providers) that transformation is not available: a stand-alone check that copied the same calls failed with NoSuchAlgorithmException: Cannot find any provider supporting AES/GCM/PKCS5Padding, which HttpClientFactory reports as TikaConfigException("bad encryption info"). Whether another security provider changes this was not tested, and no 4.1.0 plugin configuration class was found that exposes the flag as a JSON key (on the main branch at the time of writing, the name appears only in HttpClientFactory and the security page). Prefer ${env:NAME} and a secret manager; if you rely on the encrypted form, test it end to end on your JVM first.

CVE history

The Tika security page describes itself as an incomplete list of known and fixed CVEs and other vulnerabilities in Tika or its dependencies. The list below groups the entries by the lesson they teach, with the affected versions from the CVE records where they differ from the security page. Every entry was fixed long ago except where noted; none affects Tika 4.1.0.

CVE Issue Affected

Exposed servers and callers

CVE-2015-3271

tika-server: remote callers could read host files through the fileUrl header.

1.9 (security page: 1.9? to 1.10)

CVE-2018-1335

tika-server: crafted headers injected commands into the server’s command line. Only servers open to untrusted clients.

1.7 to 1.17

CVE-2026-66756 (2026-07-30, CVSS 4.0 6.9)

tika-server: the /unpack endpoint could be configured past the secure defaults ("improper protection of alternate path").

4.0.0-alpha-1 only; fixed in 4.0.0-beta-1

CVE-2018-11762

tika-app: without --extract-dir, an embedded file with an absolute path could overwrite that file (Zip Slip).

0.9 to 1.18

XML external entities and entity expansion

CVE-2025-54988 and CVE-2025-66516

XXE through XFA in PDFs (above).

1.13 to 3.2.1

CVE-2016-4434

XXE through spreadsheets in OOXML files and XMP metadata in PDF and other formats.

before 1.13

CVE-2018-11761

XML parsers did not limit entity expansion (denial of service).

0.1 to 1.18

CVE-2018-11796

XML entity expansion in SAX parsers after reset().

to 1.19

CVE-2016-2175, CVE-2019-0228

XXE in PDFBox (the second in XFDF loading, which regular Tika parsing was unlikely to reach).

to 1.12, to 1.20

Files read from the host

CVE-2026-66755 (2026-07-30, CVSS 4.0 5.9)

Relative path traversal in the ISA-Tab parser (tika-parser-scientific-module): a value in an ISA-Tab investigation file could make Tika read a file outside the dataset directory into the extracted text, when an attacker can place files in a directory the application parses.

1.8 to 3.3.1 and 4.0.0-alpha-1; fixed in 3.3.2 and 4.0.0-beta-1

Code execution through dependencies

CVE-2016-6809

Java deserialisation of objects embedded in MATLAB files (JMatIO).

before 1.14

CVE-2018-7489

Jackson polymorphic deserialisation.

to 1.17

CVE-2021-44228, CVE-2021-44832

Log4Shell and the JDBC appender issue in Log4j 2, as shipped with Tika 2.x.

2.0.0-BETA to 2.1.0, to 2.2.1

CVE-2018-20346

Remote code execution in the provided SQLite library.

to 1.20

Denial of service in parsers (the largest group)

CVE-2020-9489

System.exit in the OneNote parser; OOM and loops in the ICNS, MP3, MP4, SAS7BDAT, OneNote and image parsers.

to 1.24

CVE-2019-10094, CVE-2019-10088

A zip "quine" causing a StackOverflowError, and an OOM from a crafted zip, in RecursiveParserWrapper.

1.7 to 1.21

CVE-2022-30216, CVE-2022-30973, CVE-2022-33879

Regular-expression DoS in StandardsExtractingContentHandler.

to 2.4.0 and 1.28.3

CVE-2022-25169, CVE-2023-42503 and many more

BPG parser memory use, Commons Compress TAR parsing, and dozens of loops and OOMs in PDFBox, POI, Commons Compress and Tika’s own parsers.

various, 1.x to 2.9.0

Three lessons:

  • Most entries are denial of service from a crafted file. The security model is the reason they are no longer CVEs when you parse in-process, and process isolation is the reason they do not hurt when you do not.

  • The server CVEs all assumed untrusted callers. Every one of them is harmless behind the controls of this page.

  • Many entries are in dependencies. Upgrading Tika as a whole (one version, from the BOM) is the fix; patching one artifact is not (CVE-2025-66516).

Source: Apache Tika, Security (the list and the reporters), the CVE records linked in the table, retrieved from the MITRE CVE service on 2026-10-12, and the oss-security announcement of CVE-2026-66756. CVE-2026-66755 and CVE-2026-66756 were not on Tika’s security page at the baseline date; the CVE records are the source for both.

Staying updated

  • Run a supported line. At the baseline date that is 4.1.x (latest 4.1.0) and the 3.x maintenance line (latest 3.3.2). The end of 3.x support is stated differently in two official places: the 4.1.x site’s roadmap says March 2027, the wiki roadmap says January 2027 or six months after 4.0.0, whichever comes first. Plan the move to 4.x now. 2.x and 1.x are end of life.

  • One version for everything. Import tika-bom; never mix tika-core and parser modules of different versions.

  • Pin container images. Use a numbered tag such as apache/tika:4.1.0-1 (the suffix is the image build), not latest, and rebuild when either Tika or the image’s base layer gets a security fix.

  • Watch the sources that have the news. The Tika home page and announce@apache.org, the Tika mailing lists, the CVE records (search for "Apache Tika"), and the oss-security list where Apache publishes advisories. Tika’s own security page lags behind the records, as the 2025 and 2026 entries show.

  • Scan dependencies. Dependabot, Renovate or OWASP Dependency-Check catch vulnerable Tika, PDFBox, POI and Commons Compress versions in the resolved tree, including ones pulled in transitively.

  • Patch the external tools too. Tesseract, ImageMagick, ExifTool, FFmpeg and file in your image.

  • Keep a regression corpus. A directory of hostile and broken samples (the renamed invoice, the broken manuscript, a zip bomb, an XFA PDF) parsed in a test after every upgrade, as described in Robustness, Limits & Timeouts.

  • Re-read the migration notes at each minor upgrade: defaults and flags change (3.3.2 gated /status, 4.x split enableUnsecureFeatures, 4.1.0 added ${env:NAME}).

From the book to 4.x

Tika in Action was written for Tika 1.0, before the security model was published and before every CVE above, so this page does not rely on it. The changes since then:

Topic 1.x (the book) 2.x and 3.x 4.x

Isolation

In-process parsing; ForkParser as an option

ForkParser, tika-server’s auto-restart, tika-pipes

Pipes is the only forking mechanism: PipesForkParser, tika-app batch mode, tika-server (always forked), tika-grpc

Security model

None published

Security page with the CVE list

Security model page: in-process DoS is not a vulnerability, escaping Pipes is

Risky server features

fileUrl header (1.9, CVE-2015-3271), configuration through headers

enableUnsecureFeatures; header configuration still present

allowPipes, allowPerRequestConfig, presets; headers removed; unknown keys rejected

Secrets in configuration

Plain values in XML

Plain values in XML

${env:NAME} interpolation (4.1.0)

Exception detail

Full

Full

exception-reporting levels, operator-only

TLS

Not covered

Not checked for this page

server.tlsConfig (logged as beta), mTLS; tika-grpc --secure

The 3.x-era entries are taken from the security and migration pages, not from a 3.x run.

Pitfalls checklist

  • Publishing tika-server with docker run -p 9998:9998 on a host with a public interface.

  • Believing that allowPipes=false and allowPerRequestConfig=false make an exposed server safe. They are defence in depth only.

  • Exposing tika-grpc in Kubernetes with mesh mTLS but no NetworkPolicy: every authenticated pod can use it.

  • Parsing uploads with AutoDetectParser in the web application’s JVM and treating the resulting OOM as Tika’s security problem.

  • Upgrading tika-parser-pdf-module but not tika-core (CVE-2025-66516).

  • Trusting the client’s Content-Type or file name, or passing them to detection when validating.

  • Storing uploads under the client’s file name, or extracting archive entries under their internal names.

  • Rendering extracted text or links unescaped, or exporting extracted values to a spreadsheet without formula neutralisation.

  • Leaving exception-reporting at FULL for a service whose callers should not see paths or document fragments.

  • Writing a bare exiftool in commandLine instead of an absolute path, or enabling returnStderr on a public-facing output.

  • Putting secrets in tika-config.json, in forkedJvmArgs, or in logs of the resolved configuration.

  • Relying on credentialsAESEncrypted without testing it on your JVM.

  • Running a -full image (with OCR and other tools) where the plain image would do.

What was verified and what was not

Verified on 2026-10-12 against Tika 4.1.0 on Java 25:

  • the default binding of tika-server to 127.0.0.1, the 403 body for /tika/config/text, the start-up refusal when pipes is listed without allowPipes, the 404 for an endpoint left out of endpoints, the 413 for a body over maxRequestSizeBytes, TLS with ${env:KEYSTORE_PASSWORD} (including the beta warning), the FULL versus MESSAGE_REDACTED exception output, the 400 for a per-request exception-reporting, and the start-up errors for a wrong and an unset KEYSTORE_PASSWORD (all on the official tika-server-standard-4.1.0.zip);

  • UploadGate, SandboxedIntake and the external parser with FileCommandDetector, compiled and run with the outputs shown; the filePath key, found through the load error;

  • the XXE probe with a canary file (described, not reproduced);

  • the HttpClientFactory, ExternalParserConfig, FileCommandDetector, TikaServerConfig, TikaJsonConfig and PipesForkParserConfig sources at the 4.1.0 tag, and the AES/GCM/PKCS5Padding transformation with a stand-alone check;

  • the security model, security, server, TLS, gRPC, Docker, limits, external parser and server migration pages of the 4.1.x documentation, and the CVE records of every CVE with a link in the table.

Not verified:

  • the nginx configuration, the Kubernetes NetworkPolicy and the Docker commands, none of which was run for this page;

  • tika-grpc and its TLS flags (taken from the 4.1.x gRPC page);

  • whether credentialsAESEncrypted works with a security provider other than the JDK defaults;

  • the CVEs without a link in the table, which come from Tika’s security page only, and the 3.x behaviour, which comes from the documentation;

  • the Tika 4.1.x documentation says its index page was generated with AI assistance and needs human review; where its pages disagreed (the TLS page on environment variables), the run and the source were taken as authoritative.