This is an automated email from the ASF dual-hosted git repository.
rvesse pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/jena.git
The following commit(s) were added to refs/heads/main by this push:
new cb15e1a82c Add draft threat model + SECURITY.md/AGENTS.md
discoverability
cb15e1a82c is described below
commit cb15e1a82cdd35d159c61637011b1dc957426739
Author: Jarek Potiuk <[email protected]>
AuthorDate: Tue Jun 2 20:17:21 2026 +0200
Add draft threat model + SECURITY.md/AGENTS.md discoverability
Generated-by: Claude Code
---
AGENTS.md | 27 ++++++++
SECURITY.md | 29 ++++++++
THREAT_MODEL.md | 210 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 266 insertions(+)
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000000..1a28bf1d4e
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,27 @@
+<!--
+SPDX-License-Identifier: Apache-2.0
+
+Licensed 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
+
+ https://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.
+-->
+
+# Agent Guide for jena
+
+This file is read by automated agents (security scanners, code
+analyzers, AI assistants) operating on this repository.
+
+## Security
+
+Security model: [SECURITY.md](./SECURITY.md)
+
+Agents that scan this repository should consult `SECURITY.md` and the
+threat model it links before reporting issues.
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000000..c8c12d6ffe
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,29 @@
+<!--
+SPDX-License-Identifier: Apache-2.0
+
+Licensed 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
+
+ https://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.
+-->
+
+# Security Policy
+
+## Reporting a Vulnerability
+
+`apache/jena` follows the [Apache Software Foundation security
process](https://www.apache.org/security/). Please report suspected
+vulnerabilities privately to `[email protected]`; do not open public
+GitHub issues or pull requests for security reports.
+
+## Threat Model
+
+What the project treats as in scope and out of scope, the security
+properties it provides and disclaims, the adversary model, and how
+findings are triaged are documented in [THREAT_MODEL.md](./THREAT_MODEL.md).
diff --git a/THREAT_MODEL.md b/THREAT_MODEL.md
new file mode 100644
index 0000000000..150f42c601
--- /dev/null
+++ b/THREAT_MODEL.md
@@ -0,0 +1,210 @@
+<!--
+SPDX-License-Identifier: Apache-2.0
+
+Licensed 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
+
+ https://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.
+-->
+
+# Apache Jena — Threat Model (v1)
+
+## §1 Header
+
+- **Project:** Apache Jena (`apache/jena`), `main`, against which this threat
model was written. A monorepo: the RDF/SPARQL Java framework (`jena-core`,
`jena-arq`, `jena-base`, RIOT parsers, `jena-tdb1`/`jena-tdb2` stores, SHACL
(`jena-shacl`), ShEx (`jena-shex`), GeoSPARQL `jena-geosparql`, text index
`jena-text` **and** the Fuseki HTTP server (`jena-fuseki2`).
+- **Date:** 2026-06-02 (v0); 2026-06 (v1, PMC-reviewed). **Status:** v1 —
reviewed by the Jena PMC (Rob Vesse + Andy Seaborne) on apache/jena#3966; their
inline suggestions are folded in and their answers to the §14 questions promote
the load-bearing claims to *(maintainer)* (see §14). **Author:** ASF Security
team (drafted via the Scovetta rubric); now PMC-reviewed.
+- **Version binding:** versioned with the project; a report against version
*N* is triaged against the model as it stood at *N*.
+- **Reporting cross-reference:** §8-property violations → report privately per
ASF process (`[email protected]` → `[email protected]`); §3/§9 findings
are closed citing this document.
+- **Provenance legend:** *(documented)* = Jena's own docs/repo; *(maintainer)*
= confirmed by a Jena PMC member through this process (andy@ has ratified
destination + the help-with-model request); *(inferred)* = reasoned from
architecture, not yet confirmed — each has a matching §14 open question.
+- **Confidence:** v1, PMC-reviewed — rvesse + afs answered every §14 question
and folded their inline edits in; the high-value query-surface claims
(SERVICE/SSRF, file/URI, JS functions, XXE, JSON-LD remote-context, the
resource line) are now *(maintainer)*.
+- **What Jena is:** Apache Jena is a Java framework for building Semantic-Web
/ linked-data applications using RDF and SPARQL. It provides an in-process API
to RDF data held in memory or in a native store (TDB), the ARQ SPARQL
query/update engine, RIOT parsers/serialisers for RDF syntaxes (Turtle,
RDF/XML, JSON-LD, N-Triples, …), and **Fuseki** — a standalone HTTP server
exposing SPARQL query, SPARQL Update, and the Graph Store Protocol over the
network. *(documented — README, jena.apach [...]
+
+## §2 Scope and intended use
+
+- **Two deployment shapes** *(maintainer — andy@)*:
+ - **Fuseki** — a long-running **HTTP server** that answers SPARQL Query and
SPARQL Update as well as the SPARQL Graph Store Protocol (read and read-write
forms) over the network. The primary network trust surface.
+ - **The Jena Java API** — `jena-core`/`jena-arq`/TDB embedded **in-process**
in another application. Trusted caller; the bytes/queries it feeds Jena are
that application's responsibility.
+- **Caller roles** (Fuseki is a network service — the role splits):
+ - **anonymous SPARQL client** — issues SPARQL queries over HTTP.
**Default-public for SPARQL query** *(documented — Fuseki security docs:
"SPARQL endpoints are open to the public but administrative functions are
limited to localhost")*.
+ - **authenticated user / admin** — gated by Apache Shiro (`shiro.ini`);
admin functions (`/$/*`) restricted to localhost by default *(documented)*.
+ - **operator/deployer** — configures Shiro, datasets, TDB location, and
which endpoints are read-only vs updatable. **Trusted.** *(inferred)*
+ - **embedding application** (Java API) — trusted; supplies queries/RDF to
the library. *(inferred)*
+
+**Component-family table** *(monorepo; in/out of model):*
+
+| Family | Entry point | Touches OS/network | In model? |
+| --- | --- | --- | --- |
+| Fuseki HTTP server | `jena-fuseki2` — SPARQL query / Update / Graph Store
Protocol, admin `/$/*` | network (listens) | **In — primary boundary**
*(documented)* |
+| SPARQL engine (ARQ) | `jena-arq` — query/update eval, `SERVICE` federation,
custom functions | network out (SERVICE), file (file: URLs) | **In — high
value** *(inferred)* |
+| RDF I/O (RIOT) | `jena-arq`/`jena-core` parsers (RDF/XML, Turtle, JSON-LD,
…) | parses untrusted RDF | **In — XXE / parser-DoS surface** *(inferred)* |
+| Stores + text index | `jena-tdb1`, `jena-tdb2`; **`jena-text` (Lucene)** |
filesystem | **In.** On-disk store is operator-trusted and private to the
owning process *(maintainer)*; the Lucene text index is reachable from SPARQL
via `text:query` — an in-model query surface *(maintainer — afs flagged
jena-text)* |
+| IRI / langtag | `jena-iri3986`, `jena-langtag`, `jena-base` | none | **In
(input parsing)** *(inferred)* |
+| Extensions | `jena-geosparql`, `jena-serviceenhancer` | SERVICE | **In
(reachable from queries)** *(inferred)* |
+| Validations | `jena-shacl`, `jena-shex` | HTTP GET requests (imports) | **In
(import-fetch = SSRF surface)** *(maintainer — afs)* |
+| Client/API helpers | `jena-rdfconnection`, `jena-querybuilder`,
`jena-rdfpatch`, `jena-commonsrdf`, `jena-ontapi` | none | **In as libraries
(memory/correctness)** *(inferred)* |
+| CLI tools | `jena-cmds` | filesystem | **In iff fed untrusted input; usually
operator-run** *(inferred)* |
+| Examples / tests / benchmarks | `jena-examples`, `jena-integration-tests`,
`jena-benchmarks` | n/a | **Out** *(see §3)* |
+
+## §3 Out of scope (explicit non-goals)
+
+- **`jena-examples`, `jena-integration-tests`, `jena-benchmarks`** —
illustrative/test, not production. *(inferred)*
+- **Attackers who control the host, the Fuseki config (`shiro.ini`, dataset
config), the TDB data directory, or the embedding Java application.**
Operator-trusted. *(inferred)*
+- **The embedding application's own use of the Java API** — if an app feeds
attacker-controlled SPARQL it built by string-concatenation to ARQ, that
injection is the app's bug, not Jena's (analogous to SQL injection in a JDBC
caller). *(inferred)*
+- **Generic DoS / query-complexity exhaustion** beyond a to-be-confirmed line
— Andy raised resource-volume as a concern; the §8 resource line + §14 frame
it. *(inferred)*
+- **Confidentiality of RDF data at rest / TLS on the wire** — operator
deployment (reverse proxy for TLS; filesystem perms for TDB). *(inferred)*
+
+## §4 Trust boundaries and data flow
+
+- **Primary boundary: the Fuseki SPARQL endpoint.** Queries arrive over HTTP
from (by default) **anonymous** clients. The boundary question is what an
anonymous/low-privilege SPARQL query can reach: read data it shouldn't,
**write** (SPARQL Update / GSP) without authorisation, make Fuseki issue
outbound requests (`SERVICE` → SSRF), read local files (`file:` URLs / FROM),
execute code (ARQ custom/JavaScript functions if enabled), or exhaust
resources. *(inferred; public-query default docu [...]
+- **Admin boundary:** the `/$/*` admin surface is localhost-only by default
*(documented)*; exposing it to the network (without configuring
authentication/authorisation) is an operator misconfiguration.
+- **RDF-parse boundary:** any endpoint that **parses** caller-supplied RDF
(Update bodies, GSP PUT/POST, content negotiation) runs RIOT on untrusted
bytes. RDF/XML external-entity (XXE) processing is **off by default**
*(maintainer — afs)*. **JSON-LD `@context` resolution, however, fetches remote
files by default** — a remote-read/SSRF surface inherited from the JSON-LD
dependency (W3C JSON-LD WG mitigation in progress); safer than XXE but real
*(maintainer — afs)*. Plus the general pars [...]
+- **Reachability preconditions:**
+ - A finding in ARQ/RIOT/stores is **in-model** iff reachable from a Fuseki
request at the relevant role (default: anonymous query; authenticated for
Update). *(inferred)*
+ - A finding reachable only through the **in-process Java API** with
caller-supplied trusted input is `OUT-OF-MODEL: trusted-input` (the embedding
app owns it). *(inferred)*
+ - A finding requiring operator config (`shiro.ini`, exposing admin, enabling
JS functions) is `OUT-OF-MODEL: trusted-input` / `non-default-build`.
*(inferred)*
+
+## §5 Assumptions about the environment
+
+- **Runtime:** JVM (Java; "old in places" per andy@). *(maintainer)*
+- **Fuseki auth:** Apache Shiro via `$FUSEKI_BASE/shiro.ini`; changing it
needs a restart *(documented — Fuseki security docs)*.
+- **Store:** TDB1/TDB2 on the local filesystem, private to the owning
Fuseki/JVM process, multiple processes accessing a single store location
prevented by code. *(maintainer)*
+- **Network:** TLS is the deployer's (reverse proxy); Fuseki's bundled example
setup is plaintext *(documented — "no TLS, passwords in plain text")*.
+- **Negative side-effects inventory** (inferred — wave-1/2 target): Fuseki
listens on HTTP; ARQ can make **outbound** network requests via `SERVICE`
(federation), `SERVICE` can be disabled by operator in configuration; ARQ can
read **`file:`/http: URLs** named in queries (FROM/FROM NAMED/SERVICE); RIOT
parses untrusted RDF; ARQ may execute **custom/JavaScript functions** if the
operator enabled them; TDB reads/writes the data directory. *(inferred — these
are the load-bearing confirmations)*
+
+## §5a Build-time and configuration variants
+
+Security-relevant configuration *(Fuseki auth documented; the rest inferred —
confirm defaults):*
+
+| Knob | Default | Effect / stance |
+| --- | --- | --- |
+| Fuseki Shiro auth (`shiro.ini`) | SPARQL **query** public; admin `/$/*`
**localhost-only** | *(documented)* Restricting query access requires Shiro
`[urls]` ACLs. |
+| Fuseki example user setup | `admin`/`pw`, plaintext, no TLS | *(documented)*
explicitly "not recommended for production". Any "default admin/pw in prod"
report → `OUT-OF-MODEL: non-default-build`. |
+| SPARQL **Update** / Graph Store write | per-dataset (read-only vs read-write
service) — **default to confirm** | *(inferred)* If a dataset ships
update-enabled + unauthenticated, anonymous write is in-model; if read-only by
default, anonymous write is not reachable. **Wave-1 question.** |
+| `SERVICE` (federated query) | may be disabled by operator config
**(documented)** | *(inferred)* SSRF surface
+| ARQ **JavaScript / custom functions** | opt-in feature, requires explicit
operator config of both Fuseki and JVM | *(inferred)* If enabled, SPARQL can
execute code, executable JS functions controlled by explicit white list
*(documented)*, some JS functions, e.g. `eval()`, are explicitly blacklisted
regardless of whitelist → by-design-if-operator-enabled, like a trusted
extension. Java custom functions require explicit operator configuration of
class path, if added to class path operat [...]
+| RDF/XML & external-entity handling in RIOT | XXE off | *(inferred)* Whether
external entities / `file:` access are disabled by default in the parsers. |
+| JSON_LD & external context handling in RIOT | On | Accessed by http/https or
local file. |
+| Query timeout / result limits | query timeout configurable at server or
per-dataset level *(documented)* | *(inferred)* the resource/DoS lever (Andy's
concern). |
+
+## §6 Assumptions about inputs
+
+Per-surface trust table *(Fuseki defaults documented; the rest inferred):*
+
+| Surface | Input | Attacker-controllable? | Caller/operator must enforce |
+| --- | --- | --- | --- |
+| Fuseki SPARQL query endpoint | SPARQL query text | **yes (anonymous by
default)** | Shiro ACLs if data is sensitive; SERVICE/file/JS-function
restrictions; query timeout |
+| Fuseki SPARQL Update / GSP | update text / RDF body | **yes — must be
authorised** | read-only-by-default or Shiro-gated write; RDF parse hardening |
+| RDF parse (RIOT) anywhere | RDF/XML, Turtle, JSON-LD, … | **yes** | RDF/XML
XXE off by default *(maintainer)*; bounded nesting/size |
+| JSON-LD `@context` resolution (RIOT) | `@context` URL | **yes** |
remote-context fetch is **on by default** (SSRF / remote-read) — restrict on
untrusted-input endpoints *(maintainer — afs)* |
+| `SERVICE <url>` (federation) | target URL | **yes — enabled by default; SSRF
is conceded `VALID`** *(maintainer)* | no allow-list yet — disable `SERVICE` or
add egress controls on untrusted endpoints |
+| `FROM` / `FROM NAMED` URI | dataset URI | **dataset-impl-dependent**
*(maintainer)* | with TDB2 these are in-dataset graph names, **not fetched**;
only an arbitrary-URI-configured dataset reads `file:`/remote |
+| Fuseki admin `/$/*` | dataset mgmt, backups | **must not be on the public
net** | localhost-only (default) / operator network |
+| Java API (`QueryExecution`, `Model.read`) | query / RDF from the app | no —
the embedding app's trust | app validates its own untrusted inputs |
+
+- **Size/shape/rate:** query-cost / result-size / parser-nesting bounds; §8
resource line. *(inferred)*
+
+## §7 Adversary model
+
+- **Anonymous SPARQL client (primary)** — can reach Fuseki's public query
endpoint; goals: read non-public graphs, write via an exposed Update endpoint,
SSRF via `SERVICE`, local-file read via `file:`/FROM, code execution via JS
functions (if enabled), resource exhaustion via expensive queries. *(inferred;
public-query default documented)*
+- **Authenticated low-privilege user** — bounded by Shiro/dataset ACLs; goal:
exceed them. *(inferred)*
+- **Crafted-RDF attacker** — supplies malicious RDF (RDF/XML XXE,
deeply-nested/oversized documents) to any parse path. *(inferred)*
+- **Out of scope:** operator/host control; the embedding app supplying its own
trusted input; anyone who can edit `shiro.ini` or enable JS functions.
*(inferred)*
+
+## §8 Security properties the project provides
+
+*(All inferred pending PMC confirmation except where Fuseki defaults are
documented.)*
+
+- **Admin surface is localhost-bound by default.** Fuseki's `/$/*` admin
functions are not reachable from the network unless the operator exposes them.
*Violation symptom:* an admin function reachable anonymously over the network
in the default config. *Severity:* CVE-class. *(documented — Fuseki security
docs)*
+- **Shiro access control is enforced when configured.** A Shiro `[urls]` ACL
restricting an endpoint cannot be bypassed by request manipulation. *Violation
symptom:* a restricted endpoint reached without satisfying its Shiro rule.
*Severity:* CVE-class. *(inferred)*
+- **SPARQL operations cannot escape the dataset's authorised scope.** An
anonymous/low-priv query cannot read graphs, write data, reach the filesystem,
or make Fuseki act as an SSRF proxy beyond what the dataset config permits.
*Violation symptom:* SERVICE-SSRF, `file:` read, cross-graph read, or
unauthorised write from an in-scope query. *Severity:* CVE-class. *(inferred —
the core boundary to ratify; these are the classic Jena CVE classes)*
+- **RDF parsing is safe against untrusted documents.** RIOT parsing of
attacker RDF does not resolve external entities (XXE), execute code, or
recurse/allocate unboundedly. *Violation symptom:* XXE, SSRF, or DoS from a
parsed RDF document. *Severity:* CVE-class. *(inferred)*
+- **Resource bounds.** Expensive SPARQL queries, or large RDF bodies are an
operator-tuned limit (query timeout). *(maintainer)*
+
+## §9 Security properties the project does *not* provide
+
+- **No protection if the operator exposes the admin surface, ships the example
`admin`/`pw` setup, or runs without TLS** — deployment hardening (pending §5a
rulings). *(documented that the example setup is not for production)*
+- **No defense when ARQ JavaScript/custom functions are enabled on an
untrusted endpoint** — JS functions are opt-in with an explicit allow-list
(`eval()` etc. blacklisted regardless); custom Java functions require the
operator to add trusted code to the class path. Reachable code execution is
**`by-design-operator-enabled`** *(maintainer — rvesse)*. **False friend:** a
SPARQL endpoint being "read-only" does not by itself prevent SSRF (`SERVICE`)
unless `SERVICE` is separately restricted.
+- **No SPARQL-injection defense for the embedding application** — an app that
concatenates untrusted input into a query string owns that bug (use
parameterised queries / `QueryBuilder`). *(inferred)*
+- **No transport security / authentication unless the operator configures
Shiro + TLS.** *(documented/inferred)*
+- **No generic-DoS / query-complexity guarantee** beyond operator-set query
timeouts + reverse-proxy size limits; a trivial query can compute a huge
cross-product, which is inherent to spec-compliant SPARQL, not a Jena bug
*(maintainer — rvesse)*.
+- **Well-known classes — note the split:** **SSRF via `SERVICE`** from an
anonymous query against the default config is a **conceded `VALID` attack
vector** *(maintainer — rvesse)*, not merely operator-disclaimed (there is no
allow-list yet; operators must disable `SERVICE` or add egress controls). Left
to the caller/operator: SPARQL injection (embedding app),
algorithmic-complexity DoS (operator timeouts), and — only for a dataset
explicitly configured for arbitrary-URI access — `file:` [...]
+
+## §10 Downstream responsibilities (operator/deployer)
+
+- **Put Fuseki behind auth (Shiro) + TLS** before exposing sensitive data;
never ship the example `admin`/`pw` setup to production. *(documented)*
+- **Keep the admin `/$/*` surface localhost-only / operator-network.**
*(documented)*
+- **Make datasets read-only unless write is intended**, and gate SPARQL Update
/ GSP behind Shiro. *(inferred)*
+- **Restrict or disable `SERVICE` federation and `file:` access** on endpoints
reachable by untrusted clients (SSRF / local-file). *(inferred)*
+- **Do not enable ARQ JavaScript/custom functions on untrusted endpoints.**
*(inferred)*
+- **Set query timeouts / result-size limits** appropriate to capacity (the
volume lever). *(inferred)*
+- **Use parameterised queries** (`QueryBuilder`/parameterised
`QueryExecution`) in embedding apps; never string-concatenate untrusted input
into SPARQL. *(inferred)*
+
+## §11 Known misuse patterns
+
+- Exposing a public, update-enabled SPARQL endpoint with no auth. *(inferred)*
+- Leaving `SERVICE` reachable from anonymous queries (SSRF / file read).
*(inferred)*
+- Enabling ARQ JS functions on a public endpoint. *(inferred)*
+- Shipping the example `admin`/`pw` / no-TLS Fuseki setup to production.
*(documented as not-for-prod)*
+- Building SPARQL by concatenating untrusted strings in an embedding app.
*(inferred)*
+- Parsing untrusted RDF/XML without external-entity protections. *(inferred)*
+
+## §11a Known non-findings (recurring false positives)
+
+*(Seed list — confirmations are the highest-leverage scan-suppression input.)*
+
+- "Fuseki SPARQL endpoint is open without auth" — public **query** is the
documented default; restricting it is the operator's Shiro config. A report is
`VALID` only if a *configured* restriction is bypassed or an *update/admin*
surface is anonymously reachable. *(documented default)*
+- "Default `admin`/`pw`, no TLS" — the example setup, documented as
not-for-production → `OUT-OF-MODEL: non-default-build`. *(documented)*
+- "SPARQL query consumes lots of CPU/memory" — operator-tuned via query
timeout + reverse-proxy size limits; a tiny query computing a huge
cross-product is inherent to spec-compliant SPARQL (affects every engine), not
a Jena bug *(maintainer — rvesse)*.
+- "ARQ can call JavaScript / custom functions" — only if the operator enabled
them, with a JS allow-list / operator-supplied Java code;
`by-design-operator-enabled`. `OUT-OF-MODEL: trusted-input` /
`non-default-build` unless reachable anonymously *(maintainer — rvesse)*.
+- "Embedding app built an injectable SPARQL string" — the app's bug, not
Jena's; parameterised queries are the recommended pattern. `OUT-OF-MODEL:
trusted-input` *(maintainer — rvesse)*.
+- "Fuseki/TDB1 has a memory leak" — unbounded memory growth under continuous
read/write load is a known issue; the WAL guarantees no data loss on
crash/restart. A recurring mailing-list topic, not a vulnerability *(maintainer
— rvesse; TDB FAQ)*.
+- "TDB database is far larger on disk than the input" — sparse files (metrics
vary by tool/filesystem) plus TDB2's MVCC trees orphaning old blocks per write;
expected. The `compaction` operation reclaims space (run periodically)
*(maintainer — rvesse; TDB FAQ)*.
+
+## §12 Conditions that would change this model
+
+- A change to Fuseki's default auth posture (public-query / localhost-admin),
the example-setup defaults, or the SPARQL-Update default. *(documented knobs)*
+- A change to `SERVICE`/`file:`/JS-function defaults or their restrictability.
*(inferred)*
+- A new network surface or a new parser. *(inferred)*
+- A report that cannot be routed to one §13 disposition → revise the model.
+
+## §13 Triage dispositions
+
+| Disposition | Meaning | Licensed by |
+| --- | --- | --- |
+| `VALID` | Violates a §8 property via an in-scope adversary/input
(config-bypass, anonymous write/admin, SSRF/file-read/XXE/code-exec from an
in-scope query under default config). | §8, §6, §7 |
+| `VALID-HARDENING` | No §8 property broken, but a §11 misuse is easy enough
to harden (safer defaults, SERVICE allow-list, parser limits). | §11 |
+| `OUT-OF-MODEL: trusted-input` | Requires operator config (shiro.ini,
enabling JS functions, exposing admin) or the embedding app's own untrusted
input. | §6, §7 |
+| `OUT-OF-MODEL: adversary-not-in-scope` | Requires host/JVM/config control. |
§7 |
+| `OUT-OF-MODEL: unsupported-component` | Lands in `jena-examples` / tests /
benchmarks. | §3 |
+| `OUT-OF-MODEL: non-default-build` | Only manifests under a
discouraged/non-default §5a setting (example creds, JS functions on, admin
exposed). | §5a |
+| `BY-DESIGN: property-disclaimed` | Concerns a §9-disclaimed property
(operator-enabled code exec, no-TLS-by-default, embedding-app SPARQL
injection). | §9 |
+| `KNOWN-NON-FINDING` | Matches a §11a entry. | §11a |
+| `MODEL-GAP` | Cannot be routed — triggers §12. | §12 |
+
+## §14 Open questions — RESOLVED by the Jena PMC (2026-06)
+
+Reviewed on [apache/jena#3966](https://github.com/apache/jena/pull/3966) by
**Rob Vesse (`@rvesse`)** and **Andy Seaborne (`@afs`)**, who folded their
inline suggestions into the model and answered the open questions. Confirmed
claims are promoted to *(maintainer)*; the answers below are the durable record.
+
+**Wave 1 — scope & Fuseki defaults**
+1. **Scope** *(maintainer)*: the `apache/jena` monorepo with Fuseki + ARQ +
RIOT + TDB as the in-model core, `jena-examples`/tests/benchmarks out. afs
added that the **Lucene-based text index (`jena-text`)**, reachable from SPARQL
via `text:query`, is in scope (§2/§6).
+2. **Update default** *(maintainer — rvesse)*: deployment-dependent. Fuseki
started **without a config file** is **read-only unless `--update`** is passed.
With a config file, only the operations the config declares are available
(Update must be explicitly configured) — though the documentation's example
configs do include update services.
+3. **Defaults** *(maintainer)*: confirmed — public SPARQL query,
localhost-only admin, example `admin`/`pw` + no-TLS explicitly
not-for-production (`non-default-build`).
+
+**Wave 2 — high-value query surfaces (the Jena CVE classes)**
+4. **`SERVICE` federation (SSRF)** *(maintainer — rvesse)*: **enabled by
default**, **disableable** in config, **no allow-list capability currently** (a
noted hardening gap; the Service Enhancer module may help). The PMC **concedes
SSRF via `SERVICE` is a valid attack vector** and that the docs should call it
out more explicitly → an SSRF from an anonymous query against a default config
is **`VALID`**, not merely disclaimed.
+5. **`file:` / arbitrary-URI read** via `FROM`/`FROM NAMED`/`SERVICE`
*(maintainer — rvesse)*: **depends on the dataset implementation.** With a
persistent store like **TDB2, `FROM`/`FROM NAMED` only resolve graphs *within
the dataset* — the URIs are treated as graph names and are *not* fetched.**
Fuseki *can* be configured to allow arbitrary-URI access (a
documentation/hardening point), but that is not the TDB-backed default.
+6. **ARQ JavaScript / custom functions** *(maintainer — rvesse)*: **opt-in**,
with an **explicit allow-list of permitted JS functions** (`eval()` etc.
blacklisted regardless of the allow-list). Custom **Java** functions require
the operator to add code to the class path — operator responsibility to trust
it. Reachable code execution is **`by-design-operator-enabled`**, not a Jena
vulnerability.
+7. **RIOT / RDF-XML XXE** *(maintainer — afs's area; parsers rewritten
recently)*: external-entity processing is **off by default** and afs (the
authority here) believes the parsers are safe against untrusted RDF. **JSON-LD
context resolution, however, fetches remote files by default** — a behavior of
the JSON-LD dependency (the W3C JSON-LD WG is working on documenting/mitigating
it); safer than XXE but a genuine remote-read/SSRF surface (§4/§6).
+
+**Wave 3 — resources, API, meta**
+8. **Resource/DoS** *(maintainer — rvesse)*: **operator-tuned** via query
timeout (server or per-dataset) + reverse-proxy request-size limits.
Super-linear cost is **not a bug** — a tiny query can compute a massive
cross-product (`SELECT * WHERE { ?a ?b ?c . ?d ?e ?f . ?g ?h ?i }`), and as a
spec-compliant SPARQL engine Jena is no different from any other here. (The
earlier "super-linear" framing is dropped.)
+9. **In-process Java API** *(maintainer — rvesse)*: trusted-caller — an
embedding app that concatenates untrusted input into SPARQL owns that
injection; **parameterised queries are the recommended pattern**.
+10. **§11a recurring non-findings** *(maintainer — rvesse, from the TDB FAQ)*:
(a) **"Fuseki/TDB has a memory leak"** — unbounded memory growth under
continuous read/write load is a known issue; the WAL ensures no data is lost on
crash/restart. (b) **"Database is much larger on disk than the input"** —
sparse files (disk-usage metrics vary by tool/filesystem) plus TDB2's MVCC
trees orphaning old blocks on each write; expected, and a **compaction**
operation reclaims the space (recommende [...]
+11. **Meta** *(maintainer)*: the in-repo `THREAT_MODEL.md` is canonical and
references the website Fuseki security docs; the PMC owns revision.